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