1///////////////////////////////////////////////////////////////////////////// 2// Name: src/univ/listbox.cpp 3// Purpose: wxListBox implementation 4// Author: Vadim Zeitlin 5// Modified by: 6// Created: 30.08.00 7// RCS-ID: $Id: listbox.cpp 42816 2006-10-31 08:50:17Z RD $ 8// Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com) 9// Licence: wxWindows licence 10///////////////////////////////////////////////////////////////////////////// 11 12// ============================================================================ 13// declarations 14// ============================================================================ 15 16// ---------------------------------------------------------------------------- 17// headers 18// ---------------------------------------------------------------------------- 19 20#include "wx/wxprec.h" 21 22#ifdef __BORLANDC__ 23 #pragma hdrstop 24#endif 25 26#if wxUSE_LISTBOX 27 28#ifndef WX_PRECOMP 29 #include "wx/log.h" 30 31 #include "wx/dcclient.h" 32 #include "wx/listbox.h" 33 #include "wx/validate.h" 34#endif 35 36#include "wx/univ/renderer.h" 37#include "wx/univ/inphand.h" 38#include "wx/univ/theme.h" 39 40// ---------------------------------------------------------------------------- 41// wxStdListboxInputHandler: handles mouse and kbd in a single or multi 42// selection listbox 43// ---------------------------------------------------------------------------- 44 45class WXDLLEXPORT wxStdListboxInputHandler : public wxStdInputHandler 46{ 47public: 48 // if pressing the mouse button in a multiselection listbox should toggle 49 // the item under mouse immediately, then specify true as the second 50 // parameter (this is the standard behaviour, under GTK the item is toggled 51 // only when the mouse is released in the multi selection listbox) 52 wxStdListboxInputHandler(wxInputHandler *inphand, 53 bool toggleOnPressAlways = true); 54 55 // base class methods 56 virtual bool HandleKey(wxInputConsumer *consumer, 57 const wxKeyEvent& event, 58 bool pressed); 59 virtual bool HandleMouse(wxInputConsumer *consumer, 60 const wxMouseEvent& event); 61 virtual bool HandleMouseMove(wxInputConsumer *consumer, 62 const wxMouseEvent& event); 63 64protected: 65 // return the item under mouse, 0 if the mouse is above the listbox or 66 // GetCount() if it is below it 67 int HitTest(const wxListBox *listbox, const wxMouseEvent& event); 68 69 // parts of HitTest(): first finds the pseudo (because not in range) index 70 // of the item and the second one adjusts it if necessary - that is if the 71 // third one returns false 72 int HitTestUnsafe(const wxListBox *listbox, const wxMouseEvent& event); 73 int FixItemIndex(const wxListBox *listbox, int item); 74 bool IsValidIndex(const wxListBox *listbox, int item); 75 76 // init m_btnCapture and m_actionMouse 77 wxControlAction SetupCapture(wxListBox *lbox, 78 const wxMouseEvent& event, 79 int item); 80 81 wxRenderer *m_renderer; 82 83 // the button which initiated the mouse capture (currently 0 or 1) 84 int m_btnCapture; 85 86 // the action to perform when the mouse moves while we capture it 87 wxControlAction m_actionMouse; 88 89 // the ctor parameter toggleOnPressAlways (see comments near it) 90 bool m_toggleOnPressAlways; 91 92 // do we track the mouse outside the window when it is captured? 93 bool m_trackMouseOutside; 94}; 95 96// ============================================================================ 97// implementation of wxListBox 98// ============================================================================ 99 100IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControl) 101 102BEGIN_EVENT_TABLE(wxListBox, wxListBoxBase) 103 EVT_SIZE(wxListBox::OnSize) 104END_EVENT_TABLE() 105 106// ---------------------------------------------------------------------------- 107// construction 108// ---------------------------------------------------------------------------- 109 110void wxListBox::Init() 111{ 112 // will be calculated later when needed 113 m_lineHeight = 0; 114 m_itemsPerPage = 0; 115 m_maxWidth = 0; 116 m_scrollRangeY = 0; 117 m_maxWidthItem = -1; 118 m_strings = NULL; 119 120 // no items hence no current item 121 m_current = -1; 122 m_selAnchor = -1; 123 m_currentChanged = false; 124 125 // no need to update anything initially 126 m_updateCount = 0; 127 128 // no scrollbars to show nor update 129 m_updateScrollbarX = 130 m_showScrollbarX = 131 m_updateScrollbarY = 132 m_showScrollbarY = false; 133} 134 135wxListBox::wxListBox(wxWindow *parent, 136 wxWindowID id, 137 const wxPoint &pos, 138 const wxSize &size, 139 const wxArrayString& choices, 140 long style, 141 const wxValidator& validator, 142 const wxString &name) 143 :wxScrollHelper(this) 144{ 145 Init(); 146 147 Create(parent, id, pos, size, choices, style, validator, name); 148} 149 150bool wxListBox::Create(wxWindow *parent, 151 wxWindowID id, 152 const wxPoint &pos, 153 const wxSize &size, 154 const wxArrayString& choices, 155 long style, 156 const wxValidator& validator, 157 const wxString &name) 158{ 159 wxCArrayString chs(choices); 160 161 return Create(parent, id, pos, size, chs.GetCount(), chs.GetStrings(), 162 style, validator, name); 163} 164 165bool wxListBox::Create(wxWindow *parent, 166 wxWindowID id, 167 const wxPoint &pos, 168 const wxSize &size, 169 int n, 170 const wxString choices[], 171 long style, 172 const wxValidator& validator, 173 const wxString &name) 174{ 175 // for compatibility accept both the new and old styles - they mean the 176 // same thing for us 177 if ( style & wxLB_ALWAYS_SB ) 178 style |= wxALWAYS_SHOW_SB; 179 180 // if we don't have neither multiple nor extended flag, we must have the 181 // single selection listbox 182 if ( !(style & (wxLB_MULTIPLE | wxLB_EXTENDED)) ) 183 style |= wxLB_SINGLE; 184 185#if wxUSE_TWO_WINDOWS 186 style |= wxVSCROLL|wxHSCROLL; 187 if ((style & wxBORDER_MASK) == 0) 188 style |= wxBORDER_SUNKEN; 189#endif 190 191 if ( !wxControl::Create(parent, id, pos, size, style, 192 validator, name) ) 193 return false; 194 195 m_strings = new wxArrayString; 196 197 Set(n, choices); 198 199 SetInitialSize(size); 200 201 CreateInputHandler(wxINP_HANDLER_LISTBOX); 202 203 return true; 204} 205 206wxListBox::~wxListBox() 207{ 208 // call this just to free the client data -- and avoid leaking memory 209 DoClear(); 210 211 delete m_strings; 212 213 m_strings = NULL; 214} 215 216// ---------------------------------------------------------------------------- 217// adding/inserting strings 218// ---------------------------------------------------------------------------- 219 220int wxCMPFUNC_CONV wxListBoxSortNoCase(wxString* s1, wxString* s2) 221{ 222 return s1->CmpNoCase(*s2); 223} 224 225int wxListBox::DoAppendOnly(const wxString& item) 226{ 227 unsigned int index; 228 229 if ( IsSorted() ) 230 { 231 m_strings->Add(item); 232 m_strings->Sort(wxListBoxSortNoCase); 233 index = m_strings->Index(item); 234 } 235 else 236 { 237 index = m_strings->GetCount(); 238 m_strings->Add(item); 239 } 240 241 return index; 242} 243 244int wxListBox::DoAppend(const wxString& item) 245{ 246 size_t index = DoAppendOnly( item ); 247 248 m_itemsClientData.Insert(NULL, index); 249 250 m_updateScrollbarY = true; 251 252 if ( HasHorzScrollbar() ) 253 { 254 // has the max width increased? 255 wxCoord width; 256 GetTextExtent(item, &width, NULL); 257 if ( width > m_maxWidth ) 258 { 259 m_maxWidth = width; 260 m_maxWidthItem = index; 261 m_updateScrollbarX = true; 262 } 263 } 264 265 RefreshFromItemToEnd(index); 266 267 return index; 268} 269 270void wxListBox::DoInsertItems(const wxArrayString& items, unsigned int pos) 271{ 272 // the position of the item being added to a sorted listbox can't be 273 // specified 274 wxCHECK_RET( !IsSorted(), _T("can't insert items into sorted listbox") ); 275 276 unsigned int count = items.GetCount(); 277 for ( unsigned int n = 0; n < count; n++ ) 278 { 279 m_strings->Insert(items[n], pos + n); 280 m_itemsClientData.Insert(NULL, pos + n); 281 } 282 283 // the number of items has changed so we might have to show the scrollbar 284 m_updateScrollbarY = true; 285 286 // the max width also might have changed - just recalculate it instead of 287 // keeping track of it here, this is probably more efficient for a typical 288 // use pattern 289 RefreshHorzScrollbar(); 290 291 // note that we have to refresh all the items after the ones we inserted, 292 // not just these items 293 RefreshFromItemToEnd(pos); 294} 295 296void wxListBox::DoSetItems(const wxArrayString& items, void **clientData) 297{ 298 DoClear(); 299 300 unsigned int count = items.GetCount(); 301 if ( !count ) 302 return; 303 304 m_strings->Alloc(count); 305 306 m_itemsClientData.Alloc(count); 307 for ( unsigned int n = 0; n < count; n++ ) 308 { 309 unsigned int index = DoAppendOnly(items[n]); 310 311 m_itemsClientData.Insert(clientData ? clientData[n] : NULL, index); 312 } 313 314 m_updateScrollbarY = true; 315 316 RefreshAll(); 317} 318 319void wxListBox::SetString(unsigned int n, const wxString& s) 320{ 321 wxCHECK_RET( !IsSorted(), _T("can't set string in sorted listbox") ); 322 323 (*m_strings)[n] = s; 324 325 if ( HasHorzScrollbar() ) 326 { 327 // we need to update m_maxWidth as changing the string may cause the 328 // horz scrollbar [dis]appear 329 wxCoord width; 330 331 GetTextExtent(s, &width, NULL); 332 333 // it might have increased if the new string is long 334 if ( width > m_maxWidth ) 335 { 336 m_maxWidth = width; 337 m_maxWidthItem = n; 338 m_updateScrollbarX = true; 339 } 340 // or also decreased if the old string was the longest one 341 else if ( n == (unsigned int)m_maxWidthItem ) 342 { 343 RefreshHorzScrollbar(); 344 } 345 } 346 347 RefreshItem(n); 348} 349 350// ---------------------------------------------------------------------------- 351// removing strings 352// ---------------------------------------------------------------------------- 353 354void wxListBox::DoClear() 355{ 356 m_strings->Clear(); 357 358 if ( HasClientObjectData() ) 359 { 360 unsigned int count = m_itemsClientData.GetCount(); 361 for ( unsigned int n = 0; n < count; n++ ) 362 { 363 delete (wxClientData *) m_itemsClientData[n]; 364 } 365 } 366 367 m_itemsClientData.Clear(); 368 m_selections.Clear(); 369 370 m_current = -1; 371} 372 373void wxListBox::Clear() 374{ 375 DoClear(); 376 377 m_updateScrollbarY = true; 378 379 RefreshHorzScrollbar(); 380 381 RefreshAll(); 382} 383 384void wxListBox::Delete(unsigned int n) 385{ 386 wxCHECK_RET( IsValid(n), 387 _T("invalid index in wxListBox::Delete") ); 388 389 // do it before removing the index as otherwise the last item will not be 390 // refreshed (as GetCount() will be decremented) 391 RefreshFromItemToEnd(n); 392 393 m_strings->RemoveAt(n); 394 395 if ( HasClientObjectData() ) 396 { 397 delete (wxClientData *)m_itemsClientData[n]; 398 } 399 400 m_itemsClientData.RemoveAt(n); 401 402 // when the item disappears we must not keep using its index 403 if ( (int)n == m_current ) 404 { 405 m_current = -1; 406 } 407 else if ( (int)n < m_current ) 408 { 409 m_current--; 410 } 411 //else: current item may stay 412 413 // update the selections array: the indices of all seletected items after 414 // the one being deleted must change and the item itselfm ust be removed 415 int index = wxNOT_FOUND; 416 unsigned int count = m_selections.GetCount(); 417 for ( unsigned int item = 0; item < count; item++ ) 418 { 419 if ( m_selections[item] == (int)n ) 420 { 421 // remember to delete it later 422 index = item; 423 } 424 else if ( m_selections[item] > (int)n ) 425 { 426 // to account for the index shift 427 m_selections[item]--; 428 } 429 //else: nothing changed for this one 430 } 431 432 if ( index != wxNOT_FOUND ) 433 { 434 m_selections.RemoveAt(index); 435 } 436 437 // the number of items has changed, hence the scrollbar may disappear 438 m_updateScrollbarY = true; 439 440 // finally, if the longest item was deleted the scrollbar may disappear 441 if ( (int)n == m_maxWidthItem ) 442 { 443 RefreshHorzScrollbar(); 444 } 445} 446 447// ---------------------------------------------------------------------------- 448// client data handling 449// ---------------------------------------------------------------------------- 450 451void wxListBox::DoSetItemClientData(unsigned int n, void* clientData) 452{ 453 m_itemsClientData[n] = clientData; 454} 455 456void *wxListBox::DoGetItemClientData(unsigned int n) const 457{ 458 return m_itemsClientData[n]; 459} 460 461void wxListBox::DoSetItemClientObject(unsigned int n, wxClientData* clientData) 462{ 463 m_itemsClientData[n] = clientData; 464} 465 466wxClientData* wxListBox::DoGetItemClientObject(unsigned int n) const 467{ 468 return (wxClientData *)m_itemsClientData[n]; 469} 470 471// ---------------------------------------------------------------------------- 472// selection 473// ---------------------------------------------------------------------------- 474 475void wxListBox::DoSetSelection(int n, bool select) 476{ 477 if ( select ) 478 { 479 if ( n == wxNOT_FOUND ) 480 { 481 if ( !HasMultipleSelection() ) 482 { 483 // selecting wxNOT_FOUND is documented to deselect all items 484 DeselectAll(); 485 return; 486 } 487 } 488 else if ( m_selections.Index(n) == wxNOT_FOUND ) 489 { 490 if ( !HasMultipleSelection() ) 491 { 492 // selecting an item in a single selection listbox deselects 493 // all the others 494 DeselectAll(); 495 } 496 497 m_selections.Add(n); 498 499 RefreshItem(n); 500 } 501 //else: already selected 502 } 503 else // unselect 504 { 505 int index = m_selections.Index(n); 506 if ( index != wxNOT_FOUND ) 507 { 508 m_selections.RemoveAt(index); 509 510 RefreshItem(n); 511 } 512 //else: not selected 513 } 514 515 // sanity check: a single selection listbox can't have more than one item 516 // selected 517 wxASSERT_MSG( HasMultipleSelection() || (m_selections.GetCount() < 2), 518 _T("multiple selected items in single selection lbox?") ); 519 520 if ( select ) 521 { 522 // the newly selected item becomes the current one 523 SetCurrentItem(n); 524 } 525} 526 527int wxListBox::GetSelection() const 528{ 529 wxCHECK_MSG( !HasMultipleSelection(), wxNOT_FOUND, 530 _T("use wxListBox::GetSelections for ths listbox") ); 531 532 return m_selections.IsEmpty() ? wxNOT_FOUND : m_selections[0]; 533} 534 535int wxCMPFUNC_CONV wxCompareInts(int *n, int *m) 536{ 537 return *n - *m; 538} 539 540int wxListBox::GetSelections(wxArrayInt& selections) const 541{ 542 // always return sorted array to the user 543 selections = m_selections; 544 unsigned int count = m_selections.GetCount(); 545 546 // don't call sort on an empty array 547 if ( count ) 548 { 549 selections.Sort(wxCompareInts); 550 } 551 552 return count; 553} 554 555// ---------------------------------------------------------------------------- 556// refresh logic: we use delayed refreshing which allows to avoid multiple 557// refreshes (and hence flicker) in case when several listbox items are 558// added/deleted/changed subsequently 559// ---------------------------------------------------------------------------- 560 561void wxListBox::RefreshFromItemToEnd(int from) 562{ 563 RefreshItems(from, GetCount() - from); 564} 565 566void wxListBox::RefreshItems(int from, int count) 567{ 568 switch ( m_updateCount ) 569 { 570 case 0: 571 m_updateFrom = from; 572 m_updateCount = count; 573 break; 574 575 case -1: 576 // we refresh everything anyhow 577 break; 578 579 default: 580 // add these items to the others which we have to refresh 581 if ( m_updateFrom < from ) 582 { 583 count += from - m_updateFrom; 584 if ( m_updateCount < count ) 585 m_updateCount = count; 586 } 587 else // m_updateFrom >= from 588 { 589 int updateLast = wxMax(m_updateFrom + m_updateCount, 590 from + count); 591 m_updateFrom = from; 592 m_updateCount = updateLast - m_updateFrom; 593 } 594 } 595} 596 597void wxListBox::RefreshItem(int n) 598{ 599 switch ( m_updateCount ) 600 { 601 case 0: 602 // refresh this item only 603 m_updateFrom = n; 604 m_updateCount = 1; 605 break; 606 607 case -1: 608 // we refresh everything anyhow 609 break; 610 611 default: 612 // add this item to the others which we have to refresh 613 if ( m_updateFrom < n ) 614 { 615 if ( m_updateCount < n - m_updateFrom + 1 ) 616 m_updateCount = n - m_updateFrom + 1; 617 } 618 else // n <= m_updateFrom 619 { 620 m_updateCount += m_updateFrom - n; 621 m_updateFrom = n; 622 } 623 } 624} 625 626void wxListBox::RefreshAll() 627{ 628 m_updateCount = -1; 629} 630 631void wxListBox::RefreshHorzScrollbar() 632{ 633 m_maxWidth = 0; // recalculate it 634 m_updateScrollbarX = true; 635} 636 637void wxListBox::UpdateScrollbars() 638{ 639 wxSize size = GetClientSize(); 640 641 // is our height enough to show all items? 642 unsigned int nLines = GetCount(); 643 wxCoord lineHeight = GetLineHeight(); 644 bool showScrollbarY = (int)nLines*lineHeight > size.y; 645 646 // check the width too if required 647 wxCoord charWidth, maxWidth; 648 bool showScrollbarX; 649 if ( HasHorzScrollbar() ) 650 { 651 charWidth = GetCharWidth(); 652 maxWidth = GetMaxWidth(); 653 showScrollbarX = maxWidth > size.x; 654 } 655 else // never show it 656 { 657 charWidth = maxWidth = 0; 658 showScrollbarX = false; 659 } 660 661 // what should be the scrollbar range now? 662 int scrollRangeX = showScrollbarX 663 ? (maxWidth + charWidth - 1) / charWidth + 2 // FIXME 664 : 0; 665 int scrollRangeY = showScrollbarY 666 ? nLines + 667 (size.y % lineHeight + lineHeight - 1) / lineHeight 668 : 0; 669 670 // reset scrollbars if something changed: either the visibility status 671 // or the range of a scrollbar which is shown 672 if ( (showScrollbarY != m_showScrollbarY) || 673 (showScrollbarX != m_showScrollbarX) || 674 (showScrollbarY && (scrollRangeY != m_scrollRangeY)) || 675 (showScrollbarX && (scrollRangeX != m_scrollRangeX)) ) 676 { 677 int x, y; 678 GetViewStart(&x, &y); 679 SetScrollbars(charWidth, lineHeight, 680 scrollRangeX, scrollRangeY, 681 x, y); 682 683 m_showScrollbarX = showScrollbarX; 684 m_showScrollbarY = showScrollbarY; 685 686 m_scrollRangeX = scrollRangeX; 687 m_scrollRangeY = scrollRangeY; 688 } 689} 690 691void wxListBox::UpdateItems() 692{ 693 // only refresh the items which must be refreshed 694 if ( m_updateCount == -1 ) 695 { 696 // refresh all 697 wxLogTrace(_T("listbox"), _T("Refreshing all")); 698 699 Refresh(); 700 } 701 else 702 { 703 wxSize size = GetClientSize(); 704 wxRect rect; 705 rect.width = size.x; 706 rect.height = size.y; 707 rect.y += m_updateFrom*GetLineHeight(); 708 rect.height = m_updateCount*GetLineHeight(); 709 710 // we don't need to calculate x position as we always refresh the 711 // entire line(s) 712 CalcScrolledPosition(0, rect.y, NULL, &rect.y); 713 714 wxLogTrace(_T("listbox"), _T("Refreshing items %d..%d (%d-%d)"), 715 m_updateFrom, m_updateFrom + m_updateCount - 1, 716 rect.GetTop(), rect.GetBottom()); 717 718 Refresh(true, &rect); 719 } 720} 721 722void wxListBox::OnInternalIdle() 723{ 724 if ( m_updateScrollbarY || m_updateScrollbarX ) 725 { 726 UpdateScrollbars(); 727 728 m_updateScrollbarX = 729 m_updateScrollbarY = false; 730 } 731 732 if ( m_currentChanged ) 733 { 734 DoEnsureVisible(m_current); 735 736 m_currentChanged = false; 737 } 738 739 if ( m_updateCount ) 740 { 741 UpdateItems(); 742 743 m_updateCount = 0; 744 } 745 wxListBoxBase::OnInternalIdle(); 746} 747 748// ---------------------------------------------------------------------------- 749// drawing 750// ---------------------------------------------------------------------------- 751 752wxBorder wxListBox::GetDefaultBorder() const 753{ 754 return wxBORDER_SUNKEN; 755} 756 757void wxListBox::DoDraw(wxControlRenderer *renderer) 758{ 759 // adjust the DC to account for scrolling 760 wxDC& dc = renderer->GetDC(); 761 PrepareDC(dc); 762 dc.SetFont(GetFont()); 763 764 // get the update rect 765 wxRect rectUpdate = GetUpdateClientRect(); 766 767 int yTop, yBottom; 768 CalcUnscrolledPosition(0, rectUpdate.GetTop(), NULL, &yTop); 769 CalcUnscrolledPosition(0, rectUpdate.GetBottom(), NULL, &yBottom); 770 771 // get the items which must be redrawn 772 wxCoord lineHeight = GetLineHeight(); 773 unsigned int itemFirst = yTop / lineHeight, 774 itemLast = (yBottom + lineHeight - 1) / lineHeight, 775 itemMax = m_strings->GetCount(); 776 777 if ( itemFirst >= itemMax ) 778 return; 779 780 if ( itemLast > itemMax ) 781 itemLast = itemMax; 782 783 // do draw them 784 wxLogTrace(_T("listbox"), _T("Repainting items %d..%d"), 785 itemFirst, itemLast); 786 787 DoDrawRange(renderer, itemFirst, itemLast); 788} 789 790void wxListBox::DoDrawRange(wxControlRenderer *renderer, 791 int itemFirst, int itemLast) 792{ 793 renderer->DrawItems(this, itemFirst, itemLast); 794} 795 796// ---------------------------------------------------------------------------- 797// size calculations 798// ---------------------------------------------------------------------------- 799 800bool wxListBox::SetFont(const wxFont& font) 801{ 802 if ( !wxControl::SetFont(font) ) 803 return false; 804 805 CalcItemsPerPage(); 806 807 RefreshAll(); 808 809 return true; 810} 811 812void wxListBox::CalcItemsPerPage() 813{ 814 m_lineHeight = GetRenderer()->GetListboxItemHeight(GetCharHeight()); 815 m_itemsPerPage = GetClientSize().y / m_lineHeight; 816} 817 818int wxListBox::GetItemsPerPage() const 819{ 820 if ( !m_itemsPerPage ) 821 { 822 wxConstCast(this, wxListBox)->CalcItemsPerPage(); 823 } 824 825 return m_itemsPerPage; 826} 827 828wxCoord wxListBox::GetLineHeight() const 829{ 830 if ( !m_lineHeight ) 831 { 832 wxConstCast(this, wxListBox)->CalcItemsPerPage(); 833 } 834 835 return m_lineHeight; 836} 837 838wxCoord wxListBox::GetMaxWidth() const 839{ 840 if ( m_maxWidth == 0 ) 841 { 842 wxListBox *self = wxConstCast(this, wxListBox); 843 wxCoord width; 844 unsigned int count = m_strings->GetCount(); 845 for ( unsigned int n = 0; n < count; n++ ) 846 { 847 GetTextExtent(this->GetString(n), &width, NULL); 848 if ( width > m_maxWidth ) 849 { 850 self->m_maxWidth = width; 851 self->m_maxWidthItem = n; 852 } 853 } 854 } 855 856 return m_maxWidth; 857} 858 859void wxListBox::OnSize(wxSizeEvent& event) 860{ 861 // recalculate the number of items per page 862 CalcItemsPerPage(); 863 864 // the scrollbars might [dis]appear 865 m_updateScrollbarX = 866 m_updateScrollbarY = true; 867 868 event.Skip(); 869} 870 871void wxListBox::DoSetFirstItem(int n) 872{ 873 SetCurrentItem(n); 874} 875 876void wxListBox::DoSetSize(int x, int y, 877 int width, int height, 878 int sizeFlags) 879{ 880 if ( GetWindowStyle() & wxLB_INT_HEIGHT ) 881 { 882 // we must round up the height to an entire number of rows 883 884 // the client area must contain an int number of rows, so take borders 885 // into account 886 wxRect rectBorders = GetRenderer()->GetBorderDimensions(GetBorder()); 887 wxCoord hBorders = rectBorders.y + rectBorders.height; 888 889 wxCoord hLine = GetLineHeight(); 890 height = ((height - hBorders + hLine - 1) / hLine)*hLine + hBorders; 891 } 892 893 wxListBoxBase::DoSetSize(x, y, width, height, sizeFlags); 894} 895 896wxSize wxListBox::DoGetBestClientSize() const 897{ 898 wxCoord width = 0, 899 height = 0; 900 901 unsigned int count = m_strings->GetCount(); 902 for ( unsigned int n = 0; n < count; n++ ) 903 { 904 wxCoord w,h; 905 GetTextExtent(this->GetString(n), &w, &h); 906 907 if ( w > width ) 908 width = w; 909 if ( h > height ) 910 height = h; 911 } 912 913 // if the listbox is empty, still give it some non zero (even if 914 // arbitrary) size - otherwise, leave small margin around the strings 915 if ( !width ) 916 width = 100; 917 else 918 width += 3*GetCharWidth(); 919 920 if ( !height ) 921 height = GetCharHeight(); 922 923 // we need the height of the entire listbox, not just of one line 924 height *= wxMax(count, 7); 925 926 return wxSize(width, height); 927} 928 929// ---------------------------------------------------------------------------- 930// listbox actions 931// ---------------------------------------------------------------------------- 932 933bool wxListBox::SendEvent(wxEventType type, int item) 934{ 935 wxCommandEvent event(type, m_windowId); 936 event.SetEventObject(this); 937 938 // use the current item by default 939 if ( item == -1 ) 940 { 941 item = m_current; 942 } 943 944 // client data and string parameters only make sense if we have an item 945 if ( item != -1 ) 946 { 947 if ( HasClientObjectData() ) 948 event.SetClientObject(GetClientObject(item)); 949 else if ( HasClientUntypedData() ) 950 event.SetClientData(GetClientData(item)); 951 952 event.SetString(GetString(item)); 953 } 954 955 event.SetInt(item); 956 957 return GetEventHandler()->ProcessEvent(event); 958} 959 960void wxListBox::SetCurrentItem(int n) 961{ 962 if ( n != m_current ) 963 { 964 if ( m_current != -1 ) 965 RefreshItem(m_current); 966 967 m_current = n; 968 969 if ( m_current != -1 ) 970 { 971 m_currentChanged = true; 972 973 RefreshItem(m_current); 974 } 975 } 976 //else: nothing to do 977} 978 979bool wxListBox::FindItem(const wxString& prefix, bool strictlyAfter) 980{ 981 unsigned int count = GetCount(); 982 if ( count==0 ) 983 { 984 // empty listbox, we can't find anything in it 985 return false; 986 } 987 988 // start either from the current item or from the next one if strictlyAfter 989 // is true 990 int first; 991 if ( strictlyAfter ) 992 { 993 // the following line will set first correctly to 0 if there is no 994 // selection (m_current == -1) 995 first = m_current == (int)(count - 1) ? 0 : m_current + 1; 996 } 997 else // start with the current 998 { 999 first = m_current == -1 ? 0 : m_current; 1000 } 1001 1002 int last = first == 0 ? count - 1 : first - 1; 1003 1004 // if this is not true we'd never exit from the loop below! 1005 wxASSERT_MSG( first < (int)count && last < (int)count, _T("logic error") ); 1006 1007 // precompute it outside the loop 1008 size_t len = prefix.length(); 1009 1010 // loop over all items in the listbox 1011 for ( int item = first; item != (int)last; item < (int)(count - 1) ? item++ : item = 0 ) 1012 { 1013 if ( wxStrnicmp(this->GetString(item).c_str(), prefix, len) == 0 ) 1014 { 1015 SetCurrentItem(item); 1016 1017 if ( !(GetWindowStyle() & wxLB_MULTIPLE) ) 1018 { 1019 DeselectAll(item); 1020 SelectAndNotify(item); 1021 1022 if ( GetWindowStyle() & wxLB_EXTENDED ) 1023 AnchorSelection(item); 1024 } 1025 1026 return true; 1027 } 1028 } 1029 1030 // nothing found 1031 return false; 1032} 1033 1034void wxListBox::EnsureVisible(int n) 1035{ 1036 if ( m_updateScrollbarY ) 1037 { 1038 UpdateScrollbars(); 1039 1040 m_updateScrollbarX = 1041 m_updateScrollbarY = false; 1042 } 1043 1044 DoEnsureVisible(n); 1045} 1046 1047void wxListBox::DoEnsureVisible(int n) 1048{ 1049 if ( !m_showScrollbarY ) 1050 { 1051 // nothing to do - everything is shown anyhow 1052 return; 1053 } 1054 1055 int first; 1056 GetViewStart(0, &first); 1057 if ( first > n ) 1058 { 1059 // we need to scroll upwards, so make the current item appear on top 1060 // of the shown range 1061 Scroll(0, n); 1062 } 1063 else 1064 { 1065 int last = first + GetClientSize().y / GetLineHeight() - 1; 1066 if ( last < n ) 1067 { 1068 // scroll down: the current item appears at the bottom of the 1069 // range 1070 Scroll(0, n - (last - first)); 1071 } 1072 } 1073} 1074 1075void wxListBox::ChangeCurrent(int diff) 1076{ 1077 int current = m_current == -1 ? 0 : m_current; 1078 1079 current += diff; 1080 1081 int last = GetCount() - 1; 1082 if ( current < 0 ) 1083 current = 0; 1084 else if ( current > last ) 1085 current = last; 1086 1087 SetCurrentItem(current); 1088} 1089 1090void wxListBox::ExtendSelection(int itemTo) 1091{ 1092 // if we don't have the explicit values for selection start/end, make them 1093 // up 1094 if ( m_selAnchor == -1 ) 1095 m_selAnchor = m_current; 1096 1097 if ( itemTo == -1 ) 1098 itemTo = m_current; 1099 1100 // swap the start/end of selection range if necessary 1101 int itemFrom = m_selAnchor; 1102 if ( itemFrom > itemTo ) 1103 { 1104 int itemTmp = itemFrom; 1105 itemFrom = itemTo; 1106 itemTo = itemTmp; 1107 } 1108 1109 // the selection should now include all items in the range between the 1110 // anchor and the specified item and only them 1111 1112 int n; 1113 for ( n = 0; n < itemFrom; n++ ) 1114 { 1115 Deselect(n); 1116 } 1117 1118 for ( ; n <= itemTo; n++ ) 1119 { 1120 SetSelection(n); 1121 } 1122 1123 unsigned int count = GetCount(); 1124 for ( ; n < (int)count; n++ ) 1125 { 1126 Deselect(n); 1127 } 1128} 1129 1130void wxListBox::DoSelect(int item, bool sel) 1131{ 1132 if ( item != -1 ) 1133 { 1134 // go to this item first 1135 SetCurrentItem(item); 1136 } 1137 1138 // the current item is the one we want to change: either it was just 1139 // changed above to be the same as item or item == -1 in which we case we 1140 // are supposed to use the current one anyhow 1141 if ( m_current != -1 ) 1142 { 1143 // [de]select it 1144 SetSelection(m_current, sel); 1145 } 1146} 1147 1148void wxListBox::SelectAndNotify(int item) 1149{ 1150 DoSelect(item); 1151 1152 SendEvent(wxEVT_COMMAND_LISTBOX_SELECTED); 1153} 1154 1155void wxListBox::Activate(int item) 1156{ 1157 if ( item != -1 ) 1158 SetCurrentItem(item); 1159 else 1160 item = m_current; 1161 1162 if ( !(GetWindowStyle() & wxLB_MULTIPLE) ) 1163 { 1164 DeselectAll(item); 1165 } 1166 1167 if ( item != -1 ) 1168 { 1169 DoSelect(item); 1170 1171 SendEvent(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED); 1172 } 1173} 1174 1175// ---------------------------------------------------------------------------- 1176// input handling 1177// ---------------------------------------------------------------------------- 1178 1179/* 1180 The numArg here is the listbox item index while the strArg is used 1181 differently for the different actions: 1182 1183 a) for wxACTION_LISTBOX_FIND it has the natural meaning: this is the string 1184 to find 1185 1186 b) for wxACTION_LISTBOX_SELECT and wxACTION_LISTBOX_EXTENDSEL it is used 1187 to decide if the listbox should send the notification event (it is empty) 1188 or not (it is not): this allows us to reuse the same action for when the 1189 user is dragging the mouse when it has been released although in the 1190 first case no notification is sent while in the second it is sent. 1191 */ 1192bool wxListBox::PerformAction(const wxControlAction& action, 1193 long numArg, 1194 const wxString& strArg) 1195{ 1196 int item = (int)numArg; 1197 1198 if ( action == wxACTION_LISTBOX_SETFOCUS ) 1199 { 1200 SetCurrentItem(item); 1201 } 1202 else if ( action == wxACTION_LISTBOX_ACTIVATE ) 1203 { 1204 Activate(item); 1205 } 1206 else if ( action == wxACTION_LISTBOX_TOGGLE ) 1207 { 1208 if ( item == -1 ) 1209 item = m_current; 1210 1211 if ( IsSelected(item) ) 1212 DoUnselect(item); 1213 else 1214 SelectAndNotify(item); 1215 } 1216 else if ( action == wxACTION_LISTBOX_SELECT ) 1217 { 1218 DeselectAll(item); 1219 1220 if ( strArg.empty() ) 1221 SelectAndNotify(item); 1222 else 1223 DoSelect(item); 1224 } 1225 else if ( action == wxACTION_LISTBOX_SELECTADD ) 1226 DoSelect(item); 1227 else if ( action == wxACTION_LISTBOX_UNSELECT ) 1228 DoUnselect(item); 1229 else if ( action == wxACTION_LISTBOX_MOVEDOWN ) 1230 ChangeCurrent(1); 1231 else if ( action == wxACTION_LISTBOX_MOVEUP ) 1232 ChangeCurrent(-1); 1233 else if ( action == wxACTION_LISTBOX_PAGEDOWN ) 1234 ChangeCurrent(GetItemsPerPage()); 1235 else if ( action == wxACTION_LISTBOX_PAGEUP ) 1236 ChangeCurrent(-GetItemsPerPage()); 1237 else if ( action == wxACTION_LISTBOX_START ) 1238 SetCurrentItem(0); 1239 else if ( action == wxACTION_LISTBOX_END ) 1240 SetCurrentItem(GetCount() - 1); 1241 else if ( action == wxACTION_LISTBOX_UNSELECTALL ) 1242 DeselectAll(item); 1243 else if ( action == wxACTION_LISTBOX_EXTENDSEL ) 1244 ExtendSelection(item); 1245 else if ( action == wxACTION_LISTBOX_FIND ) 1246 FindNextItem(strArg); 1247 else if ( action == wxACTION_LISTBOX_ANCHOR ) 1248 AnchorSelection(item == -1 ? m_current : item); 1249 else if ( action == wxACTION_LISTBOX_SELECTALL || 1250 action == wxACTION_LISTBOX_SELTOGGLE ) 1251 wxFAIL_MSG(_T("unimplemented yet")); 1252 else 1253 return wxControl::PerformAction(action, numArg, strArg); 1254 1255 return true; 1256} 1257 1258/* static */ 1259wxInputHandler *wxListBox::GetStdInputHandler(wxInputHandler *handlerDef) 1260{ 1261 static wxStdListboxInputHandler s_handler(handlerDef); 1262 1263 return &s_handler; 1264} 1265 1266// ============================================================================ 1267// implementation of wxStdListboxInputHandler 1268// ============================================================================ 1269 1270wxStdListboxInputHandler::wxStdListboxInputHandler(wxInputHandler *handler, 1271 bool toggleOnPressAlways) 1272 : wxStdInputHandler(handler) 1273{ 1274 m_btnCapture = 0; 1275 m_toggleOnPressAlways = toggleOnPressAlways; 1276 m_actionMouse = wxACTION_NONE; 1277 m_trackMouseOutside = true; 1278} 1279 1280int wxStdListboxInputHandler::HitTest(const wxListBox *lbox, 1281 const wxMouseEvent& event) 1282{ 1283 int item = HitTestUnsafe(lbox, event); 1284 1285 return FixItemIndex(lbox, item); 1286} 1287 1288int wxStdListboxInputHandler::HitTestUnsafe(const wxListBox *lbox, 1289 const wxMouseEvent& event) 1290{ 1291 wxPoint pt = event.GetPosition(); 1292 pt -= lbox->GetClientAreaOrigin(); 1293 int y; 1294 lbox->CalcUnscrolledPosition(0, pt.y, NULL, &y); 1295 return y / lbox->GetLineHeight(); 1296} 1297 1298int wxStdListboxInputHandler::FixItemIndex(const wxListBox *lbox, 1299 int item) 1300{ 1301 if ( item < 0 ) 1302 { 1303 // mouse is above the first item 1304 item = 0; 1305 } 1306 else if ( (unsigned int)item >= lbox->GetCount() ) 1307 { 1308 // mouse is below the last item 1309 item = lbox->GetCount() - 1; 1310 } 1311 1312 return item; 1313} 1314 1315bool wxStdListboxInputHandler::IsValidIndex(const wxListBox *lbox, int item) 1316{ 1317 return item >= 0 && (unsigned int)item < lbox->GetCount(); 1318} 1319 1320wxControlAction 1321wxStdListboxInputHandler::SetupCapture(wxListBox *lbox, 1322 const wxMouseEvent& event, 1323 int item) 1324{ 1325 // we currently only allow selecting with the left mouse button, if we 1326 // do need to allow using other buttons too we might use the code 1327 // inside #if 0 1328#if 0 1329 m_btnCapture = event.LeftDown() 1330 ? 1 1331 : event.RightDown() 1332 ? 3 1333 : 2; 1334#else 1335 m_btnCapture = 1; 1336#endif // 0/1 1337 1338 wxControlAction action; 1339 if ( lbox->HasMultipleSelection() ) 1340 { 1341 if ( lbox->GetWindowStyle() & wxLB_MULTIPLE ) 1342 { 1343 if ( m_toggleOnPressAlways ) 1344 { 1345 // toggle the item right now 1346 action = wxACTION_LISTBOX_TOGGLE; 1347 } 1348 //else: later 1349 1350 m_actionMouse = wxACTION_LISTBOX_SETFOCUS; 1351 } 1352 else // wxLB_EXTENDED listbox 1353 { 1354 // simple click in an extended sel listbox clears the old 1355 // selection and adds the clicked item to it then, ctrl-click 1356 // toggles an item to it and shift-click adds a range between 1357 // the old selection anchor and the clicked item 1358 if ( event.ControlDown() ) 1359 { 1360 lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item); 1361 1362 action = wxACTION_LISTBOX_TOGGLE; 1363 } 1364 else if ( event.ShiftDown() ) 1365 { 1366 action = wxACTION_LISTBOX_EXTENDSEL; 1367 } 1368 else // simple click 1369 { 1370 lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item); 1371 1372 action = wxACTION_LISTBOX_SELECT; 1373 } 1374 1375 m_actionMouse = wxACTION_LISTBOX_EXTENDSEL; 1376 } 1377 } 1378 else // single selection 1379 { 1380 m_actionMouse = 1381 action = wxACTION_LISTBOX_SELECT; 1382 } 1383 1384 // by default we always do track it 1385 m_trackMouseOutside = true; 1386 1387 return action; 1388} 1389 1390bool wxStdListboxInputHandler::HandleKey(wxInputConsumer *consumer, 1391 const wxKeyEvent& event, 1392 bool pressed) 1393{ 1394 // we're only interested in the key press events 1395 if ( pressed && !event.AltDown() ) 1396 { 1397 bool isMoveCmd = true; 1398 int style = consumer->GetInputWindow()->GetWindowStyle(); 1399 1400 wxControlAction action; 1401 wxString strArg; 1402 1403 int keycode = event.GetKeyCode(); 1404 switch ( keycode ) 1405 { 1406 // movement 1407 case WXK_UP: 1408 action = wxACTION_LISTBOX_MOVEUP; 1409 break; 1410 1411 case WXK_DOWN: 1412 action = wxACTION_LISTBOX_MOVEDOWN; 1413 break; 1414 1415 case WXK_PAGEUP: 1416 action = wxACTION_LISTBOX_PAGEUP; 1417 break; 1418 1419 case WXK_PAGEDOWN: 1420 action = wxACTION_LISTBOX_PAGEDOWN; 1421 break; 1422 1423 case WXK_HOME: 1424 action = wxACTION_LISTBOX_START; 1425 break; 1426 1427 case WXK_END: 1428 action = wxACTION_LISTBOX_END; 1429 break; 1430 1431 // selection 1432 case WXK_SPACE: 1433 if ( style & wxLB_MULTIPLE ) 1434 { 1435 action = wxACTION_LISTBOX_TOGGLE; 1436 isMoveCmd = false; 1437 } 1438 break; 1439 1440 case WXK_RETURN: 1441 action = wxACTION_LISTBOX_ACTIVATE; 1442 isMoveCmd = false; 1443 break; 1444 1445 default: 1446 if ( (keycode < 255) && wxIsalnum((wxChar)keycode) ) 1447 { 1448 action = wxACTION_LISTBOX_FIND; 1449 strArg = (wxChar)keycode; 1450 } 1451 } 1452 1453 if ( !action.IsEmpty() ) 1454 { 1455 consumer->PerformAction(action, -1, strArg); 1456 1457 if ( isMoveCmd ) 1458 { 1459 if ( style & wxLB_SINGLE ) 1460 { 1461 // the current item is always the one selected 1462 consumer->PerformAction(wxACTION_LISTBOX_SELECT); 1463 } 1464 else if ( style & wxLB_EXTENDED ) 1465 { 1466 if ( event.ShiftDown() ) 1467 consumer->PerformAction(wxACTION_LISTBOX_EXTENDSEL); 1468 else 1469 { 1470 // select the item and make it the new selection anchor 1471 consumer->PerformAction(wxACTION_LISTBOX_SELECT); 1472 consumer->PerformAction(wxACTION_LISTBOX_ANCHOR); 1473 } 1474 } 1475 //else: nothing to do for multiple selection listboxes 1476 } 1477 1478 return true; 1479 } 1480 } 1481 1482 return wxStdInputHandler::HandleKey(consumer, event, pressed); 1483} 1484 1485bool wxStdListboxInputHandler::HandleMouse(wxInputConsumer *consumer, 1486 const wxMouseEvent& event) 1487{ 1488 wxListBox *lbox = wxStaticCast(consumer->GetInputWindow(), wxListBox); 1489 int item = HitTest(lbox, event); 1490 wxControlAction action; 1491 1492 // when the left mouse button is pressed, capture the mouse and track the 1493 // item under mouse (if the mouse leaves the window, we will still be 1494 // getting the mouse move messages generated by wxScrollWindow) 1495 if ( event.LeftDown() ) 1496 { 1497 // capture the mouse to track the selected item 1498 lbox->CaptureMouse(); 1499 1500 action = SetupCapture(lbox, event, item); 1501 } 1502 else if ( m_btnCapture && event.ButtonUp(m_btnCapture) ) 1503 { 1504 // when the left mouse button is released, release the mouse too 1505 wxWindow *winCapture = wxWindow::GetCapture(); 1506 if ( winCapture ) 1507 { 1508 winCapture->ReleaseMouse(); 1509 m_btnCapture = 0; 1510 1511 action = m_actionMouse; 1512 } 1513 //else: the mouse wasn't presed over the listbox, only released here 1514 } 1515 else if ( event.LeftDClick() ) 1516 { 1517 action = wxACTION_LISTBOX_ACTIVATE; 1518 } 1519 1520 if ( !action.IsEmpty() ) 1521 { 1522 lbox->PerformAction(action, item); 1523 1524 return true; 1525 } 1526 1527 return wxStdInputHandler::HandleMouse(consumer, event); 1528} 1529 1530bool wxStdListboxInputHandler::HandleMouseMove(wxInputConsumer *consumer, 1531 const wxMouseEvent& event) 1532{ 1533 wxWindow *winCapture = wxWindow::GetCapture(); 1534 if ( winCapture && (event.GetEventObject() == winCapture) ) 1535 { 1536 wxListBox *lbox = wxStaticCast(consumer->GetInputWindow(), wxListBox); 1537 1538 if ( !m_btnCapture || !m_trackMouseOutside ) 1539 { 1540 // someone captured the mouse for us (we always set m_btnCapture 1541 // when we do it ourselves): in this case we only react to 1542 // the mouse messages when they happen inside the listbox 1543 if ( lbox->HitTest(event.GetPosition()) != wxHT_WINDOW_INSIDE ) 1544 return false; 1545 } 1546 1547 int item = HitTest(lbox, event); 1548 if ( !m_btnCapture ) 1549 { 1550 // now that we have the mouse inside the listbox, do capture it 1551 // normally - but ensure that we will still ignore the outside 1552 // events 1553 SetupCapture(lbox, event, item); 1554 1555 m_trackMouseOutside = false; 1556 } 1557 1558 if ( IsValidIndex(lbox, item) ) 1559 { 1560 // pass something into strArg to tell the listbox that it shouldn't 1561 // send the notification message: see PerformAction() above 1562 lbox->PerformAction(m_actionMouse, item, _T("no")); 1563 } 1564 // else: don't pass invalid index to the listbox 1565 } 1566 else // we don't have capture any more 1567 { 1568 if ( m_btnCapture ) 1569 { 1570 // if we lost capture unexpectedly (someone else took the capture 1571 // from us), return to a consistent state 1572 m_btnCapture = 0; 1573 } 1574 } 1575 1576 return wxStdInputHandler::HandleMouseMove(consumer, event); 1577} 1578 1579#endif // wxUSE_LISTBOX 1580