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