1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/univ/scrolbar.cpp
3// Purpose:     wxScrollBar implementation
4// Author:      Vadim Zeitlin
5// Modified by:
6// Created:     20.08.00
7// RCS-ID:      $Id: scrolbar.cpp 42821 2006-10-31 09:26:55Z VS $
8// Copyright:   (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
9// Licence:     wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20#include "wx/wxprec.h"
21
22#ifdef __BORLANDC__
23    #pragma hdrstop
24#endif
25
26#if wxUSE_SCROLLBAR
27
28#include "wx/scrolbar.h"
29
30#ifndef WX_PRECOMP
31    #include "wx/timer.h"
32    #include "wx/dcclient.h"
33    #include "wx/validate.h"
34    #include "wx/log.h"
35#endif
36
37#include "wx/univ/scrtimer.h"
38
39#include "wx/univ/renderer.h"
40#include "wx/univ/inphand.h"
41#include "wx/univ/theme.h"
42
43#define WXDEBUG_SCROLLBAR
44
45#ifndef __WXDEBUG__
46    #undef WXDEBUG_SCROLLBAR
47#endif // !__WXDEBUG__
48
49#if defined(WXDEBUG_SCROLLBAR) && defined(__WXMSW__) && !defined(__WXMICROWIN__)
50#include "wx/msw/private.h"
51#endif
52
53// ----------------------------------------------------------------------------
54// wxScrollBarTimer: this class is used to repeatedly scroll the scrollbar
55// when the mouse is help pressed on the arrow or on the bar. It generates the
56// given scroll action command periodically.
57// ----------------------------------------------------------------------------
58
59class wxScrollBarTimer : public wxScrollTimer
60{
61public:
62    wxScrollBarTimer(wxStdScrollBarInputHandler *handler,
63                     const wxControlAction& action,
64                     wxScrollBar *control);
65
66protected:
67    virtual bool DoNotify();
68
69private:
70    wxStdScrollBarInputHandler *m_handler;
71    wxControlAction m_action;
72    wxScrollBar    *m_control;
73};
74
75// ============================================================================
76// implementation
77// ============================================================================
78
79IMPLEMENT_DYNAMIC_CLASS(wxScrollBar, wxControl)
80
81BEGIN_EVENT_TABLE(wxScrollBar, wxScrollBarBase)
82END_EVENT_TABLE()
83
84// ----------------------------------------------------------------------------
85// creation
86// ----------------------------------------------------------------------------
87
88#ifdef __VISUALC__
89    // warning C4355: 'this' : used in base member initializer list
90    #pragma warning(disable:4355)  // so what? disable it...
91#endif
92
93wxScrollBar::wxScrollBar()
94           : m_arrows(this)
95{
96    Init();
97}
98
99wxScrollBar::wxScrollBar(wxWindow *parent,
100                         wxWindowID id,
101                         const wxPoint& pos,
102                         const wxSize& size,
103                         long style,
104                         const wxValidator& validator,
105                         const wxString& name)
106           : m_arrows(this)
107{
108    Init();
109
110    (void)Create(parent, id, pos, size, style, validator, name);
111}
112
113#ifdef __VISUALC__
114    // warning C4355: 'this' : used in base member initializer list
115    #pragma warning(default:4355)
116#endif
117
118void wxScrollBar::Init()
119{
120    m_range =
121    m_thumbSize =
122    m_thumbPos =
123    m_pageSize = 0;
124
125    m_thumbPosOld = -1;
126
127    for ( size_t n = 0; n < WXSIZEOF(m_elementsState); n++ )
128    {
129        m_elementsState[n] = 0;
130    }
131
132    m_dirty = false;
133}
134
135bool wxScrollBar::Create(wxWindow *parent,
136                         wxWindowID id,
137                         const wxPoint &pos,
138                         const wxSize &size,
139                         long style,
140                         const wxValidator& validator,
141                         const wxString &name)
142{
143    // the scrollbars never have the border
144    style &= ~wxBORDER_MASK;
145
146    if ( !wxControl::Create(parent, id, pos, size, style, validator, name) )
147        return false;
148
149    SetInitialSize(size);
150
151    // override the cursor of the target window (if any)
152    SetCursor(wxCURSOR_ARROW);
153
154    CreateInputHandler(wxINP_HANDLER_SCROLLBAR);
155
156    return true;
157}
158
159wxScrollBar::~wxScrollBar()
160{
161}
162
163// ----------------------------------------------------------------------------
164// misc accessors
165// ----------------------------------------------------------------------------
166
167bool wxScrollBar::IsStandalone() const
168{
169    wxWindow *parent = GetParent();
170    if ( !parent )
171    {
172        return true;
173    }
174
175    return (parent->GetScrollbar(wxHORIZONTAL) != this) &&
176           (parent->GetScrollbar(wxVERTICAL) != this);
177}
178
179bool wxScrollBar::AcceptsFocus() const
180{
181    // the window scrollbars never accept focus
182    return wxScrollBarBase::AcceptsFocus() && IsStandalone();
183}
184
185// ----------------------------------------------------------------------------
186// scrollbar API
187// ----------------------------------------------------------------------------
188
189void wxScrollBar::DoSetThumb(int pos)
190{
191    // don't assert hecks here, we're a private function which is meant to be
192    // called with any args at all
193    if ( pos < 0 )
194    {
195        pos = 0;
196    }
197    else if ( pos > m_range - m_thumbSize )
198    {
199        pos = m_range - m_thumbSize;
200    }
201
202    if ( m_thumbPos == pos )
203    {
204        // nothing changed, avoid refreshes which would provoke flicker
205        return;
206    }
207
208    if ( m_thumbPosOld == -1 )
209    {
210        // remember the old thumb position
211        m_thumbPosOld = m_thumbPos;
212    }
213
214    m_thumbPos = pos;
215
216    // we have to refresh the part of the bar which was under the thumb and the
217    // thumb itself
218    m_elementsState[Element_Thumb] |= wxCONTROL_DIRTY;
219    m_elementsState[m_thumbPos > m_thumbPosOld
220                        ? Element_Bar_1 : Element_Bar_2] |= wxCONTROL_DIRTY;
221    m_dirty = true;
222}
223
224int wxScrollBar::GetThumbPosition() const
225{
226    return m_thumbPos;
227}
228
229int wxScrollBar::GetThumbSize() const
230{
231    return m_thumbSize;
232}
233
234int wxScrollBar::GetPageSize() const
235{
236    return m_pageSize;
237}
238
239int wxScrollBar::GetRange() const
240{
241    return m_range;
242}
243
244void wxScrollBar::SetThumbPosition(int pos)
245{
246    wxCHECK_RET( pos >= 0 && pos <= m_range, _T("thumb position out of range") );
247
248    DoSetThumb(pos);
249}
250
251void wxScrollBar::SetScrollbar(int position, int thumbSize,
252                               int range, int pageSize,
253                               bool refresh)
254{
255    // we only refresh everything when the range changes, thumb position
256    // changes are handled in OnIdle
257    bool needsRefresh = (range != m_range) ||
258                        (thumbSize != m_thumbSize) ||
259                        (pageSize != m_pageSize);
260
261    // set all parameters
262    m_range = range;
263    m_thumbSize = thumbSize;
264    SetThumbPosition(position);
265    m_pageSize = pageSize;
266
267    // ignore refresh parameter unless we really need to refresh everything -
268    // there ir a lot of existing code which just calls SetScrollbar() without
269    // specifying the last parameter even though it doesn't need at all to
270    // refresh the window immediately
271    if ( refresh && needsRefresh )
272    {
273        // and update the window
274        Refresh();
275        Update();
276    }
277}
278
279// ----------------------------------------------------------------------------
280// geometry
281// ----------------------------------------------------------------------------
282
283wxSize wxScrollBar::DoGetBestClientSize() const
284{
285    // this dimension is completely arbitrary
286    static const wxCoord SIZE = 140;
287
288    wxSize size = m_renderer->GetScrollbarArrowSize();
289    if ( IsVertical() )
290    {
291        size.y = SIZE;
292    }
293    else // horizontal
294    {
295        size.x = SIZE;
296    }
297
298    return size;
299}
300
301wxScrollArrows::Arrow wxScrollBar::HitTestArrow(const wxPoint& pt) const
302{
303    switch ( HitTestBar(pt) )
304    {
305        case wxHT_SCROLLBAR_ARROW_LINE_1:
306            return wxScrollArrows::Arrow_First;
307
308        case wxHT_SCROLLBAR_ARROW_LINE_2:
309            return wxScrollArrows::Arrow_Second;
310
311        default:
312            return wxScrollArrows::Arrow_None;
313    }
314}
315
316wxHitTest wxScrollBar::HitTestBar(const wxPoint& pt) const
317{
318    // we only need to work with either x or y coord depending on the
319    // orientation, choose one (but still check the other one to verify if the
320    // mouse is in the window at all)
321    const wxSize sizeArrowSB = m_renderer->GetScrollbarArrowSize();
322
323    wxCoord coord, sizeArrow, sizeTotal;
324    wxSize size = GetSize();
325    if ( GetWindowStyle() & wxVERTICAL )
326    {
327        if ( pt.x < 0 || pt.x > size.x )
328            return wxHT_NOWHERE;
329
330        coord = pt.y;
331        sizeArrow = sizeArrowSB.y;
332        sizeTotal = size.y;
333    }
334    else // horizontal
335    {
336        if ( pt.y < 0 || pt.y > size.y )
337            return wxHT_NOWHERE;
338
339        coord = pt.x;
340        sizeArrow = sizeArrowSB.x;
341        sizeTotal = size.x;
342    }
343
344    // test for the arrows first as it's faster
345    if ( coord < 0 || coord > sizeTotal )
346    {
347        return wxHT_NOWHERE;
348    }
349    else if ( coord < sizeArrow )
350    {
351        return wxHT_SCROLLBAR_ARROW_LINE_1;
352    }
353    else if ( coord > sizeTotal - sizeArrow )
354    {
355        return wxHT_SCROLLBAR_ARROW_LINE_2;
356    }
357    else
358    {
359        // calculate the thumb position in pixels
360        sizeTotal -= 2*sizeArrow;
361        wxCoord thumbStart, thumbEnd;
362        int range = GetRange();
363        if ( !range )
364        {
365            // clicking the scrollbar without range has no effect
366            return wxHT_NOWHERE;
367        }
368        else
369        {
370            GetScrollBarThumbSize(sizeTotal,
371                                  GetThumbPosition(),
372                                  GetThumbSize(),
373                                  range,
374                                  &thumbStart,
375                                  &thumbEnd);
376        }
377
378        // now compare with the thumb position
379        coord -= sizeArrow;
380        if ( coord < thumbStart )
381            return wxHT_SCROLLBAR_BAR_1;
382        else if ( coord > thumbEnd )
383            return wxHT_SCROLLBAR_BAR_2;
384        else
385            return wxHT_SCROLLBAR_THUMB;
386    }
387}
388
389/* static */
390void wxScrollBar::GetScrollBarThumbSize(wxCoord length,
391                                        int thumbPos,
392                                        int thumbSize,
393                                        int range,
394                                        wxCoord *thumbStart,
395                                        wxCoord *thumbEnd)
396{
397    // the thumb can't be made less than this number of pixels
398    static const wxCoord thumbMinWidth = 8; // FIXME: should be configurable
399
400    *thumbStart = (length*thumbPos) / range;
401    *thumbEnd = (length*(thumbPos + thumbSize)) / range;
402
403    if ( *thumbEnd - *thumbStart < thumbMinWidth )
404    {
405        // adjust the end if possible
406        if ( *thumbStart <= length - thumbMinWidth )
407        {
408            // yes, just make it wider
409            *thumbEnd = *thumbStart + thumbMinWidth;
410        }
411        else // it is at the bottom of the scrollbar
412        {
413            // so move it a bit up
414            *thumbStart = length - thumbMinWidth;
415            *thumbEnd = length;
416        }
417    }
418}
419
420wxRect wxScrollBar::GetScrollbarRect(wxScrollBar::Element elem,
421                                     int thumbPos) const
422{
423    if ( thumbPos == -1 )
424    {
425        thumbPos = GetThumbPosition();
426    }
427
428    const wxSize sizeArrow = m_renderer->GetScrollbarArrowSize();
429
430    wxSize sizeTotal = GetClientSize();
431    wxCoord *start, *width;
432    wxCoord length, arrow;
433    wxRect rect;
434    if ( IsVertical() )
435    {
436        rect.x = 0;
437        rect.width = sizeTotal.x;
438        length = sizeTotal.y;
439        start = &rect.y;
440        width = &rect.height;
441        arrow = sizeArrow.y;
442    }
443    else // horizontal
444    {
445        rect.y = 0;
446        rect.height = sizeTotal.y;
447        length = sizeTotal.x;
448        start = &rect.x;
449        width = &rect.width;
450        arrow = sizeArrow.x;
451    }
452
453    switch ( elem )
454    {
455        case wxScrollBar::Element_Arrow_Line_1:
456            *start = 0;
457            *width = arrow;
458            break;
459
460        case wxScrollBar::Element_Arrow_Line_2:
461            *start = length - arrow;
462            *width = arrow;
463            break;
464
465        case wxScrollBar::Element_Arrow_Page_1:
466        case wxScrollBar::Element_Arrow_Page_2:
467            // we don't have them at all
468            break;
469
470        case wxScrollBar::Element_Thumb:
471        case wxScrollBar::Element_Bar_1:
472        case wxScrollBar::Element_Bar_2:
473            // we need to calculate the thumb position - do it
474            {
475                length -= 2*arrow;
476                wxCoord thumbStart, thumbEnd;
477                int range = GetRange();
478                if ( !range )
479                {
480                    thumbStart =
481                    thumbEnd = 0;
482                }
483                else
484                {
485                    GetScrollBarThumbSize(length,
486                                          thumbPos,
487                                          GetThumbSize(),
488                                          range,
489                                          &thumbStart,
490                                          &thumbEnd);
491                }
492
493                if ( elem == wxScrollBar::Element_Thumb )
494                {
495                    *start = thumbStart;
496                    *width = thumbEnd - thumbStart;
497                }
498                else if ( elem == wxScrollBar::Element_Bar_1 )
499                {
500                    *start = 0;
501                    *width = thumbStart;
502                }
503                else // elem == wxScrollBar::Element_Bar_2
504                {
505                    *start = thumbEnd;
506                    *width = length - thumbEnd;
507                }
508
509                // everything is relative to the start of the shaft so far
510                *start += arrow;
511            }
512            break;
513
514        case wxScrollBar::Element_Max:
515        default:
516            wxFAIL_MSG( _T("unknown scrollbar element") );
517    }
518
519    return rect;
520}
521
522wxCoord wxScrollBar::GetScrollbarSize() const
523{
524    const wxSize sizeArrowSB = m_renderer->GetScrollbarArrowSize();
525
526    wxCoord sizeArrow, sizeTotal;
527    if ( GetWindowStyle() & wxVERTICAL )
528    {
529        sizeArrow = sizeArrowSB.y;
530        sizeTotal = GetSize().y;
531    }
532    else // horizontal
533    {
534        sizeArrow = sizeArrowSB.x;
535        sizeTotal = GetSize().x;
536    }
537
538    return sizeTotal - 2*sizeArrow;
539}
540
541
542wxCoord wxScrollBar::ScrollbarToPixel(int thumbPos)
543{
544    int range = GetRange();
545    if ( !range )
546    {
547        // the only valid position anyhow
548        return 0;
549    }
550
551    if ( thumbPos == -1 )
552    {
553        // by default use the current thumb position
554        thumbPos = GetThumbPosition();
555    }
556
557    const wxSize sizeArrow = m_renderer->GetScrollbarArrowSize();
558    return (thumbPos * GetScrollbarSize()) / range
559             + (IsVertical() ? sizeArrow.y : sizeArrow.x);
560}
561
562int wxScrollBar::PixelToScrollbar(wxCoord coord)
563{
564    const wxSize sizeArrow = m_renderer->GetScrollbarArrowSize();
565    return ((coord - (IsVertical() ? sizeArrow.y : sizeArrow.x)) *
566               GetRange() ) / GetScrollbarSize();
567}
568
569// ----------------------------------------------------------------------------
570// drawing
571// ----------------------------------------------------------------------------
572
573void wxScrollBar::OnInternalIdle()
574{
575    UpdateThumb();
576    wxControl::OnInternalIdle();
577}
578
579void wxScrollBar::UpdateThumb()
580{
581    if ( m_dirty )
582    {
583        for ( size_t n = 0; n < WXSIZEOF(m_elementsState); n++ )
584        {
585            if ( m_elementsState[n] & wxCONTROL_DIRTY )
586            {
587                wxRect rect = GetScrollbarRect((Element)n);
588
589                if ( rect.width && rect.height )
590                {
591                    // we try to avoid redrawing the entire shaft (which might
592                    // be quite long) if possible by only redrawing the area
593                    // wich really changed
594                    if ( (n == Element_Bar_1 || n == Element_Bar_2) &&
595                            (m_thumbPosOld != -1) )
596                    {
597                        // the less efficient but more reliable (i.e. this will
598                        // probably work everywhere) version: refresh the
599                        // distance covered by thumb since the last update
600#if 0
601                        wxRect rectOld =
602                            GetRenderer()->GetScrollbarRect(this,
603                                                            (Element)n,
604                                                            m_thumbPosOld);
605                        if ( IsVertical() )
606                        {
607                            if ( n == Element_Bar_1 )
608                                rect.SetTop(rectOld.GetBottom());
609                            else
610                                rect.SetBottom(rectOld.GetBottom());
611                        }
612                        else // horizontal
613                        {
614                            if ( n == Element_Bar_1 )
615                                rect.SetLeft(rectOld.GetRight());
616                            else
617                                rect.SetRight(rectOld.GetRight());
618                        }
619#else                   // efficient version: only repaint the area occupied by
620                        // the thumb previously - we can't do better than this
621                        rect = GetScrollbarRect(Element_Thumb, m_thumbPosOld);
622#endif // 0/1
623                    }
624
625#ifdef WXDEBUG_SCROLLBAR
626        static bool s_refreshDebug = false;
627        if ( s_refreshDebug )
628        {
629            wxClientDC dc(this);
630            dc.SetBrush(*wxCYAN_BRUSH);
631            dc.SetPen(*wxTRANSPARENT_PEN);
632            dc.DrawRectangle(rect);
633
634            // under Unix we use "--sync" X option for this
635            #if defined(__WXMSW__) && !defined(__WXMICROWIN__)
636                ::GdiFlush();
637                ::Sleep(200);
638            #endif // __WXMSW__
639        }
640#endif // WXDEBUG_SCROLLBAR
641
642                    Refresh(false, &rect);
643                }
644
645                m_elementsState[n] &= ~wxCONTROL_DIRTY;
646            }
647        }
648
649        m_dirty = false;
650    }
651}
652
653void wxScrollBar::DoDraw(wxControlRenderer *renderer)
654{
655    renderer->DrawScrollbar(this, m_thumbPosOld);
656
657    // clear all dirty flags
658    m_dirty = false;
659    m_thumbPosOld = -1;
660}
661
662// ----------------------------------------------------------------------------
663// state flags
664// ----------------------------------------------------------------------------
665
666static inline wxScrollBar::Element ElementForArrow(wxScrollArrows::Arrow arrow)
667{
668    return arrow == wxScrollArrows::Arrow_First
669            ? wxScrollBar::Element_Arrow_Line_1
670            : wxScrollBar::Element_Arrow_Line_2;
671}
672
673int wxScrollBar::GetArrowState(wxScrollArrows::Arrow arrow) const
674{
675    return GetState(ElementForArrow(arrow));
676}
677
678void wxScrollBar::SetArrowFlag(wxScrollArrows::Arrow arrow, int flag, bool set)
679{
680    Element which = ElementForArrow(arrow);
681    int state = GetState(which);
682    if ( set )
683        state |= flag;
684    else
685        state &= ~flag;
686
687    SetState(which, state);
688}
689
690int wxScrollBar::GetState(Element which) const
691{
692    // if the entire scrollbar is disabled, all of its elements are too
693    int flags = m_elementsState[which];
694    if ( !IsEnabled() )
695        flags |= wxCONTROL_DISABLED;
696
697    return flags;
698}
699
700void wxScrollBar::SetState(Element which, int flags)
701{
702    if ( (int)(m_elementsState[which] & ~wxCONTROL_DIRTY) != flags )
703    {
704        m_elementsState[which] = flags | wxCONTROL_DIRTY;
705
706        m_dirty = true;
707    }
708}
709
710// ----------------------------------------------------------------------------
711// input processing
712// ----------------------------------------------------------------------------
713
714bool wxScrollBar::OnArrow(wxScrollArrows::Arrow arrow)
715{
716    int oldThumbPos = GetThumbPosition();
717    PerformAction(arrow == wxScrollArrows::Arrow_First
718                    ? wxACTION_SCROLL_LINE_UP
719                    : wxACTION_SCROLL_LINE_DOWN);
720
721    // did we scroll till the end?
722    return GetThumbPosition() != oldThumbPos;
723}
724
725bool wxScrollBar::PerformAction(const wxControlAction& action,
726                                long numArg,
727                                const wxString& strArg)
728{
729    int thumbOld = m_thumbPos;
730
731    bool notify = false; // send an event about the change?
732
733    wxEventType scrollType;
734
735    // test for thumb move first as these events happen in quick succession
736    if ( action == wxACTION_SCROLL_THUMB_MOVE )
737    {
738        DoSetThumb(numArg);
739
740        // VS: we have to force redraw here, otherwise the thumb will lack
741        //     behind mouse cursor
742        UpdateThumb();
743
744        scrollType = wxEVT_SCROLLWIN_THUMBTRACK;
745    }
746    else if ( action == wxACTION_SCROLL_LINE_UP )
747    {
748        scrollType = wxEVT_SCROLLWIN_LINEUP;
749        ScrollLines(-1);
750    }
751    else if ( action == wxACTION_SCROLL_LINE_DOWN )
752    {
753        scrollType = wxEVT_SCROLLWIN_LINEDOWN;
754        ScrollLines(1);
755    }
756    else if ( action == wxACTION_SCROLL_PAGE_UP )
757    {
758        scrollType = wxEVT_SCROLLWIN_PAGEUP;
759        ScrollPages(-1);
760    }
761    else if ( action == wxACTION_SCROLL_PAGE_DOWN )
762    {
763        scrollType = wxEVT_SCROLLWIN_PAGEDOWN;
764        ScrollPages(1);
765    }
766    else if ( action == wxACTION_SCROLL_START )
767    {
768        scrollType = wxEVT_SCROLLWIN_THUMBRELEASE; // anything better?
769        ScrollToStart();
770    }
771    else if ( action == wxACTION_SCROLL_END )
772    {
773        scrollType = wxEVT_SCROLLWIN_THUMBRELEASE; // anything better?
774        ScrollToEnd();
775    }
776    else if ( action == wxACTION_SCROLL_THUMB_DRAG )
777    {
778        // we won't use it but this line suppresses the compiler
779        // warning about "variable may be used without having been
780        // initialized"
781        scrollType = wxEVT_NULL;
782    }
783    else if ( action == wxACTION_SCROLL_THUMB_RELEASE )
784    {
785        // always notify about this
786        notify = true;
787        scrollType = wxEVT_SCROLLWIN_THUMBRELEASE;
788    }
789    else
790        return wxControl::PerformAction(action, numArg, strArg);
791
792    // has scrollbar position changed?
793    bool changed = m_thumbPos != thumbOld;
794    if ( notify || changed )
795    {
796        if ( IsStandalone() )
797        {
798            // we should generate EVT_SCROLL events for the standalone
799            // scrollbars and not the EVT_SCROLLWIN ones
800            //
801            // NB: we assume that scrollbar events are sequentially numbered
802            //     but this should be ok as other code relies on this as well
803            scrollType += wxEVT_SCROLL_TOP - wxEVT_SCROLLWIN_TOP;
804            wxScrollEvent event(scrollType, this->GetId(), m_thumbPos,
805                                IsVertical() ? wxVERTICAL : wxHORIZONTAL);
806            event.SetEventObject(this);
807            GetEventHandler()->ProcessEvent(event);
808        }
809        else // part of the window
810        {
811            wxScrollWinEvent event(scrollType, m_thumbPos,
812                                   IsVertical() ? wxVERTICAL : wxHORIZONTAL);
813            event.SetEventObject(this);
814            GetParent()->GetEventHandler()->ProcessEvent(event);
815        }
816    }
817
818    return true;
819}
820
821void wxScrollBar::ScrollToStart()
822{
823    DoSetThumb(0);
824}
825
826void wxScrollBar::ScrollToEnd()
827{
828    DoSetThumb(m_range - m_thumbSize);
829}
830
831bool wxScrollBar::ScrollLines(int nLines)
832{
833    DoSetThumb(m_thumbPos + nLines);
834    return true;
835}
836
837bool wxScrollBar::ScrollPages(int nPages)
838{
839    DoSetThumb(m_thumbPos + nPages*m_pageSize);
840    return true;
841}
842
843/* static */
844wxInputHandler *wxScrollBar::GetStdInputHandler(wxInputHandler *handlerDef)
845{
846    static wxStdScrollBarInputHandler
847        s_handler(wxTheme::Get()->GetRenderer(), handlerDef);
848
849    return &s_handler;
850}
851
852// ============================================================================
853// scroll bar input handler
854// ============================================================================
855
856// ----------------------------------------------------------------------------
857// wxScrollBarTimer
858// ----------------------------------------------------------------------------
859
860wxScrollBarTimer::wxScrollBarTimer(wxStdScrollBarInputHandler *handler,
861                                   const wxControlAction& action,
862                                   wxScrollBar *control)
863{
864    m_handler = handler;
865    m_action = action;
866    m_control = control;
867}
868
869bool wxScrollBarTimer::DoNotify()
870{
871    return m_handler->OnScrollTimer(m_control, m_action);
872}
873
874// ----------------------------------------------------------------------------
875// wxStdScrollBarInputHandler
876// ----------------------------------------------------------------------------
877
878wxStdScrollBarInputHandler::wxStdScrollBarInputHandler(wxRenderer *renderer,
879                                                       wxInputHandler *handler)
880                          : wxStdInputHandler(handler)
881{
882    m_renderer = renderer;
883    m_winCapture = NULL;
884    m_htLast = wxHT_NOWHERE;
885    m_timerScroll = NULL;
886}
887
888wxStdScrollBarInputHandler::~wxStdScrollBarInputHandler()
889{
890    // normally, it's NULL by now but just in case the user somehow managed to
891    // keep the mouse captured until now...
892    delete m_timerScroll;
893}
894
895void wxStdScrollBarInputHandler::SetElementState(wxScrollBar *control,
896                                                 int flag,
897                                                 bool doIt)
898{
899    if ( m_htLast > wxHT_SCROLLBAR_FIRST && m_htLast < wxHT_SCROLLBAR_LAST )
900    {
901        wxScrollBar::Element
902            elem = (wxScrollBar::Element)(m_htLast - wxHT_SCROLLBAR_FIRST - 1);
903
904        int flags = control->GetState(elem);
905        if ( doIt )
906            flags |= flag;
907        else
908            flags &= ~flag;
909        control->SetState(elem, flags);
910    }
911}
912
913bool wxStdScrollBarInputHandler::OnScrollTimer(wxScrollBar *scrollbar,
914                                               const wxControlAction& action)
915{
916    int oldThumbPos = scrollbar->GetThumbPosition();
917    scrollbar->PerformAction(action);
918    if ( scrollbar->GetThumbPosition() != oldThumbPos )
919        return true;
920
921    // we scrolled till the end
922    m_timerScroll->Stop();
923
924    return false;
925}
926
927void wxStdScrollBarInputHandler::StopScrolling(wxScrollBar *control)
928{
929    // return everything to the normal state
930    if ( m_winCapture )
931    {
932        m_winCapture->ReleaseMouse();
933        m_winCapture = NULL;
934    }
935
936    m_btnCapture = -1;
937
938    if ( m_timerScroll )
939    {
940        delete m_timerScroll;
941        m_timerScroll = NULL;
942    }
943
944    // unpress the arrow and highlight the current element
945    Press(control, false);
946}
947
948wxCoord
949wxStdScrollBarInputHandler::GetMouseCoord(const wxScrollBar *scrollbar,
950                                          const wxMouseEvent& event) const
951{
952    wxPoint pt = event.GetPosition();
953    return scrollbar->GetWindowStyle() & wxVERTICAL ? pt.y : pt.x;
954}
955
956void wxStdScrollBarInputHandler::HandleThumbMove(wxScrollBar *scrollbar,
957                                                 const wxMouseEvent& event)
958{
959    int thumbPos = GetMouseCoord(scrollbar, event) - m_ofsMouse;
960    thumbPos = scrollbar->PixelToScrollbar(thumbPos);
961    scrollbar->PerformAction(wxACTION_SCROLL_THUMB_MOVE, thumbPos);
962}
963
964bool wxStdScrollBarInputHandler::HandleKey(wxInputConsumer *consumer,
965                                           const wxKeyEvent& event,
966                                           bool pressed)
967{
968    // we only react to the key presses here
969    if ( pressed )
970    {
971        wxControlAction action;
972        switch ( event.GetKeyCode() )
973        {
974            case WXK_DOWN:
975            case WXK_RIGHT:     action = wxACTION_SCROLL_LINE_DOWN; break;
976            case WXK_UP:
977            case WXK_LEFT:      action = wxACTION_SCROLL_LINE_UP;   break;
978            case WXK_HOME:      action = wxACTION_SCROLL_START;     break;
979            case WXK_END:       action = wxACTION_SCROLL_END;       break;
980            case WXK_PAGEUP:    action = wxACTION_SCROLL_PAGE_UP;   break;
981            case WXK_PAGEDOWN:  action = wxACTION_SCROLL_PAGE_DOWN; break;
982        }
983
984        if ( !action.IsEmpty() )
985        {
986            consumer->PerformAction(action);
987
988            return true;
989        }
990    }
991
992    return wxStdInputHandler::HandleKey(consumer, event, pressed);
993}
994
995bool wxStdScrollBarInputHandler::HandleMouse(wxInputConsumer *consumer,
996                                             const wxMouseEvent& event)
997{
998    // is this a click event from an acceptable button?
999    int btn = event.GetButton();
1000    if ( btn == wxMOUSE_BTN_LEFT )
1001    {
1002        // determine which part of the window mouse is in
1003        wxScrollBar *scrollbar = wxStaticCast(consumer->GetInputWindow(), wxScrollBar);
1004        wxHitTest ht = scrollbar->HitTestBar(event.GetPosition());
1005
1006        // when the mouse is pressed on any scrollbar element, we capture it
1007        // and hold capture until the same mouse button is released
1008        if ( event.ButtonDown() || event.ButtonDClick() )
1009        {
1010            if ( !m_winCapture )
1011            {
1012                m_btnCapture = btn;
1013                m_winCapture = consumer->GetInputWindow();
1014                m_winCapture->CaptureMouse();
1015
1016                // generate the command
1017                bool hasAction = true;
1018                wxControlAction action;
1019                switch ( ht )
1020                {
1021                    case wxHT_SCROLLBAR_ARROW_LINE_1:
1022                        action = wxACTION_SCROLL_LINE_UP;
1023                        break;
1024
1025                    case wxHT_SCROLLBAR_ARROW_LINE_2:
1026                        action = wxACTION_SCROLL_LINE_DOWN;
1027                        break;
1028
1029                    case wxHT_SCROLLBAR_BAR_1:
1030                        action = wxACTION_SCROLL_PAGE_UP;
1031                        m_ptStartScrolling = event.GetPosition();
1032                        break;
1033
1034                    case wxHT_SCROLLBAR_BAR_2:
1035                        action = wxACTION_SCROLL_PAGE_DOWN;
1036                        m_ptStartScrolling = event.GetPosition();
1037                        break;
1038
1039                    case wxHT_SCROLLBAR_THUMB:
1040                        consumer->PerformAction(wxACTION_SCROLL_THUMB_DRAG);
1041                        m_ofsMouse = GetMouseCoord(scrollbar, event) -
1042                                     scrollbar->ScrollbarToPixel();
1043
1044                        // fall through: there is no immediate action
1045
1046                    default:
1047                        hasAction = false;
1048                }
1049
1050                // remove highlighting
1051                Highlight(scrollbar, false);
1052                m_htLast = ht;
1053
1054                // and press the arrow or highlight thumb now instead
1055                if ( m_htLast == wxHT_SCROLLBAR_THUMB )
1056                    Highlight(scrollbar, true);
1057                else
1058                    Press(scrollbar, true);
1059
1060                // start dragging
1061                if ( hasAction )
1062                {
1063                    m_timerScroll = new wxScrollBarTimer(this, action,
1064                                                         scrollbar);
1065                    m_timerScroll->StartAutoScroll();
1066                }
1067                //else: no (immediate) action
1068
1069            }
1070            //else: mouse already captured, nothing to do
1071        }
1072        // release mouse if the *same* button went up
1073        else if ( btn == m_btnCapture )
1074        {
1075            if ( m_winCapture )
1076            {
1077                StopScrolling(scrollbar);
1078
1079                // if we were dragging the thumb, send the last event
1080                if ( m_htLast == wxHT_SCROLLBAR_THUMB )
1081                {
1082                    scrollbar->PerformAction(wxACTION_SCROLL_THUMB_RELEASE);
1083                }
1084
1085                m_htLast = ht;
1086                Highlight(scrollbar, true);
1087            }
1088            else
1089            {
1090                // this is not supposed to happen as the button can't go up
1091                // without going down previously and then we'd have
1092                // m_winCapture by now
1093                wxFAIL_MSG( _T("logic error in mouse capturing code") );
1094            }
1095        }
1096    }
1097
1098    return wxStdInputHandler::HandleMouse(consumer, event);
1099}
1100
1101bool wxStdScrollBarInputHandler::HandleMouseMove(wxInputConsumer *consumer,
1102                                                 const wxMouseEvent& event)
1103{
1104    wxScrollBar *scrollbar = wxStaticCast(consumer->GetInputWindow(), wxScrollBar);
1105
1106    if ( m_winCapture )
1107    {
1108        if ( (m_htLast == wxHT_SCROLLBAR_THUMB) && event.Dragging() )
1109        {
1110            // make the thumb follow the mouse by keeping the same offset
1111            // between the mouse position and the top/left of the thumb
1112            HandleThumbMove(scrollbar, event);
1113
1114            return true;
1115        }
1116
1117        // no other changes are possible while the mouse is captured
1118        return false;
1119    }
1120
1121    bool isArrow = scrollbar->GetArrows().HandleMouseMove(event);
1122
1123    if ( event.Dragging() )
1124    {
1125        wxHitTest ht = scrollbar->HitTestBar(event.GetPosition());
1126        if ( ht == m_htLast )
1127        {
1128            // nothing changed
1129            return false;
1130        }
1131
1132#ifdef DEBUG_MOUSE
1133        wxLogDebug("Scrollbar::OnMouseMove: ht = %d", ht);
1134#endif // DEBUG_MOUSE
1135
1136        Highlight(scrollbar, false);
1137        m_htLast = ht;
1138
1139        if ( !isArrow )
1140            Highlight(scrollbar, true);
1141        //else: already done by wxScrollArrows::HandleMouseMove
1142    }
1143    else if ( event.Leaving() )
1144    {
1145        if ( !isArrow )
1146            Highlight(scrollbar, false);
1147
1148        m_htLast = wxHT_NOWHERE;
1149    }
1150    else // event.Entering()
1151    {
1152        // we don't process this event
1153        return false;
1154    }
1155
1156    // we did something
1157    return true;
1158}
1159
1160#endif // wxUSE_SCROLLBAR
1161
1162#if wxUSE_TIMER
1163
1164// ----------------------------------------------------------------------------
1165// wxScrollTimer
1166// ----------------------------------------------------------------------------
1167
1168wxScrollTimer::wxScrollTimer()
1169{
1170    m_skipNext = false;
1171}
1172
1173void wxScrollTimer::StartAutoScroll()
1174{
1175    // start scrolling immediately
1176    if ( !DoNotify() )
1177    {
1178        // ... and end it too
1179        return;
1180    }
1181
1182    // there is an initial delay before the scrollbar starts scrolling -
1183    // implement it by ignoring the first timer expiration and only start
1184    // scrolling from the second one
1185    m_skipNext = true;
1186    Start(200); // FIXME: hardcoded delay
1187}
1188
1189void wxScrollTimer::Notify()
1190{
1191    if ( m_skipNext )
1192    {
1193        // scroll normally now - reduce the delay
1194        Stop();
1195        Start(50); // FIXME: hardcoded delay
1196
1197        m_skipNext = false;
1198    }
1199    else
1200    {
1201        // if DoNotify() returns false, we're already deleted by the timer
1202        // event handler, so don't do anything else here
1203        (void)DoNotify();
1204    }
1205}
1206
1207#endif // wxUSE_TIMER
1208