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 bool shortcutFollowsText = display == QQuickIconLabel::TextBesideIcon
108 || display == QQuickIconLabel::TextOnly;
109 const qreal implicitWidth = shortcutFollowsText
110 ? iconImplicitWidth + textImplicitWidth + shortcutTextImplicitWidth + effectiveSpacing
111 : qMax(qMax(iconImplicitWidth, textImplicitWidth), shortcutTextImplicitWidth);
112 const qreal implicitHeight = display == QQuickIconLabel::TextUnderIcon
113 ? iconImplicitHeight + textImplicitHeight + shortcutTextImplicitHeight + effectiveSpacing
114 : qMax(qMax(iconImplicitHeight, textImplicitHeight), shortcutTextImplicitHeight);
115 q->setImplicitSize(implicitWidth + horizontalPadding, implicitHeight + verticalPadding);
116}
117
118void QQuickMenuItemIconLabelPrivate::layout()
119{
120 Q_Q(QQuickIconLabel);
121 if (!componentComplete)
122 return;
123
124 const qreal availableWidth = width - leftPadding - rightPadding;
125 const qreal availableHeight = height - topPadding - bottomPadding;
126
127 auto layoutShortcutLabel = [this, availableWidth, availableHeight](const qreal availableWidthForShortcut){
128 if (availableWidthForShortcut - spacing - shortcutLabel->implicitWidth() >= 0) {
129 // There's enough space for everyone.
130 shortcutLabel->setVisible(true);
131
132 // We can simply align ourselves to the right of the entire available space.
133 const QRectF shortcutTextRect = alignedRect(mirrored, Qt::AlignRight | Qt::AlignVCenter, QSizeF(
134 qMin(shortcutLabel->implicitWidth(), availableWidth),
135 qMin(shortcutLabel->implicitHeight(), availableHeight)),
136 QRectF(leftPadding, topPadding, availableWidth, availableHeight));
137 shortcutLabel->setSize(shortcutTextRect.size());
138 shortcutLabel->setPosition(shortcutTextRect.topLeft());
139 } else {
140 // No space for us. As Widgets doesn't bother with eliding, neither do we; we just
141 // don't show the shortcut if there's no room for it. The most important part of the
142 // menu item is the text, anyway.
143 shortcutLabel->setVisible(false);
144 }
145 };
146
147 switch (display) {
148 case QQuickIconLabel::IconOnly:
149 QQuickIconLabelPrivate::layout();
150 return;
151 case QQuickIconLabel::TextOnly: {
152 if (label) {
153 const QRectF textRect = alignedRect(mirrored, alignment, QSizeF(
154 qMin(label->implicitWidth(), availableWidth),
155 qMin(label->implicitHeight(), availableHeight)),
156 QRectF(leftPadding, topPadding, availableWidth, availableHeight));
157 label->setSize(textRect.size());
158 label->setPosition(textRect.topLeft());
159 }
160
161 if (shortcutLabel)
162 layoutShortcutLabel(availableWidth - label->width());
163 break;
164 } case QQuickIconLabel::TextUnderIcon: {
165 // TextUnderIcon doesn't make sense for menu items, but we don't need to break
166 // any existing behaviour, so while we don't support showing shortcuts for it, we can
167 // continue doing what we were doing before.
168 QQuickIconLabelPrivate::layout();
169 return;
170 }
171 case QQuickIconLabel::TextBesideIcon:
172 default:
173 QSizeF iconSize(0, 0);
174 QSizeF textSize(0, 0);
175 QSizeF shortcutLabelSize(0, 0);
176 if (image) {
177 iconSize.setWidth(qMin(image->implicitWidth(), availableWidth));
178 iconSize.setHeight(qMin(image->implicitHeight(), availableHeight));
179 }
180 qreal spacingBetweenIconAndLabel = 0;
181 if (label) {
182 if (!iconSize.isEmpty())
183 spacingBetweenIconAndLabel = spacing;
184 textSize.setWidth(qMin(label->implicitWidth(), availableWidth - iconSize.width()
185 - spacingBetweenIconAndLabel));
186 textSize.setHeight(qMin(label->implicitHeight(), availableHeight));
187 }
188
189 qreal spacingBetweenLabelAndShortcutLabel = 0;
190 if (shortcutLabel) {
191 if (!textSize.isEmpty())
192 spacingBetweenLabelAndShortcutLabel = spacing;
193 shortcutLabelSize.setWidth(qMin(shortcutLabel->implicitWidth(), availableWidth - iconSize.width()
194 - spacingBetweenIconAndLabel - textSize.width() - spacingBetweenLabelAndShortcutLabel));
195 shortcutLabelSize.setHeight(qMin(shortcutLabel->implicitHeight(), availableHeight));
196 }
197
198 /*!
199 For IconLabel, the layout would look like this:
200
201 +-------+-+--------------+
202 | | | |
203 | Icon | | Text |
204 | | | |
205 +-------+-+--------------+
206
207 For us, it looks like this:
208
209 +-------+-+--------------+-+------------+
210 | | | | | |
211 | Icon | | Text | | Shortcut |
212 | | | | | |
213 +-------+-+--------------+-+------------+
214 */
215 const QRectF combinedImageAndLabelRect = alignedRect(mirrored, alignment, QSizeF(
216 iconSize.width() + spacingBetweenIconAndLabel + textSize.width(),
217 qMax(iconSize.height(), textSize.height())),
218 QRectF(leftPadding, topPadding, availableWidth, availableHeight));
219 if (image) {
220 const QRectF iconRect = alignedRect(mirrored, Qt::AlignLeft | Qt::AlignVCenter, iconSize, combinedImageAndLabelRect);
221 image->setSize(iconRect.size());
222 image->snapPositionTo(iconRect.topLeft());
223 }
224 if (label) {
225 const QRectF textRect = alignedRect(mirrored, Qt::AlignRight | Qt::AlignVCenter, textSize, combinedImageAndLabelRect);
226 label->setSize(textRect.size());
227 label->setPosition(textRect.topLeft());
228 }
229 if (shortcutLabel)
230 layoutShortcutLabel(availableWidth - combinedImageAndLabelRect.width());
231 break;
232 }
233
234 q->setBaselineOffset(label ? label->y() + label->baselineOffset() : 0);
235}
236
237void QQuickMenuItemIconLabelPrivate::itemDestroyed(QQuickItem *item)
238{
239 QQuickIconLabelPrivate::itemDestroyed(item);
240
241 if (item == shortcutLabel)
242 shortcutLabel = nullptr;
243}
244
245void QQuickMenuItemIconLabelPrivate::textChange()
246{
247 updateOrSyncShortcutLabel();
248}
249
250void QQuickMenuItemIconLabelPrivate::displayChange()
251{
252 updateOrSyncShortcutLabel();
253}
254
255bool QQuickMenuItemIconLabelPrivate::updateShortcutLabel()
256{
257 if (!hasShortcut())
258 return destroyShortcutLabel();
259 return createShortcutLabel();
260}
261
262void QQuickMenuItemIconLabelPrivate::syncShortcutLabel()
263{
264 if (!shortcutLabel)
265 return;
266
267#if QT_CONFIG(shortcut)
268 shortcutLabel->setText(shortcut().toString(QKeySequence::NativeText));
269#endif
270}
271
272QQuickMenuItemIconLabel::~QQuickMenuItemIconLabel()
273{
274 Q_D(QQuickMenuItemIconLabel);
275 if (d->shortcutLabel)
276 d->unwatchChanges(d->shortcutLabel);
277}
278
279QQuickMenuItemIconLabel::QQuickMenuItemIconLabel(QQuickItem *parent)
280 : QQuickIconLabel(*(new QQuickMenuItemIconLabelPrivate), parent)
281{
282}
283
284QQuickMenuItem *QQuickMenuItemIconLabel::menuItem() const
285{
286 Q_D(const QQuickMenuItemIconLabel);
287 return d->menuItem;
288}
289
290void QQuickMenuItemIconLabel::setMenuItem(QQuickMenuItem *menuItem)
291{
292 Q_D(QQuickMenuItemIconLabel);
293 if (d->menuItem == menuItem)
294 return;
295
296 d->menuItem = menuItem;
297 d->updateOrSyncShortcutLabel();
298 emit menuItemChanged();
299}
300
301void QQuickMenuItemIconLabel::componentComplete()
302{
303 Q_D(QQuickMenuItemIconLabel);
304 if (d->shortcutLabel)
305 QQuickIconLabelPrivate::completeComponent(d->shortcutLabel);
306
307 QQuickIconLabel::componentComplete();
308
309 // See QQuickIconLabel::componentComplete for why we do this.
310 const auto paintOrderChildItems = QQuickItemPrivate::get(this)->paintOrderChildItems();
311 const auto bottomMostFosterChildIt = std::find_if(paintOrderChildItems.constBegin(),
312 paintOrderChildItems.constEnd(), [d](QQuickItem *item) {
313 return item != d->label && item != d->image && item != d->shortcutLabel;
314 });
315 if (bottomMostFosterChildIt != paintOrderChildItems.constEnd()) {
316 const QQuickItem *bottomMostFosterChild = *bottomMostFosterChildIt;
317 if (d->shortcutLabel)
318 d->shortcutLabel->stackBefore(bottomMostFosterChild);
319 }
320}
321
322QT_END_NAMESPACE
323
324#include "moc_qquickmenuitemiconlabel_p.cpp"
Combined button and popup list for selecting options.