]> git.saurik.com Git - wxWidgets.git/blob - src/generic/vlbox.cpp
Handle Shift-TAB correctly in wxOSX/Carbon wxComboBox.
[wxWidgets.git] / src / generic / vlbox.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/vlbox.cpp
3 // Purpose: implementation of wxVListBox
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 31.05.03
7 // RCS-ID: $Id$
8 // Copyright: (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif
26
27 #if wxUSE_LISTBOX
28
29 #include "wx/vlbox.h"
30
31 #ifndef WX_PRECOMP
32 #include "wx/settings.h"
33 #include "wx/dcclient.h"
34 #include "wx/listbox.h"
35 #endif //WX_PRECOMP
36
37 #include "wx/dcbuffer.h"
38 #include "wx/selstore.h"
39 #include "wx/renderer.h"
40
41 // ----------------------------------------------------------------------------
42 // event tables
43 // ----------------------------------------------------------------------------
44
45 BEGIN_EVENT_TABLE(wxVListBox, wxVScrolledWindow)
46 EVT_PAINT(wxVListBox::OnPaint)
47
48 EVT_KEY_DOWN(wxVListBox::OnKeyDown)
49 EVT_LEFT_DOWN(wxVListBox::OnLeftDown)
50 EVT_LEFT_DCLICK(wxVListBox::OnLeftDClick)
51
52 EVT_SET_FOCUS(wxVListBox::OnSetOrKillFocus)
53 EVT_KILL_FOCUS(wxVListBox::OnSetOrKillFocus)
54
55 EVT_SIZE(wxVListBox::OnSize)
56 END_EVENT_TABLE()
57
58 // ============================================================================
59 // implementation
60 // ============================================================================
61
62 IMPLEMENT_ABSTRACT_CLASS(wxVListBox, wxVScrolledWindow)
63 const char wxVListBoxNameStr[] = "wxVListBox";
64
65 // ----------------------------------------------------------------------------
66 // wxVListBox creation
67 // ----------------------------------------------------------------------------
68
69 void wxVListBox::Init()
70 {
71 m_current =
72 m_anchor = wxNOT_FOUND;
73 m_selStore = NULL;
74 }
75
76 bool wxVListBox::Create(wxWindow *parent,
77 wxWindowID id,
78 const wxPoint& pos,
79 const wxSize& size,
80 long style,
81 const wxString& name)
82 {
83 #ifdef __WXMSW__
84 if ( (style & wxBORDER_MASK) == wxDEFAULT )
85 style |= wxBORDER_THEME;
86 #endif
87
88 style |= wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE;
89 if ( !wxVScrolledWindow::Create(parent, id, pos, size, style, name) )
90 return false;
91
92 if ( style & wxLB_MULTIPLE )
93 m_selStore = new wxSelectionStore;
94
95 // make sure the native widget has the right colour since we do
96 // transparent drawing by default
97 SetBackgroundColour(GetBackgroundColour());
98
99 // leave m_colBgSel in an invalid state: it means for OnDrawBackground()
100 // to use wxRendererNative instead of painting selection bg ourselves
101 m_colBgSel = wxNullColour;
102
103 // flicker-free drawing requires this
104 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
105
106 return true;
107 }
108
109 wxVListBox::~wxVListBox()
110 {
111 delete m_selStore;
112 }
113
114 void wxVListBox::SetItemCount(size_t count)
115 {
116 // don't leave the current index invalid
117 if ( m_current != wxNOT_FOUND && (size_t)m_current >= count )
118 m_current = count - 1; // also ok when count == 0 as wxNOT_FOUND == -1
119
120 if ( m_selStore )
121 {
122 // tell the selection store that our number of items has changed
123 m_selStore->SetItemCount(count);
124 }
125
126 SetRowCount(count);
127 }
128
129 // ----------------------------------------------------------------------------
130 // selection handling
131 // ----------------------------------------------------------------------------
132
133 bool wxVListBox::IsSelected(size_t line) const
134 {
135 return m_selStore ? m_selStore->IsSelected(line) : (int)line == m_current;
136 }
137
138 bool wxVListBox::Select(size_t item, bool select)
139 {
140 wxCHECK_MSG( m_selStore, false,
141 wxT("Select() may only be used with multiselection listbox") );
142
143 wxCHECK_MSG( item < GetItemCount(), false,
144 wxT("Select(): invalid item index") );
145
146 bool changed = m_selStore->SelectItem(item, select);
147 if ( changed )
148 {
149 // selection really changed
150 RefreshRow(item);
151 }
152
153 DoSetCurrent(item);
154
155 return changed;
156 }
157
158 bool wxVListBox::SelectRange(size_t from, size_t to)
159 {
160 wxCHECK_MSG( m_selStore, false,
161 wxT("SelectRange() may only be used with multiselection listbox") );
162
163 // make sure items are in correct order
164 if ( from > to )
165 {
166 size_t tmp = from;
167 from = to;
168 to = tmp;
169 }
170
171 wxCHECK_MSG( to < GetItemCount(), false,
172 wxT("SelectRange(): invalid item index") );
173
174 wxArrayInt changed;
175 if ( !m_selStore->SelectRange(from, to, true, &changed) )
176 {
177 // too many items have changed, we didn't record them in changed array
178 // so we have no choice but to refresh everything between from and to
179 RefreshRows(from, to);
180 }
181 else // we've got the indices of the changed items
182 {
183 const size_t count = changed.GetCount();
184 if ( !count )
185 {
186 // nothing changed
187 return false;
188 }
189
190 // refresh just the lines which have really changed
191 for ( size_t n = 0; n < count; n++ )
192 {
193 RefreshRow(changed[n]);
194 }
195 }
196
197 // something changed
198 return true;
199 }
200
201 bool wxVListBox::DoSelectAll(bool select)
202 {
203 wxCHECK_MSG( m_selStore, false,
204 wxT("SelectAll may only be used with multiselection listbox") );
205
206 size_t count = GetItemCount();
207 if ( count )
208 {
209 wxArrayInt changed;
210 if ( !m_selStore->SelectRange(0, count - 1, select) ||
211 !changed.IsEmpty() )
212 {
213 Refresh();
214
215 // something changed
216 return true;
217 }
218 }
219
220 return false;
221 }
222
223 bool wxVListBox::DoSetCurrent(int current)
224 {
225 wxASSERT_MSG( current == wxNOT_FOUND ||
226 (current >= 0 && (size_t)current < GetItemCount()),
227 wxT("wxVListBox::DoSetCurrent(): invalid item index") );
228
229 if ( current == m_current )
230 {
231 // nothing to do
232 return false;
233 }
234
235 if ( m_current != wxNOT_FOUND )
236 RefreshRow(m_current);
237
238 m_current = current;
239
240 if ( m_current != wxNOT_FOUND )
241 {
242 // if the line is not visible at all, we scroll it into view but we
243 // don't need to refresh it -- it will be redrawn anyhow
244 if ( !IsVisible(m_current) )
245 {
246 ScrollToRow(m_current);
247 }
248 else // line is at least partly visible
249 {
250 // it is, indeed, only partly visible, so scroll it into view to
251 // make it entirely visible
252 // BUT scrolling down when m_current is first visible makes it
253 // completely hidden, so that is even worse
254 while ( (size_t)m_current + 1 == GetVisibleRowsEnd() &&
255 (size_t)m_current != GetVisibleRowsBegin() &&
256 ScrollToRow(GetVisibleBegin() + 1) ) ;
257
258 // but in any case refresh it as even if it was only partly visible
259 // before we need to redraw it entirely as its background changed
260 RefreshRow(m_current);
261 }
262 }
263
264 return true;
265 }
266
267 void wxVListBox::InitEvent(wxCommandEvent& event, int n)
268 {
269 event.SetEventObject(this);
270 event.SetInt(n);
271 }
272
273 void wxVListBox::SendSelectedEvent()
274 {
275 wxASSERT_MSG( m_current != wxNOT_FOUND,
276 wxT("SendSelectedEvent() shouldn't be called") );
277
278 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, GetId());
279 InitEvent(event, m_current);
280 (void)GetEventHandler()->ProcessEvent(event);
281 }
282
283 void wxVListBox::SetSelection(int selection)
284 {
285 wxCHECK_RET( selection == wxNOT_FOUND ||
286 (selection >= 0 && (size_t)selection < GetItemCount()),
287 wxT("wxVListBox::SetSelection(): invalid item index") );
288
289 if ( HasMultipleSelection() )
290 {
291 if (selection != wxNOT_FOUND)
292 Select(selection);
293 else
294 DeselectAll();
295 m_anchor = selection;
296 }
297
298 DoSetCurrent(selection);
299 }
300
301 size_t wxVListBox::GetSelectedCount() const
302 {
303 return m_selStore ? m_selStore->GetSelectedCount()
304 : m_current == wxNOT_FOUND ? 0 : 1;
305 }
306
307 int wxVListBox::GetFirstSelected(unsigned long& cookie) const
308 {
309 cookie = 0;
310
311 return GetNextSelected(cookie);
312 }
313
314 int wxVListBox::GetNextSelected(unsigned long& cookie) const
315 {
316 wxCHECK_MSG( m_selStore, wxNOT_FOUND,
317 wxT("GetFirst/NextSelected() may only be used with multiselection listboxes") );
318
319 while ( cookie < GetItemCount() )
320 {
321 if ( IsSelected(cookie++) )
322 return cookie - 1;
323 }
324
325 return wxNOT_FOUND;
326 }
327
328 void wxVListBox::RefreshSelected()
329 {
330 // only refresh those items which are currently visible and selected:
331 for ( size_t n = GetVisibleBegin(), end = GetVisibleEnd(); n < end; n++ )
332 {
333 if ( IsSelected(n) )
334 RefreshRow(n);
335 }
336 }
337
338 wxRect wxVListBox::GetItemRect(size_t n) const
339 {
340 wxRect itemrect;
341
342 // check that this item is visible
343 const size_t lineMax = GetVisibleEnd();
344 if ( n >= lineMax )
345 return itemrect;
346 size_t line = GetVisibleBegin();
347 if ( n < line )
348 return itemrect;
349
350 while ( line <= n )
351 {
352 itemrect.y += itemrect.height;
353 itemrect.height = OnGetRowHeight(line);
354
355 line++;
356 }
357
358 itemrect.width = GetClientSize().x;
359
360 return itemrect;
361 }
362
363 // ----------------------------------------------------------------------------
364 // wxVListBox appearance parameters
365 // ----------------------------------------------------------------------------
366
367 void wxVListBox::SetMargins(const wxPoint& pt)
368 {
369 if ( pt != m_ptMargins )
370 {
371 m_ptMargins = pt;
372
373 Refresh();
374 }
375 }
376
377 void wxVListBox::SetSelectionBackground(const wxColour& col)
378 {
379 m_colBgSel = col;
380 }
381
382 // ----------------------------------------------------------------------------
383 // wxVListBox painting
384 // ----------------------------------------------------------------------------
385
386 wxCoord wxVListBox::OnGetRowHeight(size_t line) const
387 {
388 return OnMeasureItem(line) + 2*m_ptMargins.y;
389 }
390
391 void wxVListBox::OnDrawSeparator(wxDC& WXUNUSED(dc),
392 wxRect& WXUNUSED(rect),
393 size_t WXUNUSED(n)) const
394 {
395 }
396
397 bool
398 wxVListBox::DoDrawSolidBackground(const wxColour& col,
399 wxDC& dc,
400 const wxRect& rect,
401 size_t n) const
402 {
403 if ( !col.IsOk() )
404 return false;
405
406 // we need to render selected and current items differently
407 const bool isSelected = IsSelected(n),
408 isCurrent = IsCurrent(n);
409 if ( isSelected || isCurrent )
410 {
411 if ( isSelected )
412 {
413 dc.SetBrush(wxBrush(col, wxBRUSHSTYLE_SOLID));
414 }
415 else // !selected
416 {
417 dc.SetBrush(*wxTRANSPARENT_BRUSH);
418 }
419 dc.SetPen(*(isCurrent ? wxBLACK_PEN : wxTRANSPARENT_PEN));
420 dc.DrawRectangle(rect);
421 }
422 //else: do nothing for the normal items
423
424 return true;
425 }
426
427 void wxVListBox::OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const
428 {
429 // use wxRendererNative for more native look unless we use custom bg colour
430 if ( !DoDrawSolidBackground(m_colBgSel, dc, rect, n) )
431 {
432 int flags = 0;
433 if ( IsSelected(n) )
434 flags |= wxCONTROL_SELECTED;
435 if ( IsCurrent(n) )
436 flags |= wxCONTROL_CURRENT;
437 if ( wxWindow::FindFocus() == const_cast<wxVListBox*>(this) )
438 flags |= wxCONTROL_FOCUSED;
439
440 wxRendererNative::Get().DrawItemSelectionRect(
441 const_cast<wxVListBox *>(this), dc, rect, flags);
442 }
443 }
444
445 void wxVListBox::OnPaint(wxPaintEvent& WXUNUSED(event))
446 {
447 wxSize clientSize = GetClientSize();
448
449 wxAutoBufferedPaintDC dc(this);
450
451 // the update rectangle
452 wxRect rectUpdate = GetUpdateClientRect();
453
454 // fill it with background colour
455 dc.SetBackground(GetBackgroundColour());
456 dc.Clear();
457
458 // the bounding rectangle of the current line
459 wxRect rectRow;
460 rectRow.width = clientSize.x;
461
462 // iterate over all visible lines
463 const size_t lineMax = GetVisibleEnd();
464 for ( size_t line = GetVisibleBegin(); line < lineMax; line++ )
465 {
466 const wxCoord hRow = OnGetRowHeight(line);
467
468 rectRow.height = hRow;
469
470 // and draw the ones which intersect the update rect
471 if ( rectRow.Intersects(rectUpdate) )
472 {
473 // don't allow drawing outside of the lines rectangle
474 wxDCClipper clip(dc, rectRow);
475
476 wxRect rect = rectRow;
477 OnDrawBackground(dc, rect, line);
478
479 OnDrawSeparator(dc, rect, line);
480
481 rect.Deflate(m_ptMargins.x, m_ptMargins.y);
482 OnDrawItem(dc, rect, line);
483 }
484 else // no intersection
485 {
486 if ( rectRow.GetTop() > rectUpdate.GetBottom() )
487 {
488 // we are already below the update rect, no need to continue
489 // further
490 break;
491 }
492 //else: the next line may intersect the update rect
493 }
494
495 rectRow.y += hRow;
496 }
497 }
498
499 void wxVListBox::OnSetOrKillFocus(wxFocusEvent& WXUNUSED(event))
500 {
501 // we need to repaint the selection when we get the focus since
502 // wxRendererNative in general draws the focused selection differently
503 // from the unfocused selection (see OnDrawItem):
504 RefreshSelected();
505 }
506
507 void wxVListBox::OnSize(wxSizeEvent& event)
508 {
509 UpdateScrollbar();
510 event.Skip();
511 }
512
513 // ============================================================================
514 // wxVListBox keyboard/mouse handling
515 // ============================================================================
516
517 void wxVListBox::DoHandleItemClick(int item, int flags)
518 {
519 // has anything worth telling the client code about happened?
520 bool notify = false;
521
522 if ( HasMultipleSelection() )
523 {
524 // select the iteem clicked?
525 bool select = true;
526
527 // NB: the keyboard interface we implement here corresponds to
528 // wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
529 // sense IMHO
530 if ( flags & ItemClick_Shift )
531 {
532 if ( m_current != wxNOT_FOUND )
533 {
534 if ( m_anchor == wxNOT_FOUND )
535 m_anchor = m_current;
536
537 select = false;
538
539 // only the range from the selection anchor to new m_current
540 // must be selected
541 if ( DeselectAll() )
542 notify = true;
543
544 if ( SelectRange(m_anchor, item) )
545 notify = true;
546 }
547 //else: treat it as ordinary click/keypress
548 }
549 else // Shift not pressed
550 {
551 m_anchor = item;
552
553 if ( flags & ItemClick_Ctrl )
554 {
555 select = false;
556
557 if ( !(flags & ItemClick_Kbd) )
558 {
559 Toggle(item);
560
561 // the status of the item has definitely changed
562 notify = true;
563 }
564 //else: Ctrl-arrow pressed, don't change selection
565 }
566 //else: behave as in single selection case
567 }
568
569 if ( select )
570 {
571 // make the clicked item the only selection
572 if ( DeselectAll() )
573 notify = true;
574
575 if ( Select(item) )
576 notify = true;
577 }
578 }
579
580 // in any case the item should become the current one
581 if ( DoSetCurrent(item) )
582 {
583 if ( !HasMultipleSelection() )
584 {
585 // this has also changed the selection for single selection case
586 notify = true;
587 }
588 }
589
590 if ( notify )
591 {
592 // notify the user about the selection change
593 SendSelectedEvent();
594 }
595 //else: nothing changed at all
596 }
597
598 // ----------------------------------------------------------------------------
599 // keyboard handling
600 // ----------------------------------------------------------------------------
601
602 void wxVListBox::OnKeyDown(wxKeyEvent& event)
603 {
604 // flags for DoHandleItemClick()
605 int flags = ItemClick_Kbd;
606
607 int current;
608 switch ( event.GetKeyCode() )
609 {
610 case WXK_HOME:
611 case WXK_NUMPAD_HOME:
612 current = 0;
613 break;
614
615 case WXK_END:
616 case WXK_NUMPAD_END:
617 current = GetRowCount() - 1;
618 break;
619
620 case WXK_DOWN:
621 case WXK_NUMPAD_DOWN:
622 if ( m_current == (int)GetRowCount() - 1 )
623 return;
624
625 current = m_current + 1;
626 break;
627
628 case WXK_UP:
629 case WXK_NUMPAD_UP:
630 if ( m_current == wxNOT_FOUND )
631 current = GetRowCount() - 1;
632 else if ( m_current != 0 )
633 current = m_current - 1;
634 else // m_current == 0
635 return;
636 break;
637
638 case WXK_PAGEDOWN:
639 case WXK_NUMPAD_PAGEDOWN:
640 PageDown();
641 current = GetVisibleBegin();
642 break;
643
644 case WXK_PAGEUP:
645 case WXK_NUMPAD_PAGEUP:
646 if ( m_current == (int)GetVisibleBegin() )
647 {
648 PageUp();
649 }
650
651 current = GetVisibleBegin();
652 break;
653
654 case WXK_SPACE:
655 // hack: pressing space should work like a mouse click rather than
656 // like a keyboard arrow press, so trick DoHandleItemClick() in
657 // thinking we were clicked
658 flags &= ~ItemClick_Kbd;
659 current = m_current;
660 break;
661
662 #ifdef __WXMSW__
663 case WXK_TAB:
664 // Since we are using wxWANTS_CHARS we need to send navigation
665 // events for the tabs on MSW
666 HandleAsNavigationKey(event);
667 // fall through to default
668 #endif
669 default:
670 event.Skip();
671 current = 0; // just to silent the stupid compiler warnings
672 wxUnusedVar(current);
673 return;
674 }
675
676 if ( event.ShiftDown() )
677 flags |= ItemClick_Shift;
678 if ( event.ControlDown() )
679 flags |= ItemClick_Ctrl;
680
681 DoHandleItemClick(current, flags);
682 }
683
684 // ----------------------------------------------------------------------------
685 // wxVListBox mouse handling
686 // ----------------------------------------------------------------------------
687
688 void wxVListBox::OnLeftDown(wxMouseEvent& event)
689 {
690 SetFocus();
691
692 int item = VirtualHitTest(event.GetPosition().y);
693
694 if ( item != wxNOT_FOUND )
695 {
696 int flags = 0;
697 if ( event.ShiftDown() )
698 flags |= ItemClick_Shift;
699
700 // under Mac Apple-click is used in the same way as Ctrl-click
701 // elsewhere
702 #ifdef __WXMAC__
703 if ( event.MetaDown() )
704 #else
705 if ( event.ControlDown() )
706 #endif
707 flags |= ItemClick_Ctrl;
708
709 DoHandleItemClick(item, flags);
710 }
711 }
712
713 void wxVListBox::OnLeftDClick(wxMouseEvent& eventMouse)
714 {
715 int item = VirtualHitTest(eventMouse.GetPosition().y);
716 if ( item != wxNOT_FOUND )
717 {
718
719 // if item double-clicked was not yet selected, then treat
720 // this event as a left-click instead
721 if ( item == m_current )
722 {
723 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, GetId());
724 InitEvent(event, item);
725 (void)GetEventHandler()->ProcessEvent(event);
726 }
727 else
728 {
729 OnLeftDown(eventMouse);
730 }
731
732 }
733 }
734
735
736 // ----------------------------------------------------------------------------
737 // use the same default attributes as wxListBox
738 // ----------------------------------------------------------------------------
739
740 //static
741 wxVisualAttributes
742 wxVListBox::GetClassDefaultAttributes(wxWindowVariant variant)
743 {
744 return wxListBox::GetClassDefaultAttributes(variant);
745 }
746
747 #endif