/***************************************************************************
 *   Copyright (C) 2007 by Faubet Pierre   *
 *   pierre.faubet@e.ujf-grenoble.fr   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <qevent.h>

#include <qwt_plot_canvas.h>
#include <qwt_plot_layout.h>
#include <qwt_scale_engine.h>
#include <qwt_scale_widget.h>

#include "scrollbar.h"
#include "scrollzoomer.h"

class ScrollData
{
public:
    ScrollData():
            scrollBar(NULL),
            position(ScrollZoomer::OppositeToScale),
#if QT_VERSION < 0x040000
            mode(QScrollView::Auto)
#else
            mode(Qt::ScrollBarAsNeeded)
#endif
    {
    }

    ~ScrollData()
    {
        delete scrollBar;
    }

    ScrollBar *scrollBar;
    ScrollZoomer::ScrollBarPosition position;
#if QT_VERSION < 0x040000

    QScrollView::ScrollBarMode mode;
#else

    Qt::ScrollBarPolicy mode;
#endif
};

ScrollZoomer::ScrollZoomer(QwtPlotCanvas *canvas):
        QwtPlotZoomer(canvas),
        d_cornerWidget(NULL),
        d_hScrollData(NULL),
        d_vScrollData(NULL),
        d_inZoom(false)
{
    if ( !canvas )
        return;

    d_hScrollData = new ScrollData;
    d_vScrollData = new ScrollData;
}

ScrollZoomer::~ScrollZoomer()
{
    delete d_cornerWidget;
    delete d_vScrollData;
    delete d_hScrollData;
}

void ScrollZoomer::rescale()
{
    QwtScaleWidget *xScale = plot()->axisWidget(xAxis());
    QwtScaleWidget *yScale = plot()->axisWidget(yAxis());

    if ( zoomRectIndex() <= 0 )
    {
        if ( d_inZoom )
        {
            xScale->setMinBorderDist(0, 0);
            yScale->setMinBorderDist(0, 0);
            d_inZoom = false;
        }
    }
    else
    {
        if ( !d_inZoom )
        {
            /*
             We set a minimum border distance. 
             Otherwise the canvas size changes when scrolling, 
             between situations where the major ticks are at
             the canvas borders (requiring extra space for the label)
             and situations where all labels can be painted below/top
             or left/right of the canvas.
             */
            int start, end;

            xScale->getBorderDistHint(start, end);
            xScale->setMinBorderDist(start, end);

            yScale->getBorderDistHint(start, end);
            yScale->setMinBorderDist(start, end);

            d_inZoom = false;
        }
    }

    QwtPlotZoomer::rescale();
    updateScrollBars();
}

ScrollBar *ScrollZoomer::scrollBar(Qt::Orientation o)
{
    ScrollBar *&sb = (o == Qt::Vertical)
                     ? d_vScrollData->scrollBar : d_hScrollData->scrollBar;

    if ( sb == NULL )
    {
        sb = new ScrollBar(o, canvas());
        sb->hide();
        connect(sb,
                SIGNAL(valueChanged(Qt::Orientation, double, double)),
                SLOT(scrollBarMoved(Qt::Orientation, double, double)));
    }
    return sb;
}

ScrollBar *ScrollZoomer::horizontalScrollBar() const
{
    return d_hScrollData->scrollBar;
}

ScrollBar *ScrollZoomer::verticalScrollBar() const
{
    return d_vScrollData->scrollBar;
}

#if QT_VERSION < 0x040000
void ScrollZoomer::setHScrollBarMode(QScrollView::ScrollBarMode mode)
#else
void ScrollZoomer::setHScrollBarMode(Qt::ScrollBarPolicy mode)
#endif
{
    if ( hScrollBarMode() != mode )
    {
        d_hScrollData->mode = mode;
        updateScrollBars();
    }
}

#if QT_VERSION < 0x040000
void ScrollZoomer::setVScrollBarMode(QScrollView::ScrollBarMode mode)
#else
void ScrollZoomer::setVScrollBarMode(Qt::ScrollBarPolicy mode)
#endif
{
    if ( vScrollBarMode() != mode )
    {
        d_vScrollData->mode = mode;
        updateScrollBars();
    }
}

#if QT_VERSION < 0x040000
QScrollView::ScrollBarMode ScrollZoomer::hScrollBarMode() const
#else
Qt::ScrollBarPolicy ScrollZoomer::hScrollBarMode() const
#endif
{
    return d_hScrollData->mode;
}

#if QT_VERSION < 0x040000
QScrollView::ScrollBarMode ScrollZoomer::vScrollBarMode() const
#else
Qt::ScrollBarPolicy ScrollZoomer::vScrollBarMode() const
#endif
{
    return d_vScrollData->mode;
}

void ScrollZoomer::setHScrollBarPosition(ScrollBarPosition pos)
{
    if ( d_hScrollData->position != pos )
    {
        d_hScrollData->position = pos;
        updateScrollBars();
    }
}

void ScrollZoomer::setVScrollBarPosition(ScrollBarPosition pos)
{
    if ( d_vScrollData->position != pos )
    {
        d_vScrollData->position = pos;
        updateScrollBars();
    }
}

ScrollZoomer::ScrollBarPosition ScrollZoomer::hScrollBarPosition() const
{
    return d_hScrollData->position;
}

ScrollZoomer::ScrollBarPosition ScrollZoomer::vScrollBarPosition() const
{
    return d_vScrollData->position;
}

void ScrollZoomer::setCornerWidget(QWidget *w)
{
    if ( w != d_cornerWidget )
    {
        if ( canvas() )
        {
            delete d_cornerWidget;
            d_cornerWidget = w;
            if ( d_cornerWidget->parent() != canvas() )
            {
#if QT_VERSION < 0x040000
                d_cornerWidget->reparent(canvas(), QPoint(0, 0));
#else

                d_cornerWidget->setParent(canvas());
#endif

            }

            updateScrollBars();
        }
    }
}

QWidget *ScrollZoomer::cornerWidget() const
{
    return d_cornerWidget;
}

bool ScrollZoomer::eventFilter(QObject *o, QEvent *e)
{
    if (  o == canvas() )
    {
        switch(e->type())
        {
        case QEvent::Resize:
            {
                const int fw = ((QwtPlotCanvas *)canvas())->frameWidth();

                QRect rect;
                rect.setSize(((QResizeEvent *)e)->size());
                rect.setRect(rect.x() + fw, rect.y() + fw,
                             rect.width() - 2 * fw, rect.height() - 2 * fw);

                layoutScrollBars(rect);
                break;
            }
        case QEvent::ChildRemoved:
            {
                const QObject *child = ((QChildEvent *)e)->child();
                if ( child == d_cornerWidget )
                    d_cornerWidget = NULL;
                else if ( child == d_hScrollData->scrollBar )
                    d_hScrollData->scrollBar = NULL;
                else if ( child == d_vScrollData->scrollBar )
                    d_vScrollData->scrollBar = NULL;
                break;
            }
        default:
            break;
        }
    }
    return QwtPlotZoomer::eventFilter(o, e);
}

bool ScrollZoomer::needScrollBar(Qt::Orientation o) const
{
#if QT_VERSION < 0x040000
    QScrollView::ScrollBarMode mode;
#else

    Qt::ScrollBarPolicy mode;
#endif

    double zoomMin, zoomMax, baseMin, baseMax;

    if ( o == Qt::Horizontal )
    {
        mode = d_hScrollData->mode;
        baseMin = zoomBase().left();
        baseMax = zoomBase().right();
        zoomMin = zoomRect().left();
        zoomMax = zoomRect().right();
    }
    else
    {
        mode = d_vScrollData->mode;
        baseMin = zoomBase().top();
        baseMax = zoomBase().bottom();
        zoomMin = zoomRect().top();
        zoomMax = zoomRect().bottom();
    }

    bool needed = false;
    switch(mode)
    {
#if QT_VERSION < 0x040000
    case QScrollView::AlwaysOn:
#else

    case Qt::ScrollBarAlwaysOn:
#endif

        needed = true;
        break;
#if QT_VERSION < 0x040000

    case QScrollView::AlwaysOff:
#else

    case Qt::ScrollBarAlwaysOff:
#endif

        needed = false;
        break;
    default:
        {
            if ( baseMin < zoomMin || baseMax > zoomMax )
                needed = true;
            break;
        }
    }
    return needed;
}

void ScrollZoomer::updateScrollBars()
{
    if ( !canvas() )
        return;

    const int xAxis = QwtPlotZoomer::xAxis();
    const int yAxis = QwtPlotZoomer::yAxis();

    int xScrollBarAxis = xAxis;
    if ( hScrollBarPosition() == OppositeToScale )
        xScrollBarAxis = oppositeAxis(xScrollBarAxis);

    int yScrollBarAxis = yAxis;
    if ( vScrollBarPosition() == OppositeToScale )
        yScrollBarAxis = oppositeAxis(yScrollBarAxis);


    QwtPlotLayout *layout = plot()->plotLayout();

    bool showHScrollBar = needScrollBar(Qt::Horizontal);
    if ( showHScrollBar )
    {
        ScrollBar *sb = scrollBar(Qt::Horizontal);

        sb->setPalette(plot()->palette());

        const QwtScaleEngine *se = plot()->axisScaleEngine(xAxis);
        sb->setInverted(se->testAttribute(QwtScaleEngine::Inverted));

        sb->setBase(zoomBase().left(), zoomBase().right());
        sb->moveSlider(zoomRect().left(), zoomRect().right());

        if ( !sb->isVisibleTo(canvas()) )
        {
            sb->show();
            layout->setCanvasMargin(layout->canvasMargin(xScrollBarAxis)
                                    + sb->extent(), xScrollBarAxis);
        }
    }
    else
    {
        if ( horizontalScrollBar() )
        {
            horizontalScrollBar()->hide();
            layout->setCanvasMargin(layout->canvasMargin(xScrollBarAxis)
                                    - horizontalScrollBar()->extent(), xScrollBarAxis);
        }
    }

    bool showVScrollBar = needScrollBar(Qt::Vertical);
    if ( showVScrollBar )
    {
        ScrollBar *sb = scrollBar(Qt::Vertical);

        sb->setPalette(plot()->palette());

        const QwtScaleEngine *se = plot()->axisScaleEngine(xAxis);
        sb->setInverted(!(se->testAttribute(QwtScaleEngine::Inverted)));

        sb->setBase(zoomBase().top(), zoomBase().bottom());
        sb->moveSlider(zoomRect().top(), zoomRect().bottom());

        if ( !sb->isVisibleTo(canvas()) )
        {
            sb->show();
            layout->setCanvasMargin(layout->canvasMargin(yScrollBarAxis)
                                    + sb->extent(), yScrollBarAxis);
        }
    }
    else
    {
        if ( verticalScrollBar() )
        {
            verticalScrollBar()->hide();
            layout->setCanvasMargin(layout->canvasMargin(yScrollBarAxis)
                                    - verticalScrollBar()->extent(), yScrollBarAxis);
        }
    }

    if ( showHScrollBar && showVScrollBar )
    {
        if ( d_cornerWidget == NULL )
        {
            d_cornerWidget = new QWidget(canvas());
#if QT_VERSION >= 0x040100

            d_cornerWidget->setAutoFillBackground(true);
#endif

            d_cornerWidget->setPalette(plot()->palette());
        }
        d_cornerWidget->show();
    }
    else
    {
        if ( d_cornerWidget )
            d_cornerWidget->hide();
    }

    layoutScrollBars(((QwtPlotCanvas *)canvas())->contentsRect());
    plot()->updateLayout();
}

void ScrollZoomer::layoutScrollBars(const QRect &rect)
{
    int hPos = xAxis();
    if ( hScrollBarPosition() == OppositeToScale )
        hPos = oppositeAxis(hPos);

    int vPos = yAxis();
    if ( vScrollBarPosition() == OppositeToScale )
        vPos = oppositeAxis(vPos);

    ScrollBar *hScrollBar = horizontalScrollBar();
    ScrollBar *vScrollBar = verticalScrollBar();

    const int hdim = hScrollBar ? hScrollBar->extent() : 0;
    const int vdim = vScrollBar ? vScrollBar->extent() : 0;

    if ( hScrollBar && hScrollBar->isVisible() )
    {
        int x = rect.x();
        int y = (hPos == QwtPlot::xTop)
                ? rect.top() : rect.bottom() - hdim + 1;
        int w = rect.width();

        if ( vScrollBar && vScrollBar->isVisible() )
        {
            if ( vPos == QwtPlot::yLeft )
                x += vdim;
            w -= vdim;
        }

        hScrollBar->setGeometry(x, y, w, hdim);
    }
    if ( vScrollBar && vScrollBar->isVisible() )
    {
        int pos = yAxis();
        if ( vScrollBarPosition() == OppositeToScale )
            pos = oppositeAxis(pos);

        int x = (vPos == QwtPlot::yLeft)
                ? rect.left() : rect.right() - vdim + 1;
        int y = rect.y();

        int h = rect.height();

        if ( hScrollBar && hScrollBar->isVisible() )
        {
            if ( hPos == QwtPlot::xTop )
                y += hdim;

            h -= hdim;
        }

        vScrollBar->setGeometry(x, y, vdim, h);
    }
    if ( hScrollBar && hScrollBar->isVisible() &&
            vScrollBar && vScrollBar->isVisible() )
    {
        if ( d_cornerWidget )
        {
            QRect cornerRect(
                vScrollBar->pos().x(), hScrollBar->pos().y(),
                vdim, hdim);
            d_cornerWidget->setGeometry(cornerRect);
        }
    }
}

void ScrollZoomer::scrollBarMoved(Qt::Orientation o, double min, double)
{
    if ( o == Qt::Horizontal )
        move(min, zoomRect().top());
    else
        move(zoomRect().left(), min);

    emit zoomed(zoomRect());
}

int ScrollZoomer::oppositeAxis(int axis) const
{
    switch(axis)
    {
    case QwtPlot::xBottom:
        return QwtPlot::xTop;
    case QwtPlot::xTop:
        return QwtPlot::xBottom;
    case QwtPlot::yLeft:
        return QwtPlot::yRight;
    case QwtPlot::yRight:
        return QwtPlot::yLeft;
    default:
        break;
    }

    return axis;
}
