1 /////////////////////////////////////////////////////////////////////////////
2 // Name: univ/scrolbar.cpp
3 // Purpose: wxScrollBar implementation
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2000 Vadim Zeitlin
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
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"
46 #define WXDEBUG_SCROLLBAR
49 #undef WXDEBUG_SCROLLBAR
50 #endif // !__WXDEBUG__
52 // ----------------------------------------------------------------------------
53 // wxScrollBarTimer: this class is used to repeatedly scroll the scrollbar
54 // when the mouse is help pressed on the arrow or on the bar. It generates the
55 // given scroll action command periodically.
56 // ----------------------------------------------------------------------------
58 class wxScrollBarTimer
: public wxScrollTimer
61 wxScrollBarTimer(wxStdScrollBarInputHandler
*handler
,
62 const wxControlAction
& action
,
63 wxScrollBar
*control
);
66 virtual bool DoNotify();
69 wxStdScrollBarInputHandler
*m_handler
;
70 wxControlAction m_action
;
71 wxScrollBar
*m_control
;
74 // ============================================================================
76 // ============================================================================
78 IMPLEMENT_DYNAMIC_CLASS(wxScrollBar
, wxControl
)
80 BEGIN_EVENT_TABLE(wxScrollBar
, wxScrollBarBase
)
81 EVT_IDLE(wxScrollBar::OnIdle
)
84 // ----------------------------------------------------------------------------
86 // ----------------------------------------------------------------------------
89 // warning C4355: 'this' : used in base member initializer list
90 #pragma warning(disable:4355) // so what? disable it...
93 wxScrollBar::wxScrollBar()
99 wxScrollBar::wxScrollBar(wxWindow
*parent
,
104 const wxValidator
& validator
,
105 const wxString
& name
)
110 (void)Create(parent
, id
, pos
, size
, style
, validator
, name
);
114 // warning C4355: 'this' : used in base member initializer list
115 #pragma warning(default:4355)
118 void wxScrollBar::Init()
127 for ( size_t n
= 0; n
< WXSIZEOF(m_elementsState
); n
++ )
129 m_elementsState
[n
] = 0;
135 bool wxScrollBar::Create(wxWindow
*parent
,
140 const wxValidator
& validator
,
141 const wxString
&name
)
143 // the scrollbars never have the border
144 style
&= ~wxBORDER_MASK
;
146 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, wxDefaultValidator
, name
) )
151 // override the cursor of the target window (if any)
152 SetCursor(wxCURSOR_ARROW
);
154 CreateInputHandler(wxINP_HANDLER_SCROLLBAR
);
159 wxScrollBar::~wxScrollBar()
163 // ----------------------------------------------------------------------------
165 // ----------------------------------------------------------------------------
167 void wxScrollBar::DoSetThumb(int pos
)
169 // don't assert hecks here, we're a private function which is meant to be
170 // called with any args at all
175 else if ( pos
> m_range
- m_thumbSize
)
177 pos
= m_range
- m_thumbSize
;
180 if ( m_thumbPos
== pos
)
182 // nothing changed, avoid refreshes which would provoke flicker
186 if ( m_thumbPosOld
== -1 )
188 // remember the old thumb position
189 m_thumbPosOld
= m_thumbPos
;
194 // we have to refresh the part of the bar which was under the thumb and the
196 m_elementsState
[Element_Thumb
] |= wxCONTROL_DIRTY
;
197 m_elementsState
[m_thumbPos
> m_thumbPosOld
198 ? Element_Bar_1
: Element_Bar_2
] |= wxCONTROL_DIRTY
;
202 int wxScrollBar::GetThumbPosition() const
207 int wxScrollBar::GetThumbSize() const
212 int wxScrollBar::GetPageSize() const
217 int wxScrollBar::GetRange() const
222 void wxScrollBar::SetThumbPosition(int pos
)
224 wxCHECK_RET( pos
>= 0 && pos
<= m_range
, _T("thumb position out of range") );
229 void wxScrollBar::SetScrollbar(int position
, int thumbSize
,
230 int range
, int pageSize
,
233 // we only refresh everythign when the range changes, thumb position
234 // changes are handled in OnIdle
235 bool needsRefresh
= (range
!= m_range
) ||
236 (thumbSize
!= m_thumbSize
) ||
237 (pageSize
!= m_pageSize
);
239 // set all parameters
241 m_thumbSize
= thumbSize
;
242 SetThumbPosition(position
);
243 m_pageSize
= pageSize
;
245 // ignore refresh parameter unless we really need to refresh everything -
246 // there ir a lot of existing code which just calls SetScrollbar() without
247 // specifying the last parameter even though it doesn't need at all to
248 // refresh the window immediately
249 if ( refresh
&& needsRefresh
)
251 // and update the window
257 // ----------------------------------------------------------------------------
259 // ----------------------------------------------------------------------------
261 wxSize
wxScrollBar::DoGetBestClientSize() const
263 // this dimension is completely arbitrary
264 static const wxCoord SIZE
= 140;
266 wxSize size
= m_renderer
->GetScrollbarArrowSize();
279 wxScrollArrows::Arrow
wxScrollBar::HitTest(const wxPoint
& pt
) const
281 switch ( m_renderer
->HitTestScrollbar(this, pt
) )
283 case wxHT_SCROLLBAR_ARROW_LINE_1
:
284 return wxScrollArrows::Arrow_First
;
286 case wxHT_SCROLLBAR_ARROW_LINE_2
:
287 return wxScrollArrows::Arrow_Second
;
290 return wxScrollArrows::Arrow_None
;
294 // ----------------------------------------------------------------------------
296 // ----------------------------------------------------------------------------
298 void wxScrollBar::OnIdle(wxIdleEvent
& event
)
302 for ( size_t n
= 0; n
< WXSIZEOF(m_elementsState
); n
++ )
304 if ( m_elementsState
[n
] & wxCONTROL_DIRTY
)
306 wxRect rect
= GetRenderer()->GetScrollbarRect(this, (Element
)n
);
308 if ( rect
.width
&& rect
.height
)
310 // we try to avoid redrawing the entire shaft (which might
311 // be quite long) if possible by only redrawing the area
312 // wich really changed
313 if ( (n
== Element_Bar_1
|| n
== Element_Bar_2
) &&
314 (m_thumbPosOld
!= -1) )
316 // the less efficient but more reliable (i.e. this will
317 // probably work everywhere) version: refresh the
318 // distance covered by thumb since the last update
321 GetRenderer()->GetScrollbarRect(this,
326 if ( n
== Element_Bar_1
)
327 rect
.SetTop(rectOld
.GetBottom());
329 rect
.SetBottom(rectOld
.GetBottom());
333 if ( n
== Element_Bar_1
)
334 rect
.SetLeft(rectOld
.GetRight());
336 rect
.SetRight(rectOld
.GetRight());
338 #else // efficient version: only repaint the area occupied by
339 // the thumb previously - we can't do better than this
340 rect
= GetRenderer()->GetScrollbarRect(this,
346 #ifdef WXDEBUG_SCROLLBAR
347 static bool s_refreshDebug
= FALSE
;
348 if ( s_refreshDebug
)
351 dc
.SetBrush(*wxCYAN_BRUSH
);
352 dc
.SetPen(*wxTRANSPARENT_PEN
);
353 dc
.DrawRectangle(rect
);
355 // under Unix we use "--sync" X option for this
361 #endif // WXDEBUG_SCROLLBAR
363 Refresh(TRUE
, &rect
);
366 m_elementsState
[n
] &= ~wxCONTROL_DIRTY
;
376 void wxScrollBar::DoDraw(wxControlRenderer
*renderer
)
378 renderer
->DrawScrollbar(this, m_thumbPosOld
);
380 // clear all dirty flags
385 // ----------------------------------------------------------------------------
387 // ----------------------------------------------------------------------------
389 static inline wxScrollBar::Element
ElementForArrow(wxScrollArrows::Arrow arrow
)
391 return arrow
== wxScrollArrows::Arrow_First
392 ? wxScrollBar::Element_Arrow_Line_1
393 : wxScrollBar::Element_Arrow_Line_2
;
396 int wxScrollBar::GetArrowState(wxScrollArrows::Arrow arrow
) const
398 return GetState(ElementForArrow(arrow
));
401 void wxScrollBar::SetArrowFlag(wxScrollArrows::Arrow arrow
, int flag
, bool set
)
403 Element which
= ElementForArrow(arrow
);
404 int state
= GetState(which
);
410 SetState(which
, state
);
413 int wxScrollBar::GetState(Element which
) const
415 // if the entire scrollbar is disabled, all of its elements are too
416 int flags
= m_elementsState
[which
];
418 flags
|= wxCONTROL_DISABLED
;
423 void wxScrollBar::SetState(Element which
, int flags
)
425 if ( (int)(m_elementsState
[which
] & ~wxCONTROL_DIRTY
) != flags
)
427 m_elementsState
[which
] = flags
| wxCONTROL_DIRTY
;
433 // ----------------------------------------------------------------------------
435 // ----------------------------------------------------------------------------
437 bool wxScrollBar::OnArrow(wxScrollArrows::Arrow arrow
)
439 int oldThumbPos
= GetThumbPosition();
440 PerformAction(arrow
== wxScrollArrows::Arrow_First
441 ? wxACTION_SCROLL_LINE_UP
442 : wxACTION_SCROLL_LINE_DOWN
);
444 // did we scroll till the end?
445 return GetThumbPosition() != oldThumbPos
;
448 bool wxScrollBar::PerformAction(const wxControlAction
& action
,
450 const wxString
& strArg
)
452 int thumbOld
= m_thumbPos
;
454 bool notify
= FALSE
; // send an event about the change?
456 wxEventType scrollType
;
458 // test for thumb move first as these events happen in quick succession
459 if ( action
== wxACTION_SCROLL_THUMB_MOVE
)
463 scrollType
= wxEVT_SCROLLWIN_THUMBTRACK
;
465 else if ( action
== wxACTION_SCROLL_LINE_UP
)
467 scrollType
= wxEVT_SCROLLWIN_LINEUP
;
470 else if ( action
== wxACTION_SCROLL_LINE_DOWN
)
472 scrollType
= wxEVT_SCROLLWIN_LINEDOWN
;
475 else if ( action
== wxACTION_SCROLL_PAGE_UP
)
477 scrollType
= wxEVT_SCROLLWIN_PAGEUP
;
480 else if ( action
== wxACTION_SCROLL_PAGE_DOWN
)
482 scrollType
= wxEVT_SCROLLWIN_PAGEDOWN
;
485 else if ( action
== wxACTION_SCROLL_START
)
487 scrollType
= wxEVT_SCROLLWIN_THUMBRELEASE
; // anything better?
490 else if ( action
== wxACTION_SCROLL_END
)
492 scrollType
= wxEVT_SCROLLWIN_THUMBRELEASE
; // anything better?
495 else if ( action
== wxACTION_SCROLL_THUMB_DRAG
)
497 // we won't use it but this line suppresses the compiler
498 // warning about "variable may be used without having been
500 scrollType
= wxEVT_NULL
;
502 else if ( action
== wxACTION_SCROLL_THUMB_RELEASE
)
504 // always notify about this
506 scrollType
= wxEVT_SCROLLWIN_THUMBRELEASE
;
509 return wxControl::PerformAction(action
, numArg
, strArg
);
511 // has scrollbar position changed?
512 bool changed
= m_thumbPos
!= thumbOld
;
513 if ( notify
|| changed
)
515 wxScrollWinEvent
event(scrollType
, m_thumbPos
,
516 IsVertical() ? wxVERTICAL
: wxHORIZONTAL
);
517 event
.SetEventObject(this);
518 GetParent()->GetEventHandler()->ProcessEvent(event
);
524 void wxScrollBar::ScrollToStart()
529 void wxScrollBar::ScrollToEnd()
531 DoSetThumb(m_range
- m_thumbSize
);
534 void wxScrollBar::ScrollLines(int nLines
)
536 DoSetThumb(m_thumbPos
+ nLines
);
539 void wxScrollBar::ScrollPages(int nPages
)
541 DoSetThumb(m_thumbPos
+ nPages
*m_pageSize
);
544 // ============================================================================
545 // scroll bar input handler
546 // ============================================================================
548 // ----------------------------------------------------------------------------
550 // ----------------------------------------------------------------------------
552 wxScrollBarTimer::wxScrollBarTimer(wxStdScrollBarInputHandler
*handler
,
553 const wxControlAction
& action
,
554 wxScrollBar
*control
)
561 bool wxScrollBarTimer::DoNotify()
563 return m_handler
->OnScrollTimer(m_control
, m_action
);
566 // ----------------------------------------------------------------------------
567 // wxStdScrollBarInputHandler
568 // ----------------------------------------------------------------------------
570 wxStdScrollBarInputHandler::wxStdScrollBarInputHandler(wxRenderer
*renderer
,
571 wxInputHandler
*handler
)
572 : wxStdInputHandler(handler
)
574 m_renderer
= renderer
;
576 m_htLast
= wxHT_NOWHERE
;
577 m_timerScroll
= NULL
;
580 wxStdScrollBarInputHandler::~wxStdScrollBarInputHandler()
582 // normally, it's NULL by now but just in case the user somehow managed to
583 // keep the mouse captured until now...
584 delete m_timerScroll
;
587 void wxStdScrollBarInputHandler::SetElementState(wxScrollBar
*control
,
591 if ( m_htLast
> wxHT_SCROLLBAR_FIRST
&& m_htLast
< wxHT_SCROLLBAR_LAST
)
594 elem
= (wxScrollBar::Element
)(m_htLast
- wxHT_SCROLLBAR_FIRST
- 1);
596 int flags
= control
->GetState(elem
);
601 control
->SetState(elem
, flags
);
605 bool wxStdScrollBarInputHandler::OnScrollTimer(wxScrollBar
*scrollbar
,
606 const wxControlAction
& action
)
608 int oldThumbPos
= scrollbar
->GetThumbPosition();
609 scrollbar
->PerformAction(action
);
610 if ( scrollbar
->GetThumbPosition() != oldThumbPos
)
613 // we scrolled till the end
614 m_timerScroll
->Stop();
619 void wxStdScrollBarInputHandler::StopScrolling(wxScrollBar
*control
)
621 // return everything to the normal state
624 m_winCapture
->ReleaseMouse();
632 delete m_timerScroll
;
633 m_timerScroll
= NULL
;
636 // unpress the arrow and highlight the current element
637 Press(control
, FALSE
);
641 wxStdScrollBarInputHandler::GetMouseCoord(const wxScrollBar
*scrollbar
,
642 const wxMouseEvent
& event
) const
644 wxPoint pt
= event
.GetPosition();
645 return scrollbar
->GetWindowStyle() & wxVERTICAL
? pt
.y
: pt
.x
;
648 void wxStdScrollBarInputHandler::HandleThumbMove(wxScrollBar
*scrollbar
,
649 const wxMouseEvent
& event
)
651 int thumbPos
= GetMouseCoord(scrollbar
, event
) - m_ofsMouse
;
652 thumbPos
= m_renderer
->PixelToScrollbar(scrollbar
, thumbPos
);
653 scrollbar
->PerformAction(wxACTION_SCROLL_THUMB_MOVE
, thumbPos
);
656 bool wxStdScrollBarInputHandler::HandleKey(wxControl
*control
,
657 const wxKeyEvent
& event
,
660 // we only react to the key presses here
663 wxControlAction action
;
664 switch ( event
.GetKeyCode() )
667 case WXK_RIGHT
: action
= wxACTION_SCROLL_LINE_DOWN
; break;
669 case WXK_LEFT
: action
= wxACTION_SCROLL_LINE_UP
; break;
670 case WXK_HOME
: action
= wxACTION_SCROLL_START
; break;
671 case WXK_END
: action
= wxACTION_SCROLL_END
; break;
672 case WXK_PRIOR
: action
= wxACTION_SCROLL_PAGE_UP
; break;
673 case WXK_NEXT
: action
= wxACTION_SCROLL_PAGE_DOWN
; break;
678 control
->PerformAction(action
);
684 return wxStdInputHandler::HandleKey(control
, event
, pressed
);
687 bool wxStdScrollBarInputHandler::HandleMouse(wxControl
*control
,
688 const wxMouseEvent
& event
)
690 // is this a click event from an acceptable button?
691 int btn
= event
.GetButton();
692 if ( (btn
!= -1) && IsAllowedButton(btn
) )
694 // determine which part of the window mouse is in
695 wxScrollBar
*scrollbar
= wxStaticCast(control
, wxScrollBar
);
696 wxHitTest ht
= m_renderer
->HitTestScrollbar
702 // when the mouse is pressed on any scrollbar element, we capture it
703 // and hold capture until the same mouse button is released
704 if ( event
.ButtonDown() || event
.ButtonDClick() )
709 m_winCapture
= control
;
710 m_winCapture
->CaptureMouse();
712 // generate the command
713 bool hasAction
= TRUE
;
714 wxControlAction action
;
717 case wxHT_SCROLLBAR_ARROW_LINE_1
:
718 action
= wxACTION_SCROLL_LINE_UP
;
721 case wxHT_SCROLLBAR_ARROW_LINE_2
:
722 action
= wxACTION_SCROLL_LINE_DOWN
;
725 case wxHT_SCROLLBAR_BAR_1
:
726 action
= wxACTION_SCROLL_PAGE_UP
;
727 m_ptStartScrolling
= event
.GetPosition();
730 case wxHT_SCROLLBAR_BAR_2
:
731 action
= wxACTION_SCROLL_PAGE_DOWN
;
732 m_ptStartScrolling
= event
.GetPosition();
735 case wxHT_SCROLLBAR_THUMB
:
736 control
->PerformAction(wxACTION_SCROLL_THUMB_DRAG
);
737 m_ofsMouse
= GetMouseCoord(scrollbar
, event
) -
738 m_renderer
->ScrollbarToPixel(scrollbar
);
740 // fall through: there is no immediate action
746 // remove highlighting
747 Highlight(scrollbar
, FALSE
);
750 // and press the arrow or highlight thumb now instead
751 if ( m_htLast
== wxHT_SCROLLBAR_THUMB
)
752 Highlight(scrollbar
, TRUE
);
754 Press(scrollbar
, TRUE
);
759 m_timerScroll
= new wxScrollBarTimer(this, action
,
761 m_timerScroll
->StartAutoScroll();
763 //else: no (immediate) action
766 //else: mouse already captured, nothing to do
768 // release mouse if the *same* button went up
769 else if ( btn
== m_btnCapture
)
773 StopScrolling(scrollbar
);
775 // if we were dragging the thumb, send the last event
776 if ( m_htLast
== wxHT_SCROLLBAR_THUMB
)
778 scrollbar
->PerformAction(wxACTION_SCROLL_THUMB_RELEASE
);
782 Highlight(scrollbar
, TRUE
);
786 // this is not supposed to happen as the button can't go up
787 // without going down previously and then we'd have
788 // m_winCapture by now
789 wxFAIL_MSG( _T("logic error in mouse capturing code") );
794 return wxStdInputHandler::HandleMouse(control
, event
);
797 bool wxStdScrollBarInputHandler::HandleMouseMove(wxControl
*control
,
798 const wxMouseEvent
& event
)
800 wxScrollBar
*scrollbar
= wxStaticCast(control
, wxScrollBar
);
804 if ( (m_htLast
== wxHT_SCROLLBAR_THUMB
) && event
.Moving() )
806 // make the thumb follow the mouse by keeping the same offset
807 // between the mouse position and the top/left of the thumb
808 HandleThumbMove(scrollbar
, event
);
813 // no other changes are possible while the mouse is captured
817 bool isArrow
= scrollbar
->GetArrows().HandleMouseMove(event
);
819 if ( event
.Moving() )
821 wxHitTest ht
= m_renderer
->HitTestScrollbar
826 if ( ht
== m_htLast
)
833 wxLogDebug("Scrollbar::OnMouseMove: ht = %d", ht
);
834 #endif // DEBUG_MOUSE
836 Highlight(scrollbar
, FALSE
);
840 Highlight(scrollbar
, TRUE
);
841 //else: already done by wxScrollArrows::HandleMouseMove
843 else if ( event
.Leaving() )
846 Highlight(scrollbar
, FALSE
);
848 m_htLast
= wxHT_NOWHERE
;
850 else // event.Entering()
852 // we don't process this event
860 #endif // wxUSE_SCROLLBAR
862 // ----------------------------------------------------------------------------
864 // ----------------------------------------------------------------------------
866 wxScrollTimer::wxScrollTimer()
871 void wxScrollTimer::StartAutoScroll()
873 // start scrolling immediately
876 // ... and end it too
880 // there is an initial delay before the scrollbar starts scrolling -
881 // implement it by ignoring the first timer expiration and only start
882 // scrolling from the second one
884 Start(200); // FIXME: hardcoded delay
887 void wxScrollTimer::Notify()
891 // scroll normally now - reduce the delay
893 Start(50); // FIXME: hardcoded delay
899 // if DoNotify() returns false, we're already deleted by the timer
900 // event handler, so don't do anything else here