def __init__(self, parent=None): ''' Constructor ''' QMainWindow.__init__(self, parent) self.ui = Ui_ConsoleWindow() self.ui.setupUi(self) self.prompt_is_dirty = True self.regular_prompt = ">>> " self.more_prompt = "... " self.prompt = self.regular_prompt QApplication.clipboard().dataChanged.connect(self.onClipboardDataChanged) self.te = self.ui.plainTextEdit self.te.selectionChanged.connect(self.onSelectionChanged) self.te.cursorPositionChanged.connect(self.onCursorPositionChanged) self.append_blue("Welcome to the Cinemol python console!\n") # This header text is intended to look just like the standard python banner self.append_blue("Python " + sys.version + " on " + sys.platform) # self.append('Type "help", "copyright", "credits" or "license" for more information.') self.append("") # Need return before prompt # make cursor about the size of a letter cursor_size = QFontMetrics(self.te.font()).width("m") if cursor_size > 0: self.te.setCursorWidth(cursor_size) else: self.te.setCursorWidth(1) self.command_ring = CommandRing() self.multiline_command = "" self.te.installEventFilter(self) self.te.viewport().installEventFilter(self) self.command_buffer = "" self.current_command_start_position = None self.place_new_prompt() self.stdout = ConsoleStdoutStream(self) self.stderr = ConsoleStderrStream(self) self.stdin = ConsoleStdinStream(self) self.recent_files = recent_file.RecentFileList(self.run_script_file, "input_script_files", self.ui.menuOpen_recent)
class Console(QMainWindow): ''' Command window for interactive python commands ''' def __init__(self, parent=None): ''' Constructor ''' QMainWindow.__init__(self, parent) self.ui = Ui_ConsoleWindow() self.ui.setupUi(self) self.prompt_is_dirty = True self.regular_prompt = ">>> " self.more_prompt = "... " self.prompt = self.regular_prompt QApplication.clipboard().dataChanged.connect(self.onClipboardDataChanged) self.te = self.ui.plainTextEdit self.te.selectionChanged.connect(self.onSelectionChanged) self.te.cursorPositionChanged.connect(self.onCursorPositionChanged) self.append_blue("Welcome to the Cinemol python console!\n") # This header text is intended to look just like the standard python banner self.append_blue("Python " + sys.version + " on " + sys.platform) # self.append('Type "help", "copyright", "credits" or "license" for more information.') self.append("") # Need return before prompt # make cursor about the size of a letter cursor_size = QFontMetrics(self.te.font()).width("m") if cursor_size > 0: self.te.setCursorWidth(cursor_size) else: self.te.setCursorWidth(1) self.command_ring = CommandRing() self.multiline_command = "" self.te.installEventFilter(self) self.te.viewport().installEventFilter(self) self.command_buffer = "" self.current_command_start_position = None self.place_new_prompt() self.stdout = ConsoleStdoutStream(self) self.stderr = ConsoleStderrStream(self) self.stdin = ConsoleStdinStream(self) self.recent_files = recent_file.RecentFileList(self.run_script_file, "input_script_files", self.ui.menuOpen_recent) def pause_command_capture(self): self.command_buffer = self.get_current_command() self.te.moveCursor(QTextCursor.End) self.latest_good_cursor = self.te.textCursor() self.current_command_start_position = self.latest_good_cursor.position() # self.latest_good_cursor = None # Replace current command with a new one def replace_current_command(self, newCommand): cursor = self.te.textCursor() cursor.setPosition(self.current_command_start_position, QTextCursor.MoveAnchor) cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) cursor.insertText(newCommand) def resume_command_capture(self): self.te.moveCursor(QTextCursor.End) self.latest_good_cursor = self.te.textCursor() self.current_command_start_position = self.latest_good_cursor.position() def show_previous_command(self): provisional_command = self.get_current_command() previous_command = self.command_ring.get_previous_command(provisional_command) if previous_command != provisional_command: self.replace_current_command(previous_command) def show_next_command(self): provisional_command = self.get_current_command() next_command = self.command_ring.get_next_command(provisional_command) if next_command != provisional_command: self.replace_current_command(next_command) def cursor_is_in_editing_region(self, cursor): # Want to be to the right of the prompt... if cursor.positionInBlock() < len(self.prompt): return False # ... and in the final line. if cursor.blockNumber() != self.te.blockCount() - 1: return False if cursor.anchor() != cursor.position(): # Anchor might be outside of editing region anchorCursor = QTextCursor(cursor) anchorCursor.setPosition(cursor.anchor()) if anchorCursor.positionInBlock() < len(self.prompt): return False if anchorCursor.blockNumber() != self.te.blockCount() - 1: return False return True def eventFilter(self, watched, event): if event.type() == QEvent.MouseButtonPress: # On unix, we want to update cursor position on middle # button press, before deciding whether editing is possible. mouseEvent = event # Qt 4.6 has only "MidButton", not "MiddleButton" if mouseEvent.buttons() == Qt.MidButton: newCursor = self.te.cursorForPosition(mouseEvent.pos()) self.te.setTextCursor(newCursor) elif event.type() == QEvent.KeyPress: keyEvent = event if self.prompt_is_dirty: self.place_new_prompt(True) # CONTROL-keystrokes key = keyEvent.key() if keyEvent.modifiers() & Qt.ControlModifier: # Ctrl-p and Ctrl-n for command history if key == Qt.Key_P: self.show_previous_command() return True elif key == Qt.Key_N: self.show_next_command() return True elif keyEvent.modifiers() & Qt.AltModifier: pass elif keyEvent.modifiers() & Qt.MetaModifier: pass else: # non-Ctrl keystrokes # Use up and down arrows for history if key == Qt.Key_Up: self.show_previous_command() return True elif key == Qt.Key_Down: self.show_next_command() return True # Prevent left arrow from leaving editing area elif (key == Qt.Key_Left) or (key == Qt.Key_Backspace): # Qt 4.6 lacks QTextCursor.positionInBlock if (self.te.textCursor().positionInBlock() == len(self.prompt)) and self.cursor_is_in_editing_region(self.te.textCursor()): return True # no moving left into prompt with arrow key # Trigger command execution with <Return> elif (key == Qt.Key_Return) or (key == Qt.Key_Enter): self.run_console_command() return True # Consume event. We will take care of inserting the newline. # If this is a printing character, make sure the editing console is activated if len(keyEvent.text()) > 0: if not self.cursor_is_in_editing_region(self.te.textCursor()): self.te.setTextCursor(self.latest_good_cursor) return QMainWindow.eventFilter(self, watched, event) def run_console_command(self): # Scroll down after command, if and only if bottom is visible now. end_is_visible = self.te.document().lastBlock().isVisible() command = self.get_current_command() self.command_buffer = "" self.command_ring.add_history(command) self.run_command_string(command, end_is_visible) def get_current_command(self): cursor = self.te.textCursor() cursor.setPosition(self.current_command_start_position, QTextCursor.MoveAnchor) cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) command = cursor.selectedText() cursor.clearSelection() return self.command_buffer + command def place_new_prompt(self, make_visible=True): self.te.setUndoRedoEnabled(False) # self.pause_command_capture() cursor = self.te.textCursor() cursor.movePosition(QTextCursor.End) cursor.insertHtml('<font color="black">' + _escape_html(self.prompt) + '</font>') self.te.setTextCursor(cursor) # self.te.insertPlainText(self.prompt) self.te.moveCursor(QTextCursor.End) if make_visible: self.te.ensureCursorVisible() self.latest_good_cursor = self.te.textCursor() self.current_command_start_position = self.latest_good_cursor.position() # self.te.insertPlainText(self.command_buffer) self.command_buffer = "" # self.resume_command_capture() self.te.setUndoRedoEnabled(True) self.prompt_is_dirty = False def append(self, message): self.ui.plainTextEdit.appendPlainText(message) def append_red(self, message): cursor = self.te.textCursor() cursor.movePosition(QTextCursor.End) cursor.insertHtml('<font color="red">' + _escape_html(message) + '</font>') self.te.setTextCursor(cursor) def append_blue(self, message): cursor = self.te.textCursor() cursor.movePosition(QTextCursor.End) cursor.insertHtml('<font color="blue">' + _escape_html(message) + '</font>') self.te.setTextCursor(cursor) def run_command_string(self, command, end_is_visible = True): "Used for both interactive commands and script files" # Clear undo/redo buffer, we don't want prompts and output in there. self.te.setUndoRedoEnabled(False) if len(self.multiline_command) > 0: # multi-line command can only be ended with a blank line. if len(command) == 0: command = self.multiline_command + "\n" # execute it now else: self.multiline_command = self.multiline_command + "\n" + command command = "" # skip execution until next time # Add carriage return, so output will appear on subsequent line. # (It would be too late if we waited for plainTextEdit # to process the <Return>) self.te.moveCursor(QTextCursor.End) self.append("") # We consumed the key event, so we have to add the newline. if len(command) > 0: self.prompt = self.regular_prompt try: co = code.compile_command(command, filename='<console>') if co is None: # incomplete command self.multiline_command = command self.prompt = self.more_prompt else: exec co in console_context._context() self.multiline_command = "" except: # Exception e: print_exc() self.multiline_command = "" self.place_new_prompt(end_is_visible) @QtCore.Slot(str) def run_script_file(self, file_name): # print "run_script_file", file_name end_is_visible = self.te.document().lastBlock().isVisible() self.te.moveCursor(QTextCursor.End) self.multiline_command = "" self.prompt = self.regular_prompt self.append_blue("\n[Running script file " + file_name + "]\n") try: execfile(file_name, console_context._context()) except: print_exc() self.recent_files.add_file(file_name) self.place_new_prompt(end_is_visible) @QtCore.Slot() def on_actionRun_script_triggered(self): start_dir = QSettings().value("script_input_dir") file_name = QFileDialog.getOpenFileName( self, "Open cinemol python script file", start_dir, self.tr("Python files(*.py)"))[0] if file_name == "": return self.run_script_file(file_name) QSettings().setValue("script_input_dir", os.path.dirname(file_name)) @QtCore.Slot() def onClipboardDataChanged(self): pass @QtCore.Slot() def onSelectionChanged(self): pass @QtCore.Slot() def onCursorPositionChanged(self): # Don't allow editing outside the editing area. current_cursor = self.te.textCursor() if self.cursor_is_in_editing_region(current_cursor): # This is a good spot. Within the editing area self.latest_good_cursor = current_cursor bReadOnly = False else: bReadOnly = True if bReadOnly != self.te.isReadOnly(): self.te.setReadOnly(bReadOnly) if bReadOnly: self.ui.actionPaste.setEnabled(False) self.ui.actionCut.setEnabled(False) else: # Performance problem with canPaste() method. # self.te.canPaste() # slow ~120 ms # emit pasteAvailable(self.te.canPaste()) # slow # emit pasteAvailable(!QApplication::clipboard().text().isEmpty()) # QApplication::clipboard().text().isEmpty() # slow ~ 120 ms self.ui.actionPaste.setEnabled(True) # whatever...