PDA

View Full Version : Problem with QListView Drag & Drop



QtNewbieNeedsHelp
19th October 2013, 06:08
hello,

I have a problem with my QListView Drag & Drop. I am using Qt 5.1.

I have implemented a custom object and a custom model which I use with a standard QListView (if I substitute the list view with a QTreeView the code works perfectly).

When I set the view mode as IconMode the following happens :
If I drag & Drop an item in my list view it just disappears. The dropMimeData is never called.
While if I drag my item on top of another item it seems to work (i.e. the dragged item is being added as a child).
If the item is dragged into the empty space it just disappears.

if I set the view mode to ListMode the following happens :
If I drag an item over another item it just disappears, while if I drag it to the end of the list , the item is added at the bottom of the list.

so here is some code



#include <QApplication>
#include <QListView>
#include <QTreeView>

#include "ObjectBase.h"
#include "QtTreeModelBase.h"

int main(int argc, char **argv)
{
QApplication a(argc, argv);

QListView lw;

cObjectBase* pRoot = new cObjectBase(NULL);
pRoot->setObjectName("Root");

for(int i = 1; i < 10; ++i)
{
cObjectBase* pChild = new cObjectBase(pRoot);
pChild->setObjectName(QString("Item %1").arg(i));
}

QTreeModelBase* pTreeModelBase = new QTreeModelBase("Objects", pRoot, NULL);
pTreeModelBase->Reset(pRoot);

lw.setModel(pTreeModelBase);

lw.setDragEnabled(true);
lw.viewport()->setAcceptDrops(true);
lw.setDefaultDropAction(Qt::MoveAction);
lw.setDropIndicatorShown(true);
lw.setViewMode(QListView::ListMode);
lw.setDragDropMode(QAbstractItemView::InternalMove );

lw.show();

a.exec();
}


and here my object



#ifndef OBJECT_BASE_H
#define OBJECT_BASE_H

#include <QObject>
#include <QString>

#include <QtCore/QXmlStreamReader>
#include <QtCore/QXmlStreamWriter>

#include <QAbstractitemview>

class cObjectBase : public QObject
{
Q_OBJECT
public:
cObjectBase(cObjectBase* pParent);
virtual ~cObjectBase();

static const QString ObjectTag;

virtual void SetParent(cObjectBase* pParent, bool bConsiderChildren);
cObjectBase* GetParent()const;
void AddChild(cObjectBase* pChild);
void InsertChild(int row, cObjectBase *pChild);
void SwapChildren(int oldRow, int newRow);
cObjectBase* TakeChild(int row);
cObjectBase* TakeChild(cObjectBase* pChild);
int RowOfChild(cObjectBase* pChild) const;
int ChildCount(bool bIgnoreManager) const;
cObjectBase* ChildAt(int row) const;
void RemoveAllChildren(); //will not delete children

void WriteToXML(QXmlStreamWriter* pWriter, bool bSkipChildren, bool bDragDrop)const;
virtual void WriteAttributesToXML(QXmlStreamWriter* pWriter, bool bDragDrop)const;

void ReadFromXML(QXmlStreamReader* pReader, const QString& parentPath, bool bSkipChildren = false);
virtual void ReadAttributesFromXML(QXmlStreamReader* pReader, bool bDragDrop);

protected:
static const QString NameTag;

cObjectBase* mpParent;
QList<cObjectBase*> mChildren;
};

#endif


implemented as



#include "ObjectBase.h"
#include "QtTreeModelBase.h"


const QString cObjectBase::ObjectTag("Object");


cObjectBase::cObjectBase(cObjectBase* pParent) :
QObject(NULL), //not setting parents on purpose here
mpParent(pParent)
{
setObjectName("New Object");
if(pParent)
{
pParent->AddChild(this);
}
}

cObjectBase::~cObjectBase()
{
qDeleteAll(mChildren);
}

void cObjectBase::SetParent(cObjectBase* pParent, bool bConsiderChildren)
{
if(bConsiderChildren)
{
if(mpParent)
{
if(mpParent != pParent)
{
mpParent->TakeChild(this);
}
}

if(pParent)
{
pParent->AddChild(this);
}
}

mpParent = pParent;
}

cObjectBase* cObjectBase::GetParent()const
{
return mpParent;
}

cObjectBase* cObjectBase::TakeChild(int row)
{
cObjectBase* pItem = mChildren.takeAt(row);
Q_ASSERT(pItem);
pItem->mpParent = 0;
return pItem;
}

cObjectBase* cObjectBase::TakeChild(cObjectBase* pChild)
{
for (unsigned int row = 0; row < mChildren.count(); ++row)
{
cObjectBase* pMyChild = mChildren.at(row);

if(pMyChild == pChild)
{
return TakeChild(row);
}
}

return NULL;
}

void cObjectBase::InsertChild(int row, cObjectBase* pChild)
{
pChild->mpParent = this;
mChildren.insert(row, pChild);
}

void cObjectBase::AddChild(cObjectBase* pItem)
{
pItem->SetParent(this, false);
mChildren << pItem;
}

void cObjectBase::SwapChildren(int oldRow, int newRow)
{
mChildren.swap(oldRow, newRow);
}

int cObjectBase::RowOfChild(cObjectBase* pChild) const
{
return mChildren.indexOf(pChild);
}

int cObjectBase::ChildCount(bool bIgnoreManager) const
{
return mChildren.count();
}

cObjectBase* cObjectBase::ChildAt(int row) const
{
return mChildren.value(row);
}

void cObjectBase::RemoveAllChildren()
{
mChildren.clear();
}

const QString cObjectBase::NameTag("Name");

void cObjectBase::WriteToXML(QXmlStreamWriter* pWriter, bool bSkipChildren, bool bDragDrop) const
{
pWriter->writeStartElement(ObjectTag);

WriteAttributesToXML(pWriter, bDragDrop);

foreach(cObjectBase* pChild, mChildren)
{
pChild->WriteToXML(pWriter, bSkipChildren, bDragDrop);
}

pWriter->writeEndElement(); //end "Object"
}

void cObjectBase::WriteAttributesToXML(QXmlStreamWriter * pWriter, bool bDragDrop)const
{
pWriter->writeAttribute(NameTag, objectName());
}

void cObjectBase::ReadAttributesFromXML(QXmlStreamReade r* pReader, bool bDragDrop)
{
QString name = pReader->attributes().value(NameTag).toString();
setObjectName(name);
}


and now the model



#ifndef QTREEMODELBASE_H
#define QTREEMODELBASE_H

#include <QAbstractItemModel>

#include <QtCore/QXmlStreamReader>
#include <QtCore/QXmlStreamWriter>

class QMimeData;
class cObjectBase;

class QTreeModelBase : public QAbstractItemModel
{
Q_OBJECT

public:
QTreeModelBase(QString columnName, cObjectBase *root, QObject *parent = 0 );

void ResetRoot(cObjectBase* root);
cObjectBase* GetRoot()const { return mpRootItem; }

virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;

QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const;

int rowCount( const QModelIndex &parent = QModelIndex() ) const;
virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;

QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const;
virtual QModelIndex parent( const QModelIndex &index ) const;

cObjectBase* GetBaseObject(const QModelIndex& index ) const;

bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex());

QModelIndex GetObjectIndex(cObjectBase* pObjectBase, const QModelIndex& parent);
QModelIndex GetObjectIndex(cObjectBase* pObjectBase);

void Reset(cObjectBase* pObjectBase);

Qt::DropActions supportedDragActions() const { return Qt::MoveAction; }
Qt::DropActions supportedDropActions() const { return Qt::MoveAction; }

QStringList mimeTypes() const;
QMimeData *mimeData(const QModelIndexList &indexes) const;
bool dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent);

protected:
QString mColumnName;
cObjectBase *mpRootItem;

private:
void WriteObjectAndChildren(QXmlStreamWriter *writer, cObjectBase* pItem) const;
bool ReadObjects(QXmlStreamReader* pReader, cObjectBase* pItem);
};

#endif // OBJECTTREEMODEL_H


implemented as



#include <QMetaObject>
#include <QMimeData>

#include "QtTreeModelBase.h"
#include "ObjectBase.h"

const QString MimeType = "cObjectBase.xml";

QTreeModelBase::QTreeModelBase(QString columnName, cObjectBase *root, QObject *parent ) :
QAbstractItemModel( parent )
{
mColumnName = columnName;
mpRootItem = root;
}

void QTreeModelBase::ResetRoot(cObjectBase* root)
{
mpRootItem = root;
}

Qt::ItemFlags QTreeModelBase::flags(const QModelIndex &index) const
{
return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
}

QVariant QTreeModelBase::data( const QModelIndex &index, int role) const
{
if( !index.isValid() )
return QVariant();

cObjectBase* pItem = GetBaseObject(index);

if(!pItem)
{
return QVariant();
}

if( role == Qt::DisplayRole )
{
switch( index.column() )
{
case 0:
{
QString name = pItem->objectName();
return name;
}
break;
default:
break;
}
}

return QVariant();
}

QVariant QTreeModelBase::headerData(int section, Qt::Orientation orientation, int role ) const
{
if( role != Qt::DisplayRole || orientation != Qt::Horizontal )
return QVariant();

switch( section )
{
case 0:
return mColumnName;
default:
return QVariant();
}
}

int QTreeModelBase::rowCount(const QModelIndex &parent ) const
{
if (parent.isValid() && parent.column() != 0)
return 0;

cObjectBase* pParentItem = GetBaseObject(parent);
return pParentItem ? pParentItem->ChildCount(false) : 0;
}

int QTreeModelBase::columnCount(const QModelIndex &parent ) const
{
return 1; //parent.isValid() ? 1 : 0;
}

QModelIndex QTreeModelBase::index(int row, int column, const QModelIndex &parent ) const
{
if (!mpRootItem || row < 0 || column < 0 || column >= 1 || (parent.isValid() && parent.column() != 0))
return QModelIndex();

cObjectBase* pParentItem = GetBaseObject(parent);
Q_ASSERT(pParentItem);

cObjectBase* pItem = pParentItem->ChildAt(row);
if(pItem)
{
QModelIndex index = createIndex(row, column, pItem);
return index;
}

return QModelIndex();
}

QModelIndex QTreeModelBase::parent(const QModelIndex &index) const
{
if( !index.isValid() )
return QModelIndex();

cObjectBase* pChildItem = GetBaseObject(index);
if(pChildItem)
{
cObjectBase* pParentItem = static_cast<cObjectBase*>(pChildItem->GetParent());
if(pParentItem)
{
if(pParentItem == mpRootItem)
{
return QModelIndex();
}

cObjectBase* pGrandParentItem = static_cast<cObjectBase*>(pParentItem->GetParent());
if(pGrandParentItem)
{
int row = pGrandParentItem->RowOfChild(pParentItem);
return createIndex(row, 0, pParentItem);
}
}
}

return QModelIndex();
}

cObjectBase* QTreeModelBase::GetBaseObject(const QModelIndex& index ) const
{
if( !index.isValid() )
{
return mpRootItem;
}

return static_cast<cObjectBase*>( index.internalPointer() );
}

bool QTreeModelBase::removeRows(int row, int count, const QModelIndex &parent)
{
if(!mpRootItem)
{
return false;
}

cObjectBase* pItem = parent.isValid() ? GetBaseObject(parent) : mpRootItem;
beginRemoveRows(parent, row, row + count - 1);
for (int i = 0; i < count; ++i)
{
/*delete */pItem->TakeChild(row);
}
endRemoveRows();
layoutChanged();
return true;
}

QModelIndex QTreeModelBase::GetObjectIndex(cObjectBase* pObjectBase, const QModelIndex& parent)
{
int myRowCount = rowCount(parent);

for(int row = 0; row < myRowCount; ++row)
{
QModelIndex i = index(row, 0, parent);

if(i.isValid())
{
cObjectBase* pObject = GetBaseObject(i);
if(pObject == pObjectBase)
{
return i;
}

QModelIndex r = GetObjectIndex(pObjectBase, i);
if(r.isValid())
{
cObjectBase* pObj = GetBaseObject(i);
if(pObj == pObjectBase)
{
return r;
}
}
}
}

return QModelIndex();
}

QModelIndex QTreeModelBase::GetObjectIndex(cObjectBase* pObjectBase)
{
cObjectBase* pParentObject = pObjectBase->GetParent();

QList<cObjectBase*> parentsList;

while(pParentObject && pParentObject != mpRootItem)
{
parentsList.push_front(pParentObject);

pParentObject = pParentObject->GetParent();
}

QModelIndex parentIndex = QModelIndex();

if(!parentsList.isEmpty())
{
cObjectBase* pCurrentItem = (*parentsList.begin());
parentsList.pop_front();
parentIndex = GetObjectIndex(pCurrentItem, parentIndex);

while(pCurrentItem && !parentsList.isEmpty())
{
pCurrentItem = (*parentsList.begin());
parentsList.pop_front();
parentIndex = GetObjectIndex(pCurrentItem, parentIndex);
}
}

QModelIndex returnIndex = parentIndex;

return returnIndex;
}

void QTreeModelBase::Reset(cObjectBase* pObjectBase)
{
beginResetModel();
ResetRoot(pObjectBase);
endResetModel();
}


QStringList QTreeModelBase::mimeTypes() const
{
return QStringList() << MimeType;
}


QMimeData* QTreeModelBase::mimeData(const QModelIndexList &indexes) const
{
Q_ASSERT(indexes.count());
if (indexes.count() != 1)
{
return NULL;
}

cObjectBase* pItem = GetBaseObject(indexes.at(0));

if(pItem)
{
QMimeData* pMimeData = new QMimeData;
QByteArray xmlData;
QXmlStreamWriter writer(&xmlData);
WriteObjectAndChildren(&writer, pItem);
static const int MaxCompression = 9;
pMimeData->setData(MimeType, qCompress(xmlData, MaxCompression));
return pMimeData;
}

return NULL;
}


bool QTreeModelBase::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;

if (action != Qt::MoveAction || column > 0 || !mimeData || !mimeData->hasFormat(MimeType))
return false;

cObjectBase* pItem = GetBaseObject(parent);

if(pItem)
{
QByteArray xmlData = qUncompress(mimeData->data(MimeType));
QXmlStreamReader reader(xmlData);
if (row == -1)
{
row = parent.isValid() ? parent.row() : mpRootItem->ChildCount(true);
}
beginInsertRows(parent, row, row);
bool bDropped = ReadObjects(&reader, pItem);
endInsertRows();
return bDropped;
}
return false;
}

void QTreeModelBase::WriteObjectAndChildren(QXmlStreamW riter* pWriter, cObjectBase* pItem) const
{
if(pItem != mpRootItem)
{
pItem->WriteToXML(pWriter, false, true);
}
}

bool QTreeModelBase::ReadObjects(QXmlStreamReader* pReader, cObjectBase* pItem)
{
while(!pReader->atEnd())
{
pReader->readNext();
if(pReader->isStartElement())
{
if(pReader->name() == cObjectBase::ObjectTag)
{
cObjectBase* pNewObject = new cObjectBase(NULL);
pNewObject->ReadAttributesFromXML(pReader, true);

pItem->AddChild(pNewObject);
pItem = pNewObject;
}
}
else if(pReader->isEndElement())
{
if(pReader->name() == cObjectBase::ObjectTag)
{
Q_ASSERT(pItem);
pItem = pItem->GetParent();
Q_ASSERT(pItem);
}
}
}
return true;
}


can anyone see why I have got the drag and drop bug ?
I saw that apparently there was a bug in an earlier version of Qt that sounds very similar but it is claimed fixed.

https://bugreports.qt-project.org/browse/QTBUG-31061

QtNewbieNeedsHelp
19th October 2013, 21:03
it appears that also this bug was reported and closed and it sounds very similar to my problem... maybe there is still a Qt bug with QListView drag & drop

https://bugreports.qt-project.org/browse/QTBUG-6848?page=com.atlassian.jira.plugin.system.issueta bpanels:comment-tabpanel

QtNewbieNeedsHelp
22nd October 2013, 03:38
should I report this as a Qt bug ? or has really nobody here ever implemented a custom model to be used with QListView and drag and drop ?