]>
git.saurik.com Git - wxWidgets.git/blob - src/generic/vlbox.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/vlbox.cpp
3 // Purpose: implementation of wxVListBox
4 // Author: Vadim Zeitlin
7 // Copyright: (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
31 #include "wx/settings.h"
32 #include "wx/dcclient.h"
33 #include "wx/listbox.h"
36 #include "wx/dcbuffer.h"
37 #include "wx/selstore.h"
38 #include "wx/renderer.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
)
51 EVT_SET_FOCUS(wxVListBox::OnSetOrKillFocus
)
52 EVT_KILL_FOCUS(wxVListBox::OnSetOrKillFocus
)
54 EVT_SIZE(wxVListBox::OnSize
)
57 // ============================================================================
59 // ============================================================================
61 IMPLEMENT_ABSTRACT_CLASS(wxVListBox
, wxVScrolledWindow
)
62 const char wxVListBoxNameStr
[] = "wxVListBox";
64 // ----------------------------------------------------------------------------
65 // wxVListBox creation
66 // ----------------------------------------------------------------------------
68 void wxVListBox::Init()
71 m_anchor
= wxNOT_FOUND
;
75 bool wxVListBox::Create(wxWindow
*parent
,
83 if ( (style
& wxBORDER_MASK
) == wxDEFAULT
)
84 style
|= wxBORDER_THEME
;
87 style
|= wxWANTS_CHARS
| wxFULL_REPAINT_ON_RESIZE
;
88 if ( !wxVScrolledWindow::Create(parent
, id
, pos
, size
, style
, name
) )
91 if ( style
& wxLB_MULTIPLE
)
92 m_selStore
= new wxSelectionStore
;
94 // make sure the native widget has the right colour since we do
95 // transparent drawing by default
96 SetBackgroundColour(GetBackgroundColour());
98 // leave m_colBgSel in an invalid state: it means for OnDrawBackground()
99 // to use wxRendererNative instead of painting selection bg ourselves
100 m_colBgSel
= wxNullColour
;
102 // flicker-free drawing requires this
103 SetBackgroundStyle(wxBG_STYLE_CUSTOM
);
108 wxVListBox::~wxVListBox()
113 void wxVListBox::SetItemCount(size_t count
)
115 // don't leave the current index invalid
116 if ( m_current
!= wxNOT_FOUND
&& (size_t)m_current
>= count
)
117 m_current
= count
- 1; // also ok when count == 0 as wxNOT_FOUND == -1
121 // tell the selection store that our number of items has changed
122 m_selStore
->SetItemCount(count
);
128 // ----------------------------------------------------------------------------
129 // selection handling
130 // ----------------------------------------------------------------------------
132 bool wxVListBox::IsSelected(size_t line
) const
134 return m_selStore
? m_selStore
->IsSelected(line
) : (int)line
== m_current
;
137 bool wxVListBox::Select(size_t item
, bool select
)
139 wxCHECK_MSG( m_selStore
, false,
140 wxT("Select() may only be used with multiselection listbox") );
142 wxCHECK_MSG( item
< GetItemCount(), false,
143 wxT("Select(): invalid item index") );
145 bool changed
= m_selStore
->SelectItem(item
, select
);
148 // selection really changed
157 bool wxVListBox::SelectRange(size_t from
, size_t to
)
159 wxCHECK_MSG( m_selStore
, false,
160 wxT("SelectRange() may only be used with multiselection listbox") );
162 // make sure items are in correct order
170 wxCHECK_MSG( to
< GetItemCount(), false,
171 wxT("SelectRange(): invalid item index") );
174 if ( !m_selStore
->SelectRange(from
, to
, true, &changed
) )
176 // too many items have changed, we didn't record them in changed array
177 // so we have no choice but to refresh everything between from and to
178 RefreshRows(from
, to
);
180 else // we've got the indices of the changed items
182 const size_t count
= changed
.GetCount();
189 // refresh just the lines which have really changed
190 for ( size_t n
= 0; n
< count
; n
++ )
192 RefreshRow(changed
[n
]);
200 bool wxVListBox::DoSelectAll(bool select
)
202 wxCHECK_MSG( m_selStore
, false,
203 wxT("SelectAll may only be used with multiselection listbox") );
205 size_t count
= GetItemCount();
209 if ( !m_selStore
->SelectRange(0, count
- 1, select
) ||
222 bool wxVListBox::DoSetCurrent(int current
)
224 wxASSERT_MSG( current
== wxNOT_FOUND
||
225 (current
>= 0 && (size_t)current
< GetItemCount()),
226 wxT("wxVListBox::DoSetCurrent(): invalid item index") );
228 if ( current
== m_current
)
234 if ( m_current
!= wxNOT_FOUND
)
235 RefreshRow(m_current
);
239 if ( m_current
!= wxNOT_FOUND
)
241 // if the line is not visible at all, we scroll it into view but we
242 // don't need to refresh it -- it will be redrawn anyhow
243 if ( !IsVisible(m_current
) )
245 ScrollToRow(m_current
);
247 else // line is at least partly visible
249 // it is, indeed, only partly visible, so scroll it into view to
250 // make it entirely visible
251 // BUT scrolling down when m_current is first visible makes it
252 // completely hidden, so that is even worse
253 while ( (size_t)m_current
+ 1 == GetVisibleRowsEnd() &&
254 (size_t)m_current
!= GetVisibleRowsBegin() &&
255 ScrollToRow(GetVisibleBegin() + 1) ) ;
257 // but in any case refresh it as even if it was only partly visible
258 // before we need to redraw it entirely as its background changed
259 RefreshRow(m_current
);
266 void wxVListBox::InitEvent(wxCommandEvent
& event
, int n
)
268 event
.SetEventObject(this);
272 void wxVListBox::SendSelectedEvent()
274 wxASSERT_MSG( m_current
!= wxNOT_FOUND
,
275 wxT("SendSelectedEvent() shouldn't be called") );
277 wxCommandEvent
event(wxEVT_LISTBOX
, GetId());
278 InitEvent(event
, m_current
);
279 (void)GetEventHandler()->ProcessEvent(event
);
282 void wxVListBox::SetSelection(int selection
)
284 wxCHECK_RET( selection
== wxNOT_FOUND
||
285 (selection
>= 0 && (size_t)selection
< GetItemCount()),
286 wxT("wxVListBox::SetSelection(): invalid item index") );
288 if ( HasMultipleSelection() )
290 if (selection
!= wxNOT_FOUND
)
294 m_anchor
= selection
;
297 DoSetCurrent(selection
);
300 size_t wxVListBox::GetSelectedCount() const
302 return m_selStore
? m_selStore
->GetSelectedCount()
303 : m_current
== wxNOT_FOUND
? 0 : 1;
306 int wxVListBox::GetFirstSelected(unsigned long& cookie
) const
310 return GetNextSelected(cookie
);
313 int wxVListBox::GetNextSelected(unsigned long& cookie
) const
315 wxCHECK_MSG( m_selStore
, wxNOT_FOUND
,
316 wxT("GetFirst/NextSelected() may only be used with multiselection listboxes") );
318 while ( cookie
< GetItemCount() )
320 if ( IsSelected(cookie
++) )
327 void wxVListBox::RefreshSelected()
329 // only refresh those items which are currently visible and selected:
330 for ( size_t n
= GetVisibleBegin(), end
= GetVisibleEnd(); n
< end
; n
++ )
337 wxRect
wxVListBox::GetItemRect(size_t n
) const
341 // check that this item is visible
342 const size_t lineMax
= GetVisibleEnd();
345 size_t line
= GetVisibleBegin();
351 itemrect
.y
+= itemrect
.height
;
352 itemrect
.height
= OnGetRowHeight(line
);
357 itemrect
.width
= GetClientSize().x
;
362 // ----------------------------------------------------------------------------
363 // wxVListBox appearance parameters
364 // ----------------------------------------------------------------------------
366 void wxVListBox::SetMargins(const wxPoint
& pt
)
368 if ( pt
!= m_ptMargins
)
376 void wxVListBox::SetSelectionBackground(const wxColour
& col
)
381 // ----------------------------------------------------------------------------
382 // wxVListBox painting
383 // ----------------------------------------------------------------------------
385 wxCoord
wxVListBox::OnGetRowHeight(size_t line
) const
387 return OnMeasureItem(line
) + 2*m_ptMargins
.y
;
390 void wxVListBox::OnDrawSeparator(wxDC
& WXUNUSED(dc
),
391 wxRect
& WXUNUSED(rect
),
392 size_t WXUNUSED(n
)) const
397 wxVListBox::DoDrawSolidBackground(const wxColour
& col
,
405 // we need to render selected and current items differently
406 const bool isSelected
= IsSelected(n
),
407 isCurrent
= IsCurrent(n
);
408 if ( isSelected
|| isCurrent
)
412 dc
.SetBrush(wxBrush(col
, wxBRUSHSTYLE_SOLID
));
416 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
418 dc
.SetPen(*(isCurrent
? wxBLACK_PEN
: wxTRANSPARENT_PEN
));
419 dc
.DrawRectangle(rect
);
421 //else: do nothing for the normal items
426 void wxVListBox::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
428 // use wxRendererNative for more native look unless we use custom bg colour
429 if ( !DoDrawSolidBackground(m_colBgSel
, dc
, rect
, n
) )
433 flags
|= wxCONTROL_SELECTED
;
435 flags
|= wxCONTROL_CURRENT
;
436 if ( wxWindow::FindFocus() == const_cast<wxVListBox
*>(this) )
437 flags
|= wxCONTROL_FOCUSED
;
439 wxRendererNative::Get().DrawItemSelectionRect(
440 const_cast<wxVListBox
*>(this), dc
, rect
, flags
);
444 void wxVListBox::OnPaint(wxPaintEvent
& WXUNUSED(event
))
446 wxSize clientSize
= GetClientSize();
448 wxAutoBufferedPaintDC
dc(this);
450 // the update rectangle
451 wxRect rectUpdate
= GetUpdateClientRect();
453 // fill it with background colour
454 dc
.SetBackground(GetBackgroundColour());
457 // the bounding rectangle of the current line
459 rectRow
.width
= clientSize
.x
;
461 // iterate over all visible lines
462 const size_t lineMax
= GetVisibleEnd();
463 for ( size_t line
= GetVisibleBegin(); line
< lineMax
; line
++ )
465 const wxCoord hRow
= OnGetRowHeight(line
);
467 rectRow
.height
= hRow
;
469 // and draw the ones which intersect the update rect
470 if ( rectRow
.Intersects(rectUpdate
) )
472 // don't allow drawing outside of the lines rectangle
473 wxDCClipper
clip(dc
, rectRow
);
475 wxRect rect
= rectRow
;
476 OnDrawBackground(dc
, rect
, line
);
478 OnDrawSeparator(dc
, rect
, line
);
480 rect
.Deflate(m_ptMargins
.x
, m_ptMargins
.y
);
481 OnDrawItem(dc
, rect
, line
);
483 else // no intersection
485 if ( rectRow
.GetTop() > rectUpdate
.GetBottom() )
487 // we are already below the update rect, no need to continue
491 //else: the next line may intersect the update rect
498 void wxVListBox::OnSetOrKillFocus(wxFocusEvent
& WXUNUSED(event
))
500 // we need to repaint the selection when we get the focus since
501 // wxRendererNative in general draws the focused selection differently
502 // from the unfocused selection (see OnDrawItem):
506 void wxVListBox::OnSize(wxSizeEvent
& event
)
512 // ============================================================================
513 // wxVListBox keyboard/mouse handling
514 // ============================================================================
516 void wxVListBox::DoHandleItemClick(int item
, int flags
)
518 // has anything worth telling the client code about happened?
521 if ( HasMultipleSelection() )
523 // select the iteem clicked?
526 // NB: the keyboard interface we implement here corresponds to
527 // wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
529 if ( flags
& ItemClick_Shift
)
531 if ( m_current
!= wxNOT_FOUND
)
533 if ( m_anchor
== wxNOT_FOUND
)
534 m_anchor
= m_current
;
538 // only the range from the selection anchor to new m_current
543 if ( SelectRange(m_anchor
, item
) )
546 //else: treat it as ordinary click/keypress
548 else // Shift not pressed
552 if ( flags
& ItemClick_Ctrl
)
556 if ( !(flags
& ItemClick_Kbd
) )
560 // the status of the item has definitely changed
563 //else: Ctrl-arrow pressed, don't change selection
565 //else: behave as in single selection case
570 // make the clicked item the only selection
579 // in any case the item should become the current one
580 if ( DoSetCurrent(item
) )
582 if ( !HasMultipleSelection() )
584 // this has also changed the selection for single selection case
591 // notify the user about the selection change
594 //else: nothing changed at all
597 // ----------------------------------------------------------------------------
599 // ----------------------------------------------------------------------------
601 void wxVListBox::OnKeyDown(wxKeyEvent
& event
)
603 // flags for DoHandleItemClick()
604 int flags
= ItemClick_Kbd
;
607 switch ( event
.GetKeyCode() )
610 case WXK_NUMPAD_HOME
:
616 current
= GetRowCount() - 1;
620 case WXK_NUMPAD_DOWN
:
621 if ( m_current
== (int)GetRowCount() - 1 )
624 current
= m_current
+ 1;
629 if ( m_current
== wxNOT_FOUND
)
630 current
= GetRowCount() - 1;
631 else if ( m_current
!= 0 )
632 current
= m_current
- 1;
633 else // m_current == 0
638 case WXK_NUMPAD_PAGEDOWN
:
640 current
= GetVisibleBegin();
644 case WXK_NUMPAD_PAGEUP
:
645 if ( m_current
== (int)GetVisibleBegin() )
650 current
= GetVisibleBegin();
654 // hack: pressing space should work like a mouse click rather than
655 // like a keyboard arrow press, so trick DoHandleItemClick() in
656 // thinking we were clicked
657 flags
&= ~ItemClick_Kbd
;
663 // Since we are using wxWANTS_CHARS we need to send navigation
664 // events for the tabs on MSW
665 HandleAsNavigationKey(event
);
666 // fall through to default
670 current
= 0; // just to silent the stupid compiler warnings
671 wxUnusedVar(current
);
675 if ( event
.ShiftDown() )
676 flags
|= ItemClick_Shift
;
677 if ( event
.ControlDown() )
678 flags
|= ItemClick_Ctrl
;
680 DoHandleItemClick(current
, flags
);
683 // ----------------------------------------------------------------------------
684 // wxVListBox mouse handling
685 // ----------------------------------------------------------------------------
687 void wxVListBox::OnLeftDown(wxMouseEvent
& event
)
691 int item
= VirtualHitTest(event
.GetPosition().y
);
693 if ( item
!= wxNOT_FOUND
)
696 if ( event
.ShiftDown() )
697 flags
|= ItemClick_Shift
;
699 // under Mac Apple-click is used in the same way as Ctrl-click
702 if ( event
.MetaDown() )
704 if ( event
.ControlDown() )
706 flags
|= ItemClick_Ctrl
;
708 DoHandleItemClick(item
, flags
);
712 void wxVListBox::OnLeftDClick(wxMouseEvent
& eventMouse
)
714 int item
= VirtualHitTest(eventMouse
.GetPosition().y
);
715 if ( item
!= wxNOT_FOUND
)
718 // if item double-clicked was not yet selected, then treat
719 // this event as a left-click instead
720 if ( item
== m_current
)
722 wxCommandEvent
event(wxEVT_LISTBOX_DCLICK
, GetId());
723 InitEvent(event
, item
);
724 (void)GetEventHandler()->ProcessEvent(event
);
728 OnLeftDown(eventMouse
);
735 // ----------------------------------------------------------------------------
736 // use the same default attributes as wxListBox
737 // ----------------------------------------------------------------------------
741 wxVListBox::GetClassDefaultAttributes(wxWindowVariant variant
)
743 return wxListBox::GetClassDefaultAttributes(variant
);