python - instalar - pyqt5-tools
Llamar a QColorDialog desencadenado desde el editor QStyledItemDelegate produce un bloqueo (0)
este script falla, al cerrar el QColorDialog, después de ser llamado desde una subclase QTableView, se desencadena desde una señal.
from collections import OrderedDict
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QColor,QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import (
QAbstractItemView, QApplication, QColorDialog, QComboBox, QDoubleSpinBox,
QGridLayout, QStyledItemDelegate, QSpinBox, QTableView, QLineEdit, QWidget
)
class Colors():
colors = OrderedDict()
colorNames = OrderedDict()
for colorName in QColor.colorNames():
color = QColor(colorName)
rgb = color.getRgb()[:3]
colors[rgb] = color
colorNames[rgb] = colorName
@classmethod
def updateColor(cls, rgbstr):
try:
rgb, name = cls.str2rgb(rgbstr)
except ValueError as e:
print(''Colors.updateColor: Invalid color value %s: %s'' % (rgbstr, e))
raise
else:
color = QColor(*rgb)
if rgb not in cls.colors:
cls.colors[rgb] = color
cls.colors.move_to_end(rgb, last = False)
if name:
cls.colorNames[rgb] = name
return cls.colorLabel(color), color
@classmethod
def lookupColor(cls, color):
rgb = color.getRgb()[:3]
name = cls.colorNames.get(rgb, '''')
return rgb, name
@classmethod
def colorLabel(cls, color):
rgb, name = cls.lookupColor(color)
label = cls.rgb2str(rgb)
if name:
label += " (%s)" % name
return label
@staticmethod
def rgb2str(rgb):
return '',''.join(map(str, rgb))
@staticmethod
def rgb2name(rgb):
return ''#%02x%02x%02x'' % (rgb)
@staticmethod
def str2rgb(rgbstr):
if not rgbstr:
raise ValueError(''Invalid RGB tuple: %r'' % rgbstr)
name = ''''
if '' '' in rgbstr:
rgbstr, name = rgbstr.split('' '', 1)
# convert r,g,b string to int tuple
rgb = tuple(map(int, rgbstr.split('','')))[:3]
# sanity checks
if len(rgb) != 3:
raise ValueError(''Invalid RGB tuple length: %s'' % str(rgb))
if max(rgb) > 256:
raise ValueError(''Invalid values in RGB tuple: %s'' % str(rgb))
if min(rgb) < 0:
raise ValueError(''Invalid values in RGB tuple: %s'' % str(rgb))
if name and name[0] == ''('' and name[-1] == '')'':
name = name[1:-1]
#print(''str2rgb(%s): %s "%s"'' % (rgbstr, rgb, name))
return rgb, name
class ModelEditor():
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
editor.setFrame(False)
return editor
def setData(self, editor, value):
editor.setText(value)
def setEditorData(self, editor, index):
self.setData(editor, index.data(Qt.EditRole))
def getData(self, editor):
return editor.text()
def setModelData(self, editor, model, index):
model.setData(index, self.getData(editor), Qt.EditRole)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class IntRange(ModelEditor):
def __init__(self, start = None, stop = None, step = None):
self.start = start
self.stop = stop
self.step = step
def createEditor(self, parent, option, index):
editor = QSpinBox(parent)
editor.setFrame(False)
if self.start is not None:
editor.setMinimum(self.start)
if self.stop is not None:
editor.setMaximum(self.stop)
if self.step is not None:
editor.setSingleStep(self.step)
return editor
def setData(self, editor, value):
editor.setValue(value)
def getData(self, editor):
editor.interpretText()
return editor.value()
class FloatRange(IntRange):
def __init__(self, start = None, stop = None, step = None, decimals = None):
self.start = start
self.stop = stop
self.step = step
self.decimals = decimals
def createEditor(self, parent, option, index):
editor = QDoubleSpinBox(parent)
editor.setFrame(False)
if self.start is not None:
editor.setMinimum(self.start)
if self.stop is not None:
editor.setMaximum(self.stop)
if self.step is not None:
editor.setSingleStep(self.step)
if self.decimals is not None:
editor.setDecimals(self.decimals)
return editor
class Choice(ModelEditor):
def __init__(self, *choices):
self.choices = choices
def createEditor(self, parent, option, index):
editor = QComboBox(parent)
editor.setFrame(False)
editor.setAutoFillBackground(True)
for i, choice in enumerate(self.choices):
editor.insertItem(i, choice)
return editor
def setData(self, editor, value):
editor.setCurrentIndex(self.getIndex(value))
def getData(self, editor):
return editor.currentText()
def getIndex(self, value):
return self.choices.index(value)
class Bool(Choice):
def __init__(self, choices = None):
if choices is not None:
self.choices = choices
else:
self.choices = (''false'', ''true'')
class Text(ModelEditor):
pass
class Color(ModelEditor):
def __init__(self):
self.chooseColor = ''Choose Color...''
self.lastValue = None
self.lastColor = None
self.itemView = None
def createEditor(self, parent, option, index):
self.itemView = itemView = parent.parent()
print(itemView)
editor = QComboBox(parent)
editor.setFrame(False)
editor.setAutoFillBackground(True)
editor.setEditable(True)
for i, (rgb, color) in enumerate(Colors.colors.items()):
label = Colors.colorLabel(color)
editor.insertItem(i, label)
editor.setItemData(i, color, Qt.DecorationRole)
# add color chooser item
editor.insertSeparator(i+1)
editor.insertItem(i+2, self.chooseColor)
editor.currentIndexChanged[str].connect(self.currentTextChanged)
return editor
def currentTextChanged(self, value):
if value == self.chooseColor:
label = Colors.colorLabel(self.lastColor)
print(''selectColorDialog:'', label)
self.itemView.selectColorDialog.emit()
else:
try:
label, color = Colors.updateColor(value)
except ValueError:
return
else:
print(''currentTextChanged: %s, %s'' % (label, color))
self.lastColor = color
self.lastValue = label
def setData(self, editor, value):
print(''Color.setData(%s)'' % value)
try:
label, color = Colors.updateColor(value)
except ValueError:
return
idx = editor.findText(label)
if idx < 0:
editor.insertItem(0, label)
idx = 0
editor.setCurrentIndex(idx)
self.lastValue = label
self.lastColor = color
if 0:
editor.showPopup()
def getData(self, editor):
value = editor.currentText()
print(''Color.getData(%s)'' % value)
if value == self.chooseColor and self.lastValue:
value = self.lastValue
print(''Color.getData(%s)'' % value)
try:
label, color = Colors.updateColor(value)
except ValueError:
return
self.lastValue = label
self.lastColor = color
print(''Color.getData(%s) -> %s'' % (value, label))
return label
class ItemDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
cls = index.data(Qt.UserRole)
if isinstance(cls, ModelEditor):
return cls.createEditor(parent, option, index)
else:
return super(ItemDelegate, self).createEditor(parent, option, index)
def setEditorData(self, editor, index):
cls = index.data(Qt.UserRole)
if isinstance(cls, ModelEditor):
cls.setEditorData(editor, index)
else:
super(ItemDelegate, self).setEditorData(editor, index)
def setModelData(self, editor, model, index):
cls = index.data(Qt.UserRole)
if isinstance(cls, ModelEditor):
cls.setModelData(editor, model, index)
else:
super(ItemDelegate, self).setModelData(editor, model, index)
def updateEditorGeometry(self, editor, option, index):
cls = index.data(Qt.UserRole)
if isinstance(cls, ModelEditor):
cls.updateEditorGeometry(editor, option, index)
else:
super(ItemDelegate, self).updateEditorGeometry(editor, option, index)
class ColorItem(QStandardItem):
def __init__(self, color = None):
super(ColorItem, self).__init__()
if color is not None:
self.setData(color)
def setData(self, value, role = Qt.EditRole):
if role == Qt.EditRole:
try:
rgb, name = Colors.str2rgb(value)
except ValueError as e:
print(''Invalid color value: %s'' % e)
else:
color = QColor(*rgb)
# display color icon
super(ColorItem, self).setData(color, Qt.DecorationRole)
# replace color label
value = Colors.colorLabel(color)
super(ColorItem, self).setData(value, role)
def type(self):
return QStandardItem.UserType + 234
class TableView(QTableView):
selectColorDialog = pyqtSignal()
def __init__(self, model, parent = None):
super(TableView, self).__init__(parent)
self.setModel(model)
self.selectColorDialog.connect(self.colorDialog)
def colorDialog(self):
index = self.currentIndex()
print(''colorDialog:'', index, index.data(Qt.EditRole))
label, color = Colors.updateColor(index.data(Qt.EditRole))
color = QColorDialog.getColor(color, self)
if color.isValid():
rgb, name = Colors.lookupColor(color)
label = Colors.colorLabel(color)
print(label)
model = index.model()
print(model.data(index))
print(index.row(), index.column())
model.setData(index, label, Qt.EditRole)
class Window(QWidget):
def __init__(self, parent = None):
super(Window, self).__init__(parent)
self.setWindowTitle("Multiplex Item Delegate")
data = [
# label, value, class, args
[ ''intrange'', 0, IntRange, (-10, 10, 1)],
[ ''floatrange'', 0.0, FloatRange, (-1.0, 1.0, 0.1, 2)],
[ ''choice'', ''one'', Choice, (''one'', ''two'', ''three'', ''four'', ''hundred'')],
[ ''bool'', ''false'', Bool, ()],
[ ''text'', ''a text'', Text, ()],
[ ''color'', ''0,139,139'', Color, ()],
[ ''color'', ''255,215,0'', Color, ()],
]
# create model, view, and delegate
self.model = model = QStandardItemModel(len(data), 2)
tableView = TableView(model)
delegate = ItemDelegate()
tableView.setItemDelegate(delegate)
tableView.horizontalHeader().setStretchLastSection(True)
if 1:
tableView.setEditTriggers(
QAbstractItemView.EditKeyPressed |
QAbstractItemView.CurrentChanged |
QAbstractItemView.DoubleClicked |
QAbstractItemView.SelectedClicked)
else:
tableView.setEditTriggers(
QAbstractItemView.EditKeyPressed |
QAbstractItemView.DoubleClicked |
QAbstractItemView.SelectedClicked)
# fill model with data
for row, (label, value, cls, args) in enumerate(data):
# column 0
model.setItem(row, 0, QStandardItem(label))
# column 1
if cls == Color:
item = ColorItem()
else:
item = QStandardItem()
item.setData(value, Qt.EditRole)
item.setData(cls(*args), Qt.UserRole)
model.setItem(row, 1, item)
# finalize window
layout = QGridLayout()
layout.addWidget(tableView, 0, 0)
self.setLayout(layout)
self.resize(400, 300)
def result(self):
model = self.model
for row in range(model.rowCount()):
label = model.item(row, 0).data(Qt.EditRole)
value = model.item(row, 1).data(Qt.EditRole)
print(''[%s] %s: %s'' % (row, label, value))
if __name__ == ''__main__'':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
ret = app.exec_()
window.result()
sys.exit(ret)
Cuando se inicia, aparecen varios editores para diferentes tipos en la segunda columna. Los editores de color abren un QComboBox con varios colores predefinidos. El último elemento activa una señal en QTableView, llamando a QColorDialog.getColor ().
Las versiones anteriores intentaban abrir el QColorDialog directamente desde el editor QStyledItemDelegate, similar al código en esta edición , con el mismo resultado (segfault), a diferencia del problema vinculado.
Se podría observar que al abrir el diálogo, se desencadena un cambio en el contexto de la ventana, lo que da como resultado la destrucción del editor delegado de la vista de tabla. Por lo tanto, implementé la señal selectColorDialog, que opera de manera completamente independiente del editor delegado. El backtrace gdb apunta a algunos métodos QAccessible, activados desde QComboBox, pero no puedo ver la relación aquí. Otros intentos para hacer frente a este problema utilizaron QComboBoxes subclasificados con QLineEdits subclase, capturaron e ignoraron los permisos de salida, ocultación y cierre durante el tiempo de vida del diálogo de color, pero fallaron de manera similar.
Program received signal SIGSEGV, Segmentation fault.
0x0000000000cb9270 in ?? ()
(gdb) bt
#0 0x0000000000cb9270 in ?? ()
#1 0x00007ffff31c5e87 in QAccessible::queryAccessibleInterface(QObject*) () from /usr/lib64/libQt5Gui.so.5
#2 0x00007ffff31c6963 in QAccessibleEvent::accessibleInterface() const () from /usr/lib64/libQt5Gui.so.5
#3 0x00007ffff31c6d9f in QAccessible::updateAccessibility(QAccessibleEvent*) () from /usr/lib64/libQt5Gui.so.5
#4 0x00007fffeebc055a in QComboBoxPrivate::_q_emitCurrentIndexChanged(QModelIndex const&) ()
from /usr/lib64/libQt5Widgets.so.5
#5 0x00007fffeebc28f5 in QComboBoxPrivate::setCurrentIndex(QModelIndex const&) () from /usr/lib64/libQt5Widgets.so.5
#6 0x00007fffeebc2a56 in QComboBoxPrivate::_q_itemSelected(QModelIndex const&) () from /usr/lib64/libQt5Widgets.so.5
#7 0x00007fffeebc873d in QComboBox::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) ()
from /usr/lib64/libQt5Widgets.so.5
#8 0x00007ffff5ebb255 in QMetaObject::activate(QObject*, int, int, void**) () from /usr/lib64/libQt5Core.so.5
#9 0x00007fffeebbf61b in QComboBoxPrivateContainer::eventFilter(QObject*, QEvent*) ()
from /usr/lib64/libQt5Widgets.so.5
#10 0x00007ffff5e93351 in QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) ()
from /usr/lib64/libQt5Core.so.5
#11 0x00007fffeeaca265 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /usr/lib64/libQt5Widgets.so.5
#12 0x00007fffeead10fb in QApplication::notify(QObject*, QEvent*) () from /usr/lib64/libQt5Widgets.so.5
#13 0x00007fffef385dee in ?? () from /usr/lib64/python3.4/site-packages/PyQt5/QtWidgets.so
#14 0x00007ffff5e93485 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /usr/lib64/libQt5Core.so.5
#15 0x00007fffeeb29b4b in QWidgetWindow::event(QEvent*) () from /usr/lib64/libQt5Widgets.so.5
#16 0x00007fffeeaca28c in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /usr/lib64/libQt5Widgets.so.5
#17 0x00007fffeead11c0 in QApplication::notify(QObject*, QEvent*) () from /usr/lib64/libQt5Widgets.so.5
#18 0x00007fffef385dee in ?? () from /usr/lib64/python3.4/site-packages/PyQt5/QtWidgets.so
#19 0x00007ffff5e93485 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /usr/lib64/libQt5Core.so.5
#20 0x00007ffff31e9ec0 in QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyEvent*) ()
¿Alguna idea de alguien, qué está mal aquí? ¡Me encantaría llegar al fondo de esto!
Environment:
Python: 3.4.5
Sip: 4.19.2
Qt5: 5.8.0
PyQt5: 5.8.2
Linux: openSUSE/KDE4