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 ) );