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