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

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


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.

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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: working.png
Type: image/png
Size: 10779 bytes
Desc: not available
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20160211/076a71e4/attachment-0001.png>


More information about the PyQt mailing list