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
37BEGIN_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
43END_EVENT_TABLE()
44
45
46// ============================================================================
47// implementation
48// ============================================================================
49
50IMPLEMENT_ABSTRACT_CLASS(wxVScrolledWindow, wxPanel)
51
52// ----------------------------------------------------------------------------
53// initialization
54// ----------------------------------------------------------------------------
55
56void 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
76wxCoord 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
108wxCoord 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
129size_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
163void wxVScrolledWindow::RemoveScrollbar()
164{
165    m_lineFirst = 0;
166    m_nVisible = m_lineMax;
167    SetScrollbar(wxVERTICAL, 0, 0, 0);
168}
169
170void 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
223void 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
243void 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
265void 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
296void wxVScrolledWindow::RefreshAll()
297{
298    UpdateScrollbar();
299
300    Refresh();
301}
302
303bool 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
324int 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
341bool 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
397bool wxVScrolledWindow::ScrollLines(int lines)
398{
399    lines += m_lineFirst;
400    if ( lines < 0 )
401        lines = 0;
402
403    return ScrollToLine(lines);
404}
405
406bool 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
436void wxVScrolledWindow::OnSize(wxSizeEvent& event)
437{
438    UpdateScrollbar();
439
440    event.Skip();
441}
442
443void 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
499void 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