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 "qmenu.h" |
41 | |
42 | #include <QtWidgets/private/qtwidgetsglobal_p.h> |
43 | #include <QtWidgets/private/qwidgetwindow_p.h> |
44 | |
45 | #include "qdebug.h" |
46 | #include "qstyle.h" |
47 | #include "qevent.h" |
48 | #include "qtimer.h" |
49 | #include "qlayout.h" |
50 | #include "qpainter.h" |
51 | #include <qpa/qplatformtheme.h> |
52 | #ifdef Q_OS_MACOS |
53 | #include "qmacnativewidget_mac.h" |
54 | #endif |
55 | #include "qapplication.h" |
56 | #include "qdesktopwidget.h" |
57 | #ifndef QT_NO_ACCESSIBILITY |
58 | # include "qaccessible.h" |
59 | #endif |
60 | #if QT_CONFIG(effects) |
61 | # include <private/qeffects_p.h> |
62 | #endif |
63 | #if QT_CONFIG(whatsthis) |
64 | # include <qwhatsthis.h> |
65 | #endif |
66 | |
67 | #include "qmenu_p.h" |
68 | #if QT_CONFIG(menubar) |
69 | #include "qmenubar_p.h" |
70 | #endif |
71 | #include "qwidgetaction.h" |
72 | #if QT_CONFIG(toolbutton) |
73 | #include "qtoolbutton.h" |
74 | #endif |
75 | #include "qpushbutton.h" |
76 | #include "qtooltip.h" |
77 | #include <qwindow.h> |
78 | #include <private/qpushbutton_p.h> |
79 | #include <private/qaction_p.h> |
80 | #include <private/qguiapplication_p.h> |
81 | #include <qpa/qplatformtheme.h> |
82 | #include <private/qdesktopwidget_p.h> |
83 | #include <private/qstyle_p.h> |
84 | |
85 | QT_BEGIN_NAMESPACE |
86 | |
87 | QMenu *QMenuPrivate:: = nullptr; |
88 | |
89 | /* QMenu code */ |
90 | // internal class used for the torn off popup |
91 | class : public QMenu |
92 | { |
93 | Q_OBJECT |
94 | class : public QMenuPrivate |
95 | { |
96 | Q_DECLARE_PUBLIC(QTornOffMenu) |
97 | public: |
98 | (QMenu *p) : causedMenu(p), initialized(false) { |
99 | tornoff = 1; |
100 | causedPopup.widget = nullptr; |
101 | causedPopup.action = p->d_func()->causedPopup.action; |
102 | causedStack = p->d_func()->calcCausedStack(); |
103 | } |
104 | |
105 | void (const QSize &) { |
106 | Q_Q(QTornOffMenu); |
107 | QSize size = menuSize; |
108 | const QPoint p = (!initialized) ? causedMenu->pos() : q->pos(); |
109 | QRect screen = popupGeometry(screen: QDesktopWidgetPrivate::screenNumber(p)); |
110 | const int desktopFrame = q->style()->pixelMetric(metric: QStyle::PM_MenuDesktopFrameWidth, option: nullptr, widget: q); |
111 | const int titleBarHeight = q->style()->pixelMetric(metric: QStyle::PM_TitleBarHeight, option: nullptr, widget: q); |
112 | if (scroll && (size.height() > screen.height() - titleBarHeight || size.width() > screen.width())) { |
113 | const int fw = q->style()->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: nullptr, widget: q); |
114 | const int hmargin = q->style()->pixelMetric(metric: QStyle::PM_MenuHMargin, option: nullptr, widget: q); |
115 | scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown); |
116 | size.setWidth(qMin(a: actionRects.at(i: getLastVisibleAction()).right() + fw + hmargin + rightmargin + 1, b: screen.width())); |
117 | size.setHeight(screen.height() - desktopFrame * 2 - titleBarHeight); |
118 | } |
119 | q->setFixedSize(size); |
120 | } |
121 | |
122 | QVector<QPointer<QWidget> > () const override { return causedStack; } |
123 | QPointer<QMenu> ; |
124 | QVector<QPointer<QWidget> > ; |
125 | bool ; |
126 | }; |
127 | |
128 | public: |
129 | (QMenu *p) : QMenu(*(new QTornOffMenuPrivate(p))) |
130 | { |
131 | Q_D(QTornOffMenu); |
132 | // make the torn-off menu a sibling of p (instead of a child) |
133 | QWidget *parentWidget = d->causedStack.isEmpty() ? p : d->causedStack.constLast(); |
134 | if (parentWidget->parentWidget()) |
135 | parentWidget = parentWidget->parentWidget(); |
136 | setParent(parent: parentWidget, f: Qt::Window | Qt::Tool); |
137 | setAttribute(Qt::WA_DeleteOnClose, on: true); |
138 | setAttribute(Qt::WA_X11NetWmWindowTypeMenu, on: true); |
139 | updateWindowTitle(); |
140 | setEnabled(p->isEnabled()); |
141 | #if QT_CONFIG(style_stylesheet) |
142 | setStyleSheet(p->styleSheet()); |
143 | #endif |
144 | if (style() != p->style()) |
145 | setStyle(p->style()); |
146 | setContentsMargins(p->contentsMargins()); |
147 | setLayoutDirection(p->layoutDirection()); |
148 | //QObject::connect(this, SIGNAL(triggered(QAction*)), this, SLOT(onTrigger(QAction*))); |
149 | //QObject::connect(this, SIGNAL(hovered(QAction*)), this, SLOT(onHovered(QAction*))); |
150 | QList<QAction*> items = p->actions(); |
151 | for(int i = 0; i < items.count(); i++) |
152 | addAction(action: items.at(i)); |
153 | d->setMenuSize(sizeHint()); |
154 | d->initialized = true; |
155 | } |
156 | void (QMenu *, QActionEvent *act) |
157 | { |
158 | Q_D(QTornOffMenu); |
159 | if(menu != d->causedMenu) |
160 | return; |
161 | if (act->type() == QEvent::ActionAdded) { |
162 | insertAction(before: act->before(), action: act->action()); |
163 | } else if (act->type() == QEvent::ActionRemoved) |
164 | removeAction(action: act->action()); |
165 | } |
166 | void (QActionEvent *e) override |
167 | { |
168 | Q_D(QTornOffMenu); |
169 | QMenu::actionEvent(e); |
170 | if (d->initialized) { |
171 | d->setMenuSize(sizeHint()); |
172 | } |
173 | } |
174 | |
175 | void () |
176 | { |
177 | Q_D(QTornOffMenu); |
178 | if (!d->causedMenu) |
179 | return; |
180 | const QString &cleanTitle = QPlatformTheme::removeMnemonics(original: d->causedMenu->title()).trimmed(); |
181 | setWindowTitle(cleanTitle); |
182 | } |
183 | |
184 | public slots: |
185 | void (QAction *action) { d_func()->activateAction(action, QAction::Trigger, self: false); } |
186 | void (QAction *action) { d_func()->activateAction(action, QAction::Hover, self: false); } |
187 | |
188 | private: |
189 | Q_DECLARE_PRIVATE(QTornOffMenu) |
190 | friend class QMenuPrivate; |
191 | }; |
192 | |
193 | void QMenuPrivate::() |
194 | { |
195 | Q_Q(QMenu); |
196 | #if QT_CONFIG(whatsthis) |
197 | q->setAttribute(Qt::WA_CustomWhatsThis); |
198 | #endif |
199 | q->setAttribute(Qt::WA_X11NetWmWindowTypePopupMenu); |
200 | defaultMenuAction = menuAction = new QAction(q); |
201 | menuAction->d_func()->menu = q; |
202 | QObject::connect(sender: menuAction, signal: &QAction::changed, slot: [this] { |
203 | if (!tornPopup.isNull()) |
204 | tornPopup->updateWindowTitle(); |
205 | }); |
206 | q->setMouseTracking(q->style()->styleHint(stylehint: QStyle::SH_Menu_MouseTracking, opt: nullptr, widget: q)); |
207 | if (q->style()->styleHint(stylehint: QStyle::SH_Menu_Scrollable, opt: nullptr, widget: q)) { |
208 | scroll = new QMenuPrivate::QMenuScroller; |
209 | scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone; |
210 | } |
211 | |
212 | sloppyState.initialize(menu: q); |
213 | delayState.initialize(parent: q); |
214 | mousePopupDelay = q->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuPopupDelay, opt: nullptr, widget: q); |
215 | } |
216 | |
217 | QPlatformMenu *QMenuPrivate::() |
218 | { |
219 | Q_Q(QMenu); |
220 | if (platformMenu.isNull()) |
221 | q->setPlatformMenu(QGuiApplicationPrivate::platformTheme()->createPlatformMenu()); |
222 | return platformMenu.data(); |
223 | } |
224 | |
225 | void QMenuPrivate::(QPlatformMenu *) |
226 | { |
227 | Q_Q(QMenu); |
228 | if (!platformMenu.isNull() && !platformMenu->parent()) |
229 | delete platformMenu.data(); |
230 | |
231 | platformMenu = menu; |
232 | if (!platformMenu.isNull()) { |
233 | QObject::connect(sender: platformMenu, SIGNAL(aboutToShow()), receiver: q, SLOT(_q_platformMenuAboutToShow())); |
234 | QObject::connect(sender: platformMenu, SIGNAL(aboutToHide()), receiver: q, SIGNAL(aboutToHide())); |
235 | } |
236 | } |
237 | |
238 | void QMenuPrivate::() |
239 | { |
240 | Q_Q(QMenu); |
241 | if (platformMenu.isNull()) |
242 | return; |
243 | |
244 | QPlatformMenuItem *beforeItem = nullptr; |
245 | const QList<QAction*> actions = q->actions(); |
246 | for (QList<QAction*>::const_reverse_iterator it = actions.rbegin(), end = actions.rend(); it != end; ++it) { |
247 | QPlatformMenuItem * = insertActionInPlatformMenu(action: *it, beforeItem); |
248 | beforeItem = menuItem; |
249 | } |
250 | platformMenu->syncSeparatorsCollapsible(enable: collapsibleSeparators); |
251 | platformMenu->setEnabled(q->isEnabled()); |
252 | } |
253 | |
254 | void QMenuPrivate::(const QAction *action, QPlatformMenuItem *item) |
255 | { |
256 | item->setText(action->text()); |
257 | item->setIsSeparator(action->isSeparator()); |
258 | if (action->isIconVisibleInMenu()) { |
259 | item->setIcon(action->icon()); |
260 | if (QWidget *w = action->parentWidget()) { |
261 | QStyleOption opt; |
262 | opt.init(w); |
263 | item->setIconSize(w->style()->pixelMetric(metric: QStyle::PM_SmallIconSize, option: &opt, widget: w)); |
264 | } else { |
265 | QStyleOption opt; |
266 | item->setIconSize(QApplication::style()->pixelMetric(metric: QStyle::PM_SmallIconSize, option: &opt, widget: nullptr)); |
267 | } |
268 | } else { |
269 | item->setIcon(QIcon()); |
270 | } |
271 | item->setVisible(action->isVisible()); |
272 | #if QT_CONFIG(shortcut) |
273 | item->setShortcut(action->shortcut()); |
274 | #endif |
275 | item->setCheckable(action->isCheckable()); |
276 | item->setChecked(action->isChecked()); |
277 | item->setHasExclusiveGroup(action->actionGroup() && action->actionGroup()->isExclusive()); |
278 | item->setFont(action->font()); |
279 | item->setRole((QPlatformMenuItem::MenuRole) action->menuRole()); |
280 | item->setEnabled(action->isEnabled()); |
281 | |
282 | if (action->menu()) { |
283 | if (!action->menu()->platformMenu()) |
284 | action->menu()->setPlatformMenu(platformMenu->createSubMenu()); |
285 | item->setMenu(action->menu()->platformMenu()); |
286 | } else { |
287 | item->setMenu(nullptr); |
288 | } |
289 | } |
290 | |
291 | QPlatformMenuItem * QMenuPrivate::(const QAction *action, QPlatformMenuItem *beforeItem) |
292 | { |
293 | QPlatformMenuItem * = platformMenu->createMenuItem(); |
294 | Q_ASSERT(menuItem); |
295 | |
296 | menuItem->setTag(reinterpret_cast<quintptr>(action)); |
297 | QObject::connect(sender: menuItem, signal: &QPlatformMenuItem::activated, receiver: action, slot: &QAction::trigger, type: Qt::QueuedConnection); |
298 | QObject::connect(sender: menuItem, signal: &QPlatformMenuItem::hovered, receiver: action, slot: &QAction::hovered, type: Qt::QueuedConnection); |
299 | copyActionToPlatformItem(action, item: menuItem); |
300 | platformMenu->insertMenuItem(menuItem, before: beforeItem); |
301 | |
302 | return menuItem; |
303 | } |
304 | |
305 | int QMenuPrivate::() const |
306 | { |
307 | Q_Q(const QMenu); |
308 | return qMax(a: QApplication::globalStrut().height(), b: q->style()->pixelMetric(metric: QStyle::PM_MenuScrollerHeight, option: nullptr, widget: q)); |
309 | } |
310 | |
311 | // Windows and KDE allow menus to cover the taskbar, while GNOME and macOS |
312 | // don't. Torn-off menus are again different |
313 | inline bool QMenuPrivate::() const |
314 | { |
315 | return !tornoff && QStylePrivate::useFullScreenForPopup(); |
316 | } |
317 | |
318 | QRect QMenuPrivate::() const |
319 | { |
320 | Q_Q(const QMenu); |
321 | return useFullScreenForPopup() |
322 | ? QDesktopWidgetPrivate::screenGeometry(widget: q) |
323 | : QDesktopWidgetPrivate::availableGeometry(widget: q); |
324 | } |
325 | |
326 | QRect QMenuPrivate::(int screen) const |
327 | { |
328 | return useFullScreenForPopup() |
329 | ? QDesktopWidgetPrivate::screenGeometry(screen) |
330 | : QDesktopWidgetPrivate::availableGeometry(screen); |
331 | } |
332 | |
333 | QVector<QPointer<QWidget> > QMenuPrivate::() const |
334 | { |
335 | QVector<QPointer<QWidget> > ret; |
336 | for(QWidget *widget = causedPopup.widget; widget; ) { |
337 | ret.append(t: widget); |
338 | if (QTornOffMenu * = qobject_cast<QTornOffMenu*>(object: widget)) |
339 | ret += qtmenu->d_func()->causedStack; |
340 | if (QMenu * = qobject_cast<QMenu*>(object: widget)) |
341 | widget = qmenu->d_func()->causedPopup.widget; |
342 | else |
343 | break; |
344 | } |
345 | return ret; |
346 | } |
347 | |
348 | bool QMenuPrivate::() const |
349 | { |
350 | return qobject_cast<const QMenuBar *>(object: topCausedWidget()) == nullptr; |
351 | } |
352 | |
353 | void QMenuPrivate::() const |
354 | { |
355 | updateActionRects(screen: popupGeometry()); |
356 | } |
357 | |
358 | void QMenuPrivate::(const QRect &screen) const |
359 | { |
360 | Q_Q(const QMenu); |
361 | if (!itemsDirty) |
362 | return; |
363 | |
364 | q->ensurePolished(); |
365 | |
366 | //let's reinitialize the buffer |
367 | actionRects.resize(asize: actions.count()); |
368 | actionRects.fill(from: QRect()); |
369 | |
370 | int lastVisibleAction = getLastVisibleAction(); |
371 | |
372 | QStyle *style = q->style(); |
373 | QStyleOption opt; |
374 | opt.init(w: q); |
375 | const int hmargin = style->pixelMetric(metric: QStyle::PM_MenuHMargin, option: &opt, widget: q), |
376 | vmargin = style->pixelMetric(metric: QStyle::PM_MenuVMargin, option: &opt, widget: q), |
377 | icone = style->pixelMetric(metric: QStyle::PM_SmallIconSize, option: &opt, widget: q); |
378 | const int fw = style->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: &opt, widget: q); |
379 | const int deskFw = style->pixelMetric(metric: QStyle::PM_MenuDesktopFrameWidth, option: &opt, widget: q); |
380 | const int tearoffHeight = tearoff ? style->pixelMetric(metric: QStyle::PM_MenuTearoffHeight, option: &opt, widget: q) : 0; |
381 | const int base_y = vmargin + fw + topmargin + (scroll ? scroll->scrollOffset : 0) + tearoffHeight; |
382 | const int column_max_y = screen.height() - 2 * deskFw - (vmargin + bottommargin + fw); |
383 | int max_column_width = 0; |
384 | int y = base_y; |
385 | |
386 | //for compatibility now - will have to refactor this away |
387 | tabWidth = 0; |
388 | maxIconWidth = 0; |
389 | hasCheckableItems = false; |
390 | ncols = 1; |
391 | |
392 | for (int i = 0; i < actions.count(); ++i) { |
393 | QAction *action = actions.at(i); |
394 | if (action->isSeparator() || !action->isVisible() || widgetItems.contains(akey: action)) |
395 | continue; |
396 | //..and some members |
397 | hasCheckableItems |= action->isCheckable(); |
398 | QIcon is = action->icon(); |
399 | if (!is.isNull()) { |
400 | maxIconWidth = qMax<uint>(a: maxIconWidth, b: icone + 4); |
401 | } |
402 | } |
403 | |
404 | //calculate size |
405 | QFontMetrics qfm = q->fontMetrics(); |
406 | bool previousWasSeparator = true; // this is true to allow removing the leading separators |
407 | const bool = isContextMenu(); |
408 | for(int i = 0; i <= lastVisibleAction; i++) { |
409 | QAction *action = actions.at(i); |
410 | const bool isSection = action->isSeparator() && (!action->text().isEmpty() || !action->icon().isNull()); |
411 | const bool isPlainSeparator = (isSection && !q->style()->styleHint(stylehint: QStyle::SH_Menu_SupportsSections)) |
412 | || (action->isSeparator() && !isSection); |
413 | |
414 | if (!action->isVisible() || |
415 | (collapsibleSeparators && previousWasSeparator && isPlainSeparator)) |
416 | continue; // we continue, this action will get an empty QRect |
417 | |
418 | previousWasSeparator = isPlainSeparator; |
419 | |
420 | //let the style modify the above size.. |
421 | QStyleOptionMenuItem opt; |
422 | q->initStyleOption(option: &opt, action); |
423 | const QFontMetrics &fm = opt.fontMetrics; |
424 | |
425 | QSize sz; |
426 | if (QWidget *w = widgetItems.value(akey: action)) { |
427 | sz = w->sizeHint().expandedTo(otherSize: w->minimumSize()).expandedTo(otherSize: w->minimumSizeHint()).boundedTo(otherSize: w->maximumSize()); |
428 | } else { |
429 | //calc what I think the size is.. |
430 | if (action->isSeparator()) { |
431 | sz = QSize(2, 2); |
432 | } else { |
433 | QString s = action->text(); |
434 | int t = s.indexOf(c: QLatin1Char('\t')); |
435 | if (t != -1) { |
436 | tabWidth = qMax(a: int(tabWidth), b: qfm.horizontalAdvance(s.mid(position: t+1))); |
437 | s = s.left(n: t); |
438 | #ifndef QT_NO_SHORTCUT |
439 | } else if (action->isShortcutVisibleInContextMenu() || !contextMenu) { |
440 | QKeySequence seq = action->shortcut(); |
441 | if (!seq.isEmpty()) |
442 | tabWidth = qMax(a: int(tabWidth), b: qfm.horizontalAdvance(seq.toString(format: QKeySequence::NativeText))); |
443 | #endif |
444 | } |
445 | sz.setWidth(fm.boundingRect(r: QRect(), flags: Qt::TextSingleLine | Qt::TextShowMnemonic, text: s).width()); |
446 | sz.setHeight(qMax(a: fm.height(), b: qfm.height())); |
447 | |
448 | QIcon is = action->icon(); |
449 | if (!is.isNull()) { |
450 | QSize is_sz = QSize(icone, icone); |
451 | if (is_sz.height() > sz.height()) |
452 | sz.setHeight(is_sz.height()); |
453 | } |
454 | } |
455 | sz = style->sizeFromContents(ct: QStyle::CT_MenuItem, opt: &opt, contentsSize: sz, w: q); |
456 | } |
457 | |
458 | |
459 | if (!sz.isEmpty()) { |
460 | max_column_width = qMax(a: max_column_width, b: sz.width()); |
461 | //wrapping |
462 | if (!scroll && y + sz.height() > column_max_y) { |
463 | ncols++; |
464 | y = base_y; |
465 | } else { |
466 | y += sz.height(); |
467 | } |
468 | //update the item |
469 | actionRects[i] = QRect(0, 0, sz.width(), sz.height()); |
470 | } |
471 | } |
472 | |
473 | max_column_width += tabWidth; //finally add in the tab width |
474 | if (!tornoff || (tornoff && scroll)) { // exclude non-scrollable tear-off menu since the tear-off menu has a fixed size |
475 | const int sfcMargin = style->sizeFromContents(ct: QStyle::CT_Menu, opt: &opt, contentsSize: QApplication::globalStrut(), w: q).width() - QApplication::globalStrut().width(); |
476 | const int min_column_width = q->minimumWidth() - (sfcMargin + leftmargin + rightmargin + 2 * (fw + hmargin)); |
477 | max_column_width = qMax(a: min_column_width, b: max_column_width); |
478 | } |
479 | |
480 | //calculate position |
481 | int x = hmargin + fw + leftmargin; |
482 | y = base_y; |
483 | |
484 | for(int i = 0; i < actions.count(); i++) { |
485 | QRect &rect = actionRects[i]; |
486 | if (rect.isNull()) |
487 | continue; |
488 | if (!scroll && y + rect.height() > column_max_y) { |
489 | x += max_column_width + hmargin; |
490 | y = base_y; |
491 | } |
492 | rect.translate(dx: x, dy: y); //move |
493 | rect.setWidth(max_column_width); //uniform width |
494 | |
495 | //we need to update the widgets geometry |
496 | if (QWidget *widget = widgetItems.value(akey: actions.at(i))) { |
497 | widget->setGeometry(rect); |
498 | widget->setVisible(actions.at(i)->isVisible()); |
499 | } |
500 | |
501 | y += rect.height(); |
502 | } |
503 | itemsDirty = 0; |
504 | } |
505 | |
506 | int QMenuPrivate::() const |
507 | { |
508 | //let's try to get the last visible action |
509 | int lastVisibleAction = actions.count() - 1; |
510 | for (;lastVisibleAction >= 0; --lastVisibleAction) { |
511 | const QAction *action = actions.at(i: lastVisibleAction); |
512 | if (action->isVisible()) { |
513 | //removing trailing separators |
514 | if (action->isSeparator() && collapsibleSeparators) |
515 | continue; |
516 | break; |
517 | } |
518 | } |
519 | return lastVisibleAction; |
520 | } |
521 | |
522 | |
523 | QRect QMenuPrivate::(QAction *act) const |
524 | { |
525 | int index = actions.indexOf(t: act); |
526 | if (index == -1) |
527 | return QRect(); |
528 | |
529 | updateActionRects(); |
530 | |
531 | //we found the action |
532 | return actionRects.at(i: index); |
533 | } |
534 | |
535 | void QMenuPrivate::() |
536 | { |
537 | Q_Q(QMenu); |
538 | bool = q->style()->styleHint(stylehint: QStyle::SH_Menu_FadeOutOnHide); |
539 | if (!tornoff) { |
540 | QWidget *caused = causedPopup.widget; |
541 | hideMenu(menu: q); //hide after getting causedPopup |
542 | while(caused) { |
543 | #if QT_CONFIG(menubar) |
544 | if (QMenuBar *mb = qobject_cast<QMenuBar*>(object: caused)) { |
545 | mb->d_func()->setCurrentAction(nullptr); |
546 | mb->d_func()->setKeyboardMode(false); |
547 | caused = nullptr; |
548 | } else |
549 | #endif |
550 | if (QMenu *m = qobject_cast<QMenu*>(object: caused)) { |
551 | caused = m->d_func()->causedPopup.widget; |
552 | if (!m->d_func()->tornoff) |
553 | hideMenu(menu: m); |
554 | if (!fadeMenus) // Mac doesn't clear the action until after hidden. |
555 | m->d_func()->setCurrentAction(nullptr); |
556 | } else { caused = nullptr; |
557 | } |
558 | } |
559 | } |
560 | setCurrentAction(nullptr); |
561 | } |
562 | |
563 | void QMenuPrivate::(QMenu *) |
564 | { |
565 | if (!menu) |
566 | return; |
567 | |
568 | // See two execs below. They may trigger an akward situation |
569 | // when 'menu' (also known as 'q' or 'this' in the many functions |
570 | // around) to become a dangling pointer if the loop manages |
571 | // to execute 'deferred delete' ... posted while executing |
572 | // this same loop. Not good! |
573 | struct Reposter : QObject |
574 | { |
575 | Reposter(QMenu *) : q(menu) |
576 | { |
577 | Q_ASSERT(q); |
578 | q->installEventFilter(filterObj: this); |
579 | } |
580 | ~Reposter() |
581 | { |
582 | if (deleteLater) |
583 | q->deleteLater(); |
584 | } |
585 | bool eventFilter(QObject *obj, QEvent *event) override |
586 | { |
587 | if (obj == q && event->type() == QEvent::DeferredDelete) |
588 | return deleteLater = true; |
589 | |
590 | return QObject::eventFilter(watched: obj, event); |
591 | } |
592 | QMenu *q = nullptr; |
593 | bool deleteLater = false; |
594 | }; |
595 | |
596 | #if QT_CONFIG(effects) |
597 | QSignalBlocker blocker(menu); |
598 | aboutToHide = true; |
599 | // Flash item which is about to trigger (if any). |
600 | if (menu->style()->styleHint(stylehint: QStyle::SH_Menu_FlashTriggeredItem) |
601 | && currentAction && currentAction == actionAboutToTrigger |
602 | && menu->actions().contains(t: currentAction)) { |
603 | QEventLoop eventLoop; |
604 | QAction *activeAction = currentAction; |
605 | |
606 | menu->setActiveAction(nullptr); |
607 | const Reposter deleteDeleteLate(menu); |
608 | QTimer::singleShot(msec: 60, receiver: &eventLoop, SLOT(quit())); |
609 | eventLoop.exec(); |
610 | |
611 | // Select and wait 20 ms. |
612 | menu->setActiveAction(activeAction); |
613 | QTimer::singleShot(msec: 20, receiver: &eventLoop, SLOT(quit())); |
614 | eventLoop.exec(); |
615 | } |
616 | |
617 | aboutToHide = false; |
618 | blocker.unblock(); |
619 | #endif // QT_CONFIG(effects) |
620 | if (activeMenu == menu) |
621 | activeMenu = nullptr; |
622 | menu->d_func()->causedPopup.action = nullptr; |
623 | menu->close(); |
624 | menu->d_func()->causedPopup.widget = nullptr; |
625 | } |
626 | |
627 | void QMenuPrivate::(QAction *action, int delay, bool activateFirst) |
628 | { |
629 | Q_Q(QMenu); |
630 | if (action) { |
631 | if (action->isEnabled()) { |
632 | if (!delay) |
633 | q->internalDelayedPopup(); |
634 | else if (action->menu() && !action->menu()->isVisible()) |
635 | delayState.start(timeout: delay, toStartAction: action); |
636 | else if (!action->menu()) |
637 | delayState.stop(); |
638 | if (activateFirst && action->menu()) |
639 | action->menu()->d_func()->setFirstActionActive(); |
640 | } |
641 | } else if (QMenu * = activeMenu) { //hide the current item |
642 | hideMenu(menu); |
643 | } |
644 | } |
645 | |
646 | void QMenuPrivate::() |
647 | { |
648 | Q_Q(QMenu); |
649 | QAction *current = currentAction; |
650 | if(current && (!current->isEnabled() || current->menu() || current->isSeparator())) |
651 | current = nullptr; |
652 | for(QWidget *caused = q; caused;) { |
653 | if (QMenu *m = qobject_cast<QMenu*>(object: caused)) { |
654 | caused = m->d_func()->causedPopup.widget; |
655 | if (m->d_func()->eventLoop) |
656 | m->d_func()->syncAction = current; // synchronous operation |
657 | } else { |
658 | break; |
659 | } |
660 | } |
661 | } |
662 | |
663 | |
664 | void QMenuPrivate::() |
665 | { |
666 | Q_Q(QMenu); |
667 | updateActionRects(); |
668 | for(int i = 0, saccum = 0; i < actions.count(); i++) { |
669 | const QRect &rect = actionRects.at(i); |
670 | if (rect.isNull()) |
671 | continue; |
672 | if (scroll && scroll->scrollFlags & QMenuScroller::ScrollUp) { |
673 | saccum -= rect.height(); |
674 | if (saccum > scroll->scrollOffset - scrollerHeight()) |
675 | continue; |
676 | } |
677 | QAction *act = actions.at(i); |
678 | if (!act->isSeparator() && |
679 | (q->style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled, opt: nullptr, widget: q) |
680 | || act->isEnabled())) { |
681 | setCurrentAction(act); |
682 | break; |
683 | } |
684 | } |
685 | } |
686 | |
687 | // popup == -1 means do not popup, 0 means immediately, others mean use a timer |
688 | void QMenuPrivate::(QAction *action, int , SelectionReason reason, bool activateFirst) |
689 | { |
690 | Q_Q(QMenu); |
691 | tearoffHighlighted = 0; |
692 | |
693 | if (action |
694 | && (action->isSeparator() |
695 | || (!action->isEnabled() && !q->style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled, opt: nullptr, widget: q)))) |
696 | action = nullptr; |
697 | |
698 | // Reselect the currently active action in case mouse moved over other menu items when |
699 | // moving from sub menu action to sub menu (QTBUG-20094). |
700 | if (reason != SelectedFromKeyboard) { |
701 | if (QMenu * = qobject_cast<QMenu*>(object: causedPopup.widget)) { |
702 | if (causedPopup.action && menu->d_func()->activeMenu == q) |
703 | // Reselect parent menu action only if mouse is over a menu and parent menu action is not already selected (QTBUG-47987) |
704 | if (hasReceievedEnter && menu->d_func()->currentAction != causedPopup.action) |
705 | menu->d_func()->setCurrentAction(action: causedPopup.action, popup: 0, reason, activateFirst: false); |
706 | } |
707 | } |
708 | |
709 | if (currentAction) |
710 | q->update(actionRect(act: currentAction)); |
711 | |
712 | QMenu * = activeMenu; |
713 | QAction *previousAction = currentAction; |
714 | |
715 | currentAction = action; |
716 | if (action) { |
717 | if (!action->isSeparator()) { |
718 | activateAction(action, QAction::Hover); |
719 | if (popup != -1) { |
720 | // if the menu is visible then activate the required action, |
721 | // otherwise we just mark the action as currentAction |
722 | // and activate it when the menu will be popuped. |
723 | if (q->isVisible()) |
724 | popupAction(action: currentAction, delay: popup, activateFirst); |
725 | } |
726 | q->update(actionRect(act: action)); |
727 | |
728 | if (reason == SelectedFromKeyboard) { |
729 | QWidget *widget = widgetItems.value(akey: action); |
730 | if (widget) { |
731 | if (widget->focusPolicy() != Qt::NoFocus) |
732 | widget->setFocus(Qt::TabFocusReason); |
733 | } else { |
734 | //when the action has no QWidget, the QMenu itself should |
735 | // get the focus |
736 | // Since the menu is a pop-up, it uses the popup reason. |
737 | if (!q->hasFocus()) { |
738 | q->setFocus(Qt::PopupFocusReason); |
739 | } |
740 | } |
741 | } |
742 | } |
743 | #if QT_CONFIG(statustip) |
744 | } else if (previousAction) { |
745 | previousAction->d_func()->showStatusText(w: topCausedWidget(), str: QString()); |
746 | #endif |
747 | } |
748 | if (hideActiveMenu && previousAction != currentAction) { |
749 | if (popup == -1) { |
750 | #if QT_CONFIG(effects) |
751 | // kill any running effect |
752 | qFadeEffect(nullptr); |
753 | qScrollEffect(nullptr); |
754 | #endif |
755 | hideMenu(menu: hideActiveMenu); |
756 | } else if (!currentAction || !currentAction->menu()) { |
757 | sloppyState.startTimerIfNotRunning(); |
758 | } |
759 | } |
760 | } |
761 | |
762 | void QMenuSloppyState::() |
763 | { |
764 | m_enabled = false; |
765 | m_first_mouse = true; |
766 | m_init_guard = false; |
767 | m_use_reset_action = true; |
768 | m_uni_dir_discarded_count = 0; |
769 | m_time.stop(); |
770 | m_reset_action = nullptr; |
771 | m_origin_action = nullptr; |
772 | m_action_rect = QRect(); |
773 | m_previous_point = QPointF(); |
774 | if (m_sub_menu) { |
775 | QMenuPrivate::get(m: m_sub_menu)->sloppyState.m_parent = nullptr; |
776 | m_sub_menu = nullptr; |
777 | } |
778 | } |
779 | void QMenuSloppyState::() |
780 | { |
781 | QMenuPrivate * = QMenuPrivate::get(m: m_menu); |
782 | |
783 | if (m_discard_state_when_entering_parent && m_sub_menu == menuPriv->activeMenu) { |
784 | menuPriv->hideMenu(menu: m_sub_menu); |
785 | reset(); |
786 | } |
787 | if (m_parent) |
788 | m_parent->childEnter(); |
789 | } |
790 | |
791 | void QMenuSloppyState::() |
792 | { |
793 | stopTimer(); |
794 | if (m_parent) |
795 | m_parent->childEnter(); |
796 | } |
797 | |
798 | void QMenuSloppyState::() |
799 | { |
800 | if (!m_dont_start_time_on_leave) { |
801 | if (m_parent) |
802 | m_parent->childLeave(); |
803 | startTimerIfNotRunning(); |
804 | } |
805 | } |
806 | |
807 | void QMenuSloppyState::() |
808 | { |
809 | if (m_enabled && !QMenuPrivate::get(m: m_menu)->hasReceievedEnter) { |
810 | startTimerIfNotRunning(); |
811 | if (m_parent) |
812 | m_parent->childLeave(); |
813 | } |
814 | } |
815 | |
816 | void QMenuSloppyState::(const QRect &actionRect, QAction *resetAction, QMenu *) |
817 | { |
818 | m_enabled = true; |
819 | m_init_guard = true; |
820 | m_use_reset_action = true; |
821 | m_time.stop(); |
822 | m_action_rect = actionRect; |
823 | if (m_sub_menu) |
824 | QMenuPrivate::get(m: m_sub_menu)->sloppyState.m_parent = nullptr; |
825 | m_sub_menu = subMenu; |
826 | QMenuPrivate::get(m: subMenu)->sloppyState.m_parent = this; |
827 | m_reset_action = resetAction; |
828 | m_origin_action = resetAction; |
829 | } |
830 | |
831 | bool QMenuSloppyState::() const |
832 | { |
833 | return m_parent && m_parent->m_menu && QMenuPrivate::get(m: m_parent->m_menu)->delayState.timer.isActive(); |
834 | } |
835 | |
836 | class ResetOnDestroy |
837 | { |
838 | public: |
839 | (QMenuSloppyState *sloppyState, bool *guard) |
840 | : toReset(sloppyState) |
841 | , guard(guard) |
842 | { |
843 | *guard = false; |
844 | } |
845 | |
846 | ~ResetOnDestroy() |
847 | { |
848 | if (!*guard) |
849 | toReset->reset(); |
850 | } |
851 | |
852 | QMenuSloppyState *toReset; |
853 | bool *guard; |
854 | }; |
855 | |
856 | void QMenuSloppyState::() |
857 | { |
858 | QMenuPrivate * = QMenuPrivate::get(m: m_menu); |
859 | |
860 | bool reallyHasMouse = menu_priv->hasReceievedEnter; |
861 | if (!reallyHasMouse) { |
862 | // Check whether the menu really has a mouse, because only active popup |
863 | // menu gets the enter/leave events. Currently Cocoa is an exception. |
864 | const QPoint lastCursorPos = QGuiApplicationPrivate::lastCursorPosition.toPoint(); |
865 | reallyHasMouse = m_menu->frameGeometry().contains(p: lastCursorPos); |
866 | } |
867 | |
868 | if (menu_priv->currentAction == m_reset_action |
869 | && reallyHasMouse |
870 | && (menu_priv->currentAction |
871 | && menu_priv->currentAction->menu() == menu_priv->activeMenu)) { |
872 | return; |
873 | } |
874 | |
875 | ResetOnDestroy resetState(this, &m_init_guard); |
876 | |
877 | if (hasParentActiveDelayTimer() || !m_menu->isVisible()) |
878 | return; |
879 | |
880 | if (m_sub_menu) |
881 | menu_priv->hideMenu(menu: m_sub_menu); |
882 | |
883 | if (reallyHasMouse) { |
884 | if (m_use_reset_action) |
885 | menu_priv->setCurrentAction(action: m_reset_action, popup: 0); |
886 | } else { |
887 | menu_priv->setCurrentAction(action: nullptr, popup: 0); |
888 | } |
889 | } |
890 | |
891 | //return the top causedPopup.widget that is not a QMenu |
892 | QWidget *QMenuPrivate::() const |
893 | { |
894 | QWidget* top = causedPopup.widget; |
895 | while (QMenu* m = qobject_cast<QMenu *>(object: top)) |
896 | top = m->d_func()->causedPopup.widget; |
897 | return top; |
898 | } |
899 | |
900 | QAction *QMenuPrivate::(QPoint p) const |
901 | { |
902 | if (!rect().contains(p)) //sanity check |
903 | return nullptr; |
904 | |
905 | for(int i = 0; i < actionRects.count(); i++) { |
906 | if (actionRects.at(i).contains(p)) |
907 | return actions.at(i); |
908 | } |
909 | return nullptr; |
910 | } |
911 | |
912 | void QMenuPrivate::(QAction *a) |
913 | { |
914 | Q_Q(QMenu); |
915 | QObject::disconnect(sender: menuAction, SIGNAL(destroyed()), receiver: q, SLOT(_q_overrideMenuActionDestroyed())); |
916 | if (a) { |
917 | menuAction = a; |
918 | QObject::connect(sender: a, SIGNAL(destroyed()), receiver: q, SLOT(_q_overrideMenuActionDestroyed())); |
919 | } else { //we revert back to the default action created by the QMenu itself |
920 | menuAction = defaultMenuAction; |
921 | } |
922 | } |
923 | |
924 | void QMenuPrivate::() |
925 | { |
926 | menuAction=defaultMenuAction; |
927 | } |
928 | |
929 | void QMenuPrivate::() |
930 | { |
931 | Q_Q(QMenu); |
932 | //we need to mimic the cause of the popup's layout direction |
933 | //to allow setting it on a mainwindow for example |
934 | //we call setLayoutDirection_helper to not overwrite a user-defined value |
935 | if (!q->testAttribute(attribute: Qt::WA_SetLayoutDirection)) { |
936 | if (QWidget *w = causedPopup.widget) |
937 | setLayoutDirection_helper(w->layoutDirection()); |
938 | else if (QWidget *w = q->parentWidget()) |
939 | setLayoutDirection_helper(w->layoutDirection()); |
940 | else |
941 | setLayoutDirection_helper(QGuiApplication::layoutDirection()); |
942 | } |
943 | } |
944 | |
945 | void QMenuPrivate::(QPainter *painter, QMenuPrivate::ScrollerTearOffItem::Type type, const QRect &rect) |
946 | { |
947 | if (!painter || rect.isEmpty()) |
948 | return; |
949 | |
950 | if (!scroll || !(scroll->scrollFlags & (QMenuPrivate::QMenuScroller::ScrollUp |
951 | | QMenuPrivate::QMenuScroller::ScrollDown))) |
952 | return; |
953 | |
954 | Q_Q(QMenu); |
955 | QStyleOptionMenuItem ; |
956 | menuOpt.initFrom(w: q); |
957 | menuOpt.state = QStyle::State_None; |
958 | menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; |
959 | menuOpt.maxIconWidth = 0; |
960 | menuOpt.tabWidth = 0; |
961 | menuOpt.rect = rect; |
962 | menuOpt.menuItemType = QStyleOptionMenuItem::Scroller; |
963 | menuOpt.state |= QStyle::State_Enabled; |
964 | if (type == QMenuPrivate::ScrollerTearOffItem::ScrollDown) |
965 | menuOpt.state |= QStyle::State_DownArrow; |
966 | |
967 | painter->setClipRect(menuOpt.rect); |
968 | q->style()->drawControl(element: QStyle::CE_MenuScroller, opt: &menuOpt, p: painter, w: q); |
969 | } |
970 | |
971 | void QMenuPrivate::(QPainter *painter, const QRect &rect) |
972 | { |
973 | if (!painter || rect.isEmpty()) |
974 | return; |
975 | |
976 | if (!tearoff) |
977 | return; |
978 | |
979 | Q_Q(QMenu); |
980 | QStyleOptionMenuItem ; |
981 | menuOpt.initFrom(w: q); |
982 | menuOpt.state = QStyle::State_None; |
983 | menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; |
984 | menuOpt.maxIconWidth = 0; |
985 | menuOpt.tabWidth = 0; |
986 | menuOpt.rect = rect; |
987 | menuOpt.menuItemType = QStyleOptionMenuItem::TearOff; |
988 | if (tearoffHighlighted) |
989 | menuOpt.state |= QStyle::State_Selected; |
990 | |
991 | painter->setClipRect(menuOpt.rect); |
992 | q->style()->drawControl(element: QStyle::CE_MenuTearoff, opt: &menuOpt, p: painter, w: q); |
993 | } |
994 | |
995 | QRect QMenuPrivate::() const |
996 | { |
997 | Q_Q(const QMenu); |
998 | QStyle *style = q->style(); |
999 | QStyleOption opt(0); |
1000 | opt.init(w: q); |
1001 | const int hmargin = style->pixelMetric(metric: QStyle::PM_MenuHMargin, option: &opt, widget: q); |
1002 | const int vmargin = style->pixelMetric(metric: QStyle::PM_MenuVMargin, option: &opt, widget: q); |
1003 | const int fw = style->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: &opt, widget: q); |
1004 | return (q->rect().adjusted(xp1: hmargin + fw + leftmargin, yp1: vmargin + fw + topmargin, |
1005 | xp2: -(hmargin + fw + rightmargin), yp2: -(vmargin + fw + bottommargin))); |
1006 | } |
1007 | |
1008 | QMenuPrivate::ScrollerTearOffItem::(QMenuPrivate::ScrollerTearOffItem::Type type, QMenuPrivate *mPrivate, QWidget *parent, Qt::WindowFlags f) |
1009 | : QWidget(parent, f), menuPrivate(mPrivate), scrollType(type) |
1010 | { |
1011 | if (parent) |
1012 | setMouseTracking(parent->style()->styleHint(stylehint: QStyle::SH_Menu_MouseTracking, opt: nullptr, widget: parent)); |
1013 | } |
1014 | |
1015 | void QMenuPrivate::ScrollerTearOffItem::(QPaintEvent *e) |
1016 | { |
1017 | if (!e->rect().intersects(r: rect())) |
1018 | return; |
1019 | |
1020 | QPainter p(this); |
1021 | QWidget *parent = parentWidget(); |
1022 | |
1023 | //paint scroll up / down arrows |
1024 | menuPrivate->drawScroller(painter: &p, type: scrollType, rect: QRect(0, 0, width(), menuPrivate->scrollerHeight())); |
1025 | //paint the tear off |
1026 | if (scrollType == QMenuPrivate::ScrollerTearOffItem::ScrollUp) { |
1027 | QRect rect(0, 0, width(), parent->style()->pixelMetric(metric: QStyle::PM_MenuTearoffHeight, option: nullptr, widget: parent)); |
1028 | if (menuPrivate->scroll && menuPrivate->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) |
1029 | rect.translate(dx: 0, dy: menuPrivate->scrollerHeight()); |
1030 | menuPrivate->drawTearOff(painter: &p, rect); |
1031 | } |
1032 | } |
1033 | |
1034 | void QMenuPrivate::ScrollerTearOffItem::(const QRect &rect) |
1035 | { |
1036 | if (rect.isEmpty()) |
1037 | setVisible(false); |
1038 | else { |
1039 | setGeometry(rect); |
1040 | raise(); |
1041 | setVisible(true); |
1042 | } |
1043 | } |
1044 | |
1045 | |
1046 | /*! |
1047 | Returns the action associated with this menu. |
1048 | */ |
1049 | QAction *QMenu::() const |
1050 | { |
1051 | return d_func()->menuAction; |
1052 | } |
1053 | |
1054 | /*! |
1055 | \property QMenu::title |
1056 | \brief The title of the menu |
1057 | |
1058 | This is equivalent to the QAction::text property of the menuAction(). |
1059 | |
1060 | By default, this property contains an empty string. |
1061 | */ |
1062 | QString QMenu::() const |
1063 | { |
1064 | return d_func()->menuAction->text(); |
1065 | } |
1066 | |
1067 | void QMenu::(const QString &text) |
1068 | { |
1069 | d_func()->menuAction->setText(text); |
1070 | } |
1071 | |
1072 | /*! |
1073 | \property QMenu::icon |
1074 | |
1075 | \brief The icon of the menu |
1076 | |
1077 | This is equivalent to the QAction::icon property of the menuAction(). |
1078 | |
1079 | By default, if no icon is explicitly set, this property contains a null icon. |
1080 | */ |
1081 | QIcon QMenu::() const |
1082 | { |
1083 | return d_func()->menuAction->icon(); |
1084 | } |
1085 | |
1086 | void QMenu::(const QIcon &icon) |
1087 | { |
1088 | d_func()->menuAction->setIcon(icon); |
1089 | } |
1090 | |
1091 | |
1092 | //actually performs the scrolling |
1093 | void QMenuPrivate::(QAction *action, QMenuScroller::ScrollLocation location, bool active) |
1094 | { |
1095 | Q_Q(QMenu); |
1096 | if (!scroll || !scroll->scrollFlags) |
1097 | return; |
1098 | updateActionRects(); |
1099 | int newOffset = 0; |
1100 | const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollerHeight() : 0; |
1101 | const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0; |
1102 | const int vmargin = q->style()->pixelMetric(metric: QStyle::PM_MenuVMargin, option: nullptr, widget: q); |
1103 | const int fw = q->style()->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: nullptr, widget: q); |
1104 | |
1105 | if (location == QMenuScroller::ScrollTop) { |
1106 | for(int i = 0, saccum = 0; i < actions.count(); i++) { |
1107 | if (actions.at(i) == action) { |
1108 | newOffset = topScroll - saccum; |
1109 | break; |
1110 | } |
1111 | saccum += actionRects.at(i).height(); |
1112 | } |
1113 | } else { |
1114 | for(int i = 0, saccum = 0; i < actions.count(); i++) { |
1115 | saccum += actionRects.at(i).height(); |
1116 | if (actions.at(i) == action) { |
1117 | if (location == QMenuScroller::ScrollCenter) |
1118 | newOffset = ((q->height() / 2) - botScroll) - (saccum - topScroll); |
1119 | else |
1120 | newOffset = (q->height() - botScroll) - saccum; |
1121 | break; |
1122 | } |
1123 | } |
1124 | if(newOffset) |
1125 | newOffset -= fw * 2; |
1126 | } |
1127 | |
1128 | //figure out which scroll flags |
1129 | uint newScrollFlags = QMenuScroller::ScrollNone; |
1130 | if (newOffset < 0) //easy and cheap one |
1131 | newScrollFlags |= QMenuScroller::ScrollUp; |
1132 | int saccum = newOffset; |
1133 | for(int i = 0; i < actionRects.count(); i++) { |
1134 | saccum += actionRects.at(i).height(); |
1135 | if (saccum > q->height()) { |
1136 | newScrollFlags |= QMenuScroller::ScrollDown; |
1137 | break; |
1138 | } |
1139 | } |
1140 | |
1141 | if (!(newScrollFlags & QMenuScroller::ScrollDown) && (scroll->scrollFlags & QMenuScroller::ScrollDown)) { |
1142 | newOffset = q->height() - (saccum - newOffset) - fw*2 - vmargin - topmargin - bottommargin; //last item at bottom |
1143 | if (tearoff) |
1144 | newOffset -= q->style()->pixelMetric(metric: QStyle::PM_MenuTearoffHeight, option: nullptr, widget: q); |
1145 | } |
1146 | |
1147 | if (!(newScrollFlags & QMenuScroller::ScrollUp) && (scroll->scrollFlags & QMenuScroller::ScrollUp)) { |
1148 | newOffset = 0; //first item at top |
1149 | } |
1150 | |
1151 | if (newScrollFlags & QMenuScroller::ScrollUp) |
1152 | newOffset -= vmargin; |
1153 | |
1154 | QRect screen = popupGeometry(); |
1155 | const int desktopFrame = q->style()->pixelMetric(metric: QStyle::PM_MenuDesktopFrameWidth, option: nullptr, widget: q); |
1156 | if (q->height() < screen.height()-(desktopFrame*2)-1) { |
1157 | QRect geom = q->geometry(); |
1158 | if (newOffset > scroll->scrollOffset && (scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollUp)) { //scroll up |
1159 | const int newHeight = geom.height()-(newOffset-scroll->scrollOffset); |
1160 | if(newHeight > geom.height()) |
1161 | geom.setHeight(newHeight); |
1162 | } else if(scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollDown) { |
1163 | int newTop = geom.top() + (newOffset-scroll->scrollOffset); |
1164 | if (newTop < desktopFrame+screen.top()) |
1165 | newTop = desktopFrame+screen.top(); |
1166 | if (newTop < geom.top()) { |
1167 | geom.setTop(newTop); |
1168 | newOffset = 0; |
1169 | newScrollFlags &= ~QMenuScroller::ScrollUp; |
1170 | } |
1171 | } |
1172 | if (geom.bottom() > screen.bottom() - desktopFrame) |
1173 | geom.setBottom(screen.bottom() - desktopFrame); |
1174 | if (geom.top() < desktopFrame+screen.top()) |
1175 | geom.setTop(desktopFrame+screen.top()); |
1176 | if (geom != q->geometry()) { |
1177 | #if 0 |
1178 | if (newScrollFlags & QMenuScroller::ScrollDown && |
1179 | q->geometry().top() - geom.top() >= -newOffset) |
1180 | newScrollFlags &= ~QMenuScroller::ScrollDown; |
1181 | #endif |
1182 | q->setGeometry(geom); |
1183 | } |
1184 | } |
1185 | |
1186 | //actually update flags |
1187 | const int delta = qMin(a: 0, b: newOffset) - scroll->scrollOffset; //make sure the new offset is always negative |
1188 | if (!itemsDirty && delta) { |
1189 | //we've scrolled so we need to update the action rects |
1190 | for (int i = 0; i < actionRects.count(); ++i) { |
1191 | QRect ¤t = actionRects[i]; |
1192 | current.moveTop(pos: current.top() + delta); |
1193 | |
1194 | //we need to update the widgets geometry |
1195 | if (QWidget *w = widgetItems.value(akey: actions.at(i))) |
1196 | w->setGeometry(current); |
1197 | } |
1198 | } |
1199 | scroll->scrollOffset += delta; |
1200 | scroll->scrollFlags = newScrollFlags; |
1201 | if (active) |
1202 | setCurrentAction(action); |
1203 | |
1204 | q->update(); //issue an update so we see all the new state.. |
1205 | } |
1206 | |
1207 | void QMenuPrivate::(QMenuScroller::ScrollLocation location, bool active) |
1208 | { |
1209 | Q_Q(QMenu); |
1210 | updateActionRects(); |
1211 | if(location == QMenuScroller::ScrollBottom) { |
1212 | for(int i = actions.size()-1; i >= 0; --i) { |
1213 | QAction *act = actions.at(i); |
1214 | if (actionRects.at(i).isNull()) |
1215 | continue; |
1216 | if (!act->isSeparator() && |
1217 | (q->style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled, opt: nullptr, widget: q) |
1218 | || act->isEnabled())) { |
1219 | if(scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown) |
1220 | scrollMenu(action: act, location: QMenuPrivate::QMenuScroller::ScrollBottom, active); |
1221 | else if(active) |
1222 | setCurrentAction(action: act, /*popup*/-1, reason: QMenuPrivate::SelectedFromKeyboard); |
1223 | break; |
1224 | } |
1225 | } |
1226 | } else if(location == QMenuScroller::ScrollTop) { |
1227 | for(int i = 0; i < actions.size(); ++i) { |
1228 | QAction *act = actions.at(i); |
1229 | if (actionRects.at(i).isNull()) |
1230 | continue; |
1231 | if (!act->isSeparator() && |
1232 | (q->style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled, opt: nullptr, widget: q) |
1233 | || act->isEnabled())) { |
1234 | if(scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) |
1235 | scrollMenu(action: act, location: QMenuPrivate::QMenuScroller::ScrollTop, active); |
1236 | else if(active) |
1237 | setCurrentAction(action: act, /*popup*/-1, reason: QMenuPrivate::SelectedFromKeyboard); |
1238 | break; |
1239 | } |
1240 | } |
1241 | } |
1242 | } |
1243 | |
1244 | //only directional |
1245 | void QMenuPrivate::(QMenuScroller::ScrollDirection direction, bool page, bool active) |
1246 | { |
1247 | Q_Q(QMenu); |
1248 | if (!scroll || !(scroll->scrollFlags & direction)) //not really possible... |
1249 | return; |
1250 | updateActionRects(); |
1251 | const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollerHeight() : 0; |
1252 | const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0; |
1253 | const int vmargin = q->style()->pixelMetric(metric: QStyle::PM_MenuVMargin, option: nullptr, widget: q); |
1254 | const int fw = q->style()->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: nullptr, widget: q); |
1255 | const int offset = topScroll ? topScroll-vmargin : 0; |
1256 | if (direction == QMenuScroller::ScrollUp) { |
1257 | for(int i = 0, saccum = 0; i < actions.count(); i++) { |
1258 | saccum -= actionRects.at(i).height(); |
1259 | if (saccum <= scroll->scrollOffset-offset) { |
1260 | scrollMenu(action: actions.at(i), location: page ? QMenuScroller::ScrollBottom : QMenuScroller::ScrollTop, active); |
1261 | break; |
1262 | } |
1263 | } |
1264 | } else if (direction == QMenuScroller::ScrollDown) { |
1265 | bool scrolled = false; |
1266 | for(int i = 0, saccum = 0; i < actions.count(); i++) { |
1267 | const int iHeight = actionRects.at(i).height(); |
1268 | saccum -= iHeight; |
1269 | if (saccum <= scroll->scrollOffset-offset) { |
1270 | const int scrollerArea = q->height() - botScroll - fw*2; |
1271 | int visible = (scroll->scrollOffset-offset) - saccum; |
1272 | for(i++ ; i < actions.count(); i++) { |
1273 | visible += actionRects.at(i).height(); |
1274 | if (visible > scrollerArea - topScroll) { |
1275 | scrolled = true; |
1276 | scrollMenu(action: actions.at(i), location: page ? QMenuScroller::ScrollTop : QMenuScroller::ScrollBottom, active); |
1277 | break; |
1278 | } |
1279 | } |
1280 | break; |
1281 | } |
1282 | } |
1283 | if(!scrolled) { |
1284 | scroll->scrollFlags &= ~QMenuScroller::ScrollDown; |
1285 | q->update(); |
1286 | } |
1287 | } |
1288 | } |
1289 | |
1290 | /* This is poor-mans eventfilters. This avoids the use of |
1291 | eventFilter (which can be nasty for users of QMenuBar's). */ |
1292 | bool QMenuPrivate::(QMouseEvent *e) |
1293 | { |
1294 | Q_Q(QMenu); |
1295 | QPoint pos = q->mapFromGlobal(e->globalPos()); |
1296 | |
1297 | QStyle *style = q->style(); |
1298 | QStyleOption opt(0); |
1299 | opt.init(w: q); |
1300 | const int hmargin = style->pixelMetric(metric: QStyle::PM_MenuHMargin, option: &opt, widget: q); |
1301 | const int vmargin = style->pixelMetric(metric: QStyle::PM_MenuVMargin, option: &opt, widget: q); |
1302 | const int fw = style->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: &opt, widget: q); |
1303 | |
1304 | if (scroll && !activeMenu) { //let the scroller "steal" the event |
1305 | bool isScroll = false; |
1306 | if (pos.x() >= 0 && pos.x() < q->width()) { |
1307 | for (int dir = QMenuScroller::ScrollUp; dir <= QMenuScroller::ScrollDown; dir = dir << 1) { |
1308 | if (scroll->scrollFlags & dir) { |
1309 | if (dir == QMenuScroller::ScrollUp) |
1310 | isScroll = (pos.y() <= scrollerHeight() + fw + vmargin + topmargin); |
1311 | else if (dir == QMenuScroller::ScrollDown) |
1312 | isScroll = (pos.y() >= q->height() - scrollerHeight() - fw - vmargin - bottommargin); |
1313 | if (isScroll) { |
1314 | scroll->scrollDirection = dir; |
1315 | break; |
1316 | } |
1317 | } |
1318 | } |
1319 | } |
1320 | if (isScroll) { |
1321 | scroll->scrollTimer.start(msec: 50, obj: q); |
1322 | return true; |
1323 | } else { |
1324 | scroll->scrollTimer.stop(); |
1325 | } |
1326 | } |
1327 | |
1328 | if (tearoff) { //let the tear off thingie "steal" the event.. |
1329 | QRect tearRect(leftmargin + hmargin + fw, topmargin + vmargin + fw, q->width() - fw * 2 - hmargin * 2 -leftmargin - rightmargin, |
1330 | q->style()->pixelMetric(metric: QStyle::PM_MenuTearoffHeight, option: &opt, widget: q)); |
1331 | if (scroll && scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) |
1332 | tearRect.translate(dx: 0, dy: scrollerHeight()); |
1333 | q->update(tearRect); |
1334 | if (tearRect.contains(p: pos) && hasMouseMoved(globalPos: e->globalPos())) { |
1335 | setCurrentAction(action: nullptr); |
1336 | tearoffHighlighted = 1; |
1337 | if (e->type() == QEvent::MouseButtonRelease) { |
1338 | if (!tornPopup) |
1339 | tornPopup = new QTornOffMenu(q); |
1340 | tornPopup->setGeometry(q->geometry()); |
1341 | tornPopup->show(); |
1342 | hideUpToMenuBar(); |
1343 | } |
1344 | return true; |
1345 | } |
1346 | tearoffHighlighted = 0; |
1347 | } |
1348 | |
1349 | if (q->frameGeometry().contains(p: e->globalPos())) |
1350 | return false; //otherwise if the event is in our rect we want it.. |
1351 | |
1352 | for(QWidget *caused = causedPopup.widget; caused;) { |
1353 | bool passOnEvent = false; |
1354 | QWidget *next_widget = nullptr; |
1355 | QPoint cpos = caused->mapFromGlobal(e->globalPos()); |
1356 | #if QT_CONFIG(menubar) |
1357 | if (QMenuBar *mb = qobject_cast<QMenuBar*>(object: caused)) { |
1358 | passOnEvent = mb->rect().contains(p: cpos); |
1359 | } else |
1360 | #endif |
1361 | if (QMenu *m = qobject_cast<QMenu*>(object: caused)) { |
1362 | passOnEvent = m->rect().contains(p: cpos); |
1363 | next_widget = m->d_func()->causedPopup.widget; |
1364 | } |
1365 | if (passOnEvent) { |
1366 | if (e->type() != QEvent::MouseButtonRelease || mouseDown == caused) { |
1367 | QMouseEvent new_e(e->type(), cpos, caused->mapTo(caused->topLevelWidget(), cpos), e->screenPos(), |
1368 | e->button(), e->buttons(), e->modifiers(), e->source()); |
1369 | QCoreApplication::sendEvent(receiver: caused, event: &new_e); |
1370 | return true; |
1371 | } |
1372 | } |
1373 | caused = next_widget; |
1374 | if (!caused) |
1375 | sloppyState.leave(); // Start timers |
1376 | } |
1377 | return false; |
1378 | } |
1379 | |
1380 | void QMenuPrivate::(const QVector<QPointer<QWidget> > &causedStack, QAction *action, QAction::ActionEvent action_e, bool self) |
1381 | { |
1382 | QBoolBlocker guard(activationRecursionGuard); |
1383 | if(self) |
1384 | action->activate(event: action_e); |
1385 | |
1386 | for(int i = 0; i < causedStack.size(); ++i) { |
1387 | QPointer<QWidget> widget = causedStack.at(i); |
1388 | if (!widget) |
1389 | continue; |
1390 | //fire |
1391 | if (QMenu * = qobject_cast<QMenu*>(object: widget)) { |
1392 | widget = qmenu->d_func()->causedPopup.widget; |
1393 | if (action_e == QAction::Trigger) { |
1394 | emit qmenu->triggered(action); |
1395 | } else if (action_e == QAction::Hover) { |
1396 | emit qmenu->hovered(action); |
1397 | } |
1398 | #if QT_CONFIG(menubar) |
1399 | } else if (QMenuBar * = qobject_cast<QMenuBar*>(object: widget)) { |
1400 | if (action_e == QAction::Trigger) { |
1401 | emit qmenubar->triggered(action); |
1402 | } else if (action_e == QAction::Hover) { |
1403 | emit qmenubar->hovered(action); |
1404 | } |
1405 | break; //nothing more.. |
1406 | #endif |
1407 | } |
1408 | } |
1409 | } |
1410 | |
1411 | void QMenuPrivate::(QAction *action, QAction::ActionEvent action_e, bool self) |
1412 | { |
1413 | Q_Q(QMenu); |
1414 | #if QT_CONFIG(whatsthis) |
1415 | bool inWhatsThisMode = QWhatsThis::inWhatsThisMode(); |
1416 | #endif |
1417 | if (!action || !q->isEnabled() |
1418 | || (action_e == QAction::Trigger |
1419 | #if QT_CONFIG(whatsthis) |
1420 | && !inWhatsThisMode |
1421 | #endif |
1422 | && (action->isSeparator() ||!action->isEnabled()))) |
1423 | return; |
1424 | |
1425 | /* I have to save the caused stack here because it will be undone after popup execution (ie in the hide). |
1426 | Then I iterate over the list to actually send the events. --Sam |
1427 | */ |
1428 | const QVector<QPointer<QWidget> > causedStack = calcCausedStack(); |
1429 | if (action_e == QAction::Trigger) { |
1430 | #if QT_CONFIG(whatsthis) |
1431 | if (!inWhatsThisMode) |
1432 | actionAboutToTrigger = action; |
1433 | #endif |
1434 | |
1435 | if (q->testAttribute(attribute: Qt::WA_DontShowOnScreen)) { |
1436 | hideUpToMenuBar(); |
1437 | } else { |
1438 | for(QWidget *widget = QApplication::activePopupWidget(); widget; ) { |
1439 | if (QMenu * = qobject_cast<QMenu*>(object: widget)) { |
1440 | if(qmenu == q) |
1441 | hideUpToMenuBar(); |
1442 | widget = qmenu->d_func()->causedPopup.widget; |
1443 | } else { |
1444 | break; |
1445 | } |
1446 | } |
1447 | } |
1448 | |
1449 | #if QT_CONFIG(whatsthis) |
1450 | if (inWhatsThisMode) { |
1451 | QString s = action->whatsThis(); |
1452 | if (s.isEmpty()) |
1453 | s = whatsThis; |
1454 | QWhatsThis::showText(pos: q->mapToGlobal(actionRect(act: action).center()), text: s, w: q); |
1455 | return; |
1456 | } |
1457 | #endif |
1458 | } |
1459 | |
1460 | |
1461 | activateCausedStack(causedStack, action, action_e, self); |
1462 | |
1463 | |
1464 | if (action_e == QAction::Hover) { |
1465 | #ifndef QT_NO_ACCESSIBILITY |
1466 | if (QAccessible::isActive()) { |
1467 | int actionIndex = indexOf(act: action); |
1468 | QAccessibleEvent focusEvent(q, QAccessible::Focus); |
1469 | focusEvent.setChild(actionIndex); |
1470 | QAccessible::updateAccessibility(event: &focusEvent); |
1471 | } |
1472 | #endif |
1473 | action->showStatusText(widget: topCausedWidget()); |
1474 | } else { |
1475 | actionAboutToTrigger = nullptr; |
1476 | } |
1477 | } |
1478 | |
1479 | void QMenuPrivate::() |
1480 | { |
1481 | Q_Q(QMenu); |
1482 | if (QAction *action = qobject_cast<QAction *>(object: q->sender())) { |
1483 | QPointer<QAction> actionGuard = action; |
1484 | if (platformMenu && widgetItems.value(akey: action)) |
1485 | platformMenu->dismiss(); |
1486 | emit q->triggered(action); |
1487 | if (!activationRecursionGuard && actionGuard) { |
1488 | //in case the action has not been activated by the mouse |
1489 | //we check the parent hierarchy |
1490 | QVector< QPointer<QWidget> > list; |
1491 | for(QWidget *widget = q->parentWidget(); widget; ) { |
1492 | if (qobject_cast<QMenu*>(object: widget) |
1493 | #if QT_CONFIG(menubar) |
1494 | || qobject_cast<QMenuBar*>(object: widget) |
1495 | #endif |
1496 | ) { |
1497 | list.append(t: widget); |
1498 | widget = widget->parentWidget(); |
1499 | } else { |
1500 | break; |
1501 | } |
1502 | } |
1503 | activateCausedStack(causedStack: list, action, action_e: QAction::Trigger, self: false); |
1504 | // if a widget action fires, we need to hide the menu explicitly |
1505 | if (qobject_cast<QWidgetAction*>(object: action)) |
1506 | hideUpToMenuBar(); |
1507 | } |
1508 | } |
1509 | } |
1510 | |
1511 | void QMenuPrivate::() |
1512 | { |
1513 | Q_Q(QMenu); |
1514 | if (QAction * action = qobject_cast<QAction *>(object: q->sender())) { |
1515 | emit q->hovered(action); |
1516 | } |
1517 | } |
1518 | |
1519 | void QMenuPrivate::() |
1520 | { |
1521 | Q_Q(QMenu); |
1522 | |
1523 | emit q->aboutToShow(); |
1524 | |
1525 | #ifdef Q_OS_MACOS |
1526 | if (platformMenu) { |
1527 | const auto actions = q->actions(); |
1528 | for (QAction *action : actions) { |
1529 | if (QWidget *widget = widgetItems.value(action)) |
1530 | if (widget->parent() == q) { |
1531 | QPlatformMenuItem *menuItem = platformMenu->menuItemForTag(reinterpret_cast<quintptr>(action)); |
1532 | moveWidgetToPlatformItem(widget, menuItem); |
1533 | platformMenu->syncMenuItem(menuItem); |
1534 | } |
1535 | } |
1536 | } |
1537 | #endif |
1538 | } |
1539 | |
1540 | bool QMenuPrivate::(const QPoint &globalPos) |
1541 | { |
1542 | //determines if the mouse has moved (ie its initial position has |
1543 | //changed by more than QApplication::startDragDistance() |
1544 | //or if there were at least 6 mouse motions) |
1545 | return motions > 6 || |
1546 | QApplication::startDragDistance() < (mousePopupPos - globalPos).manhattanLength(); |
1547 | } |
1548 | |
1549 | |
1550 | /*! |
1551 | Initialize \a option with the values from this menu and information from \a action. This method |
1552 | is useful for subclasses when they need a QStyleOptionMenuItem, but don't want |
1553 | to fill in all the information themselves. |
1554 | |
1555 | \sa QStyleOption::initFrom(), QMenuBar::initStyleOption() |
1556 | */ |
1557 | void QMenu::(QStyleOptionMenuItem *option, const QAction *action) const |
1558 | { |
1559 | if (!option || !action) |
1560 | return; |
1561 | |
1562 | Q_D(const QMenu); |
1563 | option->initFrom(w: this); |
1564 | option->palette = palette(); |
1565 | option->state = QStyle::State_None; |
1566 | |
1567 | if (window()->isActiveWindow()) |
1568 | option->state |= QStyle::State_Active; |
1569 | if (isEnabled() && action->isEnabled() |
1570 | && (!action->menu() || action->menu()->isEnabled())) |
1571 | option->state |= QStyle::State_Enabled; |
1572 | else |
1573 | option->palette.setCurrentColorGroup(QPalette::Disabled); |
1574 | |
1575 | option->font = action->font().resolve(font()); |
1576 | option->fontMetrics = QFontMetrics(option->font); |
1577 | |
1578 | if (d->currentAction && d->currentAction == action && !d->currentAction->isSeparator()) { |
1579 | option->state |= QStyle::State_Selected |
1580 | | (QMenuPrivate::mouseDown ? QStyle::State_Sunken : QStyle::State_None); |
1581 | } |
1582 | |
1583 | option->menuHasCheckableItems = d->hasCheckableItems; |
1584 | if (!action->isCheckable()) { |
1585 | option->checkType = QStyleOptionMenuItem::NotCheckable; |
1586 | } else { |
1587 | option->checkType = (action->actionGroup() && action->actionGroup()->isExclusive()) |
1588 | ? QStyleOptionMenuItem::Exclusive : QStyleOptionMenuItem::NonExclusive; |
1589 | option->checked = action->isChecked(); |
1590 | } |
1591 | if (action->menu()) |
1592 | option->menuItemType = QStyleOptionMenuItem::SubMenu; |
1593 | else if (action->isSeparator()) |
1594 | option->menuItemType = QStyleOptionMenuItem::Separator; |
1595 | else if (d->defaultAction == action) |
1596 | option->menuItemType = QStyleOptionMenuItem::DefaultItem; |
1597 | else |
1598 | option->menuItemType = QStyleOptionMenuItem::Normal; |
1599 | if (action->isIconVisibleInMenu()) |
1600 | option->icon = action->icon(); |
1601 | QString textAndAccel = action->text(); |
1602 | #ifndef QT_NO_SHORTCUT |
1603 | if ((action->isShortcutVisibleInContextMenu() || !d->isContextMenu()) |
1604 | && textAndAccel.indexOf(c: QLatin1Char('\t')) == -1) { |
1605 | QKeySequence seq = action->shortcut(); |
1606 | if (!seq.isEmpty()) |
1607 | textAndAccel += QLatin1Char('\t') + seq.toString(format: QKeySequence::NativeText); |
1608 | } |
1609 | #endif |
1610 | option->text = textAndAccel; |
1611 | option->tabWidth = d->tabWidth; |
1612 | option->maxIconWidth = d->maxIconWidth; |
1613 | option->menuRect = rect(); |
1614 | } |
1615 | |
1616 | /*! |
1617 | \class QMenu |
1618 | \brief The QMenu class provides a menu widget for use in menu |
1619 | bars, context menus, and other popup menus. |
1620 | |
1621 | \ingroup mainwindow-classes |
1622 | \ingroup basicwidgets |
1623 | \inmodule QtWidgets |
1624 | |
1625 | \image fusion-menu.png |
1626 | |
1627 | A menu widget is a selection menu. It can be either a pull-down |
1628 | menu in a menu bar or a standalone context menu. Pull-down menus |
1629 | are shown by the menu bar when the user clicks on the respective |
1630 | item or presses the specified shortcut key. Use |
1631 | QMenuBar::addMenu() to insert a menu into a menu bar. Context |
1632 | menus are usually invoked by some special keyboard key or by |
1633 | right-clicking. They can be executed either asynchronously with |
1634 | popup() or synchronously with exec(). Menus can also be invoked in |
1635 | response to button presses; these are just like context menus |
1636 | except for how they are invoked. |
1637 | |
1638 | \section1 Actions |
1639 | |
1640 | A menu consists of a list of action items. Actions are added with |
1641 | the addAction(), addActions() and insertAction() functions. An action |
1642 | is represented vertically and rendered by QStyle. In addition, actions |
1643 | can have a text label, an optional icon drawn on the very left side, |
1644 | and shortcut key sequence such as "Ctrl+X". |
1645 | |
1646 | The existing actions held by a menu can be found with actions(). |
1647 | |
1648 | There are four kinds of action items: separators, actions that |
1649 | show a submenu, widgets, and actions that perform an action. |
1650 | Separators are inserted with addSeparator(), submenus with addMenu(), |
1651 | and all other items are considered action items. |
1652 | |
1653 | When inserting action items you usually specify a receiver and a |
1654 | slot. The receiver will be notifed whenever the item is |
1655 | \l{QAction::triggered()}{triggered()}. In addition, QMenu provides |
1656 | two signals, triggered() and hovered(), which signal the |
1657 | QAction that was triggered from the menu. |
1658 | |
1659 | You clear a menu with clear() and remove individual action items |
1660 | with removeAction(). |
1661 | |
1662 | A QMenu can also provide a tear-off menu. A tear-off menu is a |
1663 | top-level window that contains a copy of the menu. This makes it |
1664 | possible for the user to "tear off" frequently used menus and |
1665 | position them in a convenient place on the screen. If you want |
1666 | this functionality for a particular menu, insert a tear-off handle |
1667 | with setTearOffEnabled(). When using tear-off menus, bear in mind |
1668 | that the concept isn't typically used on Microsoft Windows so |
1669 | some users may not be familiar with it. Consider using a QToolBar |
1670 | instead. |
1671 | |
1672 | Widgets can be inserted into menus with the QWidgetAction class. |
1673 | Instances of this class are used to hold widgets, and are inserted |
1674 | into menus with the addAction() overload that takes a QAction. If the |
1675 | QWidgetAction fires the triggered() signal, the menu will close. |
1676 | |
1677 | \warning To make QMenu visible on the screen, exec() or popup() should be |
1678 | used instead of show(). |
1679 | |
1680 | \section1 QMenu on \macos with Qt Build Against Cocoa |
1681 | |
1682 | QMenu can be inserted only once in a menu/menubar. Subsequent insertions will |
1683 | have no effect or will result in a disabled menu item. |
1684 | |
1685 | See the \l{mainwindows/menus}{Menus} example for an example of how |
1686 | to use QMenuBar and QMenu in your application. |
1687 | |
1688 | \b{Important inherited functions:} addAction(), removeAction(), clear(), |
1689 | addSeparator(), and addMenu(). |
1690 | |
1691 | \sa QMenuBar, {fowler}{GUI Design Handbook: Menu, Drop-Down and Pop-Up}, |
1692 | {Application Example}, {Menus Example} |
1693 | */ |
1694 | |
1695 | |
1696 | /*! |
1697 | Constructs a menu with parent \a parent. |
1698 | |
1699 | Although a popup menu is always a top-level widget, if a parent is |
1700 | passed the popup menu will be deleted when that parent is |
1701 | destroyed (as with any other QObject). |
1702 | */ |
1703 | QMenu::(QWidget *parent) |
1704 | : QWidget(*new QMenuPrivate, parent, Qt::Popup) |
1705 | { |
1706 | Q_D(QMenu); |
1707 | d->init(); |
1708 | } |
1709 | |
1710 | /*! |
1711 | Constructs a menu with a \a title and a \a parent. |
1712 | |
1713 | Although a popup menu is always a top-level widget, if a parent is |
1714 | passed the popup menu will be deleted when that parent is |
1715 | destroyed (as with any other QObject). |
1716 | |
1717 | \sa title |
1718 | */ |
1719 | QMenu::(const QString &title, QWidget *parent) |
1720 | : QMenu(parent) |
1721 | { |
1722 | Q_D(QMenu); |
1723 | d->menuAction->setText(title); |
1724 | } |
1725 | |
1726 | /*! \internal |
1727 | */ |
1728 | QMenu::(QMenuPrivate &dd, QWidget *parent) |
1729 | : QWidget(dd, parent, Qt::Popup) |
1730 | { |
1731 | Q_D(QMenu); |
1732 | d->init(); |
1733 | } |
1734 | |
1735 | /*! |
1736 | Destroys the menu. |
1737 | */ |
1738 | QMenu::() |
1739 | { |
1740 | Q_D(QMenu); |
1741 | if (!d->widgetItems.isEmpty()) { // avoid detach on shared null hash |
1742 | QHash<QAction *, QWidget *>::iterator it = d->widgetItems.begin(); |
1743 | for (; it != d->widgetItems.end(); ++it) { |
1744 | if (QWidget *widget = it.value()) { |
1745 | QWidgetAction *action = static_cast<QWidgetAction *>(it.key()); |
1746 | action->releaseWidget(widget); |
1747 | *it = 0; |
1748 | } |
1749 | } |
1750 | } |
1751 | |
1752 | if (d->eventLoop) |
1753 | d->eventLoop->exit(); |
1754 | hideTearOffMenu(); |
1755 | } |
1756 | |
1757 | /*! |
1758 | This convenience function creates a new action with \a text. |
1759 | The function adds the newly created action to the menu's |
1760 | list of actions, and returns it. |
1761 | |
1762 | QMenu takes ownership of the returned QAction. |
1763 | |
1764 | \sa QWidget::addAction() |
1765 | */ |
1766 | QAction *QMenu::(const QString &text) |
1767 | { |
1768 | QAction *ret = new QAction(text, this); |
1769 | addAction(action: ret); |
1770 | return ret; |
1771 | } |
1772 | |
1773 | /*! |
1774 | \overload |
1775 | |
1776 | This convenience function creates a new action with an \a icon |
1777 | and some \a text. The function adds the newly created action to |
1778 | the menu's list of actions, and returns it. |
1779 | |
1780 | QMenu takes ownership of the returned QAction. |
1781 | |
1782 | \sa QWidget::addAction() |
1783 | */ |
1784 | QAction *QMenu::(const QIcon &icon, const QString &text) |
1785 | { |
1786 | QAction *ret = new QAction(icon, text, this); |
1787 | addAction(action: ret); |
1788 | return ret; |
1789 | } |
1790 | |
1791 | /*! |
1792 | \overload |
1793 | |
1794 | This convenience function creates a new action with the text \a |
1795 | text and an optional shortcut \a shortcut. The action's |
1796 | \l{QAction::triggered()}{triggered()} signal is connected to the |
1797 | \a receiver's \a member slot. The function adds the newly created |
1798 | action to the menu's list of actions and returns it. |
1799 | |
1800 | QMenu takes ownership of the returned QAction. |
1801 | |
1802 | \sa QWidget::addAction() |
1803 | */ |
1804 | QAction *QMenu::(const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut) |
1805 | { |
1806 | QAction *action = new QAction(text, this); |
1807 | #ifdef QT_NO_SHORTCUT |
1808 | Q_UNUSED(shortcut); |
1809 | #else |
1810 | action->setShortcut(shortcut); |
1811 | #endif |
1812 | QObject::connect(sender: action, SIGNAL(triggered(bool)), receiver, member); |
1813 | addAction(action); |
1814 | return action; |
1815 | } |
1816 | |
1817 | /*!\fn template<typename Functor> QAction *QMenu::addAction(const QString &text, Functor functor, const QKeySequence &shortcut = 0) |
1818 | |
1819 | \since 5.6 |
1820 | |
1821 | \overload |
1822 | |
1823 | This convenience function creates a new action with the text \a |
1824 | text and an optional shortcut \a shortcut. The action's |
1825 | \l{QAction::triggered()}{triggered()} signal is connected to the |
1826 | \a functor. The function adds the newly created |
1827 | action to the menu's list of actions and returns it. |
1828 | |
1829 | QMenu takes ownership of the returned QAction. |
1830 | */ |
1831 | |
1832 | /*!\fn template<typename Functor> QAction *QMenu::addAction(const QString &text, const QObject *context, Functor functor, const QKeySequence &shortcut) |
1833 | |
1834 | \since 5.6 |
1835 | |
1836 | \overload |
1837 | |
1838 | This convenience function creates a new action with the text \a |
1839 | text and an optional shortcut \a shortcut. The action's |
1840 | \l{QAction::triggered()}{triggered()} signal is connected to the |
1841 | \a functor. The functor can be a pointer to a member function of |
1842 | the \a context object. The newly created action is added to the |
1843 | menu's list of actions and a pointer to it is returned. |
1844 | |
1845 | If the \a context object is destroyed, the functor will not be called. |
1846 | |
1847 | QMenu takes ownership of the returned QAction. |
1848 | */ |
1849 | |
1850 | /*!\fn template<typename Functor> QAction *QMenu::addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut = 0) |
1851 | |
1852 | \since 5.6 |
1853 | |
1854 | \overload |
1855 | |
1856 | This convenience function creates a new action with an \a icon |
1857 | and some \a text and an optional shortcut \a shortcut. The action's |
1858 | \l{QAction::triggered()}{triggered()} signal is connected to the |
1859 | \a functor. The function adds the newly created |
1860 | action to the menu's list of actions and returns it. |
1861 | |
1862 | QMenu takes ownership of the returned QAction. |
1863 | */ |
1864 | |
1865 | /*!\fn template<typename Functor> QAction *QMenu::addAction(const QIcon &icon, const QString &text, const QObject *context, Functor functor, const QKeySequence &shortcut) |
1866 | |
1867 | \since 5.6 |
1868 | |
1869 | \overload |
1870 | |
1871 | This convenience function creates a new action with an \a icon |
1872 | and some \a text and an optional shortcut \a shortcut. The action's |
1873 | \l{QAction::triggered()}{triggered()} signal is connected to the |
1874 | \a functor. The \a functor can be a pointer to a member function |
1875 | of the \a context object. The newly created action is added to the |
1876 | menu's list of actions and a pointer to it is returned. |
1877 | |
1878 | If \a context is destroyed, the functor will not be called. |
1879 | |
1880 | QMenu takes ownership of the returned QAction. |
1881 | */ |
1882 | |
1883 | /*! |
1884 | \overload |
1885 | |
1886 | This convenience function creates a new action with an \a icon and |
1887 | some \a text and an optional shortcut \a shortcut. The action's |
1888 | \l{QAction::triggered()}{triggered()} signal is connected to the |
1889 | \a member slot of the \a receiver object. The function adds the |
1890 | newly created action to the menu's list of actions, and returns it. |
1891 | |
1892 | QMenu takes ownership of the returned QAction. |
1893 | |
1894 | \sa QWidget::addAction() |
1895 | */ |
1896 | QAction *QMenu::(const QIcon &icon, const QString &text, const QObject *receiver, |
1897 | const char* member, const QKeySequence &shortcut) |
1898 | { |
1899 | QAction *action = new QAction(icon, text, this); |
1900 | #ifdef QT_NO_SHORTCUT |
1901 | Q_UNUSED(shortcut); |
1902 | #else |
1903 | action->setShortcut(shortcut); |
1904 | #endif |
1905 | QObject::connect(sender: action, SIGNAL(triggered(bool)), receiver, member); |
1906 | addAction(action); |
1907 | return action; |
1908 | } |
1909 | |
1910 | /*! |
1911 | This convenience function adds \a menu as a submenu to this menu. |
1912 | It returns \a menu's menuAction(). This menu does not take |
1913 | ownership of \a menu. |
1914 | |
1915 | \sa QWidget::addAction(), QMenu::menuAction() |
1916 | */ |
1917 | QAction *QMenu::(QMenu *) |
1918 | { |
1919 | QAction *action = menu->menuAction(); |
1920 | addAction(action); |
1921 | return action; |
1922 | } |
1923 | |
1924 | /*! |
1925 | Appends a new QMenu with \a title to the menu. The menu |
1926 | takes ownership of the menu. Returns the new menu. |
1927 | |
1928 | \sa QWidget::addAction(), QMenu::menuAction() |
1929 | */ |
1930 | QMenu *QMenu::(const QString &title) |
1931 | { |
1932 | QMenu * = new QMenu(title, this); |
1933 | addAction(action: menu->menuAction()); |
1934 | return menu; |
1935 | } |
1936 | |
1937 | /*! |
1938 | Appends a new QMenu with \a icon and \a title to the menu. The menu |
1939 | takes ownership of the menu. Returns the new menu. |
1940 | |
1941 | \sa QWidget::addAction(), QMenu::menuAction() |
1942 | */ |
1943 | QMenu *QMenu::(const QIcon &icon, const QString &title) |
1944 | { |
1945 | QMenu * = new QMenu(title, this); |
1946 | menu->setIcon(icon); |
1947 | addAction(action: menu->menuAction()); |
1948 | return menu; |
1949 | } |
1950 | |
1951 | /*! |
1952 | This convenience function creates a new separator action, i.e. an |
1953 | action with QAction::isSeparator() returning true, and adds the new |
1954 | action to this menu's list of actions. It returns the newly |
1955 | created action. |
1956 | |
1957 | QMenu takes ownership of the returned QAction. |
1958 | |
1959 | \sa QWidget::addAction() |
1960 | */ |
1961 | QAction *QMenu::() |
1962 | { |
1963 | QAction *action = new QAction(this); |
1964 | action->setSeparator(true); |
1965 | addAction(action); |
1966 | return action; |
1967 | } |
1968 | |
1969 | /*! |
1970 | \since 5.1 |
1971 | |
1972 | This convenience function creates a new section action, i.e. an |
1973 | action with QAction::isSeparator() returning true but also |
1974 | having \a text hint, and adds the new action to this menu's list |
1975 | of actions. It returns the newly created action. |
1976 | |
1977 | The rendering of the hint is style and platform dependent. Widget |
1978 | styles can use the text information in the rendering for sections, |
1979 | or can choose to ignore it and render sections like simple separators. |
1980 | |
1981 | QMenu takes ownership of the returned QAction. |
1982 | |
1983 | \sa QWidget::addAction() |
1984 | */ |
1985 | QAction *QMenu::(const QString &text) |
1986 | { |
1987 | QAction *action = new QAction(text, this); |
1988 | action->setSeparator(true); |
1989 | addAction(action); |
1990 | return action; |
1991 | } |
1992 | |
1993 | /*! |
1994 | \since 5.1 |
1995 | |
1996 | This convenience function creates a new section action, i.e. an |
1997 | action with QAction::isSeparator() returning true but also |
1998 | having \a text and \a icon hints, and adds the new action to this menu's |
1999 | list of actions. It returns the newly created action. |
2000 | |
2001 | The rendering of the hints is style and platform dependent. Widget |
2002 | styles can use the text and icon information in the rendering for sections, |
2003 | or can choose to ignore them and render sections like simple separators. |
2004 | |
2005 | QMenu takes ownership of the returned QAction. |
2006 | |
2007 | \sa QWidget::addAction() |
2008 | */ |
2009 | QAction *QMenu::(const QIcon &icon, const QString &text) |
2010 | { |
2011 | QAction *action = new QAction(icon, text, this); |
2012 | action->setSeparator(true); |
2013 | addAction(action); |
2014 | return action; |
2015 | } |
2016 | |
2017 | /*! |
2018 | This convenience function inserts \a menu before action \a before |
2019 | and returns the menus menuAction(). |
2020 | |
2021 | \sa QWidget::insertAction(), addMenu() |
2022 | */ |
2023 | QAction *QMenu::(QAction *before, QMenu *) |
2024 | { |
2025 | QAction *action = menu->menuAction(); |
2026 | insertAction(before, action); |
2027 | return action; |
2028 | } |
2029 | |
2030 | /*! |
2031 | This convenience function creates a new separator action, i.e. an |
2032 | action with QAction::isSeparator() returning true. The function inserts |
2033 | the newly created action into this menu's list of actions before |
2034 | action \a before and returns it. |
2035 | |
2036 | QMenu takes ownership of the returned QAction. |
2037 | |
2038 | \sa QWidget::insertAction(), addSeparator() |
2039 | */ |
2040 | QAction *QMenu::(QAction *before) |
2041 | { |
2042 | QAction *action = new QAction(this); |
2043 | action->setSeparator(true); |
2044 | insertAction(before, action); |
2045 | return action; |
2046 | } |
2047 | |
2048 | /*! |
2049 | \since 5.1 |
2050 | |
2051 | This convenience function creates a new title action, i.e. an |
2052 | action with QAction::isSeparator() returning true but also having |
2053 | \a text hint. The function inserts the newly created action |
2054 | into this menu's list of actions before action \a before and |
2055 | returns it. |
2056 | |
2057 | The rendering of the hint is style and platform dependent. Widget |
2058 | styles can use the text information in the rendering for sections, |
2059 | or can choose to ignore it and render sections like simple separators. |
2060 | |
2061 | QMenu takes ownership of the returned QAction. |
2062 | |
2063 | \sa QWidget::insertAction(), addSection() |
2064 | */ |
2065 | QAction *QMenu::(QAction *before, const QString &text) |
2066 | { |
2067 | QAction *action = new QAction(text, this); |
2068 | action->setSeparator(true); |
2069 | insertAction(before, action); |
2070 | return action; |
2071 | } |
2072 | |
2073 | /*! |
2074 | \since 5.1 |
2075 | |
2076 | This convenience function creates a new title action, i.e. an |
2077 | action with QAction::isSeparator() returning true but also having |
2078 | \a text and \a icon hints. The function inserts the newly created action |
2079 | into this menu's list of actions before action \a before and returns it. |
2080 | |
2081 | The rendering of the hints is style and platform dependent. Widget |
2082 | styles can use the text and icon information in the rendering for sections, |
2083 | or can choose to ignore them and render sections like simple separators. |
2084 | |
2085 | QMenu takes ownership of the returned QAction. |
2086 | |
2087 | \sa QWidget::insertAction(), addSection() |
2088 | */ |
2089 | QAction *QMenu::(QAction *before, const QIcon &icon, const QString &text) |
2090 | { |
2091 | QAction *action = new QAction(icon, text, this); |
2092 | action->setSeparator(true); |
2093 | insertAction(before, action); |
2094 | return action; |
2095 | } |
2096 | |
2097 | /*! |
2098 | This sets the default action to \a act. The default action may have |
2099 | a visual cue, depending on the current QStyle. A default action |
2100 | usually indicates what will happen by default when a drop occurs. |
2101 | |
2102 | \sa defaultAction() |
2103 | */ |
2104 | void QMenu::(QAction *act) |
2105 | { |
2106 | d_func()->defaultAction = act; |
2107 | } |
2108 | |
2109 | /*! |
2110 | Returns the current default action. |
2111 | |
2112 | \sa setDefaultAction() |
2113 | */ |
2114 | QAction *QMenu::() const |
2115 | { |
2116 | return d_func()->defaultAction; |
2117 | } |
2118 | |
2119 | /*! |
2120 | \property QMenu::tearOffEnabled |
2121 | \brief whether the menu supports being torn off |
2122 | |
2123 | When true, the menu contains a special tear-off item (often shown as a dashed |
2124 | line at the top of the menu) that creates a copy of the menu when it is |
2125 | triggered. |
2126 | |
2127 | This "torn-off" copy lives in a separate window. It contains the same menu |
2128 | items as the original menu, with the exception of the tear-off handle. |
2129 | |
2130 | By default, this property is \c false. |
2131 | */ |
2132 | void QMenu::(bool b) |
2133 | { |
2134 | Q_D(QMenu); |
2135 | if (d->tearoff == b) |
2136 | return; |
2137 | if (!b) |
2138 | hideTearOffMenu(); |
2139 | d->tearoff = b; |
2140 | |
2141 | d->itemsDirty = true; |
2142 | if (isVisible()) |
2143 | resize(sizeHint()); |
2144 | } |
2145 | |
2146 | bool QMenu::() const |
2147 | { |
2148 | return d_func()->tearoff; |
2149 | } |
2150 | |
2151 | /*! |
2152 | When a menu is torn off a second menu is shown to display the menu |
2153 | contents in a new window. When the menu is in this mode and the menu |
2154 | is visible returns \c true; otherwise false. |
2155 | |
2156 | \sa showTearOffMenu(), hideTearOffMenu(), isTearOffEnabled() |
2157 | */ |
2158 | bool QMenu::() const |
2159 | { |
2160 | if (d_func()->tornPopup) |
2161 | return d_func()->tornPopup->isVisible(); |
2162 | return false; |
2163 | } |
2164 | |
2165 | /*! |
2166 | \since 5.7 |
2167 | |
2168 | This function will forcibly show the torn off menu making it |
2169 | appear on the user's desktop at the specified \e global position \a pos. |
2170 | |
2171 | \sa hideTearOffMenu(), isTearOffMenuVisible(), isTearOffEnabled() |
2172 | */ |
2173 | void QMenu::(const QPoint &pos) |
2174 | { |
2175 | Q_D(QMenu); |
2176 | if (!d->tornPopup) |
2177 | d->tornPopup = new QTornOffMenu(this); |
2178 | const QSize &s = sizeHint(); |
2179 | d->tornPopup->setGeometry(ax: pos.x(), ay: pos.y(), aw: s.width(), ah: s.height()); |
2180 | d->tornPopup->show(); |
2181 | } |
2182 | |
2183 | /*! |
2184 | \overload |
2185 | \since 5.7 |
2186 | |
2187 | This function will forcibly show the torn off menu making it |
2188 | appear on the user's desktop under the mouse currsor. |
2189 | |
2190 | \sa hideTearOffMenu(), isTearOffMenuVisible(), isTearOffEnabled() |
2191 | */ |
2192 | void QMenu::() |
2193 | { |
2194 | showTearOffMenu(pos: QCursor::pos()); |
2195 | } |
2196 | |
2197 | /*! |
2198 | This function will forcibly hide the torn off menu making it |
2199 | disappear from the user's desktop. |
2200 | |
2201 | \sa showTearOffMenu(), isTearOffMenuVisible(), isTearOffEnabled() |
2202 | */ |
2203 | void QMenu::() |
2204 | { |
2205 | Q_D(QMenu); |
2206 | if (d->tornPopup) { |
2207 | d->tornPopup->close(); |
2208 | // QTornOffMenu sets WA_DeleteOnClose, so we |
2209 | // should consider the torn-off menu deleted. |
2210 | // This way showTearOffMenu() will not try to |
2211 | // reuse the dying torn-off menu. |
2212 | d->tornPopup = nullptr; |
2213 | } |
2214 | } |
2215 | |
2216 | |
2217 | /*! |
2218 | Sets the currently highlighted action to \a act. |
2219 | */ |
2220 | void QMenu::(QAction *act) |
2221 | { |
2222 | Q_D(QMenu); |
2223 | d->setCurrentAction(action: act, popup: 0); |
2224 | if (d->scroll) |
2225 | d->scrollMenu(action: act, location: QMenuPrivate::QMenuScroller::ScrollCenter); |
2226 | } |
2227 | |
2228 | |
2229 | /*! |
2230 | Returns the currently highlighted action, or \nullptr if no |
2231 | action is currently highlighted. |
2232 | */ |
2233 | QAction *QMenu::() const |
2234 | { |
2235 | return d_func()->currentAction; |
2236 | } |
2237 | |
2238 | /*! |
2239 | \since 4.2 |
2240 | |
2241 | Returns \c true if there are no visible actions inserted into the menu, false |
2242 | otherwise. |
2243 | |
2244 | \sa QWidget::actions() |
2245 | */ |
2246 | |
2247 | bool QMenu::() const |
2248 | { |
2249 | bool ret = true; |
2250 | for(int i = 0; ret && i < actions().count(); ++i) { |
2251 | const QAction *action = actions().at(i); |
2252 | if (!action->isSeparator() && action->isVisible()) { |
2253 | ret = false; |
2254 | } |
2255 | } |
2256 | return ret; |
2257 | } |
2258 | |
2259 | /*! |
2260 | Removes all the menu's actions. Actions owned by the menu and not |
2261 | shown in any other widget are deleted. |
2262 | |
2263 | \sa removeAction() |
2264 | */ |
2265 | void QMenu::() |
2266 | { |
2267 | QList<QAction*> acts = actions(); |
2268 | |
2269 | for(int i = 0; i < acts.size(); i++) { |
2270 | removeAction(action: acts[i]); |
2271 | if (acts[i]->parent() == this && acts[i]->d_func()->widgets.isEmpty()) |
2272 | delete acts[i]; |
2273 | } |
2274 | } |
2275 | |
2276 | /*! |
2277 | If a menu does not fit on the screen it lays itself out so that it |
2278 | does fit. It is style dependent what layout means (for example, on |
2279 | Windows it will use multiple columns). |
2280 | |
2281 | This functions returns the number of columns necessary. |
2282 | */ |
2283 | int QMenu::columnCount() const |
2284 | { |
2285 | return d_func()->ncols; |
2286 | } |
2287 | |
2288 | /*! |
2289 | Returns the item at \a pt; returns \nullptr if there is no item there. |
2290 | */ |
2291 | QAction *QMenu::(const QPoint &pt) const |
2292 | { |
2293 | if (QAction *ret = d_func()->actionAt(p: pt)) |
2294 | return ret; |
2295 | return nullptr; |
2296 | } |
2297 | |
2298 | /*! |
2299 | Returns the geometry of action \a act. |
2300 | */ |
2301 | QRect QMenu::(QAction *act) const |
2302 | { |
2303 | return d_func()->actionRect(act); |
2304 | } |
2305 | |
2306 | /*! |
2307 | \reimp |
2308 | */ |
2309 | QSize QMenu::() const |
2310 | { |
2311 | Q_D(const QMenu); |
2312 | d->updateActionRects(); |
2313 | |
2314 | QSize s; |
2315 | for (int i = 0; i < d->actionRects.count(); ++i) { |
2316 | const QRect &rect = d->actionRects.at(i); |
2317 | if (rect.isNull()) |
2318 | continue; |
2319 | if (rect.bottom() >= s.height()) |
2320 | s.setHeight(rect.y() + rect.height()); |
2321 | if (rect.right() >= s.width()) |
2322 | s.setWidth(rect.x() + rect.width()); |
2323 | } |
2324 | // Note that the action rects calculated above already include |
2325 | // the top and left margins, so we only need to add margins for |
2326 | // the bottom and right. |
2327 | QStyleOption opt(0); |
2328 | opt.init(w: this); |
2329 | const int fw = style()->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: &opt, widget: this); |
2330 | s.rwidth() += style()->pixelMetric(metric: QStyle::PM_MenuHMargin, option: &opt, widget: this) + fw + d->rightmargin; |
2331 | s.rheight() += style()->pixelMetric(metric: QStyle::PM_MenuVMargin, option: &opt, widget: this) + fw + d->bottommargin; |
2332 | |
2333 | return style()->sizeFromContents(ct: QStyle::CT_Menu, opt: &opt, |
2334 | contentsSize: s.expandedTo(otherSize: QApplication::globalStrut()), w: this); |
2335 | } |
2336 | |
2337 | /*! |
2338 | Displays the menu so that the action \a atAction will be at the |
2339 | specified \e global position \a p. To translate a widget's local |
2340 | coordinates into global coordinates, use QWidget::mapToGlobal(). |
2341 | |
2342 | When positioning a menu with exec() or popup(), bear in mind that |
2343 | you cannot rely on the menu's current size(). For performance |
2344 | reasons, the menu adapts its size only when necessary, so in many |
2345 | cases, the size before and after the show is different. Instead, |
2346 | use sizeHint() which calculates the proper size depending on the |
2347 | menu's current contents. |
2348 | |
2349 | \sa QWidget::mapToGlobal(), exec() |
2350 | */ |
2351 | void QMenu::(const QPoint &p, QAction *atAction) |
2352 | { |
2353 | Q_D(QMenu); |
2354 | d->popup(p, atAction); |
2355 | } |
2356 | |
2357 | void QMenuPrivate::(const QPoint &p, QAction *atAction, PositionFunction positionFunction) |
2358 | { |
2359 | Q_Q(QMenu); |
2360 | if (scroll) { // reset scroll state from last popup |
2361 | if (scroll->scrollOffset) |
2362 | itemsDirty = 1; // sizeHint will be incorrect if there is previous scroll |
2363 | scroll->scrollOffset = 0; |
2364 | scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone; |
2365 | } |
2366 | tearoffHighlighted = 0; |
2367 | motions = 0; |
2368 | doChildEffects = true; |
2369 | updateLayoutDirection(); |
2370 | |
2371 | q->ensurePolished(); // Get the right font |
2372 | |
2373 | // Ensure that we get correct sizeHints by placing this window on the correct screen. |
2374 | // However if the QMenu was constructed with a QDesktopScreenWidget as its parent, |
2375 | // then initialScreenIndex was set, so we should respect that for the lifetime of this menu. |
2376 | // Use d->popupScreen to remember, because initialScreenIndex will be reset after the first showing. |
2377 | // However if eventLoop exists, then exec() already did this by calling createWinId(); so leave it alone. (QTBUG-76162) |
2378 | if (!eventLoop) { |
2379 | bool screenSet = false; |
2380 | const int screenIndex = topData()->initialScreenIndex; |
2381 | if (screenIndex >= 0) |
2382 | popupScreen = screenIndex; |
2383 | if (auto s = QGuiApplication::screens().value(i: popupScreen)) { |
2384 | if (setScreen(s)) |
2385 | itemsDirty = true; |
2386 | screenSet = true; |
2387 | } else if (QMenu * = qobject_cast<QMenu *>(object: parent)) { |
2388 | // a submenu is always opened from an open parent menu, |
2389 | // so show it on the same screen where the parent is. (QTBUG-76162) |
2390 | if (setScreen(parentMenu->screen())) |
2391 | itemsDirty = true; |
2392 | screenSet = true; |
2393 | } |
2394 | if (!screenSet && setScreenForPoint(p)) |
2395 | itemsDirty = true; |
2396 | } |
2397 | |
2398 | const bool = isContextMenu(); |
2399 | if (lastContextMenu != contextMenu) { |
2400 | itemsDirty = true; |
2401 | lastContextMenu = contextMenu; |
2402 | } |
2403 | |
2404 | #if QT_CONFIG(menubar) |
2405 | // if this menu is part of a chain attached to a QMenuBar, set the |
2406 | // _NET_WM_WINDOW_TYPE_DROPDOWN_MENU X11 window type |
2407 | q->setAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu, on: qobject_cast<QMenuBar *>(object: topCausedWidget()) != nullptr); |
2408 | #endif |
2409 | |
2410 | emit q->aboutToShow(); |
2411 | const bool actionListChanged = itemsDirty; |
2412 | |
2413 | QRect screen; |
2414 | #if QT_CONFIG(graphicsview) |
2415 | bool isEmbedded = !bypassGraphicsProxyWidget(p: q) && QMenuPrivate::nearestGraphicsProxyWidget(origin: q); |
2416 | if (isEmbedded) |
2417 | screen = popupGeometry(); |
2418 | else |
2419 | #endif |
2420 | screen = popupGeometry(screen: QDesktopWidgetPrivate::screenNumber(p)); |
2421 | updateActionRects(screen); |
2422 | |
2423 | QPoint pos; |
2424 | QPushButton *causedButton = qobject_cast<QPushButton*>(object: causedPopup.widget); |
2425 | if (actionListChanged && causedButton) |
2426 | pos = QPushButtonPrivate::get(b: causedButton)->adjustedMenuPosition(); |
2427 | else |
2428 | pos = p; |
2429 | |
2430 | const QSize (q->sizeHint()); |
2431 | QSize size = menuSizeHint; |
2432 | |
2433 | if (positionFunction) |
2434 | pos = positionFunction(menuSizeHint); |
2435 | |
2436 | const int desktopFrame = q->style()->pixelMetric(metric: QStyle::PM_MenuDesktopFrameWidth, option: nullptr, widget: q); |
2437 | bool adjustToDesktop = !q->window()->testAttribute(attribute: Qt::WA_DontShowOnScreen); |
2438 | |
2439 | // if the screens have very different geometries and the menu is too big, we have to recalculate |
2440 | if ((size.height() > screen.height() || size.width() > screen.width()) || |
2441 | // Layout is not right, we might be able to save horizontal space |
2442 | (ncols >1 && size.height() < screen.height())) { |
2443 | size.setWidth(qMin(a: menuSizeHint.width(), b: screen.width() - desktopFrame * 2)); |
2444 | size.setHeight(qMin(a: menuSizeHint.height(), b: screen.height() - desktopFrame * 2)); |
2445 | adjustToDesktop = true; |
2446 | } |
2447 | |
2448 | #ifdef QT_KEYPAD_NAVIGATION |
2449 | if (!atAction && QApplicationPrivate::keypadNavigationEnabled()) { |
2450 | // Try to have one item activated |
2451 | if (defaultAction && defaultAction->isEnabled()) { |
2452 | atAction = defaultAction; |
2453 | // TODO: This works for first level menus, not yet sub menus |
2454 | } else { |
2455 | for (QAction *action : qAsConst(actions)) |
2456 | if (action->isEnabled()) { |
2457 | atAction = action; |
2458 | break; |
2459 | } |
2460 | } |
2461 | currentAction = atAction; |
2462 | } |
2463 | #endif |
2464 | if (ncols > 1) { |
2465 | pos.setY(screen.top() + desktopFrame); |
2466 | } else if (atAction) { |
2467 | for (int i = 0, above_height = 0; i < actions.count(); i++) { |
2468 | QAction *action = actions.at(i); |
2469 | if (action == atAction) { |
2470 | int newY = pos.y() - above_height; |
2471 | if (scroll && newY < desktopFrame) { |
2472 | scroll->scrollFlags = scroll->scrollFlags |
2473 | | QMenuPrivate::QMenuScroller::ScrollUp; |
2474 | scroll->scrollOffset = newY; |
2475 | newY = desktopFrame; |
2476 | } |
2477 | pos.setY(newY); |
2478 | |
2479 | if (scroll && scroll->scrollFlags != QMenuPrivate::QMenuScroller::ScrollNone |
2480 | && !q->style()->styleHint(stylehint: QStyle::SH_Menu_FillScreenWithScroll, opt: nullptr, widget: q)) { |
2481 | int below_height = above_height + scroll->scrollOffset; |
2482 | for (int i2 = i; i2 < actionRects.count(); i2++) |
2483 | below_height += actionRects.at(i: i2).height(); |
2484 | size.setHeight(below_height); |
2485 | } |
2486 | break; |
2487 | } else { |
2488 | above_height += actionRects.at(i).height(); |
2489 | } |
2490 | } |
2491 | } |
2492 | |
2493 | QPoint mouse = QCursor::pos(); |
2494 | mousePopupPos = mouse; |
2495 | const bool snapToMouse = !causedPopup.widget && (QRect(p.x() - 3, p.y() - 3, 6, 6).contains(p: mouse)); |
2496 | |
2497 | if (adjustToDesktop) { |
2498 | // handle popup falling "off screen" |
2499 | if (q->isRightToLeft()) { |
2500 | if (snapToMouse) // position flowing left from the mouse |
2501 | pos.setX(mouse.x() - size.width()); |
2502 | |
2503 | #if QT_CONFIG(menubar) |
2504 | // if the menu is in a menubar or is a submenu, it should be right-aligned |
2505 | if (qobject_cast<QMenuBar*>(object: causedPopup.widget) || qobject_cast<QMenu*>(object: causedPopup.widget)) |
2506 | pos.rx() -= size.width(); |
2507 | #endif // QT_CONFIG(menubar) |
2508 | |
2509 | if (pos.x() < screen.left() + desktopFrame) |
2510 | pos.setX(qMax(a: p.x(), b: screen.left() + desktopFrame)); |
2511 | if (pos.x() + size.width() - 1 > screen.right() - desktopFrame) |
2512 | pos.setX(qMax(a: p.x() - size.width(), b: screen.right() - desktopFrame - size.width() + 1)); |
2513 | } else { |
2514 | if (pos.x() + size.width() - 1 > screen.right() - desktopFrame) |
2515 | pos.setX(screen.right() - desktopFrame - size.width() + 1); |
2516 | if (pos.x() < screen.left() + desktopFrame) |
2517 | pos.setX(screen.left() + desktopFrame); |
2518 | } |
2519 | if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) { |
2520 | if(snapToMouse) |
2521 | pos.setY(qMin(a: mouse.y() - (size.height() + desktopFrame), b: screen.bottom()-desktopFrame-size.height()+1)); |
2522 | else |
2523 | pos.setY(qMax(a: p.y() - (size.height() + desktopFrame), b: screen.bottom()-desktopFrame-size.height()+1)); |
2524 | } |
2525 | |
2526 | if (pos.y() < screen.top() + desktopFrame) |
2527 | pos.setY(screen.top() + desktopFrame); |
2528 | if (pos.y() + menuSizeHint.height() - 1 > screen.bottom() - desktopFrame) { |
2529 | if (scroll) { |
2530 | scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown); |
2531 | int y = qMax(a: screen.y(),b: pos.y()); |
2532 | size.setHeight(screen.bottom() - (desktopFrame * 2) - y); |
2533 | } else { |
2534 | // Too big for screen, bias to see bottom of menu (for some reason) |
2535 | pos.setY(screen.bottom() - size.height() + 1); |
2536 | } |
2537 | } |
2538 | } |
2539 | const int = q->style()->pixelMetric(metric: QStyle::PM_SubMenuOverlap, option: nullptr, widget: q); |
2540 | QMenu *caused = qobject_cast<QMenu*>(object: causedPopup.widget); |
2541 | if (caused && caused->geometry().width() + menuSizeHint.width() + subMenuOffset < screen.width()) { |
2542 | QRect parentActionRect(caused->d_func()->actionRect(act: caused->d_func()->currentAction)); |
2543 | const QPoint actionTopLeft = caused->mapToGlobal(parentActionRect.topLeft()); |
2544 | parentActionRect.moveTopLeft(p: actionTopLeft); |
2545 | if (q->isRightToLeft()) { |
2546 | if ((pos.x() + menuSizeHint.width() > parentActionRect.left() - subMenuOffset) |
2547 | && (pos.x() < parentActionRect.right())) |
2548 | { |
2549 | pos.rx() = parentActionRect.left() - menuSizeHint.width(); |
2550 | if (pos.x() < screen.x()) |
2551 | pos.rx() = parentActionRect.right(); |
2552 | if (pos.x() + menuSizeHint.width() > screen.x() + screen.width()) |
2553 | pos.rx() = screen.x(); |
2554 | } |
2555 | } else { |
2556 | if ((pos.x() < parentActionRect.right() + subMenuOffset) |
2557 | && (pos.x() + menuSizeHint.width() > parentActionRect.left())) |
2558 | { |
2559 | pos.rx() = parentActionRect.right(); |
2560 | if (pos.x() + menuSizeHint.width() > screen.x() + screen.width()) |
2561 | pos.rx() = parentActionRect.left() - menuSizeHint.width(); |
2562 | if (pos.x() < screen.x()) |
2563 | pos.rx() = screen.x() + screen.width() - menuSizeHint.width(); |
2564 | } |
2565 | } |
2566 | } |
2567 | q->setGeometry(QRect(pos, size)); |
2568 | #if QT_CONFIG(effects) |
2569 | int hGuess = q->isRightToLeft() ? QEffects::LeftScroll : QEffects::RightScroll; |
2570 | int vGuess = QEffects::DownScroll; |
2571 | if (q->isRightToLeft()) { |
2572 | if ((snapToMouse && (pos.x() + size.width() / 2 > mouse.x())) || |
2573 | (qobject_cast<QMenu*>(object: causedPopup.widget) && pos.x() + size.width() / 2 > causedPopup.widget->x())) |
2574 | hGuess = QEffects::RightScroll; |
2575 | } else { |
2576 | if ((snapToMouse && (pos.x() + size.width() / 2 < mouse.x())) || |
2577 | (qobject_cast<QMenu*>(object: causedPopup.widget) && pos.x() + size.width() / 2 < causedPopup.widget->x())) |
2578 | hGuess = QEffects::LeftScroll; |
2579 | } |
2580 | |
2581 | #if QT_CONFIG(menubar) |
2582 | if ((snapToMouse && (pos.y() + size.height() / 2 < mouse.y())) || |
2583 | (qobject_cast<QMenuBar*>(object: causedPopup.widget) && |
2584 | pos.y() + size.width() / 2 < causedPopup.widget->mapToGlobal(causedPopup.widget->pos()).y())) |
2585 | vGuess = QEffects::UpScroll; |
2586 | #endif |
2587 | if (QApplication::isEffectEnabled(Qt::UI_AnimateMenu)) { |
2588 | bool doChildEffects = true; |
2589 | #if QT_CONFIG(menubar) |
2590 | if (QMenuBar *mb = qobject_cast<QMenuBar*>(object: causedPopup.widget)) { |
2591 | doChildEffects = mb->d_func()->doChildEffects; |
2592 | mb->d_func()->doChildEffects = false; |
2593 | } else |
2594 | #endif |
2595 | if (QMenu *m = qobject_cast<QMenu*>(object: causedPopup.widget)) { |
2596 | doChildEffects = m->d_func()->doChildEffects; |
2597 | m->d_func()->doChildEffects = false; |
2598 | } |
2599 | |
2600 | if (doChildEffects) { |
2601 | if (QApplication::isEffectEnabled(Qt::UI_FadeMenu)) |
2602 | qFadeEffect(q); |
2603 | else if (causedPopup.widget) |
2604 | qScrollEffect(q, dir: qobject_cast<QMenu*>(object: causedPopup.widget) ? hGuess : vGuess); |
2605 | else |
2606 | qScrollEffect(q, dir: hGuess | vGuess); |
2607 | } else { |
2608 | // kill any running effect |
2609 | qFadeEffect(nullptr); |
2610 | qScrollEffect(nullptr); |
2611 | |
2612 | q->show(); |
2613 | } |
2614 | } else |
2615 | #endif |
2616 | { |
2617 | q->show(); |
2618 | } |
2619 | |
2620 | #ifndef QT_NO_ACCESSIBILITY |
2621 | QAccessibleEvent event(q, QAccessible::PopupMenuStart); |
2622 | QAccessible::updateAccessibility(event: &event); |
2623 | #endif |
2624 | } |
2625 | |
2626 | /*! |
2627 | Executes this menu synchronously. |
2628 | |
2629 | This is equivalent to \c{exec(pos())}. |
2630 | |
2631 | This returns the triggered QAction in either the popup menu or one |
2632 | of its submenus, or \nullptr if no item was triggered (normally |
2633 | because the user pressed Esc). |
2634 | |
2635 | In most situations you'll want to specify the position yourself, |
2636 | for example, the current mouse position: |
2637 | \snippet code/src_gui_widgets_qmenu.cpp 0 |
2638 | or aligned to a widget: |
2639 | \snippet code/src_gui_widgets_qmenu.cpp 1 |
2640 | or in reaction to a QMouseEvent *e: |
2641 | \snippet code/src_gui_widgets_qmenu.cpp 2 |
2642 | */ |
2643 | QAction *QMenu::() |
2644 | { |
2645 | return exec(pos: pos()); |
2646 | } |
2647 | |
2648 | |
2649 | /*! |
2650 | \overload |
2651 | |
2652 | Executes this menu synchronously. |
2653 | |
2654 | Pops up the menu so that the action \a action will be at the |
2655 | specified \e global position \a p. To translate a widget's local |
2656 | coordinates into global coordinates, use QWidget::mapToGlobal(). |
2657 | |
2658 | This returns the triggered QAction in either the popup menu or one |
2659 | of its submenus, or \nullptr if no item was triggered (normally |
2660 | because the user pressed Esc). |
2661 | |
2662 | Note that all signals are emitted as usual. If you connect a |
2663 | QAction to a slot and call the menu's exec(), you get the result |
2664 | both via the signal-slot connection and in the return value of |
2665 | exec(). |
2666 | |
2667 | Common usage is to position the menu at the current mouse |
2668 | position: |
2669 | \snippet code/src_gui_widgets_qmenu.cpp 3 |
2670 | or aligned to a widget: |
2671 | \snippet code/src_gui_widgets_qmenu.cpp 4 |
2672 | or in reaction to a QMouseEvent *e: |
2673 | \snippet code/src_gui_widgets_qmenu.cpp 5 |
2674 | |
2675 | When positioning a menu with exec() or popup(), bear in mind that |
2676 | you cannot rely on the menu's current size(). For performance |
2677 | reasons, the menu adapts its size only when necessary. So in many |
2678 | cases, the size before and after the show is different. Instead, |
2679 | use sizeHint() which calculates the proper size depending on the |
2680 | menu's current contents. |
2681 | |
2682 | \sa popup(), QWidget::mapToGlobal() |
2683 | */ |
2684 | QAction *QMenu::(const QPoint &p, QAction *action) |
2685 | { |
2686 | Q_D(QMenu); |
2687 | return d->exec(p, action); |
2688 | } |
2689 | |
2690 | QAction *QMenuPrivate::(const QPoint &p, QAction *action, PositionFunction positionFunction) |
2691 | { |
2692 | Q_Q(QMenu); |
2693 | q->ensurePolished(); |
2694 | q->createWinId(); |
2695 | QEventLoop evtLoop; |
2696 | eventLoop = &evtLoop; |
2697 | popup(p, atAction: action, positionFunction); |
2698 | |
2699 | QPointer<QObject> guard = q; |
2700 | (void) evtLoop.exec(); |
2701 | if (guard.isNull()) |
2702 | return nullptr; |
2703 | |
2704 | action = syncAction; |
2705 | syncAction = nullptr; |
2706 | eventLoop = nullptr; |
2707 | return action; |
2708 | } |
2709 | |
2710 | /*! |
2711 | \overload |
2712 | |
2713 | Executes a menu synchronously. |
2714 | |
2715 | The menu's actions are specified by the list of \a actions. The menu will |
2716 | pop up so that the specified action, \a at, appears at global position \a |
2717 | pos. If \a at is not specified then the menu appears at position \a |
2718 | pos. \a parent is the menu's parent widget; specifying the parent will |
2719 | provide context when \a pos alone is not enough to decide where the menu |
2720 | should go (e.g., with multiple desktops or when the parent is embedded in |
2721 | QGraphicsView). |
2722 | |
2723 | The function returns the triggered QAction in either the popup |
2724 | menu or one of its submenus, or \nullptr if no item was triggered |
2725 | (normally because the user pressed Esc). |
2726 | |
2727 | This is equivalent to: |
2728 | \snippet code/src_gui_widgets_qmenu.cpp 6 |
2729 | |
2730 | \sa popup(), QWidget::mapToGlobal() |
2731 | */ |
2732 | #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) |
2733 | QAction *QMenu::exec(const QList<QAction *> &actions, const QPoint &pos, QAction *at, QWidget *parent) |
2734 | #else |
2735 | QAction *QMenu::(QList<QAction*> actions, const QPoint &pos, QAction *at, QWidget *parent) |
2736 | #endif |
2737 | { |
2738 | QMenu (parent); |
2739 | menu.addActions(actions); |
2740 | return menu.exec(p: pos, action: at); |
2741 | } |
2742 | |
2743 | /*! |
2744 | \reimp |
2745 | */ |
2746 | void QMenu::(QHideEvent *) |
2747 | { |
2748 | Q_D(QMenu); |
2749 | emit aboutToHide(); |
2750 | if (d->eventLoop) |
2751 | d->eventLoop->exit(); |
2752 | d->setCurrentAction(action: nullptr); |
2753 | #ifndef QT_NO_ACCESSIBILITY |
2754 | QAccessibleEvent event(this, QAccessible::PopupMenuEnd); |
2755 | QAccessible::updateAccessibility(event: &event); |
2756 | #endif |
2757 | #if QT_CONFIG(menubar) |
2758 | if (QMenuBar *mb = qobject_cast<QMenuBar*>(object: d->causedPopup.widget)) |
2759 | mb->d_func()->setCurrentAction(nullptr); |
2760 | #endif |
2761 | if (QMenuPrivate::mouseDown == this) |
2762 | QMenuPrivate::mouseDown = nullptr; |
2763 | d->hasHadMouse = false; |
2764 | if (d->activeMenu) |
2765 | d->hideMenu(menu: d->activeMenu); |
2766 | d->causedPopup.widget = nullptr; |
2767 | d->causedPopup.action = nullptr; |
2768 | if (d->scroll) |
2769 | d->scroll->scrollTimer.stop(); //make sure the timer stops |
2770 | } |
2771 | |
2772 | /*! |
2773 | \reimp |
2774 | */ |
2775 | void QMenu::(QPaintEvent *e) |
2776 | { |
2777 | Q_D(QMenu); |
2778 | d->updateActionRects(); |
2779 | QPainter p(this); |
2780 | QRegion emptyArea = QRegion(rect()); |
2781 | |
2782 | QStyleOptionMenuItem ; |
2783 | menuOpt.initFrom(w: this); |
2784 | menuOpt.state = QStyle::State_None; |
2785 | menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; |
2786 | menuOpt.maxIconWidth = 0; |
2787 | menuOpt.tabWidth = 0; |
2788 | style()->drawPrimitive(pe: QStyle::PE_PanelMenu, opt: &menuOpt, p: &p, w: this); |
2789 | |
2790 | //calculate the scroll up / down rect |
2791 | const int fw = style()->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: nullptr, widget: this); |
2792 | const int hmargin = style()->pixelMetric(metric: QStyle::PM_MenuHMargin,option: nullptr, widget: this); |
2793 | const int vmargin = style()->pixelMetric(metric: QStyle::PM_MenuVMargin, option: nullptr, widget: this); |
2794 | |
2795 | QRect scrollUpRect, scrollDownRect; |
2796 | const int leftmargin = fw + hmargin + d->leftmargin; |
2797 | const int topmargin = fw + vmargin + d->topmargin; |
2798 | const int bottommargin = fw + vmargin + d->bottommargin; |
2799 | const int contentWidth = width() - (fw + hmargin) * 2 - d->leftmargin - d->rightmargin; |
2800 | if (d->scroll) { |
2801 | if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) |
2802 | scrollUpRect.setRect(ax: leftmargin, ay: topmargin, aw: contentWidth, ah: d->scrollerHeight()); |
2803 | |
2804 | if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown) |
2805 | scrollDownRect.setRect(ax: leftmargin, ay: height() - d->scrollerHeight() - bottommargin, |
2806 | aw: contentWidth, ah: d->scrollerHeight()); |
2807 | } |
2808 | |
2809 | //calculate the tear off rect |
2810 | QRect tearOffRect; |
2811 | if (d->tearoff) { |
2812 | tearOffRect.setRect(ax: leftmargin, ay: topmargin, aw: contentWidth, |
2813 | ah: style()->pixelMetric(metric: QStyle::PM_MenuTearoffHeight, option: nullptr, widget: this)); |
2814 | if (d->scroll && d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) |
2815 | tearOffRect.translate(dx: 0, dy: d->scrollerHeight()); |
2816 | } |
2817 | |
2818 | //draw the items that need updating.. |
2819 | QRect scrollUpTearOffRect = scrollUpRect.united(r: tearOffRect); |
2820 | for (int i = 0; i < d->actions.count(); ++i) { |
2821 | QAction *action = d->actions.at(i); |
2822 | QRect actionRect = d->actionRects.at(i); |
2823 | if (!e->rect().intersects(r: actionRect) |
2824 | || d->widgetItems.value(akey: action)) |
2825 | continue; |
2826 | //set the clip region to be extra safe (and adjust for the scrollers) |
2827 | emptyArea -= QRegion(actionRect); |
2828 | |
2829 | QRect adjustedActionRect = actionRect; |
2830 | if (!scrollUpTearOffRect.isEmpty() && adjustedActionRect.bottom() <= scrollUpTearOffRect.top()) |
2831 | continue; |
2832 | |
2833 | if (!scrollDownRect.isEmpty() && adjustedActionRect.top() >= scrollDownRect.bottom()) |
2834 | continue; |
2835 | |
2836 | if (adjustedActionRect.intersects(r: scrollUpTearOffRect)) { |
2837 | if (adjustedActionRect.bottom() <= scrollUpTearOffRect.bottom()) |
2838 | continue; |
2839 | else |
2840 | adjustedActionRect.setTop(scrollUpTearOffRect.bottom()+1); |
2841 | } |
2842 | |
2843 | if (adjustedActionRect.intersects(r: scrollDownRect)) { |
2844 | if (adjustedActionRect.top() >= scrollDownRect.top()) |
2845 | continue; |
2846 | else |
2847 | adjustedActionRect.setBottom(scrollDownRect.top()-1); |
2848 | } |
2849 | |
2850 | QRegion adjustedActionReg(adjustedActionRect); |
2851 | p.setClipRegion(adjustedActionReg); |
2852 | |
2853 | QStyleOptionMenuItem opt; |
2854 | initStyleOption(option: &opt, action); |
2855 | opt.rect = actionRect; |
2856 | style()->drawControl(element: QStyle::CE_MenuItem, opt: &opt, p: &p, w: this); |
2857 | } |
2858 | |
2859 | emptyArea -= QRegion(scrollUpTearOffRect); |
2860 | emptyArea -= QRegion(scrollDownRect); |
2861 | |
2862 | if (d->scrollUpTearOffItem || d->scrollDownItem) { |
2863 | if (d->scrollUpTearOffItem) |
2864 | d->scrollUpTearOffItem->updateScrollerRects(rect: scrollUpTearOffRect); |
2865 | if (d->scrollDownItem) |
2866 | d->scrollDownItem->updateScrollerRects(rect: scrollDownRect); |
2867 | } else { |
2868 | //paint scroll up /down |
2869 | d->drawScroller(painter: &p, type: QMenuPrivate::ScrollerTearOffItem::ScrollUp, rect: scrollUpRect); |
2870 | d->drawScroller(painter: &p, type: QMenuPrivate::ScrollerTearOffItem::ScrollDown, rect: scrollDownRect); |
2871 | //paint the tear off.. |
2872 | d->drawTearOff(painter: &p, rect: tearOffRect); |
2873 | } |
2874 | |
2875 | //draw border |
2876 | if (fw) { |
2877 | QRegion borderReg; |
2878 | borderReg += QRect(0, 0, fw, height()); //left |
2879 | borderReg += QRect(width()-fw, 0, fw, height()); //right |
2880 | borderReg += QRect(0, 0, width(), fw); //top |
2881 | borderReg += QRect(0, height()-fw, width(), fw); //bottom |
2882 | p.setClipRegion(borderReg); |
2883 | emptyArea -= borderReg; |
2884 | QStyleOptionFrame frame; |
2885 | frame.rect = rect(); |
2886 | frame.palette = palette(); |
2887 | frame.state = QStyle::State_None; |
2888 | frame.lineWidth = style()->pixelMetric(metric: QStyle::PM_MenuPanelWidth, option: &frame); |
2889 | frame.midLineWidth = 0; |
2890 | style()->drawPrimitive(pe: QStyle::PE_FrameMenu, opt: &frame, p: &p, w: this); |
2891 | } |
2892 | |
2893 | //finally the rest of the spaces |
2894 | p.setClipRegion(emptyArea); |
2895 | menuOpt.state = QStyle::State_None; |
2896 | menuOpt.menuItemType = QStyleOptionMenuItem::EmptyArea; |
2897 | menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; |
2898 | menuOpt.rect = rect(); |
2899 | menuOpt.menuRect = rect(); |
2900 | style()->drawControl(element: QStyle::CE_MenuEmptyArea, opt: &menuOpt, p: &p, w: this); |
2901 | } |
2902 | |
2903 | #if QT_CONFIG(wheelevent) |
2904 | /*! |
2905 | \reimp |
2906 | */ |
2907 | void QMenu::(QWheelEvent *e) |
2908 | { |
2909 | Q_D(QMenu); |
2910 | if (d->scroll && rect().contains(p: e->position().toPoint())) |
2911 | d->scrollMenu(direction: e->angleDelta().y() > 0 ? |
2912 | QMenuPrivate::QMenuScroller::ScrollUp : QMenuPrivate::QMenuScroller::ScrollDown); |
2913 | } |
2914 | #endif |
2915 | |
2916 | /*! |
2917 | \reimp |
2918 | */ |
2919 | void QMenu::(QMouseEvent *e) |
2920 | { |
2921 | Q_D(QMenu); |
2922 | if (d->aboutToHide || d->mouseEventTaken(e)) |
2923 | return; |
2924 | // Workaround for XCB on multiple screens which doesn't have offset. If the menu is open on one screen |
2925 | // and mouse clicks on second screen, e->pos() is QPoint(0,0) and the menu doesn't hide. This trick makes |
2926 | // possible to hide the menu when mouse clicks on another screen (e->screenPos() returns correct value). |
2927 | // Only when mouse clicks in QPoint(0,0) on second screen, the menu doesn't hide. |
2928 | if ((e->pos().isNull() && !e->screenPos().isNull()) || !rect().contains(p: e->pos())) { |
2929 | if (d->noReplayFor |
2930 | && QRect(d->noReplayFor->mapToGlobal(QPoint()), d->noReplayFor->size()).contains(p: e->globalPos())) |
2931 | setAttribute(Qt::WA_NoMouseReplay); |
2932 | if (d->eventLoop) // synchronous operation |
2933 | d->syncAction = nullptr; |
2934 | d->hideUpToMenuBar(); |
2935 | return; |
2936 | } |
2937 | QMenuPrivate::mouseDown = this; |
2938 | |
2939 | QAction *action = d->actionAt(p: e->pos()); |
2940 | d->setCurrentAction(action, popup: 20); |
2941 | update(); |
2942 | } |
2943 | |
2944 | /*! |
2945 | \reimp |
2946 | */ |
2947 | void QMenu::(QMouseEvent *e) |
2948 | { |
2949 | Q_D(QMenu); |
2950 | if (d->aboutToHide || d->mouseEventTaken(e)) |
2951 | return; |
2952 | if (QMenuPrivate::mouseDown != this) { |
2953 | QMenuPrivate::mouseDown = nullptr; |
2954 | return; |
2955 | } |
2956 | |
2957 | QMenuPrivate::mouseDown = nullptr; |
2958 | d->setSyncAction(); |
2959 | QAction *action = d->actionAt(p: e->pos()); |
2960 | |
2961 | if (action && action == d->currentAction) { |
2962 | if (!action->menu()){ |
2963 | #if defined(Q_OS_WIN) |
2964 | //On Windows only context menus can be activated with the right button |
2965 | if (e->button() == Qt::LeftButton || d->topCausedWidget() == 0) |
2966 | #endif |
2967 | d->activateAction(action, action_e: QAction::Trigger); |
2968 | } |
2969 | } else if ((!action || action->isEnabled()) && d->hasMouseMoved(globalPos: e->globalPos())) { |
2970 | d->hideUpToMenuBar(); |
2971 | } |
2972 | } |
2973 | |
2974 | /*! |
2975 | \reimp |
2976 | */ |
2977 | void QMenu::(QEvent *e) |
2978 | { |
2979 | Q_D(QMenu); |
2980 | if (e->type() == QEvent::StyleChange || e->type() == QEvent::FontChange || |
2981 | e->type() == QEvent::LayoutDirectionChange) { |
2982 | d->itemsDirty = 1; |
2983 | setMouseTracking(style()->styleHint(stylehint: QStyle::SH_Menu_MouseTracking, opt: nullptr, widget: this)); |
2984 | if (isVisible()) |
2985 | resize(sizeHint()); |
2986 | if (!style()->styleHint(stylehint: QStyle::SH_Menu_Scrollable, opt: nullptr, widget: this)) { |
2987 | delete d->scroll; |
2988 | d->scroll = nullptr; |
2989 | } else if (!d->scroll) { |
2990 | d->scroll = new QMenuPrivate::QMenuScroller; |
2991 | d->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone; |
2992 | } |
2993 | } else if (e->type() == QEvent::EnabledChange) { |
2994 | if (d->tornPopup) // torn-off menu |
2995 | d->tornPopup->setEnabled(isEnabled()); |
2996 | d->menuAction->setEnabled(isEnabled()); |
2997 | if (!d->platformMenu.isNull()) |
2998 | d->platformMenu->setEnabled(isEnabled()); |
2999 | } |
3000 | QWidget::changeEvent(e); |
3001 | } |
3002 | |
3003 | |
3004 | /*! |
3005 | \reimp |
3006 | */ |
3007 | bool |
3008 | QMenu::(QEvent *e) |
3009 | { |
3010 | Q_D(QMenu); |
3011 | switch (e->type()) { |
3012 | case QEvent::Polish: |
3013 | d->updateLayoutDirection(); |
3014 | break; |
3015 | case QEvent::ShortcutOverride: { |
3016 | QKeyEvent *kev = static_cast<QKeyEvent*>(e); |
3017 | if (kev->key() == Qt::Key_Up || kev->key() == Qt::Key_Down |
3018 | || kev->key() == Qt::Key_Left || kev->key() == Qt::Key_Right |
3019 | || kev->key() == Qt::Key_Enter || kev->key() == Qt::Key_Return |
3020 | #ifndef QT_NO_SHORTCUT |
3021 | || kev->matches(key: QKeySequence::Cancel) |
3022 | #endif |
3023 | ) { |
3024 | e->accept(); |
3025 | return true; |
3026 | } |
3027 | } |
3028 | break; |
3029 | case QEvent::KeyPress: { |
3030 | QKeyEvent *ke = (QKeyEvent*)e; |
3031 | if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) { |
3032 | keyPressEvent(ke); |
3033 | return true; |
3034 | } |
3035 | } break; |
3036 | case QEvent::MouseButtonPress: |
3037 | case QEvent::ContextMenu: { |
3038 | bool = true; |
3039 | if (e->type() == QEvent::MouseButtonPress) |
3040 | canPopup = (static_cast<QMouseEvent*>(e)->button() == Qt::LeftButton); |
3041 | if (canPopup && d->delayState.timer.isActive()) { |
3042 | d->delayState.stop(); |
3043 | internalDelayedPopup(); |
3044 | } |
3045 | } |
3046 | break; |
3047 | case QEvent::Resize: { |
3048 | QStyleHintReturnMask ; |
3049 | QStyleOption option; |
3050 | option.initFrom(w: this); |
3051 | if (style()->styleHint(stylehint: QStyle::SH_Menu_Mask, opt: &option, widget: this, returnData: &menuMask)) { |
3052 | setMask(menuMask.region); |
3053 | } |
3054 | d->itemsDirty = 1; |
3055 | d->updateActionRects(); |
3056 | break; } |
3057 | case QEvent::Show: |
3058 | QMenuPrivate::mouseDown = nullptr; |
3059 | d->updateActionRects(); |
3060 | d->sloppyState.reset(); |
3061 | if (d->currentAction) |
3062 | d->popupAction(action: d->currentAction, delay: 0, activateFirst: false); |
3063 | break; |
3064 | #ifndef QT_NO_TOOLTIP |
3065 | case QEvent::ToolTip: |
3066 | if (d->toolTipsVisible) { |
3067 | const QHelpEvent *ev = static_cast<const QHelpEvent*>(e); |
3068 | if (const QAction *action = actionAt(pt: ev->pos())) { |
3069 | const QString toolTip = action->d_func()->tooltip; |
3070 | if (!toolTip.isEmpty()) |
3071 | QToolTip::showText(pos: ev->globalPos(), text: toolTip, w: this); |
3072 | return true; |
3073 | } |
3074 | } |
3075 | break; |
3076 | #endif // QT_NO_TOOLTIP |
3077 | #if QT_CONFIG(whatsthis) |
3078 | case QEvent::QueryWhatsThis: |
3079 | e->setAccepted(d->whatsThis.size()); |
3080 | if (QAction *action = d->actionAt(p: static_cast<QHelpEvent*>(e)->pos())) { |
3081 | if (action->whatsThis().size() || action->menu()) |
3082 | e->accept(); |
3083 | } |
3084 | return true; |
3085 | #endif |
3086 | default: |
3087 | break; |
3088 | } |
3089 | return QWidget::event(event: e); |
3090 | } |
3091 | |
3092 | /*! |
3093 | \reimp |
3094 | */ |
3095 | bool QMenu::(bool next) |
3096 | { |
3097 | setFocus(); |
3098 | QKeyEvent ev(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); |
3099 | keyPressEvent(&ev); |
3100 | return true; |
3101 | } |
3102 | |
3103 | /*! |
3104 | \reimp |
3105 | */ |
3106 | void QMenu::(QKeyEvent *e) |
3107 | { |
3108 | Q_D(QMenu); |
3109 | d->updateActionRects(); |
3110 | int key = e->key(); |
3111 | if (isRightToLeft()) { // in reverse mode open/close key for submenues are reversed |
3112 | if (key == Qt::Key_Left) |
3113 | key = Qt::Key_Right; |
3114 | else if (key == Qt::Key_Right) |
3115 | key = Qt::Key_Left; |
3116 | } |
3117 | #ifndef Q_OS_MAC |
3118 | if (key == Qt::Key_Tab) //means down |
3119 | key = Qt::Key_Down; |
3120 | if (key == Qt::Key_Backtab) //means up |
3121 | key = Qt::Key_Up; |
3122 | #endif |
3123 | |
3124 | bool key_consumed = false; |
3125 | switch(key) { |
3126 | case Qt::Key_Home: |
3127 | key_consumed = true; |
3128 | if (d->scroll) |
3129 | d->scrollMenu(location: QMenuPrivate::QMenuScroller::ScrollTop, active: true); |
3130 | break; |
3131 | case Qt::Key_End: |
3132 | key_consumed = true; |
3133 | if (d->scroll) |
3134 | d->scrollMenu(location: QMenuPrivate::QMenuScroller::ScrollBottom, active: true); |
3135 | break; |
3136 | case Qt::Key_PageUp: |
3137 | key_consumed = true; |
3138 | if (d->currentAction && d->scroll) { |
3139 | if(d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) |
3140 | d->scrollMenu(direction: QMenuPrivate::QMenuScroller::ScrollUp, page: true, active: true); |
3141 | else |
3142 | d->scrollMenu(location: QMenuPrivate::QMenuScroller::ScrollTop, active: true); |
3143 | } |
3144 | break; |
3145 | case Qt::Key_PageDown: |
3146 | key_consumed = true; |
3147 | if (d->currentAction && d->scroll) { |
3148 | if(d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown) |
3149 | d->scrollMenu(direction: QMenuPrivate::QMenuScroller::ScrollDown, page: true, active: true); |
3150 | else |
3151 | d->scrollMenu(location: QMenuPrivate::QMenuScroller::ScrollBottom, active: true); |
3152 | } |
3153 | break; |
3154 | case Qt::Key_Up: |
3155 | case Qt::Key_Down: { |
3156 | key_consumed = true; |
3157 | QAction *nextAction = nullptr; |
3158 | QMenuPrivate::QMenuScroller::ScrollLocation scroll_loc = QMenuPrivate::QMenuScroller::ScrollStay; |
3159 | if (!d->currentAction) { |
3160 | if(key == Qt::Key_Down) { |
3161 | for(int i = 0; i < d->actions.count(); ++i) { |
3162 | QAction *act = d->actions.at(i); |
3163 | if (d->actionRects.at(i).isNull()) |
3164 | continue; |
3165 | if (!act->isSeparator() && |
3166 | (style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled, opt: nullptr, widget: this) |
3167 | || act->isEnabled())) { |
3168 | nextAction = act; |
3169 | break; |
3170 | } |
3171 | } |
3172 | } else { |
3173 | for(int i = d->actions.count()-1; i >= 0; --i) { |
3174 | QAction *act = d->actions.at(i); |
3175 | if (d->actionRects.at(i).isNull()) |
3176 | continue; |
3177 | if (!act->isSeparator() && |
3178 | (style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled, opt: nullptr, widget: this) |
3179 | || act->isEnabled())) { |
3180 | nextAction = act; |
3181 | break; |
3182 | } |
3183 | } |
3184 | } |
3185 | } else { |
3186 | for(int i = 0, y = 0; !nextAction && i < d->actions.count(); i++) { |
3187 | QAction *act = d->actions.at(i); |
3188 | if (act == d->currentAction) { |
3189 | if (key == Qt::Key_Up) { |
3190 | for(int next_i = i-1; true; next_i--) { |
3191 | if (next_i == -1) { |
3192 | if (!style()->styleHint(stylehint: QStyle::SH_Menu_SelectionWrap, opt: nullptr, widget: this)) |
3193 | break; |
3194 | if (d->scroll) |
3195 | scroll_loc = QMenuPrivate::QMenuScroller::ScrollBottom; |
3196 | next_i = d->actionRects.count()-1; |
3197 | } |
3198 | QAction *next = d->actions.at(i: next_i); |
3199 | if (next == d->currentAction) |
3200 | break; |
3201 | if (d->actionRects.at(i: next_i).isNull()) |
3202 | continue; |
3203 | if (next->isSeparator() || |
3204 | (!next->isEnabled() && |
3205 | !style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled, opt: nullptr, widget: this))) |
3206 | continue; |
3207 | nextAction = next; |
3208 | if (d->scroll && (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)) { |
3209 | int topVisible = d->scrollerHeight(); |
3210 | if (d->tearoff) |
3211 | topVisible += style()->pixelMetric(metric: QStyle::PM_MenuTearoffHeight, option: nullptr, widget: this); |
3212 | if (((y + d->scroll->scrollOffset) - topVisible) <= d->actionRects.at(i: next_i).height()) |
3213 | scroll_loc = QMenuPrivate::QMenuScroller::ScrollTop; |
3214 | } |
3215 | break; |
3216 | } |
3217 | if (!nextAction && d->tearoff) |
3218 | d->tearoffHighlighted = 1; |
3219 | } else { |
3220 | y += d->actionRects.at(i).height(); |
3221 | for(int next_i = i+1; true; next_i++) { |
3222 | if (next_i == d->actionRects.count()) { |
3223 | if (!style()->styleHint(stylehint: QStyle::SH_Menu_SelectionWrap, opt: nullptr, widget: this)) |
3224 | break; |
3225 | if (d->scroll) |
3226 | scroll_loc = QMenuPrivate::QMenuScroller::ScrollTop; |
3227 | next_i = 0; |
3228 | } |
3229 | QAction *next = d->actions.at(i: next_i); |
3230 | if (next == d->currentAction) |
3231 | break; |
3232 | if (d->actionRects.at(i: next_i).isNull()) |
3233 | continue; |
3234 | if (next->isSeparator() || |
3235 | (!next->isEnabled() && |
3236 | !style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled, opt: nullptr, widget: this))) |
3237 | continue; |
3238 | nextAction = next; |
3239 | if (d->scroll && (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)) { |
3240 | int bottomVisible = height() - d->scrollerHeight(); |
3241 | if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) |
3242 | bottomVisible -= d->scrollerHeight(); |
3243 | if (d->tearoff) |
3244 | bottomVisible -= style()->pixelMetric(metric: QStyle::PM_MenuTearoffHeight, option: nullptr, widget: this); |
3245 | if ((y + d->scroll->scrollOffset + d->actionRects.at(i: next_i).height()) > bottomVisible) |
3246 | scroll_loc = QMenuPrivate::QMenuScroller::ScrollBottom; |
3247 | } |
3248 | break; |
3249 | } |
3250 | } |
3251 | break; |
3252 | } |
3253 | y += d->actionRects.at(i).height(); |
3254 | } |
3255 | } |
3256 | if (nextAction) { |
3257 | if (d->scroll && scroll_loc != QMenuPrivate::QMenuScroller::ScrollStay) { |
3258 | d->scroll->scrollTimer.stop(); |
3259 | d->scrollMenu(action: nextAction, location: scroll_loc); |
3260 | } |
3261 | d->setCurrentAction(action: nextAction, /*popup*/-1, reason: QMenuPrivate::SelectedFromKeyboard); |
3262 | } |
3263 | break; } |
3264 | |
3265 | case Qt::Key_Right: |
3266 | if (d->currentAction && d->currentAction->isEnabled() && d->currentAction->menu()) { |
3267 | d->popupAction(action: d->currentAction, delay: 0, activateFirst: true); |
3268 | key_consumed = true; |
3269 | break; |
3270 | } |
3271 | Q_FALLTHROUGH(); |
3272 | case Qt::Key_Left: { |
3273 | if (d->currentAction && !d->scroll) { |
3274 | QAction *nextAction = nullptr; |
3275 | if (key == Qt:: |
---|