Use wxSelectionStore in wxDataViewCtrl generic implementation.

This makes the code (slightly) shorter and more clear and is more efficient as
selecting all items in wxDataViewCtrl is now a O(1) operation instead of being
O(N), where N is the number of items -- and the latter could take quite a long
time (and consume non-negligible amount of memory) for large N.

Increase the size of the virtual list control from 1000 to 10000000 in the
sample to show this.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@77905 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2014-09-27 20:46:25 +00:00
parent adf8f9d0cd
commit 36a5983f64
3 changed files with 99 additions and 173 deletions

View File

@@ -104,6 +104,7 @@ wxMSW:
- Allow using sizers for laying out wxMDIClientWindow (Artur Wieczorek). - Allow using sizers for laying out wxMDIClientWindow (Artur Wieczorek).
- Fix updating wxSlider background when its parent background changes. - Fix updating wxSlider background when its parent background changes.
- Implement wxListBox::EnsureVisible() (RIVDSL). - Implement wxListBox::EnsureVisible() (RIVDSL).
- Drastically improve efficiency of selecting all items in wxDataViewCtrl.
wxOSX/Cocoa: wxOSX/Cocoa:

View File

@@ -327,7 +327,7 @@ static int my_sort( int *v1, int *v2 )
return *v1-*v2; return *v1-*v2;
} }
#define INITIAL_NUMBER_OF_ITEMS 10000 #define INITIAL_NUMBER_OF_ITEMS 10000000
MyListModel::MyListModel() : MyListModel::MyListModel() :
wxDataViewVirtualListModel( INITIAL_NUMBER_OF_ITEMS ) wxDataViewVirtualListModel( INITIAL_NUMBER_OF_ITEMS )

View File

@@ -46,6 +46,7 @@
#include "wx/imaglist.h" #include "wx/imaglist.h"
#include "wx/headerctrl.h" #include "wx/headerctrl.h"
#include "wx/dnd.h" #include "wx/dnd.h"
#include "wx/selstore.h"
#include "wx/stopwatch.h" #include "wx/stopwatch.h"
#include "wx/weakref.h" #include "wx/weakref.h"
@@ -772,9 +773,8 @@ public:
unsigned int GetLastVisibleRow(); unsigned int GetLastVisibleRow();
unsigned int GetRowCount() const; unsigned int GetRowCount() const;
const wxDataViewSelection& GetSelections() const { return m_selection; } const wxSelectionStore& GetSelections() const { return m_selection; }
void SetSelections( const wxDataViewSelection & sel ) void ClearSelection() { m_selection.SelectRange(0, GetRowCount() - 1, false); }
{ m_selection = sel; UpdateDisplay(); }
void Select( const wxArrayInt& aSelections ); void Select( const wxArrayInt& aSelections );
void SelectAllRows( bool on ); void SelectAllRows( bool on );
void SelectRow( unsigned int row, bool on ); void SelectRow( unsigned int row, bool on );
@@ -844,6 +844,13 @@ public:
void FinishEditing(); void FinishEditing();
private: private:
void InvalidateCount() { m_count = -1; }
void UpdateCount(int count)
{
m_count = count;
m_selection.SetItemCount(count);
}
int RecalculateCount() const; int RecalculateCount() const;
// Return false only if the event was vetoed by its handler. // Return false only if the event was vetoed by its handler.
@@ -864,7 +871,7 @@ private:
wxDataViewColumn *m_currentCol; wxDataViewColumn *m_currentCol;
unsigned int m_currentRow; unsigned int m_currentRow;
wxDataViewSelection m_selection; wxSelectionStore m_selection;
wxDataViewRenameTimer *m_renameTimer; wxDataViewRenameTimer *m_renameTimer;
bool m_lastOnSame; bool m_lastOnSame;
@@ -1469,13 +1476,6 @@ void wxDataViewRenameTimer::Notify()
static void BuildTreeHelper( const wxDataViewModel * model, const wxDataViewItem & item, static void BuildTreeHelper( const wxDataViewModel * model, const wxDataViewItem & item,
wxDataViewTreeNode * node); wxDataViewTreeNode * node);
int LINKAGEMODE wxDataViewSelectionCmp( unsigned int row1, unsigned int row2 )
{
if (row1 > row2) return 1;
if (row1 == row2) return 0;
return -1;
}
IMPLEMENT_ABSTRACT_CLASS(wxDataViewMainWindow, wxWindow) IMPLEMENT_ABSTRACT_CLASS(wxDataViewMainWindow, wxWindow)
BEGIN_EVENT_TABLE(wxDataViewMainWindow,wxWindow) BEGIN_EVENT_TABLE(wxDataViewMainWindow,wxWindow)
@@ -1489,9 +1489,7 @@ END_EVENT_TABLE()
wxDataViewMainWindow::wxDataViewMainWindow( wxDataViewCtrl *parent, wxWindowID id, wxDataViewMainWindow::wxDataViewMainWindow( wxDataViewCtrl *parent, wxWindowID id,
const wxPoint &pos, const wxSize &size, const wxString &name ) : const wxPoint &pos, const wxSize &size, const wxString &name ) :
wxWindow( parent, id, pos, size, wxWANTS_CHARS|wxBORDER_NONE, name ), wxWindow( parent, id, pos, size, wxWANTS_CHARS|wxBORDER_NONE, name )
m_selection( wxDataViewSelectionCmp )
{ {
SetOwner( parent ); SetOwner( parent );
@@ -1950,7 +1948,7 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
// redraw the background for the items which are selected/current // redraw the background for the items which are selected/current
for (unsigned int item = item_start; item < item_last; item++) for (unsigned int item = item_start; item < item_last; item++)
{ {
bool selected = m_selection.Index( item ) != wxNOT_FOUND; bool selected = m_selection.IsSelected(item);
if (selected || item == m_currentRow) if (selected || item == m_currentRow)
{ {
@@ -2104,7 +2102,7 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
cell->PrepareForItem(model, dataitem, col->GetModelColumn()); cell->PrepareForItem(model, dataitem, col->GetModelColumn());
// draw the background // draw the background
bool selected = m_selection.Index( item ) != wxNOT_FOUND; bool selected = m_selection.IsSelected(item);
if ( !selected ) if ( !selected )
DrawCellBackground( cell, dc, cell_rect ); DrawCellBackground( cell, dc, cell_rect );
@@ -2306,7 +2304,7 @@ bool wxDataViewMainWindow::ItemAdded(const wxDataViewItem & parent, const wxData
{ {
wxDataViewVirtualListModel *list_model = wxDataViewVirtualListModel *list_model =
(wxDataViewVirtualListModel*) GetModel(); (wxDataViewVirtualListModel*) GetModel();
m_count = list_model->GetCount(); UpdateCount(list_model->GetCount());
} }
else else
{ {
@@ -2371,7 +2369,7 @@ bool wxDataViewMainWindow::ItemAdded(const wxDataViewItem & parent, const wxData
parentNode->ChangeSubTreeCount(+1); parentNode->ChangeSubTreeCount(+1);
parentNode->InsertChild(itemNode, nodePos); parentNode->InsertChild(itemNode, nodePos);
m_count = -1; InvalidateCount();
} }
GetOwner()->InvalidateColBestWidths(); GetOwner()->InvalidateColBestWidths();
@@ -2387,27 +2385,9 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
{ {
wxDataViewVirtualListModel *list_model = wxDataViewVirtualListModel *list_model =
(wxDataViewVirtualListModel*) GetModel(); (wxDataViewVirtualListModel*) GetModel();
m_count = list_model->GetCount(); UpdateCount(list_model->GetCount());
if ( !m_selection.empty() )
{
const int row = GetRowByItem(item);
int rowIndexInSelection = wxNOT_FOUND;
const size_t selCount = m_selection.size();
for ( size_t i = 0; i < selCount; i++ )
{
if ( m_selection[i] == (unsigned)row )
rowIndexInSelection = i;
else if ( m_selection[i] > (unsigned)row )
m_selection[i]--;
}
if ( rowIndexInSelection != wxNOT_FOUND )
m_selection.RemoveAt(rowIndexInSelection);
}
m_selection.OnItemDelete(GetRowByItem(item));
} }
else // general case else // general case
{ {
@@ -2460,7 +2440,7 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
parentNode->ChangeSubTreeCount(-itemsDeleted); parentNode->ChangeSubTreeCount(-itemsDeleted);
// Make the row number invalid and get a new valid one when user call GetRowCount // Make the row number invalid and get a new valid one when user call GetRowCount
m_count = -1; InvalidateCount();
// If this was the last child to be removed, it's possible the parent // If this was the last child to be removed, it's possible the parent
// node became a leaf. Let's ask the model about it. // node became a leaf. Let's ask the model about it.
@@ -2478,7 +2458,7 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
} }
// Update selection by removing 'item' and its entire children tree from the selection. // Update selection by removing 'item' and its entire children tree from the selection.
if ( !m_selection.empty() ) if ( !m_selection.IsEmpty() )
{ {
// we can't call GetRowByItem() on 'item', as it's already deleted, so compute it from // we can't call GetRowByItem() on 'item', as it's already deleted, so compute it from
// the parent ('parentNode') and position in its list of children // the parent ('parentNode') and position in its list of children
@@ -2498,20 +2478,7 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
1; 1;
} }
wxDataViewSelection newsel(wxDataViewSelectionCmp); m_selection.OnItemsDeleted(itemRow, itemsDeleted);
const size_t numSelections = m_selection.size();
for ( size_t i = 0; i < numSelections; ++i )
{
const int s = m_selection[i];
if ( s < itemRow )
newsel.push_back(s);
else if ( s >= itemRow + itemsDeleted )
newsel.push_back(s - itemsDeleted);
// else: deleted item, remove from selection
}
m_selection = newsel;
} }
} }
@@ -2731,7 +2698,7 @@ unsigned int wxDataViewMainWindow::GetRowCount() const
{ {
wxDataViewMainWindow* const wxDataViewMainWindow* const
self = const_cast<wxDataViewMainWindow*>(this); self = const_cast<wxDataViewMainWindow*>(this);
self->m_count = RecalculateCount(); self->UpdateCount(RecalculateCount());
self->UpdateDisplay(); self->UpdateDisplay();
} }
return m_count; return m_count;
@@ -2751,56 +2718,38 @@ void wxDataViewMainWindow::SelectAllRows( bool on )
if (on) if (on)
{ {
m_selection.Clear(); m_selection.SelectRange(0, GetRowCount() - 1);
for (unsigned int i = 0; i < GetRowCount(); i++)
m_selection.Add( i );
Refresh(); Refresh();
} }
else else if (!m_selection.IsEmpty())
{ {
unsigned int first_visible = GetFirstVisibleRow(); for (unsigned i = GetFirstVisibleRow(); i <= GetLastVisibleRow(); i++)
unsigned int last_visible = GetLastVisibleRow();
unsigned int i;
for (i = 0; i < m_selection.GetCount(); i++)
{ {
unsigned int row = m_selection[i]; if (m_selection.IsSelected(i))
if ((row >= first_visible) && (row <= last_visible)) RefreshRow(i);
RefreshRow( row );
} }
m_selection.Clear(); ClearSelection();
} }
} }
void wxDataViewMainWindow::SelectRow( unsigned int row, bool on ) void wxDataViewMainWindow::SelectRow( unsigned int row, bool on )
{ {
if (m_selection.Index( row ) == wxNOT_FOUND) if ( m_selection.SelectItem(row, on) )
{ RefreshRow(row);
if (on)
{
m_selection.Add( row );
RefreshRow( row );
}
}
else
{
if (!on)
{
m_selection.Remove( row );
RefreshRow( row );
}
}
} }
void wxDataViewMainWindow::SelectRows( unsigned int from, unsigned int to ) void wxDataViewMainWindow::SelectRows( unsigned int from, unsigned int to )
{ {
for (unsigned int i = from; i <= to; i++) wxArrayInt changed;
if ( m_selection.SelectRange(from, to, true, &changed) )
{ {
if (m_selection.Index( i ) == wxNOT_FOUND) for (unsigned i = 0; i < changed.size(); i++)
{ RefreshRow(changed[i]);
m_selection.Add( i ); }
} else // Selection of too many rows has changed.
{
RefreshRows( from, to );
} }
RefreshRows( from, to );
} }
void wxDataViewMainWindow::Select( const wxArrayInt& aSelections ) void wxDataViewMainWindow::Select( const wxArrayInt& aSelections )
@@ -2809,23 +2758,20 @@ void wxDataViewMainWindow::Select( const wxArrayInt& aSelections )
{ {
int n = aSelections[i]; int n = aSelections[i];
m_selection.Add( n ); if ( m_selection.SelectItem(n) )
RefreshRow( n ); RefreshRow( n );
} }
} }
void wxDataViewMainWindow::ReverseRowSelection( unsigned int row ) void wxDataViewMainWindow::ReverseRowSelection( unsigned int row )
{ {
if (m_selection.Index( row ) == wxNOT_FOUND) m_selection.SelectItem(row, !m_selection.IsSelected(row));
m_selection.Add( row );
else
m_selection.Remove( row );
RefreshRow( row ); RefreshRow( row );
} }
bool wxDataViewMainWindow::IsRowSelected( unsigned int row ) bool wxDataViewMainWindow::IsRowSelected( unsigned int row )
{ {
return (m_selection.Index( row ) != wxNOT_FOUND); return m_selection.IsSelected(row);
} }
void wxDataViewMainWindow::SendSelectionChangedEvent( const wxDataViewItem& item) void wxDataViewMainWindow::SendSelectionChangedEvent( const wxDataViewItem& item)
@@ -3197,23 +3143,16 @@ void wxDataViewMainWindow::Expand( unsigned int row )
::BuildTreeHelper(GetModel(), node->GetItem(), node); ::BuildTreeHelper(GetModel(), node->GetItem(), node);
} }
// By expanding the node all row indices that are currently in the selection list const unsigned countNewRows = node->GetSubTreeCount();
// and are greater than our node have become invalid. So we have to correct that now.
const unsigned rowAdjustment = node->GetSubTreeCount();
for(unsigned i=0; i<m_selection.size(); ++i)
{
const unsigned testRow = m_selection[i];
// all rows above us are not affected, so skip them
if(testRow <= row)
continue;
m_selection[i] += rowAdjustment; // Shift all stored indices after this row by the number of newly added
} // rows.
m_selection.OnItemsInserted(row + 1, countNewRows);
if ( m_currentRow > row )
ChangeCurrentRow(m_currentRow + countNewRows);
if(m_currentRow > row) if ( m_count != -1 )
ChangeCurrentRow(m_currentRow + rowAdjustment); m_count += countNewRows;
m_count = -1;
UpdateDisplay(); UpdateDisplay();
// Send the expanded event // Send the expanded event
SendExpanderEvent(wxEVT_DATAVIEW_ITEM_EXPANDED,node->GetItem()); SendExpanderEvent(wxEVT_DATAVIEW_ITEM_EXPANDED,node->GetItem());
@@ -3240,55 +3179,28 @@ void wxDataViewMainWindow::Collapse(unsigned int row)
return; return;
} }
// Find out if there are selected items below the current node. const unsigned countDeletedRows = node->GetSubTreeCount();
bool selectCollapsingRow = false;
const unsigned rowAdjustment = node->GetSubTreeCount(); if ( m_selection.OnItemsDeleted(row + 1, countDeletedRows) )
unsigned maxRowToBeTested = row + rowAdjustment;
for(unsigned i=0; i<m_selection.size(); ++i)
{ {
const unsigned testRow = m_selection[i]; SendSelectionChangedEvent(GetItemByRow(row));
if(testRow > row && testRow <= maxRowToBeTested)
{
selectCollapsingRow = true;
// get out as soon as we have found a node that is selected
break;
}
} }
node->ToggleOpen(); node->ToggleOpen();
// If the node to be closed has selected items the user won't see those any longer. // Adjust the current row if necessary.
// We select the collapsing node in this case. if ( m_currentRow > row )
if(selectCollapsingRow)
{ {
SelectAllRows(false); // If the current row was among the collapsed items, make the
ChangeCurrentRow(row); // parent itself current.
SelectRow(row, true); if ( m_currentRow <= row + countDeletedRows )
SendSelectionChangedEvent(GetItemByRow(row));
}
else
{
// if there were no selected items below our node we still need to "fix" the
// selection list to adjust for the changing of the row indices.
// We actually do the opposite of what we are doing in Expand().
for(unsigned i=0; i<m_selection.size(); ++i)
{
const unsigned testRow = m_selection[i];
// all rows above us are not affected, so skip them
if(testRow <= row)
continue;
m_selection[i] -= rowAdjustment;
}
// if the "current row" is being collapsed away we change it to the current row ;-)
if(m_currentRow > row && m_currentRow <= maxRowToBeTested)
ChangeCurrentRow(row); ChangeCurrentRow(row);
else if(m_currentRow > row) else // Otherwise just update the index.
ChangeCurrentRow(m_currentRow - rowAdjustment); ChangeCurrentRow(m_currentRow - countDeletedRows);
} }
m_count = -1; if ( m_count != -1 )
m_count -= countDeletedRows;
UpdateDisplay(); UpdateDisplay();
SendExpanderEvent(wxEVT_DATAVIEW_ITEM_COLLAPSED,node->GetItem()); SendExpanderEvent(wxEVT_DATAVIEW_ITEM_COLLAPSED,node->GetItem());
} }
@@ -3564,7 +3476,7 @@ void wxDataViewMainWindow::BuildTree(wxDataViewModel * model)
if (GetModel()->IsVirtualListModel()) if (GetModel()->IsVirtualListModel())
{ {
m_count = -1; InvalidateCount();
return; return;
} }
@@ -3574,7 +3486,7 @@ void wxDataViewMainWindow::BuildTree(wxDataViewModel * model)
wxDataViewItem item; wxDataViewItem item;
SortPrepare(); SortPrepare();
BuildTreeHelper( model, item, m_root); BuildTreeHelper( model, item, m_root);
m_count = -1; InvalidateCount();
} }
void wxDataViewMainWindow::DestroyTree() void wxDataViewMainWindow::DestroyTree()
@@ -3789,16 +3701,21 @@ void wxDataViewMainWindow::OnChar( wxKeyEvent &event )
} }
else else
{ {
if( !m_selection.empty() ) if ( !m_selection.IsEmpty() )
{ {
// Mimic Windows 7 behavior: edit the item that has focus // Mimic Windows 7 behavior: edit the item that has focus
// if it is selected and the first selected item if focus // if it is selected and the first selected item if focus
// is out of selection. // is out of selection.
int sel; unsigned sel;
if ( m_selection.Index(m_currentRow) != wxNOT_FOUND ) if ( m_selection.IsSelected(m_currentRow) )
{
sel = m_currentRow; sel = m_currentRow;
else }
sel = m_selection[0]; else // Focused item is not selected.
{
wxSelectionStore::IterationState cookie;
sel = m_selection.GetFirstSelectedItem(cookie);
}
const wxDataViewItem item = GetItemByRow(sel); const wxDataViewItem item = GetItemByRow(sel);
@@ -3887,7 +3804,12 @@ void wxDataViewMainWindow::OnVerticalNavigation(const wxKeyEvent& event, int del
SelectRows(oldCurrent, newCurrent); SelectRows(oldCurrent, newCurrent);
if (oldCurrent!=newCurrent) if (oldCurrent!=newCurrent)
SendSelectionChangedEvent(GetItemByRow(m_selection[0])); {
wxSelectionStore::IterationState cookie;
const unsigned firstSel = m_selection.GetFirstSelectedItem(cookie);
if ( firstSel != wxSelectionStore::NO_SELECTION )
SendSelectionChangedEvent(GetItemByRow(firstSel));
}
} }
else // !shift else // !shift
{ {
@@ -4211,7 +4133,7 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
// not middle) button clears the existing selection. // not middle) button clears the existing selection.
if (m_owner && (event.LeftDown() || event.RightDown())) if (m_owner && (event.LeftDown() || event.RightDown()))
{ {
if (!GetSelections().empty()) if (!m_selection.IsEmpty())
{ {
m_owner->UnselectAll(); m_owner->UnselectAll();
SendSelectionChangedEvent(wxDataViewItem()); SendSelectionChangedEvent(wxDataViewItem());
@@ -4437,7 +4359,11 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
} }
SelectRows(lineFrom, lineTo); SelectRows(lineFrom, lineTo);
SendSelectionChangedEvent(GetItemByRow(m_selection[0]) );
wxSelectionStore::IterationState cookie;
const unsigned firstSel = m_selection.GetFirstSelectedItem(cookie);
if ( firstSel != wxSelectionStore::NO_SELECTION )
SendSelectionChangedEvent(GetItemByRow(firstSel) );
} }
else // !ctrl, !shift else // !ctrl, !shift
{ {
@@ -5247,18 +5173,20 @@ wxDataViewColumn *wxDataViewCtrl::GetCurrentColumn() const
int wxDataViewCtrl::GetSelectedItemsCount() const int wxDataViewCtrl::GetSelectedItemsCount() const
{ {
return m_clientArea->GetSelections().size(); return m_clientArea->GetSelections().GetSelectedCount();
} }
int wxDataViewCtrl::GetSelections( wxDataViewItemArray & sel ) const int wxDataViewCtrl::GetSelections( wxDataViewItemArray & sel ) const
{ {
sel.Empty(); sel.Empty();
const wxDataViewSelection& selections = m_clientArea->GetSelections(); const wxSelectionStore& selections = m_clientArea->GetSelections();
const size_t len = selections.size(); wxSelectionStore::IterationState cookie;
for ( size_t i = 0; i < len; i++ ) for ( unsigned row = selections.GetFirstSelectedItem(cookie);
row != wxSelectionStore::NO_SELECTION;
row = selections.GetNextSelectedItem(cookie) )
{ {
wxDataViewItem item = m_clientArea->GetItemByRow(selections[i]); wxDataViewItem item = m_clientArea->GetItemByRow(row);
if ( item.IsOk() ) if ( item.IsOk() )
{ {
sel.Add(item); sel.Add(item);
@@ -5274,12 +5202,11 @@ int wxDataViewCtrl::GetSelections( wxDataViewItemArray & sel ) const
void wxDataViewCtrl::SetSelections( const wxDataViewItemArray & sel ) void wxDataViewCtrl::SetSelections( const wxDataViewItemArray & sel )
{ {
wxDataViewSelection selection(wxDataViewSelectionCmp); m_clientArea->ClearSelection();
wxDataViewItem last_parent; wxDataViewItem last_parent;
int len = sel.GetCount(); for ( size_t i = 0; i < sel.size(); i++ )
for( int i = 0; i < len; i ++ )
{ {
wxDataViewItem item = sel[i]; wxDataViewItem item = sel[i];
wxDataViewItem parent = GetModel()->GetParent( item ); wxDataViewItem parent = GetModel()->GetParent( item );
@@ -5292,10 +5219,8 @@ void wxDataViewCtrl::SetSelections( const wxDataViewItemArray & sel )
last_parent = parent; last_parent = parent;
int row = m_clientArea->GetRowByItem( item ); int row = m_clientArea->GetRowByItem( item );
if( row >= 0 ) if( row >= 0 )
selection.Add( static_cast<unsigned int>(row) ); m_clientArea->SelectRow(static_cast<unsigned int>(row), true);
} }
m_clientArea->SetSelections( selection );
} }
void wxDataViewCtrl::Select( const wxDataViewItem & item ) void wxDataViewCtrl::Select( const wxDataViewItem & item )