Skip to content

Commit 097a195

Browse files
committedDec 29, 2013
[composer] Improve appearance of rulers, add small tick marks to ruler (fix #5656)
1 parent d02be61 commit 097a195

File tree

2 files changed

+267
-31
lines changed

2 files changed

+267
-31
lines changed
 

‎src/gui/qgscomposerruler.cpp

Lines changed: 246 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
#include <cmath>
88

99
const int RULER_MIN_SIZE = 20;
10+
const int COUNT_VALID_MULTIPLES = 3;
11+
const int COUNT_VALID_MAGNITUDES = 5;
12+
const int QgsComposerRuler::validScaleMultiples[] = {1, 2, 5};
13+
const int QgsComposerRuler::validScaleMagnitudes[] = {1, 10, 100, 1000, 10000};
1014

1115
QgsComposerRuler::QgsComposerRuler( QgsComposerRuler::Direction d ): QWidget( 0 ), mDirection( d ), mComposition( 0 ), mLineSnapItem( 0 )
1216
{
@@ -34,20 +38,22 @@ void QgsComposerRuler::paintEvent( QPaintEvent* event )
3438

3539
QTransform t = mTransform.inverted();
3640

37-
//find optimal ruler display scale (steps of 1, 10 or 50)
38-
double pixelDiff1 = mTransform.map( QPointF( 1, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
39-
double pixelDiff10 = mTransform.map( QPointF( 10, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
40-
//double pixelDiff50 = mTransform.map( QPointF( 50, 0 ) ).x() - mTransform.map( QPointF( 5, 0 ) ).x();
41+
//calculate minimum size required for ruler text
42+
QFont rulerFont = p.font();
43+
rulerFont.setPointSize( 8 );
44+
QFontMetrics rulerFontMetrics( rulerFont );
45+
//minimum gap required between major ticks is 3 digits * 250%
46+
double minFontPixelsWidth = rulerFontMetrics.width( "000" ) * 2.5;
47+
p.setFont( rulerFont );
4148

42-
double mmDisplay = 50.0;
43-
if ( pixelDiff1 > 25 )
44-
{
45-
mmDisplay = 1.0;
46-
}
47-
else if ( pixelDiff10 > 25 )
48-
{
49-
mmDisplay = 10.0;
50-
}
49+
//find optimum scale for ruler (size of numbered divisions)
50+
int magnitude = 1;
51+
int multiple = 1;
52+
int mmDisplay;
53+
mmDisplay = optimumScale( minFontPixelsWidth, magnitude, multiple );
54+
55+
//find optimum number of small divisions
56+
int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
5157

5258
if ( mDirection == Horizontal )
5359
{
@@ -60,20 +66,25 @@ void QgsComposerRuler::paintEvent( QPaintEvent* event )
6066
double startX = t.map( QPointF( 0, 0 ) ).x();
6167
double endX = t.map( QPointF( width(), 0 ) ).x();
6268

63-
double markerPos = ( floor( startX / mmDisplay ) + 1 ) * mmDisplay; //marker position in mm
69+
//start marker position in mm
70+
double markerPos = ( floor( startX / mmDisplay ) + 1 ) * mmDisplay;
71+
72+
//draw minor ticks marks which occur before first major tick
73+
drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
74+
6475
while ( markerPos <= endX )
6576
{
66-
if ( markerPos >= 0 && markerPos <= mComposition->paperWidth() ) //todo: need to know paper size
67-
{
68-
double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
69-
p.drawLine( pixelCoord, 0, pixelCoord, RULER_MIN_SIZE );
70-
p.drawText( QPointF( pixelCoord + 2, RULER_MIN_SIZE / 2.0 ), QString::number(( int )( markerPos ) ) );
71-
}
77+
double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
78+
79+
//draw large division and text
80+
p.drawLine( pixelCoord, 0, pixelCoord, RULER_MIN_SIZE );
81+
p.drawText( QPointF( pixelCoord + 2, RULER_MIN_SIZE / 2.0 + 2 ), QString::number( markerPos ) );
82+
83+
//draw small divisions
84+
drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
85+
7286
markerPos += mmDisplay;
7387
}
74-
75-
p.setPen( QColor( Qt::red ) );
76-
p.drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), RULER_MIN_SIZE );
7788
}
7889
else //vertical
7990
{
@@ -89,36 +100,240 @@ void QgsComposerRuler::paintEvent( QPaintEvent* event )
89100
{
90101
startPage = 0;
91102
}
103+
104+
if ( startY < 0 )
105+
{
106+
double beforePageCoord = 0;
107+
double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
108+
109+
//draw negative rulers which fall before first page
110+
while ( beforePageCoord > startY )
111+
{
112+
double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
113+
p.drawLine( 0, pixelCoord, RULER_MIN_SIZE, pixelCoord );
114+
//calc size of label
115+
QString label = QString::number( beforePageCoord );
116+
int labelSize = rulerFontMetrics.width( label );
117+
118+
//draw label only if it fits in before start of next page
119+
if ( pixelCoord + labelSize + 8 < firstPageY )
120+
{
121+
drawRotatedText( &p, QPointF( RULER_MIN_SIZE / 2.0 + 2.0, pixelCoord + 4.0 + labelSize ), label );
122+
}
123+
124+
//draw small divisions
125+
drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
126+
127+
beforePageCoord -= mmDisplay;
128+
}
129+
130+
//draw minor ticks marks which occur before first major tick
131+
drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
132+
}
133+
92134
int endPage = ( int )( endY / ( mComposition->paperHeight() + mComposition->spaceBetweenPages() ) );
93135
if ( endPage > ( mComposition->numPages() - 1 ) )
94136
{
95137
endPage = mComposition->numPages() - 1;
96138
}
97139

140+
double nextPageStartPos = 0;
141+
int nextPageStartPixel = 0;
142+
98143
for ( int i = startPage; i <= endPage; ++i )
99144
{
100145
double pageCoord = 0; //page coordinate in mm
101146
//total (composition) coordinate in mm, including space between pages
102147
double totalCoord = i * ( mComposition->paperHeight() + mComposition->spaceBetweenPages() );
103-
while ( pageCoord < mComposition->paperHeight() )
148+
149+
//position of next page
150+
if ( i < endPage )
151+
{
152+
//not the last page
153+
nextPageStartPos = ( i + 1 ) * ( mComposition->paperHeight() + mComposition->spaceBetweenPages() );
154+
nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
155+
}
156+
else
157+
{
158+
//is the last page
159+
nextPageStartPos = 0;
160+
nextPageStartPixel = 0;
161+
}
162+
163+
while (( totalCoord < nextPageStartPos ) || (( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
104164
{
105-
if ( totalCoord > endY )
106-
{
107-
break;
108-
}
109165
double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
110166
p.drawLine( 0, pixelCoord, RULER_MIN_SIZE, pixelCoord );
111-
p.drawText( QPointF( 0, pixelCoord - 2.0 ), QString::number( pageCoord ) );
167+
//calc size of label
168+
QString label = QString::number( pageCoord );
169+
int labelSize = rulerFontMetrics.width( label );
170+
171+
//draw label only if it fits in before start of next page
172+
if (( pixelCoord + labelSize + 8 < nextPageStartPixel )
173+
|| ( nextPageStartPixel == 0 ) )
174+
{
175+
drawRotatedText( &p, QPointF( RULER_MIN_SIZE / 2.0 + 2.0, pixelCoord + 4.0 + labelSize ), label );
176+
}
177+
178+
//draw small divisions
179+
drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
180+
112181
pageCoord += mmDisplay;
113182
totalCoord += mmDisplay;
114183
}
115184
}
185+
}
186+
187+
//draw current marker pos
188+
drawMarkerPos( &p );
189+
}
116190

117-
p.setPen( QColor( Qt::red ) );
118-
p.drawLine( 0, mMarkerPos.y(), RULER_MIN_SIZE, mMarkerPos.y() );
191+
void QgsComposerRuler::drawMarkerPos( QPainter *painter )
192+
{
193+
//draw current marker pos in red
194+
painter->setPen( QColor( Qt::red ) );
195+
if ( mDirection == Horizontal )
196+
{
197+
painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), RULER_MIN_SIZE );
119198
}
199+
else
200+
{
201+
painter->drawLine( 0, mMarkerPos.y(), RULER_MIN_SIZE, mMarkerPos.y() );
202+
}
203+
}
204+
205+
void QgsComposerRuler::drawRotatedText( QPainter *painter, QPointF pos, const QString &text )
206+
{
207+
painter->save();
208+
painter->translate( pos.x(), pos.y() );
209+
painter->rotate( 270 );
210+
painter->drawText( 0, 0, text );
211+
painter->restore();
120212
}
121213

214+
void QgsComposerRuler::drawSmallDivisions( QPainter *painter, double startPos, int numDivisions, double rulerScale, double maxPos )
215+
{
216+
//draw small divisions starting at startPos (in mm)
217+
double smallMarkerPos = startPos;
218+
double smallDivisionSpacing = rulerScale / numDivisions;
219+
220+
double pixelCoord;
221+
222+
//draw numDivisions small divisions
223+
for ( int i = 0; i < numDivisions; ++i )
224+
{
225+
smallMarkerPos += smallDivisionSpacing;
226+
227+
if ( maxPos > 0 && smallMarkerPos > maxPos )
228+
{
229+
//stop drawing current division position is past maxPos
230+
return;
231+
}
232+
233+
//calculate pixelCoordinate of the current division
234+
if ( mDirection == Horizontal )
235+
{
236+
pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
237+
}
238+
else
239+
{
240+
pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
241+
}
242+
243+
//calculate height of small division line
244+
double lineSize;
245+
if (( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
246+
{
247+
//if drawing the 5th line of 10 or drawing the 2nd line of 4, then draw it slightly longer
248+
lineSize = RULER_MIN_SIZE / 1.5;
249+
}
250+
else
251+
{
252+
lineSize = RULER_MIN_SIZE / 1.25;
253+
}
254+
255+
//draw either horizontal or vertical line depending on ruler direction
256+
if ( mDirection == Horizontal )
257+
{
258+
painter->drawLine( pixelCoord, lineSize, pixelCoord, RULER_MIN_SIZE );
259+
}
260+
else
261+
{
262+
painter->drawLine( lineSize, pixelCoord, RULER_MIN_SIZE, pixelCoord );
263+
}
264+
}
265+
}
266+
267+
int QgsComposerRuler::optimumScale( double minPixelDiff, int &magnitude, int &multiple )
268+
{
269+
//find optimal ruler display scale
270+
271+
//loop through magnitudes and multiples to find optimum scale
272+
for ( unsigned int magnitudeCandidate = 0; magnitudeCandidate < COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
273+
{
274+
for ( unsigned int multipleCandidate = 0; multipleCandidate < COUNT_VALID_MULTIPLES; ++multipleCandidate )
275+
{
276+
int candidateScale = validScaleMultiples[multipleCandidate] * validScaleMagnitudes[magnitudeCandidate];
277+
//find pixel size for each step using this candidate scale
278+
double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
279+
if ( pixelDiff > minPixelDiff )
280+
{
281+
//found the optimum major scale
282+
magnitude = validScaleMagnitudes[magnitudeCandidate];
283+
multiple = validScaleMultiples[multipleCandidate];
284+
return candidateScale;
285+
}
286+
}
287+
}
288+
289+
return 100000;
290+
}
291+
292+
int QgsComposerRuler::optimumNumberDivisions( double rulerScale, int scaleMultiple )
293+
{
294+
//calculate size in pixels of each marked ruler unit
295+
double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
296+
297+
//now calculate optimum small tick scale, depending on marked ruler units
298+
QList<int> validSmallDivisions;
299+
switch ( scaleMultiple )
300+
{
301+
case 1:
302+
//numbers increase by 1 increment each time, eg 1, 2, 3 or 10, 20, 30
303+
//so we can draw either 10, 5 or 2 small ticks and have each fall on a nice value
304+
validSmallDivisions << 10 << 5 << 2;
305+
break;
306+
case 2:
307+
//numbers increase by 2 increments each time, eg 2, 4, 6 or 20, 40, 60
308+
//so we can draw either 10, 4 or 2 small ticks and have each fall on a nice value
309+
validSmallDivisions << 10 << 4 << 2;
310+
break;
311+
case 5:
312+
//numbers increase by 5 increments each time, eg 5, 10, 15 or 100, 500, 1000
313+
//so we can draw either 10 or 5 small ticks and have each fall on a nice value
314+
validSmallDivisions << 10 << 5;
315+
break;
316+
}
317+
318+
//calculate the most number of small divisions we can draw without them being too close to each other
319+
QList<int>::iterator divisions_it;
320+
for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
321+
{
322+
//find pixel size for this small division
323+
double candidateSize = largeDivisionSize / ( *divisions_it );
324+
//small divisions must be seperated by at least 4 pixels
325+
if ( candidateSize >= 4 )
326+
{
327+
//found a good candidate, return it
328+
return ( *divisions_it );
329+
}
330+
}
331+
332+
//unable to find a good candidate
333+
return 0;
334+
}
335+
336+
122337
void QgsComposerRuler::setSceneTransform( const QTransform& transform )
123338
{
124339
QString debug = QString::number( transform.dx() ) + "," + QString::number( transform.dy() ) + ","

‎src/gui/qgscomposerruler.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class GUI_EXPORT QgsComposerRuler: public QWidget
3636
void mousePressEvent( QMouseEvent* event );
3737

3838
private:
39+
static const int validScaleMultiples[];
40+
static const int validScaleMagnitudes[];
41+
3942
Direction mDirection;
4043
QTransform mTransform;
4144
QPointF mMarkerPos;
@@ -46,6 +49,24 @@ class GUI_EXPORT QgsComposerRuler: public QWidget
4649

4750
void setSnapLinePosition( const QPointF& pos );
4851

52+
//calculate optimum labeled units for ruler so that labels are a good distance apart
53+
int optimumScale( double minPixelDiff, int &magnitude, int &multiple );
54+
//calculate number of small divisions for each ruler unit, ensuring that they
55+
//are sufficiently spaced
56+
int optimumNumberDivisions( double rulerScale, int scaleMultiple );
57+
58+
//draws vertical text on a painter
59+
void drawRotatedText( QPainter *painter, QPointF pos, const QString &text );
60+
61+
/* Draws small ruler divisions
62+
* Starting at startPos in mm, for numDivisions divisions, with major division spacing of rulerScale (in mm)
63+
* Stop drawing if position exceeds maxPos
64+
*/
65+
void drawSmallDivisions( QPainter *painter, double startPos, int numDivisions, double rulerScale, double maxPos = 0 );
66+
67+
//draw current marker pos on ruler
68+
void drawMarkerPos( QPainter *painter );
69+
4970
signals:
5071
/**Is emitted when mouse cursor coordinates change*/
5172
void cursorPosChanged( QPointF );

0 commit comments

Comments
 (0)
Please sign in to comment.