Files
wxWidgets/src/univ/listbox.cpp
2006-10-17 18:21:05 +00:00

1572 lines
42 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/univ/listbox.cpp
// Purpose: wxListBox implementation
// Author: Vadim Zeitlin
// Modified by:
// Created: 30.08.00
// RCS-ID: $Id$
// Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_LISTBOX
#ifndef WX_PRECOMP
#include "wx/log.h"
#include "wx/dcclient.h"
#include "wx/listbox.h"
#include "wx/validate.h"
#endif
#include "wx/univ/renderer.h"
#include "wx/univ/inphand.h"
#include "wx/univ/theme.h"
// ----------------------------------------------------------------------------
// wxStdListboxInputHandler: handles mouse and kbd in a single or multi
// selection listbox
// ----------------------------------------------------------------------------
class WXDLLEXPORT wxStdListboxInputHandler : public wxStdInputHandler
{
public:
// if pressing the mouse button in a multiselection listbox should toggle
// the item under mouse immediately, then specify true as the second
// parameter (this is the standard behaviour, under GTK the item is toggled
// only when the mouse is released in the multi selection listbox)
wxStdListboxInputHandler(wxInputHandler *inphand,
bool toggleOnPressAlways = true);
// base class methods
virtual bool HandleKey(wxInputConsumer *consumer,
const wxKeyEvent& event,
bool pressed);
virtual bool HandleMouse(wxInputConsumer *consumer,
const wxMouseEvent& event);
virtual bool HandleMouseMove(wxInputConsumer *consumer,
const wxMouseEvent& event);
protected:
// return the item under mouse, 0 if the mouse is above the listbox or
// GetCount() if it is below it
int HitTest(const wxListBox *listbox, const wxMouseEvent& event);
// parts of HitTest(): first finds the pseudo (because not in range) index
// of the item and the second one adjusts it if necessary - that is if the
// third one returns false
int HitTestUnsafe(const wxListBox *listbox, const wxMouseEvent& event);
int FixItemIndex(const wxListBox *listbox, int item);
bool IsValidIndex(const wxListBox *listbox, int item);
// init m_btnCapture and m_actionMouse
wxControlAction SetupCapture(wxListBox *lbox,
const wxMouseEvent& event,
int item);
wxRenderer *m_renderer;
// the button which initiated the mouse capture (currently 0 or 1)
int m_btnCapture;
// the action to perform when the mouse moves while we capture it
wxControlAction m_actionMouse;
// the ctor parameter toggleOnPressAlways (see comments near it)
bool m_toggleOnPressAlways;
// do we track the mouse outside the window when it is captured?
bool m_trackMouseOutside;
};
// ============================================================================
// implementation of wxListBox
// ============================================================================
IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControl)
BEGIN_EVENT_TABLE(wxListBox, wxListBoxBase)
EVT_SIZE(wxListBox::OnSize)
END_EVENT_TABLE()
// ----------------------------------------------------------------------------
// construction
// ----------------------------------------------------------------------------
void wxListBox::Init()
{
// will be calculated later when needed
m_lineHeight = 0;
m_itemsPerPage = 0;
m_maxWidth = 0;
m_scrollRangeY = 0;
m_maxWidthItem = -1;
m_strings = NULL;
// no items hence no current item
m_current = -1;
m_selAnchor = -1;
m_currentChanged = false;
// no need to update anything initially
m_updateCount = 0;
// no scrollbars to show nor update
m_updateScrollbarX =
m_showScrollbarX =
m_updateScrollbarY =
m_showScrollbarY = false;
}
wxListBox::wxListBox(wxWindow *parent,
wxWindowID id,
const wxPoint &pos,
const wxSize &size,
const wxArrayString& choices,
long style,
const wxValidator& validator,
const wxString &name)
:wxScrollHelper(this)
{
Init();
Create(parent, id, pos, size, choices, style, validator, name);
}
bool wxListBox::Create(wxWindow *parent,
wxWindowID id,
const wxPoint &pos,
const wxSize &size,
const wxArrayString& choices,
long style,
const wxValidator& validator,
const wxString &name)
{
wxCArrayString chs(choices);
return Create(parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
style, validator, name);
}
bool wxListBox::Create(wxWindow *parent,
wxWindowID id,
const wxPoint &pos,
const wxSize &size,
int n,
const wxString choices[],
long style,
const wxValidator& validator,
const wxString &name)
{
// for compatibility accept both the new and old styles - they mean the
// same thing for us
if ( style & wxLB_ALWAYS_SB )
style |= wxALWAYS_SHOW_SB;
// if we don't have neither multiple nor extended flag, we must have the
// single selection listbox
if ( !(style & (wxLB_MULTIPLE | wxLB_EXTENDED)) )
style |= wxLB_SINGLE;
#if wxUSE_TWO_WINDOWS
style |= wxVSCROLL|wxHSCROLL;
if ((style & wxBORDER_MASK) == 0)
style |= wxBORDER_SUNKEN;
#endif
if ( !wxControl::Create(parent, id, pos, size, style,
validator, name) )
return false;
m_strings = new wxArrayString;
Set(n, choices);
SetBestSize(size);
CreateInputHandler(wxINP_HANDLER_LISTBOX);
return true;
}
wxListBox::~wxListBox()
{
// call this just to free the client data -- and avoid leaking memory
DoClear();
delete m_strings;
m_strings = NULL;
}
// ----------------------------------------------------------------------------
// adding/inserting strings
// ----------------------------------------------------------------------------
int wxCMPFUNC_CONV wxListBoxSortNoCase(wxString* s1, wxString* s2)
{
return s1->CmpNoCase(*s2);
}
int wxListBox::DoAppendOnly(const wxString& item)
{
unsigned int index;
if ( IsSorted() )
{
m_strings->Add(item);
m_strings->Sort(wxListBoxSortNoCase);
index = m_strings->Index(item);
}
else
{
index = m_strings->GetCount();
m_strings->Add(item);
}
return index;
}
int wxListBox::DoAppend(const wxString& item)
{
size_t index = DoAppendOnly( item );
m_itemsClientData.Insert(NULL, index);
m_updateScrollbarY = true;
if ( HasHorzScrollbar() )
{
// has the max width increased?
wxCoord width;
GetTextExtent(item, &width, NULL);
if ( width > m_maxWidth )
{
m_maxWidth = width;
m_maxWidthItem = index;
m_updateScrollbarX = true;
}
}
RefreshFromItemToEnd(index);
return index;
}
void wxListBox::DoInsertItems(const wxArrayString& items, unsigned int pos)
{
// the position of the item being added to a sorted listbox can't be
// specified
wxCHECK_RET( !IsSorted(), _T("can't insert items into sorted listbox") );
unsigned int count = items.GetCount();
for ( unsigned int n = 0; n < count; n++ )
{
m_strings->Insert(items[n], pos + n);
m_itemsClientData.Insert(NULL, pos + n);
}
// the number of items has changed so we might have to show the scrollbar
m_updateScrollbarY = true;
// the max width also might have changed - just recalculate it instead of
// keeping track of it here, this is probably more efficient for a typical
// use pattern
RefreshHorzScrollbar();
// note that we have to refresh all the items after the ones we inserted,
// not just these items
RefreshFromItemToEnd(pos);
}
void wxListBox::DoSetItems(const wxArrayString& items, void **clientData)
{
DoClear();
unsigned int count = items.GetCount();
if ( !count )
return;
m_strings->Alloc(count);
m_itemsClientData.Alloc(count);
for ( unsigned int n = 0; n < count; n++ )
{
unsigned int index = DoAppendOnly(items[n]);
m_itemsClientData.Insert(clientData ? clientData[n] : NULL, index);
}
m_updateScrollbarY = true;
RefreshAll();
}
void wxListBox::SetString(unsigned int n, const wxString& s)
{
wxCHECK_RET( !IsSorted(), _T("can't set string in sorted listbox") );
(*m_strings)[n] = s;
if ( HasHorzScrollbar() )
{
// we need to update m_maxWidth as changing the string may cause the
// horz scrollbar [dis]appear
wxCoord width;
GetTextExtent(s, &width, NULL);
// it might have increased if the new string is long
if ( width > m_maxWidth )
{
m_maxWidth = width;
m_maxWidthItem = n;
m_updateScrollbarX = true;
}
// or also decreased if the old string was the longest one
else if ( n == (unsigned int)m_maxWidthItem )
{
RefreshHorzScrollbar();
}
}
RefreshItem(n);
}
// ----------------------------------------------------------------------------
// removing strings
// ----------------------------------------------------------------------------
void wxListBox::DoClear()
{
m_strings->Clear();
if ( HasClientObjectData() )
{
unsigned int count = m_itemsClientData.GetCount();
for ( unsigned int n = 0; n < count; n++ )
{
delete (wxClientData *) m_itemsClientData[n];
}
}
m_itemsClientData.Clear();
m_selections.Clear();
m_current = -1;
}
void wxListBox::Clear()
{
DoClear();
m_updateScrollbarY = true;
RefreshHorzScrollbar();
RefreshAll();
}
void wxListBox::Delete(unsigned int n)
{
wxCHECK_RET( IsValid(n),
_T("invalid index in wxListBox::Delete") );
// do it before removing the index as otherwise the last item will not be
// refreshed (as GetCount() will be decremented)
RefreshFromItemToEnd(n);
m_strings->RemoveAt(n);
if ( HasClientObjectData() )
{
delete (wxClientData *)m_itemsClientData[n];
}
m_itemsClientData.RemoveAt(n);
// when the item disappears we must not keep using its index
if ( (int)n == m_current )
{
m_current = -1;
}
else if ( (int)n < m_current )
{
m_current--;
}
//else: current item may stay
// update the selections array: the indices of all seletected items after
// the one being deleted must change and the item itselfm ust be removed
int index = wxNOT_FOUND;
unsigned int count = m_selections.GetCount();
for ( unsigned int item = 0; item < count; item++ )
{
if ( m_selections[item] == (int)n )
{
// remember to delete it later
index = item;
}
else if ( m_selections[item] > (int)n )
{
// to account for the index shift
m_selections[item]--;
}
//else: nothing changed for this one
}
if ( index != wxNOT_FOUND )
{
m_selections.RemoveAt(index);
}
// the number of items has changed, hence the scrollbar may disappear
m_updateScrollbarY = true;
// finally, if the longest item was deleted the scrollbar may disappear
if ( (int)n == m_maxWidthItem )
{
RefreshHorzScrollbar();
}
}
// ----------------------------------------------------------------------------
// client data handling
// ----------------------------------------------------------------------------
void wxListBox::DoSetItemClientData(unsigned int n, void* clientData)
{
m_itemsClientData[n] = clientData;
}
void *wxListBox::DoGetItemClientData(unsigned int n) const
{
return m_itemsClientData[n];
}
void wxListBox::DoSetItemClientObject(unsigned int n, wxClientData* clientData)
{
m_itemsClientData[n] = clientData;
}
wxClientData* wxListBox::DoGetItemClientObject(unsigned int n) const
{
return (wxClientData *)m_itemsClientData[n];
}
// ----------------------------------------------------------------------------
// selection
// ----------------------------------------------------------------------------
void wxListBox::DoSetSelection(int n, bool select)
{
if ( select )
{
if ( m_selections.Index(n) == wxNOT_FOUND )
{
if ( !HasMultipleSelection() )
{
// selecting an item in a single selection listbox deselects
// all the others
DeselectAll();
return;
}
m_selections.Add(n);
RefreshItem(n);
}
//else: already selected
}
else // unselect
{
int index = m_selections.Index(n);
if ( index != wxNOT_FOUND )
{
m_selections.RemoveAt(index);
RefreshItem(n);
}
//else: not selected
}
// sanity check: a single selection listbox can't have more than one item
// selected
wxASSERT_MSG( HasMultipleSelection() || (m_selections.GetCount() < 2),
_T("multiple selected items in single selection lbox?") );
if ( select )
{
// the newly selected item becomes the current one
SetCurrentItem(n);
}
}
int wxListBox::GetSelection() const
{
wxCHECK_MSG( !HasMultipleSelection(), wxNOT_FOUND,
_T("use wxListBox::GetSelections for ths listbox") );
return m_selections.IsEmpty() ? wxNOT_FOUND : m_selections[0];
}
int wxCMPFUNC_CONV wxCompareInts(int *n, int *m)
{
return *n - *m;
}
int wxListBox::GetSelections(wxArrayInt& selections) const
{
// always return sorted array to the user
selections = m_selections;
unsigned int count = m_selections.GetCount();
// don't call sort on an empty array
if ( count )
{
selections.Sort(wxCompareInts);
}
return count;
}
// ----------------------------------------------------------------------------
// refresh logic: we use delayed refreshing which allows to avoid multiple
// refreshes (and hence flicker) in case when several listbox items are
// added/deleted/changed subsequently
// ----------------------------------------------------------------------------
void wxListBox::RefreshFromItemToEnd(int from)
{
RefreshItems(from, GetCount() - from);
}
void wxListBox::RefreshItems(int from, int count)
{
switch ( m_updateCount )
{
case 0:
m_updateFrom = from;
m_updateCount = count;
break;
case -1:
// we refresh everything anyhow
break;
default:
// add these items to the others which we have to refresh
if ( m_updateFrom < from )
{
count += from - m_updateFrom;
if ( m_updateCount < count )
m_updateCount = count;
}
else // m_updateFrom >= from
{
int updateLast = wxMax(m_updateFrom + m_updateCount,
from + count);
m_updateFrom = from;
m_updateCount = updateLast - m_updateFrom;
}
}
}
void wxListBox::RefreshItem(int n)
{
switch ( m_updateCount )
{
case 0:
// refresh this item only
m_updateFrom = n;
m_updateCount = 1;
break;
case -1:
// we refresh everything anyhow
break;
default:
// add this item to the others which we have to refresh
if ( m_updateFrom < n )
{
if ( m_updateCount < n - m_updateFrom + 1 )
m_updateCount = n - m_updateFrom + 1;
}
else // n <= m_updateFrom
{
m_updateCount += m_updateFrom - n;
m_updateFrom = n;
}
}
}
void wxListBox::RefreshAll()
{
m_updateCount = -1;
}
void wxListBox::RefreshHorzScrollbar()
{
m_maxWidth = 0; // recalculate it
m_updateScrollbarX = true;
}
void wxListBox::UpdateScrollbars()
{
wxSize size = GetClientSize();
// is our height enough to show all items?
unsigned int nLines = GetCount();
wxCoord lineHeight = GetLineHeight();
bool showScrollbarY = (int)nLines*lineHeight > size.y;
// check the width too if required
wxCoord charWidth, maxWidth;
bool showScrollbarX;
if ( HasHorzScrollbar() )
{
charWidth = GetCharWidth();
maxWidth = GetMaxWidth();
showScrollbarX = maxWidth > size.x;
}
else // never show it
{
charWidth = maxWidth = 0;
showScrollbarX = false;
}
// what should be the scrollbar range now?
int scrollRangeX = showScrollbarX
? (maxWidth + charWidth - 1) / charWidth + 2 // FIXME
: 0;
int scrollRangeY = showScrollbarY
? nLines +
(size.y % lineHeight + lineHeight - 1) / lineHeight
: 0;
// reset scrollbars if something changed: either the visibility status
// or the range of a scrollbar which is shown
if ( (showScrollbarY != m_showScrollbarY) ||
(showScrollbarX != m_showScrollbarX) ||
(showScrollbarY && (scrollRangeY != m_scrollRangeY)) ||
(showScrollbarX && (scrollRangeX != m_scrollRangeX)) )
{
int x, y;
GetViewStart(&x, &y);
SetScrollbars(charWidth, lineHeight,
scrollRangeX, scrollRangeY,
x, y);
m_showScrollbarX = showScrollbarX;
m_showScrollbarY = showScrollbarY;
m_scrollRangeX = scrollRangeX;
m_scrollRangeY = scrollRangeY;
}
}
void wxListBox::UpdateItems()
{
// only refresh the items which must be refreshed
if ( m_updateCount == -1 )
{
// refresh all
wxLogTrace(_T("listbox"), _T("Refreshing all"));
Refresh();
}
else
{
wxSize size = GetClientSize();
wxRect rect;
rect.width = size.x;
rect.height = size.y;
rect.y += m_updateFrom*GetLineHeight();
rect.height = m_updateCount*GetLineHeight();
// we don't need to calculate x position as we always refresh the
// entire line(s)
CalcScrolledPosition(0, rect.y, NULL, &rect.y);
wxLogTrace(_T("listbox"), _T("Refreshing items %d..%d (%d-%d)"),
m_updateFrom, m_updateFrom + m_updateCount - 1,
rect.GetTop(), rect.GetBottom());
Refresh(true, &rect);
}
}
void wxListBox::OnInternalIdle()
{
if ( m_updateScrollbarY || m_updateScrollbarX )
{
UpdateScrollbars();
m_updateScrollbarX =
m_updateScrollbarY = false;
}
if ( m_currentChanged )
{
DoEnsureVisible(m_current);
m_currentChanged = false;
}
if ( m_updateCount )
{
UpdateItems();
m_updateCount = 0;
}
wxListBoxBase::OnInternalIdle();
}
// ----------------------------------------------------------------------------
// drawing
// ----------------------------------------------------------------------------
wxBorder wxListBox::GetDefaultBorder() const
{
return wxBORDER_SUNKEN;
}
void wxListBox::DoDraw(wxControlRenderer *renderer)
{
// adjust the DC to account for scrolling
wxDC& dc = renderer->GetDC();
PrepareDC(dc);
dc.SetFont(GetFont());
// get the update rect
wxRect rectUpdate = GetUpdateClientRect();
int yTop, yBottom;
CalcUnscrolledPosition(0, rectUpdate.GetTop(), NULL, &yTop);
CalcUnscrolledPosition(0, rectUpdate.GetBottom(), NULL, &yBottom);
// get the items which must be redrawn
wxCoord lineHeight = GetLineHeight();
unsigned int itemFirst = yTop / lineHeight,
itemLast = (yBottom + lineHeight - 1) / lineHeight,
itemMax = m_strings->GetCount();
if ( itemFirst >= itemMax )
return;
if ( itemLast > itemMax )
itemLast = itemMax;
// do draw them
wxLogTrace(_T("listbox"), _T("Repainting items %d..%d"),
itemFirst, itemLast);
DoDrawRange(renderer, itemFirst, itemLast);
}
void wxListBox::DoDrawRange(wxControlRenderer *renderer,
int itemFirst, int itemLast)
{
renderer->DrawItems(this, itemFirst, itemLast);
}
// ----------------------------------------------------------------------------
// size calculations
// ----------------------------------------------------------------------------
bool wxListBox::SetFont(const wxFont& font)
{
if ( !wxControl::SetFont(font) )
return false;
CalcItemsPerPage();
RefreshAll();
return true;
}
void wxListBox::CalcItemsPerPage()
{
m_lineHeight = GetRenderer()->GetListboxItemHeight(GetCharHeight());
m_itemsPerPage = GetClientSize().y / m_lineHeight;
}
int wxListBox::GetItemsPerPage() const
{
if ( !m_itemsPerPage )
{
wxConstCast(this, wxListBox)->CalcItemsPerPage();
}
return m_itemsPerPage;
}
wxCoord wxListBox::GetLineHeight() const
{
if ( !m_lineHeight )
{
wxConstCast(this, wxListBox)->CalcItemsPerPage();
}
return m_lineHeight;
}
wxCoord wxListBox::GetMaxWidth() const
{
if ( m_maxWidth == 0 )
{
wxListBox *self = wxConstCast(this, wxListBox);
wxCoord width;
unsigned int count = m_strings->GetCount();
for ( unsigned int n = 0; n < count; n++ )
{
GetTextExtent(this->GetString(n), &width, NULL);
if ( width > m_maxWidth )
{
self->m_maxWidth = width;
self->m_maxWidthItem = n;
}
}
}
return m_maxWidth;
}
void wxListBox::OnSize(wxSizeEvent& event)
{
// recalculate the number of items per page
CalcItemsPerPage();
// the scrollbars might [dis]appear
m_updateScrollbarX =
m_updateScrollbarY = true;
event.Skip();
}
void wxListBox::DoSetFirstItem(int n)
{
SetCurrentItem(n);
}
void wxListBox::DoSetSize(int x, int y,
int width, int height,
int sizeFlags)
{
if ( GetWindowStyle() & wxLB_INT_HEIGHT )
{
// we must round up the height to an entire number of rows
// the client area must contain an int number of rows, so take borders
// into account
wxRect rectBorders = GetRenderer()->GetBorderDimensions(GetBorder());
wxCoord hBorders = rectBorders.y + rectBorders.height;
wxCoord hLine = GetLineHeight();
height = ((height - hBorders + hLine - 1) / hLine)*hLine + hBorders;
}
wxListBoxBase::DoSetSize(x, y, width, height, sizeFlags);
}
wxSize wxListBox::DoGetBestClientSize() const
{
wxCoord width = 0,
height = 0;
unsigned int count = m_strings->GetCount();
for ( unsigned int n = 0; n < count; n++ )
{
wxCoord w,h;
GetTextExtent(this->GetString(n), &w, &h);
if ( w > width )
width = w;
if ( h > height )
height = h;
}
// if the listbox is empty, still give it some non zero (even if
// arbitrary) size - otherwise, leave small margin around the strings
if ( !width )
width = 100;
else
width += 3*GetCharWidth();
if ( !height )
height = GetCharHeight();
// we need the height of the entire listbox, not just of one line
height *= wxMax(count, 7);
return wxSize(width, height);
}
// ----------------------------------------------------------------------------
// listbox actions
// ----------------------------------------------------------------------------
bool wxListBox::SendEvent(wxEventType type, int item)
{
wxCommandEvent event(type, m_windowId);
event.SetEventObject(this);
// use the current item by default
if ( item == -1 )
{
item = m_current;
}
// client data and string parameters only make sense if we have an item
if ( item != -1 )
{
if ( HasClientObjectData() )
event.SetClientObject(GetClientObject(item));
else if ( HasClientUntypedData() )
event.SetClientData(GetClientData(item));
event.SetString(GetString(item));
}
event.SetInt(item);
return GetEventHandler()->ProcessEvent(event);
}
void wxListBox::SetCurrentItem(int n)
{
if ( n != m_current )
{
if ( m_current != -1 )
RefreshItem(m_current);
m_current = n;
if ( m_current != -1 )
{
m_currentChanged = true;
RefreshItem(m_current);
}
}
//else: nothing to do
}
bool wxListBox::FindItem(const wxString& prefix, bool strictlyAfter)
{
unsigned int count = GetCount();
if ( count==0 )
{
// empty listbox, we can't find anything in it
return false;
}
// start either from the current item or from the next one if strictlyAfter
// is true
int first;
if ( strictlyAfter )
{
// the following line will set first correctly to 0 if there is no
// selection (m_current == -1)
first = m_current == (int)(count - 1) ? 0 : m_current + 1;
}
else // start with the current
{
first = m_current == -1 ? 0 : m_current;
}
int last = first == 0 ? count - 1 : first - 1;
// if this is not true we'd never exit from the loop below!
wxASSERT_MSG( first < (int)count && last < (int)count, _T("logic error") );
// precompute it outside the loop
size_t len = prefix.length();
// loop over all items in the listbox
for ( int item = first; item != (int)last; item < (int)(count - 1) ? item++ : item = 0 )
{
if ( wxStrnicmp(this->GetString(item).c_str(), prefix, len) == 0 )
{
SetCurrentItem(item);
if ( !(GetWindowStyle() & wxLB_MULTIPLE) )
{
DeselectAll(item);
SelectAndNotify(item);
if ( GetWindowStyle() & wxLB_EXTENDED )
AnchorSelection(item);
}
return true;
}
}
// nothing found
return false;
}
void wxListBox::EnsureVisible(int n)
{
if ( m_updateScrollbarY )
{
UpdateScrollbars();
m_updateScrollbarX =
m_updateScrollbarY = false;
}
DoEnsureVisible(n);
}
void wxListBox::DoEnsureVisible(int n)
{
if ( !m_showScrollbarY )
{
// nothing to do - everything is shown anyhow
return;
}
int first;
GetViewStart(0, &first);
if ( first > n )
{
// we need to scroll upwards, so make the current item appear on top
// of the shown range
Scroll(0, n);
}
else
{
int last = first + GetClientSize().y / GetLineHeight() - 1;
if ( last < n )
{
// scroll down: the current item appears at the bottom of the
// range
Scroll(0, n - (last - first));
}
}
}
void wxListBox::ChangeCurrent(int diff)
{
int current = m_current == -1 ? 0 : m_current;
current += diff;
int last = GetCount() - 1;
if ( current < 0 )
current = 0;
else if ( current > last )
current = last;
SetCurrentItem(current);
}
void wxListBox::ExtendSelection(int itemTo)
{
// if we don't have the explicit values for selection start/end, make them
// up
if ( m_selAnchor == -1 )
m_selAnchor = m_current;
if ( itemTo == -1 )
itemTo = m_current;
// swap the start/end of selection range if necessary
int itemFrom = m_selAnchor;
if ( itemFrom > itemTo )
{
int itemTmp = itemFrom;
itemFrom = itemTo;
itemTo = itemTmp;
}
// the selection should now include all items in the range between the
// anchor and the specified item and only them
int n;
for ( n = 0; n < itemFrom; n++ )
{
Deselect(n);
}
for ( ; n <= itemTo; n++ )
{
SetSelection(n);
}
unsigned int count = GetCount();
for ( ; n < (int)count; n++ )
{
Deselect(n);
}
}
void wxListBox::DoSelect(int item, bool sel)
{
if ( item != -1 )
{
// go to this item first
SetCurrentItem(item);
}
// the current item is the one we want to change: either it was just
// changed above to be the same as item or item == -1 in which we case we
// are supposed to use the current one anyhow
if ( m_current != -1 )
{
// [de]select it
SetSelection(m_current, sel);
}
}
void wxListBox::SelectAndNotify(int item)
{
DoSelect(item);
SendEvent(wxEVT_COMMAND_LISTBOX_SELECTED);
}
void wxListBox::Activate(int item)
{
if ( item != -1 )
SetCurrentItem(item);
else
item = m_current;
if ( !(GetWindowStyle() & wxLB_MULTIPLE) )
{
DeselectAll(item);
}
if ( item != -1 )
{
DoSelect(item);
SendEvent(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED);
}
}
// ----------------------------------------------------------------------------
// input handling
// ----------------------------------------------------------------------------
/*
The numArg here is the listbox item index while the strArg is used
differently for the different actions:
a) for wxACTION_LISTBOX_FIND it has the natural meaning: this is the string
to find
b) for wxACTION_LISTBOX_SELECT and wxACTION_LISTBOX_EXTENDSEL it is used
to decide if the listbox should send the notification event (it is empty)
or not (it is not): this allows us to reuse the same action for when the
user is dragging the mouse when it has been released although in the
first case no notification is sent while in the second it is sent.
*/
bool wxListBox::PerformAction(const wxControlAction& action,
long numArg,
const wxString& strArg)
{
int item = (int)numArg;
if ( action == wxACTION_LISTBOX_SETFOCUS )
{
SetCurrentItem(item);
}
else if ( action == wxACTION_LISTBOX_ACTIVATE )
{
Activate(item);
}
else if ( action == wxACTION_LISTBOX_TOGGLE )
{
if ( item == -1 )
item = m_current;
if ( IsSelected(item) )
DoUnselect(item);
else
SelectAndNotify(item);
}
else if ( action == wxACTION_LISTBOX_SELECT )
{
DeselectAll(item);
if ( strArg.empty() )
SelectAndNotify(item);
else
DoSelect(item);
}
else if ( action == wxACTION_LISTBOX_SELECTADD )
DoSelect(item);
else if ( action == wxACTION_LISTBOX_UNSELECT )
DoUnselect(item);
else if ( action == wxACTION_LISTBOX_MOVEDOWN )
ChangeCurrent(1);
else if ( action == wxACTION_LISTBOX_MOVEUP )
ChangeCurrent(-1);
else if ( action == wxACTION_LISTBOX_PAGEDOWN )
ChangeCurrent(GetItemsPerPage());
else if ( action == wxACTION_LISTBOX_PAGEUP )
ChangeCurrent(-GetItemsPerPage());
else if ( action == wxACTION_LISTBOX_START )
SetCurrentItem(0);
else if ( action == wxACTION_LISTBOX_END )
SetCurrentItem(GetCount() - 1);
else if ( action == wxACTION_LISTBOX_UNSELECTALL )
DeselectAll(item);
else if ( action == wxACTION_LISTBOX_EXTENDSEL )
ExtendSelection(item);
else if ( action == wxACTION_LISTBOX_FIND )
FindNextItem(strArg);
else if ( action == wxACTION_LISTBOX_ANCHOR )
AnchorSelection(item == -1 ? m_current : item);
else if ( action == wxACTION_LISTBOX_SELECTALL ||
action == wxACTION_LISTBOX_SELTOGGLE )
wxFAIL_MSG(_T("unimplemented yet"));
else
return wxControl::PerformAction(action, numArg, strArg);
return true;
}
/* static */
wxInputHandler *wxListBox::GetStdInputHandler(wxInputHandler *handlerDef)
{
static wxStdListboxInputHandler s_handler(handlerDef);
return &s_handler;
}
// ============================================================================
// implementation of wxStdListboxInputHandler
// ============================================================================
wxStdListboxInputHandler::wxStdListboxInputHandler(wxInputHandler *handler,
bool toggleOnPressAlways)
: wxStdInputHandler(handler)
{
m_btnCapture = 0;
m_toggleOnPressAlways = toggleOnPressAlways;
m_actionMouse = wxACTION_NONE;
m_trackMouseOutside = true;
}
int wxStdListboxInputHandler::HitTest(const wxListBox *lbox,
const wxMouseEvent& event)
{
int item = HitTestUnsafe(lbox, event);
return FixItemIndex(lbox, item);
}
int wxStdListboxInputHandler::HitTestUnsafe(const wxListBox *lbox,
const wxMouseEvent& event)
{
wxPoint pt = event.GetPosition();
pt -= lbox->GetClientAreaOrigin();
int y;
lbox->CalcUnscrolledPosition(0, pt.y, NULL, &y);
return y / lbox->GetLineHeight();
}
int wxStdListboxInputHandler::FixItemIndex(const wxListBox *lbox,
int item)
{
if ( item < 0 )
{
// mouse is above the first item
item = 0;
}
else if ( (unsigned int)item >= lbox->GetCount() )
{
// mouse is below the last item
item = lbox->GetCount() - 1;
}
return item;
}
bool wxStdListboxInputHandler::IsValidIndex(const wxListBox *lbox, int item)
{
return item >= 0 && (unsigned int)item < lbox->GetCount();
}
wxControlAction
wxStdListboxInputHandler::SetupCapture(wxListBox *lbox,
const wxMouseEvent& event,
int item)
{
// we currently only allow selecting with the left mouse button, if we
// do need to allow using other buttons too we might use the code
// inside #if 0
#if 0
m_btnCapture = event.LeftDown()
? 1
: event.RightDown()
? 3
: 2;
#else
m_btnCapture = 1;
#endif // 0/1
wxControlAction action;
if ( lbox->HasMultipleSelection() )
{
if ( lbox->GetWindowStyle() & wxLB_MULTIPLE )
{
if ( m_toggleOnPressAlways )
{
// toggle the item right now
action = wxACTION_LISTBOX_TOGGLE;
}
//else: later
m_actionMouse = wxACTION_LISTBOX_SETFOCUS;
}
else // wxLB_EXTENDED listbox
{
// simple click in an extended sel listbox clears the old
// selection and adds the clicked item to it then, ctrl-click
// toggles an item to it and shift-click adds a range between
// the old selection anchor and the clicked item
if ( event.ControlDown() )
{
lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item);
action = wxACTION_LISTBOX_TOGGLE;
}
else if ( event.ShiftDown() )
{
action = wxACTION_LISTBOX_EXTENDSEL;
}
else // simple click
{
lbox->PerformAction(wxACTION_LISTBOX_ANCHOR, item);
action = wxACTION_LISTBOX_SELECT;
}
m_actionMouse = wxACTION_LISTBOX_EXTENDSEL;
}
}
else // single selection
{
m_actionMouse =
action = wxACTION_LISTBOX_SELECT;
}
// by default we always do track it
m_trackMouseOutside = true;
return action;
}
bool wxStdListboxInputHandler::HandleKey(wxInputConsumer *consumer,
const wxKeyEvent& event,
bool pressed)
{
// we're only interested in the key press events
if ( pressed && !event.AltDown() )
{
bool isMoveCmd = true;
int style = consumer->GetInputWindow()->GetWindowStyle();
wxControlAction action;
wxString strArg;
int keycode = event.GetKeyCode();
switch ( keycode )
{
// movement
case WXK_UP:
action = wxACTION_LISTBOX_MOVEUP;
break;
case WXK_DOWN:
action = wxACTION_LISTBOX_MOVEDOWN;
break;
case WXK_PAGEUP:
action = wxACTION_LISTBOX_PAGEUP;
break;
case WXK_PAGEDOWN:
action = wxACTION_LISTBOX_PAGEDOWN;
break;
case WXK_HOME:
action = wxACTION_LISTBOX_START;
break;
case WXK_END:
action = wxACTION_LISTBOX_END;
break;
// selection
case WXK_SPACE:
if ( style & wxLB_MULTIPLE )
{
action = wxACTION_LISTBOX_TOGGLE;
isMoveCmd = false;
}
break;
case WXK_RETURN:
action = wxACTION_LISTBOX_ACTIVATE;
isMoveCmd = false;
break;
default:
if ( (keycode < 255) && wxIsalnum((wxChar)keycode) )
{
action = wxACTION_LISTBOX_FIND;
strArg = (wxChar)keycode;
}
}
if ( !action.IsEmpty() )
{
consumer->PerformAction(action, -1, strArg);
if ( isMoveCmd )
{
if ( style & wxLB_SINGLE )
{
// the current item is always the one selected
consumer->PerformAction(wxACTION_LISTBOX_SELECT);
}
else if ( style & wxLB_EXTENDED )
{
if ( event.ShiftDown() )
consumer->PerformAction(wxACTION_LISTBOX_EXTENDSEL);
else
{
// select the item and make it the new selection anchor
consumer->PerformAction(wxACTION_LISTBOX_SELECT);
consumer->PerformAction(wxACTION_LISTBOX_ANCHOR);
}
}
//else: nothing to do for multiple selection listboxes
}
return true;
}
}
return wxStdInputHandler::HandleKey(consumer, event, pressed);
}
bool wxStdListboxInputHandler::HandleMouse(wxInputConsumer *consumer,
const wxMouseEvent& event)
{
wxListBox *lbox = wxStaticCast(consumer->GetInputWindow(), wxListBox);
int item = HitTest(lbox, event);
wxControlAction action;
// when the left mouse button is pressed, capture the mouse and track the
// item under mouse (if the mouse leaves the window, we will still be
// getting the mouse move messages generated by wxScrollWindow)
if ( event.LeftDown() )
{
// capture the mouse to track the selected item
lbox->CaptureMouse();
action = SetupCapture(lbox, event, item);
}
else if ( m_btnCapture && event.ButtonUp(m_btnCapture) )
{
// when the left mouse button is released, release the mouse too
wxWindow *winCapture = wxWindow::GetCapture();
if ( winCapture )
{
winCapture->ReleaseMouse();
m_btnCapture = 0;
action = m_actionMouse;
}
//else: the mouse wasn't presed over the listbox, only released here
}
else if ( event.LeftDClick() )
{
action = wxACTION_LISTBOX_ACTIVATE;
}
if ( !action.IsEmpty() )
{
lbox->PerformAction(action, item);
return true;
}
return wxStdInputHandler::HandleMouse(consumer, event);
}
bool wxStdListboxInputHandler::HandleMouseMove(wxInputConsumer *consumer,
const wxMouseEvent& event)
{
wxWindow *winCapture = wxWindow::GetCapture();
if ( winCapture && (event.GetEventObject() == winCapture) )
{
wxListBox *lbox = wxStaticCast(consumer->GetInputWindow(), wxListBox);
if ( !m_btnCapture || !m_trackMouseOutside )
{
// someone captured the mouse for us (we always set m_btnCapture
// when we do it ourselves): in this case we only react to
// the mouse messages when they happen inside the listbox
if ( lbox->HitTest(event.GetPosition()) != wxHT_WINDOW_INSIDE )
return false;
}
int item = HitTest(lbox, event);
if ( !m_btnCapture )
{
// now that we have the mouse inside the listbox, do capture it
// normally - but ensure that we will still ignore the outside
// events
SetupCapture(lbox, event, item);
m_trackMouseOutside = false;
}
if ( IsValidIndex(lbox, item) )
{
// pass something into strArg to tell the listbox that it shouldn't
// send the notification message: see PerformAction() above
lbox->PerformAction(m_actionMouse, item, _T("no"));
}
// else: don't pass invalid index to the listbox
}
else // we don't have capture any more
{
if ( m_btnCapture )
{
// if we lost capture unexpectedly (someone else took the capture
// from us), return to a consistent state
m_btnCapture = 0;
}
}
return wxStdInputHandler::HandleMouseMove(consumer, event);
}
#endif // wxUSE_LISTBOX