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"