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