Skip to content

Commit 45bdae4

Browse files
manisandronyalldawson
authored andcommitted
[FEATURE] Add scalebar sizing mode to fit a desired scalebar width (fix #8995)
1 parent 479ef79 commit 45bdae4

File tree

6 files changed

+433
-63
lines changed

6 files changed

+433
-63
lines changed

python/core/composer/qgscomposerscalebar.sip

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ class QgsComposerScaleBar: QgsComposerItem
2525
NauticalMiles
2626
};
2727

28+
/** Modes for setting size for scale bar segments
29+
*/
30+
enum SegmentSizeMode
31+
{
32+
SegmentSizeFixed, /*!< Scale bar segment size is fixed to a map unit*/
33+
SegmentSizeFitWidth /*!< Scale bar segment size is calculated to fit a size range*/
34+
};
35+
2836
QgsComposerScaleBar( QgsComposition* composition /TransferThis/ );
2937
~QgsComposerScaleBar();
3038

@@ -44,6 +52,65 @@ class QgsComposerScaleBar: QgsComposerItem
4452
double numUnitsPerSegment() const;
4553
void setNumUnitsPerSegment( double units );
4654

55+
/** Returns the size mode for scale bar segments.
56+
* @see setSegmentSizeMode
57+
* @see minBarWidth
58+
* @see maxBarWidth
59+
* @note added in QGIS 2.9
60+
*/
61+
SegmentSizeMode segmentSizeMode() const;
62+
63+
/** Sets the size mode for scale bar segments.
64+
* @param mode size mode
65+
* @see segmentSizeMode
66+
* @see setMinBarWidth
67+
* @see setMaxBarWidth
68+
* @note added in QGIS 2.9
69+
*/
70+
void setSegmentSizeMode( SegmentSizeMode mode );
71+
72+
/** Returns the minimum size (in millimeters) for scale bar segments. This
73+
* property is only effective if the @link segmentSizeMode @endlink is set
74+
* to @link SegmentSizeFitWidth @endlink.
75+
* @see segmentSizeMode
76+
* @see setMinBarWidth
77+
* @see maxBarWidth
78+
* @note added in QGIS 2.9
79+
*/
80+
double minBarWidth() const;
81+
82+
/** Sets the minimum size (in millimeters) for scale bar segments. This
83+
* property is only effective if the @link segmentSizeMode @endlink is set
84+
* to @link SegmentSizeFitWidth @endlink.
85+
* @param minWidth minimum width in millimeters
86+
* @see minBarWidth
87+
* @see setMaxBarWidth
88+
* @see setSegmentSizeMode
89+
* @note added in QGIS 2.9
90+
*/
91+
void setMinBarWidth( double minWidth );
92+
93+
/** Returns the maximum size (in millimeters) for scale bar segments. This
94+
* property is only effective if the @link segmentSizeMode @endlink is set
95+
* to @link SegmentSizeFitWidth @endlink.
96+
* @see segmentSizeMode
97+
* @see setMaxBarWidth
98+
* @see minBarWidth
99+
* @note added in QGIS 2.9
100+
*/
101+
double maxBarWidth() const;
102+
103+
/** Sets the maximum size (in millimeters) for scale bar segments. This
104+
* property is only effective if the @link segmentSizeMode @endlink is set
105+
* to @link SegmentSizeFitWidth @endlink.
106+
* @param minWidth maximum width in millimeters
107+
* @see minBarWidth
108+
* @see setMaxBarWidth
109+
* @see setSegmentSizeMode
110+
* @note added in QGIS 2.9
111+
*/
112+
void setMaxBarWidth( double maxWidth );
113+
47114
double numMapUnitsPerScaleBarUnit() const;
48115
void setNumMapUnitsPerScaleBarUnit( double d );
49116

src/app/composer/qgscomposerscalebarwidget.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ QgsComposerScaleBarWidget::QgsComposerScaleBarWidget( QgsComposerScaleBar* scale
3232
QgsComposerItemWidget* itemPropertiesWidget = new QgsComposerItemWidget( this, scaleBar );
3333
mainLayout->addWidget( itemPropertiesWidget );
3434

35+
mSegmentSizeRadioGroup.addButton( mFixedSizeRadio );
36+
mSegmentSizeRadioGroup.addButton( mFitWidthRadio );
37+
connect( &mSegmentSizeRadioGroup, SIGNAL( buttonClicked( QAbstractButton* ) ), this, SLOT( segmentSizeRadioChanged( QAbstractButton* ) ) );
38+
3539
blockMemberSignals( true );
3640

3741
//style combo box
@@ -221,6 +225,23 @@ void QgsComposerScaleBarWidget::setGuiElements()
221225
//units
222226
mUnitsComboBox->setCurrentIndex( mUnitsComboBox->findData(( int )mComposerScaleBar->units() ) );
223227

228+
if ( mComposerScaleBar->segmentSizeMode() == QgsComposerScaleBar::SegmentSizeFixed )
229+
{
230+
mFixedSizeRadio->setChecked( true );
231+
mSegmentSizeSpinBox->setEnabled( true );
232+
mMinWidthSpinBox->setEnabled( false );
233+
mMaxWidthSpinBox->setEnabled( false );
234+
}
235+
else /*if(mComposerScaleBar->segmentSizeMode() == QgsComposerScaleBar::SegmentSizeFitWidth)*/
236+
{
237+
mFitWidthRadio->setChecked( true );
238+
mSegmentSizeSpinBox->setEnabled( false );
239+
mMinWidthSpinBox->setEnabled( true );
240+
mMaxWidthSpinBox->setEnabled( true );
241+
}
242+
mMinWidthSpinBox->setValue( mComposerScaleBar->minBarWidth() );
243+
mMaxWidthSpinBox->setValue( mComposerScaleBar->maxBarWidth() );
244+
224245
blockMemberSignals( false );
225246
}
226247

@@ -621,6 +642,7 @@ void QgsComposerScaleBarWidget::blockMemberSignals( bool block )
621642
mFillColorButton->blockSignals( block );
622643
mFillColor2Button->blockSignals( block );
623644
mStrokeColorButton->blockSignals( block );
645+
mSegmentSizeRadioGroup.blockSignals( block );
624646
}
625647

626648
void QgsComposerScaleBarWidget::connectUpdateSignal()
@@ -664,3 +686,61 @@ void QgsComposerScaleBarWidget::on_mLineCapStyleCombo_currentIndexChanged( int i
664686
mComposerScaleBar->setLineCapStyle( mLineCapStyleCombo->penCapStyle() );
665687
mComposerScaleBar->endCommand();
666688
}
689+
690+
void QgsComposerScaleBarWidget::segmentSizeRadioChanged( QAbstractButton* radio )
691+
{
692+
bool fixedSizeMode = radio == mFixedSizeRadio;
693+
mMinWidthSpinBox->setEnabled( !fixedSizeMode );
694+
mMaxWidthSpinBox->setEnabled( !fixedSizeMode );
695+
mSegmentSizeSpinBox->setEnabled( fixedSizeMode );
696+
697+
if ( !mComposerScaleBar )
698+
{
699+
return;
700+
}
701+
702+
mComposerScaleBar->beginCommand( tr( "Scalebar segment size mode" ), QgsComposerMergeCommand::ScaleBarSegmentSize );
703+
disconnectUpdateSignal();
704+
if ( mFixedSizeRadio->isChecked() )
705+
{
706+
mComposerScaleBar->setSegmentSizeMode( QgsComposerScaleBar::SegmentSizeFixed );
707+
mComposerScaleBar->setNumUnitsPerSegment( mSegmentSizeSpinBox->value() );
708+
}
709+
else /*if(mFitWidthRadio->isChecked())*/
710+
{
711+
mComposerScaleBar->setSegmentSizeMode( QgsComposerScaleBar::SegmentSizeFitWidth );
712+
}
713+
mComposerScaleBar->update();
714+
connectUpdateSignal();
715+
mComposerScaleBar->endCommand();
716+
}
717+
718+
void QgsComposerScaleBarWidget::on_mMinWidthSpinBox_valueChanged( int )
719+
{
720+
if ( !mComposerScaleBar )
721+
{
722+
return;
723+
}
724+
725+
mComposerScaleBar->beginCommand( tr( "Scalebar segment size mode" ), QgsComposerMergeCommand::ScaleBarSegmentSize );
726+
disconnectUpdateSignal();
727+
mComposerScaleBar->setMinBarWidth( mMinWidthSpinBox->value() );
728+
mComposerScaleBar->update();
729+
connectUpdateSignal();
730+
mComposerScaleBar->endCommand();
731+
}
732+
733+
void QgsComposerScaleBarWidget::on_mMaxWidthSpinBox_valueChanged( int )
734+
{
735+
if ( !mComposerScaleBar )
736+
{
737+
return;
738+
}
739+
740+
mComposerScaleBar->beginCommand( tr( "Scalebar segment size mode" ), QgsComposerMergeCommand::ScaleBarSegmentSize );
741+
disconnectUpdateSignal();
742+
mComposerScaleBar->setMaxBarWidth( mMaxWidthSpinBox->value() );
743+
mComposerScaleBar->update();
744+
connectUpdateSignal();
745+
mComposerScaleBar->endCommand();
746+
}

src/app/composer/qgscomposerscalebarwidget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,19 @@ class QgsComposerScaleBarWidget: public QgsComposerItemBaseWidget, private Ui::Q
5454
void on_mUnitsComboBox_currentIndexChanged( int index );
5555
void on_mLineJoinStyleCombo_currentIndexChanged( int index );
5656
void on_mLineCapStyleCombo_currentIndexChanged( int index );
57+
void on_mMinWidthSpinBox_valueChanged( int i );
58+
void on_mMaxWidthSpinBox_valueChanged( int i );
5759

5860
private slots:
5961
void setGuiElements();
62+
void segmentSizeRadioChanged( QAbstractButton*radio );
6063

6164
protected:
6265
void showEvent( QShowEvent * event ) override;
6366

6467
private:
6568
QgsComposerScaleBar* mComposerScaleBar;
69+
QButtonGroup mSegmentSizeRadioGroup;
6670

6771
void refreshMapComboBox();
6872
/**Enables/disables the signals of the input gui elements*/

src/core/composer/qgscomposerscalebar.cpp

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ QgsComposerScaleBar::QgsComposerScaleBar( QgsComposition* composition )
3939
: QgsComposerItem( composition )
4040
, mComposerMap( 0 )
4141
, mNumUnitsPerSegment( 0 )
42+
, mSegmentSizeMode( SegmentSizeFixed )
43+
, mMinBarWidth( 50 )
44+
, mMaxBarWidth( 150 )
4245
, mFontColor( QColor( 0, 0, 0 ) )
4346
, mStyle( 0 )
4447
, mSegmentMillimeters( 0.0 )
@@ -114,6 +117,51 @@ void QgsComposerScaleBar::setNumUnitsPerSegment( double units )
114117
emit itemChanged();
115118
}
116119

120+
void QgsComposerScaleBar::setSegmentSizeMode( SegmentSizeMode mode )
121+
{
122+
if ( !mStyle )
123+
{
124+
mSegmentSizeMode = mode;
125+
return;
126+
}
127+
double width = mStyle->calculateBoxSize().width();
128+
mSegmentSizeMode = mode;
129+
refreshSegmentMillimeters();
130+
double widthAfter = mStyle->calculateBoxSize().width();
131+
correctXPositionAlignment( width, widthAfter );
132+
emit itemChanged();
133+
}
134+
135+
void QgsComposerScaleBar::setMinBarWidth( double minWidth )
136+
{
137+
if ( !mStyle )
138+
{
139+
mMinBarWidth = minWidth;
140+
return;
141+
}
142+
double width = mStyle->calculateBoxSize().width();
143+
mMinBarWidth = minWidth;
144+
refreshSegmentMillimeters();
145+
double widthAfter = mStyle->calculateBoxSize().width();
146+
correctXPositionAlignment( width, widthAfter );
147+
emit itemChanged();
148+
}
149+
150+
void QgsComposerScaleBar::setMaxBarWidth( double maxWidth )
151+
{
152+
if ( !mStyle )
153+
{
154+
mMaxBarWidth = maxWidth;
155+
return;
156+
}
157+
double width = mStyle->calculateBoxSize().width();
158+
mMaxBarWidth = maxWidth;
159+
refreshSegmentMillimeters();
160+
double widthAfter = mStyle->calculateBoxSize().width();
161+
correctXPositionAlignment( width, widthAfter );
162+
emit itemChanged();
163+
}
164+
117165
void QgsComposerScaleBar::setNumSegmentsLeft( int nSegmentsLeft )
118166
{
119167
if ( !mStyle )
@@ -175,18 +223,65 @@ void QgsComposerScaleBar::invalidateCurrentMap()
175223
mComposerMap = 0;
176224
}
177225

226+
// nextNiceNumber(4573.23, d) = 5000 (d=1) -> 4600 (d=10) -> 4580 (d=100) -> 4574 (d=1000) -> etc
227+
inline double nextNiceNumber( double a, double d = 1 )
228+
{
229+
double s = pow10( floor( log10( a ) ) ) / d;
230+
return ceil( a / s ) * s;
231+
}
232+
233+
// prevNiceNumber(4573.23, d) = 4000 (d=1) -> 4500 (d=10) -> 4570 (d=100) -> 4573 (d=1000) -> etc
234+
inline double prevNiceNumber( double a, double d = 1 )
235+
{
236+
double s = pow10( floor( log10( a ) ) ) / d;
237+
return floor( a / s ) * s;
238+
}
239+
178240
void QgsComposerScaleBar::refreshSegmentMillimeters()
179241
{
180242
if ( mComposerMap )
181243
{
182-
//get extent of composer map
183-
QgsRectangle composerMapRect = *( mComposerMap->currentMapExtent() );
184-
185244
//get mm dimension of composer map
186245
QRectF composerItemRect = mComposerMap->rect();
187246

188-
//calculate size depending on mNumUnitsPerSegment
189-
mSegmentMillimeters = composerItemRect.width() / mapWidth() * mNumUnitsPerSegment;
247+
if ( mSegmentSizeMode == SegmentSizeFixed )
248+
{
249+
//calculate size depending on mNumUnitsPerSegment
250+
mSegmentMillimeters = composerItemRect.width() / mapWidth() * mNumUnitsPerSegment;
251+
}
252+
else /*if(mSegmentSizeMode == SegmentSizeFitWidth)*/
253+
{
254+
if ( mMaxBarWidth < mMinBarWidth )
255+
{
256+
mSegmentMillimeters = 0;
257+
}
258+
else
259+
{
260+
double nSegments = ( mNumSegmentsLeft != 0 ) + mNumSegments;
261+
// unitsPerSegments which fit minBarWidth resp. maxBarWidth
262+
double minUnitsPerSeg = ( mMinBarWidth * mapWidth() ) / ( nSegments * composerItemRect.width() );
263+
double maxUnitsPerSeg = ( mMaxBarWidth * mapWidth() ) / ( nSegments * composerItemRect.width() );
264+
265+
// Start with coarsest "nice" number closest to minUnitsPerSeg resp
266+
// maxUnitsPerSeg, then proceed to finer numbers as long as neither
267+
// lowerNiceUnitsPerSeg nor upperNiceUnitsPerSeg are are in
268+
// [minUnitsPerSeg, maxUnitsPerSeg]
269+
double lowerNiceUnitsPerSeg = nextNiceNumber( minUnitsPerSeg );
270+
double upperNiceUnitsPerSeg = prevNiceNumber( maxUnitsPerSeg );
271+
272+
double d = 1;
273+
while ( lowerNiceUnitsPerSeg > maxUnitsPerSeg && upperNiceUnitsPerSeg < minUnitsPerSeg )
274+
{
275+
d *= 10;
276+
lowerNiceUnitsPerSeg = nextNiceNumber( minUnitsPerSeg, d );
277+
upperNiceUnitsPerSeg = prevNiceNumber( maxUnitsPerSeg, d );
278+
}
279+
280+
// Pick mNumUnitsPerSegment from {lowerNiceUnitsPerSeg, upperNiceUnitsPerSeg}, use the larger if possible
281+
mNumUnitsPerSegment = upperNiceUnitsPerSeg < minUnitsPerSeg ? lowerNiceUnitsPerSeg : upperNiceUnitsPerSeg;
282+
mSegmentMillimeters = composerItemRect.width() / mapWidth() * mNumUnitsPerSegment;
283+
}
284+
}
190285
}
191286
}
192287

@@ -568,6 +663,9 @@ bool QgsComposerScaleBar::writeXML( QDomElement& elem, QDomDocument & doc ) cons
568663
composerScaleBarElem.setAttribute( "numSegments", mNumSegments );
569664
composerScaleBarElem.setAttribute( "numSegmentsLeft", mNumSegmentsLeft );
570665
composerScaleBarElem.setAttribute( "numUnitsPerSegment", QString::number( mNumUnitsPerSegment ) );
666+
composerScaleBarElem.setAttribute( "segmentSizeMode", mSegmentSizeMode );
667+
composerScaleBarElem.setAttribute( "minBarWidth", mMinBarWidth );
668+
composerScaleBarElem.setAttribute( "maxBarWidth", mMaxBarWidth );
571669
composerScaleBarElem.setAttribute( "segmentMillimeters", QString::number( mSegmentMillimeters ) );
572670
composerScaleBarElem.setAttribute( "numMapUnitsPerScaleBarUnit", QString::number( mNumMapUnitsPerScaleBarUnit ) );
573671
composerScaleBarElem.setAttribute( "font", mFont.toString() );
@@ -646,6 +744,9 @@ bool QgsComposerScaleBar::readXML( const QDomElement& itemElem, const QDomDocume
646744
mNumSegments = itemElem.attribute( "numSegments", "2" ).toInt();
647745
mNumSegmentsLeft = itemElem.attribute( "numSegmentsLeft", "0" ).toInt();
648746
mNumUnitsPerSegment = itemElem.attribute( "numUnitsPerSegment", "1.0" ).toDouble();
747+
mSegmentSizeMode = static_cast<SegmentSizeMode>( itemElem.attribute( "segmentSizeMode", "0" ).toInt() );
748+
mMinBarWidth = itemElem.attribute( "minBarWidth", "50" ).toInt();
749+
mMaxBarWidth = itemElem.attribute( "maxBarWidth", "150" ).toInt();
649750
mSegmentMillimeters = itemElem.attribute( "segmentMillimeters", "0.0" ).toDouble();
650751
mNumMapUnitsPerScaleBarUnit = itemElem.attribute( "numMapUnitsPerScaleBarUnit", "1.0" ).toDouble();
651752
mPen.setWidthF( itemElem.attribute( "outlineWidth", "1.0" ).toDouble() );

0 commit comments

Comments
 (0)