1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/vscroll.cpp
3 // Purpose: wxVScrolledWindow implementation
4 // Author: Vadim Zeitlin
5 // Modified by: Brad Anderson
8 // Copyright: (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
31 #include "wx/vscroll.h"
33 // ============================================================================
34 // wxVarScrollHelperEvtHandler declaration
35 // ============================================================================
37 // ----------------------------------------------------------------------------
38 // wxScrollHelperEvtHandler: intercept the events from the window and forward
39 // them to wxVarScrollHelperBase
40 // ----------------------------------------------------------------------------
42 class WXDLLEXPORT wxVarScrollHelperEvtHandler
: public wxEvtHandler
45 wxVarScrollHelperEvtHandler(wxVarScrollHelperBase
*scrollHelper
)
47 m_scrollHelper
= scrollHelper
;
50 virtual bool ProcessEvent(wxEvent
& event
);
53 wxVarScrollHelperBase
*m_scrollHelper
;
55 DECLARE_NO_COPY_CLASS(wxVarScrollHelperEvtHandler
)
58 // ============================================================================
59 // wxVarScrollHelperEvtHandler implementation
60 // ============================================================================
62 bool wxVarScrollHelperEvtHandler::ProcessEvent(wxEvent
& event
)
64 wxEventType evType
= event
.GetEventType();
66 // pass it on to the real handler
67 bool processed
= wxEvtHandler::ProcessEvent(event
);
69 // always process the size events ourselves, even if the user code handles
70 // them as well, as we need to AdjustScrollbars()
72 // NB: it is important to do it after processing the event in the normal
73 // way as HandleOnSize() may generate a wxEVT_SIZE itself if the
74 // scrollbar[s] (dis)appear and it should be seen by the user code
76 if ( evType
== wxEVT_SIZE
)
78 m_scrollHelper
->HandleOnSize((wxSizeEvent
&)event
);
80 return !event
.GetSkipped();
85 // normally, nothing more to do here - except if we have a command
87 if ( event
.IsCommandEvent() )
93 // reset the skipped flag (which might have been set to true in
94 // ProcessEvent() above) to be able to test it below
95 bool wasSkipped
= event
.GetSkipped();
99 // reset the skipped flag to false as it might have been set to true in
100 // ProcessEvent() above
103 if ( evType
== wxEVT_SCROLLWIN_TOP
||
104 evType
== wxEVT_SCROLLWIN_BOTTOM
||
105 evType
== wxEVT_SCROLLWIN_LINEUP
||
106 evType
== wxEVT_SCROLLWIN_LINEDOWN
||
107 evType
== wxEVT_SCROLLWIN_PAGEUP
||
108 evType
== wxEVT_SCROLLWIN_PAGEDOWN
||
109 evType
== wxEVT_SCROLLWIN_THUMBTRACK
||
110 evType
== wxEVT_SCROLLWIN_THUMBRELEASE
)
112 m_scrollHelper
->HandleOnScroll((wxScrollWinEvent
&)event
);
113 if ( !event
.GetSkipped() )
115 // it makes sense to indicate that we processed the message as we
116 // did scroll the window (and also notice that wxAutoScrollTimer
117 // relies on our return value for continuous scrolling)
123 else if ( evType
== wxEVT_MOUSEWHEEL
)
125 m_scrollHelper
->HandleOnMouseWheel((wxMouseEvent
&)event
);
127 #endif // wxUSE_MOUSEWHEEL
130 event
.Skip(wasSkipped
);
136 // ============================================================================
137 // wxVarScrollHelperBase implementation
138 // ============================================================================
140 // ----------------------------------------------------------------------------
141 // wxVarScrollHelperBase initialization
142 // ----------------------------------------------------------------------------
144 wxVarScrollHelperBase::wxVarScrollHelperBase(wxWindow
*win
)
146 wxASSERT_MSG( win
, _T("associated window can't be NULL in wxVarScrollHelperBase") );
149 m_sumWheelRotation
= 0;
157 m_targetWindow
= (wxWindow
*)NULL
;
163 // by default, the associated window is also the target window
164 DoSetTargetWindow(win
);
168 wxVarScrollHelperBase::~wxVarScrollHelperBase()
173 // ----------------------------------------------------------------------------
174 // wxVarScrollHelperBase various helpers
175 // ----------------------------------------------------------------------------
178 wxVarScrollHelperBase::AssignOrient(wxCoord
& x
,
183 if ( GetOrientation() == wxVERTICAL
)
196 wxVarScrollHelperBase::IncOrient(wxCoord
& x
, wxCoord
& y
, wxCoord inc
)
198 if ( GetOrientation() == wxVERTICAL
)
204 wxCoord
wxVarScrollHelperBase::DoEstimateTotalSize() const
206 // estimate the total height: it is impossible to call
207 // OnGetUnitSize() for every unit because there may be too many of
208 // them, so we just make a guess using some units in the beginning,
209 // some in the end and some in the middle
210 static const size_t NUM_UNITS_TO_SAMPLE
= 10;
213 if ( m_unitMax
< 3*NUM_UNITS_TO_SAMPLE
)
215 // in this case, full calculations are faster and more correct than
217 sizeTotal
= GetUnitsSize(0, m_unitMax
);
219 else // too many units to calculate exactly
221 // look at some units in the beginning/middle/end
223 GetUnitsSize(0, NUM_UNITS_TO_SAMPLE
) +
224 GetUnitsSize(m_unitMax
- NUM_UNITS_TO_SAMPLE
,
226 GetUnitsSize(m_unitMax
/2 - NUM_UNITS_TO_SAMPLE
/2,
227 m_unitMax
/2 + NUM_UNITS_TO_SAMPLE
/2);
229 // use the height of the units we looked as the average
230 sizeTotal
= (wxCoord
)
231 (((float)sizeTotal
/ (3*NUM_UNITS_TO_SAMPLE
)) * m_unitMax
);
237 wxCoord
wxVarScrollHelperBase::GetUnitsSize(size_t unitMin
, size_t unitMax
) const
239 if ( unitMin
== unitMax
)
241 else if ( unitMin
> unitMax
)
242 return -GetUnitsSize(unitMax
, unitMin
);
243 //else: unitMin < unitMax
245 // let the user code know that we're going to need all these units
246 OnGetUnitsSizeHint(unitMin
, unitMax
);
248 // sum up their sizes
250 for ( size_t unit
= unitMin
; unit
< unitMax
; ++unit
)
252 size
+= OnGetUnitSize(unit
);
258 size_t wxVarScrollHelperBase::FindFirstVisibleFromLast(size_t unitLast
, bool full
) const
260 const wxCoord sWindow
= GetOrientationTargetSize();
262 // go upwards until we arrive at a unit such that unitLast is not visible
263 // any more when it is shown
264 size_t unitFirst
= unitLast
;
268 s
+= OnGetUnitSize(unitFirst
);
272 // for this unit to be fully visible we need to go one unit
273 // down, but if it is enough for it to be only partly visible then
274 // this unit will do as well
292 size_t wxVarScrollHelperBase::GetNewScrollPosition(wxScrollWinEvent
& event
) const
294 wxEventType evtType
= event
.GetEventType();
296 if ( evtType
== wxEVT_SCROLLWIN_TOP
)
300 else if ( evtType
== wxEVT_SCROLLWIN_BOTTOM
)
304 else if ( evtType
== wxEVT_SCROLLWIN_LINEUP
)
306 return m_unitFirst
? m_unitFirst
- 1 : 0;
308 else if ( evtType
== wxEVT_SCROLLWIN_LINEDOWN
)
310 return m_unitFirst
+ 1;
312 else if ( evtType
== wxEVT_SCROLLWIN_PAGEUP
)
314 return FindFirstVisibleFromLast(m_unitFirst
);
316 else if ( evtType
== wxEVT_SCROLLWIN_PAGEDOWN
)
318 if ( GetVisibleEnd() )
319 return GetVisibleEnd() - 1;
321 return GetVisibleEnd();
323 else if ( evtType
== wxEVT_SCROLLWIN_THUMBRELEASE
)
325 return event
.GetPosition();
327 else if ( evtType
== wxEVT_SCROLLWIN_THUMBTRACK
)
329 return event
.GetPosition();
332 // unknown scroll event?
333 wxFAIL_MSG( _T("unknown scroll event type?") );
337 void wxVarScrollHelperBase::UpdateScrollbar()
339 // if there is nothing to scroll, remove the scrollbar
346 // see how many units can we fit on screen
347 const wxCoord sWindow
= GetOrientationTargetSize();
349 // do vertical calculations
352 for ( unit
= m_unitFirst
; unit
< m_unitMax
; ++unit
)
357 s
+= OnGetUnitSize(unit
);
360 m_nUnitsVisible
= unit
- m_unitFirst
;
362 int unitsPageSize
= m_nUnitsVisible
;
365 // last unit is only partially visible, we still need the scrollbar and
366 // so we have to "fix" pageSize because if it is equal to m_unitMax
367 // the scrollbar is not shown at all under MSW
371 // set the scrollbar parameters to reflect this
372 m_win
->SetScrollbar(GetOrientation(), m_unitFirst
, unitsPageSize
, m_unitMax
);
375 void wxVarScrollHelperBase::RemoveScrollbar()
378 m_nUnitsVisible
= m_unitMax
;
379 m_win
->SetScrollbar(GetOrientation(), 0, 0, 0);
382 void wxVarScrollHelperBase::DeleteEvtHandler()
384 // search for m_handler in the handler list
385 if ( m_win
&& m_handler
)
387 if ( m_win
->RemoveEventHandler(m_handler
) )
391 //else: something is very wrong, so better [maybe] leak memory than
392 // risk a crash because of double deletion
398 void wxVarScrollHelperBase::DoSetTargetWindow(wxWindow
*target
)
400 m_targetWindow
= target
;
402 target
->MacSetClipChildren( true ) ;
405 // install the event handler which will intercept the events we're
406 // interested in (but only do it for our real window, not the target window
407 // which we scroll - we don't need to hijack its events)
408 if ( m_targetWindow
== m_win
)
410 // if we already have a handler, delete it first
413 m_handler
= new wxVarScrollHelperEvtHandler(this);
414 m_targetWindow
->PushEventHandler(m_handler
);
418 // ----------------------------------------------------------------------------
419 // wxVarScrollHelperBase operations
420 // ----------------------------------------------------------------------------
422 void wxVarScrollHelperBase::SetTargetWindow(wxWindow
*target
)
424 wxCHECK_RET( target
, wxT("target window must not be NULL") );
426 if ( target
== m_targetWindow
)
429 DoSetTargetWindow(target
);
432 void wxVarScrollHelperBase::SetUnitCount(size_t count
)
434 // save the number of units
437 // and our estimate for their total height
438 m_sizeTotal
= EstimateTotalSize();
440 // ScrollToUnit() will update the scrollbar itself if it changes the unit
441 // we pass to it because it's out of [new] range
442 size_t oldScrollPos
= m_unitFirst
;
443 DoScrollToUnit(m_unitFirst
);
444 if ( oldScrollPos
== m_unitFirst
)
446 // but if it didn't do it, we still need to update the scrollbar to
447 // reflect the changed number of units ourselves
452 void wxVarScrollHelperBase::RefreshUnit(size_t unit
)
454 // is this unit visible?
455 if ( !IsVisible(unit
) )
457 // no, it is useless to do anything
461 // calculate the rect occupied by this unit on screen
463 AssignOrient(rect
.width
, rect
.height
,
464 GetNonOrientationTargetSize(), OnGetUnitSize(unit
));
466 for ( size_t n
= GetVisibleBegin(); n
< unit
; ++n
)
468 IncOrient(rect
.x
, rect
.y
, OnGetUnitSize(n
));
472 m_targetWindow
->RefreshRect(rect
);
475 void wxVarScrollHelperBase::RefreshUnits(size_t from
, size_t to
)
477 wxASSERT_MSG( from
<= to
, _T("RefreshUnits(): empty range") );
479 // clump the range to just the visible units -- it is useless to refresh
481 if ( from
< GetVisibleBegin() )
482 from
= GetVisibleBegin();
484 if ( to
> GetVisibleEnd() )
485 to
= GetVisibleEnd();
487 // calculate the rect occupied by these units on screen
488 int orient_size
, nonorient_size
, orient_pos
;
489 orient_size
= nonorient_size
= orient_pos
= 0;
491 nonorient_size
= GetNonOrientationTargetSize();
493 for ( size_t nBefore
= GetVisibleBegin();
497 orient_pos
+= OnGetUnitSize(nBefore
);
500 for ( size_t nBetween
= from
; nBetween
<= to
; nBetween
++ )
502 orient_size
+= OnGetUnitSize(nBetween
);
506 AssignOrient(rect
.x
, rect
.y
, 0, orient_pos
);
507 AssignOrient(rect
.width
, rect
.height
, nonorient_size
, orient_size
);
510 m_targetWindow
->RefreshRect(rect
);
513 void wxVarScrollHelperBase::RefreshAll()
517 m_targetWindow
->Refresh();
520 bool wxVarScrollHelperBase::ScrollLayout()
522 if ( m_targetWindow
->GetSizer() && m_physicalScrolling
)
524 // adjust the sizer dimensions/position taking into account the
525 // virtual size and scrolled position of the window.
528 AssignOrient(x
, y
, 0, -GetScrollOffset());
531 m_targetWindow
->GetVirtualSize(&w
, &h
);
533 m_targetWindow
->GetSizer()->SetDimension(x
, y
, w
, h
);
537 // fall back to default for LayoutConstraints
538 return m_targetWindow
->wxWindow::Layout();
541 int wxVarScrollHelperBase::HitTest(wxCoord coord
) const
543 const size_t unitMax
= GetVisibleEnd();
544 for ( size_t unit
= GetVisibleBegin(); unit
< unitMax
; ++unit
)
546 coord
-= OnGetUnitSize(unit
);
554 // ----------------------------------------------------------------------------
555 // wxVarScrollHelperBase scrolling
556 // ----------------------------------------------------------------------------
558 bool wxVarScrollHelperBase::DoScrollToUnit(size_t unit
)
562 // we're empty, code below doesn't make sense in this case
566 // determine the real first unit to scroll to: we shouldn't scroll beyond
568 size_t unitFirstLast
= FindFirstVisibleFromLast(m_unitMax
- 1, true);
569 if ( unit
> unitFirstLast
)
570 unit
= unitFirstLast
;
573 if ( unit
== m_unitFirst
)
580 // remember the currently shown units for the refresh code below
581 size_t unitFirstOld
= GetVisibleBegin(),
582 unitLastOld
= GetVisibleEnd();
587 // the size of scrollbar thumb could have changed
590 // finally refresh the display -- but only redraw as few units as possible
591 // to avoid flicker. We can't do this if we have children because they
593 if ( m_targetWindow
->GetChildren().empty() &&
594 GetVisibleBegin() >= unitLastOld
|| GetVisibleEnd() <= unitFirstOld
)
596 // the simplest case: we don't have any old units left, just redraw
598 m_targetWindow
->Refresh();
600 else // scroll the window
602 if ( m_physicalScrolling
)
605 dy
= GetUnitsSize(GetVisibleBegin(), unitFirstOld
);
607 if ( GetOrientation() == wxHORIZONTAL
)
614 m_targetWindow
->ScrollWindow(dx
, dy
);
616 else // !m_physicalScrolling
618 // we still need to invalidate but we can't use ScrollWindow
619 // because physical scrolling is disabled (the user either didn't
620 // want children scrolled and/or doesn't want pixels to be
621 // physically scrolled).
622 m_targetWindow
->Refresh();
629 bool wxVarScrollHelperBase::DoScrollUnits(int units
)
631 units
+= m_unitFirst
;
635 return DoScrollToUnit(units
);
638 bool wxVarScrollHelperBase::DoScrollPages(int pages
)
640 bool didSomething
= false;
647 unit
= GetVisibleEnd();
654 unit
= FindFirstVisibleFromLast(GetVisibleEnd());
658 didSomething
= DoScrollToUnit(unit
);
664 // ----------------------------------------------------------------------------
666 // ----------------------------------------------------------------------------
668 void wxVarScrollHelperBase::HandleOnSize(wxSizeEvent
& event
)
675 void wxVarScrollHelperBase::HandleOnScroll(wxScrollWinEvent
& event
)
677 if (GetOrientation() != event
.GetOrientation())
683 DoScrollToUnit(GetNewScrollPosition(event
));
686 UpdateMacScrollWindow();
690 void wxVarScrollHelperBase::DoPrepareDC(wxDC
& dc
)
692 if ( m_physicalScrolling
)
694 wxPoint pt
= dc
.GetDeviceOrigin();
696 IncOrient(pt
.x
, pt
.y
, -GetScrollOffset());
698 dc
.SetDeviceOrigin(pt
.x
, pt
.y
);
702 int wxVarScrollHelperBase::DoCalcScrolledPosition(int coord
) const
704 return coord
- GetScrollOffset();
707 int wxVarScrollHelperBase::DoCalcUnscrolledPosition(int coord
) const
709 return coord
+ GetScrollOffset();
714 void wxVarScrollHelperBase::HandleOnMouseWheel(wxMouseEvent
& event
)
716 // we only want to process wheel events for vertical implementations.
717 // There is no way to determine wheel orientation (and on MSW horizontal
718 // wheel rotation just fakes scroll events, rather than sending a MOUSEWHEEL
720 if ( GetOrientation() != wxVERTICAL
)
723 m_sumWheelRotation
+= event
.GetWheelRotation();
724 int delta
= event
.GetWheelDelta();
726 // how much to scroll this time
727 int units_to_scroll
= -(m_sumWheelRotation
/delta
);
728 if ( !units_to_scroll
)
731 m_sumWheelRotation
+= units_to_scroll
*delta
;
733 if ( !event
.IsPageScroll() )
734 DoScrollUnits( units_to_scroll
*event
.GetLinesPerAction() );
735 else // scroll pages instead of units
736 DoScrollPages( units_to_scroll
);
739 #endif // wxUSE_MOUSEWHEEL
742 // ============================================================================
743 // wxVarHVScrollHelper implementation
744 // ============================================================================
746 // ----------------------------------------------------------------------------
747 // wxVarHVScrollHelper operations
748 // ----------------------------------------------------------------------------
750 void wxVarHVScrollHelper::SetRowColumnCount(size_t rowCount
, size_t columnCount
)
752 SetRowCount(rowCount
);
753 SetColumnCount(columnCount
);
756 bool wxVarHVScrollHelper::ScrollToRowColumn(size_t row
, size_t column
)
759 result
|= ScrollToRow(row
);
760 result
|= ScrollToColumn(column
);
764 void wxVarHVScrollHelper::RefreshRowColumn(size_t row
, size_t column
)
766 // is this unit visible?
767 if ( !IsRowVisible(row
) || !IsColumnVisible(column
) )
769 // no, it is useless to do anything
773 // calculate the rect occupied by this cell on screen
774 wxRect v_rect
, h_rect
;
775 v_rect
.height
= OnGetRowHeight(row
);
776 h_rect
.width
= OnGetColumnWidth(column
);
780 for ( n
= GetVisibleRowsBegin(); n
< row
; n
++ )
782 v_rect
.y
+= OnGetRowHeight(n
);
785 for ( n
= GetVisibleColumnsBegin(); n
< column
; n
++ )
787 h_rect
.x
+= OnGetColumnWidth(n
);
790 // refresh but specialize the behavior if we have a single target window
791 if ( wxVarVScrollHelper::GetTargetWindow() == wxVarHScrollHelper::GetTargetWindow() )
794 v_rect
.width
= h_rect
.width
;
795 wxVarVScrollHelper::GetTargetWindow()->RefreshRect(v_rect
);
800 v_rect
.width
= wxVarVScrollHelper::GetNonOrientationTargetSize();
802 h_rect
.width
= wxVarHScrollHelper::GetNonOrientationTargetSize();
804 wxVarVScrollHelper::GetTargetWindow()->RefreshRect(v_rect
);
805 wxVarHScrollHelper::GetTargetWindow()->RefreshRect(h_rect
);
809 void wxVarHVScrollHelper::RefreshRowsColumns(size_t fromRow
, size_t toRow
,
810 size_t fromColumn
, size_t toColumn
)
812 wxASSERT_MSG( fromRow
<= toRow
|| fromColumn
<= toColumn
,
813 _T("RefreshRowsColumns(): empty range") );
815 // clump the range to just the visible units -- it is useless to refresh
817 if ( fromRow
< GetVisibleRowsBegin() )
818 fromRow
= GetVisibleRowsBegin();
820 if ( toRow
> GetVisibleRowsEnd() )
821 toRow
= GetVisibleRowsEnd();
823 if ( fromColumn
< GetVisibleColumnsBegin() )
824 fromColumn
= GetVisibleColumnsBegin();
826 if ( toColumn
> GetVisibleColumnsEnd() )
827 toColumn
= GetVisibleColumnsEnd();
829 // calculate the rect occupied by these units on screen
830 wxRect v_rect
, h_rect
;
831 size_t nBefore
, nBetween
;
833 for ( nBefore
= GetVisibleRowsBegin();
837 v_rect
.y
+= OnGetRowHeight(nBefore
);
840 for ( nBetween
= fromRow
; nBetween
<= toRow
; nBetween
++ )
842 v_rect
.height
+= OnGetRowHeight(nBetween
);
845 for ( nBefore
= GetVisibleColumnsBegin();
846 nBefore
< fromColumn
;
849 h_rect
.x
+= OnGetColumnWidth(nBefore
);
852 for ( nBetween
= fromColumn
; nBetween
<= toColumn
; nBetween
++ )
854 h_rect
.width
+= OnGetColumnWidth(nBetween
);
857 // refresh but specialize the behavior if we have a single target window
858 if ( wxVarVScrollHelper::GetTargetWindow() == wxVarHScrollHelper::GetTargetWindow() )
861 v_rect
.width
= h_rect
.width
;
862 wxVarVScrollHelper::GetTargetWindow()->RefreshRect(v_rect
);
867 v_rect
.width
= wxVarVScrollHelper::GetNonOrientationTargetSize();
869 h_rect
.width
= wxVarHScrollHelper::GetNonOrientationTargetSize();
871 wxVarVScrollHelper::GetTargetWindow()->RefreshRect(v_rect
);
872 wxVarHScrollHelper::GetTargetWindow()->RefreshRect(h_rect
);
876 wxPosition
wxVarHVScrollHelper::HitTest(wxCoord x
, wxCoord y
) const
878 return wxPosition(wxVarVScrollHelper::HitTest(y
),
879 wxVarHScrollHelper::HitTest(x
));
882 void wxVarHVScrollHelper::DoPrepareDC(wxDC
& dc
)
884 wxVarVScrollHelper::DoPrepareDC(dc
);
885 wxVarHScrollHelper::DoPrepareDC(dc
);
888 bool wxVarHVScrollHelper::ScrollLayout()
890 bool layout_result
= false;
891 layout_result
|= wxVarVScrollHelper::ScrollLayout();
892 layout_result
|= wxVarHScrollHelper::ScrollLayout();
893 return layout_result
;
896 wxSize
wxVarHVScrollHelper::GetRowColumnCount() const
898 return wxSize(GetColumnCount(), GetRowCount());
901 wxPosition
wxVarHVScrollHelper::GetVisibleBegin() const
903 return wxPosition(GetVisibleRowsBegin(), GetVisibleColumnsBegin());
906 wxPosition
wxVarHVScrollHelper::GetVisibleEnd() const
908 return wxPosition(GetVisibleRowsEnd(), GetVisibleColumnsEnd());
911 bool wxVarHVScrollHelper::IsVisible(size_t row
, size_t column
) const
913 return IsRowVisible(row
) && IsColumnVisible(column
);
917 // ============================================================================
918 // wx[V/H/HV]ScrolledWindow implementations
919 // ============================================================================
921 IMPLEMENT_ABSTRACT_CLASS(wxVScrolledWindow
, wxPanel
)
922 IMPLEMENT_ABSTRACT_CLASS(wxHScrolledWindow
, wxPanel
)
923 IMPLEMENT_ABSTRACT_CLASS(wxHVScrolledWindow
, wxPanel
)