Tracking mouse coordinates in Qt - c++

Let's say I have a widget in main window, and want to track mouse position ONLY on the widget: it means that left-low corner of widget must be local (0, 0).
Q: How can I do this?
p.s. NON of functions below do that.
widget->mapFromGlobal(QCursor::pos()).x();
QCursor::pos()).x();
event->x();

I am afraid, you won't be happy with your requirement 'lower left must be (0,0). In Qt coordinate systems (0,0) is upper left. If you can accept that. The following code...
setMouseTracking(true); // E.g. set in your constructor of your widget.
// Implement in your widget
void MainWindow::mouseMoveEvent(QMouseEvent *event){
qDebug() << event->pos();
}
...will give you the coordinates of your mouse pointer in your widget.

If all you want to do is to report position of the mouse in coordinates as if the widget's lower-left corner was (0,0) and Y was ascending when going up, then the code below does it. I think the reason for wanting such code is misguided, though, since coordinates of everything else within said widget don't work this way. So why would you want it, I can't fathom, but here you go.
#include <QtWidgets>
class Window : public QLabel {
public:
Window() {
setMouseTracking(true);
setMinimumSize(100, 100);
}
void mouseMoveEvent(QMouseEvent *ev) override {
// vvv That's where the magic happens
QTransform t;
t.scale(1, -1);
t.translate(0, -height()+1);
QPoint pos = ev->pos() * t;
// ^^^
setText(QStringLiteral("%1, %2").arg(pos.x()).arg(pos.y()));
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}

Related

move QGraphicsView around QGraphicsScene

I'm making a side view drag racing game in QT c++. I want to move my view around my scene from left to right. I have the scene set to 3600x800, but i want the view the be at the far left of my scene not at the center at the start. When i press W on my keyboard I want the view to move to the left for 1px. How do I do that? I can't find anything online
scene=new QGraphicsScene(this);
view = new QGraphicsView;
scene->setSceneRect(0,0,3600,800);
view->setScene(scene);
You will never find something so particular on the internet, you should look for each part separately:
If you want it to appear on the left side then you must use horizontalScrollBar() of the GraphicsView when it is displayed, we can do that with the showEvent method.
if you want to do an action when you press any key you could overwrite the keyPressEvent method.
To move the sceneRect() you must make a copy, move it and set it again.
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QKeyEvent>
#include <QScrollBar>
class GraphicsView: public QGraphicsView{
public:
using QGraphicsView::QGraphicsView;
protected:
void keyPressEvent(QKeyEvent *event){
if(event->key() == Qt::Key_W){
if(scene()){
QRectF rect = scene()->sceneRect();
rect.translate(1, 0);
scene()->setSceneRect(rect);
}
}
}
void showEvent(QShowEvent *event){
QGraphicsView::showEvent(event);
if(isVisible()){
horizontalScrollBar()->setValue(0);
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
GraphicsView view;
scene.setSceneRect(0,0,3600,800);
view.setScene(&scene);
scene.addRect(0, 200, 400, 400, Qt::NoPen, Qt::red);
view.show();
return a.exec();
}

QT 5.7 QPainter line aligment

I am working with QT 5.7 and C++.
At the moment I try to get used to draw my own widgets with the QPainter class.
But I noticed a problem I couldn't solve.
I try to draw a border line extactly at the widget border but if I do so:
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter;
painter.begin(this);
painter.setBrush(Qt::cyan);
QBrush brush(Qt::black);
QPen pen(brush, 2);
painter.setPen(pen);
painter.drawRect(0, 0, size().width() - 1, size().height() - 1);
painter.end();
}
The Line is at the bottom and right site bigger than the others:
And before someone is telling me I have to remove the two -1 expressions,
you should know if I do this and also set the pen width to 1 there is no line anymore at the bottom and right side.
I think this artifact is caused by the "line aligment".
QT tries to tint the the pixels near the logical lines defined by the rectangle but actually because finally all have to be in pixels it has to decide.
If I am right, why there is no method to set the line aligment of the pen like in GDI+?
And how I can solve this?
Everything depends on whether you want the entire pen's width to be visible or not. By drawing the rectangle starting at 0,0, you're only showing half of the pen's width, and that makes things unnecessarily complicated - never mind that the line appears too thin. In Qt, the non-cosmetic pen is always drawn aligned to the middle of the line. Qt doesn't let you change it: you can change the drawn geometry instead.
To get it right for odd line sizes, you must give rectangle's coordinates as floating point values, and they must be fall in the middle of the line. So, e.g. if the pen is 3.0 units wide, the rectangle's geometry will be (1.5, 1.5, width()-3.0, width()-3.0).
Here's a complete example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/widget-pen-wide-38019846
#include <QtWidgets>
class Widget : public QWidget {
Q_OBJECT
Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth)
qreal m_penWidth = 1.0;
protected:
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.setPen({Qt::black, m_penWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin});
p.setBrush(Qt::cyan);
qreal d = m_penWidth/2.0;
p.drawRect(QRectF{d, d, width()-m_penWidth, height()-m_penWidth});
}
public:
explicit Widget(QWidget * parent = 0) : QWidget{parent} { }
qreal penWidth() const { return m_penWidth; }
void setPenWidth(qreal width) {
if (width == m_penWidth) return;
m_penWidth = width;
update();
}
QSize sizeHint() const override { return {100, 100}; }
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget top;
QVBoxLayout layout{&top};
Widget widget;
QSlider slider{Qt::Horizontal};
layout.addWidget(&widget);
layout.addWidget(&slider);
slider.setMinimum(100);
slider.setMaximum(1000);
QObject::connect(&slider, &QSlider::valueChanged, [&](int val){
widget.setPenWidth(val/100.0);
});
top.show();
return app.exec();
}
#include "main.moc"

QGraphicsRectItem move with mouse. How to?

I have QGraphicsView, QGraphicsScene and QGraphicsRectItem.
QGraphicsRectItem in the QGraphicsScene and the last one in the QGraphicsView. I want to move QGraphicsRectItem with mouse by clicking on it only! But in my implementation it moves if I click on any position on my QGraphicsScene. Whether it is my QGraphicsRectItem or some other place. And the second issue. The item has been moved to the center of the scene. Clicking on it again it starts to move from the home location.
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset); //p1 movable item
}
}
What do I do wrong?
UPDATE:
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
widget.h
#ifndef STEER_H
#define STEER_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMouseEvent>
#include <QPoint>
#include <QGraphicsRectItem>
class Steer : public QGraphicsView
{
Q_OBJECT
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint offset;
public:
explicit Steer(QGraphicsView *parent = 0);
~Steer(){}
public slots:
void mousePressEvent(QMouseEvent * click);
void mouseMoveEvent(QMouseEvent * event);
};
#endif // STEER_H
widget.cpp
#include "widget.h"
#include <QBrush>
Steer::Steer(QGraphicsView *parent)
: QGraphicsView(parent)
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(760, 160, 10, 80);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset);
}
}
I'd try a different approach that is a little easier to understand:
#include <QtWidgets>
class Steer : public QGraphicsView
{
public:
Steer()
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(0, 0, 10, 80);
p1->setX(760);
p1->setY(160);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
protected:
void mousePressEvent(QMouseEvent * click)
{
if (p1->contains(p1->mapFromScene(click->localPos()))) {
lastMousePos = click->pos();
} else {
lastMousePos = QPoint(-1, -1);
}
}
void mouseMoveEvent(QMouseEvent * event)
{
if(!(event->buttons() & Qt::LeftButton)) {
return;
}
if (lastMousePos == QPoint(-1, -1)) {
return;
}
p1->setPos(p1->pos() + (event->localPos() - lastMousePos));
lastMousePos = event->pos();
}
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint lastMousePos;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
There are a few things to point out here:
Don't use setRect() to set the position of a QGraphicsRectItem. It doesn't work the way you think it might. Always use setPos() to change the position of an item.
Rename offset to something more descriptive. I chose lastMousePos. Instead of just updating it once when the mouse is pressed, also update it whenever the mouse is moved. Then, it's simply a matter of getting the difference between the two points and adding that to the position of the item.
Check if the mouse is actually over the item before reacting to move events. If the mouse isn't over the item, you need some way of knowing that, hence the QPoint(-1, -1). You may want to use a separate boolean flag for this purpose. This solves the problem that you saw, where it was possible to click anywhere in the scene to get the item to move.
Also, note the mapFromScene() call: the contains() function works in local coordinates, so we must map the mouse position which is in scene coordinates before testing if it's over the item.
The event functions are not slots, they're virtual, protected functions.
You could also consider handling these events in the items themselves. You don't need to do it from within QGraphicsView, especially if you have more than one of these items that need to be dragged with the mouse.

Mouse events QT

I want to to allow user to select a region with mouse, like you can do mostly everywhere.For more clarity just imagine your desktop on Windows, and click the left button and move the mouse with the button holed. The following will happen: you will see how the region that your mouse passed is highlighted with a rectangle. That is exactly what I want to do.
p.s. Mathematically I know how to calculate, and also know how to draw the rectangle by being able to track mouse position when it is pressed.
Q1: How to track mouse position?
Q2: Any alternative way to do what I want?
The simplest way is to use the Graphics View Framework. It provides mechanism for item selection, display of a rubber band rectangle, detection of intersection of the rubber band with the items, etc. Below is a self contained example. It lets you select and drag multiple items using either Ctrl/Cmd-click to toggle selection, or rubber banding.
OpenGL is used to render the background, and you can put arbitrary OpenGL content there.
main.cpp
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGLWidget>
static qreal rnd(qreal max) { return (qrand() / static_cast<qreal>(RAND_MAX)) * max; }
class View : public QGraphicsView {
public:
View(QGraphicsScene *scene, QWidget *parent = 0) : QGraphicsView(scene, parent) {
setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
}
void drawBackground(QPainter *, const QRectF &) {
QColor bg(Qt::blue);
glClearColor(bg.redF(), bg.greenF(), bg.blueF(), 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
};
void setupScene(QGraphicsScene &s)
{
for (int i = 0; i < 10; i++) {
qreal x = rnd(1), y = rnd(1);
QAbstractGraphicsShapeItem * item = new QGraphicsRectItem(x, y, rnd(1-x), rnd(1-y));
item->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
item->setPen(QPen(Qt::red, 0));
item->setBrush(Qt::lightGray);
s.addItem(item);
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene s;
setupScene(s);
View v(&s);
v.fitInView(0, 0, 1, 1);
v.show();
v.setDragMode(QGraphicsView::RubberBandDrag);
v.setRenderHint(QPainter::Antialiasing);
return a.exec();
}

skipping the mouseEvents of the qgraphicsitem in a qgraphics scene

I know how to pass events from qgraphics scene to q graphics item ,but the problem is for the item,the mouse events for the scene is being executed.
for example in the code below when pressing on the item the output is "custom scene is pressed"
#include <QtGui>
class CustomScene : public QGraphicsScene
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(itemAt(event->pos()))
QGraphicsScene::mousePressEvent((event));
else
qDebug() << "Custom scene clicked.";
}
};
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);
CustomScene scene;
//scene().set
scene.addItem(&item);
QGraphicsView view;
view.setScene(&scene);
view.show();
return a.exec();
}
See the documentation of QGraphicsSceneMouseEvent::pos:
Returns the mouse cursor position in item coordinates.
This means if the mouse is 10 pixels away from the top and left border of your item, you will get (10,10) as coordinates no matter where on the scene the item is.
What you need is QGraphicsSceneMouseEvent::scenePos:
Returns the mouse cursor position in scene coordinates.
Change your if-statement to:
if(itemAt(event->scenePos()))
QGraphicsScene::mousePressEvent((event));
else
qDebug() << "Custom scene clicked.";