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 IMPLEMENT_ABSTRACT_CLASS(wxVListBox
, wxVScrolledWindow
)
53 // ----------------------------------------------------------------------------
54 // wxVListBox creation
55 // ----------------------------------------------------------------------------
57 void wxVListBox::Init()
60 m_anchor
= wxNOT_FOUND
;
64 bool wxVListBox::Create(wxWindow
*parent
,
71 style
|= wxWANTS_CHARS
;
72 if ( !wxVScrolledWindow::Create(parent
, id
, pos
, size
, style
, name
) )
75 if ( style
& wxLB_MULTIPLE
)
76 m_selStore
= new wxSelectionStore
;
78 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX
));
79 SetForegroundColour(parent
->GetForegroundColour());
81 // ensure that the font actually changes and is set.
83 SetFont(parent
->GetFont());
85 m_colBgSel
= wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT
);
87 m_bestSize
= GetSize();
92 wxVListBox::~wxVListBox()
97 void wxVListBox::SetItemCount(size_t count
)
101 // tell the selection store that our number of items has changed
102 m_selStore
->SetItemCount(count
);
108 // ----------------------------------------------------------------------------
109 // selection handling
110 // ----------------------------------------------------------------------------
112 bool wxVListBox::IsSelected(size_t line
) const
114 return m_selStore
? m_selStore
->IsSelected(line
) : (int)line
== m_current
;
117 bool wxVListBox::Select(size_t item
, bool select
)
119 wxCHECK_MSG( m_selStore
, false,
120 _T("Select() may only be used with multiselection listbox") );
122 wxCHECK_MSG( item
< GetItemCount(), false,
123 _T("Select(): invalid item index") );
125 bool changed
= m_selStore
->SelectItem(item
, select
);
128 // selection really changed
137 bool wxVListBox::SelectRange(size_t from
, size_t to
)
139 wxCHECK_MSG( m_selStore
, false,
140 _T("SelectRange() may only be used with multiselection listbox") );
142 // make sure items are in correct order
150 wxCHECK_MSG( to
< GetItemCount(), false,
151 _T("SelectRange(): invalid item index") );
154 if ( !m_selStore
->SelectRange(from
, to
, true, &changed
) )
156 // too many items have changed, we didn't record them in changed array
157 // so we have no choice but to refresh everything between from and to
158 RefreshLines(from
, to
);
160 else // we've got the indices of the changed items
162 const size_t count
= changed
.GetCount();
169 // refresh just the lines which have really changed
170 for ( size_t n
= 0; n
< count
; n
++ )
172 RefreshLine(changed
[n
]);
180 bool wxVListBox::DoSelectAll(bool select
)
182 wxCHECK_MSG( m_selStore
, false,
183 _T("SelectAll may only be used with multiselection listbox") );
185 size_t count
= GetItemCount();
189 if ( !m_selStore
->SelectRange(0, count
- 1, select
) ||
202 bool wxVListBox::DoSetCurrent(int current
)
204 wxASSERT_MSG( current
== wxNOT_FOUND
||
205 (current
>= 0 && (size_t)current
< GetItemCount()),
206 _T("wxVListBox::DoSetCurrent(): invalid item index") );
208 if ( current
== m_current
)
214 if ( m_current
!= wxNOT_FOUND
)
215 RefreshLine(m_current
);
219 if ( m_current
!= wxNOT_FOUND
)
221 // if the line is not visible at all, we scroll it into view but we
222 // don't need to refresh it -- it will be redrawn anyhow
223 if ( !IsVisible(m_current
) )
225 ScrollToLine(m_current
);
227 else // line is at least partly visible
229 // it is, indeed, only partly visible, so scroll it into view to
230 // make it entirely visible
231 if ( (size_t)m_current
== GetLastVisibleLine() )
233 ScrollToLine(m_current
);
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
);
245 void wxVListBox::SendSelectedEvent()
247 wxASSERT_MSG( m_current
!= wxNOT_FOUND
,
248 _T("SendSelectedEvent() shouldn't be called") );
250 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, GetId());
251 event
.SetEventObject(this);
252 event
.m_commandInt
= m_current
;
254 (void)GetEventHandler()->ProcessEvent(event
);
257 void wxVListBox::SetSelection(int selection
)
259 wxCHECK_RET( selection
== wxNOT_FOUND
||
260 (selection
>= 0 && (size_t)selection
< GetItemCount()),
261 _T("wxVListBox::SetSelection(): invalid item index") );
263 if ( HasMultipleSelection() )
266 m_anchor
= selection
;
269 DoSetCurrent(selection
);
272 size_t wxVListBox::GetSelectedCount() const
274 return m_selStore
? m_selStore
->GetSelectedCount()
275 : m_current
== wxNOT_FOUND
? 0 : 1;
278 int wxVListBox::GetFirstSelected(unsigned long& cookie
) const
282 return GetNextSelected(cookie
);
285 int wxVListBox::GetNextSelected(unsigned long& cookie
) const
287 wxCHECK_MSG( m_selStore
, wxNOT_FOUND
,
288 _T("GetFirst/NextSelected() may only be used with multiselection listboxes") );
290 while ( cookie
< GetItemCount() )
292 if ( IsSelected(cookie
++) )
299 // ----------------------------------------------------------------------------
300 // wxVListBox appearance parameters
301 // ----------------------------------------------------------------------------
303 void wxVListBox::SetMargins(const wxPoint
& pt
)
305 if ( pt
!= m_ptMargins
)
313 void wxVListBox::SetSelectionBackground(const wxColour
& col
)
318 // ----------------------------------------------------------------------------
319 // wxVListBox painting
320 // ----------------------------------------------------------------------------
322 wxCoord
wxVListBox::OnGetLineHeight(size_t line
) const
324 return OnMeasureItem(line
) + 2*m_ptMargins
.y
;
327 void wxVListBox::OnDrawSeparator(wxDC
& WXUNUSED(dc
),
328 wxRect
& WXUNUSED(rect
),
329 size_t WXUNUSED(n
)) const
333 void wxVListBox::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
335 // we need to render selected and current items differently
336 const bool isSelected
= IsSelected(n
),
337 isCurrent
= IsCurrent(n
);
338 if ( isSelected
|| isCurrent
)
342 dc
.SetBrush(wxBrush(m_colBgSel
, wxSOLID
));
346 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
349 dc
.SetPen(*(isCurrent
? wxBLACK_PEN
: wxTRANSPARENT_PEN
));
351 dc
.DrawRectangle(rect
);
353 //else: do nothing for the normal items
356 void wxVListBox::OnPaint(wxPaintEvent
& WXUNUSED(event
))
360 // the update rectangle
361 wxRect rectUpdate
= GetUpdateClientRect();
363 // the bounding rectangle of the current line
365 rectLine
.width
= GetClientSize().x
;
367 // iterate over all visible lines
368 const size_t lineMax
= GetLastVisibleLine();
369 for ( size_t line
= GetFirstVisibleLine(); line
<= lineMax
; line
++ )
371 const wxCoord hLine
= OnGetLineHeight(line
);
373 rectLine
.height
= hLine
;
375 // and draw the ones which intersect the update rect
376 if ( rectLine
.Intersects(rectUpdate
) )
378 // don't allow drawing outside of the lines rectangle
379 wxDCClipper
clip(dc
, rectLine
);
381 wxRect rect
= rectLine
;
382 OnDrawBackground(dc
, rect
, line
);
384 OnDrawSeparator(dc
, rect
, line
);
386 rect
.Deflate(m_ptMargins
.x
, m_ptMargins
.y
);
387 OnDrawItem(dc
, rect
, line
);
389 else // no intersection
391 if ( rectLine
.GetTop() > rectUpdate
.GetBottom() )
393 // we are already below the update rect, no need to continue
397 //else: the next line may intersect the update rect
404 // ============================================================================
405 // wxVListBox keyboard/mouse handling
406 // ============================================================================
408 void wxVListBox::DoHandleItemClick(int item
, int flags
)
410 // has anything worth telling the client code about happened?
413 if ( HasMultipleSelection() )
415 // select the iteem clicked?
418 // NB: the keyboard interface we implement here corresponds to
419 // wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
421 if ( flags
& ItemClick_Shift
)
423 if ( m_current
!= wxNOT_FOUND
)
425 if ( m_anchor
== wxNOT_FOUND
)
426 m_anchor
= m_current
;
430 // only the range from the selection anchor to new m_current
435 if ( SelectRange(m_anchor
, item
) )
438 //else: treat it as ordinary click/keypress
440 else // Shift not pressed
444 if ( flags
& ItemClick_Ctrl
)
448 if ( !(flags
& ItemClick_Kbd
) )
452 // the status of the item has definitely changed
455 //else: Ctrl-arrow pressed, don't change selection
457 //else: behave as in single selection case
462 // make the clicked item the only selection
471 // in any case the item should become the current one
472 if ( DoSetCurrent(item
) )
474 if ( !HasMultipleSelection() )
476 // this has also changed the selection for single selection case
483 // notify the user about the selection change
486 //else: nothing changed at all
489 // ----------------------------------------------------------------------------
491 // ----------------------------------------------------------------------------
493 void wxVListBox::OnKeyDown(wxKeyEvent
& event
)
495 // flags for DoHandleItemClick()
496 int flags
= ItemClick_Kbd
;
499 switch ( event
.GetKeyCode() )
506 current
= GetLineCount() - 1;
510 if ( m_current
== (int)GetLineCount() - 1 )
513 current
= m_current
+ 1;
517 if ( m_current
== wxNOT_FOUND
)
518 current
= GetLineCount() - 1;
519 else if ( m_current
!= 0 )
520 current
= m_current
- 1;
521 else // m_current == 0
528 current
= GetFirstVisibleLine();
533 if ( m_current
== (int)GetFirstVisibleLine() )
538 current
= GetFirstVisibleLine();
542 // hack: pressing space should work like a mouse click rather than
543 // like a keyboard arrow press, so trick DoHandleItemClick() in
544 // thinking we were clicked
545 flags
&= ~ItemClick_Kbd
;
551 // Since we are using wxWANTS_CHARS we need to send navigation
552 // events for the tabs on MSW
554 wxNavigationKeyEvent ne
;
555 ne
.SetDirection(!event
.ShiftDown());
556 ne
.SetCurrentFocus(this);
557 ne
.SetEventObject(this);
558 GetParent()->GetEventHandler()->ProcessEvent(ne
);
560 // fall through to default
564 current
= 0; // just to silent the stupid compiler warnings
565 wxUnusedVar(current
);
569 if ( event
.ShiftDown() )
570 flags
|= ItemClick_Shift
;
571 if ( event
.ControlDown() )
572 flags
|= ItemClick_Ctrl
;
574 DoHandleItemClick(current
, flags
);
577 // ----------------------------------------------------------------------------
578 // wxVListBox mouse handling
579 // ----------------------------------------------------------------------------
581 void wxVListBox::OnLeftDown(wxMouseEvent
& event
)
583 int item
= HitTest(event
.GetPosition());
585 if ( item
!= wxNOT_FOUND
)
588 if ( event
.ShiftDown() )
589 flags
|= ItemClick_Shift
;
591 // under Mac Apple-click is used in the same way as Ctrl-click
594 if ( event
.MetaDown() )
596 if ( event
.ControlDown() )
598 flags
|= ItemClick_Ctrl
;
600 DoHandleItemClick(item
, flags
);
604 void wxVListBox::OnLeftDClick(wxMouseEvent
& event
)
606 int item
= HitTest(event
.GetPosition());
607 if ( item
!= wxNOT_FOUND
)
609 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, GetId());
610 event
.SetEventObject(this);
611 event
.m_commandInt
= item
;
613 (void)GetEventHandler()->ProcessEvent(event
);