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