7
7
#include < cmath>
8
8
9
9
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 };
10
14
11
15
QgsComposerRuler::QgsComposerRuler ( QgsComposerRuler::Direction d ): QWidget( 0 ), mDirection( d ), mComposition( 0 ), mLineSnapItem( 0 )
12
16
{
@@ -34,20 +38,22 @@ void QgsComposerRuler::paintEvent( QPaintEvent* event )
34
38
35
39
QTransform t = mTransform .inverted ();
36
40
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 );
41
48
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 );
51
57
52
58
if ( mDirection == Horizontal )
53
59
{
@@ -60,20 +66,25 @@ void QgsComposerRuler::paintEvent( QPaintEvent* event )
60
66
double startX = t.map ( QPointF ( 0 , 0 ) ).x ();
61
67
double endX = t.map ( QPointF ( width (), 0 ) ).x ();
62
68
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
+
64
75
while ( markerPos <= endX )
65
76
{
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
+
72
86
markerPos += mmDisplay;
73
87
}
74
-
75
- p.setPen ( QColor ( Qt::red ) );
76
- p.drawLine ( mMarkerPos .x (), 0 , mMarkerPos .x (), RULER_MIN_SIZE );
77
88
}
78
89
else // vertical
79
90
{
@@ -89,36 +100,240 @@ void QgsComposerRuler::paintEvent( QPaintEvent* event )
89
100
{
90
101
startPage = 0 ;
91
102
}
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
+
92
134
int endPage = ( int )( endY / ( mComposition ->paperHeight () + mComposition ->spaceBetweenPages () ) );
93
135
if ( endPage > ( mComposition ->numPages () - 1 ) )
94
136
{
95
137
endPage = mComposition ->numPages () - 1 ;
96
138
}
97
139
140
+ double nextPageStartPos = 0 ;
141
+ int nextPageStartPixel = 0 ;
142
+
98
143
for ( int i = startPage; i <= endPage; ++i )
99
144
{
100
145
double pageCoord = 0 ; // page coordinate in mm
101
146
// total (composition) coordinate in mm, including space between pages
102
147
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 ) ) )
104
164
{
105
- if ( totalCoord > endY )
106
- {
107
- break ;
108
- }
109
165
double pixelCoord = mTransform .map ( QPointF ( 0 , totalCoord ) ).y ();
110
166
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
+
112
181
pageCoord += mmDisplay;
113
182
totalCoord += mmDisplay;
114
183
}
115
184
}
185
+ }
186
+
187
+ // draw current marker pos
188
+ drawMarkerPos ( &p );
189
+ }
116
190
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 );
119
198
}
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 ();
120
212
}
121
213
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
+
122
337
void QgsComposerRuler::setSceneTransform ( const QTransform& transform )
123
338
{
124
339
QString debug = QString::number ( transform.dx () ) + " ," + QString::number ( transform.dy () ) + " ,"
0 commit comments