fix window repainting when SetLineCount() is called (patch 1667599; closes bug 1639629)
[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 // 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
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
76 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
108 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
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
163 void wxVScrolledWindow::RemoveScrollbar()
164 {
165 m_lineFirst = 0;
166 m_nVisible = m_lineMax;
167 SetScrollbar(wxVERTICAL, 0, 0, 0);
168 }
169
170 void wxVScrolledWindow::UpdateScrollbar()
171 {
172 // if there is nothing to scroll, remove the scrollbar
173 if ( !m_lineMax )
174 {
175 RemoveScrollbar();
176 return;
177 }
178
179 // see how many lines can we fit on screen
180 const wxCoord hWindow = GetClientSize().y;
181
182 wxCoord h = 0;
183 size_t line;
184 for ( line = m_lineFirst; line < m_lineMax; line++ )
185 {
186 if ( h > hWindow )
187 break;
188
189 h += OnGetLineHeight(line);
190 }
191
192 // if we still have remaining space below, maybe we can fit everything?
193 if ( h < hWindow )
194 {
195 wxCoord hAll = h;
196 for ( size_t lineFirst = m_lineFirst; lineFirst > 0; lineFirst-- )
197 {
198 hAll += OnGetLineHeight(m_lineFirst - 1);
199 if ( hAll > hWindow )
200 break;
201 }
202
203 if ( hAll < hWindow )
204 {
205 // we don't need scrollbar at all
206 RemoveScrollbar();
207 return;
208 }
209 }
210
211 m_nVisible = line - m_lineFirst;
212
213 int pageSize = m_nVisible;
214 if ( h > hWindow )
215 {
216 // last line is only partially visible, we still need the scrollbar and
217 // so we have to "fix" pageSize because if it is equal to m_lineMax the
218 // scrollbar is not shown at all under MSW
219 pageSize--;
220 }
221
222 // set the scrollbar parameters to reflect this
223 SetScrollbar(wxVERTICAL, m_lineFirst, pageSize, m_lineMax);
224 }
225
226 // ----------------------------------------------------------------------------
227 // operations
228 // ----------------------------------------------------------------------------
229
230 void wxVScrolledWindow::SetLineCount(size_t count)
231 {
232 // save the number of lines
233 m_lineMax = count;
234
235 // and our estimate for their total height
236 m_heightTotal = EstimateTotalHeight();
237
238 // ScrollToLine() will update the scrollbar itself if it changes the line
239 // we pass to it because it's out of [new] range
240 size_t oldScrollPos = m_lineFirst;
241 ScrollToLine(m_lineFirst);
242 if ( oldScrollPos == m_lineFirst )
243 {
244 // but if it didn't do it, we still need to update the scrollbar to
245 // reflect the changed number of lines ourselves
246 UpdateScrollbar();
247 }
248 }
249
250 void wxVScrolledWindow::RefreshLine(size_t line)
251 {
252 // is this line visible?
253 if ( !IsVisible(line) )
254 {
255 // no, it is useless to do anything
256 return;
257 }
258
259 // calculate the rect occupied by this line on screen
260 wxRect rect;
261 rect.width = GetClientSize().x;
262 rect.height = OnGetLineHeight(line);
263 for ( size_t n = GetVisibleBegin(); n < line; n++ )
264 {
265 rect.y += OnGetLineHeight(n);
266 }
267
268 // do refresh it
269 RefreshRect(rect);
270 }
271
272 void wxVScrolledWindow::RefreshLines(size_t from, size_t to)
273 {
274 wxASSERT_MSG( from <= to, _T("RefreshLines(): empty range") );
275
276 // clump the range to just the visible lines -- it is useless to refresh
277 // the other ones
278 if ( from < GetVisibleBegin() )
279 from = GetVisibleBegin();
280
281 if ( to >= GetVisibleEnd() )
282 to = GetVisibleEnd();
283 else
284 to++;
285
286 // calculate the rect occupied by these lines on screen
287 wxRect rect;
288 rect.width = GetClientSize().x;
289 for ( size_t nBefore = GetVisibleBegin(); nBefore < from; nBefore++ )
290 {
291 rect.y += OnGetLineHeight(nBefore);
292 }
293
294 for ( size_t nBetween = from; nBetween < to; nBetween++ )
295 {
296 rect.height += OnGetLineHeight(nBetween);
297 }
298
299 // do refresh it
300 RefreshRect(rect);
301 }
302
303 void wxVScrolledWindow::RefreshAll()
304 {
305 UpdateScrollbar();
306
307 Refresh();
308 }
309
310 bool wxVScrolledWindow::Layout()
311 {
312 if ( GetSizer() )
313 {
314 // adjust the sizer dimensions/position taking into account the
315 // virtual size and scrolled position of the window.
316
317 int w = 0, h = 0;
318 GetVirtualSize(&w, &h);
319
320 // x is always 0 so no variable needed
321 int y = -GetLinesHeight(0, GetFirstVisibleLine());
322
323 GetSizer()->SetDimension(0, y, w, h);
324 return true;
325 }
326
327 // fall back to default for LayoutConstraints
328 return wxPanel::Layout();
329 }
330
331 int wxVScrolledWindow::HitTest(wxCoord WXUNUSED(x), wxCoord y) const
332 {
333 const size_t lineMax = GetVisibleEnd();
334 for ( size_t line = GetVisibleBegin(); line < lineMax; line++ )
335 {
336 y -= OnGetLineHeight(line);
337 if ( y < 0 )
338 return line;
339 }
340
341 return wxNOT_FOUND;
342 }
343
344 // ----------------------------------------------------------------------------
345 // scrolling
346 // ----------------------------------------------------------------------------
347
348 bool wxVScrolledWindow::ScrollToLine(size_t line)
349 {
350 if ( !m_lineMax )
351 {
352 // we're empty, code below doesn't make sense in this case
353 return false;
354 }
355
356 // determine the real first line to scroll to: we shouldn't scroll beyond
357 // the end
358 size_t lineFirstLast = FindFirstFromBottom(m_lineMax - 1, true);
359 if ( line > lineFirstLast )
360 line = lineFirstLast;
361
362 // anything to do?
363 if ( line == m_lineFirst )
364 {
365 // no
366 return false;
367 }
368
369
370 // remember the currently shown lines for the refresh code below
371 size_t lineFirstOld = GetVisibleBegin(),
372 lineLastOld = GetVisibleEnd();
373
374 m_lineFirst = line;
375
376
377 // the size of scrollbar thumb could have changed
378 UpdateScrollbar();
379
380
381 // finally refresh the display -- but only redraw as few lines as possible
382 // to avoid flicker
383 if ( GetVisibleBegin() >= lineLastOld ||
384 GetVisibleEnd() <= lineFirstOld )
385 {
386 // the simplest case: we don't have any old lines left, just redraw
387 // everything
388 Refresh();
389 }
390 else // overlap between the lines we showed before and should show now
391 {
392 ScrollWindow(0, GetLinesHeight(GetVisibleBegin(), lineFirstOld));
393 }
394
395 return true;
396 }
397
398 bool wxVScrolledWindow::ScrollLines(int lines)
399 {
400 lines += m_lineFirst;
401 if ( lines < 0 )
402 lines = 0;
403
404 return ScrollToLine(lines);
405 }
406
407 bool wxVScrolledWindow::ScrollPages(int pages)
408 {
409 bool didSomething = false;
410
411 while ( pages )
412 {
413 int line;
414 if ( pages > 0 )
415 {
416 line = GetVisibleEnd();
417 if ( line )
418 line--;
419 pages--;
420 }
421 else // pages < 0
422 {
423 line = FindFirstFromBottom(GetVisibleBegin());
424 pages++;
425 }
426
427 didSomething = ScrollToLine(line);
428 }
429
430 return didSomething;
431 }
432
433 // ----------------------------------------------------------------------------
434 // event handling
435 // ----------------------------------------------------------------------------
436
437 void wxVScrolledWindow::OnSize(wxSizeEvent& event)
438 {
439 UpdateScrollbar();
440
441 event.Skip();
442 }
443
444 void wxVScrolledWindow::OnScroll(wxScrollWinEvent& event)
445 {
446 size_t lineFirstNew;
447
448 const wxEventType evtType = event.GetEventType();
449
450 if ( evtType == wxEVT_SCROLLWIN_TOP )
451 {
452 lineFirstNew = 0;
453 }
454 else if ( evtType == wxEVT_SCROLLWIN_BOTTOM )
455 {
456 lineFirstNew = m_lineMax;
457 }
458 else if ( evtType == wxEVT_SCROLLWIN_LINEUP )
459 {
460 lineFirstNew = m_lineFirst ? m_lineFirst - 1 : 0;
461 }
462 else if ( evtType == wxEVT_SCROLLWIN_LINEDOWN )
463 {
464 lineFirstNew = m_lineFirst + 1;
465 }
466 else if ( evtType == wxEVT_SCROLLWIN_PAGEUP )
467 {
468 lineFirstNew = FindFirstFromBottom(m_lineFirst);
469 }
470 else if ( evtType == wxEVT_SCROLLWIN_PAGEDOWN )
471 {
472 lineFirstNew = GetVisibleEnd();
473 if ( lineFirstNew )
474 lineFirstNew--;
475 }
476 else if ( evtType == wxEVT_SCROLLWIN_THUMBRELEASE )
477 {
478 lineFirstNew = event.GetPosition();
479 }
480 else if ( evtType == wxEVT_SCROLLWIN_THUMBTRACK )
481 {
482 lineFirstNew = event.GetPosition();
483 }
484
485 else // unknown scroll event?
486 {
487 wxFAIL_MSG( _T("unknown scroll event type?") );
488 return;
489 }
490
491 ScrollToLine(lineFirstNew);
492
493 #ifdef __WXMAC__
494 Update();
495 #endif // __WXMAC__
496 }
497
498 #if wxUSE_MOUSEWHEEL
499
500 void wxVScrolledWindow::OnMouseWheel(wxMouseEvent& event)
501 {
502 m_sumWheelRotation += event.GetWheelRotation();
503 int delta = event.GetWheelDelta();
504
505 // how much to scroll this time
506 int units_to_scroll = -(m_sumWheelRotation/delta);
507 if ( !units_to_scroll )
508 return;
509
510 m_sumWheelRotation += units_to_scroll*delta;
511
512 if ( !event.IsPageScroll() )
513 ScrollLines( units_to_scroll*event.GetLinesPerAction() );
514 else
515 // scroll pages instead of lines
516 ScrollPages( units_to_scroll );
517 }
518
519 #endif // wxUSE_MOUSEWHEEL
520