diff --git a/python/PyQt6/core/auto_generated/project/qgsprojectelevationproperties.sip.in b/python/PyQt6/core/auto_generated/project/qgsprojectelevationproperties.sip.in index db2bf3583b9..a0ac59d01e4 100644 --- a/python/PyQt6/core/auto_generated/project/qgsprojectelevationproperties.sip.in +++ b/python/PyQt6/core/auto_generated/project/qgsprojectelevationproperties.sip.in @@ -84,20 +84,6 @@ elevation limits associated with the project. .. seealso:: :py:func:`elevationRangeChanged` -.. versionadded:: 3.38 -%End - - void setElevationFilterRangeSize( double size ); -%Docstring -Sets the fixed size for elevation range filtering in the project, used when interactively filtering by elevation. - -Set to -1 if no fixed elevation range size is desired. - -A fixed size forces the selected elevation range to have a matching difference between -the upper and lower elevation. - -.. seealso:: :py:func:`elevationFilterRangeSize` - .. versionadded:: 3.38 %End @@ -112,6 +98,15 @@ the upper and lower elevation. .. seealso:: :py:func:`setElevationFilterRangeSize` +.. versionadded:: 3.38 +%End + + bool invertElevationFilter() const; +%Docstring +Returns ``True`` if the elevation range filter slider should be inverted for this project. + +.. seealso:: :py:func:`setInvertElevationFilter` + .. versionadded:: 3.38 %End @@ -132,6 +127,29 @@ elevation limits associated with the project. .. seealso:: :py:func:`elevationRangeChanged` +.. versionadded:: 3.38 +%End + + void setElevationFilterRangeSize( double size ); +%Docstring +Sets the fixed size for elevation range filtering in the project, used when interactively filtering by elevation. + +Set to -1 if no fixed elevation range size is desired. + +A fixed size forces the selected elevation range to have a matching difference between +the upper and lower elevation. + +.. seealso:: :py:func:`elevationFilterRangeSize` + +.. versionadded:: 3.38 +%End + + void setInvertElevationFilter( bool invert ); +%Docstring +Sets whether the elevation range filter slider should be inverted for this project. + +.. seealso:: :py:func:`invertElevationFilter` + .. versionadded:: 3.38 %End diff --git a/python/PyQt6/gui/auto_generated/elevation/qgselevationcontrollerwidget.sip.in b/python/PyQt6/gui/auto_generated/elevation/qgselevationcontrollerwidget.sip.in index 7e87c2c5ba6..c7f9116d3e2 100644 --- a/python/PyQt6/gui/auto_generated/elevation/qgselevationcontrollerwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/elevation/qgselevationcontrollerwidget.sip.in @@ -114,6 +114,13 @@ the upper and lower elevation. .. seealso:: :py:func:`fixedRangeSize` .. seealso:: :py:func:`fixedRangeSizeChanged` +%End + + void setInverted( bool inverted ); +%Docstring +Sets whether the elevation slider should be inverted. + +.. seealso:: :py:func:`invertedChanged` %End signals: @@ -134,6 +141,13 @@ Emitted when the fixed range size is changed from the widget. .. seealso:: :py:func:`fixedRangeSize` .. seealso:: :py:func:`setFixedRangeSize` +%End + + void invertedChanged( bool inverted ); +%Docstring +Emitted when the elevation filter slider is inverted. + +.. seealso:: :py:func:`setInverted` %End }; diff --git a/python/core/auto_generated/project/qgsprojectelevationproperties.sip.in b/python/core/auto_generated/project/qgsprojectelevationproperties.sip.in index db2bf3583b9..a0ac59d01e4 100644 --- a/python/core/auto_generated/project/qgsprojectelevationproperties.sip.in +++ b/python/core/auto_generated/project/qgsprojectelevationproperties.sip.in @@ -84,20 +84,6 @@ elevation limits associated with the project. .. seealso:: :py:func:`elevationRangeChanged` -.. versionadded:: 3.38 -%End - - void setElevationFilterRangeSize( double size ); -%Docstring -Sets the fixed size for elevation range filtering in the project, used when interactively filtering by elevation. - -Set to -1 if no fixed elevation range size is desired. - -A fixed size forces the selected elevation range to have a matching difference between -the upper and lower elevation. - -.. seealso:: :py:func:`elevationFilterRangeSize` - .. versionadded:: 3.38 %End @@ -112,6 +98,15 @@ the upper and lower elevation. .. seealso:: :py:func:`setElevationFilterRangeSize` +.. versionadded:: 3.38 +%End + + bool invertElevationFilter() const; +%Docstring +Returns ``True`` if the elevation range filter slider should be inverted for this project. + +.. seealso:: :py:func:`setInvertElevationFilter` + .. versionadded:: 3.38 %End @@ -132,6 +127,29 @@ elevation limits associated with the project. .. seealso:: :py:func:`elevationRangeChanged` +.. versionadded:: 3.38 +%End + + void setElevationFilterRangeSize( double size ); +%Docstring +Sets the fixed size for elevation range filtering in the project, used when interactively filtering by elevation. + +Set to -1 if no fixed elevation range size is desired. + +A fixed size forces the selected elevation range to have a matching difference between +the upper and lower elevation. + +.. seealso:: :py:func:`elevationFilterRangeSize` + +.. versionadded:: 3.38 +%End + + void setInvertElevationFilter( bool invert ); +%Docstring +Sets whether the elevation range filter slider should be inverted for this project. + +.. seealso:: :py:func:`invertElevationFilter` + .. versionadded:: 3.38 %End diff --git a/python/gui/auto_generated/elevation/qgselevationcontrollerwidget.sip.in b/python/gui/auto_generated/elevation/qgselevationcontrollerwidget.sip.in index 7e87c2c5ba6..c7f9116d3e2 100644 --- a/python/gui/auto_generated/elevation/qgselevationcontrollerwidget.sip.in +++ b/python/gui/auto_generated/elevation/qgselevationcontrollerwidget.sip.in @@ -114,6 +114,13 @@ the upper and lower elevation. .. seealso:: :py:func:`fixedRangeSize` .. seealso:: :py:func:`fixedRangeSizeChanged` +%End + + void setInverted( bool inverted ); +%Docstring +Sets whether the elevation slider should be inverted. + +.. seealso:: :py:func:`invertedChanged` %End signals: @@ -134,6 +141,13 @@ Emitted when the fixed range size is changed from the widget. .. seealso:: :py:func:`fixedRangeSize` .. seealso:: :py:func:`setFixedRangeSize` +%End + + void invertedChanged( bool inverted ); +%Docstring +Emitted when the elevation filter slider is inverted. + +.. seealso:: :py:func:`setInverted` %End }; diff --git a/src/app/canvas/qgsappcanvasfiltering.cpp b/src/app/canvas/qgsappcanvasfiltering.cpp index 4b5a5ca9366..ab2b39c742a 100644 --- a/src/app/canvas/qgsappcanvasfiltering.cpp +++ b/src/app/canvas/qgsappcanvasfiltering.cpp @@ -68,6 +68,11 @@ void QgsAppCanvasFiltering::setupElevationControllerAction( QAction *action, Qgs { QgsProject::instance()->elevationProperties()->setElevationFilterRangeSize( size ); } ); + controller->setInverted( QgsProject::instance()->elevationProperties()->invertElevationFilter() ); + connect( controller, &QgsElevationControllerWidget::invertedChanged, this, []( bool inverted ) + { + QgsProject::instance()->elevationProperties()->setInvertElevationFilter( inverted ); + } ); } } else diff --git a/src/core/project/qgsprojectelevationproperties.cpp b/src/core/project/qgsprojectelevationproperties.cpp index 0b8aa77fd58..a567f01f935 100644 --- a/src/core/project/qgsprojectelevationproperties.cpp +++ b/src/core/project/qgsprojectelevationproperties.cpp @@ -80,6 +80,8 @@ bool QgsProjectElevationProperties::readXml( const QDomElement &element, const Q if ( !ok ) mElevationFilterRangeSize = -1; + mInvertElevationFilter = element.attribute( QStringLiteral( "FilterInvertSlider" ), QStringLiteral( "0" ) ).toInt(); + emit changed(); emit elevationRangeChanged( mElevationRange ); return true; @@ -106,6 +108,8 @@ QDomElement QgsProjectElevationProperties::writeXml( QDomDocument &document, con { element.setAttribute( QStringLiteral( "FilterRangeSize" ), mElevationFilterRangeSize ); } + element.setAttribute( QStringLiteral( "FilterInvertSlider" ), mInvertElevationFilter ? "1" : "0" ); + return element; } @@ -135,6 +139,15 @@ void QgsProjectElevationProperties::setElevationFilterRangeSize( double size ) emit changed(); } +void QgsProjectElevationProperties::setInvertElevationFilter( bool invert ) +{ + if ( mInvertElevationFilter == invert ) + return; + + mInvertElevationFilter = invert; + emit changed(); +} + void QgsProjectElevationProperties::setElevationRange( const QgsDoubleRange &range ) { if ( mElevationRange == range ) diff --git a/src/core/project/qgsprojectelevationproperties.h b/src/core/project/qgsprojectelevationproperties.h index f3d90bcf08d..2bb6e0ecf00 100644 --- a/src/core/project/qgsprojectelevationproperties.h +++ b/src/core/project/qgsprojectelevationproperties.h @@ -100,20 +100,6 @@ class CORE_EXPORT QgsProjectElevationProperties : public QObject */ QgsDoubleRange elevationRange() const { return mElevationRange; } - /** - * Sets the fixed size for elevation range filtering in the project, used when interactively filtering by elevation. - * - * Set to -1 if no fixed elevation range size is desired. - * - * A fixed size forces the selected elevation range to have a matching difference between - * the upper and lower elevation. - * - * \see elevationFilterRangeSize() - * - * \since QGIS 3.38 - */ - void setElevationFilterRangeSize( double size ); - /** * Returns the fixed size for elevation range filtering in the project, used when interactively filtering by elevation. * @@ -128,6 +114,15 @@ class CORE_EXPORT QgsProjectElevationProperties : public QObject */ double elevationFilterRangeSize() const { return mElevationFilterRangeSize; } + /** + * Returns TRUE if the elevation range filter slider should be inverted for this project. + * + * \see setInvertElevationFilter() + * + * \since QGIS 3.38 + */ + bool invertElevationFilter() const { return mInvertElevationFilter; } + public slots: /** @@ -144,6 +139,29 @@ class CORE_EXPORT QgsProjectElevationProperties : public QObject */ void setElevationRange( const QgsDoubleRange &range ); + /** + * Sets the fixed size for elevation range filtering in the project, used when interactively filtering by elevation. + * + * Set to -1 if no fixed elevation range size is desired. + * + * A fixed size forces the selected elevation range to have a matching difference between + * the upper and lower elevation. + * + * \see elevationFilterRangeSize() + * + * \since QGIS 3.38 + */ + void setElevationFilterRangeSize( double size ); + + /** + * Sets whether the elevation range filter slider should be inverted for this project. + * + * \see invertElevationFilter() + * + * \since QGIS 3.38 + */ + void setInvertElevationFilter( bool invert ); + signals: /** @@ -169,6 +187,7 @@ class CORE_EXPORT QgsProjectElevationProperties : public QObject std::unique_ptr< QgsAbstractTerrainProvider > mTerrainProvider; QgsDoubleRange mElevationRange; double mElevationFilterRangeSize = -1; + bool mInvertElevationFilter = false; }; diff --git a/src/gui/elevation/qgselevationcontrollerwidget.cpp b/src/gui/elevation/qgselevationcontrollerwidget.cpp index 49f2546fe30..0f3b24ba039 100644 --- a/src/gui/elevation/qgselevationcontrollerwidget.cpp +++ b/src/gui/elevation/qgselevationcontrollerwidget.cpp @@ -51,6 +51,9 @@ QgsElevationControllerWidget::QgsElevationControllerWidget( QWidget *parent ) mSettingsAction = new QgsElevationControllerSettingsAction( mMenu ); mMenu->addAction( mSettingsAction ); + mInvertDirectionAction = new QAction( tr( "Invert Direction" ), this ); + mInvertDirectionAction->setCheckable( true ); + mMenu->addAction( mInvertDirectionAction ); mSettingsAction->sizeSpin()->clear(); connect( mSettingsAction->sizeSpin(), qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [this]( double size ) @@ -96,6 +99,14 @@ QgsElevationControllerWidget::QgsElevationControllerWidget( QWidget *parent ) mSliderLabels->setRange( range() ); } ); + connect( mInvertDirectionAction, &QAction::toggled, this, [this]() + { + mSlider->setFlippedDirection( !mInvertDirectionAction->isChecked() ); + mSliderLabels->setInverted( mInvertDirectionAction->isChecked() ); + + emit invertedChanged( mInvertDirectionAction->isChecked() ); + } ); + // default initial value to full range setRange( rangeLimits() ); mSliderLabels->setRange( rangeLimits() ); @@ -230,6 +241,11 @@ void QgsElevationControllerWidget::setFixedRangeSize( double size ) emit fixedRangeSizeChanged( mFixedRangeSize ); } +void QgsElevationControllerWidget::setInverted( bool inverted ) +{ + mInvertDirectionAction->setChecked( inverted ); +} + // // QgsElevationControllerLabels // @@ -269,14 +285,26 @@ void QgsElevationControllerLabels::paintEvent( QPaintEvent * ) const double limitRange = mLimits.upper() - mLimits.lower(); const double lowerFraction = ( mRange.lower() - mLimits.lower() ) / limitRange; const double upperFraction = ( mRange.upper() - mLimits.lower() ) / limitRange; - const int lowerY = std::min( static_cast< int >( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * lowerFraction + fm.ascent() ) ), - rect().bottom() - fm.descent() ); - const int upperY = std::max( static_cast< int >( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * upperFraction - fm.descent() ) ), - rect().top() + fm.ascent() ); + const int lowerY = !mInverted + ? ( std::min( static_cast< int >( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * lowerFraction + fm.ascent() ) ), + rect().bottom() - fm.descent() ) ) + : ( std::max( static_cast< int >( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * lowerFraction - fm.descent() ) ), + rect().top() + fm.ascent() ) ); + const int upperY = !mInverted ? + ( std::max( static_cast< int >( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * upperFraction - fm.descent() ) ), + rect().top() + fm.ascent() ) ) + : ( std::min( static_cast< int >( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * upperFraction + fm.ascent() ) ), + rect().bottom() - fm.descent() ) ); - const bool lowerIsCloseToLimit = lowerY + fm.height() > rect().bottom() - fm.descent(); - const bool upperIsCloseToLimit = upperY - fm.height() < rect().top() + fm.ascent(); - const bool lowerIsCloseToUpperLimit = lowerY - fm.height() < rect().top() + fm.ascent(); + const bool lowerIsCloseToLimit = !mInverted + ? ( lowerY + fm.height() > rect().bottom() - fm.descent() ) + : ( lowerY - fm.height() < rect().top() + fm.ascent() ) ; + const bool upperIsCloseToLimit = !mInverted + ? ( upperY - fm.height() < rect().top() + fm.ascent() ) + : ( upperY + fm.height() > rect().bottom() - fm.descent() ) ; + const bool lowerIsCloseToUpperLimit = !mInverted + ? ( lowerY - fm.height() < rect().top() + fm.ascent() ) + : ( lowerY + fm.height() > rect().bottom() - fm.descent() ); QLocale locale; @@ -293,7 +321,7 @@ void QgsElevationControllerLabels::paintEvent( QPaintEvent * ) f.setBold( true ); path.addText( left, lowerY, f, locale.toString( mRange.lower() ) ); f.setBold( false ); - path.addText( left, rect().bottom() - fm.descent(), f, locale.toString( mLimits.lower() ) ); + path.addText( left, !mInverted ? ( rect().bottom() - fm.descent() ) : ( rect().top() + fm.ascent() ), f, locale.toString( mLimits.lower() ) ); } } @@ -304,7 +332,7 @@ void QgsElevationControllerLabels::paintEvent( QPaintEvent * ) if ( !lowerIsCloseToUpperLimit ) { f.setBold( false ); - path.addText( left, rect().top() + fm.ascent(), f, locale.toString( mLimits.upper() ) ); + path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) ); } } else @@ -319,7 +347,7 @@ void QgsElevationControllerLabels::paintEvent( QPaintEvent * ) f.setBold( true ); path.addText( left, upperY, f, locale.toString( mRange.upper() ) ); f.setBold( false ); - path.addText( left, rect().top() + fm.ascent(), f, locale.toString( mLimits.upper() ) ); + path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) ); } } } @@ -364,6 +392,15 @@ void QgsElevationControllerLabels::setRange( const QgsDoubleRange &range ) update(); } +void QgsElevationControllerLabels::setInverted( bool inverted ) +{ + if ( inverted == mInverted ) + return; + + mInverted = inverted; + update(); +} + // // QgsElevationControllerSettingsAction // diff --git a/src/gui/elevation/qgselevationcontrollerwidget.h b/src/gui/elevation/qgselevationcontrollerwidget.h index a995568cf56..8a0d9f55e94 100644 --- a/src/gui/elevation/qgselevationcontrollerwidget.h +++ b/src/gui/elevation/qgselevationcontrollerwidget.h @@ -43,11 +43,13 @@ class GUI_EXPORT QgsElevationControllerLabels : public QWidget SIP_SKIP void setLimits( const QgsDoubleRange &limits ); void setRange( const QgsDoubleRange &range ); + void setInverted( bool inverted ); private: QgsDoubleRange mLimits; QgsDoubleRange mRange; + bool mInverted = false; }; @@ -153,6 +155,13 @@ class GUI_EXPORT QgsElevationControllerWidget : public QWidget */ void setFixedRangeSize( double size ); + /** + * Sets whether the elevation slider should be inverted. + * + * \see invertedChanged() + */ + void setInverted( bool inverted ); + signals: /** @@ -171,6 +180,13 @@ class GUI_EXPORT QgsElevationControllerWidget : public QWidget */ void fixedRangeSizeChanged( double size ); + /** + * Emitted when the elevation filter slider is inverted. + * + * \see setInverted() + */ + void invertedChanged( bool inverted ); + private: void updateWidgetMask(); @@ -178,6 +194,7 @@ class GUI_EXPORT QgsElevationControllerWidget : public QWidget QToolButton *mConfigureButton = nullptr; QgsElevationControllerSettingsAction *mSettingsAction = nullptr; QMenu *mMenu = nullptr; + QAction *mInvertDirectionAction = nullptr; QgsRangeSlider *mSlider = nullptr; QgsElevationControllerLabels *mSliderLabels = nullptr; QgsDoubleRange mRangeLimits;