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