class RedirectedIOConsole(TextEditor): " Widget which implements the redirected IO console " TIMESTAMP_MARGIN = 0 # Introduced here stdoutStyle = 1 stderrStyle = 2 stdinStyle = 3 marginStyle = 4 MODE_OUTPUT = 0 MODE_INPUT = 1 def __init__(self, parent): TextEditor.__init__(self, parent, None) self.zoomTo(Settings().zoom) # line number -> [ timestamps ] self.__marginTooltip = {} self.mode = self.MODE_OUTPUT self.lastOutputPos = None self.inputEcho = True self.inputBuffer = "" self.__messages = IOConsoleMessages() self.__initGeneralSettings() self.__initMargins() self.__timestampTooltipShown = False self.__initMessageMarkers() self._updateDwellingTime() self.installEventFilter(self) return def zoomTo(self, zoomValue): " Reimplemented zoomTo " QsciScintilla.zoomTo(self, zoomValue) self.zoom = zoomValue return def eventFilter(self, obj, event): " Event filter to catch shortcuts on UBUNTU " if event.type() == QEvent.KeyPress: key = event.key() modifiers = event.modifiers() if modifiers == Qt.ShiftModifier | Qt.ControlModifier: if key == Qt.Key_Up: self.selectParagraphUp() return True if key == Qt.Key_Down: self.selectParagraphDown() return True if key == Qt.Key_C: self.onCtrlShiftC() return True if modifiers == Qt.ShiftModifier: if key == Qt.Key_End: self.selectTillDisplayEnd() return True if key == Qt.Key_Home: return self._onShiftHome() if key == Qt.Key_Insert: return self.insertText() if key == Qt.Key_Delete: return self.onShiftDel() if modifiers == Qt.ControlModifier: if key == Qt.Key_V: return self.insertText() if key == Qt.Key_X: return self.onShiftDel() if key in [Qt.Key_C, Qt.Key_Insert]: return self.onCtrlC() if key == Qt.Key_Apostrophe: # Ctrl + ' return self._onHighlight() if key == Qt.Key_Period: return self._onNextHighlight() # Ctrl + . if key == Qt.Key_Comma: return self._onPrevHighlight() # Ctrl + , if key == Qt.Key_Minus: return self._parent.onZoomOut() if key == Qt.Key_Equal: return self._parent.onZoomIn() if key == Qt.Key_0: return self._parent.onZoomReset() if key == Qt.Key_Home: return self.onFirstChar() if key == Qt.Key_End: return self.onLastChar() if modifiers == Qt.AltModifier: if key == Qt.Key_Left: self.wordPartLeft() return True if key == Qt.Key_Right: self.wordPartRight() return True if key == Qt.Key_Up: self.paragraphUp() return True if key == Qt.Key_Down: self.paragraphDown() return True if modifiers == Qt.KeypadModifier | Qt.ControlModifier: if key == Qt.Key_Minus: return self._parent.onZoomOut() if key == Qt.Key_Plus: return self._parent.onZoomIn() if key == Qt.Key_0: return self._parent.onZoomReset() if modifiers == Qt.NoModifier: if key == Qt.Key_Home: return self._onHome() if key == Qt.Key_End: self.moveToLineEnd() return True if key in [Qt.Key_Delete, Qt.Key_Backspace]: if not self.__isCutDelAvailable(): return True return False def keyPressEvent(self, event): " Triggered when a key is pressed " key = event.key() if key == Qt.Key_Escape: self.clearSearchIndicators() return if self.mode == self.MODE_OUTPUT: ScintillaWrapper.keyPressEvent(self, event) return # It is an input mode txt = event.text() if len(txt) and txt >= ' ': # Printable character if self.currentPosition() < self.lastOutputPos: # Out of the input zone return if self.inputEcho: startPos = self.currentPosition() self.SendScintilla(self.SCI_STARTSTYLING, startPos, 31) ScintillaWrapper.keyPressEvent(self, event) endPos = self.currentPosition() self.SendScintilla(self.SCI_SETSTYLING, endPos - startPos, self.stdinStyle) else: self.inputBuffer += txt return # Non-printable character or some other key if key == Qt.Key_Enter or key == Qt.Key_Return: userInput = str(self.__getUserInput()) self.switchMode(self.MODE_OUTPUT) timestampLine, _ = self.getEndPosition() self.append('\n') self.clearUndoHistory() line, pos = self.getEndPosition() self.setCursorPosition(line, pos) self.ensureLineVisible(line) endPos = self.currentPosition() startPos = self.positionBefore(endPos) self.SendScintilla(self.SCI_STARTSTYLING, startPos, 31) self.SendScintilla(self.SCI_SETSTYLING, endPos - startPos, self.stdinStyle) msg = IOConsoleMsg(IOConsoleMsg.STDIN_MESSAGE, userInput + "\n") self.__messages.append(msg) self.__addTooltip(timestampLine, msg.getTimestamp()) self.emit(SIGNAL('UserInput'), userInput) return if key == Qt.Key_Backspace: if self.currentPosition() == self.lastOutputPos: if self.inputEcho == False: self.inputBuffer = self.inputBuffer[:-1] return ScintillaWrapper.keyPressEvent(self, event) return def insertText(self): " Triggered when insert is requested " if self.isReadOnly(): return True # Check what is in the buffer text = QApplication.clipboard().text() if '\n' in text or '\r' in text: return True if not self.inputEcho: self.inputBuffer += text return True startPos = self.currentPosition() TextEditor.paste(self) endPos = self.currentPosition() self.SendScintilla(self.SCI_STARTSTYLING, startPos, 31) self.SendScintilla(self.SCI_SETSTYLING, endPos - startPos, self.stdinStyle) return True def __getUserInput(self): " Provides the collected user input " if self.mode != self.MODE_INPUT: return "" if self.inputEcho: line, pos = self.getEndPosition() _, startPos = self.lineIndexFromPosition(self.lastOutputPos) return self.getTextAtPos(line, startPos, pos - startPos) value = self.inputBuffer self.inputBuffer = "" return value def __initGeneralSettings(self): " Sets some generic look and feel " skin = GlobalData().skin self.setEolVisibility(Settings().ioconsoleshoweol) if Settings().ioconsolelinewrap: self.setWrapMode(QsciScintilla.WrapWord) else: self.setWrapMode(QsciScintilla.WrapNone) if Settings().ioconsoleshowspaces: self.setWhitespaceVisibility(QsciScintilla.WsVisible) else: self.setWhitespaceVisibility(QsciScintilla.WsInvisible) self.setPaper(skin.ioconsolePaper) self.setColor(skin.ioconsoleColor) self.setModified(False) self.setReadOnly(True) # If a lexer id bind then unnecessery visual effects appear like # disappearing assingned text style. As the lexer actually not required # at all I prefer not to struggle with styling but just not to use any # lexers # self.bindLexer( "", TexFileType ) self.setCurrentLineHighlight(False, None) self.setEdgeMode(QsciScintilla.EdgeNone) self.setCursorStyle() self.setAutoIndent(False) return def _onCursorPositionChanged(self, line, pos): " Called when the cursor changed position " self.setCursorStyle() return def setCursorStyle(self): " Sets the cursor style depending on the mode and the cursor pos " self.SendScintilla(self.SCI_SETCARETPERIOD, 750) if self.mode == self.MODE_OUTPUT: self.SendScintilla(self.SCI_SETCARETSTYLE, self.CARETSTYLE_LINE) else: currentPos = self.currentPosition() if currentPos >= self.lastOutputPos: self.SendScintilla(self.SCI_SETCARETSTYLE, self.CARETSTYLE_BLOCK) self.setReadOnly(False) else: self.SendScintilla(self.SCI_SETCARETSTYLE, self.CARETSTYLE_LINE) self.setReadOnly(True) return def switchMode(self, newMode): " Switches between input/output mode " self.mode = newMode if self.mode == self.MODE_OUTPUT: self.lastOutputPos = None self.setReadOnly(True) self.inputEcho = True self.inputBuffer = "" else: line, pos = self.getEndPosition() self.lastOutputPos = self.positionFromLineIndex(line, pos) self.setReadOnly(False) self.setCursorPosition(line, pos) self.ensureLineVisible(line) self.setCursorStyle() return def __initMargins(self): " Initializes the IO console margins " # The supported margins: timestamp # reset standard margins settings for margin in xrange(5): self.setMarginLineNumbers(margin, False) self.setMarginMarkerMask(margin, 0) self.setMarginWidth(margin, 0) self.setMarginSensitivity(margin, False) self.setMarginType(self.TIMESTAMP_MARGIN, self.TextMargin) self.setMarginMarkerMask(self.TIMESTAMP_MARGIN, 0) skin = GlobalData().skin self.setMarginsBackgroundColor(skin.ioconsolemarginPaper) self.setMarginsForegroundColor(skin.ioconsolemarginColor) # Define margin style self.SendScintilla(self.SCI_STYLESETFORE, self.marginStyle, self.convertColor(skin.ioconsolemarginColor)) self.SendScintilla(self.SCI_STYLESETBACK, self.marginStyle, self.convertColor(skin.ioconsolemarginPaper)) self.SendScintilla(self.SCI_STYLESETFONT, self.marginStyle, str(skin.ioconsolemarginFont.family())) self.SendScintilla(self.SCI_STYLESETSIZE, self.marginStyle, skin.ioconsolemarginFont.pointSize()) self.SendScintilla(self.SCI_STYLESETBOLD, self.marginStyle, skin.ioconsolemarginFont.bold()) self.SendScintilla(self.SCI_STYLESETITALIC, self.marginStyle, skin.ioconsolemarginFont.italic()) self.setMarginsFont(skin.ioconsolemarginFont) self.setTimestampMarginWidth() return def __initMessageMarkers(self): " Initializes the marker used for the IDE messages " skin = GlobalData().skin self.ideMessageMarker = self.markerDefine(QsciScintilla.Background) self.setMarkerForegroundColor(skin.ioconsoleIDEMsgColor, self.ideMessageMarker) self.setMarkerBackgroundColor(skin.ioconsoleIDEMsgPaper, self.ideMessageMarker) # stdout style self.SendScintilla(self.SCI_STYLESETFORE, self.stdoutStyle, self.convertColor(skin.ioconsoleStdoutColor)) self.SendScintilla(self.SCI_STYLESETBACK, self.stdoutStyle, self.convertColor(skin.ioconsoleStdoutPaper)) self.SendScintilla(self.SCI_STYLESETBOLD, self.stdoutStyle, skin.ioconsoleStdoutBold != 0) self.SendScintilla(self.SCI_STYLESETITALIC, self.stdoutStyle, skin.ioconsoleStdoutItalic != 0) # stdout style self.SendScintilla(self.SCI_STYLESETFORE, self.stderrStyle, self.convertColor(skin.ioconsoleStderrColor)) self.SendScintilla(self.SCI_STYLESETBACK, self.stderrStyle, self.convertColor(skin.ioconsoleStderrPaper)) self.SendScintilla(self.SCI_STYLESETBOLD, self.stderrStyle, skin.ioconsoleStderrBold != 0) self.SendScintilla(self.SCI_STYLESETITALIC, self.stderrStyle, skin.ioconsoleStderrItalic != 0) # stdin style self.SendScintilla(self.SCI_STYLESETFORE, self.stdinStyle, self.convertColor(skin.ioconsoleStdinColor)) self.SendScintilla(self.SCI_STYLESETBACK, self.stdinStyle, self.convertColor(skin.ioconsoleStdinPaper)) self.SendScintilla(self.SCI_STYLESETBOLD, self.stdinStyle, skin.ioconsoleStdinBold != 0) self.SendScintilla(self.SCI_STYLESETITALIC, self.stdinStyle, skin.ioconsoleStdinItalic != 0) return def _marginClicked(self, margin, line, modifiers): return def _styleNeeded(self, position): return def _updateDwellingTime(self): " There is always something to show " self.SendScintilla(self.SCI_SETMOUSEDWELLTIME, 250) return def _onDwellStart(self, position, x, y): " Triggered when mouse started to dwell " if not self.underMouse(): return marginNumber = self._marginNumber(x) if marginNumber == self.TIMESTAMP_MARGIN: self.__showTimestampTooltip(position, x, y) return TextEditor._onDwellStart(self, position, x, y) return def __showTimestampTooltip(self, position, x, y): " Shows a tooltip on the timestamp margin " # Calculate the line pos = self.SendScintilla(self.SCI_POSITIONFROMPOINT, x, y) line, _ = self.lineIndexFromPosition(pos) tooltip = self.__getTimestampMarginTooltip(line) if not tooltip: return QToolTip.showText(self.mapToGlobal(QPoint(x, y)), tooltip) self.__timestampTooltipShown = True return def __getTimestampMarginTooltip(self, line): " Provides the margin tooltip " if line in self.__marginTooltip: return "\n".join(self.__marginTooltip[line]) return None def _onDwellEnd(self, position, x, y): " Triggered when mouse ended to dwell " if self.__timestampTooltipShown: self.__timestampTooltipShown = False QToolTip.hideText() return def _initContextMenu(self): " Called to initialize a context menu " self._menu = QMenu(self) self.__menuUndo = self._menu.addAction( PixmapCache().getIcon('undo.png'), '&Undo', self.onUndo, "Ctrl+Z") self.__menuRedo = self._menu.addAction( PixmapCache().getIcon('redo.png'), '&Redo', self.onRedo, "Ctrl+Y") self._menu.addSeparator() self.__menuCut = self._menu.addAction( PixmapCache().getIcon('cutmenu.png'), 'Cu&t', self.onShiftDel, "Ctrl+X") self.__menuCopy = self._menu.addAction( PixmapCache().getIcon('copymenu.png'), '&Copy', self.onCtrlC, "Ctrl+C") self.__menucopyTimestamp = self._menu.addAction( PixmapCache().getIcon('copymenu.png'), '&Copy all with timestamps', self.onCtrlShiftC, "Ctrl+Shift+C") self.__menuPaste = self._menu.addAction( PixmapCache().getIcon('pastemenu.png'), '&Paste', self.insertText, "Ctrl+V") self.__menuSelectAll = self._menu.addAction( PixmapCache().getIcon('selectallmenu.png'), 'Select &all', self.selectAll, "Ctrl+A") self._menu.addSeparator() self.__menuOpenAsFile = self._menu.addAction( PixmapCache().getIcon('filemenu.png'), 'O&pen as file', self.openAsFile) self.__menuDownloadAndShow = self._menu.addAction( PixmapCache().getIcon('filemenu.png'), 'Do&wnload and show', self.downloadAndShow) self.__menuOpenInBrowser = self._menu.addAction( PixmapCache().getIcon('homepagemenu.png'), 'Open in browser', self.openInBrowser) self._menu.addSeparator() self._menu.aboutToShow.connect(self._contextMenuAboutToShow) self._menu.aboutToHide.connect(self._contextMenuAboutToHide) return def setTimestampMarginWidth(self): " Sets the timestamp margin width " settings = Settings() if settings.ioconsoleshowmargin: skin = GlobalData().skin font = QFont(skin.ioconsolemarginFont) font.setPointSize(font.pointSize() + settings.zoom) # The second parameter of the QFontMetrics is essential! # If it is not there then the width is not calculated properly. fontMetrics = QFontMetrics(font, self) # W is for extra space at the right of the timestamp width = fontMetrics.width('88:88:88.888W') self.setMarginWidth(self.TIMESTAMP_MARGIN, width) else: self.setMarginWidth(self.TIMESTAMP_MARGIN, 0) return def contextMenuEvent(self, event): " Called just before showing a context menu " event.accept() if self._marginNumber(event.x()) is None: # Editing area context menu self._menu.popup(event.globalPos()) else: # Menu for a margin pass return def _contextMenuAboutToShow(self): " IO Console context menu is about to show " self.__menuUndo.setEnabled(self.isUndoAvailable()) self.__menuRedo.setEnabled(self.isRedoAvailable()) pasteText = QApplication.clipboard().text() pasteEnable = pasteText != "" and \ '\n' not in pasteText and \ '\r' not in pasteText and \ not self.isReadOnly() # Need to make decision about menu items for modifying the input self.__menuCut.setEnabled(self.__isCutDelAvailable()) self.__menuCopy.setEnabled(self.__messages.size > 0) self.__menucopyTimestamp.setEnabled(self.__messages.size > 0) self.__menuPaste.setEnabled(pasteEnable) self.__menuSelectAll.setEnabled(self.__messages.size > 0) self.__menuOpenAsFile.setEnabled(self.openAsFileAvailable()) self.__menuDownloadAndShow.setEnabled(self.downloadAndShowAvailable()) self.__menuOpenInBrowser.setEnabled(self.downloadAndShowAvailable()) return def _contextMenuAboutToHide(self): " IO console context menu is about to hide " self.__menuUndo.setEnabled(True) self.__menuRedo.setEnabled(True) self.__menuCut.setEnabled(True) self.__menuCopy.setEnabled(True) self.__menucopyTimestamp.setEnabled(True) self.__menuPaste.setEnabled(True) self.__menuSelectAll.setEnabled(True) self.__menuOpenAsFile.setEnabled(True) self.__menuDownloadAndShow.setEnabled(True) self.__menuOpenInBrowser.setEnabled(True) return def __isCutDelAvailable(self): " Returns True if cutting or deletion is possible " if self.isReadOnly(): return False minPos = self.getSelectionStart() if minPos < self.lastOutputPos: return False return True def onShiftDel(self): " Deletes the selected text " if self.hasSelectedText(): if self.__isCutDelAvailable(): self.cut() return True return True def onUndo(self): if self.isUndoAvailable(): self.undo() return def onRedo(self): if self.isRedoAvailable(): self.redo() return def onCtrlShiftC(self): " Copy all with timestamps " QApplication.clipboard().setText( self.__messages.renderWithTimestamps()) return True def appendIDEMessage(self, text): " Appends an IDE message " msg = IOConsoleMsg(IOConsoleMsg.IDE_MESSAGE, text) self.__appendMessage(msg) return def appendStdoutMessage(self, text): " Appends an stdout message " msg = IOConsoleMsg(IOConsoleMsg.STDOUT_MESSAGE, text) self.__appendMessage(msg) return def appendStderrMessage(self, text): " Appends an stderr message " msg = IOConsoleMsg(IOConsoleMsg.STDERR_MESSAGE, text) self.__appendMessage(msg) return def __appendMessage(self, message): " Appends a new message to the console " if not self.__messages.append(message): # There was no trimming of the message list self.__renderMessage(message) else: # Some messages were stripped self.renderContent() return def renderContent(self): " Regenerates the viewer content " self.clear() self.__marginTooltip = {} for msg in self.__messages.msgs: self.__renderMessage(msg) return def __renderMessage(self, msg): " Adds a single message " timestamp = msg.getTimestamp() if msg.msgType == IOConsoleMsg.IDE_MESSAGE: # Check the text. Append \n if needed. Append the message line, pos = self.getEndPosition() if pos != 0: self.append("\n") startMarkLine = line + 1 else: startMarkLine = line self.append(msg.msgText) if not msg.msgText.endswith("\n"): self.append("\n") line, pos = self.getEndPosition() for lineNo in xrange(startMarkLine, line): self.markerAdd(lineNo, self.ideMessageMarker) self.__addTooltip(lineNo, timestamp) else: if self._parent.hiddenMessage(msg): return line, pos = self.getEndPosition() startPos = self.positionFromLineIndex(line, pos) if pos != 0: self.__addTooltip(line, timestamp) startTimestampLine = line + 1 else: startTimestampLine = line self.append(msg.msgText) line, pos = self.getEndPosition() if pos != 0: endTimestampLine = line else: endTimestampLine = line - 1 for lineNo in xrange(startTimestampLine, endTimestampLine + 1): self.__addTooltip(lineNo, timestamp) if msg.msgType == IOConsoleMsg.STDERR_MESSAGE: # Highlight as stderr styleNo = self.stderrStyle elif msg.msgType == IOConsoleMsg.STDOUT_MESSAGE: # Highlight as stdout styleNo = self.stdoutStyle else: styleNo = self.stdinStyle line, pos = self.getEndPosition() endPos = self.positionFromLineIndex(line, pos) self.SendScintilla(self.SCI_STARTSTYLING, startPos, 31) line, pos = self.getEndPosition() endPos = self.positionFromLineIndex(line, pos) self.SendScintilla(self.SCI_SETSTYLING, endPos - startPos, styleNo) self.clearUndoHistory() if Settings().ioconsoleautoscroll: line, pos = self.getEndPosition() self.gotoLine(line + 1, pos + 1) return def __addTooltip(self, lineNo, timestamp): " Adds a tooltip into the dictionary " if lineNo in self.__marginTooltip: self.__marginTooltip[lineNo].append(timestamp) else: self.__marginTooltip[lineNo] = [timestamp] self.setMarginText(lineNo, timestamp, self.marginStyle) return def clearData(self): " Clears the collected data " self.__messages.clear() self.__marginTooltip = {} return def clearAll(self): " Clears both data and visible content " self.clearData() self.clear() self.clearUndoHistory() return
class RedirectedIOConsole( TextEditor ): " Widget which implements the redirected IO console " TIMESTAMP_MARGIN = 0 # Introduced here stdoutStyle = 1 stderrStyle = 2 stdinStyle = 3 marginStyle = 4 MODE_OUTPUT = 0 MODE_INPUT = 1 def __init__( self, parent ): TextEditor.__init__( self, parent, None ) self.zoomTo( Settings().zoom ) # line number -> [ timestamps ] self.__marginTooltip = {} self.mode = self.MODE_OUTPUT self.lastOutputPos = None self.inputEcho = True self.inputBuffer = "" self.__messages = IOConsoleMessages() self.__initGeneralSettings() self.__initMargins() self.__timestampTooltipShown = False self.__initMessageMarkers() self._updateDwellingTime() self.installEventFilter( self ) return def zoomTo( self, zoomValue ): " Reimplemented zoomTo " QsciScintilla.zoomTo( self, zoomValue ) self.zoom = zoomValue return def eventFilter( self, obj, event ): " Event filter to catch shortcuts on UBUNTU " if event.type() == QEvent.KeyPress: key = event.key() modifiers = event.modifiers() if modifiers == Qt.ShiftModifier | Qt.ControlModifier: if key == Qt.Key_Up: self.selectParagraphUp() return True if key == Qt.Key_Down: self.selectParagraphDown() return True if key == Qt.Key_C: self.onCtrlShiftC() return True if modifiers == Qt.ShiftModifier: if key == Qt.Key_End: self.selectTillDisplayEnd() return True if key == Qt.Key_Home: return self._onShiftHome() if key == Qt.Key_Insert: return self.insertText() if key == Qt.Key_Delete: return self.onShiftDel() if modifiers == Qt.ControlModifier: if key == Qt.Key_V: return self.insertText() if key == Qt.Key_X: return self.onShiftDel() if key in [ Qt.Key_C, Qt.Key_Insert ]: return self.onCtrlC() if key == Qt.Key_Apostrophe: # Ctrl + ' return self._onHighlight() if key == Qt.Key_Period: return self._onNextHighlight() # Ctrl + . if key == Qt.Key_Comma: return self._onPrevHighlight() # Ctrl + , if key == Qt.Key_Minus: return self._parent.onZoomOut() if key == Qt.Key_Equal: return self._parent.onZoomIn() if key == Qt.Key_0: return self._parent.onZoomReset() if key == Qt.Key_Home: return self.onFirstChar() if key == Qt.Key_End: return self.onLastChar() if modifiers == Qt.AltModifier: if key == Qt.Key_Left: self.wordPartLeft() return True if key == Qt.Key_Right: self.wordPartRight() return True if key == Qt.Key_Up: self.paragraphUp() return True if key == Qt.Key_Down: self.paragraphDown() return True if modifiers == Qt.KeypadModifier | Qt.ControlModifier: if key == Qt.Key_Minus: return self._parent.onZoomOut() if key == Qt.Key_Plus: return self._parent.onZoomIn() if key == Qt.Key_0: return self._parent.onZoomReset() if modifiers == Qt.NoModifier: if key == Qt.Key_Home: return self._onHome() if key == Qt.Key_End: self.moveToLineEnd() return True if key in [ Qt.Key_Delete, Qt.Key_Backspace ]: if not self.__isCutDelAvailable(): return True return False def keyPressEvent( self, event ): " Triggered when a key is pressed " key = event.key() if key == Qt.Key_Escape: self.clearSearchIndicators() return if self.mode == self.MODE_OUTPUT: ScintillaWrapper.keyPressEvent( self, event ) return # It is an input mode txt = event.text() if len( txt ) and txt >= ' ': # Printable character if self.currentPosition() < self.lastOutputPos: # Out of the input zone return if self.inputEcho: startPos = self.currentPosition() self.SendScintilla( self.SCI_STARTSTYLING, startPos, 31 ) ScintillaWrapper.keyPressEvent( self, event ) endPos = self.currentPosition() self.SendScintilla( self.SCI_SETSTYLING, endPos - startPos, self.stdinStyle ) else: self.inputBuffer += txt return # Non-printable character or some other key if key == Qt.Key_Enter or key == Qt.Key_Return: userInput = str( self.__getUserInput() ) self.switchMode( self.MODE_OUTPUT ) timestampLine, _ = self.getEndPosition() self.append( '\n' ) self.clearUndoHistory() line, pos = self.getEndPosition() self.setCursorPosition( line, pos ) self.ensureLineVisible( line ) endPos = self.currentPosition() startPos = self.positionBefore( endPos ) self.SendScintilla( self.SCI_STARTSTYLING, startPos, 31 ) self.SendScintilla( self.SCI_SETSTYLING, endPos - startPos, self.stdinStyle ) msg = IOConsoleMsg( IOConsoleMsg.STDIN_MESSAGE, userInput + "\n" ) self.__messages.append( msg ) self.__addTooltip( timestampLine, msg.getTimestamp() ) self.emit( SIGNAL( 'UserInput' ), userInput ) return if key == Qt.Key_Backspace: if self.currentPosition() == self.lastOutputPos: if self.inputEcho == False: self.inputBuffer = self.inputBuffer[ : -1 ] return ScintillaWrapper.keyPressEvent( self, event ) return def insertText( self ): " Triggered when insert is requested " if self.isReadOnly(): return True # Check what is in the buffer text = QApplication.clipboard().text() if '\n' in text or '\r' in text: return True if not self.inputEcho: self.inputBuffer += text return True startPos = self.currentPosition() TextEditor.paste( self ) endPos = self.currentPosition() self.SendScintilla( self.SCI_STARTSTYLING, startPos, 31 ) self.SendScintilla( self.SCI_SETSTYLING, endPos - startPos, self.stdinStyle ) return True def __getUserInput( self ): " Provides the collected user input " if self.mode != self.MODE_INPUT: return "" if self.inputEcho: line, pos = self.getEndPosition() _, startPos = self.lineIndexFromPosition( self.lastOutputPos ) return self.getTextAtPos( line, startPos, pos - startPos ) value = self.inputBuffer self.inputBuffer = "" return value def __initGeneralSettings( self ): " Sets some generic look and feel " skin = GlobalData().skin self.setEolVisibility( Settings().ioconsoleshoweol ) if Settings().ioconsolelinewrap: self.setWrapMode( QsciScintilla.WrapWord ) else: self.setWrapMode( QsciScintilla.WrapNone ) if Settings().ioconsoleshowspaces: self.setWhitespaceVisibility( QsciScintilla.WsVisible ) else: self.setWhitespaceVisibility( QsciScintilla.WsInvisible ) self.setPaper( skin.ioconsolePaper ) self.setColor( skin.ioconsoleColor ) self.setModified( False ) self.setReadOnly( True ) # If a lexer id bind then unnecessery visual effects appear like # disappearing assingned text style. As the lexer actually not required # at all I prefer not to struggle with styling but just not to use any # lexers # self.bindLexer( "", TexFileType ) self.setCurrentLineHighlight( False, None ) self.setEdgeMode( QsciScintilla.EdgeNone ) self.setCursorStyle() self.setAutoIndent( False ) return def _onCursorPositionChanged( self, line, pos ): " Called when the cursor changed position " self.setCursorStyle() return def setCursorStyle( self ): " Sets the cursor style depending on the mode and the cursor pos " self.SendScintilla( self.SCI_SETCARETPERIOD, 750 ) if self.mode == self.MODE_OUTPUT: self.SendScintilla( self.SCI_SETCARETSTYLE, self.CARETSTYLE_LINE ) else: currentPos = self.currentPosition() if currentPos >= self.lastOutputPos: self.SendScintilla( self.SCI_SETCARETSTYLE, self.CARETSTYLE_BLOCK ) self.setReadOnly( False ) else: self.SendScintilla( self.SCI_SETCARETSTYLE, self.CARETSTYLE_LINE ) self.setReadOnly( True ) return def switchMode( self, newMode ): " Switches between input/output mode " self.mode = newMode if self.mode == self.MODE_OUTPUT: self.lastOutputPos = None self.setReadOnly( True ) self.inputEcho = True self.inputBuffer = "" else: line, pos = self.getEndPosition() self.lastOutputPos = self.positionFromLineIndex( line, pos ) self.setReadOnly( False ) self.setCursorPosition( line, pos ) self.ensureLineVisible( line ) self.setCursorStyle() return def __initMargins( self ): " Initializes the IO console margins " # The supported margins: timestamp # reset standard margins settings for margin in xrange( 5 ): self.setMarginLineNumbers( margin, False ) self.setMarginMarkerMask( margin, 0 ) self.setMarginWidth( margin, 0 ) self.setMarginSensitivity( margin, False ) self.setMarginType( self.TIMESTAMP_MARGIN, self.TextMargin ) self.setMarginMarkerMask( self.TIMESTAMP_MARGIN, 0 ) skin = GlobalData().skin self.setMarginsBackgroundColor( skin.ioconsolemarginPaper ) self.setMarginsForegroundColor( skin.ioconsolemarginColor ) # Define margin style self.SendScintilla( self.SCI_STYLESETFORE, self.marginStyle, self.convertColor( skin.ioconsolemarginColor ) ) self.SendScintilla( self.SCI_STYLESETBACK, self.marginStyle, self.convertColor( skin.ioconsolemarginPaper ) ) self.SendScintilla( self.SCI_STYLESETFONT, self.marginStyle, str( skin.ioconsolemarginFont.family() ) ) self.SendScintilla( self.SCI_STYLESETSIZE, self.marginStyle, skin.ioconsolemarginFont.pointSize() ) self.SendScintilla( self.SCI_STYLESETBOLD, self.marginStyle, skin.ioconsolemarginFont.bold() ) self.SendScintilla( self.SCI_STYLESETITALIC, self.marginStyle, skin.ioconsolemarginFont.italic() ) self.setMarginsFont( skin.ioconsolemarginFont ) self.setTimestampMarginWidth() return def __initMessageMarkers( self ): " Initializes the marker used for the IDE messages " skin = GlobalData().skin self.ideMessageMarker = self.markerDefine( QsciScintilla.Background ) self.setMarkerForegroundColor( skin.ioconsoleIDEMsgColor, self.ideMessageMarker ) self.setMarkerBackgroundColor( skin.ioconsoleIDEMsgPaper, self.ideMessageMarker ) # stdout style self.SendScintilla( self.SCI_STYLESETFORE, self.stdoutStyle, self.convertColor( skin.ioconsoleStdoutColor ) ) self.SendScintilla( self.SCI_STYLESETBACK, self.stdoutStyle, self.convertColor( skin.ioconsoleStdoutPaper ) ) self.SendScintilla( self.SCI_STYLESETBOLD, self.stdoutStyle, skin.ioconsoleStdoutBold != 0 ) self.SendScintilla( self.SCI_STYLESETITALIC, self.stdoutStyle, skin.ioconsoleStdoutItalic != 0 ) # stdout style self.SendScintilla( self.SCI_STYLESETFORE, self.stderrStyle, self.convertColor( skin.ioconsoleStderrColor ) ) self.SendScintilla( self.SCI_STYLESETBACK, self.stderrStyle, self.convertColor( skin.ioconsoleStderrPaper ) ) self.SendScintilla( self.SCI_STYLESETBOLD, self.stderrStyle, skin.ioconsoleStderrBold != 0 ) self.SendScintilla( self.SCI_STYLESETITALIC, self.stderrStyle, skin.ioconsoleStderrItalic != 0 ) # stdin style self.SendScintilla( self.SCI_STYLESETFORE, self.stdinStyle, self.convertColor( skin.ioconsoleStdinColor ) ) self.SendScintilla( self.SCI_STYLESETBACK, self.stdinStyle, self.convertColor( skin.ioconsoleStdinPaper ) ) self.SendScintilla( self.SCI_STYLESETBOLD, self.stdinStyle, skin.ioconsoleStdinBold != 0 ) self.SendScintilla( self.SCI_STYLESETITALIC, self.stdinStyle, skin.ioconsoleStdinItalic != 0 ) return def _marginClicked( self, margin, line, modifiers ): return def _styleNeeded( self, position ): return def _updateDwellingTime( self ): " There is always something to show " self.SendScintilla( self.SCI_SETMOUSEDWELLTIME, 250 ) return def _onDwellStart( self, position, x, y ): " Triggered when mouse started to dwell " if not self.underMouse(): return marginNumber = self._marginNumber( x ) if marginNumber == self.TIMESTAMP_MARGIN: self.__showTimestampTooltip( position, x, y ) return TextEditor._onDwellStart( self, position, x, y ) return def __showTimestampTooltip( self, position, x, y ): " Shows a tooltip on the timestamp margin " # Calculate the line pos = self.SendScintilla( self.SCI_POSITIONFROMPOINT, x, y ) line, _ = self.lineIndexFromPosition( pos ) tooltip = self.__getTimestampMarginTooltip( line ) if not tooltip: return QToolTip.showText( self.mapToGlobal( QPoint( x, y ) ), tooltip ) self.__timestampTooltipShown = True return def __getTimestampMarginTooltip( self, line ): " Provides the margin tooltip " if line in self.__marginTooltip: return "\n".join( self.__marginTooltip[ line ] ) return None def _onDwellEnd( self, position, x, y ): " Triggered when mouse ended to dwell " if self.__timestampTooltipShown: self.__timestampTooltipShown = False QToolTip.hideText() return def _initContextMenu( self ): " Called to initialize a context menu " self._menu = QMenu( self ) self.__menuUndo = self._menu.addAction( PixmapCache().getIcon( 'undo.png' ), '&Undo', self.onUndo, "Ctrl+Z" ) self.__menuRedo = self._menu.addAction( PixmapCache().getIcon( 'redo.png' ), '&Redo', self.onRedo, "Ctrl+Y" ) self._menu.addSeparator() self.__menuCut = self._menu.addAction( PixmapCache().getIcon( 'cutmenu.png' ), 'Cu&t', self.onShiftDel, "Ctrl+X" ) self.__menuCopy = self._menu.addAction( PixmapCache().getIcon( 'copymenu.png' ), '&Copy', self.onCtrlC, "Ctrl+C" ) self.__menucopyTimestamp = self._menu.addAction( PixmapCache().getIcon( 'copymenu.png' ), '&Copy all with timestamps', self.onCtrlShiftC, "Ctrl+Shift+C" ) self.__menuPaste = self._menu.addAction( PixmapCache().getIcon( 'pastemenu.png' ), '&Paste', self.insertText, "Ctrl+V" ) self.__menuSelectAll = self._menu.addAction( PixmapCache().getIcon( 'selectallmenu.png' ), 'Select &all', self.selectAll, "Ctrl+A" ) self._menu.addSeparator() self.__menuOpenAsFile = self._menu.addAction( PixmapCache().getIcon( 'filemenu.png' ), 'O&pen as file', self.openAsFile ) self.__menuDownloadAndShow = self._menu.addAction( PixmapCache().getIcon( 'filemenu.png' ), 'Do&wnload and show', self.downloadAndShow ) self.__menuOpenInBrowser = self._menu.addAction( PixmapCache().getIcon( 'homepagemenu.png' ), 'Open in browser', self.openInBrowser ) self._menu.addSeparator() self._menu.aboutToShow.connect( self._contextMenuAboutToShow ) self._menu.aboutToHide.connect( self._contextMenuAboutToHide ) return def setTimestampMarginWidth( self ): " Sets the timestamp margin width " settings = Settings() if settings.ioconsoleshowmargin: skin = GlobalData().skin font = QFont( skin.ioconsolemarginFont ) font.setPointSize( font.pointSize() + settings.zoom ) # The second parameter of the QFontMetrics is essential! # If it is not there then the width is not calculated properly. fontMetrics = QFontMetrics( font, self ) # W is for extra space at the right of the timestamp width = fontMetrics.width( '88:88:88.888W' ) self.setMarginWidth( self.TIMESTAMP_MARGIN, width ) else: self.setMarginWidth( self.TIMESTAMP_MARGIN, 0 ) return def contextMenuEvent( self, event ): " Called just before showing a context menu " event.accept() if self._marginNumber( event.x() ) is None: # Editing area context menu self._menu.popup( event.globalPos() ) else: # Menu for a margin pass return def _contextMenuAboutToShow( self ): " IO Console context menu is about to show " self.__menuUndo.setEnabled( self.isUndoAvailable() ) self.__menuRedo.setEnabled( self.isRedoAvailable() ) pasteText = QApplication.clipboard().text() pasteEnable = pasteText != "" and \ '\n' not in pasteText and \ '\r' not in pasteText and \ not self.isReadOnly() # Need to make decision about menu items for modifying the input self.__menuCut.setEnabled( self.__isCutDelAvailable() ) self.__menuCopy.setEnabled( self.__messages.size > 0 ) self.__menucopyTimestamp.setEnabled( self.__messages.size > 0 ) self.__menuPaste.setEnabled( pasteEnable ) self.__menuSelectAll.setEnabled( self.__messages.size > 0 ) self.__menuOpenAsFile.setEnabled( self.openAsFileAvailable() ) self.__menuDownloadAndShow.setEnabled( self.downloadAndShowAvailable() ) self.__menuOpenInBrowser.setEnabled( self.downloadAndShowAvailable() ) return def _contextMenuAboutToHide( self ): " IO console context menu is about to hide " self.__menuUndo.setEnabled( True ) self.__menuRedo.setEnabled( True ) self.__menuCut.setEnabled( True ) self.__menuCopy.setEnabled( True ) self.__menucopyTimestamp.setEnabled( True ) self.__menuPaste.setEnabled( True ) self.__menuSelectAll.setEnabled( True ) self.__menuOpenAsFile.setEnabled( True ) self.__menuDownloadAndShow.setEnabled( True ) self.__menuOpenInBrowser.setEnabled( True ) return def __isCutDelAvailable( self ): " Returns True if cutting or deletion is possible " if self.isReadOnly(): return False minPos = self.getSelectionStart() if minPos < self.lastOutputPos: return False return True def onShiftDel( self ): " Deletes the selected text " if self.hasSelectedText(): if self.__isCutDelAvailable(): self.cut() return True return True def onUndo( self ): if self.isUndoAvailable(): self.undo() return def onRedo( self ): if self.isRedoAvailable(): self.redo() return def onCtrlShiftC( self ): " Copy all with timestamps " QApplication.clipboard().setText( self.__messages.renderWithTimestamps() ) return True def appendIDEMessage( self, text ): " Appends an IDE message " msg = IOConsoleMsg( IOConsoleMsg.IDE_MESSAGE, text ) self.__appendMessage( msg ) return def appendStdoutMessage( self, text ): " Appends an stdout message " msg = IOConsoleMsg( IOConsoleMsg.STDOUT_MESSAGE, text ) self.__appendMessage( msg ) return def appendStderrMessage( self, text ): " Appends an stderr message " msg = IOConsoleMsg( IOConsoleMsg.STDERR_MESSAGE, text ) self.__appendMessage( msg ) return def __appendMessage( self, message ): " Appends a new message to the console " if not self.__messages.append( message ): # There was no trimming of the message list self.__renderMessage( message ) else: # Some messages were stripped self.renderContent() return def renderContent( self ): " Regenerates the viewer content " self.clear() self.__marginTooltip = {} for msg in self.__messages.msgs: self.__renderMessage( msg ) return def __renderMessage( self, msg ): " Adds a single message " timestamp = msg.getTimestamp() if msg.msgType == IOConsoleMsg.IDE_MESSAGE: # Check the text. Append \n if needed. Append the message line, pos = self.getEndPosition() if pos != 0: self.append( "\n" ) startMarkLine = line + 1 else: startMarkLine = line self.append( msg.msgText ) if not msg.msgText.endswith( "\n" ): self.append( "\n" ) line, pos = self.getEndPosition() for lineNo in xrange( startMarkLine, line ): self.markerAdd( lineNo, self.ideMessageMarker ) self.__addTooltip( lineNo, timestamp ) else: if self._parent.hiddenMessage( msg ): return line, pos = self.getEndPosition() startPos = self.positionFromLineIndex( line, pos ) if pos != 0: self.__addTooltip( line, timestamp ) startTimestampLine = line + 1 else: startTimestampLine = line self.append( msg.msgText ) line, pos = self.getEndPosition() if pos != 0: endTimestampLine = line else: endTimestampLine = line - 1 for lineNo in xrange( startTimestampLine, endTimestampLine + 1 ): self.__addTooltip( lineNo, timestamp ) if msg.msgType == IOConsoleMsg.STDERR_MESSAGE: # Highlight as stderr styleNo = self.stderrStyle elif msg.msgType == IOConsoleMsg.STDOUT_MESSAGE: # Highlight as stdout styleNo = self.stdoutStyle else: styleNo = self.stdinStyle line, pos = self.getEndPosition() endPos = self.positionFromLineIndex( line, pos ) self.SendScintilla( self.SCI_STARTSTYLING, startPos, 31 ) line, pos = self.getEndPosition() endPos = self.positionFromLineIndex( line, pos ) self.SendScintilla( self.SCI_SETSTYLING, endPos - startPos, styleNo ) self.clearUndoHistory() if Settings().ioconsoleautoscroll: line, pos = self.getEndPosition() self.gotoLine( line + 1, pos + 1 ) return def __addTooltip( self, lineNo, timestamp ): " Adds a tooltip into the dictionary " if lineNo in self.__marginTooltip: self.__marginTooltip[ lineNo ].append( timestamp ) else: self.__marginTooltip[ lineNo ] = [ timestamp ] self.setMarginText( lineNo, timestamp, self.marginStyle ) return def clearData( self ): " Clears the collected data " self.__messages.clear() self.__marginTooltip = {} return def clearAll( self ): " Clears both data and visible content " self.clearData() self.clear() self.clearUndoHistory() return