wxCaretSuspender only shows the caret if it was visible previously
[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
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 validator, 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 OnInternalIdle();
626 }
627
628 // ----------------------------------------------------------------------------
629 // drawing
630 // ----------------------------------------------------------------------------
631
632 wxBorder wxListBox::GetDefaultBorder() const
633 {
634 return wxBORDER_SUNKEN;
635 }
636
637 void wxListBox::DoDraw(wxControlRenderer *renderer)
638 {
639 // adjust the DC to account for scrolling
640 wxDC& dc = renderer->GetDC();
641 PrepareDC(dc);
642 dc.SetFont(GetFont());
643
644 // get the update rect
645 wxRect rectUpdate = GetUpdateClientRect();
646
647 int yTop, yBottom;
648 CalcUnscrolledPosition(0, rectUpdate.GetTop(), NULL, &yTop);
649 CalcUnscrolledPosition(0, rectUpdate.GetBottom(), NULL, &yBottom);
650
651 // get the items which must be redrawn
652 wxCoord lineHeight = GetLineHeight();
653 size_t itemFirst = yTop / lineHeight,
654 itemLast = (yBottom + lineHeight - 1) / lineHeight,
655 itemMax = m_strings.GetCount();
656
657 if ( itemFirst >= itemMax )
658 return;
659
660 if ( itemLast > itemMax )
661 itemLast = itemMax;
662
663 // do draw them
664 wxLogTrace(_T("listbox"), _T("Repainting items %d..%d"),
665 itemFirst, itemLast);
666
667 DoDrawRange(renderer, itemFirst, itemLast);
668 }
669
670 void wxListBox::DoDrawRange(wxControlRenderer *renderer,
671 int itemFirst, int itemLast)
672 {
673 renderer->DrawItems(this, itemFirst, itemLast);
674 }
675
676 // ----------------------------------------------------------------------------
677 // size calculations
678 // ----------------------------------------------------------------------------
679
680 bool wxListBox::SetFont(const wxFont& font)
681 {
682 if ( !wxControl::SetFont(font) )
683 return FALSE;
684
685 CalcItemsPerPage();
686
687 RefreshAll();
688
689 return TRUE;
690 }
691
692 void wxListBox::CalcItemsPerPage()
693 {
694 m_lineHeight = GetRenderer()->GetListboxItemHeight(GetCharHeight());
695 m_itemsPerPage = GetClientSize().y / m_lineHeight;
696 }
697
698 int wxListBox::GetItemsPerPage() const
699 {
700 if ( !m_itemsPerPage )
701 {
702 wxConstCast(this, wxListBox)->CalcItemsPerPage();
703 }
704
705 return m_itemsPerPage;
706 }
707
708 wxCoord wxListBox::GetLineHeight() const
709 {
710 if ( !m_lineHeight )
711 {
712 wxConstCast(this, wxListBox)->CalcItemsPerPage();
713 }
714
715 return m_lineHeight;
716 }
717
718 wxCoord wxListBox::GetMaxWidth() const
719 {
720 if ( m_maxWidth == 0 )
721 {
722 wxListBox *self = wxConstCast(this, wxListBox);
723 wxCoord width;
724 size_t count = m_strings.GetCount();
725 for ( size_t n = 0; n < count; n++ )
726 {
727 GetTextExtent(m_strings[n], &width, NULL);
728 if ( width > m_maxWidth )
729 {
730 self->m_maxWidth = width;
731 self->m_maxWidthItem = n;
732 }
733 }
734 }
735
736 return m_maxWidth;
737 }
738
739 void wxListBox::OnSize(wxSizeEvent& event)
740 {
741 // recalculate the number of items per page
742 CalcItemsPerPage();
743
744 // the scrollbars might [dis]appear
745 m_updateScrollbarX =
746 m_updateScrollbarY = TRUE;
747
748 event.Skip();
749 }
750
751 void wxListBox::DoSetFirstItem(int n)
752 {
753 SetCurrentItem(n);
754 }
755
756 void wxListBox::DoSetSize(int x, int y,
757 int width, int height,
758 int sizeFlags)
759 {
760 if ( GetWindowStyle() & wxLB_INT_HEIGHT )
761 {
762 // we must round up the height to an entire number of rows
763
764 // the client area must contain an int number of rows, so take borders
765 // into account
766 wxRect rectBorders = GetRenderer()->GetBorderDimensions(GetBorder());
767 wxCoord hBorders = rectBorders.y + rectBorders.height;
768
769 wxCoord hLine = GetLineHeight();
770 height = ((height - hBorders + hLine - 1) / hLine)*hLine + hBorders;
771 }
772
773 wxListBoxBase::DoSetSize(x, y, width, height, sizeFlags);
774 }
775
776 wxSize wxListBox::DoGetBestClientSize() const
777 {
778 wxCoord width = 0,
779 height = 0;
780
781 size_t count = m_strings.GetCount();
782 for ( size_t n = 0; n < count; n++ )
783 {
784 wxCoord w,h;
785 GetTextExtent(m_strings[n], &w, &h);
786
787 if ( w > width )
788 width = w;
789 if ( h > height )
790 height = h;
791 }
792
793 // if the listbox is empty, still give it some non zero (even if
794 // arbitrary) size - otherwise, leave small margin around the strings
795 if ( !width )
796 width = 100;
797 else
798 width += 3*GetCharWidth();
799
800 if ( !height )
801 height = GetCharHeight();
802
803 // we need the height of the entire listbox, not just of one line
804 height *= wxMax(count, 7);
805
806 return wxSize(width, height);
807 }
808
809 // ----------------------------------------------------------------------------
810 // listbox actions
811 // ----------------------------------------------------------------------------
812
813 bool wxListBox::SendEvent(wxEventType type, int item)
814 {
815 wxCommandEvent event(type, m_windowId);
816 event.SetEventObject(this);
817
818 // use the current item by default
819 if ( item == -1 )
820 {
821 item = m_current;
822 }
823
824 // client data and string parameters only make sense if we have an item
825 if ( item != -1 )
826 {
827 if ( HasClientObjectData() )
828 event.SetClientObject(GetClientObject(item));
829 else if ( HasClientUntypedData() )
830 event.SetClientData(GetClientData(item));
831
832 event.SetString(GetString(item));
833 }
834
835 event.m_commandInt = item;
836
837 return GetEventHandler()->ProcessEvent(event);
838 }
839
840 void wxListBox::SetCurrentItem(int n)
841 {
842 if ( n != m_current )
843 {
844 if ( m_current != -1 )
845 RefreshItem(m_current);
846
847 m_current = n;
848
849 if ( m_current != -1 )
850 {
851 m_currentChanged = TRUE;
852
853 RefreshItem(m_current);
854 }
855 }
856 //else: nothing to do
857 }
858
859 bool wxListBox::FindItem(const wxString& prefix, bool strictlyAfter)
860 {
861 int count = GetCount();
862 if ( !count )
863 {
864 // empty listbox, we can't find anything in it
865 return FALSE;
866 }
867
868 // start either from the current item or from the next one if strictlyAfter
869 // is true
870 int first;
871 if ( strictlyAfter )
872 {
873 // the following line will set first correctly to 0 if there is no
874 // selection (m_current == -1)
875 first = m_current == count - 1 ? 0 : m_current + 1;
876 }
877 else // start with the current
878 {
879 first = m_current == -1 ? 0 : m_current;
880 }
881
882 int last = first == 0 ? count - 1 : first - 1;
883
884 // if this is not true we'd never exit from the loop below!
885 wxASSERT_MSG( first < count && last < count, _T("logic error") );
886
887 // precompute it outside the loop
888 size_t len = prefix.length();
889
890 // loop over all items in the listbox
891 for ( int item = first; item != last; item < count - 1 ? item++ : item = 0 )
892 {
893 if ( wxStrnicmp(m_strings[item], prefix, len) == 0 )
894 {
895 SetCurrentItem(item);
896
897 if ( !(GetWindowStyle() & wxLB_MULTIPLE) )
898 {
899 DeselectAll(item);
900 SelectAndNotify(item);
901
902 if ( GetWindowStyle() & wxLB_EXTENDED )
903 AnchorSelection(item);
904 }
905
906 return TRUE;
907 }
908 }
909
910 // nothing found
911 return FALSE;
912 }
913
914 void wxListBox::EnsureVisible(int n)
915 {
916 if ( m_updateScrollbarY )
917 {
918 UpdateScrollbars();
919
920 m_updateScrollbarX =
921 m_updateScrollbarY = FALSE;
922 }
923
924 DoEnsureVisible(n);
925 }
926
927 void wxListBox::DoEnsureVisible(int n)
928 {
929 if ( !m_showScrollbarY )
930 {
931 // nothing to do - everything is shown anyhow
932 return;
933 }
934
935 int first;
936 GetViewStart(0, &first);
937 if ( first > n )
938 {
939 // we need to scroll upwards, so make the current item appear on top
940 // of the shown range
941 Scroll(0, n);
942 }
943 else
944 {
945 int last = first + GetClientSize().y / GetLineHeight() - 1;
946 if ( last < n )
947 {
948 // scroll down: the current item appears at the bottom of the
949 // range
950 Scroll(0, n - (last - first));
951 }
952 }
953 }
954
955 void wxListBox::ChangeCurrent(int diff)
956 {
957 int current = m_current == -1 ? 0 : m_current;
958
959 current += diff;
960
961 int last = GetCount() - 1;
962 if ( current < 0 )
963 current = 0;
964 else if ( current > last )
965 current = last;
966
967 SetCurrentItem(current);
968 }
969
970 void wxListBox::ExtendSelection(int itemTo)
971 {
972 // if we don't have the explicit values for selection start/end, make them
973 // up
974 if ( m_selAnchor == -1 )
975 m_selAnchor = m_current;
976
977 if ( itemTo == -1 )
978 itemTo = m_current;
979
980 // swap the start/end of selection range if necessary
981 int itemFrom = m_selAnchor;
982 if ( itemFrom > itemTo )
983 {
984 int itemTmp = itemFrom;
985 itemFrom = itemTo;
986 itemTo = itemTmp;
987 }
988
989 // the selection should now include all items in the range between the
990 // anchor and the specified item and only them
991
992 int n;
993 for ( n = 0; n < itemFrom; n++ )
994 {
995 Deselect(n);
996 }
997
998 for ( ; n <= itemTo; n++ )
999 {
1000 SetSelection(n);
1001 }
1002
1003 int count = GetCount();
1004 for ( ; n < count; n++ )
1005 {
1006 Deselect(n);
1007 }
1008 }
1009
1010 void wxListBox::DoSelect(int item, bool sel)
1011 {
1012 if ( item != -1 )
1013 {
1014 // go to this item first
1015 SetCurrentItem(item);
1016 }
1017
1018 // the current item is the one we want to change: either it was just
1019 // changed above to be the same as item or item == -1 in which we case we
1020 // are supposed to use the current one anyhow
1021 if ( m_current != -1 )
1022 {
1023 // [de]select it
1024 SetSelection(m_current, sel);
1025 }
1026 }
1027
1028 void wxListBox::SelectAndNotify(int item)
1029 {
1030 DoSelect(item);
1031
1032 SendEvent(wxEVT_COMMAND_LISTBOX_SELECTED);
1033 }
1034
1035 void wxListBox::Activate(int item)
1036 {
1037 if ( item != -1 )
1038 SetCurrentItem(item);
1039 else
1040 item = m_current;
1041
1042 if ( !(GetWindowStyle() & wxLB_MULTIPLE) )
1043 {
1044 DeselectAll(item);
1045 }
1046
1047 if ( item != -1 )
1048 {
1049 DoSelect(item);
1050
1051 SendEvent(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED);
1052 }
1053 }
1054
1055 // ----------------------------------------------------------------------------
1056 // input handling
1057 // ----------------------------------------------------------------------------
1058
1059 /*
1060 The numArg here is the listbox item index while the strArg is used
1061 differently for the different actions:
1062
1063 a) for wxACTION_LISTBOX_FIND it has the natural meaning: this is the string
1064 to find
1065
1066 b) for wxACTION_LISTBOX_SELECT and wxACTION_LISTBOX_EXTENDSEL it is used
1067 to decide if the listbox should send the notification event (it is empty)
1068 or not (it is not): this allows us to reuse the same action for when the
1069 user is dragging the mouse when it has been released although in the
1070 first case no notification is sent while in the second it is sent.
1071 */
1072 bool wxListBox::PerformAction(const wxControlAction& action,
1073 long numArg,
1074 const wxString& strArg)
1075 {
1076 int item = (int)numArg;
1077
1078 if ( action == wxACTION_LISTBOX_SETFOCUS )
1079 {
1080 SetCurrentItem(item);
1081 }
1082 else if ( action == wxACTION_LISTBOX_ACTIVATE )
1083 {
1084 Activate(item);
1085 }
1086 else if ( action == wxACTION_LISTBOX_TOGGLE )
1087 {
1088 if ( item == -1 )
1089 item = m_current;
1090
1091 if ( IsSelected(item) )
1092 DoUnselect(item);
1093 else
1094 SelectAndNotify(item);
1095 }
1096 else if ( action == wxACTION_LISTBOX_SELECT )
1097 {
1098 DeselectAll(item);
1099
1100 if ( strArg.empty() )
1101 SelectAndNotify(item);
1102 else
1103 DoSelect(item);
1104 }
1105 else if ( action == wxACTION_LISTBOX_SELECTADD )
1106 DoSelect(item);
1107 else if ( action == wxACTION_LISTBOX_UNSELECT )
1108 DoUnselect(item);
1109 else if ( action == wxACTION_LISTBOX_MOVEDOWN )
1110 ChangeCurrent(1);
1111 else if ( action == wxACTION_LISTBOX_MOVEUP )
1112 ChangeCurrent(-1);
1113 else if ( action == wxACTION_LISTBOX_PAGEDOWN )
1114 ChangeCurrent(GetItemsPerPage());
1115 else if ( action == wxACTION_LISTBOX_PAGEUP )
1116 ChangeCurrent(-GetItemsPerPage());
1117 else if ( action == wxACTION_LISTBOX_START )
1118 SetCurrentItem(0);
1119 else if ( action == wxACTION_LISTBOX_END )
1120 SetCurrentItem(GetCount() - 1);
1121 else if ( action == wxACTION_LISTBOX_UNSELECTALL )
1122 DeselectAll(item);
1123 else if ( action == wxACTION_LISTBOX_EXTENDSEL )
1124 ExtendSelection(item);
1125 else if ( action == wxACTION_LISTBOX_FIND )
1126 FindNextItem(strArg);
1127 else if ( action == wxACTION_LISTBOX_ANCHOR )
1128 AnchorSelection(item == -1 ? m_current : item);
1129 else if ( action == wxACTION_LISTBOX_SELECTALL ||
1130 action == wxACTION_LISTBOX_SELTOGGLE )
1131 wxFAIL_MSG(_T("unimplemented yet"));
1132 else
1133 return wxControl::PerformAction(action, numArg, strArg);
1134
1135 return TRUE;
1136 }
1137
1138 // ============================================================================
1139 // implementation of wxStdListboxInputHandler
1140 // ============================================================================
1141
1142 wxStdListboxInputHandler::wxStdListboxInputHandler(wxInputHandler *handler,
1143 bool toggleOnPressAlways)
1144 : wxStdInputHandler(handler)
1145 {
1146 m_btnCapture = 0;
1147 m_toggleOnPressAlways = toggleOnPressAlways;
1148 m_actionMouse = wxACTION_NONE;
1149 m_trackMouseOutside = TRUE;
1150 }
1151
1152 int wxStdListboxInputHandler::HitTest(const wxListBox *lbox,
1153 const wxMouseEvent& event)
1154 {
1155 int item = HitTestUnsafe(lbox, event);
1156
1157 return FixItemIndex(lbox, item);
1158 }
1159
1160 int wxStdListboxInputHandler::HitTestUnsafe(const wxListBox *lbox,
1161 const wxMouseEvent& event)
1162 {
1163 wxPoint pt = event.GetPosition();
1164 pt -= lbox->GetClientAreaOrigin();
1165 int y;
1166 lbox->CalcUnscrolledPosition(0, pt.y, NULL, &y);
1167 return y / lbox->GetLineHeight();
1168 }
1169
1170 int wxStdListboxInputHandler::FixItemIndex(const wxListBox *lbox,
1171 int item)
1172 {
1173 if ( item < 0 )
1174 {
1175 // mouse is above the first item
1176 item = 0;
1177 }
1178 else if ( item >= lbox->GetCount() )
1179 {
1180 // mouse is below the last item
1181 item = lbox->GetCount() - 1;
1182 }
1183
1184 return item;
1185 }
1186
1187 bool wxStdListboxInputHandler::IsValidIndex(const wxListBox *lbox, int item)
1188 {
1189 return item >= 0 && item < lbox->GetCount();
1190 }
1191
1192 wxControlAction
1193 wxStdListboxInputHandler::SetupCapture(wxListBox *lbox,
1194 const wxMouseEvent& event,
1195 int item)
1196 {
1197 // we currently only allow selecting with the left mouse button, if we
1198 // do need to allow using other buttons too we might use the code
1199 // inside #if 0
1200 #if 0
1201 m_btnCapture = event.LeftDown()
1202 ? 1
1203 : event.RightDown()
1204 ? 3
1205 : 2;
1206 #else
1207 m_btnCapture = 1;
1208 #endif // 0/1
1209
1210 wxControlAction action;
1211 if ( lbox->HasMultipleSelection() )
1212 {
1213 if ( lbox->GetWindowStyle() & wxLB_MULTIPLE )
1214 {
1215 if ( m_toggleOnPressAlways )
1216 {
1217 // toggle the item right now
1218 action = wxACTION_LISTBOX_TOGGLE;
1219 }
1220 //else: later
1221
1222 m_actionMouse = wxACTION_LISTBOX_SETFOCUS;
1223 }
1224 else // wxLB_EXTENDED listbox
1225 {
1226 // simple click in an extended sel listbox clears the old
1227 // selection and adds the clicked item to it then, ctrl-click
1228 // toggles an item to it and shift-click adds a range between
1229 // the old selection anchor and the clicked item
1230 if ( event.ControlDown() )
1231 {
1232 lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item);
1233
1234 action = wxACTION_LISTBOX_TOGGLE;
1235 }
1236 else if ( event.ShiftDown() )
1237 {
1238 action = wxACTION_LISTBOX_EXTENDSEL;
1239 }
1240 else // simple click
1241 {
1242 lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item);
1243
1244 action = wxACTION_LISTBOX_SELECT;
1245 }
1246
1247 m_actionMouse = wxACTION_LISTBOX_EXTENDSEL;
1248 }
1249 }
1250 else // single selection
1251 {
1252 m_actionMouse =
1253 action = wxACTION_LISTBOX_SELECT;
1254 }
1255
1256 // by default we always do track it
1257 m_trackMouseOutside = TRUE;
1258
1259 return action;
1260 }
1261
1262 bool wxStdListboxInputHandler::HandleKey(wxInputConsumer *consumer,
1263 const wxKeyEvent& event,
1264 bool pressed)
1265 {
1266 // we're only interested in the key press events
1267 if ( pressed && !event.AltDown() )
1268 {
1269 bool isMoveCmd = TRUE;
1270 int style = consumer->GetInputWindow()->GetWindowStyle();
1271
1272 wxControlAction action;
1273 wxString strArg;
1274
1275 int keycode = event.GetKeyCode();
1276 switch ( keycode )
1277 {
1278 // movement
1279 case WXK_UP:
1280 action = wxACTION_LISTBOX_MOVEUP;
1281 break;
1282
1283 case WXK_DOWN:
1284 action = wxACTION_LISTBOX_MOVEDOWN;
1285 break;
1286
1287 case WXK_PAGEUP:
1288
1289 case WXK_PRIOR:
1290 action = wxACTION_LISTBOX_PAGEUP;
1291 break;
1292
1293 case WXK_PAGEDOWN:
1294
1295 case WXK_NEXT:
1296 action = wxACTION_LISTBOX_PAGEDOWN;
1297 break;
1298
1299 case WXK_HOME:
1300 action = wxACTION_LISTBOX_START;
1301 break;
1302
1303 case WXK_END:
1304 action = wxACTION_LISTBOX_END;
1305 break;
1306
1307 // selection
1308 case WXK_SPACE:
1309 if ( style & wxLB_MULTIPLE )
1310 {
1311 action = wxACTION_LISTBOX_TOGGLE;
1312 isMoveCmd = FALSE;
1313 }
1314 break;
1315
1316 case WXK_RETURN:
1317 action = wxACTION_LISTBOX_ACTIVATE;
1318 isMoveCmd = FALSE;
1319 break;
1320
1321 default:
1322 if ( (keycode < 255) && wxIsalnum(keycode) )
1323 {
1324 action = wxACTION_LISTBOX_FIND;
1325 strArg = (wxChar)keycode;
1326 }
1327 }
1328
1329 if ( !!action )
1330 {
1331 consumer->PerformAction(action, -1, strArg);
1332
1333 if ( isMoveCmd )
1334 {
1335 if ( style & wxLB_SINGLE )
1336 {
1337 // the current item is always the one selected
1338 consumer->PerformAction(wxACTION_LISTBOX_SELECT);
1339 }
1340 else if ( style & wxLB_EXTENDED )
1341 {
1342 if ( event.ShiftDown() )
1343 consumer->PerformAction(wxACTION_LISTBOX_EXTENDSEL);
1344 else
1345 {
1346 // select the item and make it the new selection anchor
1347 consumer->PerformAction(wxACTION_LISTBOX_SELECT);
1348 consumer->PerformAction(wxACTION_LISTBOX_ANCHOR);
1349 }
1350 }
1351 //else: nothing to do for multiple selection listboxes
1352 }
1353
1354 return TRUE;
1355 }
1356 }
1357
1358 return wxStdInputHandler::HandleKey(consumer, event, pressed);
1359 }
1360
1361 bool wxStdListboxInputHandler::HandleMouse(wxInputConsumer *consumer,
1362 const wxMouseEvent& event)
1363 {
1364 wxListBox *lbox = wxStaticCast(consumer->GetInputWindow(), wxListBox);
1365 int item = HitTest(lbox, event);
1366 wxControlAction action;
1367
1368 // when the left mouse button is pressed, capture the mouse and track the
1369 // item under mouse (if the mouse leaves the window, we will still be
1370 // getting the mouse move messages generated by wxScrollWindow)
1371 if ( event.LeftDown() )
1372 {
1373 // capture the mouse to track the selected item
1374 lbox->CaptureMouse();
1375
1376 action = SetupCapture(lbox, event, item);
1377 }
1378 else if ( m_btnCapture && event.ButtonUp(m_btnCapture) )
1379 {
1380 // when the left mouse button is released, release the mouse too
1381 wxWindow *winCapture = wxWindow::GetCapture();
1382 if ( winCapture )
1383 {
1384 winCapture->ReleaseMouse();
1385 m_btnCapture = 0;
1386
1387 action = m_actionMouse;
1388 }
1389 //else: the mouse wasn't presed over the listbox, only released here
1390 }
1391 else if ( event.LeftDClick() )
1392 {
1393 action = wxACTION_LISTBOX_ACTIVATE;
1394 }
1395
1396 if ( !!action )
1397 {
1398 lbox->PerformAction(action, item);
1399
1400 return TRUE;
1401 }
1402
1403 return wxStdInputHandler::HandleMouse(consumer, event);
1404 }
1405
1406 bool wxStdListboxInputHandler::HandleMouseMove(wxInputConsumer *consumer,
1407 const wxMouseEvent& event)
1408 {
1409 wxWindow *winCapture = wxWindow::GetCapture();
1410 if ( winCapture && (event.GetEventObject() == winCapture) )
1411 {
1412 wxListBox *lbox = wxStaticCast(consumer->GetInputWindow(), wxListBox);
1413
1414 if ( !m_btnCapture || !m_trackMouseOutside )
1415 {
1416 // someone captured the mouse for us (we always set m_btnCapture
1417 // when we do it ourselves): in this case we only react to
1418 // the mouse messages when they happen inside the listbox
1419 if ( lbox->HitTest(event.GetPosition()) != wxHT_WINDOW_INSIDE )
1420 return FALSE;
1421 }
1422
1423 int item = HitTest(lbox, event);
1424 if ( !m_btnCapture )
1425 {
1426 // now that we have the mouse inside the listbox, do capture it
1427 // normally - but ensure that we will still ignore the outside
1428 // events
1429 SetupCapture(lbox, event, item);
1430
1431 m_trackMouseOutside = FALSE;
1432 }
1433
1434 if ( IsValidIndex(lbox, item) )
1435 {
1436 // pass something into strArg to tell the listbox that it shouldn't
1437 // send the notification message: see PerformAction() above
1438 lbox->PerformAction(m_actionMouse, item, _T("no"));
1439 }
1440 // else: don't pass invalid index to the listbox
1441 }
1442 else // we don't have capture any more
1443 {
1444 if ( m_btnCapture )
1445 {
1446 // if we lost capture unexpectedly (someone else took the capture
1447 // from us), return to a consistent state
1448 m_btnCapture = 0;
1449 }
1450 }
1451
1452 return wxStdInputHandler::HandleMouseMove(consumer, event);
1453 }
1454
1455 #endif // wxUSE_LISTBOX