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 |
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 | |
69 | QT_REQUIRE_CONFIG(menu); |
70 | |
71 | QT_BEGIN_NAMESPACE |
72 | |
73 | static inline int pick(Qt::Orientation o, const QPoint &pos) |
74 | { return o == Qt::Horizontal ? pos.x() : pos.y(); } |
75 | |
76 | static inline int pick(Qt::Orientation o, const QSize &size) |
77 | { return o == Qt::Horizontal ? size.width() : size.height(); } |
78 | |
79 | static inline int &rpick(Qt::Orientation o, QPoint &pos) |
80 | { return o == Qt::Horizontal ? pos.rx() : pos.ry(); } |
81 | |
82 | static inline int &rpick(Qt::Orientation o, QSize &size) |
83 | { return o == Qt::Horizontal ? size.rwidth() : size.rheight(); } |
84 | |
85 | static inline QSizePolicy::Policy pick(Qt::Orientation o, const QSizePolicy &policy) |
86 | { return o == Qt::Horizontal ? policy.horizontalPolicy() : policy.verticalPolicy(); } |
87 | |
88 | static inline int perp(Qt::Orientation o, const QPoint &pos) |
89 | { return o == Qt::Vertical ? pos.x() : pos.y(); } |
90 | |
91 | static inline int perp(Qt::Orientation o, const QSize &size) |
92 | { return o == Qt::Vertical ? size.width() : size.height(); } |
93 | |
94 | static inline int &rperp(Qt::Orientation o, QPoint &pos) |
95 | { return o == Qt::Vertical ? pos.rx() : pos.ry(); } |
96 | |
97 | static inline int &rperp(Qt::Orientation o, QSize &size) |
98 | { return o == Qt::Vertical ? size.rwidth() : size.rheight(); } |
99 | |
100 | static inline int pick(Qt::Orientation o, const QMargins &m) |
101 | { return o == Qt::Horizontal ? (m.left() + m.right()) : (m.top() + m.bottom()); } |
102 | |
103 | static inline int perp(Qt::Orientation o, const QMargins &m) |
104 | { return o == Qt::Vertical ? (m.left() + m.right()) : (m.top() + m.bottom()); } |
105 | |
106 | class ; |
107 | class QEventLoop; |
108 | |
109 | template <typename T> |
110 | class QSetValueOnDestroy |
111 | { |
112 | public: |
113 | QSetValueOnDestroy(T &toSet, T value) |
114 | : toSet(toSet) |
115 | , value(value) |
116 | { } |
117 | |
118 | ~QSetValueOnDestroy() { toSet = value; } |
119 | private: |
120 | T &toSet; |
121 | T value; |
122 | }; |
123 | |
124 | class |
125 | { |
126 | Q_DISABLE_COPY_MOVE() |
127 | public: |
128 | () |
129 | : m_enabled(false) |
130 | , m_uni_directional(false) |
131 | , m_select_other_actions(false) |
132 | , m_use_reset_action(true) |
133 | { } |
134 | |
135 | () { reset(); } |
136 | |
137 | void (QMenu *) |
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 (); |
150 | bool () const { return m_enabled; } |
151 | |
152 | enum { |
153 | , |
154 | , |
155 | |
156 | }; |
157 | |
158 | void () |
159 | { |
160 | if (m_enabled) |
161 | m_time.start(msec: m_timeout, obj: m_menu); |
162 | } |
163 | |
164 | void () |
165 | { |
166 | if (!m_time.isActive()) |
167 | startTimer(); |
168 | } |
169 | |
170 | void () |
171 | { |
172 | m_time.stop(); |
173 | } |
174 | |
175 | void (); |
176 | void (); |
177 | |
178 | void (); |
179 | void (); |
180 | |
181 | static qreal (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 (qreal oldS, qreal newS, bool wantSteeper) |
190 | { |
191 | if (wantSteeper) |
192 | return oldS <= newS; |
193 | return newS <= oldS; |
194 | } |
195 | |
196 | MouseEventResult (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 = m_sub_menu->geometry(); |
236 | QPoint = |
237 | left_to_right? sub_menu_rect.topLeft() : sub_menu_rect.topRight(); |
238 | QPoint = |
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 (const QRect &actionRect, QAction *resetAction, QMenu *); |
273 | bool () const; |
274 | void (); |
275 | int () const { return m_timeout; } |
276 | |
277 | bool (int timerId) const { return m_time.timerId() == timerId; } |
278 | QMenu *() const { return m_sub_menu; } |
279 | |
280 | private: |
281 | QMenu * = nullptr; |
282 | QAction * = nullptr; |
283 | QAction * = nullptr; |
284 | QRectF ; |
285 | QPointF ; |
286 | QPointer<QMenu> ; |
287 | QMenuSloppyState * = nullptr; |
288 | QBasicTimer ; |
289 | short = 0; |
290 | short = 0; |
291 | short = 0; |
292 | bool = false; |
293 | bool = true; |
294 | |
295 | bool : 1; |
296 | bool : 1; |
297 | bool : 1; |
298 | bool : 1; |
299 | bool : 1; |
300 | bool : 1; |
301 | }; |
302 | |
303 | class : public QWidgetPrivate |
304 | { |
305 | Q_DECLARE_PUBLIC(QMenu) |
306 | public: |
307 | using = std::function<QPoint(const QSize &)>; |
308 | |
309 | () : |
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 | () |
326 | { |
327 | delete scroll; |
328 | if (!platformMenu.isNull() && !platformMenu->parent()) |
329 | delete platformMenu.data(); |
330 | } |
331 | void (); |
332 | QPlatformMenu *(); |
333 | void (QPlatformMenu *); |
334 | void (); |
335 | void (const QAction *action, QPlatformMenuItem *item); |
336 | QPlatformMenuItem *(const QAction *action, QPlatformMenuItem *beforeItem); |
337 | |
338 | #ifdef Q_OS_MACOS |
339 | void moveWidgetToPlatformItem(QWidget *w, QPlatformMenuItem* item); |
340 | #endif |
341 | |
342 | static QMenuPrivate *(QMenu *m) { return m->d_func(); } |
343 | int () const; |
344 | |
345 | bool () const; |
346 | |
347 | //item calculations |
348 | QRect (QAction *) const; |
349 | |
350 | mutable QVector<QRect> ; |
351 | mutable QHash<QAction *, QWidget *> ; |
352 | void () const; |
353 | void (const QRect &screen) const; |
354 | QRect () const; |
355 | QRect (int screen) const; |
356 | bool () const; |
357 | int () const; |
358 | void (const QPoint &p, QAction *atAction, PositionFunction positionFunction = {}); |
359 | QAction *(const QPoint &p, QAction *action, PositionFunction positionFunction = {}); |
360 | |
361 | //selection |
362 | static QMenu *; |
363 | QPoint ; |
364 | |
365 | QAction * = nullptr; |
366 | #ifdef QT_KEYPAD_NAVIGATION |
367 | QAction *selectAction = nullptr; |
368 | QAction *cancelAction = nullptr; |
369 | #endif |
370 | struct { |
371 | () |
372 | { } |
373 | void (QMenu *parent) |
374 | { |
375 | this->parent = parent; |
376 | } |
377 | |
378 | void (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 () |
386 | { |
387 | action = nullptr; |
388 | timer.stop(); |
389 | } |
390 | |
391 | QMenu * = nullptr; |
392 | QAction * = nullptr; |
393 | QBasicTimer ; |
394 | } ; |
395 | enum { |
396 | , |
397 | |
398 | }; |
399 | QWidget *() const; |
400 | QAction *(QPoint p) const; |
401 | void (); |
402 | void (QAction *, int = -1, SelectionReason reason = SelectedFromElsewhere, bool activateFirst = false); |
403 | void (QAction *, int, bool); |
404 | void (); |
405 | |
406 | //scrolling support |
407 | struct { |
408 | enum { , , , }; |
409 | enum { =0, =0x01, =0x02 }; |
410 | int = 0; |
411 | QBasicTimer ; |
412 | quint8 = ScrollNone; |
413 | quint8 = ScrollNone; |
414 | |
415 | () { } |
416 | () { } |
417 | } * = nullptr; |
418 | void (QMenuScroller::ScrollLocation location, bool active=false); |
419 | void (QMenuScroller::ScrollDirection direction, bool page=false, bool active=false); |
420 | void (QAction *action, QMenuScroller::ScrollLocation location, bool active=false); |
421 | |
422 | //synchronous operation (ie exec()) |
423 | QEventLoop * = nullptr; |
424 | QPointer<QAction> ; |
425 | |
426 | //search buffer |
427 | QString ; |
428 | QBasicTimer ; |
429 | |
430 | //passing of mouse events up the parent hierarchy |
431 | QPointer<QMenu> ; |
432 | bool (QMouseEvent *); |
433 | |
434 | //used to walk up the popup list |
435 | struct { |
436 | QPointer<QWidget> ; |
437 | QPointer<QAction> ; |
438 | }; |
439 | virtual QVector<QPointer<QWidget> > () const; |
440 | QMenuCaused ; |
441 | void (); |
442 | void (QMenu *); |
443 | |
444 | //index mappings |
445 | inline QAction *(int i) const { return q_func()->actions().at(i); } |
446 | inline int (QAction *act) const { return q_func()->actions().indexOf(t: act); } |
447 | |
448 | //tear off support |
449 | QPointer<QTornOffMenu> ; |
450 | |
451 | QMenuSloppyState ; |
452 | |
453 | //default action |
454 | QPointer<QAction> ; |
455 | |
456 | QAction * = nullptr; |
457 | QAction * = nullptr; |
458 | |
459 | void (QAction *); |
460 | void (); |
461 | |
462 | //firing of events |
463 | void (QAction *, QAction::ActionEvent, bool self=true); |
464 | void (const QVector<QPointer<QWidget> > &, QAction *, QAction::ActionEvent, bool); |
465 | |
466 | void (); |
467 | void (); |
468 | void (); |
469 | |
470 | bool (const QPoint &globalPos); |
471 | |
472 | void (); |
473 | |
474 | QPointer<QPlatformMenu> ; |
475 | |
476 | QPointer<QAction> ; |
477 | |
478 | QPointer<QWidget> ; |
479 | |
480 | class : public QWidget { |
481 | public: |
482 | enum { , }; |
483 | (Type type, QMenuPrivate *mPrivate, |
484 | QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); |
485 | void (QPaintEvent *e) override; |
486 | void (const QRect &rect); |
487 | |
488 | private: |
489 | QMenuPrivate *; |
490 | Type ; |
491 | }; |
492 | ScrollerTearOffItem * = nullptr; |
493 | ScrollerTearOffItem * = nullptr; |
494 | |
495 | void (QPainter *painter, ScrollerTearOffItem::Type type, const QRect &rect); |
496 | void (QPainter *painter, const QRect &rect); |
497 | QRect () const; |
498 | |
499 | mutable uint = 0; |
500 | mutable uint = 0; |
501 | int = 0; |
502 | int = 0; |
503 | |
504 | bool = false; |
505 | |
506 | mutable quint8 = 0; // "255cols ought to be enough for anybody." |
507 | |
508 | mutable bool : 1; |
509 | mutable bool : 1; |
510 | bool : 1; |
511 | bool : 1; |
512 | bool : 1; |
513 | bool : 1; |
514 | bool : 1; |
515 | // Selection |
516 | bool : 1; |
517 | bool : 1; |
518 | // Tear-off menus |
519 | bool : 1; |
520 | bool : 1; |
521 | bool : 1; |
522 | //menu fading/scrolling effects |
523 | bool : 1; |
524 | |
525 | int = -1; |
526 | }; |
527 | |
528 | QT_END_NAMESPACE |
529 | |
530 | #endif // QMENU_P_H |
531 | |