Fixed QGraphicsItem position, without changing behaviour of other QGraphicsItems in scene - c++

This question is related to: Forcing QGraphicsItem To Stay Put
I'd like to have a QGraphicsItem on a fixed location when moving around in the scene.
The suggested solution is to override the void paintEvent(QPaintEvent*) of the sub-classed QGraphicsView.
void MyGraphicsView::paintEvent(QPaintEvent*) {
QPointF scenePos = mapToScene(0,0); // map viewport's top-left corner to scene
myItem->setPos(scenePos);
}
However, the problem is that I want everything else in the scene to stay intact, i.e. if I zoom or move I want all other QGraphicsItems to behave as default.
One poor way of solving this is to call void QGraphicsView::paintEvent(QPaintEvent*) from within void MyGraphicsView::paintEvent(QPaintEvent*).
void MyGraphicsView::paintEvent(QPaintEvent* event) {
QGraphicsView::paintEvent(event);
QPointF scenePos = mapToScene(0,0); // map viewport's top-left corner to scene
myItem->setPos(scenePos);
}
However, this adds a flickering behaviour to my_item since it's positioned first using QGraphicsView::paintEvent(event); and then using the added code
QPointF scenePos = mapToScene(0,0); // map viewport's top-left corner to scene
myItem->setPos(scenePos);
The question is, do I have to re-implement void MyGraphicsView::paintEvent(QPaintEvent*) from scratch and write code for both the desired behaviour of myItem and the default behaviour of all other QGraphicsItems, or is there an easier way to do this?
Thank you.

I think this is what you are looking for:
http://qt-project.org/doc/qt-4.8/qgraphicsitem.html#setFlag
QGraphicsItem::ItemIgnoresTransformations
Description from the docs:
The item ignores inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). This flag is useful for keeping text label items horizontal and unscaled, so they will still be readable if the view is transformed. When set, the item's view geometry and scene geometry will be maintained separately. You must call deviceTransform() to map coordinates and detect collisions in the view. By default, this flag is disabled. This flag was introduced in Qt 4.3. Note: With this flag set you can still scale the item itself, and that scale transformation will influence the item's children.
You may also want to parent everything that does pan around to something else. Then, you move or scale or rotate a single graphics group to affect everything except your "un-transformable" objects.
https://qt-project.org/doc/qt-4.8/graphicsview.html#the-graphics-view-coordinate-system
https://qt-project.org/doc/qt-4.8/painting-transformations.html (a cool example, though it doesn't show this feature really)
http://qt-project.org/doc/qt-4.8/demos-chip.html (great example of using QGraphicsView)
Hope that helps.
EDIT:
Example showing how you can achieve a static layer using parenting:
main.cpp
#include <QApplication>
#include "mygraphicsview.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyGraphicsView w;
w.show();
return a.exec();
}
mygraphicsview.h
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H
#include <QGraphicsView>
#include <QGraphicsItemGroup>
#include <QMouseEvent>
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
MyGraphicsView(QWidget *parent = 0);
~MyGraphicsView();
public slots:
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
bool down;
QPointF m_last_pos;
QGraphicsItemGroup * m_group;
};
#endif // MYGRAPHICSVIEW_H
mygraphicsview.cpp
#include "mygraphicsview.h"
#include <QGraphicsItem>
#include <QGraphicsEllipseItem>
#include <QGraphicsTextItem>
MyGraphicsView::MyGraphicsView(QWidget *parent)
: QGraphicsView(parent)
{
down = false;
this->setScene(new QGraphicsScene);
// Anything not added to the "group" will stay put
this->scene()->addEllipse(20, 20, 50, 50);
this->scene()->addEllipse(180, 180, 50, 50);
this->scene()->addText("Click and drag with the mouse to move only the tiny dots.");
// This group will receive all transformations
m_group = new QGraphicsItemGroup;
for(int r = 0; r < 20; r ++)
{
for(int c = 0; c < 20; c++)
{
if(c % 5 == 0 && r % 5 == 0)
{
QGraphicsTextItem * txt = new QGraphicsTextItem(QString::number(r) + "," + QString::number(c));
m_group->addToGroup(txt);
txt->setPos(r*100, c*100);
}
m_group->addToGroup(new QGraphicsEllipseItem(r *100, c*100, 5, 5));
}
}
this->scene()->addItem(m_group);
}
MyGraphicsView::~MyGraphicsView()
{
}
void MyGraphicsView::mousePressEvent(QMouseEvent *event)
{
m_last_pos = mapToScene(event->pos());
down = true;
}
void MyGraphicsView::mouseReleaseEvent(QMouseEvent *)
{
down = false;
}
void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
if(down)
{
QPointF temp = mapToScene(event->pos());
QPointF delta = temp - m_last_pos;
m_last_pos = temp;
// Apply transformation to the group, not the scene!
m_group->translate(delta.x(), delta.y());
}
}

Related

How to bounce a QWidget around the desktop

I am trying to bounce a QWidget around the screen. This is the code i tried.
class Window : public QMainWindow {
public:
void moveEvent(QMoveEvent* aEvent) override;
};
void Window::moveEvent(QMoveEvent* aEvent) {
QSizeF screenSize = QGuiApplication::primaryScreen()->screenSize();
QRect oldRect = this->geometry();
QRect newRect = oldRect;
QPoint offset;
if (newRect.left() == 0) {
offset.setX(1);
}
else if (newRect.right() == screenSize.width()) {
offset.setX(-1);
}
if (newRect.top() == 0) {
offset.setX(1);
}
else if (newRect.bottom() == screenSize.height()) {
offset.setX(-1);
}
newRect.setTopLeft(newRect.topLeft() + offset);
newRect.setBottomRight(newRect.bottomRight() + offset);
QTimer::singleShot(1, [this, newRect]() {
setGeometry(newRect);
});
}
int main(int argc, char** argv) {
QApplication app{argc, argv};
Window* w = new Window();
w->show();
w->setGeometry(w->geometry());
return app.exec();
}
However, the window does not move around the screen, but somewhat jitters in place. When i move the window with the mouse and let go. It moves sporadically around the desktop, which is also not what i want.
Does anyone know if this is possible? If so, does anyone know the right way to do this?
There are several problems with the posted code, including:
The Window class doesn't have any member-variable to keep track of its current direction of motion. Without keeping that state, it's impossible to correctly calculate the next position along that direction of motion.
Driving the animation from within moveEvent() is a bit tricky, since moveEvent() gets called in response to setGeometry() as well as in response to the user actually moving the window with the mouse; that makes unexpected feedback loops possible, resulting in unexpected behavior.
The code assumes that the screen's usable surface area starts at (0,0) and ends at (screenSize.width(),screenSize.height()), which isn't necessarily a valid assumption. The actual usable area of the screen is a rectangle given by availableGeometry().
When calling setGeometry(), you are setting the new location of the area of the window that the Qt program can actually draw into. However that's only a 99% subset of the actual on-screen area taken up by the window, because the window also includes the non-Qt-controlled regions like the title bar and the window-borders. Those parts need to fit into the availableGeometry() also, otherwise the window won't be positioned quite where you wanted it to be, which can lead to anomalies (like the window getting "stuck" on the top-edge of the screen)
In any case, here's my attempt at rewriting the code to implement a closer-to-correct "bouncing window". Note that it's still a bit glitchy if you try to mouse-drag the window around while the window is also trying to move itself around; ideally the Qt program could detect the mouse-down-event on the title bar and use that to disable its self-animation until after the corresponding mouse-up-event occurs, but AFAICT that isn't possible without resorting to OS-specific hackery, because the window-title-bar-dragging is handled by the OS, not by Qt. Therefore, I'm leaving that logic unimplemented here.
#include <QApplication>
#include <QMainWindow>
#include <QMoveEvent>
#include <QShowEvent>
#include <QScreen>
#include <QTimer>
class Window : public QMainWindow {
public:
Window() : pixelsPerStep(5), moveDelta(pixelsPerStep, pixelsPerStep)
{
updatePosition(); // this will get the QTimer-loop started
}
private:
void updatePosition()
{
const QRect windowFrameRect = frameGeometry(); // our on-screen area including window manager's decorations
const QRect windowRect = geometry(); // our on-screen area including ONLY the Qt-drawable sub-area
// Since setGeometry() sets the area not including the window manager's window-decorations, it
// can end up trying to set the window (including the window-decorations) slightly "out of bounds",
// causing the window to "stick to the top of the screen". To avoid that, we'll adjust (screenRect)
// to be slightly smaller than it really is.
QRect screenRect = QGuiApplication::primaryScreen()->availableGeometry();
screenRect.setTop( screenRect.top() + windowRect.top() - windowFrameRect.top());
screenRect.setBottom( screenRect.bottom() + windowRect.bottom() - windowFrameRect.bottom());
screenRect.setLeft( screenRect.left() + windowRect.left() - windowFrameRect.left());
screenRect.setRight( screenRect.right() + windowRect.right() - windowFrameRect.right());
// Calculate where our window should be positioned next, assuming it continues in a straight line
QRect nextRect = geometry().translated(moveDelta);
// If the window is going to be "off the edge", set it to be exactly on the edge, and reverse our direction
if (nextRect.left() <= screenRect.left()) {nextRect.moveLeft( screenRect.left()); moveDelta.setX( pixelsPerStep);}
if (nextRect.right() >= screenRect.right()) {nextRect.moveRight( screenRect.right()); moveDelta.setX(-pixelsPerStep);}
if (nextRect.top() <= screenRect.top()) {nextRect.moveTop( screenRect.top()); moveDelta.setY( pixelsPerStep);}
if (nextRect.bottom() >= screenRect.bottom()) {nextRect.moveBottom(screenRect.bottom()); moveDelta.setY(-pixelsPerStep);}
setGeometry(nextRect);
QTimer::singleShot(20, [this]() {updatePosition();});
}
const int pixelsPerStep;
QPoint moveDelta; // our current positional-offset-per-step in both X and Y direction
};
int main(int argc, char** argv) {
QApplication app{argc, argv};
Window* w = new Window();
w->show();
return app.exec();
}

Getting the wrong position from QMainWindow geometry()

I am using a class inheriting QMainWindow in a Qt GUI, and another class that handles game logic.
The purpose of the code is to place a UI element at a specific position in the window (as well as move them around as needed.) But, I am having issues. If I increase the size of the window, the Y axis gets larger than the window and places the object below the fold.
game.h
#ifndef GAME_H
#define GAME_H
#include "util.h"
#include "myrect.h"
class Game: public QObject
{
Q_OBJECT
TheColony * TC;
public:
Game(TheColony * ThC);
QRect getBoard(){ return QRect(0,0,TC->geometry().width(),TC->geometry().height()); }
private slots:
virtual void periodic();
protected:
QGraphicsScene * scene;
QGraphicsView * view;
MyRect * player;
QTimer * periodic_timer;
};
#endif // GAME_H
game.cpp
#include "game.h"
Game::Game(TheColony * ThC)
: TC(ThC){
//prepare the scene and view
scene = new QGraphicsScene(getBoard(),TC);
view = new QGraphicsView(scene);
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setGeometry(getBoard());
view->show();
TC->setCentralWidget(view);
//setup the player's position and size
player = new MyRect(QRect((view->width()/2) - 50,view->height() - 100,100,100));
player->setFlag(QGraphicsItem::ItemIsFocusable);
scene->addItem(player);
player->setFocus();
//timer used to trigger periodic checks.
periodic_timer = new QTimer();
connect(periodic_timer,SIGNAL(timeout()),this,SLOT(periodic()));
periodic_timer->start(500);
}
void Game::periodic(){
static int tcHeight = getBoard().height();
if(tcHeight != getBoard().height()){
view->setGeometry(getBoard());
player->setRect(player->rect().x(), getBoard().height() - 100,100,100);
tcHeight = getBoard().height();
}
}
On load, square is positioned as expected.
After resizing the window larger than the original, the square falls below the fold.
Was solved by freqlabs on discord (Who does not have a stack overflow account.)
I was failing to update QGraphicsScene's rect;
scene->setSceneRect(getBoard());
This left the coordinate space for the scene incorrect and caused the object to be translated wrong. I misunderstood how exactly Qt was using coordinates, I did not realize it used actual coordinate spaces with matrix translations.

Qt GraphicsScene XOR Line or Line in separate layer?

I have started to learn Qt, and try to improve my basic C++ skills.
In GraphicsScene, I try to draw a line by using the mouse (mouse events).
When I start drawing a line in GraphicsScene, a thin dashed line is drawn from the origin, where I clicked first to the current mouse position and moves with the mouse, before the second point is clicked. To erase it, I draw it in black. If I hover over already draw lines, you will see the black drawn lines on them. To undraw it without leaving marks, an XOR operation on GraphicsScene would come in handy, or if I could draw in a different layer and not touching the other layer could be handy. But I couldn't yet figure how to do it. The example is on https://github.com/JackBerkhout/QT_Draw001
In line.cpp is the function setLineP2(int x, int y), which draws and erases that thin dashed line.
Can anybody help me with this, please?
The major misconception is thinking of a QGraphicsScene as some sort of a bitmap: it's not! It is a collection of items that can render themselves, and a spatial index for them. In a scene, if you wish to delete/hide something, you must not overpaint it - instead simply delete/hide the item in question as desired. The scene will handle all the details - that's what it's for
You must forget about GDI-anything at this point. You're not painting on the raw DC here. Even when using raw GDI, you do not want to paint on the window's DC as that flickers, you should paint on a bitmap and blit the bitmap to the window.
For example, your eraseScene method adds a rectangle on top of the scene, wasting memory and resources as all the previous items are retained (you can iterate through them), whereas all it should do is to clear the scene (or its equivalent):
void MainWindow::eraseScreen(void)
{
[...]
scene->addRect(0, 0, width()+1000, height()+1000, pen, brush);
}
vs. the correct:
void MainWindow::eraseScreen(void)
{
scene->clear();
}
Below is a complete example that approximates what you presumably meant to do in your code. It is 120 lines long. It was somewhat hard to figure out what exactly you meant to do as your code is so convoluted - it's useful to describe the exact behavior in simple terms in the question.
The example uses QPainterPath to keep a list of MoveTo and LineTo elements that a QPainterPathItem renders. It also uses a QGraphicsLineItem to display the transient line.
The MyScene::PathUpdater is used to enclose the context where a path is modified, and ensure that proper pre- and post-conditions are maintained. Namely:
Since QPainterPath is implicitly shared, you should clear the path held by QGraphicsPathItem to avoid an unnecessary implicit copy. That's the precondition necessary before modifying m_path.
After m_path has been modified, the path item must be updated, as well as a new status emitted.
The following other points are worth noting:
Holding the members by value leads to a notable absence of any memory management code (!) - the compiler does it all for us. You won't find a single new or delete anywhere. They are not necessary, and we're paying no additional cost for not doing this manually. Modern C++ should look exactly like this.
The clear split between the display MainWindow and MyScene. The MainWindow knows nothing about the specifics of MyScene, and vice-versa. The code within main acts as an adapter between the two.
The leveraging of C++11.
Succinct style necessary for SO test cases and examples: for learning it's best to keep it all in one file to easily see all the parts of the code. It's only 120 lines, vs. more than twice that if split across files. Our brains leverage the locality of reference. By splitting the code you're making it harder for yourself to comprehend.
See also
Another demo of interactive item creation.
A more advanced example of status notifications.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scene-polygon-7727656
#include <QtWidgets>
class MainWindow : public QWidget
{
Q_OBJECT
QGridLayout m_layout{this};
QPushButton m_new{"New"};
QPushButton m_erase{"Erase All"};
QLabel m_label;
QGraphicsView m_view;
public:
MainWindow() {
m_layout.addWidget(&m_new, 0, 0);
m_layout.addWidget(&m_erase, 0, 1);
m_layout.addWidget(&m_label, 0, 2);
m_layout.addWidget(&m_view, 1, 0, 1, 3);
m_view.setBackgroundBrush(Qt::black);
m_view.setAlignment(Qt::AlignBottom | Qt::AlignLeft);
m_view.scale(1, -1);
connect(&m_new, &QPushButton::clicked, this, &MainWindow::newItem);
connect(&m_erase, &QPushButton::clicked, this, &MainWindow::clearScene);
}
void setScene(QGraphicsScene * scene) {
m_view.setScene(scene);
}
Q_SIGNAL void newItem();
Q_SIGNAL void clearScene();
Q_SLOT void setText(const QString & text) { m_label.setText(text); }
};
class MyScene : public QGraphicsScene {
Q_OBJECT
public:
struct Status {
int paths;
int elements;
};
private:
bool m_newItem = {};
Status m_status = {0, 0};
QPainterPath m_path;
QGraphicsPathItem m_pathItem;
QGraphicsLineItem m_lineItem;
struct PathUpdater {
Q_DISABLE_COPY(PathUpdater)
MyScene & s;
PathUpdater(MyScene & scene) : s(scene) {
s.m_pathItem.setPath({}); // avoid a copy-on-write
}
~PathUpdater() {
s.m_pathItem.setPath(s.m_path);
s.m_status = {0, s.m_path.elementCount()};
for (auto i = 0; i < s.m_status.elements; ++i) {
auto element = s.m_path.elementAt(i);
if (element.type == QPainterPath::MoveToElement)
s.m_status.paths++;
}
emit s.statusChanged(s.m_status);
}
};
void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
PathUpdater updater(*this);
auto pos = event->scenePos();
m_lineItem.setLine(0, 0, pos.x(), pos.y());
m_lineItem.setVisible(true);
if (m_path.elementCount() == 0 || m_newItem)
m_path.moveTo(pos);
m_path.lineTo(pos.x()+1,pos.y()+1); // otherwise lineTo is a NOP
m_newItem = {};
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
PathUpdater updater(*this);
auto pos = event->scenePos();
m_lineItem.setLine(0, 0, pos.x(), pos.y());
m_path.setElementPositionAt(m_path.elementCount()-1, pos.x(), pos.y());
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override {
m_lineItem.setVisible(false);
}
public:
MyScene() {
addItem(&m_pathItem);
addItem(&m_lineItem);
m_pathItem.setPen({Qt::red});
m_pathItem.setBrush(Qt::NoBrush);
m_lineItem.setPen({Qt::white});
m_lineItem.setVisible(false);
}
Q_SLOT void clear() {
PathUpdater updater(*this);
m_path = {};
}
Q_SLOT void newItem() {
m_newItem = true;
}
Q_SIGNAL void statusChanged(const MyScene::Status &);
Status status() const { return m_status; }
};
int main(int argc, char *argv[])
{
using Q = QObject;
QApplication app{argc, argv};
MainWindow w;
MyScene scene;
w.setMinimumSize(600, 600);
w.setScene(&scene);
Q::connect(&w, &MainWindow::clearScene, &scene, &MyScene::clear);
Q::connect(&w, &MainWindow::newItem, &scene, &MyScene::newItem);
auto onStatus = [&](const MyScene::Status & s){
w.setText(QStringLiteral("Paths: %1 Elements: %2").arg(s.paths).arg(s.elements));
};
Q::connect(&scene, &MyScene::statusChanged, onStatus);
onStatus(scene.status());
w.show();
return app.exec();
}
#include "main.moc"

QGraphicsScene/View Scale Understanding

I'm lost with understanding the scale value of QGraphicsScene/View.
Here is how I'm placing my targets in the scene.
QPointF Mainwindow::pointLocation(double bearing, double range){
int offset = 90; //used to offset Cartesian system
double centerX = baseSceneSize/2;//push my center location out to halfway point
double centerY = baseSceneSize/2;
double newX = centerX + qCos(qDegreesToRadians(bearing - offset)) * range;
double newY = centerY + qSin(qDegreesToRadians(bearing - offset)) * range;
QPointF newPoint = QPointF(newX, newY);
return newPoint;
}
So each target has a bearing and range. As long as I don't scale, or zoom, the scene, these values work sufficiently. My problem is that I need to implement the zooming.
Here's where things go wrong:
I have a target at Bearing 270, Range 10.
When the app runs, and my vertical slider is at a value of zero, I can see this target in my view. I should not. I need for this target to only come into view when the slider has gotten to a value of 10. Just think each position value on the slider equates to 1 nautical mile. So if a target is at 10 NMs it should only be visible once the slider is >= 10.
here is how I'm doing the zooming:
void MainWindow:: on_PlotSlider_sliderMoved(int position){
const qreal factor = 1.01;
viewScaleValue = qPow(factor, -position);//-position to invert the scale
QMatrix matrix;
matrix.scale(viewScaleValue, viewScaleValue);
view->setMatrix(matrix);
}
I've tried making the View bigger, the Scene bigger, but nothing is having the proper effect.
Here is my Scene setup:
view = ui->GraphicsView;
scene = new QGraphicsScene(this);
int baseSize = 355;
scene->setSceneRect(0,0,baseSize,baseSize);
baseSceneSize = scene->sceneRect().width();
view->setScene(scene);
How do I take the range of my target and push it out into the scene so that it lines up with the slider value?
QGraphicsView::fitInView is everything you need to select the displayed range and center the view.
Here's how you might do it. It's a complete example.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scene-radar-40680065
#include <QtWidgets>
#include <random>
First, let's obtain random target positions. The scene is scaled in e.g. Nautical Miles: thus any coordinate in the scene is meant to be in these units. This is only a convention: the scene otherwise doesn't care, nor does the view. The reference point is at 0,0: all ranges/bearings are relative to the origin.
QPointF randomPosition() {
static std::random_device dev;
static std::default_random_engine eng(dev());
static std::uniform_real_distribution<double> posDis(-100., 100.); // NM
return {posDis(eng), posDis(eng)};
}
Then, to aid in turning groups of scene items on and off (e.g. graticules), it helps to have an empty parent item for them:
class EmptyItem : public QGraphicsItem {
public:
QRectF boundingRect() const override { return QRectF(); }
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {}
};
A scene manager sets up the display. The empty items act as item collections and they can be easily made hidden/visible without having to modify child items. They also enforce the relative Z-order of their children.
class SceneManager : public QObject {
Q_OBJECT
Q_PROPERTY(bool microGraticuleVisible READ microGraticuleVisible WRITE setMicroGraticuleVisible)
QGraphicsScene m_scene;
QPen m_targetPen{Qt::green, 1};
EmptyItem m_target, m_center, m_macroGraticule, m_microGraticule;
An event filter can be installed on the view to signal when the view has been resized. This can be used to keep the view centered in spite of resizing:
bool eventFilter(QObject *watched, QEvent *event) override {
if (event->type() == QEvent::Resize
&& qobject_cast<QGraphicsView*>(watched))
emit viewResized();
return QObject::eventFilter(watched, event);
}
Scene has the following Z-order: center cross, macro- and micro-graticule, then the targets are on top.
public:
SceneManager() {
m_scene.addItem(&m_center);
m_scene.addItem(&m_macroGraticule);
m_scene.addItem(&m_microGraticule);
m_scene.addItem(&m_target);
m_targetPen.setCosmetic(true);
addGraticules();
}
We can monitor a graphics view for resizing; we also expose the visibility of the micro graticule.
void monitor(QGraphicsView *view) { view->installEventFilter(this); }
QGraphicsScene * scene() { return &m_scene; }
Q_SLOT void setMicroGraticuleVisible(bool vis) { m_microGraticule.setVisible(vis); }
bool microGraticuleVisible() const { return m_microGraticule.isVisible(); }
Q_SIGNAL void viewResized();
Targets can be randomly generated. A target has a fixed size in view coordinates. Its position, though, is subject to any scene-to-view transformations.
The pens for targets and graticules are cosmetic pens: their width is given in the view device units (pixels), not scene units.
void newTargets(int count = 200) {
qDeleteAll(m_target.childItems());
for (int i = 0; i < count; ++i) {
auto target = new QGraphicsEllipseItem(-1.5, -1.5, 3., 3., &m_target);
target->setPos(randomPosition());
target->setPen(m_targetPen);
target->setBrush(m_targetPen.color());
target->setFlags(QGraphicsItem::ItemIgnoresTransformations);
}
}
The graticules are concentric circles centered at the origin (range reference point) and a cross at the origin. The origin cross has fixed size in view units - this is indicated by the ItemIgnoresTransformations flag.
void addGraticules() {
QPen pen{Qt::white, 1};
pen.setCosmetic(true);
auto center = {QLineF{-5.,0.,5.,0.}, QLineF{0.,-5.,0.,5.}};
for (auto l : center) {
auto c = new QGraphicsLineItem{l, &m_center};
c->setFlags(QGraphicsItem::ItemIgnoresTransformations);
c->setPen(pen);
}
for (auto range = 10.; range < 101.; range += 10.) {
auto circle = new QGraphicsEllipseItem(0.-range, 0.-range, 2.*range, 2.*range, &m_macroGraticule);
circle->setPen(pen);
}
pen = QPen{Qt::white, 1, Qt::DashLine};
pen.setCosmetic(true);
for (auto range = 2.5; range < 9.9; range += 2.5) {
auto circle = new QGraphicsEllipseItem(0.-range, 0.-range, 2.*range, 2.*range, &m_microGraticule);
circle->setPen(pen);
}
}
};
The mapping between the scene units and the view is maintained as follows:
Each time the view range is changed (from e.g. the combo box), the QGraphicsView::fitInView method is called with a rectangle in scene units (of nautical miles). This takes care of all of the scaling, centering, etc.. E.g. to select a range of 10NM, we'd call view.fitInView(QRect{-10.,-10.,20.,20.), Qt::KeepAspectRatio)
The graticule(s) can be disabled/enabled as appropriate for a given range to unclutter the view.
int main(int argc, char ** argv) {
QApplication app{argc, argv};
SceneManager mgr;
mgr.newTargets();
QWidget w;
QGridLayout layout{&w};
QGraphicsView view;
QComboBox combo;
QPushButton newTargets{"New Targets"};
layout.addWidget(&view, 0, 0, 1, 2);
layout.addWidget(&combo, 1, 0);
layout.addWidget(&newTargets, 1, 1);
view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.setBackgroundBrush(Qt::black);
view.setScene(mgr.scene());
view.setRenderHint(QPainter::Antialiasing);
mgr.monitor(&view);
combo.addItems({"10", "25", "50", "100"});
auto const recenterView = [&]{
auto range = combo.currentText().toDouble();
view.fitInView(-range, -range, 2.*range, 2.*range, Qt::KeepAspectRatio);
mgr.setMicroGraticuleVisible(range <= 20.);
};
QObject::connect(&combo, &QComboBox::currentTextChanged, recenterView);
QObject::connect(&mgr, &SceneManager::viewResized, recenterView);
QObject::connect(&newTargets, &QPushButton::clicked, [&]{ mgr.newTargets(); });
w.show();
return app.exec();
}
#include "main.moc"
So as Kuba suggested, I was overcomplicating this a bit. With his help this is what ended up getting me the result I needed. Not 100% sure on some of it, but for now it's working the way I need it to.
view = ui->GraphicsView;
scene = new QGraphicsScene(this);
int baseSize = 1000; // MAGIC value that works, anything other than this, not so much
view->setSceneRect(0,0,baseSize,baseSize);
baseViewSize = view->sceneRect().width();
view->setScene(scene);
My drawPoint method works fine, no changes were needed.
Finally, here is my slider
void MainWindow:: on_PlotSlider_sliderMoved(int position){
const qreal factor = 1.01;
viewScaleValue = qPow(factor, -position);//-position to invert the scale
QMatrix matrix;
// below is the update, again 6 is a MAGIC number, no clue why 6 works...
matrix.scale((baseViewSize/6 / position, baseViewSize/6 / position);
view->setMatrix(matrix);
}
While my problem is solved, I would love some explanation as to my 2 MAGIC numbers.
Why does it all only work is the baseSize is 1000?
Why does it only scale correctly if I divide the BaseViewSize by 6?

Painting in graphicsview

I am using the graphics view to paint the graphicsitem in it. Earlier when I clicked the button the respective item was painted only once, to again paint the same entity I had topush the button again. To overcome this I constructed the signal to allow to add the entities multiple times without having the need to push the button again. But when I using vector to store the points.It does not append, limiting its capacity to 2 only. Following is my output and the code
circle.cpp
void circle::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton) {
if(mFirstClick){
x1 = e->pos().x();
y1 = e->pos().y();
mFirstClick = false;
mSecondClick = true;
}
else if(!mFirstClick && mSecondClick){
x2 = e->pos().x();
y2 = e->pos().y();
mPaintFlag = true;
mSecondClick = false;
update();
emit DrawFinished();
_store.set_point(e->pos());
store_point.push_back(_store);
qDebug() << _store.getValue();
qDebug() << "Size of vector =" << store_point.size() << "and" << store_point.capacity();
update();
}
}
mainwindow.cpp
void MainWindow::drawCircle(){
item2 = new circle;
scene->addItem(item2);
qDebug() << "Circle Created";
connect(item2, SIGNAL(DrawFinished()), this, SLOT(drawCircle()));
}
output
Circle Created
QPointF(60, 87)
Size of vector = 1 and 1
Circle Created
QPointF(77, 221)
Size of vector = 2 and 2
QPointF(333, 57)
Size of vector = 1 and 1
When I remove the signal DrawFinished(), the points store perfectly but the item gets painted only once. I need to pushthe button again:(. Following is the output after removing the signal.
QPointF(74, 80)
Size of vector = 1 and 1
QPointF(118, 165)
Size of vector = 2 and 2
QPointF(335, 97)
Size of vector = 3 and 4
What needs to be done to perfectly store the points as well as allow repainting. Please do help me to sort out all this.
Well, not sure if this would answer your request but a comment is too small to write what i want to tell you.
I don't really get what is the purpose of your signal DrawFinished(). Even if it's obvious thanks to the name, I don't think you need it.
If I sum up what you really want, you have a QGraphicView where you want to draw some shapes. Next to it, you have at least one (let's say 3) buttons to select which shapes you want to draw (Circle, Triangle, Rectangle).
Lets say you want to draw some circles, you click on the CircleButton, and then, click on the QGraphicView.
To my mind, I would create something like this:
Two classes, MainWindow and View, view which inherits from QGraphicView. Your three buttons are defined with Qt designer in your MainWindow class. So When you click on a button, you can emit a signal to notify the View.
In the View class you could have one vector for each shapes.
MainWindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void drawCircle();
void drawRectangle();
void drawTriangle();
private:
Ui::MainWindow *ui;
View view;
private slots:
void slotOnCircleButton();
void slotOnRectangleButton();
void slotOnTriangleButton();
};
MainWindow.cpp
[...]
void MainWindow::slotOnCircleButton()
{
emit(drawCircle());
}
[...]
View.h
class View : public QGraphicsView
{
Q_OBJECT
public:
explicit View(QWidget *parent = 0);
enum DrawingMode
{
UNDEFINED,
CIRCLE,
TRIANGLE,
RECTANGLE
}
signals:
public slots:
void slotOnDrawCircle();
void slotOnDrawRectangle();
void slotOnDrawTriangle();
private:
DrawingMode mode;
QVector<QPointF> vectorCircle;
QVector<QPointF> vectorTriangle;
QVector<QPointF> vectorRectangle;
};
View.cpp
[...]
void View::slotOnDrawCircle()
{
this->mode = CIRCLE;
}
[...]
void View::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton && this->mode != UNDEFINED)
{
qreal x1 = e->pos().x();
qreal y1 = e->pos().y();
if(this->mode == CIRCLE)
{
this->vectorCircle.append(e->pos());
}
else if(...)
[...]
// updatePainting();
}
}
When updating the view, you just have to travel throw your 3 vectors and drawing circle, rectangle or triangle.
This way you don't have such a spaghetti code, it's quite clear.
I didn't run the code so there is probable some minor mistakes, don't forget to make your connections and your initializations.