How to get Image pixel position loaded in QGraphicsView - Strange MapToScene() behaviour - c++

I am originally loading image in QGraphicsView and using this method for basic zoom out and zoom in functionality.
However, I am unable to retrieve actual image pixel position using mapToScene functionality in eventFilter function of Graphics_view_zoom class. The below code produces behaviour exactly as windows photo viewer zooming only selected region.
MapToScene() returns same Point as mouse event position.
Here is the class which deals with zooming.
#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();
// Here I want to get absolute image coordinates
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;
In mainwindow.cpp,
I am creating object of this class and loading an image as below:
m_GraphicsScene = new QGraphicsScene();
pixmapItem = new QGraphicsPixmapItem();
m_GraphicsScene->addItem(multiview[i].pixmapItem);
view_wrapper = new Graphics_view_zoom(ui->GraphicsView);
ui->GraphicsView->setScene(multiview[i].m_GraphicsScene);
pixmapItem->setPixmap(QPixmap::fromImage("img.jpg"));
multiview[view].m_GraphicsView->fitInView(QRectF(0,0,640,320),Qt::KeepAspectRatio);
Can anyone help with how do I achieve this ?

Keep in mind that the scaling you use only scales the scene, not the items. Given this, the position of the pixel can be obtained, so the algorithm is:
Obtain the mouse position with respect to the QGraphicsView
Transform that position with respect to the scene using mapToScene
Convert the coordinate with respect to the scene in relation to the item using mapFromScene of the QGraphicsItem.
Considering the above, I have implemented the following example:
#include <QtWidgets>
#include <random>
static QPixmap create_image(const QSize & size){
QImage image(size, QImage::Format_ARGB32);
image.fill(Qt::blue);
std::random_device rd;
std::mt19937_64 rng(rd());
std::uniform_int_distribution<int> uni(0, 255);
for(int i=0; i< image.width(); ++i)
for(int j=0; j < image.height(); ++j)
image.setPixelColor(QPoint(i, j), QColor(uni(rng), uni(rng), uni(rng)));
return QPixmap::fromImage(image);
}
class GraphicsView : public QGraphicsView
{
Q_OBJECT
Q_PROPERTY(Qt::KeyboardModifiers modifiers READ modifiers WRITE setModifiers)
public:
GraphicsView(QWidget *parent=nullptr): QGraphicsView(parent){
setScene(new QGraphicsScene);
setModifiers(Qt::ControlModifier);
auto item = scene()->addPixmap(create_image(QSize(100, 100)));
item->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
item->setPos(40, 40);
fitInView(QRectF(0, 0, 640, 320),Qt::KeepAspectRatio);
resize(640, 480);
}
void setModifiers(const Qt::KeyboardModifiers &modifiers){
m_modifiers = modifiers;
}
Qt::KeyboardModifiers modifiers() const{
return m_modifiers;
}
signals:
void pixelChanged(const QPoint &);
protected:
void mousePressEvent(QMouseEvent *event) override{
if(QGraphicsPixmapItem *item = qgraphicsitem_cast<QGraphicsPixmapItem *>(itemAt(event->pos()))){
QPointF p = item->mapFromScene(mapToScene(event->pos()));
QPoint pixel_pos = p.toPoint();
emit pixelChanged(pixel_pos);
}
QGraphicsView::mousePressEvent(event);
}
void wheelEvent(QWheelEvent *event) override{
if(event->modifiers() == m_modifiers){
double angle = event->orientation() == Qt::Vertical ? event->angleDelta().y(): event->angleDelta().x();
double factor = qPow(base, angle);
applyZoom(factor, event->pos());
}
}
private:
void applyZoom(double factor, const QPoint & fixedViewPos)
{
QPointF fixedScenePos = mapToScene(fixedViewPos);
centerOn(fixedScenePos);
scale(factor, factor);
QPointF delta = mapToScene(fixedViewPos) - mapToScene(viewport()->rect().center());
centerOn(fixedScenePos - delta);
}
Qt::KeyboardModifiers m_modifiers;
const double base = 1.0015;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
GraphicsView *view = new GraphicsView;
QLabel *label = new QLabel;
QObject::connect(view, &GraphicsView::pixelChanged, label, [label](const QPoint & p){
label->setText(QString("(%1, %2)").arg(p.x()).arg(p.y()));
});
label->setAlignment(Qt::AlignCenter);
QWidget w;
QVBoxLayout *lay = new QVBoxLayout(&w);
lay->addWidget(view);
lay->addWidget(label);
w.show();
return a.exec();
}
#include "main.moc"

It may be better to use a custom graphics scene subclassed from QGraphicsScene as this makes extracting the necessary coordinates much simpler. The only snag is you have to have the QGraphicsPixmapItem::pos available in the custom QGraphicsScene class - I have included a full working example which uses Graphics_view_zoom.h and Graphics_view_zoom.cpp from the linked question. The position of the QGraphicsPixmapItem is passed to a member of the QGraphicsScene subclass, Frame, in order to make the necessary correction.
#include <QPixmap>
#include <QGraphicsPixmapItem>
#include <QGraphicsTextItem>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <qfont.h>
#include "Graphics_view_zoom.h"
class Frame : public QGraphicsScene {
Q_OBJECT
public:
QGraphicsTextItem * coords;
QPointF pic_tl;
Frame::Frame(QWidget* parent)
: QGraphicsScene(parent) {
coords = new QGraphicsTextItem();
coords->setZValue(1);
coords->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
addItem(coords);
}
void Frame::tl(QPointF p) {
pic_tl = p;
}
protected:
void Frame::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
QPointF pos = event->scenePos();
coords->setPlainText("(" + QString("%1").arg(int(pos.x() - pic_tl.x())) + ", "
+ QString("%1").arg(int(pos.y() - pic_tl.y())) + ")");
coords->setPos(pos);
coords->adjustSize();
}
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QMainWindow* main = new QMainWindow();
QGraphicsView* GraphicsView = new QGraphicsView(main);
Graphics_view_zoom* view_wrapper = new Graphics_view_zoom(GraphicsView);
Frame* frame = new Frame(main);
QGraphicsPixmapItem* pixmapItem = new QGraphicsPixmapItem();
frame->addItem(pixmapItem);
GraphicsView->setScene(frame);
// Loads a 497x326 pixel test image
pixmapItem->setPixmap(QPixmap(":/StackOverflow/test"));
// small offset to ensure it works for pictures which are not at
// (0,0). Larger offsets produce the same result but require manual
// adjustments of the view, I have neglected those for brevity as
// they are not in the scope of the question.
pixmapItem->setPos(-20, 20);
frame->tl(pixmapItem->pos());
GraphicsView->fitInView(QRectF(0, 0, 640, 320), Qt::KeepAspectRatio);
GraphicsView->centerOn(pixmapItem->pos());
main->resize(1920, 1080);
main->show();
GraphicsView->resize(main->width(), main->height());
return a.exec();
}
This will display the image coordinates of the pixel under the mouse relative to (0,0) at the top left corner.
The mouse is not visible in these screenshots but in the first it is in exactly the upper left corner and the second it is in exactly the lower right corner. If these coordinates are needed inside the Graphics_view_zoom object then you simply have to scope the Frame instance appropriately, or pass the value as needed.
Note - the exact coordinates displayed may not precisely represent the position of the mouse in this example since they are cast to ints for demonstration, but the floating point values can be easily accessed since QGraphicsSceneMoveEvent::scenePos() returns a QPointF. Additionally, note that in running this demonstration there may be some (hopefully very small) variation on where the mouse appears to be relative to it's 'actual' position - I recommend using Qt::CrossCursor to allay this. For example on my system the default cursor is off by about a pixel for certain areas on my smaller display, this is also affected by the zoom level - higher zoom will produce more accurate results, less zoom will be less accurate.

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.

Horizontal Scrolling issue with QTableView with a child QGraphicsRectItem

I have a QGraphicsView with a scene containing QGraphicsProxyWidget (QTableView) and a QGraphicsRectItem on top of QTableView. Unfortunately, i am not able to upload any images (Imgur error) but here is a post to visualize how it looks.
My QGraphicsRectItem a vertical line, acts like a seek bar which runs horizontally on the QTableView. Basically, it looks like a time line with a seek bar commonly found in Audio and Video editing tools.
Issues:
Moving the horizontal scroll bar makes the seek bar disappear on hitting the boundaries of QTableView and it doesn't reappear anymore.
While moving the scrollbar if the seekbar hits the boundaries, scrollbar cannot be moved further.
What did i do:
I have managed to create a MVCE (Not really Minimal)
//SeekBar.h
#include <QGraphicsRectItem>
#include <QAbstractItemView>
#include <QObject>
#include <QScrollBar>
#include <QGraphicsProxyWidget>
#include "tableview.h"
class SeekBar : public QObject, public QGraphicsRectItem
{
Q_OBJECT
public:
SeekBar(int width, QTableView* view, QGraphicsScene* scene);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant& value);
public slots:
void scrollBarMoved();
private:
QGraphicsProxyWidget* proxy;
QTableView* m_view;
QScrollBar* scrollbar;
bool m_isScrollMoving;
};
//SeekBar.cpp
#include <QBrush>
#include <QGraphicsScene>
#include "seekbar.h"
SeekBar::SeekBar(int width, QTableView* view, QGraphicsScene* scene) :
QGraphicsRectItem(nullptr),
proxy(new QGraphicsProxyWidget()),
m_view(view),
m_isScrollMoving(false)
{
proxy->setWidget(m_view);
scene->addItem(proxy);
setParentItem(proxy);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
setBrush(Qt::red);
setRect(0, 0, width, m_view->height());
scrollbar = m_view->horizontalScrollBar();
connect(m_view->horizontalScrollBar(), &QScrollBar::sliderMoved, this, &SeekBar::scrollBarMoved);
}
QVariant SeekBar::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value)
{
if (change == QGraphicsItem::ItemPositionChange && !m_isScrollMoving)
{
QPointF p = value.toPointF();
qreal max = parentItem()->boundingRect().right() - boundingRect().right();
qreal min = parentItem()->boundingRect().left() - boundingRect().left();
if (p.x() > max)
{
p.setX(max);
}
else if (p.x() < min)
{
p.setX(min);
}
p.setY(pos().y());
float percentage = (p.x() - min) * 1.0 / (max - min);
int value = scrollbar->minimum() + percentage * (scrollbar->maximum() - scrollbar->minimum());
scrollbar->setValue(value);
return p;
}
else if (change == QGraphicsItem::ItemPositionChange && m_isScrollMoving)
{
QPointF p = value.toPointF();
qreal max = parentItem()->boundingRect().right() - boundingRect().right();
qreal min = parentItem()->boundingRect().left() - boundingRect().left();
setVisible(true);
if (p.x() > max)
{
hide();
}
else if (p.x() < min)
{
hide();
}
p.setY(pos().y());
return p;
}
return QGraphicsRectItem::itemChange(change, value);
}
void SeekBar::scrollBarMoved()
{
m_isScrollMoving = true;
int col = m_view->columnAt(pos().x());
setPos(m_view->columnViewportPosition(col), 0);
m_isScrollMoving = false;
}
//TableView.h
#include <QTableView>
class TableView : public QTableView
{
public:
TableView(QWidget* parent = nullptr);
};
//TableView.cpp
#include "tableview.h"
#include <QHeaderView>
#include <QStandardItemModel>
#include <QScrollBar>
TableView::TableView(QWidget* parent) : QTableView(parent)
{
QStandardItemModel* model = new QStandardItemModel(10, 10);
setModel(model);
verticalHeader()->hide();
horizontalHeader()->setHighlightSections(false);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionMode(QAbstractItemView::MultiSelection);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
}
//main.cpp
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsProxyWidget>
#include "tableview.h"
#include "seekbar.h"
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene* scene = new QGraphicsScene;
view.setScene(scene);
TableView* table = new TableView;
SeekBar* seekBarItem = new SeekBar(5, table, scene);
view.resize(640, 480);
view.show();
return a.exec();
}
What do i want:
I want to smoothly scroll the table view whenever i drag the seek bar using mouse but it should stay inside the view's boundaries.
I want to keep the seek at a fixed position(column) while scrolling using the horizontal bar. But it should NOT have any boundaries i.e. while using scrollbar, the seek should be able to pass through the view or hide but should appear at the fixed position when scrolled back.
Seek at column 5
Horizontal bar scrolled a little bit to the right
Scrolled till the end of the view
Scrolled back to column 5
EDIT1:
For instance, if the seek is at column 5, while scrolling using horizontal scroll bar it should stick to column 5 and should pass through the boundaries(or hide it when it hits the boundaries?). Seek should be visible whenever the column 5 is in the view while scrolling.
I would appreciate some ideas.

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.

qt passing object to different class

EDIT
I am creating all objects initially when the program is started in my dialog.cpp and storing all QPixmaps in an array then picking a random one from them all. That random QPixmap I want to pass to my maintargets class and draw in the scene (which is also created in the dialog.cpp).
// dialog.cpp
#include "dialog.h"
#include "scene.h"
#include "ui_dialog.h"
#include "instructions.h"
#include "settings.h"
#include "highscore.h"
#include "maintargets.h"
#include <stdlib.h>
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
// Create and configure scene
scene = new Scene;
scene->setBackgroundBrush(Qt::black);
scene->setItemIndexMethod(QGraphicsScene::NoIndex);
ui->graphicsView->setScene(scene);
scene->setSceneRect(-200, -150, 400, 300);
ui->graphicsView->setMouseTracking(true);
QPixmap tankbase1(":/images/tankbase.jpg");
ui->tankbaseplay1->setPixmap(tankbase1);
//Store targets in array and random generator
index = 0;
main_targets[0] = QPixmap(":images/darkbluelogo.jpg)");
main_targets[1] = QPixmap(":images/graylogo.jpg");
main_targets[2] = QPixmap(":images/lightbluelogo.jpg");
main_targets[3] = QPixmap(":images/limE.jpg");
main_targets[4] = QPixmap(":images/pink.jpg");
main_targets[5] = QPixmap(":images/purple.jpg");
main_targets[6] = QPixmap(":images/redlogo.jpg");
main_targets[7] = QPixmap(":images/yellow.jpg");
main_targets[8] = QPixmap(":images/brown.jpg");
index = qrand((index % 9) + 1);
//scene->addItem(main_targets[index]);
//Timer for scene advancement
QTimer *timer = new QTimer();
QObject::connect(timer, SIGNAL(timeout()), scene, SLOT(advance()));
timer->start(100);
}
Dialog::~Dialog()
{
delete ui;
}
//maintargets.h
#ifndef MAINTARGETS_H
#define MAINTARGETS_H
#include "dialog.h"
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QPainter>
#include <QRect>
class MainTargets : public QGraphicsScene
{
public:
MainTargets();
QRectF boundingRect() const;
QPainterPath shape() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
protected:
void advance(int step);
private:
qreal dx, dy;
qreal x, y;
qreal w, h;
};
#endif // MAINTARGETS_H
//maintargets.cpp
#include "maintargets.h"
MainTargets::MainTargets()
{
dx = -0.005;
dy = 0.0;
x = 1.5;
y = 0.0;
w = 100.0;
h = 70.0;
}
QRectF MainTargets::boundingRect() const
{
qreal shift = 1;
return QRectF(-w/2 -shift, - h/2
- shift, w + shift, h + shift);
}
QPainterPath MainTargets::shape() const
{
QPainterPath path;
path.addRect(boundingRect());
return path;
}
void MainTargets::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
painter->drawPixmap(-w/2, -h/2, main_targets[index]);
}
void MainTargets::advance(int step)
{
if(step == 0) return;
x = x + dx;
y = y + dy;
setPos(mapToParent(x, y));
}
After it is drawn it moves in x-direction.
Your question is very broad unfortunately, and the exact solution depends on your use case. I will mention a few different solutions for your issue, and then you can take your peek, but please read the documentation about how to ask questions on Stack Overflow because your question is very low-quality at the moment.
1) If your operation is supposed to build the other class, you can pass it as a constructor argument if it is not against your design for the construction of this class.
2) You can use a void setPixmap(QPixmap); setter if it is possible to extend your class this way, and you have access to an instance of the object in that method.
3) You can use a proxy class dealing with all this, if that is all you have access in your operation getting the random index.
4) You can set a static variable in the same source file if the other class needs this in the same source file. This is not a good idea in general, but I saw this happening, too.
5) You can set a global variable that the other class method is using. Again, this is a very bad practice.
6) You can just pass this QPixmap as an argument to the drawing function directly in the other class.
7) You can pass this to a proxy class object that will pass it to the drawing method of the other class.
8) If the other class is in a different process, you have get at least another many ways to pass it through.
As I said, it really depends on your scenario, and there is loads of ways to do it. That being said, I will remove this answer later because this question is too broad, but I wished to show you how broad what you are asking is.
Simple pass by reference was what I was lost on. This explains the process of what was needed to get the QPixmap variable in the maintargets class.
//dialog.h
private:
Ui::Dialog *ui;
//add a pointer
MainTargets* pmap;
//dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
#include "maintargets.h"
#include <stdlib.h>
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
// Create and configure scene
scene = new Scene;
scene->setBackgroundBrush(Qt::black);
scene->setItemIndexMethod(QGraphicsScene::NoIndex);
ui->graphicsView->setScene(scene);
scene->setSceneRect(-200, -150, 400, 300);
ui->graphicsView->setMouseTracking(true);
QPixmap tankbase1(":/images/tankbase.jpg");
ui->tankbaseplay1->setPixmap(tankbase1);
//Store targets in array and random generator
index = 1;
main_targets[0] = QPixmap(":images/darkbluelogo.jpg)");
main_targets[1] = QPixmap(":images/graylogo.jpg");
main_targets[2] = QPixmap(":images/lightbluelogo.jpg");
main_targets[3] = QPixmap(":images/lime.jpg");
main_targets[4] = QPixmap(":images/pink.jpg");
main_targets[5] = QPixmap(":images/purple.jpg");
main_targets[6] = QPixmap(":images/redlogo.jpg");
main_targets[7] = QPixmap(":images/yellow.jpg");
main_targets[8] = QPixmap(":images/brown.jpg");
//Timer for scene advancement
QTimer *timer = new QTimer();
QObject::connect(timer, SIGNAL(timeout()), scene, SLOT(advance()));
timer->start(10);
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::on_startButton_clicked()
{
ui->settingsButton->hide();
ui->titlescreen->hide();
ui->highscoreButton->hide();
ui->instructionButton->hide();
ui->startButton->hide();
QGraphicsTextItem *FirstP;
QString P1 = "Player1";
FirstP = scene->addText(P1);
FirstP->setFont(QFont("Nimbus Mono L", 12,QFont::Bold));
FirstP->setDefaultTextColor(Qt::white);
FirstP->setPos(-300, -220);
QGraphicsTextItem *SecondP;
QString P2 = "Player2";
SecondP = scene->addText(P2);
SecondP->setFont(QFont("Nimbus Mono L", 12,QFont::Bold));
SecondP->setDefaultTextColor(Qt::white);
SecondP->setPos(230, -220);
//Initializes function
setPixmaps();
}
void Dialog::setPixmaps()
{
index = (qrand() % (9));
//Add a new MainTarget and set equal to new pointer created in header file
pmap = new MainTargets(main_targets[index]);
pmap->setPos(355,0);
scene->addItem(pmap);
}
//maintargets.h
private:
//Add a new QPixmap to header
QPixmap p;
//maintargets.cpp
Reference in QPixmap variable in constructor and set equal to newly created pointer
#include "maintargets.h"
MainTargets::MainTargets(QPixmap& nomTargets)
{
dx = -0.005;
dy = 0.0;
x = 0.0;
y = 0.0;
w = 100.0;
h = 70.0;
p = nomTargets;
}
QRectF MainTargets::boundingRect() const
{
qreal shift = 1;
return QRectF(-w/2 -shift, - h/2
- shift, w + shift, h + shift);
}
QPainterPath MainTargets::shape() const
{
QPainterPath path;
path.addRect(boundingRect());
return path;
}
void MainTargets::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
//Set that pointer variable as the source for the
//given drawPixmap memeber
painter->drawPixmap(-w/2, -h/2, p);

How to animate two values using QTimeLine

I have a QGraphicsItem for which I want to animate a size change. Because of this, I need to vary both the height and width of the object over time. I have used the QTimeLine in the past for single-variable animations and I would like to use it here for two-variable if possible. However, I don't see a QTimeLine::setFrameRange() that works for two variables.
How can I accomplish this? Is there a better Qt class for this?
Since animations have to be attached to objects, your graphics item will be a QGraphicsObject, so you can expose the relevant properties to the Qt property system.
Since you're animating what amounts to a QSizeF, the simplest way would be to expose this as a single property and use a QPropertyAnimation.
In general, if you have to animate multiple properties in parallel, set them up as individual QPropertyAnimations. Then run those in parallel using a QParallelAnimationGroup.
It's of course possible to use a QTimeLine, but then you have to interpolate between the endpoints manually, basing on the frame number, for example. Why bother.
Below is a complete example that shows how to animate three properties at once: pos, size and rotation.
main.cpp
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QSequentialAnimationGroup>
#include <QGraphicsObject>
#include <QPainter>
#include <QApplication>
class Item : public QGraphicsObject {
Q_OBJECT
qreal m_pen;
QSizeF m_size;
Q_PROPERTY(QSizeF size READ size WRITE setSize NOTIFY newSize)
Q_SIGNAL void newSize();
qreal width() const { return m_size.width() + m_pen; }
qreal height() const { return m_size.height() + m_pen; }
public:
explicit Item(QGraphicsItem *parent = 0) :
QGraphicsObject(parent), m_pen(5.0), m_size(50.0, 50.0) {}
QSizeF size() const { return m_size; }
void setSize(const QSizeF & size) {
if (m_size != size) {
m_size = size;
update();
emit newSize();
}
}
QRectF boundingRect() const {
return QRectF(-width()/2, -height()/2, width(), height());
}
void paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) {
p->setPen(QPen(Qt::black, m_pen));
p->drawEllipse(QPointF(0,0), width()/2, height()/2);
p->setPen(QPen(Qt::red, m_pen));
p->drawLine(0, 0, 0, height()/2);
}
};
void animate(QObject * obj)
{
QParallelAnimationGroup * group = new QParallelAnimationGroup(obj);
QPropertyAnimation * pos = new QPropertyAnimation(obj, "pos", obj);
QPropertyAnimation * size = new QPropertyAnimation(obj, "size", obj);
QPropertyAnimation * rot= new QPropertyAnimation(obj, "rotation", obj);
pos->setDuration(3000);
pos->setLoopCount(-1);
pos->setEasingCurve(QEasingCurve::InOutCubic);
pos->setStartValue(QPointF(-50, -50));
pos->setEndValue(QPointF(50, 50));
size->setDuration(1500);
size->setLoopCount(-1);
size->setEasingCurve(QEasingCurve::InOutElastic);
size->setStartValue(QSizeF(100, 100));
size->setEndValue(QSizeF(100, 30));
rot->setDuration(1000);
rot->setLoopCount(-1);
rot->setStartValue(0.0);
rot->setEndValue(360.0);
group->addAnimation(pos);
group->addAnimation(size);
group->addAnimation(rot);
group->start();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene s;
QGraphicsView v(&s);
Item * item = new Item;
s.addItem(item);
v.setRenderHint(QPainter::Antialiasing);
v.setSceneRect(-125, -125, 300, 300);
v.show();
animate(item);
return a.exec();
}
#include "main.moc"