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