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).
- Fix updating wxSlider background when its parent background changes.
- Implement wxListBox::EnsureVisible() (RIVDSL).
- Drastically improve efficiency of selecting all items in wxDataViewCtrl.
wxOSX/Cocoa:

View File

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

View File

@@ -46,6 +46,7 @@
#include "wx/imaglist.h"
#include "wx/headerctrl.h"
#include "wx/dnd.h"
#include "wx/selstore.h"
#include "wx/stopwatch.h"
#include "wx/weakref.h"
@@ -772,9 +773,8 @@ public:
unsigned int GetLastVisibleRow();
unsigned int GetRowCount() const;
const wxDataViewSelection& GetSelections() const { return m_selection; }
void SetSelections( const wxDataViewSelection & sel )
{ m_selection = sel; UpdateDisplay(); }
const wxSelectionStore& GetSelections() const { return m_selection; }
void ClearSelection() { m_selection.SelectRange(0, GetRowCount() - 1, false); }
void Select( const wxArrayInt& aSelections );
void SelectAllRows( bool on );
void SelectRow( unsigned int row, bool on );
@@ -844,6 +844,13 @@ public:
void FinishEditing();
private:
void InvalidateCount() { m_count = -1; }
void UpdateCount(int count)
{
m_count = count;
m_selection.SetItemCount(count);
}
int RecalculateCount() const;
// Return false only if the event was vetoed by its handler.
@@ -864,7 +871,7 @@ private:
wxDataViewColumn *m_currentCol;
unsigned int m_currentRow;
wxDataViewSelection m_selection;
wxSelectionStore m_selection;
wxDataViewRenameTimer *m_renameTimer;
bool m_lastOnSame;
@@ -1469,13 +1476,6 @@ void wxDataViewRenameTimer::Notify()
static void BuildTreeHelper( const wxDataViewModel * model, const wxDataViewItem & item,
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)
BEGIN_EVENT_TABLE(wxDataViewMainWindow,wxWindow)
@@ -1489,9 +1489,7 @@ END_EVENT_TABLE()
wxDataViewMainWindow::wxDataViewMainWindow( wxDataViewCtrl *parent, wxWindowID id,
const wxPoint &pos, const wxSize &size, const wxString &name ) :
wxWindow( parent, id, pos, size, wxWANTS_CHARS|wxBORDER_NONE, name ),
m_selection( wxDataViewSelectionCmp )
wxWindow( parent, id, pos, size, wxWANTS_CHARS|wxBORDER_NONE, name )
{
SetOwner( parent );
@@ -1950,7 +1948,7 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
// redraw the background for the items which are selected/current
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)
{
@@ -2104,7 +2102,7 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
cell->PrepareForItem(model, dataitem, col->GetModelColumn());
// draw the background
bool selected = m_selection.Index( item ) != wxNOT_FOUND;
bool selected = m_selection.IsSelected(item);
if ( !selected )
DrawCellBackground( cell, dc, cell_rect );
@@ -2306,7 +2304,7 @@ bool wxDataViewMainWindow::ItemAdded(const wxDataViewItem & parent, const wxData
{
wxDataViewVirtualListModel *list_model =
(wxDataViewVirtualListModel*) GetModel();
m_count = list_model->GetCount();
UpdateCount(list_model->GetCount());
}
else
{
@@ -2371,7 +2369,7 @@ bool wxDataViewMainWindow::ItemAdded(const wxDataViewItem & parent, const wxData
parentNode->ChangeSubTreeCount(+1);
parentNode->InsertChild(itemNode, nodePos);
m_count = -1;
InvalidateCount();
}
GetOwner()->InvalidateColBestWidths();
@@ -2387,27 +2385,9 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
{
wxDataViewVirtualListModel *list_model =
(wxDataViewVirtualListModel*) GetModel();
m_count = 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);
}
UpdateCount(list_model->GetCount());
m_selection.OnItemDelete(GetRowByItem(item));
}
else // general case
{
@@ -2460,7 +2440,7 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
parentNode->ChangeSubTreeCount(-itemsDeleted);
// 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
// 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.
if ( !m_selection.empty() )
if ( !m_selection.IsEmpty() )
{
// 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
@@ -2498,20 +2478,7 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
1;
}
wxDataViewSelection newsel(wxDataViewSelectionCmp);
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;
m_selection.OnItemsDeleted(itemRow, itemsDeleted);
}
}
@@ -2731,7 +2698,7 @@ unsigned int wxDataViewMainWindow::GetRowCount() const
{
wxDataViewMainWindow* const
self = const_cast<wxDataViewMainWindow*>(this);
self->m_count = RecalculateCount();
self->UpdateCount(RecalculateCount());
self->UpdateDisplay();
}
return m_count;
@@ -2751,56 +2718,38 @@ void wxDataViewMainWindow::SelectAllRows( bool on )
if (on)
{
m_selection.Clear();
for (unsigned int i = 0; i < GetRowCount(); i++)
m_selection.Add( i );
m_selection.SelectRange(0, GetRowCount() - 1);
Refresh();
}
else
else if (!m_selection.IsEmpty())
{
unsigned int first_visible = GetFirstVisibleRow();
unsigned int last_visible = GetLastVisibleRow();
unsigned int i;
for (i = 0; i < m_selection.GetCount(); i++)
for (unsigned i = GetFirstVisibleRow(); i <= GetLastVisibleRow(); i++)
{
unsigned int row = m_selection[i];
if ((row >= first_visible) && (row <= last_visible))
RefreshRow( row );
if (m_selection.IsSelected(i))
RefreshRow(i);
}
m_selection.Clear();
ClearSelection();
}
}
void wxDataViewMainWindow::SelectRow( unsigned int row, bool on )
{
if (m_selection.Index( row ) == wxNOT_FOUND)
{
if (on)
{
m_selection.Add( row );
RefreshRow( row );
}
}
else
{
if (!on)
{
m_selection.Remove( row );
RefreshRow( row );
}
}
if ( m_selection.SelectItem(row, on) )
RefreshRow(row);
}
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]);
}
else // Selection of too many rows has changed.
{
m_selection.Add( i );
}
}
RefreshRows( from, to );
}
}
void wxDataViewMainWindow::Select( const wxArrayInt& aSelections )
@@ -2809,23 +2758,20 @@ void wxDataViewMainWindow::Select( const wxArrayInt& aSelections )
{
int n = aSelections[i];
m_selection.Add( n );
if ( m_selection.SelectItem(n) )
RefreshRow( n );
}
}
void wxDataViewMainWindow::ReverseRowSelection( unsigned int row )
{
if (m_selection.Index( row ) == wxNOT_FOUND)
m_selection.Add( row );
else
m_selection.Remove( row );
m_selection.SelectItem(row, !m_selection.IsSelected(row));
RefreshRow( 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)
@@ -3197,23 +3143,16 @@ void wxDataViewMainWindow::Expand( unsigned int row )
::BuildTreeHelper(GetModel(), node->GetItem(), node);
}
// By expanding the node all row indices that are currently in the selection list
// 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;
const unsigned countNewRows = node->GetSubTreeCount();
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)
ChangeCurrentRow(m_currentRow + rowAdjustment);
m_count = -1;
if ( m_count != -1 )
m_count += countNewRows;
UpdateDisplay();
// Send the expanded event
SendExpanderEvent(wxEVT_DATAVIEW_ITEM_EXPANDED,node->GetItem());
@@ -3240,55 +3179,28 @@ void wxDataViewMainWindow::Collapse(unsigned int row)
return;
}
// Find out if there are selected items below the current node.
bool selectCollapsingRow = false;
const unsigned rowAdjustment = node->GetSubTreeCount();
unsigned maxRowToBeTested = row + rowAdjustment;
for(unsigned i=0; i<m_selection.size(); ++i)
const unsigned countDeletedRows = node->GetSubTreeCount();
if ( m_selection.OnItemsDeleted(row + 1, countDeletedRows) )
{
const unsigned testRow = m_selection[i];
if(testRow > row && testRow <= maxRowToBeTested)
{
selectCollapsingRow = true;
// get out as soon as we have found a node that is selected
break;
}
SendSelectionChangedEvent(GetItemByRow(row));
}
node->ToggleOpen();
// If the node to be closed has selected items the user won't see those any longer.
// We select the collapsing node in this case.
if(selectCollapsingRow)
// Adjust the current row if necessary.
if ( m_currentRow > row )
{
SelectAllRows(false);
// If the current row was among the collapsed items, make the
// parent itself current.
if ( m_currentRow <= row + countDeletedRows )
ChangeCurrentRow(row);
SelectRow(row, true);
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;
else // Otherwise just update the index.
ChangeCurrentRow(m_currentRow - countDeletedRows);
}
// if the "current row" is being collapsed away we change it to the current row ;-)
if(m_currentRow > row && m_currentRow <= maxRowToBeTested)
ChangeCurrentRow(row);
else if(m_currentRow > row)
ChangeCurrentRow(m_currentRow - rowAdjustment);
}
m_count = -1;
if ( m_count != -1 )
m_count -= countDeletedRows;
UpdateDisplay();
SendExpanderEvent(wxEVT_DATAVIEW_ITEM_COLLAPSED,node->GetItem());
}
@@ -3564,7 +3476,7 @@ void wxDataViewMainWindow::BuildTree(wxDataViewModel * model)
if (GetModel()->IsVirtualListModel())
{
m_count = -1;
InvalidateCount();
return;
}
@@ -3574,7 +3486,7 @@ void wxDataViewMainWindow::BuildTree(wxDataViewModel * model)
wxDataViewItem item;
SortPrepare();
BuildTreeHelper( model, item, m_root);
m_count = -1;
InvalidateCount();
}
void wxDataViewMainWindow::DestroyTree()
@@ -3789,16 +3701,21 @@ void wxDataViewMainWindow::OnChar( wxKeyEvent &event )
}
else
{
if( !m_selection.empty() )
if ( !m_selection.IsEmpty() )
{
// Mimic Windows 7 behavior: edit the item that has focus
// if it is selected and the first selected item if focus
// is out of selection.
int sel;
if ( m_selection.Index(m_currentRow) != wxNOT_FOUND )
unsigned sel;
if ( m_selection.IsSelected(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);
@@ -3887,7 +3804,12 @@ void wxDataViewMainWindow::OnVerticalNavigation(const wxKeyEvent& event, int del
SelectRows(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
{
@@ -4211,7 +4133,7 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
// not middle) button clears the existing selection.
if (m_owner && (event.LeftDown() || event.RightDown()))
{
if (!GetSelections().empty())
if (!m_selection.IsEmpty())
{
m_owner->UnselectAll();
SendSelectionChangedEvent(wxDataViewItem());
@@ -4437,7 +4359,11 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
}
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
{
@@ -5247,18 +5173,20 @@ wxDataViewColumn *wxDataViewCtrl::GetCurrentColumn() const
int wxDataViewCtrl::GetSelectedItemsCount() const
{
return m_clientArea->GetSelections().size();
return m_clientArea->GetSelections().GetSelectedCount();
}
int wxDataViewCtrl::GetSelections( wxDataViewItemArray & sel ) const
{
sel.Empty();
const wxDataViewSelection& selections = m_clientArea->GetSelections();
const wxSelectionStore& selections = m_clientArea->GetSelections();
const size_t len = selections.size();
for ( size_t i = 0; i < len; i++ )
wxSelectionStore::IterationState cookie;
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() )
{
sel.Add(item);
@@ -5274,12 +5202,11 @@ int wxDataViewCtrl::GetSelections( wxDataViewItemArray & sel ) const
void wxDataViewCtrl::SetSelections( const wxDataViewItemArray & sel )
{
wxDataViewSelection selection(wxDataViewSelectionCmp);
m_clientArea->ClearSelection();
wxDataViewItem last_parent;
int len = sel.GetCount();
for( int i = 0; i < len; i ++ )
for ( size_t i = 0; i < sel.size(); i++ )
{
wxDataViewItem item = sel[i];
wxDataViewItem parent = GetModel()->GetParent( item );
@@ -5292,10 +5219,8 @@ void wxDataViewCtrl::SetSelections( const wxDataViewItemArray & sel )
last_parent = parent;
int row = m_clientArea->GetRowByItem( item );
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 )