Various wxUniversal/wxMicroWindows fixes
[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 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
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 // don't generate select events while the mouse is captured, we will only
809 // send them once it is released
810 if ( (type == wxEVT_COMMAND_LISTBOX_SELECTED) && (GetCapture() == this) )
811 return FALSE;
812
813 wxCommandEvent event(type, m_windowId);
814 event.SetEventObject(this);
815
816 // use the current item by default
817 if ( item == -1 )
818 {
819 item = m_current;
820 }
821
822 // client data and string parameters only make sense if we have an item
823 if ( item != -1 )
824 {
825 if ( HasClientObjectData() )
826 event.SetClientObject(GetClientObject(item));
827 else if ( HasClientUntypedData() )
828 event.SetClientData(GetClientData(item));
829
830 event.SetString(GetString(item));
831 }
832
833 event.m_commandInt = item;
834
835 return GetEventHandler()->ProcessEvent(event);
836 }
837
838 void wxListBox::SetCurrentItem(int n)
839 {
840 if ( n != m_current )
841 {
842 if ( m_current != -1 )
843 RefreshItem(m_current);
844
845 m_current = n;
846
847 if ( m_current != -1 )
848 {
849 m_currentChanged = TRUE;
850
851 RefreshItem(m_current);
852 }
853 }
854 //else: nothing to do
855 }
856
857 bool wxListBox::FindItem(const wxString& prefix, bool strictlyAfter)
858 {
859 int count = GetCount();
860 if ( !count )
861 {
862 // empty listbox, we can't find anything in it
863 return FALSE;
864 }
865
866 // start either from the current item or from the next one if strictlyAfter
867 // is true
868 int first;
869 if ( strictlyAfter )
870 {
871 // the following line will set first correctly to 0 if there is no
872 // selection (m_current == -1)
873 first = m_current == count - 1 ? 0 : m_current + 1;
874 }
875 else // start with the current
876 {
877 first = m_current == -1 ? 0 : m_current;
878 }
879
880 int last = first == 0 ? count - 1 : first - 1;
881
882 // if this is not true we'd never exit from the loop below!
883 wxASSERT_MSG( first < count && last < count, _T("logic error") );
884
885 // precompute it outside the loop
886 size_t len = prefix.length();
887
888 // loop over all items in the listbox
889 for ( int item = first; item != last; item < count - 1 ? item++ : item = 0 )
890 {
891 if ( wxStrnicmp(m_strings[item], prefix, len) == 0 )
892 {
893 SetCurrentItem(item);
894
895 if ( !(GetWindowStyle() & wxLB_MULTIPLE) )
896 {
897 DeselectAll(item);
898 SelectAndNotify(item);
899
900 if ( GetWindowStyle() & wxLB_EXTENDED )
901 AnchorSelection(item);
902 }
903
904 return TRUE;
905 }
906 }
907
908 // nothing found
909 return FALSE;
910 }
911
912 void wxListBox::EnsureVisible(int n)
913 {
914 if ( m_updateScrollbarY )
915 {
916 UpdateScrollbars();
917
918 m_updateScrollbarX =
919 m_updateScrollbarY = FALSE;
920 }
921
922 DoEnsureVisible(n);
923 }
924
925 void wxListBox::DoEnsureVisible(int n)
926 {
927 if ( !m_showScrollbarY )
928 {
929 // nothing to do - everything is shown anyhow
930 return;
931 }
932
933 int first;
934 GetViewStart(0, &first);
935 if ( first > n )
936 {
937 // we need to scroll upwards, so make the current item appear on top
938 // of the shown range
939 Scroll(0, n);
940 }
941 else
942 {
943 int last = first + GetClientSize().y / GetLineHeight() - 1;
944 if ( last < n )
945 {
946 // scroll down: the current item appears at the bottom of the
947 // range
948 Scroll(0, n - (last - first));
949 }
950 }
951 }
952
953 void wxListBox::ChangeCurrent(int diff)
954 {
955 int current = m_current == -1 ? 0 : m_current;
956
957 current += diff;
958
959 int last = GetCount() - 1;
960 if ( current < 0 )
961 current = 0;
962 else if ( current > last )
963 current = last;
964
965 SetCurrentItem(current);
966 }
967
968 void wxListBox::ExtendSelection(int itemTo)
969 {
970 // if we don't have the explicit values for selection start/end, make them
971 // up
972 if ( m_selAnchor == -1 )
973 m_selAnchor = m_current;
974
975 if ( itemTo == -1 )
976 itemTo = m_current;
977
978 // swap the start/end of selection range if necessary
979 int itemFrom = m_selAnchor;
980 if ( itemFrom > itemTo )
981 {
982 int itemTmp = itemFrom;
983 itemFrom = itemTo;
984 itemTo = itemTmp;
985 }
986
987 // the selection should now include all items in the range between the
988 // anchor and the specified item and only them
989
990 int n;
991 for ( n = 0; n < itemFrom; n++ )
992 {
993 Deselect(n);
994 }
995
996 for ( ; n <= itemTo; n++ )
997 {
998 SetSelection(n);
999 }
1000
1001 int count = GetCount();
1002 for ( ; n < count; n++ )
1003 {
1004 Deselect(n);
1005 }
1006 }
1007
1008 void wxListBox::Select(bool sel, int item)
1009 {
1010 if ( item != -1 )
1011 {
1012 // go to this item first
1013 SetCurrentItem(item);
1014 }
1015
1016 // the current item is the one we want to change: either it was just
1017 // changed above to be the same as item or item == -1 in which we case we
1018 // are supposed to use the current one anyhow
1019 if ( m_current != -1 )
1020 {
1021 // [de]select it
1022 SetSelection(m_current, sel);
1023 }
1024 }
1025
1026 void wxListBox::SelectAndNotify(int item)
1027 {
1028 Select(TRUE, item);
1029
1030 SendEvent(wxEVT_COMMAND_LISTBOX_SELECTED);
1031 }
1032
1033 void wxListBox::Activate(int item)
1034 {
1035 if ( item != -1 )
1036 SetCurrentItem(item);
1037 else
1038 item = m_current;
1039
1040 if ( !(GetWindowStyle() & wxLB_MULTIPLE) )
1041 {
1042 DeselectAll(item);
1043 }
1044
1045 if ( item != -1 )
1046 {
1047 Select(TRUE, item);
1048
1049 SendEvent(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED);
1050 }
1051 }
1052
1053 // ----------------------------------------------------------------------------
1054 // input handling
1055 // ----------------------------------------------------------------------------
1056
1057 bool wxListBox::PerformAction(const wxControlAction& action,
1058 long numArg,
1059 const wxString& strArg)
1060 {
1061 int item = (int)numArg;
1062
1063 if ( action == wxACTION_LISTBOX_SETFOCUS )
1064 SetCurrentItem(item);
1065 else if ( action == wxACTION_LISTBOX_ACTIVATE )
1066 Activate(item);
1067 else if ( action == wxACTION_LISTBOX_TOGGLE )
1068 {
1069 if ( item == -1 )
1070 item = m_current;
1071
1072 if ( IsSelected(item) )
1073 Unselect(item);
1074 else
1075 SelectAndNotify(item);
1076 }
1077 else if ( action == wxACTION_LISTBOX_SELECT )
1078 {
1079 DeselectAll(item);
1080 SelectAndNotify(item);
1081 }
1082 else if ( action == wxACTION_LISTBOX_SELECTADD )
1083 Select(TRUE, item);
1084 else if ( action == wxACTION_LISTBOX_UNSELECT )
1085 Select(FALSE, item);
1086 else if ( action == wxACTION_LISTBOX_MOVEDOWN )
1087 ChangeCurrent(1);
1088 else if ( action == wxACTION_LISTBOX_MOVEUP )
1089 ChangeCurrent(-1);
1090 else if ( action == wxACTION_LISTBOX_PAGEDOWN )
1091 ChangeCurrent(GetItemsPerPage());
1092 else if ( action == wxACTION_LISTBOX_PAGEUP )
1093 ChangeCurrent(-GetItemsPerPage());
1094 else if ( action == wxACTION_LISTBOX_START )
1095 SetCurrentItem(0);
1096 else if ( action == wxACTION_LISTBOX_END )
1097 SetCurrentItem(GetCount() - 1);
1098 else if ( action == wxACTION_LISTBOX_UNSELECTALL )
1099 DeselectAll(item);
1100 else if ( action == wxACTION_LISTBOX_EXTENDSEL )
1101 ExtendSelection(item);
1102 else if ( action == wxACTION_LISTBOX_FIND )
1103 FindNextItem(strArg);
1104 else if ( action == wxACTION_LISTBOX_ANCHOR )
1105 AnchorSelection(item == -1 ? m_current : item);
1106 else if ( action == wxACTION_LISTBOX_SELECTALL ||
1107 action == wxACTION_LISTBOX_SELTOGGLE )
1108 wxFAIL_MSG(_T("unimplemented yet"));
1109 else
1110 return wxControl::PerformAction(action, numArg, strArg);
1111
1112 return TRUE;
1113 }
1114
1115 // ============================================================================
1116 // implementation of wxStdListboxInputHandler
1117 // ============================================================================
1118
1119 wxStdListboxInputHandler::wxStdListboxInputHandler(wxInputHandler *handler,
1120 bool toggleOnPressAlways)
1121 : wxStdInputHandler(handler)
1122 {
1123 m_btnCapture = 0;
1124 m_toggleOnPressAlways = toggleOnPressAlways;
1125 m_actionMouse = wxACTION_NONE;
1126 m_trackMouseOutside = TRUE;
1127 }
1128
1129 int wxStdListboxInputHandler::HitTest(const wxListBox *lbox,
1130 const wxMouseEvent& event)
1131 {
1132 int item = HitTestUnsafe(lbox, event);
1133
1134 return FixItemIndex(lbox, item);
1135 }
1136
1137 int wxStdListboxInputHandler::HitTestUnsafe(const wxListBox *lbox,
1138 const wxMouseEvent& event)
1139 {
1140 wxPoint pt = event.GetPosition();
1141 pt -= lbox->GetClientAreaOrigin();
1142 int y;
1143 lbox->CalcUnscrolledPosition(0, pt.y, NULL, &y);
1144 return y / lbox->GetLineHeight();
1145 }
1146
1147 int wxStdListboxInputHandler::FixItemIndex(const wxListBox *lbox,
1148 int item)
1149 {
1150 if ( item < 0 )
1151 {
1152 // mouse is above the first item
1153 item = 0;
1154 }
1155 else if ( item >= lbox->GetCount() )
1156 {
1157 // mouse is below the last item
1158 item = lbox->GetCount() - 1;
1159 }
1160
1161 return item;
1162 }
1163
1164 bool wxStdListboxInputHandler::IsValidIndex(const wxListBox *lbox, int item)
1165 {
1166 return item >= 0 && item < lbox->GetCount();
1167 }
1168
1169 wxControlAction
1170 wxStdListboxInputHandler::SetupCapture(wxListBox *lbox,
1171 const wxMouseEvent& event,
1172 int item)
1173 {
1174 // we currently only allow selecting with the left mouse button, if we
1175 // do need to allow using other buttons too we might use the code
1176 // inside #if 0
1177 #if 0
1178 m_btnCapture = event.LeftDown()
1179 ? 1
1180 : event.RightDown()
1181 ? 3
1182 : 2;
1183 #else
1184 m_btnCapture = 1;
1185 #endif // 0/1
1186
1187 wxControlAction action;
1188 if ( lbox->HasMultipleSelection() )
1189 {
1190 if ( lbox->GetWindowStyle() & wxLB_MULTIPLE )
1191 {
1192 if ( m_toggleOnPressAlways )
1193 {
1194 // toggle the item right now
1195 action = wxACTION_LISTBOX_TOGGLE;
1196 }
1197 //else: later
1198
1199 m_actionMouse = wxACTION_LISTBOX_SETFOCUS;
1200 }
1201 else // wxLB_EXTENDED listbox
1202 {
1203 // simple click in an extended sel listbox clears the old
1204 // selection and adds the clicked item to it then, ctrl-click
1205 // toggles an item to it and shift-click adds a range between
1206 // the old selection anchor and the clicked item
1207 if ( event.ControlDown() )
1208 {
1209 lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item);
1210
1211 action = wxACTION_LISTBOX_TOGGLE;
1212 }
1213 else if ( event.ShiftDown() )
1214 {
1215 action = wxACTION_LISTBOX_EXTENDSEL;
1216 }
1217 else // simple click
1218 {
1219 lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item);
1220
1221 action = wxACTION_LISTBOX_SELECT;
1222 }
1223
1224 m_actionMouse = wxACTION_LISTBOX_EXTENDSEL;
1225 }
1226 }
1227 else // single selection
1228 {
1229 m_actionMouse =
1230 action = wxACTION_LISTBOX_SELECT;
1231 }
1232
1233 // by default we always do track it
1234 m_trackMouseOutside = TRUE;
1235
1236 return action;
1237 }
1238
1239 bool wxStdListboxInputHandler::HandleKey(wxControl *control,
1240 const wxKeyEvent& event,
1241 bool pressed)
1242 {
1243 // we're only interested in the key press events
1244 if ( pressed && !event.AltDown() )
1245 {
1246 bool isMoveCmd = TRUE;
1247 int style = control->GetWindowStyle();
1248
1249 wxControlAction action;
1250 wxString strArg;
1251
1252 int keycode = event.GetKeyCode();
1253 switch ( keycode )
1254 {
1255 // movement
1256 case WXK_UP: action = wxACTION_LISTBOX_MOVEUP; break;
1257 case WXK_DOWN: action = wxACTION_LISTBOX_MOVEDOWN; break;
1258 case WXK_PRIOR: action = wxACTION_LISTBOX_PAGEUP; break;
1259 case WXK_NEXT: action = wxACTION_LISTBOX_PAGEDOWN; break;
1260 case WXK_HOME: action = wxACTION_LISTBOX_START; break;
1261 case WXK_END: action = wxACTION_LISTBOX_END; break;
1262
1263 // selection
1264 case WXK_SPACE:
1265 if ( style & wxLB_MULTIPLE )
1266 {
1267 action = wxACTION_LISTBOX_TOGGLE;
1268 isMoveCmd = FALSE;
1269 }
1270 break;
1271
1272 case WXK_RETURN:
1273 action = wxACTION_LISTBOX_ACTIVATE;
1274 isMoveCmd = FALSE;
1275 break;
1276
1277 default:
1278 if ( (keycode < 255) && wxIsalnum(keycode) )
1279 {
1280 action = wxACTION_LISTBOX_FIND;
1281 strArg = (wxChar)keycode;
1282 }
1283 }
1284
1285 if ( !!action )
1286 {
1287 control->PerformAction(action, -1, strArg);
1288
1289 if ( isMoveCmd )
1290 {
1291 if ( style & wxLB_SINGLE )
1292 {
1293 // the current item is always the one selected
1294 control->PerformAction(wxACTION_LISTBOX_SELECT);
1295 }
1296 else if ( style & wxLB_EXTENDED )
1297 {
1298 if ( event.ShiftDown() )
1299 control->PerformAction(wxACTION_LISTBOX_EXTENDSEL);
1300 else
1301 {
1302 // select the item and make it the new selection anchor
1303 control->PerformAction(wxACTION_LISTBOX_SELECT);
1304 control->PerformAction(wxACTION_LISTBOX_ANCHOR);
1305 }
1306 }
1307 //else: nothing to do for multiple selection listboxes
1308 }
1309
1310 return TRUE;
1311 }
1312 }
1313
1314 return wxStdInputHandler::HandleKey(control, event, pressed);
1315 }
1316
1317 bool wxStdListboxInputHandler::HandleMouse(wxControl *control,
1318 const wxMouseEvent& event)
1319 {
1320 wxListBox *lbox = wxStaticCast(control, wxListBox);
1321 int item = HitTest(lbox, event);
1322 wxControlAction action;
1323
1324 // when the left mouse button is pressed, capture the mouse and track the
1325 // item under mouse (if the mouse leaves the window, we will still be
1326 // getting the mouse move messages generated by wxScrollWindow)
1327 if ( event.LeftDown() )
1328 {
1329 // capture the mouse to track the selected item
1330 lbox->CaptureMouse();
1331
1332 action = SetupCapture(lbox, event, item);
1333 }
1334 else if ( m_btnCapture && event.ButtonUp(m_btnCapture) )
1335 {
1336 // when the left mouse button is released, release the mouse too
1337 wxWindow *winCapture = wxWindow::GetCapture();
1338 if ( winCapture )
1339 {
1340 winCapture->ReleaseMouse();
1341 m_btnCapture = 0;
1342
1343 // generate the last event to triiger sending the selection event
1344 action = m_actionMouse;
1345 }
1346 //else: the mouse wasn't presed over the listbox, only released here
1347 }
1348 else if ( event.LeftDClick() )
1349 {
1350 action = wxACTION_LISTBOX_ACTIVATE;
1351 }
1352
1353 if ( !!action )
1354 {
1355 lbox->PerformAction(action, item);
1356
1357 return TRUE;
1358 }
1359
1360 return wxStdInputHandler::HandleMouse(control, event);
1361 }
1362
1363 bool wxStdListboxInputHandler::HandleMouseMove(wxControl *control,
1364 const wxMouseEvent& event)
1365 {
1366 wxWindow *winCapture = wxWindow::GetCapture();
1367 if ( winCapture && (event.GetEventObject() == winCapture) )
1368 {
1369 wxListBox *lbox = wxStaticCast(control, wxListBox);
1370
1371 if ( !m_btnCapture || !m_trackMouseOutside )
1372 {
1373 // someone captured the mouse for us (we always set m_btnCapture
1374 // when we do it ourselves): in this case we only react to
1375 // the mouse messages when they happen inside the listbox
1376 if ( lbox->HitTest(event.GetPosition()) != wxHT_WINDOW_INSIDE )
1377 return FALSE;
1378 }
1379
1380 int item = HitTest(lbox, event);
1381 if ( !m_btnCapture )
1382 {
1383 // now that we have the mouse inside the listbox, do capture it
1384 // normally - but ensure that we will still ignore the outside
1385 // events
1386 SetupCapture(lbox, event, item);
1387
1388 m_trackMouseOutside = FALSE;
1389 }
1390
1391 if ( IsValidIndex(lbox, item) )
1392 {
1393 lbox->PerformAction(m_actionMouse, item);
1394 }
1395 // else: don't pass invalid index to the listbox
1396 }
1397 else // we don't have capture any more
1398 {
1399 if ( m_btnCapture )
1400 {
1401 // if we lost capture unexpectedly (someone else took the capture
1402 // from us), return to a consistent state
1403 m_btnCapture = 0;
1404 }
1405 }
1406
1407 return wxStdInputHandler::HandleMouseMove(control, event);
1408 }
1409
1410 #endif // wxUSE_LISTBOX