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