1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qpainter.h"
5#include "qevent.h"
6#include "qpixmapcache.h"
7#include "qstyle.h"
8#include "qstyleoption.h"
9#include "qlabel_p.h"
10#include "private/qhexstring_p.h"
11#include <qmath.h>
12
13#if QT_CONFIG(style_stylesheet)
14#include "private/qstylesheetstyle_p.h"
15#endif
16#if QT_CONFIG(abstractbutton)
17#include "qabstractbutton.h"
18#endif
19#if QT_CONFIG(accessibility)
20#include <qaccessible.h>
21#endif
22
23QT_BEGIN_NAMESPACE
24
25using namespace Qt::StringLiterals;
26
27QLabelPrivate::QLabelPrivate()
28 : QFramePrivate(),
29 valid_hints(false),
30 scaledcontents(false),
31 textLayoutDirty(false),
32 textDirty(false),
33 isTextLabel(false),
34 hasShortcut(false),
35#ifndef QT_NO_CURSOR
36 validCursor(false),
37 onAnchor(false),
38#endif
39 openExternalLinks(false)
40{
41}
42
43QLabelPrivate::~QLabelPrivate()
44{
45}
46
47/*!
48 \class QLabel
49 \brief The QLabel widget provides a text or image display.
50
51 \ingroup basicwidgets
52 \inmodule QtWidgets
53
54 \image fusion-label.png
55
56 QLabel is used for displaying text or an image. No user
57 interaction functionality is provided. The visual appearance of
58 the label can be configured in various ways, and it can be used
59 for specifying a focus mnemonic key for another widget.
60
61 A QLabel can contain any of the following content types:
62
63 \table
64 \header \li Content \li Setting
65 \row \li Plain text
66 \li Pass a QString to setText().
67 \row \li Rich text
68 \li Pass a QString that contains rich text to setText().
69 \row \li A pixmap
70 \li Pass a QPixmap to setPixmap().
71 \row \li A movie
72 \li Pass a QMovie to setMovie().
73 \row \li A number
74 \li Pass an \e int or a \e double to setNum(), which converts
75 the number to plain text.
76 \row \li Nothing
77 \li The same as an empty plain text. This is the default. Set
78 by clear().
79 \endtable
80
81 \warning When passing a QString to the constructor or calling setText(),
82 make sure to sanitize your input, as QLabel tries to guess whether it
83 displays the text as plain text or as rich text, a subset of HTML 4
84 markup. You may want to call
85 setTextFormat() explicitly, e.g. in case you expect the text to be in
86 plain format but cannot control the text source (for instance when
87 displaying data loaded from the Web).
88
89 When the content is changed using any of these functions, any
90 previous content is cleared.
91
92 By default, labels display \l{alignment}{left-aligned, vertically-centered}
93 text and images, where any tabs in the text to be displayed are
94 \l{Qt::TextExpandTabs}{automatically expanded}. However, the look
95 of a QLabel can be adjusted and fine-tuned in several ways.
96
97 The positioning of the content within the QLabel widget area can
98 be tuned with setAlignment() and setIndent(). Text content can
99 also wrap lines along word boundaries with setWordWrap(). For
100 example, this code sets up a sunken panel with a two-line text in
101 the bottom right corner (both lines being flush with the right
102 side of the label):
103
104 \snippet code/src_gui_widgets_qlabel.cpp 0
105
106 The properties and functions QLabel inherits from QFrame can also
107 be used to specify the widget frame to be used for any given label.
108
109 A QLabel is often used as a label for an interactive widget. For
110 this use QLabel provides a useful mechanism for adding an
111 mnemonic (see QKeySequence) that will set the keyboard focus to
112 the other widget (called the QLabel's "buddy"). For example:
113
114 \snippet code/src_gui_widgets_qlabel.cpp 1
115
116 In this example, keyboard focus is transferred to the label's
117 buddy (the QLineEdit) when the user presses Alt+P. If the buddy
118 was a button (inheriting from QAbstractButton), triggering the
119 mnemonic would emulate a button click.
120
121 \sa QLineEdit, QTextEdit, QPixmap, QMovie
122*/
123
124#ifndef QT_NO_PICTURE
125/*!
126 \fn QPicture QLabel::picture(Qt::ReturnByValueConstant) const
127 \deprecated Use the overload without argument instead.
128 \since 5.15
129
130 Returns the label's picture.
131
132 Previously, Qt provided a version of \c picture() which returned the picture
133 by-pointer. That version is now removed. This overload allowed to
134 explicitly differentiate between the by-pointer function and the by-value.
135*/
136
137/*!
138 \since 6.0
139
140 Returns the label's picture.
141*/
142QPicture QLabel::picture() const
143{
144 Q_D(const QLabel);
145 if (d->picture)
146 return *(d->picture);
147 return QPicture();
148}
149#endif // QT_NO_PICTURE
150
151
152/*!
153 Constructs an empty label.
154
155 The \a parent and widget flag \a f, arguments are passed
156 to the QFrame constructor.
157
158 \sa setAlignment(), setFrameStyle(), setIndent()
159*/
160QLabel::QLabel(QWidget *parent, Qt::WindowFlags f)
161 : QFrame(*new QLabelPrivate(), parent, f)
162{
163 Q_D(QLabel);
164 d->init();
165}
166
167/*!
168 Constructs a label that displays the text, \a text.
169
170 The \a parent and widget flag \a f, arguments are passed
171 to the QFrame constructor.
172
173 \sa setText(), setAlignment(), setFrameStyle(), setIndent()
174*/
175QLabel::QLabel(const QString &text, QWidget *parent, Qt::WindowFlags f)
176 : QLabel(parent, f)
177{
178 setText(text);
179}
180
181
182
183/*!
184 Destroys the label.
185*/
186
187QLabel::~QLabel()
188{
189 Q_D(QLabel);
190 d->clearContents();
191}
192
193void QLabelPrivate::init()
194{
195 Q_Q(QLabel);
196
197 q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred,
198 QSizePolicy::Label));
199 setLayoutItemMargins(element: QStyle::SE_LabelLayoutItem);
200}
201
202
203/*!
204 \property QLabel::text
205 \brief the label's text
206
207 If no text has been set this will return an empty string. Setting
208 the text clears any previous content.
209
210 The text will be interpreted either as plain text or as rich
211 text, depending on the text format setting; see setTextFormat().
212 The default setting is Qt::AutoText; i.e. QLabel will try to
213 auto-detect the format of the text set.
214 See \l {Supported HTML Subset} for the definition of rich text.
215
216 If a buddy has been set, the buddy mnemonic key is updated
217 from the new text.
218
219 Note that QLabel is well-suited to display small rich text
220 documents, such as small documents that get their document
221 specific settings (font, text color, link color) from the label's
222 palette and font properties. For large documents, use QTextEdit
223 in read-only mode instead. QTextEdit can also provide a scroll bar
224 when necessary.
225
226 \note This function enables mouse tracking if \a text contains rich
227 text.
228
229 \sa setTextFormat(), setBuddy(), alignment
230*/
231
232void QLabel::setText(const QString &text)
233{
234 Q_D(QLabel);
235 if (d->text == text)
236 return;
237
238 QWidgetTextControl *oldControl = d->control;
239 d->control = nullptr;
240
241 d->clearContents();
242 d->text = text;
243 d->isTextLabel = true;
244 d->textDirty = true;
245 if (d->textformat == Qt::AutoText) {
246 if (Qt::mightBeRichText(d->text))
247 d->effectiveTextFormat = Qt::RichText;
248 else
249 d->effectiveTextFormat = Qt::PlainText;
250 } else {
251 d->effectiveTextFormat = d->textformat;
252 }
253
254 d->control = oldControl;
255
256 if (d->needTextControl()) {
257 d->ensureTextControl();
258 } else {
259 delete d->control;
260 d->control = nullptr;
261 }
262
263 if (d->effectiveTextFormat != Qt::PlainText) {
264 setMouseTracking(true);
265 } else {
266 // Note: mouse tracking not disabled intentionally
267 }
268
269#ifndef QT_NO_SHORTCUT
270 if (d->buddy)
271 d->updateShortcut();
272#endif
273
274 d->updateLabel();
275
276#if QT_CONFIG(accessibility)
277 if (accessibleName().isEmpty()) {
278 QAccessibleEvent event(this, QAccessible::NameChanged);
279 QAccessible::updateAccessibility(event: &event);
280 }
281#endif
282}
283
284QString QLabel::text() const
285{
286 Q_D(const QLabel);
287 return d->text;
288}
289
290/*!
291 Clears any label contents.
292*/
293
294void QLabel::clear()
295{
296 Q_D(QLabel);
297 d->clearContents();
298 d->updateLabel();
299}
300
301/*!
302 \property QLabel::pixmap
303 \brief the label's pixmap.
304
305 Setting the pixmap clears any previous content. The buddy
306 shortcut, if any, is disabled.
307*/
308void QLabel::setPixmap(const QPixmap &pixmap)
309{
310 Q_D(QLabel);
311 if (d->icon && d->icon->availableSizes().contains(t: pixmap.size()) &&
312 d->icon->pixmap(size: pixmap.size()).cacheKey() == pixmap.cacheKey())
313 return;
314 d->clearContents();
315 d->icon = QIcon(pixmap);
316 d->pixmapSize = pixmap.deviceIndependentSize().toSize();
317 d->updateLabel();
318}
319
320QPixmap QLabel::pixmap() const
321{
322 Q_D(const QLabel);
323 return d->icon ? d->icon->pixmap(size: d->pixmapSize) : QPixmap();
324}
325
326/*!
327 \fn QPixmap QLabel::pixmap(Qt::ReturnByValueConstant) const
328
329 \deprecated Use the overload without argument instead.
330 \since 5.15
331
332 Returns the label's pixmap.
333
334 Previously, Qt provided a version of \c pixmap() which returned the pixmap
335 by-pointer. That version has now been removed. This overload allowed to
336 explicitly differentiate between the by-pointer function and the by-value.
337
338 \code
339 QPixmap pixmapVal = label->pixmap(Qt::ReturnByValue);
340 \endcode
341*/
342
343#ifndef QT_NO_PICTURE
344/*!
345 Sets the label contents to \a picture. Any previous content is
346 cleared.
347
348 The buddy shortcut, if any, is disabled.
349
350 \sa picture(), setBuddy()
351*/
352
353void QLabel::setPicture(const QPicture &picture)
354{
355 Q_D(QLabel);
356 d->clearContents();
357 d->picture = picture;
358
359 d->updateLabel();
360}
361#endif // QT_NO_PICTURE
362
363/*!
364 Sets the label contents to plain text containing the textual
365 representation of integer \a num. Any previous content is cleared.
366 Does nothing if the integer's string representation is the same as
367 the current contents of the label.
368
369 The buddy shortcut, if any, is disabled.
370
371 \sa setText(), QString::setNum(), setBuddy()
372*/
373
374void QLabel::setNum(int num)
375{
376 setText(QString::number(num));
377}
378
379/*!
380 \overload
381
382 Sets the label contents to plain text containing the textual
383 representation of double \a num. Any previous content is cleared.
384 Does nothing if the double's string representation is the same as
385 the current contents of the label.
386
387 The buddy shortcut, if any, is disabled.
388
389 \sa setText(), QString::setNum(), setBuddy()
390*/
391
392void QLabel::setNum(double num)
393{
394 setText(QString::number(num));
395}
396
397/*!
398 \property QLabel::alignment
399 \brief the alignment of the label's contents
400
401 By default, the contents of the label are left-aligned and vertically-centered.
402
403 \sa text
404*/
405
406void QLabel::setAlignment(Qt::Alignment alignment)
407{
408 Q_D(QLabel);
409 if (alignment == (d->align & (Qt::AlignVertical_Mask|Qt::AlignHorizontal_Mask)))
410 return;
411 d->align = (d->align & ~(Qt::AlignVertical_Mask|Qt::AlignHorizontal_Mask))
412 | (alignment & (Qt::AlignVertical_Mask|Qt::AlignHorizontal_Mask));
413
414 d->updateLabel();
415}
416
417
418Qt::Alignment QLabel::alignment() const
419{
420 Q_D(const QLabel);
421 return QFlag(d->align & (Qt::AlignVertical_Mask|Qt::AlignHorizontal_Mask));
422}
423
424
425/*!
426 \property QLabel::wordWrap
427 \brief the label's word-wrapping policy
428
429 If this property is \c true then label text is wrapped where
430 necessary at word-breaks; otherwise it is not wrapped at all.
431
432 By default, word wrap is disabled.
433
434 \sa text
435*/
436void QLabel::setWordWrap(bool on)
437{
438 Q_D(QLabel);
439 if (on)
440 d->align |= Qt::TextWordWrap;
441 else
442 d->align &= ~Qt::TextWordWrap;
443
444 d->updateLabel();
445}
446
447bool QLabel::wordWrap() const
448{
449 Q_D(const QLabel);
450 return d->align & Qt::TextWordWrap;
451}
452
453/*!
454 \property QLabel::indent
455 \brief the label's text indent in pixels
456
457 If a label displays text, the indent applies to the left edge if
458 alignment() is Qt::AlignLeft, to the right edge if alignment() is
459 Qt::AlignRight, to the top edge if alignment() is Qt::AlignTop, and
460 to the bottom edge if alignment() is Qt::AlignBottom.
461
462 If indent is negative, or if no indent has been set, the label
463 computes the effective indent as follows: If frameWidth() is 0,
464 the effective indent becomes 0. If frameWidth() is greater than 0,
465 the effective indent becomes half the width of the "x" character
466 of the widget's current font().
467
468 By default, the indent is -1, meaning that an effective indent is
469 calculating in the manner described above.
470
471 \sa alignment, margin, frameWidth(), font()
472*/
473
474void QLabel::setIndent(int indent)
475{
476 Q_D(QLabel);
477 d->indent = indent;
478 d->updateLabel();
479}
480
481int QLabel::indent() const
482{
483 Q_D(const QLabel);
484 return d->indent;
485}
486
487
488/*!
489 \property QLabel::margin
490 \brief the width of the margin
491
492 The margin is the distance between the innermost pixel of the
493 frame and the outermost pixel of contents.
494
495 The default margin is 0.
496
497 \sa indent
498*/
499int QLabel::margin() const
500{
501 Q_D(const QLabel);
502 return d->margin;
503}
504
505void QLabel::setMargin(int margin)
506{
507 Q_D(QLabel);
508 if (d->margin == margin)
509 return;
510 d->margin = margin;
511 d->updateLabel();
512}
513
514/*!
515 Returns the size that will be used if the width of the label is \a
516 w. If \a w is -1, the sizeHint() is returned. If \a w is 0 minimumSizeHint() is returned
517*/
518QSize QLabelPrivate::sizeForWidth(int w) const
519{
520 Q_Q(const QLabel);
521 if (q->minimumWidth() > 0)
522 w = qMax(a: w, b: q->minimumWidth());
523 QSize contentsMargin(leftmargin + rightmargin, topmargin + bottommargin);
524
525 QRect br;
526
527 int hextra = 2 * margin;
528 int vextra = hextra;
529 QFontMetrics fm = q->fontMetrics();
530
531 if (icon && !icon->isNull()) {
532 br = QRect(QPoint(0, 0), pixmapSize);
533#ifndef QT_NO_PICTURE
534 } else if (picture && !picture->isNull()) {
535 br = picture->boundingRect();
536#endif
537#if QT_CONFIG(movie)
538 } else if (movie && !movie->currentPixmap().isNull()) {
539 br = movie->currentPixmap().rect();
540 br.setSize(movie->currentPixmap().deviceIndependentSize().toSize());
541#endif
542 } else if (isTextLabel) {
543 int align = QStyle::visualAlignment(direction: textDirection(), alignment: QFlag(this->align));
544 // Add indentation
545 int m = indent;
546
547 if (m < 0 && q->frameWidth()) // no indent, but we do have a frame
548 m = fm.horizontalAdvance(u'x') - margin*2;
549 if (m > 0) {
550 if ((align & Qt::AlignLeft) || (align & Qt::AlignRight))
551 hextra += m;
552 if ((align & Qt::AlignTop) || (align & Qt::AlignBottom))
553 vextra += m;
554 }
555
556 if (control) {
557 ensureTextLayouted();
558 const qreal oldTextWidth = control->textWidth();
559 // Calculate the length of document if w is the width
560 if (align & Qt::TextWordWrap) {
561 if (w >= 0) {
562 w = qMax(a: w-hextra-contentsMargin.width(), b: 0); // strip margin and indent
563 control->setTextWidth(w);
564 } else {
565 control->adjustSize();
566 }
567 } else {
568 control->setTextWidth(-1);
569 }
570
571 QSizeF controlSize = control->size();
572 br = QRect(QPoint(0, 0), QSize(qCeil(v: controlSize.width()), qCeil(v: controlSize.height())));
573
574 // restore state
575 control->setTextWidth(oldTextWidth);
576 } else {
577 // Turn off center alignment in order to avoid rounding errors for centering,
578 // since centering involves a division by 2. At the end, all we want is the size.
579 int flags = align & ~(Qt::AlignVCenter | Qt::AlignHCenter);
580 if (hasShortcut) {
581 flags |= Qt::TextShowMnemonic;
582 QStyleOption opt;
583 opt.initFrom(w: q);
584 if (!q->style()->styleHint(stylehint: QStyle::SH_UnderlineShortcut, opt: &opt, widget: q))
585 flags |= Qt::TextHideMnemonic;
586 }
587
588 bool tryWidth = (w < 0) && (align & Qt::TextWordWrap);
589 if (tryWidth)
590 w = qMin(a: fm.averageCharWidth() * 80, b: q->maximumSize().width());
591 else if (w < 0)
592 w = 2000;
593 w -= (hextra + contentsMargin.width());
594 br = fm.boundingRect(x: 0, y: 0, w ,h: 2000, flags, text);
595 if (tryWidth && br.height() < 4*fm.lineSpacing() && br.width() > w/2)
596 br = fm.boundingRect(x: 0, y: 0, w: w/2, h: 2000, flags, text);
597 if (tryWidth && br.height() < 2*fm.lineSpacing() && br.width() > w/4)
598 br = fm.boundingRect(x: 0, y: 0, w: w/4, h: 2000, flags, text);
599 }
600 } else {
601 br = QRect(QPoint(0, 0), QSize(fm.averageCharWidth(), fm.lineSpacing()));
602 }
603
604 const QSize contentsSize(br.width() + hextra, br.height() + vextra);
605 return (contentsSize + contentsMargin).expandedTo(otherSize: q->minimumSize());
606}
607
608
609/*!
610 \reimp
611*/
612
613int QLabel::heightForWidth(int w) const
614{
615 Q_D(const QLabel);
616 if (d->isTextLabel)
617 return d->sizeForWidth(w).height();
618 return QWidget::heightForWidth(w);
619}
620
621/*!
622 \property QLabel::openExternalLinks
623 \since 4.2
624
625 Specifies whether QLabel should automatically open links using
626 QDesktopServices::openUrl() instead of emitting the
627 linkActivated() signal.
628
629 \b{Note:} The textInteractionFlags set on the label need to include
630 either LinksAccessibleByMouse or LinksAccessibleByKeyboard.
631
632 The default value is false.
633
634 \sa textInteractionFlags()
635*/
636bool QLabel::openExternalLinks() const
637{
638 Q_D(const QLabel);
639 return d->openExternalLinks;
640}
641
642void QLabel::setOpenExternalLinks(bool open)
643{
644 Q_D(QLabel);
645 d->openExternalLinks = open;
646 if (d->control)
647 d->control->setOpenExternalLinks(open);
648}
649
650/*!
651 \property QLabel::textInteractionFlags
652 \since 4.2
653
654 Specifies how the label should interact with user input if it displays text.
655
656 If the flags contain Qt::LinksAccessibleByKeyboard the focus policy is also
657 automatically set to Qt::StrongFocus. If Qt::TextSelectableByKeyboard is set
658 then the focus policy is set to Qt::ClickFocus.
659
660 The default value is Qt::LinksAccessibleByMouse.
661*/
662void QLabel::setTextInteractionFlags(Qt::TextInteractionFlags flags)
663{
664 Q_D(QLabel);
665 if (d->textInteractionFlags == flags)
666 return;
667 d->textInteractionFlags = flags;
668 if (flags & Qt::LinksAccessibleByKeyboard)
669 setFocusPolicy(Qt::StrongFocus);
670 else if (flags & (Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse))
671 setFocusPolicy(Qt::ClickFocus);
672 else
673 setFocusPolicy(Qt::NoFocus);
674
675 if (d->needTextControl()) {
676 d->ensureTextControl();
677 } else {
678 delete d->control;
679 d->control = nullptr;
680 }
681
682 if (d->control)
683 d->control->setTextInteractionFlags(d->textInteractionFlags);
684}
685
686Qt::TextInteractionFlags QLabel::textInteractionFlags() const
687{
688 Q_D(const QLabel);
689 return d->textInteractionFlags;
690}
691
692/*!
693 Selects text from position \a start and for \a length characters.
694
695 \sa selectedText()
696
697 \b{Note:} The textInteractionFlags set on the label need to include
698 either TextSelectableByMouse or TextSelectableByKeyboard.
699
700 \since 4.7
701*/
702void QLabel::setSelection(int start, int length)
703{
704 Q_D(QLabel);
705 if (d->control) {
706 d->ensureTextPopulated();
707 QTextCursor cursor = d->control->textCursor();
708 cursor.setPosition(pos: start);
709 cursor.setPosition(pos: start + length, mode: QTextCursor::KeepAnchor);
710 d->control->setTextCursor(cursor);
711 }
712}
713
714/*!
715 \property QLabel::hasSelectedText
716 \brief whether there is any text selected
717
718 hasSelectedText() returns \c true if some or all of the text has been
719 selected by the user; otherwise returns \c false.
720
721 By default, this property is \c false.
722
723 \sa selectedText()
724
725 \b{Note:} The textInteractionFlags set on the label need to include
726 either TextSelectableByMouse or TextSelectableByKeyboard.
727
728 \since 4.7
729*/
730bool QLabel::hasSelectedText() const
731{
732 Q_D(const QLabel);
733 if (d->control)
734 return d->control->textCursor().hasSelection();
735 return false;
736}
737
738/*!
739 \property QLabel::selectedText
740 \brief the selected text
741
742 If there is no selected text this property's value is
743 an empty string.
744
745 By default, this property contains an empty string.
746
747 \sa hasSelectedText()
748
749 \b{Note:} The textInteractionFlags set on the label need to include
750 either TextSelectableByMouse or TextSelectableByKeyboard.
751
752 \since 4.7
753*/
754QString QLabel::selectedText() const
755{
756 Q_D(const QLabel);
757 if (d->control)
758 return d->control->textCursor().selectedText();
759 return QString();
760}
761
762/*!
763 selectionStart() returns the index of the first selected character in the
764 label or -1 if no text is selected.
765
766 \sa selectedText()
767
768 \b{Note:} The textInteractionFlags set on the label need to include
769 either TextSelectableByMouse or TextSelectableByKeyboard.
770
771 \since 4.7
772*/
773int QLabel::selectionStart() const
774{
775 Q_D(const QLabel);
776 if (d->control && d->control->textCursor().hasSelection())
777 return d->control->textCursor().selectionStart();
778 return -1;
779}
780
781/*!\reimp
782*/
783QSize QLabel::sizeHint() const
784{
785 Q_D(const QLabel);
786 if (!d->valid_hints)
787 (void) QLabel::minimumSizeHint();
788 return d->sh;
789}
790
791/*!
792 \reimp
793*/
794QSize QLabel::minimumSizeHint() const
795{
796 Q_D(const QLabel);
797 if (d->valid_hints) {
798 if (d->sizePolicy == sizePolicy())
799 return d->msh;
800 }
801
802 ensurePolished();
803 d->valid_hints = true;
804 d->sh = d->sizeForWidth(w: -1); // wrap ? golden ratio : min doc size
805 QSize msh(-1, -1);
806
807 if (!d->isTextLabel) {
808 msh = d->sh;
809 } else {
810 msh.rheight() = d->sizeForWidth(QWIDGETSIZE_MAX).height(); // height for one line
811 msh.rwidth() = d->sizeForWidth(w: 0).width(); // wrap ? size of biggest word : min doc size
812 if (d->sh.height() < msh.height())
813 msh.rheight() = d->sh.height();
814 }
815 d->msh = msh;
816 d->sizePolicy = sizePolicy();
817 return msh;
818}
819
820/*!\reimp
821*/
822void QLabel::mousePressEvent(QMouseEvent *ev)
823{
824 Q_D(QLabel);
825 d->sendControlEvent(e: ev);
826}
827
828/*!\reimp
829*/
830void QLabel::mouseMoveEvent(QMouseEvent *ev)
831{
832 Q_D(QLabel);
833 d->sendControlEvent(e: ev);
834}
835
836/*!\reimp
837*/
838void QLabel::mouseReleaseEvent(QMouseEvent *ev)
839{
840 Q_D(QLabel);
841 d->sendControlEvent(e: ev);
842}
843
844#ifndef QT_NO_CONTEXTMENU
845/*!\reimp
846*/
847void QLabel::contextMenuEvent(QContextMenuEvent *ev)
848{
849 Q_D(QLabel);
850 if (!d->isTextLabel) {
851 ev->ignore();
852 return;
853 }
854 QMenu *menu = d->createStandardContextMenu(pos: ev->pos());
855 if (!menu) {
856 ev->ignore();
857 return;
858 }
859 ev->accept();
860 menu->setAttribute(Qt::WA_DeleteOnClose);
861 menu->popup(pos: ev->globalPos());
862}
863#endif // QT_NO_CONTEXTMENU
864
865/*!
866 \reimp
867*/
868void QLabel::focusInEvent(QFocusEvent *ev)
869{
870 Q_D(QLabel);
871 if (d->isTextLabel) {
872 d->ensureTextControl();
873 d->sendControlEvent(e: ev);
874 }
875 QFrame::focusInEvent(event: ev);
876}
877
878/*!
879 \reimp
880*/
881void QLabel::focusOutEvent(QFocusEvent *ev)
882{
883 Q_D(QLabel);
884 if (d->control) {
885 d->sendControlEvent(e: ev);
886 QTextCursor cursor = d->control->textCursor();
887 Qt::FocusReason reason = ev->reason();
888 if (reason != Qt::ActiveWindowFocusReason
889 && reason != Qt::PopupFocusReason
890 && cursor.hasSelection()) {
891 cursor.clearSelection();
892 d->control->setTextCursor(cursor);
893 }
894 }
895
896 QFrame::focusOutEvent(event: ev);
897}
898
899/*!\reimp
900*/
901bool QLabel::focusNextPrevChild(bool next)
902{
903 Q_D(QLabel);
904 if (d->control && d->control->setFocusToNextOrPreviousAnchor(next))
905 return true;
906 return QFrame::focusNextPrevChild(next);
907}
908
909/*!\reimp
910*/
911void QLabel::keyPressEvent(QKeyEvent *ev)
912{
913 Q_D(QLabel);
914 d->sendControlEvent(e: ev);
915}
916
917/*!\reimp
918*/
919bool QLabel::event(QEvent *e)
920{
921 Q_D(QLabel);
922 QEvent::Type type = e->type();
923
924#ifndef QT_NO_SHORTCUT
925 if (type == QEvent::Shortcut) {
926 QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
927 if (se->shortcutId() == d->shortcutId) {
928 QWidget *w = d->buddy;
929 if (!w)
930 return QFrame::event(e);
931 if (w->focusPolicy() != Qt::NoFocus)
932 w->setFocus(Qt::ShortcutFocusReason);
933#if QT_CONFIG(abstractbutton)
934 QAbstractButton *button = qobject_cast<QAbstractButton *>(object: w);
935 if (button && !se->isAmbiguous())
936 button->animateClick();
937 else
938#endif
939 window()->setAttribute(Qt::WA_KeyboardFocusChange);
940 return true;
941 }
942 } else
943#endif
944 if (type == QEvent::Resize) {
945 if (d->control)
946 d->textLayoutDirty = true;
947 } else if (e->type() == QEvent::StyleChange
948#ifdef Q_OS_MAC
949 || e->type() == QEvent::MacSizeChange
950#endif
951 ) {
952 d->setLayoutItemMargins(element: QStyle::SE_LabelLayoutItem);
953 d->updateLabel();
954 } else if (type == QEvent::Polish) {
955 if (d->needTextControl())
956 d->ensureTextControl();
957 }
958
959 return QFrame::event(e);
960}
961
962/*!\reimp
963*/
964void QLabel::paintEvent(QPaintEvent *)
965{
966 Q_D(QLabel);
967 QStyle *style = QWidget::style();
968 QPainter painter(this);
969 drawFrame(&painter);
970 QRect cr = contentsRect();
971 cr.adjust(dx1: d->margin, dy1: d->margin, dx2: -d->margin, dy2: -d->margin);
972 int align = QStyle::visualAlignment(direction: d->isTextLabel ? d->textDirection()
973 : layoutDirection(), alignment: QFlag(d->align));
974
975#if QT_CONFIG(movie)
976 if (d->movie && !d->movie->currentPixmap().isNull()) {
977 if (d->scaledcontents)
978 style->drawItemPixmap(painter: &painter, rect: cr, alignment: align, pixmap: d->movie->currentPixmap().scaled(s: cr.size()));
979 else
980 style->drawItemPixmap(painter: &painter, rect: cr, alignment: align, pixmap: d->movie->currentPixmap());
981 }
982 else
983#endif
984 if (d->isTextLabel) {
985 QRectF lr = d->layoutRect().toAlignedRect();
986 QStyleOption opt;
987 opt.initFrom(w: this);
988#if QT_CONFIG(style_stylesheet)
989 if (QStyleSheetStyle* cssStyle = qt_styleSheet(style))
990 cssStyle->styleSheetPalette(w: this, opt: &opt, pal: &opt.palette);
991#endif
992 if (d->control) {
993#ifndef QT_NO_SHORTCUT
994 const bool underline = static_cast<bool>(style->styleHint(stylehint: QStyle::SH_UnderlineShortcut,
995 opt: nullptr, widget: this, returnData: nullptr));
996 if (d->shortcutId != 0
997 && underline != d->shortcutCursor.charFormat().fontUnderline()) {
998 QTextCharFormat fmt;
999 fmt.setFontUnderline(underline);
1000 d->shortcutCursor.mergeCharFormat(modifier: fmt);
1001 }
1002#endif
1003 d->ensureTextLayouted();
1004
1005 QAbstractTextDocumentLayout::PaintContext context;
1006 // Adjust the palette
1007 context.palette = opt.palette;
1008
1009 if (foregroundRole() != QPalette::Text && isEnabled())
1010 context.palette.setColor(acr: QPalette::Text, acolor: context.palette.color(cr: foregroundRole()));
1011
1012 painter.save();
1013 painter.translate(offset: lr.topLeft());
1014 painter.setClipRect(lr.translated(dx: -lr.x(), dy: -lr.y()));
1015 d->control->setPalette(context.palette);
1016 d->control->drawContents(painter: &painter, rect: QRectF(), widget: this);
1017 painter.restore();
1018 } else {
1019 int flags = align | (d->textDirection() == Qt::LeftToRight ? Qt::TextForceLeftToRight
1020 : Qt::TextForceRightToLeft);
1021 if (d->hasShortcut) {
1022 flags |= Qt::TextShowMnemonic;
1023 if (!style->styleHint(stylehint: QStyle::SH_UnderlineShortcut, opt: &opt, widget: this))
1024 flags |= Qt::TextHideMnemonic;
1025 }
1026 style->drawItemText(painter: &painter, rect: lr.toRect(), flags, pal: opt.palette, enabled: isEnabled(), text: d->text, textRole: foregroundRole());
1027 }
1028 } else
1029#ifndef QT_NO_PICTURE
1030 if (d->picture) {
1031 QRect br = d->picture->boundingRect();
1032 int rw = br.width();
1033 int rh = br.height();
1034 if (d->scaledcontents) {
1035 painter.save();
1036 painter.translate(dx: cr.x(), dy: cr.y());
1037 painter.scale(sx: (double)cr.width()/rw, sy: (double)cr.height()/rh);
1038 painter.drawPicture(x: -br.x(), y: -br.y(), p: *d->picture);
1039 painter.restore();
1040 } else {
1041 int xo = 0;
1042 int yo = 0;
1043 if (align & Qt::AlignVCenter)
1044 yo = (cr.height()-rh)/2;
1045 else if (align & Qt::AlignBottom)
1046 yo = cr.height()-rh;
1047 if (align & Qt::AlignRight)
1048 xo = cr.width()-rw;
1049 else if (align & Qt::AlignHCenter)
1050 xo = (cr.width()-rw)/2;
1051 painter.drawPicture(x: cr.x()+xo-br.x(), y: cr.y()+yo-br.y(), p: *d->picture);
1052 }
1053 } else
1054#endif
1055 if (d->icon && !d->icon->isNull()) {
1056 const qreal dpr = devicePixelRatio();
1057 const QSize size = d->scaledcontents ? cr.size() : d->pixmapSize;
1058 const auto mode = isEnabled() ? QIcon::Normal : QIcon::Disabled;
1059 QPixmap pix = d->icon->pixmap(size, devicePixelRatio: dpr, mode);
1060 // the size of the returned pixmap might not match when
1061 // - scaledContents is enabled
1062 // - the dpr does not match the one from the pixmap in QIcon
1063 // since QStyle::drawItemPixmap() stretches without Qt::SmoothTransformation
1064 // we do it here
1065 if (pix.size() != size * dpr) {
1066 const QString key = "qt_label_"_L1 % HexString<quint64>(pix.cacheKey())
1067 % HexString<quint8>(mode)
1068 % HexString<uint>(size.width())
1069 % HexString<uint>(size.height())
1070 % HexString<quint16>(qRound(d: dpr * 1000));
1071 if (!QPixmapCache::find(key, pixmap: &pix)) {
1072 pix = pix.scaled(s: size * dpr, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation);
1073 pix.setDevicePixelRatio(dpr);
1074 // using QIcon to cache the newly create pixmap is not possible
1075 // because QIcon does not clear this cache (so we grow indefinitely)
1076 // and also uses the newly added pixmap as starting point for new
1077 // scaled pixmap which makes it very blurry.
1078 // Therefore use QPixmapCache here.
1079 QPixmapCache::insert(key, pixmap: pix);
1080 }
1081 }
1082 QStyleOption opt;
1083 opt.initFrom(w: this);
1084 style->drawItemPixmap(painter: &painter, rect: cr, alignment: align, pixmap: pix);
1085 }
1086}
1087
1088
1089/*!
1090 Updates the label, but not the frame.
1091*/
1092
1093void QLabelPrivate::updateLabel()
1094{
1095 Q_Q(QLabel);
1096 valid_hints = false;
1097
1098 if (isTextLabel) {
1099 QSizePolicy policy = q->sizePolicy();
1100 const bool wrap = align & Qt::TextWordWrap;
1101 policy.setHeightForWidth(wrap);
1102 if (policy != q->sizePolicy()) // ### should be replaced by WA_WState_OwnSizePolicy idiom
1103 q->setSizePolicy(policy);
1104 textLayoutDirty = true;
1105 }
1106 q->updateGeometry();
1107 q->update(q->contentsRect());
1108}
1109
1110#ifndef QT_NO_SHORTCUT
1111/*!
1112 Sets this label's buddy to \a buddy.
1113
1114 When the user presses the shortcut key indicated by this label,
1115 the keyboard focus is transferred to the label's buddy widget.
1116
1117 The buddy mechanism is only available for QLabels that contain
1118 text in which one character is prefixed with an ampersand, '&'.
1119 This character is set as the shortcut key. See the \l
1120 QKeySequence::mnemonic() documentation for details (to display an
1121 actual ampersand, use '&&').
1122
1123 In a dialog, you might create two data entry widgets and a label
1124 for each, and set up the geometry layout so each label is just to
1125 the left of its data entry widget (its "buddy"), for example:
1126 \snippet code/src_gui_widgets_qlabel.cpp 2
1127
1128 With the code above, the focus jumps to the Name field when the
1129 user presses Alt+N, and to the Phone field when the user presses
1130 Alt+P.
1131
1132 To unset a previously set buddy, call this function with \a buddy
1133 set to nullptr.
1134
1135 \sa buddy(), setText(), QShortcut, setAlignment()
1136*/
1137
1138void QLabel::setBuddy(QWidget *buddy)
1139{
1140 Q_D(QLabel);
1141
1142 if (d->buddy)
1143 QObjectPrivate::disconnect(sender: d->buddy, signal: &QObject::destroyed,
1144 receiverPrivate: d, slot: &QLabelPrivate::buddyDeleted);
1145
1146 d->buddy = buddy;
1147
1148 if (buddy)
1149 QObjectPrivate::connect(sender: buddy, signal: &QObject::destroyed,
1150 receiverPrivate: d, slot: &QLabelPrivate::buddyDeleted);
1151
1152 if (d->isTextLabel) {
1153 if (d->shortcutId)
1154 releaseShortcut(id: d->shortcutId);
1155 d->shortcutId = 0;
1156 d->textDirty = true;
1157 if (buddy)
1158 d->updateShortcut(); // grab new shortcut
1159 d->updateLabel();
1160 }
1161}
1162
1163
1164/*!
1165 Returns this label's buddy, or nullptr if no buddy is currently set.
1166
1167 \sa setBuddy()
1168*/
1169
1170QWidget * QLabel::buddy() const
1171{
1172 Q_D(const QLabel);
1173 return d->buddy;
1174}
1175
1176void QLabelPrivate::updateShortcut()
1177{
1178 Q_Q(QLabel);
1179 Q_ASSERT(shortcutId == 0);
1180 // Introduce an extra boolean to indicate the presence of a shortcut in the
1181 // text. We cannot use the shortcutId itself because on the mac mnemonics are
1182 // off by default, so QKeySequence::mnemonic always returns an empty sequence.
1183 // But then we do want to hide the ampersands, so we can't use shortcutId.
1184 hasShortcut = false;
1185
1186 if (!text.contains(c: u'&'))
1187 return;
1188 hasShortcut = true;
1189 shortcutId = q->grabShortcut(key: QKeySequence::mnemonic(text));
1190}
1191
1192
1193void QLabelPrivate::buddyDeleted()
1194{
1195 Q_Q(QLabel);
1196 q->setBuddy(nullptr);
1197}
1198
1199#endif // QT_NO_SHORTCUT
1200
1201#if QT_CONFIG(movie)
1202void QLabelPrivate::movieUpdated(const QRect &rect)
1203{
1204 Q_Q(QLabel);
1205 if (movie && movie->isValid()) {
1206 QRect r;
1207 if (scaledcontents) {
1208 QRect cr = q->contentsRect();
1209 QRect pixmapRect(cr.topLeft(), movie->currentPixmap().size());
1210 if (pixmapRect.isEmpty())
1211 return;
1212 r.setRect(ax: cr.left(), ay: cr.top(),
1213 aw: (rect.width() * cr.width()) / pixmapRect.width(),
1214 ah: (rect.height() * cr.height()) / pixmapRect.height());
1215 } else {
1216 r = q->style()->itemPixmapRect(r: q->contentsRect(), flags: align, pixmap: movie->currentPixmap());
1217 r.translate(dx: rect.x(), dy: rect.y());
1218 r.setWidth(qMin(a: r.width(), b: rect.width()));
1219 r.setHeight(qMin(a: r.height(), b: rect.height()));
1220 }
1221 q->update(r);
1222 }
1223}
1224
1225void QLabelPrivate::movieResized(const QSize &size)
1226{
1227 Q_Q(QLabel);
1228 q->update(); //we need to refresh the whole background in case the new size is smaller
1229 valid_hints = false;
1230 movieUpdated(rect: QRect(QPoint(0,0), size));
1231 q->updateGeometry();
1232}
1233
1234/*!
1235 Sets the label contents to \a movie. Any previous content is
1236 cleared. The label does NOT take ownership of the movie.
1237
1238 The buddy shortcut, if any, is disabled.
1239
1240 \sa movie(), setBuddy()
1241*/
1242
1243void QLabel::setMovie(QMovie *movie)
1244{
1245 Q_D(QLabel);
1246 d->clearContents();
1247
1248 if (!movie)
1249 return;
1250
1251 d->movie = movie;
1252 d->movieConnections = {
1253 QObjectPrivate::connect(sender: movie, signal: &QMovie::resized, receiverPrivate: d, slot: &QLabelPrivate::movieResized),
1254 QObjectPrivate::connect(sender: movie, signal: &QMovie::updated, receiverPrivate: d, slot: &QLabelPrivate::movieUpdated),
1255 };
1256
1257 // Assume that if the movie is running,
1258 // resize/update signals will come soon enough
1259 if (movie->state() != QMovie::Running)
1260 d->updateLabel();
1261}
1262
1263#endif // QT_CONFIG(movie)
1264
1265/*!
1266 \internal
1267
1268 Clears any contents, without updating/repainting the label.
1269*/
1270
1271void QLabelPrivate::clearContents()
1272{
1273 delete control;
1274 control = nullptr;
1275 isTextLabel = false;
1276 hasShortcut = false;
1277
1278#ifndef QT_NO_PICTURE
1279 picture.reset();
1280#endif
1281 icon.reset();
1282 pixmapSize = QSize();
1283
1284 text.clear();
1285 Q_Q(QLabel);
1286#ifndef QT_NO_SHORTCUT
1287 if (shortcutId)
1288 q->releaseShortcut(id: shortcutId);
1289 shortcutId = 0;
1290#endif
1291#if QT_CONFIG(movie)
1292 for (const auto &conn : std::as_const(t&: movieConnections))
1293 QObject::disconnect(conn);
1294 movie = nullptr;
1295#endif
1296#ifndef QT_NO_CURSOR
1297 if (onAnchor) {
1298 if (validCursor)
1299 q->setCursor(cursor);
1300 else
1301 q->unsetCursor();
1302 }
1303 validCursor = false;
1304 onAnchor = false;
1305#endif
1306}
1307
1308
1309#if QT_CONFIG(movie)
1310
1311/*!
1312 Returns a pointer to the label's movie, or nullptr if no movie has been
1313 set.
1314
1315 \sa setMovie()
1316*/
1317
1318QMovie *QLabel::movie() const
1319{
1320 Q_D(const QLabel);
1321 return d->movie;
1322}
1323
1324#endif // QT_CONFIG(movie)
1325
1326/*!
1327 \property QLabel::textFormat
1328 \brief the label's text format
1329
1330 See the Qt::TextFormat enum for an explanation of the possible
1331 options.
1332
1333 The default format is Qt::AutoText.
1334
1335 \sa text()
1336*/
1337
1338Qt::TextFormat QLabel::textFormat() const
1339{
1340 Q_D(const QLabel);
1341 return d->textformat;
1342}
1343
1344void QLabel::setTextFormat(Qt::TextFormat format)
1345{
1346 Q_D(QLabel);
1347 if (format != d->textformat) {
1348 d->textformat = format;
1349 QString t = d->text;
1350 if (!t.isNull()) {
1351 d->text.clear();
1352 setText(t);
1353 }
1354 }
1355}
1356
1357/*!
1358 \since 6.1
1359
1360 Returns the resource provider for rich text of this label.
1361*/
1362QTextDocument::ResourceProvider QLabel::resourceProvider() const
1363{
1364 Q_D(const QLabel);
1365 return d->control ? d->control->document()->resourceProvider() : d->resourceProvider;
1366}
1367
1368/*!
1369 \since 6.1
1370
1371 Sets the \a provider of resources for rich text of this label.
1372
1373 \note The label \e{does not} take ownership of the \a provider.
1374*/
1375void QLabel::setResourceProvider(const QTextDocument::ResourceProvider &provider)
1376{
1377 Q_D(QLabel);
1378 d->resourceProvider = provider;
1379 if (d->control != nullptr)
1380 d->control->document()->setResourceProvider(provider);
1381}
1382
1383/*!
1384 \reimp
1385*/
1386void QLabel::changeEvent(QEvent *ev)
1387{
1388 Q_D(QLabel);
1389 if (ev->type() == QEvent::FontChange || ev->type() == QEvent::ApplicationFontChange) {
1390 if (d->isTextLabel) {
1391 if (d->control)
1392 d->control->document()->setDefaultFont(font());
1393 d->updateLabel();
1394 }
1395 } else if (ev->type() == QEvent::PaletteChange && d->control) {
1396 d->control->setPalette(palette());
1397 } else if (ev->type() == QEvent::ContentsRectChange) {
1398 d->updateLabel();
1399 }
1400 QFrame::changeEvent(ev);
1401}
1402
1403/*!
1404 \property QLabel::scaledContents
1405 \brief whether the label will scale its contents to fill all
1406 available space.
1407
1408 When enabled and the label shows a pixmap, it will scale the
1409 pixmap to fill the available space.
1410
1411 This property's default is false.
1412*/
1413bool QLabel::hasScaledContents() const
1414{
1415 Q_D(const QLabel);
1416 return d->scaledcontents;
1417}
1418
1419void QLabel::setScaledContents(bool enable)
1420{
1421 Q_D(QLabel);
1422 if ((bool)d->scaledcontents == enable)
1423 return;
1424 d->scaledcontents = enable;
1425 update(contentsRect());
1426}
1427
1428Qt::LayoutDirection QLabelPrivate::textDirection() const
1429{
1430 if (control) {
1431 QTextOption opt = control->document()->defaultTextOption();
1432 return opt.textDirection();
1433 }
1434
1435 return text.isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight;
1436}
1437
1438
1439// Returns the rect that is available for us to draw the document
1440QRect QLabelPrivate::documentRect() const
1441{
1442 Q_Q(const QLabel);
1443 Q_ASSERT_X(isTextLabel, "documentRect", "document rect called for label that is not a text label!");
1444 QRect cr = q->contentsRect();
1445 cr.adjust(dx1: margin, dy1: margin, dx2: -margin, dy2: -margin);
1446 const int align = QStyle::visualAlignment(direction: isTextLabel ? textDirection()
1447 : q->layoutDirection(), alignment: QFlag(this->align));
1448 int m = indent;
1449 if (m < 0 && q->frameWidth()) // no indent, but we do have a frame
1450 m = q->fontMetrics().horizontalAdvance(u'x') / 2 - margin;
1451 if (m > 0) {
1452 if (align & Qt::AlignLeft)
1453 cr.setLeft(cr.left() + m);
1454 if (align & Qt::AlignRight)
1455 cr.setRight(cr.right() - m);
1456 if (align & Qt::AlignTop)
1457 cr.setTop(cr.top() + m);
1458 if (align & Qt::AlignBottom)
1459 cr.setBottom(cr.bottom() - m);
1460 }
1461 return cr;
1462}
1463
1464void QLabelPrivate::ensureTextPopulated() const
1465{
1466 if (!textDirty)
1467 return;
1468 if (control) {
1469 QTextDocument *doc = control->document();
1470 if (textDirty) {
1471 if (effectiveTextFormat == Qt::PlainText) {
1472 doc->setPlainText(text);
1473#if QT_CONFIG(texthtmlparser)
1474 } else if (effectiveTextFormat == Qt::RichText) {
1475 doc->setHtml(text);
1476#endif
1477#if QT_CONFIG(textmarkdownreader)
1478 } else if (effectiveTextFormat == Qt::MarkdownText) {
1479 doc->setMarkdown(markdown: text);
1480#endif
1481 } else {
1482 doc->setPlainText(text);
1483 }
1484 doc->setUndoRedoEnabled(false);
1485
1486#ifndef QT_NO_SHORTCUT
1487 if (hasShortcut) {
1488 // Underline the first character that follows an ampersand (and remove the others ampersands)
1489 int from = 0;
1490 bool found = false;
1491 QTextCursor cursor;
1492 while (!(cursor = control->document()->find(subString: ("&"_L1), from)).isNull()) {
1493 cursor.deleteChar(); // remove the ampersand
1494 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1495 from = cursor.position();
1496 if (!found && cursor.selectedText() != "&"_L1) { //not a second &
1497 found = true;
1498 shortcutCursor = cursor;
1499 }
1500 }
1501 }
1502#endif
1503 }
1504 }
1505 textDirty = false;
1506}
1507
1508void QLabelPrivate::ensureTextLayouted() const
1509{
1510 if (!textLayoutDirty)
1511 return;
1512 ensureTextPopulated();
1513 if (control) {
1514 QTextDocument *doc = control->document();
1515 QTextOption opt = doc->defaultTextOption();
1516
1517 opt.setAlignment(QFlag(this->align));
1518
1519 if (this->align & Qt::TextWordWrap)
1520 opt.setWrapMode(QTextOption::WordWrap);
1521 else
1522 opt.setWrapMode(QTextOption::ManualWrap);
1523
1524 doc->setDefaultTextOption(opt);
1525
1526 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
1527 fmt.setMargin(0);
1528 doc->rootFrame()->setFrameFormat(fmt);
1529 doc->setTextWidth(documentRect().width());
1530 }
1531 textLayoutDirty = false;
1532}
1533
1534void QLabelPrivate::ensureTextControl() const
1535{
1536 Q_Q(const QLabel);
1537 if (!isTextLabel)
1538 return;
1539 if (!control) {
1540 control = new QWidgetTextControl(const_cast<QLabel *>(q));
1541 control->document()->setUndoRedoEnabled(false);
1542 control->document()->setDefaultFont(q->font());
1543 if (resourceProvider != nullptr)
1544 control->document()->setResourceProvider(resourceProvider);
1545 control->setTextInteractionFlags(textInteractionFlags);
1546 control->setOpenExternalLinks(openExternalLinks);
1547 control->setPalette(q->palette());
1548 control->setFocus(focus: q->hasFocus());
1549 QObject::connect(sender: control, signal: &QWidgetTextControl::updateRequest,
1550 context: q, slot: qOverload<>(&QLabel::update));
1551 QObject::connect(sender: control, signal: &QWidgetTextControl::linkActivated,
1552 context: q, slot: &QLabel::linkActivated);
1553 QObjectPrivate::connect(sender: control, signal: &QWidgetTextControl::linkHovered,
1554 receiverPrivate: this, slot: &QLabelPrivate::linkHovered);
1555 textLayoutDirty = true;
1556 textDirty = true;
1557 }
1558}
1559
1560void QLabelPrivate::sendControlEvent(QEvent *e)
1561{
1562 Q_Q(QLabel);
1563 if (!isTextLabel || !control || textInteractionFlags == Qt::NoTextInteraction) {
1564 e->ignore();
1565 return;
1566 }
1567 control->processEvent(e, coordinateOffset: -layoutRect().topLeft(), contextWidget: q);
1568}
1569
1570void QLabelPrivate::linkHovered(const QString &anchor)
1571{
1572 Q_Q(QLabel);
1573#ifndef QT_NO_CURSOR
1574 if (anchor.isEmpty()) { // restore cursor
1575 if (validCursor)
1576 q->setCursor(cursor);
1577 else
1578 q->unsetCursor();
1579 onAnchor = false;
1580 } else if (!onAnchor) {
1581 validCursor = q->testAttribute(attribute: Qt::WA_SetCursor);
1582 if (validCursor) {
1583 cursor = q->cursor();
1584 }
1585 q->setCursor(Qt::PointingHandCursor);
1586 onAnchor = true;
1587 }
1588#endif
1589 emit q->linkHovered(link: anchor);
1590}
1591
1592// Return the layout rect - this is the rect that is given to the layout painting code
1593// This may be different from the document rect since vertical alignment is not
1594// done by the text layout code
1595QRectF QLabelPrivate::layoutRect() const
1596{
1597 QRectF cr = documentRect();
1598 if (!control)
1599 return cr;
1600 ensureTextLayouted();
1601 // Calculate y position manually
1602 qreal rh = control->document()->documentLayout()->documentSize().height();
1603 qreal yo = 0;
1604 if (align & Qt::AlignVCenter)
1605 yo = qMax(a: (cr.height()-rh)/2, b: qreal(0));
1606 else if (align & Qt::AlignBottom)
1607 yo = qMax(a: cr.height()-rh, b: qreal(0));
1608 return QRectF(cr.x(), yo + cr.y(), cr.width(), cr.height());
1609}
1610
1611// Returns the point in the document rect adjusted with p
1612QPoint QLabelPrivate::layoutPoint(const QPoint& p) const
1613{
1614 QRect lr = layoutRect().toRect();
1615 return p - lr.topLeft();
1616}
1617
1618#ifndef QT_NO_CONTEXTMENU
1619QMenu *QLabelPrivate::createStandardContextMenu(const QPoint &pos)
1620{
1621 if (!control)
1622 return nullptr;
1623
1624 const QPoint p = layoutPoint(p: pos);
1625 return control->createStandardContextMenu(pos: p, parent: q_func());
1626}
1627#endif
1628
1629/*!
1630 \fn void QLabel::linkHovered(const QString &link)
1631 \since 4.2
1632
1633 This signal is emitted when the user hovers over a link. The URL
1634 referred to by the anchor is passed in \a link.
1635
1636 \sa linkActivated()
1637*/
1638
1639
1640/*!
1641 \fn void QLabel::linkActivated(const QString &link)
1642 \since 4.2
1643
1644 This signal is emitted when the user clicks a link. The URL
1645 referred to by the anchor is passed in \a link.
1646
1647 \sa linkHovered()
1648*/
1649
1650QT_END_NAMESPACE
1651
1652#include "moc_qlabel.cpp"
1653

source code of qtbase/src/widgets/widgets/qlabel.cpp