Cocoa: decouple CustomControl/FullEditControl and etc, Merge branch 'cocoa/decouple'

This commit is contained in:
rich2014 2024-08-23 00:34:57 +08:00
commit 9a92e07736
16 changed files with 995 additions and 894 deletions

View File

@ -21,7 +21,7 @@ unit LazSynCocoaIMM;
interface
uses
CocoaPrivate,
CocoaFullControlEdit,
Classes, SysUtils,
SynEditMiscClasses, LazSynIMMBase, SynEditKeyCmds, SynEditTextBase;

View File

@ -88,16 +88,6 @@ type
property CocoaOnlyState: Boolean read IsCocoaOnlyState write SetCocoaOnlyState;
end;
{ TCocoaStatusBar }
IStatusBarCallback = interface {(ICommonCallback) // not needed to inherit from ICommonCallback}
function GetBarsCount: Integer;
//todo: consider the use Cocoa native types, instead of FPC TAlignment
function GetBarItem(idx: Integer; var txt: String;
var width: Integer; var align: TAlignment): Boolean;
procedure DrawPanel(idx: Integer; const r: TRect);
end;
implementation
end.

View File

@ -0,0 +1,450 @@
unit CocoaCustomControl;
{$mode objfpc}{$H+}
{$modeswitch objectivec2}
{$interfaces corba}
interface
uses
Classes, SysUtils,
Forms,
MacOSAll, CocoaAll, CocoaPrivate, CocoaCallback,
CocoaCursor, Cocoa_Extra, CocoaUtils;
type
{ TCocoaCustomControl }
TCocoaCustomControl = objcclass(NSControl)
private
fstr : NSString;
isdrawing : integer;
faileddraw : Boolean;
public
callback: ICommonCallback;
auxMouseByParent: Boolean;
procedure dealloc; override;
function acceptsFirstResponder: LCLObjCBoolean; override;
procedure drawRect(dirtyRect: NSRect); override;
function lclGetCallback: ICommonCallback; override;
procedure lclClearCallback; override;
function lclIsMouseInAuxArea(Event: NSevent): Boolean; override;
// mouse
function acceptsFirstMouse(event: NSEvent): LCLObjCBoolean; override;
procedure mouseDown(event: NSEvent); override;
procedure mouseUp(event: NSEvent); override;
procedure rightMouseDown(event: NSEvent); override;
procedure rightMouseUp(event: NSEvent); override;
procedure rightMouseDragged(event: NSEvent); override;
procedure otherMouseDown(event: NSEvent); override;
procedure otherMouseUp(event: NSEvent); override;
procedure otherMouseDragged(event: NSEvent); override;
procedure mouseDragged(event: NSEvent); override;
procedure mouseEntered(event: NSEvent); override;
procedure mouseExited(event: NSEvent); override;
procedure mouseMoved(event: NSEvent); override;
procedure scrollWheel(event: NSEvent); override;
// nsview
procedure setFrame(aframe: NSRect); override;
// value
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,
// like Form, Grid, ListView, that are not system control and not FullEditControl.
// 1. when using IME in these controls, a temporary and one-time editor is shown
// at the bottom of the control, supporting IME such as Chinese.
// 2. refers to MacOS Finder, when using IME in the file list view,
// a small window will pop up at the bottom of the screen for input.
// the text can then be used for filename starting character match.
// 3. it is useful for implementing IME support for controls that do not
// have a text input window.
procedure keyDown(theEvent: NSEvent); override;
procedure insertText_replacementRange (aString: id; replacementRange: NSRange);
procedure setMarkedText_selectedRange_replacementRange (aString: id; selectedRange: NSRange; replacementRange: NSRange);
procedure unmarkText;
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 characterIndexForPoint (aPoint: NSPoint): NSUInteger;
end;
implementation
{ TCocoaCustomControl }
procedure TCocoaCustomControl.setStringValue(avalue: NSString);
begin
if Assigned(fstr) then fstr.release;
if ASsigned(avalue) then
fstr:=avalue.copyWithZone(nil)
else
fstr:=nil;
inherited setStringValue(avalue);
end;
function TCocoaCustomControl.stringValue: NSString;
begin
Result:=fstr;
end;
procedure TCocoaCustomControl.addSubView(aview: NSView);
var
mask: NSUInteger;
begin
inherited addSubView(aview);
if Assigned(aview) then
begin
// forcing LCL compatible "auto-move" mode. Sticking to left/top corner
if not autoresizesSubviews then
{$ifdef BOOLFIX}
setAutoresizesSubviews_(Ord(true));
{$else}
setAutoresizesSubviews(true);
{$endif}
if self.isFlipped then
mask:= NSViewMaxYMargin or NSViewMaxXMargin
else
mask:= NSViewMinYMargin or NSViewMaxXMargin;
aview.setAutoresizingMask(mask);
end;
end;
procedure TCocoaCustomControl.dealloc;
begin
if Assigned(fstr) then fstr.release;
inherited dealloc;
end;
function TCocoaCustomControl.acceptsFirstResponder: LCLObjCBoolean;
begin
Result := True;
end;
function TCocoaCustomControl.acceptsFirstMouse(event: NSEvent): LCLObjCBoolean;
begin
// By default, a mouse-down event in a window that isnt the key window
// simply brings the window forward and makes it key; the event isnt sent
// to the NSView object over which the mouse click occurs. The NSView can
// claim an initial mouse-down event, however, by overriding acceptsFirstMouse: to return YES.
// see bug #33034
Result:=true;
end;
procedure TCocoaCustomControl.drawRect(dirtyRect: NSRect);
begin
if isdrawing=0 then faileddraw:=false;
inc(isdrawing);
inherited drawRect(dirtyRect);
// Implement Color property
if Assigned(callback) then
callback.DrawBackground(NSGraphicsContext.currentContext, bounds, dirtyRect);
if CheckMainThread and Assigned(callback) then
callback.Draw(NSGraphicsContext.currentContext, bounds, dirtyRect);
dec(isdrawing);
if (isdrawing=0) and (faileddraw) then
begin
// if the frame is changed during the Paint event,
// redrawing must be triggered to ensure that the
// correctly updated NSGraphicsContext is obtained.
// 1. for the new version of macOS, just set setNeedsDisplay()
// 2. for older versions of macOS, display() must be called by itself,
// and just setting by setNeedsDisplay() will not work.
if NSAppKitVersionNumber >= NSAppKitVersionNumber11_0 then
self.lclInvalidate
else
self.display;
end;
end;
function TCocoaCustomControl.lclGetCallback: ICommonCallback;
begin
Result := callback;
end;
procedure TCocoaCustomControl.lclClearCallback;
begin
callback := nil;
end;
function TCocoaCustomControl.lclIsMouseInAuxArea(Event: NSevent): Boolean;
begin
if auxMouseByParent and Assigned(superview) then
Result := superview.lclIsMouseInAuxArea(Event)
else
Result := false;
end;
procedure TCocoaCustomControl.mouseDown(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited mouseDown(event);
end;
procedure TCocoaCustomControl.mouseDragged(event: NSEvent);
begin
window.disableCursorRects;
if not Assigned(callback) or not callback.MouseMove(event) then
// calling inherited causes the drag event to be passed to the
// parent controls
//inherited mouseDragged(event);
;
end;
procedure TCocoaCustomControl.mouseEntered(event: NSEvent);
begin
inherited mouseEntered(event);
end;
procedure TCocoaCustomControl.mouseExited(event: NSEvent);
begin
inherited mouseExited(event);
end;
procedure TCocoaCustomControl.mouseMoved(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseMove(event) then
inherited mouseMoved(event);
end;
procedure TCocoaCustomControl.scrollWheel(event: NSEvent);
begin
if Assigned(self.lclGetTarget) and (self.lclGetTarget is TScrollingWinControl) then begin
inherited scrollWheel(event);
if Assigned(callback) then
callback.scrollWheel(event);
end else begin
if NOT Assigned(callback) or NOT callback.scrollWheel(event) then
inherited scrollWheel(event);
end;
end;
procedure TCocoaCustomControl.setFrame(aframe: NSRect);
begin
if NSEqualRects(aframe, frame) then Exit;
if isdrawing>0 then
faileddraw := true;
inherited setFrame(aframe);
// it actually should come from a notifcation
if Assigned(callback) then callback.frameDidChange(self);
end;
procedure TCocoaCustomControl.mouseUp(event: NSEvent);
begin
if not window.areCursorRectsEnabled then
begin
window.enableCursorRects;
window.resetCursorRects;
CursorHelper.SetCursorAtMousePos;
end;
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited mouseUp(event);
end;
procedure TCocoaCustomControl.rightMouseDown(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited rightMouseDown(event);
end;
procedure TCocoaCustomControl.rightMouseUp(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited rightMouseUp(event);
end;
procedure TCocoaCustomControl.rightMouseDragged(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseMove(event) then
inherited rightMouseDragged(event);
end;
procedure TCocoaCustomControl.otherMouseDown(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited otherMouseDown(event);
end;
procedure TCocoaCustomControl.otherMouseUp(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited otherMouseUp(event);
end;
procedure TCocoaCustomControl.otherMouseDragged(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseMove(event) then
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.

View File

@ -0,0 +1,316 @@
unit CocoaFullControlEdit;
{$mode objfpc}{$H+}
{$modeswitch objectivec2}
{$interfaces corba}
interface
uses
Classes, SysUtils,
LazUTF8,
CocoaAll, CocoaPrivate, CocoaCustomControl, CocoaUtils;
type
{ ICocoaIMEControl }
// IME Parameters for Cocoa Interface internal and LCL Full Control Edit
// intentionally keep the Record type, emphasizing that it is only a simple type,
// only used as parameters, dont put into logical functions
TCocoaIMEParameters = record
text: ShortString; // Marked Text
textCharLength: Integer; // length in code point
textByteLength: Integer; // length in bytes
textNSLength: Integer; // length in code unit (NSString)
selectedStart: Integer; // selected range start in code point
selectedLength: Integer; // selected range length in code point
eatAmount: Integer; // delete char out of Marked Text
isFirstCall: Boolean; // if first in the IME session
end;
// the LCL Component that need Cocoa IME support need to
// implement this simple interface
// class LazSynCocoaIMM in SynEdit Component for reference
// class ATSynEdit_Adapter_CocoaIME in ATSynEdit Component for reference
ICocoaIMEControl = interface
procedure IMESessionBegin;
procedure IMESessionEnd;
procedure IMEUpdateIntermediateText( var params: TCocoaIMEParameters );
procedure IMEInsertFinalText( var params: TCocoaIMEParameters );
function IMEGetTextBound( var params: TCocoaIMEParameters ) : TRect;
end;
{ TCocoaFullControlEdit }
// backend of LCL Full Control Edit Component (such as SynEdit/ATSynEdit)
// 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, NSTextInputClientProtocol)
private
_currentParams: TCocoaIMEParameters;
_currentMarkedText: NSString;
public
imeHandler: ICocoaIMEControl;
public
procedure keyDown(theEvent: NSEvent); override;
procedure mouseDown(event: NSEvent); override;
procedure mouseUp(event: NSEvent); override;
function resignFirstResponder: ObjCBOOL; 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
{ TCocoaIMEParameters }
// set text and length in params
procedure setIMEParamsText( var params: TCocoaIMEParameters; const nsText: NSString );
begin
params.text := NSStringToString( nsText );
params.textCharLength := UTF8Length( params.text );
params.textByteLength := Length( params.text );
params.textNSLength := nsText.length;
end;
// set selected range in code point
procedure setIMESelectedRange( var params: TCocoaIMEParameters; const nsText: NSString; range: NSRange );
begin
if range.location<>NSNotFound then
begin
if range.location>nsText.length then
range.location:= 0;
if range.location+range.length>nsText.length then
range.length:= nsText.length-range.location;
end;
if range.location=NSNotFound then
params.selectedStart:= 0
else
params.selectedStart:= UTF8Length( nsText.substringToIndex(range.location).UTF8String );
if range.length=0 then
params.selectedLength:= 0
else
params.selectedLength:= UTF8Length( nsText.substringWithRange(range).UTF8String );
end;
{ TCocoaFullControlEdit }
{
for IME Key Down:
Key step for IME (such as Chinese/Japanese/Korean and DeadKeys)
1. forward key event to NSInputContext
2. NSInputContext will call TCocoaFullControlEdit(NSTextControlClient)
and then call LCL via imeHandler
}
procedure TCocoaFullControlEdit.keyDown(theEvent: NSEvent);
begin
inputContext.handleEvent(theEvent);
end;
{
for IME Close:
1. mouseDown() will not be called when click in the IME Popup Window,
so it must be clicking outside the IME Popup Windows,
which should end the IME input
2. Cocoa had called setMarkedText_selectedRange_replacementRange()
or insertText_replacementRange() first, then mouseDown() here
3. NSInputContext.handleEvent() just close IME window here
4. LCL actually handle mouse event
}
procedure TCocoaFullControlEdit.mouseDown(event: NSEvent);
begin
inputContext.handleEvent(event);
Inherited;
end;
procedure TCocoaFullControlEdit.mouseUp(event: NSEvent);
begin
inputContext.handleEvent(event);
Inherited;
end;
// prevent switch to other control when in IME input state
function TCocoaFullControlEdit.resignFirstResponder: ObjCBOOL;
begin
Result := not hasMarkedText();
end;
function isIMEDuplicateCall( const newParams, currentParams: TCocoaIMEParameters ) : Boolean;
begin
Result:= false;
if newParams.isFirstCall then exit;
if newParams.text <> currentParams.text then exit;
if newParams.selectedStart<>currentParams.selectedStart then exit;
if newParams.selectedLength<>currentParams.selectedLength then exit;
Result:= true;
end;
// send Marked/Intermediate Text to LCL Edit Control which has IME Handler
// Key step for IME (such as Chinese/Japanese/Korean and DeadKeys)
procedure TCocoaFullControlEdit.setMarkedText_selectedRange_replacementRange(
aString: id; newRange: NSRange; replacementRange: NSRange);
var
params : TCocoaIMEParameters;
nsText : NSString;
begin
params.isFirstCall:= not hasMarkedText();
// no markedText before, the first call
if params.isFirstCall then imeHandler.IMESessionBegin;
// get IME Intermediate Text
nsText:= getNSStringObject( aString );
setIMEParamsText( params, nsText );
// some IME want to select subRange of Intermediate Text
// such as Japanese
setIMESelectedRange( params, nsText, newRange );
// some IME incorrectly call setMarkedText() twice with the same parameters
if isIMEDuplicateCall( params, _currentParams ) then
exit;
// some IME want to eat some chars
// such as inputting DeadKeys
if replacementRange.location<>NSNotFound then
params.eatAmount:= 1 - replacementRange.location
else
params.eatAmount:= 0;
// Key Step to update(display) Marked/Intermediate Text
imeHandler.IMEUpdateIntermediateText( params );
if params.textNSLength=0 then
begin
// cancel Marked/Intermediate Text
imeHandler.IMESessionEnd;
unmarkText;
end
else
begin
// update Marked/Intermediate Text internal status
_currentParams:= params;
_currentMarkedText.release;
_currentMarkedText:= nsText;
_currentMarkedText.retain;
end;
end;
{
send final Text to LCL Edit Control which has IME Handler
Key step for IME (such as Chinese/Japanese/Korean and DeadKeys)
1. if in IME input state, handle text via imeHandler.IMEInsertFinalText()
2. otherwise via lclGetCallback.InputClientInsertText,
mainly for maximum forward compatibility with TCocoaCustomControl
}
procedure TCocoaFullControlEdit.insertText_replacementRange(aString: id;
replacementRange: NSRange);
var
params: TCocoaIMEParameters;
nsText : NSString;
begin
params.isFirstCall:= not hasMarkedText();
// IME final text
nsText:= getNSStringObject( aString );
setIMEParamsText( params, nsText );
// some IME want to eat some chars, such as inputting DeadKeys
if replacementRange.location<>NSNotFound then
params.eatAmount:= 1 - replacementRange.location
else
params.eatAmount:= 0;
if (not params.isFirstCall) or (params.eatAmount<>0) then
// insert IME final text
imeHandler.IMEInsertFinalText( params )
else
// insert normal text (without IME) by LCLControl.IntfUTF8KeyPress()
lclGetCallback.InputClientInsertText( params.text );
if not params.isFirstCall then
begin
imeHandler.IMESessionEnd;
unmarkText;
end;
end;
// cursor tracking
function TCocoaFullControlEdit.firstRectForCharacterRange_actualRange(
aRange: NSRange; actualRange: NSRangePointer): NSRect;
var
params: TCocoaIMEParameters;
rect : TRect;
begin
params:= _currentParams;
setIMESelectedRange( params, _currentMarkedText, aRange );
params.isFirstCall:= not hasMarkedText();
rect:= imeHandler.IMEGetTextBound( params );
LCLToNSRect( rect, NSGlobalScreenBottom, Result );
end;
procedure TCocoaFullControlEdit.unmarkText;
begin
setIMEParamsText( _currentParams, nil );
_currentParams.selectedStart:= 0;
_currentParams.selectedLength:= 0;
_currentParams.eatAmount:= 0;
_currentParams.isFirstCall:= true;
_currentMarkedText.release;
_currentMarkedText:= nil;
end;
function TCocoaFullControlEdit.markedRange: NSRange;
begin
if _currentParams.textNSLength=0 then
Result:= NSMakeRange( NSNotFound, 0 )
else
Result:= NSMakeRange( 0, _currentParams.textNSLength );
end;
function TCocoaFullControlEdit.selectedRange: NSRange;
begin
if _currentParams.textNSLength=0 then
Result:= NSMakeRange( 0, 0 )
else
Result:= NSMakeRange( _currentParams.selectedStart, _currentParams.selectedLength );
end;
function TCocoaFullControlEdit.hasMarkedText: LCLObjCBoolean;
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.

View File

@ -112,147 +112,6 @@ type
procedure lclSetEnabled(AEnabled: Boolean); message 'lclSetEnabled:'; reintroduce;
end;
{ TCocoaCustomControl }
TCocoaCustomControl = objcclass(NSControl, NSTextInputClientProtocol)
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;
procedure dealloc; override;
function acceptsFirstResponder: LCLObjCBoolean; override;
procedure drawRect(dirtyRect: NSRect); override;
function lclGetCallback: ICommonCallback; override;
procedure lclClearCallback; override;
function lclIsMouseInAuxArea(Event: NSevent): Boolean; override;
// mouse
function acceptsFirstMouse(event: NSEvent): LCLObjCBoolean; override;
procedure mouseDown(event: NSEvent); override;
procedure mouseUp(event: NSEvent); override;
procedure rightMouseDown(event: NSEvent); override;
procedure rightMouseUp(event: NSEvent); override;
procedure rightMouseDragged(event: NSEvent); override;
procedure otherMouseDown(event: NSEvent); override;
procedure otherMouseUp(event: NSEvent); override;
procedure otherMouseDragged(event: NSEvent); override;
procedure mouseDragged(event: NSEvent); override;
procedure mouseEntered(event: NSEvent); override;
procedure mouseExited(event: NSEvent); override;
procedure mouseMoved(event: NSEvent); override;
procedure scrollWheel(event: NSEvent); override;
// nsview
procedure setFrame(aframe: NSRect); override;
// value
procedure setStringValue(avalue: NSString); override;
function stringValue: NSString; override;
procedure addSubView(aview: NSView); override;
public
// NSTextInputClientProtocol related.
// implements a base NSTextInputClient for non-editable LCL CustomControl,
// like Form, Grid, ListView, that are not system control and not FullEditControl.
// 1. when using IME in these controls, a temporary and one-time editor is shown
// at the bottom of the control, supporting IME such as Chinese.
// 2. refers to MacOS Finder, when using IME in the file list view,
// a small window will pop up at the bottom of the screen for input.
// the text can then be used for filename starting character match.
// 3. it is useful for implementing IME support for controls that do not
// have a text input window.
procedure keyDown(theEvent: NSEvent); override;
procedure insertText_replacementRange (aString: id; replacementRange: NSRange);
procedure setMarkedText_selectedRange_replacementRange (aString: id; selectedRange: NSRange; replacementRange: NSRange);
procedure unmarkText;
function selectedRange: NSRange;
function markedRange: NSRange;
function hasMarkedText: LCLObjCBoolean;
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;
{ ICocoaIMEControl }
// IME Parameters for Cocoa Interface internal and LCL Full Control Edit
// intentionally keep the Record type, emphasizing that it is only a simple type,
// only used as parameters, dont put into logical functions
TCocoaIMEParameters = record
text: ShortString; // Marked Text
textCharLength: Integer; // length in code point
textByteLength: Integer; // length in bytes
textNSLength: Integer; // length in code unit (NSString)
selectedStart: Integer; // selected range start in code point
selectedLength: Integer; // selected range length in code point
eatAmount: Integer; // delete char out of Marked Text
isFirstCall: Boolean; // if first in the IME session
end;
// the LCL Component that need Cocoa IME support need to
// implement this simple interface
// class LazSynCocoaIMM in SynEdit Component for reference
// class ATSynEdit_Adapter_CocoaIME in ATSynEdit Component for reference
ICocoaIMEControl = interface
procedure IMESessionBegin;
procedure IMESessionEnd;
procedure IMEUpdateIntermediateText( var params: TCocoaIMEParameters );
procedure IMEInsertFinalText( var params: TCocoaIMEParameters );
function IMEGetTextBound( var params: TCocoaIMEParameters ) : TRect;
end;
{ TCocoaFullControlEdit }
// backend of LCL Full Control Edit Component (such as SynEdit/ATSynEdit)
// 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)
private
_currentParams: TCocoaIMEParameters;
_currentMarkedText: NSString;
public
imeHandler: ICocoaIMEControl;
public
procedure keyDown(theEvent: NSEvent); override;
procedure mouseDown(event: NSEvent); override;
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;
end;
TStatusItemData = record
Text : NSString;
Width : Integer;
Align : TAlignment;
end;
TStatusItemDataArray = array of TStatusItemData;
TCocoaStatusBar = objcclass(TCocoaCustomControl)
public
//StatusBar : TStatusBar;
barcallback : IStatusBarCallback;
panelCell : NSCell;
procedure drawRect(dirtyRect: NSRect); override;
procedure dealloc; override;
end;
{ TCocoaGroupBox }
TCocoaGroupBox = objcclass(NSBox)
@ -510,592 +369,6 @@ begin
callback := nil;
end;
{ TCocoaCustomControl }
function getNSStringObject( const aString: id ) : NSString;
begin
if aString.isKindOfClass( NSAttributedString ) then
Result:= NSAttributedString( aString ).string_
else
Result:= NSString( aString );
end;
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;
if ASsigned(avalue) then
fstr:=avalue.copyWithZone(nil)
else
fstr:=nil;
inherited setStringValue(avalue);
end;
function TCocoaCustomControl.stringValue: NSString;
begin
Result:=fstr;
end;
procedure TCocoaCustomControl.addSubView(aview: NSView);
var
mask: NSUInteger;
begin
inherited addSubView(aview);
if Assigned(aview) then
begin
// forcing LCL compatible "auto-move" mode. Sticking to left/top corner
if not autoresizesSubviews then
{$ifdef BOOLFIX}
setAutoresizesSubviews_(Ord(true));
{$else}
setAutoresizesSubviews(true);
{$endif}
if self.isFlipped then
mask:= NSViewMaxYMargin or NSViewMaxXMargin
else
mask:= NSViewMinYMargin or NSViewMaxXMargin;
aview.setAutoresizingMask(mask);
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;
inherited dealloc;
end;
function TCocoaCustomControl.acceptsFirstResponder: LCLObjCBoolean;
begin
Result := True;
end;
function TCocoaCustomControl.acceptsFirstMouse(event: NSEvent): LCLObjCBoolean;
begin
// By default, a mouse-down event in a window that isnt the key window
// simply brings the window forward and makes it key; the event isnt sent
// to the NSView object over which the mouse click occurs. The NSView can
// claim an initial mouse-down event, however, by overriding acceptsFirstMouse: to return YES.
// see bug #33034
Result:=true;
end;
procedure TCocoaCustomControl.drawRect(dirtyRect: NSRect);
begin
if isdrawing=0 then faileddraw:=false;
inc(isdrawing);
inherited drawRect(dirtyRect);
// Implement Color property
if Assigned(callback) then
callback.DrawBackground(NSGraphicsContext.currentContext, bounds, dirtyRect);
if CheckMainThread and Assigned(callback) then
callback.Draw(NSGraphicsContext.currentContext, bounds, dirtyRect);
dec(isdrawing);
if (isdrawing=0) and (faileddraw) then
begin
// if the frame is changed during the Paint event,
// redrawing must be triggered to ensure that the
// correctly updated NSGraphicsContext is obtained.
// 1. for the new version of macOS, just set setNeedsDisplay()
// 2. for older versions of macOS, display() must be called by itself,
// and just setting by setNeedsDisplay() will not work.
if NSAppKitVersionNumber >= NSAppKitVersionNumber11_0 then
self.lclInvalidate
else
self.display;
end;
end;
function TCocoaCustomControl.lclGetCallback: ICommonCallback;
begin
Result := callback;
end;
procedure TCocoaCustomControl.lclClearCallback;
begin
callback := nil;
end;
function TCocoaCustomControl.lclIsMouseInAuxArea(Event: NSevent): Boolean;
begin
if auxMouseByParent and Assigned(superview) then
Result := superview.lclIsMouseInAuxArea(Event)
else
Result := false;
end;
procedure TCocoaCustomControl.mouseDown(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited mouseDown(event);
end;
procedure TCocoaCustomControl.mouseDragged(event: NSEvent);
begin
window.disableCursorRects;
if not Assigned(callback) or not callback.MouseMove(event) then
// calling inherited causes the drag event to be passed to the
// parent controls
//inherited mouseDragged(event);
;
end;
procedure TCocoaCustomControl.mouseEntered(event: NSEvent);
begin
inherited mouseEntered(event);
end;
procedure TCocoaCustomControl.mouseExited(event: NSEvent);
begin
inherited mouseExited(event);
end;
procedure TCocoaCustomControl.mouseMoved(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseMove(event) then
inherited mouseMoved(event);
end;
procedure TCocoaCustomControl.scrollWheel(event: NSEvent);
begin
if Assigned(self.lclGetTarget) and (self.lclGetTarget is TScrollingWinControl) then begin
inherited scrollWheel(event);
if Assigned(callback) then
callback.scrollWheel(event);
end else begin
if NOT Assigned(callback) or NOT callback.scrollWheel(event) then
inherited scrollWheel(event);
end;
end;
procedure TCocoaCustomControl.setFrame(aframe: NSRect);
begin
if NSEqualRects(aframe, frame) then Exit;
if isdrawing>0 then
faileddraw := true;
inherited setFrame(aframe);
// it actually should come from a notifcation
if Assigned(callback) then callback.frameDidChange(self);
end;
procedure TCocoaCustomControl.mouseUp(event: NSEvent);
begin
if not window.areCursorRectsEnabled then
begin
window.enableCursorRects;
window.resetCursorRects;
CursorHelper.SetCursorAtMousePos;
end;
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited mouseUp(event);
end;
procedure TCocoaCustomControl.rightMouseDown(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited rightMouseDown(event);
end;
procedure TCocoaCustomControl.rightMouseUp(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited rightMouseUp(event);
end;
procedure TCocoaCustomControl.rightMouseDragged(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseMove(event) then
inherited rightMouseDragged(event);
end;
procedure TCocoaCustomControl.otherMouseDown(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited otherMouseDown(event);
end;
procedure TCocoaCustomControl.otherMouseUp(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
inherited otherMouseUp(event);
end;
procedure TCocoaCustomControl.otherMouseDragged(event: NSEvent);
begin
if not Assigned(callback) or not callback.MouseMove(event) then
inherited otherMouseDragged(event);
end;
{ TCocoaIMEParameters }
// set text and length in params
procedure setIMEParamsText( var params: TCocoaIMEParameters; const nsText: NSString );
begin
params.text := NSStringToString( nsText );
params.textCharLength := UTF8Length( params.text );
params.textByteLength := Length( params.text );
params.textNSLength := nsText.length;
end;
// set selected range in code point
procedure setIMESelectedRange( var params: TCocoaIMEParameters; const nsText: NSString; range: NSRange );
begin
if range.location<>NSNotFound then
begin
if range.location>nsText.length then
range.location:= 0;
if range.location+range.length>nsText.length then
range.length:= nsText.length-range.location;
end;
if range.location=NSNotFound then
params.selectedStart:= 0
else
params.selectedStart:= UTF8Length( nsText.substringToIndex(range.location).UTF8String );
if range.length=0 then
params.selectedLength:= 0
else
params.selectedLength:= UTF8Length( nsText.substringWithRange(range).UTF8String );
end;
{ TCocoaFullControlEdit }
{
for IME Key Down:
Key step for IME (such as Chinese/Japanese/Korean and DeadKeys)
1. forward key event to NSInputContext
2. NSInputContext will call TCocoaFullControlEdit(NSTextControlClient)
and then call LCL via imeHandler
}
procedure TCocoaFullControlEdit.keyDown(theEvent: NSEvent);
begin
inputContext.handleEvent(theEvent);
end;
{
for IME Close:
1. mouseDown() will not be called when click in the IME Popup Window,
so it must be clicking outside the IME Popup Windows,
which should end the IME input
2. Cocoa had called setMarkedText_selectedRange_replacementRange()
or insertText_replacementRange() first, then mouseDown() here
3. NSInputContext.handleEvent() just close IME window here
4. LCL actually handle mouse event
}
procedure TCocoaFullControlEdit.mouseDown(event: NSEvent);
begin
inputContext.handleEvent(event);
Inherited;
end;
procedure TCocoaFullControlEdit.mouseUp(event: NSEvent);
begin
inputContext.handleEvent(event);
Inherited;
end;
// prevent switch to other control when in IME input state
function TCocoaFullControlEdit.resignFirstResponder: ObjCBOOL;
begin
Result := not hasMarkedText();
end;
function isIMEDuplicateCall( const newParams, currentParams: TCocoaIMEParameters ) : Boolean;
begin
Result:= false;
if newParams.isFirstCall then exit;
if newParams.text <> currentParams.text then exit;
if newParams.selectedStart<>currentParams.selectedStart then exit;
if newParams.selectedLength<>currentParams.selectedLength then exit;
Result:= true;
end;
// send Marked/Intermediate Text to LCL Edit Control which has IME Handler
// Key step for IME (such as Chinese/Japanese/Korean and DeadKeys)
procedure TCocoaFullControlEdit.setMarkedText_selectedRange_replacementRange(
aString: id; newRange: NSRange; replacementRange: NSRange);
var
params : TCocoaIMEParameters;
nsText : NSString;
begin
params.isFirstCall:= not hasMarkedText();
// no markedText before, the first call
if params.isFirstCall then imeHandler.IMESessionBegin;
// get IME Intermediate Text
nsText:= getNSStringObject( aString );
setIMEParamsText( params, nsText );
// some IME want to select subRange of Intermediate Text
// such as Japanese
setIMESelectedRange( params, nsText, newRange );
// some IME incorrectly call setMarkedText() twice with the same parameters
if isIMEDuplicateCall( params, _currentParams ) then
exit;
// some IME want to eat some chars
// such as inputting DeadKeys
if replacementRange.location<>NSNotFound then
params.eatAmount:= 1 - replacementRange.location
else
params.eatAmount:= 0;
// Key Step to update(display) Marked/Intermediate Text
imeHandler.IMEUpdateIntermediateText( params );
if params.textNSLength=0 then
begin
// cancel Marked/Intermediate Text
imeHandler.IMESessionEnd;
unmarkText;
end
else
begin
// update Marked/Intermediate Text internal status
_currentParams:= params;
_currentMarkedText.release;
_currentMarkedText:= nsText;
_currentMarkedText.retain;
end;
end;
{
send final Text to LCL Edit Control which has IME Handler
Key step for IME (such as Chinese/Japanese/Korean and DeadKeys)
1. if in IME input state, handle text via imeHandler.IMEInsertFinalText()
2. otherwise via lclGetCallback.InputClientInsertText,
mainly for maximum forward compatibility with TCocoaCustomControl
}
procedure TCocoaFullControlEdit.insertText_replacementRange(aString: id;
replacementRange: NSRange);
var
params: TCocoaIMEParameters;
nsText : NSString;
begin
params.isFirstCall:= not hasMarkedText();
// IME final text
nsText:= getNSStringObject( aString );
setIMEParamsText( params, nsText );
// some IME want to eat some chars, such as inputting DeadKeys
if replacementRange.location<>NSNotFound then
params.eatAmount:= 1 - replacementRange.location
else
params.eatAmount:= 0;
if (not params.isFirstCall) or (params.eatAmount<>0) then
// insert IME final text
imeHandler.IMEInsertFinalText( params )
else
// insert normal text (without IME) by LCLControl.IntfUTF8KeyPress()
lclGetCallback.InputClientInsertText( params.text );
if not params.isFirstCall then
begin
imeHandler.IMESessionEnd;
unmarkText;
end;
end;
// cursor tracking
function TCocoaFullControlEdit.firstRectForCharacterRange_actualRange(
aRange: NSRange; actualRange: NSRangePointer): NSRect;
var
params: TCocoaIMEParameters;
rect : TRect;
begin
params:= _currentParams;
setIMESelectedRange( params, _currentMarkedText, aRange );
params.isFirstCall:= not hasMarkedText();
rect:= imeHandler.IMEGetTextBound( params );
LCLToNSRect( rect, NSGlobalScreenBottom, Result );
end;
procedure TCocoaFullControlEdit.unmarkText;
begin
setIMEParamsText( _currentParams, nil );
_currentParams.selectedStart:= 0;
_currentParams.selectedLength:= 0;
_currentParams.eatAmount:= 0;
_currentParams.isFirstCall:= true;
_currentMarkedText.release;
_currentMarkedText:= nil;
end;
function TCocoaFullControlEdit.markedRange: NSRange;
begin
if _currentParams.textNSLength=0 then
Result:= NSMakeRange( NSNotFound, 0 )
else
Result:= NSMakeRange( 0, _currentParams.textNSLength );
end;
function TCocoaFullControlEdit.selectedRange: NSRange;
begin
if _currentParams.textNSLength=0 then
Result:= NSMakeRange( 0, 0 )
else
Result:= NSMakeRange( _currentParams.selectedStart, _currentParams.selectedLength );
end;
function TCocoaFullControlEdit.hasMarkedText: LCLObjCBoolean;
begin
Result:= ( _currentParams.textNSLength > 0 );
end;
{ LCLObjectExtension }
function LCLObjectExtension.lclIsEnabled: Boolean;
@ -1531,72 +804,6 @@ begin
Point.Y := Point.Y - dlt.Top;
end;
{ TCocoaStatusBar }
procedure TCocoaStatusBar.drawRect(dirtyRect: NSRect);
var
R : TRect;
i : Integer;
cs : NSString;
nr : NSRect;
dr : NSRect;
al : TAlignment;
x : Integer;
txt : string;
cnt : Integer;
w : Integer;
const
CocoaAlign: array [TAlignment] of Integer = (NSNaturalTextAlignment, NSRightTextAlignment, NSCenterTextAlignment);
begin
if not Assigned(barcallback) then Exit;
if not Assigned(panelCell) then Exit;
panelCell.setControlView(Self);
r := lclClientFrame();
nr.origin.y := 0;
nr.size.height := self.lclFrame.Height;
x:=0;
cnt := barcallback.GetBarsCount;
for i:=0 to cnt - 1 do begin
txt := '';
w := 0;
al := taLeftJustify;
if not barcallback.GetBarItem(i, txt, w, al) then Continue;
if i = cnt - 1 then w := r.Right - x;
nr.size.width := w;
nr.origin.x := x;
// dr - draw rect. should be 1 pixel wider
// and 1 pixel taller, than the actual rect.
// to produce a better visual effect
dr := nr;
dr.size.width := dr.size.width + 1;
dr.size.height := dr.size.height + 1;
dr.origin.y := dr.origin.y-1;
cs := NSStringUtf8(txt);
panelCell.setTitle(cs);
panelCell.setAlignment(CocoaAlign[al]);
panelCell.drawWithFrame_inView(dr, Self);
cs.release;
barcallback.DrawPanel(i, NSRectToRect(nr));
inc(x, w);
if x > r.Right then break; // no place left
end;
end;
procedure TCocoaStatusBar.dealloc;
begin
if Assigned(panelCell) then panelCell.release;
inherited;
end;
{ TCocoaProgressIndicator }
function TCocoaProgressIndicator.acceptsFirstResponder: LCLObjCBoolean;

View File

@ -0,0 +1,186 @@
unit CocoaStatusBar;
{$mode objfpc}{$H+}
{$modeswitch objectivec2}
{$interfaces corba}
interface
uses
Classes, SysUtils,
LCLType, LMessages, LCLMessageGlue, ComCtrls,
CocoaAll, CocoaPrivate, CocoaCallback, CocoaGDIObjects ,CocoaWSCommon, CocoaUtils,
CocoaCustomControl;
type
{ IStatusBarCallback }
IStatusBarCallback = interface {(ICommonCallback) // not needed to inherit from ICommonCallback}
function GetBarsCount: Integer;
//todo: consider the use Cocoa native types, instead of FPC TAlignment
function GetBarItem(idx: Integer; var txt: String;
var width: Integer; var align: TAlignment): Boolean;
procedure DrawPanel(idx: Integer; const r: TRect);
end;
TStatusItemData = record
Text : NSString;
Width : Integer;
Align : TAlignment;
end;
TStatusItemDataArray = array of TStatusItemData;
TCocoaStatusBar = objcclass(TCocoaCustomControl)
public
//StatusBar : TStatusBar;
barcallback : IStatusBarCallback;
panelCell : NSCell;
procedure drawRect(dirtyRect: NSRect); override;
procedure dealloc; override;
end;
{ TStatusBarCallback }
TStatusBarCallback = class(TLCLCommonCallback, IStatusBarCallback, ICommonCallback)
function GetBarsCount: Integer;
function GetBarItem(idx: Integer; var txt: String; var width: Integer; var align: TAlignment): Boolean;
procedure DrawPanel(idx: Integer; const r: TRect);
end;
implementation
{ TCocoaStatusBar }
procedure TCocoaStatusBar.drawRect(dirtyRect: NSRect);
var
R : TRect;
i : Integer;
cs : NSString;
nr : NSRect;
dr : NSRect;
al : TAlignment;
x : Integer;
txt : string;
cnt : Integer;
w : Integer;
const
CocoaAlign: array [TAlignment] of Integer = (NSNaturalTextAlignment, NSRightTextAlignment, NSCenterTextAlignment);
begin
if not Assigned(barcallback) then Exit;
if not Assigned(panelCell) then Exit;
panelCell.setControlView(Self);
r := lclClientFrame();
nr.origin.y := 0;
nr.size.height := self.lclFrame.Height;
x:=0;
cnt := barcallback.GetBarsCount;
for i:=0 to cnt - 1 do begin
txt := '';
w := 0;
al := taLeftJustify;
if not barcallback.GetBarItem(i, txt, w, al) then Continue;
if i = cnt - 1 then w := r.Right - x;
nr.size.width := w;
nr.origin.x := x;
// dr - draw rect. should be 1 pixel wider
// and 1 pixel taller, than the actual rect.
// to produce a better visual effect
dr := nr;
dr.size.width := dr.size.width + 1;
dr.size.height := dr.size.height + 1;
dr.origin.y := dr.origin.y-1;
cs := NSStringUtf8(txt);
panelCell.setTitle(cs);
panelCell.setAlignment(CocoaAlign[al]);
panelCell.drawWithFrame_inView(dr, Self);
cs.release;
barcallback.DrawPanel(i, NSRectToRect(nr));
inc(x, w);
if x > r.Right then break; // no place left
end;
end;
procedure TCocoaStatusBar.dealloc;
begin
if Assigned(panelCell) then panelCell.release;
inherited;
end;
{ TStatusBarCallback }
function TStatusBarCallback.GetBarsCount: Integer;
begin
if TStatusBar(Target).SimplePanel
then Result := 1
else Result := TStatusBar(Target).Panels.Count;
end;
function TStatusBarCallback.GetBarItem(idx: Integer; var txt: String;
var width: Integer; var align: TAlignment): Boolean;
var
sb : TStatusBar;
begin
sb := TStatusBar(Target);
if sb.SimplePanel then begin
Result := idx = 0;
if not Result then Exit;
txt := sb.SimpleText;
width := sb.Width;
align := taLeftJustify; // todo: RTL?
end else begin
Result := (idx >= 0) and (idx < sb.Panels.Count);
if not Result then Exit;
if sb.Panels[idx].Style = psOwnerDraw
then txt := ''
else txt := sb.Panels[idx].Text;
width := sb.Panels[idx].Width;
align := sb.Panels[idx].Alignment;
end;
end;
procedure TStatusBarCallback.DrawPanel(idx: Integer; const r: TRect);
var
sb : TStatusBar;
msg : TLMDrawItems;
ctx : TCocoaContext;
dr : TDrawItemStruct;
fr : TRect;
sv : Integer;
begin
sb := TStatusBar(Target);
if sb.SimplePanel then Exit;
if (idx<0) or (idx >= sb.Panels.Count) then Exit;
if sb.Panels[idx].Style <> psOwnerDraw then Exit;
ctx := TCocoaContext.Create(NSGraphicsContext.currentContext);
sv := ctx.SaveDC;
try
FillChar(msg, sizeof(msg), 0);
FillChar(dr, sizeof(dr), 0);
msg.Ctl := Target.Handle;
msg.Msg := LM_DRAWITEM;
msg.DrawItemStruct := @dr;
dr.itemID := idx;
dr._hDC := HDC(ctx);
dr.rcItem := r;
fr := NSView(Owner).lclFrame;
ctx.InitDraw(fr.Right-fr.Left, fr.Bottom-fr.Top);
LCLMessageGlue.DeliverMessage(Target, msg);
finally
ctx.RestoreDC(sv);
ctx.Free;
end;
end;
end.

View File

@ -24,7 +24,8 @@ interface
uses
Types, Classes, SysUtils,
MacOSAll, CocoaAll, CocoaUtils, CocoaPrivate, CocoaCallback, CocoaConst;
MacOSAll, CocoaAll, CocoaUtils, CocoaPrivate, CocoaCallback, CocoaConst,
CocoaCustomControl;
type

View File

@ -72,6 +72,8 @@ function StringRemoveAcceleration(const str: String): String;
function GetNSObjectWindow(obj: NSObject): NSWindow;
function getNSStringObject( const aString: id ) : NSString;
procedure SetNSText(text: NSText; const s: String); inline;
function GetNSText(text: NSText): string; inline;
@ -959,6 +961,14 @@ begin
Result:= str.Substring(0,posLeft).Trim;
end;
function getNSStringObject( const aString: id ) : NSString;
begin
if aString.isKindOfClass( NSAttributedString ) then
Result:= NSAttributedString( aString ).string_
else
Result:= NSString( aString );
end;
procedure SetNSText(text: NSText; const s: String); inline;
var
ns: NSString;

View File

@ -24,9 +24,9 @@ interface
uses
Types, Classes, SysUtils,
MacOSAll, CocoaAll, CocoaUtils, CocoaCursor,
cocoa_extra, CocoaPrivate, CocoaCallback, CocoaTextEdits, CocoaScrollers,
LCLType, LCLProc;
LCLType, LCLProc,
MacOSAll, CocoaAll, CocoaPrivate, CocoaCallback, cocoa_extra, CocoaUtils,
CocoaCursor, CocoaCustomControl, CocoaTextEdits, CocoaScrollers;
type
@ -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:';

View File

@ -15,18 +15,10 @@ uses
WSComCtrls,
MacOSAll, CocoaAll,
CocoaPrivate, CocoaCallback, CocoaWSCommon, CocoaGDIObjects, CocoaUtils,
CocoaTabControls, CocoaButtons;
CocoaTabControls, CocoaButtons, CocoaStatusBar;
type
{ TStatusBarCallback }
TStatusBarCallback = class(TLCLCommonCallback, IStatusBarCallback, ICommonCallback)
function GetBarsCount: Integer;
function GetBarItem(idx: Integer; var txt: String; var width: Integer; var align: TAlignment): Boolean;
procedure DrawPanel(idx: Integer; const r: TRect);
end;
{ TCocoaWSStatusBar }
TCocoaWSStatusBar = class(TWSStatusBar)
@ -270,72 +262,6 @@ begin
TCocoaStepper(AUpDown.Handle).setValueWraps(ADoWrap);
end;
{ TStatusBarCallback }
function TStatusBarCallback.GetBarsCount: Integer;
begin
if TStatusBar(Target).SimplePanel
then Result := 1
else Result := TStatusBar(Target).Panels.Count;
end;
function TStatusBarCallback.GetBarItem(idx: Integer; var txt: String;
var width: Integer; var align: TAlignment): Boolean;
var
sb : TStatusBar;
begin
sb := TStatusBar(Target);
if sb.SimplePanel then begin
Result := idx = 0;
if not Result then Exit;
txt := sb.SimpleText;
width := sb.Width;
align := taLeftJustify; // todo: RTL?
end else begin
Result := (idx >= 0) and (idx < sb.Panels.Count);
if not Result then Exit;
if sb.Panels[idx].Style = psOwnerDraw
then txt := ''
else txt := sb.Panels[idx].Text;
width := sb.Panels[idx].Width;
align := sb.Panels[idx].Alignment;
end;
end;
procedure TStatusBarCallback.DrawPanel(idx: Integer; const r: TRect);
var
sb : TStatusBar;
msg : TLMDrawItems;
ctx : TCocoaContext;
dr : TDrawItemStruct;
fr : TRect;
sv : Integer;
begin
sb := TStatusBar(Target);
if sb.SimplePanel then Exit;
if (idx<0) or (idx >= sb.Panels.Count) then Exit;
if sb.Panels[idx].Style <> psOwnerDraw then Exit;
ctx := TCocoaContext.Create(NSGraphicsContext.currentContext);
sv := ctx.SaveDC;
try
FillChar(msg, sizeof(msg), 0);
FillChar(dr, sizeof(dr), 0);
msg.Ctl := Target.Handle;
msg.Msg := LM_DRAWITEM;
msg.DrawItemStruct := @dr;
dr.itemID := idx;
dr._hDC := HDC(ctx);
dr.rcItem := r;
fr := NSView(Owner).lclFrame;
ctx.InitDraw(fr.Right-fr.Left, fr.Bottom-fr.Top);
LCLMessageGlue.DeliverMessage(Target, msg);
finally
ctx.RestoreDC(sv);
ctx.Free;
end;
end;
{ TLCLTabControlCallback }
function TLCLTabControlCallback.shouldSelectTabViewItem(aTabIndex: Integer): Boolean;

View File

@ -12,7 +12,7 @@ uses
WSControls, LCLType, LCLMessageGlue, LMessages, LCLProc, LCLIntf, Graphics, Forms,
StdCtrls,
CocoaAll, CocoaInt, CocoaConfig, CocoaPrivate, CocoaCallback, CocoaUtils,
CocoaScrollers, CocoaWSScrollers,
CocoaCustomControl, CocoaScrollers, CocoaWSScrollers, CocoaFullControlEdit,
CocoaGDIObjects, CocoaCursor, CocoaCaret, cocoa_extra;
type
@ -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;

View File

@ -30,8 +30,8 @@ uses
// widgetset
WSExtCtrls, WSLCLClasses,
// LCL Cocoa
CocoaPrivate, CocoaMenus, CocoaWSCommon, CocoaGDIObjects, CocoaScrollers,
Cocoa_Extra, CocoaUtils, CocoaConfig;
CocoaPrivate, CocoaWSCommon, CocoaGDIObjects, CocoaConfig, Cocoa_Extra,
CocoaCustomControl, CocoaScrollers, CocoaMenus, CocoaUtils;
type

View File

@ -32,7 +32,7 @@ uses
// LCL Cocoa
CocoaInt, CocoaConfig, CocoaPrivate, CocoaCallback, CocoaUtils, CocoaWSCommon, CocoaMenus,
CocoaGDIObjects,
CocoaWindows, CocoaScrollers, CocoaWSScrollers, cocoa_extra;
CocoaWindows, CocoaCustomControl, CocoaScrollers, CocoaWSScrollers, cocoa_extra;
type
{ TLCLWindowCallback }

View File

@ -7,7 +7,7 @@ interface
uses
Classes, LCLType, Controls, Forms,
CocoaAll, CocoaPrivate, CocoaScrollers, CocoaUtils;
CocoaAll, CocoaPrivate, CocoaCustomControl, CocoaScrollers, CocoaUtils;
type
{ ASyncLCLControlAdjustSizer }

View File

@ -37,7 +37,7 @@ uses
CocoaPrivate, CocoaCallback, CocoaListControl, CocoaTables,
CocoaConst, CocoaConfig, CocoaWSCommon, CocoaUtils,
CocoaGDIObjects, CocoaButtons, CocoaTextEdits,
CocoaScrollers, CocoaWSScrollers, Cocoa_Extra;
CocoaCustomControl, CocoaScrollers, CocoaWSScrollers, Cocoa_Extra;
type
@ -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;

View File

@ -131,7 +131,7 @@ end;"/>
<License Value="modified LGPL-2
"/>
<Version Major="3" Minor="99"/>
<Files Count="541">
<Files Count="544">
<Item1>
<Filename Value="carbon/agl.pp"/>
<AddToUsesPkgSection Value="False"/>
@ -2673,6 +2673,21 @@ end;"/>
<AddToUsesPkgSection Value="False"/>
<UnitName Value="cocoalistcontrol"/>
</Item541>
<Item542>
<Filename Value="cocoa/cocoafullcontroledit.pas"/>
<AddToUsesPkgSection Value="False"/>
<UnitName Value="cocoafullcontroledit"/>
</Item542>
<Item543>
<Filename Value="cocoa/cocoacustomcontrol.pas"/>
<AddToUsesPkgSection Value="False"/>
<UnitName Value="CocoaCustomControl"/>
</Item543>
<Item544>
<Filename Value="cocoa/cocoastatusbar.pas"/>
<AddToUsesPkgSection Value="False"/>
<UnitName Value="CocoaStatusBar"/>
</Item544>
</Files>
<CompatibilityMode Value="True"/>
<LazDoc Paths="../../docs/xml/lcl"/>