1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#ifndef QMENU_P_H
41#define QMENU_P_H
42
43//
44// W A R N I N G
45// -------------
46//
47// This file is not part of the Qt API. It exists purely as an
48// implementation detail. This header file may change from version to
49// version without notice, or even be removed.
50//
51// We mean it.
52//
53
54#include <QtWidgets/private/qtwidgetsglobal_p.h>
55#if QT_CONFIG(menubar)
56#include "QtWidgets/qmenubar.h"
57#endif
58#include "QtWidgets/qstyleoption.h"
59#include "QtCore/qdatetime.h"
60#include "QtCore/qmap.h"
61#include "QtCore/qhash.h"
62#include "QtCore/qbasictimer.h"
63#include "private/qwidget_p.h"
64
65#include <qpa/qplatformmenu.h>
66
67#include <functional>
68
69QT_REQUIRE_CONFIG(menu);
70
71QT_BEGIN_NAMESPACE
72
73static inline int pick(Qt::Orientation o, const QPoint &pos)
74{ return o == Qt::Horizontal ? pos.x() : pos.y(); }
75
76static inline int pick(Qt::Orientation o, const QSize &size)
77{ return o == Qt::Horizontal ? size.width() : size.height(); }
78
79static inline int &rpick(Qt::Orientation o, QPoint &pos)
80{ return o == Qt::Horizontal ? pos.rx() : pos.ry(); }
81
82static inline int &rpick(Qt::Orientation o, QSize &size)
83{ return o == Qt::Horizontal ? size.rwidth() : size.rheight(); }
84
85static inline QSizePolicy::Policy pick(Qt::Orientation o, const QSizePolicy &policy)
86{ return o == Qt::Horizontal ? policy.horizontalPolicy() : policy.verticalPolicy(); }
87
88static inline int perp(Qt::Orientation o, const QPoint &pos)
89{ return o == Qt::Vertical ? pos.x() : pos.y(); }
90
91static inline int perp(Qt::Orientation o, const QSize &size)
92{ return o == Qt::Vertical ? size.width() : size.height(); }
93
94static inline int &rperp(Qt::Orientation o, QPoint &pos)
95{ return o == Qt::Vertical ? pos.rx() : pos.ry(); }
96
97static inline int &rperp(Qt::Orientation o, QSize &size)
98{ return o == Qt::Vertical ? size.rwidth() : size.rheight(); }
99
100static inline int pick(Qt::Orientation o, const QMargins &m)
101{ return o == Qt::Horizontal ? (m.left() + m.right()) : (m.top() + m.bottom()); }
102
103static inline int perp(Qt::Orientation o, const QMargins &m)
104{ return o == Qt::Vertical ? (m.left() + m.right()) : (m.top() + m.bottom()); }
105
106class QTornOffMenu;
107class QEventLoop;
108
109template <typename T>
110class QSetValueOnDestroy
111{
112public:
113 QSetValueOnDestroy(T &toSet, T value)
114 : toSet(toSet)
115 , value(value)
116 { }
117
118 ~QSetValueOnDestroy() { toSet = value; }
119private:
120 T &toSet;
121 T value;
122};
123
124class QMenuSloppyState
125{
126 Q_DISABLE_COPY_MOVE(QMenuSloppyState)
127public:
128 QMenuSloppyState()
129 : m_enabled(false)
130 , m_uni_directional(false)
131 , m_select_other_actions(false)
132 , m_use_reset_action(true)
133 { }
134
135 ~QMenuSloppyState() { reset(); }
136
137 void initialize(QMenu *menu)
138 {
139 m_menu = menu;
140 m_uni_directional = menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuUniDirection, opt: nullptr, widget: menu);
141 m_uni_dir_fail_at_count = short(menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuUniDirectionFailCount, opt: nullptr, widget: menu));
142 m_select_other_actions = menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuSloppySelectOtherActions, opt: nullptr , widget: menu);
143 m_timeout = short(menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuSloppyCloseTimeout));
144 m_discard_state_when_entering_parent = menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuResetWhenReenteringParent);
145 m_dont_start_time_on_leave = menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuDontStartSloppyOnLeave);
146 reset();
147 }
148
149 void reset();
150 bool enabled() const { return m_enabled; }
151
152 enum MouseEventResult {
153 EventIsProcessed,
154 EventShouldBePropagated,
155 EventDiscardsSloppyState
156 };
157
158 void startTimer()
159 {
160 if (m_enabled)
161 m_time.start(msec: m_timeout, obj: m_menu);
162 }
163
164 void startTimerIfNotRunning()
165 {
166 if (!m_time.isActive())
167 startTimer();
168 }
169
170 void stopTimer()
171 {
172 m_time.stop();
173 }
174
175 void enter();
176 void childEnter();
177
178 void leave();
179 void childLeave();
180
181 static qreal slope(const QPointF &p1, const QPointF &p2)
182 {
183 const QPointF slope = p2 - p1;
184 if (qFuzzyIsNull(d: slope.x()))
185 return 9999;
186 return slope.y() / slope.x();
187 }
188
189 bool checkSlope(qreal oldS, qreal newS, bool wantSteeper)
190 {
191 if (wantSteeper)
192 return oldS <= newS;
193 return newS <= oldS;
194 }
195
196 MouseEventResult processMouseEvent(const QPointF &mousePos, QAction *resetAction, QAction *currentAction)
197 {
198 if (m_parent)
199 m_parent->stopTimer();
200
201 if (!m_enabled)
202 return EventShouldBePropagated;
203
204 startTimerIfNotRunning();
205
206 if (!m_sub_menu) {
207 reset();
208 return EventShouldBePropagated;
209 }
210
211 QSetValueOnDestroy<bool> setFirstMouse(m_first_mouse, false);
212 QSetValueOnDestroy<QPointF> setPreviousPoint(m_previous_point, mousePos);
213
214 if (resetAction && resetAction->isSeparator()) {
215 m_reset_action = nullptr;
216 m_use_reset_action = true;
217 } else if (m_reset_action != resetAction) {
218 if (m_use_reset_action && resetAction) {
219 const QList<QAction *> actions = m_menu->actions();
220 const int resetIdx = actions.indexOf(t: resetAction);
221 const int originIdx = actions.indexOf(t: m_origin_action);
222 if (resetIdx > -1 && originIdx > -1 && qAbs(t: resetIdx - originIdx) > 1)
223 m_use_reset_action = false;
224 }
225 m_reset_action = resetAction;
226 }
227
228 if (m_action_rect.contains(p: mousePos)) {
229 startTimer();
230 return currentAction == m_menu->menuAction() ? EventIsProcessed : EventShouldBePropagated;
231 }
232
233 if (m_uni_directional && !m_first_mouse && resetAction != m_origin_action) {
234 bool left_to_right = m_menu->layoutDirection() == Qt::LeftToRight;
235 QRect sub_menu_rect = m_sub_menu->geometry();
236 QPoint sub_menu_top =
237 left_to_right? sub_menu_rect.topLeft() : sub_menu_rect.topRight();
238 QPoint sub_menu_bottom =
239 left_to_right? sub_menu_rect.bottomLeft() : sub_menu_rect.bottomRight();
240 qreal prev_slope_top = slope(p1: m_previous_point, p2: sub_menu_top);
241 qreal prev_slope_bottom = slope(p1: m_previous_point, p2: sub_menu_bottom);
242
243 qreal current_slope_top = slope(p1: mousePos, p2: sub_menu_top);
244 qreal current_slope_bottom = slope(p1: mousePos, p2: sub_menu_bottom);
245
246 bool slopeTop = checkSlope(oldS: prev_slope_top, newS: current_slope_top, wantSteeper: sub_menu_top.y() < mousePos.y());
247 bool slopeBottom = checkSlope(oldS: prev_slope_bottom, newS: current_slope_bottom, wantSteeper: sub_menu_bottom.y() > mousePos.y());
248 bool rightDirection = false;
249 int mouseDir = int(m_previous_point.y() - mousePos.y());
250 if (mouseDir >= 0) {
251 rightDirection = rightDirection || slopeTop;
252 }
253 if (mouseDir <= 0) {
254 rightDirection = rightDirection || slopeBottom;
255 }
256
257 if (m_uni_dir_discarded_count >= m_uni_dir_fail_at_count && !rightDirection) {
258 m_uni_dir_discarded_count = 0;
259 return EventDiscardsSloppyState;
260 }
261
262 if (!rightDirection)
263 m_uni_dir_discarded_count++;
264 else
265 m_uni_dir_discarded_count = 0;
266
267 }
268
269 return m_select_other_actions ? EventShouldBePropagated : EventIsProcessed;
270 }
271
272 void setSubMenuPopup(const QRect &actionRect, QAction *resetAction, QMenu *subMenu);
273 bool hasParentActiveDelayTimer() const;
274 void timeout();
275 int timeForTimeout() const { return m_timeout; }
276
277 bool isTimerId(int timerId) const { return m_time.timerId() == timerId; }
278 QMenu *subMenu() const { return m_sub_menu; }
279
280private:
281 QMenu *m_menu = nullptr;
282 QAction *m_reset_action = nullptr;
283 QAction *m_origin_action = nullptr;
284 QRectF m_action_rect;
285 QPointF m_previous_point;
286 QPointer<QMenu> m_sub_menu;
287 QMenuSloppyState *m_parent = nullptr;
288 QBasicTimer m_time;
289 short m_uni_dir_discarded_count = 0;
290 short m_uni_dir_fail_at_count = 0;
291 short m_timeout = 0;
292 bool m_init_guard = false;
293 bool m_first_mouse = true;
294
295 bool m_enabled : 1;
296 bool m_uni_directional : 1;
297 bool m_select_other_actions : 1;
298 bool m_discard_state_when_entering_parent : 1;
299 bool m_dont_start_time_on_leave : 1;
300 bool m_use_reset_action : 1;
301};
302
303class QMenuPrivate : public QWidgetPrivate
304{
305 Q_DECLARE_PUBLIC(QMenu)
306public:
307 using PositionFunction = std::function<QPoint(const QSize &)>;
308
309 QMenuPrivate() :
310 itemsDirty(false),
311 hasCheckableItems(false),
312 lastContextMenu(false),
313 collapsibleSeparators(true),
314 toolTipsVisible(false),
315 delayedPopupGuard(false),
316 hasReceievedEnter(false),
317 hasHadMouse(false),
318 aboutToHide(false),
319 tearoff(false),
320 tornoff(false),
321 tearoffHighlighted(false),
322 doChildEffects(false)
323 { }
324
325 ~QMenuPrivate()
326 {
327 delete scroll;
328 if (!platformMenu.isNull() && !platformMenu->parent())
329 delete platformMenu.data();
330 }
331 void init();
332 QPlatformMenu *createPlatformMenu();
333 void setPlatformMenu(QPlatformMenu *menu);
334 void syncPlatformMenu();
335 void copyActionToPlatformItem(const QAction *action, QPlatformMenuItem *item);
336 QPlatformMenuItem *insertActionInPlatformMenu(const QAction *action, QPlatformMenuItem *beforeItem);
337
338#ifdef Q_OS_MACOS
339 void moveWidgetToPlatformItem(QWidget *w, QPlatformMenuItem* item);
340#endif
341
342 static QMenuPrivate *get(QMenu *m) { return m->d_func(); }
343 int scrollerHeight() const;
344
345 bool isContextMenu() const;
346
347 //item calculations
348 QRect actionRect(QAction *) const;
349
350 mutable QVector<QRect> actionRects;
351 mutable QHash<QAction *, QWidget *> widgetItems;
352 void updateActionRects() const;
353 void updateActionRects(const QRect &screen) const;
354 QRect popupGeometry() const;
355 QRect popupGeometry(int screen) const;
356 bool useFullScreenForPopup() const;
357 int getLastVisibleAction() const;
358 void popup(const QPoint &p, QAction *atAction, PositionFunction positionFunction = {});
359 QAction *exec(const QPoint &p, QAction *action, PositionFunction positionFunction = {});
360
361 //selection
362 static QMenu *mouseDown;
363 QPoint mousePopupPos;
364
365 QAction *currentAction = nullptr;
366#ifdef QT_KEYPAD_NAVIGATION
367 QAction *selectAction = nullptr;
368 QAction *cancelAction = nullptr;
369#endif
370 struct DelayState {
371 DelayState()
372 { }
373 void initialize(QMenu *parent)
374 {
375 this->parent = parent;
376 }
377
378 void start(int timeout, QAction *toStartAction)
379 {
380 if (timer.isActive() && toStartAction == action)
381 return;
382 action = toStartAction;
383 timer.start(msec: timeout,obj: parent);
384 }
385 void stop()
386 {
387 action = nullptr;
388 timer.stop();
389 }
390
391 QMenu *parent = nullptr;
392 QAction *action = nullptr;
393 QBasicTimer timer;
394 } delayState;
395 enum SelectionReason {
396 SelectedFromKeyboard,
397 SelectedFromElsewhere
398 };
399 QWidget *topCausedWidget() const;
400 QAction *actionAt(QPoint p) const;
401 void setFirstActionActive();
402 void setCurrentAction(QAction *, int popup = -1, SelectionReason reason = SelectedFromElsewhere, bool activateFirst = false);
403 void popupAction(QAction *, int, bool);
404 void setSyncAction();
405
406 //scrolling support
407 struct QMenuScroller {
408 enum ScrollLocation { ScrollStay, ScrollBottom, ScrollTop, ScrollCenter };
409 enum ScrollDirection { ScrollNone=0, ScrollUp=0x01, ScrollDown=0x02 };
410 int scrollOffset = 0;
411 QBasicTimer scrollTimer;
412 quint8 scrollFlags = ScrollNone;
413 quint8 scrollDirection = ScrollNone;
414
415 QMenuScroller() { }
416 ~QMenuScroller() { }
417 } *scroll = nullptr;
418 void scrollMenu(QMenuScroller::ScrollLocation location, bool active=false);
419 void scrollMenu(QMenuScroller::ScrollDirection direction, bool page=false, bool active=false);
420 void scrollMenu(QAction *action, QMenuScroller::ScrollLocation location, bool active=false);
421
422 //synchronous operation (ie exec())
423 QEventLoop *eventLoop = nullptr;
424 QPointer<QAction> syncAction;
425
426 //search buffer
427 QString searchBuffer;
428 QBasicTimer searchBufferTimer;
429
430 //passing of mouse events up the parent hierarchy
431 QPointer<QMenu> activeMenu;
432 bool mouseEventTaken(QMouseEvent *);
433
434 //used to walk up the popup list
435 struct QMenuCaused {
436 QPointer<QWidget> widget;
437 QPointer<QAction> action;
438 };
439 virtual QVector<QPointer<QWidget> > calcCausedStack() const;
440 QMenuCaused causedPopup;
441 void hideUpToMenuBar();
442 void hideMenu(QMenu *menu);
443
444 //index mappings
445 inline QAction *actionAt(int i) const { return q_func()->actions().at(i); }
446 inline int indexOf(QAction *act) const { return q_func()->actions().indexOf(t: act); }
447
448 //tear off support
449 QPointer<QTornOffMenu> tornPopup;
450
451 QMenuSloppyState sloppyState;
452
453 //default action
454 QPointer<QAction> defaultAction;
455
456 QAction *menuAction = nullptr;
457 QAction *defaultMenuAction = nullptr;
458
459 void setOverrideMenuAction(QAction *);
460 void _q_overrideMenuActionDestroyed();
461
462 //firing of events
463 void activateAction(QAction *, QAction::ActionEvent, bool self=true);
464 void activateCausedStack(const QVector<QPointer<QWidget> > &, QAction *, QAction::ActionEvent, bool);
465
466 void _q_actionTriggered();
467 void _q_actionHovered();
468 void _q_platformMenuAboutToShow();
469
470 bool hasMouseMoved(const QPoint &globalPos);
471
472 void updateLayoutDirection();
473
474 QPointer<QPlatformMenu> platformMenu;
475
476 QPointer<QAction> actionAboutToTrigger;
477
478 QPointer<QWidget> noReplayFor;
479
480 class ScrollerTearOffItem : public QWidget {
481 public:
482 enum Type { ScrollUp, ScrollDown };
483 ScrollerTearOffItem(Type type, QMenuPrivate *mPrivate,
484 QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
485 void paintEvent(QPaintEvent *e) override;
486 void updateScrollerRects(const QRect &rect);
487
488 private:
489 QMenuPrivate *menuPrivate;
490 Type scrollType;
491 };
492 ScrollerTearOffItem *scrollUpTearOffItem = nullptr;
493 ScrollerTearOffItem *scrollDownItem = nullptr;
494
495 void drawScroller(QPainter *painter, ScrollerTearOffItem::Type type, const QRect &rect);
496 void drawTearOff(QPainter *painter, const QRect &rect);
497 QRect rect() const;
498
499 mutable uint maxIconWidth = 0;
500 mutable uint tabWidth = 0;
501 int motions = 0;
502 int mousePopupDelay = 0;
503
504 bool activationRecursionGuard = false;
505
506 mutable quint8 ncols = 0; // "255cols ought to be enough for anybody."
507
508 mutable bool itemsDirty : 1;
509 mutable bool hasCheckableItems : 1;
510 bool lastContextMenu : 1;
511 bool collapsibleSeparators : 1;
512 bool toolTipsVisible : 1;
513 bool delayedPopupGuard : 1;
514 bool hasReceievedEnter : 1;
515 // Selection
516 bool hasHadMouse : 1;
517 bool aboutToHide : 1;
518 // Tear-off menus
519 bool tearoff : 1;
520 bool tornoff : 1;
521 bool tearoffHighlighted : 1;
522 //menu fading/scrolling effects
523 bool doChildEffects : 1;
524
525 int popupScreen = -1;
526};
527
528QT_END_NAMESPACE
529
530#endif // QMENU_P_H
531

source code of qtbase/src/widgets/widgets/qmenu_p.h