1 unit LazSynIMM;
2 
3 {$mode objfpc}{$H+}
4 
5 {off $DEFINE WinIMEDebug}
6 {off $DEFINE WinIMEFullDeferOverwrite} // In Full IME do "overwrite selecton" when IME finishes (normally done at start)
7 {off $DEFINE WinIMEFullOverwriteSkipUndo} // In Full IME undo "overwrite selecton", if IME cancelled
8 
9 interface
10 
11 uses
12   windows, imm, Classes, SysUtils, Controls, LazLoggerBase, LCLType, LazUTF8, Graphics,
13   SynEditMiscClasses, SynTextDrawer, SynEditPointClasses, SynEditMarkupSelection,
14   SynEditMarkup, SynEditTypes, SynEditKeyCmds, LazSynEditText, SynEditTextBase,
15   SynEditMiscProcs;
16 
17 {$IFDEF WINCE} {$IF (FPC_FULLVERSION < 20700)}
ImmSetCompositionFontAnull18 function ImmSetCompositionFontA(_himc:HIMC; lplf:LPLOGFONT):BOOL; external ImmDLL name 'ImmSetCompositionFontA';
19 const
20   WM_IME_REQUEST = $0288;
21 {$ENDIF}{$ENDIF}
22 type
23 
24   { LazSynIme }
25 
26   LazSynIme = class(TSynEditFriend)
27   private
28     FInvalidateLinesMethod: TInvalidateLines;
29     FOnIMEEnd: TNotifyEvent;
30     FOnIMEStart: TNotifyEvent;
31     FIMEActive: Boolean;
32   protected
33     FInCompose: Boolean;
34     procedure InvalidateLines(FirstLine, LastLine: integer);
35     procedure StopIme(Success: Boolean); virtual;
36     procedure DoIMEStarted;
37     procedure DoIMEEnded;
38   public
39     constructor Create(AOwner: TSynEditBase); reintroduce;
40     procedure WMImeRequest(var Msg: TMessage); virtual;
41     procedure WMImeNotify(var Msg: TMessage); virtual;
42     procedure WMImeComposition(var Msg: TMessage); virtual;
43     procedure WMImeStartComposition(var Msg: TMessage); virtual;
44     procedure WMImeEndComposition(var Msg: TMessage); virtual;
45     procedure FocusKilled; virtual;
46     property InvalidateLinesMethod : TInvalidateLines write FInvalidateLinesMethod;
47     property OnIMEStart: TNotifyEvent read FOnIMEStart write FOnIMEStart;
48     property OnIMEEnd: TNotifyEvent read FOnIMEEnd write FOnIMEEnd;
49   end;
50 
51   { LazSynImeSimple }
52 
53   LazSynImeSimple = class(LazSynIme)
54   private
55     FImeBlockSelection: TSynEditSelection;
56     FImeWinX, FImeWinY: Integer;
57     FTextDrawer: TheTextDrawer;
58     procedure SetTextDrawer(AValue: TheTextDrawer);
59     procedure UpdateImeWinXY(aX, aY: Integer; aImc: HIMC = 0; aForce: Boolean = False);
60     procedure UpdateImeWinFont(aImc: HIMC = 0);
61     procedure DoStatusChanged(Sender: TObject; Changes: TSynStatusChanges);
62     procedure DoDrawerFontChanged(Sender: TObject);
63     procedure DoOnCommand(Sender: TObject; AfterProcessing: boolean; var Handled: boolean;
64       var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer;
65       HandlerData: pointer);
66     procedure DoOnMouse(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,
67       Y: Integer);
68   public
69     constructor Create(AOwner: TSynEditBase);
70     destructor Destroy; override;
71     procedure WMImeNotify(var Msg: TMessage); override;
72     procedure WMImeComposition(var Msg: TMessage); override;
73     procedure WMImeStartComposition(var Msg: TMessage); override;
74     procedure WMImeEndComposition(var Msg: TMessage); override;
75     procedure FocusKilled; override;
76     property TextDrawer: TheTextDrawer read FTextDrawer write SetTextDrawer;
77   end;
78 
79   { LazSynImeFull }
80 
81   LazSynImeFull = class(LazSynIme)
82   private
83     FAdjustLeftCharForTargets: Boolean;
84     FLeftPosForTarget, FRightPosForTarget: Integer;
85     FImeBlockSelection, FImeBlockSelection2, FImeBlockSelection3: TSynEditSelection; // TODO: create a custom markup
86     FImeMarkupSelection, FImeMarkupSelection2, FImeMarkupSelection3: TSynEditMarkupSelection;
87     FInImeMsg: Boolean;
88     {$IFnDEF WinIMEFullOverwriteSkipUndo}
89     FUndoStamp1, FUndoStamp2: TSynEditUndoGroup;
90     FNeedUndoOnCancel: Boolean;
91     {$ENDIF}
92     {$IFDEF WinIMEFullDeferOverwrite}
93     FHasPersistLock: Boolean;
94     {$ENDIF}
95     procedure SetImeTempText(const s: string);
96     procedure DoOnCommand(Sender: TObject; AfterProcessing: boolean; var Handled: boolean;
97       var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer;
98       HandlerData: pointer);
99     procedure DoOnMouse(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,
100       Y: Integer);
101     procedure DoStatusChanged(Sender: TObject; Changes: TSynStatusChanges);
102     procedure EnsureLeftChar;
103   protected
104     procedure StopIme(Success: Boolean); override;
105   public
106     constructor Create(AOwner: TSynEditBase);
107     destructor Destroy; override;
108     procedure WMImeRequest(var Msg: TMessage); override;
109     procedure WMImeNotify(var Msg: TMessage); override;
110     procedure WMImeComposition(var Msg: TMessage); override;
111     procedure WMImeStartComposition(var Msg: TMessage); override;
112     procedure WMImeEndComposition(var Msg: TMessage); override;
113     procedure FocusKilled; override;
114   public
115     property AdjustLeftCharForTargets: Boolean read FAdjustLeftCharForTargets write FAdjustLeftCharForTargets;
116   end;
117 
118 implementation
119 uses SynEdit;
120 
121 { LazSynIme }
122 
123 procedure LazSynIme.InvalidateLines(FirstLine, LastLine: integer);
124 begin
125   FInvalidateLinesMethod(FirstLine, LastLine);
126 end;
127 
128 procedure LazSynIme.StopIme(Success: Boolean);
129 var
130   imc: HIMC;
131 begin
132   if (not FInCompose) or (not FriendEdit.HandleAllocated) then exit;
133 
134   imc := ImmGetContext(FriendEdit.Handle);
135   if (imc <> 0) then begin
136     if Success then
137       ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0)
138     else
139       ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
140     ImmReleaseContext(FriendEdit.Handle, imc);
141   end;
142   DoIMEEnded;
143 end;
144 
145 procedure LazSynIme.DoIMEStarted;
146 begin
147   if FIMEActive then
148     exit;
149   FIMEActive := True;
150   if FOnIMEStart <> nil then
151     FOnIMEStart(FriendEdit);
152 end;
153 
154 procedure LazSynIme.DoIMEEnded;
155 begin
156   if not FIMEActive then
157     exit;
158   FIMEActive := False;
159   if FOnIMEEnd <> nil then
160     FOnIMEEnd(FriendEdit);
161 end;
162 
163 constructor LazSynIme.Create(AOwner: TSynEditBase);
164 begin
165   FriendEdit := AOwner;
166 end;
167 
168 procedure LazSynIme.WMImeRequest(var Msg: TMessage);
169 begin
170 end;
171 
172 procedure LazSynIme.WMImeComposition(var Msg: TMessage);
173 begin
174 end;
175 
176 procedure LazSynIme.WMImeNotify(var Msg: TMessage);
177 begin
178 end;
179 
180 procedure LazSynIme.WMImeStartComposition(var Msg: TMessage);
181 begin
182 end;
183 
184 procedure LazSynIme.WMImeEndComposition(var Msg: TMessage);
185 begin
186 end;
187 
188 procedure LazSynIme.FocusKilled;
189 begin
190 end;
191 
192 { LazSynImeSimple }
193 
194 procedure LazSynImeSimple.DoStatusChanged(Sender: TObject; Changes: TSynStatusChanges);
195 begin
196   UpdateImeWinXY(TCustomSynEdit(FriendEdit).CaretXPix, TCustomSynEdit(FriendEdit).CaretYPix);
197   if Changes * [scCaretX, scCaretY] <> [] then
198     StopIme(False);
199 end;
200 
201 procedure LazSynImeSimple.DoDrawerFontChanged(Sender: TObject);
202 var
203   imc: HIMC;
204 begin
205   if not FriendEdit.HandleAllocated then
206     exit;
207   imc := ImmGetContext(FriendEdit.Handle);
208   if (imc <> 0) then begin
209     UpdateImeWinFont(imc);
210     UpdateImeWinXY(TCustomSynEdit(FriendEdit).CaretXPix, TCustomSynEdit(FriendEdit).CaretYPix, imc);
211     ImmReleaseContext(FriendEdit.Handle, imc);
212   end;
213 end;
214 
215 procedure LazSynImeSimple.DoOnCommand(Sender: TObject; AfterProcessing: boolean;
216   var Handled: boolean; var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer;
217   HandlerData: pointer);
218 begin
219   StopIme(True);
220 end;
221 
222 procedure LazSynImeSimple.DoOnMouse(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
223   X, Y: Integer);
224 begin
225   StopIme(True);
226 end;
227 
228 procedure LazSynImeSimple.UpdateImeWinXY(aX, aY: Integer; aImc: HIMC; aForce: Boolean);
229 var
230   cf: CompositionForm;
231   imc: HIMC;
232 begin
233   if not FriendEdit.HandleAllocated then exit;
234   if (not aForce) and (aX = FImeWinX) and (aY = FImeWinY) then exit;
235   FImeWinX := aX;
236   FImeWinY := aY;
237 
238   cf.dwStyle := CFS_POINT;
239   cf.ptCurrentPos := Point(aX, aY);
240   if aImc = 0 then
241     imc := ImmGetContext(FriendEdit.Handle)
242   else
243     imc := aImc;
244   if (imc <> 0) then begin
245     ImmSetCompositionWindow(imc, @cf);
246     if (aImc = 0) then
247       ImmReleaseContext(FriendEdit.Handle, imc);
248   end;
249 end;
250 
251 procedure LazSynImeSimple.SEtTextDrawer(AValue: TheTextDrawer);
252 begin
253   if FTextDrawer = AValue then Exit;
254   if FTextDrawer <> nil then
255     FTextDrawer.UnRegisterOnFontChangeHandler(@DoDrawerFontChanged);
256   FTextDrawer := AValue;
257   if FTextDrawer <> nil then
258     FTextDrawer.RegisterOnFontChangeHandler(@DoDrawerFontChanged);
259 end;
260 
261 procedure LazSynImeSimple.UpdateImeWinFont(aImc: HIMC);
262 var
263   imc: HIMC;
264   logFont: TLogFont;
265 begin
266   if not FriendEdit.HandleAllocated then exit;
267   if aImc = 0 then
268     imc := ImmGetContext(FriendEdit.Handle)
269   else
270     imc := aImc;
271   if (imc <> 0) then begin
272     GetObject(FriendEdit.Font.Handle, SizeOf(TLogFont), @logFont);
273     ImmSetCompositionFontA(imc, @logFont);
274     if (aImc = 0) then
275       ImmReleaseContext(FriendEdit.Handle, imc);
276   end;
277 end;
278 
279 constructor LazSynImeSimple.Create(AOwner: TSynEditBase);
280 begin
281   inherited Create(AOwner);
282   FImeBlockSelection := TSynEditSelection.Create(ViewedTextBuffer, False);
283   FImeBlockSelection.InvalidateLinesMethod := @InvalidateLines;
284 
285   TCustomSynEdit(FriendEdit).RegisterStatusChangedHandler(@DoStatusChanged, [scCaretX, scCaretY, scLeftChar, scTopLine, scModified]);
286   TCustomSynEdit(FriendEdit).RegisterCommandHandler(@DoOnCommand, nil, [hcfInit]);
287   TCustomSynEdit(FriendEdit).RegisterBeforeMouseDownHandler(@DoOnMouse);
288 end;
289 
290 destructor LazSynImeSimple.Destroy;
291 begin
292   TextDrawer := nil;
293   FreeAndNil(FImeBlockSelection);
294   TCustomSynEdit(FriendEdit).UnregisterBeforeMouseDownHandler(@DoOnMouse);
295   TCustomSynEdit(FriendEdit).UnregisterCommandHandler(@DoOnCommand);
296   TCustomSynEdit(FriendEdit).UnRegisterStatusChangedHandler(@DoStatusChanged);
297   inherited Destroy;
298 end;
299 
300 procedure LazSynImeSimple.WMImeNotify(var Msg: TMessage);
301 var
302   imc: HIMC;
303 {$IFDEF WinIMEDebug}
304   s: String;
305 {$ENDIF}
306 begin
307   {$IFDEF WinIMEDebug}
308   case msg.wParam of
309     IMN_CLOSESTATUSWINDOW: s:= 'IMN_CLOSESTATUSWINDOW, ';
310     IMN_OPENSTATUSWINDOW: s:= 'IMN_OPENSTATUSWINDOW, ';
311     IMN_CHANGECANDIDATE: s:= 'IMN_CHANGECANDIDATE, ';
312     IMN_CLOSECANDIDATE: s:= 'IMN_CLOSECANDIDATE, ';
313     IMN_OPENCANDIDATE: s:= 'IMN_OPENCANDIDATE, ';
314     IMN_SETCONVERSIONMODE: s:= 'IMN_SETCONVERSIONMODE, ';
315     IMN_SETSENTENCEMODE: s:= 'IMN_SETSENTENCEMODE, ';
316     IMN_SETOPENSTATUS: s:= 'IMN_SETOPENSTATUS, ';
317     IMN_SETCANDIDATEPOS: s:= 'IMN_SETCANDIDATEPOS, ';
318     IMN_SETCOMPOSITIONFONT: s:= 'IMN_SETCOMPOSITIONFONT, ';
319     IMN_SETCOMPOSITIONWINDOW: s:= 'IMN_SETCOMPOSITIONWINDOW, ';
320     IMN_SETSTATUSWINDOWPOS: s:= 'IMN_SETSTATUSWINDOWPOS, ';
321     IMN_GUIDELINE: s:= 'IMN_GUIDELINE, ';
322     IMN_PRIVATE: s:= 'IMN_PRIVATE, ';
323   end;
324   debugln(['TCustomSynEdit.WMImeNotify ',s,' ', dbgHex(Msg.lParam), ' ,  ', dbgHex(Msg.wParam)]);
325   {$ENDIF}
326 
327   case Msg.WParam of
328     IMN_SETOPENSTATUS: begin
329       imc := ImmGetContext(FriendEdit.Handle);
330       if (imc <> 0) then begin
331         UpdateImeWinFont(imc);
332         UpdateImeWinXY(TCustomSynEdit(FriendEdit).CaretXPix, TCustomSynEdit(FriendEdit).CaretYPix, imc, True);
333         ImmReleaseContext(FriendEdit.Handle, imc);
334       end;
335     end;
336     IMN_CLOSESTATUSWINDOW:
337       StopIme(True);
338   end;
339 end;
340 
341 procedure LazSynImeSimple.WMImeComposition(var Msg: TMessage);
342 var
343   imc: HIMC;
344   ImeCount: LongWord;
345   p: PChar;
346 begin
347   if ((Msg.LParam and GCS_RESULTSTR) <> 0) then begin
348     imc := ImmGetContext(FriendEdit.Handle);
349     try
350       if imc <> 0 then begin
351         ImeCount := ImmGetCompositionStringW(imc, GCS_RESULTSTR, nil, 0);
352         {$IFDEF WinIMEDebug}
353         DebugLn(['--- GCS_RESULTSTR  ', dbgHex(ImeCount)]);
354         {$ENDIF}
355         if ImeCount > 0 then begin
356           GetMem(p, ImeCount + 2);
357           try
358             TCustomSynEdit(FriendEdit).BeginUpdate;
359             if SelectionObj.SelAvail and (not SelectionObj.Persistent) and (eoOverwriteBlock in TCustomSynEdit(FriendEdit).Options2) then
360               SelectionObj.SelText := '';
361             ImmGetCompositionStringW(imc, GCS_RESULTSTR, p, ImeCount + 2);
362             p[ImeCount] := #0;
363             p[ImeCount+1] := #0;
364             FImeBlockSelection.StartLineBytePos := CaretObj.LineBytePos;
365             FImeBlockSelection.SelText := UTF16ToUTF8(PWCHAR(p));
366             FImeBlockSelection.StartLineBytePos := FImeBlockSelection.EndLineBytePos;
367             CaretObj.LineBytePos := FImeBlockSelection.StartLineBytePos;
368             Msg.Result := 1;
369           finally
370             FreeMem(p, ImeCount + 2);
371             TCustomSynEdit(FriendEdit).EndUpdate;
372           end;
373         end;
374       end;
375     finally
376       ImmReleaseContext(FriendEdit.Handle, imc);
377     end;
378   end;
379 
380 
381 end;
382 
383 procedure LazSynImeSimple.WMImeStartComposition(var Msg: TMessage);
384 var
385   imc: HIMC;
386 begin
387   //debugln(['TCustomSynEdit.WMImeStartComposition ']);
388   imc := ImmGetContext(FriendEdit.Handle);
389   if (imc <> 0) then begin
390     UpdateImeWinFont(imc);
391     UpdateImeWinXY(TCustomSynEdit(FriendEdit).CaretXPix, TCustomSynEdit(FriendEdit).CaretYPix, imc, True);
392     ImmReleaseContext(FriendEdit.Handle, imc);
393   end;
394   FInCompose := True;
395   FImeBlockSelection.StartLineBytePos := CaretObj.LineBytePos;
396   DoIMEStarted;
397 end;
398 
399 procedure LazSynImeSimple.WMImeEndComposition(var Msg: TMessage);
400 begin
401   FInCompose := False;
402   DoIMEEnded;
403 end;
404 
405 procedure LazSynImeSimple.FocusKilled;
406 begin
407   StopIme(True);
408 end;
409 
410 { LazSynImeFull }
411 
412 procedure LazSynImeFull.DoOnCommand(Sender: TObject; AfterProcessing: boolean;
413   var Handled: boolean; var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer;
414   HandlerData: pointer);
415 begin
416   StopIme(True);
417 end;
418 
419 procedure LazSynImeFull.DoOnMouse(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
420   X, Y: Integer);
421 begin
422   StopIme(True);
423 end;
424 
425 procedure LazSynImeFull.DoStatusChanged(Sender: TObject; Changes: TSynStatusChanges);
426 begin
427   if FInImeMsg then exit;
428   StopIme(True);
429 end;
430 
431 procedure LazSynImeFull.EnsureLeftChar;
432 var
433   r: Integer;
434 begin
435   if (FLeftPosForTarget < 1) or not FAdjustLeftCharForTargets then
436     exit;
437 
438   r := FRightPosForTarget - TCustomSynEdit(FriendEdit).CharsInWindow;
439 
440   if (TCustomSynEdit(FriendEdit).LeftChar < r) then
441     TCustomSynEdit(FriendEdit).LeftChar := r;
442 
443   if (TCustomSynEdit(FriendEdit).LeftChar > FLeftPosForTarget) then
444     TCustomSynEdit(FriendEdit).LeftChar := FLeftPosForTarget;
445 
446 end;
447 
448 procedure LazSynImeFull.StopIme(Success: Boolean);
449 begin
450   inherited StopIme(Success);
451   {$IFDEF WinIMEFullDeferOverwrite}
452   if FHasPersistLock then
453     SelectionObj.DecPersistentLock;
454   FHasPersistLock := False;
455   {$ENDIF}
456 end;
457 
458 procedure LazSynImeFull.SetImeTempText(const s: string);
459 var
460   p1, p2: TPoint;
461   f: Boolean;
462 begin
463   p1 := FImeBlockSelection.FirstLineBytePos;
464 
465   f := FInImeMsg;
466   FInImeMsg := True;
467   ViewedTextBuffer.UndoList.Lock;
468   ViewedTextBuffer.RedoList.Lock;
469   FImeBlockSelection.SelText := s;
470   ViewedTextBuffer.UndoList.Unlock;
471   ViewedTextBuffer.RedoList.Unlock;
472   FInImeMsg := f;
473 
474   p2 := FImeBlockSelection.FirstLineBytePos;
475   FImeBlockSelection.StartLineBytePos := p1;
476   FImeBlockSelection.EndLineBytePos := p2;
477 end;
478 
479 constructor LazSynImeFull.Create(AOwner: TSynEditBase);
480 begin
481   inherited Create(AOwner);
482   FAdjustLeftCharForTargets := True;
483 
484   FImeBlockSelection := TSynEditSelection.Create(ViewedTextBuffer, False);
485   FImeBlockSelection.InvalidateLinesMethod := @InvalidateLines;
486   FImeBlockSelection2 := TSynEditSelection.Create(ViewedTextBuffer, False);
487   FImeBlockSelection2.InvalidateLinesMethod := @InvalidateLines;
488   FImeBlockSelection3 := TSynEditSelection.Create(ViewedTextBuffer, False);
489   FImeBlockSelection3.InvalidateLinesMethod := @InvalidateLines;
490 
491   FImeMarkupSelection  := TSynEditMarkupSelection.Create(FriendEdit, FImeBlockSelection);
492   FImeMarkupSelection2 := TSynEditMarkupSelection.Create(FriendEdit, FImeBlockSelection2);
493   FImeMarkupSelection3 := TSynEditMarkupSelection.Create(FriendEdit, FImeBlockSelection3);
494 
495   TSynEditMarkupManager(MarkupMgr).AddMarkUp(FImeMarkupSelection);
496   TSynEditMarkupManager(MarkupMgr).AddMarkUp(FImeMarkupSelection2);
497   TSynEditMarkupManager(MarkupMgr).AddMarkUp(FImeMarkupSelection3);
498 
499   FImeMarkupSelection.MarkupInfo.Clear;
500   FImeMarkupSelection.MarkupInfo.FramePriority := MaxInt-1;
501   FImeMarkupSelection.MarkupInfo.FrameColor := clDefault;
502   FImeMarkupSelection.MarkupInfo.FrameStyle := slsDotted;
503   FImeMarkupSelection.MarkupInfo.FrameEdges := sfeBottom;
504 
505   // TODO: prevent any other frame in the active IME (as it distracts from IME underlines
506   // this includes left/right frame edges (can not currently be prevented)
507 
508   // prevent any underline
509   FImeMarkupSelection.MarkupInfo.StylePriority[fsUnderline] := MaxInt;
510   FImeMarkupSelection.MarkupInfo.Style:= [];
511   FImeMarkupSelection.MarkupInfo.StyleMask:= [fsUnderline];
512 
513   FImeMarkupSelection2.MarkupInfo.Clear;
514   FImeMarkupSelection2.MarkupInfo.FramePriority := MaxInt;
515   FImeMarkupSelection2.MarkupInfo.FrameColor := clDefault;
516   FImeMarkupSelection2.MarkupInfo.FrameStyle := slsSolid;
517   FImeMarkupSelection2.MarkupInfo.FrameEdges := sfeBottom;
518 
519   FImeMarkupSelection3.MarkupInfo.Assign(TCustomSynEdit(FriendEdit).SelectedColor);
520 
521   TCustomSynEdit(FriendEdit).RegisterStatusChangedHandler(@DoStatusChanged, [scCaretX, scCaretY, scModified]);
522   TCustomSynEdit(FriendEdit).RegisterCommandHandler(@DoOnCommand, nil, [hcfInit]);
523   TCustomSynEdit(FriendEdit).RegisterBeforeMouseDownHandler(@DoOnMouse);
524 
525 end;
526 
527 destructor LazSynImeFull.Destroy;
528 begin
529   TCustomSynEdit(FriendEdit).UnregisterBeforeMouseDownHandler(@DoOnMouse);
530   TCustomSynEdit(FriendEdit).UnregisterCommandHandler(@DoOnCommand);
531   TCustomSynEdit(FriendEdit).UnRegisterStatusChangedHandler(@DoStatusChanged);
532   TSynEditMarkupManager(MarkupMgr).RemoveMarkUp(FImeMarkupSelection);
533   TSynEditMarkupManager(MarkupMgr).RemoveMarkUp(FImeMarkupSelection2);
534   TSynEditMarkupManager(MarkupMgr).RemoveMarkUp(FImeMarkupSelection3);
535 
536   FreeAndNil(FImeMarkupSelection);
537   FreeAndNil(FImeMarkupSelection2);
538   FreeAndNil(FImeMarkupSelection3);
539   FreeAndNil(FImeBlockSelection);
540   FreeAndNil(FImeBlockSelection2);
541   FreeAndNil(FImeBlockSelection3);
542   inherited Destroy;
543 end;
544 
545 procedure LazSynImeFull.WMImeRequest(var Msg: TMessage);
546 var
547   {$IFDEF WinIMEDebug}
548   s: String;
549   {$ENDIF}
550   cp: PIMECHARPOSITION;
551   p1: TPoint;
552   CWidth: TPhysicalCharWidths;
553   i, x: integer;
554 begin
555   {$IFDEF WinIMEDebug}
556   case msg.wParam of
557     IMR_COMPOSITIONWINDOW: s:= 'IMR_COMPOSITIONWINDOW, ';
558     IMR_CANDIDATEWINDOW: s:= 'IMR_CANDIDATEWINDOW, ';
559     IMR_COMPOSITIONFONT: s:= 'IMR_COMPOSITIONFONT, ';
560     IMR_RECONVERTSTRING: s:= 'IMR_RECONVERTSTRING, ';
561     IMR_CONFIRMRECONVERTSTRING: s:= 'IMR_CONFIRMRECONVERTSTRING, ';
562     IMR_DOCUMENTFEED: s:= 'IMR_DOCUMENTFEED, ';
563   end;
564   if s <> '' then debugln(['TCustomSynEdit.WMImeRequest ', s,' ' , dbgHex(Msg.lParam)]);
565   {$ENDIF}
566 
567   case msg.wParam of
568     IMR_QUERYCHARPOSITION: begin
569         cp := PIMECHARPOSITION(Msg.lParam);
570         p1 := FImeBlockSelection.StartLineBytePos;
571         if not FInCompose then
572           p1 := CaretObj.LineBytePos;
573 
574         CWidth := ViewedTextBuffer.GetPhysicalCharWidths(FImeBlockSelection.StartLinePos - 1);
575         x := p1.x - 1;
576         i := cp^.dwCharPos;
577         while (i > 0) and (x < length(CWidth)) do begin
578           inc(x);
579           while (x < length(CWidth)) and ((CWidth[x] and PCWMask) = 0) do
580             inc(x);
581           dec(i);
582         end;
583         p1.x := x + i + 1;
584         p1 := FriendEdit.ClientToScreen(TCustomSynEdit(FriendEdit).RowColumnToPixels(ViewedTextBuffer.LogicalToPhysicalPos(p1)));
585 
586         cp^.pt.y := p1.y;
587         cp^.pt.x :=  p1.x;
588         cp^.cLineHeight := TCustomSynEdit(FriendEdit).LineHeight;
589         cp^.rcDocument.TopLeft := FriendEdit.ClientToScreen(FriendEdit.ClientRect.TopLeft);
590         cp^.rcDocument.BottomRight := FriendEdit.ClientToScreen(FriendEdit.ClientRect.BottomRight);
591         {$IFDEF WinIMEDebug}
592         debugln(['--- TCustomSynEdit.WMImeRequest ** IMR_QUERYCHARPOSITION ', dbgs(cp^.dwCharPos), '  ', dbgs(x),'   ', dbgs(p1.x)]);
593         {$ENDIF}
594         Msg.Result := 1;
595       end;
596   end;
597 end;
598 
599 procedure LazSynImeFull.WMImeNotify(var Msg: TMessage);
600 {$IFDEF WinIMEDebug}
601 var
602   s: String;
603 {$ENDIF}
604 begin
605   {$IFDEF WinIMEDebug}
606   case msg.wParam of
607     IMN_CLOSESTATUSWINDOW: s:= 'IMN_CLOSESTATUSWINDOW, ';
608     IMN_OPENSTATUSWINDOW: s:= 'IMN_OPENSTATUSWINDOW, ';
609     IMN_CHANGECANDIDATE: s:= 'IMN_CHANGECANDIDATE, ';
610     IMN_CLOSECANDIDATE: s:= 'IMN_CLOSECANDIDATE, ';
611     IMN_OPENCANDIDATE: s:= 'IMN_OPENCANDIDATE, ';
612     IMN_SETCONVERSIONMODE: s:= 'IMN_SETCONVERSIONMODE, ';
613     IMN_SETSENTENCEMODE: s:= 'IMN_SETSENTENCEMODE, ';
614     IMN_SETOPENSTATUS: s:= 'IMN_SETOPENSTATUS, ';
615     IMN_SETCANDIDATEPOS: s:= 'IMN_SETCANDIDATEPOS, ';
616     IMN_SETCOMPOSITIONFONT: s:= 'IMN_SETCOMPOSITIONFONT, ';
617     IMN_SETCOMPOSITIONWINDOW: s:= 'IMN_SETCOMPOSITIONWINDOW, ';
618     IMN_SETSTATUSWINDOWPOS: s:= 'IMN_SETSTATUSWINDOWPOS, ';
619     IMN_GUIDELINE: s:= 'IMN_GUIDELINE, ';
620     IMN_PRIVATE: s:= 'IMN_PRIVATE, ';
621   end;
622   debugln(['TCustomSynEdit.WMImeNotify ',s,' ', dbgHex(Msg.lParam), ' ,  ', dbgHex(Msg.wParam)]);
623   {$ENDIF}
624 end;
625 
626 procedure LazSynImeFull.WMImeComposition(var Msg: TMessage);
627 var
628   CWidth: TPhysicalCharWidths;
629 
CharToBytenull630   function CharToByte(AStart, AChars: integer): integer;
631   begin
632     if length(CWidth) = 0 then
633       CWidth := ViewedTextBuffer.GetPhysicalCharWidths(FImeBlockSelection.StartLinePos - 1);
634     dec(AStart);
635     Result := AStart;
636     while (AChars > 0) and (Result < length(CWidth)) do begin
637       inc(Result);
638       while (Result < length(CWidth)) and ((CWidth[Result] and PCWMask) = 0) do
639         inc(Result);
640       dec(AChars);
641     end;
642     Result := Result - AStart + AChars;
643   end;
644 var
645   {$IFDEF WinIMEDebug}
646   s: String;
647   {$ENDIF}
648   imc: HIMC;
649   p: PChar;
650   ImeCount: LongWord;
651   x, i: Integer;
652   xy: TPoint;
653   grp: Boolean;
654   {$IFDEF WinIMEFullDeferOverwrite}
655   sel, sel2: Boolean;
656   {$ENDIF}
657 begin
658   {$IFDEF WinIMEDebug}
659   s := '';
660   if (Msg.lparam and GCS_COMPREADSTR)<>0 then s := s + 'GCS_COMPREADSTR, ';
661   if (Msg.lparam and GCS_COMPREADATTR)<>0 then s := s + 'GCS_COMPREADATTR, ';
662   if (Msg.lparam and GCS_COMPREADCLAUSE)<>0 then s := s + 'GCS_COMPREADCLAUSE, ';
663   //if (Msg.lparam and GCS_COMPSTR)<>0 then s := s + 'GCS_COMPSTR, ';
664   //if (Msg.lparam and GCS_COMPATTR)<>0 then s := s + 'GCS_COMPATTR, ';
665   //if (Msg.lparam and GCS_COMPCLAUSE)<>0 then s := s + 'GCS_COMPCLAUSE, ';
666   //if (Msg.lparam and GCS_CURSORPOS)<>0 then s := s + 'GCS_CURSORPOS, ';
667   if (Msg.lparam and GCS_DELTASTART)<>0 then s := s + 'GCS_DELTASTART, ';
668   if (Msg.lparam and GCS_RESULTREADSTR)<>0 then s := s + 'GCS_RESULTREADSTR, ';
669   if (Msg.lparam and GCS_RESULTREADCLAUSE)<>0 then s := s + 'GCS_RESULTREADCLAUSE, ';
670   //if (Msg.lparam and GCS_RESULTSTR)<>0 then s := s + 'GCS_RESULTSTR, ';
671   if (Msg.lparam and GCS_RESULTCLAUSE)<>0 then s := s + 'GCS_RESULTCLAUSE, ';
672   if (Msg.lparam and CS_INSERTCHAR)<>0 then s := s + ' ** CS_INSERTCHAR, ';
673   if (Msg.lparam and CS_NOMOVECARET)<>0 then s := s + ' ** CS_NOMOVECARET, ';
674   if s <> '' then debugln(['TCustomSynEdit.WMImeComposition ', s]);
675   {$ENDIF}
676 
677   if (Msg.LParam and (GCS_RESULTSTR or GCS_COMPSTR or GCS_CURSORPOS or GCS_COMPATTR {or GCS_COMPCLAUSE})) = 0 then
678     exit;
679 
680   imc := 0;
681   FInImeMsg := True;
682   SetLength(CWidth, 0);
683   try
684 
685     if ((Msg.LParam and GCS_RESULTSTR) <> 0) then begin
686       if imc = 0 then
687         imc := ImmGetContext(FriendEdit.Handle);
688       ImeCount := ImmGetCompositionStringW(imc, GCS_RESULTSTR, nil, 0);
689       {$IFDEF WinIMEDebug}
690       DebugLn(['--- GCS_RESULTSTR  ', dbgHex(ImeCount)]);
691       {$ENDIF}
692       if ImeCount > 0 then begin
693         GetMem(p, ImeCount + 2);
694         try
695           SetImeTempText('');
696           CaretObj.LineBytePos := FImeBlockSelection.StartLineBytePos;
697           grp := ViewedTextBuffer.UndoList.GroupUndo;
698           ViewedTextBuffer.UndoList.GroupUndo := True;
699           TCustomSynEdit(FriendEdit).BeginUpdate;
700           ViewedTextBuffer.UndoList.CurrentReason := ecImeStr;
701           {$IFDEF WinIMEFullDeferOverwrite}
702           if FHasPersistLock then
703             SelectionObj.DecPersistentLock;
704           FHasPersistLock := False;
705           if SelectionObj.SelAvail and (not SelectionObj.Persistent) and (eoOverwriteBlock in TCustomSynEdit(FriendEdit).Options2)
706           then begin
707             SelectionObj.SelText := '';
708             FImeBlockSelection.StartLineBytePos := SelectionObj.StartLineBytePos;
709           end;
710           {$ENDIF}
711           CaretObj.LineBytePos := FImeBlockSelection.StartLineBytePos;
712           ImmGetCompositionStringW(imc, GCS_RESULTSTR, p, ImeCount + 2);
713           p[ImeCount] := #0;
714           p[ImeCount+1] := #0;
715           FImeBlockSelection.SelText := UTF16ToUTF8(PWCHAR(p));
716           FImeBlockSelection.StartLineBytePos := FImeBlockSelection.EndLineBytePos;
717           CaretObj.LineBytePos := FImeBlockSelection.StartLineBytePos;
718           {$IFnDEF WinIMEFullOverwriteSkipUndo}
719           FNeedUndoOnCancel := False;
720           {$ENDIF}
721           Msg.Result := 1;
722         finally
723           TCustomSynEdit(FriendEdit).EndUpdate;
724           ViewedTextBuffer.UndoList.GroupUndo := grp;
725           FreeMem(p, ImeCount + 2);
726         end;
727       end;
728     end;
729 
730     if ((Msg.LParam and GCS_COMPSTR) <> 0) then begin
731       if imc = 0 then
732         imc := ImmGetContext(FriendEdit.Handle);
733       ImeCount := ImmGetCompositionStringW(imc, GCS_COMPSTR, nil, 0);
734       {$IFDEF WinIMEDebug}
735       DebugLn(['--- GCS_COMPSTR  ', dbgHex(ImeCount)]);
736       {$ENDIF}
737       if ImeCount > 0 then begin
738         GetMem(p, ImeCount + 2);
739         try
740           ImmGetCompositionStringW(imc, GCS_COMPSTR, p, ImeCount + 2);
741           p[ImeCount] := #0;
742           p[ImeCount+1] := #0;
743           {$IFDEF WinIMEFullDeferOverwrite}
744           sel := (not SelectionObj.IsBackwardSel) and (CompareCarets(SelectionObj.EndLineBytePos, FImeBlockSelection.StartLineBytePos) = 0);
745           sel2 := SelectionObj.IsBackwardSel and (CompareCarets(SelectionObj.EndLineBytePos, FImeBlockSelection.EndLineBytePos) = 0);
746           {$ENDIF}
747           SetImeTempText(UTF16ToUTF8(PWCHAR(p)));
748           {$IFDEF WinIMEFullDeferOverwrite}
749           if sel then SelectionObj.EndLineBytePos := FImeBlockSelection.StartLineBytePos;
750           if sel2 then SelectionObj.EndLineBytePos := FImeBlockSelection.EndLineBytePos;
751           {$ENDIF}
752           Msg.Result := 1;
753         finally
754           FreeMem(p, ImeCount + 2);
755         end;
756       end;
757     end;
758 
759     if ((Msg.LParam and GCS_COMPATTR) <> 0) then begin
760   //ATTR_INPUT               = $00;  // dotted undurline
761   //ATTR_TARGET_CONVERTED    = $01;  // full underline (bold underline / double width line)
762   //ATTR_CONVERTED           = $02;  // light underline (single width line)
763   //ATTR_TARGET_NOTCONVERTED = $03;  // Show as selected ?
764   //ATTR_INPUT_ERROR         = $04;  // ? none
765   //ATTR_FIXEDCONVERTED      = $05;  // ? none
766   //            low confidence => green underline.
767       if imc = 0 then
768         imc := ImmGetContext(FriendEdit.Handle);
769       ImeCount := ImmGetCompositionStringW(imc, GCS_COMPATTR, nil, 0);
770       {$IFDEF WinIMEDebug}
771       DebugLn(['***** GCS_COMPATTR  ', dbgHex(ImeCount)]);
772       {$ENDIF}
773       if ImeCount > 0 then begin
774         FLeftPosForTarget := -1;
775         FRightPosForTarget := -1;
776         xy := FImeBlockSelection.StartLineBytePos;
777         FImeBlockSelection2.StartLineBytePos := xy;
778         FImeBlockSelection2.EndLineBytePos := xy;
779         FImeBlockSelection3.StartLineBytePos := xy;
780         FImeBlockSelection3.EndLineBytePos := xy;
781         GetMem(p, ImeCount + 2);
782         try
783           ImmGetCompositionStringW(imc, GCS_COMPATTR, p, ImeCount + 2);
784           DebugLn(dbgMemRange(PByte( p), ImeCount));
785           i := 0;
786           while longword(i) < ImeCount do begin
787             if ord(p[i]) = ATTR_TARGET_CONVERTED then begin
788               x := FImeBlockSelection.StartBytePos;
789               xy.x := x + CharToByte(x, i);
790               FImeBlockSelection2.StartLineBytePos := xy;
791               if (FLeftPosForTarget < 0) or (FLeftPosForTarget > xy.x) then
792                 FLeftPosForTarget := xy.x;
793               inc(i);
794 
795               while (longword(i) < ImeCount) and (ord(p[i]) = ATTR_TARGET_CONVERTED) do
796                 inc(i);
797               xy.x := x + CharToByte(x, i);
798               FImeBlockSelection2.EndLineBytePos := xy;
799               if (FRightPosForTarget < 0) or (FRightPosForTarget < xy.x) then
800                 FRightPosForTarget := xy.x;
801               //break;
802             end;
803 
804             if ord(p[i]) = ATTR_TARGET_NOTCONVERTED then begin
805               x := FImeBlockSelection.StartBytePos;
806               xy.x := x + CharToByte(x, i);
807               if (FLeftPosForTarget < 0) or (FLeftPosForTarget > xy.x) then
808                 FLeftPosForTarget := xy.x;
809               FImeBlockSelection3.StartLineBytePos := xy;
810               inc(i);
811 
812               while (longword(i) < ImeCount) and (ord(p[i]) = ATTR_TARGET_NOTCONVERTED) do
813                 inc(i);
814               xy.x := x + CharToByte(x, i);
815               FImeBlockSelection3.EndLineBytePos := xy;
816               if (FRightPosForTarget < 0) or (FRightPosForTarget < xy.x) then
817                 FRightPosForTarget := xy.x;
818               //break;
819             end;
820 
821             inc(i);
822           end;
823 
824           Msg.Result := 1;
825         finally
826           FreeMem(p, ImeCount + 2);
827         end;
828 
829         if (FLeftPosForTarget > 0) and FAdjustLeftCharForTargets then begin
830           FLeftPosForTarget := ViewedTextBuffer.LogicalToPhysicalPos
831             (Point(FLeftPosForTarget, FImeBlockSelection.FirstLineBytePos.Y)).x;
832           if FRightPosForTarget > 0 then
833             FRightPosForTarget := ViewedTextBuffer.LogicalToPhysicalPos
834               (Point(FRightPosForTarget, FImeBlockSelection.FirstLineBytePos.Y)).x;
835           EnsureLeftChar;
836         end;
837       end;
838     end;
839 
840     (*
841     if ((Msg.LParam and GCS_COMPCLAUSE) <> 0) then begin
842       // attributes for all chars in any one clause should be the equal.
843       if imc = 0 then
844         imc := ImmGetContext(FriendEdit.Handle);
845       ImeCount := ImmGetCompositionStringW(imc, GCS_COMPCLAUSE, nil, 0);
846       {$IFDEF WinIMEDebug}
847       DebugLn(['***** GCS_COMPCLAUSE ', dbgHex(ImeCount)]);
848       {$ENDIF}
849       if ImeCount > 0 then begin
850         GetMem(p, ImeCount + 2);
851         try
852           ImmGetCompositionStringW(imc, GCS_COMPCLAUSE, p, ImeCount + 2);
853 
854 DebugLn(dbgMemRange(PByte( p), ImeCount));
855         finally
856           FreeMem(p, ImeCount + 2);
857         end;
858       end;
859     end;
860     *)
861 
862     if ((Msg.LParam and GCS_CURSORPOS) <> 0) then begin
863       if imc = 0 then
864         imc := ImmGetContext(FriendEdit.Handle);
865 
866       ImeCount := ImmGetCompositionStringW(imc, GCS_CURSORPOS, nil, 0);
867       {$IFDEF WinIMEDebug}
868       DebugLn(['--- GCS_CURSORPOS ', dbgs(ImeCount), '  FLeftPosForTarget=',FLeftPosForTarget]);
869       {$ENDIF}
870       if ImeCount >= 0 then begin    // ToDo: Comparison is always True.
871         ImeCount := ImeCount and $ffff;
872         x := FImeBlockSelection.StartBytePos;
873         x := x + CharToByte(x, ImeCount);
874         CaretObj.CharPos := ViewedTextBuffer.LogicalToPhysicalPos(Point(x, FImeBlockSelection.StartLinePos)).x;
875         // TODO: this causes full repaints
876         EnsureLeftChar;
877       end;
878     end;
879 
880   finally
881     if imc <> 0 then
882       ImmReleaseContext(FriendEdit.Handle, imc);
883     FInImeMsg := False;
884   end;
885   inherited;
886 end;
887 
888 procedure LazSynImeFull.WMImeStartComposition(var Msg: TMessage);
889 begin
890   //debugln(['TCustomSynEdit.WMImeStartComposition ']);
891   {$IFnDEF WinIMEFullDeferOverwrite}
892   if SelectionObj.SelAvail and (not SelectionObj.Persistent) and (eoOverwriteBlock in TCustomSynEdit(FriendEdit).Options2)
893   then begin
894     {$IFnDEF WinIMEFullOverwriteSkipUndo}
895     ViewedTextBuffer.UndoList.ForceGroupEnd;
896     FUndoStamp1 := ViewedTextBuffer.UndoList.PeekItem;
897     {$ENDIF}
898     TCustomSynEdit(FriendEdit).BeginUpdate;
899     ViewedTextBuffer.UndoList.CurrentReason := ecImeStr;
900     SelectionObj.SelText := '';
901     TCustomSynEdit(FriendEdit).EndUpdate;
902   {$IFnDEF WinIMEFullOverwriteSkipUndo}
903     FUndoStamp2 := ViewedTextBuffer.UndoList.PeekItem;
904     FNeedUndoOnCancel := FUndoStamp1 <> FUndoStamp2;
905   end
906   else begin
907     FNeedUndoOnCancel := False
908   {$ENDIF}
909   end;
910   {$ENDIF}
911   {$IFDEF WinIMEFullDeferOverwrite}
912   if not FHasPersistLock then
913     SelectionObj.IncPersistentLock;
914   FHasPersistLock := True;
915   {$ENDIF}
916 
917   FImeMarkupSelection3.MarkupInfo.Assign(TCustomSynEdit(FriendEdit).SelectedColor);
918   FImeBlockSelection.StartLineBytePos := CaretObj.LineBytePos;
919   FInCompose := True;
920   Msg.Result := 1;
921   DoIMEStarted;
922 end;
923 
924 procedure LazSynImeFull.WMImeEndComposition(var Msg: TMessage);
925 begin
926   //debugln(['TCustomSynEdit.WMImeEndComposition ']);
927   SetImeTempText('');
928   CaretObj.LineBytePos := FImeBlockSelection.LastLineBytePos;
929   {$IFnDEF WinIMEFullDeferOverwrite}
930   {$IFnDEF WinIMEFullOverwriteSkipUndo}
931   if FNeedUndoOnCancel and (ViewedTextBuffer.UndoList.PeekItem = FUndoStamp2) then
932     TCustomSynEdit(FriendEdit).Undo;
933   {$ENDIF}
934   {$ENDIF}
935   {$IFDEF WinIMEFullDeferOverwrite}
936   if FHasPersistLock then
937     SelectionObj.DecPersistentLock;
938   FHasPersistLock := False;
939   {$ENDIF}
940 
941   FImeBlockSelection.StartLineBytePos := CaretObj.LineBytePos;
942   FImeBlockSelection2.StartLineBytePos := CaretObj.LineBytePos;
943   FInCompose := False;
944   Msg.Result := 1;
945   DoIMEEnded;
946 end;
947 
948 procedure LazSynImeFull.FocusKilled;
949 begin
950   StopIme(True);
951 end;
952 
953 end.
954 
955