Qt mouse movement/action combination - qt

How to manage combined mouse movement (for example left click + midle click + left click release)
I have the method mousePressEvent and mouseReleaseEvent but I did not find the way to combine them.

If you need to combine the information from mousePress and mouseRelease you need to somehow keep track which button is still pressed and which one has already been released again.
Below is a rather simply example which seems to do what you describe (actions indicated by printouts)
Let me know if this helps
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QMouseEvent>
#include <QtCore>
class MyGraphicsView: public QGraphicsView
{
public:
MyGraphicsView( QGraphicsScene *scene, QWidget *parent = 0) : QGraphicsView(scene,parent)
{
active[Qt::LeftButton] = false;
active[Qt::RightButton] = false;
active[Qt::MiddleButton] = false;
}
public slots:
virtual void mousePressEvent( QMouseEvent * event );
virtual void mouseReleaseEvent( QMouseEvent * event );
std::map< Qt::MouseButton, bool > active;
};
void MyGraphicsView::mousePressEvent( QMouseEvent * event )
{
active[event->button()] = true;
if( event->button() == Qt::LeftButton && !active[Qt::MiddleButton] && !active[Qt::RightButton]) {
qDebug() << "select";
} else if (event->button() == Qt::RightButton && !active[Qt::LeftButton] && !active[Qt::MiddleButton] ) {
qDebug() << "property";
} else if ( event->button() == Qt::LeftButton && active[Qt::MiddleButton] && !active[Qt::RightButton]) {
qDebug() << "zoom";
} else if ( event->button() == Qt::MiddleButton && !active[Qt::LeftButton] && !active[Qt::RightButton] ) {
qDebug() << "pan";
}
}
void MyGraphicsView::mouseReleaseEvent( QMouseEvent * event )
{
if( event->button() == Qt::LeftButton && active[Qt::LeftButton] && active[Qt::MiddleButton] ){
qDebug() << "move";
}
active[event->button()] = false;
}
int main( int argc, char **argv )
{
QApplication app(argc, argv);
QGraphicsScene scene;
scene.setSceneRect( -100.0, -100.0, 200.0, 200.0 );
MyGraphicsView view( &scene );
view.show();
return app.exec();
}

Related

Testing Qt application with Qt Test

I have looked at the 5 Qt testing examples including the one about GUI events, but these examples are way too simple.
I want to test my program by launching it, simulating some clicks, and checking the value of instance variables that have been changed by those clicks.
I assume that this test below is illogical: a.exec() blocks the thread until the program is closed, and when the program is closed w has been deleted I think (or will be deleted later?).
So how to write system/GUI tests?
My test:
void LaunchProgramTest::LaunchProgramTestFunction() {
QApplication a(argc, argv);
MainWindow *w = new MainWindow();
w->show();
a.exec();
int testResult = w->myTestFunction();
qDebug() << testResult; //Prints big numbers like "-17891602" or "1753770528" as if testResult was not initialized
QVERIFY2(testResult == 3, "Incorrectly changed");
}
In mainWindow.h I declared a variable:
int testValue;
Mainwindow.cpp is the class for the main GUI of the program. In the constructor I added
testValue = 2;
Then in a function that is executed upon events I wrote
void MainWindow::on_actionTest_clicked() {
testValue = 3;
}
enter code hereSo)) you need to add QTest, add .pro
QT += testlib
and
#include <QTest>
I will show an example of my implementation for MousePress, the rest you can do yourself))
struct EventMK
{
int type;
QString m_widPath;
int _timer;
int width;
int height;
QPoint p;
QPoint g;
int button;
int buttons;
int modifiers;
int _key;
QString text;
void print(){
qDebug()<<"{ \n"
<<"type "<< type<<","<<"\n"
<<"Widget_Path "<< m_widPath<<","<<"\n"
<<"Timer "<< _timer<<","<<"\n"
<<"Width "<< width<<","<<"\n"
<<"Height "<< height<<","<<"\n"
<<"Pos_x "<< p.x()<<","<<"\n"
<<"Pos_y "<< p.y()<<","<<"\n"
<<"Global_x "<< g.x()<<","<<"\n"
<<"Global_y "<< g.y()<<","<<"\n"
<<"Button "<< button<<","<<"\n"
<<"Buttons "<< buttons<<","<<"\n"
<<"Modifiers "<< modifiers<<","<<"\n"
<<"Key "<< _key<<","<<"\n"
<<"Text "<< text<<"\n"
<<"}\n";
}
};
QWidget * _getWidget(EventMK ev)
{
QString wname = ev.m_widPath;
QStringList wpath = wname.split ( "/" );
return QWidgetUtils::getAWidget(&wpath);
}
void _postExecution(EventMK ev, QWidget *widget)
{
if (widget){
//set focus
QWidgetUtils::setFocusOnWidget(widget);
//end simulation
widget->setUpdatesEnabled ( true );
widget->update();
}
}
QPoint adaptedPosition(EventMK ev, QWidget *w)
{
if (w == nullptr)
return QPoint(ev.p.x(), ev.p.y());
int orig_w = ev.width;
int orig_h = ev.height;
int curr_w = w->width();
int curr_h = w->height();
int new_x = ev.p.x() * curr_w / orig_w;
int new_y = ev.p.y() * curr_h / orig_h;
return QPoint(new_x, new_y);
}
and function implementation
void executeMousePressEvent(EventMK ev)
{
QWidget* widget = _getWidget(ev);
if ( widget == nullptr )
{
qDebug()<<"error: "<<__LINE__<<__FUNCTION__;
return;
}
// _preExecutionWithMouseMove(ev, widget);
if (widget){
QTest::mousePress ( widget, (Qt::MouseButton)ev.button ,
(Qt::KeyboardModifier) ev.modifiers,
adaptedPosition(ev,widget));
}
_postExecution(ev, widget);
}
now left to fill struct EventMK , you need to populate it from MouseButtonPress events.
Here is my example
bool eventFilter(QObject *obj, QEvent *event)
{
///
/// process control
///
//window events
if (event->type() == QEvent::KeyPress)
{
handleKeyPressEvent(obj, event);
}
//mouse events
else if (event->type() == QEvent::MouseButtonPress)
{
handleMousePressEvent(obj, event);
}
else if (event->type() == QEvent::MouseButtonRelease)
{
handleMouseReleaseEvent(obj, event);
}
else if (event->type() == QEvent::MouseButtonDblClick)
{
handleMouseDoubleEvent(obj, event);
}
else if (event->type() == QEvent::Wheel)
{
handleWheelEvent(obj, event);
}
//keyboard events
else if (event->type() == QEvent::Close)
{
handleCloseEvent(obj, event);
}
///the event should continue to reach its goal...
return false;
}
and
void handleMousePressEvent(QObject *obj, QEvent *event)
{
QWidget *widget = isValidWidget(obj);
if (!widget){
return;
}
QMouseEvent *me = dynamic_cast< QMouseEvent*> ( event );
//create the event
if (widget != nullptr){
EventMK evkm;
evkm.type = QOE_MOUSE_PRESS; // set your type
evkm._timer = _timer.restart(); // add your QElapsedTimer
evkm.m_widPath = QWidgetUtils::getWidgetPath(widget);
evkm. width = widget->width();
evkm. height = widget->height();
QPoint p ( me->pos() );
QPoint g = widget->mapToGlobal ( p );
evkm. p = p;
evkm. g = g;
evkm. button = me->button();
evkm. buttons = me->buttons();
evkm. modifiers = me->modifiers();
evkm.print();
}
//send event if EventMK is valid
}
so, it turns out we can write a scenario and run what you wanted, thanks

Pop-up calendar widget of QDateEdit on mouseclick anywhere in text area and not just the down arrow,

I used event filter on my QDateEdit 'sdateEdit' as follows:
bool Class::eventFilter ( QObject *obj, QEvent *event )
{
if(event->type() == QEvent::MouseButtonPress)
{
sdateEdit->calendarWidget()->show();
}
else
return QObject::eventFilter ( obj, event );
}
But this doesnt work.
I tried .. sdateEdit->setCalendarPopup(true). This did not work as well.
In this case I have implemented a custom QDateEdit, the strategy is to use eventFilter when you click on the QLineEdit and send a click event to the arrow:
#include <QtWidgets>
class DateEdit: public QDateEdit
{
public:
DateEdit(QWidget *parent=nullptr):
QDateEdit(parent)
{
lineEdit()->installEventFilter(this);
}
bool eventFilter(QObject *watched, QEvent *event) override
{
if(watched == lineEdit() && event->type() == QEvent::MouseButtonPress){
QStyleOptionComboBox opt;
opt.init(this);
QRect r = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxArrow, this);
QPoint p = r.center();
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, p, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(this, event);
}
return QDateEdit::eventFilter(watched, event);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
DateEdit w;
w.setCalendarPopup(true);
w.show();
return a.exec();
}

Customizing scrollbar over QListWidget

I'd like to implement my own semi-transparent scrollbar, that draws on top of the QListWidget instead of taking up permanent space in its viewport. I do not wish to use QML as my QListWidget and its dynamic content is already fully developed over 6 months now.
How can I achieve that. Stylesheets are useless for that purpose as they will not determine the positioning of the scrollbar. I'd like it to be on top of the QListWidget, not on its side, taking up its space.
I'm talking about something in the neighborhood of this:
Any hints as to how to do that will be appreciated.
What you are trying to do is a perfect example of one thing that persistently annoys me about qt - if there is some graphical effect that Qt's designers haven't thought of, creating it on your own is a pain, constant fight against Qt, and usually ends with giving up anyway.
I suspect that you do it with small screens on your mind (cell phones? tablets?), so i guess there is no other way to solve this problem.
What I am trying here is hacky, but otherwise you would probably have to rewrite entire scrollbar yourself just to add those few missing details. My proposition is:
#ifndef MYSCROLLBAR_H
#define MYSCROLLBAR_H
#include <QScrollBar>
class MyScrollBar : public QScrollBar
{
Q_OBJECT
public:
explicit MyScrollBar(QWidget *parent = 0);
protected:
void showEvent ( QShowEvent * event );
signals:
public slots:
void updateMask();
};
#endif // MYSCROLLBAR_H
And in myscrollbar.cpp
#include "myscrollbar.h"
#include <QPaintEvent>
#include <QRegion>
#include <QStyleOptionSlider>
MyScrollBar::MyScrollBar(QWidget *parent) :
QScrollBar(parent)
{
connect(this, SIGNAL(valueChanged(int)), this, SLOT(updateMask()));
}
void MyScrollBar::updateMask(){
QStyleOptionSlider opt;
initStyleOption(&opt);
QRegion r(style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this));
r+= style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this);
r+= style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this);
setMask(r);
}
void MyScrollBar::showEvent ( QShowEvent * event ){
QScrollBar::showEvent(event);
updateMask();
}
Such scroller will be transparent (both visually and event-wise) in any of it's non-vital parts. It still creates some artifacts on widgets laying below it - I guess setMask() was never supposed to be used like this. To mitigate it you can connect valueChanged() signal to update() slot of the viewport of your list widget. This worked nice on my toy-example, but if you embed custom widgets in your list it might become unbearable to cope with. It might also run you into performance problems in case of more complex applications - especially if you write for mobile platforms.
Alternatively you can just "fork" entire QScrollBar class and simply modify it's paintEvent to use less subControls than SC_All - with additional setAttribute(Qt::WA_OpaquePaintEvent, false); in constructor it should provide visual transparency. Then you should also forward mouse events (if not hitting anything important) to your viewport of list widget (again, trouble with custom widgets in view).
Now what remains is writing your own layout class (or just manually positioning it) that will put both listview and scrollbar on one another in correct positions - QStackedLayout sounds nice, but it allows only one layer to be visible at any given time - clearly not what we are looking for.
Last step is switching off default scrollbars on view, and connecting signals/slots of the default (invisible) scrollbar to slots/signals of your scrollbar, to achieve effect of actual scrolling.
Shortly this will require a LOT of coding to get done. Are you sure that such a simple effect is worth it?
** EDIT: **
I create a layout class for stacking widgets on top of one another - this question gave me motivation to do it finally ;)
#ifndef STACKLAYOUT_H
#define STACKLAYOUT_H
#include <QLayout>
class StackLayout : public QLayout
{
Q_OBJECT
public:
StackLayout();
explicit StackLayout(QWidget *parent);
~StackLayout();
void addItem ( QLayoutItem * item );
int count () const;
Qt::Orientations expandingDirections () const;
bool hasHeightForWidth () const;
int heightForWidth ( int w ) const;
QLayoutItem * itemAt ( int index ) const;
bool isEmpty () const;
QSize maximumSize () const;
int minimumHeightForWidth ( int w ) const;
QSize minimumSize () const;
void setGeometry ( const QRect & r );
QSize sizeHint () const;
QLayoutItem * takeAt ( int index );
private:
QList<QLayoutItem *> itemList;
};
#endif // STACKLAYOUT_H
And the stacklayout.cpp file:
StackLayout::StackLayout()
:QLayout()
{}
StackLayout::StackLayout(QWidget *parent) :
QLayout(parent)
{
}
StackLayout::~StackLayout(){
QLayoutItem *item;
foreach (item, itemList){
delete item;
}
}
void StackLayout::addItem ( QLayoutItem * item ){
itemList.append(item);
}
int StackLayout::count () const{
return itemList.count();
}
Qt::Orientations StackLayout::expandingDirections () const{
Qt::Orientations result = 0;
QLayoutItem *item;
foreach (item, itemList){
result = result | item->expandingDirections();
}
return result;
}
bool StackLayout::hasHeightForWidth () const{
QLayoutItem *item;
foreach (item, itemList){
if (item->hasHeightForWidth())
return true;
}
return false;
}
int StackLayout::heightForWidth ( int w ) const{
int result = 0;
QLayoutItem *item;
foreach (item, itemList){
if (item->hasHeightForWidth())
result = qMax(result, item->heightForWidth(w));
}
return result;
}
QLayoutItem * StackLayout::itemAt ( int index ) const{
if (index<itemList.count())
return itemList[index];
return 0;
}
bool StackLayout::isEmpty () const{
QLayoutItem *item;
foreach (item, itemList){
if (!item->isEmpty())
return false;
}
return true;
}
QSize StackLayout::maximumSize () const{
QSize result=QLayout::maximumSize();
QLayoutItem *item;
foreach (item, itemList){
result = result.boundedTo(item->maximumSize());
}
return result;
}
int StackLayout::minimumHeightForWidth ( int w ) const{
int result = 0;
QLayoutItem *item;
foreach (item, itemList){
if (item->hasHeightForWidth())
result = qMax(result, item->minimumHeightForWidth(w));
}
return result;
}
QSize StackLayout::minimumSize () const{
QSize result=QLayout::minimumSize();
QLayoutItem *item;
foreach (item, itemList){
result = result.expandedTo(item->minimumSize());
}
return result;
}
void StackLayout::setGeometry ( const QRect & r ){
QLayoutItem *item;
foreach (item, itemList){
item->setGeometry(r);
}
}
QSize StackLayout::sizeHint () const{
QSize result=QSize(0,0);
QLayoutItem *item;
foreach (item, itemList){
result = result.expandedTo(item->sizeHint());
}
return result;
}
QLayoutItem * StackLayout::takeAt ( int index ){
if (index < itemList.count())
return itemList.takeAt(index);
return 0;
}
Assuming you already have some nice transparent scrollbar, to insert it you would do:
QWidget* w = new QWidget();
StackLayout* sl = new StackLayout(w);
QListView* lv = new QListView(w);
sl->addWidget(lv);
QHBoxLayout* hbl = new QHBoxLayout();
sl->addItem(hbl);
TransparentScrollBar* tsc = new TransparentScrollBar(w);
hbl->addWidget(tsc,0);
hbl->insertStretch(0,1);
Here is sample code for your questoin.
Not done:
Mouse dragging of scroller
Done:
Support of any mouse hover/leave events
Support of scrolling
Scroll bar is transparent for mouse events
It is good start point for any customization depending on you task. Usage:
GUI::MegaScrollBar *bar = new GUI::MegaScrollBar( ui->listWidget );
bar->resize( 40, 30 ); // First arg - width of scroller
MegaScrollBar.h
#ifndef MEGASCROLLBAR_H
#define MEGASCROLLBAR_H
#include <QWidget>
#include <QPointer>
class QAbstractItemView;
class QResizeEvent;
namespace GUI
{
class MegaScrollBar
: public QWidget
{
Q_OBJECT
public:
MegaScrollBar( QAbstractItemView *parentView );
~MegaScrollBar();
private slots:
void updatePos();
private:
bool eventFilter( QObject *obj, QEvent *event );
void onResize( QResizeEvent *e );
void paintEvent( QPaintEvent * event );
void resizeEvent( QResizeEvent * event );
QPointer< QAbstractItemView > m_view;
QPointer< QWidget > m_scrollBtn;
};
}
#endif // MEGASCROLLBAR_H
MegaScrollBar.cpp
#include "MegaScrollBar.h"
#include <QAbstractItemView>
#include <QEvent>
#include <QResizeEvent>
#include <QScrollBar>
#include <QDebug>
#include <QPainter>
#include "ScrollButton.h"
namespace GUI
{
MegaScrollBar::MegaScrollBar( QAbstractItemView *parentView )
: QWidget( parentView, Qt::FramelessWindowHint )
, m_view( parentView )
{
Q_ASSERT( parentView );
setAttribute( Qt::WA_TranslucentBackground );
setAttribute( Qt::WA_TransparentForMouseEvents );
m_scrollBtn = new ScrollButton( parentView );
m_scrollBtn->setFixedSize( 20, 40 );
m_view->installEventFilter( this );
QScrollBar *sb = m_view->verticalScrollBar();
connect( sb, SIGNAL( valueChanged( int ) ), this, SLOT( updatePos() ) );
}
MegaScrollBar::~MegaScrollBar()
{
removeEventFilter( m_view );
}
bool MegaScrollBar::eventFilter( QObject *obj, QEvent *event )
{
switch ( event->type() )
{
case QEvent::Enter:
m_scrollBtn->show();
break;
case QEvent::Leave:
m_scrollBtn->hide();
break;
case QEvent::Resize:
onResize( static_cast< QResizeEvent * >( event ) );
break;
}
return QWidget::eventFilter( obj, event );
}
void MegaScrollBar::onResize( QResizeEvent *e )
{
const int x = e->size().width() - width();
const int y = 0;
const int w = width();
const int h = e->size().height();
move( x, y );
resize( w, h );
updatePos();
}
void MegaScrollBar::updatePos()
{
QScrollBar *sb = m_view->verticalScrollBar();
const int min = sb->minimum();
const int val = sb->value();
const int max = sb->maximum();
const int x = pos().x() + ( width() - m_scrollBtn->width() ) / 2;
if ( max == 0 )
{
m_scrollBtn->move( x, pos().y() );
return ;
}
const int maxY = height() - m_scrollBtn->height();
const int y = ( maxY * val ) / max;
m_scrollBtn->move( x, y );
}
void MegaScrollBar::paintEvent( QPaintEvent * event )
{
Q_UNUSED( event );
QPainter p( this );
QRect rc( 0, 0, rect().width() - 1, rect().height() - 1 );
// Draw any scroll background
p.fillRect( rc, QColor( 255, 255, 200, 100 ) );
}
void MegaScrollBar::resizeEvent( QResizeEvent * event )
{
Q_UNUSED( event );
updatePos();
}
}
Preview:
It is possible to set up any widget for scroll button: Here is custom one:
ScrollButton.h
#ifndef SCROLLBUTTON_H
#define SCROLLBUTTON_H
#include <QWidget>
namespace GUI
{
class ScrollButton
: public QWidget
{
Q_OBJECT
public:
ScrollButton( QWidget *parent );
~ScrollButton();
private:
void paintEvent( QPaintEvent * event );
};
}
#endif // SCROLLBUTTON_H
ScrollButton.cpp
#include "ScrollButton.h"
#include <QPainter>
#include <QGraphicsOpacityEffect>
#include <QColor>
namespace GUI
{
ScrollButton::ScrollButton( QWidget *parent )
: QWidget( parent )
{
QGraphicsOpacityEffect *op = new QGraphicsOpacityEffect( this );
op->setOpacity( 0.5 );
setGraphicsEffect( op );
}
ScrollButton::~ScrollButton()
{
}
void ScrollButton::paintEvent( QPaintEvent * event )
{
Q_UNUSED( event );
// Draw any scroll button
QPainter p( this );
QRect rc( 5, 5, rect().width() - 6, rect().height() - 6 );
p.fillRect( rc, QColor( 0, 0, 0, 255 ) );
}
}
Please comment, if you can't handle mouse interaction.

QGraphicsPixmapItem, alternative methods of connecting to slots

I'm aware I need to derive from QObject in order to connect to a slot if I am using QGraphicsPixmapItem, but I am struggling to do this. I have tried alternative ways to achieve what I want, I have tried onMousePress and isSelectable i.e.
run->setFlag(QGraphicsPixmapItem::ItemIsSelectable);
if (run->isSelected())
{
qDebug() << "selected";
}
else if (!run->isSelected())
{
qDebug() << "not selected";
}
although run is selectable, the first argument is never true, it is always "not selected"
This is my code, I am working on the slot method;
mainwindow.cpp
int MainWindow::sim()
{
...
QGraphicsPixmapItem* run = new QGraphicsPixmapItem(QPixmap::fromImage(image6));
run->scale(0.3,0.3);
run->setPos(-200,-200);
run->setFlag(QGraphicsPixmapItem::ItemIsSelectable);
run->setCursor(Qt::PointingHandCursor);
connect(run, SIGNAL(selectionChanged()), this, SLOT(runClicked()));
scene->addItem(run);
//pause
QGraphicsPixmapItem* pause = new QGraphicsPixmapItem(QPixmap::fromImage(image7));
pause->scale(0.3,0.3);
pause->setPos(-160,-197);
pause->setFlag(QGraphicsPixmapItem::ItemIsSelectable);
pause->setCursor(Qt::PointingHandCursor);
connect(pause, SIGNAL(selectionChanged()), this, SLOT(pauseClicked()));
scene->addItem(pause);
...
}
void MainWindow::runClicked()
{
qDebug() << "run Clicked";
}
void MainWindow::pauseClicked()
{
qDebug() << "pause Clicked";
}
mainwindow.h
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
int sim();
...
public slots:
void runClicked();
void pauseClicked();
...
So obviously I get the error when connecting to the slots. Could anyone help please? Thank you.
To find out if your item is selected, do this:
QVariant MyItem::itemChange( GraphicsItemChange change, const QVariant& value )
{
if ( change == QGraphicsItem::ItemSelectedHasChanged ) {
qDebug() << ( isSelected() ? "selected" : "not selected" );
}
return QGraphicsItem::itemChange( change, value );
}
If you want to use signals and slots, you need to subclass both QObject and QGraphicsPixmapItem.
Because QObject doesn't contain clicked() signal, you need to implement that, too, by re-implementing
void mousePressEvent ( QGraphicsSceneMouseEvent *e ) and void mouseReleaseEvent ( QGraphicsSceneMouseEvent *e ).
MyItem:
#pragma once
#include <QGraphicsPixmapItem>
#include <qobject.h>
#include <QMouseEvent>
#include <QGraphicsSceneMouseEvent>
class MyItem: public QObject, public QGraphicsPixmapItem
/* moc.exe requires to derive from QObject first! */
{
Q_OBJECT
public:
MyItem(QGraphicsItem *parent = 0): QObject(), QGraphicsPixmapItem(parent)
{
}
MyItem(const QPixmap & pixmap, QGraphicsItem * parent = 0 ): QObject(),
QGraphicsPixmapItem(pixmap, parent)
{
}
signals:
void clicked();
protected:
// re-implement processing of mouse events
void mouseReleaseEvent ( QGraphicsSceneMouseEvent *e )
{
// check if cursor not moved since click beginning
if ((m_mouseClick) && (e->pos() == m_lastPoint))
{
// do something: for example emit Click signal
emit clicked();
}
}
void mousePressEvent ( QGraphicsSceneMouseEvent *e )
{
// store click position
m_lastPoint = e->pos();
// set the flag meaning "click begin"
m_mouseClick = true;
}
private:
bool m_mouseClick;
QPointF m_lastPoint;
};
And simple example of usage:
#include <qgraphicsview.h>
#include <qgraphicsscene.h>
#include "reader.h"
#include <qdebug.h>
class MainAppClass: public QObject
{
Q_OBJECT
public:
MainAppClass()
{
QGraphicsScene *scene = new QGraphicsScene();;
scene->setSceneRect( -100.0, -100.0, 200.0, 200.0 );
MyItem *item = new MyItem(QPixmap("about.png"));
connect(item, SIGNAL(clicked()), this, SLOT(pixmapClicked()));
scene->addItem(item);
QGraphicsView * view = new QGraphicsView( scene );
view->setRenderHints( QPainter::Antialiasing );
view->show();
}
public slots:
void pixmapClicked()
{
qDebug() << "item clicked!" ;
}
};

Can I get mouse events in a QGraphicsItem?

I have subclassed QGraphicsRectItem, and it's not receiving any mouse events. I've seen other questions similar to this say I need to enable mouse tracking, but setMouseTracking is in QWidget, and QGraphicsItem does not appear to be a QWidget.
I've implemented paint, and that's working. In my subclassed QGraphicsView I am getting mouse events.
The docs seem to think I should just override the mousePressEvent function (for example) and I should start getting the events. Whether I forward the mousePressEvent to the superclass of my QGraphicsView or not doesn't seem to make any difference.
In your subclassed QGraphicsView, you need to call the default implementations of overridden mouse event methods if you want them to propagate down to the items. For example:
CustomView::mousePressEvent(QMouseEvent *event)
{
// handle the event as you like
QGraphicsView::mousePressEvent(event); // then call default implementation
}
If you want to accept hover events, you need to call QGraphicsItem::setAcceptHoverEvents(true);. Otherwise you do not need to enable any particular mouse tracking.
EDIT: Here is a full working example:
#include <QtGui>
class CustomView : public QGraphicsView
{
protected:
void mousePressEvent(QMouseEvent *event)
{
qDebug() << "Custom view clicked.";
QGraphicsView::mousePressEvent(event);
}
};
class CustomItem : public QGraphicsRectItem
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Custom item clicked.";
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomItem item;
item.setRect(20, 20, 60, 60);
QGraphicsScene scene(0, 0, 100, 100);
scene.addItem(&item);
CustomView view;
view.setScene(&scene);
view.show();
return a.exec();
}
I went through the same problems you have encountered and I wanted to add some insights on top of Anthony's really good answer. Here is an example I wrote showing some features that can be implemented using the mouse events and the keyboard events.
Note that the events do not propagate to QGraphicsItems in a QGraphicsItemGroup or in a QList<QGraphicsItem> (it took me a while to figure that out).
#include <QtGui>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
class CustomItem : public QGraphicsEllipseItem
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::LeftButton) {
if(event->modifiers() == Qt::ShiftModifier) {
qDebug() << "Custom item left clicked with shift key.";
// add the item to the selection
setSelected(true);
} else if(event->modifiers() == Qt::AltModifier){
qDebug() << "Custom item left clicked with alt key.";
// resize the item
double radius = boundingRect().width() / 2.0;
_center = QPointF(boundingRect().topLeft().x() + pos().x() + radius, boundingRect().topLeft().y() + pos().y() + radius);
QPointF pos = event->scenePos();
qDebug() << boundingRect() << radius << this->pos() << pos << event->pos();
double dist = sqrt(pow(_center.x()-pos.x(), 2) + pow(_center.y()-pos.y(), 2));
if(dist / radius > 0.8) {
qDebug() << dist << radius << dist / radius;
_isResizing = true;
} else {
_isResizing = false;
}
} else {
qDebug() << "Custom item left clicked.";
QGraphicsItem::mousePressEvent(event);
event->accept();
}
} else if(event->button() == Qt::RightButton) {
qDebug() << "Custom item right clicked.";
event->ignore();
}
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(event->modifiers() == Qt::AltModifier && _isResizing){
QPointF pos = event->scenePos();
double dist = sqrt(pow(_center.x()-pos.x(), 2) + pow(_center.y()-pos.y(), 2));
setRect(_center.x()-this->pos().x()-dist, _center.y()-this->pos().y()-dist, dist*2, dist*2);
} else if(event->modifiers() != Qt::AltModifier) {
qDebug() << "Custom item moved.";
QGraphicsItem::mouseMoveEvent(event);
qDebug()<<"moved"<<pos();
}
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(event->modifiers() == Qt::AltModifier && _isResizing) {
_isResizing = false;
} else if(event->modifiers() != Qt::ShiftModifier) {
QGraphicsItem::mouseReleaseEvent(event);
}
}
int type() const
{
// Enable the use of qgraphicsitem_cast with this item.
return UserType+1;
}
private:
QPointF _center;
bool _isResizing;
};
class CustomScene : public QGraphicsScene
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Custom scene clicked.";
QGraphicsScene::mousePressEvent(event);
if(!event->isAccepted()) {
if(event->button() == Qt::LeftButton) {
// add a custom item to the scene
QPointF pt = event->scenePos();
CustomItem * item = new CustomItem();
item->setRect(pt.x()-25, pt.y()-25, 50, 50);
item->setFlags(QGraphicsItem::ItemIsSelectable|
QGraphicsItem::ItemIsMovable);
addItem(item);
} else if(event->button() == Qt::RightButton) {
// check whether there is an item under the cursor
QGraphicsItem * itemToRemove = NULL;
foreach(auto item, items(event->scenePos())) {
if(item->type() == QGraphicsItem::UserType+1) {
itemToRemove = item;
break;
}
}
if(itemToRemove) {
// remove the item from the graphicsScene
removeItem(itemToRemove);
}
}
}
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Custom scene moved.";
QGraphicsScene::mouseMoveEvent(event);
}
void keyPressEvent(QKeyEvent * event) {
if(event->key() == Qt::Key_Backspace) {
// remove all selected items
qDebug() << "selected items" << selectedItems().size();
while(!selectedItems().isEmpty()) {
removeItem(selectedItems().front());
}
} else {
QGraphicsScene::keyPressEvent(event);
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomItem item;
item.setRect(20, 20, 60, 60);
item.setFlags(QGraphicsItem::ItemIsSelectable|
QGraphicsItem::ItemIsMovable);
CustomScene scene;
scene.setSceneRect(0, 0, 500, 500);
scene.addItem(&item);
QGraphicsView view;
view.setScene(&scene);
view.show();
return a.exec();
}
Hope it helps too!
I had a similar problem with a view not accepting mouse clicks. The problem was that I needed to enable the view ( ui->view->setEnabled(true) ).

Resources