PDA

View Full Version : Can I get a drag&drop tobe displayed as a line instead of the big floating icon?



pir
18th May 2006, 22:14
Hi!

I have a tree view which display different object. Some of the objects are groups of objects. I want to take a object and drag it and release it into a group. If the object can be put into the object it is inserted, otherwise the object is replaced where the dragging started.

I have not found much about the drag and drop thing so I'm not sure about which components to use. I can drag a object, but it is displayed as a big floating icon and I want just a line (when between two objects) showing where in the tree the object will be put if I drop it, when the dragged object is dragged over a object it should highlight it or something showing that the dragged object will be placed in this one if dropped... the dragging and dropping is only allowed in the same tree view.

I hope you get the picture....

Any tips on where I should start look for this? Any good sample code or functions I can use?


/pir

pir
19th May 2006, 08:50
I was thinking that I mayble should do the line that indicates the dragging by drawing on top of the tree view. But The problem is that I need some kind of transparent layer to put on top on my tree view that allows me to paint on it. I suppose that it has something to do with a pixmap. But how do I put to pixmap on top over the tree view as a layer?

wysota
19th May 2006, 17:47
You don't need any layer. You can paint in paintEvent directly on the widget. dragEnterEvent will probably help you (should help you mark where to draw the line).

pir
19th May 2006, 18:30
I've managed to paint on top of the tree now by, like you said, call the paintEvent(). But the thing is that I want the line to follow the mouse and show the drop target.

The problem is that I don't understand how dragEnterEvent can help me... it seems to only start when I start a drag and I need to know how the mouse is changing during the dragging process.

I've tried dragMoveEvent(). The problem there is that I need to repaint the widget while the object is dragging and it only happens when the dragging has stopped. I tried to trigger the paintEvent by calling repaint() from inside the dragMouseEvent(). I wonder if it is the floating icon that is preventing me from repainting the widget... but I can't find out how to turn it off.

thanks for the tip!
/pir

wysota
19th May 2006, 19:20
use mouseMove to update cursor position. dragEnter will just help you to know that you are dragging an object.

pir
19th May 2006, 19:28
The problem is that I can't get the treeView to redraw the widget so the line is animated. What happens is that I start dragging and after I have stopped dragging by released the mouse the line is drawn. There is something that prevents the paintEvent to redraw the treeView while I'm dragging...

wysota
19th May 2006, 21:29
Did you call update() from mouseMoveEvent?

pir
19th May 2006, 21:50
No, I don't think I've tried that. I called repaint from dragMoveEvent().
I should try mouseMoveEvent()...

pir
20th May 2006, 00:35
Ok, I can't make it work.

This is what I have so far... it is mostly taken from the docs, with some deactivated. I hope that isn't the problem...

The problem is that when I start a drag operation the mouseMoveEvent() isn't called during the draging. To be able to react to the dragging I need to implement the dragMoveEvent(). The problem is that if I do this, it doesn't seem like paintEvent is called during the dragging. It is called after the dragging has stopped.

Here is the events I have implemented and their placement in the class. The tree view itself lies in a window.


class Outliner: public QTreeeView {

protected:

/** mouseMoveEvent() */
void mouseMoveEvent ( QMouseEvent *event )
{
if ( !(event->buttons() & Qt::LeftButton) )
return;

if ( (event->pos() - m_qDragStartPosition).manhattanLength() <
Application::startDragDistance() )
return;

QDrag *drag = new QDrag ( this );

QMimeData *mimeData = new QMimeData;

//mimeData->setData ( mimeType, data );

drag->setMimeData ( mimeData );

Qt::DropAction dropAction = drag->start ( Qt::CopyAction | Qt::MoveAction );

repaint ( );

}// mouseMoveEvent(QMouseEvent*)

/** mousePressEvent() */
void mousePressEvent ( QMouseEvent *event )
{
QTreeView::mousePressEvent ( event );

if ( event->button() == Qt::LeftButton )
m_qDragStartPosition = event->pos ( );

}// mousePressEvent(QMouseEvent*)

/** paintEvent() */
void paintEvent ( QPaintEvent *event )
{
QTreeView::paintEvent ( event );

QPainter painter ( viewport );

int x, y, w, h;
m_rDropSite.getRect ( &x, &y, &w, &h );
painter.drawLine ( 0, y, 192, y );

}// paintEvent(QPaintEvent*)

};// class Outliner

pir
20th May 2006, 14:30
Ok, I have managed to make the tree view to redraw itself during a dragMoveEvent().
Right now I'm not using the mouseMoveEvent() and mousePressEvent() functions I posted above, instead I use dragMoveEvent() and the posted paintEvent().

dragMouseEvent looks like this:



void Outliner::dragMoveEvent ( QDragMoveEvent *event )
{
// a QRect member variable telling where the drag event is currently localted, used by paintEvent().
m_rDropSite = event->answerRect ( );

// TreeModel is my custom QAbstractItemModel
TreeModel *tree = static_cast<TreeModel*> ( model() );

// look below for this one
tree->emit_layoutChanged ( );

}// dragMoveEvent(QDragMoveEvent*)


This is the thing I feel is a bit bizarr... It seems to be the only way to update
the tree view during a drag operation. This lies in the custom model, class TreeModel, for the view.
Is it ok to do like this?


/** Public member function in custom model, class TreeModel. The only thing this does is
* emit the signal layoutChanged(), forcing the tree view to execute paintEvent.
*/
void TreeModel::emit_layoutChanged ( )
{
emit layoutChanged ( );
}// emit_layoutChanged()


anyway, thanks for the tips.
/pir

pir
20th May 2006, 16:26
Hi!

Now I'm stuck again. Thought that it would be best to post this question in this thread, since it puts this question in context.

Now I want the drawn line to either be drawn between the items or in the middle of one. The reason for this is to make it easier for the user to see if the drop site will place the dragged item between two items or inside one. To do this I need to know the coordinates for the rendered items.


So the question is:
How do I get a describtion of the geometrity for a item in my QTreeView? That is, where in the view the item specified is draw defined in view pixel coordinates.

hope somebody knows how to do this or if it's possible...
pir

wysota
20th May 2006, 17:10
QRect QTreeView::visualRect ( const QModelIndex & index ) const [virtual]
Returns the rectangle on the viewport occupied by the item at index. If the index is not visible or explicitly hidden, the returned rectangle is invalid.
Reimplemented from QAbstractItemView.

But maybe this will be better for you:


QModelIndex QAbstractItemView::indexAt ( const QPoint & point ) const [pure virtual]
Returns the model index of the item at point point.
In the base class this is a pure virtual function.

pir
20th May 2006, 18:18
Thanks!
I should check my sight. Have stared at the QTreeView functions and didn't see that function...
/pir

wysota
20th May 2006, 20:06
Sometimes you just have to know the function (or at least know that it exists) to spot it in the documentation.

pir
21st May 2006, 08:36
Thanks again for the tips about the two functions, they did the trick! I wonder if there maybe could be some better way to document the functions, like in subcategories. Because it's not only in the Qt documentation this is a problem. It's the same way in Java.

The only thing left for my view class is to make the actual drag and drop functionality. But again I'm uncertain of how I should do it. Suprised?

The thing is that all examples and threads I can find is about drag n dropping items between widgets. I don't want my view to let items from other widgets be dropped on it, or let it drop it's items on other widget.
The point with my view is to be a kind of a Outliner, like in Maya, where the user can organize the scene of objects into groups. The user should be able to make a object to become a member of a group by dropping it on that group.

The model is actually a vector of pointers to the components, which can be groups of other components, in my scene. The vector has index (0,0) in the model and is acting as the root.

My question is:
Is the simplest approach to just get the target information from the drag and drop and do the actual grouping in my scene completely on my own and then rebuild my model? In this case there wouldn't be any actual dragging and dropping in normals sense... Or should I make the grouping by implement a modelItem class that handles the grouping?


/pir

wysota
21st May 2006, 09:16
Drag and drop is mostly handled by the model and not by the view.


These methods (of the model) may be of interest for you:
bool dropMimeData ( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
QMimeData * mimeData ( const QModelIndexList & indexes ) const
QStringList mimeTypes () const
Qt::DropActions supportedDropActions () const

pir
21st May 2006, 22:48
ok... stuck again.

I have problem with passing the information that is dragged... I've tried QAbstractModel::mimeData(), do I need to reimplement it or is it just ok to pass the QModelIndex into it?

Then I have a big problem with retrieving the QModelIndex back from the QMimeData.... how am I supposed to do that? There is a function called QMimeData::retrieveData(), can I use that one as it is or do I reimplement it? It is protected, which I find kind of weird if it supposed to be used in this way...

ahhhh, I'm tired of this drag n dropping stuff...

/pir

wysota
22nd May 2006, 00:38
ok... stuck again.

I have problem with passing the information that is dragged... I've tried QAbstractModel::mimeData(), do I need to reimplement it or is it just ok to pass the QModelIndex into it?

Then I have a big problem with retrieving the QModelIndex back from the QMimeData.... how am I supposed to do that? There is a function called QMimeData::retrieveData(), can I use that one as it is or do I reimplement it? It is protected, which I find kind of weird if it supposed to be used in this way...

You should not pass indexes there. You need to encode your data as some custom mime-type (for example application/x-my-mime-type). If you only drag within the same widget, you can for example use the index row number as the encoded form of data (as long as you're able to recreate an item from it). The easiest way is something like this:


QMimeData * MyModel::mimeData ( const QModelIndexList & indexes ) const{
QMimeData *mime = new QMimeData;
QString rows;
foreach(QModelIndex index, indexes){
rows += QString::number(index.row())+"; ";
}
mime->setText(rows);
return mime;
}

In dropMimeData just reconstruct indices from their row values and you're done. Of course you may choose to do it more properly and use QByteArray and a custom mime-type instead of text/plain (which is used when using setText()). Or you could even do it even more properly and encode not the index of the item but its data (as should be done with "real" drag&drop).

pir
22nd May 2006, 07:19
.../ Or you could even do it even more properly and encode not the index of the item but its data (as should be done with "real" drag&drop).


This fit my purposes best, I think. The only thing my indexes containt is a pointer to a component in my scene. So it should be sufficuent to give this pointer as information... this was actually my first approach but I couldn't find how I was supposted to convert the pointer to a QByteArray and back.

But there is one problem with this approach. I need to be able to remove the old index when the user have dropped the object, since it should be executed as a move action. So I need to find that index, and then I suppose I need to know its row and parent, and the parent's row and parent etc... maybe it's simpler to have a pointer in my model class pointing to the index being dragged and use this information instead of a mime. Could this create problems somehow?

/pir

wysota
22nd May 2006, 10:18
This fit my purposes best, I think. The only thing my indexes containt is a pointer to a component in my scene. So it should be sufficuent to give this pointer as information... this was actually my first approach but I couldn't find how I was supposted to convert the pointer to a QByteArray and back.
A pointer is an integer, just treat it as such. But if you want to encode the data itself, then I didn't mean a pointer here, but actual information which is stored in an object referenced by the pointer.



But there is one problem with this approach. I need to be able to remove the old index when the user have dropped the object, since it should be executed as a move action.
I guess the view might do it yourself if you convince it you are doing a move action (but I'm not sure, haven't tried it myself yet).


So I need to find that index, and then I suppose I need to know its row and parent, and the parent's row and parent etc...
That's why the concept of indexes exists ;) But if you implement the drop correctly, you won't have to worry about removing the item from its original place.


maybe it's simpler to have a pointer in my model class pointing to the index being dragged and use this information instead of a mime. Could this create problems somehow?
This doesn't give you any advantage. You can store the pointer in the QMimeData object as well.

Valheru
15th August 2008, 23:19
What may help you a bit is to look at my source code here : http://code.google.com/p/knewz/source/browse/trunk
I ran into this the other day, it's a bit of work to get right but not terribly complicated once you get the hang of it. There's a few things to remember while dragging and dropping stuff, but you should be able to pick them up from my code if you haven't already.
There pertinant stuff you will want to look at is the model and the mimedata implementations:
http://code.google.com/p/knewz/source/browse/trunk/src/knewzmodel.cpp
http://code.google.com/p/knewz/source/browse/trunk/src/nzbmimedata.cpp

Like you, I am just passing pointers to objects around in a QList. So with a drag and drop you just dump the pointer list into a custom mimedata class and retrieve it when it is dropped. Then you can use your underlying data structure to manipulate the data directly, encapsulating any removal and insertion of rows with the correct calls to being/endRemoveRows and begin/endInsertRows