1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/generic/vscroll.cpp
3 // Purpose:     wxVScrolledWindow implementation
4 // Author:      Vadim Zeitlin
5 // Modified by:
6 // Created:     30.05.03
7 // RCS-ID:      $Id: vscroll.cpp 57359 2008-12-15 19:09:31Z BP $
8 // Copyright:   (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
9 // Licence:     wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11 
12 // ============================================================================
13 // declarations
14 // ============================================================================
15 
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19 
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22 
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif
26 
27 #ifndef WX_PRECOMP
28     #include "wx/sizer.h"
29 #endif
30 
31 #include "wx/vscroll.h"
32 
33 // ----------------------------------------------------------------------------
34 // event tables
35 // ----------------------------------------------------------------------------
36 
BEGIN_EVENT_TABLE(wxVScrolledWindow,wxPanel)37 BEGIN_EVENT_TABLE(wxVScrolledWindow, wxPanel)
38     EVT_SIZE(wxVScrolledWindow::OnSize)
39     EVT_SCROLLWIN(wxVScrolledWindow::OnScroll)
40 #if wxUSE_MOUSEWHEEL
41     EVT_MOUSEWHEEL(wxVScrolledWindow::OnMouseWheel)
42 #endif
43 END_EVENT_TABLE()
44 
45 
46 // ============================================================================
47 // implementation
48 // ============================================================================
49 
50 IMPLEMENT_ABSTRACT_CLASS(wxVScrolledWindow, wxPanel)
51 
52 // ----------------------------------------------------------------------------
53 // initialization
54 // ----------------------------------------------------------------------------
55 
56 void wxVScrolledWindow::Init()
57 {
58     // we're initially empty
59     m_lineMax =
60     m_lineFirst = 0;
61 
62     // this one should always be strictly positive
63     m_nVisible = 1;
64 
65     m_heightTotal = 0;
66 
67 #if wxUSE_MOUSEWHEEL
68     m_sumWheelRotation = 0;
69 #endif
70 }
71 
72 // ----------------------------------------------------------------------------
73 // various helpers
74 // ----------------------------------------------------------------------------
75 
EstimateTotalHeight() const76 wxCoord wxVScrolledWindow::EstimateTotalHeight() const
77 {
78     // estimate the total height: it is impossible to call
79     // OnGetLineHeight() for every line because there may be too many of
80     // them, so we just make a guess using some lines in the beginning,
81     // some in the end and some in the middle
82     static const size_t NUM_LINES_TO_SAMPLE = 10;
83 
84     wxCoord heightTotal;
85     if ( m_lineMax < 3*NUM_LINES_TO_SAMPLE )
86     {
87         // in this case calculating exactly is faster and more correct than
88         // guessing
89         heightTotal = GetLinesHeight(0, m_lineMax);
90     }
91     else // too many lines to calculate exactly
92     {
93         // look at some lines in the beginning/middle/end
94         heightTotal =
95             GetLinesHeight(0, NUM_LINES_TO_SAMPLE) +
96                 GetLinesHeight(m_lineMax - NUM_LINES_TO_SAMPLE, m_lineMax) +
97                     GetLinesHeight(m_lineMax/2 - NUM_LINES_TO_SAMPLE/2,
98                                    m_lineMax/2 + NUM_LINES_TO_SAMPLE/2);
99 
100         // use the height of the lines we looked as the average
101         heightTotal = (wxCoord)
102                 (((float)heightTotal / (3*NUM_LINES_TO_SAMPLE)) * m_lineMax);
103     }
104 
105     return heightTotal;
106 }
107 
GetLinesHeight(size_t lineMin,size_t lineMax) const108 wxCoord wxVScrolledWindow::GetLinesHeight(size_t lineMin, size_t lineMax) const
109 {
110     if ( lineMin == lineMax )
111         return 0;
112     else if ( lineMin > lineMax )
113         return -GetLinesHeight(lineMax, lineMin);
114     //else: lineMin < lineMax
115 
116     // let the user code know that we're going to need all these lines
117     OnGetLinesHint(lineMin, lineMax);
118 
119     // do sum up their heights
120     wxCoord height = 0;
121     for ( size_t line = lineMin; line < lineMax; line++ )
122     {
123         height += OnGetLineHeight(line);
124     }
125 
126     return height;
127 }
128 
FindFirstFromBottom(size_t lineLast,bool full)129 size_t wxVScrolledWindow::FindFirstFromBottom(size_t lineLast, bool full)
130 {
131     const wxCoord hWindow = GetClientSize().y;
132 
133     // go upwards until we arrive at a line such that lineLast is not visible
134     // any more when it is shown
135     size_t lineFirst = lineLast;
136     wxCoord h = 0;
137     for ( ;; )
138     {
139         h += OnGetLineHeight(lineFirst);
140 
141         if ( h > hWindow )
142         {
143             // for this line to be fully visible we need to go one line
144             // down, but if it is enough for it to be only partly visible then
145             // this line will do as well
146             if ( full )
147             {
148                 lineFirst++;
149             }
150 
151             break;
152         }
153 
154         if ( !lineFirst )
155             break;
156 
157         lineFirst--;
158     }
159 
160     return lineFirst;
161 }
162 
RemoveScrollbar()163 void wxVScrolledWindow::RemoveScrollbar()
164 {
165     m_lineFirst = 0;
166     m_nVisible = m_lineMax;
167     SetScrollbar(wxVERTICAL, 0, 0, 0);
168 }
169 
UpdateScrollbar()170 void wxVScrolledWindow::UpdateScrollbar()
171 {
172     // see how many lines can we fit on screen
173     const wxCoord hWindow = GetClientSize().y;
174 
175     wxCoord h = 0;
176     size_t line;
177     for ( line = m_lineFirst; line < m_lineMax; line++ )
178     {
179         if ( h > hWindow )
180             break;
181 
182         h += OnGetLineHeight(line);
183     }
184 
185     // if we still have remaining space below, maybe we can fit everything?
186     if ( h < hWindow )
187     {
188         wxCoord hAll = h;
189         for ( size_t lineFirst = m_lineFirst; lineFirst > 0; lineFirst-- )
190         {
191             hAll += OnGetLineHeight(m_lineFirst - 1);
192             if ( hAll > hWindow )
193                 break;
194         }
195 
196         if ( hAll < hWindow )
197         {
198             // we don't need scrollbar at all
199             RemoveScrollbar();
200             return;
201         }
202     }
203 
204     m_nVisible = line - m_lineFirst;
205 
206     int pageSize = m_nVisible;
207     if ( h > hWindow )
208     {
209         // last line is only partially visible, we still need the scrollbar and
210         // so we have to "fix" pageSize because if it is equal to m_lineMax the
211         // scrollbar is not shown at all under MSW
212         pageSize--;
213     }
214 
215     // set the scrollbar parameters to reflect this
216     SetScrollbar(wxVERTICAL, m_lineFirst, pageSize, m_lineMax);
217 }
218 
219 // ----------------------------------------------------------------------------
220 // operations
221 // ----------------------------------------------------------------------------
222 
SetLineCount(size_t count)223 void wxVScrolledWindow::SetLineCount(size_t count)
224 {
225     // save the number of lines
226     m_lineMax = count;
227 
228     // and our estimate for their total height
229     m_heightTotal = EstimateTotalHeight();
230 
231     // recalculate the scrollbars parameters
232     if ( count )
233     {
234         m_lineFirst = 1;    // make sure it is != 0
235         ScrollToLine(0);
236     }
237     else // no items
238     {
239         RemoveScrollbar();
240     }
241 }
242 
RefreshLine(size_t line)243 void wxVScrolledWindow::RefreshLine(size_t line)
244 {
245     // is this line visible?
246     if ( !IsVisible(line) )
247     {
248         // no, it is useless to do anything
249         return;
250     }
251 
252     // calculate the rect occupied by this line on screen
253     wxRect rect;
254     rect.width = GetClientSize().x;
255     rect.height = OnGetLineHeight(line);
256     for ( size_t n = GetVisibleBegin(); n < line; n++ )
257     {
258         rect.y += OnGetLineHeight(n);
259     }
260 
261     // do refresh it
262     RefreshRect(rect);
263 }
264 
RefreshLines(size_t from,size_t to)265 void wxVScrolledWindow::RefreshLines(size_t from, size_t to)
266 {
267     wxASSERT_MSG( from <= to, _T("RefreshLines(): empty range") );
268 
269     // clump the range to just the visible lines -- it is useless to refresh
270     // the other ones
271     if ( from < GetVisibleBegin() )
272         from = GetVisibleBegin();
273 
274     if ( to >= GetVisibleEnd() )
275         to = GetVisibleEnd();
276     else
277         to++;
278 
279     // calculate the rect occupied by these lines on screen
280     wxRect rect;
281     rect.width = GetClientSize().x;
282     for ( size_t nBefore = GetVisibleBegin(); nBefore < from; nBefore++ )
283     {
284         rect.y += OnGetLineHeight(nBefore);
285     }
286 
287     for ( size_t nBetween = from; nBetween < to; nBetween++ )
288     {
289         rect.height += OnGetLineHeight(nBetween);
290     }
291 
292     // do refresh it
293     RefreshRect(rect);
294 }
295 
RefreshAll()296 void wxVScrolledWindow::RefreshAll()
297 {
298     UpdateScrollbar();
299 
300     Refresh();
301 }
302 
Layout()303 bool wxVScrolledWindow::Layout()
304 {
305     if ( GetSizer() )
306     {
307         // adjust the sizer dimensions/position taking into account the
308         // virtual size and scrolled position of the window.
309 
310         int w = 0, h = 0;
311         GetVirtualSize(&w, &h);
312 
313         // x is always 0 so no variable needed
314         int y = -GetLinesHeight(0, GetFirstVisibleLine());
315 
316         GetSizer()->SetDimension(0, y, w, h);
317         return true;
318     }
319 
320     // fall back to default for LayoutConstraints
321     return wxPanel::Layout();
322 }
323 
HitTest(wxCoord WXUNUSED (x),wxCoord y) const324 int wxVScrolledWindow::HitTest(wxCoord WXUNUSED(x), wxCoord y) const
325 {
326     const size_t lineMax = GetVisibleEnd();
327     for ( size_t line = GetVisibleBegin(); line < lineMax; line++ )
328     {
329         y -= OnGetLineHeight(line);
330         if ( y < 0 )
331             return line;
332     }
333 
334     return wxNOT_FOUND;
335 }
336 
337 // ----------------------------------------------------------------------------
338 // scrolling
339 // ----------------------------------------------------------------------------
340 
ScrollToLine(size_t line)341 bool wxVScrolledWindow::ScrollToLine(size_t line)
342 {
343     if ( !m_lineMax )
344     {
345         // we're empty, code below doesn't make sense in this case
346         return false;
347     }
348 
349     // determine the real first line to scroll to: we shouldn't scroll beyond
350     // the end
351     size_t lineFirstLast = FindFirstFromBottom(m_lineMax - 1, true);
352     if ( line > lineFirstLast )
353         line = lineFirstLast;
354 
355     // anything to do?
356     if ( line == m_lineFirst )
357     {
358         // no
359         return false;
360     }
361 
362 
363     // remember the currently shown lines for the refresh code below
364     size_t lineFirstOld = GetVisibleBegin(),
365            lineLastOld = GetVisibleEnd();
366 
367     m_lineFirst = line;
368 
369 
370     // the size of scrollbar thumb could have changed
371     UpdateScrollbar();
372 
373 
374     // finally refresh the display -- but only redraw as few lines as possible
375     // to avoid flicker
376     if ( GetChildren().empty() &&
377          (GetVisibleBegin() >= lineLastOld || GetVisibleEnd() <= lineFirstOld ) )
378     {
379         // the simplest case: we don't have any old lines left, just redraw
380         // everything
381         Refresh();
382     }
383     else // overlap between the lines we showed before and should show now
384     {
385         // Avoid scrolling visible parts of the screen on Mac
386 #ifdef __WXMAC__
387         if (!IsShownOnScreen())
388             Refresh();
389         else
390 #endif
391         ScrollWindow(0, GetLinesHeight(GetVisibleBegin(), lineFirstOld));
392     }
393 
394     return true;
395 }
396 
ScrollLines(int lines)397 bool wxVScrolledWindow::ScrollLines(int lines)
398 {
399     lines += m_lineFirst;
400     if ( lines < 0 )
401         lines = 0;
402 
403     return ScrollToLine(lines);
404 }
405 
ScrollPages(int pages)406 bool wxVScrolledWindow::ScrollPages(int pages)
407 {
408     bool didSomething = false;
409 
410     while ( pages )
411     {
412         int line;
413         if ( pages > 0 )
414         {
415             line = GetVisibleEnd();
416             if ( line )
417                 line--;
418             pages--;
419         }
420         else // pages < 0
421         {
422             line = FindFirstFromBottom(GetVisibleBegin());
423             pages++;
424         }
425 
426         didSomething = ScrollToLine(line);
427     }
428 
429     return didSomething;
430 }
431 
432 // ----------------------------------------------------------------------------
433 // event handling
434 // ----------------------------------------------------------------------------
435 
OnSize(wxSizeEvent & event)436 void wxVScrolledWindow::OnSize(wxSizeEvent& event)
437 {
438     UpdateScrollbar();
439 
440     event.Skip();
441 }
442 
OnScroll(wxScrollWinEvent & event)443 void wxVScrolledWindow::OnScroll(wxScrollWinEvent& event)
444 {
445     size_t lineFirstNew;
446 
447     const wxEventType evtType = event.GetEventType();
448 
449     if ( evtType == wxEVT_SCROLLWIN_TOP )
450     {
451         lineFirstNew = 0;
452     }
453     else if ( evtType == wxEVT_SCROLLWIN_BOTTOM )
454     {
455         lineFirstNew = m_lineMax;
456     }
457     else if ( evtType == wxEVT_SCROLLWIN_LINEUP )
458     {
459         lineFirstNew = m_lineFirst ? m_lineFirst - 1 : 0;
460     }
461     else if ( evtType == wxEVT_SCROLLWIN_LINEDOWN )
462     {
463         lineFirstNew = m_lineFirst + 1;
464     }
465     else if ( evtType == wxEVT_SCROLLWIN_PAGEUP )
466     {
467         lineFirstNew = FindFirstFromBottom(m_lineFirst);
468     }
469     else if ( evtType == wxEVT_SCROLLWIN_PAGEDOWN )
470     {
471         lineFirstNew = GetVisibleEnd();
472         if ( lineFirstNew )
473             lineFirstNew--;
474     }
475     else if ( evtType == wxEVT_SCROLLWIN_THUMBRELEASE )
476     {
477         lineFirstNew = event.GetPosition();
478     }
479     else if ( evtType == wxEVT_SCROLLWIN_THUMBTRACK )
480     {
481         lineFirstNew = event.GetPosition();
482     }
483 
484     else // unknown scroll event?
485     {
486         wxFAIL_MSG( _T("unknown scroll event type?") );
487         return;
488     }
489 
490     ScrollToLine(lineFirstNew);
491 
492 #ifdef __WXMAC__
493     Update();
494 #endif // __WXMAC__
495 }
496 
497 #if wxUSE_MOUSEWHEEL
498 
OnMouseWheel(wxMouseEvent & event)499 void wxVScrolledWindow::OnMouseWheel(wxMouseEvent& event)
500 {
501     m_sumWheelRotation += event.GetWheelRotation();
502     int delta = event.GetWheelDelta();
503 
504     // how much to scroll this time
505     int units_to_scroll = -(m_sumWheelRotation/delta);
506     if ( !units_to_scroll )
507         return;
508 
509     m_sumWheelRotation += units_to_scroll*delta;
510 
511     if ( !event.IsPageScroll() )
512         ScrollLines( units_to_scroll*event.GetLinesPerAction() );
513     else
514         // scroll pages instead of lines
515         ScrollPages( units_to_scroll );
516 }
517 
518 #endif // wxUSE_MOUSEWHEEL
519 
520