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