32#include <QPainterPath>
36#include "moc_qgselevationcontrollerwidget.cpp"
41 QVBoxLayout *vl =
new QVBoxLayout();
42 vl->setContentsMargins( 0, 0, 0, 0 );
44 mConfigureButton =
new QToolButton();
45 mConfigureButton->setPopupMode( QToolButton::InstantPopup );
47 QHBoxLayout *hl =
new QHBoxLayout();
48 hl->setContentsMargins( 0, 0, 0, 0 );
49 hl->addWidget( mConfigureButton );
52 mMenu =
new QMenu(
this );
53 mConfigureButton->setMenu( mMenu );
55 mSettingsAction =
new QgsElevationControllerSettingsAction( mMenu );
56 mMenu->addAction( mSettingsAction );
57 mInvertDirectionAction =
new QAction( tr(
"Invert Direction" ),
this );
58 mInvertDirectionAction->setCheckable(
true );
59 mMenu->addAction( mInvertDirectionAction );
61 mSettingsAction->sizeSpin()->clear();
62 connect( mSettingsAction->sizeSpin(), qOverload<double>( &QgsDoubleSpinBox::valueChanged ),
this, [
this](
double size ) {
63 setFixedRangeSize( size < 0 ? -1 : size );
66 mMenu->addSeparator();
69 mSlider->setFlippedDirection(
true );
70 mSlider->setRangeLimits( 0, 100000 );
71 mSliderLabels =
new QgsElevationControllerLabels();
73 QHBoxLayout *hlSlider =
new QHBoxLayout();
74 hlSlider->setContentsMargins( 0, 0, 0, 0 );
75 hlSlider->setSpacing( 2 );
76 hlSlider->addWidget( mSlider );
77 hlSlider->addWidget( mSliderLabels, 1 );
78 hlSlider->addStretch();
79 vl->addLayout( hlSlider );
81 setCursor( Qt::ArrowCursor );
90 if ( !
range.isInfinite() )
95 if ( mBlockSliderChanges )
99 mSliderLabels->setRange(
range() );
102 connect( mInvertDirectionAction, &QAction::toggled,
this, [
this]() {
103 mSlider->setFlippedDirection( !mInvertDirectionAction->isChecked() );
104 mSliderLabels->setInverted( mInvertDirectionAction->isChecked() );
116 QWidget::resizeEvent( event );
123 const int snappedLower =
static_cast<int>( std::floor( mCurrentRange.lower() * mSliderPrecision ) );
124 const int snappedUpper =
static_cast<int>( std::ceil( mCurrentRange.upper() * mSliderPrecision ) );
125 if ( snappedLower == mSlider->lowerValue() && snappedUpper == mSlider->upperValue() )
126 return mCurrentRange;
128 const QgsDoubleRange sliderRange( mSlider->lowerValue() / mSliderPrecision, mSlider->upperValue() / mSliderPrecision );
129 if ( mFixedRangeSize >= 0 )
133 if ( sliderRange.
upper() + mFixedRangeSize <= mRangeLimits.upper() )
161 if (
range == mCurrentRange )
164 mCurrentRange =
range;
165 mBlockSliderChanges =
true;
166 mSlider->setRange(
static_cast<int>( std::floor(
range.lower() * mSliderPrecision ) ),
static_cast<int>( std::ceil(
range.upper() * mSliderPrecision ) ) );
167 mBlockSliderChanges =
false;
170 mSliderLabels->setRange( mCurrentRange );
178 mRangeLimits = limits;
180 const double limitRange = limits.
upper() - limits.
lower();
183 mSliderPrecision = std::max( 1000, mSlider->height() ) / limitRange;
185 mBlockSliderChanges =
true;
186 mSlider->setRangeLimits(
static_cast<int>( std::floor( limits.
lower() * mSliderPrecision ) ),
static_cast<int>( std::ceil( limits.
upper() * mSliderPrecision ) ) );
189 const double newCurrentLower = std::max( mCurrentRange.lower(), limits.
lower() );
190 const double newCurrentUpper = std::min( mCurrentRange.upper(), limits.
upper() );
191 const bool rangeHasChanged = newCurrentLower != mCurrentRange.lower() || newCurrentUpper != mCurrentRange.upper();
193 mSlider->setRange(
static_cast<int>( std::floor( newCurrentLower * mSliderPrecision ) ),
static_cast<int>( std::ceil( newCurrentUpper * mSliderPrecision ) ) );
194 mCurrentRange =
QgsDoubleRange( newCurrentLower, newCurrentUpper );
195 mBlockSliderChanges =
false;
196 if ( rangeHasChanged )
199 mSliderLabels->setLimits( mRangeLimits );
202void QgsElevationControllerWidget::updateWidgetMask()
210 QRegion reg( frameGeometry() );
211 reg -= QRegion( geometry() );
212 reg += childrenRegion();
218 return mFixedRangeSize;
223 if ( size == mFixedRangeSize )
226 mFixedRangeSize = size;
227 if ( mFixedRangeSize < 0 )
229 mSlider->setFixedRangeSize( -1 );
233 mSlider->setFixedRangeSize(
static_cast<int>( std::round( mFixedRangeSize * mSliderPrecision ) ) );
235 if ( mFixedRangeSize != mSettingsAction->sizeSpin()->value() )
236 mSettingsAction->sizeSpin()->setValue( mFixedRangeSize );
242 mInvertDirectionAction->setChecked( inverted );
247 mSliderLabels->setSignificantElevations( elevations );
254QgsElevationControllerLabels::QgsElevationControllerLabels( QWidget *parent )
258 QFont smallerFont = font();
259 int fontSize = smallerFont.pointSize();
261 fontSize = std::max( fontSize - 1, 8 );
263 fontSize = std::max( fontSize - 2, 7 );
265 smallerFont.setPointSize( fontSize );
266 setFont( smallerFont );
268 const QFontMetrics fm( smallerFont );
269 setMinimumWidth( fm.horizontalAdvance(
'0' ) * 5 );
270 setAttribute( Qt::WA_TransparentForMouseEvents );
273void QgsElevationControllerLabels::paintEvent( QPaintEvent * )
275 QStyleOptionSlider styleOption;
276 styleOption.initFrom(
this );
278 const QRect sliderRect = style()->subControlRect( QStyle::CC_Slider, &styleOption, QStyle::SC_SliderHandle,
this );
279 const int sliderHeight = sliderRect.height();
282 const QFontMetrics fm( f );
284 const int left = rect().left() + 2;
286 const double limitRange = mLimits.upper() - mLimits.lower();
287 const double lowerFraction = ( mRange.lower() - mLimits.lower() ) / limitRange;
288 const double upperFraction = ( mRange.upper() - mLimits.lower() ) / limitRange;
289 const int lowerY = !mInverted
290 ? ( std::min(
static_cast<int>( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * lowerFraction + fm.ascent() ) ), rect().bottom() - fm.descent() ) )
291 : ( std::max( static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * lowerFraction - fm.descent() ) ), rect().top() + fm.ascent() ) );
292 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() ) )
293 : ( std::min(
static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * upperFraction + fm.ascent() ) ), rect().bottom() - fm.descent() ) );
295 const bool lowerIsCloseToLimit = !mInverted
296 ? ( lowerY + fm.height() > rect().bottom() - fm.descent() )
297 : ( lowerY - fm.height() < rect().top() + fm.ascent() );
298 const bool upperIsCloseToLimit = !mInverted
299 ? ( upperY - fm.height() < rect().top() + fm.ascent() )
300 : ( upperY + fm.height() > rect().bottom() - fm.descent() );
301 const bool lowerIsCloseToUpperLimit = !mInverted
302 ? ( lowerY - fm.height() < rect().top() + fm.ascent() )
303 : ( lowerY + fm.height() > rect().bottom() - fm.descent() );
309 for (
double value : std::as_const( mSignificantElevations ) )
311 const double valueFraction = ( value - mLimits.lower() ) / limitRange;
312 const double verticalCenter = !mInverted
313 ? ( std::min(
static_cast<int>( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * valueFraction + fm.capHeight() * 0.5 ) ), rect().bottom() - fm.descent() ) )
314 : ( std::max( static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * valueFraction + fm.capHeight() * 0.5 ) ), rect().top() + fm.ascent() ) );
316 const bool valueIsCloseToLower = verticalCenter + fm.height() > lowerY && verticalCenter - fm.height() < lowerY;
317 if ( valueIsCloseToLower )
320 const bool valueIsCloseToUpper = verticalCenter + fm.height() > upperY && verticalCenter - fm.height() < upperY;
321 if ( valueIsCloseToUpper )
324 const bool valueIsCloseToLowerLimit = !mInverted
325 ? ( verticalCenter + fm.height() > rect().bottom() - fm.descent() )
326 : ( verticalCenter - fm.height() < rect().top() + fm.ascent() );
327 if ( valueIsCloseToLowerLimit )
330 const bool valueIsCloseToUpperLimit = !mInverted
331 ? ( verticalCenter - fm.height() < rect().top() + fm.ascent() )
332 : ( verticalCenter + fm.height() > rect().bottom() - fm.descent() );
333 if ( valueIsCloseToUpperLimit )
336 path.addText( left, verticalCenter, f, locale.toString( value ) );
339 if ( mLimits.lower() > std::numeric_limits<double>::lowest() )
341 if ( lowerIsCloseToLimit )
344 path.addText( left, lowerY, f, locale.toString( mRange.lower() ) );
349 path.addText( left, lowerY, f, locale.toString( mRange.lower() ) );
351 path.addText( left, !mInverted ? ( rect().bottom() - fm.descent() ) : ( rect().top() + fm.ascent() ), f, locale.toString( mLimits.lower() ) );
355 if ( mLimits.upper() < std::numeric_limits<double>::max() )
357 if ( qgsDoubleNear( mRange.upper(), mRange.lower() ) )
359 if ( !lowerIsCloseToUpperLimit )
362 path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) );
367 if ( upperIsCloseToLimit )
370 path.addText( left, upperY, f, locale.toString( mRange.upper() ) );
375 path.addText( left, upperY, f, locale.toString( mRange.upper() ) );
377 path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) );
383 p.setRenderHint( QPainter::Antialiasing,
true );
384 const QColor bufferColor = palette().color( QPalette::Window );
385 const QColor textColor = palette().color( QPalette::WindowText );
386 QPen pen( bufferColor );
387 pen.setJoinStyle( Qt::RoundJoin );
388 pen.setCapStyle( Qt::RoundCap );
391 p.setBrush( Qt::NoBrush );
393 p.setPen( Qt::NoPen );
394 p.setBrush( QBrush( textColor ) );
399void QgsElevationControllerLabels::setLimits(
const QgsDoubleRange &limits )
401 if ( limits == mLimits )
404 const QFontMetrics fm( font() );
405 const int maxChars = std::max( QLocale().toString( std::floor( limits.
lower() ) ).length(), QLocale().toString( std::floor( limits.
upper() ) ).length() ) + 3;
406 setMinimumWidth( fm.horizontalAdvance(
'0' ) * maxChars );
412void QgsElevationControllerLabels::setRange(
const QgsDoubleRange &range )
414 if ( range == mRange )
421void QgsElevationControllerLabels::setInverted(
bool inverted )
423 if ( inverted == mInverted )
426 mInverted = inverted;
430void QgsElevationControllerLabels::setSignificantElevations(
const QList<double> &elevations )
432 if ( elevations == mSignificantElevations )
435 mSignificantElevations = elevations;
443QgsElevationControllerSettingsAction::QgsElevationControllerSettingsAction( QWidget *parent )
444 : QWidgetAction( parent )
446 QGridLayout *gLayout =
new QGridLayout();
447 gLayout->setContentsMargins( 3, 2, 3, 2 );
449 QLabel *label =
new QLabel( tr(
"Fixed Range Size" ) );
450 gLayout->addWidget( label, 0, 0 );
452 mSizeSpin =
new QgsDoubleSpinBox();
453 mSizeSpin->setDecimals( 4 );
454 mSizeSpin->setMinimum( -1.0 );
455 mSizeSpin->setMaximum( 999999999.0 );
456 mSizeSpin->setClearValue( -1, tr(
"Not set" ) );
457 mSizeSpin->setKeyboardTracking(
false );
458 mSizeSpin->setToolTip( tr(
"Limit elevation range to a fixed size" ) );
460 gLayout->addWidget( mSizeSpin, 0, 1 );
462 QWidget *w =
new QWidget();
463 w->setLayout( gLayout );
464 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.