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"