X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/c1aa5517acff9ce039adcd6502db2e88cdd886c7..75595b9750de618c1ccc6356159aad0918bc4786:/src/generic/vscroll.cpp diff --git a/src/generic/vscroll.cpp b/src/generic/vscroll.cpp index 59a65f856e..96fcd3f54d 100644 --- a/src/generic/vscroll.cpp +++ b/src/generic/vscroll.cpp @@ -2,7 +2,7 @@ // Name: src/generic/vscroll.cpp // Purpose: wxVScrolledWindow implementation // Author: Vadim Zeitlin -// Modified by: +// Modified by: Brad Anderson, David Warkentin // Created: 30.05.03 // RCS-ID: $Id$ // Copyright: (c) 2003 Vadim Zeitlin @@ -25,399 +25,639 @@ #endif #ifndef WX_PRECOMP + #include "wx/dc.h" #include "wx/sizer.h" #endif #include "wx/vscroll.h" +// ============================================================================ +// wxVarScrollHelperEvtHandler declaration +// ============================================================================ + // ---------------------------------------------------------------------------- -// event tables +// wxScrollHelperEvtHandler: intercept the events from the window and forward +// them to wxVarScrollHelperBase // ---------------------------------------------------------------------------- -BEGIN_EVENT_TABLE(wxVScrolledWindow, wxPanel) - EVT_SIZE(wxVScrolledWindow::OnSize) - EVT_SCROLLWIN(wxVScrolledWindow::OnScroll) -#if wxUSE_MOUSEWHEEL - EVT_MOUSEWHEEL(wxVScrolledWindow::OnMouseWheel) -#endif -END_EVENT_TABLE() +class WXDLLEXPORT wxVarScrollHelperEvtHandler : public wxEvtHandler +{ +public: + wxVarScrollHelperEvtHandler(wxVarScrollHelperBase *scrollHelper) + { + m_scrollHelper = scrollHelper; + } + + virtual bool ProcessEvent(wxEvent& event); + +private: + wxVarScrollHelperBase *m_scrollHelper; + wxDECLARE_NO_COPY_CLASS(wxVarScrollHelperEvtHandler); +}; // ============================================================================ -// implementation +// wxVarScrollHelperEvtHandler implementation // ============================================================================ -IMPLEMENT_ABSTRACT_CLASS(wxVScrolledWindow, wxPanel) +bool wxVarScrollHelperEvtHandler::ProcessEvent(wxEvent& event) +{ + wxEventType evType = event.GetEventType(); + + // pass it on to the real handler + bool processed = wxEvtHandler::ProcessEvent(event); + + // always process the size events ourselves, even if the user code handles + // them as well, as we need to AdjustScrollbars() + // + // NB: it is important to do it after processing the event in the normal + // way as HandleOnSize() may generate a wxEVT_SIZE itself if the + // scrollbar[s] (dis)appear and it should be seen by the user code + // after this one + if ( evType == wxEVT_SIZE ) + { + m_scrollHelper->HandleOnSize((wxSizeEvent &)event); + + return !event.GetSkipped(); + } + + if ( processed ) + { + // normally, nothing more to do here - except if we have a command + // event + if ( event.IsCommandEvent() ) + { + return true; + } + } + + // reset the skipped flag (which might have been set to true in + // ProcessEvent() above) to be able to test it below + bool wasSkipped = event.GetSkipped(); + if ( wasSkipped ) + event.Skip(false); + + if ( evType == wxEVT_SCROLLWIN_TOP || + evType == wxEVT_SCROLLWIN_BOTTOM || + evType == wxEVT_SCROLLWIN_LINEUP || + evType == wxEVT_SCROLLWIN_LINEDOWN || + evType == wxEVT_SCROLLWIN_PAGEUP || + evType == wxEVT_SCROLLWIN_PAGEDOWN || + evType == wxEVT_SCROLLWIN_THUMBTRACK || + evType == wxEVT_SCROLLWIN_THUMBRELEASE ) + { + m_scrollHelper->HandleOnScroll((wxScrollWinEvent &)event); + if ( !event.GetSkipped() ) + { + // it makes sense to indicate that we processed the message as we + // did scroll the window (and also notice that wxAutoScrollTimer + // relies on our return value for continuous scrolling) + processed = true; + wasSkipped = false; + } + } +#if wxUSE_MOUSEWHEEL + else if ( evType == wxEVT_MOUSEWHEEL ) + { + m_scrollHelper->HandleOnMouseWheel((wxMouseEvent &)event); + } +#endif // wxUSE_MOUSEWHEEL + + event.Skip(wasSkipped); + + return processed; +} + + +// ============================================================================ +// wxVarScrollHelperBase implementation +// ============================================================================ // ---------------------------------------------------------------------------- -// initialization +// wxVarScrollHelperBase initialization // ---------------------------------------------------------------------------- -void wxVScrolledWindow::Init() +wxVarScrollHelperBase::wxVarScrollHelperBase(wxWindow *win) { - // we're initially empty - m_lineMax = - m_lineFirst = 0; - - // this one should always be strictly positive - m_nVisible = 1; - - m_heightTotal = 0; + wxASSERT_MSG( win, wxT("associated window can't be NULL in wxVarScrollHelperBase") ); #if wxUSE_MOUSEWHEEL m_sumWheelRotation = 0; #endif + + m_unitMax = 0; + m_sizeTotal = 0; + m_unitFirst = 0; + + m_win = + m_targetWindow = NULL; + + m_physicalScrolling = true; + m_handler = NULL; + + m_win = win; + + // by default, the associated window is also the target window + DoSetTargetWindow(win); + +} + +wxVarScrollHelperBase::~wxVarScrollHelperBase() +{ + DeleteEvtHandler(); } // ---------------------------------------------------------------------------- -// various helpers +// wxVarScrollHelperBase various helpers // ---------------------------------------------------------------------------- -wxCoord wxVScrolledWindow::EstimateTotalHeight() const +void +wxVarScrollHelperBase::AssignOrient(wxCoord& x, + wxCoord& y, + wxCoord first, + wxCoord second) +{ + if ( GetOrientation() == wxVERTICAL ) + { + x = first; + y = second; + } + else // horizontal + { + x = second; + y = first; + } +} + +void +wxVarScrollHelperBase::IncOrient(wxCoord& x, wxCoord& y, wxCoord inc) +{ + if ( GetOrientation() == wxVERTICAL ) + y += inc; + else + x += inc; +} + +wxCoord wxVarScrollHelperBase::DoEstimateTotalSize() const { // estimate the total height: it is impossible to call - // OnGetLineHeight() for every line because there may be too many of - // them, so we just make a guess using some lines in the beginning, + // OnGetUnitSize() for every unit because there may be too many of + // them, so we just make a guess using some units in the beginning, // some in the end and some in the middle - static const size_t NUM_LINES_TO_SAMPLE = 10; + static const size_t NUM_UNITS_TO_SAMPLE = 10; - wxCoord heightTotal; - if ( m_lineMax < 3*NUM_LINES_TO_SAMPLE ) + wxCoord sizeTotal; + if ( m_unitMax < 3*NUM_UNITS_TO_SAMPLE ) { - // in this case calculating exactly is faster and more correct than + // in this case, full calculations are faster and more correct than // guessing - heightTotal = GetLinesHeight(0, m_lineMax); + sizeTotal = GetUnitsSize(0, m_unitMax); } - else // too many lines to calculate exactly + else // too many units to calculate exactly { - // look at some lines in the beginning/middle/end - heightTotal = - GetLinesHeight(0, NUM_LINES_TO_SAMPLE) + - GetLinesHeight(m_lineMax - NUM_LINES_TO_SAMPLE, m_lineMax) + - GetLinesHeight(m_lineMax/2 - NUM_LINES_TO_SAMPLE/2, - m_lineMax/2 + NUM_LINES_TO_SAMPLE/2); - - // use the height of the lines we looked as the average - heightTotal = (wxCoord) - (((float)heightTotal / (3*NUM_LINES_TO_SAMPLE)) * m_lineMax); + // look at some units in the beginning/middle/end + sizeTotal = + GetUnitsSize(0, NUM_UNITS_TO_SAMPLE) + + GetUnitsSize(m_unitMax - NUM_UNITS_TO_SAMPLE, + m_unitMax) + + GetUnitsSize(m_unitMax/2 - NUM_UNITS_TO_SAMPLE/2, + m_unitMax/2 + NUM_UNITS_TO_SAMPLE/2); + + // use the height of the units we looked as the average + sizeTotal = (wxCoord) + (((float)sizeTotal / (3*NUM_UNITS_TO_SAMPLE)) * m_unitMax); } - return heightTotal; + return sizeTotal; } -wxCoord wxVScrolledWindow::GetLinesHeight(size_t lineMin, size_t lineMax) const +wxCoord wxVarScrollHelperBase::GetUnitsSize(size_t unitMin, size_t unitMax) const { - if ( lineMin == lineMax ) + if ( unitMin == unitMax ) return 0; - else if ( lineMin > lineMax ) - return -GetLinesHeight(lineMax, lineMin); - //else: lineMin < lineMax + else if ( unitMin > unitMax ) + return -GetUnitsSize(unitMax, unitMin); + //else: unitMin < unitMax - // let the user code know that we're going to need all these lines - OnGetLinesHint(lineMin, lineMax); + // let the user code know that we're going to need all these units + OnGetUnitsSizeHint(unitMin, unitMax); - // do sum up their heights - wxCoord height = 0; - for ( size_t line = lineMin; line < lineMax; line++ ) + // sum up their sizes + wxCoord size = 0; + for ( size_t unit = unitMin; unit < unitMax; ++unit ) { - height += OnGetLineHeight(line); + size += OnGetUnitSize(unit); } - return height; + return size; } -size_t wxVScrolledWindow::FindFirstFromBottom(size_t lineLast, bool full) +size_t wxVarScrollHelperBase::FindFirstVisibleFromLast(size_t unitLast, bool full) const { - const wxCoord hWindow = GetClientSize().y; + const wxCoord sWindow = GetOrientationTargetSize(); - // go upwards until we arrive at a line such that lineLast is not visible + // go upwards until we arrive at a unit such that unitLast is not visible // any more when it is shown - size_t lineFirst = lineLast; - wxCoord h = 0; + size_t unitFirst = unitLast; + wxCoord s = 0; for ( ;; ) { - h += OnGetLineHeight(lineFirst); + s += OnGetUnitSize(unitFirst); - if ( h > hWindow ) + if ( s > sWindow ) { - // for this line to be fully visible we need to go one line + // for this unit to be fully visible we need to go one unit // down, but if it is enough for it to be only partly visible then - // this line will do as well + // this unit will do as well if ( full ) { - lineFirst++; + ++unitFirst; } break; } - if ( !lineFirst ) + if ( !unitFirst ) break; - lineFirst--; + --unitFirst; } - return lineFirst; + return unitFirst; } -void wxVScrolledWindow::RemoveScrollbar() +size_t wxVarScrollHelperBase::GetNewScrollPosition(wxScrollWinEvent& event) const { - m_lineFirst = 0; - m_nVisible = m_lineMax; - SetScrollbar(wxVERTICAL, 0, 0, 0); + wxEventType evtType = event.GetEventType(); + + if ( evtType == wxEVT_SCROLLWIN_TOP ) + { + return 0; + } + else if ( evtType == wxEVT_SCROLLWIN_BOTTOM ) + { + return m_unitMax; + } + else if ( evtType == wxEVT_SCROLLWIN_LINEUP ) + { + return m_unitFirst ? m_unitFirst - 1 : 0; + } + else if ( evtType == wxEVT_SCROLLWIN_LINEDOWN ) + { + return m_unitFirst + 1; + } + else if ( evtType == wxEVT_SCROLLWIN_PAGEUP ) + { + return FindFirstVisibleFromLast(m_unitFirst); + } + else if ( evtType == wxEVT_SCROLLWIN_PAGEDOWN ) + { + if ( GetVisibleEnd() ) + return GetVisibleEnd() - 1; + else + return GetVisibleEnd(); + } + else if ( evtType == wxEVT_SCROLLWIN_THUMBRELEASE ) + { + return event.GetPosition(); + } + else if ( evtType == wxEVT_SCROLLWIN_THUMBTRACK ) + { + return event.GetPosition(); + } + + // unknown scroll event? + wxFAIL_MSG( wxT("unknown scroll event type?") ); + return 0; } -void wxVScrolledWindow::UpdateScrollbar() +void wxVarScrollHelperBase::UpdateScrollbar() { - // see how many lines can we fit on screen - const wxCoord hWindow = GetClientSize().y; + // if there is nothing to scroll, remove the scrollbar + if ( !m_unitMax ) + { + RemoveScrollbar(); + return; + } - wxCoord h = 0; - size_t line; - for ( line = m_lineFirst; line < m_lineMax; line++ ) + // see how many units can we fit on screen + const wxCoord sWindow = GetOrientationTargetSize(); + + // do vertical calculations + wxCoord s = 0; + size_t unit; + for ( unit = m_unitFirst; unit < m_unitMax; ++unit ) { - if ( h > hWindow ) + if ( s > sWindow ) break; - h += OnGetLineHeight(line); + s += OnGetUnitSize(unit); } - // if we still have remaining space below, maybe we can fit everything? - if ( h < hWindow ) + m_nUnitsVisible = unit - m_unitFirst; + + int unitsPageSize = m_nUnitsVisible; + if ( s > sWindow ) { - wxCoord hAll = h; - for ( size_t lineFirst = m_lineFirst; lineFirst > 0; lineFirst-- ) - { - hAll += OnGetLineHeight(m_lineFirst - 1); - if ( hAll > hWindow ) - break; - } + // last unit is only partially visible, we still need the scrollbar and + // so we have to "fix" pageSize because if it is equal to m_unitMax + // the scrollbar is not shown at all under MSW + --unitsPageSize; + } + + // set the scrollbar parameters to reflect this + m_win->SetScrollbar(GetOrientation(), m_unitFirst, unitsPageSize, m_unitMax); +} - if ( hAll < hWindow ) +void wxVarScrollHelperBase::RemoveScrollbar() +{ + m_unitFirst = 0; + m_nUnitsVisible = m_unitMax; + m_win->SetScrollbar(GetOrientation(), 0, 0, 0); +} + +void wxVarScrollHelperBase::DeleteEvtHandler() +{ + // search for m_handler in the handler list + if ( m_win && m_handler ) + { + if ( m_win->RemoveEventHandler(m_handler) ) { - // we don't need scrollbar at all - RemoveScrollbar(); - return; + delete m_handler; } + //else: something is very wrong, so better [maybe] leak memory than + // risk a crash because of double deletion + + m_handler = NULL; } +} - m_nVisible = line - m_lineFirst; +void wxVarScrollHelperBase::DoSetTargetWindow(wxWindow *target) +{ + m_targetWindow = target; +#ifdef __WXMAC__ + target->MacSetClipChildren( true ) ; +#endif - int pageSize = m_nVisible; - if ( h > hWindow ) + // install the event handler which will intercept the events we're + // interested in (but only do it for our real window, not the target window + // which we scroll - we don't need to hijack its events) + if ( m_targetWindow == m_win ) { - // last line is only partially visible, we still need the scrollbar and - // so we have to "fix" pageSize because if it is equal to m_lineMax the - // scrollbar is not shown at all under MSW - pageSize--; - } + // if we already have a handler, delete it first + DeleteEvtHandler(); - // set the scrollbar parameters to reflect this - SetScrollbar(wxVERTICAL, m_lineFirst, pageSize, m_lineMax); + m_handler = new wxVarScrollHelperEvtHandler(this); + m_targetWindow->PushEventHandler(m_handler); + } } // ---------------------------------------------------------------------------- -// operations +// wxVarScrollHelperBase operations // ---------------------------------------------------------------------------- -void wxVScrolledWindow::SetLineCount(size_t count) +void wxVarScrollHelperBase::SetTargetWindow(wxWindow *target) { - // save the number of lines - m_lineMax = count; + wxCHECK_RET( target, wxT("target window must not be NULL") ); + + if ( target == m_targetWindow ) + return; + + DoSetTargetWindow(target); +} + +void wxVarScrollHelperBase::SetUnitCount(size_t count) +{ + // save the number of units + m_unitMax = count; // and our estimate for their total height - m_heightTotal = EstimateTotalHeight(); + m_sizeTotal = EstimateTotalSize(); - // recalculate the scrollbars parameters - if ( count ) + // ScrollToUnit() will update the scrollbar itself if it changes the unit + // we pass to it because it's out of [new] range + size_t oldScrollPos = m_unitFirst; + DoScrollToUnit(m_unitFirst); + if ( oldScrollPos == m_unitFirst ) { - m_lineFirst = 1; // make sure it is != 0 - ScrollToLine(0); - } - else // no items - { - RemoveScrollbar(); + // but if it didn't do it, we still need to update the scrollbar to + // reflect the changed number of units ourselves + UpdateScrollbar(); } } -void wxVScrolledWindow::RefreshLine(size_t line) +void wxVarScrollHelperBase::RefreshUnit(size_t unit) { - // is this line visible? - if ( !IsVisible(line) ) + // is this unit visible? + if ( !IsVisible(unit) ) { // no, it is useless to do anything return; } - // calculate the rect occupied by this line on screen + // calculate the rect occupied by this unit on screen wxRect rect; - rect.width = GetClientSize().x; - rect.height = OnGetLineHeight(line); - for ( size_t n = GetVisibleBegin(); n < line; n++ ) + AssignOrient(rect.width, rect.height, + GetNonOrientationTargetSize(), OnGetUnitSize(unit)); + + for ( size_t n = GetVisibleBegin(); n < unit; ++n ) { - rect.y += OnGetLineHeight(n); + IncOrient(rect.x, rect.y, OnGetUnitSize(n)); } // do refresh it - RefreshRect(rect); + m_targetWindow->RefreshRect(rect); } -void wxVScrolledWindow::RefreshLines(size_t from, size_t to) +void wxVarScrollHelperBase::RefreshUnits(size_t from, size_t to) { - wxASSERT_MSG( from <= to, _T("RefreshLines(): empty range") ); + wxASSERT_MSG( from <= to, wxT("RefreshUnits(): empty range") ); - // clump the range to just the visible lines -- it is useless to refresh + // clump the range to just the visible units -- it is useless to refresh // the other ones if ( from < GetVisibleBegin() ) from = GetVisibleBegin(); - if ( to >= GetVisibleEnd() ) + if ( to > GetVisibleEnd() ) to = GetVisibleEnd(); - else - to++; - // calculate the rect occupied by these lines on screen - wxRect rect; - rect.width = GetClientSize().x; - for ( size_t nBefore = GetVisibleBegin(); nBefore < from; nBefore++ ) + // calculate the rect occupied by these units on screen + int orient_size, nonorient_size, orient_pos; + orient_size = nonorient_size = orient_pos = 0; + + nonorient_size = GetNonOrientationTargetSize(); + + for ( size_t nBefore = GetVisibleBegin(); + nBefore < from; + nBefore++ ) { - rect.y += OnGetLineHeight(nBefore); + orient_pos += OnGetUnitSize(nBefore); } - for ( size_t nBetween = from; nBetween < to; nBetween++ ) + for ( size_t nBetween = from; nBetween <= to; nBetween++ ) { - rect.height += OnGetLineHeight(nBetween); + orient_size += OnGetUnitSize(nBetween); } + wxRect rect; + AssignOrient(rect.x, rect.y, 0, orient_pos); + AssignOrient(rect.width, rect.height, nonorient_size, orient_size); + // do refresh it - RefreshRect(rect); + m_targetWindow->RefreshRect(rect); } -void wxVScrolledWindow::RefreshAll() +void wxVarScrollHelperBase::RefreshAll() { UpdateScrollbar(); - Refresh(); + m_targetWindow->Refresh(); } -bool wxVScrolledWindow::Layout() +bool wxVarScrollHelperBase::ScrollLayout() { - if ( GetSizer() ) + if ( m_targetWindow->GetSizer() && m_physicalScrolling ) { // adjust the sizer dimensions/position taking into account the // virtual size and scrolled position of the window. - int w, h; - GetVirtualSize(&w, &h); + int x, y; + AssignOrient(x, y, 0, -GetScrollOffset()); - // x is always 0 so no variable needed - int y = -GetLinesHeight(0, GetFirstVisibleLine()); + int w, h; + m_targetWindow->GetVirtualSize(&w, &h); - GetSizer()->SetDimension(0, y, w, h); + m_targetWindow->GetSizer()->SetDimension(x, y, w, h); return true; } // fall back to default for LayoutConstraints - return wxPanel::Layout(); + return m_targetWindow->wxWindow::Layout(); } -int wxVScrolledWindow::HitTest(wxCoord WXUNUSED(x), wxCoord y) const +int wxVarScrollHelperBase::VirtualHitTest(wxCoord coord) const { - const size_t lineMax = GetVisibleEnd(); - for ( size_t line = GetVisibleBegin(); line < lineMax; line++ ) + const size_t unitMax = GetVisibleEnd(); + for ( size_t unit = GetVisibleBegin(); unit < unitMax; ++unit ) { - y -= OnGetLineHeight(line); - if ( y < 0 ) - return line; + coord -= OnGetUnitSize(unit); + if ( coord < 0 ) + return unit; } return wxNOT_FOUND; } // ---------------------------------------------------------------------------- -// scrolling +// wxVarScrollHelperBase scrolling // ---------------------------------------------------------------------------- -bool wxVScrolledWindow::ScrollToLine(size_t line) +bool wxVarScrollHelperBase::DoScrollToUnit(size_t unit) { - if ( !m_lineMax ) + if ( !m_unitMax ) { // we're empty, code below doesn't make sense in this case return false; } - // determine the real first line to scroll to: we shouldn't scroll beyond + // determine the real first unit to scroll to: we shouldn't scroll beyond // the end - size_t lineFirstLast = FindFirstFromBottom(m_lineMax - 1, true); - if ( line > lineFirstLast ) - line = lineFirstLast; + size_t unitFirstLast = FindFirstVisibleFromLast(m_unitMax - 1, true); + if ( unit > unitFirstLast ) + unit = unitFirstLast; // anything to do? - if ( line == m_lineFirst ) + if ( unit == m_unitFirst ) { // no return false; } - // remember the currently shown lines for the refresh code below - size_t lineFirstOld = GetVisibleBegin(), - lineLastOld = GetVisibleEnd(); + // remember the currently shown units for the refresh code below + size_t unitFirstOld = GetVisibleBegin(), + unitLastOld = GetVisibleEnd(); - m_lineFirst = line; + m_unitFirst = unit; // the size of scrollbar thumb could have changed UpdateScrollbar(); - - // finally refresh the display -- but only redraw as few lines as possible - // to avoid flicker - if ( GetVisibleBegin() >= lineLastOld || - GetVisibleEnd() <= lineFirstOld ) + // finally refresh the display -- but only redraw as few units as possible + // to avoid flicker. We can't do this if we have children because they + // won't be scrolled + if ( m_targetWindow->GetChildren().empty() && + (GetVisibleBegin() >= unitLastOld || GetVisibleEnd() <= unitFirstOld) ) { - // the simplest case: we don't have any old lines left, just redraw + // the simplest case: we don't have any old units left, just redraw // everything - Refresh(); + m_targetWindow->Refresh(); } - else // overlap between the lines we showed before and should show now + else // scroll the window { - ScrollWindow(0, GetLinesHeight(GetVisibleBegin(), lineFirstOld)); + // Avoid scrolling visible parts of the screen on Mac +#ifdef __WXMAC__ + if (m_physicalScrolling && m_targetWindow->IsShownOnScreen()) +#else + if ( m_physicalScrolling ) +#endif + { + wxCoord dx = 0, + dy = GetUnitsSize(GetVisibleBegin(), unitFirstOld); + + if ( GetOrientation() == wxHORIZONTAL ) + { + wxCoord tmp = dx; + dx = dy; + dy = tmp; + } + + m_targetWindow->ScrollWindow(dx, dy); + } + else // !m_physicalScrolling + { + // we still need to invalidate but we can't use ScrollWindow + // because physical scrolling is disabled (the user either didn't + // want children scrolled and/or doesn't want pixels to be + // physically scrolled). + m_targetWindow->Refresh(); + } } return true; } -bool wxVScrolledWindow::ScrollLines(int lines) +bool wxVarScrollHelperBase::DoScrollUnits(int units) { - lines += m_lineFirst; - if ( lines < 0 ) - lines = 0; + units += m_unitFirst; + if ( units < 0 ) + units = 0; - return ScrollToLine(lines); + return DoScrollToUnit(units); } -bool wxVScrolledWindow::ScrollPages(int pages) +bool wxVarScrollHelperBase::DoScrollPages(int pages) { bool didSomething = false; while ( pages ) { - int line; + int unit; if ( pages > 0 ) { - line = GetVisibleEnd(); - if ( line ) - line--; - pages--; + unit = GetVisibleEnd(); + if ( unit ) + --unit; + --pages; } else // pages < 0 { - line = FindFirstFromBottom(GetVisibleBegin()); - pages++; + unit = FindFirstVisibleFromLast(GetVisibleEnd()); + ++pages; } - didSomething = ScrollToLine(line); + didSomething = DoScrollToUnit(unit); } return didSomething; @@ -427,87 +667,350 @@ bool wxVScrolledWindow::ScrollPages(int pages) // event handling // ---------------------------------------------------------------------------- -void wxVScrolledWindow::OnSize(wxSizeEvent& event) +void wxVarScrollHelperBase::HandleOnSize(wxSizeEvent& event) { + if ( m_unitMax ) + { + // sometimes change in varscrollable window's size can result in + // unused empty space after the last item. Fix it by decrementing + // first visible item position according to the available space. + + // determine free space + const wxCoord sWindow = GetOrientationTargetSize(); + wxCoord s = 0; + size_t unit; + for ( unit = m_unitFirst; unit < m_unitMax; ++unit ) + { + if ( s > sWindow ) + break; + + s += OnGetUnitSize(unit); + } + wxCoord freeSpace = sWindow - s; + + // decrement first visible item index as long as there is free space + size_t idealUnitFirst; + for ( idealUnitFirst = m_unitFirst; + idealUnitFirst > 0; + idealUnitFirst-- ) + { + wxCoord us = OnGetUnitSize(idealUnitFirst-1); + if ( freeSpace < us ) + break; + freeSpace -= us; + } + m_unitFirst = idealUnitFirst; + } + UpdateScrollbar(); event.Skip(); } -void wxVScrolledWindow::OnScroll(wxScrollWinEvent& event) +void wxVarScrollHelperBase::HandleOnScroll(wxScrollWinEvent& event) { - size_t lineFirstNew; + if (GetOrientation() != event.GetOrientation()) + { + event.Skip(); + return; + } - const wxEventType evtType = event.GetEventType(); + DoScrollToUnit(GetNewScrollPosition(event)); - if ( evtType == wxEVT_SCROLLWIN_TOP ) +#ifdef __WXMAC__ + UpdateMacScrollWindow(); +#endif // __WXMAC__ +} + +void wxVarScrollHelperBase::DoPrepareDC(wxDC& dc) +{ + if ( m_physicalScrolling ) { - lineFirstNew = 0; + wxPoint pt = dc.GetDeviceOrigin(); + + IncOrient(pt.x, pt.y, -GetScrollOffset()); + + dc.SetDeviceOrigin(pt.x, pt.y); } - else if ( evtType == wxEVT_SCROLLWIN_BOTTOM ) +} + +int wxVarScrollHelperBase::DoCalcScrolledPosition(int coord) const +{ + return coord - GetScrollOffset(); +} + +int wxVarScrollHelperBase::DoCalcUnscrolledPosition(int coord) const +{ + return coord + GetScrollOffset(); +} + +#if wxUSE_MOUSEWHEEL + +void wxVarScrollHelperBase::HandleOnMouseWheel(wxMouseEvent& event) +{ + // we only want to process wheel events for vertical implementations. + // There is no way to determine wheel orientation (and on MSW horizontal + // wheel rotation just fakes scroll events, rather than sending a MOUSEWHEEL + // event). + if ( GetOrientation() != wxVERTICAL ) + return; + + m_sumWheelRotation += event.GetWheelRotation(); + int delta = event.GetWheelDelta(); + + // how much to scroll this time + int units_to_scroll = -(m_sumWheelRotation/delta); + if ( !units_to_scroll ) + return; + + m_sumWheelRotation += units_to_scroll*delta; + + if ( !event.IsPageScroll() ) + DoScrollUnits( units_to_scroll*event.GetLinesPerAction() ); + else // scroll pages instead of units + DoScrollPages( units_to_scroll ); +} + +#endif // wxUSE_MOUSEWHEEL + + +// ============================================================================ +// wxVarHVScrollHelper implementation +// ============================================================================ + +// ---------------------------------------------------------------------------- +// wxVarHVScrollHelper operations +// ---------------------------------------------------------------------------- + +void wxVarHVScrollHelper::SetRowColumnCount(size_t rowCount, size_t columnCount) +{ + SetRowCount(rowCount); + SetColumnCount(columnCount); +} + +bool wxVarHVScrollHelper::ScrollToRowColumn(size_t row, size_t column) +{ + bool result = false; + result |= ScrollToRow(row); + result |= ScrollToColumn(column); + return result; +} + +void wxVarHVScrollHelper::RefreshRowColumn(size_t row, size_t column) +{ + // is this unit visible? + if ( !IsRowVisible(row) || !IsColumnVisible(column) ) { - lineFirstNew = m_lineMax; + // no, it is useless to do anything + return; } - else if ( evtType == wxEVT_SCROLLWIN_LINEUP ) + + // calculate the rect occupied by this cell on screen + wxRect v_rect, h_rect; + v_rect.height = OnGetRowHeight(row); + h_rect.width = OnGetColumnWidth(column); + + size_t n; + + for ( n = GetVisibleRowsBegin(); n < row; n++ ) { - lineFirstNew = m_lineFirst ? m_lineFirst - 1 : 0; + v_rect.y += OnGetRowHeight(n); } - else if ( evtType == wxEVT_SCROLLWIN_LINEDOWN ) + + for ( n = GetVisibleColumnsBegin(); n < column; n++ ) { - lineFirstNew = m_lineFirst + 1; + h_rect.x += OnGetColumnWidth(n); } - else if ( evtType == wxEVT_SCROLLWIN_PAGEUP ) + + // refresh but specialize the behavior if we have a single target window + if ( wxVarVScrollHelper::GetTargetWindow() == wxVarHScrollHelper::GetTargetWindow() ) { - lineFirstNew = FindFirstFromBottom(m_lineFirst); + v_rect.x = h_rect.x; + v_rect.width = h_rect.width; + wxVarVScrollHelper::GetTargetWindow()->RefreshRect(v_rect); } - else if ( evtType == wxEVT_SCROLLWIN_PAGEDOWN ) + else { - lineFirstNew = GetVisibleEnd(); - if ( lineFirstNew ) - lineFirstNew--; + v_rect.x = 0; + v_rect.width = wxVarVScrollHelper::GetNonOrientationTargetSize(); + h_rect.y = 0; + h_rect.width = wxVarHScrollHelper::GetNonOrientationTargetSize(); + + wxVarVScrollHelper::GetTargetWindow()->RefreshRect(v_rect); + wxVarHScrollHelper::GetTargetWindow()->RefreshRect(h_rect); } - else if ( evtType == wxEVT_SCROLLWIN_THUMBRELEASE ) +} + +void wxVarHVScrollHelper::RefreshRowsColumns(size_t fromRow, size_t toRow, + size_t fromColumn, size_t toColumn) +{ + wxASSERT_MSG( fromRow <= toRow || fromColumn <= toColumn, + wxT("RefreshRowsColumns(): empty range") ); + + // clump the range to just the visible units -- it is useless to refresh + // the other ones + if ( fromRow < GetVisibleRowsBegin() ) + fromRow = GetVisibleRowsBegin(); + + if ( toRow > GetVisibleRowsEnd() ) + toRow = GetVisibleRowsEnd(); + + if ( fromColumn < GetVisibleColumnsBegin() ) + fromColumn = GetVisibleColumnsBegin(); + + if ( toColumn > GetVisibleColumnsEnd() ) + toColumn = GetVisibleColumnsEnd(); + + // calculate the rect occupied by these units on screen + wxRect v_rect, h_rect; + size_t nBefore, nBetween; + + for ( nBefore = GetVisibleRowsBegin(); + nBefore < fromRow; + nBefore++ ) { - lineFirstNew = event.GetPosition(); + v_rect.y += OnGetRowHeight(nBefore); } - else if ( evtType == wxEVT_SCROLLWIN_THUMBTRACK ) + + for ( nBetween = fromRow; nBetween <= toRow; nBetween++ ) { - lineFirstNew = event.GetPosition(); + v_rect.height += OnGetRowHeight(nBetween); } - else // unknown scroll event? + for ( nBefore = GetVisibleColumnsBegin(); + nBefore < fromColumn; + nBefore++ ) { - wxFAIL_MSG( _T("unknown scroll event type?") ); - return; + h_rect.x += OnGetColumnWidth(nBefore); } - ScrollToLine(lineFirstNew); + for ( nBetween = fromColumn; nBetween <= toColumn; nBetween++ ) + { + h_rect.width += OnGetColumnWidth(nBetween); + } -#ifdef __WXMAC__ - Update(); -#endif // __WXMAC__ + // refresh but specialize the behavior if we have a single target window + if ( wxVarVScrollHelper::GetTargetWindow() == wxVarHScrollHelper::GetTargetWindow() ) + { + v_rect.x = h_rect.x; + v_rect.width = h_rect.width; + wxVarVScrollHelper::GetTargetWindow()->RefreshRect(v_rect); + } + else + { + v_rect.x = 0; + v_rect.width = wxVarVScrollHelper::GetNonOrientationTargetSize(); + h_rect.y = 0; + h_rect.width = wxVarHScrollHelper::GetNonOrientationTargetSize(); + + wxVarVScrollHelper::GetTargetWindow()->RefreshRect(v_rect); + wxVarHScrollHelper::GetTargetWindow()->RefreshRect(h_rect); + } } -#if wxUSE_MOUSEWHEEL +wxPosition wxVarHVScrollHelper::VirtualHitTest(wxCoord x, wxCoord y) const +{ + return wxPosition(wxVarVScrollHelper::VirtualHitTest(y), + wxVarHScrollHelper::VirtualHitTest(x)); +} -void wxVScrolledWindow::OnMouseWheel(wxMouseEvent& event) +void wxVarHVScrollHelper::DoPrepareDC(wxDC& dc) { - m_sumWheelRotation += event.GetWheelRotation(); - int delta = event.GetWheelDelta(); + wxVarVScrollHelper::DoPrepareDC(dc); + wxVarHScrollHelper::DoPrepareDC(dc); +} - // how much to scroll this time - int units_to_scroll = -(m_sumWheelRotation/delta); - if ( !units_to_scroll ) - return; +bool wxVarHVScrollHelper::ScrollLayout() +{ + bool layout_result = false; + layout_result |= wxVarVScrollHelper::ScrollLayout(); + layout_result |= wxVarHScrollHelper::ScrollLayout(); + return layout_result; +} - m_sumWheelRotation += units_to_scroll*delta; +wxSize wxVarHVScrollHelper::GetRowColumnCount() const +{ + return wxSize(GetColumnCount(), GetRowCount()); +} - if ( !event.IsPageScroll() ) - ScrollLines( units_to_scroll*event.GetLinesPerAction() ); - else - // scroll pages instead of lines - ScrollPages( units_to_scroll ); +wxPosition wxVarHVScrollHelper::GetVisibleBegin() const +{ + return wxPosition(GetVisibleRowsBegin(), GetVisibleColumnsBegin()); } -#endif // wxUSE_MOUSEWHEEL +wxPosition wxVarHVScrollHelper::GetVisibleEnd() const +{ + return wxPosition(GetVisibleRowsEnd(), GetVisibleColumnsEnd()); +} + +bool wxVarHVScrollHelper::IsVisible(size_t row, size_t column) const +{ + return IsRowVisible(row) && IsColumnVisible(column); +} + + +// ============================================================================ +// wx[V/H/HV]ScrolledWindow implementations +// ============================================================================ + +IMPLEMENT_ABSTRACT_CLASS(wxVScrolledWindow, wxPanel) +IMPLEMENT_ABSTRACT_CLASS(wxHScrolledWindow, wxPanel) +IMPLEMENT_ABSTRACT_CLASS(wxHVScrolledWindow, wxPanel) + + +#if WXWIN_COMPATIBILITY_2_8 + +// =========================================================================== +// wxVarVScrollLegacyAdaptor +// =========================================================================== + +size_t wxVarVScrollLegacyAdaptor::GetFirstVisibleLine() const +{ return GetVisibleRowsBegin(); } + +size_t wxVarVScrollLegacyAdaptor::GetLastVisibleLine() const +{ return GetVisibleRowsEnd() - 1; } + +size_t wxVarVScrollLegacyAdaptor::GetLineCount() const +{ return GetRowCount(); } + +void wxVarVScrollLegacyAdaptor::SetLineCount(size_t count) +{ SetRowCount(count); } + +void wxVarVScrollLegacyAdaptor::RefreshLine(size_t line) +{ RefreshRow(line); } + +void wxVarVScrollLegacyAdaptor::RefreshLines(size_t from, size_t to) +{ RefreshRows(from, to); } + +bool wxVarVScrollLegacyAdaptor::ScrollToLine(size_t line) +{ return ScrollToRow(line); } + +bool wxVarVScrollLegacyAdaptor::ScrollLines(int lines) +{ return ScrollRows(lines); } + +bool wxVarVScrollLegacyAdaptor::ScrollPages(int pages) +{ return ScrollRowPages(pages); } + +wxCoord wxVarVScrollLegacyAdaptor::OnGetLineHeight(size_t WXUNUSED(n)) const +{ + wxFAIL_MSG( wxT("OnGetLineHeight() must be overridden if OnGetRowHeight() isn't!") ); + return -1; +} + +void wxVarVScrollLegacyAdaptor::OnGetLinesHint(size_t WXUNUSED(lineMin), + size_t WXUNUSED(lineMax)) const +{ +} + +wxCoord wxVarVScrollLegacyAdaptor::OnGetRowHeight(size_t n) const +{ + return OnGetLineHeight(n); +} + +void wxVarVScrollLegacyAdaptor::OnGetRowsHeightHint(size_t rowMin, + size_t rowMax) const +{ + OnGetLinesHint(rowMin, rowMax); +} +#endif // WXWIN_COMPATIBILITY_2_8