1
0
mirror of https://github.com/salvadordf/CEF4Delphi.git synced 2025-02-12 10:26:05 +02:00
CEF4Delphi/source/uCEFOSRIMEHandler.pas
salvadordf ca8bc9dff4 Added cef4delphi.chm help file
Added the PDS file to extract the HTML Help files using PasDoc
Added more XML documentation
Fixed some XML errors.
Removed the license copy from the pas units.
Updated the LICENSE.md file
2023-08-09 19:38:57 +02:00

707 lines
20 KiB
ObjectPascal

unit uCEFOSRIMEHandler;
{$IFDEF FPC}
{$MODE OBJFPC}{$H+}
{$ENDIF}
{$I cef.inc}
interface
uses
{$IFDEF DELPHI16_UP}
{$IFDEF MSWINDOWS}WinApi.Windows,{$ENDIF} System.Classes, System.Math, WinApi.imm,
{$ELSE}
{$IFDEF MSWINDOWS}Windows, imm, {$IFDEF FPC}imm_dyn,{$ENDIF}{$ENDIF} Classes, Math,
{$ENDIF}
uCEFTypes;
const
DEFAULT_BLINK_UNDERLINE_COLOR = $FF000000; // Black SkColor
DEFAULT_BLINK_BACKGROUND_COLOR = $00000000; // White SkColor
DEFAULT_BLINK_UNDERLINE_STYLE = CEF_CUS_SOLID;
KEYBOARD_LAYOUT_EN_US = $0409;
type
{$IFNDEF MSWINDOWS}
{$IFDEF FPC}
// Define some types to compile in Linux with Lazarus
HWND = NativeUInt;
LANGID = Word;
HIMC = Integer;
LPARAM = NativeInt;
WParam = NativeUInt;
PWChar = ^WideChar;
{$ENDIF}
{$ENDIF}
TCEFOSRIMEHandler = class
protected
FHWND : HWND;
FCompositionRange : TCefRange;
FCursorIndex : cardinal;
FIMERect : TCefRect;
FSystemCaret : boolean;
FInputLanguageID : LANGID;
FCompositionBounds : TCefRectDynArray;
FIsComposing : boolean;
FIMM32DLL : THandle;
function GetPrimaryLangID : word;
function GetSubLangID : word;
function GetInitialized : boolean;
procedure GetCompositionInfo(imc : HIMC; aParam : LPARAM; var composition_text : ustring; var underlines : TCefCompositionUnderlineDynArray; var composition_start : integer);
function GetString(imc : HIMC; aParam : WParam; aType : integer; var aResult : ustring) : boolean;
{$IFDEF MSWINDOWS}
function IsSelectionAttribute(aAttribute : AnsiChar) : boolean;
{$ENDIF}
procedure GetCompositionSelectionRange(imc : HIMC; var target_start, target_end : cardinal);
procedure GetCompositionUnderlines(imc : HIMC; target_start, target_end : cardinal; var underlines : TCefCompositionUnderlineDynArray);
public
constructor Create(aHWND : HWND);
destructor Destroy; override;
{$IFDEF MSWINDOWS}
procedure SetInputLanguage;
{$ENDIF}
procedure CreateImeWindow;
procedure DestroyImeWindow;
procedure CleanupComposition;
procedure ResetComposition;
function GetResult(aParam : LPARAM; var aResult : ustring) : boolean;
function GetComposition(aParam : LPARAM; var composition_text : ustring; var underlines : TCefCompositionUnderlineDynArray; var composition_start : integer) : boolean;
procedure EnableIME;
procedure DisableIME;
procedure CancelIME;
procedure UpdateCaretPosition(index : cardinal);
procedure ChangeCompositionRange(const selection_range : TCefRange; const character_bounds : TCefRectDynArray);
procedure MoveImeWindow;
property IsComposing : boolean read FIsComposing;
property InputLanguageID : LANGID read FInputLanguageID;
property PrimaryLangID : word read GetPrimaryLangID;
property SubLangID : word read GetSubLangID;
property Initialized : boolean read GetInitialized;
end;
// Original Chromium source code :
// https://source.chromium.org/chromium/chromium/src/+/main:ui/base/ime/win/imm32_manager.cc
// Original CEF source code :
// https://bitbucket.org/chromiumembedded/cef/src/master/tests/cefclient/browser/osr_ime_handler_win.cc
implementation
uses
{$IFDEF DELPHI16_UP}
System.SysUtils,
{$ELSE}
SysUtils,
{$ENDIF}
uCEFMiscFunctions;
constructor TCEFOSRIMEHandler.Create(aHWND : HWND);
begin
inherited Create;
FHWND := aHWND;
FCompositionRange.from := 0;
FCompositionRange.to_ := 0;
FCursorIndex := high(cardinal);
FIMERect.x := -1;
FIMERect.y := -1;
FIMERect.width := 0;
FIMERect.height := 0;
FSystemCaret := False;
FCompositionBounds := nil;
FIsComposing := False;
FIMM32DLL := 0;
{$IFDEF MSWINDOWS}
FInputLanguageID := LANG_USER_DEFAULT;
{$IFNDEF FPC}
FIMM32DLL := SafeLoadLibrary('imm32.dll');
{$ENDIF}
{$ENDIF}
end;
destructor TCEFOSRIMEHandler.Destroy;
begin
DestroyImeWindow;
{$IFDEF MSWINDOWS}
if (FIMM32DLL <> 0) then
begin
FreeLibrary(FIMM32DLL);
FIMM32DLL := 0;
end;
{$ENDIF}
if (FCompositionBounds <> nil) then
begin
Finalize(FCompositionBounds);
FCompositionBounds := nil;
end;
inherited Destroy;
end;
function TCEFOSRIMEHandler.GetPrimaryLangID : word;
begin
Result := FInputLanguageID and $03FF;
end;
function TCEFOSRIMEHandler.GetSubLangID : word;
begin
Result := (FInputLanguageID and $FC00) shr 10;
end;
function TCEFOSRIMEHandler.GetInitialized : boolean;
begin
{$IFDEF MSWINDOWS}
{$IFNDEF FPC}
Result := (FIMM32DLL <> 0);
{$ELSE}
Result := True;
{$ENDIF}
{$ELSE}
Result := False;
{$ENDIF}
end;
procedure TCEFOSRIMEHandler.GetCompositionInfo( imc : HIMC;
aParam : LPARAM;
var composition_text : ustring;
var underlines : TCefCompositionUnderlineDynArray;
var composition_start : integer);
var
TempLen, TempTargetStart, TempTargetEnd : cardinal;
i : integer;
begin
if (underlines <> nil) then
begin
Finalize(underlines, 0);
underlines := nil;
end;
TempLen := length(composition_text);
TempTargetStart := TempLen;
TempTargetEnd := TempLen;
{$IFDEF MSWINDOWS}
if ((aParam and GCS_COMPATTR) <> 0) then
GetCompositionSelectionRange(imc, TempTargetStart, TempTargetEnd);
if ((aParam and CS_NOMOVECARET) = 0) and ((aParam and GCS_CURSORPOS) <> 0) then
composition_start := ImmGetCompositionString(imc, GCS_CURSORPOS, nil, 0)
else
composition_start := 0;
if ((aParam and GCS_COMPCLAUSE) <> 0) then
GetCompositionUnderlines(imc, TempTargetStart, TempTargetEnd, underlines);
{$ENDIF}
if (underlines = nil) or (length(underlines) = 0) then
begin
i := 0;
if (TempTargetStart > 0) then inc(i);
if (TempTargetEnd > TempTargetStart) then inc(i);
if (TempTargetEnd < TempLen) then inc(i);
if (i > 0) then
begin
SetLength(underlines, i);
i := 0;
if (TempTargetStart > 0) then
begin
underlines[i].color := DEFAULT_BLINK_UNDERLINE_COLOR;
underlines[i].background_color := DEFAULT_BLINK_BACKGROUND_COLOR;
underlines[i].range.from := 0;
underlines[i].range.to_ := TempTargetStart;
underlines[i].thick := 0;
underlines[i].style := DEFAULT_BLINK_UNDERLINE_STYLE;
inc(i);
end;
if (TempTargetEnd > TempTargetStart) then
begin
underlines[i].color := DEFAULT_BLINK_UNDERLINE_COLOR;
underlines[i].background_color := DEFAULT_BLINK_BACKGROUND_COLOR;
underlines[i].range.from := TempTargetStart;
underlines[i].range.to_ := TempTargetEnd;
underlines[i].thick := 1;
underlines[i].style := DEFAULT_BLINK_UNDERLINE_STYLE;
inc(i);
end;
if (TempTargetEnd < TempLen) then
begin
underlines[i].color := DEFAULT_BLINK_UNDERLINE_COLOR;
underlines[i].background_color := DEFAULT_BLINK_BACKGROUND_COLOR;
underlines[i].range.from := TempTargetEnd;
underlines[i].range.to_ := TempLen;
underlines[i].thick := 0;
underlines[i].style := DEFAULT_BLINK_UNDERLINE_STYLE;
end;
end;
end;
end;
function TCEFOSRIMEHandler.GetString( imc : HIMC;
aParam : WParam;
aType : integer;
var aResult : ustring) : boolean;
{$IFDEF MSWINDOWS}
var
TempStringSize : integer;
TempBuffer : PWideChar;
{$ENDIF}
begin
Result := False;
{$IFDEF MSWINDOWS}
if ((aParam and aType) = 0) then exit;
TempStringSize := ImmGetCompositionStringW(imc, aType, nil, 0);
if (TempStringSize <= 0) then exit;
TempStringSize := TempStringSize + SizeOf(WideChar);
TempBuffer := nil;
try
try
GetMem(TempBuffer, TempStringSize);
FillChar(TempBuffer^, TempStringSize, 0);
ImmGetCompositionStringW(imc, aType, TempBuffer, TempStringSize);
aResult := TempBuffer;
Result := True;
except
on e : exception do
if CustomExceptionHandler('TCEFOSRIMEHandler.GetString', e) then raise;
end;
finally
if (TempBuffer <> nil) then
FreeMem(TempBuffer);
end;
{$ENDIF}
end;
{$IFDEF MSWINDOWS}
function TCEFOSRIMEHandler.IsSelectionAttribute(aAttribute : AnsiChar) : boolean;
begin
Result := (ord(aAttribute) = ATTR_TARGET_CONVERTED) or
(ord(aAttribute) = ATTR_TARGET_NOTCONVERTED);
end;
{$ENDIF}
procedure TCEFOSRIMEHandler.GetCompositionSelectionRange(imc : HIMC; var target_start, target_end : cardinal);
{$IFDEF MSWINDOWS}
var
i, TempStart, TempEnd, TempBufferLen : integer;
TempBuffer : array of AnsiChar;
{$ENDIF}
begin
{$IFDEF MSWINDOWS}
TempBuffer := nil;
try
try
TempBufferLen := ImmGetCompositionStringW(imc, GCS_COMPATTR, nil, 0);
if (TempBufferLen > 0) then
begin
SetLength(TempBuffer, TempBufferLen);
for i := 0 to pred(TempBufferLen) do TempBuffer[i] := #0;
TempBufferLen := ImmGetCompositionStringW(imc, GCS_COMPATTR, @TempBuffer[0], TempBufferLen);
TempStart := 0;
while (TempStart < TempBufferLen) do
if IsSelectionAttribute(TempBuffer[TempStart]) then
break
else
inc(TempStart);
TempEnd := TempStart;
while (TempEnd < TempBufferLen) do
if not(IsSelectionAttribute(TempBuffer[TempEnd])) then
break
else
inc(TempEnd);
target_start := TempStart;
target_end := TempEnd;
end;
except
on e : exception do
if CustomExceptionHandler('TCEFOSRIMEHandler.GetCompositionSelectionRange', e) then raise;
end;
finally
if (TempBuffer <> nil) then
begin
Finalize(TempBuffer);
TempBuffer := nil;
end;
end;
{$ENDIF}
end;
procedure TCEFOSRIMEHandler.GetCompositionUnderlines( imc : HIMC;
target_start : cardinal;
target_end : cardinal;
var underlines : TCefCompositionUnderlineDynArray);
{$IFDEF MSWINDOWS}
var
i, j, TempSize : integer;
TempUndLen : integer;
TempLen : integer;
TempBuffer : array of cardinal;
{$ENDIF}
begin
{$IFDEF MSWINDOWS}
TempBuffer := nil;
try
try
TempSize := ImmGetCompositionStringW(imc, GCS_COMPCLAUSE, nil, 0);
TempLen := TempSize div SizeOf(cardinal);
if (TempLen > 0) then
begin
SetLength(TempBuffer, TempLen);
for i := 0 to pred(TempLen) do TempBuffer[i] := 0;
ImmGetCompositionStringW(imc, GCS_COMPCLAUSE, @TempBuffer[0], TempSize);
if (underlines <> nil) then
TempUndLen := length(underlines)
else
TempUndLen := 0;
SetLength(underlines, TempUndLen + pred(TempSize));
i := 0;
while (i < pred(TempLen)) do
begin
j := i + TempUndLen;
underlines[j].range.from := TempBuffer[i];
underlines[j].range.to_ := TempBuffer[succ(i)];
underlines[j].color := DEFAULT_BLINK_UNDERLINE_COLOR;
underlines[j].background_color := DEFAULT_BLINK_BACKGROUND_COLOR;
underlines[j].style := DEFAULT_BLINK_UNDERLINE_STYLE;
if (underlines[j].range.from >= target_start) and
(underlines[j].range.to_ <= target_end) then
underlines[j].thick := 1
else
underlines[j].thick := 0;
inc(i);
end;
end;
except
on e : exception do
if CustomExceptionHandler('TCEFOSRIMEHandler.GetCompositionUnderlines', e) then raise;
end;
finally
if (TempBuffer <> nil) then
begin
Finalize(TempBuffer);
TempBuffer := nil;
end;
end;
{$ENDIF}
end;
{$IFDEF MSWINDOWS}
procedure TCEFOSRIMEHandler.SetInputLanguage;
var
TempName : array[0..KL_NAMELENGTH] of char;
TempLayout : string;
i : integer;
TempID : LANGID;
begin
TempID := KEYBOARD_LAYOUT_EN_US;
try
try
for i := 0 to KL_NAMELENGTH do TempName[i] := #0;
if GetKeyboardLayoutName(TempName) then
begin
TempLayout := trim(copy(TempName, KL_NAMELENGTH div 2, KL_NAMELENGTH));
if (length(TempLayout) > 0) then TempID := StrToInt('$' + TempLayout);
end;
except
on e : exception do
if CustomExceptionHandler('TCEFOSRIMEHandler.SetInputLanguage', e) then raise;
end;
finally
FInputLanguageID := TempID;
end;
end;
{$ENDIF}
procedure TCEFOSRIMEHandler.CreateImeWindow;
begin
{$IFDEF MSWINDOWS}
if ((PrimaryLangID = LANG_CHINESE) or (PrimaryLangID = LANG_JAPANESE)) and
not(FSystemCaret) and
CreateCaret(FHWND, 0, 1, 1) then
FSystemCaret := True;
{$ENDIF}
end;
procedure TCEFOSRIMEHandler.DestroyImeWindow;
begin
if FSystemCaret then
begin
{$IFDEF MSWINDOWS}
DestroyCaret();
{$ENDIF}
FSystemCaret := False;
end;
end;
procedure TCEFOSRIMEHandler.CleanupComposition;
var
TempIMC : HIMC;
begin
if Initialized and FIsComposing then
begin
{$IFDEF MSWINDOWS}
TempIMC := ImmGetContext(FHWND);
if (TempIMC <> 0) then
try
ImmNotifyIME(TempIMC, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
finally
ImmReleaseContext(FHWND, TempIMC);
end;
ResetComposition;
{$ENDIF}
end;
end;
procedure TCEFOSRIMEHandler.ResetComposition;
begin
FIsComposing := False;
FCursorIndex := high(cardinal);
end;
function TCEFOSRIMEHandler.GetResult(aParam : LPARAM; var aResult : ustring) : boolean;
var
TempIMC : HIMC;
begin
Result := False;
if not(Initialized) then exit;
{$IFDEF MSWINDOWS}
TempIMC := ImmGetContext(FHWND);
if (TempIMC <> 0) then
try
Result := GetString(TempIMC, aParam, GCS_RESULTSTR, aResult);
finally
ImmReleaseContext(FHWND, TempIMC);
end;
{$ENDIF}
end;
function TCEFOSRIMEHandler.GetComposition( aParam : LPARAM;
var composition_text : ustring;
var underlines : TCefCompositionUnderlineDynArray;
var composition_start : integer) : boolean;
var
TempIMC : HIMC;
begin
Result := False;
if not(Initialized) then exit;
{$IFDEF MSWINDOWS}
TempIMC := ImmGetContext(FHWND);
if (TempIMC <> 0) then
try
if GetString(TempIMC, aParam, GCS_COMPSTR, composition_text) then
begin
GetCompositionInfo(TempIMC, aParam, composition_text, underlines, composition_start);
FIsComposing := True;
Result := True;
end;
finally
ImmReleaseContext(FHWND, TempIMC);
end;
{$ENDIF}
end;
procedure TCEFOSRIMEHandler.EnableIME;
begin
{$IFDEF MSWINDOWS}
if Initialized then
begin
{$IFDEF DELPHI22_UP}
ImmAssociateContextEx(FHWND, 0, IACE_DEFAULT);
{$ELSE}
ImmAssociateContext(FHWND, 0);
{$ENDIF}
end;
{$ENDIF}
end;
procedure TCEFOSRIMEHandler.DisableIME;
begin
{$IFDEF MSWINDOWS}
if Initialized then
begin
CleanupComposition();
{$IFDEF DELPHI22_UP}
ImmAssociateContextEx(FHWND, 0, 0);
{$ELSE}
ImmAssociateContext(FHWND, 0);
{$ENDIF}
end;
{$ENDIF}
end;
procedure TCEFOSRIMEHandler.CancelIME;
var
TempIMC : HIMC;
begin
if Initialized and FIsComposing then
begin
{$IFDEF MSWINDOWS}
TempIMC := ImmGetContext(FHWND);
if (TempIMC <> 0) then
try
ImmNotifyIME(TempIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
finally
ImmReleaseContext(FHWND, TempIMC);
end;
ResetComposition;
{$ENDIF}
end;
end;
procedure TCEFOSRIMEHandler.UpdateCaretPosition(index : cardinal);
begin
FCursorIndex := index;
MoveImeWindow();
end;
procedure TCEFOSRIMEHandler.ChangeCompositionRange(const selection_range : TCefRange;
const character_bounds : TCefRectDynArray);
var
i : integer;
begin
FCompositionRange := selection_range;
if (character_bounds <> nil) then
begin
SetLength(FCompositionBounds, length(character_bounds));
i := pred(length(character_bounds));
while (i >= 0) do
begin
FCompositionBounds[i] := character_bounds[i];
dec(i);
end;
end
else
begin
Finalize(FCompositionBounds);
FCompositionBounds := nil;
end;
MoveImeWindow();
end;
procedure TCEFOSRIMEHandler.MoveImeWindow;
{$IFDEF MSWINDOWS}
var
TempRect : TCefRect;
TempLocation : cardinal;
TempIMC : HIMC;
TempCandidatePos : TCandidateForm;
TempCandidateExc : TCandidateForm;
const
CARET_MARGIN = 1;
{$ENDIF}
begin
{$IFDEF MSWINDOWS}
if not(Initialized) and (GetFocus <> FHWND) then exit;
TempRect := FIMERect;
TempLocation := FCursorIndex;
if (TempLocation = high(cardinal)) then
TempLocation := FCompositionRange.from;
if (TempLocation >= FCompositionRange.from) then
TempLocation := TempLocation - FCompositionRange.from;
if (FCompositionBounds = nil) then
exit
else
if (TempLocation < cardinal(length(FCompositionBounds))) then
TempRect := FCompositionBounds[TempLocation]
else
if (length(FCompositionBounds) > 0) then
TempRect := FCompositionBounds[pred(length(FCompositionBounds))]
else
exit;
TempIMC := ImmGetContext(FHWND);
if (TempIMC <> 0) then
try
if (PrimaryLangID = LANG_CHINESE) then
begin
TempCandidatePos.dwIndex := 0;
TempCandidatePos.dwStyle := CFS_CANDIDATEPOS;
TempCandidatePos.ptCurrentPos.X := TempRect.x;
TempCandidatePos.ptCurrentPos.Y := TempRect.y;
TempCandidatePos.rcArea.Left := 0;
TempCandidatePos.rcArea.Top := 0;
TempCandidatePos.rcArea.Right := 0;
TempCandidatePos.rcArea.Bottom := 0;
ImmSetCandidateWindow(TempIMC, @TempCandidatePos);
end;
if FSystemCaret then
case PrimaryLangID of
LANG_JAPANESE : SetCaretPos(TempRect.x, TempRect.y + TempRect.height);
else SetCaretPos(TempRect.x, TempRect.y);
end;
if (PrimaryLangID = LANG_KOREAN) then
TempRect.y := TempRect.y + CARET_MARGIN;
TempCandidateExc.dwIndex := 0;
TempCandidateExc.dwStyle := CFS_EXCLUDE;
TempCandidateExc.ptCurrentPos.X := TempRect.x;
TempCandidateExc.ptCurrentPos.Y := TempRect.y;
TempCandidateExc.rcArea.Left := TempRect.x;
TempCandidateExc.rcArea.Top := TempRect.y;
TempCandidateExc.rcArea.Right := TempRect.x + TempRect.width;
TempCandidateExc.rcArea.Bottom := TempRect.y + TempRect.height;
ImmSetCandidateWindow(TempIMC, @TempCandidateExc);
finally
ImmReleaseContext(FHWND, TempIMC);
end;
{$ENDIF}
end;
end.