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