Premise: what you ask cannot be accomplished in a completely reliable way, especially when dealing with complex layouts. Layout management of widgets having variable content sizes is not easy to achieve, and text layout certainly falls in that category.
When using rich text content, QLabel automatically "installs" a private QWidgetTextControl on itself, used to manage the display and layout of rich text, by means of an internal QTextDocument which provides more text wrap options other than the common word wrap.
The creation of that text control can be enforced, even using plain text, by explicitly setting the textFormat property to Qt::RichText.
By doing this, we can access the internal QTextDocument through findChild() and set the wrapMode of the default QTextOption when necessary, which is primarily when painting happens.
A lucky benefit of forcing the wrap mode when painting is that, when that event is called, the resize has already happened, which makes it possible to also transparently use text interaction flags, also allowing text selection.
Note that the text control always resets the wrap mode when the contents change or the label is resized; this also affects the size hint, and would prevent resizing the label to a smaller width. While we could explicitly set a minimum width or set an Ignore size policy, it wouldn't be completely appropriate when used in a layout.
In order to work around this, we need to override both size hint functions of QWidget:
minimumSizeHint() should return an arbitrary minimum size, based on the font metrics; the smallest width and height that would theoretically allow all the text to be shown, with or without wrapping;
sizeHint() of QLabel is based on minimumSizeHint(), therefore we need to override it and return the default implementation (not our own); in this
way the layout may know the preferred size of the label;
Here things become tricky, though: what would be a valid minimum size? In theory, it could be the maximum width of the narrower character in the text, so that "Hello world" could also be displayed as one letter per line, but what would be the minimum height? We cannot obviously use the line height multiplied the number of characters, otherwise the label will require too much vertical space that will probably be unnecessary. Since we don't know how much the label would be "stretched", nor how many lines some text would eventually required on those cases, we need an arbitrary height.
QLabel does that by choosing an arbitrary size based on the text contents (using an approximation of the golden ratio considering the font metrics), but we cannot use that. This means that an arbitrary minimum height must be decided, possibly based on individual cases.
In the following example, I just used a very simplistic approach: just return the width of the "M" letter, and the height of two lines. You may probably need to change that depending on the scenario, possibly providing some function that tells how many characters/lines should be considered for the minimum size hint.
I only use Qt from Python, and I implemented the above in that language, therefore I used an online AI conversion to help me getting an approximate (and not 100% valid) C++ code for the subclass:
class WrapEverywhereLabel : public QLabel {
Q_OBJECT
public:
WrapEverywhereLabel(QWidget *parent = nullptr) : QLabel(parent) {
this->setTextFormat(Qt::RichText);
this->_document = this->findChild<QTextDocument *>();
}
private:
QTextDocument *_document;
void _ensureWrapMode() {
QTextOption opt = this->_document->defaultTextOption();
if (opt.wrapMode() != QTextOption::WrapAnywhere) {
opt.setWrapMode(QTextOption::WrapAnywhere);
this->_document->setDefaultTextOption(opt);
}
}
QSize minimumSizeHint() const override {
return fontMetrics().size(0, 'M\n');
}
QSize sizeHint() const override {
return QLabel::minimumSizeHint();
}
void paintEvent(QPaintEvent *event) override {
if (this->width() < QLabel::minimumSizeHint().width()) {
_ensureWrapMode();
}
QLabel::paintEvent(event);
}
};
Note: I am well aware of the ban on AI contents on StackOverflow. The above is intended only as a possible reference to write the real implementation, not to be used as is. I could've written some pseudo code, but it would've been a bit pointless.
For reference, this is how I implemented the above in Python:
class WrapEverywhereLabel(QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTextFormat(Qt.RichText)
self._document = self.findChild(QTextDocument)
def _ensureWrapMode(self):
opt = self._document.defaultTextOption()
if opt.wrapMode() != QTextOption.WrapAnywhere:
opt.setWrapMode(QTextOption.WrapAnywhere)
self._document.setDefaultTextOption(opt)
def minimumSizeHint(self):
return self.fontMetrics().size(0, 'M\n')
def sizeHint(self):
return super().minimumSizeHint()
def paintEvent(self, event):
if self.width() < super().minimumSizeHint().width():
self._ensureWrapMode()
super().paintEvent(event)
Be aware that you may face some layout management issues, because there is no way for Qt to get an appropriate size hint when wrapping is enabled. This does not depend on my approach, but is a known Qt layout issue that cannot be fixed in arbitrary ways and depends on how windowed layout management works, which is also the reason for which it's difficult to get resizable widgets having a fixed aspect ratio.