]> git.saurik.com Git - wxWidgets.git/blob - src/univ/listbox.cpp
Use generic collapsible pane for wxOS2.
[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 return;
487 }
488
489 m_selections.Add(n);
490
491 RefreshItem(n);
492 }
493 //else: already selected
494 }
495 else // unselect
496 {
497 int index = m_selections.Index(n);
498 if ( index != wxNOT_FOUND )
499 {
500 m_selections.RemoveAt(index);
501
502 RefreshItem(n);
503 }
504 //else: not selected
505 }
506
507 // sanity check: a single selection listbox can't have more than one item
508 // selected
509 wxASSERT_MSG( HasMultipleSelection() || (m_selections.GetCount() < 2),
510 _T("multiple selected items in single selection lbox?") );
511
512 if ( select )
513 {
514 // the newly selected item becomes the current one
515 SetCurrentItem(n);
516 }
517 }
518
519 int wxListBox::GetSelection() const
520 {
521 wxCHECK_MSG( !HasMultipleSelection(), wxNOT_FOUND,
522 _T("use wxListBox::GetSelections for ths listbox") );
523
524 return m_selections.IsEmpty() ? wxNOT_FOUND : m_selections[0];
525 }
526
527 int wxCMPFUNC_CONV wxCompareInts(int *n, int *m)
528 {
529 return *n - *m;
530 }
531
532 int wxListBox::GetSelections(wxArrayInt& selections) const
533 {
534 // always return sorted array to the user
535 selections = m_selections;
536 unsigned int count = m_selections.GetCount();
537
538 // don't call sort on an empty array
539 if ( count )
540 {
541 selections.Sort(wxCompareInts);
542 }
543
544 return count;
545 }
546
547 // ----------------------------------------------------------------------------
548 // refresh logic: we use delayed refreshing which allows to avoid multiple
549 // refreshes (and hence flicker) in case when several listbox items are
550 // added/deleted/changed subsequently
551 // ----------------------------------------------------------------------------
552
553 void wxListBox::RefreshFromItemToEnd(int from)
554 {
555 RefreshItems(from, GetCount() - from);
556 }
557
558 void wxListBox::RefreshItems(int from, int count)
559 {
560 switch ( m_updateCount )
561 {
562 case 0:
563 m_updateFrom = from;
564 m_updateCount = count;
565 break;
566
567 case -1:
568 // we refresh everything anyhow
569 break;
570
571 default:
572 // add these items to the others which we have to refresh
573 if ( m_updateFrom < from )
574 {
575 count += from - m_updateFrom;
576 if ( m_updateCount < count )
577 m_updateCount = count;
578 }
579 else // m_updateFrom >= from
580 {
581 int updateLast = wxMax(m_updateFrom + m_updateCount,
582 from + count);
583 m_updateFrom = from;
584 m_updateCount = updateLast - m_updateFrom;
585 }
586 }
587 }
588
589 void wxListBox::RefreshItem(int n)
590 {
591 switch ( m_updateCount )
592 {
593 case 0:
594 // refresh this item only
595 m_updateFrom = n;
596 m_updateCount = 1;
597 break;
598
599 case -1:
600 // we refresh everything anyhow
601 break;
602
603 default:
604 // add this item to the others which we have to refresh
605 if ( m_updateFrom < n )
606 {
607 if ( m_updateCount < n - m_updateFrom + 1 )
608 m_updateCount = n - m_updateFrom + 1;
609 }
610 else // n <= m_updateFrom
611 {
612 m_updateCount += m_updateFrom - n;
613 m_updateFrom = n;
614 }
615 }
616 }
617
618 void wxListBox::RefreshAll()
619 {
620 m_updateCount = -1;
621 }
622
623 void wxListBox::RefreshHorzScrollbar()
624 {
625 m_maxWidth = 0; // recalculate it
626 m_updateScrollbarX = true;
627 }
628
629 void wxListBox::UpdateScrollbars()
630 {
631 wxSize size = GetClientSize();
632
633 // is our height enough to show all items?
634 unsigned int nLines = GetCount();
635 wxCoord lineHeight = GetLineHeight();
636 bool showScrollbarY = (int)nLines*lineHeight > size.y;
637
638 // check the width too if required
639 wxCoord charWidth, maxWidth;
640 bool showScrollbarX;
641 if ( HasHorzScrollbar() )
642 {
643 charWidth = GetCharWidth();
644 maxWidth = GetMaxWidth();
645 showScrollbarX = maxWidth > size.x;
646 }
647 else // never show it
648 {
649 charWidth = maxWidth = 0;
650 showScrollbarX = false;
651 }
652
653 // what should be the scrollbar range now?
654 int scrollRangeX = showScrollbarX
655 ? (maxWidth + charWidth - 1) / charWidth + 2 // FIXME
656 : 0;
657 int scrollRangeY = showScrollbarY
658 ? nLines +
659 (size.y % lineHeight + lineHeight - 1) / lineHeight
660 : 0;
661
662 // reset scrollbars if something changed: either the visibility status
663 // or the range of a scrollbar which is shown
664 if ( (showScrollbarY != m_showScrollbarY) ||
665 (showScrollbarX != m_showScrollbarX) ||
666 (showScrollbarY && (scrollRangeY != m_scrollRangeY)) ||
667 (showScrollbarX && (scrollRangeX != m_scrollRangeX)) )
668 {
669 int x, y;
670 GetViewStart(&x, &y);
671 SetScrollbars(charWidth, lineHeight,
672 scrollRangeX, scrollRangeY,
673 x, y);
674
675 m_showScrollbarX = showScrollbarX;
676 m_showScrollbarY = showScrollbarY;
677
678 m_scrollRangeX = scrollRangeX;
679 m_scrollRangeY = scrollRangeY;
680 }
681 }
682
683 void wxListBox::UpdateItems()
684 {
685 // only refresh the items which must be refreshed
686 if ( m_updateCount == -1 )
687 {
688 // refresh all
689 wxLogTrace(_T("listbox"), _T("Refreshing all"));
690
691 Refresh();
692 }
693 else
694 {
695 wxSize size = GetClientSize();
696 wxRect rect;
697 rect.width = size.x;
698 rect.height = size.y;
699 rect.y += m_updateFrom*GetLineHeight();
700 rect.height = m_updateCount*GetLineHeight();
701
702 // we don't need to calculate x position as we always refresh the
703 // entire line(s)
704 CalcScrolledPosition(0, rect.y, NULL, &rect.y);
705
706 wxLogTrace(_T("listbox"), _T("Refreshing items %d..%d (%d-%d)"),
707 m_updateFrom, m_updateFrom + m_updateCount - 1,
708 rect.GetTop(), rect.GetBottom());
709
710 Refresh(true, &rect);
711 }
712 }
713
714 void wxListBox::OnInternalIdle()
715 {
716 if ( m_updateScrollbarY || m_updateScrollbarX )
717 {
718 UpdateScrollbars();
719
720 m_updateScrollbarX =
721 m_updateScrollbarY = false;
722 }
723
724 if ( m_currentChanged )
725 {
726 DoEnsureVisible(m_current);
727
728 m_currentChanged = false;
729 }
730
731 if ( m_updateCount )
732 {
733 UpdateItems();
734
735 m_updateCount = 0;
736 }
737 wxListBoxBase::OnInternalIdle();
738 }
739
740 // ----------------------------------------------------------------------------
741 // drawing
742 // ----------------------------------------------------------------------------
743
744 wxBorder wxListBox::GetDefaultBorder() const
745 {
746 return wxBORDER_SUNKEN;
747 }
748
749 void wxListBox::DoDraw(wxControlRenderer *renderer)
750 {
751 // adjust the DC to account for scrolling
752 wxDC& dc = renderer->GetDC();
753 PrepareDC(dc);
754 dc.SetFont(GetFont());
755
756 // get the update rect
757 wxRect rectUpdate = GetUpdateClientRect();
758
759 int yTop, yBottom;
760 CalcUnscrolledPosition(0, rectUpdate.GetTop(), NULL, &yTop);
761 CalcUnscrolledPosition(0, rectUpdate.GetBottom(), NULL, &yBottom);
762
763 // get the items which must be redrawn
764 wxCoord lineHeight = GetLineHeight();
765 unsigned int itemFirst = yTop / lineHeight,
766 itemLast = (yBottom + lineHeight - 1) / lineHeight,
767 itemMax = m_strings->GetCount();
768
769 if ( itemFirst >= itemMax )
770 return;
771
772 if ( itemLast > itemMax )
773 itemLast = itemMax;
774
775 // do draw them
776 wxLogTrace(_T("listbox"), _T("Repainting items %d..%d"),
777 itemFirst, itemLast);
778
779 DoDrawRange(renderer, itemFirst, itemLast);
780 }
781
782 void wxListBox::DoDrawRange(wxControlRenderer *renderer,
783 int itemFirst, int itemLast)
784 {
785 renderer->DrawItems(this, itemFirst, itemLast);
786 }
787
788 // ----------------------------------------------------------------------------
789 // size calculations
790 // ----------------------------------------------------------------------------
791
792 bool wxListBox::SetFont(const wxFont& font)
793 {
794 if ( !wxControl::SetFont(font) )
795 return false;
796
797 CalcItemsPerPage();
798
799 RefreshAll();
800
801 return true;
802 }
803
804 void wxListBox::CalcItemsPerPage()
805 {
806 m_lineHeight = GetRenderer()->GetListboxItemHeight(GetCharHeight());
807 m_itemsPerPage = GetClientSize().y / m_lineHeight;
808 }
809
810 int wxListBox::GetItemsPerPage() const
811 {
812 if ( !m_itemsPerPage )
813 {
814 wxConstCast(this, wxListBox)->CalcItemsPerPage();
815 }
816
817 return m_itemsPerPage;
818 }
819
820 wxCoord wxListBox::GetLineHeight() const
821 {
822 if ( !m_lineHeight )
823 {
824 wxConstCast(this, wxListBox)->CalcItemsPerPage();
825 }
826
827 return m_lineHeight;
828 }
829
830 wxCoord wxListBox::GetMaxWidth() const
831 {
832 if ( m_maxWidth == 0 )
833 {
834 wxListBox *self = wxConstCast(this, wxListBox);
835 wxCoord width;
836 unsigned int count = m_strings->GetCount();
837 for ( unsigned int n = 0; n < count; n++ )
838 {
839 GetTextExtent(this->GetString(n), &width, NULL);
840 if ( width > m_maxWidth )
841 {
842 self->m_maxWidth = width;
843 self->m_maxWidthItem = n;
844 }
845 }
846 }
847
848 return m_maxWidth;
849 }
850
851 void wxListBox::OnSize(wxSizeEvent& event)
852 {
853 // recalculate the number of items per page
854 CalcItemsPerPage();
855
856 // the scrollbars might [dis]appear
857 m_updateScrollbarX =
858 m_updateScrollbarY = true;
859
860 event.Skip();
861 }
862
863 void wxListBox::DoSetFirstItem(int n)
864 {
865 SetCurrentItem(n);
866 }
867
868 void wxListBox::DoSetSize(int x, int y,
869 int width, int height,
870 int sizeFlags)
871 {
872 if ( GetWindowStyle() & wxLB_INT_HEIGHT )
873 {
874 // we must round up the height to an entire number of rows
875
876 // the client area must contain an int number of rows, so take borders
877 // into account
878 wxRect rectBorders = GetRenderer()->GetBorderDimensions(GetBorder());
879 wxCoord hBorders = rectBorders.y + rectBorders.height;
880
881 wxCoord hLine = GetLineHeight();
882 height = ((height - hBorders + hLine - 1) / hLine)*hLine + hBorders;
883 }
884
885 wxListBoxBase::DoSetSize(x, y, width, height, sizeFlags);
886 }
887
888 wxSize wxListBox::DoGetBestClientSize() const
889 {
890 wxCoord width = 0,
891 height = 0;
892
893 unsigned int count = m_strings->GetCount();
894 for ( unsigned int n = 0; n < count; n++ )
895 {
896 wxCoord w,h;
897 GetTextExtent(this->GetString(n), &w, &h);
898
899 if ( w > width )
900 width = w;
901 if ( h > height )
902 height = h;
903 }
904
905 // if the listbox is empty, still give it some non zero (even if
906 // arbitrary) size - otherwise, leave small margin around the strings
907 if ( !width )
908 width = 100;
909 else
910 width += 3*GetCharWidth();
911
912 if ( !height )
913 height = GetCharHeight();
914
915 // we need the height of the entire listbox, not just of one line
916 height *= wxMax(count, 7);
917
918 return wxSize(width, height);
919 }
920
921 // ----------------------------------------------------------------------------
922 // listbox actions
923 // ----------------------------------------------------------------------------
924
925 bool wxListBox::SendEvent(wxEventType type, int item)
926 {
927 wxCommandEvent event(type, m_windowId);
928 event.SetEventObject(this);
929
930 // use the current item by default
931 if ( item == -1 )
932 {
933 item = m_current;
934 }
935
936 // client data and string parameters only make sense if we have an item
937 if ( item != -1 )
938 {
939 if ( HasClientObjectData() )
940 event.SetClientObject(GetClientObject(item));
941 else if ( HasClientUntypedData() )
942 event.SetClientData(GetClientData(item));
943
944 event.SetString(GetString(item));
945 }
946
947 event.SetInt(item);
948
949 return GetEventHandler()->ProcessEvent(event);
950 }
951
952 void wxListBox::SetCurrentItem(int n)
953 {
954 if ( n != m_current )
955 {
956 if ( m_current != -1 )
957 RefreshItem(m_current);
958
959 m_current = n;
960
961 if ( m_current != -1 )
962 {
963 m_currentChanged = true;
964
965 RefreshItem(m_current);
966 }
967 }
968 //else: nothing to do
969 }
970
971 bool wxListBox::FindItem(const wxString& prefix, bool strictlyAfter)
972 {
973 unsigned int count = GetCount();
974 if ( count==0 )
975 {
976 // empty listbox, we can't find anything in it
977 return false;
978 }
979
980 // start either from the current item or from the next one if strictlyAfter
981 // is true
982 int first;
983 if ( strictlyAfter )
984 {
985 // the following line will set first correctly to 0 if there is no
986 // selection (m_current == -1)
987 first = m_current == (int)(count - 1) ? 0 : m_current + 1;
988 }
989 else // start with the current
990 {
991 first = m_current == -1 ? 0 : m_current;
992 }
993
994 int last = first == 0 ? count - 1 : first - 1;
995
996 // if this is not true we'd never exit from the loop below!
997 wxASSERT_MSG( first < (int)count && last < (int)count, _T("logic error") );
998
999 // precompute it outside the loop
1000 size_t len = prefix.length();
1001
1002 // loop over all items in the listbox
1003 for ( int item = first; item != (int)last; item < (int)(count - 1) ? item++ : item = 0 )
1004 {
1005 if ( wxStrnicmp(this->GetString(item).c_str(), prefix, len) == 0 )
1006 {
1007 SetCurrentItem(item);
1008
1009 if ( !(GetWindowStyle() & wxLB_MULTIPLE) )
1010 {
1011 DeselectAll(item);
1012 SelectAndNotify(item);
1013
1014 if ( GetWindowStyle() & wxLB_EXTENDED )
1015 AnchorSelection(item);
1016 }
1017
1018 return true;
1019 }
1020 }
1021
1022 // nothing found
1023 return false;
1024 }
1025
1026 void wxListBox::EnsureVisible(int n)
1027 {
1028 if ( m_updateScrollbarY )
1029 {
1030 UpdateScrollbars();
1031
1032 m_updateScrollbarX =
1033 m_updateScrollbarY = false;
1034 }
1035
1036 DoEnsureVisible(n);
1037 }
1038
1039 void wxListBox::DoEnsureVisible(int n)
1040 {
1041 if ( !m_showScrollbarY )
1042 {
1043 // nothing to do - everything is shown anyhow
1044 return;
1045 }
1046
1047 int first;
1048 GetViewStart(0, &first);
1049 if ( first > n )
1050 {
1051 // we need to scroll upwards, so make the current item appear on top
1052 // of the shown range
1053 Scroll(0, n);
1054 }
1055 else
1056 {
1057 int last = first + GetClientSize().y / GetLineHeight() - 1;
1058 if ( last < n )
1059 {
1060 // scroll down: the current item appears at the bottom of the
1061 // range
1062 Scroll(0, n - (last - first));
1063 }
1064 }
1065 }
1066
1067 void wxListBox::ChangeCurrent(int diff)
1068 {
1069 int current = m_current == -1 ? 0 : m_current;
1070
1071 current += diff;
1072
1073 int last = GetCount() - 1;
1074 if ( current < 0 )
1075 current = 0;
1076 else if ( current > last )
1077 current = last;
1078
1079 SetCurrentItem(current);
1080 }
1081
1082 void wxListBox::ExtendSelection(int itemTo)
1083 {
1084 // if we don't have the explicit values for selection start/end, make them
1085 // up
1086 if ( m_selAnchor == -1 )
1087 m_selAnchor = m_current;
1088
1089 if ( itemTo == -1 )
1090 itemTo = m_current;
1091
1092 // swap the start/end of selection range if necessary
1093 int itemFrom = m_selAnchor;
1094 if ( itemFrom > itemTo )
1095 {
1096 int itemTmp = itemFrom;
1097 itemFrom = itemTo;
1098 itemTo = itemTmp;
1099 }
1100
1101 // the selection should now include all items in the range between the
1102 // anchor and the specified item and only them
1103
1104 int n;
1105 for ( n = 0; n < itemFrom; n++ )
1106 {
1107 Deselect(n);
1108 }
1109
1110 for ( ; n <= itemTo; n++ )
1111 {
1112 SetSelection(n);
1113 }
1114
1115 unsigned int count = GetCount();
1116 for ( ; n < (int)count; n++ )
1117 {
1118 Deselect(n);
1119 }
1120 }
1121
1122 void wxListBox::DoSelect(int item, bool sel)
1123 {
1124 if ( item != -1 )
1125 {
1126 // go to this item first
1127 SetCurrentItem(item);
1128 }
1129
1130 // the current item is the one we want to change: either it was just
1131 // changed above to be the same as item or item == -1 in which we case we
1132 // are supposed to use the current one anyhow
1133 if ( m_current != -1 )
1134 {
1135 // [de]select it
1136 SetSelection(m_current, sel);
1137 }
1138 }
1139
1140 void wxListBox::SelectAndNotify(int item)
1141 {
1142 DoSelect(item);
1143
1144 SendEvent(wxEVT_COMMAND_LISTBOX_SELECTED);
1145 }
1146
1147 void wxListBox::Activate(int item)
1148 {
1149 if ( item != -1 )
1150 SetCurrentItem(item);
1151 else
1152 item = m_current;
1153
1154 if ( !(GetWindowStyle() & wxLB_MULTIPLE) )
1155 {
1156 DeselectAll(item);
1157 }
1158
1159 if ( item != -1 )
1160 {
1161 DoSelect(item);
1162
1163 SendEvent(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED);
1164 }
1165 }
1166
1167 // ----------------------------------------------------------------------------
1168 // input handling
1169 // ----------------------------------------------------------------------------
1170
1171 /*
1172 The numArg here is the listbox item index while the strArg is used
1173 differently for the different actions:
1174
1175 a) for wxACTION_LISTBOX_FIND it has the natural meaning: this is the string
1176 to find
1177
1178 b) for wxACTION_LISTBOX_SELECT and wxACTION_LISTBOX_EXTENDSEL it is used
1179 to decide if the listbox should send the notification event (it is empty)
1180 or not (it is not): this allows us to reuse the same action for when the
1181 user is dragging the mouse when it has been released although in the
1182 first case no notification is sent while in the second it is sent.
1183 */
1184 bool wxListBox::PerformAction(const wxControlAction& action,
1185 long numArg,
1186 const wxString& strArg)
1187 {
1188 int item = (int)numArg;
1189
1190 if ( action == wxACTION_LISTBOX_SETFOCUS )
1191 {
1192 SetCurrentItem(item);
1193 }
1194 else if ( action == wxACTION_LISTBOX_ACTIVATE )
1195 {
1196 Activate(item);
1197 }
1198 else if ( action == wxACTION_LISTBOX_TOGGLE )
1199 {
1200 if ( item == -1 )
1201 item = m_current;
1202
1203 if ( IsSelected(item) )
1204 DoUnselect(item);
1205 else
1206 SelectAndNotify(item);
1207 }
1208 else if ( action == wxACTION_LISTBOX_SELECT )
1209 {
1210 DeselectAll(item);
1211
1212 if ( strArg.empty() )
1213 SelectAndNotify(item);
1214 else
1215 DoSelect(item);
1216 }
1217 else if ( action == wxACTION_LISTBOX_SELECTADD )
1218 DoSelect(item);
1219 else if ( action == wxACTION_LISTBOX_UNSELECT )
1220 DoUnselect(item);
1221 else if ( action == wxACTION_LISTBOX_MOVEDOWN )
1222 ChangeCurrent(1);
1223 else if ( action == wxACTION_LISTBOX_MOVEUP )
1224 ChangeCurrent(-1);
1225 else if ( action == wxACTION_LISTBOX_PAGEDOWN )
1226 ChangeCurrent(GetItemsPerPage());
1227 else if ( action == wxACTION_LISTBOX_PAGEUP )
1228 ChangeCurrent(-GetItemsPerPage());
1229 else if ( action == wxACTION_LISTBOX_START )
1230 SetCurrentItem(0);
1231 else if ( action == wxACTION_LISTBOX_END )
1232 SetCurrentItem(GetCount() - 1);
1233 else if ( action == wxACTION_LISTBOX_UNSELECTALL )
1234 DeselectAll(item);
1235 else if ( action == wxACTION_LISTBOX_EXTENDSEL )
1236 ExtendSelection(item);
1237 else if ( action == wxACTION_LISTBOX_FIND )
1238 FindNextItem(strArg);
1239 else if ( action == wxACTION_LISTBOX_ANCHOR )
1240 AnchorSelection(item == -1 ? m_current : item);
1241 else if ( action == wxACTION_LISTBOX_SELECTALL ||
1242 action == wxACTION_LISTBOX_SELTOGGLE )
1243 wxFAIL_MSG(_T("unimplemented yet"));
1244 else
1245 return wxControl::PerformAction(action, numArg, strArg);
1246
1247 return true;
1248 }
1249
1250 /* static */
1251 wxInputHandler *wxListBox::GetStdInputHandler(wxInputHandler *handlerDef)
1252 {
1253 static wxStdListboxInputHandler s_handler(handlerDef);
1254
1255 return &s_handler;
1256 }
1257
1258 // ============================================================================
1259 // implementation of wxStdListboxInputHandler
1260 // ============================================================================
1261
1262 wxStdListboxInputHandler::wxStdListboxInputHandler(wxInputHandler *handler,
1263 bool toggleOnPressAlways)
1264 : wxStdInputHandler(handler)
1265 {
1266 m_btnCapture = 0;
1267 m_toggleOnPressAlways = toggleOnPressAlways;
1268 m_actionMouse = wxACTION_NONE;
1269 m_trackMouseOutside = true;
1270 }
1271
1272 int wxStdListboxInputHandler::HitTest(const wxListBox *lbox,
1273 const wxMouseEvent& event)
1274 {
1275 int item = HitTestUnsafe(lbox, event);
1276
1277 return FixItemIndex(lbox, item);
1278 }
1279
1280 int wxStdListboxInputHandler::HitTestUnsafe(const wxListBox *lbox,
1281 const wxMouseEvent& event)
1282 {
1283 wxPoint pt = event.GetPosition();
1284 pt -= lbox->GetClientAreaOrigin();
1285 int y;
1286 lbox->CalcUnscrolledPosition(0, pt.y, NULL, &y);
1287 return y / lbox->GetLineHeight();
1288 }
1289
1290 int wxStdListboxInputHandler::FixItemIndex(const wxListBox *lbox,
1291 int item)
1292 {
1293 if ( item < 0 )
1294 {
1295 // mouse is above the first item
1296 item = 0;
1297 }
1298 else if ( (unsigned int)item >= lbox->GetCount() )
1299 {
1300 // mouse is below the last item
1301 item = lbox->GetCount() - 1;
1302 }
1303
1304 return item;
1305 }
1306
1307 bool wxStdListboxInputHandler::IsValidIndex(const wxListBox *lbox, int item)
1308 {
1309 return item >= 0 && (unsigned int)item < lbox->GetCount();
1310 }
1311
1312 wxControlAction
1313 wxStdListboxInputHandler::SetupCapture(wxListBox *lbox,
1314 const wxMouseEvent& event,
1315 int item)
1316 {
1317 // we currently only allow selecting with the left mouse button, if we
1318 // do need to allow using other buttons too we might use the code
1319 // inside #if 0
1320 #if 0
1321 m_btnCapture = event.LeftDown()
1322 ? 1
1323 : event.RightDown()
1324 ? 3
1325 : 2;
1326 #else
1327 m_btnCapture = 1;
1328 #endif // 0/1
1329
1330 wxControlAction action;
1331 if ( lbox->HasMultipleSelection() )
1332 {
1333 if ( lbox->GetWindowStyle() & wxLB_MULTIPLE )
1334 {
1335 if ( m_toggleOnPressAlways )
1336 {
1337 // toggle the item right now
1338 action = wxACTION_LISTBOX_TOGGLE;
1339 }
1340 //else: later
1341
1342 m_actionMouse = wxACTION_LISTBOX_SETFOCUS;
1343 }
1344 else // wxLB_EXTENDED listbox
1345 {
1346 // simple click in an extended sel listbox clears the old
1347 // selection and adds the clicked item to it then, ctrl-click
1348 // toggles an item to it and shift-click adds a range between
1349 // the old selection anchor and the clicked item
1350 if ( event.ControlDown() )
1351 {
1352 lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item);
1353
1354 action = wxACTION_LISTBOX_TOGGLE;
1355 }
1356 else if ( event.ShiftDown() )
1357 {
1358 action = wxACTION_LISTBOX_EXTENDSEL;
1359 }
1360 else // simple click
1361 {
1362 lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item);
1363
1364 action = wxACTION_LISTBOX_SELECT;
1365 }
1366
1367 m_actionMouse = wxACTION_LISTBOX_EXTENDSEL;
1368 }
1369 }
1370 else // single selection
1371 {
1372 m_actionMouse =
1373 action = wxACTION_LISTBOX_SELECT;
1374 }
1375
1376 // by default we always do track it
1377 m_trackMouseOutside = true;
1378
1379 return action;
1380 }
1381
1382 bool wxStdListboxInputHandler::HandleKey(wxInputConsumer *consumer,
1383 const wxKeyEvent& event,
1384 bool pressed)
1385 {
1386 // we're only interested in the key press events
1387 if ( pressed && !event.AltDown() )
1388 {
1389 bool isMoveCmd = true;
1390 int style = consumer->GetInputWindow()->GetWindowStyle();
1391
1392 wxControlAction action;
1393 wxString strArg;
1394
1395 int keycode = event.GetKeyCode();
1396 switch ( keycode )
1397 {
1398 // movement
1399 case WXK_UP:
1400 action = wxACTION_LISTBOX_MOVEUP;
1401 break;
1402
1403 case WXK_DOWN:
1404 action = wxACTION_LISTBOX_MOVEDOWN;
1405 break;
1406
1407 case WXK_PAGEUP:
1408 action = wxACTION_LISTBOX_PAGEUP;
1409 break;
1410
1411 case WXK_PAGEDOWN:
1412 action = wxACTION_LISTBOX_PAGEDOWN;
1413 break;
1414
1415 case WXK_HOME:
1416 action = wxACTION_LISTBOX_START;
1417 break;
1418
1419 case WXK_END:
1420 action = wxACTION_LISTBOX_END;
1421 break;
1422
1423 // selection
1424 case WXK_SPACE:
1425 if ( style & wxLB_MULTIPLE )
1426 {
1427 action = wxACTION_LISTBOX_TOGGLE;
1428 isMoveCmd = false;
1429 }
1430 break;
1431
1432 case WXK_RETURN:
1433 action = wxACTION_LISTBOX_ACTIVATE;
1434 isMoveCmd = false;
1435 break;
1436
1437 default:
1438 if ( (keycode < 255) && wxIsalnum((wxChar)keycode) )
1439 {
1440 action = wxACTION_LISTBOX_FIND;
1441 strArg = (wxChar)keycode;
1442 }
1443 }
1444
1445 if ( !action.IsEmpty() )
1446 {
1447 consumer->PerformAction(action, -1, strArg);
1448
1449 if ( isMoveCmd )
1450 {
1451 if ( style & wxLB_SINGLE )
1452 {
1453 // the current item is always the one selected
1454 consumer->PerformAction(wxACTION_LISTBOX_SELECT);
1455 }
1456 else if ( style & wxLB_EXTENDED )
1457 {
1458 if ( event.ShiftDown() )
1459 consumer->PerformAction(wxACTION_LISTBOX_EXTENDSEL);
1460 else
1461 {
1462 // select the item and make it the new selection anchor
1463 consumer->PerformAction(wxACTION_LISTBOX_SELECT);
1464 consumer->PerformAction(wxACTION_LISTBOX_ANCHOR);
1465 }
1466 }
1467 //else: nothing to do for multiple selection listboxes
1468 }
1469
1470 return true;
1471 }
1472 }
1473
1474 return wxStdInputHandler::HandleKey(consumer, event, pressed);
1475 }
1476
1477 bool wxStdListboxInputHandler::HandleMouse(wxInputConsumer *consumer,
1478 const wxMouseEvent& event)
1479 {
1480 wxListBox *lbox = wxStaticCast(consumer->GetInputWindow(), wxListBox);
1481 int item = HitTest(lbox, event);
1482 wxControlAction action;
1483
1484 // when the left mouse button is pressed, capture the mouse and track the
1485 // item under mouse (if the mouse leaves the window, we will still be
1486 // getting the mouse move messages generated by wxScrollWindow)
1487 if ( event.LeftDown() )
1488 {
1489 // capture the mouse to track the selected item
1490 lbox->CaptureMouse();
1491
1492 action = SetupCapture(lbox, event, item);
1493 }
1494 else if ( m_btnCapture && event.ButtonUp(m_btnCapture) )
1495 {
1496 // when the left mouse button is released, release the mouse too
1497 wxWindow *winCapture = wxWindow::GetCapture();
1498 if ( winCapture )
1499 {
1500 winCapture->ReleaseMouse();
1501 m_btnCapture = 0;
1502
1503 action = m_actionMouse;
1504 }
1505 //else: the mouse wasn't presed over the listbox, only released here
1506 }
1507 else if ( event.LeftDClick() )
1508 {
1509 action = wxACTION_LISTBOX_ACTIVATE;
1510 }
1511
1512 if ( !action.IsEmpty() )
1513 {
1514 lbox->PerformAction(action, item);
1515
1516 return true;
1517 }
1518
1519 return wxStdInputHandler::HandleMouse(consumer, event);
1520 }
1521
1522 bool wxStdListboxInputHandler::HandleMouseMove(wxInputConsumer *consumer,
1523 const wxMouseEvent& event)
1524 {
1525 wxWindow *winCapture = wxWindow::GetCapture();
1526 if ( winCapture && (event.GetEventObject() == winCapture) )
1527 {
1528 wxListBox *lbox = wxStaticCast(consumer->GetInputWindow(), wxListBox);
1529
1530 if ( !m_btnCapture || !m_trackMouseOutside )
1531 {
1532 // someone captured the mouse for us (we always set m_btnCapture
1533 // when we do it ourselves): in this case we only react to
1534 // the mouse messages when they happen inside the listbox
1535 if ( lbox->HitTest(event.GetPosition()) != wxHT_WINDOW_INSIDE )
1536 return false;
1537 }
1538
1539 int item = HitTest(lbox, event);
1540 if ( !m_btnCapture )
1541 {
1542 // now that we have the mouse inside the listbox, do capture it
1543 // normally - but ensure that we will still ignore the outside
1544 // events
1545 SetupCapture(lbox, event, item);
1546
1547 m_trackMouseOutside = false;
1548 }
1549
1550 if ( IsValidIndex(lbox, item) )
1551 {
1552 // pass something into strArg to tell the listbox that it shouldn't
1553 // send the notification message: see PerformAction() above
1554 lbox->PerformAction(m_actionMouse, item, _T("no"));
1555 }
1556 // else: don't pass invalid index to the listbox
1557 }
1558 else // we don't have capture any more
1559 {
1560 if ( m_btnCapture )
1561 {
1562 // if we lost capture unexpectedly (someone else took the capture
1563 // from us), return to a consistent state
1564 m_btnCapture = 0;
1565 }
1566 }
1567
1568 return wxStdInputHandler::HandleMouseMove(consumer, event);
1569 }
1570
1571 #endif // wxUSE_LISTBOX