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