QGraphicsView size in a GridLayout - c++

I have found this: Getting the size of a QGraphicsView
But I can't figure out what does it mean to "move my initialization code to showEvent" and I can't comment on that answer.
I am want to resize a QPixmap so it could fit my QGraphicsView. I've placed my graphicsview in Designer and set GridLayout for my main window. In a MainWindow constructor I have written the following code:
ui->setupUi(this);
// Get GView size
g_sizeX = ui->mapView->width();
g_sizeY = ui->mapView->height();
// Init scene
scene = new QGraphicsScene(this);
// Init MAP pixmap and add it to scene
mapImage = new QPixmap(":/Map/europe.jpg");
QPixmap newmapImage = mapImage->scaled(g_sizeX, g_sizeY);
scene->addPixmap(newmapImage);
// Display scene in gview.
ui->mapView->setScene(scene);
But I always get size of 100x30. If I break the gridLayout, I get the correct size.
So, how should I deal with this?
Thank you.

The QGraphicsView will be resized by the QGridLayout after the widget is shown, and can be also resized later when the window is itself resized.
So you should change the size of the pixmap as a result of a QResizeEvent, either by subclassing QGraphicsView to redefine resizeEvent(), and then promoting your view object to your new class in the designer to use it instead of QGraphicsView, or by installing your MainWindow object as an event filter for the view to handle to the resize event from the MainWindow::eventFilter function.
You probably don't want to change the pixmap size in the scene, but rather adjust the view matrix so that your QGraphicsPixmapItem fits perfectly inside the view, with QGraphicsView::fitInView.
For example:
/* QGraphicsPixmapItem *pixmapItem; as a MainWindow member */
pixmapItem = scene->addPixmap(newmapImage);
/* Either always disable or enable the scrollbars (see fitInView doc) */
ui->mapView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->mapView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->mapView->installEventFilter(this);
...
bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {
if(obj == ui->mapView && evt->type() == QEvent::Resize) {
ui->mapView->fitInView(pixmapItem, Qt::KeepAspectRatioByExpanding);
}
// Call the base class implementation
return QMainWindow::eventFilter(obj, evt);
}

I believe that what is happening is that Qt only applies layouts and sets widget sizes when the widget is first displayed.
One way to work that is to override QWidget::showEvent(), and put your sizing code in there.
However, one simpler way, that often works in constructors, is to ask the widget for its sizeHint(), rather than for its not-yet-layed-out size.
In your case, that would mean changing two lines of code to:
g_sizeX = ui->mapView->sizeHint().width();
g_sizeY = ui->mapView->sizeHint().height();
If your layout isn't too complicated, and if you haven't overridden the default size policies, this may well fix things for you.

Related

Qt: How to render SVG properly to QLabel without scaling?

I'm currently having a problem where I try to render an SVG file to a QLabel and it does not display correctly:
stop sign SVG displayed wrongly in main window
This is how the SVG actually looks like:
stop sign SVG
I want the SVG to be displayed without scaling (I mean, that's what SVG is all about, isn't it) in it's original square shape. The SVG has a size specified of 256x256, but I don't care about that as long as it fills the layout's cell and it's displayed in its correct aspect ratio.
This is the meaningful part of the header:
class LoginUnavailableWindow : public QWidget {
Q_OBJECT
public:
explicit LoginUnavailableWindow(QWidget *parent = nullptr);
~LoginUnavailableWindow() override;
private:
QHBoxLayout errorLayout {this};
QLabel errorIconLabel {this};
QLabel errorTextLabel {this};
};
And this is the meaningful part of the main cpp:
LoginUnavailableWindow::LoginUnavailableWindow(QWidget *parent) : QWidget(parent) {
/* set overall layout */
errorLayout.addWidget(&errorIconLabel);
errorLayout.addWidget(&errorTextLabel);
errorLayout.setStretch(0, 1);
errorLayout.setStretch(1, 3);
/* apply size constraints */
setMinimumSize(800, 200);
setMaximumSize(800, 200);
errorTextLabel.setAlignment(Qt::AlignCenter);
errorIconLabel.setAlignment(Qt::AlignCenter);
/* set text */
QFont font = errorTextLabel.font();
font.setPointSize(48);
errorTextLabel.setFont(font);
errorTextLabel.setText("Login is currently\nunavailable");
/* render SVG */
QSvgRenderer renderer(ERROR_SVG_PATH);
QPixmap pm(errorIconLabel.size());
pm.fill(QColorConstants::Transparent);
QPainter painter(&pm);
renderer.render(&painter, pm.rect());
errorIconLabel.setPixmap(pm);
}
The actual SVG rendering code was taken from here. One of the problems that I can see is that errorIconLabel.size() is returning 100x30 which I find very confusing. How do I get the actual size of the layouts cell so that I can calculate at which resolution to render the SVG?
A lot of answers I found would use setScaledContents() which does cause the pixmap to be displayed at a more reasonable size, but then it's all blurry/pixelated since the SVG is still rendered at the wrong resolution. I would like to achieve this without introducing scaling artifacts.
Okay, I was able to solve this on my own. I've realized that the QLabel would only have the size 100x30 returned in the constructor. After the constructor finishes, a resizeEvent is issued directly and inside this resizeEvent the size of that label is returned correctly.
So my solution in the end was to implement void resizeEvent(QResizeEvent *) override in LoginUnavailableWindow and to always redraw the SVG when the size would change. In my case I force the window size fixed, but if someone else would do this, it would also solve display problems when the user would resize the window.

Qt QChart title background

Does somebody know how to make QChart look like on the image below?
I have created bar QBarChart and set its background color and color of the bar and removed axis numbers, but I don't know how to set title of the chart to look like this.
How to make the background of the title to have different color and to take same width as the QChart?
I did as Spinkoo suggested.
Created QLabel in MainWindow constructor and created function which is used to position QLabel over QChart. That function must be called after the constructor of the MainWindow, because then all of the sizes and positions of the widgets and layouts are known. That function is called after the constructor of the MainWindow and each time when Resize event happens.
void MainWindow::positionLabel()
{
// ui->widget inside which is QChart
// ui->verticalLayout inside which is ui->widget
QPoint pos = ui->widget->pos() + ui->verticalLayout->geometry().topLeft();
// m_title pointer to QLabel which is created inside constructor
m_title->setGeometry(pos.x() + 10, pos.y() + 20, ui->widget->width() - 20, CHART_TITLE_SIZE * 2.2);
this->repaint();
return;
}
This is pretty much a workaround solution, probably there should be a way to create custom QChart class which will have the look as the chart from the question. So, if someone knows how to do that, I'd appreciate sharing.

Overlay multiple widgets

I have one QWidget which contains a multiple sliders. All sliders resized to main QWidget size. As result all sliders share same draw rectangle. For sliders I overload paintEvent method, so it draw only required stuff. Here is an example code:
class MySlider : public QSlider
{
void paintEvent(QPaintEvent *event) {
...
}
}
class MyWidget : public QWidget
{
MyWidget() : QWidget() {
slider1 = new MySlider(this);
slider2 = new MySlider(this);
slider1->resize(rect().width(), rect().height());
slider2->resize(rect().width(), rect().height());
}
MySlider * slider1;
MySlider * slider2;
}
adsf
Groove is not seen with this solution (because we don't call QSlider::paintEvent), but it still exist. For this widget it is possible to use only the last created slider (slider2). The rest are visible, but they are not available.
Is it possible to overlay multiple widgets on each other and still be able to access all of them with mouse event?
Overlapping widgets isn't a good idea, expect only one is visible at the same time. What is the purpose for that overlapping?
You can set QWidget::setAttribute(Qt::WA_TransparentForMouseEvents) to not generate any mouse events for that particular widget so that only one slider will get that events. Then you are able to redirect that messages to your other sliders.

Qt: organizing my layout and QSizePolicy

The main window of my Qt/C++ program looks like this:
As you can see on the picture, the window consists essentially of:
a menu on the left
two "canvases"
What I want is straightforward to describe: I want that under resizing of the window, both canvases take as much space as possible, but still remain squares (width = height). I've been trying to achieve that unsuccessfully.
Let me quickly describe the objects:
The window is a class Window that I created deriving QWidget. It has a QGridLayout for a layout.
The window's layout has three widgets: the left menu LeftMenu *menu, and the canvases Canvas *leftCanvas, *rightCanvas. Both LeftMenu and Canvas are custom classes deriving QWidget.
(NB: the left menu actually consists of 3 different widgets (submenus), and the window also has a status bar and a top menu, but I don't think it matters for my question.)
I have been "playing" (not having fun the least bit) with QSizePolicy's etc to try to get the Canvases' sizes to behave like I want (be as large as possible inside the window, but keep height/width ratio = 1), unsuccessfully. Let me describe my latest attempt in case that is useful for you (if you already know a solution to my problem, you don't have to keep reading):
I overrode the methods heightForWidth(), sizeHint() and minimumSizeHint() for Canvas like so:
class Canvas : public QWidget
{
Q_OBJECT
friend class Window;
public:
explicit Canvas(Window* window);
...
private:
void resizeEvent(QResizeEvent *resizeEvent) override;
int heightForWidth(int width) const override {return width;}
QSize sizeHint() const override
{
int size = std::min(width(), height());
return QSize(size, size);
}
QSize minimumSizeHint() const override {return QSize(200,200);}
...
};
And the constructor of my class Window looks like (a bit simplified):
Window::Window(ActionHandler *handler)
{
leftMenu = new LeftMenu(this);
leftMenu->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
leftCanvas = new Canvas(this);
rightCanvas = new Canvas(this);
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Maximum);
policy.setHeightForWidth(true);
leftCanvas->setSizePolicy(policy);
rightCanvas->setSizePolicy(policy);
layout = new QGridLayout;
layout->setColumnMinimumWidth(0, menuWidth());
layout->addWidget(leftMenu, 0, 0);
layout->addWidget(leftCanvas, 0, 1);
layout->addWidget(rightCanvas, 0, 2);
setLayout(layout);
}
My idea was that as long as the width of the canvases is the limiting factor, the sizePolicy of the canvases should be (QSizePolicy::Expanding, QSizePolicy::Maximum). And as soon as the height of the canvases becomes the limiting factor, I would change the sizePolicy of the canvases (probably in Canvas::resizeEvent()) to the opposite: (QSizePolicy::Maximum, QSizePolicy::Expanding). Does that sound too complicated?
Anyway, it already fails, and I don't understand why. If I shrink the window horizontally it gives me this:
So, the height of the canvases does not shrink. I do not understand this behavior. In the Qt documentation (http://doc.qt.io/qt-4.8/qsizepolicy.html#Policy-enum), I read:
QSizePolicy::Maximum The sizeHint() is a maximum. The widget can be shrunk any amount without detriment if other widgets need the space (e.g. a separator line). It cannot be larger than the size provided by sizeHint().
The behavior of my canvases here seems to contradict this: their height is larger than the height provided by their sizeHint(). (I did make sure by std::couting "live": the canvas sizeHint, its height, its sizePolicy and its hasHeightForWidth parameters).

PyQt: how to handle auto-resize of widgets when their content changes

I am having some issues with the size of qt4 widgets when their content changes.
I will illustrate my problems with two simple scenarios:
Scenario 1:
I have a QLineEdit widget. Sometimes, when I'm changing its content using QLineEdit.setText(), the one-line string doesn't fit into the widget at its current size anymore. I must select the widget and use the arrow keys to scroll the string in both directions in order to see it all.
Scenario 2:
I have a QTextEdit widget. Sometimes, when I'm changing its content using QTextEdit.setHtml(), the rendered HTML content doesn't fit into the widget at its current size anymore. The widget starts displaying horizontal and/or vertical scroll bars and I can use them to scroll the HTML content.
What I would want in such scenarios is to have some logic that decides if after a content change, the new content won't fit anymore into the widget and automatically increase the widget size so everything would fit.
How are these scenarios handled?
I'm using PyQt4.
Edit: after reading both the comment and the first answer (which mentions typing content into the widget), I went over the question one more time. I was unpleasantly surprised to find out a horrible typo. I meant QTextBrowser when I wrote QTextEdit, my apologies for misleading you. That is: I have a widget which renders HTML code that I'm changing and I would want the widget to grow enough to display everything without having scrollbars.
As for QLineEdit instead of QLabel - I went for QLineEdit since I've noticed I can't select text from a QLabel with the mouse for copying it. With QLineEdit it is possible.
I'm answering in C++ here, since that's what I'm most familiar with, and your problem isn't specific to PyQt.
Normally, you just need to call QWidget::updateGeometry() when the sizeHint() may have changed, just like you need to call QWidget::update() when the contents may have changed.
Your problem, however, is that the sizeHint() doesn't change when text is added to QLineEdit and QTextEdit. For a reason: People don't expect their dialogs to grow-as-they-type :)
That said, if you really want grow-as-you-type behaviour in those widgets you need to inherit from them and reimplement sizeHint() and minimumSizeHint() to return the larger size, and potentially setText(), append() etc. to call updateGeometry() so the sizehint change is noticed.
The sizehint calculation won't be entirely trivial, and will be way easier for QLineEdit than for QTextEdit (which is secretly a QAbstractScrollArea), but you can look at the sizeHint() and minimumSizeHint() implementations for inspiration (also the one for QComboBox, which has a mode to do exactly what you want: QComboBox::AdjustToContents.
EDIT: Your two usecases (QTextBrowser w/o scrollbars and QLineEdit instead of QLabel just for selecting the text in there) can be solved by using a QLabel and a recent enough Qt. QLabel has gained both link-clicking notification and so-called "text-interaction flags" (one of which is TextSelectableByMouse) in Qt 4.2. The only difference that I was able to make out is that loading new content isn't automatic, there's no history, and there's no micro focus hinting (ie. tabbing from link to link) in QLabel.
For the QTextBrowser case you should be able to get the size of the document using
QTextBrowser::document()->size();
after setting the html, and then resizing it the QTextBrowser afterwards.
i achieve a similar effect by using the following C++ class:
textedit.h
#ifndef TEXTEDIT_H
#define TEXTEDIT_H
#include <QTextEdit>
class TextEdit : public QTextEdit
{
Q_DISABLE_COPY( TextEdit )
public:
TextEdit( QWidget* parent = NULL );
TextEdit( const QString& text, QWidget* parent = NULL );
virtual ~TextEdit();
void fitToDocument( Qt::Orientations orientations );
virtual QSize sizeHint() const;
private:
int fittedHeight_;
Qt::Orientations fittedOrientations_;
int fittedWidth_;
};
#include "textedit-inl.h"
#endif // TEXTEDIT_H
textedit-inl.h
#ifndef TEXTEDITINL_H
#define TEXTEDITINL_H
#include "textedit.h"
inline TextEdit::TextEdit( QWidget* parent ) :
QTextEdit( parent ), fittedOrientations_( 0 )
{ }
inline TextEdit::TextEdit( const QString& text, QWidget* parent ) :
QTextEdit( text, parent ), fittedOrientations_( 0 )
{ }
inline TextEdit::~TextEdit()
{ }
inline QSize TextEdit::sizeHint() const
{
QSize sizeHint = QTextEdit::sizeHint();
if( fittedOrientations_ & Qt::Horizontal )
sizeHint.setWidth( fittedWidth_ );
if( fittedOrientations_ & Qt::Vertical )
sizeHint.setHeight( fittedHeight_ );
return sizeHint;
}
#endif // TEXTEDITINL_H
textedit.cpp
#include "textedit.h"
void TextEdit::fitToDocument( Qt::Orientations orientations )
{
QSize documentSize( document()->size().toSize() );
QSizePolicy sizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
if( orientations & Qt::Horizontal ) {
fittedWidth_ = documentSize.width() + (width() - viewport()->width());
sizePolicy.setHorizontalPolicy( QSizePolicy::Fixed );
}
if( orientations & Qt::Vertical ) {
fittedHeight_ = documentSize.height() + (width() - viewport()->width());
sizePolicy.setVerticalPolicy( QSizePolicy::Fixed );
}
fittedOrientations_ = orientations;
setSizePolicy( sizePolicy );
updateGeometry();
}
for example, calling TextEdit::fitToDocument( Qt::Horizontal ) will set the widget's width to a fixed width exactly large enough to fit the document and its surroundings (e.g. a vertical scrollbar, if there is one). if your goal is to have this happen whenever the contents change, connect the QTextEdit::textChanged() signal to a slot which calls TextEdit::fitToDocument().
as for your issue with QLabel, the solution is simple: call QLabel::setTextInteractionFlags( Qt::LinksAccessibleByMouse | Qt::TextSelectableByMouse ).
Maybe take a look at Python QT Automatic Widget Resizer. It's written in python though but it may give you some ideas on how to go about doing what you need.
Ok implement sizeHint() method. And every time your content change size call updateGeometry()
When content change without changing size use update(). (updateGeometry() automatically call update()).