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:
@@ -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()) )
|
||||
|
Reference in New Issue
Block a user