Adjusting QPen thickness when scaling QGraphicsView? - qt

One amazing feature of QGraphicsView is its ability to scale itself with its scene's content (every QGraphicsItem inserted in the scene actually). The QPixmap that I have inserted scales correctly, meaning that if I provide a scale factor of 4x4 with this:
view->scale(4,4);
Pixmap are zoomed as I want to do.
But this is not the case of the rects that I am used to drawing; they aims to surrounds the pixmaps that I draw on my scene and regardless of the scale factor, they keep a thickness of 1 instead of - I guess - 4.
I have been searching documentation about all of that stuff, trying to figure out the exact purpose of "cosmetics pen", but I still can't manage to make my rectangle go thicker.
Last notice: I have a custom QGraphicsItem and the QPen which is used to draw the rectangled is instanciated on-the-fly in the
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
method.
Does it matter?
Thanks in advance and apologies for my lack of experience / knowledge in both the Qt framework and the drawing algorithms fields...

It doesn't really matter where you instantiate the QPen.
QPen has a default width of 0. This is a special value that means cosmetic is true and the width is actually 1. So if you don't want the pen to be cosmetic you have to set it to the desired width. You might also need to set cosmetic to false.
Here is a simple example:
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene scene;
QGraphicsRectItem *item1 = scene.addRect(20, 20, 20, 20);
QGraphicsRectItem *item2 = scene.addRect(50, 20, 20, 20);
QPen pen1, pen2;
pen1.setWidth(5);
pen2.setWidth(5);
pen1.setCosmetic(true);
pen2.setCosmetic(false);
item1->setPen(pen1);
item2->setPen(pen2);
view.setScene(&scene);
view.scale(4, 4); // both rects are the same size, but one a has wider pen
view.show();
return a.exec();
}

Related

How to align two widgets in a QHBoxLayout where one is aligned far left, and one is aligned far right?

I want to have a QHBoxLayout where one QLabel is on the far left, the one QLabel is on the far right.
My Google-fu fails me. :( I cannot find the solution.
Here is a screenshot of the QHBoxLayout with two QLabel widgets:
Whatever I try, I cannot get the second QLabel widget aligned on the far right.
Roughly speaking, I tried something like this:
QHBoxLayout* const hboxLayout = new QHBoxLayout{};
hboxLayout->addWidget(m_leftLabel, 1);
hboxLayout->addStretch(1);
hboxLayout->addWidget(m_rightLabel, 0, Qt::AlignmentFlag::AlignRight);
I tried various large stretch values for the first addWidget() call and addStretch().
I also tried:
m_rightLabel->setAlignment(Qt::AlignmentFlag::AlignRight)
None of these solutions works. I am sure the solution is very simple ( ! ), but I cannot find it.
How can I do this?
My solution is to set a stretch in the middle:
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QHBoxLayout *lay = new QHBoxLayout(&w);
lay->addWidget(new QLabel("Left"));
lay->addStretch();
lay->addWidget(new QLabel("Right"));
w.show();
return a.exec();
}

Rendering a large QGraphicsScene on a QImage clips it off

I am creating some images rendering the contents of a QGraphicsScene.
My project requirement is that it should handle a canvas size of 10 ft by 8 inches. On screen, and scene size, that is 8640 x 576 pixels.
I can render it fine.
The thing is, the output images need to have 300 resolution.
That means, the rendered image will have a width of 36000, which is over 2^15 - 1 = 32767 pixels.....
The output is clipped - in the code below, I would get a QImage of correct expected size (36000) but the QGraphicsScene only renders to 32767 pixels.
That is confusing... I cannot explain the outcome - if the QImage limitations were 32767 pixels, then I should not be able to create one in the first place. But I checked and the QImage "sanity check" is much higher.
Once the image is created, I do not see anything in the code for rendering QGraphicsScene that would clip at any value....
This is a simple code that is trying to expose my problem.
It creates a QImage of required size, and fills with yellow (for control).
Then it renders a QGraphicsScene with blue background brush and a red rectangle close to the right margin.
If it works correctly, the result should be: an image of width 36000, blue with a tiny red rectangle at the far right.
But... as it is, the result is an image of width 36000, blue for the first 32766 pixels then yellow for the rest, no red rectangle.
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QPainter>
void printScene(QGraphicsScene* s, qreal ratio) {
qreal w = s->width() * ratio;
qreal h = s->height() * ratio;
QRectF target(0, 0, w, h);
QImage image = QImage(w, h, QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(Qt::yellow).rgb());
QPainter painter;
painter.begin(&image);
s->render(&painter, target);
painter.end();
image.save("image.png");
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QGraphicsScene s;
s.setSceneRect(0, 0, 8640, 576);
s.setBackgroundBrush(Qt::blue);
QGraphicsView view(&s);
view.show();
QGraphicsRectItem* r = s.addRect(8530, 250, 100, 100);
r->setBrush(Qt::red);
qreal ratio = 300/72.;
printScene(&s, ratio);
return app.exec();
}
As seen in sample images, the QImage is created successfully, QGraphicsScene though only renders to 2^15 - 1... But I stepped through their code and I didn't see it stop....
(I also tried creating the original scene 36000 x something (and setting the ratio to 1), and it displays fine... it just won't render to QImage anything beyond 32767 pixels)
Am I missing some setting ? What could be the cause of the QGraphicsScene::render() to not render more ?
I would love to find out how I can render the size I want - width of 36000 pixels - or a reason why this is not possible.
I am running this in Windows 7, 32 bit Qt 5.5.1 or 4.7.4
I have found the reason for the clipping - and imagined 2 workarounds.
Why:
Stepping through the rendering code, the clip rect gets limited to 32767:
bool QRasterPaintEngine::setClipRectInDeviceCoords(const QRect &r, Qt::ClipOperation op)
{
Q_D(QRasterPaintEngine);
QRect clipRect = r & d->deviceRect;
...
}
Where deviceRect is set by
void QRasterPaintEnginePrivate::systemStateChanged()
{
deviceRectUnclipped = QRect(0, 0,
qMin(QT_RASTER_COORD_LIMIT, device->width()),
qMin(QT_RASTER_COORD_LIMIT, device->height()));
QRegion clippedDeviceRgn = systemClip & deviceRectUnclipped;
deviceRect = clippedDeviceRgn.boundingRect();
baseClip->setClipRegion(clippedDeviceRgn);
...
}
and
// This limitations comes from qgrayraster.c. Any higher and
// rasterization of shapes will produce incorrect results.
const int QT_RASTER_COORD_LIMIT = 32767;
Options:
1) Render to a max of 32767 and, if the target must be bigger, scale result. (should give slightly lower quality)
2) Create 2 images and combine them (I still need to figure that out but I think it is the better fix)

QGraphicsItemGroup moves during transformations

I have QGraphicsItems on a QGraphicsScene. I need to group them. The main reason for grouping: I need to provide a way to apply transformations on a group.
It is all very simple if I need to apply a single transformation, and the items bounding rectangle top-left is at 0,0.
I have seen how grouping and ungrouping works, preserving items' transformations relative to the scene, and that is great. But on grouping, no matter what positions my items have, the group has coordinates (0,0).
When I try any transformations, no matter what I tried with the transformation origin point, the item moves.
Simple program to show:
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene s;
QGraphicsView view(&s);
s.setSceneRect(90, -10, 420, 140);
view.show();
QGraphicsRectItem* sceneR = new QGraphicsRectItem(s.sceneRect());
s.addItem(sceneR);
QTransform t; // simple scaling transform
t.scale(2, 1); // adding rotation scares me
QGraphicsRectItem* r0 = new QGraphicsRectItem(QRectF(0, 0, 150, 100)); // for compare, not scaled
r0->setBrush(QBrush(Qt::red));
r0->setPos(100, 0);
s.addItem(r0);
QGraphicsRectItem* r1 = new QGraphicsRectItem(QRectF(0, 0, 150, 100)); // for compare, not scaled
r1->setBrush(QBrush(Qt::blue));
r1->setPos(100, 10);
s.addItem(r1);
QGraphicsRectItem* r2 = new QGraphicsRectItem(QRectF(0, 0, 150, 100)); // will make a group of a single item
r2->setFlags(QGraphicsItem::ItemIsSelectable);
r2->setBrush(QBrush(Qt::green));
r2->setPos(100, 20);
r2->setSelected(true);
s.addItem(r2);
QGraphicsItemGroup* g = s.createItemGroup(s.selectedItems());
// QPointF topLeft = g->mapToScene(g->boundingRect().topLeft());
// g->setTransformOriginPoint(topLeft);
r1->setTransform(t);
g->setTransform(t);
qDebug() << r1->pos() << r1->transform();
qDebug() << g->pos() << g->transform();
return app.exec();
}
If I apply transformations, the grouped item moves. I have not been able to figure out a logic or how to compensate.
(red = reference, not scaled; blue, reference, scaled; green, grouped and scaled)
In image above I expect the green rectangle to have the same x coordinate as the blue (I shifted vertically to see what is going on)
How can I make my scaling (and rotation) in place ?
(I am considering the alternative of just setting a common parent, and performing all the work to retrieve items transformations, but not sure how to combine them properly, especially scaling a group how to find the new position and new scale of inside items)
Found a solution - even though I am counting on the group to perform item transformations and calculate its own edges, I must do it manually as well.
Steps:
I find the top-left most point (minX, minY) of selected items' bounding rectangles (mapped to scene),
then move all items by -minX, -minY,
then after grouping, move group by minX, minY.
This sets a correct center of transformations, so the group item behaves like regular items. It also reports a pos() in its top left corner, similar to the default pos() of other QGraphicsItems.

Different alignment of widgets in QGridLayout

The following code (Qt 5, same behaviour with Qt 4.8) uses a QGridLayout to add widgets above and to the left of a QScrollArea (to serve as a kind of header later):
#include <QApplication>
#include <QMainWindow>
#include <QGridLayout>
#include <QScrollArea>
class ColoredWidget : public QWidget {
public:
ColoredWidget(const QColor& color, QWidget* parent) : QWidget(parent) {
QPalette pal;
QBrush brush(color);
brush.setStyle(Qt::SolidPattern);
pal.setBrush(QPalette::Active, QPalette::Window, brush);
setPalette(pal);
setAutoFillBackground(true);
}
};
class MainWindow : public QMainWindow {
public:
MainWindow(QWidget* parent) : QMainWindow(parent) {
resize(300, 400);
QWidget* centralwidget = new ColoredWidget(QColor(0xff, 0xf0, 0xb5), this);
QGridLayout* layout = new QGridLayout();
centralwidget->setLayout(layout);
setCentralWidget(centralwidget);
// create widget with fixed height of 20 px and maximum width of 200
QWidget* topHeader = new ColoredWidget(Qt::green, centralwidget);
topHeader->setMaximumWidth(200);
topHeader->setFixedHeight(20);
// create widget with fixed width of 20 px and maximum height of 200
QWidget* leftHeader = new ColoredWidget(Qt::blue, centralwidget);
leftHeader->setFixedWidth(20);
leftHeader->setMaximumHeight(200);
// create scroll area as main widget
QWidget* view = new QScrollArea();
layout->addWidget(topHeader, 0, 1);
layout->addWidget(leftHeader, 1, 0);
// layout->addWidget(leftHeader, 1, 0, Qt::AlignTop); // widget not displayed at all!
layout->addWidget(view, 1, 1);
}
};
int main(int argc, char ** argv) {
QApplication app( argc, argv );
MainWindow win(0);
win.show();
return app.exec();
}
The QGridLayout documentation says that "Columns and rows behave identically", thus I would expect that the two widgets are layouted the same way.
However, the left one is automatically vertically centered, while the top widget is aligned to the left within the cell (and not centered horizontally). I would like to have the left widget also at a fixed position, means aligned to the top:
Which property causes this different behaviour between the two widgets, one beeing centered, the other being at a fixed position?
How can I influence the alignment (I can probably add a flexible spacer to fix it, but ideally I would like to avoid that)? I tried with Qt::AlignTop, but that made the blue widget disappear.
Althought this question is pretty old, I will add my answer for all those that come here afterwards, like me.
Setting the alignment either with
layout->setAlignment(leftHeader, Qt::AlignHCenter | Qt::AlignTop);
or with
layout->addWidget(leftHeader, 1, 0, Qt::AlignTop);
is intuitiv and seems to be the right way. If it is setup completly it is also working as expected.
But why does the Widget vanish then?
Long story short: because leftHeader has a size of 0.
In my pretty similar situation I've setfixedHeight() and the widget reappeared, this should also work by setting a value for minimalHeight() for example.
Long Story - Details
I've put myself in position of gridLayout and tried to determine the size of leftHeader:
Outside of the layout leftHeader got a fixed width of 20 - ok, but no given height. With the defaults of QWidget this leads to a default-height of 0.
Documentation of void QGridLayout::addWidget() and void QGridLayout::addLayout() states:
... The alignment is specified by alignment. The default alignment is
0, which means that the widget fills the entire cell. ...
source
Hence for default alignment of 0 the height of leftHeader is obviously set as height of its row.
But in case a different Qt::Alignment is used, as in the question (Qt::AlignTop ), it is not that simple.
From my point of view the layout is unable to determine the correct height without further settings from the designer.
Having a quick look at the implementation of void QGridLayout::addWidget(), we can detect that a QLayoutItem(as part of QGridBox) is created inside the QGridLayout for each added widget resp. Layout.
So it seems that the QGridLayout relies on default layout mechanisms of Qt based on the settings from the added widget/Layout (like minSize, maxSize, BaseSize, Size Increment, etc ).
In combination with 1.) this implies a height of 0 for leftHeader and therefore nothing to draw resp. it seems that the widget has vanished.
Although not a minimal solution you could probably fix the alignments by adding a QHBoxLayout in the cell for the green widget and a QVBoxLayout in the cell for the blue widget. Add the green widget to the QHBoxLayout and the blue widget to the QVBoxLayout. Then apply addStretch(n) to both the QHBoxLayout and QVBoxLayout. Maybe this is what you already mentioned you could do and then we are at the same level of knowledge concerning this. I believe this is the way I have solved these kind of issues and I haven't spent more time on it.
Try this:
setAlignment(Qt::AlignHCenter | Qt::AlignTop );

Qt alignment in QGridLayout eliminates the resizing of its elements

Ok, so basically I have a simple table with a QWidget and two buttons as shown below:
QGridLayout *layout = new QGridLayout;
layout->addWidget(viewcontainer,0,0,1,2);
layout->addWidget(reset,1,0);
layout->addWidget(done,1,1);
This is basically what I want, where "reset" and "done" are buttons. Essentially it's a QWidget, viewcontainer, which resizes as the window size is changed by the user while the buttons' heights remains the same. But, the default for the gridlayout is to align the contents to the left. If I change this with:
layout->addWidget(viewcontainer,0,0,1,2, Qt::AlignCenter);
It does sort of what I want, but the graphicsscene no longer resizes (remains a small constant size). I'd like to retain the resizing while just aligning the widget to the center. Thanks.
I think the easiest solution which provides a clean solution is to nest 2 layouts.
Your 'outer' (parent) layout should be a QHBoxLayout and you can add your QGridLayout into it as an 'inner' (child) layout with addLayout().
Based on my experience you should avoid to set Qt::Alignment every time you can. It can really mess up your layout. For simple layouts it can work but for more complex ones you should avoid it. And you never know that you should extend your layout in the future or not so my suggestion is to use nested layouts.
Of course you can create a QWidget for the 'outer' layout and for the 'innser' layout as well but most of the times it should be fine to just nest 2 layouts.
Also you can use QSpacerItem to fine-tune your layout.
Have a look at this example code, I think it does what you want:
#include <QApplication>
#include <QPushButton>
#include <QGraphicsView>
#include <QGridLayout>
#include <QPalette>
class MyWidget : public QWidget
{
public:
MyWidget()
{
QGridLayout * layout = new QGridLayout(this);
QGraphicsView * gv = new QGraphicsView;
layout->addWidget(gv, 0,0, 1,2);
layout->setRowStretch(0, 1); // make the top row get more space than the second row
layout->addWidget(new QPushButton("reset"), 1,0);
layout->addWidget(new QPushButton("done"), 1,1);
}
};
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
MyWidget w;
w.show();
return app.exec();
}

Resources