Skip to content
qwt_plot_zoomer.cpp 14.8 KiB
Newer Older
pixhawk's avatar
pixhawk committed
/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
 * Qwt Widget Library
 * Copyright (C) 1997   Josef Wilgen
 * Copyright (C) 2002   Uwe Rathmann
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the Qwt License, Version 1.0
 *****************************************************************************/

#include "qwt_plot_zoomer.h"
Bryant's avatar
Bryant committed
#include "qwt_plot.h"
pixhawk's avatar
pixhawk committed
#include "qwt_scale_div.h"
Bryant's avatar
Bryant committed
#include "qwt_picker_machine.h"
#include <qalgorithms.h>
pixhawk's avatar
pixhawk committed

class QwtPlotZoomer::PrivateData
{
public:
    uint zoomRectIndex;
Bryant's avatar
Bryant committed
    QStack<QRectF> zoomStack;
pixhawk's avatar
pixhawk committed

    int maxStackDepth;
};

/*!
  \brief Create a zoomer for a plot canvas.

  The zoomer is set to those x- and y-axis of the parent plot of the
  canvas that are enabled. If both or no x-axis are enabled, the picker
  is set to QwtPlot::xBottom. If both or no y-axis are
  enabled, it is set to QwtPlot::yLeft.

Bryant's avatar
Bryant committed
  The zoomer is initialized with a QwtPickerDragRectMachine,
  the tracker mode is set to QwtPicker::ActiveOnly and the rubber band
  is set to QwtPicker::RectRubberBand
pixhawk's avatar
pixhawk committed

  \param canvas Plot canvas to observe, also the parent object
Bryant's avatar
Bryant committed
  \param doReplot Call QwtPlot::replot() for the attached plot before initializing
                  the zoomer with its scales. This might be necessary,
pixhawk's avatar
pixhawk committed
                  when the plot is in a state with pending scale changes.

  \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase()
*/
Bryant's avatar
Bryant committed
QwtPlotZoomer::QwtPlotZoomer( QWidget *canvas, bool doReplot ):
    QwtPlotPicker( canvas )
pixhawk's avatar
pixhawk committed
{
    if ( canvas )
Bryant's avatar
Bryant committed
        init( doReplot );
pixhawk's avatar
pixhawk committed
}

/*!
  \brief Create a zoomer for a plot canvas.

Bryant's avatar
Bryant committed
  The zoomer is initialized with a QwtPickerDragRectMachine,
  the tracker mode is set to QwtPicker::ActiveOnly and the rubber band
  is set to QwtPicker;;RectRubberBand
pixhawk's avatar
pixhawk committed

  \param xAxis X axis of the zoomer
  \param yAxis Y axis of the zoomer
  \param canvas Plot canvas to observe, also the parent object
Bryant's avatar
Bryant committed
  \param doReplot Call QwtPlot::replot() for the attached plot before initializing
                  the zoomer with its scales. This might be necessary,
pixhawk's avatar
pixhawk committed
                  when the plot is in a state with pending scale changes.

  \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase()
*/

Bryant's avatar
Bryant committed
QwtPlotZoomer::QwtPlotZoomer( int xAxis, int yAxis,
        QWidget *canvas, bool doReplot ):
    QwtPlotPicker( xAxis, yAxis, canvas )
pixhawk's avatar
pixhawk committed
{
    if ( canvas )
Bryant's avatar
Bryant committed
        init( doReplot );
pixhawk's avatar
pixhawk committed
}

//! Init the zoomer, used by the constructors
Bryant's avatar
Bryant committed
void QwtPlotZoomer::init( bool doReplot )
pixhawk's avatar
pixhawk committed
{
    d_data = new PrivateData;

    d_data->maxStackDepth = -1;

Bryant's avatar
Bryant committed
    setTrackerMode( ActiveOnly );
    setRubberBand( RectRubberBand );
    setStateMachine( new QwtPickerDragRectMachine() );
pixhawk's avatar
pixhawk committed

    if ( doReplot && plot() )
        plot()->replot();

Bryant's avatar
Bryant committed
    setZoomBase( scaleRect() );
pixhawk's avatar
pixhawk committed
}

QwtPlotZoomer::~QwtPlotZoomer()
{
    delete d_data;
}

/*!
  \brief Limit the number of recursive zoom operations to depth.

  A value of -1 set the depth to unlimited, 0 disables zooming.
  If the current zoom rectangle is below depth, the plot is unzoomed.

  \param depth Maximum for the stack depth
  \sa maxStackDepth()
  \note depth doesn't include the zoom base, so zoomStack().count() might be
              maxStackDepth() + 1.
*/
Bryant's avatar
Bryant committed
void QwtPlotZoomer::setMaxStackDepth( int depth )
pixhawk's avatar
pixhawk committed
{
    d_data->maxStackDepth = depth;

Bryant's avatar
Bryant committed
    if ( depth >= 0 )
    {
pixhawk's avatar
pixhawk committed
        // unzoom if the current depth is below d_data->maxStackDepth

        const int zoomOut =
Bryant's avatar
Bryant committed
            int( d_data->zoomStack.count() ) - 1 - depth; // -1 for the zoom base

        if ( zoomOut > 0 )
        {
            zoom( -zoomOut );
            for ( int i = int( d_data->zoomStack.count() ) - 1;
                i > int( d_data->zoomRectIndex ); i-- )
            {
                ( void )d_data->zoomStack.pop(); // remove trailing rects
pixhawk's avatar
pixhawk committed
            }
        }
    }
}

/*!
  \return Maximal depth of the zoom stack.
  \sa setMaxStackDepth()
*/
int QwtPlotZoomer::maxStackDepth() const
{
    return d_data->maxStackDepth;
}

/*!
Bryant's avatar
Bryant committed
  \return The zoom stack. zoomStack()[0] is the zoom base,
          zoomStack()[1] the first zoomed rectangle.
pixhawk's avatar
pixhawk committed

  \sa setZoomStack(), zoomRectIndex()
*/
Bryant's avatar
Bryant committed
const QStack<QRectF> &QwtPlotZoomer::zoomStack() const
pixhawk's avatar
pixhawk committed
{
    return d_data->zoomStack;
}

/*!
  \return Initial rectangle of the zoomer
  \sa setZoomBase(), zoomRect()
*/
Bryant's avatar
Bryant committed
QRectF QwtPlotZoomer::zoomBase() const
pixhawk's avatar
pixhawk committed
{
    return d_data->zoomStack[0];
}

/*!
  Reinitialized the zoom stack with scaleRect() as base.

Bryant's avatar
Bryant committed
  \param doReplot Call QwtPlot::replot() for the attached plot before initializing
                  the zoomer with its scales. This might be necessary,
pixhawk's avatar
pixhawk committed
                  when the plot is in a state with pending scale changes.

  \sa zoomBase(), scaleRect() QwtPlot::autoReplot(), QwtPlot::replot().
*/
Bryant's avatar
Bryant committed
void QwtPlotZoomer::setZoomBase( bool doReplot )
pixhawk's avatar
pixhawk committed
{
    QwtPlot *plt = plot();
    if ( plt == NULL )
        return;

    if ( doReplot )
        plt->replot();

    d_data->zoomStack.clear();
Bryant's avatar
Bryant committed
    d_data->zoomStack.push( scaleRect() );
pixhawk's avatar
pixhawk committed
    d_data->zoomRectIndex = 0;

    rescale();
}

/*!
  \brief Set the initial size of the zoomer.

  base is united with the current scaleRect() and the zoom stack is
Bryant's avatar
Bryant committed
  reinitialized with it as zoom base. plot is zoomed to scaleRect().
pixhawk's avatar
pixhawk committed
  \param base Zoom base
pixhawk's avatar
pixhawk committed
  \sa zoomBase(), scaleRect()
*/
Bryant's avatar
Bryant committed
void QwtPlotZoomer::setZoomBase( const QRectF &base )
pixhawk's avatar
pixhawk committed
{
    const QwtPlot *plt = plot();
    if ( !plt )
        return;

Bryant's avatar
Bryant committed
    const QRectF sRect = scaleRect();
    const QRectF bRect = base | sRect;
pixhawk's avatar
pixhawk committed

    d_data->zoomStack.clear();
Bryant's avatar
Bryant committed
    d_data->zoomStack.push( bRect );
pixhawk's avatar
pixhawk committed
    d_data->zoomRectIndex = 0;

Bryant's avatar
Bryant committed
    if ( base != sRect )
    {
        d_data->zoomStack.push( sRect );
pixhawk's avatar
pixhawk committed
        d_data->zoomRectIndex++;
    }

    rescale();
}

Bryant's avatar
Bryant committed
  \return Rectangle at the current position on the zoom stack.
pixhawk's avatar
pixhawk committed
  \sa zoomRectIndex(), scaleRect().
*/
Bryant's avatar
Bryant committed
QRectF QwtPlotZoomer::zoomRect() const
pixhawk's avatar
pixhawk committed
{
    return d_data->zoomStack[d_data->zoomRectIndex];
}

pixhawk's avatar
pixhawk committed
  \return Index of current position of zoom stack.
*/
uint QwtPlotZoomer::zoomRectIndex() const
{
    return d_data->zoomRectIndex;
}

/*!
  \brief Zoom in

  Clears all rectangles above the current position of the
Bryant's avatar
Bryant committed
  zoom stack and pushes the normalized rectangle on it.
pixhawk's avatar
pixhawk committed

  \note If the maximal stack depth is reached, zoom is ignored.
  \note The zoomed signal is emitted.
*/

Bryant's avatar
Bryant committed
void QwtPlotZoomer::zoom( const QRectF &rect )
{
    if ( d_data->maxStackDepth >= 0 &&
Bryant's avatar
Bryant committed
            int( d_data->zoomRectIndex ) >= d_data->maxStackDepth )
    {
pixhawk's avatar
pixhawk committed
        return;
    }

Bryant's avatar
Bryant committed
    const QRectF zoomRect = rect.normalized();
    if ( zoomRect != d_data->zoomStack[d_data->zoomRectIndex] )
    {
        for ( uint i = int( d_data->zoomStack.count() ) - 1;
           i > d_data->zoomRectIndex; i-- )
        {
            ( void )d_data->zoomStack.pop();
pixhawk's avatar
pixhawk committed
        }

Bryant's avatar
Bryant committed
        d_data->zoomStack.push( zoomRect );
pixhawk's avatar
pixhawk committed
        d_data->zoomRectIndex++;

        rescale();

Bryant's avatar
Bryant committed
        Q_EMIT zoomed( zoomRect );
pixhawk's avatar
pixhawk committed
    }
}

/*!
  \brief Zoom in or out

  Activate a rectangle on the zoom stack with an offset relative
Bryant's avatar
Bryant committed
  to the current position. Negative values of offset will zoom out,
pixhawk's avatar
pixhawk committed
  positive zoom in. A value of 0 zooms out to the zoom base.

  \param offset Offset relative to the current position of the zoom stack.
  \note The zoomed signal is emitted.
  \sa zoomRectIndex()
*/
Bryant's avatar
Bryant committed
void QwtPlotZoomer::zoom( int offset )
pixhawk's avatar
pixhawk committed
{
    if ( offset == 0 )
        d_data->zoomRectIndex = 0;
Bryant's avatar
Bryant committed
    else
    {
pixhawk's avatar
pixhawk committed
        int newIndex = d_data->zoomRectIndex + offset;
Bryant's avatar
Bryant committed
        newIndex = qMax( 0, newIndex );
        newIndex = qMin( int( d_data->zoomStack.count() ) - 1, newIndex );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
        d_data->zoomRectIndex = uint( newIndex );
pixhawk's avatar
pixhawk committed
    }

    rescale();

Bryant's avatar
Bryant committed
    Q_EMIT zoomed( zoomRect() );
pixhawk's avatar
pixhawk committed
}

/*!
  \brief Assign a zoom stack

  In combination with other types of navigation it might be useful to
  modify to manipulate the complete zoom stack.

  \param zoomStack New zoom stack
  \param zoomRectIndex Index of the current position of zoom stack.
                       In case of -1 the current position is at the top
                       of the stack.

  \note The zoomed signal might be emitted.
  \sa zoomStack(), zoomRectIndex()
*/
void QwtPlotZoomer::setZoomStack(
Bryant's avatar
Bryant committed
    const QStack<QRectF> &zoomStack, int zoomRectIndex )
pixhawk's avatar
pixhawk committed
{
    if ( zoomStack.isEmpty() )
        return;

    if ( d_data->maxStackDepth >= 0 &&
Bryant's avatar
Bryant committed
        int( zoomStack.count() ) > d_data->maxStackDepth )
    {
pixhawk's avatar
pixhawk committed
        return;
    }

Bryant's avatar
Bryant committed
    if ( zoomRectIndex < 0 || zoomRectIndex > int( zoomStack.count() ) )
pixhawk's avatar
pixhawk committed
        zoomRectIndex = zoomStack.count() - 1;

    const bool doRescale = zoomStack[zoomRectIndex] != zoomRect();

    d_data->zoomStack = zoomStack;
Bryant's avatar
Bryant committed
    d_data->zoomRectIndex = uint( zoomRectIndex );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    if ( doRescale )
    {
pixhawk's avatar
pixhawk committed
        rescale();
Bryant's avatar
Bryant committed
        Q_EMIT zoomed( zoomRect() );
pixhawk's avatar
pixhawk committed
    }
}

pixhawk's avatar
pixhawk committed
  Adjust the observed plot to zoomRect()

Bryant's avatar
Bryant committed
  \note Initiates QwtPlot::replot()
pixhawk's avatar
pixhawk committed
*/

void QwtPlotZoomer::rescale()
{
    QwtPlot *plt = plot();
    if ( !plt )
        return;

Bryant's avatar
Bryant committed
    const QRectF &rect = d_data->zoomStack[d_data->zoomRectIndex];
    if ( rect != scaleRect() )
    {
pixhawk's avatar
pixhawk committed
        const bool doReplot = plt->autoReplot();
Bryant's avatar
Bryant committed
        plt->setAutoReplot( false );
pixhawk's avatar
pixhawk committed

        double x1 = rect.left();
        double x2 = rect.right();
Bryant's avatar
Bryant committed
        if ( !plt->axisScaleDiv( xAxis() ).isIncreasing() )
            qSwap( x1, x2 );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
        plt->setAxisScale( xAxis(), x1, x2 );
pixhawk's avatar
pixhawk committed

        double y1 = rect.top();
        double y2 = rect.bottom();
Bryant's avatar
Bryant committed
        if ( !plt->axisScaleDiv( yAxis() ).isIncreasing() )
            qSwap( y1, y2 );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
        plt->setAxisScale( yAxis(), y1, y2 );

        plt->setAutoReplot( doReplot );
pixhawk's avatar
pixhawk committed

        plt->replot();
    }
}

/*!
  Reinitialize the axes, and set the zoom base to their scales.

  \param xAxis X axis
pixhawk's avatar
pixhawk committed
  \param yAxis Y axis
*/

Bryant's avatar
Bryant committed
void QwtPlotZoomer::setAxis( int xAxis, int yAxis )
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    if ( xAxis != QwtPlotPicker::xAxis() || yAxis != QwtPlotPicker::yAxis() )
    {
        QwtPlotPicker::setAxis( xAxis, yAxis );
        setZoomBase( scaleRect() );
pixhawk's avatar
pixhawk committed
    }
}

/*!
   Qt::MidButton zooms out one position on the zoom stack,
   Qt::RightButton to the zoom base.

   Changes the current position on the stack, but doesn't pop
   any rectangle.

   \note The mouse events can be changed, using
         QwtEventPattern::setMousePattern: 2, 1
*/
Bryant's avatar
Bryant committed
void QwtPlotZoomer::widgetMouseReleaseEvent( QMouseEvent *me )
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    if ( mouseMatch( MouseSelect2, me ) )
        zoom( 0 );
    else if ( mouseMatch( MouseSelect3, me ) )
        zoom( -1 );
    else if ( mouseMatch( MouseSelect6, me ) )
        zoom( +1 );
Bryant's avatar
Bryant committed
        QwtPlotPicker::widgetMouseReleaseEvent( me );
pixhawk's avatar
pixhawk committed
}

/*!
Bryant's avatar
Bryant committed
   Qt::Key_Plus zooms in, Qt::Key_Minus zooms out one position on the
pixhawk's avatar
pixhawk committed
   zoom stack, Qt::Key_Escape zooms out to the zoom base.

   Changes the current position on the stack, but doesn't pop
   any rectangle.

   \note The keys codes can be changed, using
         QwtEventPattern::setKeyPattern: 3, 4, 5
*/

Bryant's avatar
Bryant committed
void QwtPlotZoomer::widgetKeyPressEvent( QKeyEvent *ke )
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    if ( !isActive() )
    {
        if ( keyMatch( KeyUndo, ke ) )
            zoom( -1 );
        else if ( keyMatch( KeyRedo, ke ) )
            zoom( +1 );
        else if ( keyMatch( KeyHome, ke ) )
            zoom( 0 );
pixhawk's avatar
pixhawk committed
    }

Bryant's avatar
Bryant committed
    QwtPlotPicker::widgetKeyPressEvent( ke );
pixhawk's avatar
pixhawk committed
}

/*!
  Move the current zoom rectangle.

  \param dx X offset
  \param dy Y offset

  \note The changed rectangle is limited by the zoom base
*/
Bryant's avatar
Bryant committed
void QwtPlotZoomer::moveBy( double dx, double dy )
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    const QRectF &rect = d_data->zoomStack[d_data->zoomRectIndex];
    moveTo( QPointF( rect.left() + dx, rect.top() + dy ) );
pixhawk's avatar
pixhawk committed
}

/*!
  Move the the current zoom rectangle.

Bryant's avatar
Bryant committed
  \param pos New position
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
  \sa QRectF::moveTo()
pixhawk's avatar
pixhawk committed
  \note The changed rectangle is limited by the zoom base
*/
Bryant's avatar
Bryant committed
void QwtPlotZoomer::moveTo( const QPointF &pos )
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    double x = pos.x();
    double y = pos.y();

pixhawk's avatar
pixhawk committed
    if ( x < zoomBase().left() )
        x = zoomBase().left();
    if ( x > zoomBase().right() - zoomRect().width() )
        x = zoomBase().right() - zoomRect().width();

    if ( y < zoomBase().top() )
        y = zoomBase().top();
    if ( y > zoomBase().bottom() - zoomRect().height() )
        y = zoomBase().bottom() - zoomRect().height();

Bryant's avatar
Bryant committed
    if ( x != zoomRect().left() || y != zoomRect().top() )
    {
        d_data->zoomStack[d_data->zoomRectIndex].moveTo( x, y );
pixhawk's avatar
pixhawk committed
        rescale();
    }
}

/*!
  \brief Check and correct a selected rectangle

Bryant's avatar
Bryant committed
  Reject rectangles with a height or width < 2, otherwise
pixhawk's avatar
pixhawk committed
  expand the selected rectangle to a minimum size of 11x11
  and accept it.
Bryant's avatar
Bryant committed
  \return true If the rectangle is accepted, or has been changed
          to an accepted one.
pixhawk's avatar
pixhawk committed
*/

Bryant's avatar
Bryant committed
bool QwtPlotZoomer::accept( QPolygon &pa ) const
pixhawk's avatar
pixhawk committed
{
    if ( pa.count() < 2 )
        return false;

Bryant's avatar
Bryant committed
    QRect rect = QRect( pa[0], pa[int( pa.count() ) - 1] );
pixhawk's avatar
pixhawk committed
    rect = rect.normalized();

    const int minSize = 2;
Bryant's avatar
Bryant committed
    if ( rect.width() < minSize && rect.height() < minSize )
        return false;
pixhawk's avatar
pixhawk committed

    const int minZoomSize = 11;

    const QPoint center = rect.center();
Bryant's avatar
Bryant committed
    rect.setSize( rect.size().expandedTo( QSize( minZoomSize, minZoomSize ) ) );
    rect.moveCenter( center );
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    pa.resize( 2 );
pixhawk's avatar
pixhawk committed
    pa[0] = rect.topLeft();
    pa[1] = rect.bottomRight();

    return true;
}

/*!
  \brief Limit zooming by a minimum rectangle

  \return zoomBase().width() / 10e4, zoomBase().height() / 10e4
*/
Bryant's avatar
Bryant committed
QSizeF QwtPlotZoomer::minZoomSize() const
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    return QSizeF( d_data->zoomStack[0].width() / 10e4,
        d_data->zoomStack[0].height() / 10e4 );
pixhawk's avatar
pixhawk committed
}

pixhawk's avatar
pixhawk committed
  Rejects selections, when the stack depth is too deep, or
  the zoomed rectangle is minZoomSize().

  \sa minZoomSize(), maxStackDepth()
*/
void QwtPlotZoomer::begin()
{
Bryant's avatar
Bryant committed
    if ( d_data->maxStackDepth >= 0 )
    {
        if ( d_data->zoomRectIndex >= uint( d_data->maxStackDepth ) )
pixhawk's avatar
pixhawk committed
            return;
    }

Bryant's avatar
Bryant committed
    const QSizeF minSize = minZoomSize();
    if ( minSize.isValid() )
    {
        const QSizeF sz =
pixhawk's avatar
pixhawk committed
            d_data->zoomStack[d_data->zoomRectIndex].size() * 0.9999;

        if ( minSize.width() >= sz.width() &&
Bryant's avatar
Bryant committed
            minSize.height() >= sz.height() )
        {
pixhawk's avatar
pixhawk committed
            return;
        }
    }

    QwtPlotPicker::begin();
}

/*!
  Expand the selected rectangle to minZoomSize() and zoom in
  if accepted.

Bryant's avatar
Bryant committed
  \param ok If true, complete the selection and emit selected signals
            otherwise discard the selection.

  \sa accept(), minZoomSize()
  \return True if the selection has been accepted, false otherwise
pixhawk's avatar
pixhawk committed
*/
Bryant's avatar
Bryant committed
bool QwtPlotZoomer::end( bool ok )
pixhawk's avatar
pixhawk committed
{
Bryant's avatar
Bryant committed
    ok = QwtPlotPicker::end( ok );
    if ( !ok )
pixhawk's avatar
pixhawk committed
        return false;

    QwtPlot *plot = QwtPlotZoomer::plot();
    if ( !plot )
        return false;

Bryant's avatar
Bryant committed
    const QPolygon &pa = selection();
pixhawk's avatar
pixhawk committed
    if ( pa.count() < 2 )
        return false;

Bryant's avatar
Bryant committed
    QRect rect = QRect( pa[0], pa[int( pa.count() - 1 )] );
pixhawk's avatar
pixhawk committed
    rect = rect.normalized();

Bryant's avatar
Bryant committed
    QRectF zoomRect = invTransform( rect ).normalized();
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    const QSizeF minSize = minZoomSize();
    if ( minSize.isValid() )
    {
        const QPointF center = zoomRect.center();
        zoomRect.setSize( zoomRect.size().expandedTo( minZoomSize() ) );
        zoomRect.moveCenter( center );
    }
pixhawk's avatar
pixhawk committed

Bryant's avatar
Bryant committed
    zoom( zoomRect );
pixhawk's avatar
pixhawk committed

    return true;
}