From 235e61c311ee1cc3c74a637984ca40b4ed773ae2 Mon Sep 17 00:00:00 2001 From: Stefan Csomor Date: Mon, 21 Oct 2019 02:04:35 +0200 Subject: [PATCH] Implement macOS-specific wxDataViewCheckIconTextRenderer This implementation suffers at least from 2 problems: 1. It doesn't support icons at all. 2. It toggles the checkbox when clicking on the text and not just on the checkbox itself, as would be expected. but it's still better than the current version which simply doesn't work at all, i.e. can't be toggled in any way (and also doesn't draw itself correctly when using dark mode under macOS 10.14+), so use it for now. A better solution would be to fix the problem with ActivateCell() not working at all (see #17746) and update the code to respect drawing in dark mode. Closes #17473. Closes https://github.com/wxWidgets/wxWidgets/pull/904 --- include/wx/dvrenderers.h | 3 + include/wx/osx/dvrenderers.h | 34 +++++++ src/common/datavcmn.cpp | 4 + src/osx/cocoa/dataview.mm | 170 ++++++++++++++++++++++++++++------- 4 files changed, 177 insertions(+), 34 deletions(-) diff --git a/include/wx/dvrenderers.h b/include/wx/dvrenderers.h index aa50dc61d7..fbfb57e86d 100644 --- a/include/wx/dvrenderers.h +++ b/include/wx/dvrenderers.h @@ -539,6 +539,8 @@ typedef wxDataViewTextRenderer wxDataViewDateRenderer; // wxDataViewCheckIconTextRenderer: 3-state checkbox + text + optional icon // ---------------------------------------------------------------------------- +#ifndef __WXOSX__ + class WXDLLIMPEXP_CORE wxDataViewCheckIconTextRenderer : public wxDataViewCustomRenderer { @@ -589,6 +591,7 @@ private: wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxDataViewCheckIconTextRenderer); }; +#endif // !__WXOSX__ // this class is obsolete, its functionality was merged in // wxDataViewTextRenderer itself now, don't use it any more diff --git a/include/wx/osx/dvrenderers.h b/include/wx/osx/dvrenderers.h index 1b8231eebf..a854a3f112 100644 --- a/include/wx/osx/dvrenderers.h +++ b/include/wx/osx/dvrenderers.h @@ -184,6 +184,40 @@ private: wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxDataViewIconTextRenderer); }; +// --------------------------------------------------------- +// wxDataViewIconTextRenderer +// --------------------------------------------------------- + +class WXDLLIMPEXP_CORE wxDataViewCheckIconTextRenderer + : public wxDataViewRenderer +{ +public: + static wxString GetDefaultType() { return wxS("wxDataViewCheckIconText"); } + + explicit wxDataViewCheckIconTextRenderer + ( + wxDataViewCellMode mode = wxDATAVIEW_CELL_ACTIVATABLE, + int align = wxDVR_DEFAULT_ALIGNMENT + ); + + // This renderer can always display the 3rd ("indeterminate") checkbox + // state if the model contains cells with wxCHK_UNDETERMINED value, but it + // doesn't allow the user to set it by default. Call this method to allow + // this to happen. + void Allow3rdStateForUser(bool allow = true); + + virtual bool MacRender() wxOVERRIDE; + + virtual void OSXOnCellChanged(NSObject *value, + const wxDataViewItem& item, + unsigned col) wxOVERRIDE; + +private: + bool m_allow3rdStateForUser; + + wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxDataViewCheckIconTextRenderer); +}; + // --------------------------------------------------------- // wxDataViewToggleRenderer // --------------------------------------------------------- diff --git a/src/common/datavcmn.cpp b/src/common/datavcmn.cpp index 1bbef33667..8c8740eadc 100644 --- a/src/common/datavcmn.cpp +++ b/src/common/datavcmn.cpp @@ -1972,6 +1972,8 @@ wxSize wxDataViewDateRenderer::GetSize() const // wxDataViewCheckIconTextRenderer implementation // ---------------------------------------------------------------------------- +#ifndef __WXOSX__ + IMPLEMENT_VARIANT_OBJECT_EXPORTED(wxDataViewCheckIconText, WXDLLIMPEXP_ADV) wxIMPLEMENT_CLASS(wxDataViewCheckIconText, wxDataViewIconText); @@ -2162,6 +2164,8 @@ wxSize wxDataViewCheckIconTextRenderer::GetCheckSize() const return wxRendererNative::Get().GetCheckBoxSize(GetView()); } +#endif // !__WXOSX__ + //----------------------------------------------------------------------------- // wxDataViewListStore //----------------------------------------------------------------------------- diff --git a/src/osx/cocoa/dataview.mm b/src/osx/cocoa/dataview.mm index 78c153c088..82e16eef8b 100644 --- a/src/osx/cocoa/dataview.mm +++ b/src/osx/cocoa/dataview.mm @@ -540,7 +540,7 @@ outlineView:(NSOutlineView*)outlineView item:(id)item childIndex:(NSInteger)index { wxUnusedVar(outlineView); - + return [self setupAndCallDataViewEvents:wxEVT_DATAVIEW_ITEM_DROP dropInfo:info item:item proposedChildIndex:index] != NSDragOperationNone; } @@ -655,7 +655,7 @@ outlineView:(NSOutlineView*)outlineView -(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. @@ -734,18 +734,18 @@ outlineView:(NSOutlineView*)outlineView } wxDataFormatId formatId = event.GetDataFormat().GetType(); wxMemoryBuffer buffer; - + // copy data into buffer: if ( formatId != wxDF_INVALID) { size_t size = dataObjects->GetDataSize(formatId); - + event.SetDataSize(size); dataObjects->GetDataHere(formatId,buffer.GetWriteBuf(size)); buffer.UngetWriteBuf(size); event.SetDataBuffer(buffer.GetData()); } - + // finally, send event: if (dvc->HandleWindowEvent(event) && event.IsAllowed()) { @@ -788,38 +788,38 @@ outlineView:(NSOutlineView*)outlineView NSArray* supportedTypes( [NSArray arrayWithObjects:DataViewPboardType,NSStringPboardType,nil] ); - + NSPasteboard* pasteboard([info draggingPasteboard]); - + NSString* bestType([pasteboard availableTypeFromArray:supportedTypes]); - + if ( bestType == nil ) return NSDragOperationNone; - + NSDragOperation dragOperation = NSDragOperationNone; wxDataViewCtrl* const dvc(implementation->GetDataViewCtrl()); - + wxCHECK_MSG(dvc, false, "Pointer to data view control not set correctly."); wxCHECK_MSG(dvc->GetModel(), false, "Pointer to model not set correctly."); - + // wxDataViewEvent event(eventType, dvc, wxDataViewItemFromItem(item)); if ([bestType compare:DataViewPboardType] == NSOrderedSame) { NSArray* dataArray((NSArray*)[pasteboard propertyListForType:DataViewPboardType]); NSUInteger indexDraggedItem, noOfDraggedItems([dataArray count]); - + indexDraggedItem = 0; while (indexDraggedItem < noOfDraggedItems) { wxDataObjectComposite* dataObjects(implementation->GetDnDDataObjects((NSData*)[dataArray objectAtIndex:indexDraggedItem])); - + dragOperation = [self callDataViewEvents:eventType dataObjects:dataObjects item:item proposedChildIndex:index]; - + if ( dragOperation != NSDragOperationNone ) ++indexDraggedItem; else indexDraggedItem = noOfDraggedItems; - + // clean-up: delete dataObjects; } @@ -831,7 +831,7 @@ outlineView:(NSOutlineView*)outlineView CFDataRef osxData; wxDataObjectComposite* dataObjects (new wxDataObjectComposite()); wxTextDataObject* textDataObject(new wxTextDataObject()); - + osxData = ::CFStringCreateExternalRepresentation(kCFAllocatorDefault,(CFStringRef)[pasteboard stringForType:NSStringPboardType], #if defined(wxNEEDS_UTF16_FOR_TEXT_DATAOBJ) kCFStringEncodingUTF16, @@ -844,14 +844,14 @@ outlineView:(NSOutlineView*)outlineView else delete textDataObject; // send event if data could be copied: - + dragOperation = [self callDataViewEvents:eventType dataObjects:dataObjects item:item proposedChildIndex:index]; // clean up: ::CFRelease(osxData); delete dataObjects; } - + return dragOperation; } @@ -1179,16 +1179,16 @@ outlineView:(NSOutlineView*)outlineView wxDataViewCustomRenderer * const renderer = obj->customRenderer; - // if this method is called everything is already setup correctly, + // 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); @@ -1709,7 +1709,7 @@ outlineView:(NSOutlineView*)outlineView dvc->GetEventHandler()->ProcessEvent(event); } -// Default enter key behaviour is to begin cell editing. Subclass keyDown to +// Default enter key behaviour is to begin cell editing. Subclass keyDown to // provide a keyboard wxEVT_DATAVIEW_ITEM_ACTIVATED event and allow the NSEvent // to pass if the wxEvent is not processed. - (void)keyDown:(NSEvent *)event @@ -1926,19 +1926,19 @@ outlineView:(NSOutlineView*)outlineView { currentlyEditedColumn = [self editedColumn]; currentlyEditedRow = [self editedRow]; - + wxDataViewItem item = wxDataViewItemFromItem([self itemAtRow:currentlyEditedRow]); - + NSTableColumn* tableColumn = [[self tableColumns] objectAtIndex:currentlyEditedColumn]; wxDataViewColumn* const col([static_cast(tableColumn) getColumnPointer]); - + wxDataViewCtrl* const dvc = implementation->GetDataViewCtrl(); // Before doing anything we send an event asking if editing of this item is really wanted. wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_START_EDITING, dvc, col, item); dvc->GetEventHandler()->ProcessEvent( event ); if( !event.IsAllowed() ) return NO; - + return YES; } @@ -2402,7 +2402,7 @@ bool wxCocoaDataViewControl::Remove(const wxDataViewItem& parent) bool wxCocoaDataViewControl::Update(const wxDataViewColumn *columnPtr) { wxUnusedVar(columnPtr); - + return false; } @@ -3312,28 +3312,130 @@ void wxDataViewIconTextRenderer::OSXOnCellChanged(NSObject *value, unsigned col) { wxDataViewModel *model = GetOwner()->GetOwner()->GetModel(); - + // The icon can't be edited so get its old value and reuse it. wxVariant valueOld; model->GetValue(valueOld, item, col); - + wxDataViewIconText iconText; iconText << valueOld; - + // But replace the text with the value entered by user. iconText.SetText(ObjectToString(value)); - + wxVariant valueIconText; valueIconText << iconText; - + if ( !Validate(valueIconText) ) return; - + model->ChangeValue(valueIconText, item, col); } wxIMPLEMENT_ABSTRACT_CLASS(wxDataViewIconTextRenderer,wxDataViewRenderer); +// --------------------------------------------------------- +// wxDataViewCheckIconTextRenderer +// --------------------------------------------------------- + +IMPLEMENT_VARIANT_OBJECT_EXPORTED(wxDataViewCheckIconText, WXDLLIMPEXP_CORE) + +wxIMPLEMENT_CLASS(wxDataViewCheckIconText, wxDataViewIconText); + +wxDataViewCheckIconTextRenderer::wxDataViewCheckIconTextRenderer + ( + wxDataViewCellMode mode, + int align + ) + : wxDataViewRenderer(GetDefaultType(), mode,mode) +{ + m_allow3rdStateForUser = false; + + NSButtonCell* cell; + + cell = [[NSButtonCell alloc] init]; + [cell setAlignment:ConvertToNativeHorizontalTextAlignment(GetAlignment())]; + [cell setButtonType: NSSwitchButton]; + [cell setImagePosition:NSImageLeft]; + [cell setAllowsMixedState:YES]; + SetNativeData(new wxDataViewRendererNativeData(cell)); + [cell release]; +} + +void wxDataViewCheckIconTextRenderer::Allow3rdStateForUser(bool allow) +{ + m_allow3rdStateForUser = allow; +} + +bool wxDataViewCheckIconTextRenderer::MacRender() +{ + wxDataViewCheckIconText checkIconText; + + checkIconText << GetValue(); + + NSButtonCell* cell = (NSButtonCell*) GetNativeData()->GetItemCell(); + + int nativecbvalue = 0; + switch ( checkIconText.GetCheckedState() ) + { + case wxCHK_CHECKED: + nativecbvalue = 1; + break; + case wxCHK_UNDETERMINED: + nativecbvalue = -1; + break; + case wxCHK_UNCHECKED: + nativecbvalue = 0; + break; + } + [cell setIntValue:nativecbvalue]; + [cell setTitle:wxCFStringRef(checkIconText.GetText()).AsNSString()]; + + return true; +} + +void wxDataViewCheckIconTextRenderer::OSXOnCellChanged(NSObject *value, + const wxDataViewItem& item, + unsigned col) +{ + wxDataViewModel *model = GetOwner()->GetOwner()->GetModel(); + + // The icon can't be edited so get its old value and reuse it. + wxVariant valueOld; + model->GetValue(valueOld, item, col); + + wxDataViewCheckIconText checkIconText; + checkIconText << valueOld; + + wxCheckBoxState checkedState ; + switch ( ObjectToLong(value) ) + { + case 1: + checkedState = wxCHK_CHECKED; + break; + + case 0: + checkedState = wxCHK_UNCHECKED; + break; + + case -1: + checkedState = m_allow3rdStateForUser ? wxCHK_UNDETERMINED : wxCHK_CHECKED; + break; + } + + checkIconText.SetCheckedState(checkedState); + + wxVariant valueIconText; + valueIconText << checkIconText; + + if ( !Validate(valueIconText) ) + return; + + model->ChangeValue(valueIconText, item, col); +} + +wxIMPLEMENT_ABSTRACT_CLASS(wxDataViewCheckIconTextRenderer,wxDataViewRenderer); + // --------------------------------------------------------- // wxDataViewToggleRenderer // --------------------------------------------------------- @@ -3398,7 +3500,7 @@ wxDataViewProgressRenderer::wxDataViewProgressRenderer(const wxString& label, : wxDataViewRenderer(varianttype,mode,align) { wxUnusedVar(label); - + NSLevelIndicatorCell* cell; cell = [[NSLevelIndicatorCell alloc] initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];