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