Merge branch 'dvc-var-height'

Optimize generic wxDataViewCtrl performance with variable line heights.

Closes https://github.com/wxWidgets/wxWidgets/pull/1053
This commit is contained in:
Vadim Zeitlin
2019-01-16 01:25:06 +01:00
28 changed files with 1198 additions and 141 deletions

View File

@@ -53,6 +53,7 @@
#include "wx/stopwatch.h"
#include "wx/weakref.h"
#include "wx/generic/private/markuptext.h"
#include "wx/generic/private/rowheightcache.h"
#include "wx/generic/private/widthcalc.h"
#if wxUSE_ACCESSIBILITY
#include "wx/private/markupparser.h"
@@ -710,6 +711,9 @@ public:
bool Cleared();
void Resort()
{
if ( m_rowHeightCache )
m_rowHeightCache->Clear();
if (!IsVirtualList())
{
m_root->Resort(this);
@@ -822,6 +826,7 @@ public:
int GetLineStart( unsigned int row ) const; // row * m_lineHeight in fixed mode
int GetLineHeight( unsigned int row ) const; // m_lineHeight in fixed mode
int GetLineAt( unsigned int y ) const; // y / m_lineHeight in fixed mode
int QueryAndCacheLineHeight(unsigned int row, wxDataViewItem item) const;
void SetRowHeight( int lineHeight ) { m_lineHeight = lineHeight; }
int GetRowHeight() const { return m_lineHeight; }
@@ -911,6 +916,7 @@ private:
bool m_hasFocus;
bool m_useCellFocus;
bool m_currentColSetByKeyboard;
HeightCache *m_rowHeightCache;
#if wxUSE_DRAG_AND_DROP
int m_dragCount;
@@ -1947,6 +1953,14 @@ wxDataViewMainWindow::wxDataViewMainWindow( wxDataViewCtrl *parent, wxWindowID i
m_useCellFocus = false;
m_currentRow = (unsigned)-1;
m_lineHeight = GetDefaultRowHeight();
if (GetOwner()->HasFlag(wxDV_VARIABLE_LINE_HEIGHT))
{
m_rowHeightCache = new HeightCache();
}
else
{
m_rowHeightCache = NULL;
}
#if wxUSE_DRAG_AND_DROP
m_dragCount = 0;
@@ -1983,6 +1997,7 @@ wxDataViewMainWindow::~wxDataViewMainWindow()
{
DestroyTree();
delete m_renameTimer;
delete m_rowHeightCache;
}
@@ -2740,6 +2755,12 @@ bool wxDataViewMainWindow::ItemAdded(const wxDataViewItem & parent, const wxData
}
else
{
if ( m_rowHeightCache )
{
// specific position (row) is unclear, so clear whole height cache
m_rowHeightCache->Clear();
}
wxDataViewTreeNode *parentNode = FindNode(parent);
if ( !parentNode )
@@ -2881,6 +2902,9 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
return true;
}
if ( m_rowHeightCache )
m_rowHeightCache->Remove(GetRowByItem(parent) + itemPosInNode);
// Delete the item from wxDataViewTreeNode representation:
const int itemsDeleted = 1 + itemNode->GetSubTreeCount();
@@ -2945,6 +2969,9 @@ bool wxDataViewMainWindow::DoItemChanged(const wxDataViewItem & item, int view_c
{
if ( !IsVirtualList() )
{
if ( m_rowHeightCache )
m_rowHeightCache->Remove(GetRowByItem(item));
// Move this node to its new correct place after it was updated.
//
// In principle, we could skip the call to PutInSortOrder() if the modified
@@ -2995,6 +3022,9 @@ bool wxDataViewMainWindow::Cleared()
m_selection.Clear();
m_currentRow = (unsigned)-1;
if ( m_rowHeightCache )
m_rowHeightCache->Clear();
if (GetModel())
{
BuildTree( GetModel() );
@@ -3322,147 +3352,132 @@ wxRect wxDataViewMainWindow::GetLinesRect( unsigned int rowFrom, unsigned int ro
int wxDataViewMainWindow::GetLineStart( unsigned int row ) const
{
const wxDataViewModel *model = GetModel();
// check for the easy case first
if ( !m_rowHeightCache || !GetOwner()->HasFlag(wxDV_VARIABLE_LINE_HEIGHT) )
return row * m_lineHeight;
if (GetOwner()->GetWindowStyle() & wxDV_VARIABLE_LINE_HEIGHT)
int start = 0;
if ( m_rowHeightCache->GetLineStart(row, start) )
return start;
unsigned int r;
for (r = 0; r < row; r++)
{
// TODO make more efficient
int start = 0;
unsigned int r;
for (r = 0; r < row; r++)
int height = 0;
if ( !m_rowHeightCache->GetLineHeight(r, height) )
{
const wxDataViewTreeNode* node = GetTreeNodeByRow(r);
if (!node) return start;
// row height not in cache -> get it from the renderer...
wxDataViewItem item = GetItemByRow(r);
if (!item)
break;
wxDataViewItem item = node->GetItem();
unsigned int cols = GetOwner()->GetColumnCount();
unsigned int col;
int height = m_lineHeight;
for (col = 0; col < cols; col++)
{
const wxDataViewColumn *column = GetOwner()->GetColumn(col);
if (column->IsHidden())
continue; // skip it!
if ((col != 0) &&
model->IsContainer(item) &&
!model->HasContainerColumns(item))
continue; // skip it!
wxDataViewRenderer *renderer =
const_cast<wxDataViewRenderer*>(column->GetRenderer());
renderer->PrepareForItem(model, item, column->GetModelColumn());
height = wxMax( height, renderer->GetSize().y );
}
start += height;
height = QueryAndCacheLineHeight(r, item);
}
return start;
}
else
{
return row * m_lineHeight;
start += height;
}
return start;
}
int wxDataViewMainWindow::GetLineAt( unsigned int y ) const
{
const wxDataViewModel *model = GetModel();
// check for the easy case first
if ( !GetOwner()->HasFlag(wxDV_VARIABLE_LINE_HEIGHT) )
if ( !m_rowHeightCache || !GetOwner()->HasFlag(wxDV_VARIABLE_LINE_HEIGHT) )
return y / m_lineHeight;
// TODO make more efficient
unsigned int row = 0;
if ( m_rowHeightCache->GetLineAt(y, row) )
return row;
// OnPaint asks GetLineAt for the very last y position and this is always
// below the last item (--> an invalid item). To prevent iterating over all
// items, check if y is below the last row.
// Because this is done very often (for each repaint) its worth to handle
// this special case separately.
int height = 0;
int start = 0;
unsigned int rowCount = GetRowCount();
if (rowCount == 0 ||
(m_rowHeightCache->GetLineInfo(rowCount - 1, start, height) &&
y >= static_cast<unsigned int>(start + height)))
{
return rowCount;
}
// sum all item heights until y is reached
unsigned int yy = 0;
for (;;)
{
const wxDataViewTreeNode* node = GetTreeNodeByRow(row);
if (!node)
height = 0;
if ( !m_rowHeightCache->GetLineHeight(row, height) )
{
// not really correct...
return row + ((y-yy) / m_lineHeight);
}
// row height not in cache -> get it from the renderer...
wxDataViewItem item = GetItemByRow(row);
if ( !item )
{
wxASSERT(row >= GetRowCount());
break;
}
wxDataViewItem item = node->GetItem();
unsigned int cols = GetOwner()->GetColumnCount();
unsigned int col;
int height = m_lineHeight;
for (col = 0; col < cols; col++)
{
const wxDataViewColumn *column = GetOwner()->GetColumn(col);
if (column->IsHidden())
continue; // skip it!
if ((col != 0) &&
model->IsContainer(item) &&
!model->HasContainerColumns(item))
continue; // skip it!
wxDataViewRenderer *renderer =
const_cast<wxDataViewRenderer*>(column->GetRenderer());
renderer->PrepareForItem(model, item, column->GetModelColumn());
height = wxMax( height, renderer->GetSize().y );
height = QueryAndCacheLineHeight(row, item);
}
yy += height;
if (y < yy)
return row;
break;
row++;
}
return row;
}
int wxDataViewMainWindow::GetLineHeight( unsigned int row ) const
{
const wxDataViewModel *model = GetModel();
if (GetOwner()->GetWindowStyle() & wxDV_VARIABLE_LINE_HEIGHT)
{
wxASSERT( !IsVirtualList() );
const wxDataViewTreeNode* node = GetTreeNodeByRow(row);
// wxASSERT( node );
if (!node) return m_lineHeight;
wxDataViewItem item = node->GetItem();
int height = m_lineHeight;
unsigned int cols = GetOwner()->GetColumnCount();
unsigned int col;
for (col = 0; col < cols; col++)
{
const wxDataViewColumn *column = GetOwner()->GetColumn(col);
if (column->IsHidden())
continue; // skip it!
if ((col != 0) &&
model->IsContainer(item) &&
!model->HasContainerColumns(item))
continue; // skip it!
wxDataViewRenderer *renderer =
const_cast<wxDataViewRenderer*>(column->GetRenderer());
renderer->PrepareForItem(model, item, column->GetModelColumn());
height = wxMax( height, renderer->GetSize().y );
}
return height;
}
else
{
// check for the easy case first
if ( !m_rowHeightCache || !GetOwner()->HasFlag(wxDV_VARIABLE_LINE_HEIGHT) )
return m_lineHeight;
int height = 0;
if ( m_rowHeightCache->GetLineHeight(row, height) )
return height;
wxDataViewItem item = GetItemByRow(row);
if ( !item )
return m_lineHeight;
height = QueryAndCacheLineHeight(row, item);
return height;
}
int wxDataViewMainWindow::QueryAndCacheLineHeight(unsigned int row, wxDataViewItem item) const
{
const wxDataViewModel *model = GetModel();
int height = m_lineHeight;
unsigned int cols = GetOwner()->GetColumnCount();
unsigned int col;
for (col = 0; col < cols; col++)
{
const wxDataViewColumn *column = GetOwner()->GetColumn(col);
if (column->IsHidden())
continue; // skip it!
if ((col != 0) &&
model->IsContainer(item) &&
!model->HasContainerColumns(item))
continue; // skip it!
wxDataViewRenderer *renderer =
const_cast<wxDataViewRenderer*>(column->GetRenderer());
renderer->PrepareForItem(model, item, column->GetModelColumn());
height = wxMax(height, renderer->GetSize().y);
}
// ... and store the height in the cache
m_rowHeightCache->Put(row, height);
return height;
}
@@ -3603,6 +3618,13 @@ void wxDataViewMainWindow::Expand( unsigned int row )
if (!node->HasChildren())
return;
if ( m_rowHeightCache )
{
// Expand makes new rows visible thus we invalidates all following
// rows in the height cache
m_rowHeightCache->Remove(row);
}
if (!node->IsOpen())
{
if ( !SendExpanderEvent(wxEVT_DATAVIEW_ITEM_EXPANDING, node->GetItem()) )
@@ -3652,6 +3674,13 @@ void wxDataViewMainWindow::Collapse(unsigned int row)
if (!node->HasChildren())
return;
if ( m_rowHeightCache )
{
// Collapse hides rows thus we invalidates all following
// rows in the height cache
m_rowHeightCache->Remove(row);
}
if (node->IsOpen())
{
if ( !SendExpanderEvent(wxEVT_DATAVIEW_ITEM_COLLAPSING,node->GetItem()) )