Marble

TourWidget.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2013 Mihail Ivchenko <[email protected]>
4 // SPDX-FileCopyrightText: 2014 Sanjiban Bairagya <[email protected]>
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 
59 namespace Marble
60 {
61 
62 class TourWidgetPrivate
63 {
64 
65 public:
66  explicit TourWidgetPrivate( TourWidget *parent );
67  ~TourWidgetPrivate();
68  GeoDataFeature *getPlaylistFeature() const;
69  void updateRootIndex();
70 
71 public:
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 
94 private:
95  GeoDataTour* findTour( GeoDataFeature* feature ) const;
96  bool openDocument( GeoDataDocument *document );
97  bool saveTourAs( const QString &filename );
98  bool overrideModifications();
99 
100 public:
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 
121 TourWidgetPrivate::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 
187 TourWidgetPrivate::~TourWidgetPrivate()
188 {
189  delete m_delegate;
190 }
191 
192 TourWidget::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 
212 TourWidget::~TourWidget()
213 {
214  delete d;
215 }
216 
217 bool 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 
265 void 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 
275 void 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 
286 void 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 
300 void 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 
308 void 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 
326 void TourWidget::stopLooping()
327 {
328  d->m_isLoopingStopped = true;
329 }
330 
331 void 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 ) {
344  case QMessageBox::Save:
345  d->saveTour();
346  event->accept();
347  break;
349  event->accept();
350  break;
351  case QMessageBox::Cancel:
352  event->ignore();
353  }
354 }
355 
356 void 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 
365 void 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 
378 bool 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 
392 GeoDataTour *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 
412 void 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 
424 void 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 
435 void TourWidgetPrivate::addWait()
436 {
437  GeoDataWait *wait = new GeoDataWait();
438  wait->setDuration( 1.0 );
439  addTourPrimitive( wait );
440 }
441 
442 void TourWidgetPrivate::addSoundCue()
443 {
444  GeoDataSoundCue *soundCue = new GeoDataSoundCue();
445  addTourPrimitive( soundCue );
446 }
447 
448 void 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();
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 
491 void 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 
504 void 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 
524 void 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 
545 void 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 
569 void 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 
589 void 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 
609 void 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 
629 GeoDataFeature* 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 
641 void 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 
683 void TourWidget::addFlyTo()
684 {
685  d->addFlyTo();
686  finishAddingItem();
687 }
688 
689 void TourWidget::addWait()
690 {
691  d->addWait();
692  finishAddingItem();
693 }
694 
695 void TourWidget::addSoundCue()
696 {
697  d->addSoundCue();
698  finishAddingItem();
699 }
700 
701 void TourWidget::addPlacemark()
702 {
703  d->addPlacemark();
704  finishAddingItem();
705 }
706 
707 void TourWidget::addRemovePlacemark()
708 {
709  d->addRemovePlacemark();
710  finishAddingItem();
711 }
712 
713 void TourWidget::addChangePlacemark()
714 {
715  d->addChangePlacemark();
716  finishAddingItem();
717 }
718 
719 void TourWidget::deleteSelected()
720 {
721  d->deleteSelected();
722  GeoDataFeature *feature = d->getPlaylistFeature();
723  if ( feature ) {
724  emit featureUpdated( feature );
725  d->updateRootIndex();
726  }
727 }
728 
729 void 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 
739 void TourWidget::finishAddingItem()
740 {
741  GeoDataFeature *feature = d->getPlaylistFeature();
742  if ( feature ) {
743  emit featureUpdated( feature );
744  d->updateRootIndex();
745  }
746 }
747 
748 void TourWidget::moveDown()
749 {
750  d->moveDown();
751  GeoDataFeature *feature = d->getPlaylistFeature();
752  if ( feature ) {
753  emit featureUpdated( feature );
754  d->updateRootIndex();
755  }
756 }
757 
758 void TourWidget::moveUp()
759 {
760  d->moveUp();
761  GeoDataFeature *feature = d->getPlaylistFeature();
762  if ( feature ) {
763  emit featureUpdated( feature );
764  d->updateRootIndex();
765  }
766 }
767 
768 GeoDataObject *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 
774 void 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 
794 bool 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 
814 void 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 
825 void 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 
836 bool 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 
854 void 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 
886 bool 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 
902 bool TourWidget::openTour( const QString &filename)
903 {
904  return d->openFile( filename );
905 }
906 
907 void 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 
917 void 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 
943 void 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 
960 bool TourWidget::isPlaying() const
961 {
962  return d->m_playState;
963 }
964 
965 }
966 
967 #include "moc_TourWidget.cpp"
bool isNull() const const
void * internalPointer() const const
QVector::iterator begin()
T value() const const
Q_SCRIPTABLE Q_NOREPLY void start()
QAction * addSeparator()
ActionsContextMenu
QTime addSecs(int s) const const
QString homePath()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
static bool write(QIODevice *device, const GeoDataDocument &document, const QString &documentIdentifier)
Write the content of the given GeoDataDocument to the given I/O device.
QAction * addAction(const QString &text)
@ ObjectPointerRole
The pointer to a specific object.
typedef WindowFlags
QVariant data(int role) const const
QMessageBox::StandardButton question(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
void setIcon(const QIcon &icon)
Qt::KeyboardModifiers modifiers() const const
QAction * create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent)
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
virtual QVariant rootObject()
@ CoordinateRole
The GeoDataCoordinates coordinate.
Binds a QML item to a specific geodetic location in screen coordinates.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
Key_Delete
bool isValid() const const
int row() const const
@ Instant
Change camera position immediately (no interpolation)
Definition: MarbleGlobal.h:159
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
int key() const const
int second() const const
void update(Part *part, const QByteArray &data, qint64 dataSize)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
ControlModifier
static void normalizeLonLat(qreal &lon, qreal &lat, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize both longitude and latitude at the same time This method normalizes both latitude and longi...
QString tr(const char *sourceText, const char *disambiguation, int n)
int minute() const const
const QAbstractItemModel * model() const const
const QList< QKeySequence > & end()
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu May 26 2022 04:07:51 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.