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
)
55 EVT_SIZE(wxVListBox::OnSize
)
58 // ============================================================================
60 // ============================================================================
62 IMPLEMENT_ABSTRACT_CLASS(wxVListBox
, wxVScrolledWindow
)
63 const char wxVListBoxNameStr
[] = "wxVListBox";
65 // ----------------------------------------------------------------------------
66 // wxVListBox creation
67 // ----------------------------------------------------------------------------
69 void wxVListBox::Init()
72 m_anchor
= wxNOT_FOUND
;
76 bool wxVListBox::Create(wxWindow
*parent
,
84 if ( (style
& wxBORDER_MASK
) == wxDEFAULT
)
85 style
|= wxBORDER_THEME
;
88 style
|= wxWANTS_CHARS
| wxFULL_REPAINT_ON_RESIZE
;
89 if ( !wxVScrolledWindow::Create(parent
, id
, pos
, size
, style
, name
) )
92 if ( style
& wxLB_MULTIPLE
)
93 m_selStore
= new wxSelectionStore
;
95 // make sure the native widget has the right colour since we do
96 // transparent drawing by default
97 SetBackgroundColour(GetBackgroundColour());
99 // leave m_colBgSel in an invalid state: it means for OnDrawBackground()
100 // to use wxRendererNative instead of painting selection bg ourselves
101 m_colBgSel
= wxNullColour
;
103 // flicker-free drawing requires this
104 SetBackgroundStyle(wxBG_STYLE_CUSTOM
);
109 wxVListBox::~wxVListBox()
114 void wxVListBox::SetItemCount(size_t count
)
116 // don't leave the current index invalid
117 if ( m_current
!= wxNOT_FOUND
&& (size_t)m_current
>= count
)
118 m_current
= count
- 1; // also ok when count == 0 as wxNOT_FOUND == -1
122 // tell the selection store that our number of items has changed
123 m_selStore
->SetItemCount(count
);
129 // ----------------------------------------------------------------------------
130 // selection handling
131 // ----------------------------------------------------------------------------
133 bool wxVListBox::IsSelected(size_t line
) const
135 return m_selStore
? m_selStore
->IsSelected(line
) : (int)line
== m_current
;
138 bool wxVListBox::Select(size_t item
, bool select
)
140 wxCHECK_MSG( m_selStore
, false,
141 _T("Select() may only be used with multiselection listbox") );
143 wxCHECK_MSG( item
< GetItemCount(), false,
144 _T("Select(): invalid item index") );
146 bool changed
= m_selStore
->SelectItem(item
, select
);
149 // selection really changed
158 bool wxVListBox::SelectRange(size_t from
, size_t to
)
160 wxCHECK_MSG( m_selStore
, false,
161 _T("SelectRange() may only be used with multiselection listbox") );
163 // make sure items are in correct order
171 wxCHECK_MSG( to
< GetItemCount(), false,
172 _T("SelectRange(): invalid item index") );
175 if ( !m_selStore
->SelectRange(from
, to
, true, &changed
) )
177 // too many items have changed, we didn't record them in changed array
178 // so we have no choice but to refresh everything between from and to
179 RefreshRows(from
, to
);
181 else // we've got the indices of the changed items
183 const size_t count
= changed
.GetCount();
190 // refresh just the lines which have really changed
191 for ( size_t n
= 0; n
< count
; n
++ )
193 RefreshRow(changed
[n
]);
201 bool wxVListBox::DoSelectAll(bool select
)
203 wxCHECK_MSG( m_selStore
, false,
204 _T("SelectAll may only be used with multiselection listbox") );
206 size_t count
= GetItemCount();
210 if ( !m_selStore
->SelectRange(0, count
- 1, select
) ||
223 bool wxVListBox::DoSetCurrent(int current
)
225 wxASSERT_MSG( current
== wxNOT_FOUND
||
226 (current
>= 0 && (size_t)current
< GetItemCount()),
227 _T("wxVListBox::DoSetCurrent(): invalid item index") );
229 if ( current
== m_current
)
235 if ( m_current
!= wxNOT_FOUND
)
236 RefreshRow(m_current
);
240 if ( m_current
!= wxNOT_FOUND
)
242 // if the line is not visible at all, we scroll it into view but we
243 // don't need to refresh it -- it will be redrawn anyhow
244 if ( !IsVisible(m_current
) )
246 ScrollToRow(m_current
);
248 else // line is at least partly visible
250 // it is, indeed, only partly visible, so scroll it into view to
251 // make it entirely visible
252 while ( (size_t)m_current
+ 1 == GetVisibleRowsEnd() &&
253 ScrollToRow(GetVisibleBegin() + 1) ) ;
255 // but in any case refresh it as even if it was only partly visible
256 // before we need to redraw it entirely as its background changed
257 RefreshRow(m_current
);
264 void wxVListBox::SendSelectedEvent()
266 wxASSERT_MSG( m_current
!= wxNOT_FOUND
,
267 _T("SendSelectedEvent() shouldn't be called") );
269 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, GetId());
270 event
.SetEventObject(this);
271 event
.SetInt(m_current
);
273 (void)GetEventHandler()->ProcessEvent(event
);
276 void wxVListBox::SetSelection(int selection
)
278 wxCHECK_RET( selection
== wxNOT_FOUND
||
279 (selection
>= 0 && (size_t)selection
< GetItemCount()),
280 _T("wxVListBox::SetSelection(): invalid item index") );
282 if ( HasMultipleSelection() )
284 if (selection
!= wxNOT_FOUND
)
288 m_anchor
= selection
;
291 DoSetCurrent(selection
);
294 size_t wxVListBox::GetSelectedCount() const
296 return m_selStore
? m_selStore
->GetSelectedCount()
297 : m_current
== wxNOT_FOUND
? 0 : 1;
300 int wxVListBox::GetFirstSelected(unsigned long& cookie
) const
304 return GetNextSelected(cookie
);
307 int wxVListBox::GetNextSelected(unsigned long& cookie
) const
309 wxCHECK_MSG( m_selStore
, wxNOT_FOUND
,
310 _T("GetFirst/NextSelected() may only be used with multiselection listboxes") );
312 while ( cookie
< GetItemCount() )
314 if ( IsSelected(cookie
++) )
321 void wxVListBox::RefreshSelected()
323 // only refresh those items which are currently visible and selected:
324 for ( size_t n
= GetVisibleBegin(), end
= GetVisibleEnd(); n
< end
; n
++ )
331 wxRect
wxVListBox::GetItemRect(size_t n
) const
335 // check that this item is visible
336 const size_t lineMax
= GetVisibleEnd();
339 size_t line
= GetVisibleBegin();
345 itemrect
.y
+= itemrect
.height
;
346 itemrect
.height
= OnGetRowHeight(line
);
351 itemrect
.width
= GetClientSize().x
;
356 // ----------------------------------------------------------------------------
357 // wxVListBox appearance parameters
358 // ----------------------------------------------------------------------------
360 void wxVListBox::SetMargins(const wxPoint
& pt
)
362 if ( pt
!= m_ptMargins
)
370 void wxVListBox::SetSelectionBackground(const wxColour
& col
)
375 // ----------------------------------------------------------------------------
376 // wxVListBox painting
377 // ----------------------------------------------------------------------------
379 wxCoord
wxVListBox::OnGetRowHeight(size_t line
) const
381 return OnMeasureItem(line
) + 2*m_ptMargins
.y
;
384 void wxVListBox::OnDrawSeparator(wxDC
& WXUNUSED(dc
),
385 wxRect
& WXUNUSED(rect
),
386 size_t WXUNUSED(n
)) const
391 wxVListBox::DoDrawSolidBackground(const wxColour
& col
,
399 // we need to render selected and current items differently
400 const bool isSelected
= IsSelected(n
),
401 isCurrent
= IsCurrent(n
);
402 if ( isSelected
|| isCurrent
)
406 dc
.SetBrush(wxBrush(col
, wxBRUSHSTYLE_SOLID
));
410 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
412 dc
.SetPen(*(isCurrent
? wxBLACK_PEN
: wxTRANSPARENT_PEN
));
413 dc
.DrawRectangle(rect
);
415 //else: do nothing for the normal items
420 void wxVListBox::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
422 // use wxRendererNative for more native look unless we use custom bg colour
423 if ( !DoDrawSolidBackground(m_colBgSel
, dc
, rect
, n
) )
427 flags
|= wxCONTROL_SELECTED
;
429 flags
|= wxCONTROL_CURRENT
;
430 if ( wxWindow::FindFocus() == const_cast<wxVListBox
*>(this) )
431 flags
|= wxCONTROL_FOCUSED
;
433 wxRendererNative::Get().DrawItemSelectionRect(
434 const_cast<wxVListBox
*>(this), dc
, rect
, flags
);
438 void wxVListBox::OnPaint(wxPaintEvent
& WXUNUSED(event
))
440 wxSize clientSize
= GetClientSize();
442 wxAutoBufferedPaintDC
dc(this);
444 // the update rectangle
445 wxRect rectUpdate
= GetUpdateClientRect();
447 // fill it with background colour
448 dc
.SetBackground(GetBackgroundColour());
451 // the bounding rectangle of the current line
453 rectRow
.width
= clientSize
.x
;
455 // iterate over all visible lines
456 const size_t lineMax
= GetVisibleEnd();
457 for ( size_t line
= GetVisibleBegin(); line
< lineMax
; line
++ )
459 const wxCoord hRow
= OnGetRowHeight(line
);
461 rectRow
.height
= hRow
;
463 // and draw the ones which intersect the update rect
464 if ( rectRow
.Intersects(rectUpdate
) )
466 // don't allow drawing outside of the lines rectangle
467 wxDCClipper
clip(dc
, rectRow
);
469 wxRect rect
= rectRow
;
470 OnDrawBackground(dc
, rect
, line
);
472 OnDrawSeparator(dc
, rect
, line
);
474 rect
.Deflate(m_ptMargins
.x
, m_ptMargins
.y
);
475 OnDrawItem(dc
, rect
, line
);
477 else // no intersection
479 if ( rectRow
.GetTop() > rectUpdate
.GetBottom() )
481 // we are already below the update rect, no need to continue
485 //else: the next line may intersect the update rect
492 void wxVListBox::OnSetOrKillFocus(wxFocusEvent
& WXUNUSED(event
))
494 // we need to repaint the selection when we get the focus since
495 // wxRendererNative in general draws the focused selection differently
496 // from the unfocused selection (see OnDrawItem):
500 void wxVListBox::OnSize(wxSizeEvent
& event
)
506 // ============================================================================
507 // wxVListBox keyboard/mouse handling
508 // ============================================================================
510 void wxVListBox::DoHandleItemClick(int item
, int flags
)
512 // has anything worth telling the client code about happened?
515 if ( HasMultipleSelection() )
517 // select the iteem clicked?
520 // NB: the keyboard interface we implement here corresponds to
521 // wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
523 if ( flags
& ItemClick_Shift
)
525 if ( m_current
!= wxNOT_FOUND
)
527 if ( m_anchor
== wxNOT_FOUND
)
528 m_anchor
= m_current
;
532 // only the range from the selection anchor to new m_current
537 if ( SelectRange(m_anchor
, item
) )
540 //else: treat it as ordinary click/keypress
542 else // Shift not pressed
546 if ( flags
& ItemClick_Ctrl
)
550 if ( !(flags
& ItemClick_Kbd
) )
554 // the status of the item has definitely changed
557 //else: Ctrl-arrow pressed, don't change selection
559 //else: behave as in single selection case
564 // make the clicked item the only selection
573 // in any case the item should become the current one
574 if ( DoSetCurrent(item
) )
576 if ( !HasMultipleSelection() )
578 // this has also changed the selection for single selection case
585 // notify the user about the selection change
588 //else: nothing changed at all
591 // ----------------------------------------------------------------------------
593 // ----------------------------------------------------------------------------
595 void wxVListBox::OnKeyDown(wxKeyEvent
& event
)
597 // flags for DoHandleItemClick()
598 int flags
= ItemClick_Kbd
;
601 switch ( event
.GetKeyCode() )
608 current
= GetRowCount() - 1;
612 if ( m_current
== (int)GetRowCount() - 1 )
615 current
= m_current
+ 1;
619 if ( m_current
== wxNOT_FOUND
)
620 current
= GetRowCount() - 1;
621 else if ( m_current
!= 0 )
622 current
= m_current
- 1;
623 else // m_current == 0
629 current
= GetVisibleBegin();
633 if ( m_current
== (int)GetVisibleBegin() )
638 current
= GetVisibleBegin();
642 // hack: pressing space should work like a mouse click rather than
643 // like a keyboard arrow press, so trick DoHandleItemClick() in
644 // thinking we were clicked
645 flags
&= ~ItemClick_Kbd
;
651 // Since we are using wxWANTS_CHARS we need to send navigation
652 // events for the tabs on MSW
653 HandleAsNavigationKey(event
);
654 // fall through to default
658 current
= 0; // just to silent the stupid compiler warnings
659 wxUnusedVar(current
);
663 if ( event
.ShiftDown() )
664 flags
|= ItemClick_Shift
;
665 if ( event
.ControlDown() )
666 flags
|= ItemClick_Ctrl
;
668 DoHandleItemClick(current
, flags
);
671 // ----------------------------------------------------------------------------
672 // wxVListBox mouse handling
673 // ----------------------------------------------------------------------------
675 void wxVListBox::OnLeftDown(wxMouseEvent
& event
)
679 int item
= VirtualHitTest(event
.GetPosition().y
);
681 if ( item
!= wxNOT_FOUND
)
684 if ( event
.ShiftDown() )
685 flags
|= ItemClick_Shift
;
687 // under Mac Apple-click is used in the same way as Ctrl-click
690 if ( event
.MetaDown() )
692 if ( event
.ControlDown() )
694 flags
|= ItemClick_Ctrl
;
696 DoHandleItemClick(item
, flags
);
700 void wxVListBox::OnLeftDClick(wxMouseEvent
& eventMouse
)
702 int item
= VirtualHitTest(eventMouse
.GetPosition().y
);
703 if ( item
!= wxNOT_FOUND
)
706 // if item double-clicked was not yet selected, then treat
707 // this event as a left-click instead
708 if ( item
== m_current
)
710 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, GetId());
711 event
.SetEventObject(this);
714 (void)GetEventHandler()->ProcessEvent(event
);
718 OnLeftDown(eventMouse
);
725 // ----------------------------------------------------------------------------
726 // use the same default attributes as wxListBox
727 // ----------------------------------------------------------------------------
731 wxVListBox::GetClassDefaultAttributes(wxWindowVariant variant
)
733 return wxListBox::GetClassDefaultAttributes(variant
);