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