class QtShellChannelMixin(ChannelQObject): # Emitted when any message is received. message_received = QtCore.Signal(object) # Emitted when a reply has been received for the corresponding request type. execute_reply = QtCore.Signal(object) complete_reply = QtCore.Signal(object) object_info_reply = QtCore.Signal(object) history_reply = QtCore.Signal(object) #--------------------------------------------------------------------------- # 'ShellChannel' interface #--------------------------------------------------------------------------- def call_handlers(self, msg): """ Reimplemented to emit signals instead of making callbacks. """ # Emit the generic signal. self.message_received.emit(msg) # Emit signals for specialized message types. msg_type = msg['header']['msg_type'] signal = getattr(self, msg_type, None) if signal: signal.emit(msg)
def _get_image_tag(self, match, path=None, format="png"): """ Return (X)HTML mark-up for the image-tag given by match. Parameters ---------- match : re.SRE_Match A match to an HTML image tag as exported by Qt, with match.group("Name") containing the matched image ID. path : string|None, optional [default None] If not None, specifies a path to which supporting files may be written (e.g., for linked images). If None, all images are to be included inline. format : "png"|"svg", optional [default "png"] Format for returned or referenced images. """ if format == "png": try: image = self._get_image(match.group("name")) except KeyError: return "<b>Couldn't find image %s</b>" % match.group("name") if path is not None: if not os.path.exists(path): os.mkdir(path) relpath = os.path.basename(path) if image.save("%s/qt_img%s.png" % (path, match.group("name")), "PNG"): return '<img src="%s/qt_img%s.png">' % ( relpath, match.group("name")) else: return "<b>Couldn't save image!</b>" else: ba = QtCore.QByteArray() buffer_ = QtCore.QBuffer(ba) buffer_.open(QtCore.QIODevice.WriteOnly) image.save(buffer_, "PNG") buffer_.close() return '<img src="data:image/png;base64,\n%s\n" />' % (re.sub( r'(.{60})', r'\1\n', str(ba.toBase64()))) elif format == "svg": try: svg = str(self._name_to_svg_map[match.group("name")]) except KeyError: return "<b>Couldn't find image %s</b>" % match.group("name") # Not currently checking path, because it's tricky to find a # cross-browser way to embed external SVG images (e.g., via # object or embed tags). # Chop stand-alone header from matplotlib SVG offset = svg.find("<svg") assert (offset > -1) return svg[offset:] else: return '<b>Unrecognized image format</b>'
class QtKernelClientMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})): """ A KernelClient that provides signals and slots. """ # Emitted when the kernel client has started listening. started_channels = QtCore.Signal() # Emitted when the kernel client has stopped listening. stopped_channels = QtCore.Signal() #--------------------------------------------------------------------------- # 'KernelClient' interface #--------------------------------------------------------------------------- #------ Channel management ------------------------------------------------- def start_channels(self, *args, **kw): """ Reimplemented to emit signal. """ super(QtKernelClientMixin, self).start_channels(*args, **kw) self.started_channels.emit() def stop_channels(self): """ Reimplemented to emit signal. """ super(QtKernelClientMixin, self).stop_channels() self.stopped_channels.emit()
class QtKernelClientMixin( MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})): """ A KernelClient that provides signals and slots. """ # Emitted when the kernel client has started listening. started_channels = QtCore.Signal() # Emitted when the kernel client has stopped listening. stopped_channels = QtCore.Signal() # Use Qt-specific channel classes that emit signals. iopub_channel_class = Type(QtIOPubChannelMixin) shell_channel_class = Type(QtShellChannelMixin) stdin_channel_class = Type(QtStdInChannelMixin) hb_channel_class = Type(QtHBChannelMixin) #--------------------------------------------------------------------------- # 'KernelClient' interface #--------------------------------------------------------------------------- #------ Channel management ------------------------------------------------- def start_channels(self, *args, **kw): """ Reimplemented to emit signal. """ super(QtKernelClientMixin, self).start_channels(*args, **kw) self.started_channels.emit() def stop_channels(self): """ Reimplemented to emit signal. """ super(QtKernelClientMixin, self).stop_channels() self.stopped_channels.emit()
def svg_to_image(string, size=None): """ Convert a SVG document to a QImage. Parameters: ----------- string : basestring A Python string containing a SVG document. size : QSize, optional The size of the image that is produced. If not specified, the SVG document's default size is used. Raises: ------- ValueError If an invalid SVG string is provided. Returns: -------- A QImage of format QImage.Format_ARGB32. """ if isinstance(string, unicode_type): string = string.encode('utf-8') renderer = QtSvg.QSvgRenderer(QtCore.QByteArray(string)) if not renderer.isValid(): raise ValueError('Invalid SVG data.') if size is None: size = renderer.defaultSize() image = QtGui.QImage(size, QtGui.QImage.Format_ARGB32) painter = QtGui.QPainter(image) renderer.render(painter) return image
class QtKernelManagerMixin(HasTraits, SuperQObject): """ A KernelClient that provides signals and slots. """ __metaclass__ = MetaQObjectHasTraits kernel_restarted = QtCore.Signal()
def _get_image(self, name): """ Returns the QImage stored as the ImageResource with 'name'. """ document = self._control.document() image = document.resource(QtGui.QTextDocument.ImageResource, QtCore.QUrl(name)) return image
class TviewConsoleWidget(HistoryConsoleWidget): line_input = QtCore.Signal(str) def __init__(self, *args, **kw): super(TviewConsoleWidget, self).__init__(*args, **kw) self._prompt = '>>> ' self.clear() def sizeHint(self): return QtCore.QSize(600, 200) def add_text(self, data): assert data.endswith('\n') or data.endswith('\r') self._append_plain_text(data, before_prompt=True) self._control.moveCursor(QtGui.QTextCursor.End) def _handle_timeout(self): self._append_plain_text('%s\r\n' % time.time(), before_prompt=True) self._control.moveCursor(QtGui.QTextCursor.End) def _is_complete(self, source, interactive): return True def _execute(self, source, hidden): self.line_input.emit(source) self._show_prompt(self._prompt) return True
class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel): # Emitted when any message is received. message_received = QtCore.Signal(object) # Emitted when a reply has been received for the corresponding request # type. execute_reply = QtCore.Signal(object) complete_reply = QtCore.Signal(object) object_info_reply = QtCore.Signal(object) # Emitted when the first reply comes back. first_reply = QtCore.Signal() # Used by the first_reply signal logic to determine if a reply is the # first. _handlers_called = False #--------------------------------------------------------------------------- # 'XReqSocketChannel' interface #--------------------------------------------------------------------------- def call_handlers(self, msg): """ Reimplemented to emit signals instead of making callbacks. """ # Emit the generic signal. self.message_received.emit(msg) # Emit signals for specialized message types. msg_type = msg['msg_type'] signal = getattr(self, msg_type, None) if signal: signal.emit(msg) if not self._handlers_called: self.first_reply.emit() self._handlers_called = True #--------------------------------------------------------------------------- # 'QtXReqSocketChannel' interface #--------------------------------------------------------------------------- def reset_first_reply(self): """ Reset the first_reply signal to fire again on the next reply. """ self._handlers_called = False
def _add_image(self, image): """ Adds the specified QImage to the document and returns a QTextImageFormat that references it. """ document = self._control.document() name = str(image.cacheKey()) document.addResource(QtGui.QTextDocument.ImageResource, QtCore.QUrl(name), image) format = QtGui.QTextImageFormat() format.setName(name) return format
class QtInProcessChannel(SuperQObject, InProcessChannel): # Emitted when the channel is started. started = QtCore.Signal() # Emitted when the channel is stopped. stopped = QtCore.Signal() # Emitted when any message is received. message_received = QtCore.Signal(object) def start(self): """ Reimplemented to emit signal. """ super(QtInProcessChannel, self).start() self.started.emit() def stop(self): """ Reimplemented to emit signal. """ super(QtInProcessChannel, self).stop() self.stopped.emit() def call_handlers_later(self, *args, **kwds): """ Call the message handlers later. """ do_later = lambda: self.call_handlers(*args, **kwds) QtCore.QTimer.singleShot(0, do_later) def call_handlers(self, msg): self.message_received.emit(msg) def process_events(self): """ Process any pending GUI events. """ QtCore.QCoreApplication.instance().processEvents() def flush(self, timeout=1.0): """ Reimplemented to ensure that signals are dispatched immediately. """ super(QtInProcessChannel, self).flush() self.process_events()
class QtHBChannel(SuperQObject, HBChannel): # A longer timeout than the base class time_to_dead = 3.0 # Emitted when the kernel has died. kernel_died = QtCore.Signal(object) def call_handlers(self, since_last_heartbeat): """ Reimplemented to emit signals instead of making callbacks. """ # Emit the generic signal. self.kernel_died.emit(since_last_heartbeat)
def init_signal(self): """allow clean shutdown on sigint""" signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2)) # need a timer, so that QApplication doesn't block until a real # Qt event fires (can require mouse movement) # timer trick from http://stackoverflow.com/q/4938723/938949 timer = QtCore.QTimer() # Let the interpreter run each 200 ms: timer.timeout.connect(lambda: None) timer.start(200) # hold onto ref, so the timer doesn't get cleaned up self._sigint_timer = timer
class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel): # Emitted when any message is received. message_received = QtCore.Signal(object) # Emitted when a message of type 'stream' is received. stream_received = QtCore.Signal(object) # Emitted when a message of type 'pyin' is received. pyin_received = QtCore.Signal(object) # Emitted when a message of type 'pyout' is received. pyout_received = QtCore.Signal(object) # Emitted when a message of type 'pyerr' is received. pyerr_received = QtCore.Signal(object) # Emitted when a message of type 'display_data' is received display_data_received = QtCore.Signal(object) # Emitted when a crash report message is received from the kernel's # last-resort sys.excepthook. crash_received = QtCore.Signal(object) # Emitted when a shutdown is noticed. shutdown_reply_received = QtCore.Signal(object) #--------------------------------------------------------------------------- # 'SubSocketChannel' interface #--------------------------------------------------------------------------- def call_handlers(self, msg): """ Reimplemented to emit signals instead of making callbacks. """ # Emit the generic signal. self.message_received.emit(msg) # Emit signals for specialized message types. msg_type = msg['header']['msg_type'] signal = getattr(self, msg_type + '_received', None) if signal: signal.emit(msg) elif msg_type in ('stdout', 'stderr'): self.stream_received.emit(msg) def flush(self): """ Reimplemented to ensure that signals are dispatched immediately. """ super(QtSubSocketChannel, self).flush() QtCore.QCoreApplication.instance().processEvents()
class QtStdInSocketChannel(SocketChannelQObject, StdInSocketChannel): # Emitted when any message is received. message_received = QtCore.Signal(object) # Emitted when an input request is received. input_requested = QtCore.Signal(object) #--------------------------------------------------------------------------- # 'StdInSocketChannel' interface #--------------------------------------------------------------------------- def call_handlers(self, msg): """ Reimplemented to emit signals instead of making callbacks. """ # Emit the generic signal. self.message_received.emit(msg) # Emit signals for specialized message types. msg_type = msg['header']['msg_type'] if msg_type == 'input_request': self.input_requested.emit(msg)
class ChannelQObject(SuperQObject): # Emitted when the channel is started. started = QtCore.Signal() # Emitted when the channel is stopped. stopped = QtCore.Signal() #--------------------------------------------------------------------------- # Channel interface #--------------------------------------------------------------------------- def start(self): """ Reimplemented to emit signal. """ super(ChannelQObject, self).start() self.started.emit() def stop(self): """ Reimplemented to emit signal. """ super(ChannelQObject, self).stop() self.stopped.emit() #--------------------------------------------------------------------------- # InProcessChannel interface #--------------------------------------------------------------------------- def call_handlers_later(self, *args, **kwds): """ Call the message handlers later. """ do_later = lambda: self.call_handlers(*args, **kwds) QtCore.QTimer.singleShot(0, do_later) def process_events(self): """ Process any pending GUI events. """ QtCore.QCoreApplication.instance().processEvents()
def svg_to_clipboard(string): """ Copy a SVG document to the clipboard. Parameters: ----------- string : basestring A Python string containing a SVG document. """ if isinstance(string, unicode_type): string = string.encode('utf-8') mime_data = QtCore.QMimeData() mime_data.setData('image/svg+xml', string) QtGui.QApplication.clipboard().setMimeData(mime_data)
class QtHBSocketChannel(SocketChannelQObject, HBSocketChannel): # Emitted when the kernel has died. kernel_died = QtCore.Signal(object) #--------------------------------------------------------------------------- # 'HBSocketChannel' interface #--------------------------------------------------------------------------- def call_handlers(self, since_last_heartbeat): """ Reimplemented to emit signals instead of making callbacks. """ # Emit the generic signal. self.kernel_died.emit(since_last_heartbeat)
class SocketChannelQObject(SuperQObject): # Emitted when the channel is started. started = QtCore.Signal() # Emitted when the channel is stopped. stopped = QtCore.Signal() #--------------------------------------------------------------------------- # 'ZMQSocketChannel' interface #--------------------------------------------------------------------------- def start(self): """ Reimplemented to emit signal. """ super(SocketChannelQObject, self).start() self.started.emit() def stop(self): """ Reimplemented to emit signal. """ super(SocketChannelQObject, self).stop() self.stopped.emit()
def test_link_handling(self): noKeys = QtCore.Qt noButton = QtCore.Qt.MouseButton(0) noButtons = QtCore.Qt.MouseButtons(0) noModifiers = QtCore.Qt.KeyboardModifiers(0) MouseMove = QtCore.QEvent.MouseMove QMouseEvent = QtGui.QMouseEvent w = ConsoleWidget() cursor = w._get_prompt_cursor() w._insert_html(cursor, '<a href="http://python.org">written in</a>') obj = w._control tip = QtGui.QToolTip self.assertEqual(tip.text(), u'') # should be somewhere else elsewhereEvent = QMouseEvent(MouseMove, QtCore.QPoint(50, 50), noButton, noButtons, noModifiers) w.eventFilter(obj, elsewhereEvent) self.assertEqual(tip.isVisible(), False) self.assertEqual(tip.text(), u'') #self.assertEqual(tip.text(), u'') # should be over text overTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1, 5), noButton, noButtons, noModifiers) w.eventFilter(obj, overTextEvent) self.assertEqual(tip.isVisible(), True) self.assertEqual(tip.text(), "http://python.org") # should still be over text stillOverTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1, 5), noButton, noButtons, noModifiers) w.eventFilter(obj, stillOverTextEvent) self.assertEqual(tip.isVisible(), True) self.assertEqual(tip.text(), "http://python.org")
class QtZMQSocketChannel(ThreadedZMQSocketChannel, SuperQObject): """A ZMQ socket emitting a Qt signal when a message is received.""" message_received = QtCore.Signal(object) def process_events(self): """ Process any pending GUI events. """ QtCore.QCoreApplication.instance().processEvents() def call_handlers(self, msg): """This method is called in the ioloop thread when a message arrives. It is important to remember that this method is called in the thread so that some logic must be done to ensure that the application level handlers are called in the application thread. """ # Emit the generic signal. self.message_received.emit(msg)
def closeEvent(self, event): """ Forward the close event to every tabs contained by the windows """ if self.tab_widget.count() == 0: # no tabs, just close event.accept() return # Do Not loop on the widget count as it change while closing title = self.window().windowTitle() cancel = QtGui.QMessageBox.Cancel okay = QtGui.QMessageBox.Ok accept_role = QtGui.QMessageBox.AcceptRole if self.confirm_exit: if self.tab_widget.count() > 1: msg = "Close all tabs, stop all kernels, and Quit?" else: msg = "Close console, stop kernel, and Quit?" info = "Kernels not started here (e.g. notebooks) will be left alone." closeall = QtGui.QPushButton("&Quit", self) closeall.setShortcut('Q') box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg) box.setInformativeText(info) box.addButton(cancel) box.addButton(closeall, QtGui.QMessageBox.YesRole) box.setDefaultButton(closeall) box.setEscapeButton(cancel) pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64))) box.setIconPixmap(pixmap) reply = box.exec_() else: reply = okay if reply == cancel: event.ignore() return if reply == okay or reply == accept_role: while self.tab_widget.count() >= 1: # prevent further confirmations: widget = self.active_frontend widget._confirm_exit = False self.close_tab(widget) event.accept()
def __init__(self, text_edit): """ Create a call tip manager that is attached to the specified Qt text edit widget. """ assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) super(CallTipWidget, self).__init__(None, QtCore.Qt.ToolTip) self._hide_timer = QtCore.QBasicTimer() self._text_edit = text_edit self.setFont(text_edit.document().defaultFont()) self.setForegroundRole(QtGui.QPalette.ToolTipText) self.setBackgroundRole(QtGui.QPalette.ToolTipBase) self.setPalette(QtGui.QToolTip.palette()) self.setAlignment(QtCore.Qt.AlignLeft) self.setIndent(1) self.setFrameStyle(QtGui.QFrame.NoFrame) self.setMargin(1 + self.style().pixelMetric( QtGui.QStyle.PM_ToolTipLabelFrameWidth, None, self)) self.setWindowOpacity(self.style().styleHint( QtGui.QStyle.SH_ToolTipLabel_Opacity, None, self, None) / 255.0)
def close_tab(self, current_tab): """ Called when you need to try to close a tab. It takes the number of the tab to be closed as argument, or a reference to the widget inside this tab """ # let's be sure "tab" and "closing widget" are respectively the index # of the tab to close and a reference to the frontend to close if type(current_tab) is not int: current_tab = self.tab_widget.indexOf(current_tab) closing_widget = self.tab_widget.widget(current_tab) # when trying to be closed, widget might re-send a request to be # closed again, but will be deleted when event will be processed. So # need to check that widget still exists and skip if not. One example # of this is when 'exit' is sent in a slave tab. 'exit' will be # re-sent by this function on the master widget, which ask all slave # widgets to exit if closing_widget == None: return #get a list of all slave widgets on the same kernel. slave_tabs = self.find_slave_widgets(closing_widget) keepkernel = None #Use the prompt by default if hasattr(closing_widget, '_keep_kernel_on_exit'): #set by exit magic keepkernel = closing_widget._keep_kernel_on_exit # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None) # we set local slave tabs._hidden to True to avoid prompting for kernel # restart when they get the signal. and then "forward" the 'exit' # to the main window if keepkernel is not None: for tab in slave_tabs: tab._hidden = True if closing_widget in slave_tabs: try: self.find_master_tab(closing_widget).execute('exit') except AttributeError: self.log.info( "Master already closed or not local, closing only current tab" ) self.tab_widget.removeTab(current_tab) self.update_tab_bar_visibility() return kernel_manager = closing_widget.kernel_manager if keepkernel is None and not closing_widget._confirm_exit: # don't prompt, just terminate the kernel if we own it # or leave it alone if we don't keepkernel = closing_widget._existing if keepkernel is None: #show prompt if kernel_manager and kernel_manager.channels_running: title = self.window().windowTitle() cancel = QtGui.QMessageBox.Cancel okay = QtGui.QMessageBox.Ok if closing_widget._may_close: msg = "You are closing the tab : " + '"' + self.tab_widget.tabText( current_tab) + '"' info = "Would you like to quit the Kernel and close all attached Consoles as well?" justthis = QtGui.QPushButton("&No, just this Tab", self) justthis.setShortcut('N') closeall = QtGui.QPushButton("&Yes, close all", self) closeall.setShortcut('Y') # allow ctrl-d ctrl-d exit, like in terminal closeall.setShortcut('Ctrl+D') box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg) box.setInformativeText(info) box.addButton(cancel) box.addButton(justthis, QtGui.QMessageBox.NoRole) box.addButton(closeall, QtGui.QMessageBox.YesRole) box.setDefaultButton(closeall) box.setEscapeButton(cancel) pixmap = QtGui.QPixmap( self._app.icon.pixmap(QtCore.QSize(64, 64))) box.setIconPixmap(pixmap) reply = box.exec_() if reply == 1: # close All for slave in slave_tabs: background(slave.kernel_manager.stop_channels) self.tab_widget.removeTab( self.tab_widget.indexOf(slave)) closing_widget.execute("exit") self.tab_widget.removeTab(current_tab) background(kernel_manager.stop_channels) elif reply == 0: # close Console if not closing_widget._existing: # Have kernel: don't quit, just close the tab closing_widget.execute("exit True") self.tab_widget.removeTab(current_tab) background(kernel_manager.stop_channels) else: reply = QtGui.QMessageBox.question( self, title, "Are you sure you want to close this Console?" + "\nThe Kernel and other Consoles will remain active.", okay | cancel, defaultButton=okay) if reply == okay: self.tab_widget.removeTab(current_tab) elif keepkernel: #close console but leave kernel running (no prompt) self.tab_widget.removeTab(current_tab) background(kernel_manager.stop_channels) else: #close console and kernel (no prompt) self.tab_widget.removeTab(current_tab) if kernel_manager and kernel_manager.channels_running: for slave in slave_tabs: background(slave.kernel_manager.stop_channels) self.tab_widget.removeTab(self.tab_widget.indexOf(slave)) kernel_manager.shutdown_kernel() background(kernel_manager.stop_channels) self.update_tab_bar_visibility()
class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ A Qt frontend for a generic Python kernel. """ # An option and corresponding signal for overriding the default kernel # interrupt behavior. custom_interrupt = Bool(False) custom_interrupt_requested = QtCore.Signal() # An option and corresponding signals for overriding the default kernel # restart behavior. custom_restart = Bool(False) custom_restart_kernel_died = QtCore.Signal(float) custom_restart_requested = QtCore.Signal() # Emitted when a user visible 'execute_request' has been submitted to the # kernel from the FrontendWidget. Contains the code to be executed. executing = QtCore.Signal(object) # Emitted when a user-visible 'execute_reply' has been received from the # kernel and processed by the FrontendWidget. Contains the response message. executed = QtCore.Signal(object) # Emitted when an exit request has been received from the kernel. exit_requested = QtCore.Signal() # Protected class variables. _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos']) _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos']) _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind']) _input_splitter_class = InputSplitter _local_kernel = False #--------------------------------------------------------------------------- # 'object' interface #--------------------------------------------------------------------------- def __init__(self, *args, **kw): super(FrontendWidget, self).__init__(*args, **kw) # FrontendWidget protected variables. self._bracket_matcher = BracketMatcher(self._control) self._call_tip_widget = CallTipWidget(self._control) self._completion_lexer = CompletionLexer(PythonLexer()) self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None) self._hidden = False self._highlighter = FrontendHighlighter(self) self._input_splitter = self._input_splitter_class(input_mode='cell') self._kernel_manager = None self._request_info = {} # Configure the ConsoleWidget. self.tab_width = 4 self._set_continuation_prompt('... ') # Configure the CallTipWidget. self._call_tip_widget.setFont(self.font) self.font_changed.connect(self._call_tip_widget.setFont) # Configure actions. action = self._copy_raw_action key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C action.setEnabled(False) action.setShortcut(QtGui.QKeySequence(key)) action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) action.triggered.connect(self.copy_raw) self.copy_available.connect(action.setEnabled) self.addAction(action) # Connect signal handlers. document = self._control.document() document.contentsChange.connect(self._document_contents_change) # Set flag for whether we are connected via localhost. self._local_kernel = kw.get('local_kernel', FrontendWidget._local_kernel) #--------------------------------------------------------------------------- # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- def copy(self): """ Copy the currently selected text to the clipboard, removing prompts. """ text = self._control.textCursor().selection().toPlainText() if text: lines = map(transform_classic_prompt, text.splitlines()) text = '\n'.join(lines) QtGui.QApplication.clipboard().setText(text) #--------------------------------------------------------------------------- # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- def _is_complete(self, source, interactive): """ Returns whether 'source' can be completely processed and a new prompt created. When triggered by an Enter/Return key press, 'interactive' is True; otherwise, it is False. """ complete = self._input_splitter.push(source) if interactive: complete = not self._input_splitter.push_accepts_more() return complete def _execute(self, source, hidden): """ Execute 'source'. If 'hidden', do not show any output. See parent class :meth:`execute` docstring for full details. """ msg_id = self.kernel_manager.xreq_channel.execute(source, hidden) self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user') self._hidden = hidden if not hidden: self.executing.emit(source) def _prompt_started_hook(self): """ Called immediately after a new prompt is displayed. """ if not self._reading: self._highlighter.highlighting_on = True def _prompt_finished_hook(self): """ Called immediately after a prompt is finished, i.e. when some input will be processed and a new prompt displayed. """ # Flush all state from the input splitter so the next round of # reading input starts with a clean buffer. self._input_splitter.reset() if not self._reading: self._highlighter.highlighting_on = False def _tab_pressed(self): """ Called when the tab key is pressed. Returns whether to continue processing the event. """ # Perform tab completion if: # 1) The cursor is in the input buffer. # 2) There is a non-whitespace character before the cursor. text = self._get_input_buffer_cursor_line() if text is None: return False complete = bool(text[:self._get_input_buffer_cursor_column()].strip()) if complete: self._complete() return not complete #--------------------------------------------------------------------------- # 'ConsoleWidget' protected interface #--------------------------------------------------------------------------- def _context_menu_make(self, pos): """ Reimplemented to add an action for raw copy. """ menu = super(FrontendWidget, self)._context_menu_make(pos) for before_action in menu.actions(): if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \ QtGui.QKeySequence.ExactMatch: menu.insertAction(before_action, self._copy_raw_action) break return menu def _event_filter_console_keypress(self, event): """ Reimplemented for execution interruption and smart backspace. """ key = event.key() if self._control_key_down(event.modifiers(), include_command=False): if key == QtCore.Qt.Key_C and self._executing: self.interrupt_kernel() return True elif key == QtCore.Qt.Key_Period: message = 'Are you sure you want to restart the kernel?' self.restart_kernel(message, now=False) return True elif not event.modifiers() & QtCore.Qt.AltModifier: # Smart backspace: remove four characters in one backspace if: # 1) everything left of the cursor is whitespace # 2) the four characters immediately left of the cursor are spaces if key == QtCore.Qt.Key_Backspace: col = self._get_input_buffer_cursor_column() cursor = self._control.textCursor() if col > 3 and not cursor.hasSelection(): text = self._get_input_buffer_cursor_line()[:col] if text.endswith(' ') and not text.strip(): cursor.movePosition(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor, 4) cursor.removeSelectedText() return True return super(FrontendWidget, self)._event_filter_console_keypress(event) def _insert_continuation_prompt(self, cursor): """ Reimplemented for auto-indentation. """ super(FrontendWidget, self)._insert_continuation_prompt(cursor) cursor.insertText(' ' * self._input_splitter.indent_spaces) #--------------------------------------------------------------------------- # 'BaseFrontendMixin' abstract interface #--------------------------------------------------------------------------- def _handle_complete_reply(self, rep): """ Handle replies for tab completion. """ cursor = self._get_cursor() info = self._request_info.get('complete') if info and info.id == rep['parent_header']['msg_id'] and \ info.pos == cursor.position(): text = '.'.join(self._get_context()) cursor.movePosition(QtGui.QTextCursor.Left, n=len(text)) self._complete_with_items(cursor, rep['content']['matches']) def _handle_execute_reply(self, msg): """ Handles replies for code execution. """ info = self._request_info.get('execute') if info and info.id == msg['parent_header']['msg_id'] and \ info.kind == 'user' and not self._hidden: # Make sure that all output from the SUB channel has been processed # before writing a new prompt. self.kernel_manager.sub_channel.flush() # Reset the ANSI style information to prevent bad text in stdout # from messing up our colors. We're not a true terminal so we're # allowed to do this. if self.ansi_codes: self._ansi_processor.reset_sgr() content = msg['content'] status = content['status'] if status == 'ok': self._process_execute_ok(msg) elif status == 'error': self._process_execute_error(msg) elif status == 'abort': self._process_execute_abort(msg) self._show_interpreter_prompt_for_reply(msg) self.executed.emit(msg) def _handle_input_request(self, msg): """ Handle requests for raw_input. """ if self._hidden: raise RuntimeError( 'Request for raw input during hidden execution.') # Make sure that all output from the SUB channel has been processed # before entering readline mode. self.kernel_manager.sub_channel.flush() def callback(line): self.kernel_manager.rep_channel.input(line) self._readline(msg['content']['prompt'], callback=callback) def _handle_kernel_died(self, since_last_heartbeat): """ Handle the kernel's death by asking if the user wants to restart. """ if self.custom_restart: self.custom_restart_kernel_died.emit(since_last_heartbeat) else: message = 'The kernel heartbeat has been inactive for %.2f ' \ 'seconds. Do you want to restart the kernel? You may ' \ 'first want to check the network connection.' % \ since_last_heartbeat self.restart_kernel(message, now=True) def _handle_object_info_reply(self, rep): """ Handle replies for call tips. """ cursor = self._get_cursor() info = self._request_info.get('call_tip') if info and info.id == rep['parent_header']['msg_id'] and \ info.pos == cursor.position(): # Get the information for a call tip. For now we format the call # line as string, later we can pass False to format_call and # syntax-highlight it ourselves for nicer formatting in the # calltip. if rep['content']['ismagic']: # Don't generate a call-tip for magics. Ideally, we should # generate a tooltip, but not on ( like we do for actual # callables. call_info, doc = None, None else: call_info, doc = call_tip(rep['content'], format_call=True) if call_info or doc: self._call_tip_widget.show_call_info(call_info, doc) def _handle_pyout(self, msg): """ Handle display hook output. """ if not self._hidden and self._is_from_this_session(msg): self._append_plain_text(msg['content']['data']['text/plain'] + '\n') def _handle_stream(self, msg): """ Handle stdout, stderr, and stdin. """ if not self._hidden and self._is_from_this_session(msg): # Most consoles treat tabs as being 8 space characters. Convert tabs # to spaces so that output looks as expected regardless of this # widget's tab width. text = msg['content']['data'].expandtabs(8) self._append_plain_text(text) self._control.moveCursor(QtGui.QTextCursor.End) def _handle_shutdown_reply(self, msg): """ Handle shutdown signal, only if from other console. """ if not self._hidden and not self._is_from_this_session(msg): if self._local_kernel: if not msg['content']['restart']: sys.exit(0) else: # we just got notified of a restart! time.sleep(0.25) # wait 1/4 sec to reset # lest the request for a new prompt # goes to the old kernel self.reset() else: # remote kernel, prompt on Kernel shutdown/reset title = self.window().windowTitle() if not msg['content']['restart']: reply = QtGui.QMessageBox.question( self, title, "Kernel has been shutdown permanently. " "Close the Console?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: sys.exit(0) else: reply = QtGui.QMessageBox.question( self, title, "Kernel has been reset. Clear the Console?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: time.sleep(0.25) # wait 1/4 sec to reset # lest the request for a new prompt # goes to the old kernel self.reset() def _started_channels(self): """ Called when the KernelManager channels have started listening or when the frontend is assigned an already listening KernelManager. """ self.reset() #--------------------------------------------------------------------------- # 'FrontendWidget' public interface #--------------------------------------------------------------------------- def copy_raw(self): """ Copy the currently selected text to the clipboard without attempting to remove prompts or otherwise alter the text. """ self._control.copy() def execute_file(self, path, hidden=False): """ Attempts to execute file with 'path'. If 'hidden', no output is shown. """ self.execute('execfile("%s")' % path, hidden=hidden) def interrupt_kernel(self): """ Attempts to interrupt the running kernel. """ if self.custom_interrupt: self.custom_interrupt_requested.emit() elif self.kernel_manager.has_kernel: self.kernel_manager.interrupt_kernel() else: self._append_plain_text('Kernel process is either remote or ' 'unspecified. Cannot interrupt.\n') def reset(self): """ Resets the widget to its initial state. Similar to ``clear``, but also re-writes the banner and aborts execution if necessary. """ if self._executing: self._executing = False self._request_info['execute'] = None self._reading = False self._highlighter.highlighting_on = False self._control.clear() self._append_plain_text(self._get_banner()) self._show_interpreter_prompt() def restart_kernel(self, message, now=False): """ Attempts to restart the running kernel. """ # FIXME: now should be configurable via a checkbox in the dialog. Right # now at least the heartbeat path sets it to True and the manual restart # to False. But those should just be the pre-selected states of a # checkbox that the user could override if so desired. But I don't know # enough Qt to go implementing the checkbox now. if self.custom_restart: self.custom_restart_requested.emit() elif self.kernel_manager.has_kernel: # Pause the heart beat channel to prevent further warnings. self.kernel_manager.hb_channel.pause() # Prompt the user to restart the kernel. Un-pause the heartbeat if # they decline. (If they accept, the heartbeat will be un-paused # automatically when the kernel is restarted.) buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No result = QtGui.QMessageBox.question(self, 'Restart kernel?', message, buttons) if result == QtGui.QMessageBox.Yes: try: self.kernel_manager.restart_kernel(now=now) except RuntimeError: self._append_plain_text('Kernel started externally. ' 'Cannot restart.\n') else: self.reset() else: self.kernel_manager.hb_channel.unpause() else: self._append_plain_text('Kernel process is either remote or ' 'unspecified. Cannot restart.\n') #--------------------------------------------------------------------------- # 'FrontendWidget' protected interface #--------------------------------------------------------------------------- def _call_tip(self): """ Shows a call tip, if appropriate, at the current cursor location. """ # Decide if it makes sense to show a call tip cursor = self._get_cursor() cursor.movePosition(QtGui.QTextCursor.Left) if cursor.document().characterAt(cursor.position()) != '(': return False context = self._get_context(cursor) if not context: return False # Send the metadata request to the kernel name = '.'.join(context) msg_id = self.kernel_manager.xreq_channel.object_info(name) pos = self._get_cursor().position() self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos) return True def _complete(self): """ Performs completion at the current cursor location. """ context = self._get_context() if context: # Send the completion request to the kernel msg_id = self.kernel_manager.xreq_channel.complete( '.'.join(context), # text self._get_input_buffer_cursor_line(), # line self._get_input_buffer_cursor_column(), # cursor_pos self.input_buffer) # block pos = self._get_cursor().position() info = self._CompletionRequest(msg_id, pos) self._request_info['complete'] = info def _get_banner(self): """ Gets a banner to display at the beginning of a session. """ banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \ '"license" for more information.' return banner % (sys.version, sys.platform) def _get_context(self, cursor=None): """ Gets the context for the specified cursor (or the current cursor if none is specified). """ if cursor is None: cursor = self._get_cursor() cursor.movePosition(QtGui.QTextCursor.StartOfBlock, QtGui.QTextCursor.KeepAnchor) text = cursor.selection().toPlainText() return self._completion_lexer.get_context(text) def _process_execute_abort(self, msg): """ Process a reply for an aborted execution request. """ self._append_plain_text("ERROR: execution aborted\n") def _process_execute_error(self, msg): """ Process a reply for an execution request that resulted in an error. """ content = msg['content'] # If a SystemExit is passed along, this means exit() was called - also # all the ipython %exit magic syntax of '-k' to be used to keep # the kernel running if content['ename'] == 'SystemExit': keepkernel = content['evalue'] == '-k' or content[ 'evalue'] == 'True' self._keep_kernel_on_exit = keepkernel self.exit_requested.emit() else: traceback = ''.join(content['traceback']) self._append_plain_text(traceback) def _process_execute_ok(self, msg): """ Process a reply for a successful execution equest. """ payload = msg['content']['payload'] for item in payload: if not self._process_execute_payload(item): warning = 'Warning: received unknown payload of type %s' print(warning % repr(item['source'])) def _process_execute_payload(self, item): """ Process a single payload item from the list of payload items in an execution reply. Returns whether the payload was handled. """ # The basic FrontendWidget doesn't handle payloads, as they are a # mechanism for going beyond the standard Python interpreter model. return False def _show_interpreter_prompt(self): """ Shows a prompt for the interpreter. """ self._show_prompt('>>> ') def _show_interpreter_prompt_for_reply(self, msg): """ Shows a prompt for the interpreter given an 'execute_reply' message. """ self._show_interpreter_prompt() #------ Signal handlers ---------------------------------------------------- def _document_contents_change(self, position, removed, added): """ Called whenever the document's content changes. Display a call tip if appropriate. """ # Calculate where the cursor should be *after* the change: position += added document = self._control.document() if position == self._get_cursor().position(): self._call_tip()
class IPythonWidget(FrontendWidget): """ A FrontendWidget for an IPython kernel. """ # If set, the 'custom_edit_requested(str, int)' signal will be emitted when # an editor is needed for a file. This overrides 'editor' and 'editor_line' # settings. custom_edit = Bool(False) custom_edit_requested = QtCore.Signal(object, object) # A command for invoking a system text editor. If the string contains a # {filename} format specifier, it will be used. Otherwise, the filename will # be appended to the end the command. editor = Unicode('default', config=True) # The editor command to use when a specific line number is requested. The # string should contain two format specifiers: {line} and {filename}. If # this parameter is not specified, the line number option to the %edit magic # will be ignored. editor_line = Unicode(config=True) # A CSS stylesheet. The stylesheet can contain classes for: # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter) # 3. IPython: .error, .in-prompt, .out-prompt, etc style_sheet = Unicode(config=True) # If not empty, use this Pygments style for syntax highlighting. Otherwise, # the style sheet is queried for Pygments style information. syntax_style = Str(config=True) # Prompts. in_prompt = Str(default_in_prompt, config=True) out_prompt = Str(default_out_prompt, config=True) input_sep = Str(default_input_sep, config=True) output_sep = Str(default_output_sep, config=True) output_sep2 = Str(default_output_sep2, config=True) # FrontendWidget protected class variables. _input_splitter_class = IPythonInputSplitter # IPythonWidget protected class variables. _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number']) _payload_source_edit = zmq_shell_source + '.edit_magic' _payload_source_exit = zmq_shell_source + '.ask_exit' _payload_source_loadpy = zmq_shell_source + '.magic_loadpy' _payload_source_page = 'IPython.zmq.page.page' #--------------------------------------------------------------------------- # 'object' interface #--------------------------------------------------------------------------- def __init__(self, *args, **kw): super(IPythonWidget, self).__init__(*args, **kw) # IPythonWidget protected variables. self._code_to_load = None self._payload_handlers = { self._payload_source_edit: self._handle_payload_edit, self._payload_source_exit: self._handle_payload_exit, self._payload_source_page: self._handle_payload_page, self._payload_source_loadpy: self._handle_payload_loadpy } self._previous_prompt_obj = None self._keep_kernel_on_exit = None # Initialize widget styling. if self.style_sheet: self._style_sheet_changed() self._syntax_style_changed() else: self.set_default_style() #--------------------------------------------------------------------------- # 'BaseFrontendMixin' abstract interface #--------------------------------------------------------------------------- def _handle_complete_reply(self, rep): """ Reimplemented to support IPython's improved completion machinery. """ cursor = self._get_cursor() info = self._request_info.get('complete') if info and info.id == rep['parent_header']['msg_id'] and \ info.pos == cursor.position(): matches = rep['content']['matches'] text = rep['content']['matched_text'] offset = len(text) # Clean up matches with period and path separators if the matched # text has not been transformed. This is done by truncating all # but the last component and then suitably decreasing the offset # between the current cursor position and the start of completion. if len(matches) > 1 and matches[0][:offset] == text: parts = re.split(r'[./\\]', text) sep_count = len(parts) - 1 if sep_count: chop_length = sum(map(len, parts[:sep_count])) + sep_count matches = [match[chop_length:] for match in matches] offset -= chop_length # Move the cursor to the start of the match and complete. cursor.movePosition(QtGui.QTextCursor.Left, n=offset) self._complete_with_items(cursor, matches) def _handle_execute_reply(self, msg): """ Reimplemented to support prompt requests. """ info = self._request_info.get('execute') if info and info.id == msg['parent_header']['msg_id']: if info.kind == 'prompt': number = msg['content']['execution_count'] + 1 self._show_interpreter_prompt(number) else: super(IPythonWidget, self)._handle_execute_reply(msg) def _handle_history_tail_reply(self, msg): """ Implemented to handle history tail replies, which are only supported by the IPython kernel. """ history_items = msg['content']['history'] items = [line.rstrip() for _, _, line in history_items] self._set_history(items) def _handle_pyout(self, msg): """ Reimplemented for IPython-style "display hook". """ if not self._hidden and self._is_from_this_session(msg): content = msg['content'] prompt_number = content['execution_count'] data = content['data'] if data.has_key('text/html'): self._append_plain_text(self.output_sep) self._append_html(self._make_out_prompt(prompt_number)) html = data['text/html'] self._append_plain_text('\n') self._append_html(html + self.output_sep2) elif data.has_key('text/plain'): self._append_plain_text(self.output_sep) self._append_html(self._make_out_prompt(prompt_number)) text = data['text/plain'] self._append_plain_text(text + self.output_sep2) def _handle_display_data(self, msg): """ The base handler for the ``display_data`` message. """ # For now, we don't display data from other frontends, but we # eventually will as this allows all frontends to monitor the display # data. But we need to figure out how to handle this in the GUI. if not self._hidden and self._is_from_this_session(msg): source = msg['content']['source'] data = msg['content']['data'] metadata = msg['content']['metadata'] # In the regular IPythonWidget, we simply print the plain text # representation. if data.has_key('text/html'): html = data['text/html'] self._append_html(html) elif data.has_key('text/plain'): text = data['text/plain'] self._append_plain_text(text) # This newline seems to be needed for text and html output. self._append_plain_text(u'\n') def _started_channels(self): """ Reimplemented to make a history request. """ super(IPythonWidget, self)._started_channels() self.kernel_manager.xreq_channel.history_tail(1000) #--------------------------------------------------------------------------- # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- def copy(self): """ Copy the currently selected text to the clipboard, removing prompts if possible. """ text = self._control.textCursor().selection().toPlainText() if text: lines = map(transform_ipy_prompt, text.splitlines()) text = '\n'.join(lines) QtGui.QApplication.clipboard().setText(text) #--------------------------------------------------------------------------- # 'FrontendWidget' public interface #--------------------------------------------------------------------------- def execute_file(self, path, hidden=False): """ Reimplemented to use the 'run' magic. """ # Use forward slashes on Windows to avoid escaping each separator. if sys.platform == 'win32': path = os.path.normpath(path).replace('\\', '/') self.execute('%%run %s' % path, hidden=hidden) #--------------------------------------------------------------------------- # 'FrontendWidget' protected interface #--------------------------------------------------------------------------- def _complete(self): """ Reimplemented to support IPython's improved completion machinery. """ # We let the kernel split the input line, so we *always* send an empty # text field. Readline-based frontends do get a real text field which # they can use. text = '' # Send the completion request to the kernel msg_id = self.kernel_manager.xreq_channel.complete( text, # text self._get_input_buffer_cursor_line(), # line self._get_input_buffer_cursor_column(), # cursor_pos self.input_buffer) # block pos = self._get_cursor().position() info = self._CompletionRequest(msg_id, pos) self._request_info['complete'] = info def _get_banner(self): """ Reimplemented to return IPython's default banner. """ return default_gui_banner def _process_execute_error(self, msg): """ Reimplemented for IPython-style traceback formatting. """ content = msg['content'] traceback = '\n'.join(content['traceback']) + '\n' if False: # FIXME: For now, tracebacks come as plain text, so we can't use # the html renderer yet. Once we refactor ultratb to produce # properly styled tracebacks, this branch should be the default traceback = traceback.replace(' ', ' ') traceback = traceback.replace('\n', '<br/>') ename = content['ename'] ename_styled = '<span class="error">%s</span>' % ename traceback = traceback.replace(ename, ename_styled) self._append_html(traceback) else: # This is the fallback for now, using plain text with ansi escapes self._append_plain_text(traceback) def _process_execute_payload(self, item): """ Reimplemented to dispatch payloads to handler methods. """ handler = self._payload_handlers.get(item['source']) if handler is None: # We have no handler for this type of payload, simply ignore it return False else: handler(item) return True def _show_interpreter_prompt(self, number=None): """ Reimplemented for IPython-style prompts. """ # If a number was not specified, make a prompt number request. if number is None: msg_id = self.kernel_manager.xreq_channel.execute('', silent=True) info = self._ExecutionRequest(msg_id, 'prompt') self._request_info['execute'] = info return # Show a new prompt and save information about it so that it can be # updated later if the prompt number turns out to be wrong. self._prompt_sep = self.input_sep self._show_prompt(self._make_in_prompt(number), html=True) block = self._control.document().lastBlock() length = len(self._prompt) self._previous_prompt_obj = self._PromptBlock(block, length, number) # Update continuation prompt to reflect (possibly) new prompt length. self._set_continuation_prompt(self._make_continuation_prompt( self._prompt), html=True) # Load code from the %loadpy magic, if necessary. if self._code_to_load is not None: self.input_buffer = dedent(self._code_to_load.rstrip()) self._code_to_load = None def _show_interpreter_prompt_for_reply(self, msg): """ Reimplemented for IPython-style prompts. """ # Update the old prompt number if necessary. content = msg['content'] previous_prompt_number = content['execution_count'] if self._previous_prompt_obj and \ self._previous_prompt_obj.number != previous_prompt_number: block = self._previous_prompt_obj.block # Make sure the prompt block has not been erased. if block.isValid() and block.text(): # Remove the old prompt and insert a new prompt. cursor = QtGui.QTextCursor(block) cursor.movePosition(QtGui.QTextCursor.Right, QtGui.QTextCursor.KeepAnchor, self._previous_prompt_obj.length) prompt = self._make_in_prompt(previous_prompt_number) self._prompt = self._insert_html_fetching_plain_text( cursor, prompt) # When the HTML is inserted, Qt blows away the syntax # highlighting for the line, so we need to rehighlight it. self._highlighter.rehighlightBlock(cursor.block()) self._previous_prompt_obj = None # Show a new prompt with the kernel's estimated prompt number. self._show_interpreter_prompt(previous_prompt_number + 1) #--------------------------------------------------------------------------- # 'IPythonWidget' interface #--------------------------------------------------------------------------- def set_default_style(self, colors='lightbg'): """ Sets the widget style to the class defaults. Parameters: ----------- colors : str, optional (default lightbg) Whether to use the default IPython light background or dark background or B&W style. """ colors = colors.lower() if colors == 'lightbg': self.style_sheet = default_light_style_sheet self.syntax_style = default_light_syntax_style elif colors == 'linux': self.style_sheet = default_dark_style_sheet self.syntax_style = default_dark_syntax_style elif colors == 'nocolor': self.style_sheet = default_bw_style_sheet self.syntax_style = default_bw_syntax_style else: raise KeyError("No such color scheme: %s" % colors) #--------------------------------------------------------------------------- # 'IPythonWidget' protected interface #--------------------------------------------------------------------------- def _edit(self, filename, line=None): """ Opens a Python script for editing. Parameters: ----------- filename : str A path to a local system file. line : int, optional A line of interest in the file. """ if self.custom_edit: self.custom_edit_requested.emit(filename, line) elif self.editor == 'default': self._append_plain_text('No default editor available.\n') else: try: filename = '"%s"' % filename if line and self.editor_line: command = self.editor_line.format(filename=filename, line=line) else: try: command = self.editor.format() except KeyError: command = self.editor.format(filename=filename) else: command += ' ' + filename except KeyError: self._append_plain_text('Invalid editor command.\n') else: try: Popen(command, shell=True) except OSError: msg = 'Opening editor with command "%s" failed.\n' self._append_plain_text(msg % command) def _make_in_prompt(self, number): """ Given a prompt number, returns an HTML In prompt. """ body = self.in_prompt % number return '<span class="in-prompt">%s</span>' % body def _make_continuation_prompt(self, prompt): """ Given a plain text version of an In prompt, returns an HTML continuation prompt. """ end_chars = '...: ' space_count = len(prompt.lstrip('\n')) - len(end_chars) body = ' ' * space_count + end_chars return '<span class="in-prompt">%s</span>' % body def _make_out_prompt(self, number): """ Given a prompt number, returns an HTML Out prompt. """ body = self.out_prompt % number return '<span class="out-prompt">%s</span>' % body #------ Payload handlers -------------------------------------------------- # Payload handlers with a generic interface: each takes the opaque payload # dict, unpacks it and calls the underlying functions with the necessary # arguments. def _handle_payload_edit(self, item): self._edit(item['filename'], item['line_number']) def _handle_payload_exit(self, item): self._keep_kernel_on_exit = item['keepkernel'] self.exit_requested.emit() def _handle_payload_loadpy(self, item): # Simple save the text of the .py file for later. The text is written # to the buffer when _prompt_started_hook is called. self._code_to_load = item['text'] def _handle_payload_page(self, item): # Since the plain text widget supports only a very small subset of HTML # and we have no control over the HTML source, we only page HTML # payloads in the rich text widget. if item['html'] and self.kind == 'rich': self._page(item['html'], html=True) else: self._page(item['text'], html=False) #------ Trait change handlers -------------------------------------------- def _style_sheet_changed(self): """ Set the style sheets of the underlying widgets. """ self.setStyleSheet(self.style_sheet) self._control.document().setDefaultStyleSheet(self.style_sheet) if self._page_control: self._page_control.document().setDefaultStyleSheet( self.style_sheet) bg_color = self._control.palette().window().color() self._ansi_processor.set_background_color(bg_color) def _syntax_style_changed(self): """ Set the style for the syntax highlighter. """ if self.syntax_style: self._highlighter.set_style(self.syntax_style) else: self._highlighter.set_style_sheet(self.style_sheet)
class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ A Qt frontend for a generic Python kernel. """ # The text to show when the kernel is (re)started. banner = Unicode(config=True) # An option and corresponding signal for overriding the default kernel # interrupt behavior. custom_interrupt = Bool(False) custom_interrupt_requested = QtCore.Signal() # An option and corresponding signals for overriding the default kernel # restart behavior. custom_restart = Bool(False) custom_restart_kernel_died = QtCore.Signal(float) custom_restart_requested = QtCore.Signal() # Whether to automatically show calltips on open-parentheses. enable_calltips = Bool( True, config=True, help="Whether to draw information calltips on open-parentheses.") clear_on_kernel_restart = Bool( True, config=True, help="Whether to clear the console when the kernel is restarted") confirm_restart = Bool( True, config=True, help="Whether to ask for user confirmation when restarting kernel") lexer_class = DottedObjectName(config=True, help="The pygments lexer class to use.") def _lexer_class_changed(self, name, old, new): lexer_class = import_item(new) self.lexer = lexer_class() def _lexer_class_default(self): if py3compat.PY3: return 'pygments.lexers.Python3Lexer' else: return 'pygments.lexers.PythonLexer' lexer = Any() def _lexer_default(self): lexer_class = import_item(self.lexer_class) return lexer_class() # Emitted when a user visible 'execute_request' has been submitted to the # kernel from the FrontendWidget. Contains the code to be executed. executing = QtCore.Signal(object) # Emitted when a user-visible 'execute_reply' has been received from the # kernel and processed by the FrontendWidget. Contains the response message. executed = QtCore.Signal(object) # Emitted when an exit request has been received from the kernel. exit_requested = QtCore.Signal(object) # Protected class variables. _prompt_transformer = IPythonInputSplitter( physical_line_transforms=[classic_prompt()], logical_line_transforms=[], python_line_transforms=[], ) _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos']) _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos']) _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind']) _input_splitter_class = InputSplitter _local_kernel = False _highlighter = Instance(FrontendHighlighter) #--------------------------------------------------------------------------- # 'object' interface #--------------------------------------------------------------------------- def __init__(self, *args, **kw): super(FrontendWidget, self).__init__(*args, **kw) # FIXME: remove this when PySide min version is updated past 1.0.7 # forcefully disable calltips if PySide is < 1.0.7, because they crash if qt.QT_API == qt.QT_API_PYSIDE: import PySide if PySide.__version_info__ < (1, 0, 7): self.log.warn( "PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__) self.enable_calltips = False # FrontendWidget protected variables. self._bracket_matcher = BracketMatcher(self._control) self._call_tip_widget = CallTipWidget(self._control) self._completion_lexer = CompletionLexer(self.lexer) self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None) self._hidden = False self._highlighter = FrontendHighlighter(self, lexer=self.lexer) self._input_splitter = self._input_splitter_class() self._kernel_manager = None self._kernel_client = None self._request_info = {} self._request_info['execute'] = {} self._callback_dict = {} # Configure the ConsoleWidget. self.tab_width = 4 self._set_continuation_prompt('... ') # Configure the CallTipWidget. self._call_tip_widget.setFont(self.font) self.font_changed.connect(self._call_tip_widget.setFont) # Configure actions. action = self._copy_raw_action key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C action.setEnabled(False) action.setShortcut(QtGui.QKeySequence(key)) action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) action.triggered.connect(self.copy_raw) self.copy_available.connect(action.setEnabled) self.addAction(action) # Connect signal handlers. document = self._control.document() document.contentsChange.connect(self._document_contents_change) # Set flag for whether we are connected via localhost. self._local_kernel = kw.get('local_kernel', FrontendWidget._local_kernel) #--------------------------------------------------------------------------- # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- def copy(self): """ Copy the currently selected text to the clipboard, removing prompts. """ if self._page_control is not None and self._page_control.hasFocus(): self._page_control.copy() elif self._control.hasFocus(): text = self._control.textCursor().selection().toPlainText() if text: text = self._prompt_transformer.transform_cell(text) QtGui.QApplication.clipboard().setText(text) else: self.log.debug("frontend widget : unknown copy target") #--------------------------------------------------------------------------- # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- def _is_complete(self, source, interactive): """ Returns whether 'source' can be completely processed and a new prompt created. When triggered by an Enter/Return key press, 'interactive' is True; otherwise, it is False. """ self._input_splitter.reset() complete = self._input_splitter.push(source) if interactive: complete = not self._input_splitter.push_accepts_more() return complete def _execute(self, source, hidden): """ Execute 'source'. If 'hidden', do not show any output. See parent class :meth:`execute` docstring for full details. """ msg_id = self.kernel_client.execute(source, hidden) self._request_info['execute'][msg_id] = self._ExecutionRequest( msg_id, 'user') self._hidden = hidden if not hidden: self.executing.emit(source) def _prompt_started_hook(self): """ Called immediately after a new prompt is displayed. """ if not self._reading: self._highlighter.highlighting_on = True def _prompt_finished_hook(self): """ Called immediately after a prompt is finished, i.e. when some input will be processed and a new prompt displayed. """ # Flush all state from the input splitter so the next round of # reading input starts with a clean buffer. self._input_splitter.reset() if not self._reading: self._highlighter.highlighting_on = False def _tab_pressed(self): """ Called when the tab key is pressed. Returns whether to continue processing the event. """ # Perform tab completion if: # 1) The cursor is in the input buffer. # 2) There is a non-whitespace character before the cursor. text = self._get_input_buffer_cursor_line() if text is None: return False complete = bool(text[:self._get_input_buffer_cursor_column()].strip()) if complete: self._complete() return not complete #--------------------------------------------------------------------------- # 'ConsoleWidget' protected interface #--------------------------------------------------------------------------- def _context_menu_make(self, pos): """ Reimplemented to add an action for raw copy. """ menu = super(FrontendWidget, self)._context_menu_make(pos) for before_action in menu.actions(): if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \ QtGui.QKeySequence.ExactMatch: menu.insertAction(before_action, self._copy_raw_action) break return menu def request_interrupt_kernel(self): if self._executing: self.interrupt_kernel() def request_restart_kernel(self): message = 'Are you sure you want to restart the kernel?' self.restart_kernel(message, now=False) def _event_filter_console_keypress(self, event): """ Reimplemented for execution interruption and smart backspace. """ key = event.key() if self._control_key_down(event.modifiers(), include_command=False): if key == QtCore.Qt.Key_C and self._executing: self.request_interrupt_kernel() return True elif key == QtCore.Qt.Key_Period: self.request_restart_kernel() return True elif not event.modifiers() & QtCore.Qt.AltModifier: # Smart backspace: remove four characters in one backspace if: # 1) everything left of the cursor is whitespace # 2) the four characters immediately left of the cursor are spaces if key == QtCore.Qt.Key_Backspace: col = self._get_input_buffer_cursor_column() cursor = self._control.textCursor() if col > 3 and not cursor.hasSelection(): text = self._get_input_buffer_cursor_line()[:col] if text.endswith(' ') and not text.strip(): cursor.movePosition(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor, 4) cursor.removeSelectedText() return True return super(FrontendWidget, self)._event_filter_console_keypress(event) def _insert_continuation_prompt(self, cursor): """ Reimplemented for auto-indentation. """ super(FrontendWidget, self)._insert_continuation_prompt(cursor) cursor.insertText(' ' * self._input_splitter.indent_spaces) #--------------------------------------------------------------------------- # 'BaseFrontendMixin' abstract interface #--------------------------------------------------------------------------- def _handle_complete_reply(self, rep): """ Handle replies for tab completion. """ self.log.debug("complete: %s", rep.get('content', '')) cursor = self._get_cursor() info = self._request_info.get('complete') if info and info.id == rep['parent_header']['msg_id'] and \ info.pos == cursor.position(): text = '.'.join(self._get_context()) cursor.movePosition(QtGui.QTextCursor.Left, n=len(text)) self._complete_with_items(cursor, rep['content']['matches']) def _silent_exec_callback(self, expr, callback): """Silently execute `expr` in the kernel and call `callback` with reply the `expr` is evaluated silently in the kernel (without) output in the frontend. Call `callback` with the `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument Parameters ---------- expr : string valid string to be executed by the kernel. callback : function function accepting one argument, as a string. The string will be the `repr` of the result of evaluating `expr` The `callback` is called with the `repr()` of the result of `expr` as first argument. To get the object, do `eval()` on the passed value. See Also -------- _handle_exec_callback : private method, deal with calling callback with reply """ # generate uuid, which would be used as an indication of whether or # not the unique request originated from here (can use msg id ?) local_uuid = str(uuid.uuid1()) msg_id = self.kernel_client.execute( '', silent=True, user_expressions={local_uuid: expr}) self._callback_dict[local_uuid] = callback self._request_info['execute'][msg_id] = self._ExecutionRequest( msg_id, 'silent_exec_callback') def _handle_exec_callback(self, msg): """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback`` Parameters ---------- msg : raw message send by the kernel containing an `user_expressions` and having a 'silent_exec_callback' kind. Notes ----- This function will look for a `callback` associated with the corresponding message id. Association has been made by `_silent_exec_callback`. `callback` is then called with the `repr()` of the value of corresponding `user_expressions` as argument. `callback` is then removed from the known list so that any message coming again with the same id won't trigger it. """ user_exp = msg['content'].get('user_expressions') if not user_exp: return for expression in user_exp: if expression in self._callback_dict: self._callback_dict.pop(expression)(user_exp[expression]) def _handle_execute_reply(self, msg): """ Handles replies for code execution. """ self.log.debug("execute: %s", msg.get('content', '')) msg_id = msg['parent_header']['msg_id'] info = self._request_info['execute'].get(msg_id) # unset reading flag, because if execute finished, raw_input can't # still be pending. self._reading = False if info and info.kind == 'user' and not self._hidden: # Make sure that all output from the SUB channel has been processed # before writing a new prompt. self.kernel_client.iopub_channel.flush() # Reset the ANSI style information to prevent bad text in stdout # from messing up our colors. We're not a true terminal so we're # allowed to do this. if self.ansi_codes: self._ansi_processor.reset_sgr() content = msg['content'] status = content['status'] if status == 'ok': self._process_execute_ok(msg) elif status == 'error': self._process_execute_error(msg) elif status == 'aborted': self._process_execute_abort(msg) self._show_interpreter_prompt_for_reply(msg) self.executed.emit(msg) self._request_info['execute'].pop(msg_id) elif info and info.kind == 'silent_exec_callback' and not self._hidden: self._handle_exec_callback(msg) self._request_info['execute'].pop(msg_id) else: super(FrontendWidget, self)._handle_execute_reply(msg) def _handle_input_request(self, msg): """ Handle requests for raw_input. """ self.log.debug("input: %s", msg.get('content', '')) if self._hidden: raise RuntimeError( 'Request for raw input during hidden execution.') # Make sure that all output from the SUB channel has been processed # before entering readline mode. self.kernel_client.iopub_channel.flush() def callback(line): self.kernel_client.stdin_channel.input(line) if self._reading: self.log.debug( "Got second input request, assuming first was interrupted.") self._reading = False self._readline(msg['content']['prompt'], callback=callback) def _kernel_restarted_message(self, died=True): msg = "Kernel died, restarting" if died else "Kernel restarting" self._append_html("<br>%s<hr><br>" % msg, before_prompt=False) def _handle_kernel_died(self, since_last_heartbeat): """Handle the kernel's death (if we do not own the kernel). """ self.log.warn("kernel died: %s", since_last_heartbeat) if self.custom_restart: self.custom_restart_kernel_died.emit(since_last_heartbeat) else: self._kernel_restarted_message(died=True) self.reset() def _handle_kernel_restarted(self, died=True): """Notice that the autorestarter restarted the kernel. There's nothing to do but show a message. """ self.log.warn("kernel restarted") self._kernel_restarted_message(died=died) self.reset() def _handle_object_info_reply(self, rep): """ Handle replies for call tips. """ self.log.debug("oinfo: %s", rep.get('content', '')) cursor = self._get_cursor() info = self._request_info.get('call_tip') if info and info.id == rep['parent_header']['msg_id'] and \ info.pos == cursor.position(): # Get the information for a call tip. For now we format the call # line as string, later we can pass False to format_call and # syntax-highlight it ourselves for nicer formatting in the # calltip. content = rep['content'] # if this is from pykernel, 'docstring' will be the only key if content.get('ismagic', False): # Don't generate a call-tip for magics. Ideally, we should # generate a tooltip, but not on ( like we do for actual # callables. call_info, doc = None, None else: call_info, doc = call_tip(content, format_call=True) if call_info or doc: self._call_tip_widget.show_call_info(call_info, doc) def _handle_pyout(self, msg): """ Handle display hook output. """ self.log.debug("pyout: %s", msg.get('content', '')) if not self._hidden and self._is_from_this_session(msg): text = msg['content']['data'] self._append_plain_text(text + '\n', before_prompt=True) def _handle_stream(self, msg): """ Handle stdout, stderr, and stdin. """ self.log.debug("stream: %s", msg.get('content', '')) if not self._hidden and self._is_from_this_session(msg): # Most consoles treat tabs as being 8 space characters. Convert tabs # to spaces so that output looks as expected regardless of this # widget's tab width. text = msg['content']['data'].expandtabs(8) self._append_plain_text(text, before_prompt=True) self._control.moveCursor(QtGui.QTextCursor.End) def _handle_shutdown_reply(self, msg): """ Handle shutdown signal, only if from other console. """ self.log.warn("shutdown: %s", msg.get('content', '')) restart = msg.get('content', {}).get('restart', False) if not self._hidden and not self._is_from_this_session(msg): # got shutdown reply, request came from session other than ours if restart: # someone restarted the kernel, handle it self._handle_kernel_restarted(died=False) else: # kernel was shutdown permanently # this triggers exit_requested if the kernel was local, # and a dialog if the kernel was remote, # so we don't suddenly clear the qtconsole without asking. if self._local_kernel: self.exit_requested.emit(self) else: title = self.window().windowTitle() reply = QtGui.QMessageBox.question( self, title, "Kernel has been shutdown permanently. " "Close the Console?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: self.exit_requested.emit(self) def _handle_status(self, msg): """Handle status message""" # This is where a busy/idle indicator would be triggered, # when we make one. state = msg['content'].get('execution_state', '') if state == 'starting': # kernel started while we were running if self._executing: self._handle_kernel_restarted(died=True) elif state == 'idle': pass elif state == 'busy': pass def _started_channels(self): """ Called when the KernelManager channels have started listening or when the frontend is assigned an already listening KernelManager. """ self.reset(clear=True) #--------------------------------------------------------------------------- # 'FrontendWidget' public interface #--------------------------------------------------------------------------- def copy_raw(self): """ Copy the currently selected text to the clipboard without attempting to remove prompts or otherwise alter the text. """ self._control.copy() def execute_file(self, path, hidden=False): """ Attempts to execute file with 'path'. If 'hidden', no output is shown. """ self.execute('execfile(%r)' % path, hidden=hidden) def interrupt_kernel(self): """ Attempts to interrupt the running kernel. Also unsets _reading flag, to avoid runtime errors if raw_input is called again. """ if self.custom_interrupt: self._reading = False self.custom_interrupt_requested.emit() elif self.kernel_manager: self._reading = False self.kernel_manager.interrupt_kernel() else: self._append_plain_text( 'Cannot interrupt a kernel I did not start.\n') def reset(self, clear=False): """ Resets the widget to its initial state if ``clear`` parameter is True, otherwise prints a visual indication of the fact that the kernel restarted, but does not clear the traces from previous usage of the kernel before it was restarted. With ``clear=True``, it is similar to ``%clear``, but also re-writes the banner and aborts execution if necessary. """ if self._executing: self._executing = False self._request_info['execute'] = {} self._reading = False self._highlighter.highlighting_on = False if clear: self._control.clear() self._append_plain_text(self.banner) # update output marker for stdout/stderr, so that startup # messages appear after banner: self._append_before_prompt_pos = self._get_cursor().position() self._show_interpreter_prompt() def restart_kernel(self, message, now=False): """ Attempts to restart the running kernel. """ # FIXME: now should be configurable via a checkbox in the dialog. Right # now at least the heartbeat path sets it to True and the manual restart # to False. But those should just be the pre-selected states of a # checkbox that the user could override if so desired. But I don't know # enough Qt to go implementing the checkbox now. if self.custom_restart: self.custom_restart_requested.emit() return if self.kernel_manager: # Pause the heart beat channel to prevent further warnings. self.kernel_client.hb_channel.pause() # Prompt the user to restart the kernel. Un-pause the heartbeat if # they decline. (If they accept, the heartbeat will be un-paused # automatically when the kernel is restarted.) if self.confirm_restart: buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No result = QtGui.QMessageBox.question(self, 'Restart kernel?', message, buttons) do_restart = result == QtGui.QMessageBox.Yes else: # confirm_restart is False, so we don't need to ask user # anything, just do the restart do_restart = True if do_restart: try: self.kernel_manager.restart_kernel(now=now) except RuntimeError as e: self._append_plain_text('Error restarting kernel: %s\n' % e, before_prompt=True) else: self._append_html( "<br>Restarting kernel...\n<hr><br>", before_prompt=True, ) else: self.kernel_client.hb_channel.unpause() else: self._append_plain_text( 'Cannot restart a Kernel I did not start\n', before_prompt=True) #--------------------------------------------------------------------------- # 'FrontendWidget' protected interface #--------------------------------------------------------------------------- def _call_tip(self): """ Shows a call tip, if appropriate, at the current cursor location. """ # Decide if it makes sense to show a call tip if not self.enable_calltips: return False cursor = self._get_cursor() cursor.movePosition(QtGui.QTextCursor.Left) if cursor.document().characterAt(cursor.position()) != '(': return False context = self._get_context(cursor) if not context: return False # Send the metadata request to the kernel name = '.'.join(context) msg_id = self.kernel_client.object_info(name) pos = self._get_cursor().position() self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos) return True def _complete(self): """ Performs completion at the current cursor location. """ context = self._get_context() if context: # Send the completion request to the kernel msg_id = self.kernel_client.complete( '.'.join(context), # text self._get_input_buffer_cursor_line(), # line self._get_input_buffer_cursor_column(), # cursor_pos self.input_buffer) # block pos = self._get_cursor().position() info = self._CompletionRequest(msg_id, pos) self._request_info['complete'] = info def _get_context(self, cursor=None): """ Gets the context for the specified cursor (or the current cursor if none is specified). """ if cursor is None: cursor = self._get_cursor() cursor.movePosition(QtGui.QTextCursor.StartOfBlock, QtGui.QTextCursor.KeepAnchor) text = cursor.selection().toPlainText() return self._completion_lexer.get_context(text) def _process_execute_abort(self, msg): """ Process a reply for an aborted execution request. """ self._append_plain_text("ERROR: execution aborted\n") def _process_execute_error(self, msg): """ Process a reply for an execution request that resulted in an error. """ content = msg['content'] # If a SystemExit is passed along, this means exit() was called - also # all the ipython %exit magic syntax of '-k' to be used to keep # the kernel running if content['ename'] == 'SystemExit': keepkernel = content['evalue'] == '-k' or content[ 'evalue'] == 'True' self._keep_kernel_on_exit = keepkernel self.exit_requested.emit(self) else: traceback = ''.join(content['traceback']) self._append_plain_text(traceback) def _process_execute_ok(self, msg): """ Process a reply for a successful execution request. """ payload = msg['content']['payload'] for item in payload: if not self._process_execute_payload(item): warning = 'Warning: received unknown payload of type %s' print(warning % repr(item['source'])) def _process_execute_payload(self, item): """ Process a single payload item from the list of payload items in an execution reply. Returns whether the payload was handled. """ # The basic FrontendWidget doesn't handle payloads, as they are a # mechanism for going beyond the standard Python interpreter model. return False def _show_interpreter_prompt(self): """ Shows a prompt for the interpreter. """ self._show_prompt('>>> ') def _show_interpreter_prompt_for_reply(self, msg): """ Shows a prompt for the interpreter given an 'execute_reply' message. """ self._show_interpreter_prompt() #------ Signal handlers ---------------------------------------------------- def _document_contents_change(self, position, removed, added): """ Called whenever the document's content changes. Display a call tip if appropriate. """ # Calculate where the cursor should be *after* the change: position += added document = self._control.document() if position == self._get_cursor().position(): self._call_tip() #------ Trait default initializers ----------------------------------------- def _banner_default(self): """ Returns the standard Python banner. """ banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \ '"license" for more information.' return banner % (sys.version, sys.platform)
kc = km.client() kc.start_channels() nw = NeuropyWindow() ipw = nw.ipw config_ipw(ipw) ipw.kernel_manager = km ipw.kernel_client = kc ipw.exit_requested.connect(nw.stop) nw.show() # execute some code directly, note the output appears at the system command line: #kernel.shell.run_cell('print "x=%r, y=%r, z=%r" % (x,y,z)') # execute some code through the frontend (once the event loop is running). # The output appears in the IPythonWidget (ipw). do_later(ipw.execute, 'execfile(%r)' % 'startup.py', hidden=True) do_later(ipw.execute, 'execfile(%r)' % 'globals.py', hidden=True) guisupport.start_event_loop_qt4(app) def do_later(func, *args, **kwargs): from IPython.external.qt import QtCore QtCore.QTimer.singleShot(0, lambda: func(*args, **kwargs)) if __name__ == "__main__": # prevents "The event loop is already running" messages when calling ipshell(): QtCore.pyqtRemoveInputHook() main()
class QtKernelManager(KernelManager, SuperQObject, metaclass=MetaQObjectHasTraits): """ A KernelManager that provides signals and slots. """ # Emitted when the kernel manager has started listening. started_kernel = QtCore.Signal() # Emitted when the kernel manager has started listening. started_channels = QtCore.Signal() # Emitted when the kernel manager has stopped listening. stopped_channels = QtCore.Signal() # Use Qt-specific channel classes that emit signals. sub_channel_class = Type(QtSubSocketChannel) shell_channel_class = Type(QtShellSocketChannel) stdin_channel_class = Type(QtStdInSocketChannel) hb_channel_class = Type(QtHBSocketChannel) #--------------------------------------------------------------------------- # 'KernelManager' interface #--------------------------------------------------------------------------- #------ Kernel process management ------------------------------------------ def start_kernel(self, *args, **kw): """ Reimplemented for proper heartbeat management. """ if self._shell_channel is not None: self._shell_channel.reset_first_reply() super(QtKernelManager, self).start_kernel(*args, **kw) self.started_kernel.emit() #------ Channel management ------------------------------------------------- def start_channels(self, *args, **kw): """ Reimplemented to emit signal. """ super(QtKernelManager, self).start_channels(*args, **kw) self.started_channels.emit() def stop_channels(self): """ Reimplemented to emit signal. """ super(QtKernelManager, self).stop_channels() self.stopped_channels.emit() @property def shell_channel(self): """ Reimplemented for proper heartbeat management. """ if self._shell_channel is None: self._shell_channel = super(QtKernelManager, self).shell_channel self._shell_channel.first_reply.connect(self._first_reply) return self._shell_channel #--------------------------------------------------------------------------- # Protected interface #--------------------------------------------------------------------------- def _first_reply(self): """ Unpauses the heartbeat channel when the first reply is received on the execute channel. Note that this will *not* start the heartbeat channel if it is not already running! """ if self._hb_channel is not None: self._hb_channel.unpause()
def _get_image_tag(self, match, path=None, format="png"): """ Return (X)HTML mark-up for the image-tag given by match. Parameters ---------- match : re.SRE_Match A match to an HTML image tag as exported by Qt, with match.group("Name") containing the matched image ID. path : string|None, optional [default None] If not None, specifies a path to which supporting files may be written (e.g., for linked images). If None, all images are to be included inline. format : "png"|"svg"|"jpg", optional [default "png"] Format for returned or referenced images. """ if format in ("png", "jpg"): try: image = self._get_image(match.group("name")) except KeyError: return "<b>Couldn't find image %s</b>" % match.group("name") if path is not None: if not os.path.exists(path): os.mkdir(path) relpath = os.path.basename(path) if image.save( "%s/qt_img%s.%s" % (path, match.group("name"), format), "PNG"): return '<img src="%s/qt_img%s.%s">' % ( relpath, match.group("name"), format) else: return "<b>Couldn't save image!</b>" else: ba = QtCore.QByteArray() buffer_ = QtCore.QBuffer(ba) buffer_.open(QtCore.QIODevice.WriteOnly) image.save(buffer_, format.upper()) buffer_.close() return '<img src="data:image/%s;base64,\n%s\n" />' % ( format, re.sub(r'(.{60})', r'\1\n', str(ba.toBase64()))) elif format == "svg": try: svg = str(self._name_to_svg_map[match.group("name")]) except KeyError: if not self._svg_warning_displayed: QtGui.QMessageBox.warning( self, 'Error converting PNG to SVG.', 'Cannot convert a PNG to SVG. To fix this, add this ' 'to your ipython config:\n\n' '\tc.InlineBackendConfig.figure_format = \'svg\'\n\n' 'And regenerate the figures.', QtGui.QMessageBox.Ok) self._svg_warning_displayed = True return ( "<b>Cannot convert a PNG to SVG.</b> " "To fix this, add this to your config: " "<span>c.InlineBackendConfig.figure_format = 'svg'</span> " "and regenerate the figures.") # Not currently checking path, because it's tricky to find a # cross-browser way to embed external SVG images (e.g., via # object or embed tags). # Chop stand-alone header from matplotlib SVG offset = svg.find("<svg") assert (offset > -1) return svg[offset:] else: return '<b>Unrecognized image format</b>'
class QtKernelManagerMixin( MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})): """ A KernelClient that provides signals and slots. """ kernel_restarted = QtCore.Signal()