Solved crash in QListWidget::dropEvent when I add a UserRole to my QListItemWidget :'(
-
Even better : following this link on how to serialize a pointer in QDataStream, here is a way to directly transfer a pointer using its address and a reinterpret_cast. That's much more efficient and a great use of reinterpret_cast that we normally never use...
Some might say it's dodgy but there is a huge gain in performance as we don't have to find back an object but can transmit it straight away through its address.So no need to pass the id, we can cast the pointer address to a qulonglong and retrieve it in the receiver.
Here is the sending side:
void ItemOfferingListWidget::startDrag(Qt::DropActions supportedActions) { Q_UNUSED(supportedActions) QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); for (QListWidgetItem *item : selectedItems()) { Element *elem = item->data(Qt::UserRole).value<Element*>(); qulonglong ptrval(*reinterpret_cast<qulonglong *>(&elem)); stream << row(item) << ptrval; } mimeData->setData("application/x-qabstractitemmodeldatalist", encodedData); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction); }
And now the receiver (dropEvent)
void ItemOfferingListWidget::dropEvent(QDropEvent *event) { qDebug() << "[MB_TRACE][ItemOfferingListWidget::dropEvent]" << event->mimeData()->formats(); if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist")) { event->accept(); if (event->source() == this) { if (_allowIternalMove) { QByteArray encoded = event->mimeData()->data("application/x-qabstractitemmodeldatalist"); QDataStream stream(&encoded, QIODevice::ReadOnly); QMap<int, QListWidgetItem *> movedItems; while (!stream.atEnd()) { int row; qulonglong ptrval; stream >> row >> ptrval; // Element *elem = *reinterpret_cast<Element **>(&ptrval); // qDebug() << "Receiving elem: " << elem->getName() << " (row in original list: " << row << ")"; movedItems.insert(row, item(row)); } if (movedItems.size()) { int rowToInsert = row(itemAt(event->pos())); int index = 0; auto it = movedItems.begin(); short direction = 1; if (it.key() < rowToInsert) direction = -1; for (; it != movedItems.end() ; ++it) { QListWidgetItem *item = it.value(); takeItem(row(item)); insertItem(rowToInsert+index*direction, item); ++index; } } } } else { QByteArray encoded = event->mimeData()->data("application/x-qabstractitemmodeldatalist"); QDataStream stream(&encoded, QIODevice::ReadOnly); while (!stream.atEnd()) { int row; qulonglong ptrval; stream >> row >> ptrval; Element *elem = *reinterpret_cast<Element **>(&ptrval); qDebug() << "Receiving elem : " << elem->getName(); QListWidgetItem *item = new QListWidgetItem(elem->getName()); item->setData(Qt::UserRole, QVariant::fromValue(elem)); item->setIcon(IconFactory::getInstance()->getElementTypeIcon(elem->getElementType())); addItem(item); } if (!_allowIternalMove) sortItems(); emit draggedItemsHaveBeenTakenByOtherList(); } emit internalListChanged(); } else { event->ignore(); } }
-
Last update on the manual implementation of the internal move of items.
I realized my first implementation was wrong when we were dragging some items up as it was inverting the order when we were dropping them.
Here is a completely working version, we've to iterate the selected items in the opposite way depending the direction we're moving the items:void ItemOfferingListWidget::dropEvent(QDropEvent *event) { qDebug() << "[MB_TRACE][ItemOfferingListWidget::dropEvent]" << event->mimeData()->formats(); if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist")) { event->accept(); if (event->source() == this) { if (_allowIternalMove) { QByteArray encoded = event->mimeData()->data("application/x-qabstractitemmodeldatalist"); QDataStream stream(&encoded, QIODevice::ReadOnly); QMap<int, QListWidgetItem *> movedItems; while (!stream.atEnd()) { int row; qulonglong ptrval; stream >> row >> ptrval; // Element *elem = *reinterpret_cast<Element **>(&ptrval); // qDebug() << "Receiving elem: " << elem->getName() << " (row in original list: " << row << ")"; movedItems.insert(row, item(row)); } if (movedItems.size()) { int rowToInsert = row(itemAt(event->pos())); int index = 0; if (movedItems.firstKey() < rowToInsert) { auto it = movedItems.begin(); for (; it != movedItems.end() ; ++it) { QListWidgetItem *item = it.value(); takeItem(row(item)); insertItem(rowToInsert+index, item); ++index; } } else { auto it = movedItems.end(); do { --it; QListWidgetItem *item = it.value(); takeItem(row(item)); insertItem(rowToInsert-index, item); ++index; } while (it != movedItems.begin()); } } } } else { QByteArray encoded = event->mimeData()->data("application/x-qabstractitemmodeldatalist"); QDataStream stream(&encoded, QIODevice::ReadOnly); while (!stream.atEnd()) { int row; qulonglong ptrval; stream >> row >> ptrval; Element *elem = *reinterpret_cast<Element **>(&ptrval); qDebug() << "Receiving elem : " << elem->getName(); QListWidgetItem *item = new QListWidgetItem(elem->getName()); item->setData(Qt::UserRole, QVariant::fromValue(elem)); item->setIcon(IconFactory::getInstance()->getElementTypeIcon(elem->getElementType())); addItem(item); } if (!_allowIternalMove) sortItems(); emit draggedItemsHaveBeenTakenByOtherList(); } emit internalListChanged(); } else { event->ignore(); } }
-
Serialisation to mime of model data is already a feature of Qt, unfortunately it has never been documented.
You can check this post on how to implement the drop event (it usesQGraphicsSceneDragDropEvent
but it's identical withQDropEvent
) -
@VRonin
Thanks for this information. I've found something like this on stackoverflow (reading the QMap<int,QVariant> from the DataStream of the mime data) but I didn't manage to make it work, that is why I was using my own Drag event.I've just tried again and it is still not working but I've a log in the debugger saying:
QVariant::save: unable to save type 'Element*' (type id: 1052).
Normally I've no problem to read/write Element* in a QVariant has in the header of the Element class I've Q_DECLARE_METATYPE( Element* )
So this seems to be a problem within the QListWidget? I've the same type id that when I've my first crash (except now the app is not crashing anymore)
Here is my new code (I removed the override of startDrag)
void ItemOfferingListWidget::dropEvent(QDropEvent *event) { qDebug() << "[MB_TRACE][ItemOfferingListWidget::dropEvent]" << event->mimeData()->formats(); if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist")) { event->accept(); if (event->source() == this) { if (_allowIternalMove) { setDragDropMode(InternalMove); QListWidget::dropEvent(event); setDragDropMode(DragDrop); } else { const QByteArray mimeData = event->mimeData()->data("application/x-qabstractitemmodeldatalist"); QDataStream mimeReader(mimeData); int row,col; QMap<int,QVariant> modelData; for(;;){ mimeReader.startTransaction(); mimeReader >> row >> modelData; if(!mimeReader.commitTransaction()) break; const auto userRoleIter = modelData.constFind(Qt::UserRole); if(userRoleIter != modelData.cend()) { Element *elem = userRoleIter->value<Element*>(); qDebug() << "Receiving elem: " << elem->getName() << " (row in original list: " << row << ")"; } else{ qDebug() << "No UserRole..."; } } } } } else { event->ignore(); } }
I'm getting the error message 'QVariant::save: unable to save type 'Element*' (type id: 1052)' before arriving to the Drop event. In the drop event it seems I receive something empty. I've no log in the loop.
Any idea why? do I have to rebuild the Drag manually?
-
The error comes from the fact that you have to register the metatype, not just declare it.
The real issue here is that you are passing pointers around without a clear definition of who owns what. Be very careful not to leak memory or having double deletions
-
@VRonin
Well it's kind of obscure to me how/when and mainly where we need to register the metatype.
I've no issue anywhere else putting, reading Element* or QSet<Element*> in a QVariant, simply having declared those types.
Why would it be different in this case for QListWidgetItem?
I've added the registration in my constructor of my QListWidget but I still have the same error "QVariant::save: unable to save type 'Element*' (type id: 1052)."Here is what I've done:
#include "Model/Element.h" ItemOfferingListWidget::ItemOfferingListWidget(QWidget *parent) : QListWidget(parent), _allowIternalMove(true) { setSelectionMode(QAbstractItemView::ExtendedSelection); setDragDropMode(QAbstractItemView::DragDrop); setDropIndicatorShown(true); qRegisterMetaType<Element *> (); // setDragDropOverwriteMode(true); }
For the use of raw pointers, I know it could be dangerous, but the architecture of the project is clear : I've a unique manager (Singleton) for each kind of object that is in charge of construction / deletion.
-
@mbruel said in crash in QListWidget::dropEvent when I add a UserRole to my QListItemWidget :'(:
how/when and mainly where we need to register the metatype
Normally you call that method inside the
main()
so you can forget about it for the rest of your program.Why would it be different in this case for QListWidgetItem?
It isn't
I've a unique manager (Singleton)
http://lengthily.blogspot.com/2017/02/one-size-fits-all.html
Also keep in mind that Qt-parent child relationship does not know about your design so it might delete stuff you didn't intend to
-
There was a topic recently about this issue here:
https://forum.qt.io/topic/85798/save-qset-using-qsettings/17Seems you need to implement the stream operators << >>
This code works without error:
class FooClass {}; Q_DECLARE_METATYPE(FooClass*) QDataStream &operator<<(QDataStream &out, const FooClass*& foo) { out<< foo; return out; } QDataStream &operator>>(QDataStream &in, FooClass*& foo) { in>>foo; return in; } int main(int argc, char *argv[]) { qRegisterMetaType<FooClass *> ("fooclasspt"); FooClass foo; QVariant v=QVariant::fromValue(&foo); FooClass* fooPt=v.value<FooClass*>(); qDebug()<<&foo<<fooPt; // same address qRegisterMetaTypeStreamOperators<FooClass *> (); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); stream<<v; return 0; }
-
@mpergand
hmm, trying code. it does not seem to call
the operators ? -
You're right, but :
If the stream operators are not implemented you've got the QVariant::save: unable to save type error
more over, if you change :
QDataStream &operator<<(QDataStream &out, const FooClass*& foo)
to
QDataStream &operator<<(QDataStream &out, const FooClass* foo)without a reference to a pointer, it crashes here.
-
@mpergand
yeah, its pretty odd why one cant step into the function then.
( since it wants it )I assume at run time, it maps to others overloads then.
update:
in any case, steaming it out/in and using it seems to work :) -
I'm unable to retreive the variant.
QDataStream out(&encoded, QIODevice::ReadOnly); QVariant v2; out>>v2; QDataStream &operator>>(QDataStream &in, FooClass*& foo) { in>>foo; -> It crashes here
-
@VRonin said in crash in QListWidget::dropEvent when I add a UserRole to my QListItemWidget :'(:
http://lengthily.blogspot.com/2017/02/one-size-fits-all.html
Also keep in mind that Qt-parent child relationship does not know about your design so it might delete stuff you didn't intend toWell, I said manager to not enter in the details... but basically it's a subclass of QApplication that owns my Model (which own all the Elements). It also owns the HMI if there is one.
class MyApplication : public QApplication { private: const AppMode _mode; AppLang _lang; Model *_model; MainWindow *_hmi; static const QMap<AppLang, QString> sAppLanguages; static const QString sTranslationsFileName; static const QString sVersion; static const QString sAppName; ... };
However I use singleton for some thread safe objects as a convenient way to be able to get the instance from anywhere in the code. Mainly for to get my LogService, the IconFactory, the UndoStack and the IOService.
I think it has some sense and it is practical to be able to do such calls:Iconfactory::getInstance()->getElementTypeIcon(elem->getElementType); UndoStack::getInstance()->beginMacro(macroLabel);
Here is what the Singleton class I use:
#ifndef SINGLETON_H #define SINGLETON_H template <typename T> class Singleton { protected: // Constructor /Destructor Singleton() = default; virtual ~Singleton() = default; public: Singleton(const Singleton &other) = delete; Singleton(const Singleton &&other) = delete; Singleton & operator=(const Singleton &other) = delete; Singleton & operator=(const Singleton &&other) = delete; // Public Interface static T *getInstance(){ if (!_singleton) { _singleton = new T; } return (static_cast<T*> (_singleton)); } static void kill() { if (_singleton) { delete _singleton; _singleton = nullptr; } } private: // Unique instance static T *_singleton; }; template <typename T> T *Singleton<T>::_singleton; #endif // SINGLETON_H
Rq: for Services, I generally derive from this PureStaticClass
class PureStaticClass { PureStaticClass() = delete; PureStaticClass(const PureStaticClass &other) = delete; PureStaticClass(const PureStaticClass &&other) = delete; PureStaticClass & operator=(const PureStaticClass &other) = delete; PureStaticClass & operator=(const PureStaticClass &&other) = delete; };
@VRonin said in crash in QListWidget::dropEvent when I add a UserRole to my QListItemWidget :'(:
Also keep in mind that Qt-parent child relationship does not know about your design so it might delete stuff you didn't intend to
Yes I'm aware of that, I do it only for my Data Object that aren't updated directly by any QT objects. Those objects will have a pointer on them to extract some data but never delete it.
-
@mpergand
I've tried that and it doesn't work with work on my env. I'm still getting the issue:QVariant::save: unable to save type 'Element*' (type id: 1025).
Here is my code:
#include <QDataStream> class Element; inline QDataStream &operator>>(QDataStream &in, Element*& elem) { in >> elem; return in; } inline QDataStream &operator<<(QDataStream &in, Element*& elem) { in << elem; return in; }
if I override the startDrag event like this:
void ItemOfferingListWidget::startDrag(Qt::DropActions supportedActions) { // return QListWidget::startDrag(supportedActions); Q_UNUSED(supportedActions) QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); for (QListWidgetItem *item : selectedItems()) { QMap<int, QVariant> data; data[Qt::UserRole] = item->data(Qt::UserRole); // Element *elem = item->data(Qt::UserRole).value<Element*>(); // qulonglong ptrval(*reinterpret_cast<qulonglong *>(&elem)); stream << row(item) << data; // stream << row(item) << elem->getId() << elem->getElementTypeName(); } mimeData->setData("application/x-qabstractitemmodeldatalist", encodedData); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction); }
The error message kicks in when I try to write the data in the stream.
I'm jumping intemplate <class Key, class T> inline QDataStream &operator<<(QDataStream &s, const QMap<Key, T> &map) { return QtPrivate::writeAssociativeContainer(s, map); }
but never in my overriden function :(
-
My fault, I totally forgot you also need qRegisterMetaTypeStreamOperators
P.S.
I still don't like your design but at the end of the day if you are happy with it and it works, go for it -
@VRonin said in crash in QListWidget::dropEvent when I add a UserRole to my QListItemWidget :'(:
My fault, I totally forgot you also need qRegisterMetaTypeStreamOperators
Ok, now I don't have the "QVariant::save: unable to save type 'Element*' (type id: 1025).
" message but the app is crashing in an infinite loop in:inline QDataStream &operator>>(QDataStream &in, Element*& elem) { in >> elem; return in; }
on the first line...
Thread 1 (Thread 0x7ffff7fb8140 (LWP 16621)): #0 0x000055555557fd65 in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #1 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #2 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #3 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #4 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #5 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #6 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #7 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #8 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 ... #261515 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #261516 0x000055555557fd6a in operator>> (in=..., elem=@0x7fffffffa280: 0x0) at ../src_cosi7/Model/Element.h:370 No locals. #261517 0x0000555555580182 in QtMetaTypePrivate::QMetaTypeFunctionHelper<Element*, true>::Load (stream=..., t=0x7fffffffa280) at /opt/Qt/5.10.1/gcc_64/include/QtCore/qmetatype.h:777 No locals. #261518 0x00007ffff66c596a in QMetaType::load(QDataStream&, int, void*) () from /opt/Qt/5.10.1/gcc_64/lib/libQt5Core.so.5 No symbol table info available. #261519 0x00007ffff66ed12c in QVariant::load(QDataStream&) () from /opt/Qt/5.10.1/gcc_64/lib/libQt5Core.so.5 No symbol table info available. #261520 0x00007ffff66ed25f in operator>>(QDataStream&, QVariant&) () from /opt/Qt/5.10.1/gcc_64/lib/libQt5Core.so.5 No symbol table info available. #261521 0x00005555555ef34e in QtPrivate::readAssociativeContainer<QMap<int, QVariant> > (s=..., c=...) at /opt/Qt/5.10.1/gcc_64/include/QtCore/qdatastream.h:285 k = 256 t = {d = {data = {c = 0 '\000', uc = 0 '\000', s = 0, sc = 0 '\000', us = 0, i = 0, u = 0, l = 0, ul = 0, b = false, d = 0, f = 0, real = 0, ll = 0, ull = 0, o = 0x0, ptr = 0x0, shared = 0x0}, type = 1025, is_shared = 0, is_null = 0}} i = 0 stateSaver = {stream = 0x7fffffffa310, oldStatus = QDataStream::Ok} n = 3 #261522 0x00005555555eebff in operator>><int, QVariant> (s=..., map=...) at /opt/Qt/5.10.1/gcc_64/include/QtCore/qdatastream.h:436 No locals. #261523 0x00005555555ed678 in ItemOfferingListWidget::dropEvent (this=0x5555562138d0, event=0x7fffffffa8f0) at ../src_cosi7/hmi/GUI/widget/ItemOfferingListWidget.cpp:103 userRoleIter = {i = 0x7fffffffa480} mimeData = {d = 0x5555563d7f70} modelData = {d = 0x7ffff67e4940 <QMapDataBase::shared_null>} mimeReader = {d = {d = 0x5555563a35c0}, dev = 0x55555617f260, owndev = true, noswap = false, byteorder = QDataStream::BigEndian, ver = 17, q_status = QDataStream::Ok} row = 0 col = 0 __PRETTY_FUNCTION__ = "virtual void ItemOfferingListWidget::dropEvent(QDropEvent*)" #261524 0x00007ffff7735018 in QWidget::event(QEvent*) () from /opt/Qt/5.10.1/gcc_64/lib/libQt5Widgets.so.5 No symbol table info available.
-
That's because your code is an obvious infinite recursion. You have to fix it
-
Yes, there're something wrong with the >> operator.
Anyway, I think it's a oversized process just to save a single pointer ...
QDataStream allows to save/load a pointer to char:
operator<<(const char *s) operator>>(char *&s)
So, you simply need to use reinterpret_cast, back and forth for your custom class.
Et voilĂ -
Well I nearly lost a day on this so I think I'm just going to stay with my working version where I create my own QDrag and put by hand the reinterpret_cast of the address of my pointer.
It works well and I don't need the register and registerforstream of my metatype (neither to reimplement the stream operator on DataStream) -
@mpergand said in crash in QListWidget::dropEvent when I add a UserRole to my QListItemWidget :'(:
Yes, there're something wrong with the >> operator.
Anyway, I think it's a oversized process just to save a single pointer ...
QDataStream allows to save/load a pointer to char:
operator<<(const char *s) operator>>(char *&s)
So, you simply need to use reinterpret_cast, back and forth for your custom class.
Et voilĂYep that is what I'm doing but through a qulonglong instead of the char*
That's because your code is an obvious infinite recursion. You have to fix it
I pasted your code from the other post thinking the break would end the for loop. (I don't know the transaction objects you're using)
I've updated to be:const QByteArray mimeData = event->mimeData()->data("application/x-qabstractitemmodeldatalist"); QDataStream mimeReader(mimeData); int row, col; QMap<int,QVariant> modelData; while (!mimeReader.atEnd()) { mimeReader >> row >> col >> modelData; qDebug() << "Receiving row: " << row << " modelData.size: " << modelData.size(); const auto userRoleIter = modelData.constFind(Qt::UserRole); if(userRoleIter != modelData.cend()) { Element *elem = userRoleIter->value<Element*>(); qDebug() << "Receiving elem: " << elem->getName() << " (row in original list: " << row << ")"; } else{ qDebug() << "No UserRole..."; } }
but I still have the same issue...
I don't see the obvious recursive loop :(
this code is working well if I don't use the Qmap<int, QVariant> but build the QDrag myself.
Could you let me know what's wrong plz