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