#include "wx/osx/private.h"
#include "wx/osx/cocoa/dataview.h"
#include "wx/renderer.h"
+#include "wx/stopwatch.h"
+#include "wx/dcgraph.h"
// ============================================================================
// Constants used locally
);
// setting the size related parameters:
- const int width = column->GetWidthVariable();
int resizingMask;
if (column->IsResizeable())
{
[nativeColumn setMinWidth:column->GetMinWidth()];
[nativeColumn setMaxWidth:column->GetMaxWidth()];
}
- else // column is not resizeable [by user]
+ else // column is not resizable [by user]
{
// if the control doesn't show a header, make the columns resize
// automatically, this is particularly important for the single column
: NSTableColumnNoResizing;
}
[nativeColumn setResizingMask:resizingMask];
- [nativeColumn setWidth:width];
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
// setting the visibility:
acceptDrop:(id<NSDraggingInfo>)info
item:(id)item childIndex:(NSInteger)index
{
+ wxUnusedVar(outlineView);
+ wxUnusedVar(index);
+
NSArray* supportedTypes(
[NSArray arrayWithObjects:DataViewPboardType,NSStringPboardType,nil]
);
event.SetItem(wxDataViewItemFromItem(item));
event.SetModel(dvc->GetModel());
- BOOL dragSuccessful;
+ BOOL dragSuccessful = false;
if ( [bestType compare:DataViewPboardType] == NSOrderedSame )
{
NSArray* dataArray((NSArray*)
child:(NSInteger)index
ofItem:(id)item
{
+ wxUnusedVar(outlineView);
+
if ((item == currentParentItem) &&
(index < ((NSInteger) [self getChildCount])))
return [self getChild:index];
-(BOOL) outlineView:(NSOutlineView*)outlineView isItemExpandable:(id)item
{
+ wxUnusedVar(outlineView);
+
wxCHECK_MSG( model, 0, "Valid model in data source does not exist." );
return model->IsContainer(wxDataViewItemFromItem(item));
}
-(NSInteger) outlineView:(NSOutlineView*)outlineView numberOfChildrenOfItem:(id)item
{
+ wxUnusedVar(outlineView);
+
NSInteger noOfChildren;
wxDataViewItemArray dataViewChildren;
objectValueForTableColumn:(NSTableColumn*)tableColumn
byItem:(id)item
{
+ wxUnusedVar(outlineView);
+
wxCHECK_MSG( model, nil, "Valid model in data source does not exist." );
wxDataViewColumn* col(static_cast<wxDataViewColumn*>([[tableColumn identifier] pointer]));
forTableColumn:(NSTableColumn*)tableColumn
byItem:(id)item
{
+ wxUnusedVar(outlineView);
+
wxDataViewColumn* col(static_cast<wxDataViewColumn*>([[tableColumn identifier] pointer]));
col->GetRenderer()->
-(void) outlineView:(NSOutlineView*)outlineView sortDescriptorsDidChange:(NSArray*)oldDescriptors
{
+ wxUnusedVar(oldDescriptors);
+
// Warning: the new sort descriptors are guaranteed to be only of type
// NSSortDescriptor! Therefore, the sort descriptors for the data source
// have to be converted.
-(NSDragOperation) outlineView:(NSOutlineView*)outlineView validateDrop:(id<NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
+ wxUnusedVar(outlineView);
+ wxUnusedVar(index);
+
NSArray* supportedTypes([NSArray arrayWithObjects:DataViewPboardType,NSStringPboardType,nil]);
NSPasteboard* pasteboard([info draggingPasteboard]);
if (bestType == nil)
return NSDragOperationNone;
- NSDragOperation dragOperation;
+ NSDragOperation dragOperation = NSDragOperationNone;
wxDataViewCtrl* const dvc(implementation->GetDataViewCtrl());
wxCHECK_MSG(dvc, false, "Pointer to data view control not set correctly.");
-(BOOL) outlineView:(NSOutlineView*)outlineView writeItems:(NSArray*)writeItems toPasteboard:(NSPasteboard*)pasteboard
{
+ wxUnusedVar(outlineView);
+
// the pasteboard will be filled up with an array containing the data as
// returned by the events (including the data type) and a concatenation of
// text (string) data; the text data will only be put onto the pasteboard
if ([writeItems count] > 0)
{
bool dataStringAvailable(true); // a flag indicating if for all items a data string is available
- NSMutableArray* dataArray = [[NSMutableArray arrayWithCapacity:[writeItems count]] retain]; // data of all items
+ NSMutableArray* dataArray = [NSMutableArray arrayWithCapacity:[writeItems count]]; // data of all items
wxString dataString; // contains the string data of all items
// send a begin drag event for all selected items and proceed with
wxDataViewCustomRenderer * const renderer = obj->customRenderer;
- wxDC * const dc = renderer->GetDC();
- renderer->WXCallRender(wxFromNSRect(controlView, cellFrame), dc, 0);
- renderer->SetDC(NULL);
+ // if this method is called everything is already setup correctly,
+ CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
+ CGContextSaveGState( context );
+
+ if ( ![controlView isFlipped] )
+ {
+ CGContextTranslateCTM( context, 0, [controlView bounds].size.height );
+ CGContextScaleCTM( context, 1, -1 );
+ }
+
+ wxGCDC dc;
+ wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(context);
+ dc.SetGraphicsContext(gc);
+
+ int state = 0;
+ if ( [self isHighlighted] )
+ state |= wxDATAVIEW_CELL_SELECTED;
+
+ renderer->WXCallRender(wxFromNSRect(controlView, cellFrame), &dc, state);
+
+ CGContextRestoreGState( context );
}
-(NSRect) imageRectForBounds:(NSRect)cellFrame
//
-(void) actionDoubleClick:(id)sender
{
+ wxUnusedVar(sender);
+
// actually the documentation (NSTableView 2007-10-31) for doubleAction:
// and setDoubleAction: seems to be wrong as this action message is always
// sent whether the cell is editable or not
//
-(NSMenu*) menuForEvent:(NSEvent*)theEvent
{
+ wxUnusedVar(theEvent);
+
// this method does not do any special menu event handling but only sends
// an event message; therefore, the user has full control if a context
// menu should be shown or not
-(BOOL) outlineView:(NSOutlineView*)outlineView shouldCollapseItem:(id)item
{
+ wxUnusedVar(outlineView);
+
wxDataViewCtrl* const dvc = implementation->GetDataViewCtrl();
wxDataViewEvent event(wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSING,dvc->GetId());
-(BOOL) outlineView:(NSOutlineView*)outlineView shouldExpandItem:(id)item
{
+ wxUnusedVar(outlineView);
+
wxDataViewCtrl* const dvc = implementation->GetDataViewCtrl();
wxDataViewEvent event(wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDING,dvc->GetId());
-(BOOL) outlineView:(NSOutlineView*)outlineView shouldSelectTableColumn:(NSTableColumn*)tableColumn
{
+ wxUnusedVar(tableColumn);
+ wxUnusedVar(outlineView);
+
return NO;
}
forTableColumn:(NSTableColumn*)tableColumn
item:(id)item
{
+ wxUnusedVar(outlineView);
+
wxDataViewCtrl * const dvc = implementation->GetDataViewCtrl();
wxDataViewModel * const model = dvc->GetModel();
model->GetAttr(dvItem, colIdx, attr);
renderer->OSXApplyAttr(attr);
+ // set the state (enabled/disabled) of the item
+ renderer->OSXApplyEnabled(model->IsEnabled(dvItem, colIdx));
+
// and finally do draw it
renderer->MacRender();
}
-(void) outlineViewSelectionDidChange:(NSNotification*)notification
{
+ wxUnusedVar(notification);
+
wxDataViewCtrl* const dvc = implementation->GetDataViewCtrl();
wxDataViewEvent event(wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED,dvc->GetId());
[m_OutlineView addTableColumn:nativeColumn];
if (pos != static_cast<unsigned int>([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)
//
bool wxCocoaDataViewControl::Reload()
{
[m_DataSource clearBuffers];
+ [m_OutlineView reloadData];
[m_OutlineView scrollColumnToVisible:0];
[m_OutlineView scrollRowToVisible:0];
- [m_OutlineView reloadData];
return true;
}
bool wxCocoaDataViewControl::Update(const wxDataViewColumn *columnPtr)
{
+ wxUnusedVar(columnPtr);
+
return false;
}
//
// selection related methods (inherited from wxDataViewWidgetImpl)
//
+
+wxDataViewItem wxCocoaDataViewControl::GetCurrentItem() const
+{
+ return wxDataViewItem([[m_OutlineView itemAtRow:[m_OutlineView selectedRow]] pointer]);
+}
+
+void wxCocoaDataViewControl::SetCurrentItem(const wxDataViewItem& item)
+{
+ // We can't have unselected current item in a NSTableView, as the
+ // documentation of its deselectRow method explains, the control will
+ // automatically change the current item to the closest still selected item
+ // if the current item is deselected. So we have no choice but to select
+ // the item in the same time as making it current.
+ Select(item);
+}
+
int wxCocoaDataViewControl::GetSelections(wxDataViewItemArray& sel) const
{
NSIndexSet* selectedRowIndexes([m_OutlineView selectedRowIndexes]);
{
if (item.IsOk())
[m_OutlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[m_OutlineView rowForItem:[m_DataSource getDataViewItemFromBuffer:item]]]
- byExtendingSelection:NO];
+ byExtendingSelection:GetDataViewCtrl()->HasFlag(wxDV_MULTIPLE) ? YES : NO];
}
void wxCocoaDataViewControl::SelectAll()
[m_OutlineView reloadData];
}
+void wxCocoaDataViewControl::StartEditor( const wxDataViewItem & item, unsigned int column )
+{
+ [m_OutlineView editColumn:column row:[m_OutlineView rowForItem:[m_DataSource getDataViewItemFromBuffer:item]] withEvent:nil select:YES];
+}
+
//
// other methods (inherited from wxDataViewWidgetImpl)
//
[(id)cell setTextColor:colText];
}
+void wxDataViewRenderer::OSXApplyEnabled(bool enabled)
+{
+ [GetNativeData()->GetItemCell() setEnabled:enabled];
+}
+
IMPLEMENT_ABSTRACT_CLASS(wxDataViewRenderer,wxDataViewRendererBase)
// ---------------------------------------------------------
cell = [[NSPopUpButtonCell alloc] init];
[cell setControlSize:NSMiniControlSize];
- [cell setFont:[[NSFont fontWithName:[[cell font] fontName] size:[NSFont systemFontSizeForControlSize:NSMiniControlSize]] autorelease]];
+ [cell setFont:[NSFont fontWithName:[[cell font] fontName] size:[NSFont systemFontSizeForControlSize:NSMiniControlSize]]];
for (size_t i=0; i<choices.GetCount(); ++i)
- [cell addItemWithTitle:[[wxCFStringRef(choices[i]).AsNSString() retain] autorelease]];
+ [cell addItemWithTitle:wxCFStringRef(choices[i]).AsNSString()];
SetNativeData(new wxDataViewRendererNativeData(cell));
[cell release];
}
int align)
: wxDataViewRenderer(varianttype,mode,align)
{
+ wxUnusedVar(label);
+
NSLevelIndicatorCell* cell;
cell = [[NSLevelIndicatorCell alloc] initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
m_title(title)
{
InitCommon(width, align, flags);
- if (renderer && (renderer->GetAlignment() == wxDVR_DEFAULT_ALIGNMENT))
+ if (renderer && !renderer->IsCustomRenderer() &&
+ (renderer->GetAlignment() == wxDVR_DEFAULT_ALIGNMENT))
renderer->SetAlignment(align);
}
m_NativeDataPtr(new wxDataViewColumnNativeData())
{
InitCommon(width, align, flags);
- if (renderer && (renderer->GetAlignment() == wxDVR_DEFAULT_ALIGNMENT))
+ if (renderer && !renderer->IsCustomRenderer() &&
+ (renderer->GetAlignment() == wxDVR_DEFAULT_ALIGNMENT))
renderer->SetAlignment(align);
}
{
m_alignment = align;
[[m_NativeDataPtr->GetNativeColumnPtr() headerCell] setAlignment:ConvertToNativeHorizontalTextAlignment(align)];
- if (m_renderer && (m_renderer->GetAlignment() == wxDVR_DEFAULT_ALIGNMENT))
+ if (m_renderer && !m_renderer->IsCustomRenderer() &&
+ (m_renderer->GetAlignment() == wxDVR_DEFAULT_ALIGNMENT))
m_renderer->SetAlignment(align);
}
void wxDataViewColumn::SetReorderable(bool reorderable)
{
+ wxUnusedVar(reorderable);
+}
+
+void wxDataViewColumn::SetHidden(bool hidden)
+{
+ // How to set flag here?
+
+ [m_NativeDataPtr->GetNativeColumnPtr() setHidden:hidden];
}
-void wxDataViewColumn::SetResizeable(bool resizeable)
+bool wxDataViewColumn::IsHidden() const
{
- wxDataViewColumnBase::SetResizeable(resizeable);
- if (resizeable)
+ return [m_NativeDataPtr->GetNativeColumnPtr() isHidden];
+}
+
+void wxDataViewColumn::SetResizeable(bool resizable)
+{
+ wxDataViewColumnBase::SetResizeable(resizable);
+ if (resizable)
[m_NativeDataPtr->GetNativeColumnPtr() setResizingMask:NSTableColumnUserResizingMask];
else
[m_NativeDataPtr->GetNativeColumnPtr() setResizingMask:NSTableColumnNoResizing];
void wxDataViewColumn::SetSortable(bool sortable)
{
- wxDataViewColumnBase::SetSortable(sortable);
+ // wxDataViewColumnBase::SetSortable(sortable);
+ // Avoid endless recursion and just set the flag here
+ if (sortable)
+ m_flags |= wxDATAVIEW_COL_SORTABLE;
+ else
+ m_flags &= ~wxDATAVIEW_COL_SORTABLE;
}
void wxDataViewColumn::SetSortOrder(bool ascending)
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<wxCocoaDataViewControl*>(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))