PDA

View Full Version : Drag and drop between model views



larry104
19th July 2006, 01:46
Hi all,

I have two table model views. When I drag an item to the other view how can I tell the source view that the drop has been accepted? The idea is to grey out the item which was copied to the other view.

Any hints?
Thanks a lot.

wysota
19th July 2006, 12:40
Return true from QAbstractItemModel::dropMimeData(). Unfortunately you can't access that information from within the source. But if you force a MoveAction, the item will get deleted by the source view. If you want a different behaviour, subclass the view and reimplement startDrag().

larry104
19th July 2006, 23:08
As you said the only one who knows that mimedata was sucessfully droped is QAbstractItemModel::dropMimeData() so, I implemented a signal in the target model and a slot in the source model to inform the source that data has been transfered. Is that a resonable way to do that?

Thanks.

wysota
19th July 2006, 23:12
Is that a resonable way to do that?

Not really, but if it works then why not...

larry104
20th July 2006, 00:12
If you say

Not really, but if it works then why not...

how should I do it? I tried the subclassing approach but non of the these



void dragEnterEvent(QDragEnterEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void startDrag(Qt::DropActions supportedActions);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);


tell me that the target has really accepted the drop. Do I miss something?
Thanks.

wysota
20th July 2006, 00:38
QDrag::start() tells you that and it is called from QAbstractItemView::startDrag(), that's why I said you could reimplement it.

larry104
20th July 2006, 01:32
I see your point but, still if the user drops the data other than the target model view I wouln't know that. Subclassing startDrag would just allow me to act on the actual move out of my source view - right? There is no real feedback mechanism between source and target.

wysota
20th July 2006, 10:37
I see your point but, still if the user drops the data other than the target model view I wouln't know that. Subclassing startDrag would just allow me to act on the actual move out of my source view - right? There is no real feedback mechanism between source and target.

startDrag will notify you in the source view (by the return value from startDrag()). From then you have direct access to the source model (you can connect them through signal and slot then, emiting the signal from the view in this situation is better than from the model), I thought that was what you wanted -- to change the source model after the drop.

larry104
20th July 2006, 20:26
So, I subclassed startDrag and indeed QDrag::start() tells me that something was moved/copied but as I thought it will tell me when I droped into the target as well as any other application e.g firefox. So, I don't see a difference that the data has been accepted by the target.

wysota
21st July 2006, 00:57
I have to admit I didn't understand your post :)

QDrag::start returns an information if the drag was accepted and with which action. If it was dropped on an invalid target, it will return IgnoreAction, if it was dropped on a valid target but the drop was rejected, it will return IgnoreAction. If it was dropped on a valid target and the target did something with the data (no matter if the target is in the same application as the source or if it's a completely different program), it will return an action telling you what should you do with the data -- copy, move, link or ignore it. If you drop on Firefox and firefox accepts the drop, you'll receive CopyAction. So you'll always know what you should do with the item.

Does that solve your problem? If you have doubts, please look at the puzzle example bundled with Qt and at the definition of the puzzle widget (or whatever it's called). The widget does the thing I think you want to do, just it doesn't use model-view, but the idea remains. If a drag is started, the item gets removed from the widget, if a valid drop occurs, the item gets inserted in a new position. If an invalid drop occurs, the item gets inserted back into the place it was taken from. No matter where you actually drop the item (and by using a custom MIME type it assures that you won't make a valid drop outside the puzzle application).

larry104
21st July 2006, 01:45
... using a custom MIME type it assures ...

Yep, that does the trick to ensure that only the target is allowed to accept the drop. Unfortunately, rewriting startDrag is a little bit of work. Thanks again for the help.

wysota
21st July 2006, 09:30
Unfortunately, rewriting startDrag is a little bit of work. Thanks again for the help.

Copy and paste it from Qt sources and add the lines you need.

larry104
21st July 2006, 20:09
Hmmm, that might be embarrissing for me (after all I'm QT beginner) but I get compile errors when I just copy it.



void QAbstractItemView::startDrag(Qt::DropActions supportedActions)
{
Q_D(QAbstractItemView);
QModelIndexList indexes = selectedIndexes();
if (indexes.count() > 0) {
// setup pixmap
QRect rect = visualRect(indexes.at(0));
QList<QRect> rects;
for (int i = 0; i < indexes.count(); ++i) {
rects.append(visualRect(indexes.at(i)));
rect |= rects.at(i);
}
rect = rect.intersect(d->viewport->rect());
QPixmap pixmap(rect.size());
pixmap.fill(palette().base().color());
QPainter painter(&pixmap);
QStyleOptionViewItem option = viewOptions();
option.state |= QStyle::State_Selected;
for (int j = 0; j < indexes.count(); ++j) {
option.rect = QRect(rects.at(j).topLeft() - rect.topLeft(),
rects.at(j).size());
itemDelegate()->paint(&painter, option, indexes.at(j));
}
painter.end();
// create drag object
QDrag *drag = new QDrag(this);
drag->setPixmap(pixmap);
drag->setMimeData(model()->mimeData(indexes));
drag->setHotSpot(d->viewport->mapFromGlobal(QCursor::pos()) - rect.topLeft());
if (drag->start(supportedActions) == Qt::MoveAction)
d->removeSelectedRows();
}
}




../../../qt/qt-4.1.4/include/QtGui/qtableview.h: In member function `virtual
void MyTableView::startDrag(QFlags<Qt::DropAction>)':
../../../qt/qt-4.1.4/include/QtGui/qtableview.h:134: `QTableViewPrivate*
QTableView::d_func()' is private
mytableview.cpp:43: within this context
mytableview.cpp:43: cannot convert `QTableViewPrivate*' to `
QAbstractItemViewPrivate* const' in initialization
mytableview.cpp:53: invalid use of undefined type `struct
QAbstractItemViewPrivate'
../../../qt/qt-4.1.4/include/QtGui/qabstractitemview.h:41: forward declaration
of `struct QAbstractItemViewPrivate'
mytableview.cpp:69: invalid use of undefined type `struct
QAbstractItemViewPrivate'
../../../qt/qt-4.1.4/include/QtGui/qabstractitemview.h:41: forward declaration
of `struct QAbstractItemViewPrivate'
mytableview.cpp:71: invalid use of undefined type `struct
QAbstractItemViewPrivate'
../../../qt/qt-4.1.4/include/QtGui/qabstractitemview.h:41: forward declaration
of `struct QAbstractItemViewPrivate'
make: *** [mytableview.o] Error 1

wysota
21st July 2006, 20:13
You can't use private components of Qt classes as they are not accessible, so you have to think a workaround for using Q_D and d->. It shouldn't be too hard as d->viewport should be available as viewport() and you can remove the selected rows without using the d->removeSelectedRows() call if you need that functionality. And when you won't be using "d" anymore, you can remove the Q_D() call.

larry104
22nd July 2006, 01:06
Works perfect now - thanks again so much for all the help.

maximAL
18th January 2008, 15:13
QDrag::start returns an information if the drag was accepted and with which action. If it was dropped on an invalid target, it will return IgnoreAction, if it was dropped on a valid target but the drop was rejected, it will return IgnoreAction. If it was dropped on a valid target and the target did something with the data (no matter if the target is in the same application as the source or if it's a completely different program), it will return an action telling you what should you do with the data -- copy, move, link or ignore it.

what could be the cause if the item was dropped on a valid target, but QDrag::exec (or start) still returns IgnoreAction?

i'm creating a QDrag object in a custom item view in the startDrag(...) function like this:

p_pDrag->setMimeData(p_pMimeData);
p_pDrag->setHotSpot(QPoint());
p_pDrag->setPixmap(p_Pixmap);

// always returns IGNORE?!
Qt::DropAction p_DropAction = p_pDrag->exec(Qt::MoveAction | Qt::CopyAction);

the underlying model's (subclassed QStandardItemModel) flag function gives Qt::ItemIsDropEnabled for every index, supportedDropActions gives Qt::MoveAction | Qt::CopyAction.
i overloaded dropEvent(...) for the view and QDropEvent::dropAction() is CopyAction
i also set setDragEnabled and setAcceptDrops to true and setDragDropMode to DragDrop

but still no luck :(
d&d works fine btw, just the return value of exec is wrong...

wysota
18th January 2008, 15:33
what could be the cause if the item was dropped on a valid target, but QDrag::exec (or start) still returns IgnoreAction?

It probably means there is something wrong with your dropMimeData() implementation of the model where you drop the data. Did you remember to return true from the method?

maximAL
18th January 2008, 15:46
yes, it definetly returns true :(

wysota
19th January 2008, 00:05
Can you show me the dropMimeData() implementation and the contents of supportedDragActions and supportedDropActions?

maximAL
19th January 2008, 15:19
oh boy, thats it. i didn't set the supported drag actions :o

wysota
19th January 2008, 16:09
"The default implementation returns supportedDropActions() unless specific values have been set with setSupportedDragActions()."

So maybe you forgot to set the supported drop actions? :)