Qt Scrollbar doesn't appear on custom widget - c++

I tried to modify this example code (http://doc.qt.digia.com/4.6/widgets-scribble.html) so the image can be scrolled when the size is too big.
Basically this is a code to create a mspaint-like program (my code is almost identical to the tutorial, except i changed the class name):
class ImageDisplay : public QWidget
{
Q_OBJECT
public:
ImageDisplay(QWidget *parent = 0);
~ImageDisplay();
void LoadImage(QString img);
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent *event);
private:
bool modified;
bool scribbling;
int myPenWidth;
QColor myPenColor;
QImage svgImage;
QPoint lastPoint;
private:
void ResizeImage(QImage *image, const QSize &newSize);
void DrawLineTo(const QPoint &endPoint);
};
Code attaching custom widget to scroll area
...
scrollArea = new QScrollArea(centralWidget);
scrollArea->setWidgetResizable(true);
myImageDisplay = new ImageDisplay();
myImageDisplay->setGeometry(QRect(0, 0, 780, 559));
QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
myImageDisplay->setSizePolicy(sizePolicy);
myImageDisplay->setMinimumSize(QSize(0, 0));
scrollArea->setWidget(myImageDisplay);
gridLayout->addWidget(scrollArea, 0, 0, 1, 1);
...
I put my custom widget inside scrollarea, but the scrollbar never appears. When i debug it, the size of the widget cannot be larger than the srollarea.
I've read somewhere the size of a widget can;t be expanded larger than the container size, or sort of, i don't quite understand.
i found a inelegant solution which seems to be "hack" where i set the minimum size of the widget to be the size of the image, i put the code inside load image
void ImageDisplay::LoadImage(QString img)
{
QImage loadedImage;
loadedImage.load(img);
QSize newSize = loadedImage.size().expandedTo(size());
ResizeImage(&loadedImage, newSize);
svgImage = loadedImage;
modified = false;
update();
// "hack" code
this->setMinimumHeight(newSize.height());
this->setMinimumWidth(newSize.width());
}

This isn't really a "hack." This is how it is supposed to be. If you want the widget to never be smaller than the image it displays, then that's what you do; you tell it that it should never make itself smaller than the image. But in your code, you've specified that it can be smaller than its contents:
QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
What this means is that the widget will prefer to have a size that fits inside the visible area of its container, even if it means that its contents are getting cut off.
The more elegant way to do this is though, is using this instead:
QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
This will tell the layout to never try and resize the widget at all. You set a size when you load the image, and it will keep that size unless you manually resize it yourself.
With that being said, maybe you should be looking at using a QLabel instead of a custom QWidget class. QLabel is already set up to display images, and I believe it knows on its own which size it should be.

Related

Resizing Qlabel image upon resizing window using resizeEvent

I want to use ResizeEvent to receive the size of the window and set it to the size of a QLabel, in order to make the image stretched and adapted to the window's dimensions.by a left click of the mouse I can resize my window and the image takes a new size.
You must have the following considerations:
It is not necessary to store QPixmap by means of a pointer since when passing it to the QLabel it is copied by value.
Therefore, if you change the size of the QPixmap p will not be reflected in the QLabel since the QPixmap that has the QLabel is a copy of the one you established at the beginning.
It is not necessary to use a layout for this task since it will create an infinite loop since this also intervenes in the resizeEvent of the widget where it has been established so if you change the size of the QLabel, it will change the size of the QWidget, and this again I will try to change the QLabel, and so on.
It is not advisable to modify the original QPixmap since changing its size modifies the pixels and you will get an unexpected effect.
Using the above we obtain the following code:
*.h
#ifndef TESTSIZE_H
#define TESTSIZE_H
#include <QWidget>
class QLabel;
class testsize : public QWidget
{
Q_OBJECT
public:
explicit testsize(QWidget *parent = 0);
~testsize();
private:
QLabel *image;
QPixmap original_px;
protected:
void resizeEvent(QResizeEvent *event);
};
#endif // TESTSIZE_H
*.cpp
#include "testsize.h"
#include <QLabel>
#include <QResizeEvent>
testsize::testsize(QWidget *parent) :
QWidget(parent)
{
image = new QLabel(this);
original_px = QPixmap(":/wallpaper.jpg");
image->setPixmap(original_px);
resize(640, 480);
}
testsize::~testsize()
{
}
void testsize::resizeEvent(QResizeEvent *event)
{
QPixmap px = original_px.scaled(event->size());
image->setPixmap(px);
image->resize(event->size());
QWidget::resizeEvent(event);
}
You can find the complete example in the following link.

Creating QIcon from SVG contents in memory

How can we create the QIcon object having SVG icon contents in the memory buffer?
P.S. Initially wanted to create QSvgIconEngine, but it is hidden on the plugins layer, and I can not create it explicitly. How can I do it with loading from plugin (taking in account, that plugin is loaded)?
After digging for a while here and there, and digging in how QIcon itself uses an svg file to load an icon, here's what I learned:
QIcon, when called with an svg file (or any other image type for that matter), it calles addFile() subsequently, which only uses the extension of the file (called QFileInfo::suffix in Qt) to determine the method that converts the image file to an icon.
The method (semantically speaking) is determined by a QIconEngine instance
QIconEngine classes for every image type are not apparently simply accessible by us (the Qt developers); apparently there's a plugin system to be used and it's not available at compile-time (not simply at least)
On the other hand; How does QIcon work? When an icon is requested from QIcon, it uses the information passed to it to determine what engine to use, and creates an instance of the engine. Then, every time the icon needs to draw something, it asks the engine to draw an icon with some size given to it. The size is used on the function QIconEngine::pixmap(), which creates a pixmap with the required size, then the method QIconEngine::paint() is used to paint on that pixmap.
So, given this information, the solution is simply to write an Icon Engine that QIcon will use in order to generate the icon depending on the size passed to it. Here's how to do that:
So here's the header file SvgIconEngine.h
#ifndef SVGICONENGINE_H
#define SVGICONENGINE_H
#include <QIconEngine>
#include <QSvgRenderer>
class SVGIconEngine : public QIconEngine {
QByteArray data;
public:
explicit SVGIconEngine(const std::string &iconBuffer);
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode,
QIcon::State state) override;
QIconEngine *clone() const override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode,
QIcon::State state) override;
signals:
public slots:
};
#endif // SVGICONENGINE_H
And here's the implementation, SvgIconEngine.cpp
#include "SvgIconEngine.h"
#include <QPainter>
SVGIconEngine::SVGIconEngine(const std::string &iconBuffer) {
data = QByteArray::fromStdString(iconBuffer);
}
void SVGIconEngine::paint(QPainter *painter, const QRect &rect,
QIcon::Mode mode, QIcon::State state) {
QSvgRenderer renderer(data);
renderer.render(painter, rect);
}
QIconEngine *SVGIconEngine::clone() const { return new SVGIconEngine(*this); }
QPixmap SVGIconEngine::pixmap(const QSize &size, QIcon::Mode mode,
QIcon::State state) {
// This function is necessary to create an EMPTY pixmap. It's called always
// before paint()
QImage img(size, QImage::Format_ARGB32);
img.fill(qRgba(0, 0, 0, 0));
QPixmap pix = QPixmap::fromImage(img, Qt::NoFormatConversion);
{
QPainter painter(&pix);
QRect r(QPoint(0.0, 0.0), size);
this->paint(&painter, r, mode, state);
}
return pix;
}
Notice: You must override clone(), because it's an abstract method, and you must override pixmap(), because without that, you'll not have an empty pixmap to paint the svg on.
To use this, simply do this:
std::string iconSvgData = GetTheSvgPlainData();
QIcon theIcon(new SVGIconEngine(iconSvgData));
//Use the icon!
Notice that QIcon takes ownership of the engine object. It'll destroy it when it's destroyed.
Have fun!
I don't have c++ at hand, but it should be easy to convert:
QtGui.QIcon(
QtGui.QPixmap.fromImage(
QtGui.QImage.fromData(
b'<svg version="1.1" viewBox="0 0 32 32"'
b' xmlns="http://www.w3.org/2000/svg">'
b'<circle cx="16" cy="16" r="4.54237"/></svg>')))
This code will create transparent 32 by 32 pixels icon with a black circle.
How can we create the QIcon object having SVG icon contents in the
memory buffer?
For that all the required functionality is delivered via external interface of QSvgRenderer class. To construct that type of renderer we need to use either of:
QSvgRenderer(const QByteArray &contents, QObject *parent = Q_NULLPTR);
QSvgRenderer(QXmlStreamReader *contents, QObject *parent = Q_NULLPTR);
or we can just load the content with:
bool QSvgRenderer::load(const QByteArray &contents)
bool QSvgRenderer::load(QXmlStreamReader *contents)
And to create the actual icon:
QIcon svg2Icon(const QByteArray& svgContent, QPainter::CompositionMode mode =
QPainter::CompositionMode_SourceOver)
{
return QIcon(util::svg2Pixmap(svgContent, QSize(128, 128), mode));
}
QPixmap svg2Pixmap(const QByteArray& svgContent,
const QSize& size,
QPainter::CompositionMode mode)
{
QSvgRenderer rr(svgContent);
QImage image(size.width(), size.height(), QImage::Format_ARGB32);
QPainter painter(&image);
painter.setCompositionMode(mode);
image.fill(Qt::transparent);
rr.render(&painter);
return QPixmap::fromImage(image);
}
We can also use other composition modes as well, say,
QPainter::RasterOp_NotSourceOrDestination to invert the icon color.

How to draw when holding down the mouse button?

I am trying to create a simple painting application - it is just supposed to draw when you click and drag a cursor (just like Paint). I know I have to use QPainter but how can I handle it? How to do it? Any help would be really appreciated. I tried lurking through internet but didn't find too much info (I daw drawing lines etc. by code that you launch an app and it is here but I can not find an example of drawing something by user).
Here's Jeremy's answer in code instead of prose:
// https://github.com/KubaO/stackoverflown/tree/master/questions/simplepaint-39358392
#include <QtWidgets>
// Make a subclass of the QWidget class, so that you can override some of its
// virtual methods
class PaintWidget : public QWidget {
// Create a QPixmap object that you will use to store the bitmap
// that the user will draw [on].
QPixmap m_pixmap;
QPoint m_lastPos;
// Override the paintEvent(QPaintEvent *) [...]
void paintEvent(QPaintEvent *) override {
QPainter painter{this};
painter.drawPixmap(0, 0, m_pixmap);
}
void resizeEvent(QResizeEvent *) override {
// [...] size the QPixmap to be at least as big as the maximum size of the window
// We'll also never let it shrink so as not to lose the already drawn image.
auto newRect = m_pixmap.rect().united(rect());
if (newRect == m_pixmap.rect()) return;
QPixmap newPixmap{newRect.size()};
QPainter painter{&newPixmap};
painter.fillRect(newPixmap.rect(), Qt::white);
painter.drawPixmap(0, 0, m_pixmap);
m_pixmap = newPixmap;
}
// Override the mousePressEvent(QMouseEvent *) [...]
void mousePressEvent(QMouseEvent * ev) override {
m_lastPos = ev->pos();
draw(ev->pos());
}
// Override the mouseMoveEvent(QMouseEvent *) [...]
void mouseMoveEvent(QMouseEvent * ev) override {
draw(ev->pos());
}
void draw(const QPoint & pos) {
QPainter painter{&m_pixmap};
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen({Qt::blue, 2.0});
painter.drawLine(m_lastPos, pos);
m_lastPos = pos;
update();
}
public:
using QWidget::QWidget;
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
// Create an object of your subclass and call show()
PaintWidget ui;
ui.show();
return app.exec();
}
It is not necessary to override the mouseReleaseEvent. In a widget, the default behavior is to track the mouse movement only when a mouse button is depressed. The mouseMoveEvent won't be called unless a button is depressed.
It's a pretty broad question, but here are the basics:
Make a subclass of the QWidget class, so that you can override some of its virtual methods later on.
Create an object of your subclass and call show() on it (just before calling QApplication::exec()). This object will appear on screen as a very simple window, and it will serve as your user's painting-surface.
Create a QPixmap object that you will use to store the bitmap that the user will draw. Make sure to size the QPixmap to be at least as big as the maximum size of the window that you want to support. Call fill() on the QPixmap to fill it with your favorite background color.
Override the mousePressEvent(QMouseEvent *) method of your object to set a boolean is_mouse_down flag, and also to record the current position of the mouse pointer within the window (by calling pos() on the QMouseEvent object that gets passed in to the mousePressEvent() call and storing that into a member variable of your object).
Override the mouseMoveEvent(QMouseEvent *) method so that if is_mouse_down_is set to true, it creates a QPainter object on the stack -- pass a pointer to the QPixmap to the QPainter object's constructor so that the QPainter will draw into your QPixmap object. Then call drawLine() on the QPainter object to draw a line from the previous mouse position to your current one. Finally, call update() to tell Qt to call paintEvent() for you ASAP.
Override the mouseReleaseEvent(QMouseEvent *) method to set is_mouse_down to false again
Override the paintEvent(QPaintEvent *) method to create a QPainter object on the stack -- pass a pointer to (this) to the QPainter object's constructor, so that it will paint onto the QWidget directly. Then call drawPixmap() on the QPainter object so that it will draw your QPixmap object onto the widget's visible surface.
If you'd like to see a pre-written example, check out the Scribble application included with Qt, in $QTDIR/examples/widgets/widgets/scribble.

Can't use QPainter in paintEvent of custom QWidget (Qt5)

The error in question is as follows:
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
QPainter::setFont: Painter not active
After looking at a ton of online forum posts that all came down to people making the same mistake of trying to paint on their widgets outside of paintEvent(), I've been unable to have any luck. I tried drawing directly on this custom QWidget subclass, I've tried making a child QWidget and drawing on that. Can someone please show me what thing I'm (probably obviously to someone else) doing wrong?
Thanks in advance.
Header:
#ifndef TEXTDISPLAY_H
#define TEXTDISPLAY_H
#include <QWidget>
class TextDisplay : public QWidget
{
Q_OBJECT
public:
TextDisplay(QString text, QString fontFamily = "Helvetica", int fontSize = 20,
int fontColor = Qt::black, QWidget* parent = 0);
protected:
void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent *e) Q_DECL_OVERRIDE;
private:
QString text;
QString fontFamily;
int fontSize;
int fontColor;
};
#endif // TEXTDISPLAY_H
Cpp:
#include "textdisplay.h"
#include <QPainter>
TextDisplay::TextDisplay(QString text, QString fontFamily, int fontSize,
int fontColor, QWidget* parent)
: QWidget(parent), text(text), fontFamily(fontFamily),
fontSize(fontSize), fontColor(fontColor)
{
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
}
void TextDisplay::resizeEvent(QResizeEvent*) {
paintEvent(NULL);
}
void TextDisplay::paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setPen(fontColor);
painter.setFont(QFont(fontFamily, fontSize));
QRect rect(QPoint(0, 0), this->size());
QRect bound;
QTextOption options;
options.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
painter.drawText(rect, Qt::TextWordWrap | Qt::TextWrapAnywhere, text, &bound);
this->setMinimumHeight(bound.size().height());
}
Edit with solution:
Thanks to both responders - I needed to do two things to get it working:
a) Get rid of paintEvent(NULL). I got rid of the resizeEvent override as well, it was unnecessary as suggested.
b) Set a minimum size for the widget. Without this, the paintEvent was never called by Qt.
If you want to schedule a redraw for your widget, just call update().
If you need an immediate repaint (which you almost never need) you can call repaint() instead.
Calling paintEvent() directly will not work -- Qt needs to prepare the backing store to handle the painting, so you can't bypass the update mechanism. Instead, call the methods above, which will result in a call to paintEvent() (if the widget is visibile, not occluded, etc.etc.etc.).
Note also that you should not need to reimplement resizeEvent() just to update your widget. That should already be done for you by Qt...

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).