PDA

View Full Version : qgraphicsview cursor setting logic



mcarter
14th November 2012, 21:41
I am having an issue with QGraphicsView wrt setting the cursor when working with a QGraphicsItem. The basic setup is this, I set an initial cursor (CrossCursor=2) for the viewport of the view. I use a contextMenu (thru customContextMenuRequested) to allow a selection ("Extract Item") for creating an extraction qgraphicsitem that i can move around. When this item is created I change the viewport cursor to another cursor (ForbiddenCursor=14) and I set a specific cursor (SizeAllCursor=9) for the item. I also clear the contextMenu for the view and create a signal/slot mechanism to use a contextMenu for the item. The item contextmenu just has a "DONE" option that is suppose to delete the item and set the viewport cursor back to CrossCursor. However, the cursor viewed in the viewport after the item is deleted is still the ForbiddenCursor.

I added an implementation of the viewportEvent method for the view and added a debug message for CursorChange. I see all the expected cursor changes, but there is an extra change event at the very end after I try to set the cursor back to CrossCursor. The event shows that the cursor is being set to the ForbiddenCursor.

Digging thru the Qt source code (qgraphicsview.cpp), there is an internal originalCursor that is being saved/used when a mouse move is triggered. This is where the cursor is being set back to the ForbidenCursor in the app (followed it thru with qtcreator debugger). It seems that my item contextMenu and subsequent set cursor is not seen by the qgraphicsview and the next move of the mouse sets the cursor to this original cursor, which is really an old cursor. There is no access to this originalCursor to clear it or disable its use.

I was hoping that someone can help me find a way around this issue. I need to change the cursors of the viewport with certain modes, but I cannot find the proper way/order that allows the viewport cursor to go back to the original cursor after the items use.

-----------------------

I have a much larger application that I am seeing the issue, but I have created a small app that exhibits the same behavior as descibed above and is included below.


#include <QApplication>
#include "window.h"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window w;
w.show();
return app.exec();
}

#ifndef _WINDOW_H_
#define _WINDOW_H_

#include <QMainWindow>

class QGraphicsScene;
class View;

class Window : public QMainWindow
{
Q_OBJECT

public:
Window( QWidget * p = 0 );
~Window();

public slots:
void viewContextMenu( const QPoint & pt );
void extractContextMenu( const QPoint & pt, const QRectF & rect );

private:
QGraphicsScene * m_scene;
View * m_view;
};

#endif

#include "window.h"
#include "view.h"
#include "item.h"

#include <QAction>
#include <QGraphicsScene>
#include <QMenu>

Window::Window( QWidget * p )
: QMainWindow( p )
{
m_scene = new QGraphicsScene( 0, 0, 1000, 1000);
m_scene->setBackgroundBrush(QBrush(Qt::black, Qt::SolidPattern));

m_view = new View;
m_view->setScene(m_scene);
m_view->fitInView( m_scene->sceneRect() );
setCentralWidget(m_view);

QGraphicsRectItem * r = new QGraphicsRectItem;
r->setPen( QPen(Qt::cyan) );
r->setRect( m_scene->sceneRect() );
m_scene->addItem( r );

m_view->setContextMenuPolicy( Qt::CustomContextMenu );
connect( m_view,
SIGNAL( customContextMenuRequested(const QPoint &) ),
SLOT( viewContextMenu(const QPoint &) ) );

m_view->viewport()->setCursor( Qt::CrossCursor );
}

Window::~Window()
{
}

void Window::viewContextMenu( const QPoint & pt )
{
QMenu contextMenu;
QAction * act;

contextMenu.addAction( tr("Extract Item") );

act = contextMenu.exec( m_view->viewport()->mapToGlobal(pt) );
if( NULL != act )
{
Item *item = new Item;
item->setRect( QRectF( 0, 0, 100, 100 ) );
item->setPen( QPen(Qt::green) );
item->setBrush( QBrush(Qt::red) );
item->setPos( m_view->mapToScene(pt) );
item->setCursor( Qt::SizeAllCursor );
item->setFlag( QGraphicsItem::ItemIsMovable, true );
connect( item,
SIGNAL( requestContextMenu(const QPoint&,const QRectF&) ),
SLOT( extractContextMenu(const QPoint&,const QRectF&) ) );
m_scene->addItem(item);

m_view->setContextMenuPolicy( Qt::DefaultContextMenu );
m_view->viewport()->setCursor( Qt::ForbiddenCursor );
}
}

void Window::extractContextMenu( const QPoint & pt, const QRectF & /*rect*/ )
{
QMenu contextMenu;
QAction * act;
Item *pGI = qobject_cast<Item*>(sender());

contextMenu.addAction( tr("Done") );

act = contextMenu.exec( pt );
if( NULL != act )
{
m_view->setContextMenuPolicy( Qt::CustomContextMenu );
m_view->scene()->removeItem( pGI );
delete pGI;

m_view->viewport()->setCursor( Qt::CrossCursor );
}
}

#ifndef __VIEW_H__
#define __VIEW_H__

#include <QDebug>
#include <QEvent>
#include <QGraphicsView>

class View : public QGraphicsView
{
Q_OBJECT

public:
View( QWidget* parent = 0 ) : QGraphicsView( parent )
{
setFrameStyle( QFrame::NoFrame );
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
}

protected:
bool viewportEvent( QEvent * event )
{
if( QEvent::CursorChange == event->type() )
{
qDebug() << "Cursor Change: " << viewport()->cursor().shape();
}
return QGraphicsView::viewportEvent(event);
}
};

#endif

#ifndef __ITEM_H__
#define __ITEM_H__

#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>

class Item : public QObject, public QGraphicsRectItem
{
Q_OBJECT

public:
Item() : QGraphicsRectItem( NULL ) {}
~Item() {}

signals:
void requestContextMenu( const QPoint & pt, const QRectF & requestRect );

protected:
void contextMenuEvent( QGraphicsSceneContextMenuEvent* gscme )
{
emit requestContextMenu( gscme->screenPos(), mapRectToScene( rect() ) );
}
};

#endif

norobro
15th November 2012, 04:43
Interesting problem.

In the source code (here (http://qt.gitorious.org/qt/qt/blobs/4.8/src/gui/graphicsview/qgraphicsview.cpp#line670)) there is this little tidbit:
// Find the topmost item under the mouse with a cursor.Try making the QGraphicsRectItem that is the size of the sceneRect a class member and then change the cursor on it in your context menu slots. That way the mouseMoveEventHandler function will return before the code that resets the original cursor.

mcarter
15th November 2012, 13:25
Sorry, that sceneRect rect item code was left over from some previous test code . . . thats what happens with cut&paste. I removed that code and the same issue persists. I assume it has more to do with deleting the item before it has a chance to return to mouse move event.

wysota
15th November 2012, 14:38
Does it help if you use QApplication::setOverrideCursor() and QApplication::restoreOverrideCursor() instead of setCursor()? Of course provided it makes sense to set an override cursor for your application logic. If not then I suggest you post a custom event to your view that will restore the proper cursor after are your functionality is done. Then you'll modify the cursor after the faulty code in Qt executes so it is bound to work.

mcarter
15th November 2012, 16:04
This widget is one of several components. I need the cursor to change just on the widget itself and not on the entire application so the override cursor is not an option.

So I took your suggestion and implemented a custom event (with the code below), but the event is processed before returning back to qgraphicsview and same issue happens :-(

I even added the ability to change the viewport cursor from the view context menu, and the same thing happens . . . the set cursor and the change cursor event are happening before the qv mouse move event and when it returns to the mouse event, it sets the cursor to the original "unwanted" cursor.


#ifndef __EVENT_H__
#define __EVENT_H__

#include <QEvent>
#include <QCursor>

class SetCursorEvent : public QEvent
{
public:
SetCursorEvent( const QCursor & cursor )
: QEvent( SetCursorEvent::type() ),
m_cursor( cursor )
{}

virtual ~SetCursorEvent()
{}

static QEvent::Type type()
{
return EventType;
}

QCursor cursor() const
{
return m_cursor;
}

private:
static QEvent::Type EventType;
QCursor m_cursor;
};

#endif

#ifndef __VIEW_H__
#define __VIEW_H__

#include "event.h"

#include <QDebug>
#include <QEvent>
#include <QGraphicsView>

class View : public QGraphicsView
{
Q_OBJECT

public:
View( QWidget* parent = 0 ) : QGraphicsView( parent )
{
setFrameStyle( QFrame::NoFrame );
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
}

protected:
bool viewportEvent( QEvent * event )
{
if( QEvent::CursorChange == event->type() )
{
qDebug() << "Cursor Change:" << viewport()->cursor().shape();
}
else if( SetCursorEvent::type() == event->type() )
{
SetCursorEvent * e = static_cast<SetCursorEvent*>(event);
qDebug() << "SetCursorEvent:" << e->cursor().shape();
viewport()->setCursor( e->cursor() );
}
return QGraphicsView::viewportEvent(event);
}
};

#endif

#include "event.h"
#include "window.h"
#include "view.h"
#include "item.h"

#include <QAction>
#include <QApplication>
#include <QGraphicsScene>
#include <QMenu>

QEvent::Type SetCursorEvent::EventType =
static_cast<QEvent::Type>(QEvent::registerEventType());

Window::Window( QWidget * p )
: QMainWindow( p )
{
m_scene = new QGraphicsScene( 0, 0, 1000, 1000);
m_scene->setBackgroundBrush(QBrush(Qt::black, Qt::SolidPattern));

m_view = new View;
m_view->setScene(m_scene);
m_view->fitInView( m_scene->sceneRect() );
setCentralWidget(m_view);

m_view->setContextMenuPolicy( Qt::CustomContextMenu );
connect( m_view,
SIGNAL( customContextMenuRequested(const QPoint &) ),
SLOT( viewContextMenu(const QPoint &) ) );

m_view->viewport()->setCursor( Qt::CrossCursor );
}

Window::~Window()
{
}

void Window::viewContextMenu( const QPoint & pt )
{
QMenu contextMenu;
QAction * act;

contextMenu.addAction( tr("Extract Item") );
contextMenu.addAction( tr("Change Cursor") );

act = contextMenu.exec( m_view->viewport()->mapToGlobal(pt) );
if( NULL != act )
{
switch( contextMenu.actions().indexOf(act) )
{
case 0:
{
Item *item = new Item;
item->setRect( QRectF( 0, 0, 100, 100 ) );
item->setPen( QPen(Qt::green) );
item->setBrush( QBrush(Qt::red) );
item->setPos( m_view->mapToScene(pt) );
item->setCursor( Qt::SizeAllCursor );
item->setFlag( QGraphicsItem::ItemIsMovable, true );
connect( item,
SIGNAL( requestContextMenu(const QPoint&,const QRectF&) ),
SLOT( extractContextMenu(const QPoint&,const QRectF&) ) );
m_scene->addItem(item);

m_view->setContextMenuPolicy( Qt::DefaultContextMenu );
m_view->viewport()->setCursor( Qt::ForbiddenCursor );
break;
}
case 1:
{
Qt::CursorShape shape;
if( m_view->viewport()->cursor().shape() == Qt::OpenHandCursor )
{
shape = Qt::CrossCursor;
}
else
{
shape = Qt::OpenHandCursor;
}
SetCursorEvent * e = new SetCursorEvent( shape );
QApplication::postEvent( m_view->viewport(), e );
//m_view->viewport()->setCursor( shape );
break;
}
}
}
}

void Window::extractContextMenu( const QPoint & pt, const QRectF & /*rect*/ )
{
QMenu contextMenu;
QAction * act;
Item *pGI = qobject_cast<Item*>(sender());

contextMenu.addAction( tr("Done") );
contextMenu.addAction( tr("Change Cursor") );

act = contextMenu.exec( pt );
if( NULL != act )
{
switch( contextMenu.actions().indexOf(act) )
{
case 0:
{
m_view->setContextMenuPolicy( Qt::CustomContextMenu );
m_view->scene()->removeItem( pGI );
delete pGI;
//m_view->viewport()->setCursor( Qt::CrossCursor );
SetCursorEvent * e = new SetCursorEvent( Qt::CrossCursor );
QApplication::postEvent( m_view->viewport(), e );
break;
}
case 1:
{
//m_view->viewport()->setCursor( Qt::WaitCursor );
SetCursorEvent * e = new SetCursorEvent( Qt::WaitCursor );
QApplication::postEvent( m_view->viewport(), e );
break;
}
}
}
}

wysota
15th November 2012, 23:55
In that case use a singleshot timer or QMetaObject::invokeMethod() to call the cursor change functionality with a small delay (e.g. 20ms). The delay will not be noticable to the user.

mcarter
16th November 2012, 21:44
With singleShot timer, I can get the delay but the single/slot does not allow any arguments, eg new cursorMask . . . but I can probably work around that.
With invokeMethod, I can call the needed method with the proper argument, but I do not see a way to specify a delay? Am I missing something? Or are you saying to just use a 20 ms sleep?

wysota
17th November 2012, 00:19
With singleShot timer, I can get the delay but the single/slot does not allow any arguments, eg new cursorMask . . . but I can probably work around that.
With invokeMethod, I can call the needed method with the proper argument, but I do not see a way to specify a delay? Am I missing something? Or are you saying to just use a 20 ms sleep?

No, no sleep(). You can store the "argument" in some member variable and then fetch it from there when the timer fires.

mcarter
26th November 2012, 14:49
Right, no sleep . . . was just thinking how to use the invokeMethod.

I implemented the singleShot timer, and while it works with my linux laptop, it does not seem to work on my windows test machine. The cursor changes, but once I move the mouse it reverts to the previous cursor again.

I did find QTBUG-3732 and QTBUG-4190 in the Qt bug reports, which both seem exactly related to what I am seeing. However, they are both closed (2009/2010). What is the best procedure to re-open these bugs, because it looks like an obvious logic problem with the cursor and graphicsview. My suggestion would be to get rid of the internal originalCursor and use the cursor of the GraphicsView, since that it not really used anywhere, for the cursor of the viewport. I guess in the meantime I need to abort what I am trying to accomplish or try to "fix" graphicsview with my oen code.

Also, when I was working in windows, I noticed that the cursors are really unusable with a view that is mostly black (especially CrossCursor) . . . the cursor just "disappears". Do I need to use a custom cursor to get the same feel as with linux and X11?