sara
sara

Reputation: 3934

How to customize QLabel word wrap mode

I have a label that sometimes contain a long text with no spaces (path in the computer).

So word-wrap wraps it very weirdly.

Is there a way to make the word-wrap of the label break in the middle of the word or not only at white spaces?

Upvotes: 18

Views: 28234

Answers (7)

user42723
user42723

Reputation: 599

Insert zero-width-space characters

Based on the comment of MarSoft:

When using a path in a QLabel, insert the unicode character U+200B (zero width space) using '\u200B' after every / and/or \ to make word wrap work well.

Upvotes: 0

Max Cury
Max Cury

Reputation: 55

This issue will 100% occur, if you want to make any chat in Qt. There's my solution - works fine for most of the cases.

QString text = your_label->text();
QChar ch;
int max_message_width = your_label->width();
QFontMetrics message_metrics{your_label->font()}; 
// better to write font manually
int side_margins = your_label->contentsMargins().right() + 
your_label->contentsMargins().left(); 
//not sure about contentsMargins(), just for example

for (int cur_len = 0, pos = 0; pos < text.size(); pos++) {
    ch = text[pos];
    if (ch == ' ' || ch == '\n')
        cur_len = 0;
    else {
        cur_len += message_metrics.size(0, ch).width();
        if (cur_len > (max_message_width - side_margins)) {
            text.insert(pos, "\n");
            cur_len = 0;
        }
    }
}

Note that these empty lines of word wrap will only work if you don't resize your QLabel.

Upvotes: 0

Black magic solution

As said from other answers, you can reimplement paintEvent() for QLabel to pass the Qt::TextWrapAnywhere flag.

However, overriding paintEvent() may cause unexpected side effects since the default implementation of paintEvent() contains a lot of extra features besides just painting the text as is.

Actually the alignment flags passed to QStyle::drawItemText is stored in a private member QLabelPrivate::align. I came up with the idea to force rewrite the value of it with the Qt::TextWrapAnywhere flag set, and it works. This workaround requires a bit of C++ black magic.

class WorkaroundLabel: public QLabel {
    Q_OBJECT
public:
    WorkaroundLabel(QString text, QWidget* parent): QLabel(text, parent) {
        setWordWrap(true);
        ushort* p;
        if (FindAlignAddr(&p)) {
            *p = (*p | Qt::TextWrapAnywhere);
        } else {
            // workaround failed
        }
    }
    virtual ~WorkaroundLabel() {}
protected:
    // "ushort align;" in qtbase/src/widgets/widgets/qlabel_p.h
    bool FindAlignAddr(ushort** out) {
        Qt::Alignment align = alignment();
        void* d_raw = (void*) d_ptr.data();
        ushort* p = reinterpret_cast<ushort*>(d_raw);
        for (int i = 0; i < 1024; i += 1) {
            setAlignment(Qt::AlignLeft);
            ushort a = *p;
            setAlignment(Qt::AlignRight);
            ushort b = *p;
            if (a != b) {
                *out = p;
                setAlignment(align);
                return true;
            }
            p++;
        }
        setAlignment(align);
        return false;
    }
};

Upvotes: -2

Ted
Ted

Reputation: 523

QLabel with other wrap mode

I happen to have this same question in 2021, so I here I will share with you some of the best answers I have found so far.

TextWrapAnywhere QLabel

Subclass QLabel and and implement the paintEvent, where you can set the text alignment to TextWrapAnywhere when you drawItemText.

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QVBoxLayout, QWidget, QStyle

class SuperQLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super(SuperQLabel, self).__init__(*args, **kwargs)

        self.textalignment = Qt.AlignLeft | Qt.TextWrapAnywhere
        self.isTextLabel = True
        self.align = None

    def paintEvent(self, event):

        opt = QStyleOption()
        opt.initFrom(self)
        painter = QPainter(self)

        self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)

        self.style().drawItemText(painter, self.rect(),
                                  self.textalignment, self.palette(), True, self.text())


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.setFixedSize(100, 200)

        self.label = QLabel()
        self.label.setWordWrap(True)
        self.label.setText("1111111111111111111111111111")

        self.slabel = SuperQLabel()
        self.slabel.setText("111111111111111111111111111")

        self.centralwidget = QWidget()
        self.setCentralWidget(self.centralwidget)

        self.mainlayout = QVBoxLayout()
        self.mainlayout.addWidget(self.label)
        self.mainlayout.addWidget(self.slabel)

        self.centralwidget.setLayout(self.mainlayout)


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

enter image description here

Text Wrap at char instead of white space

According to this answer(in op's comment) provided by @ekhumoro, if you are looking for wrapping a line based on comma, you can insert a zero-width-space after the char you want to wrap and use the built in word wrap function.

Here is an example:

from PyQt5.QtCore import QRect, Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QStylePainter, QVBoxLayout, QWidget, QStyle


class CommaWrapableQLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super(CommaWrapableQLabel, self).__init__(*args, **kwargs)

    def setWordWrapAtAnychar(self, char):
        newtext = self.text().replace(char, f"{char}\u200b")
        self.setText(newtext)
        self.setWordWrap(True)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.setFixedSize(100, 200)

        self.label = QLabel()
        self.label.setWordWrap(True)
        self.label.setText(
            'Dog,Rabbit,Train,Car,Plane,Cheese,Meat,Door,Window')

        self.slabel = CommaWrapableQLabel()
        self.slabel.setText(
            'Dog,Rabbit,Train,Car,Plane,Cheese,Meat,Door,Window')
        self.slabel.setWordWrapAtAnychar(",")

        self.centralwidget = QWidget()
        self.setCentralWidget(self.centralwidget)

        self.mainlayout = QVBoxLayout()
        self.mainlayout.addWidget(self.label)
        self.mainlayout.addWidget(self.slabel)

        self.centralwidget.setLayout(self.mainlayout)


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

enter image description here

Upvotes: 2

Luca Carlon
Luca Carlon

Reputation: 9986

One way is to use the QTextOption class with a QTextDocument instead of a QLabel. This let you use QTextOption::WrapMode. QTextOption::WrapAtWordBoundaryOrAnywhere should do what you want.

Upvotes: 7

Nicola Mingotti
Nicola Mingotti

Reputation: 1008

In 2020, PySide2, it is just:

 tmp = QLabel()
 tmp.setWordWrap(True)    

Upvotes: -5

budda
budda

Reputation: 89

This isn't elegant but does work...
So say header class has Private:

QLabel *thisLabel;
QString *pathName;
QString *pathNameClean;

and of course defining thisLabel some where. so it would be nice if it was this simple....

thisLabel->setWordWrap(true);

that's fine IF AND ONLY IF the word has break points (WHICH PATHS SHOULD AVOID)

SO keep your actual path in a separate string if you need it for QFile purposes later. Then manually define a character per line number, and insert the spaces into the string.... so we'll say 50 chars is a good width...

    pathNameClean = new QString(pathName);

    int c = pathName->length();

    if( c > 50)
    {
        for(int i = 1; i <= c/50; i++)
        {
            int n = i * 50;
            pathName->insert(n, " ");
        }
    }
    thisLabel->setText(pathName);

Shazam.... simulated WordWrap with no original spaces...

just remember that pathName string is now just for pretty QLabel purposes and that the pathNameClean string is the actual path. Qt programs will crash if you try to open a file with a space injected path.....

(if there's no simple class method it's likely just a few lines of code to do... and why problem solving is a programmers best tool!)

Upvotes: 8

Related Questions