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