File indexing completed on 2024-04-28 03:49:31
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org> 0004 // 0005 0006 #include "RoutingWidget.h" 0007 0008 #include "GeoDataLineString.h" 0009 #include "GeoDataLookAt.h" 0010 #include "GeoDataPlaylist.h" 0011 #include "GeoDataTour.h" 0012 #include "GeoDataFlyTo.h" 0013 #include "GeoDataStyle.h" 0014 #include "GeoDataIconStyle.h" 0015 #include "GeoDataPlacemark.h" 0016 #include "TourPlayback.h" 0017 #include "Maneuver.h" 0018 #include "MarbleModel.h" 0019 #include "MarblePlacemarkModel.h" 0020 #include "MarbleWidget.h" 0021 #include "MarbleWidgetInputHandler.h" 0022 #include "Route.h" 0023 #include "RouteRequest.h" 0024 #include "RoutingInputWidget.h" 0025 #include "RoutingLayer.h" 0026 #include "RoutingModel.h" 0027 #include "RoutingProfilesModel.h" 0028 #include "RoutingProfileSettingsDialog.h" 0029 #include "GeoDataDocument.h" 0030 #include "GeoDataTreeModel.h" 0031 #include "GeoDataCreate.h" 0032 #include "GeoDataUpdate.h" 0033 #include "GeoDataDelete.h" 0034 #include "AlternativeRoutesModel.h" 0035 #include "RouteSyncManager.h" 0036 #include "CloudRoutesDialog.h" 0037 #include "CloudSyncManager.h" 0038 #include "PlaybackAnimatedUpdateItem.h" 0039 #include "GeoDataAnimatedUpdate.h" 0040 #include "Planet.h" 0041 0042 #include <QTimer> 0043 #include <QPainter> 0044 #include <QFileDialog> 0045 #include <QKeyEvent> 0046 #include <QMouseEvent> 0047 #include <QToolBar> 0048 #include <QToolButton> 0049 #include <QProgressDialog> 0050 0051 #include "ui_RoutingWidget.h" 0052 0053 namespace Marble 0054 { 0055 0056 struct WaypointInfo 0057 { 0058 int index; 0059 double distance; // distance to route start 0060 GeoDataCoordinates coordinates; 0061 Maneuver maneuver; 0062 QString info; 0063 0064 WaypointInfo( int index_, double distance_, const GeoDataCoordinates &coordinates_, Maneuver maneuver_, const QString& info_ ) : 0065 index( index_ ), 0066 distance( distance_ ), 0067 coordinates( coordinates_ ), 0068 maneuver( maneuver_ ), 0069 info( info_ ) 0070 { 0071 // nothing to do 0072 } 0073 }; 0074 0075 class RoutingWidgetPrivate 0076 { 0077 public: 0078 Ui::RoutingWidget m_ui; 0079 MarbleWidget *const m_widget; 0080 RoutingManager *const m_routingManager; 0081 RoutingLayer *const m_routingLayer; 0082 RoutingInputWidget *m_activeInput; 0083 QVector<RoutingInputWidget*> m_inputWidgets; 0084 RoutingInputWidget *m_inputRequest; 0085 QAbstractItemModel *const m_routingModel; 0086 RouteRequest *const m_routeRequest; 0087 RouteSyncManager *m_routeSyncManager; 0088 bool m_zoomRouteAfterDownload; 0089 QTimer m_progressTimer; 0090 QVector<QIcon> m_progressAnimation; 0091 GeoDataDocument *m_document; 0092 GeoDataTour *m_tour; 0093 TourPlayback *m_playback; 0094 int m_currentFrame; 0095 int m_iconSize; 0096 int m_collapse_width; 0097 bool m_playing; 0098 QString m_planetId; 0099 0100 QToolBar *m_toolBar; 0101 0102 QToolButton *m_openRouteButton; 0103 QToolButton *m_saveRouteButton; 0104 QAction *m_cloudSyncSeparator; 0105 QAction *m_uploadToCloudAction; 0106 QAction *m_openCloudRoutesAction; 0107 QToolButton *m_addViaButton; 0108 QToolButton *m_reverseRouteButton; 0109 QToolButton *m_clearRouteButton; 0110 QToolButton *m_configureButton; 0111 QToolButton *m_playButton; 0112 0113 QProgressDialog* m_routeUploadDialog; 0114 0115 /** Constructor */ 0116 RoutingWidgetPrivate(RoutingWidget *parent, MarbleWidget *marbleWidget ); 0117 0118 /** 0119 * @brief Toggle between simple search view and route view 0120 * If only one input field exists, hide all buttons 0121 */ 0122 void adjustInputWidgets(); 0123 0124 void adjustSearchButton(); 0125 0126 /** 0127 * @brief Change the active input widget 0128 * The active input widget influences what is shown in the paint layer 0129 * and in the list view: Either a set of placemarks that correspond to 0130 * a runner search result or the current route 0131 */ 0132 void setActiveInput( RoutingInputWidget* widget ); 0133 0134 void setupToolBar(); 0135 0136 private: 0137 void createProgressAnimation(); 0138 RoutingWidget *m_parent; 0139 }; 0140 0141 RoutingWidgetPrivate::RoutingWidgetPrivate( RoutingWidget *parent, MarbleWidget *marbleWidget ) : 0142 m_widget( marbleWidget ), 0143 m_routingManager( marbleWidget->model()->routingManager() ), 0144 m_routingLayer( marbleWidget->routingLayer() ), 0145 m_activeInput( nullptr ), 0146 m_inputRequest( nullptr ), 0147 m_routingModel( m_routingManager->routingModel() ), 0148 m_routeRequest( marbleWidget->model()->routingManager()->routeRequest() ), 0149 m_routeSyncManager( nullptr ), 0150 m_zoomRouteAfterDownload( false ), 0151 m_document( nullptr ), 0152 m_tour( nullptr ), 0153 m_playback( nullptr ), 0154 m_currentFrame( 0 ), 0155 m_iconSize( 16 ), 0156 m_collapse_width( 0 ), 0157 m_playing( false ), 0158 m_planetId(marbleWidget->model()->planetId()), 0159 m_toolBar( nullptr ), 0160 m_openRouteButton( nullptr ), 0161 m_saveRouteButton( nullptr ), 0162 m_cloudSyncSeparator( nullptr ), 0163 m_uploadToCloudAction( nullptr ), 0164 m_openCloudRoutesAction( nullptr ), 0165 m_addViaButton( nullptr ), 0166 m_reverseRouteButton( nullptr ), 0167 m_clearRouteButton( nullptr ), 0168 m_configureButton( nullptr ), 0169 m_routeUploadDialog( nullptr ), 0170 m_parent( parent ) 0171 { 0172 createProgressAnimation(); 0173 m_progressTimer.setInterval( 100 ); 0174 if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { 0175 m_iconSize = 32; 0176 } 0177 } 0178 0179 void RoutingWidgetPrivate::adjustInputWidgets() 0180 { 0181 for ( int i = 0; i < m_inputWidgets.size(); ++i ) { 0182 m_inputWidgets[i]->setIndex( i ); 0183 } 0184 0185 adjustSearchButton(); 0186 } 0187 0188 void RoutingWidgetPrivate::adjustSearchButton() 0189 { 0190 QString text = QObject::tr( "Get Directions" ); 0191 QString tooltip = QObject::tr( "Retrieve routing instructions for the selected destinations." ); 0192 0193 int validInputs = 0; 0194 for ( int i = 0; i < m_inputWidgets.size(); ++i ) { 0195 if ( m_inputWidgets[i]->hasTargetPosition() ) { 0196 ++validInputs; 0197 } 0198 } 0199 0200 if ( validInputs < 2 ) { 0201 text = QObject::tr( "Search" ); 0202 tooltip = QObject::tr( "Find places matching the search term" ); 0203 } 0204 0205 m_ui.searchButton->setText( text ); 0206 m_ui.searchButton->setToolTip( tooltip ); 0207 } 0208 0209 void RoutingWidgetPrivate::setActiveInput( RoutingInputWidget *widget ) 0210 { 0211 Q_ASSERT( widget && "Must not pass null" ); 0212 MarblePlacemarkModel *model = widget->searchResultModel(); 0213 0214 m_activeInput = widget; 0215 m_ui.directionsListView->setModel( model ); 0216 m_routingLayer->setPlacemarkModel( model ); 0217 m_routingLayer->synchronizeWith( m_ui.directionsListView->selectionModel() ); 0218 } 0219 0220 void RoutingWidgetPrivate::setupToolBar() 0221 { 0222 m_toolBar = new QToolBar; 0223 0224 m_openRouteButton = new QToolButton; 0225 m_openRouteButton->setToolTip( QObject::tr("Open Route") ); 0226 m_openRouteButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/document-open.png"))); 0227 m_toolBar->addWidget(m_openRouteButton); 0228 0229 m_saveRouteButton = new QToolButton; 0230 m_saveRouteButton->setToolTip( QObject::tr("Save Route") ); 0231 m_saveRouteButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/document-save.png"))); 0232 m_toolBar->addWidget(m_saveRouteButton); 0233 0234 m_playButton = new QToolButton; 0235 m_playButton->setToolTip( QObject::tr("Preview Route") ); 0236 m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); 0237 m_toolBar->addWidget(m_playButton); 0238 0239 m_cloudSyncSeparator = m_toolBar->addSeparator(); 0240 m_uploadToCloudAction = m_toolBar->addAction( QObject::tr("Upload to Cloud") ); 0241 m_uploadToCloudAction->setToolTip( QObject::tr("Upload to Cloud") ); 0242 m_uploadToCloudAction->setIcon(QIcon(QStringLiteral(":/icons/cloud-upload.png"))); 0243 0244 m_openCloudRoutesAction = m_toolBar->addAction( QObject::tr("Manage Cloud Routes") ); 0245 m_openCloudRoutesAction->setToolTip( QObject::tr("Manage Cloud Routes") ); 0246 m_openCloudRoutesAction->setIcon(QIcon(QStringLiteral(":/icons/cloud-download.png"))); 0247 0248 m_toolBar->addSeparator(); 0249 m_addViaButton = new QToolButton; 0250 m_addViaButton->setToolTip( QObject::tr("Add Via") ); 0251 m_addViaButton->setIcon(QIcon(QStringLiteral(":/marble/list-add.png"))); 0252 m_toolBar->addWidget(m_addViaButton); 0253 0254 m_reverseRouteButton = new QToolButton; 0255 m_reverseRouteButton->setToolTip( QObject::tr("Reverse Route") ); 0256 m_reverseRouteButton->setIcon(QIcon(QStringLiteral(":/marble/reverse.png"))); 0257 m_toolBar->addWidget(m_reverseRouteButton); 0258 0259 m_clearRouteButton = new QToolButton; 0260 m_clearRouteButton->setToolTip( QObject::tr("Clear Route") ); 0261 m_clearRouteButton->setIcon(QIcon(QStringLiteral(":/marble/edit-clear.png"))); 0262 m_toolBar->addWidget(m_clearRouteButton); 0263 0264 m_toolBar->addSeparator(); 0265 0266 m_configureButton = new QToolButton; 0267 m_configureButton->setToolTip( QObject::tr("Settings") ); 0268 m_configureButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/configure.png"))); 0269 m_toolBar->addWidget(m_configureButton); 0270 0271 QObject::connect( m_openRouteButton, SIGNAL(clicked()), 0272 m_parent, SLOT(openRoute()) ); 0273 QObject::connect( m_saveRouteButton, SIGNAL(clicked()), 0274 m_parent, SLOT(saveRoute()) ); 0275 QObject::connect( m_uploadToCloudAction, SIGNAL(triggered()), 0276 m_parent, SLOT(uploadToCloud()) ); 0277 QObject::connect( m_openCloudRoutesAction, SIGNAL(triggered()), 0278 m_parent, SLOT(openCloudRoutesDialog())); 0279 QObject::connect( m_addViaButton, SIGNAL(clicked()), 0280 m_parent, SLOT(addInputWidget()) ); 0281 QObject::connect( m_reverseRouteButton, SIGNAL(clicked()), 0282 m_routingManager, SLOT(reverseRoute()) ); 0283 QObject::connect( m_clearRouteButton, SIGNAL(clicked()), 0284 m_routingManager, SLOT(clearRoute()) ); 0285 QObject::connect( m_configureButton, SIGNAL(clicked()), 0286 m_parent, SLOT(configureProfile()) ); 0287 QObject::connect( m_playButton, SIGNAL(clicked()), 0288 m_parent, SLOT(toggleRoutePlay()) ); 0289 0290 m_toolBar->setIconSize(QSize(16, 16)); 0291 m_ui.toolBarLayout->addWidget(m_toolBar, 0, Qt::AlignLeft); 0292 } 0293 0294 void RoutingWidgetPrivate::createProgressAnimation() 0295 { 0296 // Size parameters 0297 qreal const h = m_iconSize / 2.0; // Half of the icon size 0298 qreal const q = h / 2.0; // Quarter of the icon size 0299 qreal const d = 7.5; // Circle diameter 0300 qreal const r = d / 2.0; // Circle radius 0301 0302 // Canvas parameters 0303 QImage canvas( m_iconSize, m_iconSize, QImage::Format_ARGB32 ); 0304 QPainter painter( &canvas ); 0305 painter.setRenderHint( QPainter::Antialiasing, true ); 0306 painter.setPen( QColor ( Qt::gray ) ); 0307 painter.setBrush( QColor( Qt::white ) ); 0308 0309 // Create all frames 0310 for( double t = 0.0; t < 2 * M_PI; t += M_PI / 8.0 ) { 0311 canvas.fill( Qt::transparent ); 0312 QRectF firstCircle( h - r + q * cos( t ), h - r + q * sin( t ), d, d ); 0313 QRectF secondCircle( h - r + q * cos( t + M_PI ), h - r + q * sin( t + M_PI ), d, d ); 0314 painter.drawEllipse( firstCircle ); 0315 painter.drawEllipse( secondCircle ); 0316 m_progressAnimation.push_back( QIcon( QPixmap::fromImage( canvas ) ) ); 0317 } 0318 } 0319 0320 RoutingWidget::RoutingWidget( MarbleWidget *marbleWidget, QWidget *parent ) : 0321 QWidget( parent ), d( new RoutingWidgetPrivate( this, marbleWidget ) ) 0322 { 0323 d->m_ui.setupUi( this ); 0324 d->setupToolBar(); 0325 d->m_ui.routeComboBox->setVisible( false ); 0326 d->m_ui.routeComboBox->setModel( d->m_routingManager->alternativeRoutesModel() ); 0327 layout()->setMargin( 0 ); 0328 0329 d->m_ui.routingProfileComboBox->setModel( d->m_routingManager->profilesModel() ); 0330 0331 connect( d->m_routingManager->profilesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), 0332 this, SLOT(selectFirstProfile()) ); 0333 connect( d->m_routingManager->profilesModel(), SIGNAL(modelReset()), 0334 this, SLOT(selectFirstProfile()) ); 0335 connect( d->m_routingLayer, SIGNAL(placemarkSelected(QModelIndex)), 0336 this, SLOT(activatePlacemark(QModelIndex)) ); 0337 connect( d->m_routingManager, SIGNAL(stateChanged(RoutingManager::State)), 0338 this, SLOT(updateRouteState(RoutingManager::State)) ); 0339 connect( d->m_routeRequest, SIGNAL(positionAdded(int)), 0340 this, SLOT(insertInputWidget(int)) ); 0341 connect( d->m_routeRequest, SIGNAL(positionRemoved(int)), 0342 this, SLOT(removeInputWidget(int)) ); 0343 connect( d->m_routeRequest, SIGNAL(routingProfileChanged()), 0344 this, SLOT(updateActiveRoutingProfile()) ); 0345 connect( &d->m_progressTimer, SIGNAL(timeout()), 0346 this, SLOT(updateProgress()) ); 0347 connect( d->m_ui.routeComboBox, SIGNAL(currentIndexChanged(int)), 0348 d->m_routingManager->alternativeRoutesModel(), SLOT(setCurrentRoute(int)) ); 0349 connect( d->m_routingManager->alternativeRoutesModel(), SIGNAL(currentRouteChanged(int)), 0350 d->m_ui.routeComboBox, SLOT(setCurrentIndex(int)) ); 0351 connect( d->m_ui.routingProfileComboBox, SIGNAL(currentIndexChanged(int)), 0352 this, SLOT(setRoutingProfile(int)) ); 0353 connect( d->m_ui.routingProfileComboBox, SIGNAL(activated(int)), 0354 this, SLOT(retrieveRoute()) ); 0355 connect( d->m_routingManager->alternativeRoutesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), 0356 this, SLOT(updateAlternativeRoutes()) ); 0357 0358 d->m_ui.directionsListView->setModel( d->m_routingModel ); 0359 0360 QItemSelectionModel *selectionModel = d->m_ui.directionsListView->selectionModel(); 0361 d->m_routingLayer->synchronizeWith( selectionModel ); 0362 connect( d->m_ui.directionsListView, SIGNAL(activated(QModelIndex)), 0363 this, SLOT(activateItem(QModelIndex)) ); 0364 0365 // FIXME: apply for this sector 0366 connect( d->m_ui.searchButton, SIGNAL(clicked()), 0367 this, SLOT(retrieveRoute()) ); 0368 connect( d->m_ui.showInstructionsButton, SIGNAL(clicked(bool)), 0369 this, SLOT(showDirections()) ); 0370 0371 for( int i=0; i<d->m_routeRequest->size(); ++i ) { 0372 insertInputWidget( i ); 0373 } 0374 0375 for ( int i=0; i<2 && d->m_inputWidgets.size()<2; ++i ) { 0376 // Start with source and destination if the route is empty yet 0377 addInputWidget(); 0378 } 0379 //d->m_ui.descriptionLabel->setVisible( false ); 0380 d->m_ui.resultLabel->setVisible( false ); 0381 setShowDirectionsButtonVisible( false ); 0382 updateActiveRoutingProfile(); 0383 updateCloudSyncButtons(); 0384 0385 if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { 0386 d->m_ui.directionsListView->setVisible( false ); 0387 d->m_openRouteButton->setVisible( false ); 0388 d->m_saveRouteButton->setVisible( false ); 0389 } 0390 0391 connect( marbleWidget->model(), SIGNAL(themeChanged(QString)), 0392 this, SLOT(handlePlanetChange()) ); 0393 } 0394 0395 RoutingWidget::~RoutingWidget() 0396 { 0397 delete d->m_playback; 0398 delete d->m_tour; 0399 if( d->m_document ){ 0400 d->m_widget->model()->treeModel()->removeDocument( d->m_document ); 0401 delete d->m_document; 0402 } 0403 delete d; 0404 } 0405 0406 void RoutingWidget::retrieveRoute() 0407 { 0408 if ( d->m_inputWidgets.size() == 1 ) { 0409 // Search mode 0410 d->m_inputWidgets.first()->findPlacemarks(); 0411 return; 0412 } 0413 0414 int index = d->m_ui.routingProfileComboBox->currentIndex(); 0415 if ( index == -1 ) { 0416 return; 0417 } 0418 d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) ); 0419 0420 Q_ASSERT( d->m_routeRequest->size() == d->m_inputWidgets.size() ); 0421 for ( int i = 0; i < d->m_inputWidgets.size(); ++i ) { 0422 RoutingInputWidget *widget = d->m_inputWidgets.at( i ); 0423 if ( !widget->hasTargetPosition() && widget->hasInput() ) { 0424 widget->findPlacemarks(); 0425 return; 0426 } 0427 } 0428 0429 d->m_activeInput = nullptr; 0430 if ( d->m_routeRequest->size() > 1 ) { 0431 d->m_zoomRouteAfterDownload = true; 0432 d->m_routingLayer->setPlacemarkModel( nullptr ); 0433 d->m_routingManager->retrieveRoute(); 0434 d->m_ui.directionsListView->setModel( d->m_routingModel ); 0435 d->m_routingLayer->synchronizeWith( d->m_ui.directionsListView->selectionModel() ); 0436 } 0437 0438 if( d->m_playback ) { 0439 d->m_playback->stop(); 0440 } 0441 } 0442 0443 void RoutingWidget::activateItem ( const QModelIndex &index ) 0444 { 0445 QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); 0446 0447 if ( !data.isNull() ) { 0448 GeoDataCoordinates position = qvariant_cast<GeoDataCoordinates>( data ); 0449 d->m_widget->centerOn( position, true ); 0450 } 0451 0452 if ( d->m_activeInput && index.isValid() ) { 0453 QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); 0454 if ( !data.isNull() ) { 0455 d->m_activeInput->setTargetPosition( data.value<GeoDataCoordinates>(), index.data().toString() ); 0456 } 0457 } 0458 } 0459 0460 void RoutingWidget::handleSearchResult( RoutingInputWidget *widget ) 0461 { 0462 d->setActiveInput( widget ); 0463 MarblePlacemarkModel *model = widget->searchResultModel(); 0464 0465 if ( model->rowCount() ) { 0466 QString const results = tr( "placemarks found: %1" ).arg( model->rowCount() ); 0467 d->m_ui.resultLabel->setText( results ); 0468 d->m_ui.resultLabel->setVisible( true ); 0469 // Make sure we have a selection 0470 activatePlacemark( model->index( 0, 0 ) ); 0471 } else { 0472 QString const results = tr( "No placemark found" ); 0473 d->m_ui.resultLabel->setText(QLatin1String("<font color=\"red\">") + results + QLatin1String("</font>")); 0474 d->m_ui.resultLabel->setVisible( true ); 0475 } 0476 0477 GeoDataLineString placemarks; 0478 for ( int i = 0; i < model->rowCount(); ++i ) { 0479 QVariant data = model->index( i, 0 ).data( MarblePlacemarkModel::CoordinateRole ); 0480 if ( !data.isNull() ) { 0481 placemarks << data.value<GeoDataCoordinates>(); 0482 } 0483 } 0484 0485 if ( placemarks.size() > 1 ) { 0486 d->m_widget->centerOn( GeoDataLatLonBox::fromLineString( placemarks ) ); 0487 //d->m_ui.descriptionLabel->setVisible( false ); 0488 0489 if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { 0490 d->m_ui.directionsListView->setVisible( true ); 0491 } 0492 } 0493 } 0494 0495 void RoutingWidget::centerOnInputWidget( RoutingInputWidget *widget ) 0496 { 0497 if ( widget->hasTargetPosition() ) { 0498 d->m_widget->centerOn( widget->targetPosition() ); 0499 } 0500 } 0501 0502 void RoutingWidget::activatePlacemark( const QModelIndex &index ) 0503 { 0504 if ( d->m_activeInput && index.isValid() ) { 0505 QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); 0506 if ( !data.isNull() ) { 0507 d->m_activeInput->setTargetPosition( data.value<GeoDataCoordinates>() ); 0508 } 0509 } 0510 0511 d->m_ui.directionsListView->setCurrentIndex( index ); 0512 } 0513 0514 void RoutingWidget::addInputWidget() 0515 { 0516 d->m_routeRequest->append( GeoDataCoordinates() ); 0517 } 0518 0519 void RoutingWidget::insertInputWidget( int index ) 0520 { 0521 if ( index >= 0 && index <= d->m_inputWidgets.size() ) { 0522 RoutingInputWidget *input = new RoutingInputWidget( d->m_widget->model(), index, this ); 0523 d->m_inputWidgets.insert( index, input ); 0524 connect( input, SIGNAL(searchFinished(RoutingInputWidget*)), 0525 this, SLOT(handleSearchResult(RoutingInputWidget*)) ); 0526 connect( input, SIGNAL(removalRequest(RoutingInputWidget*)), 0527 this, SLOT(removeInputWidget(RoutingInputWidget*)) ); 0528 connect( input, SIGNAL(activityRequest(RoutingInputWidget*)), 0529 this, SLOT(centerOnInputWidget(RoutingInputWidget*)) ); 0530 connect( input, SIGNAL(mapInputModeEnabled(RoutingInputWidget*,bool)), 0531 this, SLOT(requestMapPosition(RoutingInputWidget*,bool)) ); 0532 connect( input, SIGNAL(targetValidityChanged(bool)), 0533 this, SLOT(adjustSearchButton()) ); 0534 0535 d->m_ui.inputLayout->insertWidget( index, input ); 0536 d->adjustInputWidgets(); 0537 } 0538 } 0539 0540 void RoutingWidget::removeInputWidget( RoutingInputWidget *widget ) 0541 { 0542 int index = d->m_inputWidgets.indexOf( widget ); 0543 if ( index >= 0 ) { 0544 if ( d->m_inputWidgets.size() < 3 ) { 0545 widget->clear(); 0546 } else { 0547 d->m_routeRequest->remove( index ); 0548 } 0549 d->m_routingManager->retrieveRoute(); 0550 } 0551 } 0552 0553 void RoutingWidget::removeInputWidget( int index ) 0554 { 0555 if ( index >= 0 && index < d->m_inputWidgets.size() ) { 0556 RoutingInputWidget *widget = d->m_inputWidgets.at( index ); 0557 d->m_inputWidgets.remove( index ); 0558 d->m_ui.inputLayout->removeWidget( widget ); 0559 widget->deleteLater(); 0560 if ( widget == d->m_activeInput ) { 0561 d->m_activeInput = nullptr; 0562 d->m_routingLayer->setPlacemarkModel( nullptr ); 0563 d->m_ui.directionsListView->setModel( d->m_routingModel ); 0564 d->m_routingLayer->synchronizeWith( d->m_ui.directionsListView->selectionModel() ); 0565 } 0566 d->adjustInputWidgets(); 0567 } 0568 0569 if ( d->m_inputWidgets.size() < 2 ) { 0570 addInputWidget(); 0571 } 0572 } 0573 0574 void RoutingWidget::updateRouteState( RoutingManager::State state ) 0575 { 0576 clearTour(); 0577 0578 switch ( state ) { 0579 case RoutingManager::Downloading: 0580 d->m_ui.routeComboBox->setVisible( false ); 0581 d->m_ui.routeComboBox->clear(); 0582 d->m_progressTimer.start(); 0583 d->m_ui.resultLabel->setVisible( false ); 0584 break; 0585 case RoutingManager::Retrieved: { 0586 d->m_progressTimer.stop(); 0587 d->m_ui.searchButton->setIcon( QIcon() ); 0588 if ( d->m_routingManager->routingModel()->rowCount() == 0 ) { 0589 const QString results = tr( "No route found" ); 0590 d->m_ui.resultLabel->setText(QLatin1String("<font color=\"red\">") + results + QLatin1String("</font>")); 0591 d->m_ui.resultLabel->setVisible( true ); 0592 } 0593 } 0594 break; 0595 } 0596 0597 d->m_saveRouteButton->setEnabled( d->m_routingManager->routingModel()->rowCount() > 0 ); 0598 } 0599 0600 void RoutingWidget::requestMapPosition( RoutingInputWidget *widget, bool enabled ) 0601 { 0602 pointSelectionCanceled(); 0603 0604 if ( enabled ) { 0605 d->m_inputRequest = widget; 0606 d->m_widget->installEventFilter( this ); 0607 d->m_widget->setFocus( Qt::OtherFocusReason ); 0608 } 0609 } 0610 0611 void RoutingWidget::retrieveSelectedPoint( const GeoDataCoordinates &coordinates ) 0612 { 0613 if ( d->m_inputRequest && d->m_inputWidgets.contains( d->m_inputRequest ) ) { 0614 d->m_inputRequest->setTargetPosition( coordinates ); 0615 d->m_widget->update(); 0616 } 0617 0618 d->m_inputRequest = nullptr; 0619 d->m_widget->removeEventFilter( this ); 0620 } 0621 0622 void RoutingWidget::adjustSearchButton() 0623 { 0624 d->adjustSearchButton(); 0625 } 0626 0627 void RoutingWidget::pointSelectionCanceled() 0628 { 0629 if ( d->m_inputRequest && d->m_inputWidgets.contains( d->m_inputRequest ) ) { 0630 d->m_inputRequest->abortMapInputRequest(); 0631 } 0632 0633 d->m_inputRequest = nullptr; 0634 d->m_widget->removeEventFilter( this ); 0635 } 0636 0637 void RoutingWidget::configureProfile() 0638 { 0639 int index = d->m_ui.routingProfileComboBox->currentIndex(); 0640 if ( index != -1 ) { 0641 RoutingProfileSettingsDialog dialog( d->m_widget->model()->pluginManager(), d->m_routingManager->profilesModel(), this ); 0642 dialog.editProfile( d->m_ui.routingProfileComboBox->currentIndex() ); 0643 d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) ); 0644 } 0645 } 0646 0647 void RoutingWidget::updateProgress() 0648 { 0649 if ( !d->m_progressAnimation.isEmpty() ) { 0650 d->m_currentFrame = ( d->m_currentFrame + 1 ) % d->m_progressAnimation.size(); 0651 QIcon frame = d->m_progressAnimation[d->m_currentFrame]; 0652 d->m_ui.searchButton->setIcon( frame ); 0653 } 0654 } 0655 0656 void RoutingWidget::updateAlternativeRoutes() 0657 { 0658 if ( d->m_ui.routeComboBox->count() == 1) { 0659 // Parts of the route may lie outside the route trip points 0660 GeoDataLatLonBox const bbox = d->m_routingManager->routingModel()->route().bounds(); 0661 if ( d->m_zoomRouteAfterDownload ) { 0662 d->m_zoomRouteAfterDownload = false; 0663 d->m_widget->centerOn( bbox ); 0664 } 0665 } 0666 0667 d->m_ui.routeComboBox->setVisible( d->m_ui.routeComboBox->count() > 0 ); 0668 if ( d->m_ui.routeComboBox->currentIndex() < 0 && d->m_ui.routeComboBox->count() > 0 ) { 0669 d->m_ui.routeComboBox->setCurrentIndex( 0 ); 0670 } 0671 0672 QString const results = tr( "routes found: %1" ).arg( d->m_ui.routeComboBox->count() ); 0673 d->m_ui.resultLabel->setText( results ); 0674 d->m_ui.resultLabel->setVisible( true ); 0675 d->m_saveRouteButton->setEnabled( d->m_routingManager->routingModel()->rowCount() > 0 ); 0676 } 0677 0678 void RoutingWidget::setShowDirectionsButtonVisible( bool visible ) 0679 { 0680 d->m_ui.showInstructionsButton->setVisible( visible ); 0681 } 0682 0683 void RoutingWidget::setRouteSyncManager(RouteSyncManager *manager) 0684 { 0685 d->m_routeSyncManager = manager; 0686 connect( d->m_routeSyncManager, SIGNAL(routeSyncEnabledChanged(bool)), 0687 this, SLOT(updateCloudSyncButtons()) ); 0688 updateCloudSyncButtons(); 0689 } 0690 0691 void RoutingWidget::openRoute() 0692 { 0693 QString const file = QFileDialog::getOpenFileName( this, tr( "Open Route" ), 0694 d->m_routingManager->lastOpenPath(), tr("KML Files (*.kml)") ); 0695 if ( !file.isEmpty() ) { 0696 d->m_routingManager->setLastOpenPath( QFileInfo( file ).absolutePath() ); 0697 d->m_zoomRouteAfterDownload = true; 0698 d->m_routingManager->loadRoute( file ); 0699 updateAlternativeRoutes(); 0700 } 0701 } 0702 0703 void RoutingWidget::selectFirstProfile() 0704 { 0705 int count = d->m_routingManager->profilesModel()->rowCount(); 0706 if ( count && d->m_ui.routingProfileComboBox->currentIndex() < 0 ) { 0707 d->m_ui.routingProfileComboBox->setCurrentIndex( 0 ); 0708 } 0709 } 0710 0711 void RoutingWidget::setRoutingProfile( int index ) 0712 { 0713 if ( index >= 0 && index < d->m_routingManager->profilesModel()->rowCount() ) { 0714 d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) ); 0715 } 0716 } 0717 0718 void RoutingWidget::showDirections() 0719 { 0720 d->m_ui.directionsListView->setVisible( true ); 0721 } 0722 0723 void RoutingWidget::saveRoute() 0724 { 0725 QString fileName = QFileDialog::getSaveFileName( this, 0726 tr( "Save Route" ), // krazy:exclude=qclasses 0727 d->m_routingManager->lastSavePath(), 0728 tr( "KML files (*.kml)" ) ); 0729 0730 if ( !fileName.isEmpty() ) { 0731 // maemo 5 file dialog does not append the file extension 0732 if ( !fileName.endsWith(QLatin1String( ".kml" ), Qt::CaseInsensitive) ) { 0733 fileName += QLatin1String(".kml"); 0734 } 0735 d->m_routingManager->setLastSavePath( QFileInfo( fileName ).absolutePath() ); 0736 d->m_routingManager->saveRoute( fileName ); 0737 } 0738 } 0739 0740 void RoutingWidget::uploadToCloud() 0741 { 0742 Q_ASSERT( d->m_routeSyncManager ); 0743 0744 if (!d->m_routeUploadDialog) { 0745 d->m_routeUploadDialog = new QProgressDialog( d->m_widget ); 0746 d->m_routeUploadDialog->setWindowTitle( tr( "Uploading route..." ) ); 0747 d->m_routeUploadDialog->setMinimum( 0 ); 0748 d->m_routeUploadDialog->setMaximum( 100 ); 0749 d->m_routeUploadDialog->setAutoClose( true ); 0750 d->m_routeUploadDialog->setAutoReset( true ); 0751 connect( d->m_routeSyncManager, SIGNAL(routeUploadProgress(qint64,qint64)), this, SLOT(updateUploadProgress(qint64,qint64)) ); 0752 } 0753 0754 d->m_routeUploadDialog->show(); 0755 d->m_routeSyncManager->uploadRoute(); 0756 } 0757 0758 void RoutingWidget::openCloudRoutesDialog() 0759 { 0760 Q_ASSERT( d->m_routeSyncManager ); 0761 d->m_routeSyncManager->prepareRouteList(); 0762 0763 QPointer<CloudRoutesDialog> dialog = new CloudRoutesDialog( d->m_routeSyncManager->model(), d->m_widget ); 0764 connect( d->m_routeSyncManager, SIGNAL(routeListDownloadProgress(qint64,qint64)), dialog, SLOT(updateListDownloadProgressbar(qint64,qint64)) ); 0765 connect( dialog, SIGNAL(downloadButtonClicked(QString)), d->m_routeSyncManager, SLOT(downloadRoute(QString)) ); 0766 connect( dialog, SIGNAL(openButtonClicked(QString)), this, SLOT(openCloudRoute(QString)) ); 0767 connect( dialog, SIGNAL(deleteButtonClicked(QString)), d->m_routeSyncManager, SLOT(deleteRoute(QString)) ); 0768 connect( dialog, SIGNAL(removeFromCacheButtonClicked(QString)), d->m_routeSyncManager, SLOT(removeRouteFromCache(QString)) ); 0769 connect( dialog, SIGNAL(uploadToCloudButtonClicked(QString)), d->m_routeSyncManager, SLOT(uploadRoute(QString)) ); 0770 dialog->exec(); 0771 delete dialog; 0772 } 0773 0774 void RoutingWidget::updateActiveRoutingProfile() 0775 { 0776 RoutingProfile const profile = d->m_routingManager->routeRequest()->routingProfile(); 0777 QList<RoutingProfile> const profiles = d->m_routingManager->profilesModel()->profiles(); 0778 d->m_ui.routingProfileComboBox->setCurrentIndex( profiles.indexOf( profile ) ); 0779 } 0780 0781 void RoutingWidget::updateCloudSyncButtons() 0782 { 0783 bool const show = d->m_routeSyncManager && d->m_routeSyncManager->isRouteSyncEnabled(); 0784 d->m_cloudSyncSeparator->setVisible( show ); 0785 d->m_uploadToCloudAction->setVisible( show ); 0786 d->m_openCloudRoutesAction->setVisible( show ); 0787 } 0788 0789 void RoutingWidget::openCloudRoute(const QString &identifier) 0790 { 0791 Q_ASSERT( d->m_routeSyncManager ); 0792 d->m_routeSyncManager->openRoute( identifier ); 0793 d->m_widget->centerOn( d->m_routingManager->routingModel()->route().bounds() ); 0794 } 0795 0796 void RoutingWidget::updateUploadProgress(qint64 sent, qint64 total) 0797 { 0798 Q_ASSERT( d->m_routeUploadDialog ); 0799 d->m_routeUploadDialog->setValue( 100.0 * sent / total ); 0800 } 0801 0802 bool RoutingWidget::eventFilter( QObject *o, QEvent *event ) 0803 { 0804 if ( o != d->m_widget ) { 0805 return QWidget::eventFilter( o, event ); 0806 } 0807 0808 Q_ASSERT( d->m_inputRequest != nullptr ); 0809 Q_ASSERT( d->m_inputWidgets.contains( d->m_inputRequest ) ); 0810 0811 if ( event->type() == QEvent::MouseButtonPress ) { 0812 QMouseEvent *e = static_cast<QMouseEvent*>( event ); 0813 return e->button() == Qt::LeftButton; 0814 } 0815 0816 if ( event->type() == QEvent::MouseButtonRelease ) { 0817 QMouseEvent *e = static_cast<QMouseEvent*>( event ); 0818 qreal lon( 0.0 ), lat( 0.0 ); 0819 if ( e->button() == Qt::LeftButton && d->m_widget->geoCoordinates( e->pos().x(), e->pos().y(), 0820 lon, lat, GeoDataCoordinates::Radian ) ) { 0821 retrieveSelectedPoint( GeoDataCoordinates( lon, lat ) ); 0822 return true; 0823 } else { 0824 return QWidget::eventFilter( o, event ); 0825 } 0826 } 0827 0828 if ( event->type() == QEvent::MouseMove ) { 0829 d->m_widget->setCursor( Qt::CrossCursor ); 0830 return true; 0831 } 0832 0833 if ( event->type() == QEvent::KeyPress ) { 0834 QKeyEvent *e = static_cast<QKeyEvent*>( event ); 0835 if ( e->key() == Qt::Key_Escape ) { 0836 pointSelectionCanceled(); 0837 return true; 0838 } 0839 0840 return QWidget::eventFilter( o, event ); 0841 } 0842 0843 return QWidget::eventFilter( o, event ); 0844 } 0845 0846 void RoutingWidget::resizeEvent(QResizeEvent *e) 0847 { 0848 QWidget::resizeEvent(e); 0849 } 0850 0851 void RoutingWidget::toggleRoutePlay() 0852 { 0853 if( !d->m_playback ){ 0854 if( d->m_routingModel->rowCount() != 0 ){ 0855 initializeTour(); 0856 } 0857 } 0858 0859 if (!d->m_playback) 0860 return; 0861 0862 if( !d->m_playing ){ 0863 d->m_playing = true; 0864 d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-pause.png"))); 0865 0866 if( d->m_playback ){ 0867 d->m_playback->play(); 0868 } 0869 } else { 0870 d->m_playing = false; 0871 d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); 0872 d->m_playback->pause(); 0873 } 0874 } 0875 0876 void RoutingWidget::initializeTour() 0877 { 0878 d->m_tour = new GeoDataTour; 0879 if( d->m_document ){ 0880 d->m_widget->model()->treeModel()->removeDocument( d->m_document ); 0881 delete d->m_document; 0882 } 0883 d->m_document = new GeoDataDocument; 0884 d->m_document->setId(QStringLiteral("tourdoc")); 0885 d->m_document->append( d->m_tour ); 0886 0887 d->m_tour->setPlaylist( new GeoDataPlaylist ); 0888 Route const route = d->m_widget->model()->routingManager()->routingModel()->route(); 0889 GeoDataLineString path = route.path(); 0890 if ( path.size() < 1 ){ 0891 return; 0892 } 0893 0894 QList<WaypointInfo> waypoints; 0895 double totalDistance = 0.0; 0896 for( int i=0; i<route.size(); ++i ){ 0897 // TODO: QString( i )? 0898 waypoints << WaypointInfo(i, totalDistance, route.at(i).path().first(), route.at(i).maneuver(), QLatin1String("start ") + QString(i)); 0899 totalDistance += route.at( i ).distance(); 0900 } 0901 0902 if( waypoints.size() < 1 ){ 0903 return; 0904 } 0905 0906 QList<WaypointInfo> const allWaypoints = waypoints; 0907 totalDistance = 0.0; 0908 GeoDataCoordinates last = path.at( 0 ); 0909 int j=0; // next waypoint 0910 qreal planetRadius = d->m_widget->model()->planet()->radius(); 0911 for( int i=1; i<path.size(); ++i ){ 0912 GeoDataCoordinates coordinates = path.at( i ); 0913 totalDistance += planetRadius * path.at(i - 1).sphericalDistanceTo(coordinates); // Distance to route start 0914 while (totalDistance >= allWaypoints[j].distance && j+1<allWaypoints.size()) { 0915 ++j; 0916 } 0917 int const lastIndex = qBound( 0, j-1, allWaypoints.size()-1 ); // previous waypoint 0918 double const lastDistance = qAbs( totalDistance - allWaypoints[lastIndex].distance ); 0919 double const nextDistance = qAbs( allWaypoints[j].distance - totalDistance ); 0920 double const waypointDistance = qMin( lastDistance, nextDistance ); // distance to closest waypoint 0921 double const step = qBound( 100.0, waypointDistance*2, 1000.0 ); // support point distance (higher density close to waypoints) 0922 0923 double const distance = planetRadius * last.sphericalDistanceTo(coordinates); 0924 if( i > 1 && distance < step ){ 0925 continue; 0926 } 0927 last = coordinates; 0928 0929 GeoDataLookAt* lookat = new GeoDataLookAt; 0930 // Choose a zoom distance of 400, 600 or 800 meters based on the distance to the closest waypoint 0931 double const range = waypointDistance < 400 ? 400 : ( waypointDistance < 2000 ? 600 : 800 ); 0932 coordinates.setAltitude( range ); 0933 lookat->setCoordinates( coordinates ); 0934 lookat->setRange( range ); 0935 GeoDataFlyTo* flyto = new GeoDataFlyTo; 0936 double const duration = 0.75; 0937 flyto->setDuration( duration ); 0938 flyto->setView( lookat ); 0939 flyto->setFlyToMode( GeoDataFlyTo::Smooth ); 0940 d->m_tour->playlist()->addPrimitive( flyto ); 0941 0942 if( !waypoints.empty() && totalDistance > waypoints.first().distance-100 ){ 0943 WaypointInfo const waypoint = waypoints.first(); 0944 waypoints.pop_front(); 0945 GeoDataAnimatedUpdate *updateCreate = new GeoDataAnimatedUpdate; 0946 updateCreate->setUpdate( new GeoDataUpdate ); 0947 updateCreate->update()->setCreate( new GeoDataCreate ); 0948 GeoDataPlacemark *placemarkCreate = new GeoDataPlacemark; 0949 QString const waypointId = QString( "waypoint-%1" ).arg( i, 0, 10 ); 0950 placemarkCreate->setId( waypointId ); 0951 placemarkCreate->setTargetId( d->m_document->id() ); 0952 placemarkCreate->setCoordinate( waypoint.coordinates ); 0953 GeoDataStyle::Ptr style(new GeoDataStyle); 0954 style->iconStyle().setIconPath( waypoint.maneuver.directionPixmap() ); 0955 placemarkCreate->setStyle( style ); 0956 updateCreate->update()->create()->append( placemarkCreate ); 0957 d->m_tour->playlist()->addPrimitive( updateCreate ); 0958 0959 GeoDataAnimatedUpdate *updateDelete = new GeoDataAnimatedUpdate; 0960 updateDelete->setDelayedStart( 2 ); 0961 updateDelete->setUpdate( new GeoDataUpdate ); 0962 updateDelete->update()->setDelete( new GeoDataDelete ); 0963 GeoDataPlacemark *placemarkDelete = new GeoDataPlacemark; 0964 placemarkDelete->setTargetId( waypointId ); 0965 updateDelete->update()->getDelete()->append( placemarkDelete ); 0966 d->m_tour->playlist()->addPrimitive( updateDelete ); 0967 } 0968 } 0969 0970 d->m_playback = new TourPlayback; 0971 d->m_playback->setMarbleWidget( d->m_widget ); 0972 d->m_playback->setTour( d->m_tour ); 0973 d->m_widget->model()->treeModel()->addDocument( d->m_document ); 0974 QObject::connect( d->m_playback, SIGNAL(finished()), 0975 this, SLOT(seekTourToStart()) ); 0976 } 0977 0978 void RoutingWidget::centerOn( const GeoDataCoordinates &coordinates ) 0979 { 0980 if ( d->m_widget ) { 0981 GeoDataLookAt lookat; 0982 lookat.setCoordinates( coordinates ); 0983 lookat.setRange( coordinates.altitude() ); 0984 d->m_widget->flyTo( lookat, Instant ); 0985 } 0986 } 0987 0988 void RoutingWidget::clearTour() 0989 { 0990 d->m_playing = false; 0991 d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); 0992 delete d->m_playback; 0993 d->m_playback = nullptr; 0994 if( d->m_document ){ 0995 d->m_widget->model()->treeModel()->removeDocument( d->m_document ); 0996 delete d->m_document; 0997 d->m_document = nullptr; 0998 d->m_tour = nullptr; 0999 } 1000 } 1001 1002 void RoutingWidget::seekTourToStart() 1003 { 1004 Q_ASSERT( d->m_playback ); 1005 d->m_playback->stop(); 1006 d->m_playback->seek( 0 ); 1007 d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); 1008 d->m_playing = false; 1009 } 1010 1011 void RoutingWidget::handlePlanetChange() 1012 { 1013 const QString newPlanetId = d->m_widget->model()->planetId(); 1014 1015 if (newPlanetId == d->m_planetId) { 1016 return; 1017 } 1018 1019 d->m_planetId = newPlanetId; 1020 d->m_routingManager->clearRoute(); 1021 } 1022 1023 } // namespace Marble 1024 1025 #include "moc_RoutingWidget.cpp"