mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-09-03 01:00:34 +02:00
Cocoa: decouple CustomControl/FullEditControl and etc, Merge branch 'cocoa/decouple'
This commit is contained in:
commit
9a92e07736
@ -21,7 +21,7 @@ unit LazSynCocoaIMM;
|
||||
interface
|
||||
|
||||
uses
|
||||
CocoaPrivate,
|
||||
CocoaFullControlEdit,
|
||||
Classes, SysUtils,
|
||||
SynEditMiscClasses, LazSynIMMBase, SynEditKeyCmds, SynEditTextBase;
|
||||
|
||||
|
@ -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.
|
||||
|
450
lcl/interfaces/cocoa/cocoacustomcontrol.pas
Normal file
450
lcl/interfaces/cocoa/cocoacustomcontrol.pas
Normal 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 isn’t the key window
|
||||
// simply brings the window forward and makes it key; the event isn’t 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.
|
||||
|
316
lcl/interfaces/cocoa/cocoafullcontroledit.pas
Normal file
316
lcl/interfaces/cocoa/cocoafullcontroledit.pas
Normal 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, don‘t 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.
|
||||
|
@ -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, don‘t 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 isn’t the key window
|
||||
// simply brings the window forward and makes it key; the event isn’t 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;
|
||||
|
186
lcl/interfaces/cocoa/cocoastatusbar.pas
Normal file
186
lcl/interfaces/cocoa/cocoastatusbar.pas
Normal 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.
|
||||
|
@ -24,7 +24,8 @@ interface
|
||||
|
||||
uses
|
||||
Types, Classes, SysUtils,
|
||||
MacOSAll, CocoaAll, CocoaUtils, CocoaPrivate, CocoaCallback, CocoaConst;
|
||||
MacOSAll, CocoaAll, CocoaUtils, CocoaPrivate, CocoaCallback, CocoaConst,
|
||||
CocoaCustomControl;
|
||||
|
||||
type
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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:';
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -7,7 +7,7 @@ interface
|
||||
|
||||
uses
|
||||
Classes, LCLType, Controls, Forms,
|
||||
CocoaAll, CocoaPrivate, CocoaScrollers, CocoaUtils;
|
||||
CocoaAll, CocoaPrivate, CocoaCustomControl, CocoaScrollers, CocoaUtils;
|
||||
|
||||
type
|
||||
{ ASyncLCLControlAdjustSizer }
|
||||
|
@ -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;
|
||||
|
@ -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"/>
|
||||
|
Loading…
Reference in New Issue
Block a user