PDA

View Full Version : Detecting and reacting to mouse dragging in QGraphicsView?



swbluto
25th October 2009, 16:43
Hello, I'm trying to detect a mouse dragging across the screen but despite subclassing QGraphicsScene and over-writing dragMoveEvent(), it doesn't seem like my mouse dragging is being detected (The debug string isn't being executed). I'm not looking for drag and drop functionality, I'm just trying to customize what happens in the scene when a mouse is dragged across. Basically, I need the whole scene to shift as the mouse is dragged such that it's like the mouse is dragging the scene around with all of its images, kind of like how adobe's pdf reader can drag around the document when the mouse is dragged. I was thinking about changing the image's positions in the scene and not the scene itself.

In implementing this, I was thinking about detecting the mouse drag event and then changing a global variable (Just for testing) that denotes the offset, and then this offset is communicated to all the Qpixmaps on the scene through the advance() function which sets the image's individual positions. The scene needs to be "scrollable" in such a way that the image needs to be freed when it goes off the screen too far, and the new images on the other side that are coming into the scene(or viewport or whatever the thing you see is called) are loaded and shown.

I know it'd be possible to implement scrollbars and do all that funky jazz to get easy scroll functionality but scroll-bars don't make sense for my application.




#include <QApplication>
#include <QGraphicsEllipseItem>
#include <QGraphicsScene>
#include <QGraphicsView>

#include <qdatetime.h>
#include <qmainwindow.h>
#include <qstatusbar.h>
#include <qmessagebox.h>
#include <qmenubar.h>
#include <qapplication.h>
#include <qpainter.h>
#include <qprinter.h>
#include <qlabel.h>
#include <qimage.h>
#include <qpixmap.h>
#include <QMouseEvent>
#include <QStyleOptionGraphicsItem>
#include <qdebug.h>
#include <stdlib.h>
#include <qtimer.h>

QString myImgName = "myimg.png";
static QImage *myImg;


int xOffset;
int yOffset;


static const int imageRTTI = 984376;

void debug(QString s)
{
qDebug(s.toAscii());
}

class mapScene: public QGraphicsScene
{
public:
mapScene();

QPoint prevMousePoint;
QPoint currMousePoint;
protected:
void dragMoveEvent ( QGraphicsSceneDragDropEvent * event );
};

mapScene::mapScene()
{
xOffset =0;
yOffset =0;
}


void mapScene::dragMoveEvent( QGraphicsSceneDragDropEvent * event)
{
debug("I vus here!");
xOffset = 1;
yOffset = 1;
}



class ImageItem: public QGraphicsRectItem
{
public:
ImageItem( QImage img );
int rtti () const { return imageRTTI; }
void advance(int phase);
protected:
void paint( QPainter *, const QStyleOptionGraphicsItem *option, QWidget *widget );
private:
QImage image;
QPixmap pixmap;
int state;
};


void ImageItem::advance(int phase)
{
moveBy(xOffset,yOffset);
}


ImageItem::ImageItem( QImage img )
: image(img)
{
state = 0;
setRect(0, 0, image.width(), image.height());
setFlag(ItemIsMovable);
#if !defined(Q_WS_QWS)
pixmap = pixmap.fromImage(image, Qt::OrderedAlphaDither);
#endif
}

void ImageItem::paint( QPainter *p, const QStyleOptionGraphicsItem *option, QWidget * )
{
// On Qt/Embedded, we can paint a QImage as fast as a QPixmap,
// but on other platforms, we need to use a QPixmap.
#if defined(Q_WS_QWS)
p->drawImage( option->exposedRect, image, option->exposedRect, Qt::OrderedAlphaDither );
#else
p->drawPixmap( option->exposedRect, pixmap, option->exposedRect );
#endif
}

int main( int argc, char **argv )
{

QApplication app(argc, argv);

mapScene scene;
scene.setSceneRect( -100.0, -100.0, 200.0, 200.0 );

myImg = new QImage;
myImg->load(myImgName);


QAbstractGraphicsShapeItem* i = new ImageItem(*myImg);
scene.addItem(i);
i->setPos(-50,-50);

QTimer timer;
QObject::connect(&timer, SIGNAL(timeout()), &scene, SLOT(advance()));
timer.start(100);

QGraphicsView view( &scene );
view.setRenderHints( QPainter::Antialiasing );
view.show();

return app.exec();
}

swbluto
25th October 2009, 16:54
I just realized that I meant to use MouseMoveEvent() instead of DragMoveEvent.

However, it seems that this event doesn't really detect "dragging" (mouse click down and moving"). Maybe I have to manually manage the dragging event by using clicking, release and move events?

swbluto
25th October 2009, 17:25
Ok, I figured out how to detection using the MousePressEvent and MouseReleaseEvent and MouseMove event to do the "detect the mouse is dragging" part, now I need to retrieve the position of the mouse to do image-moving calculations. Does anyone have a snippet of code to do this?

I have this code currently, but it complains.



void mapScene::mouseMoveEvent( QGraphicsSceneMouseEvent * event)
{
debug("I vus here 2!");
prevMousePoint = event->lastPos();
currMousePoint = event->pos();
if(mouseDown)
{

// xOffset = event->pos().x() - event->lastPos().x();
//yOffset = 1;
}
}


Complaint:


error: invalid use of undefined type `struct QGraphicsSceneMouseEvent'
error: invalid use of undefined type `struct QGraphicsSceneMouseEvent'


And it points to the lines where prevMousePoint and currMousePoint are. Both variables are of the type QPointF.

swbluto
25th October 2009, 17:42
Apparently I had to...

#include <QtGui>

And I thought my include list was already long enough. Hopefully QtGui is the mothership of all Qt includes.

wysota
25th October 2009, 18:13
Use QGraphicsView::setDragMode() with QGraphicsView::ScrollHandDrag. You don't need to do anything else, touching the scene included.

swbluto
25th October 2009, 18:30
Use QGraphicsView::setDragMode() with QGraphicsView::ScrollHandDrag. You don't need to do anything else, touching the scene included.

Neat!

I've implemented in the code, and I do get the hand cursor with it grasping when I hold down the mouse button, but the image on the screen isn't moving. Here's the code.




#include <QApplication>
#include <QGraphicsEllipseItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QtGui>

#include <qdatetime.h>
#include <qmainwindow.h>
#include <qstatusbar.h>
#include <qmessagebox.h>
#include <qmenubar.h>
#include <qapplication.h>
#include <qpainter.h>
#include <qprinter.h>
#include <qlabel.h>
#include <qimage.h>
#include <qpixmap.h>
#include <QMouseEvent>
#include <QStyleOptionGraphicsItem>
#include <qdebug.h>
#include <stdlib.h>
#include <qtimer.h>

QString myImgName = "myimg.png";
static QImage *myImg;

int xOffset;
int yOffset;

static const int imageRTTI = 984376;

void debug(QString s)
{
qDebug(s.toAscii());
}

class ImageItem: public QGraphicsRectItem
{
public:
ImageItem( QImage img );
int rtti () const { return imageRTTI; }
void advance(int phase);
protected:
void paint( QPainter *, const QStyleOptionGraphicsItem *option, QWidget *widget );
private:
QImage image;
QPixmap pixmap;
int state;
};


void ImageItem::advance(int phase)
{

// moveBy(xOffset,yOffset);
}


ImageItem::ImageItem( QImage img )
: image(img)
{
state = 0;
setRect(0, 0, image.width(), image.height());
setFlag(ItemIsMovable);
#if !defined(Q_WS_QWS)
pixmap = pixmap.fromImage(image, Qt::OrderedAlphaDither);
#endif
}

void ImageItem::paint( QPainter *p, const QStyleOptionGraphicsItem *option, QWidget * )
{
// On Qt/Embedded, we can paint a QImage as fast as a QPixmap,
// but on other platforms, we need to use a QPixmap.
#if defined(Q_WS_QWS)
p->drawImage( option->exposedRect, image, option->exposedRect, Qt::OrderedAlphaDither );
#else
p->drawPixmap( option->exposedRect, pixmap, option->exposedRect );
#endif
}

int main( int argc, char **argv )
{

QApplication app(argc, argv);

QGraphicsScene scene;//mapScene scene;
scene.setSceneRect( -100.0, -100.0, 200.0, 200.0 );

myImg = new QImage;
myImg->load(myImgName);


QAbstractGraphicsShapeItem* i = new ImageItem(*myImg);
scene.addItem(i);
i->setPos(-50,-50);

QTimer timer;
QObject::connect(&timer, SIGNAL(timeout()), &scene, SLOT(advance()));
timer.start(100);


QGraphicsView view( &scene );
view.setDragMode(QGraphicsView::ScrollHandDrag);
view.setRenderHints( QPainter::Antialiasing );
view.show();

return app.exec();
}

wysota
25th October 2009, 20:22
Most likely your scene is smaller than your view, so there is not much to move. Make your scene larger and try again.

swbluto
25th October 2009, 20:45
So I made the scene larger using setRect, and indeed, it turned out to be draggable. I noticed the scroll bars, though, and the view was only draggable upto the extent of the scroll-bars. My application will potentially have billions of different images and the area that could be potentially viewed could be thought of being infinite, so I'm not sure how appropriate the scroll-bars would be and I'm not sure how well graphics-view can handle billions of qGraphics item entities, even though the image data itself would be loaded and deallocated as needed, so I'm not so worried about memory performance. More along the lines of the scene being able to think that there's tons of other graphics items out there. Would it be possible to allocate and deallocate graphics items as the view shifts around on the scene when dragging it around?

wysota
25th October 2009, 21:33
I noticed the scroll bars, though, and the view was only draggable upto the extent of the scroll-bars.
That's the point of this method.


My application will potentially have billions of different images and the area that could be potentially viewed could be thought of being infinite,
There is no such thing as infinity in computer science. You wouldn't be able to load billions of items into the scene anyway, so maybe the GraphicsView architecture is simply not for you. To me it seems you need a simple QWidget subclass capable of handling one or two images at most and loading them on the fly as the user "scrolls" through them. Then it is just a matter of handling mousePressEvent() and mouseMoveEvent() for your widget and reimplementing paintEvent() to do the drawing.

swbluto
26th October 2009, 00:13
Ok, so I'm trying to do things with the MousePress, MouseRelease and MouseMove events and they all seem to work as far as they seem like they're literally supposed to work, but I use a custom boolean "mouseDown" to detect if the mouse is down and realized I need to process code whenever the mouse is down, whether moving or not. MousePress only triggers on a mouse press, MouseRelease only triggers when it's released, and MouseMove only triggers when the mouse is moved. How can I implement something that's triggered whenever the custom boolean "mouseDown" is true (or any condition is true for that matter)?

Ok, I never did implement the "execute whenever mousedown" is true, but I found another way around the problem I was having. When I move the mouse with the button down and then stopped, the "change x" variable of the image didn't go to zero (i.e. the image kept moving), so I needed to find a way to detect when the mouse stopped moving and that's what the mouseDown boolean was for.

However, I found that just by resetting the "change x" variable of the image to zero after advancing the image with "advance()" did what I wanted to. So no problems there.

Now, I need to make it "unchoppy" and the image seems to "slip" in regard to the mouse moving when I move "fast". I think I need advance() to trigger whenever the mouse is moved instead of on a fixed time interval... how would I do that? I'm thinking it has something to do with slots.

wait, would it be as simple as calling advance() inside the mouseMove event function? Can I even call advance() inside the QGraphicsScene class? *investigates*

Woah! I just found that I could call advance() directly inside the QGraphicsScene subclass and now it's truly dragging! Here's my code just in case some other n00b might benefit from it.



//#include <Q4MemArray>
//#include "canvas.h"
//#include <q4progressdialog.h>
//#include <Q4PointArray>
//#include <Q4PtrList>
//#include <QPixmap>
//#include "mapscene.h"
//#include <Q4PopupMenu>

#include <QApplication>
#include <QGraphicsEllipseItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QtGui>

#include <qdatetime.h>
#include <qmainwindow.h>
#include <qstatusbar.h>
#include <qmessagebox.h>
#include <qmenubar.h>
#include <qapplication.h>
#include <qpainter.h>
#include <qprinter.h>
#include <qlabel.h>
#include <qimage.h>
#include <qpixmap.h>
#include <QMouseEvent>
#include <QStyleOptionGraphicsItem>
#include <qdebug.h>
#include <stdlib.h>
#include <qtimer.h>


QString myImgName = "myimg.png";
static QImage *myImg;

int xOffset;
int yOffset;

static const int imageRTTI = 984376;

void debug(QString s)
{
qDebug(s.toAscii());
}

class mapScene: public QGraphicsScene
{
public:
mapScene();

int mouseMoveX;
int mouseMoveY;
QPointF prevMousePoint;
QPointF currMousePoint;

bool mouseDown;
protected:
void mouseMoveEvent ( QGraphicsSceneMouseEvent * mouseEvent );
void mousePressEvent ( QGraphicsSceneMouseEvent * mouseEvent );
void mouseReleaseEvent ( QGraphicsSceneMouseEvent * mouseEvent );

};

mapScene::mapScene()
{
mouseDown = false;
mouseMoveX = 0;
mouseMoveY = 0;
xOffset =0;
yOffset =0;

}

void mapScene::mouseMoveEvent( QGraphicsSceneMouseEvent * event)
{
if(mouseDown)
{
QString dummy;
debug("mommy");
/*
debug(dummy.setNum(event->pos().x()));
debug(dummy.setNum(event->lastPos().x()));
debug(dummy.setNum(event->lastScenePos().x()));
debug(dummy.setNum(event->scenePos().x()));
*/
xOffset = (int)(event->scenePos().x() - event->lastScenePos().x());
yOffset = (int)(event->scenePos().y() - event->lastScenePos().y());
advance();
}
else
{
xOffset = 0;
yOffset = 0;
}
}

void mapScene::mousePressEvent( QGraphicsSceneMouseEvent * event)
{
mouseDown = true;
}

void mapScene::mouseReleaseEvent( QGraphicsSceneMouseEvent * event)
{
mouseDown = false;
xOffset = 0;
yOffset = 0;
}



class ImageItem: public QGraphicsRectItem
{
public:
ImageItem( QImage img );
int rtti () const { return imageRTTI; }
void advance(int phase);
protected:
void paint( QPainter *, const QStyleOptionGraphicsItem *option, QWidget *widget );
private:
QImage image;
QPixmap pixmap;
int state;
};


void ImageItem::advance(int phase)
{

moveBy(xOffset,yOffset);
xOffset = 0; yOffset = 0;
}


ImageItem::ImageItem( QImage img )
: image(img)
{
state = 0;
setRect(0, 0, image.width(), image.height());
setFlag(ItemIsMovable);
#if !defined(Q_WS_QWS)
pixmap = pixmap.fromImage(image, Qt::OrderedAlphaDither);
#endif
}

void ImageItem::paint( QPainter *p, const QStyleOptionGraphicsItem *option, QWidget * )
{
// On Qt/Embedded, we can paint a QImage as fast as a QPixmap,
// but on other platforms, we need to use a QPixmap.
#if defined(Q_WS_QWS)
p->drawImage( option->exposedRect, image, option->exposedRect, Qt::OrderedAlphaDither );
#else
p->drawPixmap( option->exposedRect, pixmap, option->exposedRect );
#endif
}

int main( int argc, char **argv )
{

QApplication app(argc, argv);

mapScene scene;//mapScene scene;
scene.setSceneRect( -500.0, -500.0, 1000.0, 1000.0 );//( -100.0, -100.0, 200.0, 200.0 );

myImg = new QImage;
myImg->load(myImgName);


QAbstractGraphicsShapeItem* i = new ImageItem(*myImg);
scene.addItem(i);
i->setPos(-50,-50);



QGraphicsView view( &scene );
// view.setDragMode(QGraphicsView::ScrollHandDrag);
view.setRenderHints( QPainter::Antialiasing );
view.show();

return app.exec();
}


I'm now trying to get this dragging behavior to work for multiple images all at the same time but only one image (coindentally, the first one that I added to the scene) moves, but if the first one goes off the screen, then the second one moves, albeit at a slower rate than the mouse moving meaning it's only getting triggered to move a part of the time the mouse is moved. It seems this is sufficiently different to deserve its own thread, so I'm posting that issue at http://www.qtcentre.org/forum/f-qt-programming-2/t-custom-mouse-dragging-an-entire-scene-25083.html .