/** ** ** @RDE_HEADER@ ** Event Dispatcher for Qt5 to delegate mouse and keyboard event to another event dispatcher ** $Id$ ** $HeadURL$ ** $Author$ ** ** ----------------------------------------------------------------------------------------------- ** ** Hardware-Environment: Intel-PC ** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 ** ** Language ........: C++ 11 ** ** ----------------------------------------------------------------------------------------------- ** ** Author ...........: Jörg Turla, SPD G, Tel. 3132 ** ** ----------------------------------------------------------------------------------------------- ** ** Notes .........: ** ** 15.04.2015, JT : First edition ** ** ----------------------------------------------------------------------------------------------- */ #include "event_mouse_navigator.h" #include "rubberband.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace SituationMap { namespace RB { int EventMouseNavigator::_pickTolerance = 5; qreal EventMouseNavigator::_zoomFactorBase = 1.002; EventMouseNavigator::EventMouseNavigator(QWidget * getEventsFrom, SituationMap::MapImpl *sitMap ) : AbstractEventDispatcher(getEventsFrom) , _sitMap(sitMap) , _cursorChanged(false) , _viewPortPos(QPointF(-1, -1)) , _selectionMode(SelectionMode::NoSelection) , _pendingRequest(RequestType::None) , _mousePanningActive(false) , _mousePanningActiveForCenterMapMidButton(false) #ifdef SITUATIONMAP_OWN_WHEEL_HANDLING , _mouseZoomTaskWatcher() #endif , _pressedMouseButton(Qt::NoButton) { // prepare identify: switch (_sitMap->mode()) { case Mode::Mode_2D: connect( _sitMap->getEsriMapGraphicsView() , &Esri::ArcGISRuntime::MapGraphicsView::identifyGraphicsOverlaysCompleted , this , &EventMouseNavigator::onGetPickingResult ); break; case Mode::Mode_3D: connect( _sitMap->getEsriSceneGraphicsView() , &Esri::ArcGISRuntime::SceneGraphicsView::identifyGraphicsOverlaysCompleted , this , &EventMouseNavigator::onGetPickingResult ); break; default: // empty break; } } // EventMouseNavigator::EventMouseNavigator bool EventMouseNavigator::eventFilter(QObject * watched, QEvent * e) { Q_UNUSED(watched); QString debugString(QLatin1String("unhandled event")); if (hasFinished()) return true; // don't accept new events if dispatcher is going to finish if (hasActiveEvent()) return true; // don't accept new events before last event has been handled completly if (e) { bool handled( false ); setActiveEvent( true ); switch( e->type() ) { case QEvent::Enter : { _pressedMouseButton = Qt::NoButton; emit enterEvent(); } break; case QEvent::Leave : { emit leaveEvent(); _pressedMouseButton = Qt::NoButton; } break; case QEvent::MouseButtonPress : { handled = false; debugString = QLatin1String("mouse button press event"); QMouseEvent * mouseEvent = static_cast(e); _pressedMouseButton = mouseEvent->button(); _mousePanningActive = false; _mouseMoveRefPos = mouseEvent->pos(); // for panning _restoreMouseCursor = getCursorFromWidget(); if (Qt::MidButton == mouseEvent->button()) { handled = true; _mousePanningActive = true; _mousePanningActiveForCenterMapMidButton = false; // setCursorToWidget( QCursor(Qt::CursorShape::ClosedHandCursor) ); } } break; case QEvent::TouchBegin : { handled = false; debugString = QLatin1String("Touch button press event"); QTouchEvent * mouseEvent = static_cast(e); _pressedMouseButton = Qt::MouseButton::LeftButton; _mousePanningActive = false; if (mouseEvent->touchPoints().size() > 0) { const QTouchEvent::TouchPoint tp = mouseEvent->touchPoints().at(0); _mouseMoveRefPos = tp.pos().toPoint(); // for panning _restoreMouseCursor = getCursorFromWidget(); } } break; case QEvent::MouseButtonRelease : { debugString = QLatin1String("mouse button release event"); QMouseEvent * mouseEvent = static_cast(e); if (Qt::LeftButton == mouseEvent->button()) { if (_cursorChanged) { setCursorToWidget(_restoreMouseCursor); _cursorChanged = false; } /* * The ESRI::MapView is doing panning on mouse move when left mouse button is pressed. * To prevent selection in this case check mouse move distance. */ if (Qt::LeftButton == _pressedMouseButton && ((mouseEvent->pos() - _mouseMoveRefPos).manhattanLength() < 3) ) { if (Rubberband::activeRubberbands(_sitMap).isEmpty()) { handled = false; // pass event to esri map to click buttons selectionRequested(mouseEvent->localPos()); // do not handled = true; because some buttons in GraphicsView needs to get the event later. } } else handled = true; // may be a double click catched at higher event filter } if (Qt::RightButton == mouseEvent->button()) { handled = true; pickRequested( mouseEvent->localPos() ); } if ((Qt::MidButton == mouseEvent->button())) { if (!_mousePanningActiveForCenterMapMidButton) { // press MidButton and Release MidButton without panning in the meantime: // center map to current mouse position: // set viewpoint : (perform this only in 2D - MapGraphicsView <-> MapSceneView) // (3D had different screenToLocation...) Esri::ArcGISRuntime::MapGraphicsView *esriMapGraphicsView = _sitMap->getEsriMapGraphicsView(); if (esriMapGraphicsView) { const QPoint &mousePos = mouseEvent->pos(); const Esri::ArcGISRuntime::Point & mapCoordEsri = esriMapGraphicsView->screenToLocation(mousePos.x(), mousePos.y()); const Esri::ArcGISRuntime::Viewpoint & curVp = esriMapGraphicsView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); const Esri::ArcGISRuntime::Viewpoint newVp(mapCoordEsri, curVp.targetScale()); esriMapGraphicsView->setViewpointAndWait(newVp); // sync / andWAIT _getEventsFrom -> update(); } } } if ((_mousePanningActive) || (Qt::MidButton == mouseEvent->button())) { handled = (Qt::MidButton == mouseEvent->button()); setCursorToWidget( _restoreMouseCursor ); } _mousePanningActive = false; } break; case QEvent::TouchEnd : { debugString = QLatin1String("Touch button release event"); QTouchEvent * mouseEvent = static_cast(e); if (mouseEvent->touchPoints().size() > 0) { const QTouchEvent::TouchPoint tp = mouseEvent->touchPoints().at(0); if (_cursorChanged) { setCursorToWidget(_restoreMouseCursor); _cursorChanged = false; } /* * The ESRI::MapView is doing panning on mouse move when left mouse button is pressed. * To prevent selection in this case check mouse move distance. */ if (Qt::LeftButton == _pressedMouseButton && ((tp.pos() - _mouseMoveRefPos).manhattanLength() < 3) ) { if (Rubberband::activeRubberbands(_sitMap).isEmpty()) { handled = false; // pass event to esri map to click buttons selectionRequested(tp.pos()); // do not handled = true; because some buttons in GraphicsView needs to get the event later. } } else handled = true; // may be a double click catched at higher event filter } _mousePanningActive = false; } break; case QEvent::MouseMove : { handled = false; debugString = QLatin1String("mouse button move event"); QMouseEvent *mouseEvent = static_cast(e); if (isSignalConnected(QMetaMethod::fromSignal(&EventMouseNavigator::mouseMovedEvent))) { emit mouseMovedEvent(mouseEvent->pos().x(), mouseEvent->pos().y()); } if (_mousePanningActive) { handled = true; if (mouseEvent->buttons().testFlag(Qt::MidButton)) { _mousePanningActiveForCenterMapMidButton = true; // move viewpoint : (perform this only in 2D - MapGraphicsView <-> MapSceneView) // (3D had different screenToLocation...) Esri::ArcGISRuntime::MapGraphicsView *esriMapGraphicsView = _sitMap->getEsriMapGraphicsView(); if (esriMapGraphicsView) { QPointF deltaMove = mouseEvent->pos()-_mouseMoveRefPos; QPointF winCenter = QPointF( qreal(_getEventsFrom->width())*0.5, qreal(_getEventsFrom->height())*0.5 ); QPointF newCenter = winCenter-deltaMove; _mouseMoveRefPos = mouseEvent->pos(); const Esri::ArcGISRuntime::Point & mapCoordEsri = esriMapGraphicsView->screenToLocation(newCenter.x(), newCenter.y()); const Esri::ArcGISRuntime::Viewpoint & curVp = esriMapGraphicsView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); const Esri::ArcGISRuntime::Viewpoint newVp(mapCoordEsri, curVp.targetScale()); esriMapGraphicsView->setViewpointAndWait(newVp); // sync / andWAIT _getEventsFrom -> update(); DEBUG_LOG(_logger) << LOGFUNCTION << " Panning to " << mapCoordEsri.x() << " - " << mapCoordEsri.y() << " newCenter pos=" << newCenter << " mouse pos=" << mouseEvent->pos() << " delta pos=" << deltaMove; } } else { setCursorToWidget( _restoreMouseCursor ); _mousePanningActive = false; } // : move viewpoint } } break; case QEvent::TouchUpdate : { handled = false; debugString = QLatin1String("touch button move event"); QTouchEvent *mouseEvent = static_cast(e); if (mouseEvent->touchPoints().size() > 0) { const QTouchEvent::TouchPoint tp = mouseEvent->touchPoints().at(0); if (isSignalConnected(QMetaMethod::fromSignal(&EventMouseNavigator::mouseMovedEvent))) { emit mouseMovedEvent(tp.pos().x(), tp.pos().y()); } if (_mousePanningActive) { handled = true; } else { setCursorToWidget( _restoreMouseCursor ); _mousePanningActive = false; } } } break; #ifdef SITUATIONMAP_OWN_WHEEL_HANDLING case QEvent::Wheel : { debugString = QLatin1String("mouse wheel event"); handled = true; if ((!_mouseZoomTaskWatcher.isValid()) || (_mouseZoomTaskWatcher.isDone())) { QWheelEvent * wheelEvent = static_cast(e); qreal delta = wheelEvent->angleDelta().manhattanLength(); bool zoomIn = (wheelEvent->angleDelta().x() < 0) || (wheelEvent->angleDelta().y() < 0); qreal zoomFactor = qreal( qRound( qPow( _zoomFactorBase, (zoomIn) ? delta : -delta ) * 10 )) / 10.0; QPointF winCenter = QPointF( qreal(_getEventsFrom->width())*0.5, qreal(_getEventsFrom->height())*0.5 ); const QPoint & cursorPosWidget = _getEventsFrom->mapFromGlobal( QCursor::pos() ); // e doesn't have a position get position from cursor here Esri::ArcGISRuntime::MapView * esriMapView = _sitMap->getEsriMapGraphicsView(); const Esri::ArcGISRuntime::Point & mapCenter = esriMapView -> screenToLocation(winCenter.x(), winCenter.y()); const Esri::ArcGISRuntime::Point & cursorPosMap = esriMapView -> screenToLocation(cursorPosWidget.x(), cursorPosWidget.y()); double mapScale = esriMapView -> mapScale() * zoomFactor; double mapX = (-cursorPosMap.x()+mapCenter.x())*zoomFactor + cursorPosMap.x(); double mapY = (-cursorPosMap.y()+mapCenter.y())*zoomFactor + cursorPosMap.y(); _mouseZoomTaskWatcher = esriMapView->setViewpointCenter( Esri::ArcGISRuntime::Point(mapX,mapY,esriMapView->map()->spatialReference()), mapScale ); if (isSignalConnected( QMetaMethod::fromSignal(&EventMouseNavigator::mouseWheelEvent) )) { emit mouseWheelEvent(zoomFactor, QPointF(cursorPosMap.x(), cursorPosMap.y()) ); } } } break; #endif case QEvent::Gesture: { QGestureEvent * gestureEvent = static_cast(e); if (gestureEvent) { const QList & gestureList = gestureEvent->gestures(); for (auto gesture : gestureList) { Qt::GestureType gestureType = gesture->gestureType(); Qt::GestureState gestureState = gesture->state(); // QTapGesture * tapGesture = nullptr; // QTapAndHoldGesture * tapAndHoldGesture = nullptr; QPanGesture * panGesture = nullptr; QPinchGesture * pinchGesture = nullptr; // QSwipeGesture * swipeGesture = nullptr; QString typeName; QString stateName; switch (gestureType) { // case Qt::TapGesture : tapGesture = (QTapGesture *)gesture; typeName = QLatin1String("TapGesture"); break; // case Qt::TapAndHoldGesture : tapAndHoldGesture = (QTapAndHoldGesture*)gesture; typeName = QLatin1String("TapAndHoldGesture"); break; case Qt::PanGesture : panGesture = (QPanGesture *)gesture; typeName = QLatin1String("PanGesture"); break; case Qt::PinchGesture : pinchGesture = (QPinchGesture *)gesture; typeName = QLatin1String("PinchGesture"); break; // case Qt::SwipeGesture : swipeGesture = (QSwipeGesture *)gesture; typeName = QLatin1String("SwipeGesture"); break; default: break; } switch (gestureState) { case Qt::NoGesture : stateName = QLatin1String("NoGesture"); break; case Qt::GestureStarted : stateName = QLatin1String("GestureStarted"); break; case Qt::GestureUpdated : stateName = QLatin1String("GestureUpdated"); break; case Qt::GestureFinished : stateName = QLatin1String("GestureFinished"); break; case Qt::GestureCanceled : stateName = QLatin1String("GestureCanceled"); break; } DEBUG_LOG(_logger) << LOGFUNCTION << " Gesture type=" << typeName << " Gesture state=" << stateName; // tapGesture = nullptr; panGesture = nullptr; // ToDo: check: Why this? if (panGesture) { QPointF deltaMove = panGesture->delta(); QPoint winCenter = QPoint( _getEventsFrom->width() / 2, _getEventsFrom->height() / 2 ); QPoint newCenter = winCenter-QPoint((int)deltaMove.x(),(int)deltaMove.y()); const Esri::ArcGISRuntime::Point mapCoordEsri = _sitMap->getEsriMapGraphicsView()->screenToLocation(newCenter.x(), newCenter.y()); Esri::ArcGISRuntime::Viewpoint curVp = _sitMap->getEsriMapGraphicsView()->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); Esri::ArcGISRuntime::Viewpoint newVp(mapCoordEsri, curVp.targetScale()); _sitMap->getEsriMapGraphicsView()->setViewpointAndWait(newVp); _getEventsFrom -> update(); if (gestureState == Qt::GestureStarted) gestureEvent -> setAccepted(panGesture, true); } if (pinchGesture) { ///// QPinchGesture::ChangeFlags changeFlags = pinchGesture->changeFlags(); ///// if (changeFlags & QPinchGesture::RotationAngleChanged) ///// { ///// qreal deltaPhi = pinchGesture->rotationAngle() - pinchGesture->lastRotationAngle(); ///// _esriMap -> setRotation( _esriMap->rotation()-deltaPhi ); ///// } ///// if (changeFlags & QPinchGesture::ScaleFactorChanged) _esriMap -> zoom( 1.0/pinchGesture->scaleFactor() ); _getEventsFrom -> update(); if (gestureState == Qt::GestureStarted) gestureEvent -> setAccepted(pinchGesture, true); } } } } break; default: break; } // switch if (!hasFinished()) setActiveEvent( false ); if (handled) e->accept(); DEBUG_LOG(_logger) << LOGFUNCTION << " Event '" << debugString << "'" << " event->type() '" << e->type() << "'" << " handled = " << handled << " mouse panning is active = " << _mousePanningActive; // // 'if (handled) return true;' lead to bad behaviour at 3D: globe is often connected to mouse, without any button pressed... // // if (handled) return true; } return AbstractEventDispatcher::eventFilter(watched,e); } // EventMouseNavigator::eventFilter bool EventMouseNavigator::checkRunningPickingTask() { if ((!_pickingTask.isCanceled()) && (_pendingRequest != RequestType::None)) { _pickingTask.cancel(); _pendingRequest = RequestType::None; return true; } return false; } // void EventMouseNavigator::checkRunningPickingTask() void EventMouseNavigator::selectionRequested( const QPointF & pos ) { // do the work only in case of slot connections are installed if (! isSignalConnected( QMetaMethod::fromSignal(&EventMouseNavigator::selectionRequest) )) { return; } if (checkRunningPickingTask()) { qWarning() << LOGFUNCTION << " request is still pending!"; } _pendingRequest = RequestType::Selection; _viewPortPos = pos; requestPicking(pos); } // EventMouseNavigator::selectionRequested void EventMouseNavigator::pickRequested( const QPointF &pos ) { // do the work only in case of slot connections are installed if (! isSignalConnected( QMetaMethod::fromSignal(&EventMouseNavigator::contextMenuRequest) )) { return; } if (checkRunningPickingTask()) { qWarning() << LOGFUNCTION << " request is still pending!"; } _pendingRequest = RequestType::Picking; _viewPortPos = pos; requestPicking(pos); } // EventMouseNavigator::pickRequested void EventMouseNavigator::requestPicking(const QPointF &pos) { _pickingTask.cancel(); // cancel old task // store new task bool returnPopUpsOnly = false; int maximumResultsPerOverlay = std::numeric_limits::max(); // huge number; _pickingTask = _sitMap->getEsriGeoView()->identifyGraphicsOverlays(pos.x(), pos.y(), _pickTolerance, returnPopUpsOnly, maximumResultsPerOverlay); //qWarning() << " XXXXXXX" << LOGFUNCTION; // check new task if (!_pickingTask.isValid()) { _pendingRequest = RequestType::None; qWarning() << LOGFUNCTION << " _identfyGraphicsOverlayTask invalid"; } } // EventMouseNavigator::requestPicking void EventMouseNavigator::onGetPickingResult(const QUuid &taskId, const QList &identifyResults) { // qWarning() << " YYYYYYY" << LOGFUNCTION; if (_pickingTask.taskId() != taskId) { qDebug() << " taskId does not match"; return; } const QList & selectedIdList = mapToUniqueMapId(identifyResults); // qWarning() << " _pendingRequest="<< (int)_pendingRequest << " count= " << _selectedIdList.count(); switch (_pendingRequest) { case RequestType::Picking: emit contextMenuRequest(selectedIdList, _viewPortPos); break; case RequestType::Selection: emit selectionRequest(selectedIdList, _viewPortPos); break; case RequestType::None: //emtpy break; } // switch // reset some 'flags' to enable a new request: _pendingRequest = RequestType::None; _viewPortPos = QPointF(-1, -1); } // EventMouseNavigator::onGetPickingResult QList EventMouseNavigator::mapToUniqueMapId(const QList &identifyResults) { QList idList; for (auto iResult : identifyResults) { // qWarning() << " process result of layer: " << iResult->graphicsOverlay(); const QList &gResults = iResult->graphics(); for (auto pickedGraphic : gResults) { // qWarning() << " process pickedGraphic: " << pickedGraphic; if (pickedGraphic->attributes()) { // qWarning() << " process pickedGraphic cont: " << pickedGraphic->attributes()->containsAttribute(QLatin1String(SituationMap::UniqueMapIdAttribute)); // qWarning() << " process pickedGraphic cont: " << pickedGraphic->attributes()->containsAttribute(QLatin1String(SituationMap::IsPickableAttribute)); bool ok; SituationMap::UniqueMapId uniqueMapId = pickedGraphic->attributes()->attributeValue(QLatin1String(SituationMap::UniqueMapIdAttribute)).toUInt(&ok); if (ok) { // qWarning() << " process pickedGraphic uid: " << uniqueMapId; if (!idList.contains(uniqueMapId)) { idList += uniqueMapId; } } else { ERROR_LOG(_logger) << " process pickedGraphic " << pickedGraphic << " uid: '" << pickedGraphic->attributes()->attributeValue(QLatin1String(SituationMap::UniqueMapIdAttribute)).toString() << "' (has uid attribute is " << pickedGraphic->attributes()->containsAttribute(QLatin1String(SituationMap::UniqueMapIdAttribute)) << ")"; } } } // foreach inner } // foreach outter return idList; } // EventMouseNavigator::mapToUniqueMapId } } /* namespace SituationMap::RB */