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 QT_CONFIG(action)
69 if (auto a = qobject_cast<QAction *>(object))
70 return correctActionContext(context, a, active_window);
71#endif
72
73#if QT_CONFIG(graphicsview)
74 if (auto gw = qobject_cast<QGraphicsWidget *>(object))
75 return correctGraphicsWidgetContext(context, w: gw, active_window);
76#endif
77
78 auto w = qobject_cast<QWidget *>(o: object);
79 if (!w) {
80 if (auto s = qobject_cast<QShortcut *>(object))
81 w = qobject_cast<QWidget *>(o: s->parent());
82 }
83
84 if (!w) {
85 auto qwindow = qobject_cast<QWindow *>(o: object);
86 while (qwindow) {
87 if (auto widget_window = qobject_cast<QWidgetWindow *>(object: qwindow)) {
88 w = widget_window->widget();
89 break;
90 }
91 qwindow = qwindow->parent();
92 }
93 }
94
95 if (w)
96 return correctWidgetContext(context, w, active_window);
97
98 return QShortcutPrivate::simpleContextMatcher(object, context);
99}
100
101static bool correctWidgetContext(Qt::ShortcutContext context, QWidget *w, QWidget *active_window)
102{
103 if (!active_window)
104 return false;
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 if (!active_window)
192 return false;
193
194 bool visible = w->isVisible();
195#if defined(Q_OS_DARWIN) && QT_CONFIG(menubar)
196 if (!QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuBar) && qobject_cast<QMenuBar *>(w))
197 visible = true;
198#endif
199
200 if (!visible || !w->isEnabled() || !w->scene())
201 return false;
202
203 if (context == Qt::ApplicationShortcut) {
204 // Applicationwide shortcuts are always reachable unless their owner
205 // is shadowed by modality. In QGV there's no modality concept, but we
206 // must still check if all views are shadowed.
207 const auto &views = w->scene()->views();
208 for (auto view : views) {
209 if (QApplicationPrivate::tryModalHelper(widget: view, rettop: nullptr))
210 return true;
211 }
212 return false;
213 }
214
215 if (context == Qt::WidgetShortcut)
216 return static_cast<QGraphicsItem *>(w) == w->scene()->focusItem();
217
218 if (context == Qt::WidgetWithChildrenShortcut) {
219 const QGraphicsItem *ti = w->scene()->focusItem();
220 if (ti && ti->isWidget()) {
221 const auto *tw = static_cast<const QGraphicsWidget *>(ti);
222 while (tw && tw != w && (tw->windowType() == Qt::Widget || tw->windowType() == Qt::Popup))
223 tw = tw->parentWidget();
224 return tw == w;
225 }
226 return false;
227 }
228
229 // Below is Qt::WindowShortcut context
230
231 // Find the active view (if any).
232 const auto &views = w->scene()->views();
233 QGraphicsView *activeView = nullptr;
234 for (auto view : views) {
235 if (view->window() == active_window) {
236 activeView = view;
237 break;
238 }
239 }
240 if (!activeView)
241 return false;
242
243 // The shortcut is reachable if owned by a windowless widget, or if the
244 // widget's window is the same as the focus item's window.
245 QGraphicsWidget *a = w->scene()->activeWindow();
246 return !w->window() || a == w->window();
247}
248#endif
249
250#if QT_CONFIG(action)
251static bool correctActionContext(Qt::ShortcutContext context, QAction *a, QWidget *active_window)
252{
253 if (!active_window)
254 return false;
255
256 const QObjectList associatedObjects = a->associatedObjects();
257#if defined(DEBUG_QSHORTCUTMAP)
258 if (associatedObjects.isEmpty())
259 qDebug() << a << "not connected to any widgets; won't trigger";
260#endif
261 for (auto object : associatedObjects) {
262#if QT_CONFIG(menu)
263 if (auto menu = qobject_cast<QMenu *>(object)) {
264#ifdef Q_OS_DARWIN
265 // On Mac, menu item shortcuts are processed before reaching any window.
266 // That means that if a menu action shortcut has not been already processed
267 // (and reaches this point), then the menu item itself has been disabled.
268 // This occurs at the QPA level on Mac, where we disable all the Cocoa menus
269 // when showing a modal window. (Notice that only the QPA menu is disabled,
270 // not the QMenu.) Since we can also reach this code by climbing the menu
271 // hierarchy (see below), or when the shortcut is not a key-equivalent, we
272 // need to check whether the QPA menu is actually disabled.
273 // When there is no QPA menu, there will be no QCocoaMenuDelegate checking
274 // for the actual shortcuts. We can then fallback to our own logic.
275 QPlatformMenu *pm = menu->platformMenu();
276 if (pm && !pm->isEnabled())
277 continue;
278#endif
279 QAction *a = menu->menuAction();
280 if (a->isVisible() && a->isEnabled() && correctActionContext(context, a, active_window))
281 return true;
282 } else
283#endif
284 if (auto widget = qobject_cast<QWidget*>(o: object)) {
285 if (correctWidgetContext(context, w: widget, active_window))
286 return true;
287 }
288#if QT_CONFIG(graphicsview)
289 else if (auto graphicsWidget = qobject_cast<QGraphicsWidget*>(object)) {
290 if (correctGraphicsWidgetContext(context, w: graphicsWidget, active_window))
291 return true;
292 }
293#endif
294 }
295
296 return false;
297}
298#endif // QT_CONFIG(action)
299
300class QtWidgetsShortcutPrivate : public QShortcutPrivate
301{
302 Q_DECLARE_PUBLIC(QShortcut)
303public:
304 QtWidgetsShortcutPrivate() = default;
305
306 QShortcutMap::ContextMatcher contextMatcher() const override
307 { return qWidgetShortcutContextMatcher; }
308
309 bool handleWhatsThis() override;
310};
311
312bool QtWidgetsShortcutPrivate::handleWhatsThis()
313{
314#if QT_CONFIG(whatsthis)
315 if (QWhatsThis::inWhatsThisMode()) {
316 QWhatsThis::showText(pos: QCursor::pos(), text: sc_whatsthis);
317 return true;
318 }
319#endif
320 return false;
321}
322
323QShortcutPrivate *QApplicationPrivate::createShortcutPrivate() const
324{
325 return new QtWidgetsShortcutPrivate;
326}
327
328QT_END_NAMESPACE
329

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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