mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-25 00:59:09 +02:00
316 lines
9.6 KiB
ObjectPascal
316 lines
9.6 KiB
ObjectPascal
{
|
|
SynEdit MacOS IME Handler:
|
|
1. various IME are fully supported, such as Chinese/Japanese/Korean and DeadKeys
|
|
2. MultiCarets supported
|
|
3. GroupUndo or not are both fully supported
|
|
|
|
in order to be compatible with MultiCarets:
|
|
1. TSynEditUndoList.BeginBlock() cannot be used directly,
|
|
only TSynEditUndoList.GroupUndo and TSynEditUndoList.ForceGroupEnd()
|
|
can be combined to Undo
|
|
2. InsertTextAtCaret() cannot be used directly, only ecChar Command can be used
|
|
3. mabye the support for MultiSelections in MultiCaretsPlugin is not perfect.
|
|
for example, Shift+Arrow can only expand the Selection of the Main Caret,
|
|
but not other Carets
|
|
|
|
macOS Lookup Word supported:
|
|
1. implement ICocoaLookupWord in LazSynImeCocoa
|
|
2. ICocoaLookupWord and ICocoaIMEControl can be implemented in different classes.
|
|
considering that LazSynImeCocoa is relatively simple, it is appropriate to
|
|
implement both interfaces in LazSynImeCocoa.
|
|
}
|
|
|
|
unit LazSynCocoaIMM;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$interfaces corba}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils,
|
|
Graphics,
|
|
CocoaFullControlEdit,
|
|
SynEditMiscClasses, LazSynIMMBase, SynEditKeyCmds, SynEditTextBase;
|
|
|
|
type
|
|
{ LazSynImeCocoa }
|
|
|
|
LazSynImeCocoa = class( LazSynIme, ICocoaIMEControl, ICocoaLookupWord )
|
|
private
|
|
_undoList: TSynEditUndoList;
|
|
_IntermediateTextBeginPos: TPoint;
|
|
public
|
|
procedure IMESessionBegin;
|
|
procedure IMESessionEnd;
|
|
procedure IMEUpdateIntermediateText( var params: TCocoaIMEParameters );
|
|
procedure IMEInsertFinalText( var params: TCocoaIMEParameters );
|
|
function IMEGetTextBound( var params: TCocoaIMEParameters ): TRect;
|
|
public
|
|
procedure LWRowColForScreenPoint( var params: TCocoaLWParameters;
|
|
const screenPoint: TPoint );
|
|
procedure LWLineForRow( var params: TCocoaLWParameters );
|
|
function LWGetTextBound( var params: TCocoaLWParameters ): TRect;
|
|
function LWGetFont( var params: TCocoaLWParameters ): TFont;
|
|
private
|
|
procedure InsertTextAtCaret_CompatibleWithMultiCarets( var params: TCocoaIMEParameters );
|
|
procedure SelectText_CompatibleWithMultiCarets( var params: TCocoaIMEParameters );
|
|
function calcBound( var params: TCocoaIMEParameters ): TRect;
|
|
function PosToPixels( const pos: TPoint ): TPoint;
|
|
public
|
|
constructor Create(AOwner: TSynEditBase);
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
LCLType, LazUTF8, SynEdit, LazSynEditText;
|
|
|
|
procedure LazSynImeCocoa.IMESessionBegin;
|
|
begin
|
|
if FriendEdit.ReadOnly then exit;
|
|
DoIMEStarted;
|
|
end;
|
|
|
|
procedure LazSynImeCocoa.IMESessionEnd;
|
|
begin
|
|
if FriendEdit.ReadOnly then exit;
|
|
ViewedTextBuffer.RedoList.Clear; // clear Intermediate Text redo items
|
|
DoIMEEnded;
|
|
end;
|
|
|
|
{
|
|
update IME Intermediate Text, Key function for IME:
|
|
1. some IME do not have a popup window and rely on the Editor
|
|
to display the Intermediate Text
|
|
2. use selection to simulate Intermediate Text display in this implementation,
|
|
may be better to use TSynEditMarkup (slightly complicated)
|
|
3. it means completely cancel the IME session if Intermediate Text is empty
|
|
4. it's First call of IMEUpdateIntermediateText or IMEInsertFinalText
|
|
if isFirstCall=True
|
|
5. eat some chars if eatAmount>0 (such as DeadKeys)
|
|
}
|
|
procedure LazSynImeCocoa.IMEUpdateIntermediateText( var params: TCocoaIMEParameters );
|
|
var
|
|
groupUndoBefore: boolean;
|
|
begin
|
|
if FriendEdit.ReadOnly
|
|
then exit;
|
|
|
|
// clear last Intermediate Text
|
|
if not params.isFirstCall then
|
|
FriendEdit.Undo;
|
|
|
|
// length=0 means to completely cancel the IME session
|
|
if params.textCharLength=0 then
|
|
exit;
|
|
|
|
// save caret pos
|
|
_IntermediateTextBeginPos := FriendEdit.LogicalCaretXY;
|
|
|
|
// in order to be compatible with MultiCarets,
|
|
// TSynEditUndoList.BeginBlock() cannot be used directly,
|
|
// only TSynEditUndoList.GroupUndo and TSynEditUndoList.ForceGroupEnd()
|
|
// can be combined to Undo
|
|
groupUndoBefore:= _undoList.GroupUndo;
|
|
_undoList.GroupUndo:= true;
|
|
_undoList.ForceGroupEnd;
|
|
|
|
// need to eat some chars, such as DeadKeys
|
|
if params.eatAmount<>0 then
|
|
TSynEdit(FriendEdit).CommandProcessor(ecDeleteLastChar, #0, nil);
|
|
|
|
// in order to be compatible with MultiCarets,
|
|
// InsertTextAtCaret() cannot be used directly,
|
|
// only ecChar Command can be used indirectly
|
|
InsertTextAtCaret_CompatibleWithMultiCarets( params );
|
|
SelectText_CompatibleWithMultiCarets( params );
|
|
|
|
_undoList.GroupUndo:= groupUndoBefore;
|
|
end;
|
|
|
|
{
|
|
insert IME Final Text, Key function for IME:
|
|
1. called only when inputting via IME, otherwise handled by UTF8KeyPress()
|
|
2. when the IME input is finished, either IMEUpdateIntermediateText(with empty text)
|
|
is called, or IMEInsertFinalText(with final text) is called,
|
|
NOT the both
|
|
3. it's First call of IMEUpdateIntermediateText or IMEInsertFinalText
|
|
if isFirstCall=True
|
|
4. eat some chars if eatAmount>0 (such as DeadKeys)
|
|
}
|
|
procedure LazSynImeCocoa.IMEInsertFinalText( var params: TCocoaIMEParameters );
|
|
begin
|
|
if FriendEdit.ReadOnly then exit;
|
|
|
|
// clear Intermediate Text
|
|
if not params.isFirstCall then
|
|
FriendEdit.Undo;
|
|
|
|
// need to eat some chars, such as DeadKeys
|
|
if params.eatAmount<>0 then
|
|
TSynEdit(FriendEdit).CommandProcessor( ecDeleteLastChar, #0, nil );
|
|
|
|
InsertTextAtCaret_CompatibleWithMultiCarets( params );
|
|
end;
|
|
|
|
{
|
|
calc Intermediate Text bound:
|
|
1. return Intermediate Text bound when in IME inut state. it's possible
|
|
to only get the bound of the Intermediate Text in a subrange
|
|
(selectedStart and selectedLength)
|
|
2. return caret pos when not in IME input state
|
|
3. in Screen Pixels
|
|
}
|
|
function LazSynImeCocoa.IMEGetTextBound( var params: TCocoaIMEParameters ) : TRect;
|
|
begin
|
|
if not params.isFirstCall then
|
|
Result:= calcBound( params )
|
|
else
|
|
Result:= TRect.Create( Point(FriendEdit.CaretXPix,FriendEdit.CaretYPix), 0, FriendEdit.LineHeight );
|
|
|
|
Result:= FriendEdit.ClientToScreen( Result );
|
|
end;
|
|
|
|
procedure LazSynImeCocoa.LWRowColForScreenPoint(
|
|
var params: TCocoaLWParameters; const screenPoint: TPoint);
|
|
var
|
|
localPoint: TPoint;
|
|
logicalPoint: TPoint;
|
|
lineText: String;
|
|
begin
|
|
localPoint:= FriendEdit.ScreenToClient( screenPoint );
|
|
logicalPoint:= TSynEdit(FriendEdit).PixelsToLogicalPos( localPoint );
|
|
params.row:= logicalPoint.Y;
|
|
if params.row > 0 then
|
|
params.row:= params.row - 1;
|
|
lineText:= FriendEdit.Lines[params.row];
|
|
if (lineText.length>0) and (logicalPoint.x<=lineText.length+1) then begin
|
|
if logicalPoint.x > lineText.length then
|
|
logicalPoint.x:= lineText.length;
|
|
params.col:= UTF8CodepointCount( pchar(lineText), logicalPoint.x ) - 1;
|
|
end else begin
|
|
params.col:= -1;
|
|
end;
|
|
end;
|
|
|
|
procedure LazSynImeCocoa.LWLineForRow( var params: TCocoaLWParameters );
|
|
begin
|
|
params.text:= FriendEdit.Lines[params.row];
|
|
end;
|
|
|
|
function LazSynImeCocoa.LWGetTextBound( var params: TCocoaLWParameters
|
|
): TRect;
|
|
var
|
|
p1: TPoint;
|
|
p2: TPoint;
|
|
lineText: String;
|
|
col1Bytes: PtrInt;
|
|
col2Bytes: PtrInt;
|
|
begin
|
|
lineText:= FriendEdit.Lines[params.row];
|
|
|
|
|
|
col1Bytes:= UTF8CodepointToByteIndex( pchar(lineText),
|
|
lineText.Length, params.col );
|
|
col2Bytes:= col1Bytes + UTF8CodepointToByteIndex( pchar(lineText)+col1Bytes,
|
|
lineText.Length-col1Bytes, params.length );
|
|
// two vertexs in bytes
|
|
p1:= Point( col1Bytes + 1, params.row + 1 );
|
|
p2:= Point( col2Bytes + 1, params.row + 1 );
|
|
|
|
// two vertexs in pixels
|
|
p1:= PosToPixels( p1 );
|
|
p2:= PosToPixels( p2 );
|
|
p2.Y:= p2.Y + FriendEdit.LineHeight - FriendEdit.ExtraLineSpacing;
|
|
|
|
// client rect in pixels
|
|
Result:= TRect.Create( p1 , p2 );
|
|
Result:= FriendEdit.ClientToScreen( Result );
|
|
end;
|
|
|
|
function LazSynImeCocoa.LWGetFont( var params: TCocoaLWParameters ): TFont;
|
|
begin
|
|
Result:= FriendEdit.Font;
|
|
end;
|
|
|
|
procedure LazSynImeCocoa.InsertTextAtCaret_CompatibleWithMultiCarets( var params: TCocoaIMEParameters );
|
|
var
|
|
i: integer;
|
|
c: integer;
|
|
ch: TUTF8Char;
|
|
begin
|
|
if params.textByteLength=0 then Exit;
|
|
i:=1;
|
|
while( i<=params.textByteLength ) do
|
|
begin
|
|
c:= Utf8CodePointLen( @params.text[i], params.textByteLength-i+1, false );
|
|
ch:= Copy( params.text, i, c );
|
|
TSynEdit(FriendEdit).CommandProcessor( ecChar, ch, nil );
|
|
inc( i, c );
|
|
end;
|
|
end;
|
|
|
|
procedure LazSynImeCocoa.SelectText_CompatibleWithMultiCarets( var params: TCocoaIMEParameters );
|
|
var
|
|
i: Integer;
|
|
start: Integer;
|
|
length: Integer;
|
|
begin
|
|
if params.selectedLength=0 then begin
|
|
start:= 0;
|
|
length:= params.textCharLength;
|
|
end else begin
|
|
start:= params.selectedStart;
|
|
length:= params.selectedLength;
|
|
end;
|
|
|
|
for i:=params.textCharLength-start downto 1 do TSynEdit(FriendEdit).CommandProcessor( ecLeft, #0, nil );
|
|
for i:=length downto 1 do TSynEdit(FriendEdit).CommandProcessor( ecSelRight, #0, nil );
|
|
end;
|
|
|
|
function LazSynImeCocoa.calcBound( var params: TCocoaIMEParameters ) : TRect;
|
|
var
|
|
p1: TPoint;
|
|
p2: TPoint;
|
|
begin
|
|
// two vertexs in bytes
|
|
p1:= _IntermediateTextBeginPos;
|
|
p2:= p1;
|
|
p1.X:= p1.X + UTF8CodepointToByteIndex( pchar(@params.text[1]),
|
|
params.textByteLength, params.selectedStart );
|
|
p2.X:= p1.X + UTF8CodepointToByteIndex( pchar(@params.text[1])+p1.X,
|
|
params.textByteLength-p1.X, params.selectedLength );
|
|
|
|
// two vertexs in pixels
|
|
p1:= PosToPixels( p1 );
|
|
p2:= PosToPixels( p2 );
|
|
p2.Y:= p2.Y + FriendEdit.LineHeight;
|
|
|
|
// client rect in pixels
|
|
Result:= TRect.Create( p1 , p2 );
|
|
end;
|
|
|
|
function LazSynImeCocoa.PosToPixels( const pos: TPoint ) : TPoint;
|
|
begin
|
|
Result:= FriendEdit.LogicalToPhysicalPos( pos );
|
|
Result:= FriendEdit.TextXYToScreenXY( Result );
|
|
Result:= TSynEdit(FriendEdit).ScreenXYToPixels( Result );
|
|
end;
|
|
|
|
constructor LazSynImeCocoa.Create( AOwner: TSynEditBase );
|
|
begin
|
|
Inherited;
|
|
_undoList:= ViewedTextBuffer.UndoList;
|
|
end;
|
|
|
|
destructor LazSynImeCocoa.Destroy;
|
|
begin
|
|
_undoList:= nil;
|
|
Inherited;
|
|
end;
|
|
|
|
end.
|
|
|