Hello everyone,
I'm implementing a QAbstractListModel derived class in PyQt that uses a
custom data type.
However, in QAbstractListModel.setData(index, value, role =
Qt.EditRole), I get passed value as a QVariant, and I cannot convert it
to my custom type:
value.toPyObject()
yields: "TypeError: unable to convert a QVariant back to a Python object"
Please find the short code (<300 lines, adpated from the StarDelegate
example of Qt) attached.
The problem occurs when using drag/drop to reorder the items inside the
view. You'll see the above error printed, and the dropped item is
replaced by a new instance of StarRating, which has 5 stars by default.
(Another problem I have with this example is that dragging items on top
of empty space in the QListView deletes the item, though I specified
Qt.MoveOnly in supportedDropActions... Maybe you happen to know what the
problem is here as well?)
How can I fix this?
Thorben
P.S.: I posted this to stackoverflow
(http://stackoverflow.com/questions/6226830/pyqt-qabstractlistmodel-setdata-gets-passed-a-qvariant-that-cannot-be-converted)
but believe this list is a better place for that question...
#make the program quit on Ctrl+C
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from math import sin, cos
class StarDelegate(QStyledItemDelegate):
def __init__(self, parent = None):
QStyledItemDelegate.__init__(self, parent)
self.currentIndex = -1
self.updateSize = True
def paint(self, painter, option, index):
self.updateSize = False
if option.state & QStyle.State_Selected:
modelIndex = index.row()
if modelIndex != self.currentIndex:
model = index.model()
self.currentIndex = modelIndex
self.updateSize = True
model.wantsUpdate()
starRating = index.data().toPyObject()
if isinstance(starRating, StarRating):
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
starRating.paint(painter, option.rect, option.palette, 'ReadOnly')
else:
QStyledItemDelegate.paint(self, painter, option, index)
def sizeHint(self, option, index):
starRating = index.data().toPyObject()
if isinstance(starRating, StarRating):
r = starRating.sizeHint()
if self.currentIndex == index.row():
r.setHeight(2*r.height())
return r
else:
return QStyledItemDelegate.sizeHint(self, option, index)
def createEditor(self, parent, option, index):
starRating = index.data().toPyObject()
if isinstance(starRating, StarRating):
editor = StarEditor(parent)
editor.editingFinished.connect(self.commitAndCloseEditor)
return editor
else:
QStyledItemDelegate.createEditor(self, parent, option, index)
def setEditorData(self, editor, index):
starRating = index.data().toPyObject()
if isinstance(starRating, StarRating):
starEditor = editor
starEditor.setStarRating(starRating)
else:
QStyledItemDelegate.setEditorData(self, editor, index)
def setModelData(self, editor, model, index):
starRating = index.data().toPyObject()
if isinstance(starRating, StarRating):
starEditor = editor
model.setData(index, starEditor.starRating())
else:
QStyledItemDelegate.setModelData(self, editor, model, index)
def commitAndCloseEditor(self):
editor = sender()
self.commitData.emit(editor)
self.closeEditor.emit(editor)
class StarEditor(QWidget):
editingFinished = pyqtSignal()
def __init__(self, parent = None):
QWidget.__init__(self, parent)
print "StarEditor"
def sizeHint(self):
r = self.myStarRating.sizeHint()
r.setHeight(2*r.height())
return r
def setStarRating(self, starRating):
self.myStarRating = starRating
self.setMouseTracking(True);
self.setAutoFillBackground(True);
def starRating(self):
return self.myStarRating
def paintEvent(self, e):
painter = QPainter(self)
self.myStarRating.paint(painter, self.rect(), self.palette(), 'Editable')
def mouseMoveEvent(self, event):
star = self.starAtPosition(event.x())
if star != self.myStarRating.starCount() and star != -1:
self.myStarRating.setStarCount(star)
self.update()
def mouseReleaseEvent(self, event):
self.editingFinished.emit()
def starAtPosition(self, x):
star = (x / (self.myStarRating.sizeHint().width() / self.myStarRating.maxStarCount())) + 1;
if star <= 0 or star > self.myStarRating.maxStarCount():
return -1
return star
class StarRating:
def __init__(self, starCount = -1, maxStarCount = 5):
self.PaintingScaleFactor = 20
self.myStarCount = starCount;
self.myMaxStarCount = maxStarCount;
self.starPolygon = QPolygonF()
self.diamondPolygon = QPolygonF()
self.starPolygon.append(QPointF(1.0, 0.5))
for i in range(1,5):
self.starPolygon.append(QPointF(0.5 + 0.5 * cos(0.8 * i * 3.14), 0.5 + 0.5 * sin(0.8 * i * 3.14)))
self.diamondPolygon.append(QPointF(0.4, 0.5))
self.diamondPolygon.append(QPointF(0.5, 0.4))
self.diamondPolygon.append(QPointF(0.6, 0.5))
self.diamondPolygon.append(QPointF(0.5, 0.6))
self.diamondPolygon.append(QPointF(0.4, 0.5))
def sizeHint(self):
return self.PaintingScaleFactor * QSize(self.myMaxStarCount, 1)
def starCount(self):
return self.myStarCount
def maxStarCount(self):
return self.myMaxStarCount
def setStarCount(self, starCount):
self.myStarCount = starCount
def setMaxStarCount(self, maxStarCount):
self.maxStarCount = maxStarCount
def paint(self, painter, rect, palette, mode):
painter.save()
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setPen(Qt.NoPen)
if mode == 'Editable':
painter.setBrush(palette.highlight())
else:
painter.setBrush(palette.foreground())
yOffset = (rect.height() - self.PaintingScaleFactor) / 2
painter.translate(rect.x(), rect.y() + yOffset)
painter.scale(self.PaintingScaleFactor, self.PaintingScaleFactor)
for i in range(0, self.myMaxStarCount):
if i < self.myStarCount:
painter.drawPolygon(self.starPolygon, Qt.WindingFill)
elif mode == 'Editable':
painter.drawPolygon(self.diamondPolygon, Qt.WindingFill)
painter.translate(1.0, 0.0)
painter.restore()
class OverlayStackModel(QAbstractListModel):
def __init__(self, parent = None):
QAbstractListModel.__init__(self, parent)
self.overlayStack = [StarRating(1), StarRating(2), StarRating(3), StarRating(4), StarRating(5)]
def rowCount(self, parent = QModelIndex()):
if not parent.isValid():
return len(self.overlayStack)
return 0
def insertRows(self, row, count, parent = QModelIndex()):
if parent.isValid():
return False
beginRow = max(0,row)
endRow = min(row+count-1, len(self.overlayStack)-1)
self.beginInsertRows(parent, beginRow, endRow)
while(beginRow <= endRow):
self.overlayStack.insert(row, StarRating(5))
beginRow += 1
self.endInsertRows()
return True
def removeRows(self, row, count, parent = QModelIndex()):
if parent.isValid():
return False
if row+count <= 0 or row >= len(self.overlayStack):
return False
beginRow = max(0,row)
endRow = min(row+count-1, len(self.overlayStack)-1)
self.beginRemoveRows(parent, beginRow, endRow)
while(beginRow <= endRow):
del self.overlayStack[row]
beginRow += 1
self.endRemoveRows()
return True
def flags(self, index):
defaultFlags = Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
if index.isValid():
return Qt.ItemIsDragEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags
def supportedDropActions(self):
return Qt.MoveAction
def data(self, index, role):
if not index.isValid():
return None
if index.row() > len(self.overlayStack):
return None
if role == Qt.DisplayRole or role == Qt.EditRole:
return self.overlayStack[index.row()]
return None
def setData(self, index, value, role = Qt.EditRole):
#print "1.) '%s'" % value.toString()
#print "2.) '%d'" % value.toInt()
starRating = value
if not isinstance(value, StarRating):
print type(value)
#print value.toString()
print value.toInt()
starRating = value.toPyObject()
self.overlayStack[index.row()] = value
self.dataChanged.emit(index, index)
return True
def headerData(section, orientation, role = Qt.DisplayRole):
if role != Qt.DisplayRole:
return None
if orientation == Qt.Horizontal:
return QString("Column %1").arg(section)
else:
return QString("Row %1").arg(section)
def wantsUpdate(self):
self.layoutChanged.emit()
class ListView(QListView):
def __init__(self, parent = None):
QListView.__init__(self, parent)
self.setDragDropMode(self.InternalMove)
self.installEventFilter(self)
self.setDragDropOverwriteMode(False)
def eventFilter(self, sender, event):
#http://stackoverflow.com/questions/1224432/
#how-do-i-respond-to-an-internal-drag-and-drop-operation-using-a-qlistwidget
if (event.type() == QEvent.ChildRemoved):
self.onOrderChanged()
return False
def onOrderChanged(self):
print "ordering changed"
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
model = OverlayStackModel()
view = ListView()
view.setModel(model)
view.setItemDelegate(StarDelegate())
view.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked)
#view.setEditTriggers(QAbstractItemView.CurrentChanged)
view.show()
def onIndexesMoved(self, i):
print "indexes moved"
view.indexesMoved.connect(onIndexesMoved)
app.exec_()
_______________________________________________
PyQt mailing list PyQt@riverbankcomputing.com
http://www.riverbankcomputing.com/mailman/listinfo/pyqt