1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/vlbox.cpp
3 // Purpose: implementation of wxVListBox
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
9 // License: wxWindows license
10 ///////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
32 #include "wx/settings.h"
33 #include "wx/dcclient.h"
34 #include "wx/listbox.h"
37 #include "wx/dcbuffer.h"
38 #include "wx/selstore.h"
40 // ----------------------------------------------------------------------------
42 // ----------------------------------------------------------------------------
44 BEGIN_EVENT_TABLE(wxVListBox
, wxVScrolledWindow
)
45 EVT_PAINT(wxVListBox::OnPaint
)
47 EVT_KEY_DOWN(wxVListBox::OnKeyDown
)
48 EVT_LEFT_DOWN(wxVListBox::OnLeftDown
)
49 EVT_LEFT_DCLICK(wxVListBox::OnLeftDClick
)
52 // ============================================================================
54 // ============================================================================
56 IMPLEMENT_ABSTRACT_CLASS(wxVListBox
, wxVScrolledWindow
)
58 // ----------------------------------------------------------------------------
59 // wxVListBox creation
60 // ----------------------------------------------------------------------------
62 void wxVListBox::Init()
65 m_anchor
= wxNOT_FOUND
;
67 m_doubleBuffer
= NULL
;
70 bool wxVListBox::Create(wxWindow
*parent
,
77 style
|= wxWANTS_CHARS
| wxFULL_REPAINT_ON_RESIZE
;
78 if ( !wxVScrolledWindow::Create(parent
, id
, pos
, size
, style
, name
) )
81 if ( style
& wxLB_MULTIPLE
)
82 m_selStore
= new wxSelectionStore
;
84 // make sure the native widget has the right colour since we do
85 // transparent drawing by default
86 SetBackgroundColour(GetBackgroundColour());
87 m_colBgSel
= wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT
);
89 // flicker-free drawing requires this
90 SetBackgroundStyle(wxBG_STYLE_CUSTOM
);
95 wxVListBox::~wxVListBox()
97 delete m_doubleBuffer
;
101 void wxVListBox::SetItemCount(size_t count
)
105 // tell the selection store that our number of items has changed
106 m_selStore
->SetItemCount(count
);
112 // ----------------------------------------------------------------------------
113 // selection handling
114 // ----------------------------------------------------------------------------
116 bool wxVListBox::IsSelected(size_t line
) const
118 return m_selStore
? m_selStore
->IsSelected(line
) : (int)line
== m_current
;
121 bool wxVListBox::Select(size_t item
, bool select
)
123 wxCHECK_MSG( m_selStore
, false,
124 _T("Select() may only be used with multiselection listbox") );
126 wxCHECK_MSG( item
< GetItemCount(), false,
127 _T("Select(): invalid item index") );
129 bool changed
= m_selStore
->SelectItem(item
, select
);
132 // selection really changed
141 bool wxVListBox::SelectRange(size_t from
, size_t to
)
143 wxCHECK_MSG( m_selStore
, false,
144 _T("SelectRange() may only be used with multiselection listbox") );
146 // make sure items are in correct order
154 wxCHECK_MSG( to
< GetItemCount(), false,
155 _T("SelectRange(): invalid item index") );
158 if ( !m_selStore
->SelectRange(from
, to
, true, &changed
) )
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
);
164 else // we've got the indices of the changed items
166 const size_t count
= changed
.GetCount();
173 // refresh just the lines which have really changed
174 for ( size_t n
= 0; n
< count
; n
++ )
176 RefreshLine(changed
[n
]);
184 bool wxVListBox::DoSelectAll(bool select
)
186 wxCHECK_MSG( m_selStore
, false,
187 _T("SelectAll may only be used with multiselection listbox") );
189 size_t count
= GetItemCount();
193 if ( !m_selStore
->SelectRange(0, count
- 1, select
) ||
206 bool wxVListBox::DoSetCurrent(int current
)
208 wxASSERT_MSG( current
== wxNOT_FOUND
||
209 (current
>= 0 && (size_t)current
< GetItemCount()),
210 _T("wxVListBox::DoSetCurrent(): invalid item index") );
212 if ( current
== m_current
)
218 if ( m_current
!= wxNOT_FOUND
)
219 RefreshLine(m_current
);
223 if ( m_current
!= wxNOT_FOUND
)
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
227 if ( !IsVisible(m_current
) )
229 ScrollToLine(m_current
);
231 else // line is at least partly visible
233 // it is, indeed, only partly visible, so scroll it into view to
234 // make it entirely visible
235 while ( (size_t)m_current
== GetLastVisibleLine() &&
236 ScrollToLine(GetVisibleBegin()+1) ) ;
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
240 RefreshLine(m_current
);
247 void wxVListBox::SendSelectedEvent()
249 wxASSERT_MSG( m_current
!= wxNOT_FOUND
,
250 _T("SendSelectedEvent() shouldn't be called") );
252 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, GetId());
253 event
.SetEventObject(this);
254 event
.SetInt(m_current
);
256 (void)GetEventHandler()->ProcessEvent(event
);
259 void wxVListBox::SetSelection(int selection
)
261 wxCHECK_RET( selection
== wxNOT_FOUND
||
262 (selection
>= 0 && (size_t)selection
< GetItemCount()),
263 _T("wxVListBox::SetSelection(): invalid item index") );
265 if ( HasMultipleSelection() )
267 if (selection
!= wxNOT_FOUND
)
271 m_anchor
= selection
;
274 DoSetCurrent(selection
);
277 size_t wxVListBox::GetSelectedCount() const
279 return m_selStore
? m_selStore
->GetSelectedCount()
280 : m_current
== wxNOT_FOUND
? 0 : 1;
283 int wxVListBox::GetFirstSelected(unsigned long& cookie
) const
287 return GetNextSelected(cookie
);
290 int wxVListBox::GetNextSelected(unsigned long& cookie
) const
292 wxCHECK_MSG( m_selStore
, wxNOT_FOUND
,
293 _T("GetFirst/NextSelected() may only be used with multiselection listboxes") );
295 while ( cookie
< GetItemCount() )
297 if ( IsSelected(cookie
++) )
304 // ----------------------------------------------------------------------------
305 // wxVListBox appearance parameters
306 // ----------------------------------------------------------------------------
308 void wxVListBox::SetMargins(const wxPoint
& pt
)
310 if ( pt
!= m_ptMargins
)
318 void wxVListBox::SetSelectionBackground(const wxColour
& col
)
323 // ----------------------------------------------------------------------------
324 // wxVListBox painting
325 // ----------------------------------------------------------------------------
327 wxCoord
wxVListBox::OnGetLineHeight(size_t line
) const
329 return OnMeasureItem(line
) + 2*m_ptMargins
.y
;
332 void wxVListBox::OnDrawSeparator(wxDC
& WXUNUSED(dc
),
333 wxRect
& WXUNUSED(rect
),
334 size_t WXUNUSED(n
)) const
338 void wxVListBox::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
340 // we need to render selected and current items differently
341 const bool isSelected
= IsSelected(n
),
342 isCurrent
= IsCurrent(n
);
343 if ( isSelected
|| isCurrent
)
347 dc
.SetBrush(wxBrush(m_colBgSel
, wxSOLID
));
351 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
354 dc
.SetPen(*(isCurrent
? wxBLACK_PEN
: wxTRANSPARENT_PEN
));
356 dc
.DrawRectangle(rect
);
358 //else: do nothing for the normal items
361 void wxVListBox::OnPaint(wxPaintEvent
& WXUNUSED(event
))
363 // If size is larger, recalculate double buffer bitmap
364 wxSize clientSize
= GetClientSize();
366 if ( !m_doubleBuffer
||
367 clientSize
.x
> m_doubleBuffer
->GetWidth() ||
368 clientSize
.y
> m_doubleBuffer
->GetHeight() )
370 delete m_doubleBuffer
;
371 m_doubleBuffer
= new wxBitmap(clientSize
.x
+25,clientSize
.y
+25);
374 wxBufferedPaintDC
dc(this,*m_doubleBuffer
);
376 // the update rectangle
377 wxRect rectUpdate
= GetUpdateClientRect();
379 // fill it with background colour
380 dc
.SetBackground(GetBackgroundColour());
383 // the bounding rectangle of the current line
385 rectLine
.width
= clientSize
.x
;
387 // iterate over all visible lines
388 const size_t lineMax
= GetVisibleEnd();
389 for ( size_t line
= GetFirstVisibleLine(); line
< lineMax
; line
++ )
391 const wxCoord hLine
= OnGetLineHeight(line
);
393 rectLine
.height
= hLine
;
395 // and draw the ones which intersect the update rect
396 if ( rectLine
.Intersects(rectUpdate
) )
398 // don't allow drawing outside of the lines rectangle
399 wxDCClipper
clip(dc
, rectLine
);
401 wxRect rect
= rectLine
;
402 OnDrawBackground(dc
, rect
, line
);
404 OnDrawSeparator(dc
, rect
, line
);
406 rect
.Deflate(m_ptMargins
.x
, m_ptMargins
.y
);
407 OnDrawItem(dc
, rect
, line
);
409 else // no intersection
411 if ( rectLine
.GetTop() > rectUpdate
.GetBottom() )
413 // we are already below the update rect, no need to continue
417 //else: the next line may intersect the update rect
424 // ============================================================================
425 // wxVListBox keyboard/mouse handling
426 // ============================================================================
428 void wxVListBox::DoHandleItemClick(int item
, int flags
)
430 // has anything worth telling the client code about happened?
433 if ( HasMultipleSelection() )
435 // select the iteem clicked?
438 // NB: the keyboard interface we implement here corresponds to
439 // wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
441 if ( flags
& ItemClick_Shift
)
443 if ( m_current
!= wxNOT_FOUND
)
445 if ( m_anchor
== wxNOT_FOUND
)
446 m_anchor
= m_current
;
450 // only the range from the selection anchor to new m_current
455 if ( SelectRange(m_anchor
, item
) )
458 //else: treat it as ordinary click/keypress
460 else // Shift not pressed
464 if ( flags
& ItemClick_Ctrl
)
468 if ( !(flags
& ItemClick_Kbd
) )
472 // the status of the item has definitely changed
475 //else: Ctrl-arrow pressed, don't change selection
477 //else: behave as in single selection case
482 // make the clicked item the only selection
491 // in any case the item should become the current one
492 if ( DoSetCurrent(item
) )
494 if ( !HasMultipleSelection() )
496 // this has also changed the selection for single selection case
503 // notify the user about the selection change
506 //else: nothing changed at all
509 // ----------------------------------------------------------------------------
511 // ----------------------------------------------------------------------------
513 void wxVListBox::OnKeyDown(wxKeyEvent
& event
)
515 // flags for DoHandleItemClick()
516 int flags
= ItemClick_Kbd
;
519 switch ( event
.GetKeyCode() )
526 current
= GetLineCount() - 1;
530 if ( m_current
== (int)GetLineCount() - 1 )
533 current
= m_current
+ 1;
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
547 current
= GetFirstVisibleLine();
551 if ( m_current
== (int)GetFirstVisibleLine() )
556 current
= GetFirstVisibleLine();
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
;
569 // Since we are using wxWANTS_CHARS we need to send navigation
570 // events for the tabs on MSW
572 wxNavigationKeyEvent ne
;
573 ne
.SetDirection(!event
.ShiftDown());
574 ne
.SetCurrentFocus(this);
575 ne
.SetEventObject(this);
576 GetParent()->GetEventHandler()->ProcessEvent(ne
);
578 // fall through to default
582 current
= 0; // just to silent the stupid compiler warnings
583 wxUnusedVar(current
);
587 if ( event
.ShiftDown() )
588 flags
|= ItemClick_Shift
;
589 if ( event
.ControlDown() )
590 flags
|= ItemClick_Ctrl
;
592 DoHandleItemClick(current
, flags
);
595 // ----------------------------------------------------------------------------
596 // wxVListBox mouse handling
597 // ----------------------------------------------------------------------------
599 void wxVListBox::OnLeftDown(wxMouseEvent
& event
)
603 int item
= HitTest(event
.GetPosition());
605 if ( item
!= wxNOT_FOUND
)
608 if ( event
.ShiftDown() )
609 flags
|= ItemClick_Shift
;
611 // under Mac Apple-click is used in the same way as Ctrl-click
614 if ( event
.MetaDown() )
616 if ( event
.ControlDown() )
618 flags
|= ItemClick_Ctrl
;
620 DoHandleItemClick(item
, flags
);
624 void wxVListBox::OnLeftDClick(wxMouseEvent
& eventMouse
)
626 int item
= HitTest(eventMouse
.GetPosition());
627 if ( item
!= wxNOT_FOUND
)
630 // if item double-clicked was not yet selected, then treat
631 // this event as a left-click instead
632 if ( item
== m_current
)
634 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, GetId());
635 event
.SetEventObject(this);
638 (void)GetEventHandler()->ProcessEvent(event
);
642 OnLeftDown(eventMouse
);
649 // ----------------------------------------------------------------------------
650 // use the same default attributes as wxListBox
651 // ----------------------------------------------------------------------------
655 wxVListBox::GetClassDefaultAttributes(wxWindowVariant variant
)
657 return wxListBox::GetClassDefaultAttributes(variant
);