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
67QT_REQUIRE_CONFIG(menu);
68
69QT_BEGIN_NAMESPACE
70
71static inline int pick(Qt::Orientation o, const QPoint &pos)
72{ return o == Qt::Horizontal ? pos.x() : pos.y(); }
73
74static inline int pick(Qt::Orientation o, const QSize &size)
75{ return o == Qt::Horizontal ? size.width() : size.height(); }
76
77static inline int &rpick(Qt::Orientation o, QPoint &pos)
78{ return o == Qt::Horizontal ? pos.rx() : pos.ry(); }
79
80static inline int &rpick(Qt::Orientation o, QSize &size)
81{ return o == Qt::Horizontal ? size.rwidth() : size.rheight(); }
82
83static inline QSizePolicy::Policy pick(Qt::Orientation o, const QSizePolicy &policy)
84{ return o == Qt::Horizontal ? policy.horizontalPolicy() : policy.verticalPolicy(); }
85
86static inline int perp(Qt::Orientation o, const QPoint &pos)
87{ return o == Qt::Vertical ? pos.x() : pos.y(); }
88
89static inline int perp(Qt::Orientation o, const QSize &size)
90{ return o == Qt::Vertical ? size.width() : size.height(); }
91
92static inline int &rperp(Qt::Orientation o, QPoint &pos)
93{ return o == Qt::Vertical ? pos.rx() : pos.ry(); }
94
95static inline int &rperp(Qt::Orientation o, QSize &size)
96{ return o == Qt::Vertical ? size.rwidth() : size.rheight(); }
97
98static inline int pick(Qt::Orientation o, const QMargins &m)
99{ return o == Qt::Horizontal ? (m.left() + m.right()) : (m.top() + m.bottom()); }
100
101static inline int perp(Qt::Orientation o, const QMargins &m)
102{ return o == Qt::Vertical ? (m.left() + m.right()) : (m.top() + m.bottom()); }
103
104class QTornOffMenu;
105class QEventLoop;
106
107template <typename T>
108class QSetValueOnDestroy
109{
110public:
111 QSetValueOnDestroy(T &toSet, T value)
112 : toSet(toSet)
113 , value(value)
114 { }
115
116 ~QSetValueOnDestroy() { toSet = value; }
117private:
118 T &toSet;
119 T value;
120};
121
122class QMenuSloppyState
123{
124 Q_DISABLE_COPY_MOVE(QMenuSloppyState)
125public:
126 QMenuSloppyState()
127 : m_enabled(false)
128 , m_uni_directional(false)
129 , m_select_other_actions(false)
130 , m_use_reset_action(true)
131 { }
132
133 ~QMenuSloppyState() { reset(); }
134
135 void initialize(QMenu *menu)
136 {
137 m_menu = menu;
138 m_uni_directional = menu->style()->styleHint(QStyle::SH_Menu_SubMenuUniDirection, nullptr, menu);
139 m_uni_dir_fail_at_count = short(menu->style()->styleHint(QStyle::SH_Menu_SubMenuUniDirectionFailCount, nullptr, menu));
140 m_select_other_actions = menu->style()->styleHint(QStyle::SH_Menu_SubMenuSloppySelectOtherActions, nullptr , menu);
141 m_timeout = short(menu->style()->styleHint(QStyle::SH_Menu_SubMenuSloppyCloseTimeout));
142 m_discard_state_when_entering_parent = menu->style()->styleHint(QStyle::SH_Menu_SubMenuResetWhenReenteringParent);
143 m_dont_start_time_on_leave = menu->style()->styleHint(QStyle::SH_Menu_SubMenuDontStartSloppyOnLeave);
144 reset();
145 }
146
147 void reset();
148 bool enabled() const { return m_enabled; }
149
150 enum MouseEventResult {
151 EventIsProcessed,
152 EventShouldBePropagated,
153 EventDiscardsSloppyState
154 };
155
156 void startTimer()
157 {
158 if (m_enabled)
159 m_time.start(m_timeout, m_menu);
160 }
161
162 void startTimerIfNotRunning()
163 {
164 if (!m_time.isActive())
165 startTimer();
166 }
167
168 void stopTimer()
169 {
170 m_time.stop();
171 }
172
173 void enter();
174 void childEnter();
175
176 void leave();
177 void childLeave();
178
179 static qreal slope(const QPointF &p1, const QPointF &p2)
180 {
181 const QPointF slope = p2 - p1;
182 if (qFuzzyIsNull(slope.x()))
183 return 9999;
184 return slope.y() / slope.x();
185 }
186
187 bool checkSlope(qreal oldS, qreal newS, bool wantSteeper)
188 {
189 if (wantSteeper)
190 return oldS <= newS;
191 return newS <= oldS;
192 }
193
194 MouseEventResult processMouseEvent(const QPointF &mousePos, QAction *resetAction, QAction *currentAction)
195 {
196 if (m_parent)
197 m_parent->stopTimer();
198
199 if (!m_enabled)
200 return EventShouldBePropagated;
201
202 startTimerIfNotRunning();
203
204 if (!m_sub_menu) {
205 reset();
206 return EventShouldBePropagated;
207 }
208
209 QSetValueOnDestroy<bool> setFirstMouse(m_first_mouse, false);
210 QSetValueOnDestroy<QPointF> setPreviousPoint(m_previous_point, mousePos);
211
212 if (resetAction && resetAction->isSeparator()) {
213 m_reset_action = nullptr;
214 m_use_reset_action = true;
215 } else if (m_reset_action != resetAction) {
216 if (m_use_reset_action && resetAction) {
217 const QList<QAction *> actions = m_menu->actions();
218 const int resetIdx = actions.indexOf(resetAction);
219 const int originIdx = actions.indexOf(m_origin_action);
220 if (resetIdx > -1 && originIdx > -1 && qAbs(resetIdx - originIdx) > 1)
221 m_use_reset_action = false;
222 }
223 m_reset_action = resetAction;
224 }
225
226 if (m_action_rect.contains(mousePos)) {
227 startTimer();
228 return currentAction == m_menu->menuAction() ? EventIsProcessed : EventShouldBePropagated;
229 }
230
231 if (m_uni_directional && !m_first_mouse && resetAction != m_origin_action) {
232 bool left_to_right = m_menu->layoutDirection() == Qt::LeftToRight;
233 QRect sub_menu_rect = m_sub_menu->geometry();
234 QPoint sub_menu_top =
235 left_to_right? sub_menu_rect.topLeft() : sub_menu_rect.topRight();
236 QPoint sub_menu_bottom =
237 left_to_right? sub_menu_rect.bottomLeft() : sub_menu_rect.bottomRight();
238 qreal prev_slope_top = slope(m_previous_point, sub_menu_top);
239 qreal prev_slope_bottom = slope(m_previous_point, sub_menu_bottom);
240
241 qreal current_slope_top = slope(mousePos, sub_menu_top);
242 qreal current_slope_bottom = slope(mousePos, sub_menu_bottom);
243
244 bool slopeTop = checkSlope(prev_slope_top, current_slope_top, sub_menu_top.y() < mousePos.y());
245 bool slopeBottom = checkSlope(prev_slope_bottom, current_slope_bottom, sub_menu_bottom.y() > mousePos.y());
246 bool rightDirection = false;
247 int mouseDir = int(m_previous_point.y() - mousePos.y());
248 if (mouseDir >= 0) {
249 rightDirection = rightDirection || slopeTop;
250 }
251 if (mouseDir <= 0) {
252 rightDirection = rightDirection || slopeBottom;
253 }
254
255 if (m_uni_dir_discarded_count >= m_uni_dir_fail_at_count && !rightDirection) {
256 m_uni_dir_discarded_count = 0;
257 return EventDiscardsSloppyState;
258 }
259
260 if (!rightDirection)
261 m_uni_dir_discarded_count++;
262 else
263 m_uni_dir_discarded_count = 0;
264
265 }
266
267 return m_select_other_actions ? EventShouldBePropagated : EventIsProcessed;
268 }
269
270 void setSubMenuPopup(const QRect &actionRect, QAction *resetAction, QMenu *subMenu);
271 bool hasParentActiveDelayTimer() const;
272 void timeout();
273 int timeForTimeout() const { return m_timeout; }
274
275 bool isTimerId(int timerId) const { return m_time.timerId() == timerId; }
276 QMenu *subMenu() const { return m_sub_menu; }
277
278private:
279 QMenu *m_menu = nullptr;
280 QAction *m_reset_action = nullptr;
281 QAction *m_origin_action = nullptr;
282 QRectF m_action_rect;
283 QPointF m_previous_point;
284 QPointer<QMenu> m_sub_menu;
285 QMenuSloppyState *m_parent = nullptr;
286 QBasicTimer m_time;
287 short m_uni_dir_discarded_count = 0;
288 short m_uni_dir_fail_at_count = 0;
289 short m_timeout = 0;
290 bool m_init_guard = false;
291 bool m_first_mouse = true;
292
293 bool m_enabled : 1;
294 bool m_uni_directional : 1;
295 bool m_select_other_actions : 1;
296 bool m_discard_state_when_entering_parent : 1;
297 bool m_dont_start_time_on_leave : 1;
298 bool m_use_reset_action : 1;
299};
300
301class QMenuPrivate : public QWidgetPrivate
302{
303 Q_DECLARE_PUBLIC(QMenu)
304public:
305 QMenuPrivate() :
306 itemsDirty(false),
307 hasCheckableItems(false),
308 lastContextMenu(false),
309 collapsibleSeparators(true),
310 toolTipsVisible(false),
311 delayedPopupGuard(false),
312 hasReceievedEnter(false),
313 hasHadMouse(false),
314 aboutToHide(false),
315 tearoff(false),
316 tornoff(false),
317 tearoffHighlighted(false),
318 doChildEffects(false)
319 { }
320
321 ~QMenuPrivate()
322 {
323 delete scroll;
324 if (!platformMenu.isNull() && !platformMenu->parent())
325 delete platformMenu.data();
326 }
327 void init();
328 QPlatformMenu *createPlatformMenu();
329 void setPlatformMenu(QPlatformMenu *menu);
330 void syncPlatformMenu();
331 void copyActionToPlatformItem(const QAction *action, QPlatformMenuItem *item);
332 QPlatformMenuItem *insertActionInPlatformMenu(const QAction *action, QPlatformMenuItem *beforeItem);
333
334#ifdef Q_OS_OSX
335 void moveWidgetToPlatformItem(QWidget *w, QPlatformMenuItem* item);
336#endif
337
338 static QMenuPrivate *get(QMenu *m) { return m->d_func(); }
339 int scrollerHeight() const;
340
341 bool isContextMenu() const;
342
343 //item calculations
344 QRect actionRect(QAction *) const;
345
346 mutable QVector<QRect> actionRects;
347 mutable QHash<QAction *, QWidget *> widgetItems;
348 void updateActionRects() const;
349 void updateActionRects(const QRect &screen) const;
350 QRect popupGeometry() const;
351 QRect popupGeometry(int screen) const;
352 bool useFullScreenForPopup() const;
353 int getLastVisibleAction() const;
354
355 //selection
356 static QMenu *mouseDown;
357 QPoint mousePopupPos;
358
359 QAction *currentAction = nullptr;
360#ifdef QT_KEYPAD_NAVIGATION
361 QAction *selectAction = nullptr;
362 QAction *cancelAction = nullptr;
363#endif
364 struct DelayState {
365 DelayState()
366 { }
367 void initialize(QMenu *parent)
368 {
369 this->parent = parent;
370 }
371
372 void start(int timeout, QAction *toStartAction)
373 {
374 if (timer.isActive() && toStartAction == action)
375 return;
376 action = toStartAction;
377 timer.start(timeout,parent);
378 }
379 void stop()
380 {
381 action = nullptr;
382 timer.stop();
383 }
384
385 QMenu *parent = nullptr;
386 QAction *action = nullptr;
387 QBasicTimer timer;
388 } delayState;
389 enum SelectionReason {
390 SelectedFromKeyboard,
391 SelectedFromElsewhere
392 };
393 QWidget *topCausedWidget() const;
394 QAction *actionAt(QPoint p) const;
395 void setFirstActionActive();
396 void setCurrentAction(QAction *, int popup = -1, SelectionReason reason = SelectedFromElsewhere, bool activateFirst = false);
397 void popupAction(QAction *, int, bool);
398 void setSyncAction();
399
400 //scrolling support
401 struct QMenuScroller {
402 enum ScrollLocation { ScrollStay, ScrollBottom, ScrollTop, ScrollCenter };
403 enum ScrollDirection { ScrollNone=0, ScrollUp=0x01, ScrollDown=0x02 };
404 int scrollOffset = 0;
405 QBasicTimer scrollTimer;
406 quint8 scrollFlags = ScrollNone;
407 quint8 scrollDirection = ScrollNone;
408
409 QMenuScroller() { }
410 ~QMenuScroller() { }
411 } *scroll = nullptr;
412 void scrollMenu(QMenuScroller::ScrollLocation location, bool active=false);
413 void scrollMenu(QMenuScroller::ScrollDirection direction, bool page=false, bool active=false);
414 void scrollMenu(QAction *action, QMenuScroller::ScrollLocation location, bool active=false);
415
416 //synchronous operation (ie exec())
417 QEventLoop *eventLoop = nullptr;
418 QPointer<QAction> syncAction;
419
420 //search buffer
421 QString searchBuffer;
422 QBasicTimer searchBufferTimer;
423
424 //passing of mouse events up the parent hierarchy
425 QPointer<QMenu> activeMenu;
426 bool mouseEventTaken(QMouseEvent *);
427
428 //used to walk up the popup list
429 struct QMenuCaused {
430 QPointer<QWidget> widget;
431 QPointer<QAction> action;
432 };
433 virtual QVector<QPointer<QWidget> > calcCausedStack() const;
434 QMenuCaused causedPopup;
435 void hideUpToMenuBar();
436 void hideMenu(QMenu *menu);
437
438 //index mappings
439 inline QAction *actionAt(int i) const { return q_func()->actions().at(i); }
440 inline int indexOf(QAction *act) const { return q_func()->actions().indexOf(act); }
441
442 //tear off support
443 QPointer<QTornOffMenu> tornPopup;
444
445 QMenuSloppyState sloppyState;
446
447 //default action
448 QPointer<QAction> defaultAction;
449
450 QAction *menuAction = nullptr;
451 QAction *defaultMenuAction = nullptr;
452
453 void setOverrideMenuAction(QAction *);
454 void _q_overrideMenuActionDestroyed();
455
456 //firing of events
457 void activateAction(QAction *, QAction::ActionEvent, bool self=true);
458 void activateCausedStack(const QVector<QPointer<QWidget> > &, QAction *, QAction::ActionEvent, bool);
459
460 void _q_actionTriggered();
461 void _q_actionHovered();
462 void _q_platformMenuAboutToShow();
463
464 bool hasMouseMoved(const QPoint &globalPos);
465
466 void updateLayoutDirection();
467
468 QPointer<QPlatformMenu> platformMenu;
469
470 QPointer<QAction> actionAboutToTrigger;
471
472 QPointer<QWidget> noReplayFor;
473
474 class ScrollerTearOffItem : public QWidget {
475 public:
476 enum Type { ScrollUp, ScrollDown };
477 ScrollerTearOffItem(Type type, QMenuPrivate *mPrivate,
478 QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
479 void paintEvent(QPaintEvent *e) override;
480 void updateScrollerRects(const QRect &rect);
481
482 private:
483 QMenuPrivate *menuPrivate;
484 Type scrollType;
485 };
486 ScrollerTearOffItem *scrollUpTearOffItem = nullptr;
487 ScrollerTearOffItem *scrollDownItem = nullptr;
488
489 void drawScroller(QPainter *painter, ScrollerTearOffItem::Type type, const QRect &rect);
490 void drawTearOff(QPainter *painter, const QRect &rect);
491 QRect rect() const;
492
493 mutable uint maxIconWidth = 0;
494 mutable uint tabWidth = 0;
495 int motions = 0;
496 int mousePopupDelay = 0;
497
498 bool activationRecursionGuard = false;
499
500 mutable quint8 ncols = 0; // "255cols ought to be enough for anybody."
501
502 mutable bool itemsDirty : 1;
503 mutable bool hasCheckableItems : 1;
504 bool lastContextMenu : 1;
505 bool collapsibleSeparators : 1;
506 bool toolTipsVisible : 1;
507 bool delayedPopupGuard : 1;
508 bool hasReceievedEnter : 1;
509 // Selection
510 bool hasHadMouse : 1;
511 bool aboutToHide : 1;
512 // Tear-off menus
513 bool tearoff : 1;
514 bool tornoff : 1;
515 bool tearoffHighlighted : 1;
516 //menu fading/scrolling effects
517 bool doChildEffects : 1;
518
519 int popupScreen = -1;
520};
521
522QT_END_NAMESPACE
523
524#endif // QMENU_P_H
525