File indexing completed on 2024-05-19 04:07:50

0001 /*
0002     SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "interactormanager.h"
0008 #include "triggermapper.h"
0009 
0010 #include <QKeyEvent>
0011 #include <QMouseEvent>
0012 #include <QWheelEvent>
0013 
0014 static const int AdditionalPriorityForExactMatches = 10000;
0015 
0016 Palapeli::InteractorManager::InteractorManager(QGraphicsView* view)
0017     : QObject(view)
0018     , m_view(view)
0019     , m_interactors(Palapeli::TriggerMapper::createInteractors(view))
0020 {
0021     connect(Palapeli::TriggerMapper::instance(), &TriggerMapper::associationsChanged, this, &InteractorManager::resetActiveTriggers);
0022 }
0023 
0024 Palapeli::InteractorManager::~InteractorManager()
0025 {
0026     qDeleteAll(m_interactors);
0027 }
0028 
0029 void Palapeli::InteractorManager::updateScene()
0030 {
0031     for (Palapeli::Interactor* interactor : std::as_const(m_interactors))
0032         interactor->updateScene();
0033 }
0034 
0035 void Palapeli::InteractorManager::resetActiveTriggers()
0036 {
0037     for (Palapeli::Interactor* interactor : std::as_const(m_interactors))
0038         interactor->setInactive();
0039 }
0040 
0041 /*
0042  * Wheel events are delivered to all interactors that accept them.
0043  */
0044 void Palapeli::InteractorManager::handleEvent(QWheelEvent* event)
0045 {
0046     //convert event
0047     const QPoint angleDelta = event->angleDelta();
0048         const int delta = (qAbs(angleDelta.x()) > qAbs(angleDelta.y())) ? angleDelta.x() : angleDelta.y();
0049     Palapeli::WheelEvent pEvent(m_view, event->position().toPoint(), delta);
0050     //check which interactors are triggered by this event
0051     Palapeli::Interactor* bestMatchInteractor = nullptr;
0052     int bestMatchPriority = -1;
0053     QMap<QByteArray, Palapeli::Interactor*>::const_iterator it1 = m_interactors.constBegin(), it2 = m_interactors.constEnd();
0054     for (; it1 != it2; ++it1)
0055     {
0056         Palapeli::Interactor* const interactor = it1.value();
0057         const Palapeli::EventProcessingFlags flags = Palapeli::TriggerMapper::instance()->testTrigger(it1.key(), event);
0058         if (!(flags & Palapeli::EventMatches))
0059             continue;
0060         int priority = interactor->priority();
0061         if ((flags & Palapeli::EventMatchesExactly) == Palapeli::EventMatchesExactly)
0062             priority += AdditionalPriorityForExactMatches;
0063         if (priority > bestMatchPriority)
0064         {
0065             bestMatchInteractor = interactor;
0066             bestMatchPriority = priority;
0067         }
0068     }
0069     //activate matching interactor with highest priority
0070     if (bestMatchInteractor)
0071         bestMatchInteractor->sendEvent(pEvent);
0072 }
0073 
0074 /*
0075  * Unlike wheel events, mouse events are not just delivered to all interactors
0076  * that may accept them. Mouse interactions usually consist of a sequence of
0077  * press-move-move-...-release events, and we deliver all events of one sequence
0078  * to exactly one interactor. The Interactor class manages the activity flag
0079  * involved in this operation, and completes incomplete event sequences.
0080  */
0081 void Palapeli::InteractorManager::handleEvent(QMouseEvent* event)
0082 {
0083     //convert event
0084     Palapeli::MouseEvent pEvent(m_view, event->pos());
0085     //save button state (this information is needed for key events *following* this event, but not available from them)
0086     m_buttons = event->buttons();
0087     if (event->type() != QEvent::MouseButtonRelease)
0088         m_buttons |= event->button();
0089     m_mousePos = event->pos();
0090     //check which interactors are triggered by this event
0091     QMap<Palapeli::Interactor*, Palapeli::EventContext> interactorData;
0092     QMap<QByteArray, Palapeli::Interactor*>::const_iterator it1 = m_interactors.constBegin(), it2 = m_interactors.constEnd();
0093     for (; it1 != it2; ++it1)
0094         interactorData[it1.value()] = Palapeli::TriggerMapper::instance()->testTrigger(it1.key(), event);
0095     //further processing in a method which is shared with the KeyEvent handler
0096     handleEventCommon(pEvent, interactorData, event->buttons() | event->button());
0097 }
0098 
0099 /*
0100  * We also need to process KeyPress and KeyRelease events for modifier changes.
0101  */
0102 void Palapeli::InteractorManager::handleEvent(QKeyEvent* event)
0103 {
0104     //convert event
0105     Palapeli::MouseEvent pEvent(m_view, m_mousePos);
0106     //check which interactors are triggered by this event
0107     QMap<Palapeli::Interactor*, Palapeli::EventContext> interactorData;
0108     QMap<QByteArray, Palapeli::Interactor*>::const_iterator it1 = m_interactors.constBegin(), it2 = m_interactors.constEnd();
0109     for (; it1 != it2; ++it1)
0110         interactorData[it1.value()] = Palapeli::TriggerMapper::instance()->testTrigger(it1.key(), event, m_buttons);
0111     //further processing in a method which is shared with the MouseEvent handler
0112     handleEventCommon(pEvent, interactorData, m_buttons);
0113 }
0114 
0115 /*
0116  * This is the common base for handleEvent(QMouseEvent*) and handleEvent(QKeyEvent*).
0117  */
0118 void Palapeli::InteractorManager::handleEventCommon(const Palapeli::MouseEvent& pEvent, QMap<Palapeli::Interactor*, Palapeli::EventContext>& interactorData, Qt::MouseButtons unhandledButtons)
0119 {
0120     //try to use active triggers where possible
0121     for (Palapeli::Interactor* interactor : std::as_const(m_interactors))
0122         if (interactor->isActive())
0123         {
0124             //fetch flags, and remove them to mark this interactor as processed
0125             EventContext context = interactorData.value(interactor);
0126             interactorData.remove(interactor);
0127             //send event, mark button as processed
0128             if ((unhandledButtons & context.triggeringButtons) || context.triggeringButtons == Qt::NoButton)
0129             {
0130                 interactor->sendEvent(pEvent, context.flags);
0131                 if (interactor->isActive())
0132                     unhandledButtons &= ~context.triggeringButtons;
0133             }
0134         }
0135     //sort remaining interactors by priority (the sorting is done by QMap)
0136     QMultiMap<int, Palapeli::Interactor*> sortedInteractors;
0137     QMapIterator<Palapeli::Interactor*, EventContext> iter1(interactorData);
0138     while (iter1.hasNext())
0139     {
0140         Palapeli::Interactor* interactor = iter1.next().key();
0141         int priority = interactor->priority();
0142         if ((iter1.value().flags & Palapeli::EventMatchesExactly) == Palapeli::EventMatchesExactly)
0143             priority += AdditionalPriorityForExactMatches;
0144         //NOTE: The minus below implements a descending sort order.
0145         sortedInteractors.insert(-priority, interactor);
0146     }
0147     //try to activate interactors with matching triggers
0148     for (Palapeli::Interactor* interactor : std::as_const(sortedInteractors))
0149     {
0150         const EventContext context = interactorData.value(interactor);
0151         //send event, mark button as processed
0152         if ((unhandledButtons & context.triggeringButtons) || context.triggeringButtons == Qt::NoButton)
0153         {
0154             interactor->sendEvent(pEvent, context.flags);
0155             if (interactor->isActive())
0156                 unhandledButtons &= ~context.triggeringButtons;
0157         }
0158         else
0159             interactor->setInactive();
0160     }
0161 }
0162 
0163 #include "moc_interactormanager.cpp"