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