1 /////////////////////////////////////////////////////////////////////////////
2 // Name: univ/scrolbar.cpp
3 // Purpose: wxScrollBar implementation
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21 #pragma implementation "univscrolbar.h"
24 #include "wx/wxprec.h"
35 #include "wx/dcclient.h"
36 #include "wx/scrolbar.h"
37 #include "wx/validate.h"
40 #include "wx/univ/scrtimer.h"
42 #include "wx/univ/renderer.h"
43 #include "wx/univ/inphand.h"
44 #include "wx/univ/theme.h"
47 #define WXDEBUG_SCROLLBAR
50 #undef WXDEBUG_SCROLLBAR
51 #endif // !__WXDEBUG__
53 #if defined(WXDEBUG_SCROLLBAR) && defined(__WXMSW__) && !defined(__WXMICROWIN__)
54 #include "wx/msw/private.h"
57 // ----------------------------------------------------------------------------
58 // wxScrollBarTimer: this class is used to repeatedly scroll the scrollbar
59 // when the mouse is help pressed on the arrow or on the bar. It generates the
60 // given scroll action command periodically.
61 // ----------------------------------------------------------------------------
63 class wxScrollBarTimer
: public wxScrollTimer
66 wxScrollBarTimer(wxStdScrollBarInputHandler
*handler
,
67 const wxControlAction
& action
,
68 wxScrollBar
*control
);
71 virtual bool DoNotify();
74 wxStdScrollBarInputHandler
*m_handler
;
75 wxControlAction m_action
;
76 wxScrollBar
*m_control
;
79 // ============================================================================
81 // ============================================================================
83 IMPLEMENT_DYNAMIC_CLASS(wxScrollBar
, wxControl
)
85 BEGIN_EVENT_TABLE(wxScrollBar
, wxScrollBarBase
)
88 // ----------------------------------------------------------------------------
90 // ----------------------------------------------------------------------------
93 // warning C4355: 'this' : used in base member initializer list
94 #pragma warning(disable:4355) // so what? disable it...
97 wxScrollBar::wxScrollBar()
103 wxScrollBar::wxScrollBar(wxWindow
*parent
,
108 const wxValidator
& validator
,
109 const wxString
& name
)
114 (void)Create(parent
, id
, pos
, size
, style
, validator
, name
);
118 // warning C4355: 'this' : used in base member initializer list
119 #pragma warning(default:4355)
122 void wxScrollBar::Init()
131 for ( size_t n
= 0; n
< WXSIZEOF(m_elementsState
); n
++ )
133 m_elementsState
[n
] = 0;
139 bool wxScrollBar::Create(wxWindow
*parent
,
144 const wxValidator
& validator
,
145 const wxString
&name
)
147 // the scrollbars never have the border
148 style
&= ~wxBORDER_MASK
;
150 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
155 // override the cursor of the target window (if any)
156 SetCursor(wxCURSOR_ARROW
);
158 CreateInputHandler(wxINP_HANDLER_SCROLLBAR
);
163 wxScrollBar::~wxScrollBar()
167 // ----------------------------------------------------------------------------
169 // ----------------------------------------------------------------------------
171 bool wxScrollBar::IsStandalone() const
173 wxWindow
*parent
= GetParent();
179 return (parent
->GetScrollbar(wxHORIZONTAL
) != this) &&
180 (parent
->GetScrollbar(wxVERTICAL
) != this);
183 bool wxScrollBar::AcceptsFocus() const
185 // the window scrollbars never accept focus
186 return wxScrollBarBase::AcceptsFocus() && IsStandalone();
189 // ----------------------------------------------------------------------------
191 // ----------------------------------------------------------------------------
193 void wxScrollBar::DoSetThumb(int pos
)
195 // don't assert hecks here, we're a private function which is meant to be
196 // called with any args at all
201 else if ( pos
> m_range
- m_thumbSize
)
203 pos
= m_range
- m_thumbSize
;
206 if ( m_thumbPos
== pos
)
208 // nothing changed, avoid refreshes which would provoke flicker
212 if ( m_thumbPosOld
== -1 )
214 // remember the old thumb position
215 m_thumbPosOld
= m_thumbPos
;
220 // we have to refresh the part of the bar which was under the thumb and the
222 m_elementsState
[Element_Thumb
] |= wxCONTROL_DIRTY
;
223 m_elementsState
[m_thumbPos
> m_thumbPosOld
224 ? Element_Bar_1
: Element_Bar_2
] |= wxCONTROL_DIRTY
;
228 int wxScrollBar::GetThumbPosition() const
233 int wxScrollBar::GetThumbSize() const
238 int wxScrollBar::GetPageSize() const
243 int wxScrollBar::GetRange() const
248 void wxScrollBar::SetThumbPosition(int pos
)
250 wxCHECK_RET( pos
>= 0 && pos
<= m_range
, _T("thumb position out of range") );
255 void wxScrollBar::SetScrollbar(int position
, int thumbSize
,
256 int range
, int pageSize
,
259 // we only refresh everything when the range changes, thumb position
260 // changes are handled in OnIdle
261 bool needsRefresh
= (range
!= m_range
) ||
262 (thumbSize
!= m_thumbSize
) ||
263 (pageSize
!= m_pageSize
);
265 // set all parameters
267 m_thumbSize
= thumbSize
;
268 SetThumbPosition(position
);
269 m_pageSize
= pageSize
;
271 // ignore refresh parameter unless we really need to refresh everything -
272 // there ir a lot of existing code which just calls SetScrollbar() without
273 // specifying the last parameter even though it doesn't need at all to
274 // refresh the window immediately
275 if ( refresh
&& needsRefresh
)
277 // and update the window
283 // ----------------------------------------------------------------------------
285 // ----------------------------------------------------------------------------
287 wxSize
wxScrollBar::DoGetBestClientSize() const
289 // this dimension is completely arbitrary
290 static const wxCoord SIZE
= 140;
292 wxSize size
= m_renderer
->GetScrollbarArrowSize();
305 wxScrollArrows::Arrow
wxScrollBar::HitTest(const wxPoint
& pt
) const
307 switch ( m_renderer
->HitTestScrollbar(this, pt
) )
309 case wxHT_SCROLLBAR_ARROW_LINE_1
:
310 return wxScrollArrows::Arrow_First
;
312 case wxHT_SCROLLBAR_ARROW_LINE_2
:
313 return wxScrollArrows::Arrow_Second
;
316 return wxScrollArrows::Arrow_None
;
320 // ----------------------------------------------------------------------------
322 // ----------------------------------------------------------------------------
324 void wxScrollBar::OnInternalIdle()
327 wxControl::OnInternalIdle();
330 void wxScrollBar::UpdateThumb()
334 for ( size_t n
= 0; n
< WXSIZEOF(m_elementsState
); n
++ )
336 if ( m_elementsState
[n
] & wxCONTROL_DIRTY
)
338 wxRect rect
= GetRenderer()->GetScrollbarRect(this, (Element
)n
);
340 if ( rect
.width
&& rect
.height
)
342 // we try to avoid redrawing the entire shaft (which might
343 // be quite long) if possible by only redrawing the area
344 // wich really changed
345 if ( (n
== Element_Bar_1
|| n
== Element_Bar_2
) &&
346 (m_thumbPosOld
!= -1) )
348 // the less efficient but more reliable (i.e. this will
349 // probably work everywhere) version: refresh the
350 // distance covered by thumb since the last update
353 GetRenderer()->GetScrollbarRect(this,
358 if ( n
== Element_Bar_1
)
359 rect
.SetTop(rectOld
.GetBottom());
361 rect
.SetBottom(rectOld
.GetBottom());
365 if ( n
== Element_Bar_1
)
366 rect
.SetLeft(rectOld
.GetRight());
368 rect
.SetRight(rectOld
.GetRight());
370 #else // efficient version: only repaint the area occupied by
371 // the thumb previously - we can't do better than this
372 rect
= GetRenderer()->GetScrollbarRect(this,
378 #ifdef WXDEBUG_SCROLLBAR
379 static bool s_refreshDebug
= false;
380 if ( s_refreshDebug
)
383 dc
.SetBrush(*wxCYAN_BRUSH
);
384 dc
.SetPen(*wxTRANSPARENT_PEN
);
385 dc
.DrawRectangle(rect
);
387 // under Unix we use "--sync" X option for this
388 #if defined(__WXMSW__) && !defined(__WXMICROWIN__)
393 #endif // WXDEBUG_SCROLLBAR
395 Refresh(false, &rect
);
398 m_elementsState
[n
] &= ~wxCONTROL_DIRTY
;
406 void wxScrollBar::DoDraw(wxControlRenderer
*renderer
)
408 renderer
->DrawScrollbar(this, m_thumbPosOld
);
410 // clear all dirty flags
415 // ----------------------------------------------------------------------------
417 // ----------------------------------------------------------------------------
419 static inline wxScrollBar::Element
ElementForArrow(wxScrollArrows::Arrow arrow
)
421 return arrow
== wxScrollArrows::Arrow_First
422 ? wxScrollBar::Element_Arrow_Line_1
423 : wxScrollBar::Element_Arrow_Line_2
;
426 int wxScrollBar::GetArrowState(wxScrollArrows::Arrow arrow
) const
428 return GetState(ElementForArrow(arrow
));
431 void wxScrollBar::SetArrowFlag(wxScrollArrows::Arrow arrow
, int flag
, bool set
)
433 Element which
= ElementForArrow(arrow
);
434 int state
= GetState(which
);
440 SetState(which
, state
);
443 int wxScrollBar::GetState(Element which
) const
445 // if the entire scrollbar is disabled, all of its elements are too
446 int flags
= m_elementsState
[which
];
448 flags
|= wxCONTROL_DISABLED
;
453 void wxScrollBar::SetState(Element which
, int flags
)
455 if ( (int)(m_elementsState
[which
] & ~wxCONTROL_DIRTY
) != flags
)
457 m_elementsState
[which
] = flags
| wxCONTROL_DIRTY
;
463 // ----------------------------------------------------------------------------
465 // ----------------------------------------------------------------------------
467 bool wxScrollBar::OnArrow(wxScrollArrows::Arrow arrow
)
469 int oldThumbPos
= GetThumbPosition();
470 PerformAction(arrow
== wxScrollArrows::Arrow_First
471 ? wxACTION_SCROLL_LINE_UP
472 : wxACTION_SCROLL_LINE_DOWN
);
474 // did we scroll till the end?
475 return GetThumbPosition() != oldThumbPos
;
478 bool wxScrollBar::PerformAction(const wxControlAction
& action
,
480 const wxString
& strArg
)
482 int thumbOld
= m_thumbPos
;
484 bool notify
= false; // send an event about the change?
486 wxEventType scrollType
;
488 // test for thumb move first as these events happen in quick succession
489 if ( action
== wxACTION_SCROLL_THUMB_MOVE
)
493 // VS: we have to force redraw here, otherwise the thumb will lack
494 // behind mouse cursor
497 scrollType
= wxEVT_SCROLLWIN_THUMBTRACK
;
499 else if ( action
== wxACTION_SCROLL_LINE_UP
)
501 scrollType
= wxEVT_SCROLLWIN_LINEUP
;
504 else if ( action
== wxACTION_SCROLL_LINE_DOWN
)
506 scrollType
= wxEVT_SCROLLWIN_LINEDOWN
;
509 else if ( action
== wxACTION_SCROLL_PAGE_UP
)
511 scrollType
= wxEVT_SCROLLWIN_PAGEUP
;
514 else if ( action
== wxACTION_SCROLL_PAGE_DOWN
)
516 scrollType
= wxEVT_SCROLLWIN_PAGEDOWN
;
519 else if ( action
== wxACTION_SCROLL_START
)
521 scrollType
= wxEVT_SCROLLWIN_THUMBRELEASE
; // anything better?
524 else if ( action
== wxACTION_SCROLL_END
)
526 scrollType
= wxEVT_SCROLLWIN_THUMBRELEASE
; // anything better?
529 else if ( action
== wxACTION_SCROLL_THUMB_DRAG
)
531 // we won't use it but this line suppresses the compiler
532 // warning about "variable may be used without having been
534 scrollType
= wxEVT_NULL
;
536 else if ( action
== wxACTION_SCROLL_THUMB_RELEASE
)
538 // always notify about this
540 scrollType
= wxEVT_SCROLLWIN_THUMBRELEASE
;
543 return wxControl::PerformAction(action
, numArg
, strArg
);
545 // has scrollbar position changed?
546 bool changed
= m_thumbPos
!= thumbOld
;
547 if ( notify
|| changed
)
549 if ( IsStandalone() )
551 // we should generate EVT_SCROLL events for the standalone
552 // scrollbars and not the EVT_SCROLLWIN ones
554 // NB: we assume that scrollbar events are sequentially numbered
555 // but this should be ok as other code relies on this as well
556 scrollType
+= wxEVT_SCROLL_TOP
- wxEVT_SCROLLWIN_TOP
;
557 wxScrollEvent
event(scrollType
, this->GetId(), m_thumbPos
,
558 IsVertical() ? wxVERTICAL
: wxHORIZONTAL
);
559 event
.SetEventObject(this);
560 GetParent()->GetEventHandler()->ProcessEvent(event
);
562 else // part of the window
564 wxScrollWinEvent
event(scrollType
, m_thumbPos
,
565 IsVertical() ? wxVERTICAL
: wxHORIZONTAL
);
566 event
.SetEventObject(this);
567 GetParent()->GetEventHandler()->ProcessEvent(event
);
574 void wxScrollBar::ScrollToStart()
579 void wxScrollBar::ScrollToEnd()
581 DoSetThumb(m_range
- m_thumbSize
);
584 bool wxScrollBar::ScrollLines(int nLines
)
586 DoSetThumb(m_thumbPos
+ nLines
);
590 bool wxScrollBar::ScrollPages(int nPages
)
592 DoSetThumb(m_thumbPos
+ nPages
*m_pageSize
);
596 // ============================================================================
597 // scroll bar input handler
598 // ============================================================================
600 // ----------------------------------------------------------------------------
602 // ----------------------------------------------------------------------------
604 wxScrollBarTimer::wxScrollBarTimer(wxStdScrollBarInputHandler
*handler
,
605 const wxControlAction
& action
,
606 wxScrollBar
*control
)
613 bool wxScrollBarTimer::DoNotify()
615 return m_handler
->OnScrollTimer(m_control
, m_action
);
618 // ----------------------------------------------------------------------------
619 // wxStdScrollBarInputHandler
620 // ----------------------------------------------------------------------------
622 wxStdScrollBarInputHandler::wxStdScrollBarInputHandler(wxRenderer
*renderer
,
623 wxInputHandler
*handler
)
624 : wxStdInputHandler(handler
)
626 m_renderer
= renderer
;
628 m_htLast
= wxHT_NOWHERE
;
629 m_timerScroll
= NULL
;
632 wxStdScrollBarInputHandler::~wxStdScrollBarInputHandler()
634 // normally, it's NULL by now but just in case the user somehow managed to
635 // keep the mouse captured until now...
636 delete m_timerScroll
;
639 void wxStdScrollBarInputHandler::SetElementState(wxScrollBar
*control
,
643 if ( m_htLast
> wxHT_SCROLLBAR_FIRST
&& m_htLast
< wxHT_SCROLLBAR_LAST
)
646 elem
= (wxScrollBar::Element
)(m_htLast
- wxHT_SCROLLBAR_FIRST
- 1);
648 int flags
= control
->GetState(elem
);
653 control
->SetState(elem
, flags
);
657 bool wxStdScrollBarInputHandler::OnScrollTimer(wxScrollBar
*scrollbar
,
658 const wxControlAction
& action
)
660 int oldThumbPos
= scrollbar
->GetThumbPosition();
661 scrollbar
->PerformAction(action
);
662 if ( scrollbar
->GetThumbPosition() != oldThumbPos
)
665 // we scrolled till the end
666 m_timerScroll
->Stop();
671 void wxStdScrollBarInputHandler::StopScrolling(wxScrollBar
*control
)
673 // return everything to the normal state
676 m_winCapture
->ReleaseMouse();
684 delete m_timerScroll
;
685 m_timerScroll
= NULL
;
688 // unpress the arrow and highlight the current element
689 Press(control
, false);
693 wxStdScrollBarInputHandler::GetMouseCoord(const wxScrollBar
*scrollbar
,
694 const wxMouseEvent
& event
) const
696 wxPoint pt
= event
.GetPosition();
697 return scrollbar
->GetWindowStyle() & wxVERTICAL
? pt
.y
: pt
.x
;
700 void wxStdScrollBarInputHandler::HandleThumbMove(wxScrollBar
*scrollbar
,
701 const wxMouseEvent
& event
)
703 int thumbPos
= GetMouseCoord(scrollbar
, event
) - m_ofsMouse
;
704 thumbPos
= m_renderer
->PixelToScrollbar(scrollbar
, thumbPos
);
705 scrollbar
->PerformAction(wxACTION_SCROLL_THUMB_MOVE
, thumbPos
);
708 bool wxStdScrollBarInputHandler::HandleKey(wxInputConsumer
*consumer
,
709 const wxKeyEvent
& event
,
712 // we only react to the key presses here
715 wxControlAction action
;
716 switch ( event
.GetKeyCode() )
719 case WXK_RIGHT
: action
= wxACTION_SCROLL_LINE_DOWN
; break;
721 case WXK_LEFT
: action
= wxACTION_SCROLL_LINE_UP
; break;
722 case WXK_HOME
: action
= wxACTION_SCROLL_START
; break;
723 case WXK_END
: action
= wxACTION_SCROLL_END
; break;
725 case WXK_PRIOR
: action
= wxACTION_SCROLL_PAGE_UP
; break;
727 case WXK_NEXT
: action
= wxACTION_SCROLL_PAGE_DOWN
; break;
730 if ( !action
.IsEmpty() )
732 consumer
->PerformAction(action
);
738 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
741 bool wxStdScrollBarInputHandler::HandleMouse(wxInputConsumer
*consumer
,
742 const wxMouseEvent
& event
)
744 // is this a click event from an acceptable button?
745 int btn
= event
.GetButton();
746 if ( (btn
!= -1) && IsAllowedButton(btn
) )
748 // determine which part of the window mouse is in
749 wxScrollBar
*scrollbar
= wxStaticCast(consumer
->GetInputWindow(), wxScrollBar
);
750 wxHitTest ht
= m_renderer
->HitTestScrollbar
756 // when the mouse is pressed on any scrollbar element, we capture it
757 // and hold capture until the same mouse button is released
758 if ( event
.ButtonDown() || event
.ButtonDClick() )
763 m_winCapture
= consumer
->GetInputWindow();
764 m_winCapture
->CaptureMouse();
766 // generate the command
767 bool hasAction
= true;
768 wxControlAction action
;
771 case wxHT_SCROLLBAR_ARROW_LINE_1
:
772 action
= wxACTION_SCROLL_LINE_UP
;
775 case wxHT_SCROLLBAR_ARROW_LINE_2
:
776 action
= wxACTION_SCROLL_LINE_DOWN
;
779 case wxHT_SCROLLBAR_BAR_1
:
780 action
= wxACTION_SCROLL_PAGE_UP
;
781 m_ptStartScrolling
= event
.GetPosition();
784 case wxHT_SCROLLBAR_BAR_2
:
785 action
= wxACTION_SCROLL_PAGE_DOWN
;
786 m_ptStartScrolling
= event
.GetPosition();
789 case wxHT_SCROLLBAR_THUMB
:
790 consumer
->PerformAction(wxACTION_SCROLL_THUMB_DRAG
);
791 m_ofsMouse
= GetMouseCoord(scrollbar
, event
) -
792 m_renderer
->ScrollbarToPixel(scrollbar
);
794 // fall through: there is no immediate action
800 // remove highlighting
801 Highlight(scrollbar
, false);
804 // and press the arrow or highlight thumb now instead
805 if ( m_htLast
== wxHT_SCROLLBAR_THUMB
)
806 Highlight(scrollbar
, true);
808 Press(scrollbar
, true);
813 m_timerScroll
= new wxScrollBarTimer(this, action
,
815 m_timerScroll
->StartAutoScroll();
817 //else: no (immediate) action
820 //else: mouse already captured, nothing to do
822 // release mouse if the *same* button went up
823 else if ( btn
== m_btnCapture
)
827 StopScrolling(scrollbar
);
829 // if we were dragging the thumb, send the last event
830 if ( m_htLast
== wxHT_SCROLLBAR_THUMB
)
832 scrollbar
->PerformAction(wxACTION_SCROLL_THUMB_RELEASE
);
836 Highlight(scrollbar
, true);
840 // this is not supposed to happen as the button can't go up
841 // without going down previously and then we'd have
842 // m_winCapture by now
843 wxFAIL_MSG( _T("logic error in mouse capturing code") );
848 return wxStdInputHandler::HandleMouse(consumer
, event
);
851 bool wxStdScrollBarInputHandler::HandleMouseMove(wxInputConsumer
*consumer
,
852 const wxMouseEvent
& event
)
854 wxScrollBar
*scrollbar
= wxStaticCast(consumer
->GetInputWindow(), wxScrollBar
);
858 if ( (m_htLast
== wxHT_SCROLLBAR_THUMB
) && event
.Dragging() )
860 // make the thumb follow the mouse by keeping the same offset
861 // between the mouse position and the top/left of the thumb
862 HandleThumbMove(scrollbar
, event
);
867 // no other changes are possible while the mouse is captured
871 bool isArrow
= scrollbar
->GetArrows().HandleMouseMove(event
);
873 if ( event
.Dragging() )
875 wxHitTest ht
= m_renderer
->HitTestScrollbar
880 if ( ht
== m_htLast
)
887 wxLogDebug("Scrollbar::OnMouseMove: ht = %d", ht
);
888 #endif // DEBUG_MOUSE
890 Highlight(scrollbar
, false);
894 Highlight(scrollbar
, true);
895 //else: already done by wxScrollArrows::HandleMouseMove
897 else if ( event
.Leaving() )
900 Highlight(scrollbar
, false);
902 m_htLast
= wxHT_NOWHERE
;
904 else // event.Entering()
906 // we don't process this event
914 #endif // wxUSE_SCROLLBAR
916 // ----------------------------------------------------------------------------
918 // ----------------------------------------------------------------------------
920 wxScrollTimer::wxScrollTimer()
925 void wxScrollTimer::StartAutoScroll()
927 // start scrolling immediately
930 // ... and end it too
934 // there is an initial delay before the scrollbar starts scrolling -
935 // implement it by ignoring the first timer expiration and only start
936 // scrolling from the second one
938 Start(200); // FIXME: hardcoded delay
941 void wxScrollTimer::Notify()
945 // scroll normally now - reduce the delay
947 Start(50); // FIXME: hardcoded delay
953 // if DoNotify() returns false, we're already deleted by the timer
954 // event handler, so don't do anything else here