source: ntrip/branches/BNC_2.12/qwt/qwt_plot_zoomer.cpp@ 9196

Last change on this file since 9196 was 4271, checked in by mervart, 12 years ago
File size: 14.7 KB
Line 
1/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
2 * Qwt Widget Library
3 * Copyright (C) 1997 Josef Wilgen
4 * Copyright (C) 2002 Uwe Rathmann
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the Qwt License, Version 1.0
8 *****************************************************************************/
9
10#include "qwt_plot_zoomer.h"
11#include "qwt_plot.h"
12#include "qwt_plot_canvas.h"
13#include "qwt_scale_div.h"
14#include "qwt_picker_machine.h"
15#include <qalgorithms.h>
16
17class QwtPlotZoomer::PrivateData
18{
19public:
20 uint zoomRectIndex;
21 QStack<QRectF> zoomStack;
22
23 int maxStackDepth;
24};
25
26/*!
27 \brief Create a zoomer for a plot canvas.
28
29 The zoomer is set to those x- and y-axis of the parent plot of the
30 canvas that are enabled. If both or no x-axis are enabled, the picker
31 is set to QwtPlot::xBottom. If both or no y-axis are
32 enabled, it is set to QwtPlot::yLeft.
33
34 The zoomer is initialized with a QwtPickerDragRectMachine,
35 the tracker mode is set to QwtPicker::ActiveOnly and the rubberband
36 is set to QwtPicker::RectRubberBand
37
38 \param canvas Plot canvas to observe, also the parent object
39 \param doReplot Call replot for the attached plot before initializing
40 the zoomer with its scales. This might be necessary,
41 when the plot is in a state with pending scale changes.
42
43 \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase()
44*/
45QwtPlotZoomer::QwtPlotZoomer( QwtPlotCanvas *canvas, bool doReplot ):
46 QwtPlotPicker( canvas )
47{
48 if ( canvas )
49 init( doReplot );
50}
51
52/*!
53 \brief Create a zoomer for a plot canvas.
54
55 The zoomer is initialized with a QwtPickerDragRectMachine,
56 the tracker mode is set to QwtPicker::ActiveOnly and the rubberband
57 is set to QwtPicker;;RectRubberBand
58
59 \param xAxis X axis of the zoomer
60 \param yAxis Y axis of the zoomer
61 \param canvas Plot canvas to observe, also the parent object
62 \param doReplot Call replot for the attached plot before initializing
63 the zoomer with its scales. This might be necessary,
64 when the plot is in a state with pending scale changes.
65
66 \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase()
67*/
68
69QwtPlotZoomer::QwtPlotZoomer( int xAxis, int yAxis,
70 QwtPlotCanvas *canvas, bool doReplot ):
71 QwtPlotPicker( xAxis, yAxis, canvas )
72{
73 if ( canvas )
74 init( doReplot );
75}
76
77//! Init the zoomer, used by the constructors
78void QwtPlotZoomer::init( bool doReplot )
79{
80 d_data = new PrivateData;
81
82 d_data->maxStackDepth = -1;
83
84 setTrackerMode( ActiveOnly );
85 setRubberBand( RectRubberBand );
86 setStateMachine( new QwtPickerDragRectMachine() );
87
88 if ( doReplot && plot() )
89 plot()->replot();
90
91 setZoomBase( scaleRect() );
92}
93
94QwtPlotZoomer::~QwtPlotZoomer()
95{
96 delete d_data;
97}
98
99/*!
100 \brief Limit the number of recursive zoom operations to depth.
101
102 A value of -1 set the depth to unlimited, 0 disables zooming.
103 If the current zoom rectangle is below depth, the plot is unzoomed.
104
105 \param depth Maximum for the stack depth
106 \sa maxStackDepth()
107 \note depth doesn't include the zoom base, so zoomStack().count() might be
108 maxStackDepth() + 1.
109*/
110void QwtPlotZoomer::setMaxStackDepth( int depth )
111{
112 d_data->maxStackDepth = depth;
113
114 if ( depth >= 0 )
115 {
116 // unzoom if the current depth is below d_data->maxStackDepth
117
118 const int zoomOut =
119 int( d_data->zoomStack.count() ) - 1 - depth; // -1 for the zoom base
120
121 if ( zoomOut > 0 )
122 {
123 zoom( -zoomOut );
124 for ( int i = int( d_data->zoomStack.count() ) - 1;
125 i > int( d_data->zoomRectIndex ); i-- )
126 {
127 ( void )d_data->zoomStack.pop(); // remove trailing rects
128 }
129 }
130 }
131}
132
133/*!
134 \return Maximal depth of the zoom stack.
135 \sa setMaxStackDepth()
136*/
137int QwtPlotZoomer::maxStackDepth() const
138{
139 return d_data->maxStackDepth;
140}
141
142/*!
143 Return the zoom stack. zoomStack()[0] is the zoom base,
144 zoomStack()[1] the first zoomed rectangle.
145
146 \sa setZoomStack(), zoomRectIndex()
147*/
148const QStack<QRectF> &QwtPlotZoomer::zoomStack() const
149{
150 return d_data->zoomStack;
151}
152
153/*!
154 \return Initial rectangle of the zoomer
155 \sa setZoomBase(), zoomRect()
156*/
157QRectF QwtPlotZoomer::zoomBase() const
158{
159 return d_data->zoomStack[0];
160}
161
162/*!
163 Reinitialized the zoom stack with scaleRect() as base.
164
165 \param doReplot Call replot for the attached plot before initializing
166 the zoomer with its scales. This might be necessary,
167 when the plot is in a state with pending scale changes.
168
169 \sa zoomBase(), scaleRect() QwtPlot::autoReplot(), QwtPlot::replot().
170*/
171void QwtPlotZoomer::setZoomBase( bool doReplot )
172{
173 QwtPlot *plt = plot();
174 if ( plt == NULL )
175 return;
176
177 if ( doReplot )
178 plt->replot();
179
180 d_data->zoomStack.clear();
181 d_data->zoomStack.push( scaleRect() );
182 d_data->zoomRectIndex = 0;
183
184 rescale();
185}
186
187/*!
188 \brief Set the initial size of the zoomer.
189
190 base is united with the current scaleRect() and the zoom stack is
191 reinitalized with it as zoom base. plot is zoomed to scaleRect().
192
193 \param base Zoom base
194
195 \sa zoomBase(), scaleRect()
196*/
197void QwtPlotZoomer::setZoomBase( const QRectF &base )
198{
199 const QwtPlot *plt = plot();
200 if ( !plt )
201 return;
202
203 const QRectF sRect = scaleRect();
204 const QRectF bRect = base | sRect;
205
206 d_data->zoomStack.clear();
207 d_data->zoomStack.push( bRect );
208 d_data->zoomRectIndex = 0;
209
210 if ( base != sRect )
211 {
212 d_data->zoomStack.push( sRect );
213 d_data->zoomRectIndex++;
214 }
215
216 rescale();
217}
218
219/*!
220 Rectangle at the current position on the zoom stack.
221
222 \sa zoomRectIndex(), scaleRect().
223*/
224QRectF QwtPlotZoomer::zoomRect() const
225{
226 return d_data->zoomStack[d_data->zoomRectIndex];
227}
228
229/*!
230 \return Index of current position of zoom stack.
231*/
232uint QwtPlotZoomer::zoomRectIndex() const
233{
234 return d_data->zoomRectIndex;
235}
236
237/*!
238 \brief Zoom in
239
240 Clears all rectangles above the current position of the
241 zoom stack and pushs the normalized rect on it.
242
243 \note If the maximal stack depth is reached, zoom is ignored.
244 \note The zoomed signal is emitted.
245*/
246
247void QwtPlotZoomer::zoom( const QRectF &rect )
248{
249 if ( d_data->maxStackDepth >= 0 &&
250 int( d_data->zoomRectIndex ) >= d_data->maxStackDepth )
251 {
252 return;
253 }
254
255 const QRectF zoomRect = rect.normalized();
256 if ( zoomRect != d_data->zoomStack[d_data->zoomRectIndex] )
257 {
258 for ( uint i = int( d_data->zoomStack.count() ) - 1;
259 i > d_data->zoomRectIndex; i-- )
260 {
261 ( void )d_data->zoomStack.pop();
262 }
263
264 d_data->zoomStack.push( zoomRect );
265 d_data->zoomRectIndex++;
266
267 rescale();
268
269 Q_EMIT zoomed( zoomRect );
270 }
271}
272
273/*!
274 \brief Zoom in or out
275
276 Activate a rectangle on the zoom stack with an offset relative
277 to the current position. Negative values of offest will zoom out,
278 positive zoom in. A value of 0 zooms out to the zoom base.
279
280 \param offset Offset relative to the current position of the zoom stack.
281 \note The zoomed signal is emitted.
282 \sa zoomRectIndex()
283*/
284void QwtPlotZoomer::zoom( int offset )
285{
286 if ( offset == 0 )
287 d_data->zoomRectIndex = 0;
288 else
289 {
290 int newIndex = d_data->zoomRectIndex + offset;
291 newIndex = qMax( 0, newIndex );
292 newIndex = qMin( int( d_data->zoomStack.count() ) - 1, newIndex );
293
294 d_data->zoomRectIndex = uint( newIndex );
295 }
296
297 rescale();
298
299 Q_EMIT zoomed( zoomRect() );
300}
301
302/*!
303 \brief Assign a zoom stack
304
305 In combination with other types of navigation it might be useful to
306 modify to manipulate the complete zoom stack.
307
308 \param zoomStack New zoom stack
309 \param zoomRectIndex Index of the current position of zoom stack.
310 In case of -1 the current position is at the top
311 of the stack.
312
313 \note The zoomed signal might be emitted.
314 \sa zoomStack(), zoomRectIndex()
315*/
316void QwtPlotZoomer::setZoomStack(
317 const QStack<QRectF> &zoomStack, int zoomRectIndex )
318{
319 if ( zoomStack.isEmpty() )
320 return;
321
322 if ( d_data->maxStackDepth >= 0 &&
323 int( zoomStack.count() ) > d_data->maxStackDepth )
324 {
325 return;
326 }
327
328 if ( zoomRectIndex < 0 || zoomRectIndex > int( zoomStack.count() ) )
329 zoomRectIndex = zoomStack.count() - 1;
330
331 const bool doRescale = zoomStack[zoomRectIndex] != zoomRect();
332
333 d_data->zoomStack = zoomStack;
334 d_data->zoomRectIndex = uint( zoomRectIndex );
335
336 if ( doRescale )
337 {
338 rescale();
339 Q_EMIT zoomed( zoomRect() );
340 }
341}
342
343/*!
344 Adjust the observed plot to zoomRect()
345
346 \note Initiates QwtPlot::replot
347*/
348
349void QwtPlotZoomer::rescale()
350{
351 QwtPlot *plt = plot();
352 if ( !plt )
353 return;
354
355 const QRectF &rect = d_data->zoomStack[d_data->zoomRectIndex];
356 if ( rect != scaleRect() )
357 {
358 const bool doReplot = plt->autoReplot();
359 plt->setAutoReplot( false );
360
361 double x1 = rect.left();
362 double x2 = rect.right();
363 if ( plt->axisScaleDiv( xAxis() )->lowerBound() >
364 plt->axisScaleDiv( xAxis() )->upperBound() )
365 {
366 qSwap( x1, x2 );
367 }
368
369 plt->setAxisScale( xAxis(), x1, x2 );
370
371 double y1 = rect.top();
372 double y2 = rect.bottom();
373 if ( plt->axisScaleDiv( yAxis() )->lowerBound() >
374 plt->axisScaleDiv( yAxis() )->upperBound() )
375 {
376 qSwap( y1, y2 );
377 }
378 plt->setAxisScale( yAxis(), y1, y2 );
379
380 plt->setAutoReplot( doReplot );
381
382 plt->replot();
383 }
384}
385
386/*!
387 Reinitialize the axes, and set the zoom base to their scales.
388
389 \param xAxis X axis
390 \param yAxis Y axis
391*/
392
393void QwtPlotZoomer::setAxis( int xAxis, int yAxis )
394{
395 if ( xAxis != QwtPlotPicker::xAxis() || yAxis != QwtPlotPicker::yAxis() )
396 {
397 QwtPlotPicker::setAxis( xAxis, yAxis );
398 setZoomBase( scaleRect() );
399 }
400}
401
402/*!
403 Qt::MidButton zooms out one position on the zoom stack,
404 Qt::RightButton to the zoom base.
405
406 Changes the current position on the stack, but doesn't pop
407 any rectangle.
408
409 \note The mouse events can be changed, using
410 QwtEventPattern::setMousePattern: 2, 1
411*/
412void QwtPlotZoomer::widgetMouseReleaseEvent( QMouseEvent *me )
413{
414 if ( mouseMatch( MouseSelect2, me ) )
415 zoom( 0 );
416 else if ( mouseMatch( MouseSelect3, me ) )
417 zoom( -1 );
418 else if ( mouseMatch( MouseSelect6, me ) )
419 zoom( +1 );
420 else
421 QwtPlotPicker::widgetMouseReleaseEvent( me );
422}
423
424/*!
425 Qt::Key_Plus zooms in, Qt::Key_Minus zooms out one position on the
426 zoom stack, Qt::Key_Escape zooms out to the zoom base.
427
428 Changes the current position on the stack, but doesn't pop
429 any rectangle.
430
431 \note The keys codes can be changed, using
432 QwtEventPattern::setKeyPattern: 3, 4, 5
433*/
434
435void QwtPlotZoomer::widgetKeyPressEvent( QKeyEvent *ke )
436{
437 if ( !isActive() )
438 {
439 if ( keyMatch( KeyUndo, ke ) )
440 zoom( -1 );
441 else if ( keyMatch( KeyRedo, ke ) )
442 zoom( +1 );
443 else if ( keyMatch( KeyHome, ke ) )
444 zoom( 0 );
445 }
446
447 QwtPlotPicker::widgetKeyPressEvent( ke );
448}
449
450/*!
451 Move the current zoom rectangle.
452
453 \param dx X offset
454 \param dy Y offset
455
456 \note The changed rectangle is limited by the zoom base
457*/
458void QwtPlotZoomer::moveBy( double dx, double dy )
459{
460 const QRectF &rect = d_data->zoomStack[d_data->zoomRectIndex];
461 moveTo( QPointF( rect.left() + dx, rect.top() + dy ) );
462}
463
464/*!
465 Move the the current zoom rectangle.
466
467 \param pos New position
468
469 \sa QRectF::moveTo()
470 \note The changed rectangle is limited by the zoom base
471*/
472void QwtPlotZoomer::moveTo( const QPointF &pos )
473{
474 double x = pos.x();
475 double y = pos.y();
476
477 if ( x < zoomBase().left() )
478 x = zoomBase().left();
479 if ( x > zoomBase().right() - zoomRect().width() )
480 x = zoomBase().right() - zoomRect().width();
481
482 if ( y < zoomBase().top() )
483 y = zoomBase().top();
484 if ( y > zoomBase().bottom() - zoomRect().height() )
485 y = zoomBase().bottom() - zoomRect().height();
486
487 if ( x != zoomRect().left() || y != zoomRect().top() )
488 {
489 d_data->zoomStack[d_data->zoomRectIndex].moveTo( x, y );
490 rescale();
491 }
492}
493
494/*!
495 \brief Check and correct a selected rectangle
496
497 Reject rectangles with a hight or width < 2, otherwise
498 expand the selected rectangle to a minimum size of 11x11
499 and accept it.
500
501 \return true If rect is accepted, or has been changed
502 to a accepted rectangle.
503*/
504
505bool QwtPlotZoomer::accept( QPolygon &pa ) const
506{
507 if ( pa.count() < 2 )
508 return false;
509
510 QRect rect = QRect( pa[0], pa[int( pa.count() ) - 1] );
511 rect = rect.normalized();
512
513 const int minSize = 2;
514 if ( rect.width() < minSize && rect.height() < minSize )
515 return false;
516
517 const int minZoomSize = 11;
518
519 const QPoint center = rect.center();
520 rect.setSize( rect.size().expandedTo( QSize( minZoomSize, minZoomSize ) ) );
521 rect.moveCenter( center );
522
523 pa.resize( 2 );
524 pa[0] = rect.topLeft();
525 pa[1] = rect.bottomRight();
526
527 return true;
528}
529
530/*!
531 \brief Limit zooming by a minimum rectangle
532
533 \return zoomBase().width() / 10e4, zoomBase().height() / 10e4
534*/
535QSizeF QwtPlotZoomer::minZoomSize() const
536{
537 return QSizeF( d_data->zoomStack[0].width() / 10e4,
538 d_data->zoomStack[0].height() / 10e4 );
539}
540
541/*!
542 Rejects selections, when the stack depth is too deep, or
543 the zoomed rectangle is minZoomSize().
544
545 \sa minZoomSize(), maxStackDepth()
546*/
547void QwtPlotZoomer::begin()
548{
549 if ( d_data->maxStackDepth >= 0 )
550 {
551 if ( d_data->zoomRectIndex >= uint( d_data->maxStackDepth ) )
552 return;
553 }
554
555 const QSizeF minSize = minZoomSize();
556 if ( minSize.isValid() )
557 {
558 const QSizeF sz =
559 d_data->zoomStack[d_data->zoomRectIndex].size() * 0.9999;
560
561 if ( minSize.width() >= sz.width() &&
562 minSize.height() >= sz.height() )
563 {
564 return;
565 }
566 }
567
568 QwtPlotPicker::begin();
569}
570
571/*!
572 Expand the selected rectangle to minZoomSize() and zoom in
573 if accepted.
574
575 \sa accept(), minZoomSize()
576*/
577bool QwtPlotZoomer::end( bool ok )
578{
579 ok = QwtPlotPicker::end( ok );
580 if ( !ok )
581 return false;
582
583 QwtPlot *plot = QwtPlotZoomer::plot();
584 if ( !plot )
585 return false;
586
587 const QPolygon &pa = selection();
588 if ( pa.count() < 2 )
589 return false;
590
591 QRect rect = QRect( pa[0], pa[int( pa.count() - 1 )] );
592 rect = rect.normalized();
593
594 QRectF zoomRect = invTransform( rect ).normalized();
595
596 const QSizeF minSize = minZoomSize();
597 if ( minSize.isValid() )
598 {
599 const QPointF center = zoomRect.center();
600 zoomRect.setSize( zoomRect.size().expandedTo( minZoomSize() ) );
601 zoomRect.moveCenter( center );
602 }
603
604 zoom( zoomRect );
605
606 return true;
607}
Note: See TracBrowser for help on using the repository browser.