/** ** ** @RDE_HEADER@ ** Implementation of base class for rubberbands ** $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 .........: ** ** 27.03.2015, JT : First edition for use with Qt5 ** ** ----------------------------------------------------------------------------------------------- */ #include "rubberband_base.h" #include "rubberband_base_internal.h" #include "rubberband_handle_proxy.h" #include "rubberband_factory.h" #include #include #include // Qt #include #include #include #include #include #include #include #include "apply.xpm" #include "abort.xpm" #include "undo.xpm" static SituationMap::Map * currentFactoryMap = nullptr; static bool allowMultipleRubberbands = false; namespace SituationMap { namespace RB { QPixmap * RubberbandBase::BaseInternalData::_applyIcon = nullptr; QPixmap * RubberbandBase::BaseInternalData::_abortIcon = nullptr; QPixmap * RubberbandBase::BaseInternalData::_undoIcon = nullptr; RubberbandBase::BaseInternalData::BaseInternalData( SituationMap::Map * map, RubberbandBase * me ) : _map(map) , _mapCtFunctor(map->coordTranslationFunctor()) , _isActive(false) , _applyIsPossible(false) , _undoIsPossible(false) , _hasUndoButton(false) , _lastClickPosition(-1,-1) , _buttonBoxSceneAnchor() , _buttonBoxFixPosition(-1,-1) , _buttonProxy() , _updateFunctorList() , _logger(getLogger("RubberbandBase")) { QGraphicsView * view = map->getQGraphicsView(); if (view) { _restoreCursor = view->viewport()->cursor(); // workaround Qt bug. remove when fixed } _buttonBox = new QWidget(); _buttonBox->setObjectName(QLatin1String("RBButtonBoxWidget")); _buttonBox->hide(); _buttonBox->setAttribute( Qt::WidgetAttribute::WA_NoSystemBackground, true ); _applyButton = new QPushButton( (_applyIcon) ? *_applyIcon : QPixmap(apply_xpm), QString(), _buttonBox ); _applyButton -> setAttribute( Qt::WidgetAttribute::WA_NoSystemBackground, true ); _applyButton -> setObjectName(QLatin1String("RBApply")); _applyButton -> setAttribute(Qt::WA_AcceptTouchEvents); _applyButton -> grabGesture( Qt::GestureType::TapGesture ); me->connect( _applyButton, &QPushButton::clicked, me, &RubberbandBase::onDo_applyButtonClicked ); _cancelButton = new QPushButton( (_abortIcon) ? *_abortIcon : QPixmap(abort_xpm), QString(), _buttonBox ); _cancelButton -> setAttribute( Qt::WidgetAttribute::WA_NoSystemBackground, true ); _cancelButton -> setObjectName(QLatin1String("RBCancel")); _cancelButton -> setAttribute(Qt::WA_AcceptTouchEvents); _cancelButton -> grabGesture( Qt::GestureType::TapGesture ); me->connect( _cancelButton, &QPushButton::clicked, me, &RubberbandBase::onDo_cancelButtonClicked ); _undoButton = new QPushButton( (_undoIcon) ? *_undoIcon : QPixmap(undo_xpm), QString(), _buttonBox ); _undoButton -> setAttribute( Qt::WidgetAttribute::WA_NoSystemBackground, true ); _undoButton -> setObjectName(QLatin1String("RBUndo")); _undoButton -> setAttribute(Qt::WA_AcceptTouchEvents); _undoButton -> grabGesture( Qt::GestureType::TapGesture ); _undoButton -> hide(); me->connect( _undoButton, &QPushButton::clicked, me, &RubberbandBase::onDo_undoButtonClicked ); _buttonLayout = new QHBoxLayout( _buttonBox ); _buttonLayout->setObjectName(QLatin1String("RBButtonBoxLayoutHBox")); _buttonLayout -> addWidget( _undoButton ); _buttonLayout -> addWidget( _cancelButton ); _buttonLayout -> addWidget( _applyButton ); } /* InternalData */ Rubberband::BaseInternalData::~BaseInternalData() { _map = nullptr; _mapCtFunctor = nullptr; _buttonProxy = nullptr; // delete RefPointer if (_buttonBox) _buttonBox -> deleteLater(); // delete all widgets of button box _buttonBox = nullptr; _buttonLayout = nullptr; _applyButton = nullptr; _cancelButton = nullptr; _undoButton = nullptr; } /* ~InternalData */ bool RubberbandBase::BaseInternalData::buttonProxyActive( const QPointF & pos ) const { if (_buttonProxy) { return ((_buttonProxy->isVisible()) && (_buttonProxy->isPositionInside(pos))); } return false; } /* bool RubberbandBase::BaseInternalData::buttonProxyActive( const QPointF & pos ) const*/ void RubberbandBase::BaseInternalData::enableAndShowOnlyPossibleButtons() { _applyButton->setEnabled(_applyIsPossible); if (_undoButton) { if (_hasUndoButton) { _undoButton -> show(); } _undoButton -> setEnabled(_undoIsPossible); } } /* void RubberbandBase::InternalData::showOnlyPossibleButtons() */ static QHash> &activeRubberbands() { static QHash> hashTable; return hashTable; } // activeRubberbands() void RubberbandBase::currentFactoryMap(SituationMap::Map * map) { ::currentFactoryMap = map; } void RubberbandBase::allowMultipleRubberbands( bool ok ) { ::allowMultipleRubberbands = ok; } void * RubberbandBase::operator new(size_t bytes) { QString exceptionMsg; // be careful it is not thread safe here if (::currentFactoryMap) { bool doNew( ::allowMultipleRubberbands ); if (!doNew) { doNew = true; const auto rubberbands = SituationMap::RB::activeRubberbands().value(::currentFactoryMap); for (const auto rbnd : rubberbands) { if (rbnd->rubberbandIsAlive()) { doNew = false; break; } } } if (doNew) { RubberbandBase::currentFactoryMap( nullptr ); RubberbandBase::allowMultipleRubberbands( false ); return ::operator new(bytes); } else exceptionMsg = QLatin1String("Another rubberband is already alive at moment"); RubberbandBase::currentFactoryMap( nullptr ); } else exceptionMsg = QLatin1String("Use rubberband factory to create rubberbands. Calling new directly is forbidden!"); RubberbandBase::currentFactoryMap( nullptr ); RubberbandBase::allowMultipleRubberbands( false ); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * because "return nullptr;" is not compliante with the c++ standard * throw exception instead * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ throw Exception( __FILE__, __LINE__, exceptionMsg ); } /* RubberbandBase::operator new */ QList RubberbandBase::activeRubberbands( SituationMap::Map * map ) { QList ret; const auto rubberbands = SituationMap::RB::activeRubberbands().value(map); for (const auto rbnd : rubberbands) { if (rbnd && rbnd->rubberbandIsAlive()) ret += rbnd; } return ret; } /* RubberbandBase::activeRubberbands */ void RubberbandBase::deleteAllActiveRubberbands(Map *map) { const QList &rbCont = RubberbandBase::activeRubberbands(map); qDeleteAll(rbCont); } /* RubberbandBase::deleteAllActiveRubberbands */ bool RubberbandBase::isRubberbandActive(SituationMap::Map * map) { return (activeRubberbands(map).size() > 0); } /* RubberbandBase::isRubberbandActive */ void RubberbandBase::abortActiveRubberbands( SituationMap::Map * map ) { const QList & rbList = activeRubberbands(map); for (auto rb : rbList) { emit rb->abort(rb); rb->doAutoDelete(); } } /* RubberbandBase::abortActiveRubberbands */ void RubberbandBase::deleteMySelf() { if (_viewPortEventDispatcher) _viewPortEventDispatcher -> onDo_kickTheBucket(); if (_viewPortEventDispatcher) _viewPortEventDispatcher -> onDo_delete(); if (_windowEventDispatcher) _windowEventDispatcher -> onDo_delete(); _viewPortEventDispatcher = nullptr; _windowEventDispatcher = nullptr; _baseInternal->_isActive = false; deleteLater(); } /* RubberbandBase::deleteMySelf */ RubberbandBase::RubberbandBase( SituationMap::Map * map, QObject * parent, bool isActive ) : QObject( parent ) , EventActionInterface() , _baseInternal( new RubberbandBase::BaseInternalData(map,this) ) , _drawColor(QColor("black")) , _fillColor(QColor("black")) , _handleDrawColor(QColor("black")) , _handleFillColor(QColor("yellow")) , _handleSelectionColor(QColor("red")) , _frameColor(QColor("black")) , _frameFillColor(QColor(150,150,0,50)) , _frameSelectionColor(QColor(255,0,0,50)) , _frameHandleMargin(10) , _pickingRatio(0) , _finishAfterConstruction(true) , _autoDelete(false) , _viewPortEventDispatcher(nullptr) , _windowEventDispatcher (nullptr) , _userData(nullptr) { _fillColor.setAlpha(100); if (isActive) activate(); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * In case of multiple rubberbands can be active, it is forbidden to * create a rubberband of the same type twice anyway. * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ bool rubberbandOfSameTypeIsActive = false; const auto rubberbands = SituationMap::RB::activeRubberbands().value(map); for (auto rbnd : rubberbands) { // Just to check the class names with debugger // const char * rbndName = typeid(*rbnd).name(); // const char * thisName = typeid(*this).name(); if (( typeid(*rbnd) == typeid(*this)) && rbnd->rubberbandIsAlive()) { rubberbandOfSameTypeIsActive = true; break; } } if (rubberbandOfSameTypeIsActive) { // disable dispatcher, so the rubberband is not alive yet. if (_viewPortEventDispatcher) { _viewPortEventDispatcher -> onDo_kickTheBucket(); _viewPortEventDispatcher -> onDo_delete(); } if (_windowEventDispatcher) _windowEventDispatcher->onDo_delete(); _viewPortEventDispatcher = nullptr; _windowEventDispatcher = nullptr; } else { SituationMap::RB::activeRubberbands()[map].append( this ); if (_windowEventDispatcher) { _windowEventDispatcher->setCursor(QCursor(Qt::CrossCursor)); _windowEventDispatcher->grab(); } if (_viewPortEventDispatcher) { _viewPortEventDispatcher->setCursor(QCursor(Qt::CrossCursor)); _viewPortEventDispatcher->grab(); } } if (map) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Sending signals of RubberbandFactory to inform about rubberband creation and destroy would be * better placed in RubberbandFactory source (rubberband_factory.cpp) but there it's a lot of * more writing stuff ... * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ SituationMap::RB::RubberbandFactory & rbndFactory = map -> rubberbandFactory(); emit rbndFactory.rubberbandCreated(); connect( this, &QObject::destroyed, &rbndFactory, &SituationMap::RB::RubberbandFactory::rubberbandDestroyed ); } } /* RubberbandBase::RubberbandBase */ RubberbandBase::~RubberbandBase() { if (_viewPortEventDispatcher) _viewPortEventDispatcher -> onDo_kickTheBucket(); if (_viewPortEventDispatcher) _viewPortEventDispatcher -> onDo_delete(); if (_windowEventDispatcher) _windowEventDispatcher -> onDo_delete(); _viewPortEventDispatcher = nullptr; _windowEventDispatcher = nullptr; /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Test if map has been already destroyed. If map exists all is nice ... * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ if (_baseInternal->_map) { SituationMap::RB::activeRubberbands()[_baseInternal->_map].removeOne( this ); emit finished( this ); } else { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ... otherwise we have to search the rubberband * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ const auto rubberbands = SituationMap::RB::activeRubberbands(); for (auto rubberList : rubberbands) rubberList.removeOne(this); } delete _baseInternal; _baseInternal = nullptr; } /* RubberbandBase::~RubberbandBase */ QPointF RubberbandBase::projectCoordinateToMap( CoordinateSystem cs, const QPointF & loc ) const { return (cs != CoordinateSystem::Geo || !_baseInternal->_mapCtFunctor) ? loc : _baseInternal->_mapCtFunctor->geographicToMap( loc ); } /* RubberbandBase::projectCoordinateToMap */ QPointF RubberbandBase::projectCoordinateFromMap( CoordinateSystem cs, const QPointF & loc ) const { return (cs != CoordinateSystem::Geo || !_baseInternal->_mapCtFunctor) ? loc : _baseInternal->_mapCtFunctor->mapToGeographic( loc ); } /* RubberbandBase::projectCoordinateFromMap */ QPointF RubberbandBase::projectCoordinateFromMap( CoordinateSystem cs, const Esri::ArcGISRuntime::Point & loc ) const { return (cs != CoordinateSystem::Geo || !_baseInternal->_mapCtFunctor) ? QPointF(loc.x(), loc.y()) : _baseInternal->_mapCtFunctor->mapToGeographic( loc ); } /* RubberbandBase::projectCoordinateFromMap */ void RubberbandBase::activate() { if (!_baseInternal->_map) return; if (!_baseInternal->_isActive) { QGraphicsView * view = _baseInternal->_map->getQGraphicsView(); if (view) { _viewPortEventDispatcher = new EventDispatcher(view->viewport(), this); _windowEventDispatcher = new EventDelegater(view, _viewPortEventDispatcher); _baseInternal->_isActive = true; MapImpl *mapImpl = static_cast(_baseInternal->_map.data()); connect(_viewPortEventDispatcher.data(), &EventDispatcher::mouseMovedEvent, mapImpl, &MapImpl::onHandleMouseMove); } } } /* RubberbandBase::activate */ bool RubberbandBase::autoSelectAction(int x, int y) { _viewPortEventDispatcher->_leftPressHandled = true; return selectAction(nullptr,x,y); } /* RubberbandBase::autoSelectAction */ void RubberbandBase::installExternalUpdate(const std::function & updateFunctor) { _baseInternal->_updateFunctorList << updateFunctor; } // void RubberbandBase::installExternalUpdate( const std::function & updateFunctor) void RubberbandBase::externalUpdate() { for (const auto &update : qAsConst(_baseInternal->_updateFunctorList)) { if (!update(this)) break; } } // void RubberbandBase::externalUpdate() void RubberbandBase::updateButtonPosition() { if ((_baseInternal->_lastClickPosition.x() < 0) || (_baseInternal->_lastClickPosition.y() < 0)) _baseInternal->_buttonBoxSceneAnchor = sceneBbox().bottomRight(); else _baseInternal->_buttonBoxSceneAnchor = _baseInternal->_lastClickPosition; // _internal->_mapView->update(); } /* RubberbandBase::updateButtonPosition */ void RubberbandBase::setFixedButtonBoxPosition( QPoint widgetPos ) { _baseInternal->_buttonBoxFixPosition = widgetPos; activateButtons(); } /* RubberbandBase::setFixedButtonBoxPosition */ void RubberbandBase::unsetFixedButtonBoxPosition() { _baseInternal->_buttonBoxFixPosition = QPoint(-1,-1); if (_baseInternal->_buttonProxy) { _baseInternal->_buttonProxy->setPos( _baseInternal->_buttonBoxSceneAnchor ); } } /* RubberbandBase::unsetFixedButtonBoxPosition */ bool RubberbandBase::isButtonBoxPositionFixed() { return ((_baseInternal->_buttonBoxFixPosition.x() >= 0) && (_baseInternal->_buttonBoxFixPosition.y() >= 0)); } void RubberbandBase::onDo_updateButtonFixCoordinates() { if (!_baseInternal->_buttonProxy) return; if (isButtonBoxPositionFixed()) { // Don't use bottom right corner of rubberband bbox to place the buttons, // rather than use fixed window position. const QPoint & pos = _baseInternal->_buttonBoxFixPosition; _baseInternal->_buttonProxy->setPos( pixel2ScenePosition(pos.x(),pos.y()) ); } } /* RubberbandStateSelect::onDo_updateButtonFixCoordinates */ void RubberbandBase::activateButtons() { DEBUG_LOG(_baseInternal->_logger) << LOGFUNCTION; if (!_baseInternal->_buttonProxy) { _baseInternal->_buttonProxy = new RubberbandHandleProxy( _baseInternal->_map->getQGraphicsView(), _baseInternal->_buttonBox ); _baseInternal->_buttonProxy->setRestoreCursor( _baseInternal->_restoreCursor ); // workaround Qt bug. remove when fixed connect( _baseInternal->_buttonProxy.get(), &RubberbandHandleProxy::adjusted, this, [this]() { this->_baseInternal->_buttonBoxSceneAnchor = this->_baseInternal->_buttonProxy->getCoordinate()._sceneCoordinate; } ); } _baseInternal->enableAndShowOnlyPossibleButtons(); if (isButtonBoxPositionFixed()) { const QPoint & pos = _baseInternal->_buttonBoxFixPosition; _baseInternal->_buttonProxy->setPos( pixel2ScenePosition(pos.x(),pos.y()) ); _baseInternal->_buttonProxy->setSceneOffset( QPointF(0,0) ); } else { _baseInternal->_buttonProxy->setPos( _baseInternal->_buttonBoxSceneAnchor ); _baseInternal->_buttonProxy->setSceneOffset( QPointF(_frameHandleMargin+5,_frameHandleMargin+5) ); } _baseInternal->_buttonProxy->show(); } /* Rubberband::activateButtons */ void RubberbandBase::deactivateButtons() { DEBUG_LOG(_baseInternal->_logger) << LOGFUNCTION; if (isButtonBoxPositionFixed()) return; if (_baseInternal->_buttonProxy) _baseInternal->_buttonProxy->hide(); } /* RubberbandBase::deactivateButtons() */ void RubberbandBase::adjustButtons() { if (_baseInternal->_buttonProxy) { activateButtons(); } } /* RubberbandBase::adjustButtons */ void RubberbandBase::applyButtonClicked() { completeStateAction( _viewPortEventDispatcher, 0.0, 0.0 ); } /* RubberbandBase::applyButtonClicked */ void RubberbandBase::cancelButtonClicked() { abortAction( _viewPortEventDispatcher, 0.0, 0.0 ); } /* RubberbandBase::cancelButtonClicked */ void RubberbandBase::undoButtonClicked() { undoAction( _viewPortEventDispatcher, 0.0, 0.0 ); } /* RubberbandBase::undoButtonClicked */ void RubberbandBase::resetIconsToDefault() { if (BaseInternalData::_applyIcon) { delete BaseInternalData::_applyIcon; BaseInternalData::_applyIcon = nullptr; } if (BaseInternalData::_abortIcon) { delete BaseInternalData::_abortIcon; BaseInternalData::_abortIcon = nullptr; } if (BaseInternalData::_undoIcon ) { delete BaseInternalData::_undoIcon; BaseInternalData::_undoIcon = nullptr; } } /* RubberbandBase::resetIconsToDefault */ void RubberbandBase::setIconApply(const QPixmap & icon) { if (BaseInternalData::_applyIcon == nullptr) BaseInternalData::_applyIcon = new QPixmap(icon); else *BaseInternalData::_applyIcon = icon; } /* RubberbandBase::setIconApply */ void RubberbandBase::setIconAbort(const QPixmap & icon) { if (BaseInternalData::_abortIcon == nullptr) BaseInternalData::_abortIcon = new QPixmap(icon); else *BaseInternalData::_abortIcon = icon; } /* RubberbandBase::setIconAbort */ void RubberbandBase::setIconUndo(const QPixmap & icon) { if (BaseInternalData::_undoIcon == nullptr) BaseInternalData::_undoIcon = new QPixmap(icon); else *BaseInternalData::_undoIcon = icon; } /* RubberbandBase::setIconUndo */ QPointF RubberbandBase::pixel2ScenePosition( int x, int y ) const { QGraphicsView * view = _baseInternal->_map->getQGraphicsView(); return (view) ? view->mapToScene(x, y) : QPointF(x,y); } /* RubberbandBase::pixel2ScenePosition */ bool RubberbandBase::buttonPositionAction(AbstractEventDispatcher *, int x, int y) { QPointF scenePos(pixel2ScenePosition(x,y)); if (!_baseInternal->buttonProxyActive(scenePos)) { _baseInternal->_buttonBoxSceneAnchor = scenePos; adjustButtons(); } return false; } /* bool RubberbandBase::buttonPositionAction(AbstractEventDispatcher * causer, int x, int y) */ void RubberbandBase::unlinkEventDispatcher( AbstractEventDispatcher* ) { _viewPortEventDispatcher = nullptr; _windowEventDispatcher = nullptr; } /* RubberbandBase::unlinkEventDispatcher */ bool RubberbandBase::abortAction( AbstractEventDispatcher * causer, int, int ) { _baseInternal->_buttonBox->hide(); if (causer) causer->onDo_kickTheBucket(); emit abort( this ); doAutoDelete(); return true; } /* RubberbandBase::abortAction */ bool RubberbandBase::applyAndEndAction( AbstractEventDispatcher * causer, int,int ) { _baseInternal->_buttonBox->hide(); if (causer) causer->onDo_kickTheBucket(); emit apply( this ); doAutoDelete(); return true; } /* RubberbandBase::applyAndEndAction */ } } /* namespace SituationMap::RB */