QLabel & Word Wrap : How to break line base on a comma (vs space)
Asked Answered
G

1

0

I am trying to make a multi line QLabel with a text without space but delimited by comma. ex : 'Cat,Dog,Rabbit,Train,Car,Plane,Cheese,Meat,Door,Window'

enter image description here

I have found that multiline is possible with setWordWrap but it breaks based on spaces.

How would it be possible to break line based on comma ?

Here is an example of code :

from PySide2.QtWidgets import *


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.setGeometry(500,100,50,100)

        line = QLabel()
        line.setMaximumWidth(150)
        line.setText('Cat,Dog,Rabbit,Train,Car,Plane,Cheese,Meat,Door,Window')
        line.setWordWrap(True)

        self.setCentralWidget(line)

        self.show()


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    app.exec_()
Graner answered 17/2, 2021 at 20:13 Comment(4)
You need the words to be all on separate lines or to act like word wrap and fit the container?Jugendstil
I would like to have the same mechanism than word wrap. In this example, it should break after Car,Graner
@Graner A quick and dirty fix is to insert a zero-width-space (\u200b) after every comma, since word-wrapping breaks after any kind of whitespace.Aryanize
@Aryanize Thanks, maybe a bit dirty but it makes the job. To my opinion this is a/the solution !Graner
J
0

One way of doing it would be to edit the text according to QLabel size.

The following triggers on every line size event, making this a costly solution.

First we add a signal :

class MainWindow(QMainWindow):
    resized = QtCore.pyqtSignal()

Then we connect signal with a method ,set wordwrap to False and add custom resize event that triggers every time label gets a new size:

self.line.setWordWrap(False)
self.line.resizeEvent = self.on_resize_event
self.resized.connect(self.add_spaces)

on_resize_event which handles size changes and triggers add_spaces :

def on_resize_event(self, event):
    if not self._flag:
        self._flag = True
        self.resized.emit()
        QtCore.QTimer.singleShot(100, lambda: setattr(self, "_flag", False))
    print(f"Resized line: {self.line.size()}")
    return super(MainWindow, self).resizeEvent(event)

Lastly we have a add_spaces method which calculates maximum line length and splits on comma.

def add_spaces(self):
    size = self.line.size()
    text = self.mystring
    result_string = ""
    temp_label = QLabel()
    temp_text = ""

    #Split the chunks by delimiter
    chunks = text.split(",")

    for i,chunk in enumerate(chunks):
        temp_text += chunk + ",";
        if len(chunks) > i+1:
            temp_label.setText(temp_text + chunks[i+1] + ",")
            width = temp_label.fontMetrics().boundingRect(temp_label.text()).width()
            if width >= size.width():
                result_string += temp_text + "\n"
                temp_text = ""
        else:
            result_string += temp_text

    self.line.setText(result_string)

Full code:

from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QLabel, QApplication


class MainWindow(QMainWindow):
    resized = QtCore.pyqtSignal()
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self._flag = False

        self.line = QLabel()
        self.line.setStyleSheet("background-color: grey;color:white;")
        self.line.setMaximumWidth(300)
        self.line.setMinimumWidth(20)

        self.mystring = 'Cat,Dog,Rabbit,Train,Car,Plane,Cheese,Meat,Door,Window,very,long,list of words,list of words,word,word,word,list of word,word,list of word,list of word'

        self.line.setText(self.mystring)



        self.setCentralWidget(self.line)

        self.line.setWordWrap(False)
        self.line.resizeEvent = self.on_resize_event
        self.resized.connect(self.add_spaces)

        self.show()
        self.add_spaces()


    def on_resize_event(self, event):
        if not self._flag:
            self._flag = True
            self.resized.emit()
            QtCore.QTimer.singleShot(100, lambda: setattr(self, "_flag", False))
        print(f"Resized line: {self.line.size()}")
        return super(MainWindow, self).resizeEvent(event)

    def add_spaces(self):
        size = self.line.size()
        text = self.mystring
        result_string = ""
        temp_label = QLabel()
        temp_text = ""

        #Split the chunks by delimiter
        chunks = text.split(",")

        for i,chunk in enumerate(chunks):
            temp_text += chunk + ",";
            if len(chunks) > i+1:
                temp_label.setText(temp_text + chunks[i+1] + ",")
                width = temp_label.fontMetrics().boundingRect(temp_label.text()).width()
                if width >= size.width():
                    result_string += temp_text + "\n"
                    temp_text = ""
            else:
                result_string += temp_text

        self.line.setText(result_string)




if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    app.exec_()
Jugendstil answered 17/2, 2021 at 22:39 Comment(4)
Be amare that changing geometry within a resizeEvent is not suggested, as it can easily lead to recursion especially with widgets that have adapting size hints just like QLabel.Oratorian
@Oratorian Yea thats true. How should the resizeEvent be handled? At the moment i changed it to have a 100ms delay to prevent that.Jugendstil
Thanks for the time spent on your answer. Might not be the answer but I learned other things and it show me how to overcome another of my problems :)Graner
@Jugendstil it should not call a resize at all, and a good (but far from easy) implementation would require proper subclass of QLabel, at least overriding resizeEvent to compute the correct word wrapping, hasHeightForWidth and heightForWidth for appropriate "ratio" hint, sizeHint for the possible "final" hint, setText for the minimum size and probably even paintEvent; all this would obviously work better if keeping an internal QTextDocument (which is what QLabel actually does).Oratorian

© 2022 - 2024 — McMap. All rights reserved.