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 | #ifndef QMENU_P_H |
5 | #define |
6 | |
7 | // |
8 | // W A R N I N G |
9 | // ------------- |
10 | // |
11 | // This file is not part of the Qt API. It exists purely as an |
12 | // implementation detail. This header file may change from version to |
13 | // version without notice, or even be removed. |
14 | // |
15 | // We mean it. |
16 | // |
17 | |
18 | #include <QtWidgets/private/qtwidgetsglobal_p.h> |
19 | #include "QtWidgets/qmenu.h" |
20 | #if QT_CONFIG(menubar) |
21 | #include "QtWidgets/qmenubar.h" |
22 | #endif |
23 | #include "QtWidgets/qstyleoption.h" |
24 | #include "QtCore/qdatetime.h" |
25 | #include "QtCore/qmap.h" |
26 | #include "QtCore/qhash.h" |
27 | #include "QtCore/qbasictimer.h" |
28 | #include "private/qwidget_p.h" |
29 | |
30 | #include <qpa/qplatformmenu.h> |
31 | |
32 | #include <functional> |
33 | |
34 | QT_REQUIRE_CONFIG(menu); |
35 | |
36 | QT_BEGIN_NAMESPACE |
37 | |
38 | static inline int pick(Qt::Orientation o, const QPoint &pos) |
39 | { return o == Qt::Horizontal ? pos.x() : pos.y(); } |
40 | |
41 | static inline int pick(Qt::Orientation o, const QSize &size) |
42 | { return o == Qt::Horizontal ? size.width() : size.height(); } |
43 | |
44 | static inline int &rpick(Qt::Orientation o, QPoint &pos) |
45 | { return o == Qt::Horizontal ? pos.rx() : pos.ry(); } |
46 | |
47 | static inline int &rpick(Qt::Orientation o, QSize &size) |
48 | { return o == Qt::Horizontal ? size.rwidth() : size.rheight(); } |
49 | |
50 | static inline QSizePolicy::Policy pick(Qt::Orientation o, const QSizePolicy &policy) |
51 | { return o == Qt::Horizontal ? policy.horizontalPolicy() : policy.verticalPolicy(); } |
52 | |
53 | static inline int perp(Qt::Orientation o, const QPoint &pos) |
54 | { return o == Qt::Vertical ? pos.x() : pos.y(); } |
55 | |
56 | static inline int perp(Qt::Orientation o, const QSize &size) |
57 | { return o == Qt::Vertical ? size.width() : size.height(); } |
58 | |
59 | static inline int &rperp(Qt::Orientation o, QPoint &pos) |
60 | { return o == Qt::Vertical ? pos.rx() : pos.ry(); } |
61 | |
62 | static inline int &rperp(Qt::Orientation o, QSize &size) |
63 | { return o == Qt::Vertical ? size.rwidth() : size.rheight(); } |
64 | |
65 | static inline int pick(Qt::Orientation o, const QMargins &m) |
66 | { return o == Qt::Horizontal ? (m.left() + m.right()) : (m.top() + m.bottom()); } |
67 | |
68 | static inline int perp(Qt::Orientation o, const QMargins &m) |
69 | { return o == Qt::Vertical ? (m.left() + m.right()) : (m.top() + m.bottom()); } |
70 | |
71 | class ; |
72 | class QEventLoop; |
73 | |
74 | template <typename T> |
75 | class QSetValueOnDestroy |
76 | { |
77 | public: |
78 | QSetValueOnDestroy(T &toSet, T value) |
79 | : toSet(toSet) |
80 | , value(value) |
81 | { } |
82 | |
83 | ~QSetValueOnDestroy() { toSet = value; } |
84 | private: |
85 | T &toSet; |
86 | T value; |
87 | }; |
88 | |
89 | class |
90 | { |
91 | Q_DISABLE_COPY_MOVE() |
92 | public: |
93 | () |
94 | : m_enabled(false) |
95 | , m_uni_directional(false) |
96 | , m_select_other_actions(false) |
97 | , m_use_reset_action(true) |
98 | { } |
99 | |
100 | () { reset(); } |
101 | |
102 | void (QMenu *) |
103 | { |
104 | m_menu = menu; |
105 | m_uni_directional = menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuUniDirection, opt: nullptr, widget: menu); |
106 | m_uni_dir_fail_at_count = short(menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuUniDirectionFailCount, opt: nullptr, widget: menu)); |
107 | m_select_other_actions = menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuSloppySelectOtherActions, opt: nullptr , widget: menu); |
108 | m_timeout = short(menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuSloppyCloseTimeout)); |
109 | m_discard_state_when_entering_parent = menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuResetWhenReenteringParent); |
110 | m_dont_start_time_on_leave = menu->style()->styleHint(stylehint: QStyle::SH_Menu_SubMenuDontStartSloppyOnLeave); |
111 | reset(); |
112 | } |
113 | |
114 | void (); |
115 | bool () const { return m_enabled; } |
116 | |
117 | enum { |
118 | , |
119 | , |
120 | |
121 | }; |
122 | |
123 | void () |
124 | { |
125 | if (m_enabled) |
126 | m_time.start(msec: m_timeout, obj: m_menu); |
127 | } |
128 | |
129 | void () |
130 | { |
131 | if (!m_time.isActive()) |
132 | startTimer(); |
133 | } |
134 | |
135 | void () |
136 | { |
137 | m_time.stop(); |
138 | } |
139 | |
140 | void (); |
141 | void (); |
142 | |
143 | void (); |
144 | void (); |
145 | |
146 | static qreal (const QPointF &p1, const QPointF &p2) |
147 | { |
148 | const QPointF slope = p2 - p1; |
149 | if (qFuzzyIsNull(d: slope.x())) |
150 | return 9999; |
151 | return slope.y() / slope.x(); |
152 | } |
153 | |
154 | bool (qreal oldS, qreal newS, bool wantSteeper) |
155 | { |
156 | if (wantSteeper) |
157 | return oldS <= newS; |
158 | return newS <= oldS; |
159 | } |
160 | |
161 | MouseEventResult (const QPointF &mousePos, QAction *resetAction, QAction *currentAction) |
162 | { |
163 | if (m_parent) |
164 | m_parent->stopTimer(); |
165 | |
166 | if (!m_enabled) |
167 | return EventShouldBePropagated; |
168 | |
169 | startTimerIfNotRunning(); |
170 | |
171 | if (!m_sub_menu) { |
172 | reset(); |
173 | return EventShouldBePropagated; |
174 | } |
175 | |
176 | QSetValueOnDestroy<bool> setFirstMouse(m_first_mouse, false); |
177 | QSetValueOnDestroy<QPointF> setPreviousPoint(m_previous_point, mousePos); |
178 | |
179 | if (resetAction && resetAction->isSeparator()) { |
180 | m_reset_action = nullptr; |
181 | m_use_reset_action = true; |
182 | } else if (m_reset_action != resetAction) { |
183 | if (m_use_reset_action && resetAction) { |
184 | const QList<QAction *> actions = m_menu->actions(); |
185 | const int resetIdx = actions.indexOf(t: resetAction); |
186 | const int originIdx = actions.indexOf(t: m_origin_action); |
187 | if (resetIdx > -1 && originIdx > -1 && qAbs(t: resetIdx - originIdx) > 1) |
188 | m_use_reset_action = false; |
189 | } |
190 | m_reset_action = resetAction; |
191 | } |
192 | |
193 | if (m_action_rect.contains(p: mousePos)) { |
194 | startTimer(); |
195 | return currentAction == m_menu->menuAction() ? EventIsProcessed : EventShouldBePropagated; |
196 | } |
197 | |
198 | if (m_uni_directional && !m_first_mouse && resetAction != m_origin_action) { |
199 | bool left_to_right = m_menu->layoutDirection() == Qt::LeftToRight; |
200 | QRect = m_sub_menu->geometry(); |
201 | QPoint = |
202 | left_to_right? sub_menu_rect.topLeft() : sub_menu_rect.topRight(); |
203 | QPoint = |
204 | left_to_right? sub_menu_rect.bottomLeft() : sub_menu_rect.bottomRight(); |
205 | qreal prev_slope_top = slope(p1: m_previous_point, p2: sub_menu_top); |
206 | qreal prev_slope_bottom = slope(p1: m_previous_point, p2: sub_menu_bottom); |
207 | |
208 | qreal current_slope_top = slope(p1: mousePos, p2: sub_menu_top); |
209 | qreal current_slope_bottom = slope(p1: mousePos, p2: sub_menu_bottom); |
210 | |
211 | bool slopeTop = checkSlope(oldS: prev_slope_top, newS: current_slope_top, wantSteeper: sub_menu_top.y() < mousePos.y()); |
212 | bool slopeBottom = checkSlope(oldS: prev_slope_bottom, newS: current_slope_bottom, wantSteeper: sub_menu_bottom.y() > mousePos.y()); |
213 | bool rightDirection = false; |
214 | int mouseDir = int(m_previous_point.y() - mousePos.y()); |
215 | if (mouseDir >= 0) { |
216 | rightDirection = rightDirection || slopeTop; |
217 | } |
218 | if (mouseDir <= 0) { |
219 | rightDirection = rightDirection || slopeBottom; |
220 | } |
221 | |
222 | if (m_uni_dir_discarded_count >= m_uni_dir_fail_at_count && !rightDirection) { |
223 | m_uni_dir_discarded_count = 0; |
224 | return EventDiscardsSloppyState; |
225 | } |
226 | |
227 | if (!rightDirection) |
228 | m_uni_dir_discarded_count++; |
229 | else |
230 | m_uni_dir_discarded_count = 0; |
231 | |
232 | } |
233 | |
234 | return m_select_other_actions ? EventShouldBePropagated : EventIsProcessed; |
235 | } |
236 | |
237 | void (const QRect &actionRect, QAction *resetAction, QMenu *); |
238 | bool () const; |
239 | void (); |
240 | int () const { return m_timeout; } |
241 | |
242 | bool (int timerId) const { return m_time.timerId() == timerId; } |
243 | QMenu *() const { return m_sub_menu; } |
244 | |
245 | private: |
246 | QMenu * = nullptr; |
247 | QAction * = nullptr; |
248 | QAction * = nullptr; |
249 | QRectF ; |
250 | QPointF ; |
251 | QPointer<QMenu> ; |
252 | QMenuSloppyState * = nullptr; |
253 | QBasicTimer ; |
254 | short = 0; |
255 | short = 0; |
256 | short = 0; |
257 | bool = false; |
258 | bool = true; |
259 | |
260 | bool : 1; |
261 | bool : 1; |
262 | bool : 1; |
263 | bool : 1; |
264 | bool : 1; |
265 | bool : 1; |
266 | }; |
267 | |
268 | class : public QWidgetPrivate |
269 | { |
270 | Q_DECLARE_PUBLIC(QMenu) |
271 | public: |
272 | using = std::function<QPoint(const QSize &)>; |
273 | |
274 | () : |
275 | itemsDirty(false), |
276 | hasCheckableItems(false), |
277 | lastContextMenu(false), |
278 | collapsibleSeparators(true), |
279 | toolTipsVisible(false), |
280 | delayedPopupGuard(false), |
281 | hasReceievedEnter(false), |
282 | hasHadMouse(false), |
283 | aboutToHide(false), |
284 | tearoff(false), |
285 | tornoff(false), |
286 | tearoffHighlighted(false), |
287 | doChildEffects(false) |
288 | { } |
289 | |
290 | () |
291 | { |
292 | delete scroll; |
293 | if (!platformMenu.isNull() && !platformMenu->parent()) |
294 | delete platformMenu.data(); |
295 | } |
296 | void (); |
297 | QPlatformMenu *(); |
298 | void (QPlatformMenu *); |
299 | void (); |
300 | void (const QAction *action, QPlatformMenuItem *item); |
301 | QPlatformMenuItem *(const QAction *action, QPlatformMenuItem *beforeItem); |
302 | |
303 | #ifdef Q_OS_MACOS |
304 | void moveWidgetToPlatformItem(QWidget *w, QPlatformMenuItem* item); |
305 | #endif |
306 | |
307 | static QMenuPrivate *(QMenu *m) { return m->d_func(); } |
308 | int () const; |
309 | |
310 | bool () const; |
311 | |
312 | //item calculations |
313 | QRect (QAction *) const; |
314 | |
315 | mutable QList<QRect> ; |
316 | mutable QHash<QAction *, QWidget *> ; |
317 | void () const; |
318 | void (const QRect &screen) const; |
319 | QRect (QScreen *screen = nullptr) const; |
320 | bool () const; |
321 | int () const; |
322 | void (const QPoint &p, QAction *atAction, PositionFunction positionFunction = {}); |
323 | QAction *(const QPoint &p, QAction *action, PositionFunction positionFunction = {}); |
324 | |
325 | //selection |
326 | static QMenu *; |
327 | QPoint ; |
328 | |
329 | QAction * = nullptr; |
330 | #ifdef QT_KEYPAD_NAVIGATION |
331 | QAction *selectAction = nullptr; |
332 | QAction *cancelAction = nullptr; |
333 | #endif |
334 | struct { |
335 | () |
336 | { } |
337 | void (QMenu *parent) |
338 | { |
339 | this->parent = parent; |
340 | } |
341 | |
342 | void (int timeout, QAction *toStartAction) |
343 | { |
344 | if (timer.isActive() && toStartAction == action) |
345 | return; |
346 | action = toStartAction; |
347 | timer.start(msec: timeout,obj: parent); |
348 | } |
349 | void () |
350 | { |
351 | action = nullptr; |
352 | timer.stop(); |
353 | } |
354 | |
355 | QMenu * = nullptr; |
356 | QAction * = nullptr; |
357 | QBasicTimer ; |
358 | } ; |
359 | enum { |
360 | , |
361 | |
362 | }; |
363 | QWidget *() const; |
364 | QAction *(QPoint p) const; |
365 | void (); |
366 | void (QAction *, int = -1, SelectionReason reason = SelectedFromElsewhere, bool activateFirst = false); |
367 | void (QAction *, int, bool); |
368 | void (); |
369 | |
370 | //scrolling support |
371 | struct { |
372 | enum { , , , }; |
373 | enum { =0, =0x01, =0x02 }; |
374 | int = 0; |
375 | QBasicTimer ; |
376 | quint8 = ScrollNone; |
377 | quint8 = ScrollNone; |
378 | |
379 | () { } |
380 | () { } |
381 | } * = nullptr; |
382 | void (QMenuScroller::ScrollLocation location, bool active=false); |
383 | void (QMenuScroller::ScrollDirection direction, bool page=false, bool active=false); |
384 | void (QAction *action, QMenuScroller::ScrollLocation location, bool active=false); |
385 | |
386 | //synchronous operation (ie exec()) |
387 | QEventLoop * = nullptr; |
388 | QPointer<QAction> ; |
389 | |
390 | //search buffer |
391 | QString ; |
392 | QBasicTimer ; |
393 | |
394 | //passing of mouse events up the parent hierarchy |
395 | QPointer<QMenu> ; |
396 | bool (QMouseEvent *); |
397 | |
398 | //used to walk up the popup list |
399 | struct { |
400 | QPointer<QWidget> ; |
401 | QPointer<QAction> ; |
402 | }; |
403 | virtual QList<QPointer<QWidget>> () const; |
404 | QMenuCaused ; |
405 | void (); |
406 | void (QMenu *); |
407 | QWindow *() const; |
408 | |
409 | //index mappings |
410 | inline QAction *(int i) const { return q_func()->actions().at(i); } |
411 | inline int (QAction *act) const { return q_func()->actions().indexOf(t: act); } |
412 | |
413 | //tear off support |
414 | QPointer<QTornOffMenu> ; |
415 | |
416 | QMenuSloppyState ; |
417 | |
418 | //default action |
419 | QPointer<QAction> ; |
420 | |
421 | QAction * = nullptr; |
422 | QAction * = nullptr; |
423 | |
424 | void (QAction *); |
425 | void (); |
426 | |
427 | //firing of events |
428 | void (QAction *, QAction::ActionEvent, bool self=true); |
429 | void (const QList<QPointer<QWidget>> &, QAction *, QAction::ActionEvent, |
430 | bool); |
431 | |
432 | void (); |
433 | void (); |
434 | void (); |
435 | |
436 | bool (const QPoint &globalPos); |
437 | |
438 | void (); |
439 | |
440 | QPointer<QPlatformMenu> ; |
441 | |
442 | QPointer<QAction> ; |
443 | |
444 | QPointer<QWidget> ; |
445 | |
446 | class : public QWidget { |
447 | public: |
448 | enum { , }; |
449 | (Type type, QMenuPrivate *mPrivate, |
450 | QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); |
451 | void (QPaintEvent *e) override; |
452 | void (const QRect &rect); |
453 | |
454 | private: |
455 | QMenuPrivate *; |
456 | Type ; |
457 | }; |
458 | ScrollerTearOffItem * = nullptr; |
459 | ScrollerTearOffItem * = nullptr; |
460 | |
461 | void (QPainter *painter, ScrollerTearOffItem::Type type, const QRect &rect); |
462 | void (QPainter *painter, const QRect &rect); |
463 | QRect () const; |
464 | |
465 | mutable uint = 0; |
466 | mutable uint = 0; |
467 | int = 0; |
468 | int = 0; |
469 | |
470 | bool = false; |
471 | |
472 | mutable quint8 = 0; // "255cols ought to be enough for anybody." |
473 | |
474 | mutable bool : 1; |
475 | mutable bool : 1; |
476 | bool : 1; |
477 | bool : 1; |
478 | bool : 1; |
479 | bool : 1; |
480 | bool : 1; |
481 | // Selection |
482 | bool : 1; |
483 | bool : 1; |
484 | // Tear-off menus |
485 | bool : 1; |
486 | bool : 1; |
487 | bool : 1; |
488 | //menu fading/scrolling effects |
489 | bool : 1; |
490 | }; |
491 | |
492 | QT_END_NAMESPACE |
493 | |
494 | #endif // QMENU_P_H |
495 | |