From: Václav Slavík Date: Sat, 30 Oct 2010 15:57:59 +0000 (+0000) Subject: For large dataview controls, don't use all items to calculate best column width. X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/0645bd388cbd7c046c341adfcbe6a17ab2e3f77c?ds=sidebyside For large dataview controls, don't use all items to calculate best column width. Instead, use just top and bottom N/2 items for some large enough value of N. N is determined dynamically so that column best width calculation doesn't take more than 50ms. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@65951 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/src/generic/datavgen.cpp b/src/generic/datavgen.cpp index 02555daa57..38ed8b0e8d 100644 --- a/src/generic/datavgen.cpp +++ b/src/generic/datavgen.cpp @@ -48,6 +48,7 @@ #include "wx/imaglist.h" #include "wx/headerctrl.h" #include "wx/dnd.h" +#include "wx/stopwatch.h" //----------------------------------------------------------------------------- // classes @@ -4121,35 +4122,136 @@ unsigned int wxDataViewCtrl::GetBestColumnWidth(int idx) const if ( m_colsBestWidths[idx] != 0 ) return m_colsBestWidths[idx]; - const unsigned count = m_clientArea->GetRowCount(); + const int count = m_clientArea->GetRowCount(); wxDataViewColumn *column = GetColumn(idx); wxDataViewRenderer *renderer = const_cast(column->GetRenderer()); - int max_width = 0; + class MaxWidthCalculator + { + public: + MaxWidthCalculator(wxDataViewMainWindow *clientArea, + wxDataViewRenderer *renderer, + const wxDataViewModel *model, + unsigned column) + : m_width(0), + m_clientArea(clientArea), + m_renderer(renderer), + m_model(model), + m_column(column) + { + } + + void UpdateWithWidth(int width) + { + m_width = wxMax(m_width, width); + } + + void UpdateWithRow(int row) + { + wxDataViewItem item = m_clientArea->GetItemByRow(row); + m_renderer->PrepareForItem(m_model, item, m_column); + m_width = wxMax(m_width, m_renderer->GetSize().x); + } + + int GetMaxWidth() const { return m_width; } + + private: + int m_width; + wxDataViewMainWindow *m_clientArea; + wxDataViewRenderer *m_renderer; + const wxDataViewModel *m_model; + unsigned m_column; + }; + + MaxWidthCalculator calculator(m_clientArea, renderer, + GetModel(), column->GetModelColumn()); if ( m_headerArea ) { - max_width = m_headerArea->GetTextExtent(column->GetTitle()).x; - + int header_width = m_headerArea->GetTextExtent(column->GetTitle()).x; // Labels on native MSW header are indented on both sides - max_width += wxRendererNative::Get().GetHeaderButtonMargin(m_headerArea); + header_width += + wxRendererNative::Get().GetHeaderButtonMargin(m_headerArea); + calculator.UpdateWithWidth(header_width); + } + + // The code below deserves some explanation. For very large controls, we + // simply can't afford to calculate sizes for all items, it takes too + // long. So the best we can do is to check the first and the last N/2 + // items in the control for some sufficiently large N and calculate best + // sizes from that. That can result in the calculated best width being too + // small for some outliers, but it's better to get slightly imperfect + // result than to wait several seconds after every update. To avoid highly + // visible miscalculations, we also include all currently visible items + // no matter what. Finally, the value of N is determined dynamically by + // measuring how much time we spent on the determining item widths so far. + +#if wxUSE_STOPWATCH + int top_part_end = count; + static const long CALC_TIMEOUT = 20/*ms*/; + // don't call wxStopWatch::Time() too often + static const unsigned CALC_CHECK_FREQ = 100; + wxStopWatch timer; +#else + // use some hard-coded limit, that's the best we can do without timer + int top_part_end = wxMin(500, count); +#endif // wxUSE_STOPWATCH/!wxUSE_STOPWATCH + + int row = 0; + + for ( row = 0; row < top_part_end; row++ ) + { +#if wxUSE_STOPWATCH + if ( row % CALC_CHECK_FREQ == CALC_CHECK_FREQ-1 && + timer.Time() > CALC_TIMEOUT ) + break; +#endif // wxUSE_STOPWATCH + calculator.UpdateWithRow(row); } - for ( unsigned row = 0; row < count; row++ ) + // row is the first unmeasured item now; that's out value of N/2 + + if ( row < count ) { - wxDataViewItem item = m_clientArea->GetItemByRow(row); + top_part_end = row; - renderer->PrepareForItem(GetModel(), item, column->GetModelColumn()); + // add bottom N/2 items now: + const int bottom_part_start = wxMax(row, count - row); + for ( row = bottom_part_start; row < count; row++ ) + { + calculator.UpdateWithRow(row); + } - max_width = (unsigned)wxMax((int)max_width, renderer->GetSize().x); + // finally, include currently visible items in the calculation: + const wxPoint origin = CalcUnscrolledPosition(wxPoint(0, 0)); + int first_visible = m_clientArea->GetLineAt(origin.y); + int last_visible = m_clientArea->GetLineAt(origin.y + GetClientSize().y); + + first_visible = wxMax(first_visible, top_part_end); + last_visible = wxMin(bottom_part_start, last_visible); + + for ( row = first_visible; row < last_visible; row++ ) + { + calculator.UpdateWithRow(row); + } + + wxLogTrace("dataview", + "determined best size from %d top, %d bottom plus %d more visible items out of %d total", + top_part_end, + count - bottom_part_start, + wxMax(0, last_visible - first_visible), + count); } + int max_width = calculator.GetMaxWidth(); if ( max_width > 0 ) max_width += 2 * PADDING_RIGHTLEFT; const_cast(this)->m_colsBestWidths[idx] = max_width; return max_width; + + #undef MEASURE_ITEM } void wxDataViewCtrl::ColumnMoved(wxDataViewColumn * WXUNUSED(col),