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