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"
39 #include "wx/renderer.h"
41 // ----------------------------------------------------------------------------
43 // ----------------------------------------------------------------------------
45 BEGIN_EVENT_TABLE(wxVListBox
, wxVScrolledWindow
)
46 EVT_PAINT(wxVListBox::OnPaint
)
48 EVT_KEY_DOWN(wxVListBox::OnKeyDown
)
49 EVT_LEFT_DOWN(wxVListBox::OnLeftDown
)
50 EVT_LEFT_DCLICK(wxVListBox::OnLeftDClick
)
52 EVT_SET_FOCUS(wxVListBox::OnSetOrKillFocus
)
53 EVT_KILL_FOCUS(wxVListBox::OnSetOrKillFocus
)
56 // ============================================================================
58 // ============================================================================
60 IMPLEMENT_ABSTRACT_CLASS(wxVListBox
, wxVScrolledWindow
)
62 // ----------------------------------------------------------------------------
63 // wxVListBox creation
64 // ----------------------------------------------------------------------------
66 void wxVListBox::Init()
69 m_anchor
= wxNOT_FOUND
;
73 bool wxVListBox::Create(wxWindow
*parent
,
80 style
|= wxWANTS_CHARS
| wxFULL_REPAINT_ON_RESIZE
;
81 if ( !wxVScrolledWindow::Create(parent
, id
, pos
, size
, style
, name
) )
84 if ( style
& wxLB_MULTIPLE
)
85 m_selStore
= new wxSelectionStore
;
87 // make sure the native widget has the right colour since we do
88 // transparent drawing by default
89 SetBackgroundColour(GetBackgroundColour());
91 // leave m_colBgSel in an invalid state: it means for OnDrawBackground()
92 // to use wxRendererNative instead of painting selection bg ourselves
93 m_colBgSel
= wxNullColour
;
95 // flicker-free drawing requires this
96 SetBackgroundStyle(wxBG_STYLE_CUSTOM
);
101 wxVListBox::~wxVListBox()
106 void wxVListBox::SetItemCount(size_t count
)
110 // tell the selection store that our number of items has changed
111 m_selStore
->SetItemCount(count
);
117 // ----------------------------------------------------------------------------
118 // selection handling
119 // ----------------------------------------------------------------------------
121 bool wxVListBox::IsSelected(size_t line
) const
123 return m_selStore
? m_selStore
->IsSelected(line
) : (int)line
== m_current
;
126 bool wxVListBox::Select(size_t item
, bool select
)
128 wxCHECK_MSG( m_selStore
, false,
129 _T("Select() may only be used with multiselection listbox") );
131 wxCHECK_MSG( item
< GetItemCount(), false,
132 _T("Select(): invalid item index") );
134 bool changed
= m_selStore
->SelectItem(item
, select
);
137 // selection really changed
146 bool wxVListBox::SelectRange(size_t from
, size_t to
)
148 wxCHECK_MSG( m_selStore
, false,
149 _T("SelectRange() may only be used with multiselection listbox") );
151 // make sure items are in correct order
159 wxCHECK_MSG( to
< GetItemCount(), false,
160 _T("SelectRange(): invalid item index") );
163 if ( !m_selStore
->SelectRange(from
, to
, true, &changed
) )
165 // too many items have changed, we didn't record them in changed array
166 // so we have no choice but to refresh everything between from and to
167 RefreshRows(from
, to
);
169 else // we've got the indices of the changed items
171 const size_t count
= changed
.GetCount();
178 // refresh just the lines which have really changed
179 for ( size_t n
= 0; n
< count
; n
++ )
181 RefreshRow(changed
[n
]);
189 bool wxVListBox::DoSelectAll(bool select
)
191 wxCHECK_MSG( m_selStore
, false,
192 _T("SelectAll may only be used with multiselection listbox") );
194 size_t count
= GetItemCount();
198 if ( !m_selStore
->SelectRange(0, count
- 1, select
) ||
211 bool wxVListBox::DoSetCurrent(int current
)
213 wxASSERT_MSG( current
== wxNOT_FOUND
||
214 (current
>= 0 && (size_t)current
< GetItemCount()),
215 _T("wxVListBox::DoSetCurrent(): invalid item index") );
217 if ( current
== m_current
)
223 if ( m_current
!= wxNOT_FOUND
)
224 RefreshRow(m_current
);
228 if ( m_current
!= wxNOT_FOUND
)
230 // if the line is not visible at all, we scroll it into view but we
231 // don't need to refresh it -- it will be redrawn anyhow
232 if ( !IsVisible(m_current
) )
234 ScrollToRow(m_current
);
236 else // line is at least partly visible
238 // it is, indeed, only partly visible, so scroll it into view to
239 // make it entirely visible
240 while ( (size_t)m_current
+ 1 == GetVisibleRowsEnd() &&
241 ScrollToRow(GetVisibleBegin() + 1) ) ;
243 // but in any case refresh it as even if it was only partly visible
244 // before we need to redraw it entirely as its background changed
245 RefreshRow(m_current
);
252 void wxVListBox::SendSelectedEvent()
254 wxASSERT_MSG( m_current
!= wxNOT_FOUND
,
255 _T("SendSelectedEvent() shouldn't be called") );
257 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, GetId());
258 event
.SetEventObject(this);
259 event
.SetInt(m_current
);
261 (void)GetEventHandler()->ProcessEvent(event
);
264 void wxVListBox::SetSelection(int selection
)
266 wxCHECK_RET( selection
== wxNOT_FOUND
||
267 (selection
>= 0 && (size_t)selection
< GetItemCount()),
268 _T("wxVListBox::SetSelection(): invalid item index") );
270 if ( HasMultipleSelection() )
272 if (selection
!= wxNOT_FOUND
)
276 m_anchor
= selection
;
279 DoSetCurrent(selection
);
282 size_t wxVListBox::GetSelectedCount() const
284 return m_selStore
? m_selStore
->GetSelectedCount()
285 : m_current
== wxNOT_FOUND
? 0 : 1;
288 int wxVListBox::GetFirstSelected(unsigned long& cookie
) const
292 return GetNextSelected(cookie
);
295 int wxVListBox::GetNextSelected(unsigned long& cookie
) const
297 wxCHECK_MSG( m_selStore
, wxNOT_FOUND
,
298 _T("GetFirst/NextSelected() may only be used with multiselection listboxes") );
300 while ( cookie
< GetItemCount() )
302 if ( IsSelected(cookie
++) )
309 void wxVListBox::RefreshSelected()
311 // only refresh those items which are currently visible and selected:
312 for ( size_t n
= GetVisibleBegin(), end
= GetVisibleEnd(); n
< end
; n
++ )
319 // ----------------------------------------------------------------------------
320 // wxVListBox appearance parameters
321 // ----------------------------------------------------------------------------
323 void wxVListBox::SetMargins(const wxPoint
& pt
)
325 if ( pt
!= m_ptMargins
)
333 void wxVListBox::SetSelectionBackground(const wxColour
& col
)
338 // ----------------------------------------------------------------------------
339 // wxVListBox painting
340 // ----------------------------------------------------------------------------
342 wxCoord
wxVListBox::OnGetRowHeight(size_t line
) const
344 return OnMeasureItem(line
) + 2*m_ptMargins
.y
;
347 void wxVListBox::OnDrawSeparator(wxDC
& WXUNUSED(dc
),
348 wxRect
& WXUNUSED(rect
),
349 size_t WXUNUSED(n
)) const
353 void wxVListBox::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
355 if ( m_colBgSel
.IsOk() )
357 // we need to render selected and current items differently
358 const bool isSelected
= IsSelected(n
),
359 isCurrent
= IsCurrent(n
);
360 if ( isSelected
|| isCurrent
)
364 dc
.SetBrush(wxBrush(m_colBgSel
, wxSOLID
));
368 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
370 dc
.SetPen(*(isCurrent
? wxBLACK_PEN
: wxTRANSPARENT_PEN
));
371 dc
.DrawRectangle(rect
);
373 //else: do nothing for the normal items
375 else // use wxRendererNative for a more native look&feel:
379 flags
|= wxCONTROL_SELECTED
;
381 flags
|= wxCONTROL_CURRENT
;
382 if ( wxWindow::FindFocus() == wx_const_cast(wxVListBox
*, this) )
383 flags
|= wxCONTROL_FOCUSED
;
385 wxRendererNative::Get().DrawItemSelectionRect(
386 wx_const_cast(wxVListBox
*, this), dc
, rect
, flags
);
390 void wxVListBox::OnPaint(wxPaintEvent
& WXUNUSED(event
))
392 wxSize clientSize
= GetClientSize();
394 wxAutoBufferedPaintDC
dc(this);
396 // the update rectangle
397 wxRect rectUpdate
= GetUpdateClientRect();
399 // fill it with background colour
400 dc
.SetBackground(GetBackgroundColour());
403 // the bounding rectangle of the current line
405 rectRow
.width
= clientSize
.x
;
407 // iterate over all visible lines
408 const size_t lineMax
= GetVisibleEnd();
409 for ( size_t line
= GetVisibleBegin(); line
< lineMax
; line
++ )
411 const wxCoord hRow
= OnGetRowHeight(line
);
413 rectRow
.height
= hRow
;
415 // and draw the ones which intersect the update rect
416 if ( rectRow
.Intersects(rectUpdate
) )
418 // don't allow drawing outside of the lines rectangle
419 wxDCClipper
clip(dc
, rectRow
);
421 wxRect rect
= rectRow
;
422 OnDrawBackground(dc
, rect
, line
);
424 OnDrawSeparator(dc
, rect
, line
);
426 rect
.Deflate(m_ptMargins
.x
, m_ptMargins
.y
);
427 OnDrawItem(dc
, rect
, line
);
429 else // no intersection
431 if ( rectRow
.GetTop() > rectUpdate
.GetBottom() )
433 // we are already below the update rect, no need to continue
437 //else: the next line may intersect the update rect
444 void wxVListBox::OnSetOrKillFocus(wxFocusEvent
& WXUNUSED(event
))
446 // we need to repaint the selection when we get the focus since
447 // wxRendererNative in general draws the focused selection differently
448 // from the unfocused selection (see OnDrawItem):
453 // ============================================================================
454 // wxVListBox keyboard/mouse handling
455 // ============================================================================
457 void wxVListBox::DoHandleItemClick(int item
, int flags
)
459 // has anything worth telling the client code about happened?
462 if ( HasMultipleSelection() )
464 // select the iteem clicked?
467 // NB: the keyboard interface we implement here corresponds to
468 // wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
470 if ( flags
& ItemClick_Shift
)
472 if ( m_current
!= wxNOT_FOUND
)
474 if ( m_anchor
== wxNOT_FOUND
)
475 m_anchor
= m_current
;
479 // only the range from the selection anchor to new m_current
484 if ( SelectRange(m_anchor
, item
) )
487 //else: treat it as ordinary click/keypress
489 else // Shift not pressed
493 if ( flags
& ItemClick_Ctrl
)
497 if ( !(flags
& ItemClick_Kbd
) )
501 // the status of the item has definitely changed
504 //else: Ctrl-arrow pressed, don't change selection
506 //else: behave as in single selection case
511 // make the clicked item the only selection
520 // in any case the item should become the current one
521 if ( DoSetCurrent(item
) )
523 if ( !HasMultipleSelection() )
525 // this has also changed the selection for single selection case
532 // notify the user about the selection change
535 //else: nothing changed at all
538 // ----------------------------------------------------------------------------
540 // ----------------------------------------------------------------------------
542 void wxVListBox::OnKeyDown(wxKeyEvent
& event
)
544 // flags for DoHandleItemClick()
545 int flags
= ItemClick_Kbd
;
548 switch ( event
.GetKeyCode() )
555 current
= GetRowCount() - 1;
559 if ( m_current
== (int)GetRowCount() - 1 )
562 current
= m_current
+ 1;
566 if ( m_current
== wxNOT_FOUND
)
567 current
= GetRowCount() - 1;
568 else if ( m_current
!= 0 )
569 current
= m_current
- 1;
570 else // m_current == 0
576 current
= GetVisibleBegin();
580 if ( m_current
== (int)GetVisibleBegin() )
585 current
= GetVisibleBegin();
589 // hack: pressing space should work like a mouse click rather than
590 // like a keyboard arrow press, so trick DoHandleItemClick() in
591 // thinking we were clicked
592 flags
&= ~ItemClick_Kbd
;
598 // Since we are using wxWANTS_CHARS we need to send navigation
599 // events for the tabs on MSW
601 wxNavigationKeyEvent ne
;
602 ne
.SetDirection(!event
.ShiftDown());
603 ne
.SetCurrentFocus(this);
604 ne
.SetEventObject(this);
605 GetParent()->GetEventHandler()->ProcessEvent(ne
);
607 // fall through to default
611 current
= 0; // just to silent the stupid compiler warnings
612 wxUnusedVar(current
);
616 if ( event
.ShiftDown() )
617 flags
|= ItemClick_Shift
;
618 if ( event
.ControlDown() )
619 flags
|= ItemClick_Ctrl
;
621 DoHandleItemClick(current
, flags
);
624 // ----------------------------------------------------------------------------
625 // wxVListBox mouse handling
626 // ----------------------------------------------------------------------------
628 void wxVListBox::OnLeftDown(wxMouseEvent
& event
)
632 int item
= HitTest(event
.GetPosition());
634 if ( item
!= wxNOT_FOUND
)
637 if ( event
.ShiftDown() )
638 flags
|= ItemClick_Shift
;
640 // under Mac Apple-click is used in the same way as Ctrl-click
643 if ( event
.MetaDown() )
645 if ( event
.ControlDown() )
647 flags
|= ItemClick_Ctrl
;
649 DoHandleItemClick(item
, flags
);
653 void wxVListBox::OnLeftDClick(wxMouseEvent
& eventMouse
)
655 int item
= HitTest(eventMouse
.GetPosition());
656 if ( item
!= wxNOT_FOUND
)
659 // if item double-clicked was not yet selected, then treat
660 // this event as a left-click instead
661 if ( item
== m_current
)
663 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, GetId());
664 event
.SetEventObject(this);
667 (void)GetEventHandler()->ProcessEvent(event
);
671 OnLeftDown(eventMouse
);
678 // ----------------------------------------------------------------------------
679 // use the same default attributes as wxListBox
680 // ----------------------------------------------------------------------------
684 wxVListBox::GetClassDefaultAttributes(wxWindowVariant variant
)
686 return wxListBox::GetClassDefaultAttributes(variant
);