1
///////////////////////////////////////////////////////////////////////////////
2
// Name: generic/vlbox.cpp
3
// Purpose: implementation of wxVListBox
4
// Author: Vadim Zeitlin
7
// RCS-ID: $Id: vlbox.cpp,v 1.22.2.2 2006/04/14 15:24:13 VZ Exp $
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"
30
#include "wx/settings.h"
31
#include "wx/dcclient.h"
35
#include "wx/dcbuffer.h"
36
#include "wx/selstore.h"
37
#include "wx/bitmap.h"
39
// ----------------------------------------------------------------------------
41
// ----------------------------------------------------------------------------
43
BEGIN_EVENT_TABLE(wxVListBox, wxVScrolledWindow)
44
EVT_PAINT(wxVListBox::OnPaint)
46
EVT_KEY_DOWN(wxVListBox::OnKeyDown)
47
EVT_LEFT_DOWN(wxVListBox::OnLeftDown)
48
EVT_LEFT_DCLICK(wxVListBox::OnLeftDClick)
51
// ============================================================================
53
// ============================================================================
55
IMPLEMENT_ABSTRACT_CLASS(wxVListBox, wxVScrolledWindow)
57
// ----------------------------------------------------------------------------
58
// wxVListBox creation
59
// ----------------------------------------------------------------------------
61
// due to ABI compatibility reasons, we need to declare double-buffer
63
static wxBitmap* gs_doubleBuffer = NULL;
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());
90
m_colBgSel = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
92
// flicker-free drawing requires this
93
SetBackgroundStyle(wxBG_STYLE_CUSTOM);
98
wxVListBox::~wxVListBox()
102
delete gs_doubleBuffer;
103
gs_doubleBuffer = NULL;
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
RefreshLines(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
RefreshLine(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
RefreshLine(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
ScrollToLine(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 == GetLastVisibleLine() &&
241
ScrollToLine(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
RefreshLine(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() )
273
m_anchor = selection;
276
DoSetCurrent(selection);
279
size_t wxVListBox::GetSelectedCount() const
281
return m_selStore ? m_selStore->GetSelectedCount()
282
: m_current == wxNOT_FOUND ? 0 : 1;
285
int wxVListBox::GetFirstSelected(unsigned long& cookie) const
289
return GetNextSelected(cookie);
292
int wxVListBox::GetNextSelected(unsigned long& cookie) const
294
wxCHECK_MSG( m_selStore, wxNOT_FOUND,
295
_T("GetFirst/NextSelected() may only be used with multiselection listboxes") );
297
while ( cookie < GetItemCount() )
299
if ( IsSelected(cookie++) )
306
// ----------------------------------------------------------------------------
307
// wxVListBox appearance parameters
308
// ----------------------------------------------------------------------------
310
void wxVListBox::SetMargins(const wxPoint& pt)
312
if ( pt != m_ptMargins )
320
void wxVListBox::SetSelectionBackground(const wxColour& col)
325
// ----------------------------------------------------------------------------
326
// wxVListBox painting
327
// ----------------------------------------------------------------------------
329
wxCoord wxVListBox::OnGetLineHeight(size_t line) const
331
return OnMeasureItem(line) + 2*m_ptMargins.y;
334
void wxVListBox::OnDrawSeparator(wxDC& WXUNUSED(dc),
335
wxRect& WXUNUSED(rect),
336
size_t WXUNUSED(n)) const
340
void wxVListBox::OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const
342
// we need to render selected and current items differently
343
const bool isSelected = IsSelected(n),
344
isCurrent = IsCurrent(n);
345
if ( isSelected || isCurrent )
349
dc.SetBrush(wxBrush(m_colBgSel, wxSOLID));
353
dc.SetBrush(*wxTRANSPARENT_BRUSH);
356
dc.SetPen(*(isCurrent ? wxBLACK_PEN : wxTRANSPARENT_PEN));
358
dc.DrawRectangle(rect);
360
//else: do nothing for the normal items
363
void wxVListBox::OnPaint(wxPaintEvent& WXUNUSED(event))
365
// If size is larger, recalculate double buffer bitmap
366
wxSize clientSize = GetClientSize();
368
if ( !gs_doubleBuffer ||
369
clientSize.x > gs_doubleBuffer->GetWidth() ||
370
clientSize.y > gs_doubleBuffer->GetHeight() )
372
delete gs_doubleBuffer;
373
gs_doubleBuffer = new wxBitmap(clientSize.x+25,clientSize.y+25);
376
wxBufferedPaintDC dc(this,*gs_doubleBuffer);
378
// the update rectangle
379
wxRect rectUpdate = GetUpdateClientRect();
381
// fill it with background colour
382
dc.SetBackground(GetBackgroundColour());
385
// the bounding rectangle of the current line
387
rectLine.width = clientSize.x;
389
// iterate over all visible lines
390
const size_t lineMax = GetLastVisibleLine();
391
for ( size_t line = GetFirstVisibleLine(); line <= lineMax; line++ )
393
const wxCoord hLine = OnGetLineHeight(line);
395
rectLine.height = hLine;
397
// and draw the ones which intersect the update rect
398
if ( rectLine.Intersects(rectUpdate) )
400
// don't allow drawing outside of the lines rectangle
401
wxDCClipper clip(dc, rectLine);
403
wxRect rect = rectLine;
404
OnDrawBackground(dc, rect, line);
406
OnDrawSeparator(dc, rect, line);
408
rect.Deflate(m_ptMargins.x, m_ptMargins.y);
409
OnDrawItem(dc, rect, line);
411
else // no intersection
413
if ( rectLine.GetTop() > rectUpdate.GetBottom() )
415
// we are already below the update rect, no need to continue
419
//else: the next line may intersect the update rect
426
// ============================================================================
427
// wxVListBox keyboard/mouse handling
428
// ============================================================================
430
void wxVListBox::DoHandleItemClick(int item, int flags)
432
// has anything worth telling the client code about happened?
435
if ( HasMultipleSelection() )
437
// select the iteem clicked?
440
// NB: the keyboard interface we implement here corresponds to
441
// wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
443
if ( flags & ItemClick_Shift )
445
if ( m_current != wxNOT_FOUND )
447
if ( m_anchor == wxNOT_FOUND )
448
m_anchor = m_current;
452
// only the range from the selection anchor to new m_current
457
if ( SelectRange(m_anchor, item) )
460
//else: treat it as ordinary click/keypress
462
else // Shift not pressed
466
if ( flags & ItemClick_Ctrl )
470
if ( !(flags & ItemClick_Kbd) )
474
// the status of the item has definitely changed
477
//else: Ctrl-arrow pressed, don't change selection
479
//else: behave as in single selection case
484
// make the clicked item the only selection
493
// in any case the item should become the current one
494
if ( DoSetCurrent(item) )
496
if ( !HasMultipleSelection() )
498
// this has also changed the selection for single selection case
505
// notify the user about the selection change
508
//else: nothing changed at all
511
// ----------------------------------------------------------------------------
513
// ----------------------------------------------------------------------------
515
void wxVListBox::OnKeyDown(wxKeyEvent& event)
517
// flags for DoHandleItemClick()
518
int flags = ItemClick_Kbd;
521
switch ( event.GetKeyCode() )
528
current = GetLineCount() - 1;
532
if ( m_current == (int)GetLineCount() - 1 )
535
current = m_current + 1;
539
if ( m_current == wxNOT_FOUND )
540
current = GetLineCount() - 1;
541
else if ( m_current != 0 )
542
current = m_current - 1;
543
else // m_current == 0
550
current = GetFirstVisibleLine();
555
if ( m_current == (int)GetFirstVisibleLine() )
560
current = GetFirstVisibleLine();
564
// hack: pressing space should work like a mouse click rather than
565
// like a keyboard arrow press, so trick DoHandleItemClick() in
566
// thinking we were clicked
567
flags &= ~ItemClick_Kbd;
573
// Since we are using wxWANTS_CHARS we need to send navigation
574
// events for the tabs on MSW
576
wxNavigationKeyEvent ne;
577
ne.SetDirection(!event.ShiftDown());
578
ne.SetCurrentFocus(this);
579
ne.SetEventObject(this);
580
GetParent()->GetEventHandler()->ProcessEvent(ne);
582
// fall through to default
586
current = 0; // just to silent the stupid compiler warnings
587
wxUnusedVar(current);
591
if ( event.ShiftDown() )
592
flags |= ItemClick_Shift;
593
if ( event.ControlDown() )
594
flags |= ItemClick_Ctrl;
596
DoHandleItemClick(current, flags);
599
// ----------------------------------------------------------------------------
600
// wxVListBox mouse handling
601
// ----------------------------------------------------------------------------
603
void wxVListBox::OnLeftDown(wxMouseEvent& event)
607
int item = HitTest(event.GetPosition());
609
if ( item != wxNOT_FOUND )
612
if ( event.ShiftDown() )
613
flags |= ItemClick_Shift;
615
// under Mac Apple-click is used in the same way as Ctrl-click
618
if ( event.MetaDown() )
620
if ( event.ControlDown() )
622
flags |= ItemClick_Ctrl;
624
DoHandleItemClick(item, flags);
628
void wxVListBox::OnLeftDClick(wxMouseEvent& eventMouse)
630
int item = HitTest(eventMouse.GetPosition());
631
if ( item != wxNOT_FOUND )
634
// if item double-clicked was not yet selected, then treat
635
// this event as a left-click instead
636
if ( item == m_current )
638
wxCommandEvent event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, GetId());
639
event.SetEventObject(this);
642
(void)GetEventHandler()->ProcessEvent(event);
646
OnLeftDown(eventMouse);
653
// ----------------------------------------------------------------------------
654
// use the same default attributes as wxListBox
655
// ----------------------------------------------------------------------------
657
#include "wx/listbox.h"
661
wxVListBox::GetClassDefaultAttributes(wxWindowVariant variant)
663
return wxListBox::GetClassDefaultAttributes(variant);