Qt: QGraphicsScene mouse position always (0,0) - c++

I've created my own class that inherits from QGraphicsScene. I've also make two methods for mouse events. Later I do qDebug() to check if position of my click is correct, and it looks that it's not correct. It always returns me QPoint(0,0).
I've tried many mapfrom - things, but nothing worked. Is there way to make those positions work correctly?
Some code:
MyScene.cpp
#include "pianoscene.h"
#include <QDebug>
#include <QGraphicsView>
MyScene::MyScene()
{
/*setRect(0,0,100,100);
QGraphicsRectItem *kek = new QGraphicsRectItem;
QPen pen;
pen.setColor(Qt::red);
kek->setRect(0,0,50,50);
kek->setPen(pen);
this->addItem(kek);*/
}
void MyScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QPoint punkt = views().first()->mapFromScene(event->pos().toPoint());
qDebug()<<"wcisk"<<punkt;
}
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qDebug()<<"wcisk"<<event->pos();
}

pos() contains the item coordinates, not the scene coordinates. To get scene coordinates, use scenePos():
void MyScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QPoint punkt = views().first()->mapFromScene(event->scenePos().toPoint());
qDebug()<<"wcisk"<<punkt;
}
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qDebug()<<"wcisk"<<event->scenePos();
}
Also, if you just need to propagate coordinates to other functions, don't use toPoint(). If you have no actual reason to convert to a QPoint, just use QPointF, as returned by scenePos() and pos(). No need for needless conversions.

Related

QGraphicsView Zooming an Image to cursor when image is smaller than view

I'm trying to implement code that will zoom in an image as well as move the image towards the cursor, but not completely re-center on the cursor (similar to how Google Maps works, or exactly like how Picasa Photo Viewer used to work if anyone's seen that). The key is that it needs to zoom in the image, and have the same pixels that were under the mouse cursor, still be under it.
The sample code provided in the accepted answer of this question is close but does not completely solve the problem, as that was a question of moreso how to zoom in with the cursor and sidesteps the problem inherent in QGraphicsView.
The problem is: as seen in the sample code's comments in the header, it is not possible to perform this function if the view is bigger than the image. In other words, if you zoom out the image such that there's no scroll bars, then zoom in, the image will zoom in to the center, not to the mouse cursor; only by zooming in enough that there are horizontal and vertical scroll bars, will it zoom-in to the cursor and keep the same pixel underneath the cursor.
This seems to be a fundamental problem with QGraphicsView: QGraphicsView::setTransformationAnchor(QGraphicsView::AnchorUnderMouse) can achieve almost the exact same result, but the Qt docs report the same problem:
Note that the effect of this property is noticeable when only a part of the scene is visible (i.e., when there are scroll bars). Otherwise, if the whole scene fits in the view, QGraphicsScene uses the view alignment to position the scene in the view.
I suspect this is because both methods are scrolling the viewport, but if the image is too small compared to the viewport, there's no way to actually scroll it.
I suspect the solution may require abandoning scrolling and actually moving the image, but I can't figure out how to move the image such that the same pixel stays underneath the mouse cursor.
Here's some code that I've implemented and does not work:
Graphics_view_zoom.h:
#include <QObject>
#include <QGraphicsView>
/*!
* This class adds ability to zoom QGraphicsView using mouse wheel. The point under cursor
* remains motionless while it's possible.
*
* Note that it becomes not possible when the scene's
* size is not large enough comparing to the viewport size. QGraphicsView centers the picture
* when it's smaller than the view. And QGraphicsView's scrolls boundaries don't allow to
* put any picture point at any viewport position.
*
* When the user starts scrolling, this class remembers original scene position and
* keeps it until scrolling is completed. It's better than getting original scene position at
* each scrolling step because that approach leads to position errors due to before-mentioned
* positioning restrictions.
*
* When zommed using scroll, this class emits zoomed() signal.
*
* Usage:
*
* new Graphics_view_zoom(view);
*
* The object will be deleted automatically when the view is deleted.
*
* You can set keyboard modifiers used for zooming using set_modified(). Zooming will be
* performed only on exact match of modifiers combination. The default modifier is Ctrl.
*
* You can change zoom velocity by calling set_zoom_factor_base().
* Zoom coefficient is calculated as zoom_factor_base^angle_delta
* (see QWheelEvent::angleDelta).
* The default zoom factor base is 1.0015.
*/
class Graphics_view_zoom : public QObject {
Q_OBJECT
public:
Graphics_view_zoom(QGraphicsView* view);
void gentle_zoom(double factor);
void set_modifiers(Qt::KeyboardModifiers modifiers);
void set_zoom_factor_base(double value);
private:
QGraphicsView* _view;
Qt::KeyboardModifiers _modifiers;
double _zoom_factor_base;
QPointF target_scene_pos, target_viewport_pos;
bool eventFilter(QObject* object, QEvent* event);
signals:
void zoomed();
};
Graphics_view_zoom.cpp:
#include "Graphics_view_zoom.h"
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <qmath.h>
Graphics_view_zoom::Graphics_view_zoom(QGraphicsView* view)
: QObject(view), _view(view)
{
_view->viewport()->installEventFilter(this);
_view->setMouseTracking(true);
_modifiers = Qt::ControlModifier;
_zoom_factor_base = 1.0015;
}
void Graphics_view_zoom::gentle_zoom(double factor) {
_view->scale(factor, factor);
_view->centerOn(target_scene_pos);
QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0,
_view->viewport()->height() / 2.0);
QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos;
_view->centerOn(_view->mapToScene(viewport_center.toPoint()));
emit zoomed();
}
void Graphics_view_zoom::set_modifiers(Qt::KeyboardModifiers modifiers) {
_modifiers = modifiers;
}
void Graphics_view_zoom::set_zoom_factor_base(double value) {
_zoom_factor_base = value;
}
bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::MouseMove) {
QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
QPointF delta = target_viewport_pos - mouse_event->pos();
if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) {
target_viewport_pos = mouse_event->pos();
target_scene_pos = _view->mapToScene(mouse_event->pos());
}
} else if (event->type() == QEvent::Wheel) {
QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
if (QApplication::keyboardModifiers() == _modifiers) {
if (wheel_event->orientation() == Qt::Vertical) {
double angle = wheel_event->angleDelta().y();
double factor = qPow(_zoom_factor_base, angle);
gentle_zoom(factor);
return true;
}
}
}
Q_UNUSED(object)
return false;
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h:
#pragma once
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
};
mainwindow.cpp:
#include "mainwindow.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPixmap>
#include <Graphics_view_zoom.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setFixedSize(1000,1000);
QGraphicsScene* s = new QGraphicsScene(this);
QGraphicsView* v = new QGraphicsView(this);
Graphics_view_zoom* z = new Graphics_view_zoom(v);
z->set_modifiers(Qt::NoModifier);
v->setScene(s);
v->setFixedSize(1000,1000);
QPixmap pix;
if(pix.load("C:\\full\\path\\to_an_image_file.jpg"))
{
s->addPixmap(pix);
}
}
I've also tried the fix found here but this also has the exact same problem: the pixel will only remain under the cursor if the image is zoomed in far enough that there are both vertical and horizontal scroll options available.

QWidget clips painting on mouseMoveEvent

I'm creating a marquee selection type tool in a QWidget and it all works fine, except for a rendering bug when dragging the marquee. If I call update without any arguments everything works beautifully, but if I call update to only include the region of the marquee as the user is dragging, then two of the edges get cutoff if the mouse is moving at a moderate speed.
Here's an image of what it looks like while dragging towards the lower right corner:
Screenshot while clicking and dragging in the widget
I thought replacing update() with repaint() might fix it but that didn't work either.
What is the correct way that I should be doing this? I've included some very basic code that demonstrates the problem.
#include <QPainter>
#include <QPaintEvent>
#include <QWidget>
class Widget : public QWidget
{
public:
Widget(QWidget *parent = nullptr)
: QWidget(parent)
{
}
void mousePressEvent(QMouseEvent *e) override
{
startPt = e->pos();
rect = QRect();
update(); // clear the entire widget
}
void mouseMoveEvent(QMouseEvent *e) override
{
rect = QRect(startPt, e->pos()).normalized();
update(rect.adjusted(-2,-2,2,2)); // adjusted to include stroke
}
void paintEvent(QPaintEvent *event) override
{
QPainter p(this);
p.setBrush(Qt::NoBrush);
p.setPen(QPen(Qt::black, 2));
p.drawRect(rect);
}
private:
QRect rect;
QPoint startPt;
};

QT: Detecting left and right mouse press events on QGraphicsItem

I am having trouble registering right click on my custom QGraphics item.
My custom class's header:
#ifndef TILE_SQUARE_H
#define TILE_SQUARE_H
#include <QPainter>
#include <QGraphicsItem>
#include <QtDebug>
#include <QMouseEvent>
class Tile_Square : public QGraphicsItem
{
public:
Tile_Square();
bool Pressed;
int MovementCostValue;
QRectF boundingRect() const;
void paint(QPainter *painter,const QStyleOptionGraphicsItem *option, QWidget *widget);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void contextMenuEvent(QGraphicsSceneContextMenuEvent *cevent);
};
#endif // TILE_SQUARE_H
And here is the implementation of said class:
#include "tile_square.h"
Tile_Square::Tile_Square()
{
Pressed = false;
MovementCostValue = 1;
}
QRectF Tile_Square::boundingRect() const
{
return QRectF(0,0,10,10);
}
void Tile_Square::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QRectF rec = boundingRect();
QBrush brush(Qt::white);
painter->fillRect(rec,brush);
painter->drawRect(rec);
}
//Left click
void Tile_Square::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QMouseEvent *mouseevent = static_cast<QMouseEvent *>(*event);
if(mouseevent->buttons() == Qt::LeftButton){
MovementCostValue++;
qDebug() << "LEFT: Movement value is: " << MovementCostValue;
}
else if(mouseevent->buttons() == Qt::RightButton){
MovementCostValue--;
qDebug() << "RIGHT: Movement value is: " << MovementCostValue;
}
update();
QGraphicsItem::mousePressEvent(event);
}
I am drawing this on a Dialog window with a graphicsview and a graphicsscene.
I wanto increase the internal int of the class on left click, and decrease it on right click. Problem is, mousepressevent registers the event and not which button was pressed. In my code you can see i tried to cast it to regular Mouse Event, but failed, obviously.
Honestly i want to write
event->buttons() == Qt::LeftButton
but the QGraphicsSceneMouseEvent *event does not have such one. What is the issue?
I also tried using the contextmenuevent, which works perfectly and registers the right click, but the regular mousepressevent gets registered too.
First, you cannot cast from QGraphicsSceneMouseEvent to QMouseEvent. QGraphicsSceneMouseEvent doesn't derive from QMouseEvent, so that's just not a safe cast to make. The buttons method is probably not actually calling the right method because that cast is bad. Second, QGraphicsSceneMouseEvent::buttons does exist, and it does what you want, but it's a mask. You should be doing this:
#include <QGraphicsSceneMouseEvent>
void Tile_Square::mousePressEvent (QGraphicsSceneMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton)
{
// ... handle left click here
}
else if (event->buttons() & Qt::RightButton)
{
// ... handle right click here
}
}
Even without treating this as a mask, I would expect that your direct comparison is likely to work as long as you don't press a combination of buttons at once. However, I haven't tested that to be sure.

Function: Getting clicked object from QGraphicsScene

I'm novice in programming and need a help.
I have a class Station, which contains X and Y fields:
Class Station {
int x
int y
...
}
All the stations are drawing on a QGraphicsScene as a circles and text:
this->scene.addEllipse(x1, y1, diam, diam, pen, QBrush(...));
I need a function getClickedStation, which is waiting for click on a QGraphicsScene, finding the circle and returns the station appropiate of its coordinates:
Station* getClickedStation(...) { ... }
Is there any ways to do it?
I've tried this just to get coordinates:
QList<QGraphicsItem*> listSelectedItems = scene.selectedItems();
QGraphicsItem* item = listSelectedItems.first();
ui->textBrowserMenu->append(QString::number(item->boundingRect().x()));
ui->textBrowserMenu->append(QString::number(item->boundingRect().y()));
but the program crashes with it...
No, you do it wrong. I wrote small example. You should subclass QGraphicsScene and reimplement mousePressEvent and process clicks in it. For example:
*.h
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QPoint>
#include <QMouseEvent>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
};
#endif // GRAPHICSSCENE_H
In cpp
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
//qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
QGraphicsItem *item = itemAt(mouseEvent->scenePos(), QTransform());// it is your clicked item, you can do everything what you want. for example send it somewhere
QGraphicsEllipseItem *ell = qgraphicsitem_cast<QGraphicsEllipseItem *>(item);
if(ell)
{
ell->setBrush(QBrush(Qt::black));
}
else
qDebug() << "not ell" << mouseEvent->scenePos();
}
}
On the scene there are a few ellipses, when you click somewhere in scene we get item under cursor and check it is it ellipse for example. If is, then we set new background to it.
Main idea are itemAt method and qgraphicsitem_cast

How to draw a line on a QPixmap using points

I have a label in my GUI that displays an image as a QPixmap. I want to be able to draw a continuous line on my image by simply clicking anywhere on the image to select the start point and then make a second point somewhere else by clicking on a other part of the image. The two points should connect immediately after placing the second point and I want to be able to continue that same line by placing more points on the image.
While I know how to draw something on a QPixmap, the mouse event which I need to use to get the coordinates for the points is really confusing me as I’m still fairly new to Qt.
Any examples for a solution would be much appreciated.
I suggest you to use QGraphicsView for this purpose. Use my code snippet which works perfectly.
Subclass QGraphicsScene:
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QPoint>
#include <QMouseEvent>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
signals:
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
public slots:
private:
QPolygon pol;
};
#endif // GRAPHICSSCENE_H
.cpp file:
#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
GraphicsScene::GraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
addPixmap(QPixmap("G:/2/qt.jpg"));//your pixmap here
}
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
//qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
QPoint pos = mouseEvent->scenePos().toPoint();
pol.append(pos);
if(pol.size() > 1)
{
QPainterPath myPath;
myPath.addPolygon(pol);
addPath(myPath,QPen(Qt::red,2));
}
}
}
Usage:
#include "graphicsscene.h"
//...
GraphicsScene *scene = new GraphicsScene(this);
ui->graphicsView->setScene(scene);
ui->graphicsView->show();
Result:
If you want save new pixmap (or just get pixmap) as image, use this code:
QPixmap pixmap(ui->graphicsView->scene()->sceneRect().size().toSize());
QString filename("example.jpg");
QPainter painter( &pixmap );
painter.setRenderHint(QPainter::Antialiasing);
ui->graphicsView->scene()->render( &painter, pixmap.rect(),pixmap.rect(), Qt::KeepAspectRatio );
painter.end();
pixmap.save(filename);
With render() you can also grab different areas of your scene.
But this code can be better: we create and paint same polygon. If we can remember last painted point, then we can paint line by line (begin of line is end of last line). In this case we don't need all points, we need just last point.
As I promised(Code improvement):just provide additional variable QPoint last; instead of QPolygon pol; and use next code:
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
//qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
QPoint pos = mouseEvent->scenePos().toPoint();
if(last.isNull())
{
last = pos;
}
else
{
addLine(QLine(last,pos),QPen(Qt::red,2));
last = pos;
}
}
}
As you can see, you store only last point and paint only last line. User can clicks thousands time and now you not need to store this unnecessary points and do this unnecessary repainting.