Add option to invert the elevation filter widget

If checked, this option reverse the slider in the elevation filter
so that the widget goes from high values at the bottom to low values at the top.

See justification in https://github.com/qgis/QGIS/issues/56995

Fixes #56995
This commit is contained in:
Nyall Dawson 2024-04-23 10:33:55 +10:00
parent 1698bd3391
commit 6ee262f401
9 changed files with 207 additions and 52 deletions

View File

@ -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

View File

@ -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
};

View File

@ -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

View File

@ -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
};

View File

@ -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

View File

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

View File

@ -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;
};

View File

@ -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
//

View File

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