1

My widget has a long label in a gridlayout which, whithout any extra line of code, inhibits shrinking the window. In this post I saw that setting label1.setMiniumSize(1, 1) enables shrinking of the window again. However, I would like to display an abbreviated form of the text in the label if shrinking the windows cuts the display of the text.

Here is an example:

  1. Starting point: label1.setText("This is a long long long long long long long long label") displays "This is a long long long long long long long long label" and inhibits shrinking of the window.

  2. After adding label1.setMinimumSize(1, 1) the window can be shrunk and it displays "This is a long lo"

  3. I would like to have the display of "This is ... ong label" or "This is a long ... long label", adapted to the really available size for the label.

Can we catch the information what size the layout manager would like to give to the label? Calculate the number of characters that correspond, Abbreviate the text, And set the new text?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
FraWa
  • 63
  • 10
  • 1
    Take a look at the [Elided Label Example](https://doc.qt.io/qt-5/qtwidgets-widgets-elidedlabel-example.html). – scopchanov Sep 18 '20 at 13:41
  • 1
    OK I read through it, but it seems quite complicated to me... Perhaps I'll try to use an elided Qlabel (as mine has just one line), (even if I'd prefer the dots in the middle). Thanks for your help. I even did not know the english word 'to elide'... – FraWa Sep 18 '20 at 13:58
  • About _I even did not know the english word 'to elide'_, nowadays it is a matter of knowing the right keyword to find the information. :) So, you are welcome! As for the complexity, it is complicated, but you might take the class, put it in your project and just use it as it is. – scopchanov Sep 18 '20 at 14:02
  • I will also give it a try to make it work like you want, so stay tuned. – scopchanov Sep 18 '20 at 14:04

4 Answers4

1

You can use QFontMetricsF to get the size of the QLabel required to draw the text. Inversely, you can override the resizeEvent() of QLabel to get the current size of the label every time it changes size and adapt your text to it.

QFontMetricsF FM(ui->label->font());
QRectF rect = FM.boundingRect("A Long Long Long Long Text");
double rectWidth = rect.width();
if (rectWidth > ui->label->width())
{
    // Change text and recalculate in a loop if it fits
}

The best place to do it would be in resizeEvent() of either the label or the parent widget.

Chaitanya
  • 177
  • 2
  • 9
  • 1
    Thank you, this looks much less complicated, than the Elided Label example. I'll try to translate it to Pyhton and test it. – FraWa Sep 18 '20 at 15:04
  • Yes, it works fine. Thanks a lot. I'll put the pyhton version in an answer below – FraWa Sep 18 '20 at 15:38
1

Here is the Python3 version of @Chaitanyas answer.

from PyQt5.QtCore import QRectF
from PyQt5.QtGui import QFontMetricsF

class PathLabel(QLabel):
def resizeEvent(self, *args, **kwargs):
    font_m = QFontMetricsF(self.font())
    text_rect = QRectF(font_m.boundingRect(self.text()))
    if text_rect.width() > self.width():  # use shorter text
        # just for test: remove ten characters
        self.setText(self.text()[:-10])
    else:  # possibly use longer text
        # check if a longer version would fit
        pass

And later in the mainwindow:

self.LbFolderName = PathLabel()

And here is what I finally use now:

class PathLabel(QLabel):
    """Use setLongText instead of setText for a usual label"""

    def __init__(self, parent=None):
        super(PathLabel, self).__init__(parent)
        self.long_text = ""

    def make_short_text(self):
        # print("make_short_text called")
        """works fine but is not perfect for fnames as the middle is hidden.
        -> better hide the middle of the path but not long filenames"""
        font_m = QFontMetricsF(self.font())  # F or not ?
        avail_width = self.width() - 3  # - 3 px for a little space at the end
        short_text = font_m.elidedText(self.long_text, Qt.ElideMiddle, avail_width)
        return short_text

    def setLongText(self, text_in):
        # print("setLongText called")
        """Use this instead of setText for a usual label"""
        self.long_text = text_in
        self.setToolTip(text_in)  # tooltip shows the full text
        short_text = self.make_short_text()
        self.setText(short_text)

    def resizeEvent(self, *args, **kwargs):
        #print("resizeEvent called")
        short_text = self.make_short_text()
        self.setText(short_text)

After creating the label,

lb_folder = PathLabel("File not yet defined")

I call

lb_folder.setLongText(full_path_to_file)
FraWa
  • 63
  • 10
  • cropping until text fits works nicely `while text_rect.width() > self.width(): # crop one by one until it fits self.setText(self.text()[:-1]) text_rect = QRectF(font_m.boundingRect(self.text()))` – lymphatic Jun 07 '21 at 23:23
0

Use the Elide Class.

static void SetTextToLabel(QLabel *label, QString text)
{
    QFontMetrics metrix(label->font());
    int width = label->width() - 2;
    QString clippedText = metrix.elidedText(text, Qt::ElideRight, width);
    label->setText(clippedText);
}

Call this function with an event whenever you want to elide a text.

X caliber
  • 52
  • 4
  • Thank you for your answer, too. As it is shorter, it was a bit more difficult to understand for me than the one of @Chaitanya but it gave me the information that you can choose the eldide mode as `ElideMiddle`. I think in the end this will be a quick solution that is close to ideal without having to take care of the details of shortening the text. – FraWa Sep 19 '20 at 10:06
0

There are different ways to solve this. Creating a custom widget is one of them.

So, I have modified the Elided Label Example according to your specifications:

  1. It supports one-line text only
  2. It lets you set the elide mode via ElidedLabel::setElideMode

Here are the files:

ElidedLabel.h

#ifndef ELIDEDLABEL_H
#define ELIDEDLABEL_H

#include <QFrame>

class ElidedLabelPrivate;

class ElidedLabel : public QFrame
{
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
    Q_PROPERTY(QFlags<Qt::AlignmentFlag> alignment READ alignment
               WRITE setAlignment NOTIFY alignmentChanged)
    Q_PROPERTY(Qt::TextElideMode elideMode READ elideMode WRITE setElideMode
               NOTIFY elideModeChanged)
public:
    explicit ElidedLabel(QWidget *parent = nullptr);
    explicit ElidedLabel(const QString &text, QWidget *parent = nullptr);
    ~ElidedLabel();

    QString text() const;
    void setText(const QString &str);
    QFlags<Qt::AlignmentFlag> alignment() const;
    void setAlignment(QFlags<Qt::AlignmentFlag> flags);
    Qt::TextElideMode elideMode() const;
    void setElideMode(Qt::TextElideMode mode);

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    ElidedLabelPrivate *m_ptr;

signals:
    void textChanged();
    void alignmentChanged();
    void elideModeChanged();
};

#endif // ELIDEDLABEL_H

ElidedLabel_p.h

#ifndef ELIDEDLABEL_P_H
#define ELIDEDLABEL_P_H

#include <Qt>
#include <QString>

class ElidedLabel;

class ElidedLabelPrivate {

    Q_DISABLE_COPY(ElidedLabelPrivate)

    explicit ElidedLabelPrivate();

    QString text;
    QFlags<Qt::AlignmentFlag> alignment;
    Qt::TextElideMode elideMode;

    friend class ElidedLabel;
};

#endif // ELIDEDLABEL_P_H

ElidedLabel.cpp

#include "ElidedLabel.h"
#include "ElidedLabel_p.h"
#include <QPaintEvent>
#include <QPainter>

ElidedLabel::ElidedLabel(QWidget *parent) :
    QFrame(parent),
    m_ptr(new ElidedLabelPrivate)
{

}

ElidedLabel::ElidedLabel(const QString &text, QWidget *parent) :
    ElidedLabel(parent)
{
    m_ptr->text = text;
}

ElidedLabel::~ElidedLabel()
{
    delete m_ptr;
}

QString ElidedLabel::text() const
{
    return m_ptr->text;
}

void ElidedLabel::setText(const QString &str)
{
    m_ptr->text = str;

    update();

    emit textChanged();
}

QFlags<Qt::AlignmentFlag> ElidedLabel::alignment() const
{
    return m_ptr->alignment;
}

void ElidedLabel::setAlignment(QFlags<Qt::AlignmentFlag> flags)
{
    m_ptr->alignment = flags;

    update();

    emit alignmentChanged();
}

Qt::TextElideMode ElidedLabel::elideMode() const
{
    return m_ptr->elideMode;
}

void ElidedLabel::setElideMode(Qt::TextElideMode mode)
{
    m_ptr->elideMode = mode;

    update();

    emit elideModeChanged();
}

void ElidedLabel::paintEvent(QPaintEvent *event)
{
    QFrame::paintEvent(event);
    QPainter painter(this);

    painter.setPen(QPalette().windowText().color());
    painter.setClipRect(event->rect());
    painter.setFont(font());
    painter.drawText(contentsRect(), m_ptr->alignment | Qt::TextSingleLine,
                     painter.fontMetrics().elidedText(m_ptr->text,
                                                      m_ptr->elideMode,
                                                      contentsRect().width(),
                                                      1));
}

ElidedLabelPrivate::ElidedLabelPrivate() :
    alignment(Qt::AlignLeft | Qt::AlignVCenter),
    elideMode(Qt::ElideRight)
{

}

This is too much code, I know, and it might looks scary. However its usage is not that difficult. Just add the given files to your project and use it like any other widget.

Here is an example main.cpp:

#include "ElidedLabel.h"
#include <QApplication>

int main(int argc, char *argv[])
{

    QApplication a(argc, argv);
    QFont f = QGuiApplication::font();

    f.setPointSize(11);

    QGuiApplication::setFont(f);

    ElidedLabel label;

    label.setText(QObject::tr("Hello Elided World! We have a very very very"
                              " long one-line text here."));
    label.setContentsMargins(10, 10, 10, 10);
    label.setElideMode(Qt::ElideMiddle);
    label.show();
    label.resize(200, 100);

    return a.exec();
}
scopchanov
  • 7,966
  • 10
  • 40
  • 68