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