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