diff --git a/IPython/frontend/qt/console/call_tip_widget.py b/IPython/frontend/qt/console/call_tip_widget.py index ac60710..cfac128 100644 --- a/IPython/frontend/qt/console/call_tip_widget.py +++ b/IPython/frontend/qt/console/call_tip_widget.py @@ -34,22 +34,32 @@ class CallTipWidget(QtGui.QLabel): self.setWindowOpacity(self.style().styleHint( QtGui.QStyle.SH_ToolTipLabel_Opacity, None, self) / 255.0) + def eventFilter(self, obj, event): + """ Reimplemented to hide on certain key presses and on parent focus + changes. + """ + if obj == self.parent(): + etype = event.type() + if (etype == QtCore.QEvent.KeyPress and + event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return, + QtCore.Qt.Key_Escape)): + self.hide() + elif etype == QtCore.QEvent.FocusOut: + self.hide() + + return QtGui.QLabel.eventFilter(self, obj, event) + #-------------------------------------------------------------------------- # 'QWidget' interface #-------------------------------------------------------------------------- def hideEvent(self, event): - """ Reimplemented to disconnect the cursor movement handler. + """ Reimplemented to disconnect signal handlers and event filter. """ QtGui.QLabel.hideEvent(self, event) - self.parent().cursorPositionChanged.disconnect(self._update_tip) - - def keyPressEvent(self, event): - """ Reimplemented to hide on certain key presses. - """ - if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return, - QtCore.Qt.Key_Escape): - self.hide() + parent = self.parent() + parent.cursorPositionChanged.disconnect(self._cursor_position_changed) + parent.removeEventFilter(self) def paintEvent(self, event): """ Reimplemented to paint the background panel. @@ -63,10 +73,12 @@ class CallTipWidget(QtGui.QLabel): QtGui.QLabel.paintEvent(self, event) def showEvent(self, event): - """ Reimplemented to connect the cursor movement handler. + """ Reimplemented to connect signal handlers and event filter. """ QtGui.QLabel.showEvent(self, event) - self.parent().cursorPositionChanged.connect(self._update_tip) + parent = self.parent() + parent.cursorPositionChanged.connect(self._cursor_position_changed) + parent.installEventFilter(self) #-------------------------------------------------------------------------- # 'CallTipWidget' interface @@ -139,38 +151,9 @@ class CallTipWidget(QtGui.QLabel): position = -1 return position, commas - def _highlight_tip(self, tip, current_argument): - """ Highlight the current argument (arguments start at 0), ending at the - next comma or unmatched closing parenthesis. - - FIXME: This is an unreliable way to do things and it isn't being - used right now. Instead, we should use inspect.getargspec - metadata for this purpose. - """ - start = tip.find('(') - if start != -1: - for i in xrange(current_argument): - start = tip.find(',', start) - if start != -1: - end = start + 1 - while end < len(tip): - char = tip[end] - depth = 0 - if (char == ',' and depth == 0): - break - elif char == '(': - depth += 1 - elif char == ')': - if depth == 0: - break - depth -= 1 - end += 1 - tip = tip[:start+1] + '' + \ - tip[start+1:end] + '' + tip[end:] - tip = tip.replace('\n', '
') - return tip - - def _update_tip(self): + #------ Signal handlers ---------------------------------------------------- + + def _cursor_position_changed(self): """ Updates the tip based on user cursor movement. """ cursor = self.parent().textCursor() diff --git a/IPython/frontend/qt/console/completion_widget.py b/IPython/frontend/qt/console/completion_widget.py index e2ef3c0..f2e0f49 100644 --- a/IPython/frontend/qt/console/completion_widget.py +++ b/IPython/frontend/qt/console/completion_widget.py @@ -7,7 +7,7 @@ class CompletionWidget(QtGui.QListWidget): """ #-------------------------------------------------------------------------- - # 'QWidget' interface + # 'QObject' interface #-------------------------------------------------------------------------- def __init__(self, parent): @@ -28,43 +28,55 @@ class CompletionWidget(QtGui.QListWidget): self.itemActivated.connect(self._complete_current) + def eventFilter(self, obj, event): + """ Reimplemented to handle keyboard input and to auto-hide when our + parent loses focus. + """ + if obj == self.parent(): + etype = event.type() + + if etype == QtCore.QEvent.KeyPress: + key, text = event.key(), event.text() + if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, + QtCore.Qt.Key_Tab): + self._complete_current() + return True + elif key == QtCore.Qt.Key_Escape: + self.hide() + return True + elif key in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, + QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown, + QtCore.Qt.Key_Home, QtCore.Qt.Key_End): + QtGui.QListWidget.keyPressEvent(self, event) + return True + + elif etype == QtCore.QEvent.FocusOut: + self.hide() + + return QtGui.QListWidget.eventFilter(self, obj, event) + + #-------------------------------------------------------------------------- + # 'QWidget' interface + #-------------------------------------------------------------------------- + def hideEvent(self, event): - """ Reimplemented to disconnect the cursor movement handler. + """ Reimplemented to disconnect signal handlers and event filter. """ QtGui.QListWidget.hideEvent(self, event) + parent = self.parent() try: - self.parent().cursorPositionChanged.disconnect(self._update_current) + parent.cursorPositionChanged.disconnect(self._update_current) except TypeError: pass - - def keyPressEvent(self, event): - """ Reimplemented to update the list. - """ - key, text = event.key(), event.text() - - if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, - QtCore.Qt.Key_Tab): - self._complete_current() - event.accept() - - elif key == QtCore.Qt.Key_Escape: - self.hide() - event.accept() - - elif key in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, - QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown, - QtCore.Qt.Key_Home, QtCore.Qt.Key_End): - QtGui.QListWidget.keyPressEvent(self, event) - event.accept() - - else: - event.ignore() + parent.removeEventFilter(self) def showEvent(self, event): - """ Reimplemented to connect the cursor movement handler. + """ Reimplemented to connect signal handlers and event filter. """ QtGui.QListWidget.showEvent(self, event) - self.parent().cursorPositionChanged.connect(self._update_current) + parent = self.parent() + parent.cursorPositionChanged.connect(self._update_current) + parent.installEventFilter(self) #-------------------------------------------------------------------------- # 'CompletionWidget' interface diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 075ef99..3218845 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -56,8 +56,7 @@ class ConsoleWidget(QtGui.QWidget): ---------- kind : str, optional [default 'plain'] The type of text widget to use. Valid values are 'plain', which - specifies a QPlainTextEdit, and 'rich', which specifies an - QTextEdit. + specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit. parent : QWidget, optional [default None] The parent for this widget. @@ -332,6 +331,12 @@ class ConsoleWidget(QtGui.QWidget): """ raise NotImplementedError + def _execute_interrupt(self): + """ Attempts to stop execution. Returns whether this method has an + implementation. + """ + return False + def _prompt_started_hook(self): """ Called immediately after a new prompt is displayed. """ @@ -469,12 +474,6 @@ class ConsoleWidget(QtGui.QWidget): QtGui.qApp.sendEvent(self._control, new_event) return True - # If the completion widget accepts the key press, return immediately. - if self._completion_widget.isVisible(): - self._completion_widget.keyPressEvent(event) - if event.isAccepted(): - return True - # Otherwise, proceed normally and do not return early. intercepted = False cursor = self._control.textCursor() @@ -488,7 +487,10 @@ class ConsoleWidget(QtGui.QWidget): intercepted = True elif ctrl_down: - if key == QtCore.Qt.Key_K: + if key == QtCore.Qt.Key_C and self._executing: + intercepted = self._execute_interrupt() + + elif key == QtCore.Qt.Key_K: if self._in_buffer(position): cursor.movePosition(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor) diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 873a1f0..5199551 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -91,28 +91,6 @@ class FrontendWidget(HistoryConsoleWidget): document.contentsChange.connect(self._document_contents_change) #--------------------------------------------------------------------------- - # 'QWidget' interface - #--------------------------------------------------------------------------- - - def focusOutEvent(self, event): - """ Reimplemented to hide calltips. - """ - self._call_tip_widget.hide() - super(FrontendWidget, self).focusOutEvent(event) - - def keyPressEvent(self, event): - """ Reimplemented to allow calltips to process events and to send - signals to the kernel. - """ - if self._executing and event.key() == QtCore.Qt.Key_C and \ - self._control_down(event.modifiers()): - self._interrupt_kernel() - else: - if self._call_tip_widget.isVisible(): - self._call_tip_widget.keyPressEvent(event) - super(FrontendWidget, self).keyPressEvent(event) - - #--------------------------------------------------------------------------- # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- @@ -131,6 +109,13 @@ class FrontendWidget(HistoryConsoleWidget): """ self.kernel_manager.xreq_channel.execute(source) self._hidden = hidden + + def _execute_interrupt(self): + """ Attempts to stop execution. Returns whether this method has an + implementation. + """ + self._interrupt_kernel() + return True def _prompt_started_hook(self): """ Called immediately after a new prompt is displayed. @@ -245,8 +230,8 @@ class FrontendWidget(HistoryConsoleWidget): # Send the metadata request to the kernel name = '.'.join(context) - self._calltip_id = self.kernel_manager.xreq_channel.object_info(name) - self._calltip_pos = self._get_cursor().position() + self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name) + self._call_tip_pos = self._get_cursor().position() return True def _complete(self): @@ -311,7 +296,7 @@ class FrontendWidget(HistoryConsoleWidget): pass def _document_contents_change(self, position, removed, added): - """ Called whenever the document's content changes. Display a calltip + """ Called whenever the document's content changes. Display a call tip if appropriate. """ # Calculate where the cursor should be *after* the change: @@ -377,8 +362,8 @@ class FrontendWidget(HistoryConsoleWidget): def _handle_object_info_reply(self, rep): cursor = self._get_cursor() - if rep['parent_header']['msg_id'] == self._calltip_id and \ - cursor.position() == self._calltip_pos: + if rep['parent_header']['msg_id'] == self._call_tip_id and \ + cursor.position() == self._call_tip_pos: doc = rep['content']['docstring'] if doc: self._call_tip_widget.show_docstring(doc)