Files
wxWidgets/src/univ/listbox.cpp
Vadim Zeitlin d131b63b6d 1. big wxScrollBar optimization: Refresh() doesn't refresh them any more
2. many fixes to refresh scrollbars when needed (as this is not done all
   the time now)
3. wxStdButtonInputHandler bug with uninit m_hasMouse fixing bug with
   moving mouse with pressed left button into button
4. wxRadioBox::SetSelection() and wxRadioButton::SetValue() clear the
   values of the other buttons in the same radio group
5. wxTextCtrl::RefreshPixelRange() calculates the end of line correctly
6. tons of wxListBox fixes
7. removed confusing "Create" button from the lbox sample, listbox is now
   recreated on the fly


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/wxUNIVERSAL@8615 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2000-10-23 01:45:21 +00:00

1279 lines
34 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: univ/listbox.cpp
// Purpose: wxListBox implementation
// Author: Vadim Zeitlin
// Modified by:
// Created: 30.08.00
// RCS-ID: $Id$
// Copyright: (c) 2000 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#ifdef __GNUG__
#pragma implementation "univlistbox.h"
#endif
#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"
// ============================================================================
// implementation of wxListBox
// ============================================================================
IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControl)
BEGIN_EVENT_TABLE(wxListBox, wxListBoxBase)
EVT_SIZE(wxListBox::OnSize)
EVT_IDLE(wxListBox::OnIdle)
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;
// 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;
}
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 ( !wxControl::Create(parent, id, pos, size, style, wxDefaultValidator, name) )
return FALSE;
SetWindow(this);
if ( style & wxLB_SORT )
m_strings = wxArrayString(TRUE /* auto sort */);
Set(n, choices);
SetBestSize(size);
return TRUE;
}
wxListBox::~wxListBox()
{
}
// ----------------------------------------------------------------------------
// adding/inserting strings
// ----------------------------------------------------------------------------
int wxListBox::DoAppend(const wxString& item)
{
size_t index = m_strings.Add(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, 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") );
size_t count = items.GetCount();
for ( size_t 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();
size_t count = items.GetCount();
if ( !count )
return;
m_strings.Alloc(count);
m_itemsClientData.Alloc(count);
for ( size_t n = 0; n < count; n++ )
{
size_t index = m_strings.Add(items[n]);
m_itemsClientData.Insert(clientData ? clientData[n] : NULL, index);
}
m_updateScrollbarY = TRUE;
RefreshAll();
}
void wxListBox::SetString(int n, const wxString& s)
{
if ( HasHorzScrollbar() )
{
// we need to update m_maxWidth as changing the string may cause the
// horz scrollbar [dis]appear
wxCoord width;
m_strings[n] = s;
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 == m_maxWidthItem )
{
RefreshHorzScrollbar();
}
}
else // no horz scrollbar
{
m_strings[n] = s;
}
RefreshItem(n);
}
// ----------------------------------------------------------------------------
// removing strings
// ----------------------------------------------------------------------------
void wxListBox::DoClear()
{
m_strings.Clear();
if ( HasClientObjectData() )
{
size_t count = m_itemsClientData.GetCount();
for ( size_t n = 0; n < count; n++ )
{
delete m_itemsClientData[n];
}
}
m_itemsClientData.Clear();
m_selections.Clear();
}
void wxListBox::Clear()
{
DoClear();
m_updateScrollbarY = TRUE;
RefreshHorzScrollbar();
RefreshAll();
}
void wxListBox::Delete(int n)
{
wxCHECK_RET( n < GetCount(), _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 m_itemsClientData[n];
}
m_itemsClientData.RemoveAt(n);
// when the item disappears we must not keep using its index
if ( n == m_current )
{
m_current = -1;
}
else if ( 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;
size_t count = m_selections.GetCount();
for ( size_t item = 0; item < count; item++ )
{
if ( m_selections[item] == n )
{
// remember to delete it later
index = item;
}
else if ( m_selections[item] > 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 ( n == m_maxWidthItem )
{
RefreshHorzScrollbar();
}
}
// ----------------------------------------------------------------------------
// client data handling
// ----------------------------------------------------------------------------
void wxListBox::DoSetItemClientData(int n, void* clientData)
{
m_itemsClientData[n] = clientData;
}
void *wxListBox::DoGetItemClientData(int n) const
{
return m_itemsClientData[n];
}
void wxListBox::DoSetItemClientObject(int n, wxClientData* clientData)
{
m_itemsClientData[n] = clientData;
}
wxClientData* wxListBox::DoGetItemClientObject(int n) const
{
return (wxClientData *)m_itemsClientData[n];
}
// ----------------------------------------------------------------------------
// selection
// ----------------------------------------------------------------------------
void wxListBox::SetSelection(int n, bool select)
{
if ( select )
{
if ( m_selections.Index(n) == wxNOT_FOUND )
{
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
}
wxASSERT_MSG( HasMultipleSelection() || (m_selections.GetCount() < 2),
_T("multiple selected items in single selection lbox?") );
}
int wxListBox::GetSelection() const
{
wxCHECK_MSG( !HasMultipleSelection(), -1,
_T("use wxListBox::GetSelections for ths listbox") );
return m_selections.IsEmpty() ? -1 : 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;
size_t 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?
int nLines = GetCount();
wxCoord lineHeight = GetLineHeight();
bool showScrollbarY = 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::OnIdle(wxIdleEvent& event)
{
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;
}
}
// ----------------------------------------------------------------------------
// 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
wxRegion rgnUpdate = GetUpdateRegion();
rgnUpdate.Intersect(GetClientRect());
wxRect rectUpdate = rgnUpdate.GetBox();
wxPoint ptOrigin = GetClientAreaOrigin();
rectUpdate.x -= ptOrigin.x;
rectUpdate.y -= ptOrigin.y;
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();
size_t 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;
size_t count = m_strings.GetCount();
for ( size_t n = 0; n < count; n++ )
{
GetTextExtent(m_strings[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);
}
wxSize wxListBox::DoGetBestClientSize() const
{
wxCoord width = 0,
height = 0;
size_t count = m_strings.GetCount();
for ( size_t n = 0; n < count; n++ )
{
wxCoord w,h;
GetTextExtent(m_strings[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(int item, wxEventType type)
{
wxCommandEvent event(type, m_windowId);
event.SetEventObject(this);
if ( item != -1 )
{
if ( HasClientObjectData() )
event.SetClientObject(GetClientObject(item));
else if ( HasClientUntypedData() )
event.SetClientData(GetClientData(item));
event.SetString(GetString(item));
}
event.m_commandInt = 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)
{
size_t len = prefix.length();
int count = GetCount();
int first = m_current == count - 1 ? 0 : m_current + 1;
int last = m_current == -1 ? count : m_current;
for ( int item = first; item != last; item < count - 1 ? item++ : item = 0 )
{
if ( wxStrnicmp(m_strings[item], prefix, len) == 0 )
{
SetCurrentItem(item);
if ( !(GetWindowStyle() & wxLB_MULTIPLE) )
{
DeselectAll(item);
Select(TRUE, 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);
}
int count = GetCount();
for ( ; n < count; n++ )
{
Deselect(n);
}
}
void wxListBox::Select(bool sel, int item)
{
if ( item != -1 )
SetCurrentItem(item);
if ( m_current != -1 )
{
// [de]select the new one
SetSelection(m_current, sel);
if ( sel )
{
SendEvent(m_current, 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 )
{
Select();
SendEvent(m_current, wxEVT_COMMAND_LISTBOX_DOUBLECLICKED);
}
}
// ----------------------------------------------------------------------------
// input handling
// ----------------------------------------------------------------------------
wxString wxListBox::GetInputHandlerType() const
{
return wxINP_HANDLER_LISTBOX;
}
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;
Select(!IsSelected(item), item);
}
else if ( action == wxACTION_LISTBOX_SELECT )
{
DeselectAll(item);
Select(TRUE, item);
}
else if ( action == wxACTION_LISTBOX_SELECTADD )
Select(TRUE, item);
else if ( action == wxACTION_LISTBOX_UNSELECT )
Select(FALSE, 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 )
FindItem(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;
}
// ============================================================================
// implementation of wxStdListboxInputHandler
// ============================================================================
wxStdListboxInputHandler::wxStdListboxInputHandler(wxInputHandler *handler,
bool toggleOnPressAlways)
: wxStdInputHandler(handler)
{
m_winCapture = NULL;
m_btnCapture = 0;
m_toggleOnPressAlways = toggleOnPressAlways;
}
int wxStdListboxInputHandler::HitTest(const wxListBox *lbox,
const wxMouseEvent& event)
{
wxPoint pt = event.GetPosition();
pt -= lbox->GetClientAreaOrigin();
int y;
lbox->CalcUnscrolledPosition(0, pt.y, NULL, &y);
int item = y / lbox->GetLineHeight();
return (item >= 0) && (item < lbox->GetCount()) ? item : -1;
}
bool wxStdListboxInputHandler::HandleKey(wxControl *control,
const wxKeyEvent& event,
bool pressed)
{
// we're only interested in the key press events
if ( pressed && !event.AltDown() )
{
bool isMoveCmd = TRUE;
int style = control->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_PRIOR: action = wxACTION_LISTBOX_PAGEUP; break;
case WXK_NEXT: 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(keycode) )
{
action = wxACTION_LISTBOX_FIND;
strArg = (wxChar)keycode;
}
}
if ( !!action )
{
control->PerformAction(action, -1, strArg);
if ( isMoveCmd )
{
if ( style & wxLB_SINGLE )
{
// the current item is always the one selected
control->PerformAction(wxACTION_LISTBOX_SELECT);
}
else if ( style & wxLB_EXTENDED )
{
if ( event.ShiftDown() )
control->PerformAction(wxACTION_LISTBOX_EXTENDSEL);
else
{
// select the item and make it the new selection anchor
control->PerformAction(wxACTION_LISTBOX_SELECT);
control->PerformAction(wxACTION_LISTBOX_ANCHOR);
}
}
//else: nothing to do for multiple selection listboxes
}
return TRUE;
}
}
return wxStdInputHandler::HandleKey(control, event, pressed);
}
bool wxStdListboxInputHandler::HandleMouse(wxControl *control,
const wxMouseEvent& event)
{
// single and extended listboxes behave similarly with respect to the
// mouse events: for both of them clicking the item selects or toggles it,
// but multiple selection listboxes are different: the item is focused
// when clicked and only toggled when the button is released
if ( (m_btnCapture &&
(control->GetWindowStyle() & wxLB_MULTIPLE) &&
event.ButtonUp(m_btnCapture))
|| event.ButtonDown()
|| event.LeftDClick() )
{
wxListBox *lbox = wxStaticCast(control, wxListBox);
int item = HitTest(lbox, event);
if ( item != -1 )
{
wxControlAction action;
if ( m_btnCapture && event.ButtonUp(m_btnCapture) )
{
if ( m_winCapture )
{
m_winCapture->ReleaseMouse();
m_winCapture = NULL;
m_btnCapture = 0;
}
else
{
// this is not supposed to happen - if we get here, the
// even is from the mouse button which had been pressed
// before and we must have captured the mouse back then
wxFAIL_MSG(_T("logic error in listbox mosue handling"));
}
if ( !m_toggleOnPressAlways )
{
// in this mode we toggle the item state when the button is
// released, i.e. now
action = wxACTION_LISTBOX_TOGGLE;
}
}
else if ( event.ButtonDown() )
{
if ( lbox->HasMultipleSelection() )
{
if ( lbox->GetWindowStyle() & wxLB_MULTIPLE )
{
// capture the mouse to track the selected item
m_winCapture = lbox;
m_winCapture->CaptureMouse();
m_btnCapture = event.LeftDown()
? 1
: event.RightDown()
? 3
: 2;
if ( m_toggleOnPressAlways )
{
// toggle the item right now
action = wxACTION_LISTBOX_TOGGLE;
}
}
else // wxLB_EXTENDED listbox
{
// simple click in an extended listbox clears the
// selection, ctrl-click toggles an item to it and
// shift-click adds a range
if ( event.ControlDown() )
{
control->PerformAction(wxACTION_LISTBOX_ANCHOR,
item);
action = wxACTION_LISTBOX_TOGGLE;
}
else if ( event.ShiftDown() )
{
action = wxACTION_LISTBOX_EXTENDSEL;
}
else // simple click
{
control->PerformAction(wxACTION_LISTBOX_ANCHOR,
item);
action = wxACTION_LISTBOX_SELECT;
}
}
}
else // single selection
{
action = wxACTION_LISTBOX_SELECT;
}
// in any case, clicking an item makes it the current one
lbox->PerformAction(wxACTION_LISTBOX_SETFOCUS, item);
}
else // event.LeftDClick()
{
action = wxACTION_LISTBOX_ACTIVATE;
}
if ( !!action )
{
lbox->PerformAction(action, item);
}
return TRUE;
}
//else: click outside the item area, ignore
}
return wxStdInputHandler::HandleMouse(control, event);
}
bool wxStdListboxInputHandler::HandleMouseMove(wxControl *control,
const wxMouseEvent& event)
{
if ( !m_winCapture || (event.GetEventObject() != m_winCapture) )
{
// we don't react to this
return FALSE;
}
// TODO: not yet... should track the mouse outside and start an auto
// scroll timer - but this should be probably done in
// wxScrolledWindow itself (?)
if ( !event.Moving() )
return FALSE;
wxListBox *lbox = wxStaticCast(control, wxListBox);
int item = HitTest(lbox, event);
if ( item == -1 )
{
// mouse is below the last item
return FALSE;
}
lbox->PerformAction(wxACTION_LISTBOX_SETFOCUS, item);
return TRUE;
}
#endif // wxUSE_LISTBOX