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 
BEGIN_EVENT_TABLE(wxListBox,wxListBoxBase)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 
wxListBox(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,const wxArrayString & choices,long style,const wxValidator & validator,const wxString & name)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 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,const wxArrayString & choices,long style,const wxValidator & validator,const wxString & name)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 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,int n,const wxString choices[],long style,const wxValidator & validator,const wxString & name)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 
~wxListBox()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 
GetCount() const223 unsigned int wxListBox::GetCount() const
224 {
225     return IsSorted() ? m_strings.sorted->size()
226                       : m_strings.unsorted->size();
227 }
228 
GetString(unsigned int n) const229 wxString wxListBox::GetString(unsigned int n) const
230 {
231     return IsSorted() ? m_strings.sorted->Item(n)
232                       : m_strings.unsorted->Item(n);
233 }
234 
FindString(const wxString & s,bool bCase) const235 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 
DoInsertItems(const wxArrayStringsAdapter & items,unsigned int pos,void ** clientData,wxClientDataType type)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 
SetString(unsigned int n,const wxString & s)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 
DoClear()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 
DoDeleteOneItem(unsigned int n)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 
DoSetItemClientData(unsigned int n,void * clientData)403 void wxListBox::DoSetItemClientData(unsigned int n, void* clientData)
404 {
405     m_itemsClientData[n] = clientData;
406 }
407 
DoGetItemClientData(unsigned int n) const408 void *wxListBox::DoGetItemClientData(unsigned int n) const
409 {
410     return m_itemsClientData[n];
411 }
412 
413 // ----------------------------------------------------------------------------
414 // selection
415 // ----------------------------------------------------------------------------
416 
DoSetSelection(int n,bool select)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 
GetSelection() const469 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 
wxCompareInts(int * n,int * m)477 static int wxCMPFUNC_CONV wxCompareInts(int *n, int *m)
478 {
479     return *n - *m;
480 }
481 
GetSelections(wxArrayInt & selections) const482 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 
RefreshFromItemToEnd(int from)503 void wxListBox::RefreshFromItemToEnd(int from)
504 {
505     RefreshItems(from, GetCount() - from);
506 }
507 
RefreshItems(int from,int count)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 
RefreshItem(int n)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 
RefreshAll()568 void wxListBox::RefreshAll()
569 {
570     m_updateCount = -1;
571 }
572 
RefreshHorzScrollbar()573 void wxListBox::RefreshHorzScrollbar()
574 {
575     m_maxWidth = 0; // recalculate it
576     m_updateScrollbarX = true;
577 }
578 
UpdateScrollbars()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 
UpdateItems()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 
OnInternalIdle()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 
GetDefaultBorder() const694 wxBorder wxListBox::GetDefaultBorder() const
695 {
696     return wxBORDER_SUNKEN;
697 }
698 
DoDraw(wxControlRenderer * renderer)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 
DoDrawRange(wxControlRenderer * renderer,int itemFirst,int itemLast)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 
SetFont(const wxFont & font)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 
CalcItemsPerPage()754 void wxListBox::CalcItemsPerPage()
755 {
756     m_lineHeight = GetRenderer()->GetListboxItemHeight(GetCharHeight());
757     m_itemsPerPage = GetClientSize().y / m_lineHeight;
758 }
759 
GetItemsPerPage() const760 int wxListBox::GetItemsPerPage() const
761 {
762     if ( !m_itemsPerPage )
763     {
764         wxConstCast(this, wxListBox)->CalcItemsPerPage();
765     }
766 
767     return m_itemsPerPage;
768 }
769 
GetLineHeight() const770 wxCoord wxListBox::GetLineHeight() const
771 {
772     if ( !m_lineHeight )
773     {
774         wxConstCast(this, wxListBox)->CalcItemsPerPage();
775     }
776 
777     return m_lineHeight;
778 }
779 
GetMaxWidth() const780 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 
OnSize(wxSizeEvent & event)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 
DoSetFirstItem(int n)813 void wxListBox::DoSetFirstItem(int n)
814 {
815     SetCurrentItem(n);
816 }
817 
DoSetSize(int x,int y,int width,int height,int sizeFlags)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 
DoGetBestClientSize() const838 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 
SendEvent(wxEventType type,int item)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 
SetCurrentItem(int n)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 
FindItem(const wxString & prefix,bool strictlyAfter)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 
EnsureVisible(int n)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 
DoEnsureVisible(int n)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 
ChangeCurrent(int diff)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 
ExtendSelection(int itemTo)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 
DoSelect(int item,bool sel)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 
SelectAndNotify(int item)1090 void wxListBox::SelectAndNotify(int item)
1091 {
1092     DoSelect(item);
1093 
1094     SendEvent(wxEVT_LISTBOX);
1095 }
1096 
Activate(int item)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  */
PerformAction(const wxControlAction & action,long numArg,const wxString & strArg)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 */
GetStdInputHandler(wxInputHandler * handlerDef)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 
wxStdListboxInputHandler(wxInputHandler * handler,bool toggleOnPressAlways)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 
HitTest(const wxListBox * lbox,const wxMouseEvent & event)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 
HitTestUnsafe(const wxListBox * lbox,const wxMouseEvent & event)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 
FixItemIndex(const wxListBox * lbox,int item)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 
IsValidIndex(const wxListBox * lbox,int item)1257 bool wxStdListboxInputHandler::IsValidIndex(const wxListBox *lbox, int item)
1258 {
1259     return item >= 0 && (unsigned int)item < lbox->GetCount();
1260 }
1261 
1262 wxControlAction
SetupCapture(wxListBox * lbox,const wxMouseEvent & event,int item)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 
HandleKey(wxInputConsumer * consumer,const wxKeyEvent & event,bool pressed)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 
HandleMouse(wxInputConsumer * consumer,const wxMouseEvent & event)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 
HandleMouseMove(wxInputConsumer * consumer,const wxMouseEvent & event)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
1522