32 #include <QRegularExpression>
41 mStartDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
42 mEndDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
43 mFixedRangeStartDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
44 mFixedRangeEndDateTime->setDateTimeRange( QDateTime( QDate( 1, 1, 1 ), QTime( 0, 0, 0 ) ), mStartDateTime->maximumDateTime() );
46 connect( mForwardButton, &QPushButton::clicked,
this, &QgsTemporalControllerWidget::togglePlayForward );
47 connect( mBackButton, &QPushButton::clicked,
this, &QgsTemporalControllerWidget::togglePlayBackward );
48 connect( mStopButton, &QPushButton::clicked,
this, &QgsTemporalControllerWidget::togglePause );
53 connect( mLoopingCheckBox, &QCheckBox::toggled,
this, [ = ](
bool state ) { mNavigationObject->
setLooping( state ); } );
55 setWidgetStateFromNavigationMode( mNavigationObject->
navigationMode() );
60 if ( mBlockFrameDurationUpdates )
63 mBlockFrameDurationUpdates++;
64 updateTimeStepInputs( timeStep );
65 mBlockFrameDurationUpdates--;
67 connect( mNavigationOff, &QPushButton::clicked,
this, &QgsTemporalControllerWidget::mNavigationOff_clicked );
68 connect( mNavigationFixedRange, &QPushButton::clicked,
this, &QgsTemporalControllerWidget::mNavigationFixedRange_clicked );
69 connect( mNavigationAnimated, &QPushButton::clicked,
this, &QgsTemporalControllerWidget::mNavigationAnimated_clicked );
78 connect( mStartDateTime, &QDateTimeEdit::dateTimeChanged,
this, &QgsTemporalControllerWidget::startEndDateTime_changed );
79 connect( mEndDateTime, &QDateTimeEdit::dateTimeChanged,
this, &QgsTemporalControllerWidget::startEndDateTime_changed );
80 connect( mFixedRangeStartDateTime, &QDateTimeEdit::dateTimeChanged,
this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
81 connect( mFixedRangeEndDateTime, &QDateTimeEdit::dateTimeChanged,
this, &QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed );
82 connect( mStepSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ),
this, &QgsTemporalControllerWidget::updateFrameDuration );
83 connect( mTimeStepsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsTemporalControllerWidget::updateFrameDuration );
84 connect( mSlider, &QSlider::valueChanged,
this, &QgsTemporalControllerWidget::timeSlider_valueChanged );
86 mStepSpinBox->setClearValue( 1 );
90 connect( mSettings, &QPushButton::clicked,
this, &QgsTemporalControllerWidget::settings_clicked );
94 mRangeMenu.reset(
new QMenu(
this ) );
96 mRangeSetToAllLayersAction =
new QAction( tr(
"Set to Full Range" ), mRangeMenu.get() );
98 connect( mRangeSetToAllLayersAction, &QAction::triggered,
this, &QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered );
99 mRangeMenu->addAction( mRangeSetToAllLayersAction );
101 mRangeSetToProjectAction =
new QAction( tr(
"Set to Preset Project Range" ), mRangeMenu.get() );
102 connect( mRangeSetToProjectAction, &QAction::triggered,
this, &QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered );
103 mRangeMenu->addAction( mRangeSetToProjectAction );
105 mRangeMenu->addSeparator();
107 mRangeLayersSubMenu.reset(
new QMenu( tr(
"Set to Single Layer's Range" ), mRangeMenu.get() ) );
108 mRangeLayersSubMenu->setEnabled(
false );
109 mRangeMenu->addMenu( mRangeLayersSubMenu.get() );
110 connect( mRangeMenu.get(), &QMenu::aboutToShow,
this, &QgsTemporalControllerWidget::aboutToShowRangeMenu );
112 mSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
113 mSetRangeButton->setMenu( mRangeMenu.get() );
114 mSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
115 mFixedRangeSetRangeButton->setPopupMode( QToolButton::MenuButtonPopup );
116 mFixedRangeSetRangeButton->setMenu( mRangeMenu.get() );
117 mFixedRangeSetRangeButton->setDefaultAction( mRangeSetToAllLayersAction );
121 QgsDateTimeRange range;
126 if ( range.begin().isValid() && range.end().isValid() )
128 whileBlocking( mStartDateTime )->setDateTime( range.begin() );
130 whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
131 whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
156 mStepSpinBox->setDecimals( 3 );
158 mStepSpinBox->setMinimum( 0.001 );
159 mStepSpinBox->setMaximum( std::numeric_limits<int>::max() );
160 mStepSpinBox->setSingleStep( 1 );
161 mStepSpinBox->setValue( 1 );
163 mForwardButton->setToolTip( tr(
"Play" ) );
164 mBackButton->setToolTip( tr(
"Reverse" ) );
165 mNextButton->setToolTip( tr(
"Go to next frame" ) );
166 mPreviousButton->setToolTip( tr(
"Go to previous frame" ) );
167 mStopButton->setToolTip( tr(
"Pause" ) );
168 mRewindButton->setToolTip( tr(
"Rewind to start" ) );
169 mFastForwardButton->setToolTip( tr(
"Fast forward to end" ) );
171 updateFrameDuration();
185 if ( mSlider->hasFocus() && e->key() == Qt::Key_Space )
189 QWidget::keyPressEvent( e );
192 void QgsTemporalControllerWidget::aboutToShowRangeMenu()
194 QgsDateTimeRange projectRange;
197 mRangeSetToProjectAction->setEnabled( projectRange.begin().isValid() && projectRange.end().isValid() );
199 mRangeLayersSubMenu->clear();
200 for (
int i = 0; i < mMapLayerModel->
rowCount(); ++i )
202 const QModelIndex index = mMapLayerModel->
index( i, 0 );
207 const QIcon icon = qvariant_cast<QIcon>( mMapLayerModel->
data( index, Qt::DecorationRole ) );
208 const QString text = mMapLayerModel->
data( index, Qt::DisplayRole ).toString();
210 if ( range.begin().isValid() && range.end().isValid() )
212 QAction *action =
new QAction( icon, text, mRangeLayersSubMenu.get() );
213 connect( action, &QAction::triggered,
this, [ = ]
216 saveRangeToProject();
218 mRangeLayersSubMenu->addAction( action );
221 mRangeLayersSubMenu->setEnabled( !mRangeLayersSubMenu->actions().isEmpty() );
224 void QgsTemporalControllerWidget::togglePlayForward()
226 mPlayingForward =
true;
230 mStopButton->setChecked(
false );
231 mBackButton->setChecked(
false );
232 mForwardButton->setChecked(
true );
237 mBackButton->setChecked(
true );
238 mForwardButton->setChecked(
false );
239 mNavigationObject->
pause();
243 void QgsTemporalControllerWidget::togglePlayBackward()
245 mPlayingForward =
false;
249 mStopButton->setChecked(
false );
250 mBackButton->setChecked(
true );
251 mForwardButton->setChecked(
false );
256 mBackButton->setChecked(
true );
257 mBackButton->setChecked(
false );
258 mNavigationObject->
pause();
262 void QgsTemporalControllerWidget::togglePause()
266 mStopButton->setChecked(
true );
267 mBackButton->setChecked(
false );
268 mForwardButton->setChecked(
false );
269 mNavigationObject->
pause();
273 mBackButton->setChecked( mPlayingForward ?
false :
true );
274 mForwardButton->setChecked( mPlayingForward ?
false :
true );
275 if ( mPlayingForward )
286 void QgsTemporalControllerWidget::updateTemporalExtent()
290 const QDateTime start = mStartDateTime->dateTime();
291 const QDateTime end = mEndDateTime->dateTime();
292 const bool isTimeInstant = start == end;
293 const QgsDateTimeRange temporalExtent = QgsDateTimeRange( start, end,
300 void QgsTemporalControllerWidget::updateFrameDuration()
302 if ( mBlockSettingUpdates )
310 if ( !mBlockFrameDurationUpdates )
322 mStepSpinBox->setEnabled(
false );
323 mStepSpinBox->setValue( 1 );
324 mSlider->setTickInterval( 1 );
325 mSlider->setTickPosition( QSlider::TicksBothSides );
329 mStepSpinBox->setEnabled(
true );
330 mSlider->setTickInterval( 0 );
331 mSlider->setTickPosition( QSlider::NoTicks );
335 void QgsTemporalControllerWidget::setWidgetStateFromProject()
337 mBlockSettingUpdates++;
340 mBlockSettingUpdates--;
344 QStringLiteral(
"/NavigationMode" ), 0, &ok ) );
348 setWidgetStateFromNavigationMode( mode );
356 const QString startString =
QgsProject::instance()->
readEntry( QStringLiteral(
"TemporalControllerWidget" ), QStringLiteral(
"/StartDateTime" ) );
357 const QString endString =
QgsProject::instance()->
readEntry( QStringLiteral(
"TemporalControllerWidget" ), QStringLiteral(
"/EndDateTime" ) );
358 if ( !startString.isEmpty() && !endString.isEmpty() )
360 whileBlocking( mStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODateWithMs ) );
361 whileBlocking( mEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODateWithMs ) );
362 whileBlocking( mFixedRangeStartDateTime )->setDateTime( QDateTime::fromString( startString, Qt::ISODateWithMs ) );
363 whileBlocking( mFixedRangeEndDateTime )->setDateTime( QDateTime::fromString( endString, Qt::ISODateWithMs ) );
367 setDatesToProjectTime();
369 updateTemporalExtent();
370 updateFrameDuration();
376 void QgsTemporalControllerWidget::mNavigationOff_clicked()
385 void QgsTemporalControllerWidget::mNavigationFixedRange_clicked()
394 void QgsTemporalControllerWidget::mNavigationAnimated_clicked()
412 mNavigationModeStackedWidget->setCurrentIndex( 0 );
415 mNavigationModeStackedWidget->setCurrentIndex( 1 );
418 mNavigationModeStackedWidget->setCurrentIndex( 2 );
423 void QgsTemporalControllerWidget::onLayersAdded(
const QList<QgsMapLayer *> &layers )
425 if ( !mHasTemporalLayersLoaded )
429 if ( layer->temporalProperties() )
431 mHasTemporalLayersLoaded |= layer->temporalProperties()->isActive();
433 if ( !mHasTemporalLayersLoaded )
437 if ( layer->isValid() && layer->temporalProperties()->isActive() && !mHasTemporalLayersLoaded )
439 mHasTemporalLayersLoaded = true;
440 firstTemporalLayerLoaded( layer );
441 mNavigationObject->setAvailableTemporalRanges( QgsTemporalUtils::usedTemporalRangesForProject( QgsProject::instance() ) );
446 firstTemporalLayerLoaded( layer );
454 void QgsTemporalControllerWidget::firstTemporalLayerLoaded(
QgsMapLayer *layer )
456 setDatesToProjectTime();
458 if (
QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer ) )
460 mBlockFrameDurationUpdates++;
461 setTimeStep( meshLayer->firstValidTimeStep() );
462 mBlockFrameDurationUpdates--;
463 updateFrameDuration();
465 else if (
QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer ) )
467 if ( rasterLayer->dataProvider() && rasterLayer->dataProvider()->temporalCapabilities() )
469 mBlockFrameDurationUpdates++;
470 setTimeStep( rasterLayer->dataProvider()->temporalCapabilities()->defaultInterval() );
471 mBlockFrameDurationUpdates--;
472 updateFrameDuration();
477 void QgsTemporalControllerWidget::onProjectCleared()
479 mHasTemporalLayersLoaded =
false;
486 const QTime startOfCurrentHour = QTime( QTime::currentTime().hour(), 0, 0 );
487 const QDateTime end = QDateTime( QDate::currentDate(), startOfCurrentHour, Qt::UTC );
488 const QDateTime start = end.addSecs( -24 * 60 * 60 );
492 whileBlocking( mFixedRangeStartDateTime )->setDateTime( start );
495 updateTemporalExtent();
497 mStepSpinBox->setValue( 1 );
500 void QgsTemporalControllerWidget::updateSlider(
const QgsDateTimeRange &range )
503 updateRangeLabel( range );
506 void QgsTemporalControllerWidget::updateRangeLabel(
const QgsDateTimeRange &range )
508 QString timeFrameFormat = QStringLiteral(
"yyyy-MM-dd HH:mm:ss" );
511 timeFrameFormat = QStringLiteral(
"yyyy-MM-dd HH:mm:ss.zzz" );
515 mCurrentRangeLabel->setText( tr(
"Current frame: %1 ≤ <i>t</i> < %2" ).arg(
516 range.begin().toString( timeFrameFormat ),
517 range.end().toString( timeFrameFormat ) ) );
520 mCurrentRangeLabel->setText( tr(
"Range: %1 ≤ <i>t</i> < %2" ).arg(
521 range.begin().toString( timeFrameFormat ),
522 range.end().toString( timeFrameFormat ) ) );
525 mCurrentRangeLabel->setText( tr(
"Temporal navigation disabled" ) );
532 return mNavigationObject;
535 void QgsTemporalControllerWidget::settings_clicked()
537 QgsTemporalMapSettingsWidget *settingsWidget =
new QgsTemporalMapSettingsWidget(
this );
538 settingsWidget->setFrameRateValue( mNavigationObject->
framesPerSecond() );
541 connect( settingsWidget, &QgsTemporalMapSettingsWidget::frameRateChanged,
this, [ = ](
double rate )
548 connect( settingsWidget, &QgsTemporalMapSettingsWidget::temporalRangeCumulativeChanged,
this, [ = ](
bool state )
557 void QgsTemporalControllerWidget::timeSlider_valueChanged(
int value )
562 void QgsTemporalControllerWidget::startEndDateTime_changed()
564 whileBlocking( mFixedRangeStartDateTime )->setDateTime( mStartDateTime->dateTime() );
565 whileBlocking( mFixedRangeEndDateTime )->setDateTime( mEndDateTime->dateTime() );
567 updateTemporalExtent();
568 saveRangeToProject();
571 void QgsTemporalControllerWidget::fixedRangeStartEndDateTime_changed()
573 whileBlocking( mStartDateTime )->setDateTime( mFixedRangeStartDateTime->dateTime() );
574 whileBlocking( mEndDateTime )->setDateTime( mFixedRangeEndDateTime->dateTime() );
576 updateTemporalExtent();
577 saveRangeToProject();
580 void QgsTemporalControllerWidget::mRangeSetToAllLayersAction_triggered()
582 setDatesToAllLayers();
583 saveRangeToProject();
586 void QgsTemporalControllerWidget::setTimeStep(
const QgsInterval &timeStep )
591 int selectedUnit = -1;
592 double selectedValue = std::numeric_limits<double>::max();
598 int stringSize = std::numeric_limits<int>::max();
599 const int precision = mStepSpinBox->decimals();
600 for (
int i = 0; i < mTimeStepsComboBox->count(); ++i )
604 QString
string = QString::number( value,
'f',
precision );
606 const thread_local QRegularExpression trailingZeroRegEx = QRegularExpression( QStringLiteral(
"0+$" ) );
608 string.remove( trailingZeroRegEx );
610 const thread_local QRegularExpression trailingPointRegEx = QRegularExpression( QStringLiteral(
"[.]+$" ) );
612 string.remove( trailingPointRegEx );
615 &&
string.size() <= stringSize
616 && value < selectedValue )
619 selectedValue = value;
620 stringSize =
string.size();
622 else if (
string !=
'0'
624 &&
string.size() < stringSize )
627 selectedValue = value ;
628 stringSize =
string.size();
634 selectedUnit = mTimeStepsComboBox->findData(
static_cast< int >( timeStep.
originalUnit() ) );
638 if ( selectedUnit >= 0 )
640 mStepSpinBox->setValue( selectedValue );
641 mTimeStepsComboBox->setCurrentIndex( selectedUnit );
644 updateFrameDuration();
647 void QgsTemporalControllerWidget::updateTimeStepInputs(
const QgsInterval &timeStep )
652 QString timeDisplayFormat = QStringLiteral(
"yyyy-MM-dd HH:mm:ss" );
655 timeDisplayFormat = QStringLiteral(
"yyyy-MM-dd HH:mm:ss.zzz" );
657 updateTemporalExtent();
659 mStartDateTime->setDisplayFormat( timeDisplayFormat );
660 mEndDateTime->setDisplayFormat( timeDisplayFormat );
661 mFixedRangeStartDateTime->setDisplayFormat( timeDisplayFormat );
662 mFixedRangeEndDateTime->setDisplayFormat( timeDisplayFormat );
665 if ( timeStep ==
QgsInterval( mStepSpinBox->value(),
672 mTimeStepsComboBox->setCurrentIndex( timeStep.
originalUnit() );
675 updateFrameDuration();
678 void QgsTemporalControllerWidget::mRangeSetToProjectAction_triggered()
680 setDatesToProjectTime();
681 saveRangeToProject();
684 void QgsTemporalControllerWidget::setDates(
const QgsDateTimeRange &range )
686 if ( range.begin().isValid() && range.end().isValid() )
688 whileBlocking( mStartDateTime )->setDateTime( range.begin() );
690 whileBlocking( mFixedRangeStartDateTime )->setDateTime( range.begin() );
691 whileBlocking( mFixedRangeEndDateTime )->setDateTime( range.end() );
692 updateTemporalExtent();
696 void QgsTemporalControllerWidget::setDatesToAllLayers()
698 QgsDateTimeRange range;
705 void QgsTemporalControllerWidget::setDatesToProjectTime()
707 QgsDateTimeRange range;
714 if ( !range.begin().isValid() || !range.end().isValid() )
724 void QgsTemporalControllerWidget::saveRangeToProject()
727 QStringLiteral(
"/StartDateTime" ), mStartDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODateWithMs ) );
729 QStringLiteral(
"/EndDateTime" ), mEndDateTime->dateTime().toTimeSpec( Qt::OffsetFromUTC ).toString( Qt::ISODateWithMs ) );