added EstimateTotalHeight
[wxWidgets.git] / src / generic / vscroll.cpp
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$
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 #include "wx/vscroll.h"
21
22 // ----------------------------------------------------------------------------
23 // event tables
24 // ----------------------------------------------------------------------------
25
26 BEGIN_EVENT_TABLE(wxVScrolledWindow, wxPanel)
27 EVT_SIZE(wxVScrolledWindow::OnSize)
28 EVT_SCROLLWIN(wxVScrolledWindow::OnScroll)
29 END_EVENT_TABLE()
30
31
32 // ============================================================================
33 // implementation
34 // ============================================================================
35
36 // ----------------------------------------------------------------------------
37 // initialization
38 // ----------------------------------------------------------------------------
39
40 void wxVScrolledWindow::Init()
41 {
42 // we're initially empty
43 m_lineMax =
44 m_lineFirst = 0;
45
46 // this one should always be strictly positive
47 m_nVisible = 1;
48
49 m_heightTotal = 0;
50 }
51
52 // ----------------------------------------------------------------------------
53 // various helpers
54 // ----------------------------------------------------------------------------
55
56 wxCoord wxVScrolledWindow::EstimateTotalHeight() const
57 {
58 // estimate the total height: it is impossible to call
59 // OnGetLineHeight() for every line because there may be too many of
60 // them, so we just make a guess using some lines in the beginning,
61 // some in the end and some in the middle
62 static const size_t NUM_LINES_TO_SAMPLE = 10;
63
64 wxCoord heightTotal;
65 if ( m_lineMax < 3*NUM_LINES_TO_SAMPLE )
66 {
67 // in this case calculating exactly is faster and more correct than
68 // guessing
69 heightTotal = GetLinesHeight(0, m_lineMax);
70 }
71 else // too many lines to calculate exactly
72 {
73 // look at some lines in the beginning/middle/end
74 heightTotal =
75 GetLinesHeight(0, NUM_LINES_TO_SAMPLE) +
76 GetLinesHeight(m_lineMax - NUM_LINES_TO_SAMPLE, m_lineMax) +
77 GetLinesHeight(m_lineMax/2 - NUM_LINES_TO_SAMPLE/2,
78 m_lineMax/2 + NUM_LINES_TO_SAMPLE/2);
79
80 // use the height of the lines we looked as the average
81 heightTotal = (wxCoord)
82 (((float)m_heightTotal / (3*NUM_LINES_TO_SAMPLE)) * m_lineMax);
83 }
84
85 return heightTotal;
86 }
87
88 wxCoord wxVScrolledWindow::GetLinesHeight(size_t lineMin, size_t lineMax) const
89 {
90 if ( lineMin == lineMax )
91 return 0;
92 else if ( lineMin > lineMax )
93 return -GetLinesHeight(lineMax, lineMin);
94 //else: lineMin < lineMax
95
96 // let the user code know that we're going to need all these lines
97 OnGetLinesHint(lineMin, lineMax);
98
99 // do sum up their heights
100 wxCoord height = 0;
101 for ( size_t line = lineMin; line < lineMax; line++ )
102 {
103 height += OnGetLineHeight(line);
104 }
105
106 return height;
107 }
108
109 size_t wxVScrolledWindow::FindFirstFromBottom(size_t lineLast, bool full)
110 {
111 const wxCoord hWindow = GetClientSize().y;
112
113 // go upwards until we arrive at a line such that lineLast is not visible
114 // any more when it is shown
115 size_t lineFirst = lineLast;
116 wxCoord h = 0;
117 for ( ;; )
118 {
119 h += OnGetLineHeight(lineFirst);
120
121 if ( h > hWindow )
122 {
123 // for this line to be fully visible we need to go one line
124 // down, but if it is enough for it to be only partly visible then
125 // this line will do as well
126 if ( full )
127 {
128 lineFirst++;
129 }
130
131 break;
132 }
133
134 if ( !lineFirst )
135 break;
136
137 lineFirst--;
138 }
139
140 return lineFirst;
141 }
142
143 void wxVScrolledWindow::UpdateScrollbar()
144 {
145 // see how many lines can we fit on screen
146 const wxCoord hWindow = GetClientSize().y;
147
148 wxCoord h = 0;
149 size_t line;
150 for ( line = m_lineFirst; line < m_lineMax; line++ )
151 {
152 if ( h > hWindow )
153 break;
154
155 h += OnGetLineHeight(line);
156 }
157
158 m_nVisible = line - m_lineFirst;
159
160 int pageSize = m_nVisible;
161 if ( h > hWindow )
162 {
163 // last line is only partially visible, we still need the scrollbar and
164 // so we have to "fix" pageSize because if it is equal to m_lineMax the
165 // scrollbar is not shown at all under MSW
166 pageSize--;
167 }
168
169 // set the scrollbar parameters to reflect this
170 SetScrollbar(wxVERTICAL, m_lineFirst, pageSize, m_lineMax);
171 }
172
173 // ----------------------------------------------------------------------------
174 // operations
175 // ----------------------------------------------------------------------------
176
177 void wxVScrolledWindow::SetLineCount(size_t count)
178 {
179 // save the number of lines
180 m_lineMax = count;
181
182 // and our estimate for their total height
183 m_heightTotal = EstimateTotalHeight();
184
185 // recalculate the scrollbars parameters
186 m_lineFirst = 1; // make sure it is != 0
187 ScrollToLine(0);
188 }
189
190 void wxVScrolledWindow::RefreshLine(size_t line)
191 {
192 // is this line visible?
193 if ( !IsVisible(line) )
194 {
195 // no, it is useless to do anything
196 return;
197 }
198
199 // calculate the rect occupied by this line on screen
200 wxRect rect;
201 rect.width = GetClientSize().x;
202 rect.height = OnGetLineHeight(line);
203 for ( size_t n = GetFirstVisibleLine(); n < line; n++ )
204 {
205 rect.y += OnGetLineHeight(n);
206 }
207
208 // do refresh it
209 RefreshRect(rect);
210 }
211
212 void wxVScrolledWindow::RefreshLines(size_t from, size_t to)
213 {
214 wxASSERT_MSG( from <= to, _T("RefreshLines(): empty range") );
215
216 // clump the range to just the visible lines -- it is useless to refresh
217 // the other ones
218 if ( from < GetFirstVisibleLine() )
219 from = GetFirstVisibleLine();
220
221 if ( to > GetLastVisibleLine() )
222 to = GetLastVisibleLine();
223
224 // calculate the rect occupied by these lines on screen
225 wxRect rect;
226 rect.width = GetClientSize().x;
227 for ( size_t nBefore = GetFirstVisibleLine(); nBefore < from; nBefore++ )
228 {
229 rect.y += OnGetLineHeight(nBefore);
230 }
231
232 for ( size_t nBetween = from; nBetween <= to; nBetween++ )
233 {
234 rect.height += OnGetLineHeight(nBetween);
235 }
236
237 // do refresh it
238 RefreshRect(rect);
239 }
240
241 void wxVScrolledWindow::RefreshAll()
242 {
243 UpdateScrollbar();
244
245 Refresh();
246 }
247
248 int wxVScrolledWindow::HitTest(wxCoord WXUNUSED(x), wxCoord y) const
249 {
250 const size_t lineMax = GetLastVisibleLine();
251 for ( size_t line = GetFirstVisibleLine(); line <= lineMax; line++ )
252 {
253 y -= OnGetLineHeight(line);
254 if ( y < 0 )
255 return line;
256 }
257
258 return wxNOT_FOUND;
259 }
260
261 // ----------------------------------------------------------------------------
262 // scrolling
263 // ----------------------------------------------------------------------------
264
265 bool wxVScrolledWindow::ScrollToLine(size_t line)
266 {
267 if ( !m_lineMax )
268 {
269 // we're empty, code below doesn't make sense in this case
270 return false;
271 }
272
273 // determine the real first line to scroll to: we shouldn't scroll beyond
274 // the end
275 size_t lineFirstLast = FindFirstFromBottom(m_lineMax - 1, true);
276 if ( line > lineFirstLast )
277 line = lineFirstLast;
278
279 // anything to do?
280 if ( line == m_lineFirst )
281 {
282 // no
283 return false;
284 }
285
286
287 // remember the currently shown lines for the refresh code below
288 size_t lineFirstOld = GetFirstVisibleLine(),
289 lineLastOld = GetLastVisibleLine();
290
291 m_lineFirst = line;
292
293
294 // the size of scrollbar thumb could have changed
295 UpdateScrollbar();
296
297
298 // finally refresh the display -- but only redraw as few lines as possible
299 // to avoid flicker
300 if ( GetFirstVisibleLine() > lineLastOld ||
301 GetLastVisibleLine() < lineFirstOld )
302 {
303 // the simplest case: we don't have any old lines left, just redraw
304 // everything
305 Refresh();
306 }
307 else // overlap between the lines we showed before and should show now
308 {
309 ScrollWindow(0, GetLinesHeight(GetFirstVisibleLine(), lineFirstOld));
310 }
311
312 return true;
313 }
314
315 bool wxVScrolledWindow::ScrollLines(int lines)
316 {
317 lines += m_lineFirst;
318 if ( lines < 0 )
319 lines = 0;
320
321 return ScrollToLine(lines);
322 }
323
324 bool wxVScrolledWindow::ScrollPages(int pages)
325 {
326 bool didSomething = false;
327
328 while ( pages )
329 {
330 int line;
331 if ( pages > 0 )
332 {
333 line = GetLastVisibleLine();
334 pages--;
335 }
336 else // pages < 0
337 {
338 line = FindFirstFromBottom(GetFirstVisibleLine());
339 pages++;
340 }
341
342 didSomething = ScrollToLine(line);
343 }
344
345 return didSomething;
346 }
347
348 // ----------------------------------------------------------------------------
349 // event handling
350 // ----------------------------------------------------------------------------
351
352 void wxVScrolledWindow::OnSize(wxSizeEvent& event)
353 {
354 UpdateScrollbar();
355
356 event.Skip();
357 }
358
359 void wxVScrolledWindow::OnScroll(wxScrollWinEvent& event)
360 {
361 size_t lineFirstNew;
362
363 const wxEventType evtType = event.GetEventType();
364 if ( evtType == wxEVT_SCROLLWIN_TOP )
365 {
366 lineFirstNew = 0;
367 }
368 else if ( evtType == wxEVT_SCROLLWIN_BOTTOM )
369 {
370 lineFirstNew = m_lineMax;
371 }
372 else if ( evtType == wxEVT_SCROLLWIN_LINEUP )
373 {
374 lineFirstNew = m_lineFirst ? m_lineFirst - 1 : 0;
375 }
376 else if ( evtType == wxEVT_SCROLLWIN_LINEDOWN )
377 {
378 lineFirstNew = m_lineFirst + 1;
379 }
380 else if ( evtType == wxEVT_SCROLLWIN_PAGEUP )
381 {
382 lineFirstNew = FindFirstFromBottom(m_lineFirst);
383 }
384 else if ( evtType == wxEVT_SCROLLWIN_PAGEDOWN )
385 {
386 lineFirstNew = GetLastVisibleLine();
387 }
388 else // unknown scroll event?
389 {
390 if ( evtType == wxEVT_SCROLLWIN_THUMBRELEASE )
391 {
392 lineFirstNew = event.GetPosition();
393 }
394 else
395 {
396 wxASSERT_MSG( evtType == wxEVT_SCROLLWIN_THUMBTRACK,
397 _T("unknown scroll event type?") );
398
399 // don't do anything, otherwise dragging the thumb around would
400 // be too slow
401 return;
402 }
403 }
404
405 ScrollToLine(lineFirstNew);
406
407 #ifdef __WXMAC__
408 Update();
409 #endif // __WXMAC__
410 }
411