Marble

TourWidget.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2013 Mihail Ivchenko <ematirov@gmail>
4// SPDX-FileCopyrightText: 2014 Sanjiban Bairagya <sanjiban22393@gmail.com>
5//
6
7#include "TourWidget.h"
8#include "FlyToEditWidget.h"
9#include "TourControlEditWidget.h"
10#include "WaitEditWidget.h"
11#include "SoundCueEditWidget.h"
12#include "TourItemDelegate.h"
13
14#include "ui_TourWidget.h"
15#include "GeoDataPlacemark.h"
16#include "GeoDataDocument.h"
17#include "GeoDataLookAt.h"
18#include "GeoDataPlaylist.h"
19#include "GeoDataTour.h"
20#include "GeoDataTreeModel.h"
21#include "GeoDataFlyTo.h"
22#include "GeoDataWait.h"
23#include "GeoDataCamera.h"
24#include "GeoDataTourControl.h"
25#include "GeoDataSoundCue.h"
26#include "GeoDataCreate.h"
27#include "GeoDataUpdate.h"
28#include "GeoDataDelete.h"
29#include "GeoDataChange.h"
30#include "GeoDataAnimatedUpdate.h"
31#include "GeoDataDocumentWriter.h"
32#include "KmlElementDictionary.h"
33#include "MarbleModel.h"
34#include "MarblePlacemarkModel.h"
35#include "MarbleWidget.h"
36#include "ParsingRunnerManager.h"
37#include "TourPlayback.h"
38#include "MovieCapture.h"
39#include "TourCaptureDialog.h"
40#include "MarbleDebug.h"
41#include "PlaybackFlyToItem.h"
42#include "EditPlacemarkDialog.h"
43#include "MarbleDirs.h"
44#include "GeoDataStyle.h"
45#include "GeoDataIconStyle.h"
46
47#include <QFileDialog>
48#include <QDir>
49#include <QModelIndex>
50#include <QMessageBox>
51#include <QPainter>
52#include <QToolButton>
53#include <QMenu>
54#include <QUrl>
55#include <QKeyEvent>
56#include <QCloseEvent>
57#include <QPointer>
58
59namespace Marble
60{
61
62class TourWidgetPrivate
63{
64
65public:
66 explicit TourWidgetPrivate( TourWidget *parent );
67 ~TourWidgetPrivate();
68 GeoDataFeature *getPlaylistFeature() const;
69 void updateRootIndex();
70
71public:
72 void openFile();
73 bool openFile( const QString &filename );
74 void createTour();
75 void saveTour();
76 void saveTourAs();
77 void mapCenterOn(const QModelIndex &index );
78 void addFlyTo();
79 void addWait();
80 void addSoundCue();
81 void addPlacemark();
82 void addRemovePlacemark();
83 void addChangePlacemark();
84 void addTourPrimitive(GeoDataTourPrimitive *primitive );
85 void deleteSelected();
86 void updateButtonsStates();
87 void moveUp();
88 void moveDown();
89 void captureTour();
90 void handlePlaybackProgress( const double position );
91 void handlePlaybackFinish();
92 GeoDataObject *rootIndexObject() const;
93
94private:
95 GeoDataTour* findTour( GeoDataFeature* feature ) const;
96 bool openDocument( GeoDataDocument *document );
97 bool saveTourAs( const QString &filename );
98 bool overrideModifications();
99
100public:
101 TourWidget *q;
102 MarbleWidget *m_widget;
103 Ui::TourWidget m_tourUi;
104 TourCaptureDialog *m_tourCaptureDialog;
105 TourPlayback m_playback;
106 TourItemDelegate *m_delegate;
107 bool m_isChanged;
108 bool m_playState;
109 bool m_isLoopingStopped;
110 GeoDataDocument* m_document;
111 QAction *m_actionToggleLoopPlay;
112 QToolButton *m_addPrimitiveButton;
113 QAction *m_actionAddFlyTo;
114 QAction *m_actionAddWait;
115 QAction *m_actionAddSoundCue;
116 QAction *m_actionAddPlacemark;
117 QAction *m_actionAddRemovePlacemark;
118 QAction *m_actionAddChangePlacemark;
119};
120
121TourWidgetPrivate::TourWidgetPrivate( TourWidget *parent )
122 : q( parent ),
123 m_widget( nullptr ),
124 m_playback( nullptr ),
125 m_delegate( nullptr ),
126 m_isChanged( false ),
127 m_playState( false ),
128 m_document( nullptr ),
129 m_addPrimitiveButton( new QToolButton )
130{
131 m_tourUi.setupUi( parent );
132 m_tourUi.m_actionRecord->setEnabled( false );
133
134 QAction *separator = m_tourUi.m_toolBarControl->insertSeparator( m_tourUi.m_actionMoveUp );
135
136 m_addPrimitiveButton->setIcon(QIcon(QStringLiteral(":/marble/flag.png")));
137 m_addPrimitiveButton->setToolTip( QObject::tr( "Add FlyTo" ) );
138 m_addPrimitiveButton->setPopupMode( QToolButton::MenuButtonPopup );
139
140 QMenu *addPrimitiveMenu = new QMenu(q);
141
142 m_actionAddFlyTo = new QAction(QIcon(QStringLiteral(":/marble/flag.png")), QObject::tr("Add FlyTo"), addPrimitiveMenu);
143 addPrimitiveMenu->addAction( m_actionAddFlyTo );
144 m_actionAddWait = new QAction(QIcon(QStringLiteral(":/marble/player-time.png")), QObject::tr("Add Wait"), addPrimitiveMenu);
145 addPrimitiveMenu->addAction( m_actionAddWait );
146 m_actionAddSoundCue = new QAction(QIcon(QStringLiteral(":/marble/audio-x-generic.png")), QObject::tr("Add SoundCue"), addPrimitiveMenu);
147 addPrimitiveMenu->addAction( m_actionAddSoundCue );
148 addPrimitiveMenu->addSeparator();
149 m_actionAddPlacemark = new QAction(QIcon(QStringLiteral(":/icons/add-placemark.png")), QObject::tr("Add Placemark"), addPrimitiveMenu);
150 addPrimitiveMenu->addAction( m_actionAddPlacemark );
151 m_actionAddRemovePlacemark = new QAction(QIcon(QStringLiteral(":/icons/remove.png")), QObject::tr("Remove placemark"), addPrimitiveMenu);
152 addPrimitiveMenu->addAction( m_actionAddRemovePlacemark );
153 m_actionAddChangePlacemark = new QAction(QIcon(QStringLiteral(":/marble/document-edit.png")), QObject::tr("Change placemark"), addPrimitiveMenu);
154 addPrimitiveMenu->addAction( m_actionAddChangePlacemark );
155 m_actionToggleLoopPlay = new QAction( QObject::tr( "Loop" ), m_tourUi.m_slider );
156 m_actionToggleLoopPlay->setCheckable( true );
157 m_actionToggleLoopPlay->setChecked( false );
158 m_tourUi.m_slider->setContextMenuPolicy( Qt::ActionsContextMenu );
159 m_tourUi.m_slider->addAction( m_actionToggleLoopPlay );
160
161 m_addPrimitiveButton->setMenu( addPrimitiveMenu );
162 m_addPrimitiveButton->setEnabled( false );
163
164 m_tourUi.m_toolBarControl->insertWidget( separator, m_addPrimitiveButton );
165
166 QObject::connect( m_tourUi.m_listView, SIGNAL(activated(QModelIndex)), q, SLOT(mapCenterOn(QModelIndex)) );
167 QObject::connect( m_addPrimitiveButton, SIGNAL(clicked()), q, SLOT(addFlyTo()) );
168 QObject::connect( m_actionAddFlyTo, SIGNAL(triggered()), q, SLOT(addFlyTo()) );
169 QObject::connect( m_actionAddWait, SIGNAL(triggered()), q, SLOT(addWait()) );
170 QObject::connect( m_actionAddSoundCue, SIGNAL(triggered()), q, SLOT(addSoundCue()) );
171 QObject::connect( m_actionAddPlacemark, SIGNAL(triggered()), q, SLOT(addPlacemark()) );
172 QObject::connect( m_actionAddRemovePlacemark, SIGNAL(triggered()), q, SLOT(addRemovePlacemark()) );
173 QObject::connect( m_actionAddChangePlacemark, SIGNAL(triggered()), q, SLOT(addChangePlacemark()) );
174 QObject::connect( m_tourUi.m_actionDelete, SIGNAL(triggered()), q, SLOT(deleteSelected()) );
175 QObject::connect( m_tourUi.m_actionMoveUp, SIGNAL(triggered()), q, SLOT(moveUp()) );
176 QObject::connect( m_tourUi.m_actionMoveDown, SIGNAL(triggered()), q, SLOT(moveDown()) );
177 QObject::connect( m_tourUi.m_actionNewTour, SIGNAL(triggered()), q, SLOT(createTour()) );
178 QObject::connect( m_tourUi.m_actionOpenTour, SIGNAL(triggered()), q, SLOT(openFile()) );
179 QObject::connect( m_tourUi.m_actionSaveTour, SIGNAL(triggered()), q, SLOT(saveTour()) );
180 QObject::connect( m_tourUi.m_actionSaveTourAs, SIGNAL(triggered()), q, SLOT(saveTourAs()) );
181 QObject::connect( m_tourUi.m_actionRecord, SIGNAL(triggered()), q, SLOT(captureTour()) );
182 QObject::connect( &m_playback, SIGNAL(finished()), q, SLOT(stopPlaying()) );
183 QObject::connect( &m_playback, SIGNAL(itemFinished(int)), q, SLOT(setHighlightedItemIndex(int)) );
184
185}
186
187TourWidgetPrivate::~TourWidgetPrivate()
188{
189 delete m_delegate;
190}
191
192TourWidget::TourWidget( QWidget *parent, Qt::WindowFlags flags )
193 : QWidget( parent, flags ),
194 d( new TourWidgetPrivate( this ) )
195{
196 layout()->setMargin( 0 );
197
198 connect( d->m_tourUi.actionPlay, SIGNAL(triggered()),
199 this, SLOT(togglePlaying()) );
200 connect( d->m_tourUi.actionStop, SIGNAL(triggered()),
201 this, SLOT(stopLooping()) );
202 connect( d->m_tourUi.actionStop, SIGNAL(triggered()),
203 this, SLOT(stopPlaying()) );
204 connect( d->m_tourUi.m_slider, SIGNAL(sliderMoved(int)),
205 this, SLOT(handleSliderMove(int)) );
206
207 d->m_tourUi.m_toolBarPlayback->setDisabled( true );
208 d->m_tourUi.m_slider->setDisabled( true );
209 d->m_tourUi.m_listView->installEventFilter( this );
210}
211
212TourWidget::~TourWidget()
213{
214 delete d;
215}
216
217bool TourWidget::eventFilter( QObject *watched, QEvent *event )
218{
219 Q_UNUSED(watched);
220
221 Q_ASSERT( watched == d->m_tourUi.m_listView );
222 GeoDataObject *rootObject = d->rootIndexObject();
223
224 if ( !rootObject ) {
225 return false;
226 }
227
228 if ( event->type() == QEvent::KeyPress ) {
229 QKeyEvent *key = static_cast<QKeyEvent*>( event );
230 QModelIndexList selectedIndexes = d->m_tourUi.m_listView->selectionModel()->selectedIndexes();
231
232 if ( key->key() == Qt::Key_Delete ) {
233 if ( !selectedIndexes.isEmpty() ) {
234 deleteSelected();
235 }
236 return true;
237 }
238
239 if ( key->key() == Qt::Key_PageDown && key->modifiers().testFlag( Qt::ControlModifier )
240 && !selectedIndexes.isEmpty() )
241 {
242 QModelIndexList::iterator end = selectedIndexes.end() - 1;
243 if (const GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
244 if ( end->row() != playlist->size() - 1 ) {
245 moveDown();
246 }
247 }
248 return true;
249 }
250
251 if ( key->key() == Qt::Key_PageUp && key->modifiers().testFlag( Qt::ControlModifier )
252 && !selectedIndexes.isEmpty() )
253 {
254 QModelIndexList::iterator start = selectedIndexes.begin();
255 if ( start->row() != 0 ) {
256 moveUp();
257 }
258 return true;
259 }
260 }
261
262 return false;
263}
264
265void TourWidget::setMarbleWidget( MarbleWidget *widget )
266{
267 d->m_widget = widget;
268 delete d->m_delegate;
269 d->m_delegate = new TourItemDelegate( d->m_tourUi.m_listView, d->m_widget, this );
270 connect( d->m_delegate, SIGNAL(edited(QModelIndex)), this, SLOT(updateDuration()) );
271 connect( d->m_delegate, SIGNAL(edited(QModelIndex)), &d->m_playback, SLOT(updateTracks()) );
272 d->m_tourUi.m_listView->setItemDelegate( d->m_delegate );
273}
274
275void TourWidget::togglePlaying()
276{
277 if( !d->m_playState ){
278 d->m_playState = true;
279 startPlaying();
280 } else {
281 d->m_playState = false;
282 pausePlaying();
283 }
284}
285
286void TourWidget::startPlaying()
287{
288 setHighlightedItemIndex( 0 );
289 d->m_isLoopingStopped = false;
290 d->m_playback.play();
291 d->m_tourUi.actionPlay->setIcon(QIcon(QStringLiteral(":/marble/playback-pause.png")));
292 d->m_tourUi.actionPlay->setEnabled( true );
293 d->m_tourUi.actionStop->setEnabled( true );
294 d->m_tourUi.m_actionRecord->setEnabled( false );
295 d->m_delegate->setEditable( false );
296 d->m_addPrimitiveButton->setEnabled( false );
297 d->m_playState = true;
298}
299
300void TourWidget::pausePlaying()
301{
302 d->m_playback.pause();
303 d->m_tourUi.actionPlay->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
304 d->m_tourUi.actionPlay->setEnabled( true );
305 d->m_tourUi.actionStop->setEnabled( true );
306}
307
308void TourWidget::stopPlaying()
309{
310 removeHighlight();
311 d->m_playback.stop();
312 d->m_tourUi.actionPlay->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
313 d->m_tourUi.actionPlay->setEnabled( true );
314 d->m_tourUi.m_actionRecord->setEnabled( true );
315 d->m_tourUi.actionStop->setEnabled( false );
316 d->m_playState = false;
317 d->m_delegate->setEditable( true );
318 d->m_addPrimitiveButton->setEnabled( true );
319
320 // Loop if the option ( m_actionLoopPlay ) is checked
321 if ( d->m_actionToggleLoopPlay->isChecked() && !d->m_isLoopingStopped ) {
322 startPlaying();
323 }
324}
325
326void TourWidget::stopLooping()
327{
328 d->m_isLoopingStopped = true;
329}
330
331void TourWidget::closeEvent( QCloseEvent *event )
332{
333 if ( !d->m_document || !d->m_isChanged ) {
334 event->accept();
335 return;
336 }
337
338 const int result = QMessageBox::question( d->m_widget,
339 QObject::tr( "Save tour" ),
340 QObject::tr( "There are unsaved Tours. Do you want to save your changes?" ),
342
343 switch ( result ) {
345 d->saveTour();
346 event->accept();
347 break;
349 event->accept();
350 break;
352 event->ignore();
353 }
354}
355
356void TourWidget::handleSliderMove( int value )
357{
358 removeHighlight();
359 d->m_playback.seek( value / 100.0 );
360 QTime nullTime( 0, 0, 0 );
361 QTime time = nullTime.addSecs( value / 100.0 );
362 d->m_tourUi.m_elapsedTime->setText(QString("%L1:%L2").arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
363}
364
365void TourWidgetPrivate::openFile()
366{
367 if ( overrideModifications() ) {
368 QString const filename = QFileDialog::getOpenFileName( q, QObject::tr( "Open Tour" ), QDir::homePath(), QObject::tr( "KML Tours (*.kml)" ) );
369 if ( !filename.isEmpty() ) {
370 ParsingRunnerManager manager( m_widget->model()->pluginManager() );
371 GeoDataDocument* document = manager.openFile( filename );
372 m_playback.setBaseUrl( QUrl::fromLocalFile( filename ) );
373 openDocument( document );
374 }
375 }
376}
377
378bool TourWidgetPrivate::openFile( const QString &filename )
379{
380 if ( overrideModifications() ) {
381 if ( !filename.isEmpty() ) {
382 ParsingRunnerManager manager( m_widget->model()->pluginManager() );
383 GeoDataDocument* document = manager.openFile( filename );
384 m_playback.setBaseUrl( QUrl::fromLocalFile( filename ) );
385 return openDocument( document );
386 }
387 }
388
389 return false;
390}
391
392GeoDataTour *TourWidgetPrivate::findTour( GeoDataFeature *feature ) const
393{
394 if (GeoDataTour *tour = (feature ? geodata_cast<GeoDataTour>(feature) : nullptr)) {
395 return tour;
396 }
397
398 GeoDataContainer *container = dynamic_cast<GeoDataContainer*>( feature );
399 if ( container ) {
400 QVector<GeoDataFeature*>::Iterator end = container->end();
401 QVector<GeoDataFeature*>::Iterator iter = container->begin();
402 for( ; iter != end; ++iter ) {
403 GeoDataTour *tour = findTour( *iter );
404 if ( tour ) {
405 return tour;
406 }
407 }
408 }
409 return nullptr;
410}
411
412void TourWidgetPrivate::mapCenterOn( const QModelIndex &index )
413{
414 QVariant coordinatesVariant = m_widget->model()->treeModel()->data( index, MarblePlacemarkModel::CoordinateRole );
415 if ( !coordinatesVariant.isNull() ) {
416 GeoDataCoordinates const coordinates = coordinatesVariant.value<GeoDataCoordinates>();
417 GeoDataLookAt lookat;
418 lookat.setCoordinates( coordinates );
419 lookat.setRange( coordinates.altitude() );
420 m_widget->flyTo( lookat, Instant );
421 }
422}
423
424void TourWidgetPrivate::addFlyTo()
425{
426 GeoDataFlyTo *flyTo = new GeoDataFlyTo();
427 GeoDataLookAt *lookat = new GeoDataLookAt( m_widget->lookAt() );
428 lookat->setAltitude( lookat->range() );
429 flyTo->setView( lookat );
430 bool isMainTrackEmpty = m_playback.mainTrackSize() == 0;
431 flyTo->setDuration( isMainTrackEmpty ? 0.0 : 1.0 );
432 addTourPrimitive( flyTo );
433}
434
435void TourWidgetPrivate::addWait()
436{
437 GeoDataWait *wait = new GeoDataWait();
438 wait->setDuration( 1.0 );
439 addTourPrimitive( wait );
440}
441
442void TourWidgetPrivate::addSoundCue()
443{
444 GeoDataSoundCue *soundCue = new GeoDataSoundCue();
445 addTourPrimitive( soundCue );
446}
447
448void TourWidgetPrivate::addPlacemark()
449{
450 // Get the normalized coordinates of the focus point. There will be automatically added a new
451 // placemark.
452 qreal lat = m_widget->focusPoint().latitude();
453 qreal lon = m_widget->focusPoint().longitude();
454 GeoDataCoordinates::normalizeLonLat( lon, lat );
455
456 GeoDataDocument *document = new GeoDataDocument;
457 if( m_document->id().isEmpty() ) {
458 if( m_document->name().isEmpty() ) {
459 m_document->setId(QStringLiteral("untitled_tour"));
460 } else {
461 m_document->setId( m_document->name().trimmed().replace( QLatin1Char(' '), QLatin1Char('_') ).toLower() );
462 }
463 }
464 document->setTargetId( m_document->id() );
465
466 GeoDataPlacemark *placemark = new GeoDataPlacemark;
467 placemark->setCoordinate( lon, lat );
468 placemark->setVisible( true );
469 placemark->setBalloonVisible( true );
470 GeoDataStyle *newStyle = new GeoDataStyle( *placemark->style() );
471 newStyle->iconStyle().setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/redflag_22.png")));
472 placemark->setStyle( GeoDataStyle::Ptr(newStyle) );
473
474 document->append( placemark );
475
476 GeoDataCreate *create = new GeoDataCreate;
477 create->append( document );
478 GeoDataUpdate *update = new GeoDataUpdate;
479 update->setCreate( create );
480 GeoDataAnimatedUpdate *animatedUpdate = new GeoDataAnimatedUpdate;
481 animatedUpdate->setUpdate( update );
482
483 if( m_delegate->editAnimatedUpdate( animatedUpdate ) ) {
484 addTourPrimitive( animatedUpdate );
485 m_delegate->setDefaultFeatureId( placemark->id() );
486 } else {
487 delete animatedUpdate;
488 }
489}
490
491void TourWidgetPrivate::addRemovePlacemark()
492{
493 GeoDataDelete *deleteItem = new GeoDataDelete;
494 GeoDataPlacemark *placemark = new GeoDataPlacemark;
495 placemark->setTargetId( m_delegate->defaultFeatureId() );
496 deleteItem->append( placemark );
497 GeoDataUpdate *update = new GeoDataUpdate;
498 update->setDelete( deleteItem );
499 GeoDataAnimatedUpdate *animatedUpdate = new GeoDataAnimatedUpdate;
500 animatedUpdate->setUpdate( update );
501 addTourPrimitive( animatedUpdate );
502}
503
504void TourWidgetPrivate::addChangePlacemark()
505{
506 GeoDataChange *change = new GeoDataChange;
507 GeoDataPlacemark *placemark = nullptr;
508 GeoDataFeature *lastFeature = m_delegate->findFeature( m_delegate->defaultFeatureId() );
509 if (GeoDataPlacemark *target = (lastFeature != nullptr ? geodata_cast<GeoDataPlacemark>(lastFeature) : nullptr)) {
510 placemark = new GeoDataPlacemark( *target );
511 placemark->setTargetId( m_delegate->defaultFeatureId() );
512 placemark->setId(QString());
513 } else {
514 placemark = new GeoDataPlacemark;
515 }
516 change->append( placemark );
517 GeoDataUpdate *update = new GeoDataUpdate;
518 update->setChange( change );
519 GeoDataAnimatedUpdate *animatedUpdate = new GeoDataAnimatedUpdate;
520 animatedUpdate->setUpdate( update );
521 addTourPrimitive( animatedUpdate );
522}
523
524void TourWidgetPrivate::addTourPrimitive( GeoDataTourPrimitive *primitive )
525{
526 GeoDataObject *rootObject = rootIndexObject();
527 if (auto playlist = geodata_cast<GeoDataPlaylist>(rootObject)) {
528 QModelIndex currentIndex = m_tourUi.m_listView->currentIndex();
529 QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
530 int row = currentIndex.isValid() ? currentIndex.row()+1 : playlist->size();
531 m_widget->model()->treeModel()->addTourPrimitive( playlistIndex, primitive, row );
532 m_isChanged = true;
533 m_tourUi.m_actionSaveTour->setEnabled( true );
534
535 // Scrolling to the inserted item.
536 if ( currentIndex.isValid() ) {
537 m_tourUi.m_listView->scrollTo( currentIndex );
538 }
539 else {
540 m_tourUi.m_listView->scrollToBottom();
541 }
542 }
543}
544
545void TourWidgetPrivate::deleteSelected()
546{
547 QString title = QObject::tr( "Remove Selected Items" );
548 QString text = QObject::tr( "Are you sure want to remove selected items?" );
550 dialog->setDefaultButton( QMessageBox::No );
551 if ( dialog->exec() == QMessageBox::Yes ) {
552 GeoDataObject *rootObject = rootIndexObject();
553 if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
554 QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
555 QModelIndexList selected = m_tourUi.m_listView->selectionModel()->selectedIndexes();
556 std::sort( selected.begin(), selected.end(), [](const QModelIndex &a, const QModelIndex &b) { return b < a; } );
557 QModelIndexList::iterator end = selected.end();
558 QModelIndexList::iterator iter = selected.begin();
559 for( ; iter != end; ++iter ) {
560 m_widget->model()->treeModel()->removeTourPrimitive( playlistIndex, iter->row() );
561 }
562 m_isChanged = true;
563 m_tourUi.m_actionSaveTour->setEnabled( true );
564 }
565 }
566 delete dialog;
567}
568
569void TourWidgetPrivate::updateButtonsStates()
570{
571 QModelIndexList selectedIndexes = m_tourUi.m_listView->selectionModel()->selectedIndexes();
572 if ( selectedIndexes.isEmpty() ) {
573 m_tourUi.m_actionDelete->setEnabled( false );
574 m_tourUi.m_actionMoveDown->setEnabled( false );
575 m_tourUi.m_actionMoveUp->setEnabled( false );
576 } else {
577 m_tourUi.m_actionDelete->setEnabled( true );
578 std::sort( selectedIndexes.begin(), selectedIndexes.end(), std::less<QModelIndex>() );
579 QModelIndexList::iterator end = selectedIndexes.end()-1;
580 QModelIndexList::iterator start = selectedIndexes.begin();
581 m_tourUi.m_actionMoveUp->setEnabled( ( start->row() != 0 ) ); // if we can move up enable action else disable.
582 GeoDataObject *rootObject = rootIndexObject();
583 if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
584 m_tourUi.m_actionMoveDown->setEnabled( ( end->row() != playlist->size()-1 ) ); // if we can move down enable action else disable.
585 }
586 }
587}
588
589void TourWidgetPrivate::moveUp()
590{
591 GeoDataObject *rootObject = rootIndexObject();
592 if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
593 QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
594 QModelIndexList selected = m_tourUi.m_listView->selectionModel()->selectedIndexes();
595 std::sort( selected.begin(), selected.end(), std::less<QModelIndex>() );
596 QModelIndexList::iterator end = selected.end();
597 QModelIndexList::iterator iter = selected.begin();
598 for( ; iter != end; ++iter ) {
599 int const index = iter->row();
600 Q_ASSERT( index > 0 );
601 m_widget->model()->treeModel()->swapTourPrimitives( playlistIndex, index-1, index );
602 }
603 m_isChanged = true;
604 m_tourUi.m_actionSaveTour->setEnabled( true );
605 updateButtonsStates();
606 }
607}
608
609void TourWidgetPrivate::moveDown()
610{
611 GeoDataObject *rootObject = rootIndexObject();
612 if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
613 QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
614 QModelIndexList selected = m_tourUi.m_listView->selectionModel()->selectedIndexes();
615 std::sort( selected.begin(), selected.end(), [](const QModelIndex &a, const QModelIndex &b) { return b < a; } );
616 QModelIndexList::iterator end = selected.end();
617 QModelIndexList::iterator iter = selected.begin();
618 for( ; iter != end; ++iter ) {
619 int const index = iter->row();
620 Q_ASSERT( index < playlist->size()-1 );
621 m_widget->model()->treeModel()->swapTourPrimitives( playlistIndex, index, index+1 );
622 }
623 m_isChanged = true;
624 m_tourUi.m_actionSaveTour->setEnabled( true );
625 updateButtonsStates();
626 }
627}
628
629GeoDataFeature* TourWidgetPrivate::getPlaylistFeature() const
630{
631 GeoDataObject *rootObject = rootIndexObject();
632 if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
633 GeoDataObject *object = playlist->parent();
634 if (GeoDataTour *tour = (object ? geodata_cast<GeoDataTour>(object) : nullptr)) {
635 return tour;
636 }
637 }
638 return nullptr;
639}
640
641void TourWidgetPrivate::updateRootIndex()
642{
643 GeoDataTour *tour = findTour( m_document );
644 if ( tour ){
645 GeoDataPlaylist *playlist = tour->playlist();
646 if ( playlist ) {
647 m_tourUi.m_listView->setModel( m_widget->model()->treeModel() );
648 m_tourUi.m_listView->setRootIndex( m_widget->model()->treeModel()->index( playlist ) );
649 QObject::connect( m_tourUi.m_listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
650 q, SLOT(updateButtonsStates()) );
651 }
652 m_playback.setMarbleWidget( m_widget );
653 m_playback.setTour( tour );
654 m_tourUi.m_slider->setMaximum( m_playback.duration() * 100 );
655 QTime nullTime( 0, 0, 0 );
656 QTime time = nullTime.addSecs( m_playback.duration() );
657 m_tourUi.m_totalTime->setText(QString("%L1:%L2").arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
658 QObject::connect( &m_playback, SIGNAL(progressChanged(double)),
659 q, SLOT(handlePlaybackProgress(double)) );
660 q->stopPlaying();
661 m_tourUi.m_toolBarPlayback->setEnabled( true );
662 bool isPlaybackEmpty = m_playback.mainTrackSize() != 0;
663 m_tourUi.actionPlay->setEnabled( isPlaybackEmpty );
664 m_tourUi.m_slider->setEnabled( isPlaybackEmpty );
665 m_tourUi.m_actionRecord->setEnabled( isPlaybackEmpty );
666 m_tourUi.actionStop->setEnabled( false );
667 if( m_playback.mainTrackSize() > 0 ) {
668 if( dynamic_cast<PlaybackFlyToItem*>( m_playback.mainTrackItemAt( 0 ) ) ) {
669 QModelIndex playlistIndex = m_widget->model()->treeModel()->index( playlist );
670 for( int i = 0; playlist && i < playlist->size(); ++i ) {
671 if (geodata_cast<GeoDataFlyTo>(playlist->primitive(i))) {
672 m_delegate->setFirstFlyTo( m_widget->model()->treeModel()->index( i, 0, playlistIndex ) );
673 break;
674 }
675 }
676 } else {
677 m_delegate->setFirstFlyTo( QPersistentModelIndex() );
678 }
679 }
680 }
681}
682
683void TourWidget::addFlyTo()
684{
685 d->addFlyTo();
686 finishAddingItem();
687}
688
689void TourWidget::addWait()
690{
691 d->addWait();
692 finishAddingItem();
693}
694
695void TourWidget::addSoundCue()
696{
697 d->addSoundCue();
698 finishAddingItem();
699}
700
701void TourWidget::addPlacemark()
702{
703 d->addPlacemark();
704 finishAddingItem();
705}
706
707void TourWidget::addRemovePlacemark()
708{
709 d->addRemovePlacemark();
710 finishAddingItem();
711}
712
713void TourWidget::addChangePlacemark()
714{
715 d->addChangePlacemark();
716 finishAddingItem();
717}
718
719void TourWidget::deleteSelected()
720{
721 d->deleteSelected();
722 GeoDataFeature *feature = d->getPlaylistFeature();
723 if ( feature ) {
724 emit featureUpdated( feature );
725 d->updateRootIndex();
726 }
727}
728
729void TourWidget::updateDuration()
730{
731 d->m_tourUi.m_slider->setMaximum( d->m_playback.duration() * 100 );
732 QTime nullTime( 0, 0, 0 );
733 QTime totalTime = nullTime.addSecs( d->m_playback.duration() );
734 d->m_tourUi.m_totalTime->setText(QString("%L1:%L2").arg(totalTime.minute(), 2, 10, QLatin1Char('0') ).arg(totalTime.second(), 2, 10, QLatin1Char('0')));
735 d->m_tourUi.m_slider->setValue( 0 );
736 d->m_tourUi.m_elapsedTime->setText(QString("%L1:%L2").arg(0, 2, 10, QLatin1Char('0')).arg(0, 2, 10, QLatin1Char('0')));
737}
738
739void TourWidget::finishAddingItem()
740{
741 GeoDataFeature *feature = d->getPlaylistFeature();
742 if ( feature ) {
743 emit featureUpdated( feature );
744 d->updateRootIndex();
745 }
746}
747
748void TourWidget::moveDown()
749{
750 d->moveDown();
751 GeoDataFeature *feature = d->getPlaylistFeature();
752 if ( feature ) {
753 emit featureUpdated( feature );
754 d->updateRootIndex();
755 }
756}
757
758void TourWidget::moveUp()
759{
760 d->moveUp();
761 GeoDataFeature *feature = d->getPlaylistFeature();
762 if ( feature ) {
763 emit featureUpdated( feature );
764 d->updateRootIndex();
765 }
766}
767
768GeoDataObject *TourWidgetPrivate::rootIndexObject() const
769{
770 QModelIndex const rootIndex = m_tourUi.m_listView->rootIndex();
771 return rootIndex.isValid() ? static_cast<GeoDataObject*>( rootIndex.internalPointer() ) : nullptr;
772}
773
774void TourWidgetPrivate::createTour()
775{
776 if ( overrideModifications() ) {
777 GeoDataDocument *document = new GeoDataDocument();
778 document->setDocumentRole( UserDocument );
779 document->setName(QStringLiteral("New Tour"));
780 document->setId(QStringLiteral("new_tour"));
781 GeoDataTour *tour = new GeoDataTour();
782 tour->setName(QStringLiteral("New Tour"));
783 GeoDataPlaylist *playlist = new GeoDataPlaylist;
784 tour->setPlaylist( playlist );
785 document->append( static_cast<GeoDataFeature*>( tour ) );
786 m_playback.setBaseUrl( QUrl::fromLocalFile( MarbleDirs::marbleDataPath() ) );
787 openDocument( document );
788 m_isChanged = true;
789 m_tourUi.m_actionSaveTour->setEnabled( true );
790 m_tourUi.m_slider->setEnabled( true );
791 }
792}
793
794bool TourWidgetPrivate::openDocument(GeoDataDocument* document)
795{
796 if ( document ) {
797 if ( m_document ) {
798 m_widget->model()->treeModel()->removeDocument( m_document );
799 delete m_document;
800 }
801 m_document = document;
802 m_widget->model()->treeModel()->addDocument( m_document );
803 m_isChanged = false;
804 updateRootIndex();
805 m_addPrimitiveButton->setEnabled( true );
806 m_tourUi.m_actionSaveTourAs->setEnabled( true );
807 m_tourUi.m_actionSaveTour->setEnabled( false );
808 m_isChanged = false;
809 return true;
810 }
811 return false;
812}
813
814void TourWidgetPrivate::saveTour()
815{
816 if ( m_document ) {
817 if ( !m_document->fileName().isEmpty() ) {
818 saveTourAs( m_document->fileName() );
819 } else {
820 saveTourAs();
821 }
822 }
823}
824
825void TourWidgetPrivate::saveTourAs()
826{
827 if ( m_document )
828 {
829 QString const filename = QFileDialog::getSaveFileName( q, QObject::tr( "Save Tour as" ), QDir::homePath(), QObject::tr( "KML Tours (*.kml)" ) );
830 if ( !filename.isEmpty() ) {
831 saveTourAs( filename );
832 }
833 }
834}
835
836bool TourWidgetPrivate::saveTourAs(const QString &filename)
837{
838 if ( !filename.isEmpty() ) {
839 if (GeoDataDocumentWriter::write(filename, *m_document)) {
840 m_tourUi.m_actionSaveTour->setEnabled( false );
841 m_isChanged = false;
842 GeoDataDocument* document = m_document;
843 if ( !document->fileName().isNull() ) {
844 m_widget->model()->removeGeoData( document->fileName() );
845 }
846 m_widget->model()->addGeoDataFile( filename );
847 m_document->setFileName( filename );
848 return true;
849 }
850 }
851 return false;
852}
853
854void TourWidgetPrivate::captureTour()
855{
856 MarbleWidget* widget = new MarbleWidget;
857 widget->setMapThemeId( m_widget->mapThemeId() );
858 widget->resize( 1280, 720 );
859
860 m_widget->model()->treeModel()->removeDocument(m_document);
861 widget->model()->treeModel()->addDocument(m_document);
862
863 GeoDataTour* tour = findTour( m_document );
864 TourPlayback* playback = new TourPlayback;
865 playback->setMarbleWidget( widget );
866 playback->setTour( tour );
867
868 m_tourUi.m_listView->setModel( widget->model()->treeModel() );
869 if( tour ){
870 m_tourUi.m_listView->setRootIndex( widget->model()->treeModel()->index( tour->playlist() ) );
871 m_tourUi.m_listView->repaint();
872
873 QPointer<TourCaptureDialog> tourCaptureDialog = new TourCaptureDialog( widget, m_widget );
874 tourCaptureDialog->setDefaultFilename( tour->name() );
875 tourCaptureDialog->setTourPlayback( playback );
876 tourCaptureDialog->exec();
877 }
878
879 delete playback;
880 widget->model()->treeModel()->removeDocument(m_document);
881 m_widget->model()->treeModel()->addDocument(m_document);
882 updateRootIndex();
883 delete widget;
884}
885
886bool TourWidgetPrivate::overrideModifications()
887{
888 if ( m_document && m_isChanged ) {
889 QString title = QObject::tr( "Discard Changes" );
890 QString text = QObject::tr( "Are you sure want to discard all unsaved changes and close current document?" );
892 dialog->setDefaultButton( QMessageBox::No );
893 if ( dialog->exec() != QMessageBox::Yes ) {
894 delete dialog;
895 return false;
896 }
897 delete dialog;
898 }
899 return true;
900}
901
902bool TourWidget::openTour( const QString &filename)
903{
904 return d->openFile( filename );
905}
906
907void TourWidgetPrivate::handlePlaybackProgress(const double position)
908{
909 if( !m_tourUi.m_slider->isSliderDown() ){
910 m_tourUi.m_slider->setValue( position * 100 );
911 QTime nullTime( 0, 0, 0 );
912 QTime time = nullTime.addSecs( position );
913 m_tourUi.m_elapsedTime->setText(QString("%L1:%L2").arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
914 }
915}
916
917void TourWidget::setHighlightedItemIndex( int index )
918{
919 GeoDataObject* rootObject = d->rootIndexObject();
920 GeoDataPlaylist* playlist = static_cast<GeoDataPlaylist*>( rootObject );
921 QModelIndex playlistIndex = d->m_widget->model()->treeModel()->index( playlist );
922
923 // Only flyTo and wait items have duration, so the other types have to be skipped.
924 int searchedIndex = 0;
925 for ( int i = 0; i < playlist->size(); i++ ) {
926
927 QModelIndex currentIndex = d->m_widget->model()->treeModel()->index( i, 0, playlistIndex );
928 GeoDataObject* object = qvariant_cast<GeoDataObject*>(currentIndex.data( MarblePlacemarkModel::ObjectPointerRole ) );
929
930 if (geodata_cast<GeoDataFlyTo>(object)
931 || geodata_cast<GeoDataWait>(object))
932 ++searchedIndex;
933
934 if ( index == searchedIndex ) {
935 d->m_tourUi.m_listView->selectionModel()->setCurrentIndex( currentIndex, QItemSelectionModel::NoUpdate );
936 d->m_tourUi.m_listView->scrollTo( currentIndex );
937 break;
938 }
939 }
940 d->m_tourUi.m_listView->viewport()->update();
941}
942
943void TourWidget::removeHighlight()
944{
945 QModelIndex index;
946
947 // Restoring the CurrentIndex to the previously selected item
948 // or clearing it if there was no selected item.
949 if ( d->m_tourUi.m_listView->selectionModel()->hasSelection() ) {
950 index = d->m_tourUi.m_listView->selectionModel()->selectedIndexes().last();
951 }
952 else {
953 index = QModelIndex();
954 }
955
956 d->m_tourUi.m_listView->selectionModel()->setCurrentIndex( index, QItemSelectionModel::NoUpdate );
957 d->m_tourUi.m_listView->viewport()->update();
958}
959
960bool TourWidget::isPlaying() const
961{
962 return d->m_playState;
963}
964
965}
966
967#include "moc_TourWidget.cpp"
This file contains the headers for MarbleModel.
This file contains the headers for MarbleWidget.
Q_SCRIPTABLE Q_NOREPLY void start()
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void update(Part *part, const QByteArray &data, qint64 dataSize)
QAction * create(GameStandardAction id, const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & end()
Binds a QML item to a specific geodetic location in screen coordinates.
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
void setIcon(const QIcon &icon)
QString homePath()
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
int key() const const
Qt::KeyboardModifiers modifiers() const const
iterator begin()
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addSeparator()
StandardButton question(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
QVariant data(int role) const const
void * internalPointer() const const
bool isValid() const const
const QAbstractItemModel * model() const const
int row() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString tr(const char *sourceText, const char *disambiguation, int n)
bool isEmpty() const const
ActionsContextMenu
Key_Delete
ControlModifier
typedef WindowFlags
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QTime addSecs(int s) const const
int minute() const const
int second() const const
QUrl fromLocalFile(const QString &localFile)
void * data()
bool isNull() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.