[PyQt] Segfault when trying to rely on QStyledItemDelegate.setEditorData/ModelData default behavior

Elvis Stansvik elvstone at gmail.com
Thu Feb 11 20:23:46 GMT 2016


2016-02-11 21:14 GMT+01:00 Elvis Stansvik <elvstone at gmail.com>:
> I found the problem, I had forgot to change the user property from
> dict to QVariant (was testing around).
>
> With the property of type QVariant, it seems to work fine.
>
> And sorry that the example was so broken apart from that (missing
> imports, syntax errors, ...) I was in a bit of a hurry to compile it
> together from my actual code.
>
> Here's the fixed one:
>
>     https://gist.github.com/estan/c051d1f798c4c46caa7d
>
> See attached screenshot for how it looks. I can now edit cells which
> contain str -> int dicts using a bunch of pop-out sliders. Yay.

And to clarify, I'm obviously not planning on having the dict
visualised as str(..) like it is now, that was just for testing :)

Elvis

>
> Elvis
>
> 2016-02-11 17:52 GMT+01:00 Elvis Stansvik <elvstone at gmail.com>:
>> Hi all,
>>
>> I'm working on a custom table model (inheriting QAbstractTableModel),
>> where I'll be using custom delegates + editors for some columns.
>>
>> As an experiment, I've tried to create a column where the item data is
>> a dict (wrapped in MyCoolItem), and made a MyCoolDelegate which
>> returns my custom editor (the DisplayRole is just the dict as a string
>> for now).
>>
>> The attached screenshot shows the look.
>>
>> The problem is that when I double click an item in the first column,
>> to bring up my custom editor, I get a segfault when
>> QStyledItemDelegate::setEditorData tries to write the user property
>> from my editor (the default behavior of
>> QStyledItemDelegate.setEditorData is to use the user property on the
>> editor as storage for the data during editing).
>>
>> The code and backtrace follows:
>>
>> Code:
>>
>> from sys import argv, exit
>>
>> from PyQt5.QtCore import (Qt, QAbstractTableModel, QModelIndex, pyqtProperty,
>>                           pyqtSignal, QVariant)
>> from PyQt5.QtWidgets import (QApplication, QWidget,
>> QStyledItemDelegate, QTableView)
>>
>>
>> class MyAbstractItem(object):
>>
>>     def data(self, role):
>>         raise NotImplementedError
>>
>>     def setData(self, value, role):
>>         raise NotImplementedError
>>
>>
>> class MyTextItem(MyAbstractItem):
>>
>>     def __init__(self, text):
>>         self._text = text
>>
>>     def data(self, role):
>>         if role != Qt.DisplayRole:
>>             return None
>>
>>         return self._text
>>
>>     def setData(self, text, role):
>>         if role != Qt.EditRole:
>>             return False
>>
>>         self._text = text
>>
>>         return True
>>
>>
>> class MyCoolItem(MyAbstractItem):
>>
>>     def __init__(self, levels):
>>         self._levels = levels  #: dict: Maps names to values
>>
>>     def data(self, role):
>>         if role == Qt.DisplayRole:
>>             return str(self._levels)  # Just as string for now.
>>         elif role == Qt.EditRole:
>>             return QVariant(self._levels)
>>
>>         return None
>>
>>     def setData(self, levels, role):
>>         if role != Qt.EditRole:
>>             return False
>>
>>         self._levels = levels.value()
>>
>>         return True
>>
>>
>> class MyCoolEditor(QWidget):
>>
>>     changed = pyqtSignal()
>>
>>     def __init__(self, parent=None):
>>         super(MyCoolEditor, self).__init__(parent)
>>         self._values = None
>>
>>     @pyqtProperty(dict, user=True)
>>     def values(self):
>>         return self._values
>>
>>     @values.setter
>>     def values(self, values):
>>         self._values = values
>>
>>         layout = QFormLayout(self)
>>
>>         for name, value in values.items():
>>             slider = QSlider(Qt.Horizontal)
>>             slider.setMinimum(0)
>>             slider.setMaximum(100)
>>             slider.setValue(value)
>>             slider.valueChanged.connect(partial(self._setLevel, name))
>>             layout.addRow(name + ':', slider)
>>
>>         self.setLayout(layout)
>>
>>     def _setValue(self, name, value):
>>         self._values[name] = value
>>         self.changed.emit()
>>
>>
>> class MyCoolDelegate(QStyledItemDelegate):
>>
>>     def __init__(self, parent=None):
>>         super(MyCoolDelegate, self).__init__(parent)
>>
>>     def paint(self, painter, option, index):
>>         super(MyCoolDelegate, self).paint(painter, option, index)
>>
>>     def sizeHint(self, option, index):
>>         super(MyCoolDelegate, self).sizeHint(option, index)
>>
>>     def createEditor(self, parent, option, index):
>>         levels = index.data(Qt.EditRole)
>>
>>         if isinstance(levels, dict):
>>             editor = MyCoolEditor(parent)
>>             editor.changed.connect(self._endEditing)
>>             return editor
>>
>>         return super(MyCoolDelegate, self).createEditor(parent, option, index)
>>
>>     def _endEditing(self):
>>         editor = self.sender()
>>         self.commitData.emit(editor)
>>         self.closeEditor.emit(editor)
>>
>>
>> class MyModel(QAbstractTableModel):
>>
>>     def __init__(self, headers, rows, parent=None):
>>         super(MyModel, self).__init__(parent)
>>
>>         self._headers = headers
>>         self._rows = rows
>>
>>     def rowCount(self, parent=QModelIndex()):
>>         if parent.isValid():
>>             return 0
>>
>>         return len(self._rows)
>>
>>     def columnCount(self, parent=QModelIndex()):
>>         return len(self._headers)
>>
>>     def headerData(self, section, orientation, role=Qt.DisplayRole):
>>         if orientation != Qt.Horizontal or role != Qt.DisplayRole:
>>             return None
>>
>>         return self._headers[section]
>>
>>     def data(self, index, role=Qt.DisplayRole):
>>         if not index.isValid():
>>             return None
>>
>>         # Delegate to the item at the given index.
>>         return self._rows[index.row()][index.column()].data(role)
>>
>>     def setData(self, index, value, role=Qt.EditRole):
>>         if not index.isValid():
>>             return False
>>
>>         # Delegate to the item at the given index.
>>         if self._rows[index.row()][index.column()].setData(value, role):
>>             self.dataChanged.emit(index, index)
>>             return True
>>
>>         return False
>>
>>     def flags(self, index):
>>         if not index.isValid():
>>             return Qt.NoItemFlags
>>
>>         return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
>>
>>
>> app = None
>>
>>
>> def main():
>>     global app
>>
>>     app = QApplication(argv)
>>
>>     model = MyModel(
>>         headers=['Cool Column', 'Text Column'],
>>         rows=[
>>             [MyCoolItem({'a': 10, 'b': 20}), MyTextItem('c')],
>>             [MyCoolItem({'d': 30, 'e': 50}), MyTextItem('f')]
>>         ],
>>         parent=app
>>     )
>>
>>     delegate = MyCoolDelegate(model)
>>
>>     view = QTableView()
>>     view.setItemDelegateForColumn(0, delegate)
>>     view.setModel(model)
>>     view.show()
>>
>>     exit(app.exec_())
>>
>>
>> if __name__ == '__main__':
>>     main()
>>
>> Backtrace:
>>
>> (gui-demo)estan at newton:~$ gdb python
>> GNU gdb (Ubuntu 7.10-1ubuntu2) 7.10
>> Copyright (C) 2015 Free Software Foundation, Inc.
>> License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
>> This is free software: you are free to change and redistribute it.
>> There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
>> and "show warranty" for details.
>> This GDB was configured as "x86_64-linux-gnu".
>> Type "show configuration" for configuration details.
>> For bug reporting instructions, please see:
>> <http://www.gnu.org/software/gdb/bugs/>.
>> Find the GDB manual and other documentation resources online at:
>> <http://www.gnu.org/software/gdb/documentation/>.
>> For help, type "help".
>> Type "apropos word" to search for commands related to "word"...
>> Reading symbols from python...(no debugging symbols found)...done.
>> (gdb) run test.py
>> Starting program: /home/estan/orexplore/pyenv/gui-demo/bin/python test.py
>> [Thread debugging using libthread_db enabled]
>> Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
>> [New Thread 0x7fffeaba3700 (LWP 23551)]
>>
>> Program received signal SIGSEGV, Segmentation fault.
>> 0x0000000000493416 in PyDict_SetItem ()
>> (gdb) bt
>> #0  0x0000000000493416 in PyDict_SetItem ()
>> #1  0x00000000004cf691 in _PyObject_GenericSetAttrWithDict ()
>> #2  0x000000000042945b in ?? ()
>> #3  0x00000000004bae16 in PyEval_EvalFrameEx ()
>> #4  0x00000000004b7986 in PyEval_EvalCodeEx ()
>> #5  0x00000000004d3bb9 in ?? ()
>> #6  0x00000000004a4516 in PyObject_CallFunction ()
>> #7  0x00007ffff66ba44a in ?? () from
>> /usr/lib/python2.7/dist-packages/PyQt5/QtCore.so
>> #8  0x00007ffff66ba692 in ?? () from
>> /usr/lib/python2.7/dist-packages/PyQt5/QtCore.so
>> #9  0x00007ffff6294534 in QMetaProperty::write(QObject*, QVariant
>> const&) const () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
>> #10 0x00007ffff62bceef in QObject::setProperty(char const*, QVariant
>> const&) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
>> #11 0x00007ffff220494b in QStyledItemDelegate::setEditorData(QWidget*,
>> QModelIndex const&) const () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #12 0x00007ffff27714c6 in ?? () from
>> /usr/lib/python2.7/dist-packages/PyQt5/QtWidgets.so
>> #13 0x00007ffff217f6ae in ?? () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #14 0x00007ffff217f8c8 in ?? () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #15 0x00007ffff217fb5c in QAbstractItemView::edit(QModelIndex const&,
>> QAbstractItemView::EditTrigger, QEvent*) () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #16 0x00007ffff2732b79 in ?? () from
>> /usr/lib/python2.7/dist-packages/PyQt5/QtWidgets.so
>> #17 0x00007ffff21838ec in
>> QAbstractItemView::mouseDoubleClickEvent(QMouseEvent*) () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #18 0x00007ffff2731feb in ?? () from
>> /usr/lib/python2.7/dist-packages/PyQt5/QtWidgets.so
>> #19 0x00007ffff1f5edf7 in QWidget::event(QEvent*) () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #20 0x00007ffff205d85e in QFrame::event(QEvent*) () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #21 0x00007ffff218338b in QAbstractItemView::viewportEvent(QEvent*) ()
>> from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #22 0x00007ffff273243b in ?? () from
>> /usr/lib/python2.7/dist-packages/PyQt5/QtWidgets.so
>> #23 0x00007ffff6285b6c in
>> QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*,
>> QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
>> #24 0x00007ffff1f1b9bc in QApplicationPrivate::notify_helper(QObject*,
>> QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #25 0x00007ffff1f215a9 in QApplication::notify(QObject*, QEvent*) ()
>> from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #26 0x00007ffff280b30e in ?? () from
>> /usr/lib/python2.7/dist-packages/PyQt5/QtWidgets.so
>> #27 0x00007ffff6285d7b in QCoreApplication::notifyInternal(QObject*,
>> QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
>> #28 0x00007ffff1f204b2 in
>> QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*,
>> QWidget*, QWidget**, QPointer<QWidget>&, bool) () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #29 0x00007ffff1f78f6b in ?? () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #30 0x00007ffff1f7b52b in ?? () from
>> /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #31 0x00007ffff1f1b9dc in QApplicationPrivate::notify_helper(QObject*,
>> QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #32 0x00007ffff1f20ea6 in QApplication::notify(QObject*, QEvent*) ()
>> from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
>> #33 0x00007ffff280b30e in ?? () from
>> /usr/lib/python2.7/dist-packages/PyQt5/QtWidgets.so
>> #34 0x00007ffff6285d7b in QCoreApplication::notifyInternal(QObject*,
>> QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
>> #35 0x00007ffff1969958 in
>> QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*)
>> () from /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5
>> #36 0x00007ffff196b2b5 in
>> QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent*)
>> () from /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5
>> #37 0x00007ffff194f228 in
>> QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>)
>> () from /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5
>> #38 0x00007fffedb870b0 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5XcbQpa.so.5
>> #39 0x00007ffff4f5dff7 in g_main_context_dispatch () from
>> /lib/x86_64-linux-gnu/libglib-2.0.so.0
>> #40 0x00007ffff4f5e250 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
>> #41 0x00007ffff4f5e2fc in g_main_context_iteration () from
>> /lib/x86_64-linux-gnu/libglib-2.0.so.0
>> #42 0x00007ffff62dc4ef in
>> QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>)
>> () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
>> #43 0x00007ffff628350a in
>> QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from
>> /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
>> #44 0x00007ffff628b5ec in QCoreApplication::exec() () from
>> /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
>> #45 0x00007ffff27d3fbb in ?? () from
>> /usr/lib/python2.7/dist-packages/PyQt5/QtWidgets.so
>> #46 0x00000000004ba14a in PyEval_EvalFrameEx ()
>> #47 0x00000000004bf10f in PyEval_EvalFrameEx ()
>> #48 0x00000000004b7986 in PyEval_EvalCodeEx ()
>> #49 0x00000000004e8f3f in ?? ()
>> #50 0x00000000004e3b02 in PyRun_FileExFlags ()
>> #51 0x00000000004e22e6 in PyRun_SimpleFileExFlags ()
>> #52 0x0000000000490fe1 in Py_Main ()
>> #53 0x00007ffff7811a40 in __libc_start_main (main=0x4909f0 <main>,
>> argc=2, argv=0x7fffffffdda8, init=<optimized out>, fini=<optimized
>> out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdd98) at
>> libc-start.c:289
>> #54 0x0000000000490919 in _start ()
>> (gdb) c
>> Continuing.
>> [Thread 0x7fffeaba3700 (LWP 23551) exited]
>>
>> Program terminated with signal SIGSEGV, Segmentation fault.
>> The program no longer exists.
>> (gdb)
>>
>>
>> Anyone know what might be going wrong here? Note that I've neglected
>> to implement setEditorData and setModelData in my custom delegate
>> (MyCoolDelegate), instead trying to rely on their default behavior,
>> which is (from QStyleItemDelegate docs):
>>
>>     "The default implementation stores the data in the editor widget's
>> user property."
>>
>> for setEditorData, and
>>
>>     "The default implementation gets the value to be stored in the
>> data model from the editor widget's user property."
>>
>> So what I've done is set the user property on MyCoolEditor be the dict
>> which it is currently editing (I use the dict wrapped in a QVariant as
>> the EditRole data).
>>
>> (The editor itself is just supposed to be a bunch of sliders for
>> changing the values associated with the keys in the dict.)
>>
>> I'm really stumped and don't quite know how to debug this, so grateful
>> for any advice.
>>
>> Best regards,
>> Elvis


More information about the PyQt mailing list