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 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"
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
)
86 EVT_IDLE(wxScrollBar::OnIdle
)
89 // ----------------------------------------------------------------------------
91 // ----------------------------------------------------------------------------
94 // warning C4355: 'this' : used in base member initializer list
95 #pragma warning(disable:4355) // so what? disable it...
98 wxScrollBar::wxScrollBar()
104 wxScrollBar::wxScrollBar(wxWindow
*parent
,
109 const wxValidator
& validator
,
110 const wxString
& name
)
115 (void)Create(parent
, id
, pos
, size
, style
, validator
, name
);
119 // warning C4355: 'this' : used in base member initializer list
120 #pragma warning(default:4355)
123 void wxScrollBar::Init()
132 for ( size_t n
= 0; n
< WXSIZEOF(m_elementsState
); n
++ )
134 m_elementsState
[n
] = 0;
140 bool wxScrollBar::Create(wxWindow
*parent
,
145 const wxValidator
& validator
,
146 const wxString
&name
)
148 // the scrollbars never have the border
149 style
&= ~wxBORDER_MASK
;
151 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, wxDefaultValidator
, name
) )
156 // override the cursor of the target window (if any)
157 SetCursor(wxCURSOR_ARROW
);
159 CreateInputHandler(wxINP_HANDLER_SCROLLBAR
);
164 wxScrollBar::~wxScrollBar()
168 bool wxScrollBar::AcceptsFocus() const
170 if (!wxWindow::AcceptsFocus()) return FALSE
;
172 wxWindow
*parent
= (wxWindow
*) GetParent();
176 if ((parent
->GetScrollbar( wxHORIZONTAL
) == this) ||
177 (parent
->GetScrollbar( wxVERTICAL
) == this))
186 // ----------------------------------------------------------------------------
188 // ----------------------------------------------------------------------------
190 void wxScrollBar::DoSetThumb(int pos
)
192 // don't assert hecks here, we're a private function which is meant to be
193 // called with any args at all
198 else if ( pos
> m_range
- m_thumbSize
)
200 pos
= m_range
- m_thumbSize
;
203 if ( m_thumbPos
== pos
)
205 // nothing changed, avoid refreshes which would provoke flicker
209 if ( m_thumbPosOld
== -1 )
211 // remember the old thumb position
212 m_thumbPosOld
= m_thumbPos
;
217 // we have to refresh the part of the bar which was under the thumb and the
219 m_elementsState
[Element_Thumb
] |= wxCONTROL_DIRTY
;
220 m_elementsState
[m_thumbPos
> m_thumbPosOld
221 ? Element_Bar_1
: Element_Bar_2
] |= wxCONTROL_DIRTY
;
225 int wxScrollBar::GetThumbPosition() const
230 int wxScrollBar::GetThumbSize() const
235 int wxScrollBar::GetPageSize() const
240 int wxScrollBar::GetRange() const
245 void wxScrollBar::SetThumbPosition(int pos
)
247 wxCHECK_RET( pos
>= 0 && pos
<= m_range
, _T("thumb position out of range") );
252 void wxScrollBar::SetScrollbar(int position
, int thumbSize
,
253 int range
, int pageSize
,
256 // we only refresh everythign when the range changes, thumb position
257 // changes are handled in OnIdle
258 bool needsRefresh
= (range
!= m_range
) ||
259 (thumbSize
!= m_thumbSize
) ||
260 (pageSize
!= m_pageSize
);
262 // set all parameters
264 m_thumbSize
= thumbSize
;
265 SetThumbPosition(position
);
266 m_pageSize
= pageSize
;
268 // ignore refresh parameter unless we really need to refresh everything -
269 // there ir a lot of existing code which just calls SetScrollbar() without
270 // specifying the last parameter even though it doesn't need at all to
271 // refresh the window immediately
272 if ( refresh
&& needsRefresh
)
274 // and update the window
280 // ----------------------------------------------------------------------------
282 // ----------------------------------------------------------------------------
284 wxSize
wxScrollBar::DoGetBestClientSize() const
286 // this dimension is completely arbitrary
287 static const wxCoord SIZE
= 140;
289 wxSize size
= m_renderer
->GetScrollbarArrowSize();
302 wxScrollArrows::Arrow
wxScrollBar::HitTest(const wxPoint
& pt
) const
304 switch ( m_renderer
->HitTestScrollbar(this, pt
) )
306 case wxHT_SCROLLBAR_ARROW_LINE_1
:
307 return wxScrollArrows::Arrow_First
;
309 case wxHT_SCROLLBAR_ARROW_LINE_2
:
310 return wxScrollArrows::Arrow_Second
;
313 return wxScrollArrows::Arrow_None
;
317 // ----------------------------------------------------------------------------
319 // ----------------------------------------------------------------------------
321 void wxScrollBar::OnIdle(wxIdleEvent
& event
)
327 void wxScrollBar::UpdateThumb()
331 for ( size_t n
= 0; n
< WXSIZEOF(m_elementsState
); n
++ )
333 if ( m_elementsState
[n
] & wxCONTROL_DIRTY
)
335 wxRect rect
= GetRenderer()->GetScrollbarRect(this, (Element
)n
);
337 if ( rect
.width
&& rect
.height
)
339 // we try to avoid redrawing the entire shaft (which might
340 // be quite long) if possible by only redrawing the area
341 // wich really changed
342 if ( (n
== Element_Bar_1
|| n
== Element_Bar_2
) &&
343 (m_thumbPosOld
!= -1) )
345 // the less efficient but more reliable (i.e. this will
346 // probably work everywhere) version: refresh the
347 // distance covered by thumb since the last update
350 GetRenderer()->GetScrollbarRect(this,
355 if ( n
== Element_Bar_1
)
356 rect
.SetTop(rectOld
.GetBottom());
358 rect
.SetBottom(rectOld
.GetBottom());
362 if ( n
== Element_Bar_1
)
363 rect
.SetLeft(rectOld
.GetRight());
365 rect
.SetRight(rectOld
.GetRight());
367 #else // efficient version: only repaint the area occupied by
368 // the thumb previously - we can't do better than this
369 rect
= GetRenderer()->GetScrollbarRect(this,
375 #ifdef WXDEBUG_SCROLLBAR
376 static bool s_refreshDebug
= FALSE
;
377 if ( s_refreshDebug
)
380 dc
.SetBrush(*wxCYAN_BRUSH
);
381 dc
.SetPen(*wxTRANSPARENT_PEN
);
382 dc
.DrawRectangle(rect
);
384 // under Unix we use "--sync" X option for this
385 #if defined(__WXMSW__) && !defined(__WXMICROWIN__)
390 #endif // WXDEBUG_SCROLLBAR
392 Refresh(FALSE
, &rect
);
395 m_elementsState
[n
] &= ~wxCONTROL_DIRTY
;
403 void wxScrollBar::DoDraw(wxControlRenderer
*renderer
)
405 renderer
->DrawScrollbar(this, m_thumbPosOld
);
407 // clear all dirty flags
412 // ----------------------------------------------------------------------------
414 // ----------------------------------------------------------------------------
416 static inline wxScrollBar::Element
ElementForArrow(wxScrollArrows::Arrow arrow
)
418 return arrow
== wxScrollArrows::Arrow_First
419 ? wxScrollBar::Element_Arrow_Line_1
420 : wxScrollBar::Element_Arrow_Line_2
;
423 int wxScrollBar::GetArrowState(wxScrollArrows::Arrow arrow
) const
425 return GetState(ElementForArrow(arrow
));
428 void wxScrollBar::SetArrowFlag(wxScrollArrows::Arrow arrow
, int flag
, bool set
)
430 Element which
= ElementForArrow(arrow
);
431 int state
= GetState(which
);
437 SetState(which
, state
);
440 int wxScrollBar::GetState(Element which
) const
442 // if the entire scrollbar is disabled, all of its elements are too
443 int flags
= m_elementsState
[which
];
445 flags
|= wxCONTROL_DISABLED
;
450 void wxScrollBar::SetState(Element which
, int flags
)
452 if ( (int)(m_elementsState
[which
] & ~wxCONTROL_DIRTY
) != flags
)
454 m_elementsState
[which
] = flags
| wxCONTROL_DIRTY
;
460 // ----------------------------------------------------------------------------
462 // ----------------------------------------------------------------------------
464 bool wxScrollBar::OnArrow(wxScrollArrows::Arrow arrow
)
466 int oldThumbPos
= GetThumbPosition();
467 PerformAction(arrow
== wxScrollArrows::Arrow_First
468 ? wxACTION_SCROLL_LINE_UP
469 : wxACTION_SCROLL_LINE_DOWN
);
471 // did we scroll till the end?
472 return GetThumbPosition() != oldThumbPos
;
475 bool wxScrollBar::PerformAction(const wxControlAction
& action
,
477 const wxString
& strArg
)
479 int thumbOld
= m_thumbPos
;
481 bool notify
= FALSE
; // send an event about the change?
483 wxEventType scrollType
;
485 // test for thumb move first as these events happen in quick succession
486 if ( action
== wxACTION_SCROLL_THUMB_MOVE
)
490 // VS: we have to force redraw here, otherwise the thumb will lack
491 // behind mouse cursor
494 scrollType
= wxEVT_SCROLLWIN_THUMBTRACK
;
496 else if ( action
== wxACTION_SCROLL_LINE_UP
)
498 scrollType
= wxEVT_SCROLLWIN_LINEUP
;
501 else if ( action
== wxACTION_SCROLL_LINE_DOWN
)
503 scrollType
= wxEVT_SCROLLWIN_LINEDOWN
;
506 else if ( action
== wxACTION_SCROLL_PAGE_UP
)
508 scrollType
= wxEVT_SCROLLWIN_PAGEUP
;
511 else if ( action
== wxACTION_SCROLL_PAGE_DOWN
)
513 scrollType
= wxEVT_SCROLLWIN_PAGEDOWN
;
516 else if ( action
== wxACTION_SCROLL_START
)
518 scrollType
= wxEVT_SCROLLWIN_THUMBRELEASE
; // anything better?
521 else if ( action
== wxACTION_SCROLL_END
)
523 scrollType
= wxEVT_SCROLLWIN_THUMBRELEASE
; // anything better?
526 else if ( action
== wxACTION_SCROLL_THUMB_DRAG
)
528 // we won't use it but this line suppresses the compiler
529 // warning about "variable may be used without having been
531 scrollType
= wxEVT_NULL
;
533 else if ( action
== wxACTION_SCROLL_THUMB_RELEASE
)
535 // always notify about this
537 scrollType
= wxEVT_SCROLLWIN_THUMBRELEASE
;
540 return wxControl::PerformAction(action
, numArg
, strArg
);
542 // has scrollbar position changed?
543 bool changed
= m_thumbPos
!= thumbOld
;
544 if ( notify
|| changed
)
546 wxScrollWinEvent
event(scrollType
, m_thumbPos
,
547 IsVertical() ? wxVERTICAL
: wxHORIZONTAL
);
548 event
.SetEventObject(this);
549 GetParent()->GetEventHandler()->ProcessEvent(event
);
555 void wxScrollBar::ScrollToStart()
560 void wxScrollBar::ScrollToEnd()
562 DoSetThumb(m_range
- m_thumbSize
);
565 bool wxScrollBar::ScrollLines(int nLines
)
567 DoSetThumb(m_thumbPos
+ nLines
);
571 bool wxScrollBar::ScrollPages(int nPages
)
573 DoSetThumb(m_thumbPos
+ nPages
*m_pageSize
);
577 // ============================================================================
578 // scroll bar input handler
579 // ============================================================================
581 // ----------------------------------------------------------------------------
583 // ----------------------------------------------------------------------------
585 wxScrollBarTimer::wxScrollBarTimer(wxStdScrollBarInputHandler
*handler
,
586 const wxControlAction
& action
,
587 wxScrollBar
*control
)
594 bool wxScrollBarTimer::DoNotify()
596 return m_handler
->OnScrollTimer(m_control
, m_action
);
599 // ----------------------------------------------------------------------------
600 // wxStdScrollBarInputHandler
601 // ----------------------------------------------------------------------------
603 wxStdScrollBarInputHandler::wxStdScrollBarInputHandler(wxRenderer
*renderer
,
604 wxInputHandler
*handler
)
605 : wxStdInputHandler(handler
)
607 m_renderer
= renderer
;
609 m_htLast
= wxHT_NOWHERE
;
610 m_timerScroll
= NULL
;
613 wxStdScrollBarInputHandler::~wxStdScrollBarInputHandler()
615 // normally, it's NULL by now but just in case the user somehow managed to
616 // keep the mouse captured until now...
617 delete m_timerScroll
;
620 void wxStdScrollBarInputHandler::SetElementState(wxScrollBar
*control
,
624 if ( m_htLast
> wxHT_SCROLLBAR_FIRST
&& m_htLast
< wxHT_SCROLLBAR_LAST
)
627 elem
= (wxScrollBar::Element
)(m_htLast
- wxHT_SCROLLBAR_FIRST
- 1);
629 int flags
= control
->GetState(elem
);
634 control
->SetState(elem
, flags
);
638 bool wxStdScrollBarInputHandler::OnScrollTimer(wxScrollBar
*scrollbar
,
639 const wxControlAction
& action
)
641 int oldThumbPos
= scrollbar
->GetThumbPosition();
642 scrollbar
->PerformAction(action
);
643 if ( scrollbar
->GetThumbPosition() != oldThumbPos
)
646 // we scrolled till the end
647 m_timerScroll
->Stop();
652 void wxStdScrollBarInputHandler::StopScrolling(wxScrollBar
*control
)
654 // return everything to the normal state
657 m_winCapture
->ReleaseMouse();
665 delete m_timerScroll
;
666 m_timerScroll
= NULL
;
669 // unpress the arrow and highlight the current element
670 Press(control
, FALSE
);
674 wxStdScrollBarInputHandler::GetMouseCoord(const wxScrollBar
*scrollbar
,
675 const wxMouseEvent
& event
) const
677 wxPoint pt
= event
.GetPosition();
678 return scrollbar
->GetWindowStyle() & wxVERTICAL
? pt
.y
: pt
.x
;
681 void wxStdScrollBarInputHandler::HandleThumbMove(wxScrollBar
*scrollbar
,
682 const wxMouseEvent
& event
)
684 int thumbPos
= GetMouseCoord(scrollbar
, event
) - m_ofsMouse
;
685 thumbPos
= m_renderer
->PixelToScrollbar(scrollbar
, thumbPos
);
686 scrollbar
->PerformAction(wxACTION_SCROLL_THUMB_MOVE
, thumbPos
);
689 bool wxStdScrollBarInputHandler::HandleKey(wxInputConsumer
*consumer
,
690 const wxKeyEvent
& event
,
693 // we only react to the key presses here
696 wxControlAction action
;
697 switch ( event
.GetKeyCode() )
700 case WXK_RIGHT
: action
= wxACTION_SCROLL_LINE_DOWN
; break;
702 case WXK_LEFT
: action
= wxACTION_SCROLL_LINE_UP
; break;
703 case WXK_HOME
: action
= wxACTION_SCROLL_START
; break;
704 case WXK_END
: action
= wxACTION_SCROLL_END
; break;
706 case WXK_PRIOR
: action
= wxACTION_SCROLL_PAGE_UP
; break;
708 case WXK_NEXT
: action
= wxACTION_SCROLL_PAGE_DOWN
; break;
713 consumer
->PerformAction(action
);
719 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
722 bool wxStdScrollBarInputHandler::HandleMouse(wxInputConsumer
*consumer
,
723 const wxMouseEvent
& event
)
725 // is this a click event from an acceptable button?
726 int btn
= event
.GetButton();
727 if ( (btn
!= -1) && IsAllowedButton(btn
) )
729 // determine which part of the window mouse is in
730 wxScrollBar
*scrollbar
= wxStaticCast(consumer
->GetInputWindow(), wxScrollBar
);
731 wxHitTest ht
= m_renderer
->HitTestScrollbar
737 // when the mouse is pressed on any scrollbar element, we capture it
738 // and hold capture until the same mouse button is released
739 if ( event
.ButtonDown() || event
.ButtonDClick() )
744 m_winCapture
= consumer
->GetInputWindow();
745 m_winCapture
->CaptureMouse();
747 // generate the command
748 bool hasAction
= TRUE
;
749 wxControlAction action
;
752 case wxHT_SCROLLBAR_ARROW_LINE_1
:
753 action
= wxACTION_SCROLL_LINE_UP
;
756 case wxHT_SCROLLBAR_ARROW_LINE_2
:
757 action
= wxACTION_SCROLL_LINE_DOWN
;
760 case wxHT_SCROLLBAR_BAR_1
:
761 action
= wxACTION_SCROLL_PAGE_UP
;
762 m_ptStartScrolling
= event
.GetPosition();
765 case wxHT_SCROLLBAR_BAR_2
:
766 action
= wxACTION_SCROLL_PAGE_DOWN
;
767 m_ptStartScrolling
= event
.GetPosition();
770 case wxHT_SCROLLBAR_THUMB
:
771 consumer
->PerformAction(wxACTION_SCROLL_THUMB_DRAG
);
772 m_ofsMouse
= GetMouseCoord(scrollbar
, event
) -
773 m_renderer
->ScrollbarToPixel(scrollbar
);
775 // fall through: there is no immediate action
781 // remove highlighting
782 Highlight(scrollbar
, FALSE
);
785 // and press the arrow or highlight thumb now instead
786 if ( m_htLast
== wxHT_SCROLLBAR_THUMB
)
787 Highlight(scrollbar
, TRUE
);
789 Press(scrollbar
, TRUE
);
794 m_timerScroll
= new wxScrollBarTimer(this, action
,
796 m_timerScroll
->StartAutoScroll();
798 //else: no (immediate) action
801 //else: mouse already captured, nothing to do
803 // release mouse if the *same* button went up
804 else if ( btn
== m_btnCapture
)
808 StopScrolling(scrollbar
);
810 // if we were dragging the thumb, send the last event
811 if ( m_htLast
== wxHT_SCROLLBAR_THUMB
)
813 scrollbar
->PerformAction(wxACTION_SCROLL_THUMB_RELEASE
);
817 Highlight(scrollbar
, TRUE
);
821 // this is not supposed to happen as the button can't go up
822 // without going down previously and then we'd have
823 // m_winCapture by now
824 wxFAIL_MSG( _T("logic error in mouse capturing code") );
829 return wxStdInputHandler::HandleMouse(consumer
, event
);
832 bool wxStdScrollBarInputHandler::HandleMouseMove(wxInputConsumer
*consumer
,
833 const wxMouseEvent
& event
)
835 wxScrollBar
*scrollbar
= wxStaticCast(consumer
->GetInputWindow(), wxScrollBar
);
839 if ( (m_htLast
== wxHT_SCROLLBAR_THUMB
) && event
.Moving() )
841 // make the thumb follow the mouse by keeping the same offset
842 // between the mouse position and the top/left of the thumb
843 HandleThumbMove(scrollbar
, event
);
848 // no other changes are possible while the mouse is captured
852 bool isArrow
= scrollbar
->GetArrows().HandleMouseMove(event
);
854 if ( event
.Moving() )
856 wxHitTest ht
= m_renderer
->HitTestScrollbar
861 if ( ht
== m_htLast
)
868 wxLogDebug("Scrollbar::OnMouseMove: ht = %d", ht
);
869 #endif // DEBUG_MOUSE
871 Highlight(scrollbar
, FALSE
);
875 Highlight(scrollbar
, TRUE
);
876 //else: already done by wxScrollArrows::HandleMouseMove
878 else if ( event
.Leaving() )
881 Highlight(scrollbar
, FALSE
);
883 m_htLast
= wxHT_NOWHERE
;
885 else // event.Entering()
887 // we don't process this event
895 #endif // wxUSE_SCROLLBAR
897 // ----------------------------------------------------------------------------
899 // ----------------------------------------------------------------------------
901 wxScrollTimer::wxScrollTimer()
906 void wxScrollTimer::StartAutoScroll()
908 // start scrolling immediately
911 // ... and end it too
915 // there is an initial delay before the scrollbar starts scrolling -
916 // implement it by ignoring the first timer expiration and only start
917 // scrolling from the second one
919 Start(200); // FIXME: hardcoded delay
922 void wxScrollTimer::Notify()
926 // scroll normally now - reduce the delay
928 Start(50); // FIXME: hardcoded delay
934 // if DoNotify() returns false, we're already deleted by the timer
935 // event handler, so don't do anything else here