//-----------------------------------------------------------------------------
// VST Plug-Ins SDK
// VSTGUI: Graphical User Interface Framework for VST plugins :
//
// CScrollView written 2004 by Arne Scheffler
//
//-----------------------------------------------------------------------------
// VSTGUI LICENSE
// © 2004, Steinberg Media Technologies, All Rights Reserved
//-----------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of the Steinberg Media Technologies nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A  PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

#ifndef __cscrollview__
#include "cscrollview.h"
#endif

#define FOREACHSUBVIEW for (CCView *pSv = pFirstView; pSv; pSv = pSv->pNext) {CView *pV = pSv->pView;
#define ENDFOR }

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CScrollContainer::CScrollContainer (const CRect &size, const CRect &containerSize, CFrame *pParent, CBitmap *pBackground)
: CViewContainer (size, pParent, pBackground)
, containerSize (containerSize)
, offset (CPoint (0, 0))
{
    setTransparency (true);
    setMode (kOnlyDirtyUpdate);
}

//-----------------------------------------------------------------------------
CScrollContainer::~CScrollContainer ()
{
}


//-----------------------------------------------------------------------------
void CScrollContainer::setContainerSize (const CRect& cs)
{
    containerSize = cs;
    setScrollOffset (offset, false);
}

//-----------------------------------------------------------------------------
void CScrollContainer::setScrollOffset (CPoint newOffset, bool redraw)
{
    if (newOffset.x < containerSize.left - containerSize.width ())
        newOffset.x = containerSize.left - containerSize.width ();
    if (newOffset.x > containerSize.right)
        newOffset.x = containerSize.right;
    if (newOffset.y < containerSize.top)
        newOffset.y = containerSize.top;
    if (newOffset.y > containerSize.bottom)
        newOffset.y = containerSize.bottom;
    CPoint diff (newOffset.x - offset.x, offset.y - newOffset.y);
    if (diff.x == 0 && diff.y == 0)
        return;
    CCView *pV = pFirstView;
    while (pV)
    {
        CRect r;
        pV->pView->getViewSize (r);
        r.offset (diff.x , diff.y);
        pV->pView->setViewSize (r);
        pV->pView->getMouseableArea (r);
        r.offset (diff.x , diff.y);
        pV->pView->setMouseableArea (r);

        pV = pV->pNext;
    }
    offset = newOffset;

    if (0) //redraw)
    {
        CDrawContext* pContext = getFrame ()->createDrawContext ();
        CPoint contextOffset = pContext->offset;
        pContext->offset.offset (-contextOffset.x, -contextOffset.y);
        pContext->offsetScreen.offset (-contextOffset.x, -contextOffset.y);
        redrawRect (pContext, size);
        pContext->offset.offset (contextOffset.x, contextOffset.y);
        pContext->offsetScreen.offset (contextOffset.x, contextOffset.y);
        pContext->forget ();
    }
    else
        setDirty (true);
}

//-----------------------------------------------------------------------------
void CScrollContainer::redrawRect (CDrawContext* context, const CRect& rect)
{
    CRect _rect (rect);
    _rect.offset (size.left, size.top);
    _rect.bound (size);
    if (bTransparencyEnabled)
    {
        // as this is transparent, we call the parentview to redraw this area.
        if (pParentView)
            pParentView->redrawRect (context, _rect);
        else if (pParentFrame)
            pParentFrame->drawRect (context, _rect);
    }
    else
        drawRect (context, _rect);
}

//-----------------------------------------------------------------------------
bool CScrollContainer::isDirty () const
{
    if (bDirty)
        return true;

    FOREACHSUBVIEW
        if (pV->isDirty ())
        {
            CRect vs = pV->getViewSize (vs);
            vs.offset (size.left, size.top);
            if (size.rectOverlap (vs))
                return true;
        }
    ENDFOR
    return false;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CScrollView::CScrollView (const CRect &size, const CRect &containerSize, CFrame* pParent, long style, long scrollbarWidth, CBitmap* pBackground)
: CViewContainer (size, pParent, pBackground)
, sc (0)
, vsb (0)
, hsb (0)
, containerSize (containerSize)
, style (style)
{
    setTransparency (true);
    setMode (kOnlyDirtyUpdate);

    CRect scsize (size);
    scsize.offset (-scsize.left, -scsize.top);
//  CScrollbar* sb = 0;
    if (style & kHorizontalScrollbar)
    {
        CRect sbr (size);
        sbr.offset (-size.left, -size.top);
        sbr.top = sbr.bottom - scrollbarWidth;
        if (style & kVerticalScrollbar)
        {
            sbr.right -= (scrollbarWidth - 1);
        }
        hsb = new CScrollbar (sbr, this, kHSBTag, CScrollbar::kHorizontal, containerSize);
        CViewContainer::addView (hsb);
        scsize.bottom = sbr.top;
    }
    if (style & kVerticalScrollbar)
    {
        CRect sbr (size);
        sbr.offset (-size.left, -size.top);
        sbr.left = sbr.right - scrollbarWidth;
        if (style & kHorizontalScrollbar)
        {
            sbr.bottom -= (scrollbarWidth - 1);
        }
        vsb = new CScrollbar (sbr, this, kVSBTag, CScrollbar::kVertical, containerSize);
        CViewContainer::addView (vsb);
        scsize.right = sbr.left;
    }

    sc = new CScrollContainer (scsize, this->containerSize, pParent);
    CViewContainer::addView (sc);
}

//-----------------------------------------------------------------------------
CScrollView::~CScrollView ()
{
}

//-----------------------------------------------------------------------------
void CScrollView::setContainerSize (const CRect& cs)
{
    containerSize = cs;
    if (sc)
    {
        sc->setContainerSize (cs);
    }
    if (vsb)
    {
        vsb->setScrollSize (cs);
        valueChanged (NULL, vsb);
    }
    if (hsb)
    {
        hsb->setScrollSize (cs);
        valueChanged (NULL, hsb);
    }
}

//-----------------------------------------------------------------------------
void CScrollView::addView (CView *pView)
{
    sc->addView (pView);
}

//-----------------------------------------------------------------------------
void CScrollView::valueChanged (CDrawContext *pContext, CControl *pControl)
{
    if (sc)
    {
        float value = pControl->getValue ();
        long tag = pControl->getTag ();
        CPoint offset;
        CRect vsize = sc->getViewSize (vsize);
        CRect csize = sc->getContainerSize ();
        sc->getScrollOffset (offset);

        switch (tag)
        {
            case kHSBTag:
            {
                offset.x = csize.left - (CCoord)((csize.width () - vsize.width ()) * value);
                sc->setScrollOffset (offset, false);
                break;
            }
            case kVSBTag:
            {
                offset.y = csize.top + (CCoord)((csize.height () - vsize.height ()) * value);
                sc->setScrollOffset (offset, false);
                break;
            }
        }
    }
}

//-----------------------------------------------------------------------------
void CScrollView::drawBackgroundRect (CDrawContext *pContext, CRect& _updateRect)
{
    CRect r (size);
    r.offset (-r.left, -r.top);
    pContext->setFrameColor (kBlackCColor);
    pContext->setLineWidth (1);
    pContext->drawRect (r);
    CViewContainer::drawBackgroundRect (pContext, _updateRect);
}

//-----------------------------------------------------------------------------
bool CScrollView::onWheel (CDrawContext *pContext, const CPoint &where, const CMouseWheelAxis axis, float distance)
{
    bool result = CViewContainer::onWheel (pContext, where, axis, distance);
    if (!result)
    {
        if (vsb && axis == kMouseWheelAxisY)
            result = vsb->onWheel (pContext, where, distance);
        else if (hsb && axis == kMouseWheelAxisX)
            result = hsb->onWheel (pContext, where, distance);
    }
    return result;
}

//-----------------------------------------------------------------------------
CScrollbar::CScrollbar (const CRect& size, CControlListener* listener, long tag, long style, const CRect& scrollSize)
: CControl (size, listener, tag, 0)
, style (style)
, scrollSize (scrollSize)
, scrollerArea (size)
, stepValue (0.1f)
, scrollerLength (0)
, drawer (0)
{
    setWheelInc (0.05f);
    scrollerArea.inset (2, 2);
    calculateScrollerLength ();
    frameColor (0, 0, 0, 255);
    scrollerColor (0, 0, 255, 255);
    backgroundColor (255, 255, 255, 200);
}

//-----------------------------------------------------------------------------
CScrollbar::~CScrollbar ()
{
}

//-----------------------------------------------------------------------------
void CScrollbar::setScrollSize (const CRect& ssize)
{
    scrollSize = ssize;
    calculateScrollerLength ();
    setDirty (true);
}

//-----------------------------------------------------------------------------
void CScrollbar::calculateScrollerLength ()
{
    CCoord newScrollerLength = scrollerLength;
    if (style == kHorizontal)
    {
        float factor = (float)size.width () / (float)scrollSize.width ();
        if (factor >= 1.f)
            factor = 0;
        newScrollerLength = (CCoord)(size.width () * factor);
    }
    else
    {
        float factor = (float)size.height () / (float)scrollSize.height ();
        if (factor >= 1.f)
            factor = 0;
        newScrollerLength = (CCoord)(size.height () * factor);
    }
    if (newScrollerLength != scrollerLength)
    {
        scrollerLength = newScrollerLength;
        setDirty (true);
    }
}

//-----------------------------------------------------------------------------
CRect CScrollbar::getScrollerRect ()
{
    CRect scrollerRect (scrollerArea);
    CCoord l = (style == kHorizontal) ? scrollerArea.width () : scrollerArea.height ();
    CCoord scrollerOffset = (CCoord)(value * (l - scrollerLength));
    if (style == kHorizontal)
    {
        scrollerRect.setWidth (scrollerLength);
        scrollerRect.offset (scrollerOffset, 0);
    }
    else
    {
        scrollerRect.setHeight (scrollerLength);
        scrollerRect.offset (0, scrollerOffset);
    }
    return scrollerRect;
}

//-----------------------------------------------------------------------------
void CScrollbar::mouse (CDrawContext* pContext, CPoint& where, long buttons)
{
    if (buttons == -1) buttons = pContext->getMouseButtons ();
    if (buttons != kLButton || scrollerLength == 0) return;

    float newValue = 0.f;

    // scrolling
    CRect scrollerRect = getScrollerRect ();
    if (where.isInside (scrollerRect))
    {
        CPoint newPoint (where);
        while (pContext->waitDrag ())
        {
            getMouseLocation (pContext, newPoint);
            newPoint.x -= where.x - scrollerRect.left;
            newPoint.y -= where.y - scrollerRect.top;
            if (style == kHorizontal)
            {
                newValue = (float)(newPoint.x - scrollerArea.left) / ((float)scrollerArea.width () - scrollerRect.width ());
            }
            else
            {
                newValue = (float)(newPoint.y - scrollerArea.top) / ((float)scrollerArea.height () - scrollerRect.height ());
            }
            if (newValue < 0.f) newValue = 0.f;
            if (newValue > 1.f) newValue = 1.f;
            if (newValue != value)
            {
                value = newValue;
                if (listener)
                    listener->valueChanged (pContext, this);
            }
            doIdleStuff ();
        }
    }
    // stepping
    else if (where.isInside (scrollerArea))
    {
        long kWaitTime = 100;
        long nextUpdateTime = getFrame ()->getTicks ();
        do
        {
            long ticks = getFrame ()->getTicks ();
            if (nextUpdateTime - ticks < 0)
            {
                if (style == kHorizontal)
                {
                    if (where.x < scrollerRect.left)
                        newValue = value - (float)scrollerLength / (float)scrollerArea.width ();
                    else
                        newValue = value + (float)scrollerLength / (float)scrollerArea.width ();
                }
                else
                {
                    if (where.y < scrollerRect.top)
                        newValue = value - (float)scrollerLength / (float)scrollerArea.height ();
                    else
                        newValue = value + (float)scrollerLength / (float)scrollerArea.height ();
                }
                if (newValue < 0.f) newValue = 0.f;
                if (newValue > 1.f) newValue = 1.f;
                if (newValue != value)
                {
                    value = newValue;
                    if (listener)
                        listener->valueChanged (pContext, this);
                }
                scrollerRect = getScrollerRect ();
                if (where.isInside (scrollerRect))
                    break;
                nextUpdateTime = ticks + kWaitTime;
            }
            doIdleStuff ();
        } while (pContext->getMouseButtons () == kLButton);
    }
}

//------------------------------------------------------------------------
bool CScrollbar::onWheel (CDrawContext *pContext, const CPoint &where, float distance)
{
    if (!bMouseEnabled)
        return false;

    if (style == kHorizontal)
        distance *= -1;

    long buttons = pContext->getMouseButtons ();
    if (buttons & kShift)
        value -= 0.1f * distance * wheelInc;
    else
        value -= distance * wheelInc;
    bounceValue ();

    if (isDirty () && listener)
        listener->valueChanged (pContext, this);
    return true;
}

//-----------------------------------------------------------------------------
void CScrollbar::drawBackground (CDrawContext* pContext)
{
    CRect r (size);
    if (drawer)
        drawer->drawScrollbarBackground (pContext, r, style, this);
    else
    {
        pContext->setLineWidth (1);
        pContext->setFillColor (backgroundColor);
        pContext->setFrameColor (frameColor);
        pContext->drawRect (r, kDrawFilledAndStroked);
    }
}

//-----------------------------------------------------------------------------
void CScrollbar::drawScroller (CDrawContext* pContext, const CRect& size)
{
    CRect r (size);
    if (drawer)
        drawer->drawScrollbarScroller (pContext, r, style, this);
    else
    {
        pContext->setLineWidth (1);
        pContext->setFillColor (scrollerColor);
        pContext->setFrameColor (frameColor);
        pContext->drawRect (r, kDrawFilledAndStroked);
    }
}

//-----------------------------------------------------------------------------
void CScrollbar::draw (CDrawContext* pContext)
{
    drawBackground (pContext);
    if (scrollerLength > 0)
    {
        CRect scrollerRect = getScrollerRect ();
        drawScroller (pContext, scrollerRect);
    }
    setDirty (false);
}
