19#include "moc_qgselevationcontrollerwidget.cpp"
33#include <QPainterPath>
39 QVBoxLayout *vl =
new QVBoxLayout();
40 vl->setContentsMargins( 0, 0, 0, 0 );
42 mConfigureButton =
new QToolButton();
43 mConfigureButton->setPopupMode( QToolButton::InstantPopup );
45 QHBoxLayout *hl =
new QHBoxLayout();
46 hl->setContentsMargins( 0, 0, 0, 0 );
47 hl->addWidget( mConfigureButton );
50 mMenu =
new QMenu(
this );
51 mConfigureButton->setMenu( mMenu );
53 mSettingsAction =
new QgsElevationControllerSettingsAction( mMenu );
54 mMenu->addAction( mSettingsAction );
55 mInvertDirectionAction =
new QAction( tr(
"Invert Direction" ),
this );
56 mInvertDirectionAction->setCheckable(
true );
57 mMenu->addAction( mInvertDirectionAction );
59 mSettingsAction->sizeSpin()->clear();
60 connect( mSettingsAction->sizeSpin(), qOverload<double>( &QgsDoubleSpinBox::valueChanged ),
this, [
this](
double size ) {
61 setFixedRangeSize( size < 0 ? -1 : size );
64 mMenu->addSeparator();
69 mSliderLabels =
new QgsElevationControllerLabels();
71 QHBoxLayout *hlSlider =
new QHBoxLayout();
72 hlSlider->setContentsMargins( 0, 0, 0, 0 );
73 hlSlider->setSpacing( 2 );
74 hlSlider->addWidget( mSlider );
75 hlSlider->addWidget( mSliderLabels, 1 );
76 hlSlider->addStretch();
77 vl->addLayout( hlSlider );
79 setCursor( Qt::ArrowCursor );
93 if ( mBlockSliderChanges )
97 mSliderLabels->setRange(
range() );
100 connect( mInvertDirectionAction, &QAction::toggled,
this, [
this]() {
102 mSliderLabels->setInverted( mInvertDirectionAction->isChecked() );
114 QWidget::resizeEvent( event );
121 const int snappedLower =
static_cast<int>( std::floor( mCurrentRange.
lower() * mSliderPrecision ) );
122 const int snappedUpper =
static_cast<int>( std::ceil( mCurrentRange.
upper() * mSliderPrecision ) );
124 return mCurrentRange;
127 if ( mFixedRangeSize >= 0 )
131 if ( sliderRange.
upper() + mFixedRangeSize <= mRangeLimits.
upper() )
159 if (
range == mCurrentRange )
162 mCurrentRange =
range;
163 mBlockSliderChanges =
true;
164 mSlider->
setRange(
static_cast<int>( std::floor(
range.
lower() * mSliderPrecision ) ),
static_cast<int>( std::ceil(
range.
upper() * mSliderPrecision ) ) );
165 mBlockSliderChanges =
false;
168 mSliderLabels->setRange( mCurrentRange );
176 mRangeLimits = limits;
178 const double limitRange = limits.
upper() - limits.
lower();
181 mSliderPrecision = std::max( 1000, mSlider->height() ) / limitRange;
183 mBlockSliderChanges =
true;
184 mSlider->
setRangeLimits(
static_cast<int>( std::floor( limits.
lower() * mSliderPrecision ) ),
static_cast<int>( std::ceil( limits.
upper() * mSliderPrecision ) ) );
187 const double newCurrentLower = std::max( mCurrentRange.
lower(), limits.
lower() );
188 const double newCurrentUpper = std::min( mCurrentRange.
upper(), limits.
upper() );
189 const bool rangeHasChanged = newCurrentLower != mCurrentRange.
lower() || newCurrentUpper != mCurrentRange.
upper();
191 mSlider->
setRange(
static_cast<int>( std::floor( newCurrentLower * mSliderPrecision ) ),
static_cast<int>( std::ceil( newCurrentUpper * mSliderPrecision ) ) );
192 mCurrentRange =
QgsDoubleRange( newCurrentLower, newCurrentUpper );
193 mBlockSliderChanges =
false;
194 if ( rangeHasChanged )
197 mSliderLabels->setLimits( mRangeLimits );
200void QgsElevationControllerWidget::updateWidgetMask()
208 QRegion reg( frameGeometry() );
209 reg -= QRegion( geometry() );
210 reg += childrenRegion();
216 return mFixedRangeSize;
221 if ( size == mFixedRangeSize )
224 mFixedRangeSize = size;
225 if ( mFixedRangeSize < 0 )
231 mSlider->
setFixedRangeSize(
static_cast<int>( std::round( mFixedRangeSize * mSliderPrecision ) ) );
233 if ( mFixedRangeSize != mSettingsAction->sizeSpin()->value() )
234 mSettingsAction->sizeSpin()->setValue( mFixedRangeSize );
240 mInvertDirectionAction->setChecked( inverted );
245 mSliderLabels->setSignificantElevations( elevations );
252QgsElevationControllerLabels::QgsElevationControllerLabels( QWidget *parent )
256 QFont smallerFont = font();
257 int fontSize = smallerFont.pointSize();
259 fontSize = std::max( fontSize - 1, 8 );
261 fontSize = std::max( fontSize - 2, 7 );
263 smallerFont.setPointSize( fontSize );
264 setFont( smallerFont );
266 const QFontMetrics fm( smallerFont );
267 setMinimumWidth( fm.horizontalAdvance(
'0' ) * 5 );
268 setAttribute( Qt::WA_TransparentForMouseEvents );
271void QgsElevationControllerLabels::paintEvent( QPaintEvent * )
273 QStyleOptionSlider styleOption;
274 styleOption.initFrom(
this );
276 const QRect sliderRect = style()->subControlRect( QStyle::CC_Slider, &styleOption, QStyle::SC_SliderHandle,
this );
277 const int sliderHeight = sliderRect.height();
280 const QFontMetrics fm( f );
282 const int left = rect().left() + 2;
284 const double limitRange = mLimits.upper() - mLimits.lower();
285 const double lowerFraction = ( mRange.lower() - mLimits.lower() ) / limitRange;
286 const double upperFraction = ( mRange.upper() - mLimits.lower() ) / limitRange;
287 const int lowerY = !mInverted
288 ? ( std::min(
static_cast<int>( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * lowerFraction + fm.ascent() ) ), rect().bottom() - fm.descent() ) )
289 : ( std::max( static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * lowerFraction - fm.descent() ) ), rect().top() + fm.ascent() ) );
290 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() ) )
291 : ( std::min( static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * upperFraction + fm.ascent() ) ), rect().bottom() - fm.descent() ) );
293 const bool lowerIsCloseToLimit = !mInverted
294 ? ( lowerY + fm.height() > rect().bottom() - fm.descent() )
295 : ( lowerY - fm.height() < rect().top() + fm.ascent() );
296 const bool upperIsCloseToLimit = !mInverted
297 ? ( upperY - fm.height() < rect().top() + fm.ascent() )
298 : ( upperY + fm.height() > rect().bottom() - fm.descent() );
299 const bool lowerIsCloseToUpperLimit = !mInverted
300 ? ( lowerY - fm.height() < rect().top() + fm.ascent() )
301 : ( 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 = !mInverted
311 ? ( std::min(
static_cast<int>( std::round( rect().bottom() - sliderHeight * 0.5 - ( rect().height() - sliderHeight ) * valueFraction + fm.capHeight() * 0.5 ) ), rect().bottom() - fm.descent() ) )
312 : ( std::max( static_cast<int>( std::round( rect().top() + sliderHeight * 0.5 + ( rect().height() - sliderHeight ) * valueFraction + fm.capHeight() * 0.5 ) ), rect().top() + fm.ascent() ) );
314 const bool valueIsCloseToLower = verticalCenter + fm.height() > lowerY && verticalCenter - fm.height() < lowerY;
315 if ( valueIsCloseToLower )
318 const bool valueIsCloseToUpper = verticalCenter + fm.height() > upperY && verticalCenter - fm.height() < upperY;
319 if ( valueIsCloseToUpper )
322 const bool valueIsCloseToLowerLimit = !mInverted
323 ? ( verticalCenter + fm.height() > rect().bottom() - fm.descent() )
324 : ( verticalCenter - fm.height() < rect().top() + fm.ascent() );
325 if ( valueIsCloseToLowerLimit )
328 const bool valueIsCloseToUpperLimit = !mInverted
329 ? ( verticalCenter - fm.height() < rect().top() + fm.ascent() )
330 : ( verticalCenter + fm.height() > rect().bottom() - fm.descent() );
331 if ( valueIsCloseToUpperLimit )
334 path.addText( left, verticalCenter, f, locale.toString( value ) );
337 if ( mLimits.lower() > std::numeric_limits<double>::lowest() )
339 if ( lowerIsCloseToLimit )
342 path.addText( left, lowerY, f, locale.toString( mRange.lower() ) );
347 path.addText( left, lowerY, f, locale.toString( mRange.lower() ) );
349 path.addText( left, !mInverted ? ( rect().bottom() - fm.descent() ) : ( rect().top() + fm.ascent() ), f, locale.toString( mLimits.
lower() ) );
353 if ( mLimits.upper() < std::numeric_limits<double>::max() )
357 if ( !lowerIsCloseToUpperLimit )
360 path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) );
365 if ( upperIsCloseToLimit )
368 path.addText( left, upperY, f, locale.toString( mRange.upper() ) );
373 path.addText( left, upperY, f, locale.toString( mRange.upper() ) );
375 path.addText( left, !mInverted ? ( rect().top() + fm.ascent() ) : ( rect().bottom() - fm.descent() ), f, locale.toString( mLimits.upper() ) );
381 p.setRenderHint( QPainter::Antialiasing,
true );
382 const QColor bufferColor = palette().color( QPalette::Window );
383 const QColor textColor = palette().color( QPalette::WindowText );
384 QPen pen( bufferColor );
385 pen.setJoinStyle( Qt::RoundJoin );
386 pen.setCapStyle( Qt::RoundCap );
389 p.setBrush( Qt::NoBrush );
391 p.setPen( Qt::NoPen );
392 p.setBrush( QBrush( textColor ) );
397void QgsElevationControllerLabels::setLimits(
const QgsDoubleRange &limits )
399 if ( limits == mLimits )
402 const QFontMetrics fm( font() );
403 const int maxChars = std::max( QLocale().toString( std::floor( limits.
lower() ) ).length(), QLocale().toString( std::floor( limits.
upper() ) ).length() ) + 3;
404 setMinimumWidth( fm.horizontalAdvance(
'0' ) * maxChars );
410void QgsElevationControllerLabels::setRange(
const QgsDoubleRange &range )
412 if ( range == mRange )
419void QgsElevationControllerLabels::setInverted(
bool inverted )
421 if ( inverted == mInverted )
424 mInverted = inverted;
428void QgsElevationControllerLabels::setSignificantElevations(
const QList<double> &elevations )
430 if ( elevations == mSignificantElevations )
433 mSignificantElevations = elevations;
441QgsElevationControllerSettingsAction::QgsElevationControllerSettingsAction( QWidget *parent )
442 : QWidgetAction( parent )
444 QGridLayout *gLayout =
new QGridLayout();
445 gLayout->setContentsMargins( 3, 2, 3, 2 );
447 QLabel *label =
new QLabel( tr(
"Fixed Range Size" ) );
448 gLayout->addWidget( label, 0, 0 );
451 mSizeSpin->setDecimals( 4 );
452 mSizeSpin->setMinimum( -1.0 );
453 mSizeSpin->setMaximum( 999999999.0 );
454 mSizeSpin->setClearValue( -1, tr(
"Not set" ) );
455 mSizeSpin->setKeyboardTracking(
false );
456 mSizeSpin->setToolTip( tr(
"Limit elevation range to a fixed size" ) );
458 gLayout->addWidget( mSizeSpin, 0, 1 );
460 QWidget *w =
new QWidget();
461 w->setLayout( gLayout );
462 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 setRangeLimits(int minimum, int maximum)
Sets the minimum and maximum range limits for values allowed in the widget.
int upperValue() const
Returns the upper value for the range selected in the widget.
void rangeChanged(int minimum, int maximum)
Emitted when the range selected in the widget is changed.
void setFlippedDirection(bool flipped)
Sets whether the slider has its values flipped.
void setRange(int lower, int upper)
Sets the current range selected in the widget.
int lowerValue() const
Returns the lower value for the range selected in the widget.
void setFixedRangeSize(int size)
Sets the slider's fixed range size.
T lower() const
Returns the lower bound of the range.
T upper() const
Returns the upper bound of the range.
int ANALYSIS_EXPORT lower(int n, int i)
Lower function.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)