32#include <QPainterPath>
37#include "moc_qgselevationcontrollerwidget.cpp"
39using namespace Qt::StringLiterals;
44 QVBoxLayout *vl =
new QVBoxLayout();
45 vl->setContentsMargins( 0, 0, 0, 0 );
47 mConfigureButton =
new QToolButton();
48 mConfigureButton->setPopupMode( QToolButton::InstantPopup );
50 QHBoxLayout *hl =
new QHBoxLayout();
51 hl->setContentsMargins( 0, 0, 0, 0 );
52 hl->addWidget( mConfigureButton );
55 mMenu =
new QMenu(
this );
56 mConfigureButton->setMenu( mMenu );
58 mSettingsAction =
new QgsElevationControllerSettingsAction( mMenu );
59 mMenu->addAction( mSettingsAction );
60 mInvertDirectionAction =
new QAction( tr(
"Invert Direction" ),
this );
61 mInvertDirectionAction->setCheckable(
true );
62 mMenu->addAction( mInvertDirectionAction );
64 mSettingsAction->sizeSpin()->clear();
65 connect( mSettingsAction->sizeSpin(), qOverload<double>( &QgsDoubleSpinBox::valueChanged ),
this, [
this](
double size ) { setFixedRangeSize( size < 0 ? -1 : size ); } );
67 mMenu->addSeparator();
70 mSlider->setFlippedDirection(
true );
71 mSlider->setRangeLimits( 0, 100000 );
72 mSliderLabels =
new QgsElevationControllerLabels();
74 QHBoxLayout *hlSlider =
new QHBoxLayout();
75 hlSlider->setContentsMargins( 0, 0, 0, 0 );
76 hlSlider->setSpacing( 2 );
77 hlSlider->addWidget( mSlider );
78 hlSlider->addWidget( mSliderLabels, 1 );
79 hlSlider->addStretch();
80 vl->addLayout( hlSlider );
82 setCursor( Qt::ArrowCursor );
91 if ( !
range.isInfinite() )
96 if ( mBlockSliderChanges )
100 mSliderLabels->setRange(
range() );
103 connect( mInvertDirectionAction, &QAction::toggled,
this, [
this]() {
104 mSlider->setFlippedDirection( !mInvertDirectionAction->isChecked() );
105 mSliderLabels->setInverted( mInvertDirectionAction->isChecked() );
117 QWidget::resizeEvent( event );
124 const int snappedLower =
static_cast<int>( std::floor( mCurrentRange.lower() * mSliderPrecision ) );
125 const int snappedUpper =
static_cast<int>( std::ceil( mCurrentRange.upper() * mSliderPrecision ) );
126 if ( snappedLower == mSlider->lowerValue() && snappedUpper == mSlider->upperValue() )
127 return mCurrentRange;
129 const QgsDoubleRange sliderRange( mSlider->lowerValue() / mSliderPrecision, mSlider->upperValue() / mSliderPrecision );
130 if ( mFixedRangeSize >= 0 )
134 if ( sliderRange.
upper() + mFixedRangeSize <= mRangeLimits.upper() )
162 if (
range == mCurrentRange )
165 mCurrentRange =
range;
166 mBlockSliderChanges =
true;
167 mSlider->setRange(
static_cast<int>( std::floor(
range.lower() * mSliderPrecision ) ),
static_cast<int>( std::ceil(
range.upper() * mSliderPrecision ) ) );
168 mBlockSliderChanges =
false;
171 mSliderLabels->setRange( mCurrentRange );
179 mRangeLimits = limits;
181 const double limitRange = limits.
upper() - limits.
lower();
184 mSliderPrecision = std::max( 1000, mSlider->height() ) / limitRange;
186 mBlockSliderChanges =
true;
187 mSlider->setRangeLimits(
static_cast<int>( std::floor( limits.
lower() * mSliderPrecision ) ),
static_cast<int>( std::ceil( limits.
upper() * mSliderPrecision ) ) );
190 const double newCurrentLower = std::max( mCurrentRange.lower(), limits.
lower() );
191 const double newCurrentUpper = std::min( mCurrentRange.upper(), limits.
upper() );
192 const bool rangeHasChanged = newCurrentLower != mCurrentRange.lower() || newCurrentUpper != mCurrentRange.upper();
194 mSlider->setRange(
static_cast<int>( std::floor( newCurrentLower * mSliderPrecision ) ),
static_cast<int>( std::ceil( newCurrentUpper * mSliderPrecision ) ) );
195 mCurrentRange =
QgsDoubleRange( newCurrentLower, newCurrentUpper );
196 mBlockSliderChanges =
false;
197 if ( rangeHasChanged )
200 mSliderLabels->setLimits( mRangeLimits );
203void QgsElevationControllerWidget::updateWidgetMask()
211 QRegion reg( frameGeometry() );
212 reg -= QRegion( geometry() );
213 reg += childrenRegion();
219 return mFixedRangeSize;
224 if ( size == mFixedRangeSize )
227 mFixedRangeSize = size;
228 if ( mFixedRangeSize < 0 )
230 mSlider->setFixedRangeSize( -1 );
234 mSlider->setFixedRangeSize(
static_cast<int>( std::round( mFixedRangeSize * mSliderPrecision ) ) );
236 if ( mFixedRangeSize != mSettingsAction->sizeSpin()->value() )
237 mSettingsAction->sizeSpin()->setValue( mFixedRangeSize );
243 mInvertDirectionAction->setChecked( inverted );
248 mSliderLabels->setSignificantElevations( elevations );
255QgsElevationControllerLabels::QgsElevationControllerLabels( QWidget *parent )
259 QFont smallerFont = font();
260 int fontSize = smallerFont.pointSize();
262 fontSize = std::max( fontSize - 1, 8 );
264 fontSize = std::max( fontSize - 2, 7 );
266 smallerFont.setPointSize( fontSize );
267 setFont( smallerFont );
269 const QFontMetrics fm( smallerFont );
270 setMinimumWidth( fm.horizontalAdvance(
'0' ) * 5 );
271 setAttribute( Qt::WA_TransparentForMouseEvents );
274void QgsElevationControllerLabels::paintEvent( QPaintEvent * )
276 QStyleOptionSlider styleOption;
277 styleOption.initFrom(
this );
279 const QRect sliderRect = style()->subControlRect( QStyle::CC_Slider, &styleOption, QStyle::SC_SliderHandle,
this );
280 const int sliderHeight = sliderRect.height();
283 const QFontMetrics fm( f );
285 const int left = rect().left() + 2;
287 const double limitRange = mLimits.upper() - mLimits.lower();
288 const double lowerFraction = ( mRange.lower() - mLimits.lower() ) / limitRange;
289 const double upperFraction = ( mRange.upper() - mLimits.lower() ) / limitRange;
292 ? ( std::min(
static_cast<int>( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * lowerFraction + fm.ascent() ) ), rect().bottom() - fm.descent() ) )
293 : ( std::max( static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * lowerFraction - fm.descent() ) ), rect().top() + fm.ascent() ) );
296 ? ( std::max(
static_cast<int>( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * upperFraction - fm.descent() ) ), rect().top() + fm.ascent() ) )
297 : ( std::min(
static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * upperFraction + fm.ascent() ) ), rect().bottom() - fm.descent() ) );
299 const bool lowerIsCloseToLimit = !mInverted ? ( lowerY + fm.height() > rect().bottom() - fm.descent() ) : ( lowerY - fm.height() < rect().top() + fm.ascent() );
300 const bool upperIsCloseToLimit = !mInverted ? ( upperY - fm.height() < rect().top() + fm.ascent() ) : ( upperY + fm.height() > rect().bottom() - fm.descent() );
301 const bool lowerIsCloseToUpperLimit = !mInverted ? ( lowerY - fm.height() < rect().top() + fm.ascent() ) : ( lowerY + fm.height() > rect().bottom() - fm.descent() );
307 for (
double value : std::as_const( mSignificantElevations ) )
309 const double valueFraction = ( value - mLimits.lower() ) / limitRange;
310 const double verticalCenter
312 ? ( std::min(
static_cast<int>( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * valueFraction + fm.capHeight() * 0.5 ) ), rect().bottom() - fm.descent() ) )
313 : ( std::max( static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * valueFraction + fm.capHeight() * 0.5 ) ), rect().top() + fm.ascent() ) );
315 const bool valueIsCloseToLower = verticalCenter + fm.height() > lowerY && verticalCenter - fm.height() < lowerY;
316 if ( valueIsCloseToLower )
319 const bool valueIsCloseToUpper = verticalCenter + fm.height() > upperY && verticalCenter - fm.height() < upperY;
320 if ( valueIsCloseToUpper )
323 const bool valueIsCloseToLowerLimit = !mInverted ? ( verticalCenter + fm.height() > rect().bottom() - fm.descent() ) : ( verticalCenter - fm.height() < rect().top() + fm.ascent() );
324 if ( valueIsCloseToLowerLimit )
327 const bool valueIsCloseToUpperLimit = !mInverted ? ( verticalCenter - fm.height() < rect().top() + fm.ascent() ) : ( verticalCenter + fm.height() > rect().bottom() - fm.descent() );
328 if ( valueIsCloseToUpperLimit )
331 path.addText( left, verticalCenter, f, locale.toString( value ) );
334 if ( mLimits.lower() > std::numeric_limits<double>::lowest() )
336 if ( lowerIsCloseToLimit )
339 path.addText( left, lowerY, f, locale.toString( mRange.lower() ) );
344 path.addText( left, lowerY, f, locale.toString( mRange.lower() ) );
346 path.addText( left, !mInverted ? ( rect().bottom() - fm.descent() ) : ( rect().top() + fm.ascent() ), f, locale.toString( mLimits.lower() ) );
350 if ( mLimits.upper() < std::numeric_limits<double>::max() )
352 if ( qgsDoubleNear( mRange.upper(), mRange.lower() ) )
354 if ( !lowerIsCloseToUpperLimit )
357 path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) );
362 if ( upperIsCloseToLimit )
365 path.addText( left, upperY, f, locale.toString( mRange.upper() ) );
370 path.addText( left, upperY, f, locale.toString( mRange.upper() ) );
372 path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) );
378 p.setRenderHint( QPainter::Antialiasing,
true );
379 const QColor bufferColor = palette().color( QPalette::Window );
380 const QColor textColor = palette().color( QPalette::WindowText );
381 QPen pen( bufferColor );
382 pen.setJoinStyle( Qt::RoundJoin );
383 pen.setCapStyle( Qt::RoundCap );
386 p.setBrush( Qt::NoBrush );
388 p.setPen( Qt::NoPen );
389 p.setBrush( QBrush( textColor ) );
394void QgsElevationControllerLabels::setLimits(
const QgsDoubleRange &limits )
396 if ( limits == mLimits )
399 const QFontMetrics fm( font() );
400 const int maxChars = std::max( QLocale().toString( std::floor( limits.
lower() ) ).length(), QLocale().toString( std::floor( limits.
upper() ) ).length() ) + 3;
401 setMinimumWidth( fm.horizontalAdvance(
'0' ) * maxChars );
407void QgsElevationControllerLabels::setRange(
const QgsDoubleRange &range )
409 if ( range == mRange )
416void QgsElevationControllerLabels::setInverted(
bool inverted )
418 if ( inverted == mInverted )
421 mInverted = inverted;
425void QgsElevationControllerLabels::setSignificantElevations(
const QList<double> &elevations )
427 if ( elevations == mSignificantElevations )
430 mSignificantElevations = elevations;
438QgsElevationControllerSettingsAction::QgsElevationControllerSettingsAction( QWidget *parent )
439 : QWidgetAction( parent )
441 QGridLayout *gLayout =
new QGridLayout();
442 gLayout->setContentsMargins( 3, 2, 3, 2 );
444 QLabel *label =
new QLabel( tr(
"Fixed Range Size" ) );
445 gLayout->addWidget( label, 0, 0 );
447 mSizeSpin =
new QgsDoubleSpinBox();
448 mSizeSpin->setDecimals( 4 );
449 mSizeSpin->setMinimum( -1.0 );
450 mSizeSpin->setMaximum( 999999999.0 );
451 mSizeSpin->setClearValue( -1, tr(
"Not set" ) );
452 mSizeSpin->setKeyboardTracking(
false );
453 mSizeSpin->setToolTip( tr(
"Limit elevation range to a fixed size" ) );
455 gLayout->addWidget( mSizeSpin, 0, 1 );
457 QWidget *w =
new QWidget();
458 w->setLayout( gLayout );
459 setDefaultWidget( w );
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
QgsRange which stores a range of double values.
bool isInfinite() const
Returns true if the range consists of all possible values.
The QgsSpinBox is a spin box with a clear button that will set the value to the defined clear value.
QgsDoubleRange elevationRange() const
Returns the project's elevation range, which indicates the upper and lower elevation limits associate...
void elevationRangeChanged(const QgsDoubleRange &range)
Emitted when the project's elevation is changed.
static QgsProject * instance()
Returns the QgsProject singleton instance.
const QgsProjectElevationProperties * elevationProperties() const
Returns the project's elevation properties, which contains the project's elevation related settings.
A slider control with two interactive endpoints, for interactive selection of a range of values.
void rangeChanged(int minimum, int maximum)
Emitted when the range selected in the widget is changed.
T lower() const
Returns the lower bound of the range.
T upper() const
Returns the upper bound of the range.