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