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