]> git.saurik.com Git - wxWidgets.git/commitdiff
For large dataview controls, don't use all items to calculate best column width.
authorVáclav Slavík <vslavik@fastmail.fm>
Sat, 30 Oct 2010 15:57:59 +0000 (15:57 +0000)
committerVáclav Slavík <vslavik@fastmail.fm>
Sat, 30 Oct 2010 15:57:59 +0000 (15:57 +0000)
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

src/generic/datavgen.cpp

index 02555daa57a1c07c7c21871b9841af495bcb6ccd..38ed8b0e8dad9bb1bea02bd74fc75b81ffcc6331 100644 (file)
@@ -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<wxDataViewRenderer*>(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<wxDataViewCtrl*>(this)->m_colsBestWidths[idx] = max_width;
     return max_width;
+
+    #undef MEASURE_ITEM
 }
 
 void wxDataViewCtrl::ColumnMoved(wxDataViewColumn * WXUNUSED(col),