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