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 ¶meters)
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