1 /*
2 SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "interactormanager.h"
8 #include "triggermapper.h"
9
10 #include <QKeyEvent>
11 #include <QMouseEvent>
12 #include <QWheelEvent>
13
14 static const int AdditionalPriorityForExactMatches = 10000;
15
InteractorManager(QGraphicsView * view)16 Palapeli::InteractorManager::InteractorManager(QGraphicsView* view)
17 : QObject(view)
18 , m_view(view)
19 , m_interactors(Palapeli::TriggerMapper::createInteractors(view))
20 {
21 connect(Palapeli::TriggerMapper::instance(), &TriggerMapper::associationsChanged, this, &InteractorManager::resetActiveTriggers);
22 }
23
~InteractorManager()24 Palapeli::InteractorManager::~InteractorManager()
25 {
26 qDeleteAll(m_interactors);
27 }
28
updateScene()29 void Palapeli::InteractorManager::updateScene()
30 {
31 for (Palapeli::Interactor* interactor : std::as_const(m_interactors))
32 interactor->updateScene();
33 }
34
resetActiveTriggers()35 void Palapeli::InteractorManager::resetActiveTriggers()
36 {
37 for (Palapeli::Interactor* interactor : std::as_const(m_interactors))
38 interactor->setInactive();
39 }
40
41 /*
42 * Wheel events are delivered to all interactors that accept them.
43 */
handleEvent(QWheelEvent * event)44 void Palapeli::InteractorManager::handleEvent(QWheelEvent* event)
45 {
46 //convert event
47 const QPoint angleDelta = event->angleDelta();
48 const int delta = (qAbs(angleDelta.x()) > qAbs(angleDelta.y())) ? angleDelta.x() : angleDelta.y();
49 Palapeli::WheelEvent pEvent(m_view, event->position().toPoint(), delta);
50 //check which interactors are triggered by this event
51 Palapeli::Interactor* bestMatchInteractor = nullptr;
52 int bestMatchPriority = -1;
53 QMap<QByteArray, Palapeli::Interactor*>::const_iterator it1 = m_interactors.constBegin(), it2 = m_interactors.constEnd();
54 for (; it1 != it2; ++it1)
55 {
56 Palapeli::Interactor* const interactor = it1.value();
57 const Palapeli::EventProcessingFlags flags = Palapeli::TriggerMapper::instance()->testTrigger(it1.key(), event);
58 if (!(flags & Palapeli::EventMatches))
59 continue;
60 int priority = interactor->priority();
61 if ((flags & Palapeli::EventMatchesExactly) == Palapeli::EventMatchesExactly)
62 priority += AdditionalPriorityForExactMatches;
63 if (priority > bestMatchPriority)
64 {
65 bestMatchInteractor = interactor;
66 bestMatchPriority = priority;
67 }
68 }
69 //activate matching interactor with highest priority
70 if (bestMatchInteractor)
71 bestMatchInteractor->sendEvent(pEvent);
72 }
73
74 /*
75 * Unlike wheel events, mouse events are not just delivered to all interactors
76 * that may accept them. Mouse interactions usually consist of a sequence of
77 * press-move-move-...-release events, and we deliver all events of one sequence
78 * to exactly one interactor. The Interactor class manages the activity flag
79 * involved in this operation, and completes incomplete event sequences.
80 */
handleEvent(QMouseEvent * event)81 void Palapeli::InteractorManager::handleEvent(QMouseEvent* event)
82 {
83 //convert event
84 Palapeli::MouseEvent pEvent(m_view, event->pos());
85 //save button state (this information is needed for key events *following* this event, but not available from them)
86 m_buttons = event->buttons();
87 if (event->type() != QEvent::MouseButtonRelease)
88 m_buttons |= event->button();
89 m_mousePos = event->pos();
90 //check which interactors are triggered by this event
91 QMap<Palapeli::Interactor*, Palapeli::EventContext> interactorData;
92 QMap<QByteArray, Palapeli::Interactor*>::const_iterator it1 = m_interactors.constBegin(), it2 = m_interactors.constEnd();
93 for (; it1 != it2; ++it1)
94 interactorData[it1.value()] = Palapeli::TriggerMapper::instance()->testTrigger(it1.key(), event);
95 //further processing in a method which is shared with the KeyEvent handler
96 handleEventCommon(pEvent, interactorData, event->buttons() | event->button());
97 }
98
99 /*
100 * We also need to process KeyPress and KeyRelease events for modifier changes.
101 */
handleEvent(QKeyEvent * event)102 void Palapeli::InteractorManager::handleEvent(QKeyEvent* event)
103 {
104 //convert event
105 Palapeli::MouseEvent pEvent(m_view, m_mousePos);
106 //check which interactors are triggered by this event
107 QMap<Palapeli::Interactor*, Palapeli::EventContext> interactorData;
108 QMap<QByteArray, Palapeli::Interactor*>::const_iterator it1 = m_interactors.constBegin(), it2 = m_interactors.constEnd();
109 for (; it1 != it2; ++it1)
110 interactorData[it1.value()] = Palapeli::TriggerMapper::instance()->testTrigger(it1.key(), event, m_buttons);
111 //further processing in a method which is shared with the MouseEvent handler
112 handleEventCommon(pEvent, interactorData, m_buttons);
113 }
114
115 /*
116 * This is the common base for handleEvent(QMouseEvent*) and handleEvent(QKeyEvent*).
117 */
handleEventCommon(const Palapeli::MouseEvent & pEvent,QMap<Palapeli::Interactor *,Palapeli::EventContext> & interactorData,Qt::MouseButtons unhandledButtons)118 void Palapeli::InteractorManager::handleEventCommon(const Palapeli::MouseEvent& pEvent, QMap<Palapeli::Interactor*, Palapeli::EventContext>& interactorData, Qt::MouseButtons unhandledButtons)
119 {
120 //try to use active triggers where possible
121 for (Palapeli::Interactor* interactor : std::as_const(m_interactors))
122 if (interactor->isActive())
123 {
124 //fetch flags, and remove them to mark this interactor as processed
125 EventContext context = interactorData.value(interactor);
126 interactorData.remove(interactor);
127 //send event, mark button as processed
128 if ((unhandledButtons & context.triggeringButtons) || context.triggeringButtons == Qt::NoButton)
129 {
130 interactor->sendEvent(pEvent, context.flags);
131 if (interactor->isActive())
132 unhandledButtons &= ~context.triggeringButtons;
133 }
134 }
135 //sort remaining interactors by priority (the sorting is done by QMap)
136 QMultiMap<int, Palapeli::Interactor*> sortedInteractors;
137 QMapIterator<Palapeli::Interactor*, EventContext> iter1(interactorData);
138 while (iter1.hasNext())
139 {
140 Palapeli::Interactor* interactor = iter1.next().key();
141 int priority = interactor->priority();
142 if ((iter1.value().flags & Palapeli::EventMatchesExactly) == Palapeli::EventMatchesExactly)
143 priority += AdditionalPriorityForExactMatches;
144 //NOTE: The minus below implements a descending sort order.
145 sortedInteractors.insert(-priority, interactor);
146 }
147 //try to activate interactors with matching triggers
148 for (Palapeli::Interactor* interactor : std::as_const(sortedInteractors))
149 {
150 const EventContext context = interactorData.value(interactor);
151 //send event, mark button as processed
152 if ((unhandledButtons & context.triggeringButtons) || context.triggeringButtons == Qt::NoButton)
153 {
154 interactor->sendEvent(pEvent, context.flags);
155 if (interactor->isActive())
156 unhandledButtons &= ~context.triggeringButtons;
157 }
158 else
159 interactor->setInactive();
160 }
161 }
162
163
164