Turn elidedlabel example into a code snippet

The example is 90% boiler plate for subclassing QFrame and providing
a bit of GUI to change the size of the label using sliders. The
interesting bit is a block of 25 lines of code, so turn those into a
snippet and add that to the QTextLayout overview documentation.

Fixes: QTBUG-111011
Pick-to: 6.5
Change-Id: I6e97b2ea47b553c8d998ad185cfac006721ef7ee
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
Volker Hilsheimer 2023-02-08 08:26:59 +01:00
parent 31518f1a4e
commit 55f2b448b0
13 changed files with 43 additions and 475 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,131 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example widgets/elidedlabel
\title Elided Label Example
\brief This example creates a widget similar to QLabel, that elides the last
visible line, if the text is too long to fit the widget's geometry.
\image elidedlabel-example.png Elided Label example on XPressMusic 5800
When text of varying length has to be displayed in a uniformly sized
area, for instance within a list or grid view where all list items have the
same size, it can be useful to give the user a visual clue when not all
text is visible. QLabel can elide text that doesn't fit within it, but only
in one line. The \c ElidedLabel widget shown in this example word wraps its
text by its width, and elides the last visible line if some text is left
out. \c TestWidget gives control to the features of \c ElidedWidget and
forms the example application.
\section1 ElidedLabel Class Definition
Like QLabel, \c ElidedLabel inherits from QFrame. Here's the definition of
the \c ElidedLabel class:
\snippet widgets/elidedlabel/elidedlabel.h 0
The \c isElided property depends the font, text content and geometry of the
widget. Whenever any of these change, the \c elisionChanged() signal might
trigger. We cache the current elision value in \c elided, so that it
doesn't have to be recomputed every time it's asked for.
\section1 ElidedLabel Class Implementation
Except for initializing the member variables, the constructor sets the size
policy to be horizontally expanding, since it's meant to fill the width of
its container and grow vertically.
\snippet widgets/elidedlabel/elidedlabel.cpp 0
Changing the \c content require a repaint of the widget.
\snippet widgets/elidedlabel/elidedlabel.cpp 1
QTextLayout is used in the \c paintEvent() to divide the \c content into
lines, that wrap on word boundaries. Each line, except the last visible
one, is drawn \c lineSpacing pixels below the previous one. The \c draw()
method of QTextLine will draw the line using the coordinate point as the
top left corner.
\snippet widgets/elidedlabel/elidedlabel.cpp 2
Unfortunately, QTextLayout does not elide text, so the last visible line
has to be treated differently. This last line is elided if it is too wide.
The \c drawText() method of QPainter draws the text starting from the base
line, which is \c ascecnt() pixels below the last drawn line.
Finally, one more line is created to see if everything fit on this line.
\snippet widgets/elidedlabel/elidedlabel.cpp 3
If the text was elided and wasn't before or vice versa, cache it in
\c elided and emit the change.
\snippet widgets/elidedlabel/elidedlabel.cpp 4
\section1 TestWidget Class Definition
\c TestWidget is a QWidget and is the main window of the example. It
contains an \c ElidedLabel which can be resized with two QSlider widgets.
\snippet widgets/elidedlabel/testwidget.h 0
\section1 TestWidget Class Implementation
The constructor initializes the whole widget. Strings of different length
are stored in \c textSamples. The user is able to switch between these.
\snippet widgets/elidedlabel/testwidget.cpp 0
An \c ElidedLabel is created to contain the first of the sample strings.
The frame is made visible to make it easier to see the actual size of the
widget.
\snippet widgets/elidedlabel/testwidget.cpp 1
The buttons and the elision label are created. By connecting the
\c elisionChanged() signal to the \c setVisible() slot of the \c label,
it will act as an indicator to when the text is elided or not. This signal
could, for instance, be used to make a "More" button visible, or similar.
\snippet widgets/elidedlabel/testwidget.cpp 2
The \c widthSlider and \c heightSlider specify the size of the
\c elidedText. Since the y-axis is inverted, the \c heightSlider has to be
inverted to act appropriately.
\snippet widgets/elidedlabel/testwidget.cpp 3
The components are all stored in a QGridLayout, which is made the layout of
the \c TestWidget.
\snippet widgets/elidedlabel/testwidget.cpp 4
The \c widthSlider and \c heightSlider have the exact same length as the
dimensions of the \c elidedText. The maximum value for both of them is
thus their lengths, and each tick indicates one pixel.
\snippet widgets/elidedlabel/testwidget.cpp 6
The \c switchText() slot simply cycles through all the available sample
texts.
\snippet widgets/elidedlabel/testwidget.cpp 7
These slots set the width and height of the \c elided text, in response to
changes in the sliders.
\section1 The \c main() Function
The \c main() function creates an instance of \c TestWidget fullscreen and
enters the message loop.
\snippet widgets/elidedlabel/main.cpp 0
*/

View File

@ -7,7 +7,6 @@ qt_internal_add_example(calendarwidget)
qt_internal_add_example(charactermap)
qt_internal_add_example(codeeditor)
qt_internal_add_example(digitalclock)
qt_internal_add_example(elidedlabel)
qt_internal_add_example(groupbox)
qt_internal_add_example(icons)
qt_internal_add_example(imageviewer)

View File

@ -1,38 +0,0 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(elidedlabel LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/widgets/widgets/elidedlabel")
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
qt_standard_project_setup()
qt_add_executable(elidedlabel
elidedlabel.cpp elidedlabel.h
main.cpp
testwidget.cpp testwidget.h
)
set_target_properties(elidedlabel PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(elidedlabel PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
install(TARGETS elidedlabel
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

View File

@ -1,74 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "elidedlabel.h"
#include <QPainter>
#include <QSizePolicy>
#include <QTextLayout>
//! [0]
ElidedLabel::ElidedLabel(const QString &text, QWidget *parent)
: QFrame(parent)
, elided(false)
, content(text)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
//! [0]
//! [1]
void ElidedLabel::setText(const QString &newText)
{
content = newText;
update();
}
//! [1]
//! [2]
void ElidedLabel::paintEvent(QPaintEvent *event)
{
QFrame::paintEvent(event);
QPainter painter(this);
QFontMetrics fontMetrics = painter.fontMetrics();
bool didElide = false;
int lineSpacing = fontMetrics.lineSpacing();
int y = 0;
QTextLayout textLayout(content, painter.font());
textLayout.beginLayout();
forever {
QTextLine line = textLayout.createLine();
if (!line.isValid())
break;
line.setLineWidth(width());
int nextLineY = y + lineSpacing;
if (height() >= nextLineY + lineSpacing) {
line.draw(&painter, QPoint(0, y));
y = nextLineY;
//! [2]
//! [3]
} else {
QString lastLine = content.mid(line.textStart());
QString elidedLastLine = fontMetrics.elidedText(lastLine, Qt::ElideRight, width());
painter.drawText(QPoint(0, y + fontMetrics.ascent()), elidedLastLine);
line = textLayout.createLine();
didElide = line.isValid();
break;
}
}
textLayout.endLayout();
//! [3]
//! [4]
if (didElide != elided) {
elided = didElide;
emit elisionChanged(didElide);
}
}
//! [4]

View File

@ -1,36 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef ELIDEDLABEL_H
#define ELIDEDLABEL_H
#include <QFrame>
#include <QString>
//! [0]
class ElidedLabel : public QFrame
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(bool isElided READ isElided)
public:
explicit ElidedLabel(const QString &text, QWidget *parent = nullptr);
void setText(const QString &text);
const QString & text() const { return content; }
bool isElided() const { return elided; }
protected:
void paintEvent(QPaintEvent *event) override;
signals:
void elisionChanged(bool elided);
private:
bool elided;
QString content;
};
//! [0]
#endif // TEXTWRAPPINGWIDGET_H

View File

@ -1,20 +0,0 @@
# Nokia Qt Examples: elided label example
QT += core gui widgets
requires(qtConfig(combobox))
TARGET = elidedlabel
TEMPLATE = app
SOURCES += \
main.cpp\
testwidget.cpp \
elidedlabel.cpp
HEADERS += \
testwidget.h \
elidedlabel.h
# install
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/widgets/elidedlabel
INSTALLS += target

View File

@ -1,16 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "testwidget.h"
#include <QApplication>
//! [0]
int main( int argc, char *argv[] )
{
QApplication application( argc, argv );
TestWidget w;
w.showFullScreen();
return application.exec();
}
//! [0]

View File

@ -1,119 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "testwidget.h"
#include "elidedlabel.h"
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
//! [0]
TestWidget::TestWidget(QWidget *parent)
: QWidget(parent)
{
const QString romeo = tr(
"But soft, what light through yonder window breaks? / "
"It is the east, and Juliet is the sun. / "
"Arise, fair sun, and kill the envious moon, / "
"Who is already sick and pale with grief / "
"That thou, her maid, art far more fair than she."
);
const QString macbeth = tr(
"To-morrow, and to-morrow, and to-morrow, / "
"Creeps in this petty pace from day to day, / "
"To the last syllable of recorded time; / "
"And all our yesterdays have lighted fools / "
"The way to dusty death. Out, out, brief candle! / "
"Life's but a walking shadow, a poor player, / "
"That struts and frets his hour upon the stage, / "
"And then is heard no more. It is a tale / "
"Told by an idiot, full of sound and fury, / "
"Signifying nothing."
);
const QString harry = tr("Feeling lucky, punk?");
textSamples << romeo << macbeth << harry;
//! [0]
//! [1]
sampleIndex = 0;
elidedText = new ElidedLabel(textSamples[sampleIndex], this);
elidedText->setFrameStyle(QFrame::Box);
//! [1]
//! [2]
QPushButton *switchButton = new QPushButton(tr("Switch text"));
connect(switchButton, &QPushButton::clicked, this, &TestWidget::switchText);
QPushButton *exitButton = new QPushButton(tr("Exit"));
connect(exitButton, &QPushButton::clicked, this, &TestWidget::close);
QLabel *label = new QLabel(tr("Elided"));
label->setVisible(elidedText->isElided());
connect(elidedText, &ElidedLabel::elisionChanged, label, &QLabel::setVisible);
//! [2]
//! [3]
widthSlider = new QSlider(Qt::Horizontal);
widthSlider->setMinimum(0);
connect(widthSlider, &QSlider::valueChanged, this, &TestWidget::onWidthChanged);
heightSlider = new QSlider(Qt::Vertical);
heightSlider->setInvertedAppearance(true);
heightSlider->setMinimum(0);
connect(heightSlider, &QSlider::valueChanged, this, &TestWidget::onHeightChanged);
//! [3]
//! [4]
QGridLayout *layout = new QGridLayout;
layout->addWidget(label, 0, 1, Qt::AlignCenter);
layout->addWidget(switchButton, 0, 2);
layout->addWidget(exitButton, 0, 3);
layout->addWidget(widthSlider, 1, 1, 1, 3);
layout->addWidget(heightSlider, 2, 0);
layout->addWidget(elidedText, 2, 1, 1, 3, Qt::AlignTop | Qt::AlignLeft);
setLayout(layout);
//! [4]
}
//! [6]
void TestWidget::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event);
int maxWidth = widthSlider->width();
widthSlider->setMaximum(maxWidth);
widthSlider->setValue(maxWidth / 2);
int maxHeight = heightSlider->height();
heightSlider->setMaximum(maxHeight);
heightSlider->setValue(maxHeight / 2);
elidedText->setFixedSize(widthSlider->value(), heightSlider->value());
}
//! [6]
//! [7]
void TestWidget::switchText()
{
sampleIndex = (sampleIndex + 1) % textSamples.size();
elidedText->setText(textSamples.at(sampleIndex));
}
//! [7]
//! [8]
void TestWidget::onWidthChanged(int width)
{
elidedText->setFixedWidth(width);
}
void TestWidget::onHeightChanged(int height)
{
elidedText->setFixedHeight(height);
}
//! [8]

View File

@ -1,38 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef TESTWIDGET_H
#define TESTWIDGET_H
#include <QSlider>
#include <QStringList>
#include <QWidget>
class ElidedLabel;
//! [0]
class TestWidget : public QWidget
{
Q_OBJECT
public:
TestWidget(QWidget *parent = nullptr);
protected:
void resizeEvent(QResizeEvent *event) override;
private slots:
void switchText();
void onWidthChanged(int width);
void onHeightChanged(int height);
private:
int sampleIndex;
QStringList textSamples;
ElidedLabel *elidedText;
QSlider *heightSlider;
QSlider *widthSlider;
};
//! [0]
#endif // TESTWIDGET_H

View File

@ -5,7 +5,6 @@ SUBDIRS = analogclock \
charactermap \
codeeditor \
digitalclock \
elidedlabel \
groupbox \
icons \
imageviewer \

View File

@ -10,6 +10,7 @@ namespace src_gui_text_qtextlayout {
struct Wrapper : public QPaintDevice
{
void wrapper1();
void elided();
};
QTextLayout textLayout;
@ -24,7 +25,7 @@ int leading = fontMetrics.leading();
qreal height = 0;
textLayout.setCacheEnabled(true);
textLayout.beginLayout();
while (1) {
while (true) {
QTextLine line = textLayout.createLine();
if (!line.isValid())
break;
@ -49,4 +50,41 @@ textLayout.draw(&painter, QPoint(0, 0));
} // Wrapper::wrapper1
void Wrapper::elided() {
QString content;
//! [elided]
QPainter painter(this);
QFontMetrics fontMetrics = painter.fontMetrics();
int lineSpacing = fontMetrics.lineSpacing();
int y = 0;
QTextLayout textLayout(content, painter.font());
textLayout.beginLayout();
while (true) {
QTextLine line = textLayout.createLine();
if (!line.isValid())
break;
line.setLineWidth(width());
const int nextLineY = y + lineSpacing;
if (height() >= nextLineY + lineSpacing) {
line.draw(&painter, QPoint(0, y));
y = nextLineY;
} else {
const QString lastLine = content.mid(line.textStart());
const QString elidedLastLine = fontMetrics.elidedText(lastLine, Qt::ElideRight, width());
painter.drawText(QPoint(0, y + fontMetrics.ascent()), elidedLastLine);
line = textLayout.createLine();
break;
}
}
textLayout.endLayout();
//! [elided]
}
} // src_gui_text_qtextlayout

View File

@ -265,6 +265,10 @@ Qt::LayoutDirection QTextInlineObject::textDirection() const
The text can then be rendered by calling the layout's draw() function:
\snippet code/src_gui_text_qtextlayout.cpp 1
It is also possible to draw each line individually, for instance to draw
the last line that fits into a widget elided:
\snippet code/src_gui_text_qtextlayout.cpp elided
For a given position in the text you can find a valid cursor position with
isValidCursorPosition(), nextCursorPosition(), and previousCursorPosition().