1 | // Copyright (C) 2017 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qquickiconlabel_p.h" |
5 | #include "qquickiconlabel_p_p.h" |
6 | #include "qquickiconimage_p.h" |
7 | #include "qquickmnemoniclabel_p.h" |
8 | |
9 | #include <QtGui/private/qguiapplication_p.h> |
10 | #include <QtQuick/private/qquickitem_p.h> |
11 | #include <QtQuick/private/qquicktext_p.h> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | static void beginClass(QQuickItem *item) |
16 | { |
17 | if (QQmlParserStatus *parserStatus = qobject_cast<QQmlParserStatus *>(object: item)) |
18 | parserStatus->classBegin(); |
19 | } |
20 | |
21 | static void completeComponent(QQuickItem *item) |
22 | { |
23 | if (QQmlParserStatus *parserStatus = qobject_cast<QQmlParserStatus *>(object: item)) |
24 | parserStatus->componentComplete(); |
25 | } |
26 | |
27 | QQuickIconLabelPrivate::~QQuickIconLabelPrivate() = default; |
28 | |
29 | bool QQuickIconLabelPrivate::hasIcon() const |
30 | { |
31 | return display != QQuickIconLabel::TextOnly && !icon.isEmpty(); |
32 | } |
33 | |
34 | bool QQuickIconLabelPrivate::hasText() const |
35 | { |
36 | return display != QQuickIconLabel::IconOnly && !text.isEmpty(); |
37 | } |
38 | |
39 | bool QQuickIconLabelPrivate::createImage() |
40 | { |
41 | Q_Q(QQuickIconLabel); |
42 | if (image) |
43 | return false; |
44 | |
45 | image = new QQuickIconImage(q); |
46 | watchChanges(item: image); |
47 | beginClass(item: image); |
48 | image->setObjectName(QStringLiteral("image" )); |
49 | image->setName(icon.name()); |
50 | image->setSource(icon.resolvedSource()); |
51 | image->setSourceSize(QSize(icon.width(), icon.height())); |
52 | image->setColor(icon.color()); |
53 | image->setCache(icon.cache()); |
54 | QQmlEngine::setContextForObject(image, qmlContext(q)); |
55 | if (componentComplete) |
56 | completeComponent(item: image); |
57 | return true; |
58 | } |
59 | |
60 | bool QQuickIconLabelPrivate::destroyImage() |
61 | { |
62 | if (!image) |
63 | return false; |
64 | |
65 | unwatchChanges(item: image); |
66 | delete image; |
67 | image = nullptr; |
68 | return true; |
69 | } |
70 | |
71 | bool QQuickIconLabelPrivate::updateImage() |
72 | { |
73 | if (!hasIcon()) |
74 | return destroyImage(); |
75 | return createImage(); |
76 | } |
77 | |
78 | void QQuickIconLabelPrivate::syncImage() |
79 | { |
80 | if (!image || icon.isEmpty()) |
81 | return; |
82 | |
83 | image->setName(icon.name()); |
84 | image->setSource(icon.resolvedSource()); |
85 | image->setSourceSize(QSize(icon.width(), icon.height())); |
86 | image->setColor(icon.color()); |
87 | image->setCache(icon.cache()); |
88 | const int valign = alignment & Qt::AlignVertical_Mask; |
89 | image->setVerticalAlignment(static_cast<QQuickImage::VAlignment>(valign)); |
90 | const int halign = alignment & Qt::AlignHorizontal_Mask; |
91 | image->setHorizontalAlignment(static_cast<QQuickImage::HAlignment>(halign)); |
92 | } |
93 | |
94 | void QQuickIconLabelPrivate::updateOrSyncImage() |
95 | { |
96 | if (updateImage()) { |
97 | if (componentComplete) { |
98 | updateImplicitSize(); |
99 | layout(); |
100 | } |
101 | } else { |
102 | syncImage(); |
103 | } |
104 | } |
105 | |
106 | bool QQuickIconLabelPrivate::createLabel() |
107 | { |
108 | Q_Q(QQuickIconLabel); |
109 | if (label) |
110 | return false; |
111 | |
112 | label = new QQuickMnemonicLabel(q); |
113 | watchChanges(item: label); |
114 | beginClass(item: label); |
115 | label->setObjectName(QStringLiteral("label" )); |
116 | label->setFont(font); |
117 | label->setColor(color); |
118 | label->setElideMode(QQuickText::ElideRight); |
119 | const int valign = alignment & Qt::AlignVertical_Mask; |
120 | label->setVAlign(static_cast<QQuickText::VAlignment>(valign)); |
121 | const int halign = alignment & Qt::AlignHorizontal_Mask; |
122 | label->setHAlign(static_cast<QQuickText::HAlignment>(halign)); |
123 | label->setText(text); |
124 | if (componentComplete) |
125 | completeComponent(item: label); |
126 | return true; |
127 | } |
128 | |
129 | bool QQuickIconLabelPrivate::destroyLabel() |
130 | { |
131 | if (!label) |
132 | return false; |
133 | |
134 | unwatchChanges(item: label); |
135 | delete label; |
136 | label = nullptr; |
137 | return true; |
138 | } |
139 | |
140 | bool QQuickIconLabelPrivate::updateLabel() |
141 | { |
142 | if (!hasText()) |
143 | return destroyLabel(); |
144 | return createLabel(); |
145 | } |
146 | |
147 | void QQuickIconLabelPrivate::syncLabel() |
148 | { |
149 | if (!label) |
150 | return; |
151 | |
152 | label->setText(text); |
153 | } |
154 | |
155 | void QQuickIconLabelPrivate::updateOrSyncLabel() |
156 | { |
157 | if (updateLabel()) { |
158 | if (componentComplete) { |
159 | updateImplicitSize(); |
160 | layout(); |
161 | } |
162 | } else { |
163 | syncLabel(); |
164 | } |
165 | } |
166 | |
167 | void QQuickIconLabelPrivate::updateImplicitSize() |
168 | { |
169 | Q_Q(QQuickIconLabel); |
170 | const bool showIcon = image && hasIcon(); |
171 | const bool showText = label && hasText(); |
172 | const qreal horizontalPadding = leftPadding + rightPadding; |
173 | const qreal verticalPadding = topPadding + bottomPadding; |
174 | const qreal iconImplicitWidth = showIcon ? image->implicitWidth() : 0; |
175 | const qreal iconImplicitHeight = showIcon ? image->implicitHeight() : 0; |
176 | const qreal textImplicitWidth = showText ? label->implicitWidth() : 0; |
177 | const qreal textImplicitHeight = showText ? label->implicitHeight() : 0; |
178 | const qreal effectiveSpacing = showText && showIcon && image->implicitWidth() > 0 ? spacing : 0; |
179 | const qreal implicitWidth = display == QQuickIconLabel::TextBesideIcon ? iconImplicitWidth + textImplicitWidth + effectiveSpacing |
180 | : qMax(a: iconImplicitWidth, b: textImplicitWidth); |
181 | const qreal implicitHeight = display == QQuickIconLabel::TextUnderIcon ? iconImplicitHeight + textImplicitHeight + effectiveSpacing |
182 | : qMax(a: iconImplicitHeight, b: textImplicitHeight); |
183 | q->setImplicitSize(implicitWidth + horizontalPadding, implicitHeight + verticalPadding); |
184 | } |
185 | |
186 | // adapted from QStyle::alignedRect() |
187 | static QRectF alignedRect(bool mirrored, Qt::Alignment alignment, const QSizeF &size, const QRectF &rectangle) |
188 | { |
189 | alignment = QGuiApplicationPrivate::visualAlignment(direction: mirrored ? Qt::RightToLeft : Qt::LeftToRight, alignment); |
190 | qreal x = rectangle.x(); |
191 | qreal y = rectangle.y(); |
192 | const qreal w = size.width(); |
193 | const qreal h = size.height(); |
194 | if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter) |
195 | y += rectangle.height() / 2 - h / 2; |
196 | else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom) |
197 | y += rectangle.height() - h; |
198 | if ((alignment & Qt::AlignRight) == Qt::AlignRight) |
199 | x += rectangle.width() - w; |
200 | else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter) |
201 | x += rectangle.width() / 2 - w / 2; |
202 | return QRectF(x, y, w, h); |
203 | } |
204 | |
205 | void QQuickIconLabelPrivate::layout() |
206 | { |
207 | Q_Q(QQuickIconLabel); |
208 | if (!componentComplete) |
209 | return; |
210 | |
211 | const qreal availableWidth = width - leftPadding - rightPadding; |
212 | const qreal availableHeight = height - topPadding - bottomPadding; |
213 | |
214 | switch (display) { |
215 | case QQuickIconLabel::IconOnly: |
216 | if (image) { |
217 | const QRectF iconRect = alignedRect(mirrored, alignment, |
218 | size: QSizeF(qMin(a: image->implicitWidth(), b: availableWidth), |
219 | qMin(a: image->implicitHeight(), b: availableHeight)), |
220 | rectangle: QRectF(leftPadding, topPadding, availableWidth, availableHeight)); |
221 | image->setSize(iconRect.size()); |
222 | image->setPosition(iconRect.topLeft()); |
223 | } |
224 | break; |
225 | case QQuickIconLabel::TextOnly: |
226 | if (label) { |
227 | const QRectF textRect = alignedRect(mirrored, alignment, |
228 | size: QSizeF(qMin(a: label->implicitWidth(), b: availableWidth), |
229 | qMin(a: label->implicitHeight(), b: availableHeight)), |
230 | rectangle: QRectF(leftPadding, topPadding, availableWidth, availableHeight)); |
231 | label->setSize(textRect.size()); |
232 | label->setPosition(textRect.topLeft()); |
233 | } |
234 | break; |
235 | |
236 | case QQuickIconLabel::TextUnderIcon: { |
237 | // Work out the sizes first, as the positions depend on them. |
238 | QSizeF iconSize; |
239 | QSizeF textSize; |
240 | if (image) { |
241 | iconSize.setWidth(qMin(a: image->implicitWidth(), b: availableWidth)); |
242 | iconSize.setHeight(qMin(a: image->implicitHeight(), b: availableHeight)); |
243 | } |
244 | qreal effectiveSpacing = 0; |
245 | if (label) { |
246 | if (!iconSize.isEmpty()) |
247 | effectiveSpacing = spacing; |
248 | textSize.setWidth(qMin(a: label->implicitWidth(), b: availableWidth)); |
249 | textSize.setHeight(qMin(a: label->implicitHeight(), b: availableHeight - iconSize.height() - effectiveSpacing)); |
250 | } |
251 | |
252 | QRectF combinedRect = alignedRect(mirrored, alignment, |
253 | size: QSizeF(qMax(a: iconSize.width(), b: textSize.width()), |
254 | iconSize.height() + effectiveSpacing + textSize.height()), |
255 | rectangle: QRectF(leftPadding, topPadding, availableWidth, availableHeight)); |
256 | if (image) { |
257 | QRectF iconRect = alignedRect(mirrored, alignment: Qt::AlignHCenter | Qt::AlignTop, size: iconSize, rectangle: combinedRect); |
258 | image->setSize(iconRect.size()); |
259 | image->setPosition(iconRect.topLeft()); |
260 | } |
261 | if (label) { |
262 | QRectF textRect = alignedRect(mirrored, alignment: Qt::AlignHCenter | Qt::AlignBottom, size: textSize, rectangle: combinedRect); |
263 | label->setSize(textRect.size()); |
264 | label->setPosition(textRect.topLeft()); |
265 | } |
266 | break; |
267 | } |
268 | |
269 | case QQuickIconLabel::TextBesideIcon: |
270 | default: |
271 | // Work out the sizes first, as the positions depend on them. |
272 | QSizeF iconSize(0, 0); |
273 | QSizeF textSize(0, 0); |
274 | if (image) { |
275 | iconSize.setWidth(qMin(a: image->implicitWidth(), b: availableWidth)); |
276 | iconSize.setHeight(qMin(a: image->implicitHeight(), b: availableHeight)); |
277 | } |
278 | qreal effectiveSpacing = 0; |
279 | if (label) { |
280 | if (!iconSize.isEmpty()) |
281 | effectiveSpacing = spacing; |
282 | textSize.setWidth(qMin(a: label->implicitWidth(), b: availableWidth - iconSize.width() - effectiveSpacing)); |
283 | textSize.setHeight(qMin(a: label->implicitHeight(), b: availableHeight)); |
284 | } |
285 | |
286 | const QRectF combinedRect = alignedRect(mirrored, alignment, |
287 | size: QSizeF(iconSize.width() + effectiveSpacing + textSize.width(), |
288 | qMax(a: iconSize.height(), b: textSize.height())), |
289 | rectangle: QRectF(leftPadding, topPadding, availableWidth, availableHeight)); |
290 | if (image) { |
291 | const QRectF iconRect = alignedRect(mirrored, alignment: Qt::AlignLeft | Qt::AlignVCenter, size: iconSize, rectangle: combinedRect); |
292 | image->setSize(iconRect.size()); |
293 | image->setPosition(iconRect.topLeft()); |
294 | } |
295 | if (label) { |
296 | const QRectF textRect = alignedRect(mirrored, alignment: Qt::AlignRight | Qt::AlignVCenter, size: textSize, rectangle: combinedRect); |
297 | label->setSize(textRect.size()); |
298 | label->setPosition(textRect.topLeft()); |
299 | } |
300 | break; |
301 | } |
302 | |
303 | q->setBaselineOffset(label ? label->y() + label->baselineOffset() : 0); |
304 | } |
305 | |
306 | static const QQuickItemPrivate::ChangeTypes itemChangeTypes = |
307 | QQuickItemPrivate::ImplicitWidth |
308 | | QQuickItemPrivate::ImplicitHeight |
309 | | QQuickItemPrivate::Destroyed; |
310 | |
311 | void QQuickIconLabelPrivate::watchChanges(QQuickItem *item) |
312 | { |
313 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
314 | itemPrivate->addItemChangeListener(listener: this, types: itemChangeTypes); |
315 | } |
316 | |
317 | void QQuickIconLabelPrivate::unwatchChanges(QQuickItem* item) |
318 | { |
319 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
320 | itemPrivate->removeItemChangeListener(this, types: itemChangeTypes); |
321 | } |
322 | |
323 | void QQuickIconLabelPrivate::itemImplicitWidthChanged(QQuickItem *) |
324 | { |
325 | updateImplicitSize(); |
326 | layout(); |
327 | } |
328 | |
329 | void QQuickIconLabelPrivate::itemImplicitHeightChanged(QQuickItem *) |
330 | { |
331 | updateImplicitSize(); |
332 | layout(); |
333 | } |
334 | |
335 | void QQuickIconLabelPrivate::itemDestroyed(QQuickItem *item) |
336 | { |
337 | unwatchChanges(item); |
338 | if (item == image) |
339 | image = nullptr; |
340 | else if (item == label) |
341 | label = nullptr; |
342 | } |
343 | |
344 | QQuickIconLabel::QQuickIconLabel(QQuickItem *parent) |
345 | : QQuickItem(*(new QQuickIconLabelPrivate), parent) |
346 | { |
347 | } |
348 | |
349 | QQuickIconLabel::~QQuickIconLabel() |
350 | { |
351 | Q_D(QQuickIconLabel); |
352 | if (d->image) |
353 | d->unwatchChanges(item: d->image); |
354 | if (d->label) |
355 | d->unwatchChanges(item: d->label); |
356 | } |
357 | |
358 | QQuickIcon QQuickIconLabel::icon() const |
359 | { |
360 | Q_D(const QQuickIconLabel); |
361 | return d->icon; |
362 | } |
363 | |
364 | void QQuickIconLabel::setIcon(const QQuickIcon &icon) |
365 | { |
366 | Q_D(QQuickIconLabel); |
367 | if (d->icon == icon) |
368 | return; |
369 | |
370 | d->icon = icon; |
371 | d->icon.ensureRelativeSourceResolved(owner: this); |
372 | d->updateOrSyncImage(); |
373 | } |
374 | |
375 | QString QQuickIconLabel::text() const |
376 | { |
377 | Q_D(const QQuickIconLabel); |
378 | return d->text; |
379 | } |
380 | |
381 | void QQuickIconLabel::setText(const QString &text) |
382 | { |
383 | Q_D(QQuickIconLabel); |
384 | if (d->text == text) |
385 | return; |
386 | |
387 | d->text = text; |
388 | d->updateOrSyncLabel(); |
389 | } |
390 | |
391 | QFont QQuickIconLabel::font() const |
392 | { |
393 | Q_D(const QQuickIconLabel); |
394 | return d->font; |
395 | } |
396 | |
397 | void QQuickIconLabel::setFont(const QFont &font) |
398 | { |
399 | Q_D(QQuickIconLabel); |
400 | if (d->font == font) |
401 | return; |
402 | |
403 | d->font = font; |
404 | if (d->label) |
405 | d->label->setFont(font); |
406 | } |
407 | |
408 | QColor QQuickIconLabel::color() const |
409 | { |
410 | Q_D(const QQuickIconLabel); |
411 | return d->color; |
412 | } |
413 | |
414 | void QQuickIconLabel::setColor(const QColor &color) |
415 | { |
416 | Q_D(QQuickIconLabel); |
417 | if (d->color == color) |
418 | return; |
419 | |
420 | d->color = color; |
421 | if (d->label) |
422 | d->label->setColor(color); |
423 | } |
424 | |
425 | QQuickIconLabel::Display QQuickIconLabel::display() const |
426 | { |
427 | Q_D(const QQuickIconLabel); |
428 | return d->display; |
429 | } |
430 | |
431 | void QQuickIconLabel::setDisplay(Display display) |
432 | { |
433 | Q_D(QQuickIconLabel); |
434 | if (d->display == display) |
435 | return; |
436 | |
437 | d->display = display; |
438 | d->updateImage(); |
439 | d->updateLabel(); |
440 | d->updateImplicitSize(); |
441 | d->layout(); |
442 | } |
443 | |
444 | qreal QQuickIconLabel::spacing() const |
445 | { |
446 | Q_D(const QQuickIconLabel); |
447 | return d->spacing; |
448 | } |
449 | |
450 | void QQuickIconLabel::setSpacing(qreal spacing) |
451 | { |
452 | Q_D(QQuickIconLabel); |
453 | if (qFuzzyCompare(p1: d->spacing, p2: spacing)) |
454 | return; |
455 | |
456 | d->spacing = spacing; |
457 | if (d->image && d->label) { |
458 | d->updateImplicitSize(); |
459 | d->layout(); |
460 | } |
461 | } |
462 | |
463 | bool QQuickIconLabel::isMirrored() const |
464 | { |
465 | Q_D(const QQuickIconLabel); |
466 | return d->mirrored; |
467 | } |
468 | |
469 | void QQuickIconLabel::setMirrored(bool mirrored) |
470 | { |
471 | Q_D(QQuickIconLabel); |
472 | if (d->mirrored == mirrored) |
473 | return; |
474 | |
475 | d->mirrored = mirrored; |
476 | d->layout(); |
477 | } |
478 | |
479 | Qt::Alignment QQuickIconLabel::alignment() const |
480 | { |
481 | Q_D(const QQuickIconLabel); |
482 | return d->alignment; |
483 | } |
484 | |
485 | void QQuickIconLabel::setAlignment(Qt::Alignment alignment) |
486 | { |
487 | Q_D(QQuickIconLabel); |
488 | const int valign = alignment & Qt::AlignVertical_Mask; |
489 | const int halign = alignment & Qt::AlignHorizontal_Mask; |
490 | const uint align = (valign ? valign : Qt::AlignVCenter) | (halign ? halign : Qt::AlignHCenter); |
491 | if (d->alignment == align) |
492 | return; |
493 | |
494 | d->alignment = static_cast<Qt::Alignment>(align); |
495 | if (d->label) { |
496 | d->label->setVAlign(static_cast<QQuickText::VAlignment>(valign)); |
497 | d->label->setHAlign(static_cast<QQuickText::HAlignment>(halign)); |
498 | } |
499 | if (d->image) { |
500 | d->image->setVerticalAlignment(static_cast<QQuickImage::VAlignment>(valign)); |
501 | d->image->setHorizontalAlignment(static_cast<QQuickImage::HAlignment>(halign)); |
502 | } |
503 | d->layout(); |
504 | } |
505 | |
506 | qreal QQuickIconLabel::topPadding() const |
507 | { |
508 | Q_D(const QQuickIconLabel); |
509 | return d->topPadding; |
510 | } |
511 | |
512 | void QQuickIconLabel::setTopPadding(qreal padding) |
513 | { |
514 | Q_D(QQuickIconLabel); |
515 | if (qFuzzyCompare(p1: d->topPadding, p2: padding)) |
516 | return; |
517 | |
518 | d->topPadding = padding; |
519 | d->updateImplicitSize(); |
520 | d->layout(); |
521 | } |
522 | |
523 | void QQuickIconLabel::resetTopPadding() |
524 | { |
525 | setTopPadding(0); |
526 | } |
527 | |
528 | qreal QQuickIconLabel::leftPadding() const |
529 | { |
530 | Q_D(const QQuickIconLabel); |
531 | return d->leftPadding; |
532 | } |
533 | |
534 | void QQuickIconLabel::setLeftPadding(qreal padding) |
535 | { |
536 | Q_D(QQuickIconLabel); |
537 | if (qFuzzyCompare(p1: d->leftPadding, p2: padding)) |
538 | return; |
539 | |
540 | d->leftPadding = padding; |
541 | d->updateImplicitSize(); |
542 | d->layout(); |
543 | } |
544 | |
545 | void QQuickIconLabel::resetLeftPadding() |
546 | { |
547 | setLeftPadding(0); |
548 | } |
549 | |
550 | qreal QQuickIconLabel::rightPadding() const |
551 | { |
552 | Q_D(const QQuickIconLabel); |
553 | return d->rightPadding; |
554 | } |
555 | |
556 | void QQuickIconLabel::setRightPadding(qreal padding) |
557 | { |
558 | Q_D(QQuickIconLabel); |
559 | if (qFuzzyCompare(p1: d->rightPadding, p2: padding)) |
560 | return; |
561 | |
562 | d->rightPadding = padding; |
563 | d->updateImplicitSize(); |
564 | d->layout(); |
565 | } |
566 | |
567 | void QQuickIconLabel::resetRightPadding() |
568 | { |
569 | setRightPadding(0); |
570 | } |
571 | |
572 | qreal QQuickIconLabel::bottomPadding() const |
573 | { |
574 | Q_D(const QQuickIconLabel); |
575 | return d->bottomPadding; |
576 | } |
577 | |
578 | void QQuickIconLabel::setBottomPadding(qreal padding) |
579 | { |
580 | Q_D(QQuickIconLabel); |
581 | if (qFuzzyCompare(p1: d->bottomPadding, p2: padding)) |
582 | return; |
583 | |
584 | d->bottomPadding = padding; |
585 | d->updateImplicitSize(); |
586 | d->layout(); |
587 | } |
588 | |
589 | void QQuickIconLabel::resetBottomPadding() |
590 | { |
591 | setBottomPadding(0); |
592 | } |
593 | |
594 | void QQuickIconLabel::componentComplete() |
595 | { |
596 | Q_D(QQuickIconLabel); |
597 | if (d->image) |
598 | completeComponent(item: d->image); |
599 | if (d->label) |
600 | completeComponent(item: d->label); |
601 | QQuickItem::componentComplete(); |
602 | d->layout(); |
603 | } |
604 | |
605 | void QQuickIconLabel::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
606 | { |
607 | Q_D(QQuickIconLabel); |
608 | QQuickItem::geometryChange(newGeometry, oldGeometry); |
609 | d->layout(); |
610 | } |
611 | |
612 | QT_END_NAMESPACE |
613 | |
614 | #include "moc_qquickiconlabel_p.cpp" |
615 | |