/////////////////////////////////////////////////////////////////////////////
// Name:        src/univ/scrolbar.cpp
// Purpose:     wxScrollBar implementation
// Author:      Vadim Zeitlin
// Created:     20.08.00
// Copyright:   (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

#include "wx/wxprec.h"


#if wxUSE_SCROLLBAR

#include "wx/scrolbar.h"

#ifndef WX_PRECOMP
    #include "wx/timer.h"
    #include "wx/dcclient.h"
    #include "wx/validate.h"
    #include "wx/log.h"
#endif

#include "wx/univ/scrtimer.h"

#include "wx/univ/renderer.h"
#include "wx/univ/inphand.h"
#include "wx/univ/theme.h"

#if wxDEBUG_LEVEL >= 2
    #define WXDEBUG_SCROLLBAR
#endif

#if defined(WXDEBUG_SCROLLBAR) && defined(__WXMSW__)
#include "wx/msw/private.h"
#endif

// ----------------------------------------------------------------------------
// wxScrollBarTimer: this class is used to repeatedly scroll the scrollbar
// when the mouse is help pressed on the arrow or on the bar. It generates the
// given scroll action command periodically.
// ----------------------------------------------------------------------------

class wxScrollBarTimer : public wxScrollTimer
{
public:
    wxScrollBarTimer(wxStdScrollBarInputHandler *handler,
                     const wxControlAction& action,
                     wxScrollBar *control);

protected:
    virtual bool DoNotify();

private:
    wxStdScrollBarInputHandler *m_handler;
    wxControlAction m_action;
    wxScrollBar    *m_control;
};

// ============================================================================
// implementation
// ============================================================================

wxBEGIN_EVENT_TABLE(wxScrollBar, wxScrollBarBase)
wxEND_EVENT_TABLE()

// ----------------------------------------------------------------------------
// creation
// ----------------------------------------------------------------------------

#ifdef __VISUALC__
    // warning C4355: 'this' : used in base member initializer list
    #pragma warning(disable:4355)  // so what? disable it...
#endif

wxScrollBar::wxScrollBar()
           : m_arrows(this)
{
    Init();
}

wxScrollBar::wxScrollBar(wxWindow *parent,
                         wxWindowID id,
                         const wxPoint& pos,
                         const wxSize& size,
                         long style,
                         const wxValidator& validator,
                         const wxString& name)
           : m_arrows(this)
{
    Init();

    (void)Create(parent, id, pos, size, style, validator, name);
}

#ifdef __VISUALC__
    // warning C4355: 'this' : used in base member initializer list
    #pragma warning(default:4355)
#endif

void wxScrollBar::Init()
{
    m_range =
    m_thumbSize =
    m_thumbPos =
    m_pageSize = 0;

    m_thumbPosOld = -1;

    for ( size_t n = 0; n < WXSIZEOF(m_elementsState); n++ )
    {
        m_elementsState[n] = 0;
    }

    m_dirty = false;
}

bool wxScrollBar::Create(wxWindow *parent,
                         wxWindowID id,
                         const wxPoint &pos,
                         const wxSize &size,
                         long style,
                         const wxValidator& validator,
                         const wxString &name)
{
    // the scrollbars never have the border
    style &= ~wxBORDER_MASK;

    if ( !wxControl::Create(parent, id, pos, size, style, validator, name) )
        return false;

    SetInitialSize(size);

    // override the cursor of the target window (if any)
    SetCursor(wxCURSOR_ARROW);

    CreateInputHandler(wxINP_HANDLER_SCROLLBAR);

    return true;
}

wxScrollBar::~wxScrollBar()
{
}

// ----------------------------------------------------------------------------
// misc accessors
// ----------------------------------------------------------------------------

bool wxScrollBar::IsStandalone() const
{
    wxWindow *parent = GetParent();
    if ( !parent )
    {
        return true;
    }

    return (parent->GetScrollbar(wxHORIZONTAL) != this) &&
           (parent->GetScrollbar(wxVERTICAL) != this);
}

bool wxScrollBar::AcceptsFocus() const
{
    // the window scrollbars never accept focus
    return wxScrollBarBase::AcceptsFocus() && IsStandalone();
}

// ----------------------------------------------------------------------------
// scrollbar API
// ----------------------------------------------------------------------------

void wxScrollBar::DoSetThumb(int pos)
{
    // don't assert hecks here, we're a private function which is meant to be
    // called with any args at all
    if ( pos < 0 )
    {
        pos = 0;
    }
    else if ( pos > m_range - m_thumbSize )
    {
        pos = m_range - m_thumbSize;
    }

    if ( m_thumbPos == pos )
    {
        // nothing changed, avoid refreshes which would provoke flicker
        return;
    }

    if ( m_thumbPosOld == -1 )
    {
        // remember the old thumb position
        m_thumbPosOld = m_thumbPos;
    }

    m_thumbPos = pos;

    // we have to refresh the part of the bar which was under the thumb and the
    // thumb itself
    m_elementsState[Element_Thumb] |= wxCONTROL_DIRTY;
    m_elementsState[m_thumbPos > m_thumbPosOld
                        ? Element_Bar_1 : Element_Bar_2] |= wxCONTROL_DIRTY;
    m_dirty = true;
}

int wxScrollBar::GetThumbPosition() const
{
    return m_thumbPos;
}

int wxScrollBar::GetThumbSize() const
{
    return m_thumbSize;
}

int wxScrollBar::GetPageSize() const
{
    return m_pageSize;
}

int wxScrollBar::GetRange() const
{
    return m_range;
}

void wxScrollBar::SetThumbPosition(int pos)
{
    wxCHECK_RET( pos >= 0 && pos <= m_range, wxT("thumb position out of range") );

    DoSetThumb(pos);
}

void wxScrollBar::SetScrollbar(int position, int thumbSize,
                               int range, int pageSize,
                               bool refresh)
{
    // we only refresh everything when the range changes, thumb position
    // changes are handled in OnIdle
    bool needsRefresh = (range != m_range) ||
                        (thumbSize != m_thumbSize) ||
                        (pageSize != m_pageSize);

    // set all parameters
    m_range = range;
    m_thumbSize = thumbSize;
    SetThumbPosition(position);
    m_pageSize = pageSize;

    // ignore refresh parameter unless we really need to refresh everything -
    // there ir a lot of existing code which just calls SetScrollbar() without
    // specifying the last parameter even though it doesn't need at all to
    // refresh the window immediately
    if ( refresh && needsRefresh )
    {
        // and update the window
        Refresh();
        Update();
    }
}

// ----------------------------------------------------------------------------
// geometry
// ----------------------------------------------------------------------------

wxSize wxScrollBar::DoGetBestClientSize() const
{
    // this dimension is completely arbitrary
    static const wxCoord SIZE = 140;

    wxSize size = m_renderer->GetScrollbarArrowSize();
    if ( IsVertical() )
    {
        size.y = SIZE;
    }
    else // horizontal
    {
        size.x = SIZE;
    }

    return size;
}

wxScrollArrows::Arrow wxScrollBar::HitTestArrow(const wxPoint& pt) const
{
    switch ( HitTestBar(pt) )
    {
        case wxHT_SCROLLBAR_ARROW_LINE_1:
            return wxScrollArrows::Arrow_First;

        case wxHT_SCROLLBAR_ARROW_LINE_2:
            return wxScrollArrows::Arrow_Second;

        default:
            return wxScrollArrows::Arrow_None;
    }
}

wxHitTest wxScrollBar::HitTestBar(const wxPoint& pt) const
{
    // we only need to work with either x or y coord depending on the
    // orientation, choose one (but still check the other one to verify if the
    // mouse is in the window at all)
    const wxSize sizeArrowSB = m_renderer->GetScrollbarArrowSize();

    wxCoord coord, sizeArrow, sizeTotal;
    wxSize size = GetSize();
    if ( GetWindowStyle() & wxVERTICAL )
    {
        if ( pt.x < 0 || pt.x > size.x )
            return wxHT_NOWHERE;

        coord = pt.y;
        sizeArrow = sizeArrowSB.y;
        sizeTotal = size.y;
    }
    else // horizontal
    {
        if ( pt.y < 0 || pt.y > size.y )
            return wxHT_NOWHERE;

        coord = pt.x;
        sizeArrow = sizeArrowSB.x;
        sizeTotal = size.x;
    }

    // test for the arrows first as it's faster
    if ( coord < 0 || coord > sizeTotal )
    {
        return wxHT_NOWHERE;
    }
    else if ( coord < sizeArrow )
    {
        return wxHT_SCROLLBAR_ARROW_LINE_1;
    }
    else if ( coord > sizeTotal - sizeArrow )
    {
        return wxHT_SCROLLBAR_ARROW_LINE_2;
    }
    else
    {
        // calculate the thumb position in pixels
        sizeTotal -= 2*sizeArrow;
        wxCoord thumbStart, thumbEnd;
        int range = GetRange();
        if ( !range )
        {
            // clicking the scrollbar without range has no effect
            return wxHT_NOWHERE;
        }
        else
        {
            GetScrollBarThumbSize(sizeTotal,
                                  GetThumbPosition(),
                                  GetThumbSize(),
                                  range,
                                  &thumbStart,
                                  &thumbEnd);
        }

        // now compare with the thumb position
        coord -= sizeArrow;
        if ( coord < thumbStart )
            return wxHT_SCROLLBAR_BAR_1;
        else if ( coord > thumbEnd )
            return wxHT_SCROLLBAR_BAR_2;
        else
            return wxHT_SCROLLBAR_THUMB;
    }
}

/* static */
void wxScrollBar::GetScrollBarThumbSize(wxCoord length,
                                        int thumbPos,
                                        int thumbSize,
                                        int range,
                                        wxCoord *thumbStart,
                                        wxCoord *thumbEnd)
{
    // the thumb can't be made less than this number of pixels
    static const wxCoord thumbMinWidth = 8; // FIXME: should be configurable

    *thumbStart = (length*thumbPos) / range;
    *thumbEnd = (length*(thumbPos + thumbSize)) / range;

    if ( *thumbEnd - *thumbStart < thumbMinWidth )
    {
        // adjust the end if possible
        if ( *thumbStart <= length - thumbMinWidth )
        {
            // yes, just make it wider
            *thumbEnd = *thumbStart + thumbMinWidth;
        }
        else // it is at the bottom of the scrollbar
        {
            // so move it a bit up
            *thumbStart = length - thumbMinWidth;
            *thumbEnd = length;
        }
    }
}

wxRect wxScrollBar::GetScrollbarRect(wxScrollBar::Element elem,
                                     int thumbPos) const
{
    if ( thumbPos == -1 )
    {
        thumbPos = GetThumbPosition();
    }

    const wxSize sizeArrow = m_renderer->GetScrollbarArrowSize();

    wxSize sizeTotal = GetClientSize();
    wxCoord *start, *width;
    wxCoord length, arrow;
    wxRect rect;
    if ( IsVertical() )
    {
        rect.x = 0;
        rect.width = sizeTotal.x;
        length = sizeTotal.y;
        start = &rect.y;
        width = &rect.height;
        arrow = sizeArrow.y;
    }
    else // horizontal
    {
        rect.y = 0;
        rect.height = sizeTotal.y;
        length = sizeTotal.x;
        start = &rect.x;
        width = &rect.width;
        arrow = sizeArrow.x;
    }

    switch ( elem )
    {
        case wxScrollBar::Element_Arrow_Line_1:
            *start = 0;
            *width = arrow;
            break;

        case wxScrollBar::Element_Arrow_Line_2:
            *start = length - arrow;
            *width = arrow;
            break;

        case wxScrollBar::Element_Arrow_Page_1:
        case wxScrollBar::Element_Arrow_Page_2:
            // we don't have them at all
            break;

        case wxScrollBar::Element_Thumb:
        case wxScrollBar::Element_Bar_1:
        case wxScrollBar::Element_Bar_2:
            // we need to calculate the thumb position - do it
            {
                length -= 2*arrow;
                wxCoord thumbStart, thumbEnd;
                int range = GetRange();
                if ( !range )
                {
                    thumbStart =
                    thumbEnd = 0;
                }
                else
                {
                    GetScrollBarThumbSize(length,
                                          thumbPos,
                                          GetThumbSize(),
                                          range,
                                          &thumbStart,
                                          &thumbEnd);
                }

                if ( elem == wxScrollBar::Element_Thumb )
                {
                    *start = thumbStart;
                    *width = thumbEnd - thumbStart;
                }
                else if ( elem == wxScrollBar::Element_Bar_1 )
                {
                    *start = 0;
                    *width = thumbStart;
                }
                else // elem == wxScrollBar::Element_Bar_2
                {
                    *start = thumbEnd;
                    *width = length - thumbEnd;
                }

                // everything is relative to the start of the shaft so far
                *start += arrow;
            }
            break;

        case wxScrollBar::Element_Max:
        default:
            wxFAIL_MSG( wxT("unknown scrollbar element") );
    }

    return rect;
}

wxCoord wxScrollBar::GetScrollbarSize() const
{
    const wxSize sizeArrowSB = m_renderer->GetScrollbarArrowSize();

    wxCoord sizeArrow, sizeTotal;
    if ( GetWindowStyle() & wxVERTICAL )
    {
        sizeArrow = sizeArrowSB.y;
        sizeTotal = GetSize().y;
    }
    else // horizontal
    {
        sizeArrow = sizeArrowSB.x;
        sizeTotal = GetSize().x;
    }

    return sizeTotal - 2*sizeArrow;
}


wxCoord wxScrollBar::ScrollbarToPixel(int thumbPos)
{
    int range = GetRange();
    if ( !range )
    {
        // the only valid position anyhow
        return 0;
    }

    if ( thumbPos == -1 )
    {
        // by default use the current thumb position
        thumbPos = GetThumbPosition();
    }

    const wxSize sizeArrow = m_renderer->GetScrollbarArrowSize();
    return (thumbPos * GetScrollbarSize()) / range
             + (IsVertical() ? sizeArrow.y : sizeArrow.x);
}

int wxScrollBar::PixelToScrollbar(wxCoord coord)
{
    const wxSize sizeArrow = m_renderer->GetScrollbarArrowSize();
    return ((coord - (IsVertical() ? sizeArrow.y : sizeArrow.x)) *
               GetRange() ) / GetScrollbarSize();
}

// ----------------------------------------------------------------------------
// drawing
// ----------------------------------------------------------------------------

void wxScrollBar::OnInternalIdle()
{
    UpdateThumb();
    wxControl::OnInternalIdle();
}

void wxScrollBar::UpdateThumb()
{
    if ( m_dirty )
    {
        for ( size_t n = 0; n < WXSIZEOF(m_elementsState); n++ )
        {
            if ( m_elementsState[n] & wxCONTROL_DIRTY )
            {
                wxRect rect = GetScrollbarRect((Element)n);

                if ( rect.width && rect.height )
                {
                    // we try to avoid redrawing the entire shaft (which might
                    // be quite long) if possible by only redrawing the area
                    // wich really changed
                    if ( (n == Element_Bar_1 || n == Element_Bar_2) &&
                            (m_thumbPosOld != -1) )
                    {
                        // the less efficient but more reliable (i.e. this will
                        // probably work everywhere) version: refresh the
                        // distance covered by thumb since the last update
#if 0
                        wxRect rectOld =
                            GetRenderer()->GetScrollbarRect(this,
                                                            (Element)n,
                                                            m_thumbPosOld);
                        if ( IsVertical() )
                        {
                            if ( n == Element_Bar_1 )
                                rect.SetTop(rectOld.GetBottom());
                            else
                                rect.SetBottom(rectOld.GetBottom());
                        }
                        else // horizontal
                        {
                            if ( n == Element_Bar_1 )
                                rect.SetLeft(rectOld.GetRight());
                            else
                                rect.SetRight(rectOld.GetRight());
                        }
#else                   // efficient version: only repaint the area occupied by
                        // the thumb previously - we can't do better than this
                        rect = GetScrollbarRect(Element_Thumb, m_thumbPosOld);
#endif // 0/1
                    }

#ifdef WXDEBUG_SCROLLBAR
        static bool s_refreshDebug = false;
        if ( s_refreshDebug )
        {
            wxClientDC dc(this);
            dc.SetBrush(*wxCYAN_BRUSH);
            dc.SetPen(*wxTRANSPARENT_PEN);
            dc.DrawRectangle(rect);

            // under Unix we use "--sync" X option for this
            #if defined(__WXMSW__)
                ::GdiFlush();
                ::Sleep(200);
            #endif // __WXMSW__
        }
#endif // WXDEBUG_SCROLLBAR

                    Refresh(false, &rect);
                }

                m_elementsState[n] &= ~wxCONTROL_DIRTY;
            }
        }

        m_dirty = false;
    }
}

void wxScrollBar::DoDraw(wxControlRenderer *renderer)
{
    renderer->DrawScrollbar(this, m_thumbPosOld);

    // clear all dirty flags
    m_dirty = false;
    m_thumbPosOld = -1;
}

// ----------------------------------------------------------------------------
// state flags
// ----------------------------------------------------------------------------

static inline wxScrollBar::Element ElementForArrow(wxScrollArrows::Arrow arrow)
{
    return arrow == wxScrollArrows::Arrow_First
            ? wxScrollBar::Element_Arrow_Line_1
            : wxScrollBar::Element_Arrow_Line_2;
}

int wxScrollBar::GetArrowState(wxScrollArrows::Arrow arrow) const
{
    return GetState(ElementForArrow(arrow));
}

void wxScrollBar::SetArrowFlag(wxScrollArrows::Arrow arrow, int flag, bool set)
{
    Element which = ElementForArrow(arrow);
    int state = GetState(which);
    if ( set )
        state |= flag;
    else
        state &= ~flag;

    SetState(which, state);
}

int wxScrollBar::GetState(Element which) const
{
    // if the entire scrollbar is disabled, all of its elements are too
    int flags = m_elementsState[which];
    if ( !IsEnabled() )
        flags |= wxCONTROL_DISABLED;

    return flags;
}

void wxScrollBar::SetState(Element which, int flags)
{
    if ( (int)(m_elementsState[which] & ~wxCONTROL_DIRTY) != flags )
    {
        m_elementsState[which] = flags | wxCONTROL_DIRTY;

        m_dirty = true;
    }
}

// ----------------------------------------------------------------------------
// input processing
// ----------------------------------------------------------------------------

bool wxScrollBar::OnArrow(wxScrollArrows::Arrow arrow)
{
    int oldThumbPos = GetThumbPosition();
    PerformAction(arrow == wxScrollArrows::Arrow_First
                    ? wxACTION_SCROLL_LINE_UP
                    : wxACTION_SCROLL_LINE_DOWN);

    // did we scroll till the end?
    return GetThumbPosition() != oldThumbPos;
}

bool wxScrollBar::PerformAction(const wxControlAction& action,
                                long numArg,
                                const wxString& strArg)
{
    int thumbOld = m_thumbPos;

    bool notify = false; // send an event about the change?

    wxEventType scrollType;

    // test for thumb move first as these events happen in quick succession
    if ( action == wxACTION_SCROLL_THUMB_MOVE )
    {
        DoSetThumb(numArg);

        // VS: we have to force redraw here, otherwise the thumb will lack
        //     behind mouse cursor
        UpdateThumb();

        scrollType = wxEVT_SCROLLWIN_THUMBTRACK;
    }
    else if ( action == wxACTION_SCROLL_LINE_UP )
    {
        scrollType = wxEVT_SCROLLWIN_LINEUP;
        ScrollLines(-1);
    }
    else if ( action == wxACTION_SCROLL_LINE_DOWN )
    {
        scrollType = wxEVT_SCROLLWIN_LINEDOWN;
        ScrollLines(1);
    }
    else if ( action == wxACTION_SCROLL_PAGE_UP )
    {
        scrollType = wxEVT_SCROLLWIN_PAGEUP;
        ScrollPages(-1);
    }
    else if ( action == wxACTION_SCROLL_PAGE_DOWN )
    {
        scrollType = wxEVT_SCROLLWIN_PAGEDOWN;
        ScrollPages(1);
    }
    else if ( action == wxACTION_SCROLL_START )
    {
        scrollType = wxEVT_SCROLLWIN_THUMBRELEASE; // anything better?
        ScrollToStart();
    }
    else if ( action == wxACTION_SCROLL_END )
    {
        scrollType = wxEVT_SCROLLWIN_THUMBRELEASE; // anything better?
        ScrollToEnd();
    }
    else if ( action == wxACTION_SCROLL_THUMB_DRAG )
    {
        // we won't use it but this line suppresses the compiler
        // warning about "variable may be used without having been
        // initialized"
        scrollType = wxEVT_NULL;
    }
    else if ( action == wxACTION_SCROLL_THUMB_RELEASE )
    {
        // always notify about this
        notify = true;
        scrollType = wxEVT_SCROLLWIN_THUMBRELEASE;
    }
    else
        return wxControl::PerformAction(action, numArg, strArg);

    // has scrollbar position changed?
    bool changed = m_thumbPos != thumbOld;
    if ( notify || changed )
    {
        if ( IsStandalone() )
        {
            // we should generate EVT_SCROLL events for the standalone
            // scrollbars and not the EVT_SCROLLWIN ones
            //
            // NB: we assume that scrollbar events are sequentially numbered
            //     but this should be ok as other code relies on this as well
            scrollType += wxEVT_SCROLL_TOP - wxEVT_SCROLLWIN_TOP;
            wxScrollEvent event(scrollType, this->GetId(), m_thumbPos,
                                IsVertical() ? wxVERTICAL : wxHORIZONTAL);
            event.SetEventObject(this);
            GetEventHandler()->ProcessEvent(event);
        }
        else // part of the window
        {
            wxScrollWinEvent event(scrollType, m_thumbPos,
                                   IsVertical() ? wxVERTICAL : wxHORIZONTAL);
            event.SetEventObject(this);
            GetParent()->GetEventHandler()->ProcessEvent(event);
        }
    }

    return true;
}

void wxScrollBar::ScrollToStart()
{
    DoSetThumb(0);
}

void wxScrollBar::ScrollToEnd()
{
    DoSetThumb(m_range - m_thumbSize);
}

bool wxScrollBar::ScrollLines(int nLines)
{
    DoSetThumb(m_thumbPos + nLines);
    return true;
}

bool wxScrollBar::ScrollPages(int nPages)
{
    DoSetThumb(m_thumbPos + nPages*m_pageSize);
    return true;
}

/* static */
wxInputHandler *wxScrollBar::GetStdInputHandler(wxInputHandler *handlerDef)
{
    static wxStdScrollBarInputHandler
        s_handler(wxTheme::Get()->GetRenderer(), handlerDef);

    return &s_handler;
}

// ============================================================================
// scroll bar input handler
// ============================================================================

// ----------------------------------------------------------------------------
// wxScrollBarTimer
// ----------------------------------------------------------------------------

wxScrollBarTimer::wxScrollBarTimer(wxStdScrollBarInputHandler *handler,
                                   const wxControlAction& action,
                                   wxScrollBar *control)
{
    m_handler = handler;
    m_action = action;
    m_control = control;
}

bool wxScrollBarTimer::DoNotify()
{
    return m_handler->OnScrollTimer(m_control, m_action);
}

// ----------------------------------------------------------------------------
// wxStdScrollBarInputHandler
// ----------------------------------------------------------------------------

wxStdScrollBarInputHandler::wxStdScrollBarInputHandler(wxRenderer *renderer,
                                                       wxInputHandler *handler)
                          : wxStdInputHandler(handler)
{
    m_renderer = renderer;
    m_winCapture = nullptr;
    m_htLast = wxHT_NOWHERE;
    m_timerScroll = nullptr;
}

wxStdScrollBarInputHandler::~wxStdScrollBarInputHandler()
{
    // normally, it's null by now but just in case the user somehow managed to
    // keep the mouse captured until now...
    delete m_timerScroll;
}

void wxStdScrollBarInputHandler::SetElementState(wxScrollBar *control,
                                                 int flag,
                                                 bool doIt)
{
    if ( m_htLast > wxHT_SCROLLBAR_FIRST && m_htLast < wxHT_SCROLLBAR_LAST )
    {
        wxScrollBar::Element
            elem = (wxScrollBar::Element)(m_htLast - wxHT_SCROLLBAR_FIRST - 1);

        int flags = control->GetState(elem);
        if ( doIt )
            flags |= flag;
        else
            flags &= ~flag;
        control->SetState(elem, flags);
    }
}

bool wxStdScrollBarInputHandler::OnScrollTimer(wxScrollBar *scrollbar,
                                               const wxControlAction& action)
{
    int oldThumbPos = scrollbar->GetThumbPosition();
    scrollbar->PerformAction(action);
    if ( scrollbar->GetThumbPosition() != oldThumbPos )
        return true;

    // we scrolled till the end
    m_timerScroll->Stop();

    return false;
}

void wxStdScrollBarInputHandler::StopScrolling(wxScrollBar *control)
{
    // return everything to the normal state
    if ( m_winCapture )
    {
        m_winCapture->ReleaseMouse();
        m_winCapture = nullptr;
    }

    m_btnCapture = -1;

    wxDELETE(m_timerScroll);

    // unpress the arrow and highlight the current element
    Press(control, false);
}

wxCoord
wxStdScrollBarInputHandler::GetMouseCoord(const wxScrollBar *scrollbar,
                                          const wxMouseEvent& event) const
{
    wxPoint pt = event.GetPosition();
    return scrollbar->GetWindowStyle() & wxVERTICAL ? pt.y : pt.x;
}

void wxStdScrollBarInputHandler::HandleThumbMove(wxScrollBar *scrollbar,
                                                 const wxMouseEvent& event)
{
    int thumbPos = GetMouseCoord(scrollbar, event) - m_ofsMouse;
    thumbPos = scrollbar->PixelToScrollbar(thumbPos);
    scrollbar->PerformAction(wxACTION_SCROLL_THUMB_MOVE, thumbPos);
}

bool wxStdScrollBarInputHandler::HandleKey(wxInputConsumer *consumer,
                                           const wxKeyEvent& event,
                                           bool pressed)
{
    // we only react to the key presses here
    if ( pressed )
    {
        wxControlAction action;
        switch ( event.GetKeyCode() )
        {
            case WXK_DOWN:
            case WXK_RIGHT:     action = wxACTION_SCROLL_LINE_DOWN; break;
            case WXK_UP:
            case WXK_LEFT:      action = wxACTION_SCROLL_LINE_UP;   break;
            case WXK_HOME:      action = wxACTION_SCROLL_START;     break;
            case WXK_END:       action = wxACTION_SCROLL_END;       break;
            case WXK_PAGEUP:    action = wxACTION_SCROLL_PAGE_UP;   break;
            case WXK_PAGEDOWN:  action = wxACTION_SCROLL_PAGE_DOWN; break;
        }

        if ( !action.IsEmpty() )
        {
            consumer->PerformAction(action);

            return true;
        }
    }

    return wxStdInputHandler::HandleKey(consumer, event, pressed);
}

bool wxStdScrollBarInputHandler::HandleMouse(wxInputConsumer *consumer,
                                             const wxMouseEvent& event)
{
    // is this a click event from an acceptable button?
    int btn = event.GetButton();
    if ( btn == wxMOUSE_BTN_LEFT )
    {
        // determine which part of the window mouse is in
        wxScrollBar *scrollbar = wxStaticCast(consumer->GetInputWindow(), wxScrollBar);
        wxHitTest ht = scrollbar->HitTestBar(event.GetPosition());

        // when the mouse is pressed on any scrollbar element, we capture it
        // and hold capture until the same mouse button is released
        if ( event.ButtonDown() || event.ButtonDClick() )
        {
            if ( !m_winCapture )
            {
                m_btnCapture = btn;
                m_winCapture = consumer->GetInputWindow();

                if ( !m_winCapture->HasCapture() )
                    m_winCapture->CaptureMouse();

                // generate the command
                bool hasAction = true;
                wxControlAction action;
                switch ( ht )
                {
                    case wxHT_SCROLLBAR_ARROW_LINE_1:
                        action = wxACTION_SCROLL_LINE_UP;
                        break;

                    case wxHT_SCROLLBAR_ARROW_LINE_2:
                        action = wxACTION_SCROLL_LINE_DOWN;
                        break;

                    case wxHT_SCROLLBAR_BAR_1:
                        action = wxACTION_SCROLL_PAGE_UP;
                        m_ptStartScrolling = event.GetPosition();
                        break;

                    case wxHT_SCROLLBAR_BAR_2:
                        action = wxACTION_SCROLL_PAGE_DOWN;
                        m_ptStartScrolling = event.GetPosition();
                        break;

                    case wxHT_SCROLLBAR_THUMB:
                        consumer->PerformAction(wxACTION_SCROLL_THUMB_DRAG);
                        m_ofsMouse = GetMouseCoord(scrollbar, event) -
                                     scrollbar->ScrollbarToPixel();
                        wxFALLTHROUGH;
                        // fall through: there is no immediate action

                    default:
                        hasAction = false;
                }

                // remove highlighting
                Highlight(scrollbar, false);
                m_htLast = ht;

                // and press the arrow or highlight thumb now instead
                if ( m_htLast == wxHT_SCROLLBAR_THUMB )
                    Highlight(scrollbar, true);
                else
                    Press(scrollbar, true);

                // start dragging
                if ( hasAction )
                {
                    m_timerScroll = new wxScrollBarTimer(this, action,
                                                         scrollbar);
                    m_timerScroll->StartAutoScroll();
                }
                //else: no (immediate) action

            }
            //else: mouse already captured, nothing to do
        }
        // release mouse if the *same* button went up
        else if ( btn == m_btnCapture )
        {
            if ( m_winCapture )
            {
                if ( m_winCapture->HasCapture() )
                    StopScrolling(scrollbar);

                // if we were dragging the thumb, send the last event
                if ( m_htLast == wxHT_SCROLLBAR_THUMB )
                {
                    scrollbar->PerformAction(wxACTION_SCROLL_THUMB_RELEASE);
                }

                m_htLast = ht;
                Highlight(scrollbar, true);
            }
            else
            {
                // this is not supposed to happen as the button can't go up
                // without going down previously and then we'd have
                // m_winCapture by now
                wxFAIL_MSG( wxT("logic error in mouse capturing code") );
            }
        }
    }

    return true;
}

bool wxStdScrollBarInputHandler::HandleMouseMove(wxInputConsumer *consumer,
                                                 const wxMouseEvent& event)
{
    wxScrollBar *scrollbar = wxStaticCast(consumer->GetInputWindow(), wxScrollBar);

    if ( m_winCapture )
    {
        if ( (m_htLast == wxHT_SCROLLBAR_THUMB) && event.Dragging() )
        {
            // make the thumb follow the mouse by keeping the same offset
            // between the mouse position and the top/left of the thumb
            HandleThumbMove(scrollbar, event);

            return true;
        }

        // no other changes are possible while the mouse is captured
        return false;
    }

    bool isArrow = scrollbar->GetArrows().HandleMouseMove(event);

    if ( event.Dragging() )
    {
        wxHitTest ht = scrollbar->HitTestBar(event.GetPosition());
        if ( ht == m_htLast )
        {
            // nothing changed
            return false;
        }

#ifdef DEBUG_MOUSE
        wxLogDebug("Scrollbar::OnMouseMove: ht = %d", ht);
#endif // DEBUG_MOUSE

        Highlight(scrollbar, false);
        m_htLast = ht;

        if ( !isArrow )
            Highlight(scrollbar, true);
        //else: already done by wxScrollArrows::HandleMouseMove
    }
    else if ( event.Leaving() )
    {
        if ( !isArrow )
            Highlight(scrollbar, false);

        m_htLast = wxHT_NOWHERE;
    }
    else // event.Entering()
    {
        // we don't process this event
        return false;
    }

    // we did something
    return true;
}

#endif // wxUSE_SCROLLBAR

#if wxUSE_TIMER && wxUSE_SCROLLBAR

// ----------------------------------------------------------------------------
// wxScrollTimer
// ----------------------------------------------------------------------------

wxScrollTimer::wxScrollTimer()
{
    m_skipNext = false;
}

void wxScrollTimer::StartAutoScroll()
{
    // start scrolling immediately
    if ( !DoNotify() )
    {
        // ... and end it too
        return;
    }

    // there is an initial delay before the scrollbar starts scrolling -
    // implement it by ignoring the first timer expiration and only start
    // scrolling from the second one
    m_skipNext = true;
    Start(200); // FIXME: hardcoded delay
}

void wxScrollTimer::Notify()
{
    if ( m_skipNext )
    {
        // scroll normally now - reduce the delay
        Stop();
        Start(50); // FIXME: hardcoded delay

        m_skipNext = false;
    }
    else
    {
        // if DoNotify() returns false, we're already deleted by the timer
        // event handler, so don't do anything else here
        (void)DoNotify();
    }
}

#endif // wxUSE_TIMER && wxUSE_SCROLLBAR
