// Author: Vadim Zeitlin
// Modified by: Brad Anderson, David Warkentin
// Created: 30.05.03
-// RCS-ID: $Id$
// Copyright: (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#include "wx/vscroll.h"
+#include "wx/utils.h" // For wxMin/wxMax().
+
// ============================================================================
// wxVarScrollHelperEvtHandler declaration
// ============================================================================
private:
wxVarScrollHelperBase *m_scrollHelper;
- DECLARE_NO_COPY_CLASS(wxVarScrollHelperEvtHandler)
+ wxDECLARE_NO_COPY_CLASS(wxVarScrollHelperEvtHandler);
};
// ============================================================================
// wxVarScrollHelperEvtHandler implementation
// ============================================================================
+// FIXME: This method totally duplicates a method with the same name in
+// wxScrollHelperEvtHandler, we really should merge them by reusing the
+// common parts in wxAnyScrollHelperBase.
bool wxVarScrollHelperEvtHandler::ProcessEvent(wxEvent& event)
{
wxEventType evType = event.GetEventType();
- // pass it on to the real handler
- bool processed = wxEvtHandler::ProcessEvent(event);
+ // Pass it on to the real handler: notice that we must not call
+ // ProcessEvent() on this object itself as it wouldn't pass it to the next
+ // handler (i.e. the real window) if we're called from a previous handler
+ // (as indicated by "process here only" flag being set) and we do want to
+ // execute the handler defined in the window we're associated with right
+ // now, without waiting until TryAfter() is called from wxEvtHandler.
+ bool processed = m_nextHandler->ProcessEvent(event);
// always process the size events ourselves, even if the user code handles
// them as well, as we need to AdjustScrollbars()
if ( evType == wxEVT_SIZE )
{
m_scrollHelper->HandleOnSize((wxSizeEvent &)event);
-
- return !event.GetSkipped();
+ return true;
}
- if ( processed )
+ if ( processed && event.IsCommandEvent())
+ return true;
+
+ // For wxEVT_PAINT the user code can either handle this event as usual or
+ // override virtual OnDraw(), so if the event hasn't been handled we need
+ // to call this virtual function ourselves.
+ if (
+#ifndef __WXUNIVERSAL__
+ // in wxUniversal "processed" will always be true, because
+ // all windows use the paint event to draw themselves.
+ // In this case we can't use this flag to determine if a custom
+ // paint event handler already drew our window and we just
+ // call OnDraw() anyway.
+ !processed &&
+#endif // !__WXUNIVERSAL__
+ evType == wxEVT_PAINT )
{
- // normally, nothing more to do here - except if we have a command
- // event
- if ( event.IsCommandEvent() )
- {
- return true;
- }
+ m_scrollHelper->HandleOnPaint((wxPaintEvent &)event);
+ return true;
}
// reset the skipped flag (which might have been set to true in
if ( wasSkipped )
event.Skip(false);
- // reset the skipped flag to false as it might have been set to true in
- // ProcessEvent() above
- event.Skip(false);
-
if ( evType == wxEVT_SCROLLWIN_TOP ||
evType == wxEVT_SCROLLWIN_BOTTOM ||
evType == wxEVT_SCROLLWIN_LINEUP ||
{
// 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)
+ // relies on our return value to stop scrolling when we are at top
+ // or bottom already)
processed = true;
wasSkipped = false;
}
}
#if wxUSE_MOUSEWHEEL
+ // Use GTK's own scroll wheel handling in GtkScrolledWindow
+#ifndef __WXGTK20__
else if ( evType == wxEVT_MOUSEWHEEL )
{
m_scrollHelper->HandleOnMouseWheel((wxMouseEvent &)event);
}
+#endif
#endif // wxUSE_MOUSEWHEEL
+ else if ( evType == wxEVT_CHAR &&
+ (m_scrollHelper->GetOrientation() == wxVERTICAL) )
+ {
+ m_scrollHelper->HandleOnChar((wxKeyEvent &)event);
+ if ( !event.GetSkipped() )
+ {
+ processed = true;
+ wasSkipped = false;
+ }
+ }
- if ( processed )
- event.Skip(wasSkipped);
+ event.Skip(wasSkipped);
+
+ // We called ProcessEvent() on the next handler, meaning that we explicitly
+ // worked around the request to process the event in this handler only. As
+ // explained above, this is unfortunately really necessary but the trouble
+ // is that the event will continue to be post-processed by the previous
+ // handler resulting in duplicate calls to event handlers. Call the special
+ // function below to prevent this from happening, base class DoTryChain()
+ // will check for it and behave accordingly.
+ //
+ // And if we're not called from DoTryChain(), this won't do anything anyhow.
+ event.DidntHonourProcessOnlyIn();
return processed;
}
// ----------------------------------------------------------------------------
wxVarScrollHelperBase::wxVarScrollHelperBase(wxWindow *win)
+ : wxAnyScrollHelperBase(win)
{
- wxASSERT_MSG( win, _T("associated window can't be NULL in wxVarScrollHelperBase") );
-
#if wxUSE_MOUSEWHEEL
m_sumWheelRotation = 0;
#endif
m_sizeTotal = 0;
m_unitFirst = 0;
- m_win =
- m_targetWindow = (wxWindow *)NULL;
-
m_physicalScrolling = true;
m_handler = NULL;
- m_win = win;
-
// by default, the associated window is also the target window
DoSetTargetWindow(win);
-
}
wxVarScrollHelperBase::~wxVarScrollHelperBase()
}
else if ( evtType == wxEVT_SCROLLWIN_PAGEUP )
{
- return FindFirstVisibleFromLast(m_unitFirst);
+ // Page up should do at least as much as line up.
+ return wxMin(FindFirstVisibleFromLast(m_unitFirst),
+ m_unitFirst ? m_unitFirst - 1 : 0);
}
else if ( evtType == wxEVT_SCROLLWIN_PAGEDOWN )
{
+ // And page down should do at least as much as line down.
if ( GetVisibleEnd() )
- return GetVisibleEnd() - 1;
+ return wxMax(GetVisibleEnd() - 1, m_unitFirst + 1);
else
- return GetVisibleEnd();
+ return wxMax(GetVisibleEnd(), m_unitFirst + 1);
}
else if ( evtType == wxEVT_SCROLLWIN_THUMBRELEASE )
{
}
// unknown scroll event?
- wxFAIL_MSG( _T("unknown scroll event type?") );
+ wxFAIL_MSG( wxT("unknown scroll event type?") );
return 0;
}
void wxVarScrollHelperBase::RefreshUnits(size_t from, size_t to)
{
- wxASSERT_MSG( from <= to, _T("RefreshUnits(): empty range") );
+ wxASSERT_MSG( from <= to, wxT("RefreshUnits(): empty range") );
// clump the range to just the visible units -- it is useless to refresh
// the other ones
to = GetVisibleEnd();
// calculate the rect occupied by these units on screen
- int orient_size, nonorient_size, orient_pos;
- orient_size = nonorient_size = orient_pos = 0;
+ int orient_size = 0,
+ orient_pos = 0;
- nonorient_size = GetNonOrientationTargetSize();
+ int nonorient_size = GetNonOrientationTargetSize();
for ( size_t nBefore = GetVisibleBegin();
nBefore < from;
// 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 )
+ (GetVisibleBegin() >= unitLastOld || GetVisibleEnd() <= unitFirstOld) )
{
// the simplest case: we don't have any old units left, just redraw
// everything
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();
h_rect.x += OnGetColumnWidth(n);
}
- // refresh but specialize the behavior if we have a single target window
+ // refresh but specialize the behaviour if we have a single target window
if ( wxVarVScrollHelper::GetTargetWindow() == wxVarHScrollHelper::GetTargetWindow() )
{
v_rect.x = h_rect.x;
size_t fromColumn, size_t toColumn)
{
wxASSERT_MSG( fromRow <= toRow || fromColumn <= toColumn,
- _T("RefreshRowsColumns(): empty range") );
+ wxT("RefreshRowsColumns(): empty range") );
// clump the range to just the visible units -- it is useless to refresh
// the other ones
h_rect.width += OnGetColumnWidth(nBetween);
}
- // refresh but specialize the behavior if we have a single target window
+ // refresh but specialize the behaviour if we have a single target window
if ( wxVarVScrollHelper::GetTargetWindow() == wxVarHScrollHelper::GetTargetWindow() )
{
v_rect.x = h_rect.x;
// wxVarVScrollLegacyAdaptor
// ===========================================================================
-size_t wxVarVScrollLegacyAdaptor::GetFirstVisibleLine() const
+size_t wxVarVScrollLegacyAdaptor::GetFirstVisibleLine() const
{ return GetVisibleRowsBegin(); }
size_t wxVarVScrollLegacyAdaptor::GetLastVisibleLine() const
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( _T("OnGetLineHeight() must be overridden if OnGetRowHeight() isn't!") );
+ wxFAIL_MSG( wxT("OnGetLineHeight() must be overridden if OnGetRowHeight() isn't!") );
return -1;
}