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: listbox.cpp 42816 2006-10-31 08:50:17Z RD $
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
45class WXDLLEXPORT wxStdListboxInputHandler : public wxStdInputHandler
46{
47public:
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
64protected:
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
100IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControl)
101
102BEGIN_EVENT_TABLE(wxListBox, wxListBoxBase)
103    EVT_SIZE(wxListBox::OnSize)
104END_EVENT_TABLE()
105
106// ----------------------------------------------------------------------------
107// construction
108// ----------------------------------------------------------------------------
109
110void 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
135wxListBox::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
150bool 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
165bool 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
206wxListBox::~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
220int wxCMPFUNC_CONV wxListBoxSortNoCase(wxString* s1, wxString* s2)
221{
222    return  s1->CmpNoCase(*s2);
223}
224
225int 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
244int 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
270void 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
296void 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
319void 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
354void 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
373void wxListBox::Clear()
374{
375    DoClear();
376
377    m_updateScrollbarY = true;
378
379    RefreshHorzScrollbar();
380
381    RefreshAll();
382}
383
384void 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
451void wxListBox::DoSetItemClientData(unsigned int n, void* clientData)
452{
453    m_itemsClientData[n] = clientData;
454}
455
456void *wxListBox::DoGetItemClientData(unsigned int n) const
457{
458    return m_itemsClientData[n];
459}
460
461void wxListBox::DoSetItemClientObject(unsigned int n, wxClientData* clientData)
462{
463    m_itemsClientData[n] = clientData;
464}
465
466wxClientData* wxListBox::DoGetItemClientObject(unsigned int n) const
467{
468    return (wxClientData *)m_itemsClientData[n];
469}
470
471// ----------------------------------------------------------------------------
472// selection
473// ----------------------------------------------------------------------------
474
475void 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
527int 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
535int wxCMPFUNC_CONV wxCompareInts(int *n, int *m)
536{
537    return *n - *m;
538}
539
540int 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
561void wxListBox::RefreshFromItemToEnd(int from)
562{
563    RefreshItems(from, GetCount() - from);
564}
565
566void 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
597void 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
626void wxListBox::RefreshAll()
627{
628    m_updateCount = -1;
629}
630
631void wxListBox::RefreshHorzScrollbar()
632{
633    m_maxWidth = 0; // recalculate it
634    m_updateScrollbarX = true;
635}
636
637void 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
691void 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
722void 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
752wxBorder wxListBox::GetDefaultBorder() const
753{
754    return wxBORDER_SUNKEN;
755}
756
757void 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
790void wxListBox::DoDrawRange(wxControlRenderer *renderer,
791                            int itemFirst, int itemLast)
792{
793    renderer->DrawItems(this, itemFirst, itemLast);
794}
795
796// ----------------------------------------------------------------------------
797// size calculations
798// ----------------------------------------------------------------------------
799
800bool 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
812void wxListBox::CalcItemsPerPage()
813{
814    m_lineHeight = GetRenderer()->GetListboxItemHeight(GetCharHeight());
815    m_itemsPerPage = GetClientSize().y / m_lineHeight;
816}
817
818int wxListBox::GetItemsPerPage() const
819{
820    if ( !m_itemsPerPage )
821    {
822        wxConstCast(this, wxListBox)->CalcItemsPerPage();
823    }
824
825    return m_itemsPerPage;
826}
827
828wxCoord wxListBox::GetLineHeight() const
829{
830    if ( !m_lineHeight )
831    {
832        wxConstCast(this, wxListBox)->CalcItemsPerPage();
833    }
834
835    return m_lineHeight;
836}
837
838wxCoord 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
859void 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
871void wxListBox::DoSetFirstItem(int n)
872{
873    SetCurrentItem(n);
874}
875
876void 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
896wxSize 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
933bool 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
960void 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
979bool 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
1034void 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
1047void 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
1075void 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
1090void 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
1130void 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
1148void wxListBox::SelectAndNotify(int item)
1149{
1150    DoSelect(item);
1151
1152    SendEvent(wxEVT_COMMAND_LISTBOX_SELECTED);
1153}
1154
1155void 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 */
1192bool 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 */
1259wxInputHandler *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
1270wxStdListboxInputHandler::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
1280int wxStdListboxInputHandler::HitTest(const wxListBox *lbox,
1281                                      const wxMouseEvent& event)
1282{
1283    int item = HitTestUnsafe(lbox, event);
1284
1285    return FixItemIndex(lbox, item);
1286}
1287
1288int 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
1298int 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
1315bool wxStdListboxInputHandler::IsValidIndex(const wxListBox *lbox, int item)
1316{
1317    return item >= 0 && (unsigned int)item < lbox->GetCount();
1318}
1319
1320wxControlAction
1321wxStdListboxInputHandler::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
1390bool 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
1485bool 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
1530bool 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
1580