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