From b06ed2f8668b52f61f08e0f72337b923365c01b8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?V=C3=A1clav=20Slav=C3=ADk?= Date: Fri, 7 Jan 2011 18:26:58 +0000 Subject: [PATCH] Implement wxCOL_WIDTH_AUTOSIZE on OS X. Only Cocoa build on 10.5+ is supported. Before that, NSOutlineView didn't have reasonable support for determining cell sizes. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@66634 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/osx/carbon/dataview.h | 1 + include/wx/osx/cocoa/dataview.h | 1 + include/wx/osx/core/dataview.h | 1 + include/wx/osx/dataview.h | 2 +- interface/wx/headercol.h | 7 +- src/osx/carbon/dataview.cpp | 10 +++ src/osx/cocoa/dataview.mm | 148 ++++++++++++++++++++++++++++++- src/osx/dataview_osx.cpp | 22 +++++ 8 files changed, 187 insertions(+), 5 deletions(-) diff --git a/include/wx/osx/carbon/dataview.h b/include/wx/osx/carbon/dataview.h index f4d8f93fd6..056a2a895c 100644 --- a/include/wx/osx/carbon/dataview.h +++ b/include/wx/osx/carbon/dataview.h @@ -376,6 +376,7 @@ public: virtual wxDataViewColumn* GetColumn (unsigned int pos) const; virtual int GetColumnPosition (wxDataViewColumn const* columnPtr) const; virtual bool InsertColumn (unsigned int pos, wxDataViewColumn* columnPtr); + virtual void FitColumnWidthToContent(unsigned int WXUNUSED(pos)) { /*not implemented*/ } // // item related methods (inherited from wxDataViewWidgetImpl) diff --git a/include/wx/osx/cocoa/dataview.h b/include/wx/osx/cocoa/dataview.h index f34720bb77..335bce5ad7 100644 --- a/include/wx/osx/cocoa/dataview.h +++ b/include/wx/osx/cocoa/dataview.h @@ -441,6 +441,7 @@ public: virtual wxDataViewColumn* GetColumn(unsigned int pos) const; virtual int GetColumnPosition(wxDataViewColumn const* columnPtr) const; virtual bool InsertColumn(unsigned int pos, wxDataViewColumn* columnPtr); + virtual void FitColumnWidthToContent(unsigned int pos); // item related methods (inherited from wxDataViewWidgetImpl) virtual bool Add(const wxDataViewItem& parent, const wxDataViewItem& item); diff --git a/include/wx/osx/core/dataview.h b/include/wx/osx/core/dataview.h index a35f7324d5..a478d83d12 100644 --- a/include/wx/osx/core/dataview.h +++ b/include/wx/osx/core/dataview.h @@ -58,6 +58,7 @@ public: virtual int GetColumnPosition (wxDataViewColumn const* columnPtr) const = 0; // returns the position of the passed column in the native control virtual bool InsertColumn (unsigned int pos, wxDataViewColumn* columnPtr) = 0; // inserts a column at pos in the native control; // the method can assume that the column's owner is already set + virtual void FitColumnWidthToContent(unsigned int pos) = 0; // resizes column to fit its content // // item related methods diff --git a/include/wx/osx/dataview.h b/include/wx/osx/dataview.h index 34e114a5a2..ae68663599 100644 --- a/include/wx/osx/dataview.h +++ b/include/wx/osx/dataview.h @@ -95,8 +95,8 @@ private: m_flags = flags & ~wxDATAVIEW_COL_HIDDEN; // TODO m_maxWidth = 30000; m_minWidth = 0; - m_width = width >= 0 ? width : wxDVC_DEFAULT_WIDTH; m_alignment = align; + SetWidth(width); } bool m_ascending; // sorting order diff --git a/interface/wx/headercol.h b/interface/wx/headercol.h index e0a65b9e62..b8509e8423 100644 --- a/interface/wx/headercol.h +++ b/interface/wx/headercol.h @@ -16,7 +16,12 @@ enum /// Special value used for column width meaning unspecified or default. wxCOL_WIDTH_DEFAULT = -1, - /// Size the column automatically to fit all values. + /** + Size the column automatically to fit all values. + + @note On OS X, this style is only implemented in the Cocoa build on + OS X >= 10.5; it behaves identically to wxCOL_WIDTH_DEFAULT otherwise. + */ wxCOL_WIDTH_AUTOSIZE = -2 }; diff --git a/src/osx/carbon/dataview.cpp b/src/osx/carbon/dataview.cpp index 48cb63956e..680d368ea0 100644 --- a/src/osx/carbon/dataview.cpp +++ b/src/osx/carbon/dataview.cpp @@ -2746,6 +2746,16 @@ void wxDataViewColumn::SetWidth(int width) { wxDataViewCtrl* dataViewCtrlPtr(GetOwner()); + switch ( width ) + { + case wxCOL_WIDTH_AUTOSIZE: + // not implemented, fall through + case wxCOL_WIDTH_DEFAULT: + width = wxDVC_DEFAULT_WIDTH; + break; + default: + break; + } if ((width >= m_minWidth) && (width <= m_maxWidth)) { diff --git a/src/osx/cocoa/dataview.mm b/src/osx/cocoa/dataview.mm index 80505a0f3c..c2132ae534 100644 --- a/src/osx/cocoa/dataview.mm +++ b/src/osx/cocoa/dataview.mm @@ -24,6 +24,7 @@ #include "wx/osx/private.h" #include "wx/osx/cocoa/dataview.h" #include "wx/renderer.h" +#include "wx/stopwatch.h" // ============================================================================ // Constants used locally @@ -321,7 +322,6 @@ NSTableColumn* CreateNativeColumn(const wxDataViewColumn *column) ); // setting the size related parameters: - const int width = column->GetWidthVariable(); int resizingMask; if (column->IsResizeable()) { @@ -340,7 +340,6 @@ NSTableColumn* CreateNativeColumn(const wxDataViewColumn *column) : NSTableColumnNoResizing; } [nativeColumn setResizingMask:resizingMask]; - [nativeColumn setWidth:width]; #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 // setting the visibility: @@ -1945,10 +1944,132 @@ bool wxCocoaDataViewControl::InsertColumn(unsigned int pos, wxDataViewColumn* co [m_OutlineView addTableColumn:nativeColumn]; if (pos != static_cast([m_OutlineView numberOfColumns]-1)) [m_OutlineView moveColumn:[m_OutlineView numberOfColumns]-1 toColumn:pos]; + + // set columns width now that it can be computed even for autosized columns: + columnPtr->SetWidth(columnPtr->GetWidthVariable()); + // done: return true; } +void wxCocoaDataViewControl::FitColumnWidthToContent(unsigned int pos) +{ +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 + const int count = GetCount(); + NSTableColumn *column = GetColumn(pos)->GetNativeData()->GetNativeColumnPtr(); + + class MaxWidthCalculator + { + public: + MaxWidthCalculator(wxCocoaOutlineView *view, + NSTableColumn *column, unsigned columnIndex) + : m_width(0), + m_view(view), + m_column(columnIndex), + m_indent(0) + { + // account for indentation in the column with expander + if ( column == [m_view outlineTableColumn] ) + m_indent = [m_view indentationPerLevel]; + } + + void UpdateWithWidth(int width) + { + m_width = wxMax(m_width, width); + } + + void UpdateWithRow(int row) + { + NSCell *cell = [m_view preparedCellAtColumn:m_column row:row]; + unsigned cellWidth = [cell cellSize].width + 1/*round the float up*/; + + if ( m_indent ) + cellWidth += m_indent * ([m_view levelForRow:row] + 1); + + m_width = wxMax(m_width, cellWidth); + } + + int GetMaxWidth() const { return m_width; } + + private: + int m_width; + wxCocoaOutlineView *m_view; + unsigned m_column; + int m_indent; + }; + + MaxWidthCalculator calculator(m_OutlineView, column, pos); + + if ( [column headerCell] ) + { + calculator.UpdateWithWidth([[column headerCell] cellSize].width + 1/*round the float up*/); + } + + // 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); + } + + // row is the first unmeasured item now; that's our value of N/2 + + if ( row < count ) + { + top_part_end = row; + + // 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); + + // finally, include currently visible items in the calculation: + const NSRange visible = [m_OutlineView rowsInRect:[m_OutlineView visibleRect]]; + const int first_visible = wxMax(visible.location, top_part_end); + const int last_visible = wxMin(first_visible + visible.length, bottom_part_start); + + 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); + } + + [column setWidth:calculator.GetMaxWidth()]; +#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 +} + // // item related methods (inherited from wxDataViewWidgetImpl) // @@ -3025,8 +3146,29 @@ void wxDataViewColumn::SetTitle(const wxString& title) void wxDataViewColumn::SetWidth(int width) { - [m_NativeDataPtr->GetNativeColumnPtr() setWidth:width]; m_width = width; + + switch ( width ) + { + case wxCOL_WIDTH_AUTOSIZE: +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 + if ( GetOwner() ) + { + wxCocoaDataViewControl *peer = static_cast(GetOwner()->GetPeer()); + peer->FitColumnWidthToContent(GetOwner()->GetColumnPosition(this)); + break; + } +#endif + // fall through if unsupported (OSX < 10.5) or not yet settable + + case wxCOL_WIDTH_DEFAULT: + width = wxDVC_DEFAULT_WIDTH; + // fall through + + default: + [m_NativeDataPtr->GetNativeColumnPtr() setWidth:width]; + break; + } } void wxDataViewColumn::SetAsSortKey(bool WXUNUSED(sort)) diff --git a/src/osx/dataview_osx.cpp b/src/osx/dataview_osx.cpp index 1dabd9f4bc..199cd2e74a 100644 --- a/src/osx/dataview_osx.cpp +++ b/src/osx/dataview_osx.cpp @@ -76,6 +76,8 @@ protected: void AdjustRowHeight(wxDataViewItem const& item); // ... and the same method for a couple of items: void AdjustRowHeights(wxDataViewItemArray const& items); + // adjust wxCOL_WIDTH_AUTOSIZE columns to fit the data + void AdjustAutosizedColumns(); private: wxDataViewCtrl* m_DataViewCtrlPtr; @@ -130,6 +132,7 @@ bool wxOSXDataViewModelNotifier::ItemChanged(wxDataViewItem const& item) m_DataViewCtrlPtr->HandleWindowEvent(dataViewEvent); // row height may have to be adjusted: AdjustRowHeight(item); + AdjustAutosizedColumns(); // done return true; } @@ -156,6 +159,7 @@ bool wxOSXDataViewModelNotifier::ItemsChanged(wxDataViewItemArray const& items) return false; // if this location is reached all items have been updated: AdjustRowHeights(items); + AdjustAutosizedColumns(); // done: return true; } @@ -174,6 +178,8 @@ bool wxOSXDataViewModelNotifier::ItemDeleted(wxDataViewItem const& parent, wxDat noFailureFlag = m_DataViewCtrlPtr->GetDataViewPeer()->Remove(parent,item); // enable automatic updating again: m_DataViewCtrlPtr->SetDeleting(false); + + AdjustAutosizedColumns(); // done: return noFailureFlag; } @@ -192,6 +198,8 @@ bool wxOSXDataViewModelNotifier::ItemsDeleted(wxDataViewItem const& parent, wxDa noFailureFlag = m_DataViewCtrlPtr->GetDataViewPeer()->Remove(parent,items); // enable automatic updating again: m_DataViewCtrlPtr->SetDeleting(false); + + AdjustAutosizedColumns(); // done: return noFailureFlag; } @@ -209,6 +217,8 @@ bool wxOSXDataViewModelNotifier::ValueChanged(wxDataViewItem const& item, unsign dataViewEvent.SetItem(item); // send the equivalent wxWidget event: m_DataViewCtrlPtr->HandleWindowEvent(dataViewEvent); + + AdjustAutosizedColumns(); // done return true; } @@ -292,6 +302,18 @@ void wxOSXDataViewModelNotifier::AdjustRowHeights(wxDataViewItemArray const& ite } } +void wxOSXDataViewModelNotifier::AdjustAutosizedColumns() +{ + unsigned count = m_DataViewCtrlPtr->GetColumnCount(); + for ( unsigned col = 0; col < count; col++ ) + { + wxDataViewColumn *column = m_DataViewCtrlPtr->GetColumnPtr(col); + + if ( column->GetWidthVariable() == wxCOL_WIDTH_AUTOSIZE ) + m_DataViewCtrlPtr->GetDataViewPeer()->FitColumnWidthToContent(col); + } +} + // --------------------------------------------------------- // wxDataViewCustomRenderer // The constructor, the implementation macro and environment -- 2.45.2