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