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