From f21a128a37a6f32ed502e2fe5bf4e82a35875043 2010-09-07 20:31:48 From: epatters Date: 2010-09-07 20:31:48 Subject: [PATCH] Fixed the segfaults on application exit. The BracketMatcher, CallTipWidget, and CompletionWidget were using the text control as their parents. This should not be a problem, but for some reason it resulted in problems during shutdown. I suspect that PyQt is bugged and was deleting the C++ objects a second time in the garbage collection phase after they had already been deleted automatically by the C++ layer of Qt. --- diff --git a/IPython/frontend/qt/console/bracket_matcher.py b/IPython/frontend/qt/console/bracket_matcher.py index 1b059ca..37ad82f 100644 --- a/IPython/frontend/qt/console/bracket_matcher.py +++ b/IPython/frontend/qt/console/bracket_matcher.py @@ -18,18 +18,19 @@ class BracketMatcher(QtCore.QObject): # 'QObject' interface #-------------------------------------------------------------------------- - def __init__(self, parent): + def __init__(self, text_edit): """ Create a call tip manager that is attached to the specified Qt text edit widget. """ - assert isinstance(parent, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) - QtCore.QObject.__init__(self, parent) + assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) + super(BracketMatcher, self).__init__() # The format to apply to matching brackets. self.format = QtGui.QTextCharFormat() self.format.setBackground(QtGui.QColor('silver')) - parent.cursorPositionChanged.connect(self._cursor_position_changed) + self._text_edit = text_edit + text_edit.cursorPositionChanged.connect(self._cursor_position_changed) #-------------------------------------------------------------------------- # Protected interface @@ -40,7 +41,7 @@ class BracketMatcher(QtCore.QObject): position of the matching bracket. Returns -1 if unsuccessful. """ # Decide what character to search for and what direction to search in. - document = self.parent().document() + document = self._text_edit.document() qchar = document.characterAt(position) start_char = qchar.toAscii() search_char = self._opening_map.get(start_char) @@ -73,7 +74,7 @@ class BracketMatcher(QtCore.QObject): """ Convenience method for selecting a character. """ selection = QtGui.QTextEdit.ExtraSelection() - cursor = self.parent().textCursor() + cursor = self._text_edit.textCursor() cursor.setPosition(position) cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor) @@ -87,15 +88,14 @@ class BracketMatcher(QtCore.QObject): """ Updates the document formatting based on the new cursor position. """ # Clear out the old formatting. - text_edit = self.parent() - text_edit.setExtraSelections([]) + self._text_edit.setExtraSelections([]) # Attempt to match a bracket for the new cursor position. - cursor = text_edit.textCursor() + cursor = self._text_edit.textCursor() if not cursor.hasSelection(): position = cursor.position() - 1 match_position = self._find_match(position) if match_position != -1: extra_selections = [ self._selection_for_character(pos) for pos in (position, match_position) ] - text_edit.setExtraSelections(extra_selections) + self._text_edit.setExtraSelections(extra_selections) diff --git a/IPython/frontend/qt/console/call_tip_widget.py b/IPython/frontend/qt/console/call_tip_widget.py index bf6ca40..4f4bb9e 100644 --- a/IPython/frontend/qt/console/call_tip_widget.py +++ b/IPython/frontend/qt/console/call_tip_widget.py @@ -14,16 +14,17 @@ class CallTipWidget(QtGui.QLabel): # 'QObject' interface #-------------------------------------------------------------------------- - def __init__(self, parent): + def __init__(self, text_edit): """ Create a call tip manager that is attached to the specified Qt text edit widget. """ - assert isinstance(parent, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) - QtGui.QLabel.__init__(self, parent, QtCore.Qt.ToolTip) + 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(parent.document().defaultFont()) + self.setFont(text_edit.document().defaultFont()) self.setForegroundRole(QtGui.QPalette.ToolTipText) self.setBackgroundRole(QtGui.QPalette.ToolTipBase) self.setPalette(QtGui.QToolTip.palette()) @@ -37,10 +38,10 @@ class CallTipWidget(QtGui.QLabel): 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 + """ Reimplemented to hide on certain key presses and on text edit focus changes. """ - if obj == self.parent(): + if obj == self._text_edit: etype = event.type() if etype == QtCore.QEvent.KeyPress: @@ -60,7 +61,7 @@ class CallTipWidget(QtGui.QLabel): elif etype == QtCore.QEvent.Leave: self._hide_later() - return QtGui.QLabel.eventFilter(self, obj, event) + return super(CallTipWidget, self).eventFilter(obj, event) def timerEvent(self, event): """ Reimplemented to hide the widget when the hide timer fires. @@ -76,21 +77,21 @@ class CallTipWidget(QtGui.QLabel): def enterEvent(self, event): """ Reimplemented to cancel the hide timer. """ - QtGui.QLabel.enterEvent(self, event) + super(CallTipWidget, self).enterEvent(event) self._hide_timer.stop() def hideEvent(self, event): """ Reimplemented to disconnect signal handlers and event filter. """ - QtGui.QLabel.hideEvent(self, event) - parent = self.parent() - parent.cursorPositionChanged.disconnect(self._cursor_position_changed) - parent.removeEventFilter(self) + super(CallTipWidget, self).hideEvent(event) + self._text_edit.cursorPositionChanged.disconnect( + self._cursor_position_changed) + self._text_edit.removeEventFilter(self) def leaveEvent(self, event): """ Reimplemented to start the hide timer. """ - QtGui.QLabel.leaveEvent(self, event) + super(CallTipWidget, self).leaveEvent(event) self._hide_later() def paintEvent(self, event): @@ -102,15 +103,15 @@ class CallTipWidget(QtGui.QLabel): painter.drawPrimitive(QtGui.QStyle.PE_PanelTipLabel, option) painter.end() - QtGui.QLabel.paintEvent(self, event) + super(CallTipWidget, self).paintEvent(event) def showEvent(self, event): """ Reimplemented to connect signal handlers and event filter. """ - QtGui.QLabel.showEvent(self, event) - parent = self.parent() - parent.cursorPositionChanged.connect(self._cursor_position_changed) - parent.installEventFilter(self) + super(CallTipWidget, self).showEvent(event) + self._text_edit.cursorPositionChanged.connect( + self._cursor_position_changed) + self._text_edit.installEventFilter(self) #-------------------------------------------------------------------------- # 'CallTipWidget' interface @@ -131,7 +132,7 @@ class CallTipWidget(QtGui.QLabel): """ Attempts to show the specified tip at the current cursor location. """ # Attempt to find the cursor position at which to show the call tip. - text_edit = self.parent() + text_edit = self._text_edit document = text_edit.document() cursor = text_edit.textCursor() search_pos = cursor.position() - 1 @@ -172,7 +173,7 @@ class CallTipWidget(QtGui.QLabel): not found) and the number commas (at depth 0) found along the way. """ commas = depth = 0 - document = self.parent().document() + document = self._text_edit.document() qchar = document.characterAt(position) while (position > 0 and qchar.isPrint() and # Need to check explicitly for line/paragraph separators: @@ -205,7 +206,7 @@ class CallTipWidget(QtGui.QLabel): def _cursor_position_changed(self): """ Updates the tip based on user cursor movement. """ - cursor = self.parent().textCursor() + cursor = self._text_edit.textCursor() if cursor.position() <= self._start_position: self.hide() else: diff --git a/IPython/frontend/qt/console/completion_widget.py b/IPython/frontend/qt/console/completion_widget.py index f2e0f49..e6fa4de 100644 --- a/IPython/frontend/qt/console/completion_widget.py +++ b/IPython/frontend/qt/console/completion_widget.py @@ -10,18 +10,20 @@ class CompletionWidget(QtGui.QListWidget): # 'QObject' interface #-------------------------------------------------------------------------- - def __init__(self, parent): + def __init__(self, text_edit): """ Create a completion widget that is attached to the specified Qt text edit widget. """ - assert isinstance(parent, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) - QtGui.QListWidget.__init__(self, parent) + assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) + super(CompletionWidget, self).__init__() + + self._text_edit = text_edit - self.setWindowFlags(QtCore.Qt.ToolTip | QtCore.Qt.WindowStaysOnTopHint) self.setAttribute(QtCore.Qt.WA_StaticContents) + self.setWindowFlags(QtCore.Qt.ToolTip | QtCore.Qt.WindowStaysOnTopHint) - # Ensure that parent keeps focus when widget is displayed. - self.setFocusProxy(parent) + # Ensure that the text edit keeps focus when widget is displayed. + self.setFocusProxy(self._text_edit) self.setFrameShadow(QtGui.QFrame.Plain) self.setFrameShape(QtGui.QFrame.StyledPanel) @@ -29,10 +31,10 @@ 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. + """ Reimplemented to handle keyboard input and to auto-hide when the + text edit loses focus. """ - if obj == self.parent(): + if obj == self._text_edit: etype = event.type() if etype == QtCore.QEvent.KeyPress: @@ -47,13 +49,13 @@ class CompletionWidget(QtGui.QListWidget): 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) + self.keyPressEvent(event) return True elif etype == QtCore.QEvent.FocusOut: self.hide() - return QtGui.QListWidget.eventFilter(self, obj, event) + return super(CompletionWidget, self).eventFilter(obj, event) #-------------------------------------------------------------------------- # 'QWidget' interface @@ -62,21 +64,16 @@ class CompletionWidget(QtGui.QListWidget): def hideEvent(self, event): """ Reimplemented to disconnect signal handlers and event filter. """ - QtGui.QListWidget.hideEvent(self, event) - parent = self.parent() - try: - parent.cursorPositionChanged.disconnect(self._update_current) - except TypeError: - pass - parent.removeEventFilter(self) + super(CompletionWidget, self).hideEvent(event) + self._text_edit.cursorPositionChanged.disconnect(self._update_current) + self._text_edit.removeEventFilter(self) def showEvent(self, event): """ Reimplemented to connect signal handlers and event filter. """ - QtGui.QListWidget.showEvent(self, event) - parent = self.parent() - parent.cursorPositionChanged.connect(self._update_current) - parent.installEventFilter(self) + super(CompletionWidget, self).showEvent(event) + self._text_edit.cursorPositionChanged.connect(self._update_current) + self._text_edit.installEventFilter(self) #-------------------------------------------------------------------------- # 'CompletionWidget' interface @@ -86,7 +83,7 @@ class CompletionWidget(QtGui.QListWidget): """ Shows the completion widget with 'items' at the position specified by 'cursor'. """ - text_edit = self.parent() + text_edit = self._text_edit point = text_edit.cursorRect(cursor).bottomRight() point = text_edit.mapToGlobal(point) screen_rect = QtGui.QApplication.desktop().availableGeometry(self) @@ -115,7 +112,7 @@ class CompletionWidget(QtGui.QListWidget): """ Returns a cursor with text between the start position and the current position selected. """ - cursor = self.parent().textCursor() + cursor = self._text_edit.textCursor() if cursor.position() >= self._start_position: cursor.setPosition(self._start_position, QtGui.QTextCursor.KeepAnchor) diff --git a/IPython/frontend/qt/console/ipythonqt.py b/IPython/frontend/qt/console/ipythonqt.py index 8e827fa..b6483c1 100644 --- a/IPython/frontend/qt/console/ipythonqt.py +++ b/IPython/frontend/qt/console/ipythonqt.py @@ -46,13 +46,17 @@ class MainWindow(QtGui.QMainWindow): def closeEvent(self, event): """ Reimplemented to prompt the user and close the kernel cleanly. """ - reply = QtGui.QMessageBox.question(self, self.window().windowTitle(), - 'Close console?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: - self._frontend.kernel_manager.shutdown_kernel() - event.accept() - else: - event.ignore() + kernel_manager = self._frontend.kernel_manager + if kernel_manager and kernel_manager.channels_running: + title = self.window().windowTitle() + reply = QtGui.QMessageBox.question(self, title, + 'Close console?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: + kernel_manager.shutdown_kernel() + #kernel_manager.stop_channels() + event.accept() + else: + event.ignore() #----------------------------------------------------------------------------- # Main entry point