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 "qshortcut.h"
5#include "private/qshortcut_p.h"
6
7#include "private/qwidget_p.h"
8
9#include <qevent.h>
10#if QT_CONFIG(whatsthis)
11#include <qwhatsthis.h>
12#endif
13#if QT_CONFIG(menu)
14#include <qmenu.h>
15#endif
16#if QT_CONFIG(menubar)
17#include <qmenubar.h>
18#endif
19#include <qapplication.h>
20#include <private/qapplication_p.h>
21#include <private/qshortcutmap_p.h>
22#if QT_CONFIG(action)
23# include <private/qaction_p.h>
24#endif
25#include <private/qwidgetwindow_p.h>
26#include <qpa/qplatformmenu.h>
27
28QT_BEGIN_NAMESPACE
29
30static bool correctWidgetContext(Qt::ShortcutContext context, QWidget *w, QWidget *active_window);
31#if QT_CONFIG(graphicsview)
32static bool correctGraphicsWidgetContext(Qt::ShortcutContext context, QGraphicsWidget *w, QWidget *active_window);
33#endif
34#if QT_CONFIG(action)
35static bool correctActionContext(Qt::ShortcutContext context, QAction *a, QWidget *active_window);
36#endif
37
38
39/*! \internal
40 Returns \c true if the widget \a w is a logical sub window of the current
41 top-level widget.
42*/
43bool qWidgetShortcutContextMatcher(QObject *object, Qt::ShortcutContext context)
44{
45 Q_ASSERT_X(object, "QShortcutMap", "Shortcut has no owner. Illegal map state!");
46
47 QWidget *active_window = QApplication::activeWindow();
48
49 // popups do not become the active window,
50 // so we fake it here to get the correct context
51 // for the shortcut system.
52 if (QApplication::activePopupWidget())
53 active_window = QApplication::activePopupWidget();
54
55 if (!active_window) {
56 QWindow *qwindow = QGuiApplication::focusWindow();
57 if (qwindow && qwindow->isActive()) {
58 while (qwindow) {
59 if (auto widgetWindow = qobject_cast<QWidgetWindow *>(object: qwindow)) {
60 active_window = widgetWindow->widget();
61 break;
62 }
63 qwindow = qwindow->parent();
64 }
65 }
66 }
67
68 if (!active_window)
69 return false;
70
71#if QT_CONFIG(action)
72 if (auto a = qobject_cast<QAction *>(object))
73 return correctActionContext(context, a, active_window);
74#endif
75
76#if QT_CONFIG(graphicsview)
77 if (auto gw = qobject_cast<QGraphicsWidget *>(object))
78 return correctGraphicsWidgetContext(context, w: gw, active_window);
79#endif
80
81 auto w = qobject_cast<QWidget *>(o: object);
82 if (!w) {
83 if (auto s = qobject_cast<QShortcut *>(object))
84 w = qobject_cast<QWidget *>(o: s->parent());
85 }
86
87 if (!w) {
88 auto qwindow = qobject_cast<QWindow *>(o: object);
89 while (qwindow) {
90 if (auto widget_window = qobject_cast<QWidgetWindow *>(object: qwindow)) {
91 w = widget_window->widget();
92 break;
93 }
94 qwindow = qwindow->parent();
95 }
96 }
97
98 if (!w)
99 return false;
100
101 return correctWidgetContext(context, w, active_window);
102}
103
104static bool correctWidgetContext(Qt::ShortcutContext context, QWidget *w, QWidget *active_window)
105{
106 bool visible = w->isVisible();
107#if QT_CONFIG(menubar)
108 if (auto menuBar = qobject_cast<QMenuBar *>(object: w)) {
109 if (auto *pmb = menuBar->platformMenuBar()) {
110 if (menuBar->parentWidget()) {
111 visible = true;
112 } else {
113 if (auto *ww = qobject_cast<QWidgetWindow *>(object: pmb->parentWindow()))
114 w = ww->widget(); // Good enough since we only care about the window
115 else
116 return false; // This is not a QWidget window. We won't deliver
117 }
118 }
119 }
120#endif
121
122 if (!visible || !w->isEnabled())
123 return false;
124
125 if (context == Qt::ApplicationShortcut)
126 return QApplicationPrivate::tryModalHelper(widget: w, rettop: nullptr); // true, unless w is shadowed by a modal dialog
127
128 if (context == Qt::WidgetShortcut)
129 return w == QApplication::focusWidget();
130
131 if (context == Qt::WidgetWithChildrenShortcut) {
132 const QWidget *tw = QApplication::focusWidget();
133 while (tw && tw != w && (tw->windowType() == Qt::Widget || tw->windowType() == Qt::Popup || tw->windowType() == Qt::SubWindow))
134 tw = tw->parentWidget();
135 return tw == w;
136 }
137
138 // Below is Qt::WindowShortcut context
139 QWidget *tlw = w->window();
140#if QT_CONFIG(graphicsview)
141 if (auto topData = static_cast<QWidgetPrivate *>(QObjectPrivate::get(o: tlw))->extra.get()) {
142 if (topData->proxyWidget) {
143 bool res = correctGraphicsWidgetContext(context, w: topData->proxyWidget, active_window);
144 return res;
145 }
146 }
147#endif
148
149 if (active_window && active_window != tlw) {
150 /* if a floating tool window is active, keep shortcuts on the parent working.
151 * and if a popup window is active (f.ex a completer), keep shortcuts on the
152 * focus proxy working */
153 if (active_window->windowType() == Qt::Tool && active_window->parentWidget()) {
154 active_window = active_window->parentWidget()->window();
155 } else if (active_window->windowType() == Qt::Popup && active_window->focusProxy()) {
156 active_window = active_window->focusProxy()->window();
157 }
158 }
159
160 if (active_window != tlw) {
161#if QT_CONFIG(menubar)
162 // If the tlw is a QMenuBar then we allow it to proceed as this indicates that
163 // the QMenuBar is a parentless one and is therefore used for multiple top level
164 // windows in the application. This is common on macOS platforms for example.
165 if (!qobject_cast<QMenuBar *>(object: tlw))
166#endif
167 return false;
168 }
169
170 /* if we live in a MDI subwindow, ignore the event if we are
171 not the active document window */
172 const QWidget* sw = w;
173 while (sw && !(sw->windowType() == Qt::SubWindow) && !sw->isWindow())
174 sw = sw->parentWidget();
175 if (sw && (sw->windowType() == Qt::SubWindow)) {
176 QWidget *focus_widget = QApplication::focusWidget();
177 while (focus_widget && focus_widget != sw)
178 focus_widget = focus_widget->parentWidget();
179 return sw == focus_widget;
180 }
181
182#if defined(DEBUG_QSHORTCUTMAP)
183 qDebug().nospace() << "..true [Pass-through]";
184#endif
185 return QApplicationPrivate::tryModalHelper(widget: w, rettop: nullptr);
186}
187
188#if QT_CONFIG(graphicsview)
189static bool correctGraphicsWidgetContext(Qt::ShortcutContext context, QGraphicsWidget *w, QWidget *active_window)
190{
191 bool visible = w->isVisible();
192#if defined(Q_OS_DARWIN) && QT_CONFIG(menubar)
193 if (!QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuBar) && qobject_cast<QMenuBar *>(w))
194 visible = true;
195#endif
196
197 if (!visible || !w->isEnabled() || !w->scene())
198 return false;
199
200 if (context == Qt::ApplicationShortcut) {
201 // Applicationwide shortcuts are always reachable unless their owner
202 // is shadowed by modality. In QGV there's no modality concept, but we
203 // must still check if all views are shadowed.
204 const auto &views = w->scene()->views();
205 for (auto view : views) {
206 if (QApplicationPrivate::tryModalHelper(widget: view, rettop: nullptr))
207 return true;
208 }
209 return false;
210 }
211
212 if (context == Qt::WidgetShortcut)
213 return static_cast<QGraphicsItem *>(w) == w->scene()->focusItem();
214
215 if (context == Qt::WidgetWithChildrenShortcut) {
216 const QGraphicsItem *ti = w->scene()->focusItem();
217 if (ti && ti->isWidget()) {
218 const auto *tw = static_cast<const QGraphicsWidget *>(ti);
219 while (tw && tw != w && (tw->windowType() == Qt::Widget || tw->windowType() == Qt::Popup))
220 tw = tw->parentWidget();
221 return tw == w;
222 }
223 return false;
224 }
225
226 // Below is Qt::WindowShortcut context
227
228 // Find the active view (if any).
229 const auto &views = w->scene()->views();
230 QGraphicsView *activeView = nullptr;
231 for (auto view : views) {
232 if (view->window() == active_window) {
233 activeView = view;
234 break;
235 }
236 }
237 if (!activeView)
238 return false;
239
240 // The shortcut is reachable if owned by a windowless widget, or if the
241 // widget's window is the same as the focus item's window.
242 QGraphicsWidget *a = w->scene()->activeWindow();
243 return !w->window() || a == w->window();
244}
245#endif
246
247#if QT_CONFIG(action)
248static bool correctActionContext(Qt::ShortcutContext context, QAction *a, QWidget *active_window)
249{
250 const QObjectList associatedObjects = a->associatedObjects();
251#if defined(DEBUG_QSHORTCUTMAP)
252 if (associatedObjects.isEmpty())
253 qDebug() << a << "not connected to any widgets; won't trigger";
254#endif
255 for (auto object : associatedObjects) {
256#if QT_CONFIG(menu)
257 if (auto menu = qobject_cast<QMenu *>(object)) {
258#ifdef Q_OS_DARWIN
259 // On Mac, menu item shortcuts are processed before reaching any window.
260 // That means that if a menu action shortcut has not been already processed
261 // (and reaches this point), then the menu item itself has been disabled.
262 // This occurs at the QPA level on Mac, where we disable all the Cocoa menus
263 // when showing a modal window. (Notice that only the QPA menu is disabled,
264 // not the QMenu.) Since we can also reach this code by climbing the menu
265 // hierarchy (see below), or when the shortcut is not a key-equivalent, we
266 // need to check whether the QPA menu is actually disabled.
267 // When there is no QPA menu, there will be no QCocoaMenuDelegate checking
268 // for the actual shortcuts. We can then fallback to our own logic.
269 QPlatformMenu *pm = menu->platformMenu();
270 if (pm && !pm->isEnabled())
271 continue;
272#endif
273 QAction *a = menu->menuAction();
274 if (a->isVisible() && a->isEnabled() && correctActionContext(context, a, active_window))
275 return true;
276 } else
277#endif
278 if (auto widget = qobject_cast<QWidget*>(o: object)) {
279 if (correctWidgetContext(context, w: widget, active_window))
280 return true;
281 }
282#if QT_CONFIG(graphicsview)
283 else if (auto graphicsWidget = qobject_cast<QGraphicsWidget*>(object)) {
284 if (correctGraphicsWidgetContext(context, w: graphicsWidget, active_window))
285 return true;
286 }
287#endif
288 }
289
290 return false;
291}
292#endif // QT_CONFIG(action)
293
294class QtWidgetsShortcutPrivate : public QShortcutPrivate
295{
296 Q_DECLARE_PUBLIC(QShortcut)
297public:
298 QtWidgetsShortcutPrivate() = default;
299
300 QShortcutMap::ContextMatcher contextMatcher() const override
301 { return qWidgetShortcutContextMatcher; }
302
303 bool handleWhatsThis() override;
304};
305
306bool QtWidgetsShortcutPrivate::handleWhatsThis()
307{
308#if QT_CONFIG(whatsthis)
309 if (QWhatsThis::inWhatsThisMode()) {
310 QWhatsThis::showText(pos: QCursor::pos(), text: sc_whatsthis);
311 return true;
312 }
313#endif
314 return false;
315}
316
317QShortcutPrivate *QApplicationPrivate::createShortcutPrivate() const
318{
319 return new QtWidgetsShortcutPrivate;
320}
321
322QT_END_NAMESPACE
323

source code of qtbase/src/widgets/kernel/qshortcut_widgets.cpp