Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickmenuitemiconlabel.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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// Qt-Security score:significant reason:default
4
9
10#include <QtQuick/private/qquicktext_p.h>
11#include <QtQuickTemplates2/private/qquickaction_p.h>
12#include <QtQuickTemplates2/private/qquickmenuitem_p.h>
13
15
16QQuickMenuItemIconLabelPrivate::~QQuickMenuItemIconLabelPrivate() = default;
17
18bool QQuickMenuItemIconLabelPrivate::hasShortcut() const
19{
20#if QT_CONFIG(shortcut)
21 // See comment in layout() for why we don't support TextUnderIcon for shortcuts.
22 return (display == QQuickIconLabel::TextOnly || display == QQuickIconLabel::TextBesideIcon)
23 // Don't store this statically because we want to be able to auto-test it.
24 && !QCoreApplication::testAttribute(Qt::AA_DontShowShortcutsInContextMenus)
25 && !shortcut().isEmpty();
26#else
27 return false;
28#endif
29}
30
31#if QT_CONFIG(shortcut)
32QKeySequence QQuickMenuItemIconLabelPrivate::shortcut() const
33{
34 return menuItem && menuItem->action() ? menuItem->action()->shortcut() : QKeySequence();
35}
36#endif
37
38bool QQuickMenuItemIconLabelPrivate::createShortcutLabel()
39{
40 Q_Q(QQuickIconLabel);
41 if (shortcutLabel)
42 return false;
43
44 shortcutLabel = new QQuickText(q);
45 watchChanges(shortcutLabel);
46 beginClass(shortcutLabel);
47 shortcutLabel->setObjectName(QStringLiteral("shortcutLabel"));
48 shortcutLabel->setFont(font);
49 shortcutLabel->setColor(color);
50 // We don't set elide because it shouldn't.
51 shortcutLabel->setVAlign(QQuickText::AlignVCenter);
52 shortcutLabel->setHAlign(QQuickText::AlignHCenter);
53#if QT_CONFIG(shortcut)
54 shortcutLabel->setText(shortcut().toString(QKeySequence::NativeText));
55#endif
56 if (componentComplete)
57 completeComponent(shortcutLabel);
58 return true;
59}
60
61bool QQuickMenuItemIconLabelPrivate::destroyShortcutLabel()
62{
63 if (!shortcutLabel)
64 return false;
65
66 unwatchChanges(shortcutLabel);
67 delete shortcutLabel;
68 shortcutLabel = nullptr;
69 return true;
70}
71
72void QQuickMenuItemIconLabelPrivate::updateOrSyncShortcutLabel()
73{
74 if (updateShortcutLabel()) {
75 if (componentComplete) {
76 updateImplicitSize();
77 layout();
78 }
79 } else {
80 syncShortcutLabel();
81 }
82}
83
84// This and layout should be kept in sync with the base class functions.
85// I couldn't think of an elegant way to use polymorphism here.
86void QQuickMenuItemIconLabelPrivate::updateImplicitSize()
87{
88 Q_Q(QQuickMenuItemIconLabel);
89 const bool showIcon = image && hasIcon();
90 const bool showText = label && hasText();
91#if QT_CONFIG(shortcut)
92 const bool showShortcutText = shortcutLabel && !shortcut().isEmpty();
93#else
94 const bool showShortcutText = false;
95#endif
96 const qreal horizontalPadding = leftPadding + rightPadding;
97 const qreal verticalPadding = topPadding + bottomPadding;
98 const qreal iconImplicitWidth = showIcon ? image->implicitWidth() : 0;
99 const qreal iconImplicitHeight = showIcon ? image->implicitHeight() : 0;
100 const qreal textImplicitWidth = showText ? label->implicitWidth() : 0;
101 const qreal textImplicitHeight = showText ? label->implicitHeight() : 0;
102 const qreal shortcutTextImplicitWidth = showShortcutText ? shortcutLabel->implicitWidth() : 0;
103 const qreal shortcutTextImplicitHeight = showShortcutText ? shortcutLabel->implicitHeight() : 0;
104 qreal effectiveSpacing = showText && showIcon && image->implicitWidth() > 0 ? spacing : 0;
105 if (showShortcutText && shortcutLabel->implicitWidth() > 0)
106 effectiveSpacing += spacing;
107 const qreal implicitWidth = display == QQuickIconLabel::TextBesideIcon
108 ? iconImplicitWidth + textImplicitWidth + shortcutTextImplicitHeight + effectiveSpacing
109 : qMax(qMax(iconImplicitWidth, textImplicitWidth), shortcutTextImplicitWidth);
110 const qreal implicitHeight = display == QQuickIconLabel::TextUnderIcon
111 ? iconImplicitHeight + textImplicitHeight + shortcutTextImplicitHeight + effectiveSpacing
112 : qMax(qMax(iconImplicitHeight, textImplicitHeight), shortcutTextImplicitHeight);
113 q->setImplicitSize(implicitWidth + horizontalPadding, implicitHeight + verticalPadding);
114}
115
116void QQuickMenuItemIconLabelPrivate::layout()
117{
118 Q_Q(QQuickIconLabel);
119 if (!componentComplete)
120 return;
121
122 const qreal availableWidth = width - leftPadding - rightPadding;
123 const qreal availableHeight = height - topPadding - bottomPadding;
124
125 auto layoutShortcutLabel = [this, availableWidth, availableHeight](const qreal availableWidthForShortcut){
126 if (availableWidthForShortcut - spacing - shortcutLabel->implicitWidth() > 0) {
127 // There's enough space for everyone.
128 shortcutLabel->setVisible(true);
129
130 // We can simply align ourselves to the right of the entire available space.
131 const QRectF shortcutTextRect = alignedRect(mirrored, Qt::AlignRight | Qt::AlignVCenter, QSizeF(
132 qMin(shortcutLabel->implicitWidth(), availableWidth),
133 qMin(shortcutLabel->implicitHeight(), availableHeight)),
134 QRectF(leftPadding, topPadding, availableWidth, availableHeight));
135 shortcutLabel->setSize(shortcutTextRect.size());
136 shortcutLabel->setPosition(shortcutTextRect.topLeft());
137 } else {
138 // No space for us. As Widgets doesn't bother with eliding, neither do we; we just
139 // don't show the shortcut if there's no room for it. The most important part of the
140 // menu item is the text, anyway.
141 shortcutLabel->setVisible(false);
142 }
143 };
144
145 switch (display) {
146 case QQuickIconLabel::IconOnly:
147 QQuickIconLabelPrivate::layout();
148 return;
149 case QQuickIconLabel::TextOnly: {
150 if (label) {
151 const QRectF textRect = alignedRect(mirrored, alignment, QSizeF(
152 qMin(label->implicitWidth(), availableWidth),
153 qMin(label->implicitHeight(), availableHeight)),
154 QRectF(leftPadding, topPadding, availableWidth, availableHeight));
155 label->setSize(textRect.size());
156 label->setPosition(textRect.topLeft());
157 }
158
159 if (shortcutLabel)
160 layoutShortcutLabel(availableWidth - (label->width() + spacing));
161 break;
162 } case QQuickIconLabel::TextUnderIcon: {
163 // TextUnderIcon doesn't make sense for menu items, but we don't need to break
164 // any existing behaviour, so while we don't support showing shortcuts for it, we can
165 // continue doing what we were doing before.
166 QQuickIconLabelPrivate::layout();
167 return;
168 }
169 case QQuickIconLabel::TextBesideIcon:
170 default:
171 QSizeF iconSize(0, 0);
172 QSizeF textSize(0, 0);
173 QSizeF shortcutLabelSize(0, 0);
174 if (image) {
175 iconSize.setWidth(qMin(image->implicitWidth(), availableWidth));
176 iconSize.setHeight(qMin(image->implicitHeight(), availableHeight));
177 }
178 qreal spacingBetweenIconAndLabel = 0;
179 if (label) {
180 if (!iconSize.isEmpty())
181 spacingBetweenIconAndLabel = spacing;
182 textSize.setWidth(qMin(label->implicitWidth(), availableWidth - iconSize.width()
183 - spacingBetweenIconAndLabel));
184 textSize.setHeight(qMin(label->implicitHeight(), availableHeight));
185 }
186
187 qreal spacingBetweenLabelAndShortcutLabel = 0;
188 if (shortcutLabel) {
189 if (!textSize.isEmpty())
190 spacingBetweenLabelAndShortcutLabel = spacing;
191 shortcutLabelSize.setWidth(qMin(shortcutLabel->implicitWidth(), availableWidth - iconSize.width()
192 - spacingBetweenIconAndLabel - textSize.width() - spacingBetweenLabelAndShortcutLabel));
193 shortcutLabelSize.setHeight(qMin(shortcutLabel->implicitHeight(), availableHeight));
194 }
195
196 /*!
197 For IconLabel, the layout would look like this:
198
199 +-------+-+--------------+
200 | | | |
201 | Icon | | Text |
202 | | | |
203 +-------+-+--------------+
204
205 For us, it looks like this:
206
207 +-------+-+--------------+-+------------+
208 | | | | | |
209 | Icon | | Text | | Shortcut |
210 | | | | | |
211 +-------+-+--------------+-+------------+
212 */
213 const QRectF combinedImageAndLabelRect = alignedRect(mirrored, alignment, QSizeF(
214 iconSize.width() + spacingBetweenIconAndLabel + textSize.width(),
215 qMax(iconSize.height(), textSize.height())),
216 QRectF(leftPadding, topPadding, availableWidth, availableHeight));
217 if (image) {
218 const QRectF iconRect = alignedRect(mirrored, Qt::AlignLeft | Qt::AlignVCenter, iconSize, combinedImageAndLabelRect);
219 image->setSize(iconRect.size());
220 image->snapPositionTo(iconRect.topLeft());
221 }
222 if (label) {
223 const QRectF textRect = alignedRect(mirrored, Qt::AlignRight | Qt::AlignVCenter, textSize, combinedImageAndLabelRect);
224 label->setSize(textRect.size());
225 label->setPosition(textRect.topLeft());
226 }
227 if (shortcutLabel)
228 layoutShortcutLabel(availableWidth - combinedImageAndLabelRect.width());
229 break;
230 }
231
232 q->setBaselineOffset(label ? label->y() + label->baselineOffset() : 0);
233}
234
235void QQuickMenuItemIconLabelPrivate::itemDestroyed(QQuickItem *item)
236{
237 QQuickIconLabelPrivate::itemDestroyed(item);
238
239 if (item == shortcutLabel)
240 shortcutLabel = nullptr;
241}
242
243void QQuickMenuItemIconLabelPrivate::textChange()
244{
245 updateOrSyncShortcutLabel();
246}
247
248void QQuickMenuItemIconLabelPrivate::displayChange()
249{
250 updateOrSyncShortcutLabel();
251}
252
253bool QQuickMenuItemIconLabelPrivate::updateShortcutLabel()
254{
255 if (!hasShortcut())
256 return destroyShortcutLabel();
257 return createShortcutLabel();
258}
259
260void QQuickMenuItemIconLabelPrivate::syncShortcutLabel()
261{
262 if (!shortcutLabel)
263 return;
264
265#if QT_CONFIG(shortcut)
266 shortcutLabel->setText(shortcut().toString(QKeySequence::NativeText));
267#endif
268}
269
270QQuickMenuItemIconLabel::~QQuickMenuItemIconLabel()
271{
272 Q_D(QQuickMenuItemIconLabel);
273 if (d->shortcutLabel)
274 d->unwatchChanges(d->shortcutLabel);
275}
276
277QQuickMenuItemIconLabel::QQuickMenuItemIconLabel(QQuickItem *parent)
278 : QQuickIconLabel(*(new QQuickMenuItemIconLabelPrivate), parent)
279{
280}
281
282QQuickMenuItem *QQuickMenuItemIconLabel::menuItem() const
283{
284 Q_D(const QQuickMenuItemIconLabel);
285 return d->menuItem;
286}
287
288void QQuickMenuItemIconLabel::setMenuItem(QQuickMenuItem *menuItem)
289{
290 Q_D(QQuickMenuItemIconLabel);
291 if (d->menuItem == menuItem)
292 return;
293
294 d->menuItem = menuItem;
295 d->updateOrSyncShortcutLabel();
296 emit menuItemChanged();
297}
298
299void QQuickMenuItemIconLabel::componentComplete()
300{
301 Q_D(QQuickMenuItemIconLabel);
302 if (d->shortcutLabel)
303 QQuickIconLabelPrivate::completeComponent(d->shortcutLabel);
304
305 QQuickIconLabel::componentComplete();
306
307 // See QQuickIconLabel::componentComplete for why we do this.
308 const auto paintOrderChildItems = QQuickItemPrivate::get(this)->paintOrderChildItems();
309 const auto bottomMostFosterChildIt = std::find_if(paintOrderChildItems.constBegin(),
310 paintOrderChildItems.constEnd(), [d](QQuickItem *item) {
311 return item != d->label && item != d->image && item != d->shortcutLabel;
312 });
313 if (bottomMostFosterChildIt != paintOrderChildItems.constEnd()) {
314 const QQuickItem *bottomMostFosterChild = *bottomMostFosterChildIt;
315 if (d->shortcutLabel)
316 d->shortcutLabel->stackBefore(bottomMostFosterChild);
317 }
318}
319
320QT_END_NAMESPACE
321
322#include "moc_qquickmenuitemiconlabel_p.cpp"
Combined button and popup list for selecting options.