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

Elvis Stansvik elvstone at gmail.com
Thu Feb 11 16:52:47 GMT 2016


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: cool_table.png
Type: image/png
Size: 11155 bytes
Desc: not available
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20160211/826d91c9/attachment-0001.png>


More information about the PyQt mailing list