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