2017-12-06 17:01:10 +01:00
|
|
|
/******************************************************************************
|
|
|
|
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
|
|
|
* This file may be used under the terms of the QSkinny License, Version 1.0
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
#include "QskShortcutMap.h"
|
2018-03-12 09:27:54 +01:00
|
|
|
#include "QskMetaInvokable.h"
|
2018-08-03 08:15:28 +02:00
|
|
|
#include "QskQuick.h"
|
2017-12-06 17:01:10 +01:00
|
|
|
|
2018-07-19 14:10:48 +02:00
|
|
|
#include <qkeysequence.h>
|
2018-08-03 08:15:28 +02:00
|
|
|
#include <qquickitem.h>
|
2018-07-19 14:10:48 +02:00
|
|
|
|
|
|
|
QSK_QT_PRIVATE_BEGIN
|
2017-12-06 17:01:10 +01:00
|
|
|
#include <QtGui/private/qguiapplication_p.h>
|
2018-07-19 14:10:48 +02:00
|
|
|
QSK_QT_PRIVATE_END
|
2017-12-06 17:01:10 +01:00
|
|
|
|
|
|
|
#include <map>
|
|
|
|
|
2017-12-07 11:53:34 +01:00
|
|
|
static inline QShortcutMap* qskShortcutMap()
|
2017-12-06 17:01:10 +01:00
|
|
|
{
|
2017-12-07 11:53:34 +01:00
|
|
|
return qGuiApp ? &QGuiApplicationPrivate::instance()->shortcutMap : nullptr;
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class QskShortcutHandler final : public QObject
|
|
|
|
{
|
2018-08-03 08:15:28 +02:00
|
|
|
public:
|
2017-12-06 17:01:10 +01:00
|
|
|
QskShortcutHandler();
|
|
|
|
|
2018-03-12 09:27:54 +01:00
|
|
|
int insert( QQuickItem*, const QKeySequence&, bool autoRepeat,
|
|
|
|
const QObject*, const QskMetaInvokable& );
|
2017-12-06 17:01:10 +01:00
|
|
|
|
|
|
|
void remove( int id );
|
|
|
|
|
|
|
|
void setEnabled( int id, bool );
|
|
|
|
void setAutoRepeat( int id, bool repeat );
|
|
|
|
|
2018-07-31 17:32:25 +02:00
|
|
|
bool eventFilter( QObject*, QEvent* ) override;
|
2017-12-06 17:01:10 +01:00
|
|
|
|
2018-08-03 08:15:28 +02:00
|
|
|
private:
|
2017-12-06 17:01:10 +01:00
|
|
|
void cleanUp( QObject* );
|
|
|
|
|
|
|
|
class InvokeData
|
|
|
|
{
|
2018-08-03 08:15:28 +02:00
|
|
|
public:
|
|
|
|
InvokeData()
|
|
|
|
: item( nullptr )
|
|
|
|
, receiver( nullptr )
|
2017-12-06 17:01:10 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
QQuickItem* item;
|
|
|
|
const QObject* receiver;
|
2018-03-12 09:27:54 +01:00
|
|
|
QskMetaInvokable invokable;
|
2017-12-06 17:01:10 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
std::map< int, InvokeData > m_invokeDataMap;
|
|
|
|
};
|
|
|
|
|
|
|
|
Q_GLOBAL_STATIC( QskShortcutHandler, qskShortcutHandler )
|
|
|
|
|
|
|
|
static bool qskContextMatcher( QObject* object, Qt::ShortcutContext context )
|
|
|
|
{
|
|
|
|
if ( context == Qt::ApplicationShortcut )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
auto item = qobject_cast< QQuickItem* >( object );
|
|
|
|
if ( item && context == Qt::WindowShortcut )
|
|
|
|
{
|
|
|
|
if ( QskShortcutMap::contextMatcher( item, context ) )
|
|
|
|
{
|
|
|
|
/*
|
2018-12-19 10:01:26 +01:00
|
|
|
Unfortunatley there is no way to know about
|
2018-03-12 09:27:54 +01:00
|
|
|
the contextItem without making it the receiver of
|
2017-12-06 17:01:10 +01:00
|
|
|
the following QShortcutEvent. So we have to install
|
|
|
|
an event handler to process and swallow it in QskShortcutHandler.
|
|
|
|
*/
|
|
|
|
|
|
|
|
item->installEventFilter( qskShortcutHandler );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QskShortcutHandler::QskShortcutHandler()
|
|
|
|
{
|
|
|
|
// to process all sort of shortcut events at the same place
|
|
|
|
installEventFilter( this );
|
|
|
|
}
|
|
|
|
|
2018-03-12 09:27:54 +01:00
|
|
|
int QskShortcutHandler::insert(
|
|
|
|
QQuickItem* item, const QKeySequence& sequence, bool autoRepeat,
|
|
|
|
const QObject* receiver, const QskMetaInvokable& invokable )
|
2017-12-06 17:01:10 +01:00
|
|
|
{
|
2018-03-12 09:27:54 +01:00
|
|
|
if ( sequence.isEmpty() )
|
2017-12-06 17:01:10 +01:00
|
|
|
{
|
2018-03-12 09:27:54 +01:00
|
|
|
qDebug() << "QskShortcutMap: invalid shortcut key sequence";
|
|
|
|
return 0;
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
|
2018-03-12 09:27:54 +01:00
|
|
|
if ( invokable.parameterCount() > 0 )
|
|
|
|
{
|
|
|
|
qDebug() << "QskShortcutMap: invalid slot parameter count";
|
|
|
|
return 0;
|
|
|
|
}
|
2017-12-06 17:01:10 +01:00
|
|
|
|
|
|
|
if ( receiver )
|
|
|
|
{
|
2018-02-15 09:48:14 +01:00
|
|
|
connect( receiver, &QObject::destroyed,
|
|
|
|
this, &QskShortcutHandler::cleanUp, Qt::UniqueConnection );
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int id = 0;
|
|
|
|
|
2017-12-07 11:53:34 +01:00
|
|
|
auto map = qskShortcutMap();
|
2017-12-06 17:01:10 +01:00
|
|
|
|
|
|
|
if ( item )
|
|
|
|
{
|
|
|
|
if ( item != receiver )
|
|
|
|
{
|
2018-02-15 09:48:14 +01:00
|
|
|
connect( item, &QObject::destroyed,
|
|
|
|
this, &QskShortcutHandler::cleanUp, Qt::UniqueConnection );
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
|
2017-12-07 11:53:34 +01:00
|
|
|
id = map->addShortcut( item, sequence, Qt::WindowShortcut, qskContextMatcher );
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-12-07 11:53:34 +01:00
|
|
|
id = map->addShortcut( this, sequence, Qt::ApplicationShortcut, qskContextMatcher );
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
auto& data = m_invokeDataMap[ id ];
|
|
|
|
|
|
|
|
data.item = item;
|
|
|
|
data.receiver = receiver;
|
2018-03-12 09:27:54 +01:00
|
|
|
data.invokable = invokable;
|
2017-12-06 17:01:10 +01:00
|
|
|
|
2018-03-12 09:27:54 +01:00
|
|
|
if ( !autoRepeat )
|
|
|
|
setAutoRepeat( id, false );
|
2017-12-06 17:01:10 +01:00
|
|
|
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskShortcutHandler::remove( int id )
|
|
|
|
{
|
|
|
|
auto it = m_invokeDataMap.find( id );
|
|
|
|
if ( it == m_invokeDataMap.end() )
|
|
|
|
return;
|
|
|
|
|
2017-12-07 11:53:34 +01:00
|
|
|
auto map = qskShortcutMap();
|
|
|
|
map->removeShortcut( id, nullptr );
|
2017-12-06 17:01:10 +01:00
|
|
|
|
|
|
|
const QQuickItem* item = it->second.item;
|
|
|
|
const QObject* receiver = it->second.receiver;
|
|
|
|
|
|
|
|
m_invokeDataMap.erase( it );
|
|
|
|
|
|
|
|
/*
|
|
|
|
Finally let's check if we can disconnect
|
|
|
|
from the destroyed signals
|
|
|
|
*/
|
|
|
|
for ( const auto& entry : qskAsConst( m_invokeDataMap ) )
|
|
|
|
{
|
|
|
|
if ( item == nullptr && receiver == nullptr )
|
|
|
|
break;
|
|
|
|
|
|
|
|
if ( entry.second.item == item )
|
|
|
|
item = nullptr;
|
|
|
|
|
|
|
|
if ( entry.second.receiver == receiver )
|
|
|
|
receiver = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( item )
|
|
|
|
item->disconnect( this );
|
|
|
|
|
|
|
|
if ( receiver && receiver != item )
|
|
|
|
receiver->disconnect( this );
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskShortcutHandler::cleanUp( QObject* object )
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
When item != receiver we might remain being connected
|
|
|
|
to destroyed signals we are not interested in anymore. TODO ...
|
|
|
|
*/
|
2017-12-07 11:53:34 +01:00
|
|
|
auto map = qskShortcutMap();
|
2017-12-06 17:01:10 +01:00
|
|
|
|
|
|
|
for ( auto it = m_invokeDataMap.begin(); it != m_invokeDataMap.end(); )
|
|
|
|
{
|
|
|
|
const auto& data = it->second;
|
|
|
|
|
|
|
|
if ( data.item == object || data.receiver == object )
|
|
|
|
{
|
2017-12-07 11:53:34 +01:00
|
|
|
if ( map )
|
|
|
|
map->removeShortcut( it->first, nullptr );
|
|
|
|
|
2017-12-06 17:01:10 +01:00
|
|
|
it = m_invokeDataMap.erase( it );
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskShortcutHandler::setEnabled( int id, bool enabled )
|
|
|
|
{
|
2017-12-07 11:53:34 +01:00
|
|
|
auto map = qskShortcutMap();
|
|
|
|
map->setShortcutEnabled( enabled, id, this );
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void QskShortcutHandler::setAutoRepeat( int id, bool repeat )
|
|
|
|
{
|
2017-12-07 11:53:34 +01:00
|
|
|
auto map = qskShortcutMap();
|
|
|
|
map->setShortcutAutoRepeat( repeat, id, this );
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool QskShortcutHandler::eventFilter( QObject* object, QEvent* event )
|
|
|
|
{
|
|
|
|
if ( event->type() != QEvent::Shortcut )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if ( object != this )
|
|
|
|
object->removeEventFilter( this );
|
|
|
|
|
|
|
|
const QShortcutEvent* se = static_cast< const QShortcutEvent* >( event );
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
// do we want to handle this ???
|
|
|
|
if ( se->isAmbiguous() )
|
|
|
|
....
|
|
|
|
#endif
|
|
|
|
|
|
|
|
const auto it = m_invokeDataMap.find( se->shortcutId() );
|
|
|
|
if ( it != m_invokeDataMap.end() )
|
|
|
|
{
|
2018-03-12 09:27:54 +01:00
|
|
|
auto& data = it->second;
|
2017-12-06 17:01:10 +01:00
|
|
|
|
2018-03-12 09:27:54 +01:00
|
|
|
Q_ASSERT( data.item == nullptr || data.item == object );
|
2017-12-06 17:01:10 +01:00
|
|
|
|
2018-08-03 08:15:28 +02:00
|
|
|
auto receiver = const_cast< QObject* >( data.receiver );
|
2018-03-12 09:27:54 +01:00
|
|
|
void* args[] = { nullptr };
|
2017-12-06 17:01:10 +01:00
|
|
|
|
2018-03-12 09:27:54 +01:00
|
|
|
data.invokable.invoke( receiver, args, Qt::AutoConnection );
|
2017-12-06 17:01:10 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// seems like someone else is also interested in shortcuts
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int QskShortcutMap::addMethod( QQuickItem* item, const QKeySequence& sequence,
|
|
|
|
bool autoRepeat, const QObject* receiver, const char* method )
|
|
|
|
{
|
|
|
|
if ( receiver == nullptr )
|
|
|
|
{
|
2018-03-12 09:27:54 +01:00
|
|
|
qDebug() << "QskShortcutMap: bad receiver for shortcut:" << sequence;
|
2017-12-06 17:01:10 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-03-12 09:27:54 +01:00
|
|
|
return qskShortcutHandler->insert(
|
|
|
|
item, sequence, autoRepeat, receiver, qskMetaMethod( receiver, method ) );
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
|
2018-03-12 09:27:54 +01:00
|
|
|
int QskShortcutMap::addFunction( QQuickItem* item, const QKeySequence& sequence,
|
|
|
|
bool autoRepeat, const QObject* receiver, const QskMetaFunction& function )
|
2017-12-06 17:01:10 +01:00
|
|
|
{
|
2018-08-03 08:15:28 +02:00
|
|
|
if ( ( receiver == nullptr ) &&
|
|
|
|
( function.functionType() == QskMetaFunction::MemberFunction ) )
|
2018-03-12 09:27:54 +01:00
|
|
|
{
|
|
|
|
qDebug() << "QskShortcutMap: bad receiver for shortcut:" << sequence;
|
|
|
|
return 0;
|
|
|
|
}
|
2017-12-06 17:01:10 +01:00
|
|
|
|
2018-03-12 09:27:54 +01:00
|
|
|
return qskShortcutHandler->insert(
|
|
|
|
item, sequence, autoRepeat, receiver, function );
|
2017-12-06 17:01:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void QskShortcutMap::setAutoRepeat( int id, bool on )
|
|
|
|
{
|
|
|
|
qskShortcutHandler->setAutoRepeat( id, on );
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskShortcutMap::setEnabled( int id, bool on )
|
|
|
|
{
|
|
|
|
qskShortcutHandler->setEnabled( id, on );
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskShortcutMap::removeShortcut( int id )
|
|
|
|
{
|
|
|
|
qskShortcutHandler->remove( id );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QskShortcutMap::contextMatcher(
|
|
|
|
const QQuickItem* item, Qt::ShortcutContext context )
|
|
|
|
{
|
|
|
|
if ( context == Qt::ApplicationShortcut )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if ( item && context == Qt::WindowShortcut )
|
|
|
|
{
|
|
|
|
const auto focusWindow = QGuiApplication::focusWindow();
|
|
|
|
|
|
|
|
const auto window = item->window();
|
|
|
|
if ( window == nullptr || window != focusWindow )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ( item )
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
We have to find out if the active focus is inside
|
|
|
|
the surronding shortcut scope.
|
|
|
|
*/
|
2018-01-19 10:07:05 +01:00
|
|
|
if ( qskIsShortcutScope( item ) )
|
2017-12-06 17:01:10 +01:00
|
|
|
{
|
|
|
|
if ( !item->hasFocus() )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
item = item->parentItem();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|