QGIS API Documentation  3.17.0-Master (a035f434f4)
qgstemporalcontrollerwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstemporalcontrollerwidget.cpp
3  ------------------------------
4  begin : February 2020
5  copyright : (C) 2020 by Samweli Mwakisambwe
6  email : samweli at kartoza dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsapplication.h"
20 #include "qgsgui.h"
21 #include "qgsmaplayermodel.h"
22 #include "qgsproject.h"
23 #include "qgsprojecttimesettings.h"
25 #include "qgstemporalutils.h"
27 #include "qgsmeshlayer.h"
28 
29 #include <QAction>
30 #include <QMenu>
31 
33  : QgsPanelWidget( parent )
34 {
35  setupUi( this );
36 
37  mNavigationObject = new QgsTemporalNavigationObject( this );
38 
39  mStartDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
40  mEndDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
41  mFixedRangeStartDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
42  mFixedRangeEndDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
43 
44  connect( mForwardButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::togglePlayForward );
45  connect( mBackButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::togglePlayBackward );
46  connect( mStopButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::togglePause );
47  connect( mNextButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::next );
48  connect( mPreviousButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::previous );
49  connect( mFastForwardButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::skipToEnd );
50  connect( mRewindButton, &QPushButton::clicked, mNavigationObject, &QgsTemporalNavigationObject::rewindToStart );
51  connect( mLoopingCheckBox, &QCheckBox::toggled, this, [ = ]( bool state ) { mNavigationObject->setLooping( state ); } );
52 
53  setWidgetStateFromNavigationMode( mNavigationObject->navigationMode() );
54  connect( mNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsTemporalControllerWidget::setWidgetStateFromNavigationMode );
55  connect( mNavigationObject, &QgsTemporalNavigationObject::temporalExtentsChanged, this, &QgsTemporalControllerWidget::setDates );
56  connect( mNavigationObject, &QgsTemporalNavigationObject::temporalFrameDurationChanged, this, [ = ]( const QgsInterval & timeStep )
57  {
58  if ( mBlockFrameDurationUpdates )
59  return;
60 
61  mBlockFrameDurationUpdates++;
62  setTimeStep( timeStep );
63  mBlockFrameDurationUpdates--;
64  } );
65  connect( mNavigationOff, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationOff_clicked );
66  connect( mNavigationFixedRange, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationFixedRange_clicked );
67  connect( mNavigationAnimated, &QPushButton::clicked, this, &QgsTemporalControllerWidget::mNavigationAnimated_clicked );
68 
69  connect( mNavigationObject, &QgsTemporalNavigationObject::stateChanged, this, [ = ]( QgsTemporalNavigationObject::AnimationState state )
70  {
71  mForwardButton->setChecked( state == QgsTemporalNavigationObject::Forward );
72  mBackButton->setChecked( state == QgsTemporalNavigationObject::Reverse );
73  mStopButton->setChecked( state == QgsTemporalNavigationObject::Idle );
74  } );
75 
76  connect( mStartDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::startEndDateTime_changed );
77  connect( mEndDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::startEndDateTime_changed );
78  connect( mFixedRangeStartDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
79  connect( mFixedRangeEndDateTime, &QDateTimeEdit::dateTimeChanged, this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
80  connect( mStepSpinBox, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsTemporalControllerWidget::updateFrameDuration );
81  connect( mTimeStepsComboBox, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsTemporalControllerWidget::updateFrameDuration );
82  connect( mSlider, &QSlider::valueChanged, this, &QgsTemporalControllerWidget::timeSlider_valueChanged );
83 
84  mStepSpinBox->setClearValue( 1 );
85 
86  connect( mNavigationObject, &QgsTemporalNavigationObject::updateTemporalRange, this, &QgsTemporalControllerWidget::updateSlider );
87 
88  connect( mSettings, &QPushButton::clicked, this, &QgsTemporalControllerWidget::settings_clicked );
89 
90  mMapLayerModel = new QgsMapLayerModel( this );
91 
92  mRangeMenu.reset( new QMenu( this ) );
93 
94  mRangeSetToAllLayersAction = new QAction( tr( "Set to Full Range" ), mRangeMenu.get() );
95  mRangeSetToAllLayersAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRefresh.svg" ) ) );
96  connect( mRangeSetToAllLayersAction, &QAction::triggered, this, &QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered );
97  mRangeMenu->addAction( mRangeSetToAllLayersAction );
98 
99  mRangeSetToProjectAction = new QAction( tr( "Set to Preset Project Range" ), mRangeMenu.get() );
100  connect( mRangeSetToProjectAction, &QAction::triggered, this, &QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered );
101  mRangeMenu->addAction( mRangeSetToProjectAction );
102 
103  mRangeMenu->addSeparator();
104 
105  mRangeLayersSubMenu.reset( new QMenu( tr( "Set to Single Layer's Range" ), mRangeMenu.get() ) );
106  mRangeLayersSubMenu->setEnabled( false );
107  mRangeMenu->addMenu( mRangeLayersSubMenu.get() );
108  connect( mRangeMenu.get(), &QMenu::aboutToShow, this, &QgsTemporalControllerWidget::aboutToShowRangeMenu );
109 
110  mSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
111  mSetRangeButton->setMenu( mRangeMenu.get() );
112  mSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
113  mFixedRangeSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
114  mFixedRangeSetRangeButton->setMenu( mRangeMenu.get() );
115  mFixedRangeSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
116 
117  connect( mExportAnimationButton, &QPushButton::clicked, this, &QgsTemporalControllerWidget::exportAnimation );
118 
119  QgsDateTimeRange range;
120 
121  if ( QgsProject::instance()->timeSettings() )
123 
124  mStartDateTime->setDisplayFormat( "yyyy-MM-dd HH:mm:ss" );
125  mEndDateTime->setDisplayFormat( "yyyy-MM-dd HH:mm:ss" );
126  mFixedRangeStartDateTime->setDisplayFormat( "yyyy-MM-dd HH:mm:ss" );
127  mFixedRangeEndDateTime->setDisplayFormat( "yyyy-MM-dd HH:mm:ss" );
128 
129  if ( range.begin().isValid() && range.end().isValid() )
130  {
131  whileBlocking( mStartDateTime )->setDateTime( range.begin() );
132  whileBlocking( mEndDateTime )->setDateTime( range.end() );
133  whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
134  whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
135  }
136 
138  {
149  } )
150  {
151  mTimeStepsComboBox->addItem( QgsUnitTypes::toString( u ), u );
152  }
153 
154  // TODO: might want to choose an appropriate default unit based on the range
155  mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( QgsUnitTypes::TemporalHours ) );
156 
157  mStepSpinBox->setMinimum( 0.0000001 );
158  mStepSpinBox->setMaximum( std::numeric_limits<int>::max() );
159  mStepSpinBox->setSingleStep( 1 );
160  mStepSpinBox->setValue( 1 );
161 
162  mForwardButton->setToolTip( tr( "Play" ) );
163  mBackButton->setToolTip( tr( "Reverse" ) );
164  mNextButton->setToolTip( tr( "Go to next frame" ) );
165  mPreviousButton->setToolTip( tr( "Go to previous frame" ) );
166  mStopButton->setToolTip( tr( "Pause" ) );
167  mRewindButton->setToolTip( tr( "Rewind to start" ) );
168  mFastForwardButton->setToolTip( tr( "Fast forward to end" ) );
169 
170  updateFrameDuration();
171 
172  connect( QgsProject::instance(), &QgsProject::readProject, this, &QgsTemporalControllerWidget::setWidgetStateFromProject );
173  connect( QgsProject::instance(), &QgsProject::layersAdded, this, &QgsTemporalControllerWidget::onLayersAdded );
174  connect( QgsProject::instance(), &QgsProject::cleared, this, &QgsTemporalControllerWidget::onProjectCleared );
175 }
176 
178 {
179  if ( mSlider->hasFocus() && e->key() == Qt::Key_Space )
180  {
181  togglePause();
182  }
183  QWidget::keyPressEvent( e );
184 }
185 
186 void QgsTemporalControllerWidget::aboutToShowRangeMenu()
187 {
188  QgsDateTimeRange projectRange;
189  if ( QgsProject::instance()->timeSettings() )
190  projectRange = QgsProject::instance()->timeSettings()->temporalRange();
191  mRangeSetToProjectAction->setEnabled( projectRange.begin().isValid() && projectRange.end().isValid() );
192 
193  mRangeLayersSubMenu->clear();
194  for ( int i = 0; i < mMapLayerModel->rowCount(); ++i )
195  {
196  QModelIndex index = mMapLayerModel->index( i, 0 );
197  QgsMapLayer *currentLayer = mMapLayerModel->data( index, QgsMapLayerModel::LayerRole ).value<QgsMapLayer *>();
198  if ( !currentLayer->temporalProperties() || !currentLayer->temporalProperties()->isActive() )
199  continue;
200 
201  QIcon icon = qvariant_cast<QIcon>( mMapLayerModel->data( index, Qt::DecorationRole ) );
202  QString text = mMapLayerModel->data( index, Qt::DisplayRole ).toString();
203  QgsDateTimeRange range = currentLayer->temporalProperties()->calculateTemporalExtent( currentLayer );
204  if ( range.begin().isValid() && range.end().isValid() )
205  {
206  QAction *action = new QAction( icon, text, mRangeLayersSubMenu.get() );
207  connect( action, &QAction::triggered, this, [ = ]
208  {
209  setDates( range );
210  saveRangeToProject();
211  } );
212  mRangeLayersSubMenu->addAction( action );
213  }
214  }
215  mRangeLayersSubMenu->setEnabled( !mRangeLayersSubMenu->actions().isEmpty() );
216 }
217 
218 void QgsTemporalControllerWidget::togglePlayForward()
219 {
220  mPlayingForward = true;
221 
222  if ( mNavigationObject->animationState() != QgsTemporalNavigationObject::Forward )
223  {
224  mStopButton->setChecked( false );
225  mBackButton->setChecked( false );
226  mForwardButton->setChecked( true );
227  mNavigationObject->playForward();
228  }
229  else
230  {
231  mBackButton->setChecked( true );
232  mForwardButton->setChecked( false );
233  mNavigationObject->pause();
234  }
235 }
236 
237 void QgsTemporalControllerWidget::togglePlayBackward()
238 {
239  mPlayingForward = false;
240 
241  if ( mNavigationObject->animationState() != QgsTemporalNavigationObject::Reverse )
242  {
243  mStopButton->setChecked( false );
244  mBackButton->setChecked( true );
245  mForwardButton->setChecked( false );
246  mNavigationObject->playBackward();
247  }
248  else
249  {
250  mBackButton->setChecked( true );
251  mBackButton->setChecked( false );
252  mNavigationObject->pause();
253  }
254 }
255 
256 void QgsTemporalControllerWidget::togglePause()
257 {
258  if ( mNavigationObject->animationState() != QgsTemporalNavigationObject::Idle )
259  {
260  mStopButton->setChecked( true );
261  mBackButton->setChecked( false );
262  mForwardButton->setChecked( false );
263  mNavigationObject->pause();
264  }
265  else
266  {
267  mBackButton->setChecked( mPlayingForward ? false : true );
268  mForwardButton->setChecked( mPlayingForward ? false : true );
269  if ( mPlayingForward )
270  {
271  mNavigationObject->playForward();
272  }
273  else
274  {
275  mNavigationObject->playBackward();
276  }
277  }
278 }
279 
280 void QgsTemporalControllerWidget::updateTemporalExtent()
281 {
282  QgsDateTimeRange temporalExtent = QgsDateTimeRange( mStartDateTime->dateTime(),
283  mEndDateTime->dateTime() );
284  mNavigationObject->setTemporalExtents( temporalExtent );
285  mSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
286  mSlider->setValue( mNavigationObject->currentFrameNumber() );
287 }
288 
289 void QgsTemporalControllerWidget::updateFrameDuration()
290 {
291  if ( mBlockSettingUpdates )
292  return;
293 
294  // save new settings into project
295  QgsProject::instance()->timeSettings()->setTimeStepUnit( static_cast< QgsUnitTypes::TemporalUnit>( mTimeStepsComboBox->currentData().toInt() ) );
296  QgsProject::instance()->timeSettings()->setTimeStep( mStepSpinBox->value() );
297 
298  if ( !mBlockFrameDurationUpdates )
299  {
300  mNavigationObject->setFrameDuration( QgsInterval( QgsProject::instance()->timeSettings()->timeStep(),
301  QgsProject::instance()->timeSettings()->timeStepUnit() ) );
302  mSlider->setValue( mNavigationObject->currentFrameNumber() );
303  }
304  mSlider->setRange( 0, mNavigationObject->totalFrameCount() - 1 );
305 }
306 
307 void QgsTemporalControllerWidget::setWidgetStateFromProject()
308 {
309  mBlockSettingUpdates++;
310  mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( QgsProject::instance()->timeSettings()->timeStepUnit() ) );
311  mStepSpinBox->setValue( QgsProject::instance()->timeSettings()->timeStep() );
312  mBlockSettingUpdates--;
313 
314  bool ok = false;
315  QgsTemporalNavigationObject::NavigationMode mode = static_cast< QgsTemporalNavigationObject::NavigationMode>( QgsProject::instance()->readNumEntry( QStringLiteral( "TemporalControllerWidget" ),
316  QStringLiteral( "/NavigationMode" ), 0, &ok ) );
317  if ( ok )
318  {
319  mNavigationObject->setNavigationMode( mode );
320  setWidgetStateFromNavigationMode( mode );
321  }
322  else
323  {
325  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::NavigationOff );
326  }
327 
328  const QString startString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/StartDateTime" ) );
329  const QString endString = QgsProject::instance()->readEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/EndDateTime" ) );
330  if ( !startString.isEmpty() && !endString.isEmpty() )
331  {
332  whileBlocking( mStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODate ) );
333  whileBlocking( mEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODate ) );
334  whileBlocking( mFixedRangeStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODate ) );
335  whileBlocking( mFixedRangeEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODate ) );
336  }
337  else
338  {
339  setDatesToProjectTime();
340  }
341  updateTemporalExtent();
342  updateFrameDuration();
343 
344  mNavigationObject->setFramesPerSecond( QgsProject::instance()->timeSettings()->framesPerSecond() );
345  mNavigationObject->setTemporalRangeCumulative( QgsProject::instance()->timeSettings()->isTemporalRangeCumulative() );
346 }
347 
348 void QgsTemporalControllerWidget::mNavigationOff_clicked()
349 {
350  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ),
351  static_cast<int>( QgsTemporalNavigationObject::NavigationOff ) );
352 
354  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::NavigationOff );
355 }
356 
357 void QgsTemporalControllerWidget::mNavigationFixedRange_clicked()
358 {
359  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ),
360  static_cast<int>( QgsTemporalNavigationObject::FixedRange ) );
361 
363  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::FixedRange );
364 }
365 
366 void QgsTemporalControllerWidget::mNavigationAnimated_clicked()
367 {
368  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ), QStringLiteral( "/NavigationMode" ),
369  static_cast<int>( QgsTemporalNavigationObject::Animated ) );
370 
372  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::Animated );
373 }
374 
375 void QgsTemporalControllerWidget::setWidgetStateFromNavigationMode( const QgsTemporalNavigationObject::NavigationMode mode )
376 {
377  mNavigationOff->setChecked( mode == QgsTemporalNavigationObject::NavigationOff );
378  mNavigationFixedRange->setChecked( mode == QgsTemporalNavigationObject::FixedRange );
379  mNavigationAnimated->setChecked( mode == QgsTemporalNavigationObject::Animated );
380 
381  switch ( mode )
382  {
384  mNavigationModeStackedWidget->setCurrentIndex( 0 );
385  break;
387  mNavigationModeStackedWidget->setCurrentIndex( 1 );
388  break;
390  mNavigationModeStackedWidget->setCurrentIndex( 2 );
391  break;
392  }
393 }
394 
395 void QgsTemporalControllerWidget::onLayersAdded( const QList<QgsMapLayer *> &layers )
396 {
397  if ( !mHasTemporalLayersLoaded )
398  {
399  for ( QgsMapLayer *layer : layers )
400  {
401  if ( layer->temporalProperties() )
402  {
403  mHasTemporalLayersLoaded |= layer->temporalProperties()->isActive();
404 
405  if ( !mHasTemporalLayersLoaded )
406  {
407  connect( layer, &QgsMapLayer::dataSourceChanged, this, [ this, layer ]
408  {
409  if ( layer->isValid() && layer->temporalProperties()->isActive() && !mHasTemporalLayersLoaded )
410  {
411  mHasTemporalLayersLoaded = true;
412  firstTemporalLayerLoaded( layer );
413  }
414  } );
415  }
416 
417  firstTemporalLayerLoaded( layer );
418  }
419  }
420  }
421 }
422 
423 void QgsTemporalControllerWidget::firstTemporalLayerLoaded( QgsMapLayer *layer )
424 {
425  setDatesToProjectTime();
426 
427  QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer );
428  if ( meshLayer )
429  {
430  mBlockFrameDurationUpdates++;
431  setTimeStep( meshLayer->firstValidTimeStep() );
432  mBlockFrameDurationUpdates--;
433  updateFrameDuration();
434  }
435 }
436 
437 void QgsTemporalControllerWidget::onProjectCleared()
438 {
439  mHasTemporalLayersLoaded = false;
440 
442  setWidgetStateFromNavigationMode( QgsTemporalNavigationObject::NavigationOff );
443 
444  whileBlocking( mStartDateTime )->setDateTime( QDateTime( QDate::currentDate(), QTime( 0, 0, 0 ), Qt::UTC ) );
445  whileBlocking( mEndDateTime )->setDateTime( mStartDateTime->dateTime() );
446  whileBlocking( mFixedRangeStartDateTime )->setDateTime( QDateTime( QDate::currentDate(), QTime( 0, 0, 0 ), Qt::UTC ) );
447  whileBlocking( mFixedRangeEndDateTime )->setDateTime( mStartDateTime->dateTime() );
448  updateTemporalExtent();
449  mTimeStepsComboBox->setCurrentIndex( mTimeStepsComboBox->findData( QgsUnitTypes::TemporalHours ) );
450  mStepSpinBox->setValue( 1 );
451 }
452 
453 void QgsTemporalControllerWidget::updateSlider( const QgsDateTimeRange &range )
454 {
455  whileBlocking( mSlider )->setValue( mNavigationObject->currentFrameNumber() );
456  updateRangeLabel( range );
457 }
458 
459 void QgsTemporalControllerWidget::updateRangeLabel( const QgsDateTimeRange &range )
460 {
461  switch ( mNavigationObject->navigationMode() )
462  {
464  mCurrentRangeLabel->setText( tr( "Frame: %1 to %2" ).arg(
465  range.begin().toString( "yyyy-MM-dd HH:mm:ss" ),
466  range.end().toString( "yyyy-MM-dd HH:mm:ss" ) ) );
467  break;
469  mCurrentRangeLabel->setText( tr( "Range: %1 to %2" ).arg(
470  range.begin().toString( "yyyy-MM-dd HH:mm:ss" ),
471  range.end().toString( "yyyy-MM-dd HH:mm:ss" ) ) );
472  break;
474  mCurrentRangeLabel->setText( tr( "Temporal navigation disabled" ) );
475  break;
476  }
477 }
478 
480 {
481  return mNavigationObject;
482 }
483 
484 void QgsTemporalControllerWidget::settings_clicked()
485 {
486  QgsTemporalMapSettingsWidget *settingsWidget = new QgsTemporalMapSettingsWidget( this );
487  settingsWidget->setFrameRateValue( mNavigationObject->framesPerSecond() );
488  settingsWidget->setIsTemporalRangeCumulative( mNavigationObject->temporalRangeCumulative() );
489 
490  connect( settingsWidget, &QgsTemporalMapSettingsWidget::frameRateChanged, this, [ = ]( double rate )
491  {
492  // save new settings into project
494  mNavigationObject->setFramesPerSecond( rate );
495  } );
496 
497  connect( settingsWidget, &QgsTemporalMapSettingsWidget::temporalRangeCumulativeChanged, this, [ = ]( bool state )
498  {
499  // save new settings into project
501  mNavigationObject->setTemporalRangeCumulative( state );
502  } );
503  openPanel( settingsWidget );
504 }
505 
506 void QgsTemporalControllerWidget::timeSlider_valueChanged( int value )
507 {
508  mNavigationObject->setCurrentFrameNumber( value );
509 }
510 
511 void QgsTemporalControllerWidget::startEndDateTime_changed()
512 {
513  whileBlocking( mFixedRangeStartDateTime )->setDateTime( mStartDateTime->dateTime() );
514  whileBlocking( mFixedRangeEndDateTime )->setDateTime( mEndDateTime->dateTime() );
515 
516  updateTemporalExtent();
517  saveRangeToProject();
518 }
519 
520 void QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed()
521 {
522  whileBlocking( mStartDateTime )->setDateTime( mFixedRangeStartDateTime->dateTime() );
523  whileBlocking( mEndDateTime )->setDateTime( mFixedRangeEndDateTime->dateTime() );
524 
525  updateTemporalExtent();
526  saveRangeToProject();
527 }
528 
529 void QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered()
530 {
531  setDatesToAllLayers();
532  saveRangeToProject();
533 }
534 
535 void QgsTemporalControllerWidget::setTimeStep( const QgsInterval &timeStep )
536 {
537  if ( ! timeStep.isValid() || timeStep.seconds() <= 0 )
538  return;
539 
540  // Search the time unit the most appropriate :
541  // the one that gives the smallest time step value for double spin box with round value (if possible) and/or the less signifiant digits
542 
543  int selectedUnit = -1;
544  int stringSize = std::numeric_limits<int>::max();
545  int precision = mStepSpinBox->decimals();
546  double selectedValue = std::numeric_limits<double>::max();
547  for ( int i = 0; i < mTimeStepsComboBox->count(); ++i )
548  {
549  QgsUnitTypes::TemporalUnit unit = static_cast<QgsUnitTypes::TemporalUnit>( mTimeStepsComboBox->itemData( i ).toInt() );
550  double value = timeStep.seconds() * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::TemporalSeconds, unit );
551  QString string = QString::number( value, 'f', precision );
552  string.remove( QRegExp( "0+$" ) ); //remove trailing zero
553  string.remove( QRegExp( "[.]+$" ) ); //remove last point if present
554 
555  if ( value >= 1
556  && string.size() <= stringSize // less significant digit than currently selected
557  && value < selectedValue ) // less than currently selected
558  {
559  selectedUnit = i;
560  selectedValue = value;
561  stringSize = string.size();
562  }
563  else if ( string != '0'
564  && string.size() < precision + 2 //round value (ex: 0.xx with precision=3)
565  && string.size() < stringSize ) //less significant digit than currently selected
566  {
567  selectedUnit = i ;
568  selectedValue = value ;
569  stringSize = string.size();
570  }
571  }
572 
573  if ( selectedUnit >= 0 )
574  {
575  mStepSpinBox->setValue( selectedValue );
576  mTimeStepsComboBox->setCurrentIndex( selectedUnit );
577  }
578 
579  updateFrameDuration();
580 }
581 
582 void QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered()
583 {
584  setDatesToProjectTime();
585  saveRangeToProject();
586 }
587 
588 void QgsTemporalControllerWidget::setDates( const QgsDateTimeRange &range )
589 {
590  if ( range.begin().isValid() && range.end().isValid() )
591  {
592  whileBlocking( mStartDateTime )->setDateTime( range.begin() );
593  whileBlocking( mEndDateTime )->setDateTime( range.end() );
594  whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
595  whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
596  updateTemporalExtent();
597  }
598 }
599 
600 void QgsTemporalControllerWidget::setDatesToAllLayers()
601 {
602  QgsDateTimeRange range;
604  setDates( range );
605 }
606 
607 void QgsTemporalControllerWidget::setDatesToProjectTime()
608 {
609  QgsDateTimeRange range;
610 
611  // by default try taking the project's fixed temporal extent
612  if ( QgsProject::instance()->timeSettings() )
614 
615  // if that's not set, calculate the extent from the project's layers
616  if ( !range.begin().isValid() || !range.end().isValid() )
617  {
619  }
620 
621  setDates( range );
622 }
623 
624 void QgsTemporalControllerWidget::saveRangeToProject()
625 {
626  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ),
627  QStringLiteral( "/StartDateTime" ), mStartDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODate ) );
628  QgsProject::instance()->writeEntry( QStringLiteral( "TemporalControllerWidget" ),
629  QStringLiteral( "/EndDateTime" ), mEndDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODate ) );
630 }
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
int precision
long long totalFrameCount() const
Returns the total number of frames for the navigation.
Base class for all map layer types.
Definition: qgsmaplayer.h:83
void layersAdded(const QList< QgsMapLayer *> &layers)
Emitted when one or more layers were added to the registry.
void setFramesPerSecond(double rate)
Sets the project&#39;s default animation frame rate, in frames per second.
void stateChanged(AnimationState state)
Emitted whenever the animation state changes.
static Q_INVOKABLE QString toString(QgsUnitTypes::DistanceUnit unit)
Returns a translated string representing a distance unit.
bool isValid() const
Returns true if the interval is valid.
Definition: qgsinterval.h:181
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
bool isActive() const
Returns true if the temporal property is active.
void pause()
Pauses the temporal navigation.
void keyPressEvent(QKeyEvent *e) override
void updateTemporalRange(const QgsDateTimeRange &range)
Signals that a temporal range has changed and needs to be updated in all connected objects...
void setCurrentFrameNumber(long long frame)
Sets the current animation frame number.
virtual QgsDateTimeRange calculateTemporalExtent(QgsMapLayer *layer) const
Attempts to calculate the overall temporal extent for the specified layer, using the settings defined...
double seconds() const
Returns the interval duration in seconds.
Definition: qgsinterval.h:168
void setTimeStepUnit(QgsUnitTypes::TemporalUnit unit)
Sets the project&#39;s time step (length of one animation frame) unit, which is used as the default value...
void setFrameDuration(QgsInterval duration)
Sets the frame duration, which dictates the temporal length of each frame in the animation.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QgsUnitTypes::TemporalUnit timeStepUnit() const
Returns the project&#39;s time step (length of one animation frame) unit, which is used as the default va...
Stores pointer to the map layer itself.
Base class for any widget that can be shown as a inline panel.
void playForward()
Starts the animation playing in a forward direction up till the end of all frames.
NavigationMode navigationMode() const
Returns the currenttemporal navigation mode.
void navigationModeChanged(NavigationMode mode)
Emitted whenever the navigation mode changes.
QgsDateTimeRange temporalRange() const
Returns the project&#39;s temporal range, which indicates the earliest and latest datetime ranges associa...
QgsTemporalController * temporalController()
Returns the temporal controller object used by this object in navigation.
Temporal navigation relies on frames within a datetime range.
void temporalFrameDurationChanged(const QgsInterval &interval)
Emitted whenever the frameDuration interval of the controller changes.
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
void temporalExtentsChanged(const QgsDateTimeRange &extent)
Emitted whenever the temporalExtent extent changes.
NavigationMode
Represents the current temporal navigation mode.
void setFramesPerSecond(double rate)
Sets the animation frame rate, in frames per second.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer&#39;s temporal properties.
Definition: qgsmaplayer.h:1229
const QgsProjectTimeSettings * timeSettings() const
Returns the project&#39;s time settings, which contains the project&#39;s temporal range and other time based...
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean value to the project file.
void rewindToStart()
Rewinds the temporal navigation to start of the temporal extent.
void setNavigationMode(const NavigationMode mode)
Sets the temporal navigation mode.
void skipToEnd()
Skips the temporal navigation to end of the temporal extent.
static QgsDateTimeRange calculateTemporalRangeForProject(QgsProject *project)
Calculates the temporal range for a project.
The QgsMapLayerModel class is a model to display layers in widgets.
void readProject(const QDomDocument &)
Emitted when a project is being read.
Implements a temporal controller based on a frame by frame navigation and animation.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
void dataSourceChanged()
Emitted whenever the layer&#39;s data source has been changed.
void exportAnimation()
Triggered when an animation should be exported.
A controller base class for temporal objects, contains a signal for notifying updates of the objects ...
void next()
Advances to the next frame.
A representation of the interval between two datetime values.
Definition: qgsinterval.h:40
bool temporalRangeCumulative() const
Returns the animation temporal range cumulative settings.
AnimationState
Represents the current animation state.
QgsInterval firstValidTimeStep() const
Returns the first valid time step of the dataset groups, invalid QgInterval if no time step is presen...
void setTimeStep(double step)
Sets the project&#39;s time step (length of one animation frame), which is used as the default value when...
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:263
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
void cleared()
Emitted when the project is cleared (and additionally when an open project is cleared just before a n...
void setTemporalRangeCumulative(bool state)
Sets the animation temporal range as cumulative.
void previous()
Jumps back to the previous frame.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:469
void setIsTemporalRangeCumulative(bool state)
Sets the project&#39;s temporal range as cumulative in animation settings.
TemporalUnit
Temporal units.
Definition: qgsunittypes.h:149
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:94
long long currentFrameNumber() const
Returns the current frame number.
static Q_INVOKABLE double fromUnitToUnitFactor(QgsUnitTypes::DistanceUnit fromUnit, QgsUnitTypes::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
void setLooping(bool loop)
Sets whether the animation should loop after hitting the end or start frame.
AnimationState animationState() const
Returns the current animation state.
void playBackward()
Starts the animation playing in a reverse direction until the beginning of the time range...
QgsTemporalControllerWidget(QWidget *parent=nullptr)
Constructor for QgsTemporalControllerWidget, with the specified parent widget.
double framesPerSecond() const
Returns the animation frame rate, in frames per second.
void setTemporalExtents(const QgsDateTimeRange &extents)
Sets the navigation temporal extents, which dictate the earliest and latest date time possible in the...
Temporal navigation relies on a fixed datetime range.