Draw a line in column 80 of QPlainTextEdit - c++

I'm writing a text editor and using Qt for the GUI. I'm a noob in Qt and I'm having trouble to do this.
I need to draw a line in the column 80 of the QPlainTextEdit but I really don't know how. I'm using QPainter but I just can't get it right, any help?

Here's how I'd do it. It's admittedly not entirely trivial. The inputs to determining the 80th column position are:
80 x the average character width in floating point. Using the integer value will magnify the roundoff error by a factor of 80. Thus use QFontMetricsF.
The offset due to scrollbars comes from contentOffset(). It'd be bad to use horizontalScrollbar()->value(). The latter currently works, but relies on the implementation-specific detail. QPlainTextEdit happens to map scrollbar values to pixels -- who knows if it won't change tomorrow. It's not documented, thus falls under unspecified behavior.
The QTextDocument implements its own margin, available via documentMargin().
Another pitfall: you must paint on the viewport() in any class that derives from QAbstractScrollArea -- and QPlainTextEdit does so. If you don't, your paintEvent becomes a no-op. It's documented, but you must be clever enough to actually look into documentation. I'd consider it a bad corner case of an API that does something unexpected. In every other paintEvent, you simply create QPainter p or QPainter p(this) and it works.
Note: this is tested, compileable code.
//main.cpp
#include <cmath>
#include <QtWidgets>
class Edit : public QPlainTextEdit
{
public:
Edit(QWidget * parent = 0) : QPlainTextEdit(parent) {}
protected:
void paintEvent(QPaintEvent * ev)
{
QPlainTextEdit::paintEvent(ev);
const QRect rect = ev->rect();
const QFont font = currentCharFormat().font();
int x80 = round(QFontMetricsF(font).averageCharWidth() * 80.0)
+ contentOffset().x()
+ document()->documentMargin();
QPainter p(viewport());
p.setPen(QPen("gray"));
p.drawLine(x80, rect.top(), x80, rect.bottom());
qDebug() << x80 << contentOffset() << document()->documentMargin() << font << endl;
}
};
static QString filler()
{
QString str;
for (char c = '0'; c < '9'; ++ c) {
str.append(QString(10, c));
}
return str;
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
Edit ed;
QTextCharFormat fmt = ed.currentCharFormat();
fmt.setFontFamily("courier");
fmt.setFontFixedPitch(true);
ed.setCurrentCharFormat(fmt);
ed.setLineWrapMode(QPlainTextEdit::NoWrap);
qDebug() << fmt.font() << endl;
ed.setPlainText(filler());
ed.show();
app.exec();
}
#include "main.moc"

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();
}

Qt5 C++ resize font based on label width

I have written what I think is a pretty good font resize algorithm that I use on labels in a QGridLayout. The algorithm works rather well on its own. However, the first time I run the algorithm, the layout hasn't been painted yet, and as such, the grid widgets which own the labels haven't been sized to the layout, which means my width() call is wrong, though I have assigned the label text prior to the resize (which doesn't matter). My question is, what is the best way to know the bounding rectangles have been created to fit the layout so my calculation is on the actual label width?
Note, this is Qt5.7, so I know that fm.width() is obsolete, but that's what's available in Raspbian.
My app is reading Sonos metadata and showing it on a 7" RPi display. I'm resizing the metadata that is too long to fit in the layout grids. So, I have a QStackedLayout with a set of layouts to show (clock when Sonos isn't playing, metadata when it is), and I frequently poll the a local Sonos server to find out if there is metadata to show. This works well. What doesn't happen is that on the first time the Sonos metadata layout is shown, the QGridLayout hasn't actually been laid out yet (I believe), so the labels are too big. At some point after the first time I fill in the metadata, the grid layout gets "shown" and the labels are then the correct size. The problem is, by then, it's all done setting metadata and the labels look funny in some cases.
int pointSize = FontSize::Default;
for (auto label : m_labels) {
QFont f = label->font();
if (label->accessibleName() == "title")
pointSize = FontSize::Title;
QFontMetrics fm(f);
if (fm.width(label->text()) > label->width()) {
float factor = (float)label->width() / (float)fm.width(label->text());
if (factor <= .6) {
factor = .6;
}
f.setPointSizeF((f.pointSize() * factor) * .9);
qDebug() << __FUNCTION__ << ": label width:" << label->width();
qDebug() << __FUNCTION__ << ": font width:" << fm.width(label->text());
qDebug() << __FUNCTION__ << ": Calculated font scaling factor to be" << (float)(factor * .9);
qDebug() << __FUNCTION__ << ": Reset font size for text\"" << label->text() << "\" to" << f.pointSize();
}
else
f.setPointSize(pointSize);
label->setFont(f);
}
On an 800x480 display, this results in the first event label width being wrong
calculateLabelFontSize : label width: 640
calculateLabelFontSize : font width: 2051
The label width the next time it's called would end up being correct with a width of 584. However, because I don't always call it a second time, and because sometimes, the Sonos metadata hiccups and causes the display to revert, it may always be wrong, or it may just be right the first time no matter what.
I've considered trying to overload my gui app paintEvent(QPaintEvent *event), but the QPaintEvent class doesn't give me a way to determine which widget called for the event, so I can't just ignore the event until I want it. At least, not that I have determined, so if that's possible, please let me know.
I've tried overloading showEvent, but that doesn't run except when the frame is first shown, so no help there.
I've considered subclassing QGridLayout to simply overload the paintEvent() and use that, but I do have an issue with that solution. There is a progress bar in this layout, which means that the paintEvent is going to fire every half second as I get metadata from the Sonos server. Since I don't know if the paintEvent is firing for the time update, or for the text being set, I get a lot of resize attempts.
I've also simply run the algorithm a second time on the next event, but it's a really weird looking graphical hiccup when the fonts realign, and I don't like it.
Last up, I may just subclass QLabel, simply to overload paintEvent, and use that custom label just for the labels that would be resizable. However, is the label in the layout painted before or after the container it lives in inside the layout? I'll be testing this today I think, but I'm not convinced it would work.
I figure this has a much easier solution though. I just can't figure it out.
I borrowed code from https://github.com/jonaias/DynamicFontSizeWidgets which provided the resize algorithm. It's nice, but iterative. I have been tinkering with a math based solution to calculate instead of just trying to fit by resizing until it works. However, that solution still doesn't work well enough to use. This isn't slow, and does what is implied. It resizes the font until it fits.
This all is done in paintEvent() because using resizeEvent() may result in either an infinite resize loop or the layout may resize itself and suddenly everything looks shifted/squished/stretched. Doing it in paintEvent means it's only called once every time you update the text, but not otherwise.
I may come back to this to try to find a math based solution. I still think this isn't the correct way, but I found something that worked well enough for now.
NewLabel.h
#ifndef NEWLABEL_H
#define NEWLABEL_H
#include <QtCore/QtCore>
#include <QtWidgets/QtWidgets>
#include <cmath>
class NewLabel : public QLabel
{
Q_OBJECT
public:
explicit NewLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
explicit NewLabel(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
virtual ~NewLabel() {}
void setDefaultPointSize(int p)
{
m_defaultPointSize = static_cast<float>(p);
}
protected:
void paintEvent(QPaintEvent *e) override;
private:
float getWidgetMaximumFontSize(QWidget *widget, QString text);
float m_defaultPointSize;
};
#endif
NewLabel.cpp
#define FONT_PRECISION (0.5)
NewLabel::NewLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) : QLabel(text, parent, f)
{
m_defaultPointSize = 12;
}
NewLabel::NewLabel(QWidget *parent, Qt::WindowFlags f) :
QLabel(parent, f)
{
m_defaultPointSize = 12;
}
void NewLabel::paintEvent(QPaintEvent *e)
{
QFont newFont = font();
float fontSize = getWidgetMaximumFontSize(this, this->text());
if (fontSize < m_defaultPointSize) {
newFont.setPointSizeF(fontSize);
setFont(newFont);
}
QLabel::paintEvent(e);
}
float NewLabel::getWidgetMaximumFontSize(QWidget *widget, QString text)
{
QFont font = widget->font();
const QRect widgetRect = widget->contentsRect();
const float widgetWidth = widgetRect.width();
const float widgetHeight = widgetRect.height();
QRectF newFontSizeRect;
float currentSize = font.pointSizeF();
float step = currentSize/2.0;
/* If too small, increase step */
if (step<=FONT_PRECISION){
step = FONT_PRECISION*4.0;
}
float lastTestedSize = currentSize;
float currentHeight = 0;
float currentWidth = 0;
if (text==""){
return currentSize;
}
/* Only stop when step is small enough and new size is smaller than QWidget */
while(step>FONT_PRECISION || (currentHeight > widgetHeight) || (currentWidth > widgetWidth)){
/* Keep last tested value */
lastTestedSize = currentSize;
/* Test label with its font */
font.setPointSizeF(currentSize);
/* Use font metrics to test */
QFontMetricsF fm(font);
/* Check if widget is QLabel */
QLabel *label = qobject_cast<QLabel*>(widget);
if (label) {
newFontSizeRect = fm.boundingRect(widgetRect, (label->wordWrap()?Qt::TextWordWrap:0) | label->alignment(), text);
}
else{
newFontSizeRect = fm.boundingRect(widgetRect, 0, text);
}
currentHeight = newFontSizeRect.height();
currentWidth = newFontSizeRect.width();
/* If new font size is too big, decrease it */
if ((currentHeight > widgetHeight) || (currentWidth > widgetWidth)){
//qDebug() << "-- contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect" << newFontSizeRect << "Tight" << text << currentSize;
currentSize -=step;
/* if step is small enough, keep it constant, so it converge to biggest font size */
if (step>FONT_PRECISION){
step/=2.0;
}
/* Do not allow negative size */
if (currentSize<=0){
break;
}
}
/* If new font size is smaller than maximum possible size, increase it */
else{
//qDebug() << "++ contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect" << newFontSizeRect << "Tight" << text << currentSize;
currentSize +=step;
}
}
return lastTestedSize;
}

Setting minimum width of QPushButton while keeping default minimumSizeHint behavior

In my application, I need all QPushButtons to have a width of at least 150px, but bigger if needed.
To do so, I am using a global stylesheet (which contains many other properties) with this simple constraint :
QPushButton {
min-width: 150px;
}
The thing is, I also want buttons with a text that doesn't fit inside 150px to be unable to shrink to a width below which the whole text wouldn't be displayed.
This is supposed to be the normal behavior for a QPushButton, but the problem is that, as explained in the documentation for minimumSizeHint() :
QLayout will never resize a widget to a size smaller than the minimum size hint unless minimumSize() is set or the size policy is set to QSizePolicy::Ignore. If minimumSize() is set, the minimum size hint will be ignored.
This leads to some cases where buttons with long texts are displayed at the right size at startup, but when shrinking the window, the button gets too small to display all the text.
I have prepared a simple example that shows this behavior :
#include <QWidget>
#include <QApplication>
#include <QHBoxLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget *w = new QWidget();
QLayout* layout = new QHBoxLayout(w);
QPushButton* buttonA = new QPushButton("A", w);
QPushButton* buttonB = new QPushButton("B", w);
buttonB->setStyleSheet("min-width: 150px");
QPushButton* buttonC = new QPushButton("Long long text that doesn't fit in 150px", w);
QPushButton* buttonD = new QPushButton("Very very very long text that doesn't fit in 150px", w);
buttonD->setStyleSheet("min-width: 150px");
layout->addWidget(buttonA);
layout->addWidget(buttonB);
layout->addWidget(buttonC);
layout->addWidget(buttonD);
w->show();
return a.exec();
}
I thought about using dynamic properties to set the minimum width constraint only to buttons that have a base width < 150px, but this doesn't seem to be doable.
Is there a way to do what I want with stylesheets, or do I have to subclass QPushButton and override the minimumSizeHint() method (which I'd like to avoid as I would have to replace a lot of buttons in my app)?
It would be better if you sub-class QPushButton. But there are 2 work around for this problem:
You can strip the long text to specific numbers of characters and use setToolTip(QString) function to show full text when mouse will enter the button.
You can override parent widget resizeEvent and can check the widths and go for approach number 1 if the size is getting really small.
Use Elide Text example to get idea about elide for QPushButton. Not sure if this will work.
I haven't been able to find a way to do this using only stylesheets, but here is the solution I came up with by subclassing QPushButton
MyPushButton.h
#include <QPushButton>
class MyPushButton
: public QPushButton
{
Q_OBJECT
Q_PROPERTY(int minWidth READ minWidth WRITE setMinWidth)
public:
MyPushButton(QWidget* parent = nullptr);
virtual ~MyPushButton();
int minWidth() const;
void setMinWidth(int width);
virtual QSize minimumSizeHint() const override;
private:
int _minWidth;
};
MyPushButton.cpp
#include "MyPushButton.h"
MyPushButton::MyPushButton(QWidget* parent)
: QPushButton(parent)
, _minWidth(0)
{
}
MyPushButton::~MyPushButton()
{
}
int MyPushButton::minWidth() const
{
return _minWidth;
}
void MyPushButton::setMinWidth(int width)
{
_minWidth = width;
}
QSize MyPushButton::minimumSizeHint() const
{
// if the minimum width is less than minWidth, set it to minWidth
// else return the default minimumSizeHint
return QSize(qMax(QPushButton::minimumSizeHint().width(), _minWidth),
QPushButton::minimumSizeHint().height());
}
stylesheet.qss
MyPushButton {
qproperty-minWidth: 150;
}
Buttons with a minimumSizeHint width lower than 150px will be forced to that size, bigger ones will keep the default behavior of QPushButton.

QPainter's setWorldTransform default behavior?

The following code shows the window on the left (see image below). Nevertheless, uncommenting the line marked with /*[identity transform]*/, the window in the right is generated. As reported by qInfo() in the console output, nothing relevant for the coordinate transform seems to change.
Could anybody explain me the reason? I can't find it in the documentation.
class SomeItem : public QGraphicsEllipseItem
{
public:
explicit SomeItem(const QRectF& rect, QGraphicsItem* parent = nullptr) :
QGraphicsEllipseItem(rect,parent){}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QTransform tr;
//painter->setWorldTransform(tr); /*[identity transform]*/
qInfo() << painter->window().left() << painter->window().right()
<< painter->window().top() << painter->window().bottom();
qInfo() << painter->viewport().left() << painter->viewport().right()
<< painter->viewport().top() << painter->viewport().bottom();
qInfo() << painter->matrix().m11() << painter->matrix().m12()
<< painter->matrix().m21() << painter->matrix().m22();
/* <--->*/
QGraphicsEllipseItem::paint(painter,option,widget);
}
};
int main(int argc, char **argv)
{
QApplication app (argc, argv);
QGraphicsScene ms;
ms.setSceneRect(-20,-20,40,40);
SomeItem* si = new SomeItem(QRectF(-10,-10,20,20));
ms.addItem(si);
QGraphicsView view(&ms);
view.show();
return app.exec();
}
Console output (for both cases):
0 197 0 97
0 197 0 97
1 0 0 1
Paint method uses local coordinate system. This means that origin of painter is usually located at top left corner of QGraphicsItem (note this is base class for everything in QGraphicsScene). In case of QGraphicsEllipseItem it must be center of it.
Apparently this is implemented by transforming QPainter used by QGraphicsView widget when void QWidget::paintEvent(QPaintEvent *event) is processed.
Simply each QGraphicsItem in QGraphicsScene painted by QGraphicsView transform painter for its needs.
When you restore identity transformation, you got a painter in state which applies for QGraphicsView paint event. So it is top left corner of it.
You where lucky that nothing got broken since you are painting outside of boundingRect.
Reasoning on Marek's answer, I guess to have found a geometrical explaination. My fault was to inspect painter->matrix() rather than painter->transform(). Indeed, Qmatrix does not manage translation, while painter->transform().m31() and painter->transform().m32() do.
Substituting the following lines
qInfo() << painter->transform().m31() << painter->transform().m32();
in place of /*<--->*/ provides console outputs for the two cases, respectively,
99 49
and
0 0

How to automatically increase/decrease text size in label in Qt

I have a Qt application where I have a textedit and a label. When a user presses the button, the textedit text should be displayed on label. For the label I have set few properties like word wrap is enabled and horizontal and vertically it is aligned center. Below is the screenshot :
Now I have to automatically adjust the size of the text in label so that if someone enters a large string, then it should fit inside the label, that means size of text should decrease. And if the text string is small, then size should increase automatically to fill up the complete label. Currently if I am typing it the large string, it looks like something:
As you can see, in the above image, text is moving out of the label. It should remain inside the label.
How to detect in application if the text is moving out of the label height & width. Then how to reduce the text size. I want the size to automatically increase if the string is small and decrease it string is large to fill up the complete label. Is there any class or something provided in QT. Any help or example please. Thanks.
EDIT: With the below code I am able to reduce the size of text to fit inside the label width but not able to make the text multi line.
QString string = ui->textEdit->toPlainText(); //Getting data from textEdit
ui->label->setAlignment(Qt::AlignCenter); //Aligning label text to center
QFont f("Arial",50); //Setting the default font size to 50
QFontMetrics fm(f);
ui->label->setFont(f); //Setting the font to the label
int width = fm.width(string); //Getting the width of the string
int size;
while(width >= 870) //870 is the max width of label
{
size = ui->label->font().pointSize()-1; //Reduce font size by 1
QFont newFont("Arial",size);
QFontMetrics nfm(newFont);
ui->label->setFont(newFont); //Set the new font with new size
width = nfm.width(string); //Get the new width
}
ui->label->setText(string);
You (S. Andrew) solved it a little bit different like I proposed (just a statement but not critics). You did the word wrapping by yourself.
I wrote a minimal complete application to check how the Qt internal word wrapping can be used for your problem:
// standard C++ header:
#include <iostream>
#include <string>
// Qt header:
#include <QApplication>
#include <QBoxLayout>
#include <QFrame>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QMainWindow>
#include <QStyle>
using namespace std;
class Label: public QLabel {
public:
void layout();
QRect documentRect(); // borrowed from QLabelPrivate
protected:
virtual void resizeEvent(QResizeEvent *pQEvent);
};
QRect Label::documentRect()
{
QRect rect = contentsRect();
int m = margin(); rect.adjust(m, m, -m, -m);
layoutDirection();
const int align
= QStyle::visualAlignment(layoutDirection(), QLabel::alignment());
int i = indent();
if (i < 0 && frameWidth()) { // no indent, but we do have a frame
m = fontMetrics().width(QLatin1Char('x')) / 2 - m;
}
if (m > 0) {
if (align & Qt::AlignLeft) rect.setLeft(rect.left() + m);
if (align & Qt::AlignRight) rect.setRight(rect.right() - m);
if (align & Qt::AlignTop) rect.setTop(rect.top() + m);
if (align & Qt::AlignBottom) rect.setBottom(rect.bottom() - m);
}
return rect;
}
void Label::layout()
{
// get initial settings
QString text = this->text();
QRect rectLbl = documentRect(); // wrong: contentsRect();
QFont font = this->font();
int size = font.pointSize();
QFontMetrics fontMetrics(font);
QRect rect = fontMetrics.boundingRect(rectLbl,
Qt::TextWordWrap, text);
// decide whether to increase or decrease
int step = rect.height() > rectLbl.height() ? -1 : 1;
// iterate until text fits best into rectangle of label
for (;;) {
font.setPointSize(size + step);
QFontMetrics fontMetrics(font);
rect = fontMetrics.boundingRect(rectLbl,
Qt::TextWordWrap, text);
if (size <= 1) {
cout << "Font cannot be made smaller!" << endl;
break;
}
if (step < 0) {
size += step;
if (rect.height() < rectLbl.height()) break;
} else {
if (rect.height() > rectLbl.height()) break;
size += step;
}
}
// apply result of iteration
font.setPointSize(size);
setFont(font);
}
void Label::resizeEvent(QResizeEvent *pQEvent)
{
QLabel::resizeEvent(pQEvent);
layout();
}
int main(int argc, char **argv)
{
cout << QT_VERSION_STR << endl;
// main application
#undef qApp // undef macro qApp out of the way
QApplication qApp(argc, argv);
// setup GUI
QMainWindow qWin;
QGroupBox qGBox;
QVBoxLayout qBox;
Label qLbl;
qLbl.setFrameStyle(Label::Box);
qLbl.setFrameShadow(Label::Sunken);
qLbl.setWordWrap(true);
qBox.addWidget(&qLbl, 1);
QLineEdit qTxt;
qBox.addWidget(&qTxt, 0);
qGBox.setLayout(&qBox);
qWin.setCentralWidget(&qGBox);
qWin.show();
// install signal handlers
QObject::connect(&qTxt, &QLineEdit::editingFinished,
[&qTxt, &qLbl]() {
QString text = qTxt.text();
qLbl.setText(text);
qLbl.layout();
});
return qApp.exec();
}
Compiled and tested with VS2013 / Qt 5.6 on Windows 10 (64 bit):
When playing around with this test application, I recognized that the text fits not everytimes perfectly into the QLabel. I tried to improve the code exchanging QRect rectLbl = rect(); with QRect rectLbl = contentsRect();. This made it better but still not perfect. It seems there is some finetuning necessary (where the development starts to become effort). (See update at end of text.)
Actually, it would not be necessary to derive QLabel. In my first implementation, layout() was a function with QLabel& and const QString& as parameters.
After I got the font size management working, I intended to consider resize events also. Googling a little bit, I found the solution to apply event filters. However, event filters are called before the event is processed but I need after. Finally, I decided to inherit QLabel and to overload QLabel::resizeEvent() to keep things simple.
Btw. I noticed it is even not necessary to set
height eventually to a very large value
as I suggested in a comment earlier. It seems that QFontMetrics::boundingRect(const QRect &rect, int flags, ...) increases the height automa[gt]ically to keep required width when Qt::TextWordWrap is enabled.
Update:
#annacarolina encouraged me to investigate a little bit deeper into this issue that font size is sometimes choosen to large. Some debugging in Label::layout() uncovered that sometimes computed rect looked like unwrapped text where visual output was wrapped. This made me suspiciuous about correctness of the rectLbl. Thus, I started in qlabel.cpp on woboq.org but actually the Qt forum QLabel: Resize font to contentsRect provided the final hint which leaded me to QLabelPrivate::documentRect() (actually again on woboq.org where I already had looked for enlightment). Thus, I added a method Label::documentRect() to my class. This makes results much better (although I'm not fully convinced about "perfect").
In the following code, I am making a logic where I am first getting all the words in the string. Then I am appending the words in QList<QString> data and checking if the width of the appended words is smaller than then width of the label. If the width goes above the width of label then I break it using \n. So in this way I made a list of the sub strings whose total width is around the width of the label and saved it in the List. Then I am calculating the width of sub strings stored in the list and then decreasing its font size till its total width is less than width of the label. After this I am displaying it on the label.
QList<QString> data;
CountWords Word;
ui->label->clear();
QString string = ui->textEdit->toPlainText(); //Getting data from textEdit
QStringList count = Word.GetWords(string); //Here I get the list of words in string
ui->label->setAlignment(Qt::AlignCenter); //Aligning label text to center
QFont f("Arial",50); //Setting the default font size to 50
QFontMetrics fm(f);
ui->label->setFont(f); //Setting the font to the label
int size,fontSize;
QString temp = ui->label->text();
int last = count.size();
//Saving the words in QList
for(int i=0;i<count.size();i++)
{
temp.append(count[i]+" ");
size = fm.width(temp);
if(size > 870)
{
temp.append("\n");
data << temp;
//data.append(temp);
temp.clear();
}
if((last-1)==i)
{
subString.append("\n");
data << subString;
subString.clear();
}
}
//decreasing the font size
QList<int> wide;
for(int i=0;i<data.size();i++)
{
wide << fm.width(data[i]);
while(wide[i] >= 870)
{
fontSize = ui->label->font().pointSize() - 1;
QFont newFont("Arial",fontSize);
QFontMetrics nfm(newFont);
ui->label->setFont(newFont);
wide[i] = 0;
wide[i] = nfm.width(data[i]);
}
}
//Finally displaying it on label
QString labelData;
for(int i=0;i<data.size();i++)
{
labelData = ui->label->text();
labelData.append(data[i]);
ui->label->setText(labelData);
}
After struggling with this issue, I create DynamicFontSizeLabel and DynamicFontSizePushButton widgets. Hope it helps.
https://github.com/jonaias/DynamicFontSizeWidgets/
Thanks Scheff for some inspiration.