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