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