Exemple #1
0
    def completerTree(self):
        """
        Returns the completion tree for this instance.
        
        :return     <QTreeWidget>
        """
        if not self._completerTree:
            self._completerTree = QTreeWidget(self)
            self._completerTree.setWindowFlags(Qt.Popup)
            self._completerTree.setAlternatingRowColors(True)
            self._completerTree.installEventFilter(self)
            self._completerTree.itemClicked.connect(self.acceptCompletion)
            self._completerTree.setRootIsDecorated(False)
            self._completerTree.header().hide()

        return self._completerTree
Exemple #2
0
 def completerTree( self ):
     """
     Returns the completion tree for this instance.
     
     :return     <QTreeWidget>
     """
     if not self._completerTree:
         self._completerTree = QTreeWidget(self)
         self._completerTree.setWindowFlags(Qt.Popup)
         self._completerTree.setAlternatingRowColors( True )
         self._completerTree.installEventFilter(self)
         self._completerTree.itemClicked.connect( self.acceptCompletion )
         self._completerTree.setRootIsDecorated(False)
         self._completerTree.header().hide()
         
     return self._completerTree
Exemple #3
0
class XConsoleEdit(XLoggerWidget):
    __designer_icon__ = projexui.resources.find('img/ui/console.png')
    
    executeRequested = Signal(str)
    
    def __init__(self, parent):
        super(XConsoleEdit, self).__init__(parent)
        
        # create custom properties
        self._scope = __main__.__dict__
        self._initialized = False
        self._completerTree = None
        self._commandStack = []
        self._history = []
        self._currentHistoryIndex = 0
        self._waitingForInput = False
        self._commandLineInteraction = False
        self._highlighter = XPythonHighlighter(self.document())
        
        # setup the look for the console
        self.setReadOnly(False)
        self.waitForInput()
        self.setConfigurable(False)

    def _information(self, msg):
        locker = QMutexLocker(self._mutex)
        
        msg = projex.text.nativestring(msg)
        self.moveCursor(QTextCursor.End)
        self.setCurrentMode(logging.INFO)
        self.insertPlainText(msg)
        self.scrollToEnd()

    def _error(self, msg):
        locker = QMutexLocker(self._mutex)
        
        msg = projex.text.nativestring(msg)
        self.moveCursor(QTextCursor.End)
        self.setCurrentMode(logging.ERROR)
        self.insertPlainText(msg)
        self.scrollToEnd()
        
        if not self._waitingForInput:
            self._waitingForInput = True
            QTimer.singleShot(50, self.waitForInput)

    def acceptCompletion( self ):
        """
        Accepts the current completion and inserts the code into the edit.
        
        :return     <bool> accepted
        """
        tree = self._completerTree
        if not tree:
            return False
            
        tree.hide()
        
        item = tree.currentItem()
        if not item:
            return False
        
        # clear the previously typed code for the block
        cursor  = self.textCursor()
        text    = cursor.block().text()
        col     = cursor.columnNumber()
        end     = col
        
        while col:
            col -= 1
            if text[col] in ('.', ' '):
                col += 1
                break
        
        # insert the current text
        cursor.setPosition(cursor.position() - (end-col), cursor.KeepAnchor)
        cursor.removeSelectedText()
        self.insertPlainText(item.text(0))
        return True
    
    def applyCommand(self):
        """
        Applies the current line of code as an interactive python command.
        """
        # generate the command information
        cursor      = self.textCursor()
        cursor.movePosition(cursor.EndOfLine)
        
        line        = projex.text.nativestring(cursor.block().text())
        at_end      = cursor.atEnd()
        modifiers   = QApplication.instance().keyboardModifiers()
        mod_mode    = at_end or modifiers == Qt.ShiftModifier
        
        # test the line for information
        if mod_mode and line.endswith(':'):
            cursor.movePosition(cursor.EndOfLine)
            
            line = re.sub('^>>> ', '', line)
            line = re.sub('^\.\.\. ', '', line)
            count = len(line) - len(line.lstrip()) + 4
            
            self.insertPlainText('\n... ' + count * ' ')
            return False
        
        elif mod_mode and line.startswith('...') and \
            (line.strip() != '...' or not at_end):
            cursor.movePosition(cursor.EndOfLine)
            line = re.sub('^\.\.\. ', '', line)
            count = len(line) - len(line.lstrip())
            self.insertPlainText('\n... ' + count * ' ')
            return False
        
        # if we're not at the end of the console, then add it to the end
        elif line.startswith('>>>') or line.startswith('...'):
            # move to the top of the command structure
            line = projex.text.nativestring(cursor.block().text())
            while line.startswith('...'):
                cursor.movePosition(cursor.PreviousBlock)
                line = projex.text.nativestring(cursor.block().text())
            
            # calculate the command
            cursor.movePosition(cursor.EndOfLine)
            line = projex.text.nativestring(cursor.block().text())
            ended = False
            lines = []
            
            while True:
                # add the new block
                lines.append(line)
                
                if cursor.atEnd():
                    ended = True
                    break
                
                # move to the next line
                cursor.movePosition(cursor.NextBlock)
                cursor.movePosition(cursor.EndOfLine)
                
                line = projex.text.nativestring(cursor.block().text())
                
                # check for a new command or the end of the command
                if not line.startswith('...'):
                    break
            
            command = '\n'.join(lines)
            
            # if we did not end up at the end of the command block, then
            # copy it for modification
            if not (ended and command):
                self.waitForInput()
                self.insertPlainText(command.replace('>>> ', ''))
                cursor.movePosition(cursor.End)
                return False
        
        else:
            self.waitForInput()
            return False
        
        self.executeCommand(command)
        return True
    
    def cancelCompletion( self ):
        """
        Cancels the current completion.
        """
        if self._completerTree:
            self._completerTree.hide()
    
    def clear(self):
        """
        Clears the current text and starts a new input line.
        """
        super(XConsoleEdit, self).clear()
        self.waitForInput()

    def commandLineInteraction(self):
        """
        Returns whether or not the console is using interaction like the
        command line.
        
        :return     <bool>
        """
        return self._commandLineInteraction

    def completerTree( self ):
        """
        Returns the completion tree for this instance.
        
        :return     <QTreeWidget>
        """
        if not self._completerTree:
            self._completerTree = QTreeWidget(self)
            self._completerTree.setWindowFlags(Qt.Popup)
            self._completerTree.setAlternatingRowColors( True )
            self._completerTree.installEventFilter(self)
            self._completerTree.itemClicked.connect( self.acceptCompletion )
            self._completerTree.setRootIsDecorated(False)
            self._completerTree.header().hide()
            
        return self._completerTree
    
    def eventFilter(self, obj, event):
        """
        Filters particular events for a given QObject through this class. \
        Will use this to intercept events to the completer tree widget while \
        filtering.
        
        :param      obj     | <QObject>
                    event   | <QEvent>
        
        :return     <bool> consumed
        """
        if not obj == self._completerTree:
            return False
        
        if event.type() != event.KeyPress:
            return False
            
        if event.key() == Qt.Key_Escape:
            QToolTip.hideText()
            self.cancelCompletion()
            return False
        
        elif event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab):
            self.acceptCompletion()
            return False
        
        elif event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown):
            return False
        
        else:
            self.keyPressEvent(event)
            
            # update the completer
            cursor   = self.textCursor()
            text     = projex.text.nativestring(cursor.block().text())
            text     = text[:cursor.columnNumber()].split(' ')[-1]
            text     = text.split('.')[-1]
            
            self._completerTree.blockSignals(True)
            self._completerTree.setUpdatesEnabled(False)
            
            self._completerTree.setCurrentItem(None)
            
            for i in range(self._completerTree.topLevelItemCount()):
                item = self._completerTree.topLevelItem(i)
                if projex.text.nativestring(item.text(0)).startswith(text):
                    self._completerTree.setCurrentItem(item)
                    break
            
            self._completerTree.blockSignals(False)
            self._completerTree.setUpdatesEnabled(True)
            
            return True
    
    def executeCommand(self, command):
        """
        Executes the inputed command in the global scope.
        
        :param      command | <unicode>
        
        :return     <variant>
        """
        if not command.strip():
            return self.waitForInput()
        
        # store the current block
        self._history.append(command)
        self._currentHistoryIndex = len(self._history)
        
        lines = []
        for line in command.split('\n'):
            line = re.sub('^>>> ', '', line)
            line = re.sub('^\.\.\. ', '', line)
            lines.append(line)
        
        command = '\n'.join(lines)
        
        # ensure we are at the end
        self.moveCursor(QTextCursor.End)
        self.scrollToEnd()
        self.insertPlainText('\n')
        cmdresult = None
        
        try:
            cmdresult = eval(command, self.scope(), self.scope())
        
        except SyntaxError:
            exec(command) in self.scope(), self.scope()
        
        else:
            if cmdresult is not None:
                # check to see if the command we executed actually caused
                # the destruction of this object -- if it did, then
                # the commands below will error
                if self.isDestroyed():
                    return
                
                try:
                    result = projex.text.nativestring(repr(cmdresult))
                except:
                    result = '<<< error formatting result to utf-8 >>>'
                
                self.information(result)
        
        finally:
            self.waitForInput()
    
    def dragEnterEvent(self, event):
        if event.keyboardModifiers() == Qt.ShiftModifier:
            event.acceptProposedAction()
        else:
            super(XConsoleEdit, self).dragEnterEvent(event)
    
    def dragMoveEvent(self, event):
        if event.keyboardModifiers() == Qt.ShiftModifier:
            event.acceptProposedAction()
        else:
            super(XConsoleEdit, self).dragMoveEvent(event)
    
    def dropEvent(self, event):
        if event.keyboardModifiers() == Qt.ShiftModifier:
            self.insertPlainText('\n' + projexui.formatDropEvent(event))
        else:
            super(XConsoleEdit, self).dropEvent(event)
    
    def highlighter(self):
        """
        Returns the console highlighter for this widget.
        
        :return     <XPythonHighlighter>
        """
        return self._highlighter

    def gotoHome(self):
        """
        Navigates to the home position for the edit.
        """
        mode = QTextCursor.MoveAnchor
        
        # select the home
        if QApplication.instance().keyboardModifiers() == Qt.ShiftModifier:
            mode = QTextCursor.KeepAnchor
        
        cursor = self.textCursor()
        block  = projex.text.nativestring(cursor.block().text())
        
        cursor.movePosition( QTextCursor.StartOfBlock, mode )
        if block.startswith('>>> '):
            cursor.movePosition(QTextCursor.Right, mode, 4)
        elif block.startswith('... '):
            match = re.match('...\s*', block)
            cursor.movePosition(QTextCursor.Right, mode, match.end())
        
        self.setTextCursor(cursor)

    def insertFromMimeData(self, source):
        """
        Inserts the information from the inputed source.
        
        :param      source | <QMimeData>
        """
        lines = projex.text.nativestring(source.text()).splitlines()
        for i in range(1, len(lines)):
            if not lines[i].startswith('... '):
                lines[i] = '... ' + lines[i]
        
        if len(lines) > 1:
            lines.append('... ')
        
        self.insertPlainText('\n'.join(lines))

    def insertNextCommand(self):
        """
        Inserts the previous command from history into the line.
        """
        self._currentHistoryIndex += 1
        if 0 <= self._currentHistoryIndex < len(self._history):
            cmd = self._history[self._currentHistoryIndex]
        else:
            cmd = '>>> '
            self._currentHistoryIndex = -1
        
        self.replaceCommand(cmd)
    
    def insertPreviousCommand(self):
        """
        Inserts the previous command from history into the line.
        """
        self._currentHistoryIndex -= 1
        if 0 <= self._currentHistoryIndex < len(self._history):
            cmd = self._history[self._currentHistoryIndex]
        else:
            cmd = '>>> '
            self._currentHistoryIndex = len(self._history)
        
        self.replaceCommand(cmd)
    
    def keyPressEvent(self, event):
        """
        Overloads the key press event to control keystroke modifications for \
        the console widget.
        
        :param      event | <QKeyEvent>
        """
        # enter || return keys will apply the command
        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.applyCommand()
            event.accept()
        
        # home key will move the cursor to the home position
        elif event.key() == Qt.Key_Home:
            self.gotoHome()
            event.accept()
        
        elif event.key() in (Qt.Key_Backspace, Qt.Key_Delete):
            super(XConsoleEdit, self).keyPressEvent(event)
            
            # update the completer
            cursor   = self.textCursor()
            text     = projex.text.nativestring(cursor.block().text())
            text     = text[:cursor.columnNumber()].split(' ')[-1]
            
            if not '.' in text:
                self.cancelCompletion()
            
        # period key will trigger a completion popup
        elif event.key() == Qt.Key_Period or \
             (Qt.Key_A <= event.key() <= Qt.Key_Z):
            super(XConsoleEdit, self).keyPressEvent(event)
            self.startCompletion(force=event.key() == Qt.Key_Period)
        
        # space, tab, backspace and delete will cancel the completion
        elif event.key() == Qt.Key_Space:
            self.cancelCompletion()
            super(XConsoleEdit, self).keyPressEvent(event)
        
        # left parenthesis will start method help
        elif event.key() == Qt.Key_ParenLeft:
            self.cancelCompletion()
            self.showMethodToolTip()
            super(XConsoleEdit, self).keyPressEvent(event)
        
        # Ctrl+Up will load previous commands
        elif event.key() == Qt.Key_Up:
            if self.commandLineInteraction() or \
               event.modifiers() & Qt.ControlModifier:
                self.insertPreviousCommand()
                event.accept()
            else:
                super(XConsoleEdit, self).keyPressEvent(event)
        
        # Ctrl+Down will load future commands
        elif event.key() == Qt.Key_Down:
            if self.commandLineInteraction() or \
               event.modifiers() & Qt.ControlModifier:
                self.insertNextCommand()
                event.accept()
            else:
                super(XConsoleEdit, self).keyPressEvent(event)
        
        # otherwise, handle the event like normal
        else:
            super(XConsoleEdit, self).keyPressEvent(event)
    
    def objectAtCursor(self):
        """
        Returns the python object that the text is representing.
        
        :return     <object> || None
        """
        
        # determine the text block
        cursor   = self.textCursor()
        text     = projex.text.nativestring(cursor.block().text())
        position = cursor.positionInBlock() - 1
        
        if not text:
            return (None, '')
        
        symbol   = ''
        for match in re.finditer('[\w\.]+', text):
            if match.start() <= position <= match.end():
                symbol = match.group()
                break
        
        if not symbol:
            return (None, '')
        
        parts = symbol.split('.')
        if len(parts) == 1:
            return (self.scope(), parts[0])
        
        part = parts[0]
        obj = self.scope().get(part)
        for part in parts[1:-1]:
            try:
                obj = getattr(obj, part)
            except AttributeError:
                return (None, '')
        
        return (obj, parts[-1])

    def restoreSettings(self, settings):
        hist = []
        settings.beginGroup('console')
        for key in sorted(settings.childKeys()):
            hist.append(unwrapVariant(settings.value(key)))
        settings.endGroup()
        
        self._history = hist

    def saveSettings(self, settings):
        settings.beginGroup('console')
        for i, text in enumerate(self._history):
            settings.setValue('command_{0}'.format(i), wrapVariant(text))
        settings.endGroup()
    
    def showEvent(self, event):
        super(XConsoleEdit, self).showEvent(event)
        
        if not self._initialized:
            self._initialized = True
            
            # create connections
            if os.environ.get('XUI_DISABLE_CONSOLE') != '1':
                hook = XIOHook.instance()
                hook.printed.connect(self._information)
                hook.errored.connect(self._error)
            
            # setup the header
            opts = {'version': sys.version, 'platform': sys.platform}
            self.setText(HEADER.format(**opts))
            self.waitForInput()
    
    def showMethodToolTip(self):
        """
        Pops up a tooltip message with the help for the object under the \
        cursor.
        
        :return     <bool> success
        """
        self.cancelCompletion()
        
        obj, _ = self.objectAtCursor()
        if not obj:
            return False
        
        docs = inspect.getdoc(obj)
        if not docs:
            return False
        
        # determine the cursor position
        rect   = self.cursorRect()
        cursor = self.textCursor()
        point  = QPoint(rect.left(), rect.top() + 18)
        
        QToolTip.showText(self.mapToGlobal(point), docs, self)
        
        return True
    
    def replaceCommand(self, cmd):
        # move to the top of the command structure
        self.moveCursor(QTextCursor.End)
        cursor = self.textCursor()
        
        line = projex.text.nativestring(cursor.block().text())
        while line.startswith('...'):
            cursor.movePosition(cursor.PreviousBlock)
            line = projex.text.nativestring(cursor.block().text())
        
        # calculate the command
        cursor.movePosition(cursor.StartOfLine)
        cursor.movePosition(cursor.End, cursor.KeepAnchor)
        cursor.removeSelectedText()
        cursor.insertText(cmd)
        self.moveCursor(cursor.End)
    
    def scope(self):
        """
        Returns the dictionary scope that will be used when working
        with this editor.
        
        :return     <dict>
        """
        return self._scope
    
    def setCommandLineInteraction(self, state=True):
        """
        Sets whether or not the interaction should follow command-line
        standards (Up/Down navigation) or not (CTRL+Up/Down).
        
        :param      state | <bool>
        """
        self._commandLineInteraction = state
    
    def setScope(self, scope):
        """
        Sets the scope that will be used for this editor.
        
        :param      scope | <dict>
        """
        self._scope = scope
    
    def startCompletion(self, force=False):
        """
        Starts a new completion popup for the current object.
        
        :return     <bool> success
        """
        # add the top level items
        tree = self.completerTree()
        if not force and tree.isVisible():
            return
        
        tree.clear()
        
        # make sure we have a valid object
        obj, remain = self.objectAtCursor()
        
        if obj is None:
            tree.hide()
            return
        
        # determine the cursor position
        rect   = self.cursorRect()
        cursor = self.textCursor()
        point  = QPoint(rect.left(), rect.top() + 18)
        
        # compare the ids since some things might overload the __eq__
        # comparator
        if id(obj) == id(self._scope):
            o_keys = obj.keys()
        elif obj is not None:
            o_keys = dir(obj)
        
        keys = [key for key in sorted(o_keys) if not key.startswith('_')]
        if id(obj) == id(self._scope):
            if not remain:
                return False
            else:
                keys = filter(lambda x: x.startswith(remain[0]), keys)
        
        if not keys:
            return False
        
        for key in keys:
            tree.addTopLevelItem(QTreeWidgetItem([key]))
        
        tree.move(self.mapToGlobal(point))
        tree.show()
        return True
    
    def waitForInput(self):
        """
        Inserts a new input command into the console editor.
        """
        self._waitingForInput = False
        
        try:
            if self.isDestroyed() or self.isReadOnly():
                return
        except RuntimeError:
            return
        
        self.moveCursor(QTextCursor.End)
        if self.textCursor().block().text() == '>>> ':
            return
        
        # if there is already text on the line, then start a new line
        newln = '>>> '
        if projex.text.nativestring(self.textCursor().block().text()):
            newln = '\n' + newln
        
        # insert the text
        self.setCurrentMode('standard')
        self.insertPlainText(newln)
        self.scrollToEnd()
        
        self._blankCache = ''

    x_commandLineInteraction = Property(bool, commandLineInteraction,
                                              setCommandLineInteraction)
Exemple #4
0
class XConsoleEdit(XLoggerWidget):
    __designer_icon__ = projexui.resources.find('img/ui/console.png')

    executeRequested = Signal(str)

    def __init__(self, parent):
        super(XConsoleEdit, self).__init__(parent)

        # create custom properties
        self._scope = __main__.__dict__
        self._initialized = False
        self._completerTree = None
        self._commandStack = []
        self._history = []
        self._currentHistoryIndex = 0
        self._waitingForInput = False
        self._commandLineInteraction = False
        self._highlighter = XPythonHighlighter(self.document())

        # setup the look for the console
        self.setReadOnly(False)
        self.waitForInput()
        self.setConfigurable(False)

    def _information(self, msg):
        locker = QMutexLocker(self._mutex)

        msg = projex.text.nativestring(msg)
        self.moveCursor(QTextCursor.End)
        self.setCurrentMode(logging.INFO)
        self.insertPlainText(msg)
        self.scrollToEnd()

    def _error(self, msg):
        locker = QMutexLocker(self._mutex)

        msg = projex.text.nativestring(msg)
        self.moveCursor(QTextCursor.End)
        self.setCurrentMode(logging.ERROR)
        self.insertPlainText(msg)
        self.scrollToEnd()

        if not self._waitingForInput:
            self._waitingForInput = True
            QTimer.singleShot(50, self.waitForInput)

    def acceptCompletion(self):
        """
        Accepts the current completion and inserts the code into the edit.
        
        :return     <bool> accepted
        """
        tree = self._completerTree
        if not tree:
            return False

        tree.hide()

        item = tree.currentItem()
        if not item:
            return False

        # clear the previously typed code for the block
        cursor = self.textCursor()
        text = cursor.block().text()
        col = cursor.columnNumber()
        end = col

        while col:
            col -= 1
            if text[col] in ('.', ' '):
                col += 1
                break

        # insert the current text
        cursor.setPosition(cursor.position() - (end - col), cursor.KeepAnchor)
        cursor.removeSelectedText()
        self.insertPlainText(item.text(0))
        return True

    def applyCommand(self):
        """
        Applies the current line of code as an interactive python command.
        """
        # generate the command information
        cursor = self.textCursor()
        cursor.movePosition(cursor.EndOfLine)

        line = projex.text.nativestring(cursor.block().text())
        at_end = cursor.atEnd()
        modifiers = QApplication.instance().keyboardModifiers()
        mod_mode = at_end or modifiers == Qt.ShiftModifier

        # test the line for information
        if mod_mode and line.endswith(':'):
            cursor.movePosition(cursor.EndOfLine)

            line = re.sub('^>>> ', '', line)
            line = re.sub('^\.\.\. ', '', line)
            count = len(line) - len(line.lstrip()) + 4

            self.insertPlainText('\n... ' + count * ' ')
            return False

        elif mod_mode and line.startswith('...') and \
            (line.strip() != '...' or not at_end):
            cursor.movePosition(cursor.EndOfLine)
            line = re.sub('^\.\.\. ', '', line)
            count = len(line) - len(line.lstrip())
            self.insertPlainText('\n... ' + count * ' ')
            return False

        # if we're not at the end of the console, then add it to the end
        elif line.startswith('>>>') or line.startswith('...'):
            # move to the top of the command structure
            line = projex.text.nativestring(cursor.block().text())
            while line.startswith('...'):
                cursor.movePosition(cursor.PreviousBlock)
                line = projex.text.nativestring(cursor.block().text())

            # calculate the command
            cursor.movePosition(cursor.EndOfLine)
            line = projex.text.nativestring(cursor.block().text())
            ended = False
            lines = []

            while True:
                # add the new block
                lines.append(line)

                if cursor.atEnd():
                    ended = True
                    break

                # move to the next line
                cursor.movePosition(cursor.NextBlock)
                cursor.movePosition(cursor.EndOfLine)

                line = projex.text.nativestring(cursor.block().text())

                # check for a new command or the end of the command
                if not line.startswith('...'):
                    break

            command = '\n'.join(lines)

            # if we did not end up at the end of the command block, then
            # copy it for modification
            if not (ended and command):
                self.waitForInput()
                self.insertPlainText(command.replace('>>> ', ''))
                cursor.movePosition(cursor.End)
                return False

        else:
            self.waitForInput()
            return False

        self.executeCommand(command)
        return True

    def cancelCompletion(self):
        """
        Cancels the current completion.
        """
        if self._completerTree:
            self._completerTree.hide()

    def clear(self):
        """
        Clears the current text and starts a new input line.
        """
        super(XConsoleEdit, self).clear()
        self.waitForInput()

    def commandLineInteraction(self):
        """
        Returns whether or not the console is using interaction like the
        command line.
        
        :return     <bool>
        """
        return self._commandLineInteraction

    def completerTree(self):
        """
        Returns the completion tree for this instance.
        
        :return     <QTreeWidget>
        """
        if not self._completerTree:
            self._completerTree = QTreeWidget(self)
            self._completerTree.setWindowFlags(Qt.Popup)
            self._completerTree.setAlternatingRowColors(True)
            self._completerTree.installEventFilter(self)
            self._completerTree.itemClicked.connect(self.acceptCompletion)
            self._completerTree.setRootIsDecorated(False)
            self._completerTree.header().hide()

        return self._completerTree

    def eventFilter(self, obj, event):
        """
        Filters particular events for a given QObject through this class. \
        Will use this to intercept events to the completer tree widget while \
        filtering.
        
        :param      obj     | <QObject>
                    event   | <QEvent>
        
        :return     <bool> consumed
        """
        if not obj == self._completerTree:
            return False

        if event.type() != event.KeyPress:
            return False

        if event.key() == Qt.Key_Escape:
            QToolTip.hideText()
            self.cancelCompletion()
            return False

        elif event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab):
            self.acceptCompletion()
            return False

        elif event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp,
                             Qt.Key_PageDown):
            return False

        else:
            self.keyPressEvent(event)

            # update the completer
            cursor = self.textCursor()
            text = projex.text.nativestring(cursor.block().text())
            text = text[:cursor.columnNumber()].split(' ')[-1]
            text = text.split('.')[-1]

            self._completerTree.blockSignals(True)
            self._completerTree.setUpdatesEnabled(False)

            self._completerTree.setCurrentItem(None)

            for i in range(self._completerTree.topLevelItemCount()):
                item = self._completerTree.topLevelItem(i)
                if projex.text.nativestring(item.text(0)).startswith(text):
                    self._completerTree.setCurrentItem(item)
                    break

            self._completerTree.blockSignals(False)
            self._completerTree.setUpdatesEnabled(True)

            return True

    def executeCommand(self, command):
        """
        Executes the inputed command in the global scope.
        
        :param      command | <unicode>
        
        :return     <variant>
        """
        if not command.strip():
            return self.waitForInput()

        # store the current block
        self._history.append(command)
        self._currentHistoryIndex = len(self._history)

        lines = []
        for line in command.split('\n'):
            line = re.sub('^>>> ', '', line)
            line = re.sub('^\.\.\. ', '', line)
            lines.append(line)

        command = '\n'.join(lines)

        # ensure we are at the end
        self.moveCursor(QTextCursor.End)
        self.scrollToEnd()
        self.insertPlainText('\n')
        cmdresult = None

        try:
            cmdresult = eval(command, self.scope(), self.scope())

        except SyntaxError:
            exec(command) in self.scope(), self.scope()

        else:
            if cmdresult is not None:
                # check to see if the command we executed actually caused
                # the destruction of this object -- if it did, then
                # the commands below will error
                if self.isDestroyed():
                    return

                try:
                    result = projex.text.nativestring(repr(cmdresult))
                except:
                    result = '<<< error formatting result to utf-8 >>>'

                self.information(result)

        finally:
            self.waitForInput()

    def dragEnterEvent(self, event):
        if event.keyboardModifiers() == Qt.ShiftModifier:
            event.acceptProposedAction()
        else:
            super(XConsoleEdit, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        if event.keyboardModifiers() == Qt.ShiftModifier:
            event.acceptProposedAction()
        else:
            super(XConsoleEdit, self).dragMoveEvent(event)

    def dropEvent(self, event):
        if event.keyboardModifiers() == Qt.ShiftModifier:
            self.insertPlainText('\n' + projexui.formatDropEvent(event))
        else:
            super(XConsoleEdit, self).dropEvent(event)

    def highlighter(self):
        """
        Returns the console highlighter for this widget.
        
        :return     <XPythonHighlighter>
        """
        return self._highlighter

    def gotoHome(self):
        """
        Navigates to the home position for the edit.
        """
        mode = QTextCursor.MoveAnchor

        # select the home
        if QApplication.instance().keyboardModifiers() == Qt.ShiftModifier:
            mode = QTextCursor.KeepAnchor

        cursor = self.textCursor()
        block = projex.text.nativestring(cursor.block().text())

        cursor.movePosition(QTextCursor.StartOfBlock, mode)
        if block.startswith('>>> '):
            cursor.movePosition(QTextCursor.Right, mode, 4)
        elif block.startswith('... '):
            match = re.match('...\s*', block)
            cursor.movePosition(QTextCursor.Right, mode, match.end())

        self.setTextCursor(cursor)

    def insertFromMimeData(self, source):
        """
        Inserts the information from the inputed source.
        
        :param      source | <QMimeData>
        """
        lines = projex.text.nativestring(source.text()).splitlines()
        for i in range(1, len(lines)):
            if not lines[i].startswith('... '):
                lines[i] = '... ' + lines[i]

        if len(lines) > 1:
            lines.append('... ')

        self.insertPlainText('\n'.join(lines))

    def insertNextCommand(self):
        """
        Inserts the previous command from history into the line.
        """
        self._currentHistoryIndex += 1
        if 0 <= self._currentHistoryIndex < len(self._history):
            cmd = self._history[self._currentHistoryIndex]
        else:
            cmd = '>>> '
            self._currentHistoryIndex = -1

        self.replaceCommand(cmd)

    def insertPreviousCommand(self):
        """
        Inserts the previous command from history into the line.
        """
        self._currentHistoryIndex -= 1
        if 0 <= self._currentHistoryIndex < len(self._history):
            cmd = self._history[self._currentHistoryIndex]
        else:
            cmd = '>>> '
            self._currentHistoryIndex = len(self._history)

        self.replaceCommand(cmd)

    def keyPressEvent(self, event):
        """
        Overloads the key press event to control keystroke modifications for \
        the console widget.
        
        :param      event | <QKeyEvent>
        """
        # enter || return keys will apply the command
        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.applyCommand()
            event.accept()

        # home key will move the cursor to the home position
        elif event.key() == Qt.Key_Home:
            self.gotoHome()
            event.accept()

        elif event.key() in (Qt.Key_Backspace, Qt.Key_Delete):
            super(XConsoleEdit, self).keyPressEvent(event)

            # update the completer
            cursor = self.textCursor()
            text = projex.text.nativestring(cursor.block().text())
            text = text[:cursor.columnNumber()].split(' ')[-1]

            if not '.' in text:
                self.cancelCompletion()

        # period key will trigger a completion popup
        elif event.key() == Qt.Key_Period or \
             (Qt.Key_A <= event.key() <= Qt.Key_Z):
            super(XConsoleEdit, self).keyPressEvent(event)
            self.startCompletion(force=event.key() == Qt.Key_Period)

        # space, tab, backspace and delete will cancel the completion
        elif event.key() == Qt.Key_Space:
            self.cancelCompletion()
            super(XConsoleEdit, self).keyPressEvent(event)

        # left parenthesis will start method help
        elif event.key() == Qt.Key_ParenLeft:
            self.cancelCompletion()
            self.showMethodToolTip()
            super(XConsoleEdit, self).keyPressEvent(event)

        # Ctrl+Up will load previous commands
        elif event.key() == Qt.Key_Up:
            if self.commandLineInteraction() or \
               event.modifiers() & Qt.ControlModifier:
                self.insertPreviousCommand()
                event.accept()
            else:
                super(XConsoleEdit, self).keyPressEvent(event)

        # Ctrl+Down will load future commands
        elif event.key() == Qt.Key_Down:
            if self.commandLineInteraction() or \
               event.modifiers() & Qt.ControlModifier:
                self.insertNextCommand()
                event.accept()
            else:
                super(XConsoleEdit, self).keyPressEvent(event)

        # otherwise, handle the event like normal
        else:
            super(XConsoleEdit, self).keyPressEvent(event)

    def objectAtCursor(self):
        """
        Returns the python object that the text is representing.
        
        :return     <object> || None
        """

        # determine the text block
        cursor = self.textCursor()
        text = projex.text.nativestring(cursor.block().text())
        position = cursor.positionInBlock() - 1

        if not text:
            return (None, '')

        symbol = ''
        for match in re.finditer('[\w\.]+', text):
            if match.start() <= position <= match.end():
                symbol = match.group()
                break

        if not symbol:
            return (None, '')

        parts = symbol.split('.')
        if len(parts) == 1:
            return (self.scope(), parts[0])

        part = parts[0]
        obj = self.scope().get(part)
        for part in parts[1:-1]:
            try:
                obj = getattr(obj, part)
            except AttributeError:
                return (None, '')

        return (obj, parts[-1])

    def restoreSettings(self, settings):
        hist = []
        settings.beginGroup('console')
        for key in sorted(settings.childKeys()):
            hist.append(unwrapVariant(settings.value(key)))
        settings.endGroup()

        self._history = hist

    def saveSettings(self, settings):
        settings.beginGroup('console')
        for i, text in enumerate(self._history):
            settings.setValue('command_{0}'.format(i), wrapVariant(text))
        settings.endGroup()

    def showEvent(self, event):
        super(XConsoleEdit, self).showEvent(event)

        if not self._initialized:
            self._initialized = True

            # create connections
            if os.environ.get('XUI_DISABLE_CONSOLE') != '1':
                hook = XIOHook.instance()
                hook.printed.connect(self._information)
                hook.errored.connect(self._error)

            # setup the header
            opts = {'version': sys.version, 'platform': sys.platform}
            self.setText(HEADER.format(**opts))
            self.waitForInput()

    def showMethodToolTip(self):
        """
        Pops up a tooltip message with the help for the object under the \
        cursor.
        
        :return     <bool> success
        """
        self.cancelCompletion()

        obj, _ = self.objectAtCursor()
        if not obj:
            return False

        docs = inspect.getdoc(obj)
        if not docs:
            return False

        # determine the cursor position
        rect = self.cursorRect()
        cursor = self.textCursor()
        point = QPoint(rect.left(), rect.top() + 18)

        QToolTip.showText(self.mapToGlobal(point), docs, self)

        return True

    def replaceCommand(self, cmd):
        # move to the top of the command structure
        self.moveCursor(QTextCursor.End)
        cursor = self.textCursor()

        line = projex.text.nativestring(cursor.block().text())
        while line.startswith('...'):
            cursor.movePosition(cursor.PreviousBlock)
            line = projex.text.nativestring(cursor.block().text())

        # calculate the command
        cursor.movePosition(cursor.StartOfLine)
        cursor.movePosition(cursor.End, cursor.KeepAnchor)
        cursor.removeSelectedText()
        cursor.insertText(cmd)
        self.moveCursor(cursor.End)

    def scope(self):
        """
        Returns the dictionary scope that will be used when working
        with this editor.
        
        :return     <dict>
        """
        return self._scope

    def setCommandLineInteraction(self, state=True):
        """
        Sets whether or not the interaction should follow command-line
        standards (Up/Down navigation) or not (CTRL+Up/Down).
        
        :param      state | <bool>
        """
        self._commandLineInteraction = state

    def setScope(self, scope):
        """
        Sets the scope that will be used for this editor.
        
        :param      scope | <dict>
        """
        self._scope = scope

    def startCompletion(self, force=False):
        """
        Starts a new completion popup for the current object.
        
        :return     <bool> success
        """
        # add the top level items
        tree = self.completerTree()
        if not force and tree.isVisible():
            return

        tree.clear()

        # make sure we have a valid object
        obj, remain = self.objectAtCursor()

        if obj is None:
            tree.hide()
            return

        # determine the cursor position
        rect = self.cursorRect()
        cursor = self.textCursor()
        point = QPoint(rect.left(), rect.top() + 18)

        # compare the ids since some things might overload the __eq__
        # comparator
        if id(obj) == id(self._scope):
            o_keys = obj.keys()
        elif obj is not None:
            o_keys = dir(obj)

        keys = [key for key in sorted(o_keys) if not key.startswith('_')]
        if id(obj) == id(self._scope):
            if not remain:
                return False
            else:
                keys = filter(lambda x: x.startswith(remain[0]), keys)

        if not keys:
            return False

        for key in keys:
            tree.addTopLevelItem(QTreeWidgetItem([key]))

        tree.move(self.mapToGlobal(point))
        tree.show()
        return True

    def waitForInput(self):
        """
        Inserts a new input command into the console editor.
        """
        self._waitingForInput = False

        try:
            if self.isDestroyed() or self.isReadOnly():
                return
        except RuntimeError:
            return

        self.moveCursor(QTextCursor.End)
        if self.textCursor().block().text() == '>>> ':
            return

        # if there is already text on the line, then start a new line
        newln = '>>> '
        if projex.text.nativestring(self.textCursor().block().text()):
            newln = '\n' + newln

        # insert the text
        self.setCurrentMode('standard')
        self.insertPlainText(newln)
        self.scrollToEnd()

        self._blankCache = ''

    x_commandLineInteraction = Property(bool, commandLineInteraction,
                                        setCommandLineInteraction)