diff --git a/examples/automotive/main.cpp b/examples/automotive/main.cpp index e6a7a725..2a6606cb 100644 --- a/examples/automotive/main.cpp +++ b/examples/automotive/main.cpp @@ -1,7 +1,7 @@ #include "MainWindow.h" #include "SkinFactory.h" -#include +#include #include #include @@ -35,10 +35,10 @@ int main( int argc, char** argv ) cout << "CTRL-S to change the skin." << endl; cout << "CTRL-T to change the color scheme, when the \"Default\" skin is active." << endl; - QskShortcut::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_T ), + QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_T ), false, &skinFactory, SLOT(toggleScheme()) ); - QskShortcut::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_S ), + QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_S ), false, &skinFactory, SLOT(rotateSkin()) ); // With CTRL-B you can rotate a couple of visual debug modes diff --git a/examples/buttons/buttons.qml b/examples/buttons/buttons.qml index 4f01e504..ca77e321 100644 --- a/examples/buttons/buttons.qml +++ b/examples/buttons/buttons.qml @@ -12,6 +12,12 @@ Qsk.Window height: 600 color: "Beige" + Qsk.Shortcut + { + sequence : "Ctrl+X" + onActivated: console.log( "Ctrl+X" ) + } + Qsk.LinearBox { orientation: Qt.Horizontal diff --git a/examples/desktop/main.cpp b/examples/desktop/main.cpp index e5a8ce69..dd17c258 100644 --- a/examples/desktop/main.cpp +++ b/examples/desktop/main.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include @@ -34,7 +34,7 @@ public: setSizePolicy( QskSizePolicy::MinimumExpanding, QskSizePolicy::MinimumExpanding ); - QskShortcut::addShortcut( this, QKeySequence( Qt::Key_P ), true, + QskShortcutMap::addShortcut( this, QKeySequence( Qt::Key_P ), true, [=] { qDebug() << graphicSource; } ); } }; diff --git a/src/common/QskModule.cpp b/src/common/QskModule.cpp index 2bcc29c5..f7f23919 100644 --- a/src/common/QskModule.cpp +++ b/src/common/QskModule.cpp @@ -24,6 +24,7 @@ #include "QskScrollArea.h" #include "QskSeparator.h" #include "QskSimpleListBox.h" +#include "QskShortcut.h" #include "QskSlider.h" #include "QskStackBox.h" #include "QskStandardSymbol.h" @@ -239,6 +240,7 @@ void QskModule::registerTypes() qRegisterMetaType< QskSkin* >(); QSK_REGISTER( QskMain, "Main" ); + QSK_REGISTER( QskShortcut, "Shortcut" ); QSK_REGISTER( QskWindow, "Window" ); diff --git a/src/controls/QskShortcut.cpp b/src/controls/QskShortcut.cpp index c51c0a5c..f8030be2 100644 --- a/src/controls/QskShortcut.cpp +++ b/src/controls/QskShortcut.cpp @@ -4,245 +4,221 @@ *****************************************************************************/ #include "QskShortcut.h" -#include "QskControl.h" - +#include "QskShortcutMap.h" +#include #include -#include -#include -#include #include -#include - static inline QShortcutMap& qskShortcutMap() { return QGuiApplicationPrivate::instance()->shortcutMap; } -class QskShortcutHandler final : public QObject -{ -public: - QskShortcutHandler(); - - int add( QQuickItem*, const QKeySequence&, - const QObject* receiver, const char* method ); - - int add( QQuickItem*, const QKeySequence&, - const QObject* receiver, QtPrivate::QSlotObjectBase* ); - - void remove( int id ); - - void setEnabled( int id, bool ); - void setAutoRepeat( int id, bool repeat ); - - virtual bool eventFilter( QObject*, QEvent* ) override final; - -private: - int insert( QQuickItem*, const QKeySequence&, - const QObject* receiver, const QMetaMethod&, QtPrivate::QSlotObjectBase* ); - - void cleanUp( QObject* ); - - static bool contextMatcher( QObject*, Qt::ShortcutContext ); - - class InvokeData - { - public: - InvokeData(): - item( nullptr ), - receiver( nullptr ), - slotObject( nullptr ) - { - } - - ~InvokeData() - { - if ( slotObject ) - slotObject->destroyIfLastRef(); - } - - QQuickItem* item; - const QObject* receiver; - QMetaMethod method; - QtPrivate::QSlotObjectBase* slotObject; - }; - - std::map< int, InvokeData > m_invokeDataMap; -}; - -Q_GLOBAL_STATIC( QskShortcutHandler, qskShortcutHandler ) - -QskShortcutHandler::QskShortcutHandler() -{ - installEventFilter( this ); -} - -int QskShortcutHandler::add( QQuickItem* item, const QKeySequence& key, - const QObject* receiver, const char* method ) -{ - int id = 0; - - if ( receiver ) - { - const QMetaObject* metaObject = receiver->metaObject(); - - const int methodIndex = metaObject->indexOfMethod( - QMetaObject::normalizedSignature( method ).constData() + 1 ); - - if ( methodIndex >= 0 ) - { - id = insert( item, key, - receiver, metaObject->method( methodIndex ), nullptr ); - } - } - - return id; -} - -int QskShortcutHandler::add( QQuickItem* item, const QKeySequence& key, - const QObject* receiver, QtPrivate::QSlotObjectBase* slotObj ) -{ - return insert( item, key, receiver, QMetaMethod(), slotObj ); -} - -int QskShortcutHandler::insert( - QQuickItem* item, const QKeySequence& key, - const QObject* receiver, const QMetaMethod& method, - QtPrivate::QSlotObjectBase* slotObject ) -{ - if ( receiver ) - { - receiver->disconnect( this ); - connect( receiver, &QObject::destroyed, this, &QskShortcutHandler::cleanUp ); - } - - int id = 0; - - auto& map = qskShortcutMap(); - - if ( item ) - { - if ( item != receiver ) - { - item->disconnect( this ); - connect( item, &QObject::destroyed, this, &QskShortcutHandler::cleanUp ); - } - - id = map.addShortcut( item, key, Qt::WindowShortcut, contextMatcher ); - } - else - { - id = map.addShortcut( this, key, Qt::ApplicationShortcut, contextMatcher ); - } - - auto& data = m_invokeDataMap[ id ]; - - data.item = item; - data.receiver = receiver; - - if ( slotObject ) - data.slotObject = slotObject; - else - data.method = method; - - return id; -} - -void QskShortcutHandler::remove( int id ) -{ - auto it = m_invokeDataMap.find( id ); - if ( it == m_invokeDataMap.end() ) - return; - - auto& map = qskShortcutMap(); - map.removeShortcut( id, nullptr ); - - 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 ... - */ - auto& map = qskShortcutMap(); - - for ( auto it = m_invokeDataMap.begin(); it != m_invokeDataMap.end(); ) - { - const auto& data = it->second; - - if ( data.item == object || data.receiver == object ) - { - map.removeShortcut( it->first, nullptr ); - it = m_invokeDataMap.erase( it ); - - continue; - } - - ++it; - } -} - -bool QskShortcutHandler::contextMatcher( QObject* object, Qt::ShortcutContext context ) +static bool qskContextMatcher( QObject* object, Qt::ShortcutContext context ) { if ( context == Qt::ApplicationShortcut ) return true; if ( context == Qt::WindowShortcut ) { - const auto focusWindow = QGuiApplication::focusWindow(); + if ( const auto shortcut = qobject_cast< const QskShortcut* >( object ) ) + return shortcut->isFocusInScope(); + } - if ( auto item = qobject_cast< const QQuickItem* >( object ) ) + return false; +} + +class QskShortcut::PrivateData +{ +public: + PrivateData(): + id( 0 ), + autoRepeat( true ), + enabled( true ), + isWindowContext( true ), + isComplete( true ) + { + } + + ~PrivateData() + { + if ( id != 0 ) + qskShortcutMap().removeShortcut( id, nullptr ); + } + + void resetShortcut( QskShortcut* shortcut ) + { + if ( !isComplete ) + return; + + auto& map = qskShortcutMap(); + + const int oldId = id; + + if ( id != 0 ) { - const auto window = item->window(); - if ( window == nullptr || window != focusWindow ) - { - return false; - } + map.removeShortcut( id, nullptr ); + id = 0; + } - while ( item ) - { - /* - We have to find out if the active focus is inside - the surronding shortcut scope. - */ - if ( QskControl::isShortcutScope( item ) ) - { - if ( !item->hasFocus() ) - return false; - } + if ( !sequence.isEmpty() ) + { + id = map.addShortcut( shortcut, sequence, + shortcut->context(), qskContextMatcher ); - item = item->parentItem(); - } + if ( !autoRepeat ) + map.setShortcutAutoRepeat( false, id, shortcut ); + + if ( !enabled ) + map.setShortcutEnabled( false, id, shortcut ); + } + + if ( oldId != id ) + shortcut->Q_EMIT shortcutIdChanged( id ); + } + +public: + QKeySequence sequence; + + int id; + + bool autoRepeat : 1; + bool enabled : 1; + bool isWindowContext : 1; + bool isComplete : 1; +}; + +QskShortcut::QskShortcut( QObject* parent ): + Inherited( parent ), + m_data( new PrivateData ) +{ +} + +QskShortcut::QskShortcut( const QKeySequence& sequence, QObject* parent ): + QskShortcut( sequence, Qt::WindowShortcut, parent ) +{ +} + +QskShortcut::QskShortcut( const QKeySequence& sequence, + Qt::ShortcutContext context, QObject* parent ): + Inherited( parent ), + m_data( new PrivateData ) +{ + m_data->sequence = sequence; + m_data->isWindowContext = ( context == Qt::WindowShortcut ); + m_data->resetShortcut( this ); +} + +QskShortcut::~QskShortcut() +{ +} + +int QskShortcut::shortcutId() const +{ + return m_data->id; +} + +Qt::ShortcutContext QskShortcut::context() const +{ + return m_data->isWindowContext + ? Qt::WindowShortcut : Qt::ApplicationShortcut; +} + +void QskShortcut::setContext( Qt::ShortcutContext context ) +{ + if ( context == Qt::ApplicationShortcut + || context == Qt::WindowShortcut ) + { + const bool isWindowContext = ( context == Qt::WindowShortcut ); + + if ( isWindowContext != m_data->isWindowContext ) + { + m_data->isWindowContext = isWindowContext; + m_data->resetShortcut( this ); + + Q_EMIT contextChanged(); + } + } +} + +void QskShortcut::setSequence( const QKeySequence& sequence ) +{ + if ( sequence != m_data->sequence ) + { + m_data->sequence = sequence; + m_data->resetShortcut( this ); + + Q_EMIT sequenceChanged(); + } +} + +QKeySequence QskShortcut::sequence() const +{ + return m_data->sequence; +} + +void QskShortcut::setSequenceVariant( const QVariant& sequence ) +{ + if ( sequence.type() == QVariant::Int ) + setSequence( static_cast( sequence.toInt() ) ); + else + setSequence( QKeySequence::fromString( sequence.toString() ) ); +} + +QVariant QskShortcut::sequenceVariant() const +{ + return m_data->sequence.toString(); +} + +void QskShortcut::setEnabled( bool on ) +{ + if ( on != m_data->enabled ) + { + m_data->enabled = on; + + if ( m_data->id != 0 ) + qskShortcutMap().setShortcutEnabled( on, m_data->id, this ); + + Q_EMIT enabledChanged(); + } +} + +bool QskShortcut::isEnabled() const +{ + return m_data->enabled; +} + +void QskShortcut::setAutoRepeat( bool on ) +{ + if ( on != m_data->autoRepeat ) + { + m_data->autoRepeat = on; + + if ( m_data->id != 0 ) + qskShortcutMap().setShortcutEnabled( on, m_data->id, this ); + + Q_EMIT autoRepeatChanged(); + } +} + +bool QskShortcut::autoRepeat() const +{ + return m_data->autoRepeat; +} + +bool QskShortcut::event( QEvent* event ) +{ + if ( event->type() == QEvent::Shortcut ) + { + auto* shortcutEvent = static_cast< QShortcutEvent* >( event ); + + if ( shortcutEvent->shortcutId() == m_data->id ) + { + if ( shortcutEvent->isAmbiguous() ) + Q_EMIT activatedAmbiguously(); + else + Q_EMIT activated(); - // we want to process the following QShortcutEvent - object->installEventFilter( qskShortcutHandler ); return true; } } @@ -250,105 +226,46 @@ bool QskShortcutHandler::contextMatcher( QObject* object, Qt::ShortcutContext co return false; } -void QskShortcutHandler::setEnabled( int id, bool enabled ) +bool QskShortcut::isFocusInScope() const { - auto& map = qskShortcutMap(); - map.setShortcutEnabled( enabled, id, this ); -} - -void QskShortcutHandler::setAutoRepeat( int id, bool repeat ) -{ - auto& map = qskShortcutMap(); - map.setShortcutAutoRepeat( repeat, id, this ); -} - -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() ) - { - const auto& invokeData = it->second; - - Q_ASSERT( invokeData.item == nullptr || invokeData.item == object ); - - auto receiver = const_cast< QObject* >( invokeData.receiver ); - - if ( invokeData.slotObject ) - { - void* args[] = { 0 }; - - if ( receiver && receiver->thread() != thread() ) - { - QCoreApplication::postEvent( receiver, - new QMetaCallEvent( invokeData.slotObject, nullptr, 0, 0, nullptr, args ) ); - } - else - { - invokeData.slotObject->call( receiver, args ); - } - } - else - { - invokeData.method.invoke( receiver, Qt::AutoConnection ); - } - + if ( !m_data->isWindowContext ) return true; - } - // seems like someone else is also interested in shortcuts - return false; -} + const QQuickItem* contextItem = nullptr; -int QskShortcut::addMethod( QQuickItem* item, const QKeySequence& key, - bool autoRepeat, const QObject* receiver, const char* method ) -{ - if ( receiver == nullptr ) + if ( parent()->isWindowType() ) { - return 0; + if ( auto window = qobject_cast< const QQuickWindow* >( parent() ) ) + contextItem = window->contentItem(); + } + else + { + contextItem = qobject_cast< const QQuickItem* >( parent() ); } - int id = qskShortcutHandler->add( item, key, receiver, method ); - if ( id && !autoRepeat ) - qskShortcutHandler->setAutoRepeat( id, false ); - - return id; + if ( contextItem ) + { + return QskShortcutMap::contextMatcher( contextItem, Qt::WindowShortcut ); + } + else + { + qWarning( "QskShortcut has no valid parent for Qt::WindowShortcut" ); + return false; + } } -int QskShortcut::addSlotObject( QQuickItem* item, const QKeySequence& key, - bool autoRepeat, const QObject* receiver, QtPrivate::QSlotObjectBase* slotObject ) +void QskShortcut::classBegin() { - int id = qskShortcutHandler->add( item, key, receiver, slotObject ); - if ( id && !autoRepeat ) - qskShortcutHandler->setAutoRepeat( id, false ); - - return id; + m_data->isComplete = false; } -void QskShortcut::setAutoRepeat( int id, bool on ) +void QskShortcut::componentComplete() { - qskShortcutHandler->setAutoRepeat( id, on ); + if ( m_data->isComplete == false ) + { + m_data->isComplete = true; + m_data->resetShortcut( this ); + } } -void QskShortcut::setEnabled( int id, bool on ) -{ - qskShortcutHandler->setEnabled( id, on ); -} - -void QskShortcut::removeShortcut( int id ) -{ - qskShortcutHandler->remove( id ); -} +#include "moc_QskShortcut.cpp" diff --git a/src/controls/QskShortcut.h b/src/controls/QskShortcut.h index 3e2ef2f9..2b4c4e69 100644 --- a/src/controls/QskShortcut.h +++ b/src/controls/QskShortcut.h @@ -7,219 +7,87 @@ #define QSK_SHORTCUT_H #include "QskGlobal.h" -#include -class QQuickItem; +#include +#include +#include +#include + class QKeySequence; -class QSK_EXPORT QskShortcut +/* + For QML, with C++ there is also QskShortcutMap that does + not need to create QObjects per shortcut + */ +class QSK_EXPORT QskShortcut : public QObject, public QQmlParserStatus { + Q_OBJECT + Q_INTERFACES( QQmlParserStatus ) + + Q_PROPERTY( QVariant sequence READ sequenceVariant + WRITE setSequenceVariant NOTIFY sequenceChanged FINAL) + + Q_PROPERTY( Qt::ShortcutContext context READ context + WRITE setContext NOTIFY contextChanged ) + + Q_PROPERTY( bool enabled READ isEnabled + WRITE setEnabled NOTIFY enabledChanged ) + + Q_PROPERTY( bool autoRepeat READ autoRepeat + WRITE setAutoRepeat NOTIFY autoRepeatChanged ) + + Q_PROPERTY( int shortcutId READ shortcutId + NOTIFY shortcutIdChanged ) + + using Inherited = QObject; + public: - static void setAutoRepeat( int, bool on ); - static void setEnabled( int, bool on ); + QskShortcut( QObject* parent = nullptr ); - static void removeShortcut( int ); + QskShortcut( const QKeySequence&, QObject* = nullptr ); + QskShortcut( const QKeySequence&, Qt::ShortcutContext, QObject* = nullptr ); - // -- traditional slots - static int addShortcut( const QKeySequence&, bool autoRepeat, - const QObject* receiver, const char* method ); + virtual ~QskShortcut(); - static int addShortcut( QQuickWindow*, const QKeySequence&, bool autoRepeat, - const QObject* receiver, const char* method ); + int shortcutId() const; - static int addShortcut( QQuickItem*, const QKeySequence&, bool autoRepeat, - const QObject* receiver, const char* method ); + void setSequence( const QKeySequence& ); + QKeySequence sequence() const; - // -- calling a QObject method - template< typename Func1 > - static int addShortcut( const QKeySequence&, bool autoRepeat, - const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ); + // for QML + void setSequenceVariant( const QVariant& ); + QVariant sequenceVariant() const; - template< typename Func1 > - static int addShortcut( QQuickWindow*, const QKeySequence&, bool autoRepeat, - const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ); + Qt::ShortcutContext context() const; + void setContext(Qt::ShortcutContext context); - template< typename Func1 > - static int addShortcut( QQuickItem*, const QKeySequence&, bool autoRepeat, - const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ); + void setEnabled( bool ); + bool isEnabled() const; - // -- calling a functor or function pointer inside a thread context - template< typename Func1 > - static int addShortcut( const QKeySequence&, bool autoRepeat, - const QObject* context, Func1 slot ); + void setAutoRepeat( bool ); + bool autoRepeat() const; - template< typename Func1 > - static int addShortcut( QQuickWindow*, const QKeySequence&, bool autoRepeat, - const QObject* context, Func1 slot ); + virtual bool isFocusInScope() const; - template< typename Func1 > - static int addShortcut( QQuickItem*, const QKeySequence&, bool autoRepeat, - const QObject* context, Func1 slot ); +Q_SIGNALS: + void sequenceChanged(); + void contextChanged(); + void enabledChanged(); + void autoRepeatChanged(); - // -- calling a functor or function pointer - template< typename Func1 > - static int addShortcut( const QKeySequence&, bool autoRepeat, Func1 slot ); + void activated(); + void activatedAmbiguously(); - template< typename Func1 > - static int addShortcut( QQuickWindow*, const QKeySequence&, bool autoRepeat, Func1 slot ); + int shortcutIdChanged( int ) const; - template< typename Func1 > - static int addShortcut( QQuickItem*, const QKeySequence&, bool autoRepeat, Func1 slot ); +protected: + virtual bool event( QEvent* ) override; + virtual void classBegin() override; + virtual void componentComplete() override; private: - QskShortcut() = delete; - ~QskShortcut() = delete; - - static int addMethod( QQuickItem*, const QKeySequence&, bool autoRepeat, - const QObject* receiver, const char* method ); - - template< typename Func1 > - static int addMemberSlot( QQuickItem*, const QKeySequence&, bool autoRepeat, - const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ); - - template< typename Func1 > - static int addFunctorSlot( QQuickItem*, const QKeySequence&, - bool autoRepeat, const QObject* context, Func1 slot ); - - static int addSlotObject( - QQuickItem* item, const QKeySequence&, bool autoRepeat, - const QObject* receiver, QtPrivate::QSlotObjectBase* ); + class PrivateData; + std::unique_ptr< PrivateData > m_data; }; -template< typename Func1 > -inline int QskShortcut::addMemberSlot( - QQuickItem* item, const QKeySequence& key, bool autoRepeat, - const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ) -{ - using namespace QtPrivate; - typedef FunctionPointer< Func1 > SlotType; - - Q_STATIC_ASSERT_X( int( SlotType::ArgumentCount ) == 0, - "The slot must not have any arguments."); - - return addSlotObject( item, key, autoRepeat, receiver, - new QSlotObject< Func1, typename SlotType::Arguments, void >( slot ) ); -} - -template< typename Func1 > -inline int QskShortcut::addFunctorSlot( - QQuickItem* item, const QKeySequence& key, bool autoRepeat, - const QObject* context, Func1 slot ) -{ - using namespace QtPrivate; - typedef FunctionPointer< Func1 > SlotType; - - Q_STATIC_ASSERT_X( int( SlotType::ArgumentCount ) <= 0, - "The slot must not have any arguments."); - - Q_STATIC_ASSERT_X( !SlotType::IsPointerToMemberFunction, - "The slot must be no member function." ); - - using Args = List_Left< void, 0 >::Value; - - return addSlotObject( item, key, autoRepeat, context, - new QFunctorSlotObject< Func1, 0, Args, void >( slot ) ); -} - -// -- traditional slots - -inline int QskShortcut::addShortcut( - QQuickItem* item, const QKeySequence& key, bool autoRepeat, - const QObject* receiver, const char* method ) -{ - return addMethod( item, key, autoRepeat, receiver, method ); -} - -inline int QskShortcut::addShortcut( - const QKeySequence& key, bool autoRepeat, - const QObject* receiver, const char* method ) -{ - return addMethod( nullptr, key, autoRepeat, receiver, method ); -} - -inline int QskShortcut::addShortcut( - QQuickWindow* window, const QKeySequence& key, bool autoRepeat, - const QObject* receiver, const char* method ) -{ - auto item = window ? window->contentItem() : nullptr; - return addMethod( item, key, autoRepeat, receiver, method ); -} - -// -- calling a QObject method - -template< typename Func1 > -inline int QskShortcut::addShortcut( - QQuickItem* item, const QKeySequence& key, bool autoRepeat, - const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ) -{ - return addMemberSlot( item, key, autoRepeat, receiver, slot ); -} - - -template< typename Func1 > -inline int QskShortcut::addShortcut( - QQuickWindow* window, const QKeySequence& key, bool autoRepeat, - const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ) -{ - auto item = window ? window->contentItem() : nullptr; - return addMemberSlot( item, key, autoRepeat, receiver, slot ); -} - -template< typename Func1 > -inline int QskShortcut::addShortcut( - const QKeySequence& key, bool autoRepeat, - const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ) -{ - return addMemberSlot( nullptr, key, autoRepeat, receiver, slot ); -} - -// -- calling a functor or function pointer with context - -template< typename Func1 > -inline int QskShortcut::addShortcut( - QQuickItem* item, const QKeySequence& key, bool autoRepeat, - const QObject* context, Func1 slot ) -{ - return addFunctorSlot( item, key, autoRepeat, context, slot ); -} - -template< typename Func1 > -inline int QskShortcut::addShortcut( - QQuickWindow* window, const QKeySequence& key, bool autoRepeat, - const QObject* context, Func1 slot ) -{ - auto item = window ? window->contentItem() : nullptr; - return addFunctorSlot( item, key, autoRepeat, context, slot ); -} - -template< typename Func1 > -inline int QskShortcut::addShortcut( const QKeySequence& key, bool autoRepeat, - const QObject* context, Func1 slot ) -{ - return addFunctorSlot( nullptr, key, autoRepeat, context, slot ); -} - -// -- calling a functor or function pointer - -template< typename Func1 > -inline int QskShortcut::addShortcut( - QQuickItem* item, const QKeySequence& key, bool autoRepeat, Func1 slot ) -{ - return addFunctorSlot( item, key, autoRepeat, nullptr, slot ); -} - -template< typename Func1 > -inline int QskShortcut::addShortcut( - QQuickWindow* window, const QKeySequence& key, bool autoRepeat, Func1 slot ) -{ - auto item = window ? window->contentItem() : nullptr; - return addFunctorSlot( item, key, autoRepeat, nullptr, autoRepeat, slot ); -} - -template< typename Func1 > -int QskShortcut::addShortcut( const QKeySequence& key, bool autoRepeat, Func1 slot ) -{ - return addFunctorSlot( nullptr, key, autoRepeat, nullptr, slot ); -} - #endif diff --git a/src/controls/QskShortcutMap.cpp b/src/controls/QskShortcutMap.cpp new file mode 100644 index 00000000..8d306f78 --- /dev/null +++ b/src/controls/QskShortcutMap.cpp @@ -0,0 +1,375 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskShortcutMap.h" +#include "QskControl.h" + +#include +#include +#include +#include +#include + +#include + +static inline QShortcutMap& qskShortcutMap() +{ + return QGuiApplicationPrivate::instance()->shortcutMap; +} + +class QskShortcutHandler final : public QObject +{ +public: + QskShortcutHandler(); + + int add( QQuickItem*, const QKeySequence&, + const QObject* receiver, const char* method ); + + int add( QQuickItem*, const QKeySequence&, + const QObject* receiver, QtPrivate::QSlotObjectBase* ); + + void remove( int id ); + + void setEnabled( int id, bool ); + void setAutoRepeat( int id, bool repeat ); + + virtual bool eventFilter( QObject*, QEvent* ) override final; + +private: + int insert( QQuickItem*, const QKeySequence&, + const QObject* receiver, const QMetaMethod&, QtPrivate::QSlotObjectBase* ); + + void cleanUp( QObject* ); + + class InvokeData + { + public: + InvokeData(): + item( nullptr ), + receiver( nullptr ), + slotObject( nullptr ) + { + } + + ~InvokeData() + { + if ( slotObject ) + slotObject->destroyIfLastRef(); + } + + QQuickItem* item; + const QObject* receiver; + QMetaMethod method; + QtPrivate::QSlotObjectBase* slotObject; + }; + + 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 ) ) + { + /* + Unfortunatley there is no way to have to know about + the contextItem without making it the receiver of + 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 ); +} + +int QskShortcutHandler::add( QQuickItem* item, const QKeySequence& sequence, + const QObject* receiver, const char* method ) +{ + int id = 0; + + if ( receiver ) + { + const QMetaObject* metaObject = receiver->metaObject(); + + const int methodIndex = metaObject->indexOfMethod( + QMetaObject::normalizedSignature( method ).constData() + 1 ); + + if ( methodIndex >= 0 ) + { + id = insert( item, sequence, + receiver, metaObject->method( methodIndex ), nullptr ); + } + } + + return id; +} + +int QskShortcutHandler::add( QQuickItem* item, const QKeySequence& sequence, + const QObject* receiver, QtPrivate::QSlotObjectBase* slotObj ) +{ + return insert( item, sequence, receiver, QMetaMethod(), slotObj ); +} + +int QskShortcutHandler::insert( + QQuickItem* item, const QKeySequence& sequence, + const QObject* receiver, const QMetaMethod& method, + QtPrivate::QSlotObjectBase* slotObject ) +{ + if ( receiver ) + { + receiver->disconnect( this ); + connect( receiver, &QObject::destroyed, this, &QskShortcutHandler::cleanUp ); + } + + int id = 0; + + auto& map = qskShortcutMap(); + + if ( item ) + { + if ( item != receiver ) + { + item->disconnect( this ); + connect( item, &QObject::destroyed, this, &QskShortcutHandler::cleanUp ); + } + + id = map.addShortcut( item, sequence, Qt::WindowShortcut, qskContextMatcher ); + } + else + { + id = map.addShortcut( this, sequence, Qt::ApplicationShortcut, qskContextMatcher ); + } + + auto& data = m_invokeDataMap[ id ]; + + data.item = item; + data.receiver = receiver; + + if ( slotObject ) + data.slotObject = slotObject; + else + data.method = method; + + return id; +} + +void QskShortcutHandler::remove( int id ) +{ + auto it = m_invokeDataMap.find( id ); + if ( it == m_invokeDataMap.end() ) + return; + + auto& map = qskShortcutMap(); + map.removeShortcut( id, nullptr ); + + 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 ... + */ + auto& map = qskShortcutMap(); + + for ( auto it = m_invokeDataMap.begin(); it != m_invokeDataMap.end(); ) + { + const auto& data = it->second; + + if ( data.item == object || data.receiver == object ) + { + map.removeShortcut( it->first, nullptr ); + it = m_invokeDataMap.erase( it ); + + continue; + } + + ++it; + } +} + +void QskShortcutHandler::setEnabled( int id, bool enabled ) +{ + auto& map = qskShortcutMap(); + map.setShortcutEnabled( enabled, id, this ); +} + +void QskShortcutHandler::setAutoRepeat( int id, bool repeat ) +{ + auto& map = qskShortcutMap(); + map.setShortcutAutoRepeat( repeat, id, this ); +} + +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() ) + { + const auto& invokeData = it->second; + + Q_ASSERT( invokeData.item == nullptr || invokeData.item == object ); + + auto receiver = const_cast< QObject* >( invokeData.receiver ); + + if ( invokeData.slotObject ) + { + void* args[] = { 0 }; + + if ( receiver && receiver->thread() != thread() ) + { + QCoreApplication::postEvent( receiver, + new QMetaCallEvent( invokeData.slotObject, nullptr, 0, 0, nullptr, args ) ); + } + else + { + invokeData.slotObject->call( receiver, args ); + } + } + else + { + invokeData.method.invoke( receiver, Qt::AutoConnection ); + } + + 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 ) + { + return 0; + } + + int id = qskShortcutHandler->add( item, sequence, receiver, method ); + if ( id && !autoRepeat ) + qskShortcutHandler->setAutoRepeat( id, false ); + + return id; +} + +int QskShortcutMap::addSlotObject( QQuickItem* item, const QKeySequence& sequence, + bool autoRepeat, const QObject* receiver, QtPrivate::QSlotObjectBase* slotObject ) +{ + int id = qskShortcutHandler->add( item, sequence, receiver, slotObject ); + if ( id && !autoRepeat ) + qskShortcutHandler->setAutoRepeat( id, false ); + + return id; +} + +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. + */ + if ( QskControl::isShortcutScope( item ) ) + { + if ( !item->hasFocus() ) + return false; + } + + item = item->parentItem(); + } + + return true; + } + + return false; +} + diff --git a/src/controls/QskShortcutMap.h b/src/controls/QskShortcutMap.h new file mode 100644 index 00000000..d6c5c569 --- /dev/null +++ b/src/controls/QskShortcutMap.h @@ -0,0 +1,228 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_SHORTCUT_MAP_H +#define QSK_SHORTCUT_MAP_H + +#include "QskGlobal.h" +#include + +class QQuickItem; +class QKeySequence; + +class QSK_EXPORT QskShortcutMap +{ +public: + static void setAutoRepeat( int, bool on ); + static void setEnabled( int, bool on ); + + static void removeShortcut( int ); + + // -- traditional slots + static int addShortcut( const QKeySequence&, bool autoRepeat, + const QObject* receiver, const char* method ); + + static int addShortcut( QQuickWindow*, const QKeySequence&, bool autoRepeat, + const QObject* receiver, const char* method ); + + static int addShortcut( QQuickItem*, const QKeySequence&, bool autoRepeat, + const QObject* receiver, const char* method ); + + // -- calling a QObject method + template< typename Func1 > + static int addShortcut( const QKeySequence&, bool autoRepeat, + const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ); + + template< typename Func1 > + static int addShortcut( QQuickWindow*, const QKeySequence&, bool autoRepeat, + const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ); + + template< typename Func1 > + static int addShortcut( QQuickItem*, const QKeySequence&, bool autoRepeat, + const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ); + + // -- calling a functor or function pointer inside a thread context + template< typename Func1 > + static int addShortcut( const QKeySequence&, bool autoRepeat, + const QObject* context, Func1 slot ); + + template< typename Func1 > + static int addShortcut( QQuickWindow*, const QKeySequence&, bool autoRepeat, + const QObject* context, Func1 slot ); + + template< typename Func1 > + static int addShortcut( QQuickItem*, const QKeySequence&, bool autoRepeat, + const QObject* context, Func1 slot ); + + // -- calling a functor or function pointer + template< typename Func1 > + static int addShortcut( const QKeySequence&, bool autoRepeat, Func1 slot ); + + template< typename Func1 > + static int addShortcut( QQuickWindow*, const QKeySequence&, bool autoRepeat, Func1 slot ); + + template< typename Func1 > + static int addShortcut( QQuickItem*, const QKeySequence&, bool autoRepeat, Func1 slot ); + + static bool contextMatcher( const QQuickItem*, Qt::ShortcutContext ); + +private: + QskShortcutMap() = delete; + ~QskShortcutMap() = delete; + + static int addMethod( QQuickItem*, const QKeySequence&, bool autoRepeat, + const QObject* receiver, const char* method ); + + template< typename Func1 > + static int addMemberSlot( QQuickItem*, const QKeySequence&, bool autoRepeat, + const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ); + + template< typename Func1 > + static int addFunctorSlot( QQuickItem*, const QKeySequence&, + bool autoRepeat, const QObject* context, Func1 slot ); + + static int addSlotObject( + QQuickItem* item, const QKeySequence&, bool autoRepeat, + const QObject* receiver, QtPrivate::QSlotObjectBase* ); +}; + +template< typename Func1 > +inline int QskShortcutMap::addMemberSlot( + QQuickItem* item, const QKeySequence& sequence, bool autoRepeat, + const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ) +{ + using namespace QtPrivate; + typedef FunctionPointer< Func1 > SlotType; + + Q_STATIC_ASSERT_X( int( SlotType::ArgumentCount ) == 0, + "The slot must not have any arguments."); + + return addSlotObject( item, sequence, autoRepeat, receiver, + new QSlotObject< Func1, typename SlotType::Arguments, void >( slot ) ); +} + +template< typename Func1 > +inline int QskShortcutMap::addFunctorSlot( + QQuickItem* item, const QKeySequence& sequence, bool autoRepeat, + const QObject* context, Func1 slot ) +{ + using namespace QtPrivate; + typedef FunctionPointer< Func1 > SlotType; + + Q_STATIC_ASSERT_X( int( SlotType::ArgumentCount ) <= 0, + "The slot must not have any arguments."); + + Q_STATIC_ASSERT_X( !SlotType::IsPointerToMemberFunction, + "The slot must be no member function." ); + + using Args = List_Left< void, 0 >::Value; + + return addSlotObject( item, sequence, autoRepeat, context, + new QFunctorSlotObject< Func1, 0, Args, void >( slot ) ); +} + +// -- traditional slots + +inline int QskShortcutMap::addShortcut( + QQuickItem* item, const QKeySequence& sequence, bool autoRepeat, + const QObject* receiver, const char* method ) +{ + return addMethod( item, sequence, autoRepeat, receiver, method ); +} + +inline int QskShortcutMap::addShortcut( + const QKeySequence& sequence, bool autoRepeat, + const QObject* receiver, const char* method ) +{ + return addMethod( nullptr, sequence, autoRepeat, receiver, method ); +} + +inline int QskShortcutMap::addShortcut( + QQuickWindow* window, const QKeySequence& sequence, bool autoRepeat, + const QObject* receiver, const char* method ) +{ + auto item = window ? window->contentItem() : nullptr; + return addMethod( item, sequence, autoRepeat, receiver, method ); +} + +// -- calling a QObject method + +template< typename Func1 > +inline int QskShortcutMap::addShortcut( + QQuickItem* item, const QKeySequence& sequence, bool autoRepeat, + const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ) +{ + return addMemberSlot( item, sequence, autoRepeat, receiver, slot ); +} + + +template< typename Func1 > +inline int QskShortcutMap::addShortcut( + QQuickWindow* window, const QKeySequence& sequence, bool autoRepeat, + const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ) +{ + auto item = window ? window->contentItem() : nullptr; + return addMemberSlot( item, sequence, autoRepeat, receiver, slot ); +} + +template< typename Func1 > +inline int QskShortcutMap::addShortcut( + const QKeySequence& sequence, bool autoRepeat, + const typename QtPrivate::FunctionPointer< Func1 >::Object* receiver, Func1 slot ) +{ + return addMemberSlot( nullptr, sequence, autoRepeat, receiver, slot ); +} + +// -- calling a functor or function pointer with context + +template< typename Func1 > +inline int QskShortcutMap::addShortcut( + QQuickItem* item, const QKeySequence& sequence, bool autoRepeat, + const QObject* context, Func1 slot ) +{ + return addFunctorSlot( item, sequence, autoRepeat, context, slot ); +} + +template< typename Func1 > +inline int QskShortcutMap::addShortcut( + QQuickWindow* window, const QKeySequence& sequence, bool autoRepeat, + const QObject* context, Func1 slot ) +{ + auto item = window ? window->contentItem() : nullptr; + return addFunctorSlot( item, sequence, autoRepeat, context, slot ); +} + +template< typename Func1 > +inline int QskShortcutMap::addShortcut( const QKeySequence& sequence, bool autoRepeat, + const QObject* context, Func1 slot ) +{ + return addFunctorSlot( nullptr, sequence, autoRepeat, context, slot ); +} + +// -- calling a functor or function pointer + +template< typename Func1 > +inline int QskShortcutMap::addShortcut( + QQuickItem* item, const QKeySequence& sequence, bool autoRepeat, Func1 slot ) +{ + return addFunctorSlot( item, sequence, autoRepeat, nullptr, slot ); +} + +template< typename Func1 > +inline int QskShortcutMap::addShortcut( + QQuickWindow* window, const QKeySequence& sequence, bool autoRepeat, Func1 slot ) +{ + auto item = window ? window->contentItem() : nullptr; + return addFunctorSlot( item, sequence, autoRepeat, nullptr, autoRepeat, slot ); +} + +template< typename Func1 > +int QskShortcutMap::addShortcut( + const QKeySequence& sequence, bool autoRepeat, Func1 slot ) +{ + return addFunctorSlot( nullptr, sequence, autoRepeat, nullptr, slot ); +} + +#endif diff --git a/src/src.pro b/src/src.pro index c5788d1f..0716bcf1 100644 --- a/src/src.pro +++ b/src/src.pro @@ -152,6 +152,7 @@ HEADERS += \ controls/QskSeparatorSkinlet.h \ controls/QskSetup.h \ controls/QskShortcut.h \ + controls/QskShortcutMap.h \ controls/QskSimpleListBox.h \ controls/QskSkinFactory.h \ controls/QskSkin.h \ @@ -215,6 +216,7 @@ SOURCES += \ controls/QskSeparatorSkinlet.cpp \ controls/QskSetup.cpp \ controls/QskShortcut.cpp \ + controls/QskShortcutMap.cpp \ controls/QskSimpleListBox.cpp \ controls/QskSkin.cpp \ controls/QskSkinHintTable.cpp \ diff --git a/support/SkinnyShortcut.cpp b/support/SkinnyShortcut.cpp index 4554400b..3fdc3744 100644 --- a/support/SkinnyShortcut.cpp +++ b/support/SkinnyShortcut.cpp @@ -1,7 +1,7 @@ #include "SkinnyShortcut.h" #include -#include +#include #include #include #include @@ -31,21 +31,21 @@ void SkinnyShortcut::enable( Types types ) if ( types & RotateSkin ) { - QskShortcut::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_S ), + QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_S ), false, &s_shortcut, &SkinnyShortcut::rotateSkin ); cout << "CTRL-S to change the skin." << endl; } if ( types & DebugBackground ) { - QskShortcut::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_B ), + QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_B ), false, &s_shortcut, &SkinnyShortcut::showBackground ); cout << "CTRL-B to enable visual debugging modes." << endl; } if ( types & DebugStatistics ) { - QskShortcut::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ), + QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ), false, &s_shortcut, &SkinnyShortcut::debugStatistics ); cout << "CTRL-K to dump statistics about the items/nodes being currently used." << endl; } @@ -55,7 +55,7 @@ void SkinnyShortcut::enable( Types types ) // QKeySequence::Quit crashes the application // when not being implemented by the platform !! - QskShortcut::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_Q ), + QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_Q ), false, QGuiApplication::instance(), &QGuiApplication::quit ); cout << "CTRL-Q to terminate the application." << endl; }