Improve selection and focus events generation in wxGenericLisCtrl
Avoid sending spurious wxEVT_LIST_ITEM_{FOCUSED, SELECTED, DESELECTED} events and make the generic version consistent with the behaviour of the native wxMSW one. Also add/extend the tests and slightly improve the sample. Closes https://github.com/wxWidgets/wxWidgets/pull/2044
This commit is contained in:
committed by
Vadim Zeitlin
parent
80a3cd2db9
commit
fedc80eee3
@@ -950,6 +950,7 @@ bool wxListLineData::Highlight( bool on )
|
||||
return false;
|
||||
|
||||
m_highlighted = on;
|
||||
m_owner->UpdateSelectionCount(on);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1571,6 +1572,7 @@ wxEND_EVENT_TABLE()
|
||||
void wxListMainWindow::Init()
|
||||
{
|
||||
m_dirty = true;
|
||||
m_selCount =
|
||||
m_countVirt = 0;
|
||||
m_lineFrom =
|
||||
m_lineTo = (size_t)-1;
|
||||
@@ -1598,7 +1600,8 @@ void wxListMainWindow::Init()
|
||||
m_current =
|
||||
m_lineLastClicked =
|
||||
m_lineSelectSingleOnUp =
|
||||
m_lineBeforeLastClicked = (size_t)-1;
|
||||
m_lineBeforeLastClicked =
|
||||
m_anchor = (size_t)-1;
|
||||
|
||||
m_hasCheckBoxes = false;
|
||||
}
|
||||
@@ -1866,8 +1869,13 @@ bool wxListMainWindow::IsHighlighted(size_t line) const
|
||||
|
||||
void wxListMainWindow::HighlightLines( size_t lineFrom,
|
||||
size_t lineTo,
|
||||
bool highlight )
|
||||
bool highlight,
|
||||
SendEvent sendEvent )
|
||||
{
|
||||
// It is safe to swap the bounds here if they are not in order.
|
||||
if ( lineFrom > lineTo )
|
||||
wxSwap(lineFrom, lineTo);
|
||||
|
||||
if ( IsVirtual() )
|
||||
{
|
||||
wxArrayInt linesChanged;
|
||||
@@ -1890,13 +1898,13 @@ void wxListMainWindow::HighlightLines( size_t lineFrom,
|
||||
{
|
||||
for ( size_t line = lineFrom; line <= lineTo; line++ )
|
||||
{
|
||||
if ( HighlightLine(line, highlight) )
|
||||
if ( HighlightLine(line, highlight, sendEvent) )
|
||||
RefreshLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool wxListMainWindow::HighlightLine( size_t line, bool highlight )
|
||||
bool wxListMainWindow::HighlightLine( size_t line, bool highlight, SendEvent sendEvent )
|
||||
{
|
||||
bool changed;
|
||||
|
||||
@@ -1912,7 +1920,7 @@ bool wxListMainWindow::HighlightLine( size_t line, bool highlight )
|
||||
changed = ld->Highlight(highlight);
|
||||
}
|
||||
|
||||
if ( changed )
|
||||
if ( changed && sendEvent )
|
||||
{
|
||||
SendNotify( line, highlight ? wxEVT_LIST_ITEM_SELECTED
|
||||
: wxEVT_LIST_ITEM_DESELECTED );
|
||||
@@ -2197,6 +2205,58 @@ void wxListMainWindow::HighlightAll( bool on )
|
||||
}
|
||||
}
|
||||
|
||||
void wxListMainWindow::HighlightOnly( size_t line, size_t oldLine )
|
||||
{
|
||||
const unsigned selCount = GetSelectedItemCount();
|
||||
|
||||
if ( selCount == 1 && IsHighlighted(line) )
|
||||
{
|
||||
return; // Nothing changed.
|
||||
}
|
||||
|
||||
if ( oldLine != (size_t)-1 )
|
||||
{
|
||||
IsHighlighted(oldLine) ? ReverseHighlight(oldLine)
|
||||
: RefreshLine(oldLine); // refresh the old focus to remove it
|
||||
}
|
||||
|
||||
if ( selCount > 1 ) // multiple-selection only
|
||||
{
|
||||
// Deselecting many items at once will generate wxEVT_XXX_DESELECTED event
|
||||
// for each one of them. although this may be inefficient if the number of
|
||||
// deselected items is too much, we keep doing this (for non-virtual list
|
||||
// controls) for backward compatibility concerns. For virtual listctrl (in
|
||||
// multi-selection mode), wxMSW sends only a notification to indicate that
|
||||
// something has been deselected. Notice that to be fully compatible with
|
||||
// wxMSW behaviour, _line_ shouldn't be deselected if it was selected.
|
||||
|
||||
const SendEvent sendEvent = IsVirtual() ? SendEvent_None : SendEvent_Normal;
|
||||
|
||||
size_t lineFrom = 0,
|
||||
lineTo = GetItemCount() - 1;
|
||||
|
||||
if ( line > lineFrom && line < lineTo )
|
||||
{
|
||||
HighlightLines(lineFrom, line - 1, false, sendEvent);
|
||||
HighlightLines(line + 1, lineTo, false, sendEvent);
|
||||
}
|
||||
else // _line_ is equal to lineFrom or lineTo
|
||||
{
|
||||
line == lineFrom ? ++lineFrom : --lineTo;
|
||||
HighlightLines(lineFrom, lineTo, false, sendEvent);
|
||||
}
|
||||
|
||||
// If we didn't send the event for individual items above, send it for all of them now.
|
||||
if ( sendEvent == SendEvent_None )
|
||||
SendNotify((size_t)-1, wxEVT_LIST_ITEM_DESELECTED);
|
||||
}
|
||||
|
||||
// _line_ should be the only selected item.
|
||||
HighlightLine(line);
|
||||
// refresh the new focus to add it.
|
||||
RefreshLine(line);
|
||||
}
|
||||
|
||||
void wxListMainWindow::OnChildFocus(wxChildFocusEvent& WXUNUSED(event))
|
||||
{
|
||||
// Do nothing here. This prevents the default handler in wxScrolledWindow
|
||||
@@ -2241,8 +2301,13 @@ void wxListMainWindow::SendNotify( size_t line,
|
||||
GetParent()->GetEventHandler()->ProcessEvent( le );
|
||||
}
|
||||
|
||||
void wxListMainWindow::ChangeCurrent(size_t current)
|
||||
bool wxListMainWindow::ChangeCurrentWithoutEvent(size_t current)
|
||||
{
|
||||
if ( current == m_current )
|
||||
{
|
||||
return false; // Nothing changed!
|
||||
}
|
||||
|
||||
m_current = current;
|
||||
|
||||
// as the current item changed, we shouldn't start editing it when the
|
||||
@@ -2250,7 +2315,13 @@ void wxListMainWindow::ChangeCurrent(size_t current)
|
||||
if ( m_renameTimer->IsRunning() )
|
||||
m_renameTimer->Stop();
|
||||
|
||||
SendNotify(current, wxEVT_LIST_ITEM_FOCUSED);
|
||||
return true;
|
||||
}
|
||||
|
||||
void wxListMainWindow::ChangeCurrent(size_t current)
|
||||
{
|
||||
if ( ChangeCurrentWithoutEvent(current) )
|
||||
SendNotify(current, wxEVT_LIST_ITEM_FOCUSED);
|
||||
}
|
||||
|
||||
wxTextCtrl *wxListMainWindow::EditLabel(long item, wxClassInfo* textControlClass)
|
||||
@@ -2396,7 +2467,11 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
|
||||
evtCtx.SetEventObject(GetParent());
|
||||
GetParent()->GetEventHandler()->ProcessEvent(evtCtx);
|
||||
}
|
||||
return;
|
||||
|
||||
if ( IsEmpty() )
|
||||
return;
|
||||
|
||||
// Continue processing...
|
||||
}
|
||||
|
||||
if (m_dirty)
|
||||
@@ -2521,8 +2596,7 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
|
||||
if (m_lineSelectSingleOnUp != (size_t)-1)
|
||||
{
|
||||
// select single line
|
||||
HighlightAll( false );
|
||||
ReverseHighlight(m_lineSelectSingleOnUp);
|
||||
HighlightOnly(m_lineSelectSingleOnUp);
|
||||
}
|
||||
|
||||
if (m_lastOnSame)
|
||||
@@ -2542,6 +2616,14 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
|
||||
m_lastOnSame = false;
|
||||
}
|
||||
|
||||
if ( GetSelectedItemCount() == 1 || event.CmdDown() )
|
||||
{
|
||||
// In multiple selection mode, the anchor is set to the first selected
|
||||
// item or can be changed to m_current if Ctrl key is down, as is the
|
||||
// case under wxMSW.
|
||||
m_anchor = m_current;
|
||||
}
|
||||
|
||||
m_lineSelectSingleOnUp = (size_t)-1;
|
||||
}
|
||||
else
|
||||
@@ -2561,9 +2643,8 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
|
||||
// Multi-selections should not be cleared if a selected item is clicked.
|
||||
if (!IsHighlighted(current))
|
||||
{
|
||||
HighlightAll(false);
|
||||
ChangeCurrent(current);
|
||||
ReverseHighlight(m_current);
|
||||
HighlightOnly(m_current);
|
||||
}
|
||||
|
||||
SendNotify( current, wxEVT_LIST_ITEM_RIGHT_CLICK, event.GetPosition() );
|
||||
@@ -2581,7 +2662,7 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
|
||||
m_lineLastClicked = current;
|
||||
|
||||
size_t oldCurrent = m_current;
|
||||
bool oldWasSelected = IsHighlighted(m_current);
|
||||
bool oldWasSelected = HasCurrent() && IsHighlighted(m_current);
|
||||
|
||||
bool cmdModifierDown = event.CmdDown();
|
||||
if ( IsSingleSel() || !(cmdModifierDown || event.ShiftDown()) )
|
||||
@@ -2592,11 +2673,8 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
|
||||
}
|
||||
else if (IsSingleSel() || !IsHighlighted(current))
|
||||
{
|
||||
HighlightAll(false);
|
||||
|
||||
ChangeCurrent(current);
|
||||
|
||||
ReverseHighlight(m_current);
|
||||
HighlightOnly(m_current, oldWasSelected ? oldCurrent : (size_t)-1);
|
||||
}
|
||||
else // multi sel & current is highlighted & no mod keys
|
||||
{
|
||||
@@ -2616,16 +2694,15 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
|
||||
{
|
||||
ChangeCurrent(current);
|
||||
|
||||
size_t lineFrom = oldCurrent,
|
||||
lineTo = current;
|
||||
|
||||
if ( lineTo < lineFrom )
|
||||
if ( oldCurrent == (size_t)-1 )
|
||||
{
|
||||
lineTo = lineFrom;
|
||||
lineFrom = m_current;
|
||||
// Highlight m_current only if there is no previous selection.
|
||||
HighlightLine(m_current);
|
||||
}
|
||||
else if ( oldCurrent != current && m_anchor != (size_t)-1 )
|
||||
{
|
||||
ExtendSelection(oldCurrent, current);
|
||||
}
|
||||
|
||||
HighlightLines(lineFrom, lineTo);
|
||||
}
|
||||
else // !ctrl, !shift
|
||||
{
|
||||
@@ -2634,7 +2711,7 @@ void wxListMainWindow::OnMouse( wxMouseEvent &event )
|
||||
}
|
||||
}
|
||||
|
||||
if (m_current != oldCurrent)
|
||||
if (m_current != oldCurrent && oldCurrent != (size_t)-1)
|
||||
RefreshLine( oldCurrent );
|
||||
|
||||
// Set the flag telling us whether the next click on this item should
|
||||
@@ -2739,6 +2816,81 @@ bool wxListMainWindow::ScrollList(int WXUNUSED(dx), int dy)
|
||||
// keyboard handling
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Helper function which handles items selection correctly and efficiently. and
|
||||
// simply, mimic the wxMSW behaviour. And The benefit of this function is that it
|
||||
// ensures that wxEVT_LIST_ITEM_{DE}SELECTED events are only generated for items
|
||||
// freshly (de)selected (i.e. items that have really changed state). Useful for
|
||||
// multi-selection mode when selecting using mouse or arrows with Shift key down.
|
||||
void wxListMainWindow::ExtendSelection(size_t oldCurrent, size_t newCurrent)
|
||||
{
|
||||
// Refresh the old/new focus to remove/add it
|
||||
RefreshLine(oldCurrent);
|
||||
RefreshLine(newCurrent);
|
||||
|
||||
// Given a selection [anchor, old], to change/extend it to new (i.e. the
|
||||
// selection becomes [anchor, new]) we discriminate three possible cases:
|
||||
//
|
||||
// Case 1) new < old <= anchor || anchor <= old < new
|
||||
// i.e. oldCurrent between anchor and newCurrent, in which case we:
|
||||
// - Highlight everything between anchor and newCurrent (inclusive).
|
||||
//
|
||||
// Case 2) old < new <= anchor || anchor <= new < old
|
||||
// i.e. newCurrent between anchor and oldCurrent, in which case we:
|
||||
// - Unhighlight everything between oldCurrent and newCurrent (exclusive).
|
||||
//
|
||||
// Case 3) old < anchor < new || new < anchor < old
|
||||
// i.e. anchor between oldCurrent and newCurrent, in which case we
|
||||
// - Highlight everything between anchor and newCurrent (inclusive).
|
||||
// - Unhighlight everything between anchor (exclusive) and oldCurrent.
|
||||
|
||||
size_t lineFrom, lineTo;
|
||||
|
||||
if ( (newCurrent < oldCurrent && oldCurrent <= m_anchor) ||
|
||||
(newCurrent > oldCurrent && oldCurrent >= m_anchor) )
|
||||
{
|
||||
lineFrom = m_anchor;
|
||||
lineTo = newCurrent;
|
||||
|
||||
HighlightLines(lineFrom, lineTo);
|
||||
}
|
||||
else if ( (oldCurrent < newCurrent && newCurrent <= m_anchor) ||
|
||||
(oldCurrent > newCurrent && newCurrent >= m_anchor) )
|
||||
{
|
||||
lineFrom = oldCurrent;
|
||||
lineTo = newCurrent;
|
||||
|
||||
// Exclude newCurrent from being deselected
|
||||
(lineTo < lineFrom) ? ++lineTo : --lineTo;
|
||||
|
||||
HighlightLines(lineFrom, lineTo, false);
|
||||
|
||||
// For virtual listctrl (in multi-selection mode), wxMSW sends only
|
||||
// a notification to indicate that something has been deselected.
|
||||
if ( IsVirtual() )
|
||||
SendNotify((size_t)-1, wxEVT_LIST_ITEM_DESELECTED);
|
||||
}
|
||||
else if ( (oldCurrent < m_anchor && m_anchor < newCurrent) ||
|
||||
(newCurrent < m_anchor && m_anchor < oldCurrent) )
|
||||
{
|
||||
lineFrom = m_anchor;
|
||||
lineTo = oldCurrent;
|
||||
|
||||
// Exclude anchor from being deselected
|
||||
(lineTo < lineFrom) ? --lineFrom : ++lineFrom;
|
||||
|
||||
HighlightLines(lineFrom, lineTo, false);
|
||||
|
||||
// See above.
|
||||
if ( IsVirtual() )
|
||||
SendNotify((size_t)-1, wxEVT_LIST_ITEM_DESELECTED);
|
||||
|
||||
lineFrom = m_anchor;
|
||||
lineTo = newCurrent;
|
||||
|
||||
HighlightLines(lineFrom, lineTo);
|
||||
}
|
||||
}
|
||||
|
||||
void wxListMainWindow::OnArrowChar(size_t newCurrent, const wxKeyEvent& event)
|
||||
{
|
||||
wxCHECK_RET( newCurrent < (size_t)GetItemCount(),
|
||||
@@ -2746,43 +2898,34 @@ void wxListMainWindow::OnArrowChar(size_t newCurrent, const wxKeyEvent& event)
|
||||
|
||||
size_t oldCurrent = m_current;
|
||||
|
||||
ChangeCurrent(newCurrent);
|
||||
|
||||
// in single selection we just ignore Shift as we can't select several
|
||||
// items anyhow
|
||||
if ( event.ShiftDown() && !IsSingleSel() )
|
||||
{
|
||||
ChangeCurrent(newCurrent);
|
||||
|
||||
// refresh the old focus to remove it
|
||||
RefreshLine( oldCurrent );
|
||||
|
||||
// select all the items between the old and the new one
|
||||
if ( oldCurrent > newCurrent )
|
||||
{
|
||||
newCurrent = oldCurrent;
|
||||
oldCurrent = m_current;
|
||||
}
|
||||
|
||||
HighlightLines(oldCurrent, newCurrent);
|
||||
ExtendSelection(oldCurrent, newCurrent);
|
||||
}
|
||||
else // !shift
|
||||
{
|
||||
// all previously selected items are unselected unless ctrl is held
|
||||
// in a multiselection control
|
||||
// all previously selected items are unselected unless ctrl is held in
|
||||
// a multi-selection control. in single selection mode we must always
|
||||
// have a selected item.
|
||||
if ( !event.ControlDown() || IsSingleSel() )
|
||||
HighlightAll(false);
|
||||
{
|
||||
HighlightOnly(m_current, oldCurrent);
|
||||
|
||||
ChangeCurrent(newCurrent);
|
||||
|
||||
// refresh the old focus to remove it
|
||||
RefreshLine( oldCurrent );
|
||||
|
||||
// in single selection mode we must always have a selected item
|
||||
if ( !event.ControlDown() || IsSingleSel() )
|
||||
HighlightLine( m_current, true );
|
||||
// Update anchor
|
||||
m_anchor = m_current;
|
||||
}
|
||||
else
|
||||
{
|
||||
// refresh the old/new focus to remove/add it
|
||||
RefreshLine(oldCurrent);
|
||||
RefreshLine(m_current);
|
||||
}
|
||||
}
|
||||
|
||||
RefreshLine( m_current );
|
||||
|
||||
MoveToFocus();
|
||||
}
|
||||
|
||||
@@ -2799,10 +2942,11 @@ void wxListMainWindow::OnKeyDown( wxKeyEvent &event )
|
||||
|
||||
// send a list event
|
||||
wxListEvent le( wxEVT_LIST_KEY_DOWN, parent->GetId() );
|
||||
const size_t current = ShouldSendEventForCurrent() ? m_current : (size_t)-1;
|
||||
le.m_item.m_itemId =
|
||||
le.m_itemIndex = m_current;
|
||||
if (HasCurrent())
|
||||
GetLine(m_current)->GetItem( 0, le.m_item );
|
||||
le.m_itemIndex = current;
|
||||
if ( current != (size_t)-1 )
|
||||
GetLine(current)->GetItem( 0, le.m_item );
|
||||
le.m_code = event.GetKeyCode();
|
||||
le.SetEventObject( parent );
|
||||
if (parent->GetEventHandler()->ProcessEvent( le ))
|
||||
@@ -2956,7 +3100,7 @@ void wxListMainWindow::OnChar( wxKeyEvent &event )
|
||||
{
|
||||
ReverseHighlight(m_current);
|
||||
}
|
||||
else // normal space press
|
||||
else if ( ShouldSendEventForCurrent() ) // normal space press
|
||||
{
|
||||
SendNotify( m_current, wxEVT_LIST_ITEM_ACTIVATED );
|
||||
}
|
||||
@@ -2969,7 +3113,7 @@ void wxListMainWindow::OnChar( wxKeyEvent &event )
|
||||
|
||||
case WXK_RETURN:
|
||||
case WXK_EXECUTE:
|
||||
if ( event.HasModifiers() )
|
||||
if ( event.HasModifiers() || !ShouldSendEventForCurrent() )
|
||||
{
|
||||
event.Skip();
|
||||
break;
|
||||
@@ -3078,6 +3222,7 @@ void wxListMainWindow::OnSetFocus( wxFocusEvent &WXUNUSED(event) )
|
||||
{
|
||||
m_hasFocus = true;
|
||||
|
||||
UpdateCurrent();
|
||||
RefreshSelected();
|
||||
}
|
||||
}
|
||||
@@ -3621,17 +3766,7 @@ int wxListMainWindow::GetSelectedItemCount() const
|
||||
if ( IsVirtual() )
|
||||
return m_selStore.GetSelectedCount();
|
||||
|
||||
// TODO: we probably should maintain the number of items selected even for
|
||||
// non virtual controls as enumerating all lines is really slow...
|
||||
size_t countSel = 0;
|
||||
size_t count = GetItemCount();
|
||||
for ( size_t line = 0; line < count; line++ )
|
||||
{
|
||||
if ( GetLine(line)->IsHighlighted() )
|
||||
countSel++;
|
||||
}
|
||||
|
||||
return countSel;
|
||||
return m_selCount;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -4036,9 +4171,6 @@ void wxListMainWindow::RecalculatePositions(bool noRefresh)
|
||||
|
||||
if ( !noRefresh )
|
||||
{
|
||||
// FIXME: why should we call it from here?
|
||||
UpdateCurrent();
|
||||
|
||||
RefreshAll();
|
||||
}
|
||||
}
|
||||
@@ -4059,7 +4191,13 @@ void wxListMainWindow::RefreshAll()
|
||||
void wxListMainWindow::UpdateCurrent()
|
||||
{
|
||||
if ( !HasCurrent() && !IsEmpty() )
|
||||
ChangeCurrent(0);
|
||||
{
|
||||
// Initialise m_current to the first item without sending any
|
||||
// wxEVT_LIST_ITEM_FOCUSED event (typicaly when the control gains focus)
|
||||
// and this is to allow changing the focused item using the arrow keys.
|
||||
// which is the behaviour found in the wxMSW port.
|
||||
ChangeCurrentWithoutEvent(0);
|
||||
}
|
||||
}
|
||||
|
||||
long wxListMainWindow::GetNextItem( long item,
|
||||
@@ -4248,6 +4386,10 @@ void wxListMainWindow::DoDeleteAllItems()
|
||||
m_countVirt = 0;
|
||||
m_selStore.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_selCount = 0;
|
||||
}
|
||||
|
||||
if ( InReportView() )
|
||||
ResetVisibleLinesRange();
|
||||
|
Reference in New Issue
Block a user