// SPDX-License-Identifier: LGPL-2.1-or-later // // SPDX-FileCopyrightText: 2010 Dennis Nienhüser // #include "RoutingWidget.h" #include "GeoDataLineString.h" #include "GeoDataLookAt.h" #include "GeoDataPlaylist.h" #include "GeoDataTour.h" #include "GeoDataFlyTo.h" #include "GeoDataStyle.h" #include "GeoDataIconStyle.h" #include "GeoDataPlacemark.h" #include "TourPlayback.h" #include "Maneuver.h" #include "MarbleModel.h" #include "MarblePlacemarkModel.h" #include "MarbleWidget.h" #include "MarbleWidgetInputHandler.h" #include "Route.h" #include "RouteRequest.h" #include "RoutingInputWidget.h" #include "RoutingLayer.h" #include "RoutingModel.h" #include "RoutingProfilesModel.h" #include "RoutingProfileSettingsDialog.h" #include "GeoDataDocument.h" #include "GeoDataTreeModel.h" #include "GeoDataCreate.h" #include "GeoDataUpdate.h" #include "GeoDataDelete.h" #include "AlternativeRoutesModel.h" #include "RouteSyncManager.h" #include "CloudRoutesDialog.h" #include "CloudSyncManager.h" #include "PlaybackAnimatedUpdateItem.h" #include "GeoDataAnimatedUpdate.h" #include "Planet.h" #include #include #include #include #include #include #include #include #include "ui_RoutingWidget.h" namespace Marble { struct WaypointInfo { int index; double distance; // distance to route start GeoDataCoordinates coordinates; Maneuver maneuver; QString info; WaypointInfo( int index_, double distance_, const GeoDataCoordinates &coordinates_, Maneuver maneuver_, const QString& info_ ) : index( index_ ), distance( distance_ ), coordinates( coordinates_ ), maneuver( maneuver_ ), info( info_ ) { // nothing to do } }; class RoutingWidgetPrivate { public: Ui::RoutingWidget m_ui; MarbleWidget *const m_widget; RoutingManager *const m_routingManager; RoutingLayer *const m_routingLayer; RoutingInputWidget *m_activeInput; QVector m_inputWidgets; RoutingInputWidget *m_inputRequest; QAbstractItemModel *const m_routingModel; RouteRequest *const m_routeRequest; RouteSyncManager *m_routeSyncManager; bool m_zoomRouteAfterDownload; QTimer m_progressTimer; QVector m_progressAnimation; GeoDataDocument *m_document; GeoDataTour *m_tour; TourPlayback *m_playback; int m_currentFrame; int m_iconSize; int m_collapse_width; bool m_playing; QString m_planetId; QToolBar *m_toolBar; QToolButton *m_openRouteButton; QToolButton *m_saveRouteButton; QAction *m_cloudSyncSeparator; QAction *m_uploadToCloudAction; QAction *m_openCloudRoutesAction; QToolButton *m_addViaButton; QToolButton *m_reverseRouteButton; QToolButton *m_clearRouteButton; QToolButton *m_configureButton; QToolButton *m_playButton; QProgressDialog* m_routeUploadDialog; /** Constructor */ RoutingWidgetPrivate(RoutingWidget *parent, MarbleWidget *marbleWidget ); /** * @brief Toggle between simple search view and route view * If only one input field exists, hide all buttons */ void adjustInputWidgets(); void adjustSearchButton(); /** * @brief Change the active input widget * The active input widget influences what is shown in the paint layer * and in the list view: Either a set of placemarks that correspond to * a runner search result or the current route */ void setActiveInput( RoutingInputWidget* widget ); void setupToolBar(); private: void createProgressAnimation(); RoutingWidget *m_parent; }; RoutingWidgetPrivate::RoutingWidgetPrivate( RoutingWidget *parent, MarbleWidget *marbleWidget ) : m_widget( marbleWidget ), m_routingManager( marbleWidget->model()->routingManager() ), m_routingLayer( marbleWidget->routingLayer() ), m_activeInput( nullptr ), m_inputRequest( nullptr ), m_routingModel( m_routingManager->routingModel() ), m_routeRequest( marbleWidget->model()->routingManager()->routeRequest() ), m_routeSyncManager( nullptr ), m_zoomRouteAfterDownload( false ), m_document( nullptr ), m_tour( nullptr ), m_playback( nullptr ), m_currentFrame( 0 ), m_iconSize( 16 ), m_collapse_width( 0 ), m_playing( false ), m_planetId(marbleWidget->model()->planetId()), m_toolBar( nullptr ), m_openRouteButton( nullptr ), m_saveRouteButton( nullptr ), m_cloudSyncSeparator( nullptr ), m_uploadToCloudAction( nullptr ), m_openCloudRoutesAction( nullptr ), m_addViaButton( nullptr ), m_reverseRouteButton( nullptr ), m_clearRouteButton( nullptr ), m_configureButton( nullptr ), m_routeUploadDialog( nullptr ), m_parent( parent ) { createProgressAnimation(); m_progressTimer.setInterval( 100 ); if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { m_iconSize = 32; } } void RoutingWidgetPrivate::adjustInputWidgets() { for ( int i = 0; i < m_inputWidgets.size(); ++i ) { m_inputWidgets[i]->setIndex( i ); } adjustSearchButton(); } void RoutingWidgetPrivate::adjustSearchButton() { QString text = QObject::tr( "Get Directions" ); QString tooltip = QObject::tr( "Retrieve routing instructions for the selected destinations." ); int validInputs = 0; for ( int i = 0; i < m_inputWidgets.size(); ++i ) { if ( m_inputWidgets[i]->hasTargetPosition() ) { ++validInputs; } } if ( validInputs < 2 ) { text = QObject::tr( "Search" ); tooltip = QObject::tr( "Find places matching the search term" ); } m_ui.searchButton->setText( text ); m_ui.searchButton->setToolTip( tooltip ); } void RoutingWidgetPrivate::setActiveInput( RoutingInputWidget *widget ) { Q_ASSERT( widget && "Must not pass null" ); MarblePlacemarkModel *model = widget->searchResultModel(); m_activeInput = widget; m_ui.directionsListView->setModel( model ); m_routingLayer->setPlacemarkModel( model ); m_routingLayer->synchronizeWith( m_ui.directionsListView->selectionModel() ); } void RoutingWidgetPrivate::setupToolBar() { m_toolBar = new QToolBar; m_openRouteButton = new QToolButton; m_openRouteButton->setToolTip( QObject::tr("Open Route") ); m_openRouteButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/document-open.png"))); m_toolBar->addWidget(m_openRouteButton); m_saveRouteButton = new QToolButton; m_saveRouteButton->setToolTip( QObject::tr("Save Route") ); m_saveRouteButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/document-save.png"))); m_toolBar->addWidget(m_saveRouteButton); m_playButton = new QToolButton; m_playButton->setToolTip( QObject::tr("Preview Route") ); m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); m_toolBar->addWidget(m_playButton); m_cloudSyncSeparator = m_toolBar->addSeparator(); m_uploadToCloudAction = m_toolBar->addAction( QObject::tr("Upload to Cloud") ); m_uploadToCloudAction->setToolTip( QObject::tr("Upload to Cloud") ); m_uploadToCloudAction->setIcon(QIcon(QStringLiteral(":/icons/cloud-upload.png"))); m_openCloudRoutesAction = m_toolBar->addAction( QObject::tr("Manage Cloud Routes") ); m_openCloudRoutesAction->setToolTip( QObject::tr("Manage Cloud Routes") ); m_openCloudRoutesAction->setIcon(QIcon(QStringLiteral(":/icons/cloud-download.png"))); m_toolBar->addSeparator(); m_addViaButton = new QToolButton; m_addViaButton->setToolTip( QObject::tr("Add Via") ); m_addViaButton->setIcon(QIcon(QStringLiteral(":/marble/list-add.png"))); m_toolBar->addWidget(m_addViaButton); m_reverseRouteButton = new QToolButton; m_reverseRouteButton->setToolTip( QObject::tr("Reverse Route") ); m_reverseRouteButton->setIcon(QIcon(QStringLiteral(":/marble/reverse.png"))); m_toolBar->addWidget(m_reverseRouteButton); m_clearRouteButton = new QToolButton; m_clearRouteButton->setToolTip( QObject::tr("Clear Route") ); m_clearRouteButton->setIcon(QIcon(QStringLiteral(":/marble/edit-clear.png"))); m_toolBar->addWidget(m_clearRouteButton); m_toolBar->addSeparator(); m_configureButton = new QToolButton; m_configureButton->setToolTip( QObject::tr("Settings") ); m_configureButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/configure.png"))); m_toolBar->addWidget(m_configureButton); QObject::connect( m_openRouteButton, SIGNAL(clicked()), m_parent, SLOT(openRoute()) ); QObject::connect( m_saveRouteButton, SIGNAL(clicked()), m_parent, SLOT(saveRoute()) ); QObject::connect( m_uploadToCloudAction, SIGNAL(triggered()), m_parent, SLOT(uploadToCloud()) ); QObject::connect( m_openCloudRoutesAction, SIGNAL(triggered()), m_parent, SLOT(openCloudRoutesDialog())); QObject::connect( m_addViaButton, SIGNAL(clicked()), m_parent, SLOT(addInputWidget()) ); QObject::connect( m_reverseRouteButton, SIGNAL(clicked()), m_routingManager, SLOT(reverseRoute()) ); QObject::connect( m_clearRouteButton, SIGNAL(clicked()), m_routingManager, SLOT(clearRoute()) ); QObject::connect( m_configureButton, SIGNAL(clicked()), m_parent, SLOT(configureProfile()) ); QObject::connect( m_playButton, SIGNAL(clicked()), m_parent, SLOT(toggleRoutePlay()) ); m_toolBar->setIconSize(QSize(16, 16)); m_ui.toolBarLayout->addWidget(m_toolBar, 0, Qt::AlignLeft); } void RoutingWidgetPrivate::createProgressAnimation() { // Size parameters qreal const h = m_iconSize / 2.0; // Half of the icon size qreal const q = h / 2.0; // Quarter of the icon size qreal const d = 7.5; // Circle diameter qreal const r = d / 2.0; // Circle radius // Canvas parameters QImage canvas( m_iconSize, m_iconSize, QImage::Format_ARGB32 ); QPainter painter( &canvas ); painter.setRenderHint( QPainter::Antialiasing, true ); painter.setPen( QColor ( Qt::gray ) ); painter.setBrush( QColor( Qt::white ) ); // Create all frames for( double t = 0.0; t < 2 * M_PI; t += M_PI / 8.0 ) { canvas.fill( Qt::transparent ); QRectF firstCircle( h - r + q * cos( t ), h - r + q * sin( t ), d, d ); QRectF secondCircle( h - r + q * cos( t + M_PI ), h - r + q * sin( t + M_PI ), d, d ); painter.drawEllipse( firstCircle ); painter.drawEllipse( secondCircle ); m_progressAnimation.push_back( QIcon( QPixmap::fromImage( canvas ) ) ); } } RoutingWidget::RoutingWidget( MarbleWidget *marbleWidget, QWidget *parent ) : QWidget( parent ), d( new RoutingWidgetPrivate( this, marbleWidget ) ) { d->m_ui.setupUi( this ); d->setupToolBar(); d->m_ui.routeComboBox->setVisible( false ); d->m_ui.routeComboBox->setModel( d->m_routingManager->alternativeRoutesModel() ); layout()->setMargin( 0 ); d->m_ui.routingProfileComboBox->setModel( d->m_routingManager->profilesModel() ); connect( d->m_routingManager->profilesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(selectFirstProfile()) ); connect( d->m_routingManager->profilesModel(), SIGNAL(modelReset()), this, SLOT(selectFirstProfile()) ); connect( d->m_routingLayer, SIGNAL(placemarkSelected(QModelIndex)), this, SLOT(activatePlacemark(QModelIndex)) ); connect( d->m_routingManager, SIGNAL(stateChanged(RoutingManager::State)), this, SLOT(updateRouteState(RoutingManager::State)) ); connect( d->m_routeRequest, SIGNAL(positionAdded(int)), this, SLOT(insertInputWidget(int)) ); connect( d->m_routeRequest, SIGNAL(positionRemoved(int)), this, SLOT(removeInputWidget(int)) ); connect( d->m_routeRequest, SIGNAL(routingProfileChanged()), this, SLOT(updateActiveRoutingProfile()) ); connect( &d->m_progressTimer, SIGNAL(timeout()), this, SLOT(updateProgress()) ); connect( d->m_ui.routeComboBox, SIGNAL(currentIndexChanged(int)), d->m_routingManager->alternativeRoutesModel(), SLOT(setCurrentRoute(int)) ); connect( d->m_routingManager->alternativeRoutesModel(), SIGNAL(currentRouteChanged(int)), d->m_ui.routeComboBox, SLOT(setCurrentIndex(int)) ); connect( d->m_ui.routingProfileComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setRoutingProfile(int)) ); connect( d->m_ui.routingProfileComboBox, SIGNAL(activated(int)), this, SLOT(retrieveRoute()) ); connect( d->m_routingManager->alternativeRoutesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateAlternativeRoutes()) ); d->m_ui.directionsListView->setModel( d->m_routingModel ); QItemSelectionModel *selectionModel = d->m_ui.directionsListView->selectionModel(); d->m_routingLayer->synchronizeWith( selectionModel ); connect( d->m_ui.directionsListView, SIGNAL(activated(QModelIndex)), this, SLOT(activateItem(QModelIndex)) ); // FIXME: apply for this sector connect( d->m_ui.searchButton, SIGNAL(clicked()), this, SLOT(retrieveRoute()) ); connect( d->m_ui.showInstructionsButton, SIGNAL(clicked(bool)), this, SLOT(showDirections()) ); for( int i=0; im_routeRequest->size(); ++i ) { insertInputWidget( i ); } for ( int i=0; i<2 && d->m_inputWidgets.size()<2; ++i ) { // Start with source and destination if the route is empty yet addInputWidget(); } //d->m_ui.descriptionLabel->setVisible( false ); d->m_ui.resultLabel->setVisible( false ); setShowDirectionsButtonVisible( false ); updateActiveRoutingProfile(); updateCloudSyncButtons(); if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { d->m_ui.directionsListView->setVisible( false ); d->m_openRouteButton->setVisible( false ); d->m_saveRouteButton->setVisible( false ); } connect( marbleWidget->model(), SIGNAL(themeChanged(QString)), this, SLOT(handlePlanetChange()) ); } RoutingWidget::~RoutingWidget() { delete d->m_playback; delete d->m_tour; if( d->m_document ){ d->m_widget->model()->treeModel()->removeDocument( d->m_document ); delete d->m_document; } delete d; } void RoutingWidget::retrieveRoute() { if ( d->m_inputWidgets.size() == 1 ) { // Search mode d->m_inputWidgets.first()->findPlacemarks(); return; } int index = d->m_ui.routingProfileComboBox->currentIndex(); if ( index == -1 ) { return; } d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) ); Q_ASSERT( d->m_routeRequest->size() == d->m_inputWidgets.size() ); for ( int i = 0; i < d->m_inputWidgets.size(); ++i ) { RoutingInputWidget *widget = d->m_inputWidgets.at( i ); if ( !widget->hasTargetPosition() && widget->hasInput() ) { widget->findPlacemarks(); return; } } d->m_activeInput = nullptr; if ( d->m_routeRequest->size() > 1 ) { d->m_zoomRouteAfterDownload = true; d->m_routingLayer->setPlacemarkModel( nullptr ); d->m_routingManager->retrieveRoute(); d->m_ui.directionsListView->setModel( d->m_routingModel ); d->m_routingLayer->synchronizeWith( d->m_ui.directionsListView->selectionModel() ); } if( d->m_playback ) { d->m_playback->stop(); } } void RoutingWidget::activateItem ( const QModelIndex &index ) { QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); if ( !data.isNull() ) { GeoDataCoordinates position = qvariant_cast( data ); d->m_widget->centerOn( position, true ); } if ( d->m_activeInput && index.isValid() ) { QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); if ( !data.isNull() ) { d->m_activeInput->setTargetPosition( data.value(), index.data().toString() ); } } } void RoutingWidget::handleSearchResult( RoutingInputWidget *widget ) { d->setActiveInput( widget ); MarblePlacemarkModel *model = widget->searchResultModel(); if ( model->rowCount() ) { QString const results = tr( "placemarks found: %1" ).arg( model->rowCount() ); d->m_ui.resultLabel->setText( results ); d->m_ui.resultLabel->setVisible( true ); // Make sure we have a selection activatePlacemark( model->index( 0, 0 ) ); } else { QString const results = tr( "No placemark found" ); d->m_ui.resultLabel->setText(QLatin1String("") + results + QLatin1String("")); d->m_ui.resultLabel->setVisible( true ); } GeoDataLineString placemarks; for ( int i = 0; i < model->rowCount(); ++i ) { QVariant data = model->index( i, 0 ).data( MarblePlacemarkModel::CoordinateRole ); if ( !data.isNull() ) { placemarks << data.value(); } } if ( placemarks.size() > 1 ) { d->m_widget->centerOn( GeoDataLatLonBox::fromLineString( placemarks ) ); //d->m_ui.descriptionLabel->setVisible( false ); if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { d->m_ui.directionsListView->setVisible( true ); } } } void RoutingWidget::centerOnInputWidget( RoutingInputWidget *widget ) { if ( widget->hasTargetPosition() ) { d->m_widget->centerOn( widget->targetPosition() ); } } void RoutingWidget::activatePlacemark( const QModelIndex &index ) { if ( d->m_activeInput && index.isValid() ) { QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); if ( !data.isNull() ) { d->m_activeInput->setTargetPosition( data.value() ); } } d->m_ui.directionsListView->setCurrentIndex( index ); } void RoutingWidget::addInputWidget() { d->m_routeRequest->append( GeoDataCoordinates() ); } void RoutingWidget::insertInputWidget( int index ) { if ( index >= 0 && index <= d->m_inputWidgets.size() ) { RoutingInputWidget *input = new RoutingInputWidget( d->m_widget->model(), index, this ); d->m_inputWidgets.insert( index, input ); connect( input, SIGNAL(searchFinished(RoutingInputWidget*)), this, SLOT(handleSearchResult(RoutingInputWidget*)) ); connect( input, SIGNAL(removalRequest(RoutingInputWidget*)), this, SLOT(removeInputWidget(RoutingInputWidget*)) ); connect( input, SIGNAL(activityRequest(RoutingInputWidget*)), this, SLOT(centerOnInputWidget(RoutingInputWidget*)) ); connect( input, SIGNAL(mapInputModeEnabled(RoutingInputWidget*,bool)), this, SLOT(requestMapPosition(RoutingInputWidget*,bool)) ); connect( input, SIGNAL(targetValidityChanged(bool)), this, SLOT(adjustSearchButton()) ); d->m_ui.inputLayout->insertWidget( index, input ); d->adjustInputWidgets(); } } void RoutingWidget::removeInputWidget( RoutingInputWidget *widget ) { int index = d->m_inputWidgets.indexOf( widget ); if ( index >= 0 ) { if ( d->m_inputWidgets.size() < 3 ) { widget->clear(); } else { d->m_routeRequest->remove( index ); } d->m_routingManager->retrieveRoute(); } } void RoutingWidget::removeInputWidget( int index ) { if ( index >= 0 && index < d->m_inputWidgets.size() ) { RoutingInputWidget *widget = d->m_inputWidgets.at( index ); d->m_inputWidgets.remove( index ); d->m_ui.inputLayout->removeWidget( widget ); widget->deleteLater(); if ( widget == d->m_activeInput ) { d->m_activeInput = nullptr; d->m_routingLayer->setPlacemarkModel( nullptr ); d->m_ui.directionsListView->setModel( d->m_routingModel ); d->m_routingLayer->synchronizeWith( d->m_ui.directionsListView->selectionModel() ); } d->adjustInputWidgets(); } if ( d->m_inputWidgets.size() < 2 ) { addInputWidget(); } } void RoutingWidget::updateRouteState( RoutingManager::State state ) { clearTour(); switch ( state ) { case RoutingManager::Downloading: d->m_ui.routeComboBox->setVisible( false ); d->m_ui.routeComboBox->clear(); d->m_progressTimer.start(); d->m_ui.resultLabel->setVisible( false ); break; case RoutingManager::Retrieved: { d->m_progressTimer.stop(); d->m_ui.searchButton->setIcon( QIcon() ); if ( d->m_routingManager->routingModel()->rowCount() == 0 ) { const QString results = tr( "No route found" ); d->m_ui.resultLabel->setText(QLatin1String("") + results + QLatin1String("")); d->m_ui.resultLabel->setVisible( true ); } } break; } d->m_saveRouteButton->setEnabled( d->m_routingManager->routingModel()->rowCount() > 0 ); } void RoutingWidget::requestMapPosition( RoutingInputWidget *widget, bool enabled ) { pointSelectionCanceled(); if ( enabled ) { d->m_inputRequest = widget; d->m_widget->installEventFilter( this ); d->m_widget->setFocus( Qt::OtherFocusReason ); } } void RoutingWidget::retrieveSelectedPoint( const GeoDataCoordinates &coordinates ) { if ( d->m_inputRequest && d->m_inputWidgets.contains( d->m_inputRequest ) ) { d->m_inputRequest->setTargetPosition( coordinates ); d->m_widget->update(); } d->m_inputRequest = nullptr; d->m_widget->removeEventFilter( this ); } void RoutingWidget::adjustSearchButton() { d->adjustSearchButton(); } void RoutingWidget::pointSelectionCanceled() { if ( d->m_inputRequest && d->m_inputWidgets.contains( d->m_inputRequest ) ) { d->m_inputRequest->abortMapInputRequest(); } d->m_inputRequest = nullptr; d->m_widget->removeEventFilter( this ); } void RoutingWidget::configureProfile() { int index = d->m_ui.routingProfileComboBox->currentIndex(); if ( index != -1 ) { RoutingProfileSettingsDialog dialog( d->m_widget->model()->pluginManager(), d->m_routingManager->profilesModel(), this ); dialog.editProfile( d->m_ui.routingProfileComboBox->currentIndex() ); d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) ); } } void RoutingWidget::updateProgress() { if ( !d->m_progressAnimation.isEmpty() ) { d->m_currentFrame = ( d->m_currentFrame + 1 ) % d->m_progressAnimation.size(); QIcon frame = d->m_progressAnimation[d->m_currentFrame]; d->m_ui.searchButton->setIcon( frame ); } } void RoutingWidget::updateAlternativeRoutes() { if ( d->m_ui.routeComboBox->count() == 1) { // Parts of the route may lie outside the route trip points GeoDataLatLonBox const bbox = d->m_routingManager->routingModel()->route().bounds(); if ( d->m_zoomRouteAfterDownload ) { d->m_zoomRouteAfterDownload = false; d->m_widget->centerOn( bbox ); } } d->m_ui.routeComboBox->setVisible( d->m_ui.routeComboBox->count() > 0 ); if ( d->m_ui.routeComboBox->currentIndex() < 0 && d->m_ui.routeComboBox->count() > 0 ) { d->m_ui.routeComboBox->setCurrentIndex( 0 ); } QString const results = tr( "routes found: %1" ).arg( d->m_ui.routeComboBox->count() ); d->m_ui.resultLabel->setText( results ); d->m_ui.resultLabel->setVisible( true ); d->m_saveRouteButton->setEnabled( d->m_routingManager->routingModel()->rowCount() > 0 ); } void RoutingWidget::setShowDirectionsButtonVisible( bool visible ) { d->m_ui.showInstructionsButton->setVisible( visible ); } void RoutingWidget::setRouteSyncManager(RouteSyncManager *manager) { d->m_routeSyncManager = manager; connect( d->m_routeSyncManager, SIGNAL(routeSyncEnabledChanged(bool)), this, SLOT(updateCloudSyncButtons()) ); updateCloudSyncButtons(); } void RoutingWidget::openRoute() { QString const file = QFileDialog::getOpenFileName( this, tr( "Open Route" ), d->m_routingManager->lastOpenPath(), tr("KML Files (*.kml)") ); if ( !file.isEmpty() ) { d->m_routingManager->setLastOpenPath( QFileInfo( file ).absolutePath() ); d->m_zoomRouteAfterDownload = true; d->m_routingManager->loadRoute( file ); updateAlternativeRoutes(); } } void RoutingWidget::selectFirstProfile() { int count = d->m_routingManager->profilesModel()->rowCount(); if ( count && d->m_ui.routingProfileComboBox->currentIndex() < 0 ) { d->m_ui.routingProfileComboBox->setCurrentIndex( 0 ); } } void RoutingWidget::setRoutingProfile( int index ) { if ( index >= 0 && index < d->m_routingManager->profilesModel()->rowCount() ) { d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) ); } } void RoutingWidget::showDirections() { d->m_ui.directionsListView->setVisible( true ); } void RoutingWidget::saveRoute() { QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Route" ), // krazy:exclude=qclasses d->m_routingManager->lastSavePath(), tr( "KML files (*.kml)" ) ); if ( !fileName.isEmpty() ) { // maemo 5 file dialog does not append the file extension if ( !fileName.endsWith(QLatin1String( ".kml" ), Qt::CaseInsensitive) ) { fileName += QLatin1String(".kml"); } d->m_routingManager->setLastSavePath( QFileInfo( fileName ).absolutePath() ); d->m_routingManager->saveRoute( fileName ); } } void RoutingWidget::uploadToCloud() { Q_ASSERT( d->m_routeSyncManager ); if (!d->m_routeUploadDialog) { d->m_routeUploadDialog = new QProgressDialog( d->m_widget ); d->m_routeUploadDialog->setWindowTitle( tr( "Uploading route..." ) ); d->m_routeUploadDialog->setMinimum( 0 ); d->m_routeUploadDialog->setMaximum( 100 ); d->m_routeUploadDialog->setAutoClose( true ); d->m_routeUploadDialog->setAutoReset( true ); connect( d->m_routeSyncManager, SIGNAL(routeUploadProgress(qint64,qint64)), this, SLOT(updateUploadProgress(qint64,qint64)) ); } d->m_routeUploadDialog->show(); d->m_routeSyncManager->uploadRoute(); } void RoutingWidget::openCloudRoutesDialog() { Q_ASSERT( d->m_routeSyncManager ); d->m_routeSyncManager->prepareRouteList(); QPointer dialog = new CloudRoutesDialog( d->m_routeSyncManager->model(), d->m_widget ); connect( d->m_routeSyncManager, SIGNAL(routeListDownloadProgress(qint64,qint64)), dialog, SLOT(updateListDownloadProgressbar(qint64,qint64)) ); connect( dialog, SIGNAL(downloadButtonClicked(QString)), d->m_routeSyncManager, SLOT(downloadRoute(QString)) ); connect( dialog, SIGNAL(openButtonClicked(QString)), this, SLOT(openCloudRoute(QString)) ); connect( dialog, SIGNAL(deleteButtonClicked(QString)), d->m_routeSyncManager, SLOT(deleteRoute(QString)) ); connect( dialog, SIGNAL(removeFromCacheButtonClicked(QString)), d->m_routeSyncManager, SLOT(removeRouteFromCache(QString)) ); connect( dialog, SIGNAL(uploadToCloudButtonClicked(QString)), d->m_routeSyncManager, SLOT(uploadRoute(QString)) ); dialog->exec(); delete dialog; } void RoutingWidget::updateActiveRoutingProfile() { RoutingProfile const profile = d->m_routingManager->routeRequest()->routingProfile(); QList const profiles = d->m_routingManager->profilesModel()->profiles(); d->m_ui.routingProfileComboBox->setCurrentIndex( profiles.indexOf( profile ) ); } void RoutingWidget::updateCloudSyncButtons() { bool const show = d->m_routeSyncManager && d->m_routeSyncManager->isRouteSyncEnabled(); d->m_cloudSyncSeparator->setVisible( show ); d->m_uploadToCloudAction->setVisible( show ); d->m_openCloudRoutesAction->setVisible( show ); } void RoutingWidget::openCloudRoute(const QString &identifier) { Q_ASSERT( d->m_routeSyncManager ); d->m_routeSyncManager->openRoute( identifier ); d->m_widget->centerOn( d->m_routingManager->routingModel()->route().bounds() ); } void RoutingWidget::updateUploadProgress(qint64 sent, qint64 total) { Q_ASSERT( d->m_routeUploadDialog ); d->m_routeUploadDialog->setValue( 100.0 * sent / total ); } bool RoutingWidget::eventFilter( QObject *o, QEvent *event ) { if ( o != d->m_widget ) { return QWidget::eventFilter( o, event ); } Q_ASSERT( d->m_inputRequest != nullptr ); Q_ASSERT( d->m_inputWidgets.contains( d->m_inputRequest ) ); if ( event->type() == QEvent::MouseButtonPress ) { QMouseEvent *e = static_cast( event ); return e->button() == Qt::LeftButton; } if ( event->type() == QEvent::MouseButtonRelease ) { QMouseEvent *e = static_cast( event ); qreal lon( 0.0 ), lat( 0.0 ); if ( e->button() == Qt::LeftButton && d->m_widget->geoCoordinates( e->pos().x(), e->pos().y(), lon, lat, GeoDataCoordinates::Radian ) ) { retrieveSelectedPoint( GeoDataCoordinates( lon, lat ) ); return true; } else { return QWidget::eventFilter( o, event ); } } if ( event->type() == QEvent::MouseMove ) { d->m_widget->setCursor( Qt::CrossCursor ); return true; } if ( event->type() == QEvent::KeyPress ) { QKeyEvent *e = static_cast( event ); if ( e->key() == Qt::Key_Escape ) { pointSelectionCanceled(); return true; } return QWidget::eventFilter( o, event ); } return QWidget::eventFilter( o, event ); } void RoutingWidget::resizeEvent(QResizeEvent *e) { QWidget::resizeEvent(e); } void RoutingWidget::toggleRoutePlay() { if( !d->m_playback ){ if( d->m_routingModel->rowCount() != 0 ){ initializeTour(); } } if (!d->m_playback) return; if( !d->m_playing ){ d->m_playing = true; d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-pause.png"))); if( d->m_playback ){ d->m_playback->play(); } } else { d->m_playing = false; d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); d->m_playback->pause(); } } void RoutingWidget::initializeTour() { d->m_tour = new GeoDataTour; if( d->m_document ){ d->m_widget->model()->treeModel()->removeDocument( d->m_document ); delete d->m_document; } d->m_document = new GeoDataDocument; d->m_document->setId(QStringLiteral("tourdoc")); d->m_document->append( d->m_tour ); d->m_tour->setPlaylist( new GeoDataPlaylist ); Route const route = d->m_widget->model()->routingManager()->routingModel()->route(); GeoDataLineString path = route.path(); if ( path.size() < 1 ){ return; } QList waypoints; double totalDistance = 0.0; for( int i=0; i const allWaypoints = waypoints; totalDistance = 0.0; GeoDataCoordinates last = path.at( 0 ); int j=0; // next waypoint qreal planetRadius = d->m_widget->model()->planet()->radius(); for( int i=1; i= allWaypoints[j].distance && j+1 1 && distance < step ){ continue; } last = coordinates; GeoDataLookAt* lookat = new GeoDataLookAt; // Choose a zoom distance of 400, 600 or 800 meters based on the distance to the closest waypoint double const range = waypointDistance < 400 ? 400 : ( waypointDistance < 2000 ? 600 : 800 ); coordinates.setAltitude( range ); lookat->setCoordinates( coordinates ); lookat->setRange( range ); GeoDataFlyTo* flyto = new GeoDataFlyTo; double const duration = 0.75; flyto->setDuration( duration ); flyto->setView( lookat ); flyto->setFlyToMode( GeoDataFlyTo::Smooth ); d->m_tour->playlist()->addPrimitive( flyto ); if( !waypoints.empty() && totalDistance > waypoints.first().distance-100 ){ WaypointInfo const waypoint = waypoints.first(); waypoints.pop_front(); GeoDataAnimatedUpdate *updateCreate = new GeoDataAnimatedUpdate; updateCreate->setUpdate( new GeoDataUpdate ); updateCreate->update()->setCreate( new GeoDataCreate ); GeoDataPlacemark *placemarkCreate = new GeoDataPlacemark; QString const waypointId = QString( "waypoint-%1" ).arg( i, 0, 10 ); placemarkCreate->setId( waypointId ); placemarkCreate->setTargetId( d->m_document->id() ); placemarkCreate->setCoordinate( waypoint.coordinates ); GeoDataStyle::Ptr style(new GeoDataStyle); style->iconStyle().setIconPath( waypoint.maneuver.directionPixmap() ); placemarkCreate->setStyle( style ); updateCreate->update()->create()->append( placemarkCreate ); d->m_tour->playlist()->addPrimitive( updateCreate ); GeoDataAnimatedUpdate *updateDelete = new GeoDataAnimatedUpdate; updateDelete->setDelayedStart( 2 ); updateDelete->setUpdate( new GeoDataUpdate ); updateDelete->update()->setDelete( new GeoDataDelete ); GeoDataPlacemark *placemarkDelete = new GeoDataPlacemark; placemarkDelete->setTargetId( waypointId ); updateDelete->update()->getDelete()->append( placemarkDelete ); d->m_tour->playlist()->addPrimitive( updateDelete ); } } d->m_playback = new TourPlayback; d->m_playback->setMarbleWidget( d->m_widget ); d->m_playback->setTour( d->m_tour ); d->m_widget->model()->treeModel()->addDocument( d->m_document ); QObject::connect( d->m_playback, SIGNAL(finished()), this, SLOT(seekTourToStart()) ); } void RoutingWidget::centerOn( const GeoDataCoordinates &coordinates ) { if ( d->m_widget ) { GeoDataLookAt lookat; lookat.setCoordinates( coordinates ); lookat.setRange( coordinates.altitude() ); d->m_widget->flyTo( lookat, Instant ); } } void RoutingWidget::clearTour() { d->m_playing = false; d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); delete d->m_playback; d->m_playback = nullptr; if( d->m_document ){ d->m_widget->model()->treeModel()->removeDocument( d->m_document ); delete d->m_document; d->m_document = nullptr; d->m_tour = nullptr; } } void RoutingWidget::seekTourToStart() { Q_ASSERT( d->m_playback ); d->m_playback->stop(); d->m_playback->seek( 0 ); d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); d->m_playing = false; } void RoutingWidget::handlePlanetChange() { const QString newPlanetId = d->m_widget->model()->planetId(); if (newPlanetId == d->m_planetId) { return; } d->m_planetId = newPlanetId; d->m_routingManager->clearRoute(); } } // namespace Marble #include "moc_RoutingWidget.cpp"