File indexing completed on 2024-05-12 04:06:25

0001 /*
0002     SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "triggermapper.h"
0008 #include "constraintinteractor.h"
0009 #include "interactors.h"
0010 
0011 #include <QKeyEvent>
0012 #include <QMouseEvent>
0013 #include <QWheelEvent>
0014 #include <KConfig>
0015 #include <KConfigGroup>
0016 #include <KSharedConfig>
0017 
0018 Palapeli::TriggerMapper* Palapeli::TriggerMapper::instance()
0019 {
0020     static Palapeli::TriggerMapper instance;
0021     return &instance;
0022 }
0023 
0024 QMap<QByteArray, Palapeli::Interactor*> Palapeli::TriggerMapper::createInteractors(QGraphicsView* view)
0025 {
0026     QMap<QByteArray, Palapeli::Interactor*> result;
0027     result["MovePiece"] = new Palapeli::MovePieceInteractor(view);
0028     result["SelectPiece"] = new Palapeli::SelectPieceInteractor(view);
0029     result["TeleportPiece"] = new Palapeli::TeleportPieceInteractor(view);
0030     result["MoveViewport"] = new Palapeli::MoveViewportInteractor(view);
0031     result["ToggleCloseUp"] = new Palapeli::ToggleCloseUpInteractor(view);
0032     result["ZoomViewport"] = new Palapeli::ZoomViewportInteractor(view);
0033     result["ScrollViewportHoriz"] = new Palapeli::ScrollViewportInteractor(Qt::Horizontal, view);
0034     result["ScrollViewportVert"] = new Palapeli::ScrollViewportInteractor(Qt::Vertical, view);
0035     result["RubberBand"] = new Palapeli::RubberBandInteractor(view);
0036     result["Constraints"] = new Palapeli::ConstraintInteractor(view);
0037     result["ToggleConstraints"] = new Palapeli::ToggleConstraintInteractor(view);
0038     return result;
0039 }
0040 
0041 QMap<QByteArray, Palapeli::Trigger> Palapeli::TriggerMapper::defaultAssociations()
0042 {
0043     QMap<QByteArray, Palapeli::Trigger> result;
0044     result.insert("MovePiece", Palapeli::Trigger("LeftButton;NoModifier"));
0045     result.insert("SelectPiece", Palapeli::Trigger("LeftButton;ControlModifier"));
0046     result.insert("TeleportPiece", Palapeli::Trigger("LeftButton;ShiftModifier"));
0047     result.insert("MoveViewport", Palapeli::Trigger("RightButton;NoModifier"));
0048     result.insert("ToggleCloseUp", Palapeli::Trigger("MidButton;NoModifier"));
0049     result.insert("ZoomViewport", Palapeli::Trigger("wheel:Vertical;NoModifier"));
0050     result.insert("RubberBand", Palapeli::Trigger("LeftButton;NoModifier"));
0051     result.insert("Constraints", Palapeli::Trigger("LeftButton;NoModifier"));
0052     return result;
0053 }
0054 
0055 Palapeli::TriggerMapper::TriggerMapper()
0056 {
0057     //initialize quasi-static data
0058     m_keyModifierMap[Qt::Key_Shift] = Qt::ShiftModifier;
0059     m_keyModifierMap[Qt::Key_Control] = Qt::ControlModifier;
0060     m_keyModifierMap[Qt::Key_Alt] = Qt::AltModifier;
0061     m_keyModifierMap[Qt::Key_Meta] = Qt::MetaModifier;
0062     //initialize dynamic data
0063     readSettings();
0064 }
0065 
0066 QMap<QByteArray, Palapeli::Trigger> Palapeli::TriggerMapper::associations() const
0067 {
0068     return m_associations;
0069 }
0070 
0071 void Palapeli::TriggerMapper::readSettings()
0072 {
0073     m_associations.clear();
0074     m_associations = Palapeli::TriggerMapper::defaultAssociations();
0075     //read config
0076     KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("Mouse Interaction"));
0077     const QStringList configKeys = group.keyList();
0078     for (const QString& configKey : configKeys)
0079     {
0080         const QByteArray interactorKey = configKey.toLatin1();
0081         const QList<QByteArray> triggers = group.readEntry(configKey, QList<QByteArray>());
0082         for (const Palapeli::Trigger trigger : triggers) //implicit casts FTW
0083             if (trigger.isValid()) {
0084                 // Remove default and insert config value(s).
0085                 m_associations.insert(interactorKey, trigger);
0086             }
0087     }
0088     //announce update to InteractorManagers
0089     Q_EMIT associationsChanged();
0090 }
0091 
0092 void Palapeli::TriggerMapper::setAssociations(const QMap<QByteArray, Palapeli::Trigger>& associations)
0093 {
0094     m_associations = associations;
0095     //announce update to InteractorManagers
0096     Q_EMIT associationsChanged();
0097     //assemble trigger serializations
0098     QMap<QByteArray, QList<QByteArray> > triggerSerializations;
0099     {
0100         QMap<QByteArray, Palapeli::Trigger>::const_iterator it1 = m_associations.constBegin(), it2 = m_associations.constEnd();
0101         for (; it1 != it2; ++it1)
0102             triggerSerializations[it1.key()] << it1.value().serialized();
0103     }
0104     //clear config
0105     KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("Mouse Interaction"));
0106     const auto keys = group.keyList();
0107     for (const QString& key : keys)
0108         group.deleteEntry(key);
0109     //write config (in a way that supports multiple triggers for one interactor)
0110     QMap<QByteArray, QList<QByteArray> >::const_iterator it1 = triggerSerializations.constBegin(), it2 = triggerSerializations.constEnd();
0111     for (; it1 != it2; ++it1)
0112         group.writeEntry(it1.key().data(), it1.value());
0113     KSharedConfig::openConfig()->sync();
0114 }
0115 
0116 Palapeli::EventProcessingFlags Palapeli::TriggerMapper::testTrigger(const QByteArray& interactor, QWheelEvent* event) const
0117 {
0118     Palapeli::EventProcessingFlags result;
0119     QMap<QByteArray, Palapeli::Trigger>::const_iterator it1 = m_associations.begin(), it2 = m_associations.end();
0120     for (; it1 != it2; ++it1)
0121         if (it1.key() == interactor)
0122             result |= testTrigger(it1.value(), event);
0123     return result;
0124 }
0125 
0126 Palapeli::EventContext Palapeli::TriggerMapper::testTrigger(const QByteArray& interactor, QMouseEvent* event) const
0127 {
0128     Palapeli::EventContext result;
0129     QMap<QByteArray, Palapeli::Trigger>::const_iterator it1 = m_associations.begin(), it2 = m_associations.end();
0130     for (; it1 != it2; ++it1)
0131         if (it1.key() == interactor)
0132         {
0133             const Palapeli::EventProcessingFlags flags = testTrigger(it1.value(), event);
0134             result.flags |= flags;
0135             if (flags & Palapeli::EventMatches)
0136                 result.triggeringButtons |= it1.value().button();
0137         }
0138     return result;
0139 }
0140 
0141 Palapeli::EventContext Palapeli::TriggerMapper::testTrigger(const QByteArray& interactor, QKeyEvent* event, Qt::MouseButtons buttons) const
0142 {
0143     Palapeli::EventContext result;
0144     QMap<QByteArray, Palapeli::Trigger>::const_iterator it1 = m_associations.begin(), it2 = m_associations.end();
0145     for (; it1 != it2; ++it1)
0146         if (it1.key() == interactor)
0147         {
0148             result.flags |= testTrigger(it1.value(), event, buttons);
0149             result.triggeringButtons |= it1.value().button();
0150         }
0151     return result;
0152 }
0153 
0154 Palapeli::EventProcessingFlags Palapeli::TriggerMapper::testTrigger(const Palapeli::Trigger& trigger, QWheelEvent* event) const
0155 {
0156     if (trigger.isValid())
0157     {
0158         const bool testModifiers = trigger.modifiers() == Qt::NoModifier || trigger.modifiers() == event->modifiers();
0159         const bool checkDirection = trigger.wheelDirection() != 0;
0160         const QPoint angleDelta = event->angleDelta();
0161         const Qt::Orientation orientation = (qAbs(angleDelta.x()) > qAbs(angleDelta.y()) ? Qt::Horizontal : Qt::Vertical);
0162         const bool testDirection = (trigger.wheelDirection() == orientation);
0163         if (testModifiers && checkDirection && testDirection)
0164         {
0165             if (trigger.modifiers() == event->modifiers())
0166                 return Palapeli::EventMatchesExactly;
0167             else
0168                 return Palapeli::EventMatches;
0169         }
0170     }
0171     //if execution comes to this point, trigger does not match
0172         return {};
0173 }
0174 
0175 Palapeli::EventProcessingFlags Palapeli::TriggerMapper::testTrigger(const Palapeli::Trigger& trigger, QMouseEvent* event) const
0176 {
0177     if (trigger.isValid())
0178     {
0179         const bool testModifiers = trigger.modifiers() == Qt::NoModifier || trigger.modifiers() == event->modifiers();
0180         const Palapeli::EventProcessingFlags positiveResult = (trigger.modifiers() == event->modifiers()) ? Palapeli::EventMatchesExactly : Palapeli::EventMatches;
0181         const bool checkDirection = trigger.wheelDirection() == 0;
0182         if (testModifiers && checkDirection)
0183         {
0184             if (trigger.button() == Qt::NoButton)
0185                 //trigger matches
0186                 return positiveResult;
0187             const bool checkButtons = (event->button() | event->buttons()) & trigger.button();
0188             if (checkButtons)
0189             {
0190                 //trigger matches - construct result
0191                 Palapeli::EventProcessingFlags result = positiveResult;
0192                 if (event->button() == trigger.button())
0193                 {
0194                     if (event->type() == QEvent::MouseButtonPress)
0195                         result |= Palapeli::EventStartsInteraction;
0196                     if (event->type() == QEvent::MouseButtonRelease)
0197                         result |= Palapeli::EventConcludesInteraction;
0198                 }
0199                 return result;
0200             }
0201         }
0202     }
0203     //if execution comes to this point, trigger does not match
0204         return {};
0205 }
0206 
0207 Palapeli::EventProcessingFlags Palapeli::TriggerMapper::testTrigger(const Palapeli::Trigger& trigger, QKeyEvent* event, Qt::MouseButtons buttons) const
0208 {
0209     if (trigger.isValid())
0210     {
0211         //read modifiers
0212         const Qt::KeyboardModifier keyModifier = m_keyModifierMap.value((Qt::Key) event->key(), Qt::NoModifier);
0213         const Qt::KeyboardModifiers modifiers = keyModifier | event->modifiers();
0214         //checking
0215         const bool testModifiers = trigger.modifiers() == Qt::NoModifier || trigger.modifiers() == modifiers;
0216         const Palapeli::EventProcessingFlags positiveResult = trigger.modifiers() == event->modifiers() ? Palapeli::EventMatchesExactly : Palapeli::EventMatches;
0217         const bool checkDirection = trigger.wheelDirection() == 0;
0218         const bool checkButton = (trigger.button() & buttons) || trigger.button() == Qt::NoButton;
0219         if (testModifiers && checkDirection && checkButton)
0220         {
0221             //trigger matches - construct result
0222             Palapeli::EventProcessingFlags result = positiveResult;
0223             if (keyModifier != Qt::NoModifier)
0224             {
0225                 if (event->type() == QEvent::KeyPress)
0226                     result |= Palapeli::EventStartsInteraction;
0227                 if (event->type() == QEvent::KeyRelease)
0228                     result |= Palapeli::EventConcludesInteraction;
0229             }
0230             return result;
0231         }
0232     }
0233     //if execution comes to this point, trigger does not match
0234         return {};
0235 }
0236 
0237 #include "moc_triggermapper.cpp"