added checks for the item index
[wxWidgets.git] / src / generic / vlbox.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: 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 #ifndef WX_PRECOMP
28 #include "wx/settings.h"
29 #include "wx/dcclient.h"
30 #endif //WX_PRECOMP
31
32 #include "wx/vlbox.h"
33 #include "wx/selstore.h"
34
35 // ----------------------------------------------------------------------------
36 // event tables
37 // ----------------------------------------------------------------------------
38
39 BEGIN_EVENT_TABLE(wxVListBox, wxVScrolledWindow)
40 EVT_PAINT(wxVListBox::OnPaint)
41
42 EVT_KEY_DOWN(wxVListBox::OnKeyDown)
43 EVT_LEFT_DOWN(wxVListBox::OnLeftDown)
44 EVT_LEFT_DCLICK(wxVListBox::OnLeftDClick)
45 END_EVENT_TABLE()
46
47 // ============================================================================
48 // implementation
49 // ============================================================================
50
51 // ----------------------------------------------------------------------------
52 // wxVListBox creation
53 // ----------------------------------------------------------------------------
54
55 void wxVListBox::Init()
56 {
57 m_current = wxNOT_FOUND;
58 m_selStore = NULL;
59 }
60
61 bool wxVListBox::Create(wxWindow *parent,
62 wxWindowID id,
63 const wxPoint& pos,
64 const wxSize& size,
65 long style,
66 const wxString& name)
67 {
68 if ( !wxVScrolledWindow::Create(parent, id, pos, size, style, name) )
69 return false;
70
71 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX));
72
73 if ( style & wxLB_MULTIPLE )
74 m_selStore = new wxSelectionStore;
75
76 return true;
77 }
78
79 wxVListBox::~wxVListBox()
80 {
81 delete m_selStore;
82 }
83
84 void wxVListBox::SetItemCount(size_t count)
85 {
86 if ( m_selStore )
87 {
88 // tell the selection store that our number of items has changed
89 m_selStore->SetItemCount(count);
90 }
91
92 SetLineCount(count);
93 }
94
95 // ----------------------------------------------------------------------------
96 // selection handling
97 // ----------------------------------------------------------------------------
98
99 bool wxVListBox::IsSelected(size_t line) const
100 {
101 return m_selStore ? m_selStore->IsSelected(line) : (int)line == m_current;
102 }
103
104 bool wxVListBox::Select(size_t item, bool select)
105 {
106 wxCHECK_MSG( m_selStore, false,
107 _T("Select() may only be used with multiselection listbox") );
108
109 wxCHECK_MSG( item < GetItemCount(), false,
110 _T("Select(): invalid item index") );
111
112 bool changed = m_selStore->SelectItem(item, select);
113 if ( changed )
114 {
115 // selection really changed
116 RefreshLine(item);
117 }
118
119 DoSetCurrent(item);
120
121 return changed;
122 }
123
124 bool wxVListBox::SelectRange(size_t from, size_t to)
125 {
126 wxCHECK_MSG( m_selStore, false,
127 _T("SelectRange() may only be used with multiselection listbox") );
128
129 // make sure items are in correct order
130 if ( from > to )
131 {
132 size_t tmp = from;
133 from = to;
134 to = tmp;
135 }
136
137 wxCHECK_MSG( to < GetItemCount(), false,
138 _T("SelectRange(): invalid item index") );
139
140 wxArrayInt changed;
141 if ( !m_selStore->SelectRange(from, to, true, &changed) )
142 {
143 // too many items have changed, we didn't record them in changed array
144 // so we have no choice but to refresh everything between from and to
145 RefreshLines(from, to);
146 }
147 else // we've got the indices of the changed items
148 {
149 const size_t count = changed.GetCount();
150 if ( !count )
151 {
152 // nothing changed
153 return false;
154 }
155
156 // refresh just the lines which have really changed
157 for ( size_t n = 0; n < count; n++ )
158 {
159 RefreshLine(changed[n]);
160 }
161 }
162
163 // something changed
164 return true;
165 }
166
167 bool wxVListBox::DoSelectAll(bool select)
168 {
169 wxCHECK_MSG( m_selStore, false,
170 _T("SelectAll may only be used with multiselection listbox") );
171
172 size_t count = GetItemCount();
173 if ( count )
174 {
175 wxArrayInt changed;
176 if ( !m_selStore->SelectRange(0, count - 1, select) ||
177 !changed.IsEmpty() )
178 {
179 Refresh();
180
181 // something changed
182 return true;
183 }
184 }
185
186 return false;
187 }
188
189 bool wxVListBox::DoSetCurrent(int current)
190 {
191 wxASSERT_MSG( current == wxNOT_FOUND ||
192 (current >= 0 && (size_t)current < GetItemCount()),
193 _T("wxVListBox::DoSetCurrent(): invalid item index") );
194
195 if ( current == m_current )
196 {
197 // nothing to do
198 return false;
199 }
200
201 if ( m_current != wxNOT_FOUND )
202 RefreshLine(m_current);
203
204 m_current = current;
205
206 if ( m_current != wxNOT_FOUND )
207 {
208 // if the line is not visible at all, we scroll it into view but we
209 // don't need to refresh it -- it will be redrawn anyhow
210 if ( !IsVisible(m_current) )
211 {
212 ScrollToLine(m_current);
213 }
214 else // line is at least partly visible
215 {
216 // it is, indeed, only partly visible, so scroll it into view to
217 // make it entirely visible
218 if ( (size_t)m_current == GetLastVisibleLine() )
219 {
220 ScrollToLine(m_current);
221 }
222
223 // but in any case refresh it as even if it was only partly visible
224 // before we need to redraw it entirely as its background changed
225 RefreshLine(m_current);
226 }
227 }
228
229 return true;
230 }
231
232 void wxVListBox::SendSelectedEvent()
233 {
234 wxASSERT_MSG( m_current != wxNOT_FOUND,
235 _T("SendSelectedEvent() shouldn't be called") );
236
237 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, GetId());
238 event.SetEventObject(this);
239 event.m_commandInt = m_current;
240
241 (void)GetEventHandler()->ProcessEvent(event);
242 }
243
244 void wxVListBox::SetSelection(int selection)
245 {
246 wxCHECK_RET( selection == wxNOT_FOUND ||
247 (selection >= 0 && (size_t)selection < GetItemCount()),
248 _T("wxVListBox::SetSelection(): invalid item index") );
249
250 wxASSERT_MSG( !HasMultipleSelection(),
251 _T("SetSelection() is invalid with multiselection listbox") );
252
253 DoSetCurrent(selection);
254 }
255
256 size_t wxVListBox::GetSelectedCount() const
257 {
258 return m_selStore ? m_selStore->GetSelectedCount()
259 : m_current == wxNOT_FOUND ? 0 : 1;
260 }
261
262 int wxVListBox::GetFirstSelected(unsigned long& cookie) const
263 {
264 cookie = 0;
265
266 return GetNextSelected(cookie);
267 }
268
269 int wxVListBox::GetNextSelected(unsigned long& cookie) const
270 {
271 wxCHECK_MSG( m_selStore, wxNOT_FOUND,
272 _T("GetFirst/NextSelected() may only be used with multiselection listboxes") );
273
274 while ( cookie < GetItemCount() )
275 {
276 if ( IsSelected(cookie++) )
277 return cookie - 1;
278 }
279
280 return wxNOT_FOUND;
281 }
282
283 // ----------------------------------------------------------------------------
284 // wxVListBox painting
285 // ----------------------------------------------------------------------------
286
287 void wxVListBox::SetMargins(const wxPoint& pt)
288 {
289 if ( pt != m_ptMargins )
290 {
291 m_ptMargins = pt;
292
293 Refresh();
294 }
295 }
296
297 wxCoord wxVListBox::OnGetLineHeight(size_t line) const
298 {
299 return OnMeasureItem(line) + 2*m_ptMargins.y;
300 }
301
302 void wxVListBox::OnDrawSeparator(wxDC& WXUNUSED(dc),
303 wxRect& WXUNUSED(rect),
304 size_t WXUNUSED(n)) const
305 {
306 }
307
308 void wxVListBox::OnPaint(wxPaintEvent& event)
309 {
310 wxPaintDC dc(this);
311
312 // the update rectangle
313 wxRect rectUpdate = GetUpdateClientRect();
314
315 // the bounding rectangle of the current line
316 wxRect rectLine;
317 rectLine.width = GetClientSize().x;
318
319 // iterate over all visible lines
320 const size_t lineMax = GetLastVisibleLine();
321 for ( size_t line = GetFirstVisibleLine(); line <= lineMax; line++ )
322 {
323 const wxCoord hLine = OnGetLineHeight(line);
324
325 rectLine.height = hLine;
326
327 // and draw the ones which intersect the update rect
328 if ( rectLine.Intersects(rectUpdate) )
329 {
330 // don't allow drawing outside of the lines rectangle
331 wxDCClipper clip(dc, rectLine);
332
333 // we need to render selected and current items differently
334 const bool isSelected = IsSelected(line);
335 if ( isSelected || IsCurrent(line) )
336 {
337 if ( isSelected )
338 {
339 wxBrush brush(wxSystemSettings::
340 GetColour(wxSYS_COLOUR_HIGHLIGHT),
341 wxSOLID);
342 dc.SetBrush(brush);
343 }
344 else // !selected
345 {
346 dc.SetBrush(*wxTRANSPARENT_BRUSH);
347 }
348
349 dc.SetPen(*(IsCurrent(line) ? wxBLACK_PEN : wxTRANSPARENT_PEN));
350
351 dc.DrawRectangle(rectLine);
352 }
353
354 wxRect rect = rectLine;
355 OnDrawSeparator(dc, rect, line);
356
357 rect.Deflate(m_ptMargins.x, m_ptMargins.y);
358 OnDrawItem(dc, rect, line);
359 }
360 else // no intersection
361 {
362 if ( rectLine.GetTop() > rectUpdate.GetBottom() )
363 {
364 // we are already below the update rect, no need to continue
365 // further
366 break;
367 }
368 //else: the next line may intersect the update rect
369 }
370
371 rectLine.y += hLine;
372 }
373 }
374
375 // ============================================================================
376 // wxVListBox keyboard/mouse handling
377 // ============================================================================
378
379 void wxVListBox::DoHandleItemClick(int item, bool shiftDown, bool ctrlDown)
380 {
381 // has anything worth telling the client code about happened?
382 bool notify = false;
383
384 if ( HasMultipleSelection() )
385 {
386 // select the iteem clicked?
387 bool select = true;
388
389 // NB: the keyboard interface we implement here corresponds to
390 // wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
391 // sense IMHO
392 if ( shiftDown )
393 {
394 if ( m_current != wxNOT_FOUND )
395 {
396 select = false;
397
398 // only the range from old m_current to new m_current must be
399 // selected
400 if ( DeselectAll() )
401 notify = true;
402
403 if ( SelectRange(m_current, item) )
404 notify = true;
405 }
406 //else: treat it as ordinary click/keypress
407 }
408 else if ( ctrlDown )
409 {
410 select = false;
411
412 Toggle(item);
413
414 // the status of the item has definitely changed
415 notify = true;
416 }
417 //else: behave as in single selection case
418
419 if ( select )
420 {
421 // make the clicked item the only selection
422 if ( DeselectAll() )
423 notify = true;
424
425 if ( Select(item) )
426 notify = true;
427 }
428 }
429
430 // in any case the item should become the current one
431 if ( DoSetCurrent(item) )
432 {
433 if ( !HasMultipleSelection() )
434 {
435 // this has also changed the selection for single selection case
436 notify = true;
437 }
438 }
439
440 if ( notify )
441 {
442 // notify the user about the selection change
443 SendSelectedEvent();
444 }
445 //else: nothing changed at all
446 }
447
448 // ----------------------------------------------------------------------------
449 // keyboard handling
450 // ----------------------------------------------------------------------------
451
452 void wxVListBox::OnKeyDown(wxKeyEvent& event)
453 {
454 int current = 0; // just to silent the stupid compiler warnings
455 switch ( event.GetKeyCode() )
456 {
457 case WXK_HOME:
458 current = 0;
459 break;
460
461 case WXK_END:
462 current = GetLineCount() - 1;
463 break;
464
465 case WXK_DOWN:
466 if ( m_current == (int)GetLineCount() - 1 )
467 return;
468
469 current = m_current + 1;
470 break;
471
472 case WXK_UP:
473 if ( m_current == wxNOT_FOUND )
474 current = GetLineCount() - 1;
475 else if ( m_current != 0 )
476 current = m_current - 1;
477 else // m_current == 0
478 return;
479 break;
480
481 case WXK_PAGEDOWN:
482 case WXK_NEXT:
483 PageDown();
484 current = GetFirstVisibleLine();
485 break;
486
487 case WXK_PAGEUP:
488 case WXK_PRIOR:
489 if ( m_current == (int)GetFirstVisibleLine() )
490 {
491 PageUp();
492 }
493
494 current = GetFirstVisibleLine();
495 break;
496
497 default:
498 event.Skip();
499 return;
500 }
501
502 DoHandleItemClick(current, event.ShiftDown(), event.ControlDown());
503 }
504
505 // ----------------------------------------------------------------------------
506 // wxVListBox mouse handling
507 // ----------------------------------------------------------------------------
508
509 void wxVListBox::OnLeftDown(wxMouseEvent& event)
510 {
511 int item = HitTest(event.GetPosition());
512
513 if ( item != wxNOT_FOUND )
514 {
515 // under Mac Apple-click is used in the same way as Ctrl-click
516 // elsewhere
517 DoHandleItemClick(item, event.ShiftDown(),
518 #ifdef __WXMAC__
519 event.MetaDown()
520 #else
521 event.ControlDown()
522 #endif
523 );
524 }
525 }
526
527 void wxVListBox::OnLeftDClick(wxMouseEvent& event)
528 {
529 int item = HitTest(event.GetPosition());
530 if ( item != wxNOT_FOUND )
531 {
532 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, GetId());
533 event.SetEventObject(this);
534 event.m_commandInt = item;
535
536 (void)GetEventHandler()->ProcessEvent(event);
537 }
538 }
539