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 ) {
66 setFixedRangeSize( size < 0 ? -1 : size );
69 mMenu->addSeparator();
72 mSlider->setFlippedDirection(
true );
73 mSlider->setRangeLimits( 0, 100000 );
74 mSliderLabels =
new QgsElevationControllerLabels();
76 QHBoxLayout *hlSlider =
new QHBoxLayout();
77 hlSlider->setContentsMargins( 0, 0, 0, 0 );
78 hlSlider->setSpacing( 2 );
79 hlSlider->addWidget( mSlider );
80 hlSlider->addWidget( mSliderLabels, 1 );
81 hlSlider->addStretch();
82 vl->addLayout( hlSlider );
84 setCursor( Qt::ArrowCursor );
93 if ( !
range.isInfinite() )
98 if ( mBlockSliderChanges )
102 mSliderLabels->setRange(
range() );
105 connect( mInvertDirectionAction, &QAction::toggled,
this, [
this]() {
106 mSlider->setFlippedDirection( !mInvertDirectionAction->isChecked() );
107 mSliderLabels->setInverted( mInvertDirectionAction->isChecked() );
119 QWidget::resizeEvent( event );
126 const int snappedLower =
static_cast<int>( std::floor( mCurrentRange.lower() * mSliderPrecision ) );
127 const int snappedUpper =
static_cast<int>( std::ceil( mCurrentRange.upper() * mSliderPrecision ) );
128 if ( snappedLower == mSlider->lowerValue() && snappedUpper == mSlider->upperValue() )
129 return mCurrentRange;
131 const QgsDoubleRange sliderRange( mSlider->lowerValue() / mSliderPrecision, mSlider->upperValue() / mSliderPrecision );
132 if ( mFixedRangeSize >= 0 )
136 if ( sliderRange.
upper() + mFixedRangeSize <= mRangeLimits.upper() )
164 if (
range == mCurrentRange )
167 mCurrentRange =
range;
168 mBlockSliderChanges =
true;
169 mSlider->setRange(
static_cast<int>( std::floor(
range.lower() * mSliderPrecision ) ),
static_cast<int>( std::ceil(
range.upper() * mSliderPrecision ) ) );
170 mBlockSliderChanges =
false;
173 mSliderLabels->setRange( mCurrentRange );
181 mRangeLimits = limits;
183 const double limitRange = limits.
upper() - limits.
lower();
186 mSliderPrecision = std::max( 1000, mSlider->height() ) / limitRange;
188 mBlockSliderChanges =
true;
189 mSlider->setRangeLimits(
static_cast<int>( std::floor( limits.
lower() * mSliderPrecision ) ),
static_cast<int>( std::ceil( limits.
upper() * mSliderPrecision ) ) );
192 const double newCurrentLower = std::max( mCurrentRange.lower(), limits.
lower() );
193 const double newCurrentUpper = std::min( mCurrentRange.upper(), limits.
upper() );
194 const bool rangeHasChanged = newCurrentLower != mCurrentRange.lower() || newCurrentUpper != mCurrentRange.upper();
196 mSlider->setRange(
static_cast<int>( std::floor( newCurrentLower * mSliderPrecision ) ),
static_cast<int>( std::ceil( newCurrentUpper * mSliderPrecision ) ) );
197 mCurrentRange =
QgsDoubleRange( newCurrentLower, newCurrentUpper );
198 mBlockSliderChanges =
false;
199 if ( rangeHasChanged )
202 mSliderLabels->setLimits( mRangeLimits );
205void QgsElevationControllerWidget::updateWidgetMask()
213 QRegion reg( frameGeometry() );
214 reg -= QRegion( geometry() );
215 reg += childrenRegion();
221 return mFixedRangeSize;
226 if ( size == mFixedRangeSize )
229 mFixedRangeSize = size;
230 if ( mFixedRangeSize < 0 )
232 mSlider->setFixedRangeSize( -1 );
236 mSlider->setFixedRangeSize(
static_cast<int>( std::round( mFixedRangeSize * mSliderPrecision ) ) );
238 if ( mFixedRangeSize != mSettingsAction->sizeSpin()->value() )
239 mSettingsAction->sizeSpin()->setValue( mFixedRangeSize );
245 mInvertDirectionAction->setChecked( inverted );
250 mSliderLabels->setSignificantElevations( elevations );
257QgsElevationControllerLabels::QgsElevationControllerLabels( QWidget *parent )
261 QFont smallerFont = font();
262 int fontSize = smallerFont.pointSize();
264 fontSize = std::max( fontSize - 1, 8 );
266 fontSize = std::max( fontSize - 2, 7 );
268 smallerFont.setPointSize( fontSize );
269 setFont( smallerFont );
271 const QFontMetrics fm( smallerFont );
272 setMinimumWidth( fm.horizontalAdvance(
'0' ) * 5 );
273 setAttribute( Qt::WA_TransparentForMouseEvents );
276void QgsElevationControllerLabels::paintEvent( QPaintEvent * )
278 QStyleOptionSlider styleOption;
279 styleOption.initFrom(
this );
281 const QRect sliderRect = style()->subControlRect( QStyle::CC_Slider, &styleOption, QStyle::SC_SliderHandle,
this );
282 const int sliderHeight = sliderRect.height();
285 const QFontMetrics fm( f );
287 const int left = rect().left() + 2;
289 const double limitRange = mLimits.upper() - mLimits.lower();
290 const double lowerFraction = ( mRange.lower() - mLimits.lower() ) / limitRange;
291 const double upperFraction = ( mRange.upper() - mLimits.lower() ) / limitRange;
292 const int lowerY = !mInverted
293 ? ( std::min(
static_cast<int>( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * lowerFraction + fm.ascent() ) ), rect().bottom() - fm.descent() ) )
294 : ( std::max( static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * lowerFraction - fm.descent() ) ), rect().top() + fm.ascent() ) );
295 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() ) )
296 : ( std::min(
static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * upperFraction + fm.ascent() ) ), rect().bottom() - fm.descent() ) );
298 const bool lowerIsCloseToLimit = !mInverted
299 ? ( lowerY + fm.height() > rect().bottom() - fm.descent() )
300 : ( lowerY - fm.height() < rect().top() + fm.ascent() );
301 const bool upperIsCloseToLimit = !mInverted
302 ? ( upperY - fm.height() < rect().top() + fm.ascent() )
303 : ( upperY + fm.height() > rect().bottom() - fm.descent() );
304 const bool lowerIsCloseToUpperLimit = !mInverted
305 ? ( lowerY - fm.height() < rect().top() + fm.ascent() )
306 : ( lowerY + fm.height() > rect().bottom() - fm.descent() );
312 for (
double value : std::as_const( mSignificantElevations ) )
314 const double valueFraction = ( value - mLimits.lower() ) / limitRange;
315 const double verticalCenter = !mInverted
316 ? ( std::min(
static_cast<int>( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * valueFraction + fm.capHeight() * 0.5 ) ), rect().bottom() - fm.descent() ) )
317 : ( std::max( static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * valueFraction + fm.capHeight() * 0.5 ) ), rect().top() + fm.ascent() ) );
319 const bool valueIsCloseToLower = verticalCenter + fm.height() > lowerY && verticalCenter - fm.height() < lowerY;
320 if ( valueIsCloseToLower )
323 const bool valueIsCloseToUpper = verticalCenter + fm.height() > upperY && verticalCenter - fm.height() < upperY;
324 if ( valueIsCloseToUpper )
327 const bool valueIsCloseToLowerLimit = !mInverted
328 ? ( verticalCenter + fm.height() > rect().bottom() - fm.descent() )
329 : ( verticalCenter - fm.height() < rect().top() + fm.ascent() );
330 if ( valueIsCloseToLowerLimit )
333 const bool valueIsCloseToUpperLimit = !mInverted
334 ? ( verticalCenter - fm.height() < rect().top() + fm.ascent() )
335 : ( verticalCenter + fm.height() > rect().bottom() - fm.descent() );
336 if ( valueIsCloseToUpperLimit )
339 path.addText( left, verticalCenter, f, locale.toString( value ) );
342 if ( mLimits.lower() > std::numeric_limits<double>::lowest() )
344 if ( lowerIsCloseToLimit )
347 path.addText( left, lowerY, f, locale.toString( mRange.lower() ) );
352 path.addText( left, lowerY, f, locale.toString( mRange.lower() ) );
354 path.addText( left, !mInverted ? ( rect().bottom() - fm.descent() ) : ( rect().top() + fm.ascent() ), f, locale.toString( mLimits.lower() ) );
358 if ( mLimits.upper() < std::numeric_limits<double>::max() )
360 if ( qgsDoubleNear( mRange.upper(), mRange.lower() ) )
362 if ( !lowerIsCloseToUpperLimit )
365 path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) );
370 if ( upperIsCloseToLimit )
373 path.addText( left, upperY, f, locale.toString( mRange.upper() ) );
378 path.addText( left, upperY, f, locale.toString( mRange.upper() ) );
380 path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) );
386 p.setRenderHint( QPainter::Antialiasing,
true );
387 const QColor bufferColor = palette().color( QPalette::Window );
388 const QColor textColor = palette().color( QPalette::WindowText );
389 QPen pen( bufferColor );
390 pen.setJoinStyle( Qt::RoundJoin );
391 pen.setCapStyle( Qt::RoundCap );
394 p.setBrush( Qt::NoBrush );
396 p.setPen( Qt::NoPen );
397 p.setBrush( QBrush( textColor ) );
402void QgsElevationControllerLabels::setLimits(
const QgsDoubleRange &limits )
404 if ( limits == mLimits )
407 const QFontMetrics fm( font() );
408 const int maxChars = std::max( QLocale().toString( std::floor( limits.
lower() ) ).length(), QLocale().toString( std::floor( limits.
upper() ) ).length() ) + 3;
409 setMinimumWidth( fm.horizontalAdvance(
'0' ) * maxChars );
415void QgsElevationControllerLabels::setRange(
const QgsDoubleRange &range )
417 if ( range == mRange )
424void QgsElevationControllerLabels::setInverted(
bool inverted )
426 if ( inverted == mInverted )
429 mInverted = inverted;
433void QgsElevationControllerLabels::setSignificantElevations(
const QList<double> &elevations )
435 if ( elevations == mSignificantElevations )
438 mSignificantElevations = elevations;
446QgsElevationControllerSettingsAction::QgsElevationControllerSettingsAction( QWidget *parent )
447 : QWidgetAction( parent )
449 QGridLayout *gLayout =
new QGridLayout();
450 gLayout->setContentsMargins( 3, 2, 3, 2 );
452 QLabel *label =
new QLabel( tr(
"Fixed Range Size" ) );
453 gLayout->addWidget( label, 0, 0 );
455 mSizeSpin =
new QgsDoubleSpinBox();
456 mSizeSpin->setDecimals( 4 );
457 mSizeSpin->setMinimum( -1.0 );
458 mSizeSpin->setMaximum( 999999999.0 );
459 mSizeSpin->setClearValue( -1, tr(
"Not set" ) );
460 mSizeSpin->setKeyboardTracking(
false );
461 mSizeSpin->setToolTip( tr(
"Limit elevation range to a fixed size" ) );
463 gLayout->addWidget( mSizeSpin, 0, 1 );
465 QWidget *w =
new QWidget();
466 w->setLayout( gLayout );
467 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.