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