1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2013 - 2017 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 * Copyright (C) 2015 - 2017 Piotr Wójcik <chocimier@tlen.pl>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **************************************************************************/
20 
21 #include "GesturesManager.h"
22 #include "Application.h"
23 #include "IniSettings.h"
24 #include "JsonSettings.h"
25 #include "SessionsManager.h"
26 #include "SettingsManager.h"
27 
28 #include <QtCore/QJsonArray>
29 #include <QtCore/QJsonObject>
30 #include <QtCore/QMetaEnum>
31 #include <QtCore/QPointer>
32 #include <QtCore/QRegularExpression>
33 #include <QtCore/QTextStream>
34 
35 #include <limits>
36 
37 #define UNKNOWN_GESTURE -1
38 #define NATIVE_GESTURE -2
39 
40 namespace Otter
41 {
42 
Step()43 MouseProfile::Gesture::Step::Step()
44 {
45 }
46 
Step(QEvent::Type typeValue,MouseGestures::MouseAction directionValue,Qt::KeyboardModifiers modifiersValue)47 MouseProfile::Gesture::Step::Step(QEvent::Type typeValue, MouseGestures::MouseAction directionValue, Qt::KeyboardModifiers modifiersValue) : type(typeValue),
48 	modifiers(modifiersValue),
49 	direction(directionValue)
50 {
51 }
52 
Step(QEvent::Type typeValue,Qt::MouseButton buttonValue,Qt::KeyboardModifiers modifiersValue)53 MouseProfile::Gesture::Step::Step(QEvent::Type typeValue, Qt::MouseButton buttonValue, Qt::KeyboardModifiers modifiersValue) : type(typeValue),
54 	button(buttonValue),
55 	modifiers(modifiersValue)
56 {
57 }
58 
Step(const QInputEvent * event)59 MouseProfile::Gesture::Step::Step(const QInputEvent *event) : type(event->type()),
60 	modifiers(event->modifiers())
61 {
62 	switch (event->type())
63 	{
64 		case QEvent::MouseButtonPress:
65 		case QEvent::MouseButtonRelease:
66 		case QEvent::MouseButtonDblClick:
67 			button = static_cast<const QMouseEvent*>(event)->button();
68 
69 			break;
70 		case QEvent::Wheel:
71 			{
72 				const QPoint delta(static_cast<const QWheelEvent*>(event)->angleDelta());
73 
74 				if (qAbs(delta.x()) > qAbs(delta.y()))
75 				{
76 					direction = (delta.x() > 0) ? MouseGestures::MoveRightMouseAction : MouseGestures::MoveLeftMouseAction;
77 				}
78 				else if (qAbs(delta.y()) > 0)
79 				{
80 					direction = (delta.y() > 0) ? MouseGestures::MoveUpMouseAction : MouseGestures::MoveDownMouseAction;
81 				}
82 			}
83 
84 			break;
85 		default:
86 			break;
87 	}
88 }
89 
toString() const90 QString MouseProfile::Gesture::Step::toString() const
91 {
92 	QString string;
93 
94 	switch (type)
95 	{
96 		case QEvent::MouseButtonPress:
97 			string += QLatin1String("press");
98 
99 			break;
100 		case QEvent::MouseButtonRelease:
101 			string += QLatin1String("release");
102 
103 			break;
104 		case QEvent::MouseButtonDblClick:
105 			string += QLatin1String("doubleClick");
106 
107 			break;
108 		case QEvent::Wheel:
109 			string += QLatin1String("scroll");
110 
111 			break;
112 		case QEvent::MouseMove:
113 			string += QLatin1String("move");
114 
115 			break;
116 		default:
117 			break;
118 	}
119 
120 	if (type == QEvent::Wheel || type == QEvent::MouseMove)
121 	{
122 		switch (direction)
123 		{
124 			case MouseGestures::MoveUpMouseAction:
125 				string += QLatin1String("Up");
126 
127 				break;
128 			case MouseGestures::MoveDownMouseAction:
129 				string += QLatin1String("Down");
130 
131 				break;
132 			case MouseGestures::MoveLeftMouseAction:
133 				string += QLatin1String("Left");
134 
135 				break;
136 			case MouseGestures::MoveRightMouseAction:
137 				string += QLatin1String("Right");
138 
139 				break;
140 			case MouseGestures::MoveHorizontallyMouseAction:
141 				string += QLatin1String("Horizontal");
142 
143 				break;
144 			case MouseGestures::MoveVerticallyMouseAction:
145 				string += QLatin1String("Vertical");
146 
147 				break;
148 			default:
149 				break;
150 		}
151 	}
152 	else
153 	{
154 		switch (button)
155 		{
156 			case Qt::LeftButton:
157 				string += QLatin1String("Left");
158 
159 				break;
160 			case Qt::RightButton:
161 				string += QLatin1String("Right");
162 
163 				break;
164 			case Qt::MiddleButton:
165 				string += QLatin1String("Middle");
166 
167 				break;
168 			case Qt::BackButton:
169 				string += QLatin1String("Back");
170 
171 				break;
172 			case Qt::ForwardButton:
173 				string += QLatin1String("Forward");
174 
175 				break;
176 			case Qt::TaskButton:
177 				string += QLatin1String("Task");
178 
179 				break;
180 			default:
181 				break;
182 		}
183 
184 		for (int i = 4; i <= 24; ++i)
185 		{
186 			if (button == static_cast<Qt::MouseButton>(Qt::ExtraButton1 << (i - 1)))
187 			{
188 				string += QStringLiteral("Extra%1").arg(i);
189 			}
190 		}
191 	}
192 
193 	if (modifiers.testFlag(Qt::ShiftModifier))
194 	{
195 		string += QLatin1String("+shift");
196 	}
197 
198 	if (modifiers.testFlag(Qt::ControlModifier))
199 	{
200 		string += QLatin1String("+ctrl");
201 	}
202 
203 	if (modifiers.testFlag(Qt::AltModifier))
204 	{
205 		string += QLatin1String("+alt");
206 	}
207 
208 	if (modifiers.testFlag(Qt::MetaModifier))
209 	{
210 		string += QLatin1String("+meta");
211 	}
212 
213 	return string;
214 }
215 
fromString(const QString & string)216 MouseProfile::Gesture::Step MouseProfile::Gesture::Step::fromString(const QString &string)
217 {
218 	Step step;
219 	const QStringList parts(string.split(QLatin1Char('+')));
220 	const QString event(parts.first());
221 
222 	if (event.startsWith(QLatin1String("press")))
223 	{
224 		step.type = QEvent::MouseButtonPress;
225 	}
226 	else if (event.startsWith(QLatin1String("release")))
227 	{
228 		step.type = QEvent::MouseButtonRelease;
229 	}
230 	else if (event.startsWith(QLatin1String("doubleClick")))
231 	{
232 		step.type = QEvent::MouseButtonDblClick;
233 	}
234 	else if (event.startsWith(QLatin1String("scroll")))
235 	{
236 		step.type = QEvent::Wheel;
237 	}
238 	else if (event.startsWith(QLatin1String("move")))
239 	{
240 		step.type = QEvent::MouseMove;
241 	}
242 
243 	if (step.type == QEvent::Wheel || step.type == QEvent::MouseMove)
244 	{
245 		if (event.endsWith(QLatin1String("Up")))
246 		{
247 			step.direction = MouseGestures::MoveUpMouseAction;
248 		}
249 		else if (event.endsWith(QLatin1String("Down")))
250 		{
251 			step.direction = MouseGestures::MoveDownMouseAction;
252 		}
253 		else if (event.endsWith(QLatin1String("Left")))
254 		{
255 			step.direction = MouseGestures::MoveLeftMouseAction;
256 		}
257 		else if (event.endsWith(QLatin1String("Right")))
258 		{
259 			step.direction = MouseGestures::MoveRightMouseAction;
260 		}
261 		else if (event.endsWith(QLatin1String("Horizontal")))
262 		{
263 			step.direction = MouseGestures::MoveHorizontallyMouseAction;
264 		}
265 		else if (event.endsWith(QLatin1String("Vertical")))
266 		{
267 			step.direction = MouseGestures::MoveVerticallyMouseAction;
268 		}
269 	}
270 	else
271 	{
272 		if (event.endsWith(QLatin1String("Left")))
273 		{
274 			step.button = Qt::LeftButton;
275 		}
276 		else if (event.endsWith(QLatin1String("Right")))
277 		{
278 			step.button = Qt::RightButton;
279 		}
280 		else if (event.endsWith(QLatin1String("Middle")))
281 		{
282 			step.button = Qt::MiddleButton;
283 		}
284 		else if (event.endsWith(QLatin1String("Back")))
285 		{
286 			step.button = Qt::BackButton;
287 		}
288 		else if (event.endsWith(QLatin1String("Forward")))
289 		{
290 			step.button = Qt::ForwardButton;
291 		}
292 		else if (event.endsWith(QLatin1String("Task")))
293 		{
294 			step.button = Qt::TaskButton;
295 		}
296 		else
297 		{
298 			const int number(QRegularExpression(QLatin1String("Extra(\\d{1,2})$")).match(event).captured(1).toInt());
299 
300 			if (1 <= number && number <= 24)
301 			{
302 				step.button = static_cast<Qt::MouseButton>(Qt::ExtraButton1 << (number - 1));
303 			}
304 		}
305 	}
306 
307 	for (int i = 1; i < parts.count(); ++i)
308 	{
309 		if (parts.at(i) == QLatin1String("shift"))
310 		{
311 			step.modifiers |= Qt::ShiftModifier;
312 		}
313 		else if (parts.at(i) == QLatin1String("ctrl"))
314 		{
315 			step.modifiers |= Qt::ControlModifier;
316 		}
317 		else if (parts.at(i) == QLatin1String("alt"))
318 		{
319 			step.modifiers |= Qt::AltModifier;
320 		}
321 		else if (parts.at(i) == QLatin1String("meta"))
322 		{
323 			step.modifiers |= Qt::MetaModifier;
324 		}
325 	}
326 
327 	return step;
328 }
329 
operator ==(const Step & other) const330 bool MouseProfile::Gesture::Step::operator ==(const Step &other) const
331 {
332 	return (type == other.type && button == other.button && direction == other.direction && (modifiers == other.modifiers || type == QEvent::MouseMove));
333 }
334 
operator !=(const Step & other) const335 bool MouseProfile::Gesture::Step::operator !=(const Step &other) const
336 {
337 	return !(*this == other);
338 }
339 
operator ==(const Gesture & other) const340 bool MouseProfile::Gesture::operator ==(const Gesture &other) const
341 {
342 	return (steps == other.steps && parameters == other.parameters && action == other.action);
343 }
344 
MouseProfile(const QString & identifier,LoadMode mode)345 MouseProfile::MouseProfile(const QString &identifier, LoadMode mode) : Addon(),
346 	m_identifier(identifier),
347 	m_isModified(false)
348 {
349 	if (identifier.isEmpty())
350 	{
351 		return;
352 	}
353 
354 	const JsonSettings settings(SessionsManager::getReadableDataPath(QLatin1String("mouse/") + identifier + QLatin1String(".json")));
355 	const QStringList comments(settings.getComment().split(QLatin1Char('\n')));
356 
357 	for (int i = 0; i < comments.count(); ++i)
358 	{
359 		const QString key(comments.at(i).section(QLatin1Char(':'), 0, 0).trimmed());
360 		const QString value(comments.at(i).section(QLatin1Char(':'), 1).trimmed());
361 
362 		if (key == QLatin1String("Title"))
363 		{
364 			m_title = value;
365 		}
366 		else if (key == QLatin1String("Description"))
367 		{
368 			m_description = value;
369 		}
370 		else if (key == QLatin1String("Author"))
371 		{
372 			m_author = value;
373 		}
374 		else if (key == QLatin1String("Version"))
375 		{
376 			m_version = value;
377 		}
378 	}
379 
380 	if (mode == MetaDataOnlyMode)
381 	{
382 		return;
383 	}
384 
385 	const QJsonArray contextsArray(settings.array());
386 
387 	for (int i = 0; i < contextsArray.count(); ++i)
388 	{
389 		const QJsonObject contextObject(contextsArray.at(i).toObject());
390 		const GesturesManager::GesturesContext context(static_cast<GesturesManager::GesturesContext>(GesturesManager::getContextIdentifier(contextObject.value(QLatin1String("context")).toString())));
391 
392 		if (context == GesturesManager::UnknownContext)
393 		{
394 			continue;
395 		}
396 
397 		const QJsonArray gesturesArray(contextObject.value(QLatin1String("gestures")).toArray());
398 
399 		for (int j = 0; j < gesturesArray.count(); ++j)
400 		{
401 			const QJsonObject actionObject(gesturesArray.at(j).toObject());
402 			const QJsonArray stepsArray(actionObject.value(QLatin1String("steps")).toArray());
403 
404 			if (stepsArray.isEmpty())
405 			{
406 				continue;
407 			}
408 
409 			const QString actionIdentifier(actionObject.value(QLatin1String("action")).toString());
410 			const bool isNativeGesture(actionIdentifier == QLatin1String("NoAction"));
411 			const int action(isNativeGesture ? NATIVE_GESTURE : ActionsManager::getActionIdentifier(actionIdentifier));
412 
413 			if (action < 0 && !isNativeGesture)
414 			{
415 				continue;
416 			}
417 
418 			QVector<MouseProfile::Gesture::Step> steps;
419 			steps.reserve(stepsArray.count());
420 
421 			for (int k = 0; k < stepsArray.count(); ++k)
422 			{
423 				steps.append(Gesture::Step::fromString(stepsArray.at(k).toString()));
424 			}
425 
426 			MouseProfile::Gesture definition;
427 			definition.steps = steps;
428 			definition.parameters = actionObject.value(QLatin1String("parameters")).toVariant().toMap();
429 			definition.action = action;
430 
431 			m_definitions[context].append(definition);
432 		}
433 	}
434 }
435 
setTitle(const QString & title)436 void MouseProfile::setTitle(const QString &title)
437 {
438 	if (title != m_title)
439 	{
440 		m_title = title;
441 		m_isModified = true;
442 	}
443 }
444 
setDescription(const QString & description)445 void MouseProfile::setDescription(const QString &description)
446 {
447 	if (description != m_description)
448 	{
449 		m_description = description;
450 		m_isModified = true;
451 	}
452 }
453 
setAuthor(const QString & author)454 void MouseProfile::setAuthor(const QString &author)
455 {
456 	if (author != m_author)
457 	{
458 		m_author = author;
459 		m_isModified = true;
460 	}
461 }
462 
setVersion(const QString & version)463 void MouseProfile::setVersion(const QString &version)
464 {
465 	if (version != m_version)
466 	{
467 		m_version = version;
468 		m_isModified = true;
469 	}
470 }
471 
setDefinitions(const QHash<int,QVector<MouseProfile::Gesture>> & definitions)472 void MouseProfile::setDefinitions(const QHash<int, QVector<MouseProfile::Gesture> > &definitions)
473 {
474 	if (definitions != m_definitions)
475 	{
476 		m_definitions = definitions;
477 		m_isModified = true;
478 	}
479 }
480 
setModified(bool isModified)481 void MouseProfile::setModified(bool isModified)
482 {
483 	m_isModified = isModified;
484 }
485 
getName() const486 QString MouseProfile::getName() const
487 {
488 	return m_identifier;
489 }
490 
getTitle() const491 QString MouseProfile::getTitle() const
492 {
493 	return (m_title.isEmpty() ? QCoreApplication::translate("Otter::MouseProfile", "(Untitled)") : m_title);
494 }
495 
getDescription() const496 QString MouseProfile::getDescription() const
497 {
498 	return m_description;
499 }
500 
getAuthor() const501 QString MouseProfile::getAuthor() const
502 {
503 	return m_author;
504 }
505 
getVersion() const506 QString MouseProfile::getVersion() const
507 {
508 	return m_version;
509 }
510 
getDefinitions() const511 QHash<int, QVector<MouseProfile::Gesture> > MouseProfile::getDefinitions() const
512 {
513 	return m_definitions;
514 }
515 
getType() const516 Addon::AddonType MouseProfile::getType() const
517 {
518 	return Addon::UnknownType;
519 }
520 
isModified() const521 bool MouseProfile::isModified() const
522 {
523 	return m_isModified;
524 }
525 
save()526 bool MouseProfile::save()
527 {
528 	JsonSettings settings(SessionsManager::getWritableDataPath(QLatin1String("mouse/") + m_identifier + QLatin1String(".json")));
529 	QString comment;
530 	QTextStream stream(&comment);
531 	stream.setCodec("UTF-8");
532 	stream << QLatin1String("Title: ") << (m_title.isEmpty() ? QT_TR_NOOP("(Untitled)") : m_title) << QLatin1Char('\n');
533 	stream << QLatin1String("Description: ") << m_description << QLatin1Char('\n');
534 	stream << QLatin1String("Type: mouse-profile\n");
535 	stream << QLatin1String("Author: ") << m_author << QLatin1Char('\n');
536 	stream << QLatin1String("Version: ") << m_version;
537 
538 	settings.setComment(comment);
539 
540 	QJsonArray contextsArray;
541 	QHash<int, QVector<MouseProfile::Gesture> >::const_iterator contextsIterator;
542 
543 	for (contextsIterator = m_definitions.begin(); contextsIterator != m_definitions.end(); ++contextsIterator)
544 	{
545 		QJsonArray gesturesArray;
546 
547 		for (int i = 0; i < contextsIterator.value().count(); ++i)
548 		{
549 			const MouseProfile::Gesture &gesture(contextsIterator.value().at(i));
550 			QJsonArray stepsArray;
551 
552 			for (int j = 0; j < gesture.steps.count(); ++j)
553 			{
554 				stepsArray.append(gesture.steps.at(j).toString());
555 			}
556 
557 			QJsonObject gestureObject{{QLatin1String("action"), ((gesture.action == NATIVE_GESTURE) ? QLatin1String("NoAction") : ActionsManager::getActionName(gesture.action))}, {QLatin1String("steps"), stepsArray}};
558 
559 			if (!gesture.parameters.isEmpty())
560 			{
561 				gestureObject.insert(QLatin1String("parameters"), QJsonObject::fromVariantMap(gesture.parameters));
562 			}
563 
564 			gesturesArray.append(gestureObject);
565 		}
566 
567 		contextsArray.append(QJsonObject({{QLatin1String("context"), GesturesManager::getContextName(contextsIterator.key())}, {QLatin1String("gestures"), gesturesArray}}));
568 	}
569 
570 	settings.setArray(contextsArray);
571 
572 	const bool result(settings.save());
573 
574 	if (result)
575 	{
576 		m_isModified = false;
577 	}
578 
579 	return result;
580 }
581 
582 GesturesManager* GesturesManager::m_instance(nullptr);
583 MouseGestures::Recognizer* GesturesManager::m_recognizer(nullptr);
584 QPointer<QObject> GesturesManager::m_trackedObject(nullptr);
585 QPoint GesturesManager::m_lastClick;
586 QPoint GesturesManager::m_lastPosition;
587 QVariantMap GesturesManager::m_parameters;
588 QHash<GesturesManager::GesturesContext, QVector<MouseProfile::Gesture> > GesturesManager::m_gestures;
589 QHash<GesturesManager::GesturesContext, QVector<QVector<MouseProfile::Gesture::Step> > > GesturesManager::m_nativeGestures;
590 QVector<QInputEvent*> GesturesManager::m_events;
591 QVector<MouseProfile::Gesture::Step> GesturesManager::m_steps;
592 QVector<GesturesManager::GesturesContext> GesturesManager::m_contexts;
593 int GesturesManager::m_gesturesContextEnumerator(0);
594 bool GesturesManager::m_isReleasing(false);
595 bool GesturesManager::m_afterScroll(false);
596 
GesturesManager(QObject * parent)597 GesturesManager::GesturesManager(QObject *parent) : QObject(parent),
598 	m_reloadTimer(0)
599 {
600 	connect(SettingsManager::getInstance(), &SettingsManager::optionChanged, this, &GesturesManager::handleOptionChanged);
601 }
602 
createInstance()603 void GesturesManager::createInstance()
604 {
605 	if (!m_instance)
606 	{
607 		m_nativeGestures[GesturesManager::GenericContext] = {{{QEvent::MouseButtonDblClick, Qt::LeftButton}}, {{QEvent::MouseButtonPress, Qt::LeftButton}, {QEvent::MouseButtonRelease, Qt::LeftButton}}, {{QEvent::MouseButtonPress, Qt::LeftButton}, {QEvent::MouseMove, MouseGestures::UnknownMouseAction}}};
608 		m_nativeGestures[GesturesManager::LinkContext] = {{{QEvent::MouseButtonPress, Qt::LeftButton}, {QEvent::MouseButtonRelease, Qt::LeftButton}}, {{QEvent::MouseButtonPress, Qt::LeftButton}, {QEvent::MouseMove, MouseGestures::UnknownMouseAction}}};
609 		m_nativeGestures[GesturesManager::ContentEditableContext] = {{{QEvent::MouseButtonPress, Qt::MiddleButton}}};
610 		m_nativeGestures[GesturesManager::TabHandleContext] = {{{QEvent::MouseButtonPress, Qt::LeftButton}, {QEvent::MouseMove, MouseGestures::UnknownMouseAction}}};
611 
612 		m_instance = new GesturesManager(QCoreApplication::instance());
613 		m_gesturesContextEnumerator = GesturesManager::staticMetaObject.indexOfEnumerator(QLatin1String("GesturesContext").data());
614 
615 		loadProfiles();
616 	}
617 }
618 
timerEvent(QTimerEvent * event)619 void GesturesManager::timerEvent(QTimerEvent *event)
620 {
621 	if (event->timerId() == m_reloadTimer)
622 	{
623 		killTimer(m_reloadTimer);
624 
625 		m_reloadTimer = 0;
626 
627 		loadProfiles();
628 	}
629 }
630 
loadProfiles()631 void GesturesManager::loadProfiles()
632 {
633 	m_gestures.clear();
634 
635 	MouseProfile::Gesture contextMenuGestureDefinition;
636 	contextMenuGestureDefinition.steps = {MouseProfile::Gesture::Step(QEvent::MouseButtonPress, Qt::RightButton), MouseProfile::Gesture::Step(QEvent::MouseButtonRelease, Qt::RightButton)};
637 	contextMenuGestureDefinition.action = ActionsManager::ContextMenuAction;
638 
639 	for (int i = (UnknownContext + 1); i < OtherContext; ++i)
640 	{
641 		m_gestures[static_cast<GesturesContext>(i)] = {contextMenuGestureDefinition};
642 	}
643 
644 	const QStringList gestureProfiles(SettingsManager::getOption(SettingsManager::Browser_MouseProfilesOrderOption).toStringList());
645 	const bool areMouseGesturesEnabled(SettingsManager::getOption(SettingsManager::Browser_EnableMouseGesturesOption).toBool());
646 
647 	for (int i = 0; i < gestureProfiles.count(); ++i)
648 	{
649 		const MouseProfile profile(gestureProfiles.at(i));
650 		const QHash<int, QVector<MouseProfile::Gesture> > contexts(profile.getDefinitions());
651 		QHash<int, QVector<MouseProfile::Gesture> >::const_iterator iterator;
652 
653 		for (iterator = contexts.begin(); iterator != contexts.end(); ++iterator)
654 		{
655 			const QVector<MouseProfile::Gesture> &gestures(iterator.value());
656 
657 			for (int j = 0; j < gestures.count(); ++j)
658 			{
659 				bool isAllowed(true);
660 
661 				if (!areMouseGesturesEnabled)
662 				{
663 					for (int k = 0; k < gestures.at(j).steps.count(); ++k)
664 					{
665 						if (gestures.at(j).steps.at(k).type == QEvent::MouseMove)
666 						{
667 							isAllowed = false;
668 
669 							break;
670 						}
671 					}
672 				}
673 
674 				if (isAllowed)
675 				{
676 					m_gestures[static_cast<GesturesContext>(iterator.key())].append(gestures.at(j));
677 				}
678 			}
679 		}
680 	}
681 }
682 
recognizeMoveStep(const QInputEvent * event)683 void GesturesManager::recognizeMoveStep(const QInputEvent *event)
684 {
685 	if (!m_recognizer)
686 	{
687 		return;
688 	}
689 
690 	QHash<int, MouseGestures::ActionList> possibleMoves;
691 
692 	for (int i = 0; i < m_contexts.count(); ++i)
693 	{
694 		const QVector<MouseProfile::Gesture> gestures(m_gestures[m_contexts.at(i)]);
695 
696 		for (int j = 0; j < gestures.count(); ++j)
697 		{
698 			const QVector<MouseProfile::Gesture::Step> steps(gestures.at(j).steps);
699 
700 			if (steps.count() > m_steps.count() && steps[m_steps.count()].type == QEvent::MouseMove && steps.mid(0, m_steps.count()) == m_steps)
701 			{
702 				MouseGestures::ActionList moves;
703 
704 				for (int k = m_steps.count(); (k < steps.count() && steps.at(k).type == QEvent::MouseMove); ++k)
705 				{
706 					moves.push_back(steps.at(k).direction);
707 				}
708 
709 				if (!moves.empty())
710 				{
711 					possibleMoves.insert(m_recognizer->registerGesture(moves), moves);
712 				}
713 			}
714 		}
715 	}
716 
717 	const QMouseEvent *mouseEvent(static_cast<const QMouseEvent*>(event));
718 
719 	if (mouseEvent)
720 	{
721 		m_recognizer->addPosition(mouseEvent->pos().x(), mouseEvent->pos().y());
722 	}
723 
724 	const int gesture(m_recognizer->endGesture());
725 	const MouseGestures::ActionList moves(possibleMoves.value(gesture));
726 	MouseGestures::ActionList::const_iterator iterator;
727 
728 	for (iterator = moves.begin(); iterator != moves.end(); ++iterator)
729 	{
730 		m_steps.append(MouseProfile::Gesture::Step(QEvent::MouseMove, *iterator, event->modifiers()));
731 	}
732 
733 	if (m_steps.empty() && calculateLastMoveDistance(true) >= QApplication::startDragDistance())
734 	{
735 		m_steps.append(MouseProfile::Gesture::Step(QEvent::MouseMove, MouseGestures::UnknownMouseAction, event->modifiers()));
736 	}
737 }
738 
cancelGesture()739 void GesturesManager::cancelGesture()
740 {
741 	releaseObject();
742 
743 	m_steps.clear();
744 
745 	qDeleteAll(m_events);
746 
747 	m_events.clear();
748 }
749 
releaseObject()750 void GesturesManager::releaseObject()
751 {
752 	if (m_trackedObject)
753 	{
754 		m_trackedObject->removeEventFilter(m_instance);
755 
756 		disconnect(m_trackedObject, &QObject::destroyed, m_instance, &GesturesManager::endGesture);
757 	}
758 
759 	m_trackedObject = nullptr;
760 }
761 
endGesture()762 void GesturesManager::endGesture()
763 {
764 	cancelGesture();
765 }
766 
handleOptionChanged(int identifier)767 void GesturesManager::handleOptionChanged(int identifier)
768 {
769 	switch (identifier)
770 	{
771 		case SettingsManager::Browser_EnableMouseGesturesOption:
772 		case SettingsManager::Browser_MouseProfilesOrderOption:
773 			if (m_reloadTimer == 0)
774 			{
775 				m_reloadTimer = startTimer(250);
776 			}
777 
778 			break;
779 		default:
780 			break;
781 	}
782 }
783 
getInstance()784 GesturesManager* GesturesManager::getInstance()
785 {
786 	return m_instance;
787 }
788 
getTrackedObject()789 QObject* GesturesManager::getTrackedObject()
790 {
791 	return m_trackedObject;
792 }
793 
getContextName(int identifier)794 QString GesturesManager::getContextName(int identifier)
795 {
796 	QString name(GesturesManager::staticMetaObject.enumerator(m_gesturesContextEnumerator).valueToKey(identifier));
797 
798 	if (!name.isEmpty())
799 	{
800 		name.chop(7);
801 
802 		return name;
803 	}
804 
805 	return {};
806 }
807 
matchGesture()808 MouseProfile::Gesture GesturesManager::matchGesture()
809 {
810 	MouseProfile::Gesture bestGesture;
811 	bestGesture.action = UNKNOWN_GESTURE;
812 
813 	int lowestDifference(std::numeric_limits<int>::max());
814 
815 	for (int i = 0; i < m_contexts.count(); ++i)
816 	{
817 		const QVector<QVector<MouseProfile::Gesture::Step> > nativeGestures(m_nativeGestures[m_contexts.at(i)]);
818 
819 		for (int j = 0; j < nativeGestures.count(); ++j)
820 		{
821 			const int difference(calculateGesturesDifference(nativeGestures.at(j)));
822 
823 			if (difference == 0)
824 			{
825 				MouseProfile::Gesture gesture;
826 				gesture.action = NATIVE_GESTURE;
827 
828 				return gesture;
829 			}
830 
831 			if (difference < lowestDifference)
832 			{
833 				bestGesture = {};
834 				bestGesture.action = NATIVE_GESTURE;
835 
836 				lowestDifference = difference;
837 			}
838 		}
839 
840 		const QVector<MouseProfile::Gesture> gestures(m_gestures[m_contexts.at(i)]);
841 
842 		for (int j = 0; j < gestures.count(); ++j)
843 		{
844 			const int difference(calculateGesturesDifference(gestures.at(j).steps));
845 
846 			if (difference == 0)
847 			{
848 				return gestures.at(j);
849 			}
850 
851 			if (difference < lowestDifference)
852 			{
853 				bestGesture = gestures.at(j);
854 				lowestDifference = difference;
855 			}
856 		}
857 	}
858 
859 	return bestGesture;
860 }
861 
getContextIdentifier(const QString & name)862 int GesturesManager::getContextIdentifier(const QString &name)
863 {
864 	if (!name.endsWith(QLatin1String("Context")))
865 	{
866 		return GesturesManager::staticMetaObject.enumerator(m_gesturesContextEnumerator).keyToValue((name + QLatin1String("Context")).toLatin1());
867 	}
868 
869 	return GesturesManager::staticMetaObject.enumerator(m_gesturesContextEnumerator).keyToValue(name.toLatin1());
870 }
871 
calculateLastMoveDistance(bool measureFinished)872 int GesturesManager::calculateLastMoveDistance(bool measureFinished)
873 {
874 	int result(0);
875 	int index(m_events.count() - 1);
876 
877 	if (!measureFinished && (index < 0 || m_events.at(index)->type() != QEvent::MouseMove))
878 	{
879 		return result;
880 	}
881 
882 	while (index >= 0 && m_events.at(index)->type() != QEvent::MouseMove)
883 	{
884 		--index;
885 	}
886 
887 	for (; index > 0 && m_events.at(index - 1)->type() == QEvent::MouseMove; --index)
888 	{
889 		const QMouseEvent *current(static_cast<QMouseEvent*>(m_events.at(index)));
890 		const QMouseEvent *previous(static_cast<QMouseEvent*>(m_events.at(index - 1)));
891 
892 		if (!current || !previous)
893 		{
894 			break;
895 		}
896 
897 		result += (previous->pos() - current->pos()).manhattanLength();
898 	}
899 
900 	return result;
901 }
902 
calculateGesturesDifference(const QVector<MouseProfile::Gesture::Step> & steps)903 int GesturesManager::calculateGesturesDifference(const QVector<MouseProfile::Gesture::Step> &steps)
904 {
905 	if (m_steps.count() != steps.count())
906 	{
907 		return std::numeric_limits<int>::max();
908 	}
909 
910 	int difference(0);
911 
912 	for (int i = 0; i < steps.count(); ++i)
913 	{
914 		const MouseProfile::Gesture::Step matchedStep(steps.at(i));
915 		const MouseProfile::Gesture::Step recordedStep(m_steps.at(i));
916 		int stepDifference(0);
917 
918 		if (i == (steps.count() - 1) && matchedStep.type == QEvent::MouseButtonPress && recordedStep.type == QEvent::MouseButtonDblClick && matchedStep.button == recordedStep.button && matchedStep.modifiers == recordedStep.modifiers)
919 		{
920 			stepDifference += 100;
921 		}
922 
923 		if (recordedStep.type == matchedStep.type && (matchedStep.type == QEvent::MouseButtonPress || matchedStep.type == QEvent::MouseButtonRelease || matchedStep.type == QEvent::MouseButtonDblClick) && recordedStep.button == matchedStep.button && (recordedStep.modifiers | matchedStep.modifiers) == recordedStep.modifiers)
924 		{
925 			if (recordedStep.modifiers.testFlag(Qt::ControlModifier) && !matchedStep.modifiers.testFlag(Qt::ControlModifier))
926 			{
927 				stepDifference += 8;
928 			}
929 
930 			if (recordedStep.modifiers.testFlag(Qt::ShiftModifier) && !matchedStep.modifiers.testFlag(Qt::ShiftModifier))
931 			{
932 				stepDifference += 4;
933 			}
934 
935 			if (recordedStep.modifiers.testFlag(Qt::AltModifier) && !matchedStep.modifiers.testFlag(Qt::AltModifier))
936 			{
937 				stepDifference += 2;
938 			}
939 
940 			if (recordedStep.modifiers.testFlag(Qt::MetaModifier) && !matchedStep.modifiers.testFlag(Qt::MetaModifier))
941 			{
942 				stepDifference += 1;
943 			}
944 		}
945 
946 		if (stepDifference == 0 && matchedStep != recordedStep)
947 		{
948 			return std::numeric_limits<int>::max();
949 		}
950 
951 		difference += stepDifference;
952 	}
953 
954 	return difference;
955 }
956 
startGesture(QObject * object,QEvent * event,QVector<GesturesContext> contexts,const QVariantMap & parameters)957 bool GesturesManager::startGesture(QObject *object, QEvent *event, QVector<GesturesContext> contexts, const QVariantMap &parameters)
958 {
959 	QInputEvent *inputEvent(static_cast<QInputEvent*>(event));
960 
961 	if (!object || !inputEvent || m_gestures.keys().toSet().intersect(contexts.toList().toSet()).isEmpty() || m_events.contains(inputEvent))
962 	{
963 		return false;
964 	}
965 
966 	m_parameters = parameters;
967 
968 	if (!m_trackedObject)
969 	{
970 		m_contexts = contexts;
971 		m_isReleasing = false;
972 		m_afterScroll = false;
973 	}
974 
975 	createInstance();
976 	releaseObject();
977 
978 	m_trackedObject = object;
979 
980 	if (m_trackedObject)
981 	{
982 		m_trackedObject->installEventFilter(m_instance);
983 
984 		connect(m_trackedObject, &QObject::destroyed, m_instance, &GesturesManager::endGesture);
985 	}
986 
987 	return (m_trackedObject && m_instance->eventFilter(m_trackedObject, event));
988 }
989 
continueGesture(QObject * object)990 bool GesturesManager::continueGesture(QObject *object)
991 {
992 	if (!m_trackedObject)
993 	{
994 		return false;
995 	}
996 
997 	if (!object)
998 	{
999 		cancelGesture();
1000 
1001 		return false;
1002 	}
1003 
1004 	releaseObject();
1005 
1006 	m_trackedObject = object;
1007 	m_trackedObject->installEventFilter(m_instance);
1008 
1009 	connect(m_trackedObject, &QObject::destroyed, m_instance, &GesturesManager::endGesture);
1010 
1011 	return true;
1012 }
1013 
triggerAction(const MouseProfile::Gesture & gesture)1014 bool GesturesManager::triggerAction(const MouseProfile::Gesture &gesture)
1015 {
1016 	if (gesture.action == UNKNOWN_GESTURE)
1017 	{
1018 		return false;
1019 	}
1020 
1021 	m_trackedObject->removeEventFilter(m_instance);
1022 
1023 	if (gesture.action == NATIVE_GESTURE)
1024 	{
1025 		for (int i = 0; i < m_events.count(); ++i)
1026 		{
1027 			QCoreApplication::sendEvent(m_trackedObject, m_events.at(i));
1028 		}
1029 
1030 		cancelGesture();
1031 	}
1032 	else if (gesture.action == ActionsManager::ContextMenuAction)
1033 	{
1034 		if (m_trackedObject)
1035 		{
1036 			QContextMenuEvent event(QContextMenuEvent::Other, m_lastPosition);
1037 
1038 			QCoreApplication::sendEvent(m_trackedObject, &event);
1039 		}
1040 	}
1041 	else
1042 	{
1043 		QVariantMap parameters(gesture.parameters);
1044 		parameters.unite(m_parameters);
1045 
1046 		Application::triggerAction(gesture.action, parameters, m_trackedObject, ActionsManager::MouseTrigger);
1047 	}
1048 
1049 	if (m_trackedObject)
1050 	{
1051 		m_trackedObject->installEventFilter(m_instance);
1052 	}
1053 
1054 	return true;
1055 }
1056 
isTracking()1057 bool GesturesManager::isTracking()
1058 {
1059 	return (m_trackedObject != nullptr);
1060 }
1061 
eventFilter(QObject * object,QEvent * event)1062 bool GesturesManager::eventFilter(QObject *object, QEvent *event)
1063 {
1064 	QMouseEvent *mouseEvent(nullptr);
1065 	MouseProfile::Gesture gesture;
1066 
1067 	switch (event->type())
1068 	{
1069 		case QEvent::MouseButtonPress:
1070 		case QEvent::MouseButtonRelease:
1071 		case QEvent::MouseButtonDblClick:
1072 			mouseEvent = static_cast<QMouseEvent*>(event);
1073 
1074 			if (!mouseEvent)
1075 			{
1076 				break;
1077 			}
1078 
1079 			if (!m_events.isEmpty() && m_events.last()->type() == event->type())
1080 			{
1081 				const QMouseEvent *previousMouseEvent(static_cast<QMouseEvent*>(m_events.last()));
1082 
1083 				if (previousMouseEvent && previousMouseEvent->button() == mouseEvent->button() && previousMouseEvent->modifiers() == mouseEvent->modifiers())
1084 				{
1085 					break;
1086 				}
1087 			}
1088 
1089 			m_events.append(new QMouseEvent(event->type(), mouseEvent->localPos(), mouseEvent->windowPos(), mouseEvent->screenPos(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()));
1090 
1091 			if (m_afterScroll && event->type() == QEvent::MouseButtonRelease)
1092 			{
1093 				break;
1094 			}
1095 
1096 			m_lastPosition = mouseEvent->pos();
1097 			m_lastClick = mouseEvent->pos();
1098 
1099 			recognizeMoveStep(mouseEvent);
1100 
1101 			m_steps.append(MouseProfile::Gesture::Step(mouseEvent));
1102 
1103 			if (m_isReleasing && event->type() == QEvent::MouseButtonRelease)
1104 			{
1105 				for (int i = (m_steps.count() - 1); i >= 0; --i)
1106 				{
1107 					if (m_steps.at(i).button == mouseEvent->button())
1108 					{
1109 						m_steps.removeAt(i);
1110 					}
1111 				}
1112 			}
1113 			else
1114 			{
1115 				m_isReleasing = false;
1116 			}
1117 
1118 			if (m_recognizer)
1119 			{
1120 				delete m_recognizer;
1121 
1122 				m_recognizer = nullptr;
1123 			}
1124 
1125 			gesture = matchGesture();
1126 
1127 			if (triggerAction(gesture))
1128 			{
1129 				m_isReleasing = true;
1130 			}
1131 
1132 			m_afterScroll = false;
1133 
1134 			break;
1135 		case QEvent::MouseMove:
1136 			mouseEvent = static_cast<QMouseEvent*>(event);
1137 
1138 			if (!mouseEvent)
1139 			{
1140 				break;
1141 			}
1142 
1143 			m_events.append(new QMouseEvent(event->type(), mouseEvent->localPos(), mouseEvent->windowPos(), mouseEvent->screenPos(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()));
1144 
1145 			m_afterScroll = false;
1146 
1147 			if (!m_recognizer)
1148 			{
1149 				m_recognizer = new MouseGestures::Recognizer();
1150 				m_recognizer->startGesture(m_lastClick.x(), m_lastClick.y());
1151 			}
1152 
1153 			m_lastPosition = mouseEvent->pos();
1154 
1155 			m_recognizer->addPosition(mouseEvent->pos().x(), mouseEvent->pos().y());
1156 
1157 			if (calculateLastMoveDistance() >= QApplication::startDragDistance())
1158 			{
1159 				m_steps.append(MouseProfile::Gesture::Step(QEvent::MouseMove, MouseGestures::UnknownMouseAction, mouseEvent->modifiers()));
1160 
1161 				gesture = matchGesture();
1162 
1163 				if (gesture.action != UNKNOWN_GESTURE)
1164 				{
1165 					recognizeMoveStep(mouseEvent);
1166 
1167 					triggerAction(gesture);
1168 				}
1169 				else
1170 				{
1171 					m_steps.pop_back();
1172 				}
1173 			}
1174 
1175 			break;
1176 		case QEvent::Wheel:
1177 			{
1178 				const QWheelEvent *wheelEvent(static_cast<QWheelEvent*>(event));
1179 
1180 				if (!wheelEvent)
1181 				{
1182 					break;
1183 				}
1184 
1185 				m_events.append(new QWheelEvent(wheelEvent->pos(), wheelEvent->globalPos(), wheelEvent->pixelDelta(), wheelEvent->angleDelta(), wheelEvent->delta(), wheelEvent->orientation(), wheelEvent->buttons(), wheelEvent->modifiers()));
1186 
1187 				recognizeMoveStep(wheelEvent);
1188 
1189 				m_steps.append(MouseProfile::Gesture::Step(wheelEvent));
1190 
1191 				m_lastClick = wheelEvent->pos();
1192 
1193 				if (m_recognizer)
1194 				{
1195 					delete m_recognizer;
1196 
1197 					m_recognizer = nullptr;
1198 				}
1199 
1200 				gesture = matchGesture();
1201 
1202 				triggerAction(gesture);
1203 
1204 				while (!m_steps.isEmpty() && m_steps.at(m_steps.count() - 1).type == QEvent::Wheel)
1205 				{
1206 					m_steps.removeAt(m_steps.count() - 1);
1207 				}
1208 
1209 				while (!m_events.isEmpty() && m_events.at(m_events.count() - 1)->type() == QEvent::Wheel)
1210 				{
1211 					m_events.removeAt(m_events.count() - 1);
1212 				}
1213 
1214 				m_afterScroll = true;
1215 
1216 				break;
1217 			}
1218 		default:
1219 			return QObject::eventFilter(object, event);
1220 	}
1221 
1222 	if (m_trackedObject && mouseEvent && mouseEvent->buttons() == Qt::NoButton)
1223 	{
1224 		cancelGesture();
1225 	}
1226 
1227 	return (!m_steps.isEmpty() || gesture.action != UNKNOWN_GESTURE);
1228 }
1229 
1230 }
1231