source: ntrip/trunk/BNC/qwt/qwt_plot_zoomer.cpp@ 9061

Last change on this file since 9061 was 8127, checked in by stoecker, 8 years ago

update qwt and qwtpolar, many QT5 fixes (unfinished)

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