From 224719b67b7bc0d141cfee0bc9740794af4b93e9 Mon Sep 17 00:00:00 2001 From: rich2014 Date: Fri, 23 Aug 2024 00:33:23 +0800 Subject: [PATCH] Cocoa: split BaseInputClient responsibility from TCocoaCustomControl into TCocoaCustomControlWithBaseInputClient --- lcl/interfaces/cocoa/cocoacustomcontrol.pas | 305 +++++++++--------- lcl/interfaces/cocoa/cocoafullcontroledit.pas | 37 ++- lcl/interfaces/cocoa/cocoawindows.pas | 2 +- lcl/interfaces/cocoa/cocoawscommon.pas | 4 +- lcl/interfaces/cocoa/cocoawsstdctrls.pas | 2 +- 5 files changed, 189 insertions(+), 161 deletions(-) diff --git a/lcl/interfaces/cocoa/cocoacustomcontrol.pas b/lcl/interfaces/cocoa/cocoacustomcontrol.pas index d9b57e6fc6..d95481c9c3 100644 --- a/lcl/interfaces/cocoa/cocoacustomcontrol.pas +++ b/lcl/interfaces/cocoa/cocoacustomcontrol.pas @@ -15,17 +15,12 @@ uses type { TCocoaCustomControl } - TCocoaCustomControl = objcclass(NSControl, NSTextInputClientProtocol) + TCocoaCustomControl = objcclass(NSControl) private fstr : NSString; isdrawing : integer; faileddraw : Boolean; - - _inIME: Boolean; - private - function getWindowEditor(): NSTextView; message 'getWindowEditor'; - procedure DoCallInputClientInsertText(nsText:NSString); message 'DoCallInputClientInsertText:'; public callback: ICommonCallback; auxMouseByParent: Boolean; @@ -56,7 +51,17 @@ type procedure setStringValue(avalue: NSString); override; function stringValue: NSString; override; procedure addSubView(aview: NSView); override; + procedure doCommandBySelector (aSelector: SEL); override; + end; + { TCocoaCustomControlWithBaseInputClient } + + TCocoaCustomControlWithBaseInputClient = objcclass(TCocoaCustomControl, NSTextInputClientProtocol) + private + _inIME: Boolean; + private + function getWindowEditor(): NSTextView; message 'getWindowEditor'; + procedure DoCallInputClientInsertText(nsText:NSString); message 'DoCallInputClientInsertText:'; public // NSTextInputClientProtocol related. // implements a base NSTextInputClient for non-editable LCL CustomControl, @@ -75,22 +80,17 @@ type function selectedRange: NSRange; function markedRange: NSRange; function hasMarkedText: LCLObjCBoolean; + function firstRectForCharacterRange_actualRange (aRange: NSRange; actualRange: NSRangePointer): NSRect; + function attributedSubstringForProposedRange_actualRange (aRange: NSRange; actualRange: NSRangePointer): NSAttributedString; function validAttributesForMarkedText: NSArray; - function firstRectForCharacterRange_actualRange (aRange: NSRange; actualRange: NSRangePointer): NSRect; function characterIndexForPoint (aPoint: NSPoint): NSUInteger; - procedure doCommandBySelector (aSelector: SEL); override; end; implementation { TCocoaCustomControl } -function TCocoaCustomControl.getWindowEditor(): NSTextView; -begin - Result:= NSTextView( self.window.fieldEditor_forObject(true,nil) ); -end; - procedure TCocoaCustomControl.setStringValue(avalue: NSString); begin if Assigned(fstr) then fstr.release; @@ -129,142 +129,6 @@ begin end; end; -procedure TCocoaCustomControl.keyDown(theEvent: NSEvent); -var - textView: NSView; - isFirst: Boolean; -begin - if (not _inIME) and (theEvent.keyCode in - [kVK_Return, kVK_ANSI_KeypadEnter, kVK_Escape, kVK_Space]) then - begin - inherited; - exit; - end; - - isFirst:= not _inIME; - inputContext.handleEvent(theEvent); - if _inIME and isFirst then - begin - textView:= getWindowEditor(); - textView.setFrameSize( NSMakeSize(self.frame.size.width,16) ); - self.addSubView( textView ); - end - else if not _inIME then - inputContext.discardMarkedText; -end; - -procedure TCocoaCustomControl.DoCallInputClientInsertText(nsText:NSString); -begin - if Assigned(callback) then - callback.InputClientInsertText(nsText.UTF8String); - nsText.release; -end; - -// in TCocoaCustomControl, such as Form, Grid, ListView, -// after inputting text, another control may be focused. -// in insertText_replacementRange(), Cocoa/InputContext doesn't like it, -// so calling InputClientInsertText() asynchronously. -procedure TCocoaCustomControl.insertText_replacementRange(aString: id; - replacementRange: NSRange); -var - nsText: NSString; -begin - if not _inIME then exit; - - unmarkText; - - nsText:= getNSStringObject(aString).copy; - performSelector_withObject_afterDelay(ObjCSelector('DoCallInputClientInsertText:'), nsText, 0 ); -end; - -procedure TCocoaCustomControl.setMarkedText_selectedRange_replacementRange( - aString: id; selectedRange: NSRange; replacementRange: NSRange); -var - textView: NSTextView; - nsText: NSString; -begin - nsText:= getNSStringObject(aString); - if nsText.length > 0 then - begin - _inIME:= true; - textView:= getWindowEditor(); - if Assigned(textView) then - textView.setMarkedText_selectedRange_replacementRange(aString,selectedRange,replacementRange); - end - else - unmarkText; -end; - -function TCocoaCustomControl.hasMarkedText: LCLObjCBoolean; -begin - Result := _inIME; -end; - -procedure TCocoaCustomControl.unmarkText; -var - textView: NSTextView; -begin - _inIME:= false; - textView:= getWindowEditor(); - if Assigned(textView) then - textView.removeFromSuperview; -end; - -function TCocoaCustomControl.firstRectForCharacterRange_actualRange( - aRange: NSRange; actualRange: NSRangePointer): NSRect; -var - point: NSPoint; - rect: NSRect; -begin - point:= self.convertPoint_toView(NSZeroPoint, nil); - rect:= NSMakeRect(point.x, point.y, 0, 16); - Result:= self.window.convertRectToScreen(rect); -end; - -function TCocoaCustomControl.selectedRange: NSRange; -var - textView: NSText; -begin - textView:= getWindowEditor(); - if not Assigned(textView) then - Result:= NSMakeRange( NSNotFound, 0 ) - else - Result:= textView.selectedRange; -end; - -function TCocoaCustomControl.markedRange: NSRange; -var - textView: NSTextView; -begin - textView:= getWindowEditor(); - if not Assigned(textView) then - Result:= NSMakeRange( NSNotFound, 0 ) - else - Result:= textView.markedRange; -end; - -function TCocoaCustomControl.attributedSubstringForProposedRange_actualRange( - aRange: NSRange; actualRange: NSRangePointer): NSAttributedString; -begin - Result := nil; -end; - -function TCocoaCustomControl.validAttributesForMarkedText: NSArray; -begin - Result := nil; -end; - -function TCocoaCustomControl.characterIndexForPoint(aPoint: NSPoint - ): NSUInteger; -begin - Result := 0; -end; - -procedure TCocoaCustomControl.doCommandBySelector(aSelector: SEL); -begin - inherited doCommandBySelector(ASelector); -end; - procedure TCocoaCustomControl.dealloc; begin if Assigned(fstr) then fstr.release; @@ -439,5 +303,148 @@ begin inherited otherMouseDragged(event); end; +procedure TCocoaCustomControl.doCommandBySelector(aSelector: SEL); +begin + inherited doCommandBySelector(ASelector); +end; + +{ TCocoaCustomControlWithBaseInputClient } + +function TCocoaCustomControlWithBaseInputClient.getWindowEditor(): NSTextView; +begin + Result:= NSTextView( self.window.fieldEditor_forObject(true,nil) ); +end; + +procedure TCocoaCustomControlWithBaseInputClient.DoCallInputClientInsertText(nsText:NSString); +begin + if Assigned(callback) then + callback.InputClientInsertText(nsText.UTF8String); + nsText.release; +end; + +procedure TCocoaCustomControlWithBaseInputClient.keyDown(theEvent: NSEvent); +var + textView: NSView; + isFirst: Boolean; +begin + if (not _inIME) and (theEvent.keyCode in + [kVK_Return, kVK_ANSI_KeypadEnter, kVK_Escape, kVK_Space]) then + begin + inherited; + exit; + end; + + isFirst:= not _inIME; + inputContext.handleEvent(theEvent); + if _inIME and isFirst then + begin + textView:= getWindowEditor(); + textView.setFrameSize( NSMakeSize(self.frame.size.width,16) ); + self.addSubView( textView ); + end + else if not _inIME then + inputContext.discardMarkedText; +end; + +// in TCocoaCustomControl, such as Form, Grid, ListView, +// after inputting text, another control may be focused. +// in insertText_replacementRange(), Cocoa/InputContext doesn't like it, +// so calling InputClientInsertText() asynchronously. +procedure TCocoaCustomControlWithBaseInputClient.insertText_replacementRange(aString: id; + replacementRange: NSRange); +var + nsText: NSString; +begin + if not _inIME then exit; + + unmarkText; + + nsText:= getNSStringObject(aString).copy; + performSelector_withObject_afterDelay(ObjCSelector('DoCallInputClientInsertText:'), nsText, 0 ); +end; + +procedure TCocoaCustomControlWithBaseInputClient.setMarkedText_selectedRange_replacementRange( + aString: id; selectedRange: NSRange; replacementRange: NSRange); +var + textView: NSTextView; + nsText: NSString; +begin + nsText:= getNSStringObject(aString); + if nsText.length > 0 then + begin + _inIME:= true; + textView:= getWindowEditor(); + if Assigned(textView) then + textView.setMarkedText_selectedRange_replacementRange(aString,selectedRange,replacementRange); + end + else + unmarkText; +end; + +function TCocoaCustomControlWithBaseInputClient.hasMarkedText: LCLObjCBoolean; +begin + Result := _inIME; +end; + +procedure TCocoaCustomControlWithBaseInputClient.unmarkText; +var + textView: NSTextView; +begin + _inIME:= false; + textView:= getWindowEditor(); + if Assigned(textView) then + textView.removeFromSuperview; +end; + +function TCocoaCustomControlWithBaseInputClient.firstRectForCharacterRange_actualRange( + aRange: NSRange; actualRange: NSRangePointer): NSRect; +var + point: NSPoint; + rect: NSRect; +begin + point:= self.convertPoint_toView(NSZeroPoint, nil); + rect:= NSMakeRect(point.x, point.y, 0, 16); + Result:= self.window.convertRectToScreen(rect); +end; + +function TCocoaCustomControlWithBaseInputClient.selectedRange: NSRange; +var + textView: NSText; +begin + textView:= getWindowEditor(); + if not Assigned(textView) then + Result:= NSMakeRange( NSNotFound, 0 ) + else + Result:= textView.selectedRange; +end; + +function TCocoaCustomControlWithBaseInputClient.markedRange: NSRange; +var + textView: NSTextView; +begin + textView:= getWindowEditor(); + if not Assigned(textView) then + Result:= NSMakeRange( NSNotFound, 0 ) + else + Result:= textView.markedRange; +end; + +function TCocoaCustomControlWithBaseInputClient.attributedSubstringForProposedRange_actualRange( + aRange: NSRange; actualRange: NSRangePointer): NSAttributedString; +begin + Result := nil; +end; + +function TCocoaCustomControlWithBaseInputClient.validAttributesForMarkedText: NSArray; +begin + Result := nil; +end; + +function TCocoaCustomControlWithBaseInputClient.characterIndexForPoint(aPoint: NSPoint + ): NSUInteger; +begin + Result := 0; +end; + end. diff --git a/lcl/interfaces/cocoa/cocoafullcontroledit.pas b/lcl/interfaces/cocoa/cocoafullcontroledit.pas index 0c1f996eea..d973a53f68 100644 --- a/lcl/interfaces/cocoa/cocoafullcontroledit.pas +++ b/lcl/interfaces/cocoa/cocoafullcontroledit.pas @@ -46,7 +46,7 @@ type // Key Class for Cocoa IME support // 1. obtain IME capability from Cocoa by implementing NSTextInputClientProtocol // 2. synchronize IME data with LCL via ICocoaIMEControl - TCocoaFullControlEdit = objcclass(TCocoaCustomControl) + TCocoaFullControlEdit = objcclass(TCocoaCustomControl, NSTextInputClientProtocol) private _currentParams: TCocoaIMEParameters; _currentMarkedText: NSString; @@ -58,13 +58,17 @@ type procedure mouseUp(event: NSEvent); override; function resignFirstResponder: ObjCBOOL; override; - procedure setMarkedText_selectedRange_replacementRange (aString: id; newRange: NSRange; replacementRange: NSRange); override; - procedure insertText_replacementRange (aString: id; replacementRange: NSRange); override; - procedure unmarkText; override; - function markedRange: NSRange; override; - function selectedRange: NSRange; override; - function hasMarkedText: LCLObjCBoolean; override; - function firstRectForCharacterRange_actualRange ({%H-}aRange: NSRange; {%H-}actualRange: NSRangePointer): NSRect; override; + procedure insertText_replacementRange (aString: id; replacementRange: NSRange); + procedure setMarkedText_selectedRange_replacementRange (aString: id; newRange: NSRange; replacementRange: NSRange); + procedure unmarkText; + function selectedRange: NSRange; + function markedRange: NSRange; + function hasMarkedText: LCLObjCBoolean; + function firstRectForCharacterRange_actualRange ({%H-}aRange: NSRange; {%H-}actualRange: NSRangePointer): NSRect; + + function attributedSubstringForProposedRange_actualRange (aRange: NSRange; actualRange: NSRangePointer): NSAttributedString; + function validAttributesForMarkedText: NSArray; + function characterIndexForPoint (aPoint: NSPoint): NSUInteger; end; implementation @@ -291,5 +295,22 @@ begin Result:= ( _currentParams.textNSLength > 0 ); end; +function TCocoaFullControlEdit.attributedSubstringForProposedRange_actualRange( + aRange: NSRange; actualRange: NSRangePointer): NSAttributedString; +begin + Result := nil; +end; + +function TCocoaFullControlEdit.validAttributesForMarkedText: NSArray; +begin + Result := nil; +end; + +function TCocoaFullControlEdit.characterIndexForPoint(aPoint: NSPoint + ): NSUInteger; +begin + Result := 0; +end; + end. diff --git a/lcl/interfaces/cocoa/cocoawindows.pas b/lcl/interfaces/cocoa/cocoawindows.pas index 4daf2b7cf6..d0bbc55f5f 100644 --- a/lcl/interfaces/cocoa/cocoawindows.pas +++ b/lcl/interfaces/cocoa/cocoawindows.pas @@ -184,7 +184,7 @@ type { TCocoaWindowContentDocument } - TCocoaWindowContentDocument = objcclass(TCocoaCustomControl) + TCocoaWindowContentDocument = objcclass(TCocoaCustomControlWithBaseInputClient) protected procedure didBecomeKeyNotification(sender: NSNotification); message 'didBecomeKeyNotification:'; procedure didResignKeyNotification(sender: NSNotification); message 'didResignKeyNotification:'; diff --git a/lcl/interfaces/cocoa/cocoawscommon.pas b/lcl/interfaces/cocoa/cocoawscommon.pas index ba0eca0586..3d21228e65 100644 --- a/lcl/interfaces/cocoa/cocoawscommon.pas +++ b/lcl/interfaces/cocoa/cocoawscommon.pas @@ -1978,13 +1978,13 @@ begin ctrl := TCocoaFullControlEdit.alloc.lclInitWithCreateParams(AParams); lcl := TLCLFullControlEditCallback.Create(ctrl, AWinControl); TCocoaFullControlEdit(ctrl).imeHandler := imeHandler; - ctrl.unmarkText; + TCocoaFullControlEdit(ctrl).unmarkText; end else begin // AWinControl not implements ICocoaIMEControl // AWinControl is a normal Custom Control - ctrl := TCocoaCustomControl.alloc.lclInitWithCreateParams(AParams); + ctrl := TCocoaCustomControlWithBaseInputClient.alloc.lclInitWithCreateParams(AParams); lcl := TLCLCommonCallback.Create(ctrl, AWinControl); end; lcl.BlockCocoaUpDown := true; diff --git a/lcl/interfaces/cocoa/cocoawsstdctrls.pas b/lcl/interfaces/cocoa/cocoawsstdctrls.pas index b9d475dde1..d83dbdb3cf 100644 --- a/lcl/interfaces/cocoa/cocoawsstdctrls.pas +++ b/lcl/interfaces/cocoa/cocoawsstdctrls.pas @@ -2375,7 +2375,7 @@ begin // set a content view in order to be able to customize drawing for labels/color ns := GetNSRect(AParams.X, AParams.Y, AParams.Width, AParams.Height); - lGroupBoxContents := TCocoaCustomControl(TCocoaCustomControl.alloc.initWithFrame(ns)); + lGroupBoxContents := TCocoaCustomControl.alloc.initWithFrame(ns); lGroupBoxContents.callback := box.callback; //TLCLCustomControlCallback.Create(lGroupBoxContents, AWinControl); //str := Format('%X=%X', [PtrUInt(box.callback), PtrUInt(lGroupBoxContents.callback)]); lGroupBoxContents.autorelease;