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"