File indexing completed on 2024-04-21 03:50:02

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2013 Mihail Ivchenko <ematirov@gmail>
0004 // SPDX-FileCopyrightText: 2014 Sanjiban Bairagya <sanjiban22393@gmail.com>
0005 //
0006 
0007 #include "TourWidget.h"
0008 #include "FlyToEditWidget.h"
0009 #include "TourControlEditWidget.h"
0010 #include "WaitEditWidget.h"
0011 #include "SoundCueEditWidget.h"
0012 #include "TourItemDelegate.h"
0013 
0014 #include "ui_TourWidget.h"
0015 #include "GeoDataPlacemark.h"
0016 #include "GeoDataDocument.h"
0017 #include "GeoDataLookAt.h"
0018 #include "GeoDataPlaylist.h"
0019 #include "GeoDataTour.h"
0020 #include "GeoDataTreeModel.h"
0021 #include "GeoDataFlyTo.h"
0022 #include "GeoDataWait.h"
0023 #include "GeoDataCamera.h"
0024 #include "GeoDataTourControl.h"
0025 #include "GeoDataSoundCue.h"
0026 #include "GeoDataCreate.h"
0027 #include "GeoDataUpdate.h"
0028 #include "GeoDataDelete.h"
0029 #include "GeoDataChange.h"
0030 #include "GeoDataAnimatedUpdate.h"
0031 #include "GeoDataDocumentWriter.h"
0032 #include "KmlElementDictionary.h"
0033 #include "MarbleModel.h"
0034 #include "MarblePlacemarkModel.h"
0035 #include "MarbleWidget.h"
0036 #include "ParsingRunnerManager.h"
0037 #include "TourPlayback.h"
0038 #include "MovieCapture.h"
0039 #include "TourCaptureDialog.h"
0040 #include "MarbleDebug.h"
0041 #include "PlaybackFlyToItem.h"
0042 #include "EditPlacemarkDialog.h"
0043 #include "MarbleDirs.h"
0044 #include "GeoDataStyle.h"
0045 #include "GeoDataIconStyle.h"
0046 
0047 #include <QFileDialog>
0048 #include <QDir>
0049 #include <QModelIndex>
0050 #include <QMessageBox>
0051 #include <QPainter>
0052 #include <QToolButton>
0053 #include <QMenu>
0054 #include <QUrl>
0055 #include <QKeyEvent>
0056 #include <QCloseEvent>
0057 #include <QPointer>
0058 
0059 namespace Marble
0060 {
0061 
0062 class TourWidgetPrivate
0063 {
0064 
0065 public:
0066     explicit TourWidgetPrivate( TourWidget *parent );
0067     ~TourWidgetPrivate();
0068     GeoDataFeature *getPlaylistFeature() const;
0069     void updateRootIndex();
0070 
0071 public:
0072     void openFile();
0073     bool openFile( const QString &filename );
0074     void createTour();
0075     void saveTour();
0076     void saveTourAs();
0077     void mapCenterOn(const QModelIndex &index );
0078     void addFlyTo();
0079     void addWait();
0080     void addSoundCue();
0081     void addPlacemark();
0082     void addRemovePlacemark();
0083     void addChangePlacemark();
0084     void addTourPrimitive(GeoDataTourPrimitive *primitive );
0085     void deleteSelected();
0086     void updateButtonsStates();
0087     void moveUp();
0088     void moveDown();
0089     void captureTour();
0090     void handlePlaybackProgress( const double position );
0091     void handlePlaybackFinish();
0092     GeoDataObject *rootIndexObject() const;
0093 
0094 private:
0095     GeoDataTour* findTour( GeoDataFeature* feature ) const;
0096     bool openDocument( GeoDataDocument *document );
0097     bool saveTourAs( const QString &filename );
0098     bool overrideModifications();
0099 
0100 public:
0101     TourWidget *q;
0102     MarbleWidget *m_widget;
0103     Ui::TourWidget  m_tourUi;
0104     TourCaptureDialog *m_tourCaptureDialog;
0105     TourPlayback m_playback;
0106     TourItemDelegate *m_delegate;
0107     bool m_isChanged;
0108     bool m_playState;
0109     bool m_isLoopingStopped;
0110     GeoDataDocument* m_document;
0111     QAction *m_actionToggleLoopPlay;
0112     QToolButton *m_addPrimitiveButton;
0113     QAction *m_actionAddFlyTo;
0114     QAction *m_actionAddWait;
0115     QAction *m_actionAddSoundCue;
0116     QAction *m_actionAddPlacemark;
0117     QAction *m_actionAddRemovePlacemark;
0118     QAction *m_actionAddChangePlacemark;
0119 };
0120 
0121 TourWidgetPrivate::TourWidgetPrivate( TourWidget *parent )
0122     : q( parent ),
0123       m_widget( nullptr ),
0124       m_playback( nullptr ),
0125       m_delegate( nullptr ),
0126       m_isChanged( false ),
0127       m_playState( false ),
0128       m_document( nullptr ),
0129       m_addPrimitiveButton( new QToolButton )
0130 {
0131     m_tourUi.setupUi( parent );
0132     m_tourUi.m_actionRecord->setEnabled( false );
0133 
0134     QAction *separator = m_tourUi.m_toolBarControl->insertSeparator( m_tourUi.m_actionMoveUp );
0135 
0136     m_addPrimitiveButton->setIcon(QIcon(QStringLiteral(":/marble/flag.png")));
0137     m_addPrimitiveButton->setToolTip( QObject::tr( "Add FlyTo" ) );
0138     m_addPrimitiveButton->setPopupMode( QToolButton::MenuButtonPopup );
0139 
0140     QMenu *addPrimitiveMenu = new QMenu(q);
0141 
0142     m_actionAddFlyTo = new QAction(QIcon(QStringLiteral(":/marble/flag.png")), QObject::tr("Add FlyTo"), addPrimitiveMenu);
0143     addPrimitiveMenu->addAction( m_actionAddFlyTo );
0144     m_actionAddWait = new QAction(QIcon(QStringLiteral(":/marble/player-time.png")), QObject::tr("Add Wait"), addPrimitiveMenu);
0145     addPrimitiveMenu->addAction( m_actionAddWait );
0146     m_actionAddSoundCue = new QAction(QIcon(QStringLiteral(":/marble/audio-x-generic.png")), QObject::tr("Add SoundCue"), addPrimitiveMenu);
0147     addPrimitiveMenu->addAction( m_actionAddSoundCue );
0148     addPrimitiveMenu->addSeparator();
0149     m_actionAddPlacemark = new QAction(QIcon(QStringLiteral(":/icons/add-placemark.png")), QObject::tr("Add Placemark"), addPrimitiveMenu);
0150     addPrimitiveMenu->addAction( m_actionAddPlacemark );
0151     m_actionAddRemovePlacemark = new QAction(QIcon(QStringLiteral(":/icons/remove.png")), QObject::tr("Remove placemark"), addPrimitiveMenu);
0152     addPrimitiveMenu->addAction( m_actionAddRemovePlacemark );
0153     m_actionAddChangePlacemark = new QAction(QIcon(QStringLiteral(":/marble/document-edit.png")), QObject::tr("Change placemark"), addPrimitiveMenu);
0154     addPrimitiveMenu->addAction( m_actionAddChangePlacemark );
0155     m_actionToggleLoopPlay = new QAction( QObject::tr( "Loop" ), m_tourUi.m_slider );
0156     m_actionToggleLoopPlay->setCheckable( true );
0157     m_actionToggleLoopPlay->setChecked( false );
0158     m_tourUi.m_slider->setContextMenuPolicy( Qt::ActionsContextMenu );
0159     m_tourUi.m_slider->addAction( m_actionToggleLoopPlay );
0160 
0161     m_addPrimitiveButton->setMenu( addPrimitiveMenu );
0162     m_addPrimitiveButton->setEnabled( false );
0163 
0164     m_tourUi.m_toolBarControl->insertWidget( separator, m_addPrimitiveButton );
0165 
0166     QObject::connect( m_tourUi.m_listView, SIGNAL(activated(QModelIndex)), q, SLOT(mapCenterOn(QModelIndex)) );
0167     QObject::connect( m_addPrimitiveButton, SIGNAL(clicked()), q, SLOT(addFlyTo()) );
0168     QObject::connect( m_actionAddFlyTo, SIGNAL(triggered()), q, SLOT(addFlyTo()) );
0169     QObject::connect( m_actionAddWait, SIGNAL(triggered()), q, SLOT(addWait()) );
0170     QObject::connect( m_actionAddSoundCue, SIGNAL(triggered()), q, SLOT(addSoundCue()) );
0171     QObject::connect( m_actionAddPlacemark, SIGNAL(triggered()), q, SLOT(addPlacemark()) );
0172     QObject::connect( m_actionAddRemovePlacemark, SIGNAL(triggered()), q, SLOT(addRemovePlacemark()) );
0173     QObject::connect( m_actionAddChangePlacemark, SIGNAL(triggered()), q, SLOT(addChangePlacemark()) );
0174     QObject::connect( m_tourUi.m_actionDelete, SIGNAL(triggered()), q, SLOT(deleteSelected()) );
0175     QObject::connect( m_tourUi.m_actionMoveUp, SIGNAL(triggered()), q, SLOT(moveUp()) );
0176     QObject::connect( m_tourUi.m_actionMoveDown, SIGNAL(triggered()), q, SLOT(moveDown()) );
0177     QObject::connect( m_tourUi.m_actionNewTour, SIGNAL(triggered()), q, SLOT(createTour()) );
0178     QObject::connect( m_tourUi.m_actionOpenTour, SIGNAL(triggered()), q, SLOT(openFile()) );
0179     QObject::connect( m_tourUi.m_actionSaveTour, SIGNAL(triggered()), q, SLOT(saveTour()) );
0180     QObject::connect( m_tourUi.m_actionSaveTourAs, SIGNAL(triggered()), q, SLOT(saveTourAs()) );
0181     QObject::connect( m_tourUi.m_actionRecord, SIGNAL(triggered()), q, SLOT(captureTour()) );
0182     QObject::connect( &m_playback, SIGNAL(finished()), q, SLOT(stopPlaying()) );
0183     QObject::connect( &m_playback, SIGNAL(itemFinished(int)), q, SLOT(setHighlightedItemIndex(int)) );
0184 
0185 }
0186 
0187 TourWidgetPrivate::~TourWidgetPrivate()
0188 {
0189     delete m_delegate;
0190 }
0191 
0192 TourWidget::TourWidget( QWidget *parent, Qt::WindowFlags flags )
0193     : QWidget( parent, flags ),
0194       d( new TourWidgetPrivate( this ) )
0195 {
0196     layout()->setMargin( 0 );
0197 
0198     connect( d->m_tourUi.actionPlay, SIGNAL(triggered()),
0199             this, SLOT(togglePlaying()) );
0200     connect( d->m_tourUi.actionStop, SIGNAL(triggered()),
0201             this, SLOT(stopLooping()) );
0202     connect( d->m_tourUi.actionStop, SIGNAL(triggered()),
0203             this, SLOT(stopPlaying()) );
0204     connect( d->m_tourUi.m_slider, SIGNAL(sliderMoved(int)),
0205              this, SLOT(handleSliderMove(int)) );
0206 
0207     d->m_tourUi.m_toolBarPlayback->setDisabled( true );
0208     d->m_tourUi.m_slider->setDisabled( true );
0209     d->m_tourUi.m_listView->installEventFilter( this );
0210 }
0211 
0212 TourWidget::~TourWidget()
0213 {
0214     delete d;
0215 }
0216 
0217 bool TourWidget::eventFilter( QObject *watched, QEvent *event )
0218 {
0219     Q_UNUSED(watched);
0220 
0221     Q_ASSERT( watched == d->m_tourUi.m_listView );
0222     GeoDataObject *rootObject =  d->rootIndexObject();
0223 
0224     if ( !rootObject ) {
0225         return false;
0226     }
0227 
0228     if ( event->type() == QEvent::KeyPress ) {
0229         QKeyEvent *key = static_cast<QKeyEvent*>( event );
0230         QModelIndexList selectedIndexes = d->m_tourUi.m_listView->selectionModel()->selectedIndexes();
0231 
0232         if ( key->key() == Qt::Key_Delete ) {
0233             if ( !selectedIndexes.isEmpty() ) {
0234                 deleteSelected();
0235             }
0236             return true;
0237         }
0238 
0239         if ( key->key() == Qt::Key_PageDown && key->modifiers().testFlag( Qt::ControlModifier )
0240              && !selectedIndexes.isEmpty() )
0241         {
0242             QModelIndexList::iterator end = selectedIndexes.end() - 1;
0243             if (const GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
0244                 if ( end->row() != playlist->size() - 1 ) {
0245                     moveDown();
0246                 }
0247             }
0248             return true;
0249         }
0250 
0251         if ( key->key() == Qt::Key_PageUp && key->modifiers().testFlag( Qt::ControlModifier )
0252              && !selectedIndexes.isEmpty() )
0253         {
0254             QModelIndexList::iterator start = selectedIndexes.begin();
0255             if ( start->row() != 0 ) {
0256                 moveUp();
0257             }
0258             return true;
0259         }
0260     }
0261 
0262     return false;
0263 }
0264 
0265 void TourWidget::setMarbleWidget( MarbleWidget *widget )
0266 {
0267     d->m_widget = widget;
0268     delete d->m_delegate;
0269     d->m_delegate = new TourItemDelegate( d->m_tourUi.m_listView, d->m_widget, this );
0270     connect( d->m_delegate, SIGNAL(edited(QModelIndex)), this, SLOT(updateDuration()) );
0271     connect( d->m_delegate, SIGNAL(edited(QModelIndex)), &d->m_playback, SLOT(updateTracks()) );
0272     d->m_tourUi.m_listView->setItemDelegate( d->m_delegate );
0273 }
0274 
0275 void TourWidget::togglePlaying()
0276 {
0277     if( !d->m_playState ){
0278         d->m_playState = true;
0279         startPlaying();
0280     } else {
0281         d->m_playState = false;
0282         pausePlaying();
0283     }
0284 }
0285 
0286 void TourWidget::startPlaying()
0287 {
0288     setHighlightedItemIndex( 0 );
0289     d->m_isLoopingStopped = false;
0290     d->m_playback.play();
0291     d->m_tourUi.actionPlay->setIcon(QIcon(QStringLiteral(":/marble/playback-pause.png")));
0292     d->m_tourUi.actionPlay->setEnabled( true );
0293     d->m_tourUi.actionStop->setEnabled( true );
0294     d->m_tourUi.m_actionRecord->setEnabled( false );
0295     d->m_delegate->setEditable( false );
0296     d->m_addPrimitiveButton->setEnabled( false );
0297     d->m_playState = true;
0298 }
0299 
0300 void TourWidget::pausePlaying()
0301 {
0302     d->m_playback.pause();
0303     d->m_tourUi.actionPlay->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
0304     d->m_tourUi.actionPlay->setEnabled( true );
0305     d->m_tourUi.actionStop->setEnabled( true );
0306 }
0307 
0308 void TourWidget::stopPlaying()
0309 {
0310     removeHighlight();
0311     d->m_playback.stop();
0312     d->m_tourUi.actionPlay->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
0313     d->m_tourUi.actionPlay->setEnabled( true );
0314     d->m_tourUi.m_actionRecord->setEnabled( true );
0315     d->m_tourUi.actionStop->setEnabled( false );
0316     d->m_playState = false;
0317     d->m_delegate->setEditable( true );
0318     d->m_addPrimitiveButton->setEnabled( true );
0319 
0320     // Loop if the option ( m_actionLoopPlay ) is checked
0321     if ( d->m_actionToggleLoopPlay->isChecked() && !d->m_isLoopingStopped ) {
0322         startPlaying();
0323     }
0324 }
0325 
0326 void TourWidget::stopLooping()
0327 {
0328     d->m_isLoopingStopped = true;
0329 }
0330 
0331 void TourWidget::closeEvent( QCloseEvent *event )
0332 {
0333     if ( !d->m_document || !d->m_isChanged ) {
0334         event->accept();
0335         return;
0336     }
0337 
0338     const int result = QMessageBox::question( d->m_widget,
0339                                              QObject::tr( "Save tour" ),
0340                                              QObject::tr( "There are unsaved Tours. Do you want to save your changes?" ),
0341                                              QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel );
0342 
0343     switch ( result ) {
0344     case QMessageBox::Save:
0345         d->saveTour();
0346         event->accept();
0347         break;
0348     case QMessageBox::Discard:
0349         event->accept();
0350         break;
0351     case QMessageBox::Cancel:
0352         event->ignore();
0353     }
0354 }
0355 
0356 void TourWidget::handleSliderMove( int value )
0357 {
0358     removeHighlight();
0359     d->m_playback.seek( value / 100.0 );
0360     QTime nullTime( 0, 0, 0 );
0361     QTime time = nullTime.addSecs(  value / 100.0 );
0362     d->m_tourUi.m_elapsedTime->setText(QString("%L1:%L2").arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
0363 }
0364 
0365 void TourWidgetPrivate::openFile()
0366 {
0367     if ( overrideModifications() ) {
0368         QString const filename = QFileDialog::getOpenFileName( q, QObject::tr( "Open Tour" ), QDir::homePath(), QObject::tr( "KML Tours (*.kml)" ) );
0369         if ( !filename.isEmpty() ) {
0370             ParsingRunnerManager manager( m_widget->model()->pluginManager() );
0371             GeoDataDocument* document = manager.openFile( filename );
0372             m_playback.setBaseUrl( QUrl::fromLocalFile( filename ) );
0373             openDocument( document );
0374         }
0375     }
0376 }
0377 
0378 bool TourWidgetPrivate::openFile( const QString &filename )
0379 {
0380     if ( overrideModifications() ) {
0381         if ( !filename.isEmpty() ) {
0382             ParsingRunnerManager manager( m_widget->model()->pluginManager() );
0383             GeoDataDocument* document = manager.openFile( filename );
0384             m_playback.setBaseUrl( QUrl::fromLocalFile( filename ) );
0385             return openDocument( document );
0386         }
0387     }
0388 
0389     return false;
0390 }
0391 
0392 GeoDataTour *TourWidgetPrivate::findTour( GeoDataFeature *feature ) const
0393 {
0394     if (GeoDataTour *tour = (feature ? geodata_cast<GeoDataTour>(feature) : nullptr)) {
0395         return tour;
0396     }
0397 
0398     GeoDataContainer *container = dynamic_cast<GeoDataContainer*>( feature );
0399     if ( container ) {
0400         QVector<GeoDataFeature*>::Iterator end = container->end();
0401         QVector<GeoDataFeature*>::Iterator iter = container->begin();
0402         for( ; iter != end; ++iter ) {
0403             GeoDataTour *tour = findTour( *iter );
0404             if ( tour ) {
0405                 return tour;
0406             }
0407         }
0408     }
0409     return nullptr;
0410 }
0411 
0412 void TourWidgetPrivate::mapCenterOn( const QModelIndex &index )
0413 {
0414     QVariant coordinatesVariant = m_widget->model()->treeModel()->data( index, MarblePlacemarkModel::CoordinateRole );
0415     if ( !coordinatesVariant.isNull() ) {
0416         GeoDataCoordinates const coordinates = coordinatesVariant.value<GeoDataCoordinates>();
0417         GeoDataLookAt lookat;
0418         lookat.setCoordinates( coordinates );
0419         lookat.setRange( coordinates.altitude() );
0420         m_widget->flyTo( lookat, Instant );
0421     }
0422 }
0423 
0424 void TourWidgetPrivate::addFlyTo()
0425 {
0426     GeoDataFlyTo *flyTo = new GeoDataFlyTo();
0427     GeoDataLookAt *lookat = new GeoDataLookAt( m_widget->lookAt() );
0428     lookat->setAltitude( lookat->range() );
0429     flyTo->setView( lookat );
0430     bool isMainTrackEmpty = m_playback.mainTrackSize() == 0;
0431     flyTo->setDuration( isMainTrackEmpty ? 0.0 : 1.0 );
0432     addTourPrimitive( flyTo );
0433 }
0434 
0435 void TourWidgetPrivate::addWait()
0436 {
0437     GeoDataWait *wait = new GeoDataWait();
0438     wait->setDuration( 1.0 );
0439     addTourPrimitive( wait );
0440 }
0441 
0442 void TourWidgetPrivate::addSoundCue()
0443 {
0444     GeoDataSoundCue *soundCue = new GeoDataSoundCue();
0445     addTourPrimitive( soundCue );
0446 }
0447 
0448 void TourWidgetPrivate::addPlacemark()
0449 {
0450     // Get the normalized coordinates of the focus point. There will be automatically added a new
0451     // placemark.
0452     qreal lat = m_widget->focusPoint().latitude();
0453     qreal lon = m_widget->focusPoint().longitude();
0454     GeoDataCoordinates::normalizeLonLat( lon, lat );
0455 
0456     GeoDataDocument *document = new GeoDataDocument;
0457     if( m_document->id().isEmpty() ) {
0458         if( m_document->name().isEmpty() ) {
0459             m_document->setId(QStringLiteral("untitled_tour"));
0460         } else {
0461             m_document->setId( m_document->name().trimmed().replace( QLatin1Char(' '), QLatin1Char('_') ).toLower() );
0462         }
0463     }
0464     document->setTargetId( m_document->id() );
0465 
0466     GeoDataPlacemark *placemark = new GeoDataPlacemark;
0467     placemark->setCoordinate( lon, lat );
0468     placemark->setVisible( true );
0469     placemark->setBalloonVisible( true );
0470     GeoDataStyle *newStyle = new GeoDataStyle( *placemark->style() );
0471     newStyle->iconStyle().setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/redflag_22.png")));
0472     placemark->setStyle( GeoDataStyle::Ptr(newStyle) );
0473 
0474     document->append( placemark );
0475 
0476     GeoDataCreate *create = new GeoDataCreate;
0477     create->append( document );
0478     GeoDataUpdate *update = new GeoDataUpdate;
0479     update->setCreate( create );
0480     GeoDataAnimatedUpdate *animatedUpdate = new GeoDataAnimatedUpdate;
0481     animatedUpdate->setUpdate( update );
0482 
0483     if( m_delegate->editAnimatedUpdate( animatedUpdate ) ) {
0484         addTourPrimitive( animatedUpdate );
0485         m_delegate->setDefaultFeatureId( placemark->id() );
0486     } else {
0487         delete animatedUpdate;
0488     }
0489 }
0490 
0491 void TourWidgetPrivate::addRemovePlacemark()
0492 {
0493     GeoDataDelete *deleteItem = new GeoDataDelete;
0494     GeoDataPlacemark *placemark = new GeoDataPlacemark;
0495     placemark->setTargetId( m_delegate->defaultFeatureId() );
0496     deleteItem->append( placemark );
0497     GeoDataUpdate *update = new GeoDataUpdate;
0498     update->setDelete( deleteItem );
0499     GeoDataAnimatedUpdate *animatedUpdate = new GeoDataAnimatedUpdate;
0500     animatedUpdate->setUpdate( update );
0501     addTourPrimitive( animatedUpdate );
0502 }
0503 
0504 void TourWidgetPrivate::addChangePlacemark()
0505 {
0506     GeoDataChange *change = new GeoDataChange;
0507     GeoDataPlacemark *placemark = nullptr;
0508     GeoDataFeature *lastFeature = m_delegate->findFeature( m_delegate->defaultFeatureId() );
0509     if (GeoDataPlacemark *target = (lastFeature != nullptr ? geodata_cast<GeoDataPlacemark>(lastFeature) : nullptr)) {
0510         placemark = new GeoDataPlacemark( *target );
0511         placemark->setTargetId( m_delegate->defaultFeatureId() );
0512         placemark->setId(QString());
0513     } else {
0514         placemark = new GeoDataPlacemark;
0515     }
0516     change->append( placemark );
0517     GeoDataUpdate *update = new GeoDataUpdate;
0518     update->setChange( change );
0519     GeoDataAnimatedUpdate *animatedUpdate = new GeoDataAnimatedUpdate;
0520     animatedUpdate->setUpdate( update );
0521     addTourPrimitive( animatedUpdate );
0522 }
0523 
0524 void TourWidgetPrivate::addTourPrimitive( GeoDataTourPrimitive *primitive )
0525 {
0526     GeoDataObject *rootObject =  rootIndexObject();
0527     if (auto playlist = geodata_cast<GeoDataPlaylist>(rootObject)) {
0528         QModelIndex currentIndex = m_tourUi.m_listView->currentIndex();
0529         QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
0530         int row = currentIndex.isValid() ? currentIndex.row()+1 : playlist->size();
0531         m_widget->model()->treeModel()->addTourPrimitive( playlistIndex, primitive, row );
0532         m_isChanged = true;
0533         m_tourUi.m_actionSaveTour->setEnabled( true );
0534 
0535         // Scrolling to the inserted item.
0536         if ( currentIndex.isValid() ) {
0537             m_tourUi.m_listView->scrollTo( currentIndex );
0538         }
0539         else {
0540             m_tourUi.m_listView->scrollToBottom();
0541         }
0542     }
0543 }
0544 
0545 void TourWidgetPrivate::deleteSelected()
0546 {
0547     QString title = QObject::tr( "Remove Selected Items" );
0548     QString text = QObject::tr( "Are you sure want to remove selected items?" );
0549     QPointer<QMessageBox> dialog = new QMessageBox( QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::No, q );
0550     dialog->setDefaultButton( QMessageBox::No );
0551     if ( dialog->exec() == QMessageBox::Yes ) {
0552         GeoDataObject *rootObject =  rootIndexObject();
0553         if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
0554             QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
0555             QModelIndexList selected = m_tourUi.m_listView->selectionModel()->selectedIndexes();
0556             std::sort( selected.begin(), selected.end(), [](const QModelIndex &a, const QModelIndex &b) { return b < a; } );
0557             QModelIndexList::iterator end = selected.end();
0558             QModelIndexList::iterator iter = selected.begin();
0559             for( ; iter != end; ++iter ) {
0560                 m_widget->model()->treeModel()->removeTourPrimitive( playlistIndex, iter->row() );
0561             }
0562             m_isChanged = true;
0563             m_tourUi.m_actionSaveTour->setEnabled( true );
0564         }
0565     }
0566     delete dialog;
0567 }
0568 
0569 void TourWidgetPrivate::updateButtonsStates()
0570 {
0571     QModelIndexList selectedIndexes = m_tourUi.m_listView->selectionModel()->selectedIndexes();
0572     if ( selectedIndexes.isEmpty() ) {
0573         m_tourUi.m_actionDelete->setEnabled( false );
0574         m_tourUi.m_actionMoveDown->setEnabled( false );
0575         m_tourUi.m_actionMoveUp->setEnabled( false );
0576     } else {
0577         m_tourUi.m_actionDelete->setEnabled( true );
0578         std::sort( selectedIndexes.begin(), selectedIndexes.end(), std::less<QModelIndex>() );
0579         QModelIndexList::iterator end = selectedIndexes.end()-1;
0580         QModelIndexList::iterator start = selectedIndexes.begin();
0581         m_tourUi.m_actionMoveUp->setEnabled( ( start->row() != 0 ) ); // if we can move up enable action else disable.
0582         GeoDataObject *rootObject =  rootIndexObject();
0583         if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
0584             m_tourUi.m_actionMoveDown->setEnabled( ( end->row() != playlist->size()-1 ) ); // if we can move down enable action else disable.
0585         }
0586     }
0587 }
0588 
0589 void TourWidgetPrivate::moveUp()
0590 {
0591     GeoDataObject *rootObject =  rootIndexObject();
0592     if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
0593         QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
0594         QModelIndexList selected = m_tourUi.m_listView->selectionModel()->selectedIndexes();
0595         std::sort( selected.begin(), selected.end(), std::less<QModelIndex>() );
0596         QModelIndexList::iterator end = selected.end();
0597         QModelIndexList::iterator iter = selected.begin();
0598         for( ; iter != end; ++iter ) {
0599             int const index = iter->row();
0600             Q_ASSERT( index > 0 );
0601             m_widget->model()->treeModel()->swapTourPrimitives( playlistIndex, index-1, index );
0602         }
0603         m_isChanged = true;
0604         m_tourUi.m_actionSaveTour->setEnabled( true );
0605         updateButtonsStates();
0606     }
0607 }
0608 
0609 void TourWidgetPrivate::moveDown()
0610 {
0611     GeoDataObject *rootObject = rootIndexObject();
0612     if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
0613         QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
0614         QModelIndexList selected = m_tourUi.m_listView->selectionModel()->selectedIndexes();
0615         std::sort( selected.begin(), selected.end(), [](const QModelIndex &a, const QModelIndex &b) { return b < a; } );
0616         QModelIndexList::iterator end = selected.end();
0617         QModelIndexList::iterator iter = selected.begin();
0618         for( ; iter != end; ++iter ) {
0619             int const index = iter->row();
0620             Q_ASSERT( index < playlist->size()-1 );
0621             m_widget->model()->treeModel()->swapTourPrimitives( playlistIndex, index, index+1 );
0622         }
0623         m_isChanged = true;
0624         m_tourUi.m_actionSaveTour->setEnabled( true );
0625         updateButtonsStates();
0626     }
0627 }
0628 
0629 GeoDataFeature* TourWidgetPrivate::getPlaylistFeature() const
0630 {
0631     GeoDataObject *rootObject = rootIndexObject();
0632     if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
0633         GeoDataObject *object = playlist->parent();
0634         if (GeoDataTour *tour = (object ? geodata_cast<GeoDataTour>(object) : nullptr)) {
0635             return tour;
0636         }
0637     }
0638     return nullptr;
0639 }
0640 
0641 void TourWidgetPrivate::updateRootIndex()
0642 {
0643     GeoDataTour *tour = findTour( m_document );
0644     if ( tour ){
0645         GeoDataPlaylist *playlist = tour->playlist();
0646         if ( playlist ) {
0647             m_tourUi.m_listView->setModel( m_widget->model()->treeModel() );
0648             m_tourUi.m_listView->setRootIndex( m_widget->model()->treeModel()->index( playlist ) );
0649             QObject::connect( m_tourUi.m_listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
0650                               q, SLOT(updateButtonsStates()) );
0651         }
0652         m_playback.setMarbleWidget( m_widget );
0653         m_playback.setTour( tour );
0654         m_tourUi.m_slider->setMaximum( m_playback.duration() * 100 );
0655         QTime nullTime( 0, 0, 0 );
0656         QTime time = nullTime.addSecs( m_playback.duration() );
0657         m_tourUi.m_totalTime->setText(QString("%L1:%L2").arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
0658         QObject::connect( &m_playback, SIGNAL(progressChanged(double)),
0659                          q, SLOT(handlePlaybackProgress(double)) );
0660         q->stopPlaying();
0661         m_tourUi.m_toolBarPlayback->setEnabled( true );
0662         bool isPlaybackEmpty = m_playback.mainTrackSize() != 0;
0663         m_tourUi.actionPlay->setEnabled( isPlaybackEmpty );
0664         m_tourUi.m_slider->setEnabled( isPlaybackEmpty );
0665         m_tourUi.m_actionRecord->setEnabled( isPlaybackEmpty );
0666         m_tourUi.actionStop->setEnabled( false );
0667         if( m_playback.mainTrackSize() > 0 ) {
0668             if( dynamic_cast<PlaybackFlyToItem*>( m_playback.mainTrackItemAt( 0 ) ) ) {
0669                 QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
0670                 for( int i = 0; playlist && i < playlist->size(); ++i ) {
0671                     if (geodata_cast<GeoDataFlyTo>(playlist->primitive(i))) {
0672                         m_delegate->setFirstFlyTo( m_widget->model()->treeModel()->index( i, 0, playlistIndex ) );
0673                         break;
0674                     }
0675                 }
0676             } else {
0677                 m_delegate->setFirstFlyTo( QPersistentModelIndex() );
0678             }
0679         }
0680     }
0681 }
0682 
0683 void TourWidget::addFlyTo()
0684 {
0685     d->addFlyTo();
0686     finishAddingItem();
0687 }
0688 
0689 void TourWidget::addWait()
0690 {
0691     d->addWait();
0692     finishAddingItem();
0693 }
0694 
0695 void TourWidget::addSoundCue()
0696 {
0697     d->addSoundCue();
0698     finishAddingItem();
0699 }
0700 
0701 void TourWidget::addPlacemark()
0702 {
0703     d->addPlacemark();
0704     finishAddingItem();
0705 }
0706 
0707 void TourWidget::addRemovePlacemark()
0708 {
0709     d->addRemovePlacemark();
0710     finishAddingItem();
0711 }
0712 
0713 void TourWidget::addChangePlacemark()
0714 {
0715     d->addChangePlacemark();
0716     finishAddingItem();
0717 }
0718 
0719 void TourWidget::deleteSelected()
0720 {
0721     d->deleteSelected();
0722     GeoDataFeature *feature = d->getPlaylistFeature();
0723     if ( feature ) {
0724         emit featureUpdated( feature );
0725         d->updateRootIndex();
0726     }
0727 }
0728 
0729 void TourWidget::updateDuration()
0730 {
0731     d->m_tourUi.m_slider->setMaximum( d->m_playback.duration() * 100 );
0732     QTime nullTime( 0, 0, 0 );
0733     QTime totalTime = nullTime.addSecs( d->m_playback.duration() );
0734     d->m_tourUi.m_totalTime->setText(QString("%L1:%L2").arg(totalTime.minute(), 2, 10, QLatin1Char('0') ).arg(totalTime.second(), 2, 10, QLatin1Char('0')));
0735     d->m_tourUi.m_slider->setValue( 0 );
0736     d->m_tourUi.m_elapsedTime->setText(QString("%L1:%L2").arg(0, 2, 10, QLatin1Char('0')).arg(0, 2, 10, QLatin1Char('0')));
0737 }
0738 
0739 void TourWidget::finishAddingItem()
0740 {
0741     GeoDataFeature *feature = d->getPlaylistFeature();
0742     if ( feature ) {
0743         emit featureUpdated( feature );
0744         d->updateRootIndex();
0745     }
0746 }
0747 
0748 void TourWidget::moveDown()
0749 {
0750     d->moveDown();
0751     GeoDataFeature *feature = d->getPlaylistFeature();
0752     if ( feature ) {
0753         emit featureUpdated( feature );
0754         d->updateRootIndex();
0755     }
0756 }
0757 
0758 void TourWidget::moveUp()
0759 {
0760     d->moveUp();
0761     GeoDataFeature *feature = d->getPlaylistFeature();
0762     if ( feature ) {
0763         emit featureUpdated( feature );
0764         d->updateRootIndex();
0765     }
0766 }
0767 
0768 GeoDataObject *TourWidgetPrivate::rootIndexObject() const
0769 {
0770     QModelIndex const rootIndex = m_tourUi.m_listView->rootIndex();
0771     return rootIndex.isValid() ? static_cast<GeoDataObject*>( rootIndex.internalPointer() ) : nullptr;
0772 }
0773 
0774 void TourWidgetPrivate::createTour()
0775 {
0776     if ( overrideModifications() ) {
0777         GeoDataDocument *document = new GeoDataDocument();
0778         document->setDocumentRole( UserDocument );
0779         document->setName(QStringLiteral("New Tour"));
0780         document->setId(QStringLiteral("new_tour"));
0781         GeoDataTour *tour = new GeoDataTour();
0782         tour->setName(QStringLiteral("New Tour"));
0783         GeoDataPlaylist *playlist = new GeoDataPlaylist;
0784         tour->setPlaylist( playlist );
0785         document->append( static_cast<GeoDataFeature*>( tour ) );
0786         m_playback.setBaseUrl( QUrl::fromLocalFile( MarbleDirs::marbleDataPath() ) );
0787         openDocument( document );
0788         m_isChanged = true;
0789         m_tourUi.m_actionSaveTour->setEnabled( true );
0790         m_tourUi.m_slider->setEnabled( true );
0791     }
0792 }
0793 
0794 bool TourWidgetPrivate::openDocument(GeoDataDocument* document)
0795 {
0796     if ( document ) {
0797         if ( m_document ) {
0798             m_widget->model()->treeModel()->removeDocument( m_document );
0799             delete m_document;
0800         }
0801         m_document = document;
0802         m_widget->model()->treeModel()->addDocument( m_document );
0803         m_isChanged = false;
0804         updateRootIndex();
0805         m_addPrimitiveButton->setEnabled( true );
0806         m_tourUi.m_actionSaveTourAs->setEnabled( true );
0807         m_tourUi.m_actionSaveTour->setEnabled( false );
0808         m_isChanged = false;
0809         return true;
0810     }
0811     return false;
0812 }
0813 
0814 void TourWidgetPrivate::saveTour()
0815 {
0816     if ( m_document ) {
0817         if ( !m_document->fileName().isEmpty() ) {
0818             saveTourAs( m_document->fileName() );
0819         } else {
0820             saveTourAs();
0821         }
0822     }
0823 }
0824 
0825 void TourWidgetPrivate::saveTourAs()
0826 {
0827    if ( m_document )
0828    {
0829        QString const filename = QFileDialog::getSaveFileName( q, QObject::tr( "Save Tour as" ), QDir::homePath(), QObject::tr( "KML Tours (*.kml)" ) );
0830        if ( !filename.isEmpty() ) {
0831             saveTourAs( filename );
0832        }
0833    }
0834 }
0835 
0836 bool TourWidgetPrivate::saveTourAs(const QString &filename)
0837 {
0838     if ( !filename.isEmpty() ) {
0839         if (GeoDataDocumentWriter::write(filename, *m_document)) {
0840             m_tourUi.m_actionSaveTour->setEnabled( false );
0841             m_isChanged = false;
0842             GeoDataDocument* document = m_document;
0843             if ( !document->fileName().isNull() ) {
0844                 m_widget->model()->removeGeoData( document->fileName() );
0845             }
0846             m_widget->model()->addGeoDataFile( filename );
0847             m_document->setFileName( filename );
0848             return true;
0849         }
0850     }
0851     return false;
0852 }
0853 
0854 void TourWidgetPrivate::captureTour()
0855 {
0856     MarbleWidget* widget = new MarbleWidget;
0857     widget->setMapThemeId( m_widget->mapThemeId() );
0858     widget->resize( 1280, 720 );
0859 
0860     m_widget->model()->treeModel()->removeDocument(m_document);
0861     widget->model()->treeModel()->addDocument(m_document);
0862 
0863     GeoDataTour* tour = findTour( m_document );
0864     TourPlayback* playback = new TourPlayback;
0865     playback->setMarbleWidget( widget );
0866     playback->setTour( tour );
0867 
0868     m_tourUi.m_listView->setModel( widget->model()->treeModel() );
0869     if( tour ){
0870         m_tourUi.m_listView->setRootIndex( widget->model()->treeModel()->index( tour->playlist() ) );
0871         m_tourUi.m_listView->repaint();
0872 
0873         QPointer<TourCaptureDialog> tourCaptureDialog = new TourCaptureDialog( widget, m_widget );
0874         tourCaptureDialog->setDefaultFilename( tour->name() );
0875         tourCaptureDialog->setTourPlayback( playback );
0876         tourCaptureDialog->exec();
0877     }
0878 
0879     delete playback;
0880     widget->model()->treeModel()->removeDocument(m_document);
0881     m_widget->model()->treeModel()->addDocument(m_document);
0882     updateRootIndex();
0883     delete widget;
0884 }
0885 
0886 bool TourWidgetPrivate::overrideModifications()
0887 {
0888     if ( m_document && m_isChanged ) {
0889         QString title = QObject::tr( "Discard Changes" );
0890         QString text = QObject::tr( "Are you sure want to discard all unsaved changes and close current document?" );
0891         QPointer<QMessageBox> dialog = new QMessageBox( QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::No, q );
0892         dialog->setDefaultButton( QMessageBox::No );
0893         if ( dialog->exec() != QMessageBox::Yes ) {
0894             delete dialog;
0895             return false;
0896         }
0897         delete dialog;
0898     }
0899     return true;
0900 }
0901 
0902 bool TourWidget::openTour( const QString &filename)
0903 {
0904     return d->openFile( filename );
0905 }
0906 
0907 void TourWidgetPrivate::handlePlaybackProgress(const double position)
0908 {
0909     if( !m_tourUi.m_slider->isSliderDown() ){
0910         m_tourUi.m_slider->setValue( position * 100 );
0911         QTime nullTime( 0, 0, 0 );
0912         QTime time = nullTime.addSecs( position );
0913         m_tourUi.m_elapsedTime->setText(QString("%L1:%L2").arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
0914     }
0915 }
0916 
0917 void TourWidget::setHighlightedItemIndex( int index )
0918 {
0919     GeoDataObject* rootObject =  d->rootIndexObject();
0920     GeoDataPlaylist* playlist = static_cast<GeoDataPlaylist*>( rootObject );
0921     QModelIndex playlistIndex = d->m_widget->model()->treeModel()->index( playlist );
0922 
0923     // Only flyTo and wait items have duration, so the other types have to be skipped.
0924     int searchedIndex = 0;
0925     for ( int  i = 0; i < playlist->size(); i++ ) {
0926 
0927         QModelIndex currentIndex = d->m_widget->model()->treeModel()->index( i, 0, playlistIndex );
0928         GeoDataObject* object = qvariant_cast<GeoDataObject*>(currentIndex.data( MarblePlacemarkModel::ObjectPointerRole ) );
0929 
0930         if (geodata_cast<GeoDataFlyTo>(object)
0931           || geodata_cast<GeoDataWait>(object))
0932                 ++searchedIndex;
0933 
0934         if ( index == searchedIndex ) {
0935             d->m_tourUi.m_listView->selectionModel()->setCurrentIndex( currentIndex, QItemSelectionModel::NoUpdate );
0936             d->m_tourUi.m_listView->scrollTo( currentIndex );
0937             break;
0938         }
0939     }
0940     d->m_tourUi.m_listView->viewport()->update();
0941 }
0942 
0943 void TourWidget::removeHighlight()
0944 {
0945     QModelIndex index;
0946 
0947     // Restoring the CurrentIndex to the previously selected item
0948     // or clearing it if there was no selected item.
0949     if ( d->m_tourUi.m_listView->selectionModel()->hasSelection() ) {
0950         index = d->m_tourUi.m_listView->selectionModel()->selectedIndexes().last();
0951     }
0952     else {
0953         index = QModelIndex();
0954     }
0955 
0956     d->m_tourUi.m_listView->selectionModel()->setCurrentIndex( index, QItemSelectionModel::NoUpdate );
0957     d->m_tourUi.m_listView->viewport()->update();
0958 }
0959 
0960 bool TourWidget::isPlaying() const
0961 {
0962     return d->m_playState;
0963 }
0964 
0965 }
0966 
0967 #include "moc_TourWidget.cpp"