QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskShortcutMap.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskShortcutMap.h"
7#include "QskMetaInvokable.h"
8#include "QskQuick.h"
9
10#include <qkeysequence.h>
11#include <qquickitem.h>
12
13QSK_QT_PRIVATE_BEGIN
14#include <QtGui/private/qguiapplication_p.h>
15QSK_QT_PRIVATE_END
16
17#include <map>
18
19static inline QShortcutMap* qskShortcutMap()
20{
21 return qGuiApp ? &QGuiApplicationPrivate::instance()->shortcutMap : nullptr;
22}
23
24class QskShortcutHandler final : public QObject
25{
26 public:
27 QskShortcutHandler();
28
29 int insert( QQuickItem*, const QKeySequence&, bool autoRepeat,
30 const QObject*, const QskMetaInvokable& );
31
32 void remove( int id );
33
34 void setEnabled( int id, bool );
35 void setEnabled( const QKeySequence&, bool on );
36 void setAutoRepeat( int id, bool repeat );
37
38 bool eventFilter( QObject*, QEvent* ) override;
39 bool invoke( QQuickItem*, const QKeySequence& );
40
41 private:
42 void cleanUp( QObject* );
43
44 class InvokeData
45 {
46 public:
47 InvokeData()
48 : item( nullptr )
49 , receiver( nullptr )
50 {
51 }
52
53 void invoke() const
54 {
55 auto that = const_cast< InvokeData* >( this );
56 auto object = const_cast< QObject* >( receiver );
57
58 void* args[] = { nullptr };
59 that->invokable.invoke( object, args, Qt::AutoConnection );
60 }
61
62 QKeySequence sequence;
63 QQuickItem* item;
64 const QObject* receiver;
65 QskMetaInvokable invokable;
66 };
67
68 std::map< int, InvokeData > m_invokeDataMap;
69};
70
71Q_GLOBAL_STATIC( QskShortcutHandler, qskShortcutHandler )
72
73static bool qskContextMatcher( QObject* object, Qt::ShortcutContext context )
74{
75 if ( context == Qt::ApplicationShortcut )
76 return true;
77
78 auto item = qobject_cast< QQuickItem* >( object );
79 if ( item && context == Qt::WindowShortcut )
80 {
81 if ( QskShortcutMap::contextMatcher( item, context ) )
82 {
83 /*
84 Unfortunatley there is no way to know about
85 the contextItem without making it the receiver of
86 the following QShortcutEvent. So we have to install
87 an event handler to process and swallow it in QskShortcutHandler.
88 */
89
90 item->installEventFilter( qskShortcutHandler );
91 return true;
92 }
93 }
94
95 return false;
96}
97
98QskShortcutHandler::QskShortcutHandler()
99{
100 // to process all sort of shortcut events at the same place
101 installEventFilter( this );
102}
103
104int QskShortcutHandler::insert(
105 QQuickItem* item, const QKeySequence& sequence, bool autoRepeat,
106 const QObject* receiver, const QskMetaInvokable& invokable )
107{
108 if ( sequence.isEmpty() )
109 {
110 qDebug() << "QskShortcutMap: invalid shortcut key sequence";
111 return 0;
112 }
113
114 if ( invokable.parameterCount() > 0 )
115 {
116 qDebug() << "QskShortcutMap: invalid slot parameter count";
117 return 0;
118 }
119
120 if ( receiver )
121 {
122 connect( receiver, &QObject::destroyed,
123 this, &QskShortcutHandler::cleanUp, Qt::UniqueConnection );
124 }
125
126 int id = 0;
127
128 auto map = qskShortcutMap();
129
130 if ( item )
131 {
132 if ( item != receiver )
133 {
134 connect( item, &QObject::destroyed,
135 this, &QskShortcutHandler::cleanUp, Qt::UniqueConnection );
136 }
137
138 id = map->addShortcut( item, sequence, Qt::WindowShortcut, qskContextMatcher );
139 }
140 else
141 {
142 id = map->addShortcut( this, sequence, Qt::ApplicationShortcut, qskContextMatcher );
143 }
144
145 auto& data = m_invokeDataMap[ id ];
146
147 data.sequence = sequence;
148 data.item = item;
149 data.receiver = receiver;
150 data.invokable = invokable;
151
152 if ( !autoRepeat )
153 setAutoRepeat( id, false );
154
155 return id;
156}
157
158void QskShortcutHandler::remove( int id )
159{
160 auto it = m_invokeDataMap.find( id );
161 if ( it == m_invokeDataMap.end() )
162 return;
163
164 auto map = qskShortcutMap();
165 map->removeShortcut( id, nullptr );
166
167 const QQuickItem* item = it->second.item;
168 const QObject* receiver = it->second.receiver;
169
170 m_invokeDataMap.erase( it );
171
172 /*
173 Finally let's check if we can disconnect
174 from the destroyed signals
175 */
176 for ( const auto& entry : std::as_const( m_invokeDataMap ) )
177 {
178 if ( item == nullptr && receiver == nullptr )
179 break;
180
181 if ( entry.second.item == item )
182 item = nullptr;
183
184 if ( entry.second.receiver == receiver )
185 receiver = nullptr;
186 }
187
188 if ( item )
189 item->disconnect( this );
190
191 if ( receiver && receiver != item )
192 receiver->disconnect( this );
193}
194
195void QskShortcutHandler::cleanUp( QObject* object )
196{
197 /*
198 When item != receiver we might remain being connected
199 to destroyed signals we are not interested in anymore. TODO ...
200 */
201 auto map = qskShortcutMap();
202
203 for ( auto it = m_invokeDataMap.begin(); it != m_invokeDataMap.end(); )
204 {
205 const auto& data = it->second;
206
207 if ( data.item == object || data.receiver == object )
208 {
209 if ( map )
210 map->removeShortcut( it->first, nullptr );
211
212 it = m_invokeDataMap.erase( it );
213
214 continue;
215 }
216
217 ++it;
218 }
219}
220
221void QskShortcutHandler::setEnabled( const QKeySequence& sequence, bool on )
222{
223 for ( auto it = m_invokeDataMap.begin();
224 it != m_invokeDataMap.end(); ++it )
225 {
226 if ( it->second.sequence == sequence )
227 setEnabled( it->first, on );
228 }
229}
230
231void QskShortcutHandler::setEnabled( int id, bool enabled )
232{
233 auto map = qskShortcutMap();
234 map->setShortcutEnabled( enabled, id, this );
235}
236
237void QskShortcutHandler::setAutoRepeat( int id, bool repeat )
238{
239 auto map = qskShortcutMap();
240 map->setShortcutAutoRepeat( repeat, id, this );
241}
242
243bool QskShortcutHandler::eventFilter( QObject* object, QEvent* event )
244{
245 if ( event->type() != QEvent::Shortcut )
246 return false;
247
248 if ( object != this )
249 object->removeEventFilter( this );
250
251 const QShortcutEvent* se = static_cast< const QShortcutEvent* >( event );
252
253#if 0
254 // do we want to handle this ???
255 if ( se->isAmbiguous() )
256 ....
257#endif
258
259 const auto it = m_invokeDataMap.find( se->shortcutId() );
260 if ( it != m_invokeDataMap.end() )
261 {
262 auto& data = it->second;
263
264 Q_ASSERT( data.item == nullptr || data.item == object );
265
266 data.invoke();
267
268 return true;
269 }
270
271 // seems like someone else is also interested in shortcuts
272 return false;
273}
274
275bool QskShortcutHandler::invoke( QQuickItem* item, const QKeySequence& sequence )
276{
277 bool found = false;
278
279 for ( const auto& entry : std::as_const( m_invokeDataMap ) )
280 {
281 auto& data = entry.second;
282
283 if ( ( data.item == item ) && ( data.sequence == sequence ) )
284 {
285 data.invoke();
286 found = true;
287 }
288 }
289
290 return found;
291}
292
293int QskShortcutMap::addMethod( QQuickItem* item, const QKeySequence& sequence,
294 bool autoRepeat, const QObject* receiver, const char* method )
295{
296 if ( receiver == nullptr )
297 {
298 qDebug() << "QskShortcutMap: bad receiver for shortcut:" << sequence;
299 return 0;
300 }
301
302 return qskShortcutHandler->insert(
303 item, sequence, autoRepeat, receiver, qskMetaMethod( receiver, method ) );
304}
305
306int QskShortcutMap::addFunction( QQuickItem* item, const QKeySequence& sequence,
307 bool autoRepeat, const QObject* receiver, const QskMetaFunction& function )
308{
309 if ( ( receiver == nullptr ) &&
310 ( function.functionType() == QskMetaFunction::MemberFunction ) )
311 {
312 qDebug() << "QskShortcutMap: bad receiver for shortcut:" << sequence;
313 return 0;
314 }
315
316 return qskShortcutHandler->insert(
317 item, sequence, autoRepeat, receiver, function );
318}
319
320bool QskShortcutMap::invokeCallback( const QKeySequence& sequence )
321{
322 QQuickItem* item = nullptr;
323 return qskShortcutHandler->invoke( item, sequence );
324}
325
326bool QskShortcutMap::invokeCallback( QQuickWindow* window, const QKeySequence& sequence )
327{
328 auto item = window ? window->contentItem() : nullptr;
329 return qskShortcutHandler->invoke( item, sequence );
330}
331
332bool QskShortcutMap::invokeCallback( QQuickItem* item, const QKeySequence& sequence )
333{
334 return qskShortcutHandler->invoke( item, sequence );
335}
336
337void QskShortcutMap::setAutoRepeat( int id, bool on )
338{
339 qskShortcutHandler->setAutoRepeat( id, on );
340}
341
342void QskShortcutMap::setEnabled( const QKeySequence& sequence, bool on )
343{
344 qskShortcutHandler->setEnabled( sequence, on );
345}
346
347void QskShortcutMap::setEnabled( int id, bool on )
348{
349 qskShortcutHandler->setEnabled( id, on );
350}
351
352void QskShortcutMap::removeShortcut( int id )
353{
354 qskShortcutHandler->remove( id );
355}
356
357bool QskShortcutMap::contextMatcher(
358 const QQuickItem* item, Qt::ShortcutContext context )
359{
360 if ( context == Qt::ApplicationShortcut )
361 return true;
362
363 if ( item && context == Qt::WindowShortcut )
364 {
365 const auto focusWindow = QGuiApplication::focusWindow();
366
367 const auto window = item->window();
368 if ( window == nullptr || window != focusWindow )
369 {
370 return false;
371 }
372
373 while ( item )
374 {
375 /*
376 We have to find out if the active focus is inside
377 the surronding shortcut scope.
378 */
379 if ( qskIsShortcutScope( item ) )
380 {
381 if ( !item->hasFocus() )
382 return false;
383 }
384
385 item = item->parentItem();
386 }
387
388 return true;
389 }
390
391 return false;
392}