Marble

MarbleInputHandler.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
4// SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
5// SPDX-FileCopyrightText: 2014 Adam Dabrowski <adamdbrw@gmail.com>
6//
7
8#include "MarbleInputHandler.h"
9
10#include <QCursor>
11#include <QGestureEvent>
12#include <QMouseEvent>
13#include <QPinchGesture>
14#include <QPixmap>
15#include <QPoint>
16#include <QPointer>
17#include <QTimer>
18
19#include "AbstractDataPluginItem.h"
20#include "AbstractFloatItem.h"
21#include "AbstractProjection.h"
22#include "MarbleAbstractPresenter.h"
23#include "MarbleDebug.h"
24#include "MarbleGlobal.h"
25#include "MarbleMap.h"
26#include "RenderPlugin.h"
27#include "ViewportParams.h"
28#include "kineticmodel.h"
29
30namespace Marble
31{
32
33const int TOOLTIP_START_INTERVAL = 1000;
34
35class Q_DECL_HIDDEN MarbleInputHandler::Protected
36{
37public:
38 Protected(MarbleAbstractPresenter *marblePresenter);
39
40 MarbleAbstractPresenter *const m_marblePresenter;
41 bool m_positionSignalConnected;
42 QTimer *m_mouseWheelTimer;
43 Qt::MouseButtons m_disabledMouseButtons;
44 qreal m_wheelZoomTargetDistance;
45 bool m_panViaArrowsEnabled;
46 bool m_inertialEarthRotation;
47 bool m_mouseViewRotation;
48 int m_steps;
49 const int m_discreteZoomSteps = 120;
50};
51
52MarbleInputHandler::Protected::Protected(MarbleAbstractPresenter *marblePresenter)
53 : m_marblePresenter(marblePresenter)
54 , m_positionSignalConnected(false)
55 , m_mouseWheelTimer(nullptr)
56 , m_disabledMouseButtons(Qt::NoButton)
57 , m_wheelZoomTargetDistance(0.0)
58 , m_panViaArrowsEnabled(true)
59 , m_inertialEarthRotation(true)
60 , m_mouseViewRotation(true)
61 , m_steps(0)
62{
63}
64
65MarbleInputHandler::MarbleInputHandler(MarbleAbstractPresenter *marblePresenter)
66 : d(new Protected(marblePresenter))
67{
68 d->m_mouseWheelTimer = new QTimer(this);
69 connect(d->m_mouseWheelTimer, &QTimer::timeout, this, &MarbleInputHandler::restoreViewContext);
70
71 connect(d->m_marblePresenter->map(), SIGNAL(renderPluginInitialized(RenderPlugin *)), this, SLOT(installPluginEventFilter(RenderPlugin *)));
72}
73
74MarbleInputHandler::~MarbleInputHandler()
75{
76 delete d->m_mouseWheelTimer;
77 delete d;
78}
79
80void MarbleInputHandler::setPositionSignalConnected(bool connected)
81{
82 d->m_positionSignalConnected = connected;
83}
84
85bool MarbleInputHandler::isPositionSignalConnected() const
86{
87 return d->m_positionSignalConnected;
88}
89
90void MarbleInputHandler::setMouseButtonPopupEnabled(Qt::MouseButton mouseButton, bool enabled)
91{
92 if (enabled) {
93 d->m_disabledMouseButtons &= ~Qt::MouseButtons(mouseButton);
94 } else {
95 d->m_disabledMouseButtons |= mouseButton;
96 }
97}
98
99bool MarbleInputHandler::isMouseButtonPopupEnabled(Qt::MouseButton mouseButton) const
100{
101 return !(d->m_disabledMouseButtons & mouseButton);
102}
103
104void MarbleInputHandler::setPanViaArrowsEnabled(bool enabled)
105{
106 d->m_panViaArrowsEnabled = enabled;
107}
108
109bool MarbleInputHandler::panViaArrowsEnabled() const
110{
111 return d->m_panViaArrowsEnabled;
112}
113
114void MarbleInputHandler::setInertialEarthRotationEnabled(bool enabled)
115{
116 d->m_inertialEarthRotation = enabled;
117}
118
119bool MarbleInputHandler::inertialEarthRotationEnabled() const
120{
121 return d->m_inertialEarthRotation;
122}
123
124void MarbleInputHandler::setMouseViewRotationEnabled(bool enabled)
125{
126 d->m_mouseViewRotation = enabled;
127}
128
129bool MarbleInputHandler::mouseViewRotationEnabled() const
130{
131 return d->m_mouseViewRotation;
132}
133
134void MarbleInputHandler::stopInertialEarthRotation()
135{
136}
137
138class Q_DECL_HIDDEN MarbleDefaultInputHandler::Private
139{
140public:
141 Private();
142 ~Private();
143
144 QPixmap m_curpmtl;
145 QPixmap m_curpmtc;
146 QPixmap m_curpmtr;
147 QPixmap m_curpmcr;
148 QPixmap m_curpmcl;
149 QPixmap m_curpmbl;
150 QPixmap m_curpmbc;
151 QPixmap m_curpmbr;
152
153 QCursor m_arrowCur[3][3];
154
155 // Indicates if the left mouse button has been pressed already.
156 bool m_leftPressed;
157 // Indicates if the middle mouse button has been pressed already.
158 bool m_midPressed;
159 // The mouse pointer x position when the left mouse button has been pressed.
160 int m_leftPressedX;
161 // The mouse pointer y position when the left mouse button has been pressed.
162 int m_leftPressedY;
163 // The mouse pointer y position when the middle mouse button has been pressed.
164 int m_midPressedY;
165 int m_startingRadius;
166
167 // Indicates if the right mouse button has been pressed already.
168 bool m_rightPressed;
169 // Point where the right mouse button has been pressed on.
170 QPoint m_rightOrigin;
171 // Position to calculate the heading.
172 // Indicates previous position since mouse has been moved.
173 QPoint m_rightPosition;
174 // Indicates the heading when the right mouse button has been pressed
175 // and mouse is moving.
176 qreal m_heading;
177
178 // The center longitude in radian when the left mouse button has been pressed.
179 qreal m_leftPressedLon;
180 // The center latitude in radian when the left mouse button has been pressed.
181 qreal m_leftPressedLat;
182
183 int m_dragThreshold;
184 QTimer m_lmbTimer;
185 QTimer m_pressAndHoldTimer;
186
187 // Models to handle the kinetic spinning.
188 KineticModel m_kineticSpinning;
189
190 QPoint m_selectionOrigin;
191
192 QPointer<AbstractDataPluginItem> m_lastToolTipItem;
193 QTimer m_toolTipTimer;
194 QPoint m_toolTipPosition;
195};
196
197MarbleDefaultInputHandler::Private::Private()
198 : m_leftPressed(false)
199 , m_midPressed(false)
200 , m_rightPressed(false)
201 , m_heading(0)
202 , m_dragThreshold(MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ? 15 : 3)
203{
204 m_curpmtl.load(QStringLiteral(":/marble/cursor/tl.png"));
205 m_curpmtc.load(QStringLiteral(":/marble/cursor/tc.png"));
206 m_curpmtr.load(QStringLiteral(":/marble/cursor/tr.png"));
207 m_curpmcr.load(QStringLiteral(":/marble/cursor/cr.png"));
208 m_curpmcl.load(QStringLiteral(":/marble/cursor/cl.png"));
209 m_curpmbl.load(QStringLiteral(":/marble/cursor/bl.png"));
210 m_curpmbc.load(QStringLiteral(":/marble/cursor/bc.png"));
211 m_curpmbr.load(QStringLiteral(":/marble/cursor/br.png"));
212
213 m_arrowCur[0][0] = QCursor(m_curpmtl, 2, 2);
214 m_arrowCur[1][0] = QCursor(m_curpmtc, 10, 3);
215 m_arrowCur[2][0] = QCursor(m_curpmtr, 19, 2);
216 m_arrowCur[0][1] = QCursor(m_curpmcl, 3, 10);
217 m_arrowCur[1][1] = QCursor(Qt::OpenHandCursor);
218 m_arrowCur[2][1] = QCursor(m_curpmcr, 18, 10);
219 m_arrowCur[0][2] = QCursor(m_curpmbl, 2, 19);
220 m_arrowCur[1][2] = QCursor(m_curpmbc, 11, 18);
221 m_arrowCur[2][2] = QCursor(m_curpmbr, 19, 19);
222}
223
224MarbleDefaultInputHandler::Private::~Private() = default;
225
226MarbleDefaultInputHandler::MarbleDefaultInputHandler(MarbleAbstractPresenter *marblePresenter)
227 : MarbleInputHandler(marblePresenter)
228 , d(new Private())
229{
230 d->m_toolTipTimer.setSingleShot(true);
231 d->m_toolTipTimer.setInterval(TOOLTIP_START_INTERVAL);
232 connect(&d->m_toolTipTimer, SIGNAL(timeout()), this, SLOT(openItemToolTip()));
233 d->m_lmbTimer.setSingleShot(true);
234 connect(&d->m_lmbTimer, SIGNAL(timeout()), this, SLOT(lmbTimeout()));
235
236 d->m_kineticSpinning.setUpdateInterval(35);
237 connect(&d->m_kineticSpinning, SIGNAL(positionChanged(qreal, qreal)), MarbleInputHandler::d->m_marblePresenter, SLOT(centerOn(qreal, qreal)));
238 connect(&d->m_kineticSpinning, SIGNAL(headingChanged(qreal)), MarbleInputHandler::d->m_marblePresenter, SLOT(headingOn(qreal)));
239 connect(&d->m_kineticSpinning, SIGNAL(finished()), SLOT(restoreViewContext()));
240
241 // Left and right mouse button signals.
242 connect(this, SIGNAL(rmbRequest(int, int)), this, SLOT(showRmbMenu(int, int)));
243 connect(this, SIGNAL(lmbRequest(int, int)), this, SLOT(showLmbMenu(int, int)));
244
245 d->m_pressAndHoldTimer.setInterval(800);
246 d->m_pressAndHoldTimer.setSingleShot(true);
247 connect(&d->m_pressAndHoldTimer, SIGNAL(timeout()), this, SLOT(handlePressAndHold()));
248}
249
250MarbleDefaultInputHandler::~MarbleDefaultInputHandler()
251{
252 delete d;
253}
254
255void MarbleDefaultInputHandler::stopInertialEarthRotation()
256{
257 d->m_kineticSpinning.stop();
258}
259
260void MarbleDefaultInputHandler::lmbTimeout()
261{
262 if (!selectionRubber()->isVisible()) {
263 qreal clickedLon = 0;
264 qreal clickedLat = 0;
265
266 bool isPointOnGlobe = MarbleInputHandler::d->m_marblePresenter->map()->geoCoordinates(d->m_leftPressedX,
267 d->m_leftPressedY,
268 clickedLon,
269 clickedLat,
270 GeoDataCoordinates::Degree);
271 Q_EMIT lmbRequest(d->m_leftPressedX, d->m_leftPressedY);
272
273 /**
274 * Q_EMIT mouse click only when the clicked
275 * position is within the globe.
276 */
277 if (isPointOnGlobe) {
278 Q_EMIT mouseClickGeoPosition(clickedLon, clickedLat, GeoDataCoordinates::Degree);
279 }
280 }
281}
282
283void MarbleInputHandler::restoreViewContext()
284{
285 // Needs to stop the timer since it repeats otherwise.
286 d->m_mouseWheelTimer->stop();
287
288 // Redraw the map with the quality set for Still (if necessary).
289 d->m_marblePresenter->setViewContext(Still);
290 d->m_marblePresenter->map()->viewport()->resetFocusPoint();
291 d->m_wheelZoomTargetDistance = 0.0;
292}
293
294void MarbleDefaultInputHandler::hideSelectionIfCtrlReleased(QEvent *e)
295{
296 if (selectionRubber()->isVisible() && e->type() == QEvent::MouseMove) {
297 auto event = static_cast<QMouseEvent *>(e);
298 if (!(event->modifiers() & Qt::ControlModifier)) {
299 selectionRubber()->hide();
300 }
301 }
302}
303
304bool MarbleDefaultInputHandler::handleDoubleClick(QMouseEvent *event)
305{
306 qreal mouseLon;
307 qreal mouseLat;
308 const bool isMouseAboveMap =
309 MarbleInputHandler::d->m_marblePresenter->map()->geoCoordinates(event->x(), event->y(), mouseLon, mouseLat, GeoDataCoordinates::Radian);
310 if (isMouseAboveMap) {
311 d->m_pressAndHoldTimer.stop();
312 d->m_lmbTimer.stop();
313 MarbleInputHandler::d->m_marblePresenter->moveTo(event->pos(), 0.67);
314 }
315 return acceptMouse();
316}
317
318bool MarbleDefaultInputHandler::handleWheel(QWheelEvent *wheelevt)
319{
320 MarbleAbstractPresenter *marblePresenter = MarbleInputHandler::d->m_marblePresenter;
321 marblePresenter->setViewContext(Animation);
322
323 if ((MarbleInputHandler::d->m_steps > 0 && wheelevt->angleDelta().y() < 0) || (MarbleInputHandler::d->m_steps < 0 && wheelevt->angleDelta().y() > 0)) {
324 MarbleInputHandler::d->m_steps = wheelevt->angleDelta().y();
325 } else {
326 MarbleInputHandler::d->m_steps += wheelevt->angleDelta().y();
327 }
328
329 if (marblePresenter->map()->discreteZoom()) {
330 if (qAbs(MarbleInputHandler::d->m_steps) >= MarbleInputHandler::d->m_discreteZoomSteps) {
331 marblePresenter->zoomAtBy(wheelevt->position().toPoint(), MarbleInputHandler::d->m_steps);
332 MarbleInputHandler::d->m_steps = 0;
333 }
334 } else {
335 qreal zoom = marblePresenter->zoom();
336 qreal target = MarbleInputHandler::d->m_wheelZoomTargetDistance;
337 if (marblePresenter->animationsEnabled() && target > 0.0) {
338 // Do not use intermediate (interpolated) distance values caused by animations
339 zoom = marblePresenter->zoomFromDistance(target);
340 }
341 qreal newDistance = marblePresenter->distanceFromZoom(zoom + MarbleInputHandler::d->m_steps);
342 MarbleInputHandler::d->m_wheelZoomTargetDistance = newDistance;
343 marblePresenter->zoomAt(wheelevt->position().toPoint(), newDistance);
344 if (MarbleInputHandler::d->m_inertialEarthRotation) {
345 d->m_kineticSpinning.jumpToPosition(MarbleInputHandler::d->m_marblePresenter->centerLongitude(),
346 MarbleInputHandler::d->m_marblePresenter->centerLatitude());
347 }
348 MarbleInputHandler::d->m_steps = 0;
349 }
350
351 MarbleInputHandler::d->m_mouseWheelTimer->start(400);
352 return true;
353}
354
355bool MarbleDefaultInputHandler::handlePinch(const QPointF &center, qreal scaleFactor, Qt::GestureState state)
356{
357 qreal destLat;
358 qreal destLon;
359
360 MarbleAbstractPresenter *marblePresenter = MarbleInputHandler::d->m_marblePresenter;
361
362 bool isValid = marblePresenter->map()->geoCoordinates(center.x(), center.y(), destLon, destLat, GeoDataCoordinates::Radian);
363
364 if (isValid) {
365 marblePresenter->map()->viewport()->setFocusPoint(GeoDataCoordinates(destLon, destLat));
366 }
367
368 qreal zoom, target, newDistance;
369
370 qreal zoomDelta = scaleFactor > 1.0 ? scaleFactor : -1.0 / scaleFactor;
371
372 switch (state) {
373 case Qt::NoGesture:
374 break;
376 marblePresenter->setViewContext(Animation);
377 d->m_pressAndHoldTimer.stop();
378 d->m_lmbTimer.stop();
379 d->m_midPressed = false;
380 d->m_leftPressed = false;
381 break;
383 zoom = marblePresenter->zoom();
384 target = MarbleInputHandler::d->m_wheelZoomTargetDistance;
385 if (marblePresenter->animationsEnabled() && target > 0.0) {
386 // Do not use intermediate (interpolated) distance values caused by animations
387 zoom = marblePresenter->zoomFromDistance(target);
388 }
389 newDistance = marblePresenter->distanceFromZoom(zoom + 20 * zoomDelta);
390 MarbleInputHandler::d->m_wheelZoomTargetDistance = newDistance;
391 marblePresenter->zoomAt(center.toPoint(), newDistance);
392 break;
394 marblePresenter->map()->viewport()->resetFocusPoint();
395 marblePresenter->setViewContext(Still);
396 break;
398 marblePresenter->map()->viewport()->resetFocusPoint();
399 marblePresenter->setViewContext(Still);
400 break;
401 }
402 return true;
403}
404
405bool MarbleDefaultInputHandler::handleGesture(QGestureEvent *ge)
406{
407 auto pinch = static_cast<QPinchGesture *>(ge->gesture(Qt::PinchGesture));
408 if (!pinch) {
409 return false;
410 }
411
412 qreal scaleFactor = pinch->scaleFactor();
413 QPointF center = pinch->centerPoint();
414
415 return handlePinch(center, scaleFactor, pinch->state());
416}
417
418void MarbleDefaultInputHandler::checkReleasedMove(QMouseEvent *event)
419{
420 // To prevent error from lost MouseButtonRelease events
421 if (event->type() == QEvent::MouseMove && !(event->buttons() & Qt::LeftButton)) {
422 if (d->m_leftPressed) {
423 d->m_leftPressed = false;
424
425 if (MarbleInputHandler::d->m_inertialEarthRotation) {
426 d->m_kineticSpinning.start();
427 } else {
428 MarbleInputHandler::d->m_marblePresenter->setViewContext(Still);
429 }
430 }
431 }
432 if (event->type() == QEvent::MouseMove && !(event->buttons() & Qt::MiddleButton)) {
433 d->m_midPressed = false;
434 }
435}
436
437void MarbleDefaultInputHandler::handleMouseButtonPress(QMouseEvent *event)
438{
439 if (event->button() == Qt::LeftButton) {
440 d->m_pressAndHoldTimer.start();
441 handleLeftMouseButtonPress(event);
442 }
443
444 if (event->button() == Qt::MiddleButton) {
445 handleMiddleMouseButtonPress(event);
446 }
447
448 if (event->button() == Qt::RightButton) {
449 handleRightMouseButtonPress(event);
450 }
451}
452
453void MarbleDefaultInputHandler::handleLeftMouseButtonPress(QMouseEvent *event)
454{
455 // silently enable the animation context without triggering a repaint
456 MarbleInputHandler::d->m_marblePresenter->map()->blockSignals(true);
457 MarbleInputHandler::d->m_marblePresenter->setViewContext(Animation);
458 MarbleInputHandler::d->m_marblePresenter->map()->blockSignals(false);
459
460 if (isMouseButtonPopupEnabled(Qt::LeftButton)) {
461 d->m_lmbTimer.start(400);
462 }
463
464 d->m_leftPressed = true;
465 d->m_midPressed = false;
466 selectionRubber()->hide();
467
468 // On the single event of a mouse button press these
469 // values get stored, to enable us to e.g. calculate the
470 // distance of a mouse drag while the mouse button is
471 // still down.
472 d->m_leftPressedX = event->x();
473 d->m_leftPressedY = event->y();
474
475 // Calculate translation of center point
476 d->m_leftPressedLon = MarbleInputHandler::d->m_marblePresenter->centerLongitude();
477 d->m_leftPressedLat = MarbleInputHandler::d->m_marblePresenter->centerLatitude();
478
479 if (MarbleInputHandler::d->m_inertialEarthRotation) {
480 d->m_kineticSpinning.stop();
481 d->m_kineticSpinning.setPosition(d->m_leftPressedLon, d->m_leftPressedLat);
482 }
483
484 if (event->modifiers() & Qt::ControlModifier) {
485 mDebug() << "Starting selection";
486 d->m_pressAndHoldTimer.stop();
487 d->m_lmbTimer.stop();
488 d->m_selectionOrigin = event->pos();
489 selectionRubber()->setGeometry(QRect(d->m_selectionOrigin, QSize()));
490 selectionRubber()->show();
491 }
492}
493
494void MarbleDefaultInputHandler::handleMiddleMouseButtonPress(QMouseEvent *event)
495{
496 d->m_midPressed = true;
497 d->m_leftPressed = false;
498 d->m_startingRadius = MarbleInputHandler::d->m_marblePresenter->radius();
499 d->m_midPressedY = event->y();
500
501 if (MarbleInputHandler::d->m_inertialEarthRotation) {
502 d->m_kineticSpinning.start();
503 }
504
505 selectionRubber()->hide();
506 MarbleInputHandler::d->m_marblePresenter->setViewContext(Animation);
507}
508
509void MarbleDefaultInputHandler::handleRightMouseButtonPress(QMouseEvent *event)
510{
511 d->m_rightPressed = true;
512 d->m_rightOrigin = event->pos();
513 d->m_rightPosition = event->pos();
514 d->m_heading = MarbleInputHandler::d->m_marblePresenter->map()->heading();
515 if (MarbleInputHandler::d->m_inertialEarthRotation) {
516 d->m_kineticSpinning.stop();
517 d->m_kineticSpinning.setHeading(d->m_heading);
518 }
519}
520
521void MarbleDefaultInputHandler::handleMouseButtonRelease(QMouseEvent *event)
522{
523 if (event->button() == Qt::LeftButton) {
524 d->m_pressAndHoldTimer.stop();
525 // Q_EMIT current coordinates to be interpreted
526 // as requested
527 Q_EMIT mouseClickScreenPosition(d->m_leftPressedX, d->m_leftPressedY);
528
529 d->m_leftPressed = false;
530 if (MarbleInputHandler::d->m_inertialEarthRotation) {
531 d->m_kineticSpinning.start();
532 } else {
533 MarbleInputHandler::d->m_marblePresenter->setViewContext(Still);
534 }
535 }
536
537 if (event->button() == Qt::MiddleButton) {
538 d->m_midPressed = false;
539
540 MarbleInputHandler::d->m_marblePresenter->setViewContext(Still);
541 }
542
543 if (event->type() == QEvent::MouseButtonRelease && event->button() == Qt::RightButton) {
544 if (d->m_rightOrigin == event->pos()) {
545 Q_EMIT rmbRequest(event->x(), event->y());
546 }
547 d->m_rightPressed = false;
548
549 if (MarbleInputHandler::d->m_inertialEarthRotation) {
550 d->m_kineticSpinning.start();
551 } else {
552 MarbleInputHandler::d->m_marblePresenter->setViewContext(Still);
553 }
554 }
555
556 if (event->type() == QEvent::MouseButtonRelease && event->button() == Qt::LeftButton && selectionRubber()->isVisible()) {
557 mDebug() << "Leaving selection";
558 MarbleInputHandler::d->m_marblePresenter->setSelection(selectionRubber()->geometry());
559 selectionRubber()->hide();
560 }
561}
562
563void MarbleDefaultInputHandler::notifyPosition(bool isMouseAboveMap, qreal mouseLon, qreal mouseLat)
564{
565 // Q_EMIT the position string only if the signal got attached
566 if (MarbleInputHandler::d->m_positionSignalConnected) {
567 if (!isMouseAboveMap) {
568 Q_EMIT mouseMoveGeoPosition(QCoreApplication::translate("Marble", NOT_AVAILABLE));
569 } else {
570 QString position = GeoDataCoordinates(mouseLon, mouseLat).toString();
571 Q_EMIT mouseMoveGeoPosition(position);
572 }
573 }
574}
575
576void MarbleDefaultInputHandler::adjustCursorShape(const QPoint &mousePosition, const QPoint &mouseDirection)
577{
578 // Find out if there are data items and if one has defined an action
579 QList<AbstractDataPluginItem *> dataItems = MarbleInputHandler::d->m_marblePresenter->map()->whichItemAt(mousePosition);
580 bool dataAction = false;
584 for (; it != end && dataAction == false && toolTipItem.isNull(); ++it) {
585 if ((*it)->action()) {
586 dataAction = true;
587 }
588
589 if (!(*it)->toolTip().isNull() && toolTipItem.isNull()) {
590 toolTipItem = (*it);
591 }
592 }
593
594 if (toolTipItem.isNull()) {
595 d->m_toolTipTimer.stop();
596 } else if (!(d->m_lastToolTipItem.data() == toolTipItem.data())) {
597 d->m_toolTipTimer.start();
598 d->m_lastToolTipItem = toolTipItem;
599 d->m_toolTipPosition = mousePosition;
600 } else {
601 if (!d->m_toolTipTimer.isActive()) {
602 d->m_toolTipTimer.start();
603 }
604 d->m_toolTipPosition = mousePosition;
605 }
606
607 if (!dataAction && !MarbleInputHandler::d->m_marblePresenter->map()->hasFeatureAt(mousePosition)) {
608 if (!d->m_leftPressed) {
609 d->m_arrowCur[1][1] = QCursor(Qt::OpenHandCursor);
610 } else {
611 d->m_arrowCur[1][1] = QCursor(Qt::ClosedHandCursor);
612 }
613 } else {
614 if (!d->m_leftPressed) {
615 d->m_arrowCur[1][1] = QCursor(Qt::PointingHandCursor);
616 }
617 }
618
619 if (panViaArrowsEnabled()) {
620 setCursor(d->m_arrowCur[mouseDirection.x() + 1][mouseDirection.y() + 1]);
621 } else {
622 setCursor(d->m_arrowCur[1][1]);
623 }
624}
625
626QPoint MarbleDefaultInputHandler::mouseMovedOutside(QMouseEvent *event)
627{ // Returns a 2d vector representing the direction in which the mouse left
628 int dirX = 0;
629 int dirY = 0;
630 int polarity = MarbleInputHandler::d->m_marblePresenter->viewport()->polarity();
631
632 if (d->m_leftPressed) {
633 d->m_leftPressed = false;
634
635 if (MarbleInputHandler::d->m_inertialEarthRotation) {
636 d->m_kineticSpinning.start();
637 }
638 }
639
640 QRect boundingRect = MarbleInputHandler::d->m_marblePresenter->viewport()->mapRegion().boundingRect();
641
642 if (boundingRect.width() != 0) {
643 dirX = (int)(3 * (event->x() - boundingRect.left()) / boundingRect.width()) - 1;
644 }
645 if (dirX > 1) {
646 dirX = 1;
647 }
648 if (dirX < -1) {
649 dirX = -1;
650 }
651
652 if (boundingRect.height() != 0) {
653 dirY = (int)(3 * (event->y() - boundingRect.top()) / boundingRect.height()) - 1;
654 }
655 if (dirY > 1) {
656 dirY = 1;
657 }
658 if (dirY < -1) {
659 dirY = -1;
660 }
661
662 if (event->button() == Qt::LeftButton && event->type() == QEvent::MouseButtonPress && panViaArrowsEnabled() && !d->m_kineticSpinning.hasVelocity()) {
663 d->m_pressAndHoldTimer.stop();
664 d->m_lmbTimer.stop();
665 qreal moveStep = MarbleInputHandler::d->m_marblePresenter->moveStep();
666 if (polarity < 0) {
667 MarbleInputHandler::d->m_marblePresenter->rotateBy(-moveStep * (qreal)(+dirX), moveStep * (qreal)(+dirY));
668 } else {
669 MarbleInputHandler::d->m_marblePresenter->rotateBy(-moveStep * (qreal)(-dirX), moveStep * (qreal)(+dirY));
670 }
671 }
672
673 if (!MarbleInputHandler::d->m_inertialEarthRotation) {
674 MarbleInputHandler::d->m_marblePresenter->setViewContext(Still);
675 }
676
677 return {dirX, dirY};
678}
679
680bool MarbleDefaultInputHandler::handleMouseEvent(QMouseEvent *event)
681{
682 QPoint direction;
683
684 checkReleasedMove(event);
685
686 // Do not handle (and therefore eat) mouse press and release events
687 // that occur above visible float items. Mouse motion events are still
688 // handled, however.
689 if (event->type() != QEvent::MouseMove && !selectionRubber()->isVisible()) {
690 auto const floatItems = MarbleInputHandler::d->m_marblePresenter->map()->floatItems();
691 for (AbstractFloatItem *floatItem : floatItems) {
692 if (floatItem->enabled() && floatItem->visible() && floatItem->contains(event->pos())) {
693 d->m_pressAndHoldTimer.stop();
694 d->m_lmbTimer.stop();
695 return false;
696 }
697 }
698 }
699
700 qreal mouseLon;
701 qreal mouseLat;
702 const bool isMouseAboveMap =
703 MarbleInputHandler::d->m_marblePresenter->map()->geoCoordinates(event->x(), event->y(), mouseLon, mouseLat, GeoDataCoordinates::Radian);
704 notifyPosition(isMouseAboveMap, mouseLon, mouseLat);
705
706 QPoint mousePosition(event->x(), event->y());
707
708 if (isMouseAboveMap || selectionRubber()->isVisible() || MarbleInputHandler::d->m_marblePresenter->map()->hasFeatureAt(mousePosition)) {
709 if (event->type() == QEvent::MouseButtonPress) {
710 handleMouseButtonPress(event);
711 }
712
713 if (event->type() == QEvent::MouseButtonRelease) {
714 handleMouseButtonRelease(event);
715 }
716
717 const bool supportsViewportRotation = MarbleInputHandler::d->m_marblePresenter->map()->projection() == Spherical;
718
719 // Regarding all kinds of mouse moves:
720 if (d->m_leftPressed && !selectionRubber()->isVisible()) {
721 auto radius = (qreal)(MarbleInputHandler::d->m_marblePresenter->radius());
722 qreal deltax = event->x() - d->m_leftPressedX;
723 qreal deltay = event->y() - d->m_leftPressedY;
724
725 if (qAbs(deltax) > d->m_dragThreshold || qAbs(deltay) > d->m_dragThreshold || !d->m_lmbTimer.isActive()) {
726 MarbleInputHandler::d->m_marblePresenter->setViewContext(Animation);
727
728 d->m_pressAndHoldTimer.stop();
729 d->m_lmbTimer.stop();
730 Quaternion quat = Quaternion::fromSpherical(-M_PI / 2 * deltax / radius, +M_PI / 2 * deltay / radius);
731 if (supportsViewportRotation) {
732 const qreal heading = MarbleInputHandler::d->m_marblePresenter->map()->heading();
733 const Quaternion rotation = Quaternion::fromEuler(0, 0, heading * DEG2RAD);
734 quat.rotateAroundAxis(rotation);
735 }
736 qreal lon, lat;
737 quat.getSpherical(lon, lat);
738 const qreal posLon = d->m_leftPressedLon + RAD2DEG * lon;
739 const qreal posLat = d->m_leftPressedLat + RAD2DEG * lat;
740 MarbleInputHandler::d->m_marblePresenter->centerOn(posLon, posLat);
741 if (MarbleInputHandler::d->m_inertialEarthRotation) {
742 d->m_kineticSpinning.setPosition(posLon, posLat);
743 }
744 }
745 }
746
747 if (d->m_midPressed) {
748 int eventy = event->y();
749 int dy = d->m_midPressedY - eventy;
750 MarbleInputHandler::d->m_marblePresenter->setRadius(d->m_startingRadius * pow(1.005, dy));
751 }
752
753 if (d->m_rightPressed && supportsViewportRotation && MarbleInputHandler::d->m_mouseViewRotation) {
754 qreal centerX, centerY;
755 MarbleInputHandler::d->m_marblePresenter->map()->screenCoordinates(MarbleInputHandler::d->m_marblePresenter->centerLongitude(),
756 MarbleInputHandler::d->m_marblePresenter->centerLatitude(),
757 centerX,
758 centerY);
759
760 // Deltas from previous position.
761 int dx = event->x() - d->m_rightPosition.x();
762 int dy = event->y() - d->m_rightPosition.y();
763
764 d->m_rightPosition = event->pos();
765
766 // Moving on the bottom should be opposite direction.
767 int sign = event->y() > centerY ? -1 : 1;
768 // Left top and right bottom sides for y axis should be opposite direction.
769 if ((event->x() < centerX && event->y() < centerY) || (event->x() > centerX && event->y() > centerY)) {
770 dy *= -1;
771 }
772
773 const qreal speedFactor = 0.3;
774 d->m_heading += (dx + dy) * sign * speedFactor;
775 MarbleInputHandler::d->m_marblePresenter->map()->setHeading(d->m_heading);
776 if (MarbleInputHandler::d->m_inertialEarthRotation) {
777 d->m_kineticSpinning.setHeading(d->m_heading);
778 }
779 }
780
781 if (selectionRubber()->isVisible()) {
782 // We change selection.
783 selectionRubber()->setGeometry(QRect(d->m_selectionOrigin, event->pos()).normalized());
784 }
785 } else {
786 direction = mouseMovedOutside(event);
787 }
788
789 if (MarbleInputHandler::d->m_marblePresenter->viewContext() != Animation) {
790 adjustCursorShape(mousePosition, direction);
791 }
792 return acceptMouse();
793}
794
795bool MarbleDefaultInputHandler::acceptMouse()
796{
797 // let others, especially float items, still process the event
798 // Note: This caused a bug in combination with oxygen, see https://bugs.kde.org/show_bug.cgi?id=242414
799 // and changing it a related regression, see https://bugs.kde.org/show_bug.cgi?id=324862
800 return false;
801}
802
803bool MarbleDefaultInputHandler::eventFilter(QObject *o, QEvent *e)
804{
805 Q_UNUSED(o);
806
807 if (layersEventFilter(o, e)) {
808 return true;
809 }
810
811 hideSelectionIfCtrlReleased(e);
812
813 switch (e->type()) {
816 case QEvent::TouchEnd:
817 return handleTouch(static_cast<QTouchEvent *>(e));
818 case QEvent::KeyPress:
819 return handleKeyPress(static_cast<QKeyEvent *>(e));
820 case QEvent::Gesture:
821 return handleGesture(static_cast<QGestureEvent *>(e));
822 case QEvent::Wheel:
823 return handleWheel(static_cast<QWheelEvent *>(e));
825 return handleDoubleClick(static_cast<QMouseEvent *>(e));
829 return handleMouseEvent(static_cast<QMouseEvent *>(e));
830 default:
831 return false;
832 }
833}
834
835bool MarbleDefaultInputHandler::handleTouch(QTouchEvent *)
836{
837 return false; // reimplement to handle in cases of QML and PinchArea element
838}
839
840bool MarbleDefaultInputHandler::handleKeyPress(QKeyEvent *event)
841{
842 if (event->type() == QEvent::KeyPress) {
843 MarbleAbstractPresenter *marblePresenter = MarbleInputHandler::d->m_marblePresenter;
844 bool handled = true;
845 switch (event->key()) {
846 case Qt::Key_Left:
847 stopInertialEarthRotation();
848 marblePresenter->moveByStep(-1, 0, Marble::Linear);
849 break;
850 case Qt::Key_Right:
851 stopInertialEarthRotation();
852 marblePresenter->moveByStep(1, 0, Marble::Linear);
853 break;
854 case Qt::Key_Up:
855 stopInertialEarthRotation();
856 marblePresenter->moveByStep(0, -1, Marble::Linear);
857 break;
858 case Qt::Key_Down:
859 stopInertialEarthRotation();
860 marblePresenter->moveByStep(0, 1, Marble::Linear);
861 break;
862 case Qt::Key_Plus:
863 if (event->modifiers() != Qt::ControlModifier) {
864 stopInertialEarthRotation();
865 marblePresenter->zoomIn();
866 }
867 break;
868 case Qt::Key_Minus:
869 if (event->modifiers() != Qt::ControlModifier) {
870 stopInertialEarthRotation();
871 marblePresenter->zoomOut();
872 }
873 break;
874 case Qt::Key_Home:
875 stopInertialEarthRotation();
876 marblePresenter->goHome();
877 break;
878 default:
879 handled = false;
880 break;
881 }
882
883 return handled;
884 }
885 return false;
886}
887
888void MarbleDefaultInputHandler::handleMouseButtonPressAndHold(const QPoint &)
889{
890 // Default implementation does nothing
891}
892
893void MarbleDefaultInputHandler::handlePressAndHold()
894{
895 handleMouseButtonPressAndHold(QPoint(d->m_leftPressedX, d->m_leftPressedY));
896}
897
898const AbstractDataPluginItem *MarbleDefaultInputHandler::lastToolTipItem() const
899{
900 return d->m_lastToolTipItem;
901}
902
903QTimer *MarbleDefaultInputHandler::toolTipTimer()
904{
905 return &d->m_toolTipTimer;
906}
907
908QPoint MarbleDefaultInputHandler::toolTipPosition() const
909{
910 return d->m_toolTipPosition;
911}
912
913}
914
915#include "moc_MarbleInputHandler.cpp"
This file contains the headers for AbstractProjection.
This file contains the headers for MarbleMap.
This file contains the headers for ViewportParams.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
bool isValid(QStringView ifopt)
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
Binds a QML item to a specific geodetic location in screen coordinates.
@ Linear
Linear interpolation of lon, lat and distance to ground.
@ Spherical
Spherical projection ("Orthographic")
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
Type type() const const
QGesture * gesture(Qt::GestureType type) const const
iterator begin()
iterator end()
int x() const const
int y() const const
T * data() const const
bool isNull() const const
QPoint toPoint() const const
int height() const const
int left() const const
QRect normalized() const const
int top() const const
int width() const const
QPointF position() const const
OpenHandCursor
GestureState
PinchGesture
Key_Left
ControlModifier
typedef MouseButtons
QTextStream & center(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
QPoint angleDelta() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 12:02:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.