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