1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: 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"
28 #include "wx/settings.h"
29 #include "wx/dcclient.h"
33 #include "wx/selstore.h"
35 // ----------------------------------------------------------------------------
37 // ----------------------------------------------------------------------------
39 BEGIN_EVENT_TABLE(wxVListBox
, wxVScrolledWindow
)
40 EVT_PAINT(wxVListBox
::OnPaint
)
42 EVT_KEY_DOWN(wxVListBox
::OnKeyDown
)
43 EVT_LEFT_DOWN(wxVListBox
::OnLeftDown
)
44 EVT_LEFT_DCLICK(wxVListBox
::OnLeftDClick
)
47 // ============================================================================
49 // ============================================================================
51 // ----------------------------------------------------------------------------
52 // wxVListBox creation
53 // ----------------------------------------------------------------------------
55 void wxVListBox
::Init()
58 m_anchor
= wxNOT_FOUND
;
62 bool wxVListBox
::Create(wxWindow
*parent
,
69 if ( !wxVScrolledWindow
::Create(parent
, id
, pos
, size
, style
, name
) )
72 if ( style
& wxLB_MULTIPLE
)
73 m_selStore
= new wxSelectionStore
;
75 SetBackgroundColour(wxSystemSettings
::GetColour(wxSYS_COLOUR_LISTBOX
));
76 m_colBgSel
= wxSystemSettings
::GetColour(wxSYS_COLOUR_HIGHLIGHT
);
81 wxVListBox
::~wxVListBox()
86 void wxVListBox
::SetItemCount(size_t count
)
90 // tell the selection store that our number of items has changed
91 m_selStore
->SetItemCount(count
);
97 // ----------------------------------------------------------------------------
99 // ----------------------------------------------------------------------------
101 bool wxVListBox
::IsSelected(size_t line
) const
103 return m_selStore ? m_selStore
->IsSelected(line
) : (int)line
== m_current
;
106 bool wxVListBox
::Select(size_t item
, bool select
)
108 wxCHECK_MSG( m_selStore
, false,
109 _T("Select() may only be used with multiselection listbox") );
111 wxCHECK_MSG( item
< GetItemCount(), false,
112 _T("Select(): invalid item index") );
114 bool changed
= m_selStore
->SelectItem(item
, select
);
117 // selection really changed
126 bool wxVListBox
::SelectRange(size_t from
, size_t to
)
128 wxCHECK_MSG( m_selStore
, false,
129 _T("SelectRange() may only be used with multiselection listbox") );
131 // make sure items are in correct order
139 wxCHECK_MSG( to
< GetItemCount(), false,
140 _T("SelectRange(): invalid item index") );
143 if ( !m_selStore
->SelectRange(from
, to
, true, &changed
) )
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
);
149 else // we've got the indices of the changed items
151 const size_t count
= changed
.GetCount();
158 // refresh just the lines which have really changed
159 for ( size_t n
= 0; n
< count
; n
++ )
161 RefreshLine(changed
[n
]);
169 bool wxVListBox
::DoSelectAll(bool select
)
171 wxCHECK_MSG( m_selStore
, false,
172 _T("SelectAll may only be used with multiselection listbox") );
174 size_t count
= GetItemCount();
178 if ( !m_selStore
->SelectRange(0, count
- 1, select
) ||
191 bool wxVListBox
::DoSetCurrent(int current
)
193 wxASSERT_MSG( current
== wxNOT_FOUND
||
194 (current
>= 0 && (size_t)current
< GetItemCount()),
195 _T("wxVListBox::DoSetCurrent(): invalid item index") );
197 if ( current
== m_current
)
203 if ( m_current
!= wxNOT_FOUND
)
204 RefreshLine(m_current
);
208 if ( m_current
!= wxNOT_FOUND
)
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
) )
214 ScrollToLine(m_current
);
216 else // line is at least partly visible
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() )
222 ScrollToLine(m_current
);
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
);
234 void wxVListBox
::SendSelectedEvent()
236 wxASSERT_MSG( m_current
!= wxNOT_FOUND
,
237 _T("SendSelectedEvent() shouldn't be called") );
239 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, GetId());
240 event
.SetEventObject(this);
241 event
.m_commandInt
= m_current
;
243 (void)GetEventHandler()->ProcessEvent(event
);
246 void wxVListBox
::SetSelection(int selection
)
248 wxCHECK_RET( selection
== wxNOT_FOUND
||
249 (selection
>= 0 && (size_t)selection
< GetItemCount()),
250 _T("wxVListBox::SetSelection(): invalid item index") );
252 wxASSERT_MSG( !HasMultipleSelection(),
253 _T("SetSelection() is invalid with multiselection listbox") );
255 DoSetCurrent(selection
);
258 size_t wxVListBox
::GetSelectedCount() const
260 return m_selStore ? m_selStore
->GetSelectedCount()
261 : m_current
== wxNOT_FOUND ?
0 : 1;
264 int wxVListBox
::GetFirstSelected(unsigned long& cookie
) const
268 return GetNextSelected(cookie
);
271 int wxVListBox
::GetNextSelected(unsigned long& cookie
) const
273 wxCHECK_MSG( m_selStore
, wxNOT_FOUND
,
274 _T("GetFirst/NextSelected() may only be used with multiselection listboxes") );
276 while ( cookie
< GetItemCount() )
278 if ( IsSelected(cookie
++) )
285 // ----------------------------------------------------------------------------
286 // wxVListBox appearance parameters
287 // ----------------------------------------------------------------------------
289 void wxVListBox
::SetMargins(const wxPoint
& pt
)
291 if ( pt
!= m_ptMargins
)
299 void wxVListBox
::SetSelectionBackground(const wxColour
& col
)
304 // ----------------------------------------------------------------------------
305 // wxVListBox painting
306 // ----------------------------------------------------------------------------
308 wxCoord wxVListBox
::OnGetLineHeight(size_t line
) const
310 return OnMeasureItem(line
) + 2*m_ptMargins
.y
;
313 void wxVListBox
::OnDrawSeparator(wxDC
& WXUNUSED(dc
),
314 wxRect
& WXUNUSED(rect
),
315 size_t WXUNUSED(n
)) const
319 void wxVListBox
::OnPaint(wxPaintEvent
& event
)
323 // the update rectangle
324 wxRect rectUpdate
= GetUpdateClientRect();
326 // the bounding rectangle of the current line
328 rectLine
.width
= GetClientSize().x
;
330 // iterate over all visible lines
331 const size_t lineMax
= GetLastVisibleLine();
332 for ( size_t line
= GetFirstVisibleLine(); line
<= lineMax
; line
++ )
334 const wxCoord hLine
= OnGetLineHeight(line
);
336 rectLine
.height
= hLine
;
338 // and draw the ones which intersect the update rect
339 if ( rectLine
.Intersects(rectUpdate
) )
341 // don't allow drawing outside of the lines rectangle
342 wxDCClipper
clip(dc
, rectLine
);
344 // we need to render selected and current items differently
345 const bool isSelected
= IsSelected(line
);
346 if ( isSelected
|| IsCurrent(line
) )
350 dc
.SetBrush(wxBrush(m_colBgSel
, wxSOLID
));
354 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
357 dc
.SetPen(*(IsCurrent(line
) ? wxBLACK_PEN
: wxTRANSPARENT_PEN
));
359 dc
.DrawRectangle(rectLine
);
362 wxRect rect
= rectLine
;
363 OnDrawSeparator(dc
, rect
, line
);
365 rect
.Deflate(m_ptMargins
.x
, m_ptMargins
.y
);
366 OnDrawItem(dc
, rect
, line
);
368 else // no intersection
370 if ( rectLine
.GetTop() > rectUpdate
.GetBottom() )
372 // we are already below the update rect, no need to continue
376 //else: the next line may intersect the update rect
383 // ============================================================================
384 // wxVListBox keyboard/mouse handling
385 // ============================================================================
387 void wxVListBox
::DoHandleItemClick(int item
, int flags
)
389 // has anything worth telling the client code about happened?
392 if ( HasMultipleSelection() )
394 // select the iteem clicked?
397 // NB: the keyboard interface we implement here corresponds to
398 // wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
400 if ( flags
& ItemClick_Shift
)
402 if ( m_current
!= wxNOT_FOUND
)
404 if ( m_anchor
== wxNOT_FOUND
)
405 m_anchor
= m_current
;
409 // only the range from the selection anchor to new m_current
414 if ( SelectRange(m_anchor
, item
) )
417 //else: treat it as ordinary click/keypress
419 else // Shift not pressed
423 if ( flags
& ItemClick_Ctrl
)
427 if ( !(flags
& ItemClick_Kbd
) )
431 // the status of the item has definitely changed
434 //else: Ctrl-arrow pressed, don't change selection
436 //else: behave as in single selection case
441 // make the clicked item the only selection
450 // in any case the item should become the current one
451 if ( DoSetCurrent(item
) )
453 if ( !HasMultipleSelection() )
455 // this has also changed the selection for single selection case
462 // notify the user about the selection change
465 //else: nothing changed at all
468 // ----------------------------------------------------------------------------
470 // ----------------------------------------------------------------------------
472 void wxVListBox
::OnKeyDown(wxKeyEvent
& event
)
474 // flags for DoHandleItemClick()
475 int flags
= ItemClick_Kbd
;
477 int current
= 0; // just to silent the stupid compiler warnings
478 switch ( event
.GetKeyCode() )
485 current
= GetLineCount() - 1;
489 if ( m_current
== (int)GetLineCount() - 1 )
492 current
= m_current
+ 1;
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
507 current
= GetFirstVisibleLine();
512 if ( m_current
== (int)GetFirstVisibleLine() )
517 current
= GetFirstVisibleLine();
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
;
533 if ( event
.ShiftDown() )
534 flags
|= ItemClick_Shift
;
535 if ( event
.ControlDown() )
536 flags
|= ItemClick_Ctrl
;
538 DoHandleItemClick(current
, flags
);
541 // ----------------------------------------------------------------------------
542 // wxVListBox mouse handling
543 // ----------------------------------------------------------------------------
545 void wxVListBox
::OnLeftDown(wxMouseEvent
& event
)
547 int item
= HitTest(event
.GetPosition());
549 if ( item
!= wxNOT_FOUND
)
552 if ( event
.ShiftDown() )
553 flags
|= ItemClick_Shift
;
555 // under Mac Apple-click is used in the same way as Ctrl-click
558 if ( event
.MetaDown() )
560 if ( event
.ControlDown() )
562 flags
|= ItemClick_Ctrl
;
564 DoHandleItemClick(item
, flags
);
568 void wxVListBox
::OnLeftDClick(wxMouseEvent
& event
)
570 int item
= HitTest(event
.GetPosition());
571 if ( item
!= wxNOT_FOUND
)
573 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, GetId());
574 event
.SetEventObject(this);
575 event
.m_commandInt
= item
;
577 (void)GetEventHandler()->ProcessEvent(event
);