I would like to create a QGraphicsRectItem and display its name with a QGraphicsSimpleTextItem. I want the size of the text to be unaffected by the zoom. I also want the position of the text to be centered on the QGraphicsRectItem.
Here is my attempt so far :
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QPen>
#include <QWheelEvent>
#include <cmath>
#include <QDebug>
class MainView : public QGraphicsView {
public:
MainView(QGraphicsScene *scene) : QGraphicsView(scene) { setBackgroundBrush(QBrush(QColor(255, 255, 255)));}
protected:
void wheelEvent(QWheelEvent *event) {
double scaleFactor = pow(2.0, event->delta() / 240.0);
scale(scaleFactor, scaleFactor);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 800, 800);
QGraphicsRectItem* rectItem = new QGraphicsRectItem(QRectF(0, 0, 400, 200));
rectItem->setPos(200, 200);
rectItem->setBrush(QColor(255, 0, 0));
scene.addItem(rectItem);
QGraphicsSimpleTextItem *nameItem = new QGraphicsSimpleTextItem("name", rectItem);
QFont f = nameItem->font();
f.setPointSize(12);
nameItem->setFont(f);
nameItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
nameItem->setPos(rectItem->rect().center());
MainView view(&scene);
view.show();
return a.exec();
}
Unfortunately you can see : on the capture that when I unzoom ( on the right ), the text doesn't stay inside the rectangle.
How to keep the text inside the rectangle and centered ?
Thank you.
I also want the position of the text to be centered on the QGraphicsRectItem
What you're seeing is correct, since you're scaling about the top left of the QGraphicsView and the text item is placed in the centre of the rectangle.
If you scaled about the centre of your QGraphicsRectItem, you'd see the text would maintain its position in the centre of the rect.
Another way of looking at this is to position the text in the top left corner of the rectangle. You'll note that when you scale here, the text will appear correct, up until it can no longer fit in the rectangle.
Continue scaling and you'll see that the top left of the text is still in the centre, but since the text does not obey the transform, it is pushed outside
It may appear that the top left of the text is below the rectangle, but the bounding rect of the text takes accents into consideration (e.g รจ).
So, by having the text positioned in the centre of the rect, rather than the top left, the appearance of the text being outside the rect is exacerbated.
Once you've zoomed out so far, the transform of the rect is dealing in fractions of a point size, but the non transformed text is unaffected and so the difference between, for example, 0.6 and 0.9 of a pixel is irrelevant and will be positioned at the same pixel.
You need to consider what you're trying to achieve. Is it really necessary to zoom to that extent or can you restrict it beyond a certain point, where you won't notice this issue?
Related
I am adding a compass to the top right corner of a QGraphicsView like so:
ui.compass = new QLabel(viewport());
ui.compass->setPixmap(pm);
ui.compass->setContentsMargins(viewport()->width() - pm.width() - 2, 2, 0, 0);
When the QGraphicsView is resized, I simply reset the left margin (the same code as the last line above) to keep it on the right.
The problem happens when I resize the QGraphicsView to any width wider than when the application started, in which case, the compass is clipped (as seen in the screenshot below) and eventually disappears completely, as if there is an invisible curtain is slides behind. When resizing smaller than the initial size, all works as expected. After resizing smaller, it can be resized larger also without issue until reaching that initial width again.
Edit: the application saves the geometry on close and restores next start up. If I resize the window to be larger, such that compass is not visible, close the application and start it again, the graphics view starts as the larger size and the compass is visible, until I try to resize wider again.
Edit 2: added minimum working example. Just resize the application wider than start up and watch the compass image disappear.
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QLabel>
#include <QPixmap>
class MyGraphicsView : public QGraphicsView
{
public:
MyGraphicsView()
: pm(":/images/Compass.svg")
, compass(new QLabel(viewport()))
{
compass->setPixmap(pm);
}
private:
void resizeEvent(QResizeEvent* event) override
{
QGraphicsView::resizeEvent(event);
QMargins margins = compass->contentsMargins();
margins.setLeft(viewport()->width() - pm.width() - 2);
compass->setContentsMargins(margins);
}
QLabel* compass;
QPixmap pm;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow win;
win.setCentralWidget(new MyGraphicsView());
win.show();
return app.exec();
}
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.
I am learning Qt5 and I use Qt 5.9.1. Now I have a problem: how to keep the graphics in the center of main window even when the size of main window changes?
Last year I learned MFC in my class and the teacher told us that in order to make the graphics always stay in the window, we should do as followings:
Let the origin of viewport be the center of client area;
Let the size of viewport be the size of client area;
Let the origin of window be the center of graphics' bounding rectangle;
Let the size of window be the size of graphics' bounding rectangle .
So I do the same thing in Qt5:
// main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QtGuiApplication1 w;
// get the size and center of client area
// and then pass the values to QtGuiApplication
int width = QApplication::desktop()->availableGeometry().width();
int height = QApplication::desktop()->availableGeometry().height();
QPoint center = QApplication::desktop()->availableGeometry().center();
w.setViewport(center,width,height);
w.show();
return a.exec();
}
// GtGuiApplication.cpp
void QtGuiApplication1::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// set the viewport
painter.setViewport(centerOfViewport.x(),centerOfViewport.y(), widthOfViewport, heightOfViewport);
static const QPointF points[4] = {
QPointF(10.0, 10.0),
QPointF(10.0, 80.0),
QPointF(50.0, 80.0),
QPointF(10.0, 10.0)
};
// set the window
painter.setWindow(30,45,40,70);
painter.drawPolyline(points, 4);
}
However all these things didn't work. Before I set the viewport and window:
And after I did the setting:
I do not understand what you indicate in the line, maybe it is fulfilled within MFC, but the rules are not accepted without analyzing them, you have to understand them and it seems that you want to apply them without understanding them correctly.
According to what you point out, you want the polygon to always be centered within the window, and in your code you use the size of the window which does not make sense since the window can be anywhere on the screen, from there we go badly.
If you want the center of the polygon to be the center of the window, then you must calculate both points considering in the first reference source the topleff of the rectangle bordering the polygon, and in the second the rectangle of the window, if we subtract both positions we obtain what must be moved by the painter so that both points coincide.
void QtGuiApplication1::paintEvent(QPaintEvent *)
{
QPainter painter(this);
static const QVector<QPointF> points = {
QPointF(10.0, 10.0),
QPointF(10.0, 80.0),
QPointF(50.0, 80.0),
QPointF(10.0, 10.0)
};
QPainterPath path;
path.addPolygon(QPolygonF(points));
QPointF center_path = path.boundingRect().center();
painter.translate(rect().center()-center_path);
painter.drawPath(path);
}
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"
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();
}