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