4

I have an application with a mainwindow in which I create a QGraphicsScene like this:

DiagramWindow::DiagramWindow()
{
    scene = new QGraphicsScene(0, 0, 600, 500);

Then on the same program, I'm calling another window with another (different)QGraphicsScene. Both of these scenes have their respective QGraphicsViews and I use the same custom class to draw QGraphicsItem in each scene/window.

Now I'm trying to implement drag and drop between the two scenes/windows using this and I'm getting an effect that I think is similar/the same as in this SO question . Basically, when I drag a QGraphicsItem from the second window/scene to the main window, it does not trigger the event on the scene, BUT it does trigger in in the main window's toolbar/ borders.

My event handling functions are:

void DiagramWindow::dragEnterEvent(QDragEnterEvent *event)
{
    qDebug() << "I'm on the main window!";

    event->acceptProposedAction();
}

and

void DiagramWindow::dropEvent(QDropEvent *event)
{
    event->acceptProposedAction();
    qDebug() << "got a drop!";
}

According to the answers there, I would have to setAcceptDrops() in a QGraphicsScene (which is not possible), so the trick seems to be to overload the QGraphicsScene::dragMoveEvent(). Since I don't have a specific class for my QGraphicsScene (just for it's parent DiagramWindow), I don't know how I can write a function to target the scene's specific dragMoveEvent().

QUESTION 1 I was hoping I could do something like:

DiagramWindow->scene::dragMoveEvent()
{
    ...
}

But of course this is not possible. I'm really new to C++/Qt and the general workflow/syntax dynamics still ellude me. How can I target the QGraphicsScene inside my MainWindow to write the event handling function?

QUESTION 2 Also, I noticed that by rewriting these event handling functions, I (obviously) lost most of the funcionality I had in the main window - selecting and moving around the QGraphicsItems no longer works. Is there anyway I can make these events trigger only if the events are being originated in the second window? I have looked at QDrag->source() but I'm not getting how it works either - something like, if the events originate in the second window, do this, else, keep doing what you were doing before - which I don't actually know what is... :)

Community
  • 1
  • 1
Joum
  • 3,189
  • 3
  • 33
  • 64

1 Answers1

5

Question1

If the event is received by the diagramWindow, and want it receive by the scene which is currently displayed by a view, then what you should do is pass the event to the view, that will convert it to a QGraphicsSceneDragDropEvent and redirect it to the scene:

void DiagramWindow::dragMoveEvent(event)
{
    view->dragMoveEvent(event);
}

Question 2

Don't know much about Drag event so can't help, but to get the previous behaviour depending on a if statement, you should do:

void MyDerivedClass::myEvent(event)
{
    if(...)
        // do specific behaviour
    else
        QBaseClass::myEvent(event); // default behaviour
}

assuming your class MyDerivedClass(in your case, DiagramWindow) inherits from the Qt class QBaseClass (in your case, QMainWindow?), and the event method you want to override is myEvent() (in your case, dragMoveEvent).

Working example:

I don't know exacly what your class DiagramWindow is, but here is a working example that should give you all the necessary ideas to make it work in your case. I suggest you start from this working example, and modify it to get what you need.

main.cpp:

#include <QApplication>

#include "Scene.h"
#include "View.h"

int main(int argc, char * argv[])
{
    QApplication app(argc,argv);

    Scene * scene1 = new Scene(1);
    View * view1 = new View(scene1, 1);

    Scene * scene2 = new Scene(2);
    View * view2 = new View(scene2,2);

    view1->show();
    view2->show();
    return app.exec();
}

Scene.h:

#ifndef SCENE_H
#define SCENE_H

#include <QGraphicsScene>
#include <QGraphicsEllipseItem>

class Item;

class Item: public QGraphicsEllipseItem
{
public:
    Item(int x,int y);

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
};


class Scene: public QGraphicsScene
{
public:
    Scene(int i);

protected:
    virtual void dragEnterEvent ( QGraphicsSceneDragDropEvent * event );
    virtual void dragLeaveEvent ( QGraphicsSceneDragDropEvent * event );
    virtual void dragMoveEvent ( QGraphicsSceneDragDropEvent * event );
    virtual void dropEvent ( QGraphicsSceneDragDropEvent * event );

    int i;
};

#endif

Scene.cpp:

#include "Scene.h"

#include <QtDebug>
#include <QGraphicsSceneMouseEvent>
#include <QDrag>
#include <QMimeData>

Item::Item(int x, int y) : QGraphicsEllipseItem(x,y,50,50) {}

void Item::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    qDebug() << "item mouse press";

    // Create the mime  data that will be transfered  from one scene
    // to another
    QMimeData * mimeData = new QMimeData;

    // In our case, the data will be the address of the item.
    //
    // Note: This is  UNSAFE, and just for the  sake of example. The
    // good way to do it is to create your own mime type, containing
    // all the information necessary to recreate an identical Item.
    // 
    // This  is because  drag  and  drop is  meant  to work  between
    // applications, and the address  of your item is not accessible
    // by  other  applications   (deferencing  it  would  produce  a
    // segfault). It  works fine  in this case  since you  perform a
    // drag  and   drop  between  different  windows   of  the  same
    // application.
    Item * item = this;
    QByteArray byteArray(reinterpret_cast<char*>(&item),sizeof(Item*));
    mimeData->setData("Item",byteArray);

    // start the event
    QDrag * drag = new QDrag(event->widget());
    drag->setMimeData(mimeData);
    drag->start();
}

Scene::Scene(int i) : i(i)
{
    Item * item = new Item(100+100*i,100);
    addItem(item);
}           

void Scene::dragEnterEvent ( QGraphicsSceneDragDropEvent * event )
{
    qDebug() << "scene" << i << "drag enter"; 
}

void Scene::dragLeaveEvent ( QGraphicsSceneDragDropEvent * event )
{
    qDebug() << "scene" << i << "drag leave"; 
}

void Scene::dragMoveEvent ( QGraphicsSceneDragDropEvent * event )
{
    qDebug() << "scene" << i << "drag move";
}


void Scene::dropEvent ( QGraphicsSceneDragDropEvent * event )
{
    qDebug() << "scene" << i << "drop";

    // retrieve the address of the item from the mime data
    QByteArray byteArray = event->mimeData()->data("Item");
    Item * item = *reinterpret_cast<Item**>(byteArray.data());

    // add the item  to the scene (automatically remove  it from the
    // other scene)
    addItem(item);
}

View.h:

#ifndef VIEW_H
#define VIEW_H

#include <QGraphicsView>
#include "Scene.h"

class View: public QGraphicsView
{
public:
    View(Scene * scene, int i);

protected:
    virtual void dragEnterEvent ( QDragEnterEvent * event );
    virtual void dragLeaveEvent ( QDragLeaveEvent * event );
    virtual void dragMoveEvent ( QDragMoveEvent * event );
    virtual void dropEvent ( QDropEvent * event );

private:
    Scene * scene_;
    int i;
};

#endif

View.cpp:

#include "View.h"
#include <QtDebug>

View::View(Scene * scene, int i) :
    QGraphicsView(scene),
    scene_(scene),
    i(i)
{
}

void View::dragEnterEvent ( QDragEnterEvent * event )
{
    qDebug() << "view" << i << "drag enter";
    QGraphicsView::dragEnterEvent(event);
}

void View::dragLeaveEvent ( QDragLeaveEvent * event )
{
    qDebug() << "view" << i <<"drag leave";
    QGraphicsView::dragLeaveEvent(event);
}

void View::dragMoveEvent ( QDragMoveEvent * event )
{
    qDebug() << "view" << i << "drag move";
    QGraphicsView::dragMoveEvent(event);
}

void View::dropEvent ( QDropEvent * event )
{
    qDebug() << "view" << i << "drop";
    QGraphicsView::dropEvent(event);
}

In your case, if you need to explicitly call view->someDragDropEvent(event) from your DiagramWindow, then you just have to change the protected: to public:. But I don't think it is necessary, just try without reimplementing the drag and drop event in DiagramWindow, and it should automatically call the one of your view.

Boris Dalstein
  • 7,015
  • 4
  • 30
  • 59
  • More infoon question 2 meaning: http://stackoverflow.com/questions/357307/c-how-to-call-a-parent-class-function-from-derived-class-function – Boris Dalstein Jun 21 '13 at 11:24
  • Your reply to the first question doesn't work because the `dragMoveEvent` for QGraphicsScene requires a `QGraphicsSceneDragDropEvent` type of event, and the `dragMoveEvent` of DiagramWindow is of type `QDragEnterEvent`. So some sort of _conversion_ seems to be required. Any ideas? – Joum Jun 21 '13 at 12:32
  • Ok, indeed, I checked the doc. So what you do instead is calling `view->dragMoveEvent()`, that will make the conversion (converting from mouse coordinate to scene coordinate), and redirect to its attached QGraphicsScene. – Boris Dalstein Jun 21 '13 at 12:41
  • Nope... it is protected within the context, I've tried it too. – Joum Jun 21 '13 at 12:55
  • 1
    Ok, true. Just make it public ;-) If you use a straight QGraphicsView, this means using a derived class instead, in which you reimplement the event method, but declare it public instead of protected. In the reimplementation, you just call the base method as explained in the second part of my answer, without the "if". Alternatively, you can keep the method protected but declare DiagramWindow as a friend class of your class inheriting QGraphicsView. – Boris Dalstein Jun 21 '13 at 13:09
  • @Joum: maybe there is a nicer method involving installing an event filter or using event handlers, but I'm not used to this, check the doc by yourself if you want to consider this. – Boris Dalstein Jun 21 '13 at 13:11
  • Regarding my second question, I'm actually trying a different approach, I'll use a QListWidget instead of a QGraphicsScene so that stuff doesn't interfere with each other. As for the first question, I guess I'll really have to reimplement everything... Just hoped someone would know a simpler way... :( – Joum Jun 21 '13 at 13:31
  • 1
    @Joum: No, you definitely don't need to reimplement things yourself, it is not that hard. I got a working example to give you an idea on how to do. Hope that helps :) – Boris Dalstein Jun 21 '13 at 23:39
  • this is awesome, thank you so much! I'll have to lose some time refactoring my program to integrate this in, and hopefully it won't ruin most of the functionality I already have, but this is really helpful, thank you! – Joum Jun 24 '13 at 07:55
  • got it to work, but let's imagine I want to _copy_ the item instead of _move_ it. So, how do I prevent the original item from disapearing from the scene? – Joum Jun 24 '13 at 09:45
  • Then, you obviously have to copy it, you can't share a QGraphicsItem between two scenes. Not sure if it works, but you should simply try: `addItem(new Item(*item))`. If it doesn't, then you'll have to implement your own copy constructor. The trickier was in my opinion to pass the information between the two windows, now it is up to you to do whatever you want with it ;-) – Boris Dalstein Jun 24 '13 at 10:03
  • @Joum: see my comment above – Boris Dalstein Jun 24 '13 at 10:05
  • thank you once again. Sadly, it does not work. I'll try to handle it from here! Thanks! – Joum Jun 24 '13 at 10:38