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