View Issue Details

IDProjectCategoryView StatusLast Update
0038730LazarusLCLpublic2021-06-15 13:07
ReporterDo-wan Kim Assigned ToMartin Friebe  
PrioritynormalSeverityfeatureReproducibilityN/A
Status assignedResolutionopen 
Product Version2.0.13 (SVN) 
Summary0038730: Basic linux fcitx Input Method fix for synedit.
DescriptionIt is linux Input Method 'fcitx' fix for synedit lazarus trunk.
I tested only on Korean.

Candidate windows arrow key not working with it.
It is not perfect.

I tested it under i386 ubuntu linux.
TagsNo tags attached.
Fixed in Revision
LazTarget
WidgetsetGTK 2
Attached Files

Activities

Do-wan Kim

2021-04-08 16:20

reporter  

synedit_linux_fcitx.patch (10,243 bytes)   
Index: components/synedit/synedit.pp
===================================================================
--- components/synedit/synedit.pp	(리비전 64943)
+++ components/synedit/synedit.pp	(작업 사본)
@@ -1249,6 +1249,10 @@
     property OnSpecialLineColors: TSpecialLineColorsEvent read FOnSpecialLineColors write SetSpecialLineColors;  deprecated;
     property OnSpecialLineMarkup: TSpecialLineMarkupEvent read FOnSpecialLineMarkup write SetSpecialLineMarkup;
     property OnStatusChange: TStatusChangeEvent read fOnStatusChange write fOnStatusChange;
+{$ifdef LCLGtk2}
+  protected
+    procedure GTK_IMComposition(var Message:TMessage); message LM_IM_COMPOSITION;
+{$endif}
   end;
 
   TSynEdit = class(TCustomSynEdit)
@@ -1370,6 +1374,11 @@
 
 implementation
 
+{$ifdef LCLGtk2}
+uses
+  Gtk2Globals;
+{$endif}
+
 var
   LOG_SynMouseEvents: PLazLoggerLogGroup;
 
@@ -4333,6 +4342,30 @@
   Result := FMarkupManager.Count;
 end;
 
+{$ifdef LCLGtk2}
+procedure TCustomSynEdit.GTK_IMComposition(var Message: TMessage);
+var
+  IMStr:TUTF8Char;
+begin
+  if (not ReadOnly) then
+  begin
+    // to do : accurate candidate position
+    // set candidate position
+    if (Message.WParam and (GTK_IM_FLAG_START or GTK_IM_FLAG_COMPOSITION))<>0 then
+      IM_Context_Set_Cursor_Pos(CaretXPix,CaretYPix+LineHeight);
+    // delete previous character by selection
+    if Message.WParam and GTK_IM_FLAG_REPLACE<>0 then
+      CommandProcessor(ecSelLeft,#0,nil);
+    // valid string at composition & commit
+    if Message.WParam and (GTK_IM_FLAG_START or GTK_IM_FLAG_END)=0 then
+    begin
+      IMStr:=pchar(Message.LParam);
+      UTF8KeyPress(IMStr);
+    end;
+  end;
+end;
+{$endif}
+
 procedure TCustomSynEdit.SetCaretTypeSize(AType: TSynCaretType; AWidth, AHeight, AXOffs,
   AYOffs: Integer);
 begin
Index: lcl/interfaces/gtk2/gtk2globals.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2globals.pp	(리비전 64943)
+++ lcl/interfaces/gtk2/gtk2globals.pp	(작업 사본)
@@ -63,9 +63,13 @@
 var
   im_context: PGtkIMContext = nil;
   im_context_widget: PGtkWidget = nil;
-  im_context_string: string = '';
+  // refined patch works with fcitx
+  im_context_use: Boolean =False;
+  im_context_skipdelete: Boolean =False;
+  im_context_string: string ='';
 
 procedure ResetDefaultIMContext;
+procedure IM_Context_Set_Cursor_Pos(X, Y: gint);
 
 var
   LastFileSelectRow : gint;
@@ -419,10 +423,20 @@
     gtk_im_context_reset(im_context);
     gtk_im_context_set_client_window(im_context,nil);
   end;
-  im_context_widget:=nil;
+  im_context_use:=False;
   im_context_string:='';
+  im_context_skipdelete:=False;
 end;
 
+procedure IM_Context_Set_Cursor_Pos(X, Y: gint);
+var
+  CurPos: TGdkRectangle;
+begin
+  CurPos.x:=X;
+  CurPos.y:=Y;
+  gtk_im_context_set_cursor_location(im_context,@CurPos);
+end;
+
 procedure AddCharsetEncoding(CharSet: Byte; CharSetReg, CharSetCod: CharSetStr;
   ToEnum:boolean=true; CrPart:boolean=false; CcPart:boolean=false);
 var
Index: lcl/interfaces/gtk2/gtk2proc.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2proc.inc	(리비전 64943)
+++ lcl/interfaces/gtk2/gtk2proc.inc	(작업 사본)
@@ -1999,7 +1999,7 @@
       end;
       Exit;
     end;
-    Result := (AEvent^.Length > 0) or (GetSpecialChar <> #0);
+    Result := ((not im_context_use) and (AEvent^.Length > 0)) or (GetSpecialChar <> #0);
   end;
   
   function KeyAlreadyHandledByGtk: boolean;
@@ -2006,7 +2006,7 @@
   begin
     Result := false;
     if AWidget = nil then exit;
-    
+
     if GtkWidgetIsA(AWidget, gtk_entry_get_type)
     then begin
       // the gtk_entry handles the following keys
@@ -2386,7 +2386,7 @@
 
       Msg.KeyData := CommonKeyData or (Flags shl 16) or $0001 {TODO:  repeatcount};
 
-      if not KeyAlreadyHandledByGtk
+      if (not KeyAlreadyHandledByGtk) and (not im_context_use)
       then begin
         // send the (Sys)KeyDown message directly to the LCL
         NotifyApplicationUserInput(TControl(TargetObj), Msg.Msg);
@@ -2458,7 +2458,10 @@
         begin
           OldCharacter := Character;
           // send the key after navigation keys were handled
-          Result := TWinControl(LCLObject).IntfUTF8KeyPress(Character, 1, SysKey);
+          if not im_context_use then
+            Result := TWinControl(LCLObject).IntfUTF8KeyPress(Character, 1, SysKey)
+            else
+              Result:=True;
           if Result or (Character = '') then
             // dont' stop key event here, just clear it since we need a keyUp event
             ClearKey
Index: lcl/interfaces/gtk2/gtk2widgetset.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2widgetset.inc	(리비전 64943)
+++ lcl/interfaces/gtk2/gtk2widgetset.inc	(작업 사본)
@@ -54,7 +54,11 @@
   Status := GTKFocusCB(Widget, Event, Data);
 
   if GtkWidgetIsA(Widget,GTK_APIWIDGETCLIENT_TYPE) then
-    Result := Status
+  begin
+    Result := Status;
+    gtk_im_context_focus_in(im_context);  // set IM focus
+    im_context_widget:=widget;
+  end
   else
     Result := False;
 end;
@@ -79,7 +83,12 @@
   Status := GTKKillFocusCB(Widget, Event, Data);
 
   if GtkWidgetIsA(Widget,GTK_APIWIDGETCLIENT_TYPE) then
-    Result := Status
+  begin
+    Result := Status;
+    gtk_im_context_focus_out(im_context); // kill IM focus
+    gtk_im_context_reset(im_context);
+    im_context_widget:=nil;
+  end
   else
     Result := False;
 end;
@@ -215,12 +224,101 @@
 end;
 
 procedure gtk_commit_cb ({%H-}context: PGtkIMContext; const Str: Pgchar;
-  {%H-}Data: Pointer); cdecl;
+   {%H-}Data: Pointer); cdecl;
+var
+  Control:TWinControl;
+  Flag:WPARAM;
 begin
   //DebugLn(['gtk_commit_cb ',dbgstr(Str),'="',Str,'"']);
   im_context_string:=Str;
+  { Handle commit string at ending composition,
+    it cannot handled by HandleGTKKeyUpDown }
+  if im_context_use and (im_context_widget<>nil) and (im_context_string<>'') then
+      begin
+        Control:=TWinControl(GetNearestLCLObject(im_context_widget));
+        Flag:=GTK_IM_FLAG_COMMIT;
+        if not im_context_skipDelete then
+          Flag:=Flag or GTK_IM_FLAG_REPLACE;
+        SendMessage(Control.Handle,LM_IM_COMPOSITION,Flag,LPARAM(pchar(im_context_string)));
+        im_context_string:='';
+        im_context_skipDelete:=True;
+      end;
 end;
 
+procedure gtk_preedit_start_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+begin
+  if (im_context_widget<>nil) and
+     (GetNearestLCLObject(im_context_widget) is TCustomControl) then
+  begin
+    im_context_use:=True;
+    im_context_skipDelete:=True;
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    Flag:=GTK_IM_FLAG_START;
+    SendMessage(Control.Handle,LM_IM_COMPOSITION,Flag,LPARAM(Data));
+  end else
+    im_context_use:=False;
+end;
+
+procedure gtk_preedit_end_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+begin
+  if (im_context_widget<>nil) and
+     (GetNearestLCLObject(im_context_widget) is TCustomControl) then
+  begin
+    im_context_use:=False;
+    im_context_skipDelete:=True;
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    Flag:=GTK_IM_FLAG_END;
+    SendMessage(control.Handle,LM_IM_COMPOSITION,Flag,LPARAM(Data));
+  end;
+end;
+
+procedure gtk_preedit_changed_cb({%H-}context:PGtkIMContext; {%H-}Data:Pointer); cdecl;
+var
+  str:Pgchar;
+  pangoattr:PPangoAttrList;
+  curpos:gint;
+  control:TWinControl;
+  Flag:WPARAM;
+begin
+  if (im_context_widget<>nil) then
+  begin
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    gtk_im_context_get_preedit_string(context,@str,pangoattr,@curpos);
+    im_context_string:=str;
+    g_free(str);
+    pango_attr_list_unref(pangoattr);
+    Flag:=GTK_IM_FLAG_COMPOSITION;
+    if (not im_context_skipDelete) and (Length(im_context_string)>0) then begin
+      Flag:=Flag or GTK_IM_FLAG_REPLACE;
+    end else
+      im_context_skipDelete:=False;
+    SendMessage(control.Handle,LM_IM_COMPOSITION,Flag,LPARAM(pchar(im_context_string)));
+  end;
+end;
+
+function gtk_retrieve_surrounding_cb({%H-}context:PGtkIMContext; {%H-}Data:Pointer):gboolean; cdecl;
+var
+  control:TWinControl;
+begin
+  Result:=False;
+  if (im_context_widget<>nil) then
+  begin
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    SendMessage(control.Handle,LM_IM_COMPOSITION,GTK_IM_FLAG_COMPOSITION,0);
+  end;
+end;
+
+function gtk_delete_surrounding_cb({%H-}context:PGtkIMContext; offset, n_chars:gint; {%H-}Data:Pointer):gboolean; cdecl;
+begin
+  Result:=False;
+end;
+
 {------------------------------------------------------------------------------
   Function: TGtk2WidgetSet._SetCallbackEx
 
@@ -959,6 +1057,17 @@
   im_context:=gtk_im_multicontext_new;
   g_signal_connect (G_OBJECT (im_context), 'commit',
     G_CALLBACK (@gtk_commit_cb), nil);
+  // Input method
+  g_signal_connect (G_OBJECT (im_context), 'preedit-start',
+    G_CALLBACK (@gtk_preedit_start_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'preedit-end',
+    G_CALLBACK (@gtk_preedit_end_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'preedit-changed',
+    G_CALLBACK (@gtk_preedit_changed_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'retrieve_surrounding',
+    G_CALLBACK (@gtk_retrieve_surrounding_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'delete_surrounding',
+    G_CALLBACK (@gtk_delete_surrounding_cb), nil);
   {$IFDEF HASX}
   if IsNoTransientWM then
   begin
Index: lcl/lmessages.pp
===================================================================
--- lcl/lmessages.pp	(리비전 64943)
+++ lcl/lmessages.pp	(작업 사본)
@@ -97,7 +97,15 @@
   LM_INTERFACELAST  = LM_LCL + 199;
   
   LM_UNKNOWN        = LM_INTERFACELAST + 1;
+  LM_IM_COMPOSITION = LM_UNKNOWN + 1; // gtk IM
 
+  // GTK IM Flags
+  GTK_IM_FLAG_START       = 1;
+  GTK_IM_FLAG_COMPOSITION = 2;
+  GTK_IM_FLAG_END         = 4;
+  GTK_IM_FLAG_COMMIT      = 8;
+  GTK_IM_FLAG_REPLACE     = 16;
+
   //-------------
   //end of messages that are sent to the interface
   //-------------
synedit_linux_fcitx.patch (10,243 bytes)   

CudaText man

2021-04-09 16:26

reporter   ~0130200

Do-wan, if you will help us here, it will be good:
https://github.com/Alexey-T/CudaText/issues/2874
I cannot test this stuff myself.

Do-wan Kim

2021-04-09 16:43

reporter   ~0130201

@CudaText man
It still have bugs. I will try.

Do-wan Kim

2021-04-10 12:36

reporter   ~0130227

Fix candidiate window position properly.
synedit_im_fcitx_chn_fixcandidate.patch (10,908 bytes)   
Index: components/synedit/synedit.pp
===================================================================
--- components/synedit/synedit.pp	(리비전 64946)
+++ components/synedit/synedit.pp	(작업 사본)
@@ -623,6 +623,9 @@
     FOnClickLink: TMouseEvent;
     FOnMouseLink: TSynMouseLinkEvent;
     FPendingFoldState: String;
+    {$ifdef LCLGtk2}
+    FIMESelText: string;
+    {$endif}
 
     procedure UpdateScreenCaret;
     procedure AquirePrimarySelection;
@@ -1249,6 +1252,10 @@
     property OnSpecialLineColors: TSpecialLineColorsEvent read FOnSpecialLineColors write SetSpecialLineColors;  deprecated;
     property OnSpecialLineMarkup: TSpecialLineMarkupEvent read FOnSpecialLineMarkup write SetSpecialLineMarkup;
     property OnStatusChange: TStatusChangeEvent read fOnStatusChange write fOnStatusChange;
+{$ifdef LCLGtk2}
+  protected
+    procedure GTK_IMComposition(var Message: TMessage); message LM_IM_COMPOSITION;
+{$endif}
   end;
 
   TSynEdit = class(TCustomSynEdit)
@@ -1370,6 +1377,11 @@
 
 implementation
 
+{$ifdef LCLGtk2}
+uses
+  gtk2, Gtk2Globals;
+{$endif}
+
 var
   LOG_SynMouseEvents: PLazLoggerLogGroup;
 
@@ -4333,6 +4345,49 @@
   Result := FMarkupManager.Count;
 end;
 
+{$ifdef LCLGtk2}
+procedure TCustomSynEdit.GTK_IMComposition(var Message: TMessage);
+var
+  IMStr:string;
+  i:Integer;
+begin
+  if (not ReadOnly) then
+  begin
+    if (Message.WParam and GTK_IM_FLAG_START<>0) then
+      FIMESelText:=SelText;
+    // to do : accurate candidate position
+    // set candidate position
+    if (Message.WParam and (GTK_IM_FLAG_START or GTK_IM_FLAG_PREEDIT))<>0 then
+      IM_Context_Set_Cursor_Pos(CaretXPix,CaretYPix+LineHeight);
+    // valid string at composition & commit
+    if Message.WParam and (GTK_IM_FLAG_COMMIT or GTK_IM_FLAG_PREEDIT)<>0 then
+    begin
+      // insert preedit or commit string
+      IMStr:=pchar(Message.LParam);
+      for i:=1 to Length(IMStr) do
+        CommandProcessor(ecChar,IMStr[i],nil);
+      // select last preedit
+      if (Message.WParam and GTK_IM_FLAG_COMMIT=0) then
+      begin
+        if Length(IMStr)>0 then
+          for i:=1 to Length(UTF8Decode(IMStr)) do
+            CommandProcessor(ecSelLeft,#0,nil)
+      end
+      else
+        FIMESelText:='';
+    end;
+    // end composition
+    if (Message.WParam and GTK_IM_FLAG_END<>0) and SelAvail then
+    begin
+      ClearSelection;
+      // restore selecttion before preedit.
+      for i:=1 to Length(FIMESelText) do
+        CommandProcessor(ecChar,FIMESelText[i],nil);
+    end;
+  end;
+end;
+{$endif}
+
 procedure TCustomSynEdit.SetCaretTypeSize(AType: TSynCaretType; AWidth, AHeight, AXOffs,
   AYOffs: Integer);
 begin
Index: lcl/interfaces/gtk2/gtk2globals.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2globals.pp	(리비전 64946)
+++ lcl/interfaces/gtk2/gtk2globals.pp	(작업 사본)
@@ -63,9 +63,14 @@
 var
   im_context: PGtkIMContext = nil;
   im_context_widget: PGtkWidget = nil;
+  im_context_skipdelete: Boolean = False;
+  im_context_use: Boolean = False;
   im_context_string: string = '';
+  im_context_string_commit: string = '';
+  im_context_string_preedit: string = '';
 
 procedure ResetDefaultIMContext;
+procedure IM_Context_Set_Cursor_Pos(X, Y: gint);
 
 var
   LastFileSelectRow : gint;
@@ -420,9 +425,23 @@
     gtk_im_context_set_client_window(im_context,nil);
   end;
   im_context_widget:=nil;
+  im_context_use:=False;
+  im_context_skipdelete:=True;
   im_context_string:='';
+  im_context_string_commit:='';
 end;
 
+procedure IM_Context_Set_Cursor_Pos(X, Y: gint);
+var
+  CurPos: TGdkRectangle;
+begin
+  CurPos.x:=X;
+  CurPos.y:=Y;
+  CurPos.width:=0; // fix invalid candidate window position
+  CurPos.height:=0;
+  gtk_im_context_set_cursor_location(im_context,@CurPos);
+end;
+
 procedure AddCharsetEncoding(CharSet: Byte; CharSetReg, CharSetCod: CharSetStr;
   ToEnum:boolean=true; CrPart:boolean=false; CcPart:boolean=false);
 var
Index: lcl/interfaces/gtk2/gtk2proc.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2proc.inc	(리비전 64946)
+++ lcl/interfaces/gtk2/gtk2proc.inc	(작업 사본)
@@ -1999,7 +1999,7 @@
       end;
       Exit;
     end;
-    Result := (AEvent^.Length > 0) or (GetSpecialChar <> #0);
+    Result := ((not im_context_use) and (AEvent^.Length > 0)) or (GetSpecialChar <> #0);
   end;
   
   function KeyAlreadyHandledByGtk: boolean;
@@ -2386,7 +2386,7 @@
 
       Msg.KeyData := CommonKeyData or (Flags shl 16) or $0001 {TODO:  repeatcount};
 
-      if not KeyAlreadyHandledByGtk
+      if (not KeyAlreadyHandledByGtk) and (not im_context_use)
       then begin
         // send the (Sys)KeyDown message directly to the LCL
         NotifyApplicationUserInput(TControl(TargetObj), Msg.Msg);
@@ -2424,7 +2424,7 @@
   end;
 
   // send keypresses
-  if not EventStopped and AHandleDown then
+  if not EventStopped and (AHandleDown or (im_context_string<>'')) then
   begin
     // send the UTF8 keypress
     PassUTF8AsKeyPress := False;
@@ -2437,7 +2437,13 @@
         im_context_string:='';// clear, to avoid sending again
       end
       else
+      if im_context_string_commit <> '' then
       begin
+        Character := UTF8Copy(im_context_string_commit,1,1);
+        im_context_string_commit:='';// clear, to avoid sending again
+      end
+      else
+      begin
         KeyPressesChar := GetSpecialChar;
         if KeyPressesChar <> #0 then
           Character := KeyPressesChar
Index: lcl/interfaces/gtk2/gtk2widgetset.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2widgetset.inc	(리비전 64946)
+++ lcl/interfaces/gtk2/gtk2widgetset.inc	(작업 사본)
@@ -46,6 +46,7 @@
 end;
 {$ENDIF}
 
+// To do: eat 2 key stroke after re-focus.
 function GTK2FocusCB( widget: PGtkWidget; event:PGdkEventFocus;
   data: gPointer) : GBoolean; cdecl;
 var
@@ -54,7 +55,12 @@
   Status := GTKFocusCB(Widget, Event, Data);
 
   if GtkWidgetIsA(Widget,GTK_APIWIDGETCLIENT_TYPE) then
-    Result := Status
+  begin
+    Result := Status;
+    gtk_im_context_set_client_window(im_context,GetControlWindow(widget));
+    gtk_im_context_focus_in(im_context);
+    im_context_widget:=widget;
+  end
   else
     Result := False;
 end;
@@ -79,7 +85,13 @@
   Status := GTKKillFocusCB(Widget, Event, Data);
 
   if GtkWidgetIsA(Widget,GTK_APIWIDGETCLIENT_TYPE) then
-    Result := Status
+  begin
+    Result := Status;
+    gtk_im_context_focus_out(im_context);
+    gtk_im_context_set_client_window(im_context,nil);
+    im_context_widget:=nil;
+    ResetDefaultIMContext;
+  end
   else
     Result := False;
 end;
@@ -216,11 +228,91 @@
 
 procedure gtk_commit_cb ({%H-}context: PGtkIMContext; const Str: Pgchar;
   {%H-}Data: Pointer); cdecl;
+var
+  control:TWinControl;
+  i:Integer;
 begin
   //DebugLn(['gtk_commit_cb ',dbgstr(Str),'="',Str,'"']);
-  im_context_string:=Str;
+  { fix double normal character input }
+  if not im_context_use then
+    im_context_string:=Str // key at non-composition
+    else
+      im_context_string_commit:=Str; // key at composition
+  { commit composition string, not key }
+  if (im_context_widget<>nil) then
+      begin
+        im_context_skipdelete:=True;
+        Control:=TWinControl(GetNearestLCLObject(im_context_widget));
+        SendMessage(control.Handle,LM_IM_COMPOSITION,GTK_IM_FLAG_COMMIT,LPARAM(pchar(im_context_string_commit)));
+        im_context_string_commit:='';
+      end;
 end;
 
+procedure gtk_preedit_start_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+begin
+  if (im_context_widget<>nil) and
+     (GetNearestLCLObject(im_context_widget) is TCustomControl) then
+  begin
+    im_context_use:=True;
+    im_context_skipDelete:=True;
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    SendMessage(control.Handle,LM_IM_COMPOSITION,GTK_IM_FLAG_START,LPARAM(context));
+  end;
+end;
+
+procedure gtk_preedit_end_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+begin
+  if (im_context_widget<>nil) and
+     (GetNearestLCLObject(im_context_widget) is TCustomControl) then
+  begin
+    im_context_use:=False;
+    im_context_skipDelete:=False;
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    SendMessage(control.Handle,LM_IM_COMPOSITION,GTK_IM_FLAG_END,LPARAM(context));
+  end;
+end;
+
+procedure gtk_preedit_changed_cb({%H-}context:PGtkIMContext; {%H-}Data:Pointer); cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+  str:Pgchar;
+  pangoattr:PPangoAttrList;
+  curpos:gint;
+begin
+  if (im_context_widget<>nil) then
+  begin
+    im_context_use:=True;
+    gtk_im_context_get_preedit_string(context,@str,pangoattr,@curpos);
+    im_context_string_preedit:=str;
+    g_free(str);
+    pango_attr_list_unref(pangoattr);
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    Flag:=GTK_IM_FLAG_PREEDIT;
+    if (not im_context_skipDelete) then begin
+      Flag:=Flag or GTK_IM_FLAG_REPLACE;
+    end else
+      im_context_skipDelete:=False;
+    SendMessage(control.Handle,LM_IM_COMPOSITION,Flag,LPARAM(pchar(im_context_string_preedit)));
+  end;
+end;
+
+function gtk_retrieve_surrounding_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer):gboolean; cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+begin
+  Result:=False;
+  //im_context_use:=True;
+end;
+
+
 {------------------------------------------------------------------------------
   Function: TGtk2WidgetSet._SetCallbackEx
 
@@ -959,6 +1051,15 @@
   im_context:=gtk_im_multicontext_new;
   g_signal_connect (G_OBJECT (im_context), 'commit',
     G_CALLBACK (@gtk_commit_cb), nil);
+  // Input method
+  g_signal_connect (G_OBJECT (im_context), 'preedit-start',
+    G_CALLBACK (@gtk_preedit_start_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'preedit-end',
+    G_CALLBACK (@gtk_preedit_end_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'preedit-changed',
+    G_CALLBACK (@gtk_preedit_changed_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'retrieve_surrounding',
+    G_CALLBACK (@gtk_retrieve_surrounding_cb), nil);
   {$IFDEF HASX}
   if IsNoTransientWM then
   begin
Index: lcl/lmessages.pp
===================================================================
--- lcl/lmessages.pp	(리비전 64946)
+++ lcl/lmessages.pp	(작업 사본)
@@ -97,7 +97,15 @@
   LM_INTERFACELAST  = LM_LCL + 199;
   
   LM_UNKNOWN        = LM_INTERFACELAST + 1;
+  LM_IM_COMPOSITION = LM_USER + $FFF0; // gtk IM
 
+  // GTK IM Flags
+  GTK_IM_FLAG_START       = 1;
+  GTK_IM_FLAG_PREEDIT     = 2;
+  GTK_IM_FLAG_END         = 4;
+  GTK_IM_FLAG_COMMIT      = 8;
+  GTK_IM_FLAG_REPLACE     = 16;
+
   //-------------
   //end of messages that are sent to the interface
   //-------------

Martin Friebe

2021-04-13 18:47

manager   ~0130360

OK, thanks first.

An applied version is now available here https://github.com/User4martin/lazarus/compare/f-syn-linux-im
(I might force push occasionally, to rebase on the latest work)

I haven't fully tested yet, or looked in deep into it.

1)
The most important bit of testing will be to ensure that the parts added to the widgetset will not break anything (i.e. not cause any issues, such as the duplicated key stuff some users had/have).
In my brief testing so far I have not noted any issues.

The widgetset changes must work (i.e. at least keep current behaviour, or improve on current behaviour) for:
- native wigdets (TEdit, TMemo, editable dropdowns
- SynEdit, without the patch applied (that is any none native control, without special IM handling)
- SynEdit with the patch applied.

On some systems (e.g. Ubuntu) I tested with Japanese Kana, and at least some basic input can be made, even without the patch. That must of course be kept. And it appears that the patch does not interfere => So good.

2) SynEdit
Look at the windows IME. => the actual code is in a separate unit.
The gtk2 code should be moved.
Though that can be done, when all other issues are resolved.

3) OPTIONAL
Detecting when SynEdit is not patched, and send the final string.
This may be some work, if it requires to display a window to edit the IM input.
I have to check how my Ubuntu currently deals with that

4) OPTIONAL / More of an idea for the future....
Investigate if the messages used by WIN IME can be re-used.
So other (end user) components may benefit, if they already impelment the Windows messages.

Do-wan Kim

2021-04-14 00:55

reporter   ~0130362

Last edited: 2021-04-14 01:06

View 3 revisions

Clean unused code patch.
Some code is missing in your 'gtk2proc.inc'. It used for non-composition state key inputing.

'retrieve_surrounding' signal may not need for IM processing.
synedit_fcitx_clean.patch (10,699 bytes)   
Index: components/synedit/synedit.pp
===================================================================
--- components/synedit/synedit.pp	(리비전 64976)
+++ components/synedit/synedit.pp	(작업 사본)
@@ -623,6 +623,9 @@
     FOnClickLink: TMouseEvent;
     FOnMouseLink: TSynMouseLinkEvent;
     FPendingFoldState: String;
+    {$ifdef LCLGtk2}
+    FIMESelText: string;
+    {$endif}
 
     procedure UpdateScreenCaret;
     procedure AquirePrimarySelection;
@@ -1249,6 +1252,10 @@
     property OnSpecialLineColors: TSpecialLineColorsEvent read FOnSpecialLineColors write SetSpecialLineColors;  deprecated;
     property OnSpecialLineMarkup: TSpecialLineMarkupEvent read FOnSpecialLineMarkup write SetSpecialLineMarkup;
     property OnStatusChange: TStatusChangeEvent read fOnStatusChange write fOnStatusChange;
+{$ifdef LCLGtk2}
+  protected
+    procedure GTK_IMComposition(var Message: TMessage); message LM_IM_COMPOSITION;
+{$endif}
   end;
 
   TSynEdit = class(TCustomSynEdit)
@@ -1370,6 +1377,11 @@
 
 implementation
 
+{$ifdef LCLGtk2}
+uses
+  gtk2, Gtk2Globals;
+{$endif}
+
 var
   LOG_SynMouseEvents: PLazLoggerLogGroup;
 
@@ -4333,6 +4345,49 @@
   Result := FMarkupManager.Count;
 end;
 
+{$ifdef LCLGtk2}
+procedure TCustomSynEdit.GTK_IMComposition(var Message: TMessage);
+var
+  IMStr:string;
+  i:Integer;
+begin
+  if (not ReadOnly) then
+  begin
+    if (Message.WParam and GTK_IM_FLAG_START<>0) then
+      FIMESelText:=SelText;
+    // to do : accurate candidate position
+    // set candidate position
+    if (Message.WParam and (GTK_IM_FLAG_START or GTK_IM_FLAG_PREEDIT))<>0 then
+      IM_Context_Set_Cursor_Pos(CaretXPix,CaretYPix+LineHeight);
+    // valid string at composition & commit
+    if Message.WParam and (GTK_IM_FLAG_COMMIT or GTK_IM_FLAG_PREEDIT)<>0 then
+    begin
+      // insert preedit or commit string
+      IMStr:=pchar(Message.LParam);
+      for i:=1 to Length(IMStr) do
+        CommandProcessor(ecChar,IMStr[i],nil);
+      // select last preedit
+      if (Message.WParam and GTK_IM_FLAG_COMMIT=0) then
+      begin
+        if Length(IMStr)>0 then
+          for i:=1 to Length(UTF8Decode(IMStr)) do
+            CommandProcessor(ecSelLeft,#0,nil)
+      end
+      else
+        FIMESelText:='';
+    end;
+    // end composition
+    if (Message.WParam and GTK_IM_FLAG_END<>0) and (FIMESelText<>'') then
+    begin
+      ClearSelection;
+      // restore selection before preedit.
+      for i:=1 to Length(FIMESelText) do
+        CommandProcessor(ecChar,FIMESelText[i],nil);
+    end;
+  end;
+end;
+{$endif}
+
 procedure TCustomSynEdit.SetCaretTypeSize(AType: TSynCaretType; AWidth, AHeight, AXOffs,
   AYOffs: Integer);
 begin
Index: lcl/interfaces/gtk2/gtk2globals.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2globals.pp	(리비전 64976)
+++ lcl/interfaces/gtk2/gtk2globals.pp	(작업 사본)
@@ -63,9 +63,14 @@
 var
   im_context: PGtkIMContext = nil;
   im_context_widget: PGtkWidget = nil;
+  im_context_skipdelete: Boolean = False;
+  im_context_use: Boolean = False;
   im_context_string: string = '';
+  im_context_string_commit: string = '';
+  im_context_string_preedit: string = '';
 
 procedure ResetDefaultIMContext;
+procedure IM_Context_Set_Cursor_Pos(X, Y: gint);
 
 var
   LastFileSelectRow : gint;
@@ -420,9 +425,23 @@
     gtk_im_context_set_client_window(im_context,nil);
   end;
   im_context_widget:=nil;
+  im_context_use:=False;
+  im_context_skipdelete:=True;
   im_context_string:='';
+  im_context_string_commit:='';
 end;
 
+procedure IM_Context_Set_Cursor_Pos(X, Y: gint);
+var
+  CurPos: TGdkRectangle;
+begin
+  CurPos.x:=X;
+  CurPos.y:=Y;
+  CurPos.width:=0; // fix invalid candidate window position
+  CurPos.height:=0;
+  gtk_im_context_set_cursor_location(im_context,@CurPos);
+end;
+
 procedure AddCharsetEncoding(CharSet: Byte; CharSetReg, CharSetCod: CharSetStr;
   ToEnum:boolean=true; CrPart:boolean=false; CcPart:boolean=false);
 var
Index: lcl/interfaces/gtk2/gtk2proc.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2proc.inc	(리비전 64976)
+++ lcl/interfaces/gtk2/gtk2proc.inc	(작업 사본)
@@ -1999,7 +1999,7 @@
       end;
       Exit;
     end;
-    Result := (AEvent^.Length > 0) or (GetSpecialChar <> #0);
+    Result := ((not im_context_use) and (AEvent^.Length > 0)) or (GetSpecialChar <> #0);
   end;
   
   function KeyAlreadyHandledByGtk: boolean;
@@ -2386,7 +2386,7 @@
 
       Msg.KeyData := CommonKeyData or (Flags shl 16) or $0001 {TODO:  repeatcount};
 
-      if not KeyAlreadyHandledByGtk
+      if (not KeyAlreadyHandledByGtk)
       then begin
         // send the (Sys)KeyDown message directly to the LCL
         NotifyApplicationUserInput(TControl(TargetObj), Msg.Msg);
@@ -2424,7 +2424,10 @@
   end;
 
   // send keypresses
-  if not EventStopped and AHandleDown then
+  // im_context_string checking be used for process when non-composition state
+  // without checking, non-composite keys(number backspace enter, and etc) doesn't input.
+  // (check with press number keys or backspace without candidate window in cjk input state)
+  if not EventStopped and (AHandleDown or (im_context_string<>'')) then
   begin
     // send the UTF8 keypress
     PassUTF8AsKeyPress := False;
Index: lcl/interfaces/gtk2/gtk2widgetset.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2widgetset.inc	(리비전 64976)
+++ lcl/interfaces/gtk2/gtk2widgetset.inc	(작업 사본)
@@ -46,6 +46,7 @@
 end;
 {$ENDIF}
 
+// To do: eat 2 key stroke after re-focus.
 function GTK2FocusCB( widget: PGtkWidget; event:PGdkEventFocus;
   data: gPointer) : GBoolean; cdecl;
 var
@@ -54,7 +55,12 @@
   Status := GTKFocusCB(Widget, Event, Data);
 
   if GtkWidgetIsA(Widget,GTK_APIWIDGETCLIENT_TYPE) then
-    Result := Status
+  begin
+    Result := Status;
+    gtk_im_context_set_client_window(im_context,GetControlWindow(widget));
+    gtk_im_context_focus_in(im_context);
+    im_context_widget:=widget;
+  end
   else
     Result := False;
 end;
@@ -79,7 +85,13 @@
   Status := GTKKillFocusCB(Widget, Event, Data);
 
   if GtkWidgetIsA(Widget,GTK_APIWIDGETCLIENT_TYPE) then
-    Result := Status
+  begin
+    Result := Status;
+    gtk_im_context_focus_out(im_context);
+    gtk_im_context_set_client_window(im_context,nil);
+    im_context_widget:=nil;
+    ResetDefaultIMContext;
+  end
   else
     Result := False;
 end;
@@ -216,11 +228,91 @@
 
 procedure gtk_commit_cb ({%H-}context: PGtkIMContext; const Str: Pgchar;
   {%H-}Data: Pointer); cdecl;
+var
+  control:TWinControl;
+  i:Integer;
 begin
   //DebugLn(['gtk_commit_cb ',dbgstr(Str),'="',Str,'"']);
-  im_context_string:=Str;
+  { fix double normal character input }
+  if not im_context_use then
+    im_context_string:=Str // key at non-composition
+    else
+      im_context_string_commit:=Str; // key at composition
+  { commit composition string, not key }
+  if (im_context_widget<>nil) then
+      begin
+        im_context_skipdelete:=True;
+        Control:=TWinControl(GetNearestLCLObject(im_context_widget));
+        SendMessage(control.Handle,LM_IM_COMPOSITION,GTK_IM_FLAG_COMMIT,LPARAM(pchar(im_context_string_commit)));
+        im_context_string_commit:='';
+      end;
 end;
 
+procedure gtk_preedit_start_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+begin
+  if (im_context_widget<>nil) and
+     (GetNearestLCLObject(im_context_widget) is TCustomControl) then
+  begin
+    im_context_use:=True;
+    im_context_skipDelete:=True;
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    SendMessage(control.Handle,LM_IM_COMPOSITION,GTK_IM_FLAG_START,LPARAM(context));
+  end;
+end;
+
+procedure gtk_preedit_end_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+begin
+  if (im_context_widget<>nil) and
+     (GetNearestLCLObject(im_context_widget) is TCustomControl) then
+  begin
+    im_context_use:=False;
+    im_context_skipDelete:=False;
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    SendMessage(control.Handle,LM_IM_COMPOSITION,GTK_IM_FLAG_END,LPARAM(context));
+  end;
+end;
+
+procedure gtk_preedit_changed_cb({%H-}context:PGtkIMContext; {%H-}Data:Pointer); cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+  str:Pgchar;
+  pangoattr:PPangoAttrList;
+  curpos:gint;
+begin
+  if (im_context_widget<>nil) then
+  begin
+    im_context_use:=True;
+    gtk_im_context_get_preedit_string(context,@str,pangoattr,@curpos);
+    im_context_string_preedit:=str;
+    g_free(str);
+    pango_attr_list_unref(pangoattr);
+    control:=TWinControl(GetNearestLCLObject(im_context_widget));
+    Flag:=GTK_IM_FLAG_PREEDIT;
+    if (not im_context_skipDelete) then begin
+      Flag:=Flag or GTK_IM_FLAG_REPLACE;
+    end else
+      im_context_skipDelete:=False;
+    SendMessage(control.Handle,LM_IM_COMPOSITION,Flag,LPARAM(pchar(im_context_string_preedit)));
+  end;
+end;
+
+function gtk_retrieve_surrounding_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer):gboolean; cdecl;
+var
+  control:TWinControl;
+  Flag:WPARAM;
+begin
+  Result:=False;
+  //im_context_use:=True;
+end;
+
+
 {------------------------------------------------------------------------------
   Function: TGtk2WidgetSet._SetCallbackEx
 
@@ -959,6 +1051,15 @@
   im_context:=gtk_im_multicontext_new;
   g_signal_connect (G_OBJECT (im_context), 'commit',
     G_CALLBACK (@gtk_commit_cb), nil);
+  // Input method
+  g_signal_connect (G_OBJECT (im_context), 'preedit-start',
+    G_CALLBACK (@gtk_preedit_start_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'preedit-end',
+    G_CALLBACK (@gtk_preedit_end_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'preedit-changed',
+    G_CALLBACK (@gtk_preedit_changed_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'retrieve_surrounding',
+    G_CALLBACK (@gtk_retrieve_surrounding_cb), nil);
   {$IFDEF HASX}
   if IsNoTransientWM then
   begin
Index: lcl/lmessages.pp
===================================================================
--- lcl/lmessages.pp	(리비전 64976)
+++ lcl/lmessages.pp	(작업 사본)
@@ -97,7 +97,15 @@
   LM_INTERFACELAST  = LM_LCL + 199;
   
   LM_UNKNOWN        = LM_INTERFACELAST + 1;
+  LM_IM_COMPOSITION = LM_USER + $FFF0; // gtk IM
 
+  // GTK IM Flags
+  GTK_IM_FLAG_START       = 1;
+  GTK_IM_FLAG_PREEDIT     = 2;
+  GTK_IM_FLAG_END         = 4;
+  GTK_IM_FLAG_COMMIT      = 8;
+  GTK_IM_FLAG_REPLACE     = 16;
+
   //-------------
   //end of messages that are sent to the interface
   //-------------
synedit_fcitx_clean.patch (10,699 bytes)   

CudaText man

2021-04-20 09:34

reporter   ~0130475

Martin, I am interested in this fix. could you validate it please?

Martin Friebe

2021-06-06 17:53

manager   ~0131187

I have taken the last patch "synedit_fcitx_clean.patch", put some IFDEF around and committed it to svn trunk.

I still have problem with setting up my linux to trigger the code at all. (and I only have time every now and then).
But now anyone can compile with the defines, and test themself.

The patch also has some code, that refers to double key stroke issues.
It would be nice to find, if that ma

So if anyone knows how to
- produce (before our IDE fix) the double key stroke issue
- activate an IM on linux
could you please test?

Define with -d (or via IDE)

WITH_GTK2_IM to activate the gtk 2 parts
WITHOUT_GTK_DOUBLEKEYPRESS_CHECK to deactivate the current fix for double keystrokes

Deactivating the current fix, should mean you get duplicates unless its fixed by the code in the IM.
Note that the IM does not actually target the issue, so if we have to keep the old fix then ok.


Gtk2IME This will activate the code in SynEdit => in order to allow input via an IM.
If you can get that to work, I have questions how to setup my Linux to trigger it....

Do-wan Kim

2021-06-07 03:03

reporter   ~0131190

It works good.

Do-wan Kim

2021-06-08 06:14

reporter   ~0131194

More reliable patch. It works partially under ibus IM.
38730_fcitx_im.patch (3,448 bytes)   
Index: components/synedit/lazsyngtk2imm.pas
===================================================================
--- components/synedit/lazsyngtk2imm.pas	(리비전 65182)
+++ components/synedit/lazsyngtk2imm.pas	(작업 사본)
@@ -1,6 +1,7 @@
 unit LazSynGtk2IMM;
 
 {$mode objfpc}{$H+}
+{$define WITH_GTK2_IM}
 
 interface
 
@@ -40,15 +41,15 @@
 {$IFDEF WITH_GTK2_IM}
   if (not FriendEdit.ReadOnly) then
   begin
-    if (Message.WParam and GTK_IM_FLAG_START<>0) then
-      FIMESelText := FriendEdit.SelText;
-    // to do : accurate candidate position
     // set candidate position
     if (Message.WParam and (GTK_IM_FLAG_START or GTK_IM_FLAG_PREEDIT))<>0 then
       IM_Context_Set_Cursor_Pos(FriendEdit.CaretXPix,FriendEdit.CaretYPix+FriendEdit.LineHeight);
     // valid string at composition & commit
-    if Message.WParam and (GTK_IM_FLAG_COMMIT or GTK_IM_FLAG_PREEDIT)<>0 then
+    if (Message.WParam and (GTK_IM_FLAG_COMMIT or GTK_IM_FLAG_PREEDIT)<>0) then
     begin
+      // save selected text
+      if Message.WParam and GTK_IM_FLAG_REPLACE=0 then
+        FIMESelText:=FriendEdit.SelText;
       // insert preedit or commit string
       IMStr:=pchar(Message.LParam);
       for i:=1 to Length(IMStr) do
@@ -63,7 +64,8 @@
       else
         FIMESelText:='';
     end;
-    // end composition
+    // end composition and complete composition
+    // To Do : skip insert saved selection after commit with ibus.
     if (Message.WParam and GTK_IM_FLAG_END<>0) and (FIMESelText<>'') then
     begin
       TSynEdit(FriendEdit).ClearSelection;
Index: components/synedit/synedit.pp
===================================================================
--- components/synedit/synedit.pp	(리비전 65182)
+++ components/synedit/synedit.pp	(작업 사본)
@@ -49,7 +49,9 @@
     {$DEFINE WinIMEFull}
   {$ENDIF}
 {$ENDIF}
-{..$DEFINE Gtk2IME}
+{$ifdef LCLGTK2}
+{$DEFINE Gtk2IME}
+{$endif}
 
 {$I synedit.inc}
 
Index: lcl/interfaces/gtk2/gtk2globals.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2globals.pp	(리비전 65182)
+++ lcl/interfaces/gtk2/gtk2globals.pp	(작업 사본)
@@ -9,6 +9,7 @@
 unit Gtk2Globals;
 
 {$mode objfpc}{$H+}
+{$define WITH_GTK2_IM}
 
 interface
 
Index: lcl/interfaces/gtk2/gtk2int.pas
===================================================================
--- lcl/interfaces/gtk2/gtk2int.pas	(리비전 65182)
+++ lcl/interfaces/gtk2/gtk2int.pas	(작업 사본)
@@ -17,6 +17,7 @@
 unit Gtk2Int;
 
 {$mode objfpc}{$H+}
+{$define WITH_GTK2_IM}
 
 interface
 
Index: lcl/interfaces/gtk2/gtk2proc.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2proc.pp	(리비전 65182)
+++ lcl/interfaces/gtk2/gtk2proc.pp	(작업 사본)
@@ -16,6 +16,7 @@
 unit Gtk2Proc;
 
 {$mode objfpc}{$H+}
+{$define WITH_GTK2_IM}
 
 interface
 
Index: lcl/interfaces/gtk2/gtk2widgetset.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2widgetset.inc	(리비전 65182)
+++ lcl/interfaces/gtk2/gtk2widgetset.inc	(작업 사본)
@@ -282,8 +282,6 @@
   if (im_context_widget<>nil) and
      (GetNearestLCLObject(im_context_widget) is TCustomControl) then
   begin
-    im_context_use:=False;
-    im_context_skipDelete:=False;
     control:=TWinControl(GetNearestLCLObject(im_context_widget));
     SendMessage(control.Handle,LM_IM_COMPOSITION,GTK_IM_FLAG_END,LPARAM(context));
   end;
38730_fcitx_im.patch (3,448 bytes)   

CudaText man

2021-06-08 13:17

reporter   ~0131200

I wait until this will be applied, and then I can adapt ATSynEdit.

Do-wan Kim

2021-06-09 03:39

reporter   ~0131209

More fix in Synedit for IBUS IM.
38730_fcitex_ibus_more_fix.patch (4,211 bytes)   
Index: components/synedit/lazsyngtk2imm.pas
===================================================================
--- components/synedit/lazsyngtk2imm.pas	(리비전 65182)
+++ components/synedit/lazsyngtk2imm.pas	(작업 사본)
@@ -1,6 +1,7 @@
 unit LazSynGtk2IMM;
 
 {$mode objfpc}{$H+}
+{$define WITH_GTK2_IM}
 
 interface
 
@@ -40,17 +41,21 @@
 {$IFDEF WITH_GTK2_IM}
   if (not FriendEdit.ReadOnly) then
   begin
-    if (Message.WParam and GTK_IM_FLAG_START<>0) then
-      FIMESelText := FriendEdit.SelText;
-    // to do : accurate candidate position
     // set candidate position
     if (Message.WParam and (GTK_IM_FLAG_START or GTK_IM_FLAG_PREEDIT))<>0 then
       IM_Context_Set_Cursor_Pos(FriendEdit.CaretXPix,FriendEdit.CaretYPix+FriendEdit.LineHeight);
     // valid string at composition & commit
-    if Message.WParam and (GTK_IM_FLAG_COMMIT or GTK_IM_FLAG_PREEDIT)<>0 then
+    if (Message.WParam and (GTK_IM_FLAG_COMMIT or GTK_IM_FLAG_PREEDIT)<>0) then
     begin
+      // save selected text
+      if Message.WParam and GTK_IM_FLAG_REPLACE=0 then
+        FIMESelText:=FriendEdit.SelText;
       // insert preedit or commit string
       IMStr:=pchar(Message.LParam);
+      // for IBUS IM
+      if (Length(IMStr)=0) and (Message.WParam and GTK_IM_FLAG_REPLACE<>0) then
+        TSynEdit(FriendEdit).CommandProcessor(ecDeleteChar,#0,nil)
+      else
       for i:=1 to Length(IMStr) do
         TSynEdit(FriendEdit).CommandProcessor(ecChar,IMStr[i],nil);
       // select last preedit
@@ -58,12 +63,13 @@
       begin
         if Length(IMStr)>0 then
           for i:=1 to Length(UTF8Decode(IMStr)) do
-            TSynEdit(FriendEdit).CommandProcessor(ecSelLeft,#0,nil)
+            TSynEdit(FriendEdit).CommandProcessor(ecSelLeft,#0,nil);
       end
       else
         FIMESelText:='';
     end;
-    // end composition
+    // end composition and complete composition
+    // To Do : skip insert saved selection after commit with ibus.
     if (Message.WParam and GTK_IM_FLAG_END<>0) and (FIMESelText<>'') then
     begin
       TSynEdit(FriendEdit).ClearSelection;
@@ -70,6 +76,7 @@
       // restore selection before preedit.
       for i:=1 to Length(FIMESelText) do
         TSynEdit(FriendEdit).CommandProcessor(ecChar,FIMESelText[i],nil);
+      FIMESelText:='';
     end;
   end;
 {$ENDIF}
Index: components/synedit/synedit.pp
===================================================================
--- components/synedit/synedit.pp	(리비전 65182)
+++ components/synedit/synedit.pp	(작업 사본)
@@ -49,7 +49,9 @@
     {$DEFINE WinIMEFull}
   {$ENDIF}
 {$ENDIF}
-{..$DEFINE Gtk2IME}
+{$ifdef LCLGTK2}
+{$DEFINE Gtk2IME}
+{$endif}
 
 {$I synedit.inc}
 
Index: lcl/interfaces/gtk2/gtk2globals.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2globals.pp	(리비전 65182)
+++ lcl/interfaces/gtk2/gtk2globals.pp	(작업 사본)
@@ -9,6 +9,7 @@
 unit Gtk2Globals;
 
 {$mode objfpc}{$H+}
+{$define WITH_GTK2_IM}
 
 interface
 
Index: lcl/interfaces/gtk2/gtk2int.pas
===================================================================
--- lcl/interfaces/gtk2/gtk2int.pas	(리비전 65182)
+++ lcl/interfaces/gtk2/gtk2int.pas	(작업 사본)
@@ -17,6 +17,7 @@
 unit Gtk2Int;
 
 {$mode objfpc}{$H+}
+{$define WITH_GTK2_IM}
 
 interface
 
Index: lcl/interfaces/gtk2/gtk2proc.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2proc.pp	(리비전 65182)
+++ lcl/interfaces/gtk2/gtk2proc.pp	(작업 사본)
@@ -16,6 +16,7 @@
 unit Gtk2Proc;
 
 {$mode objfpc}{$H+}
+{$define WITH_GTK2_IM}
 
 interface
 
Index: lcl/interfaces/gtk2/gtk2widgetset.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2widgetset.inc	(리비전 65182)
+++ lcl/interfaces/gtk2/gtk2widgetset.inc	(작업 사본)
@@ -282,8 +282,6 @@
   if (im_context_widget<>nil) and
      (GetNearestLCLObject(im_context_widget) is TCustomControl) then
   begin
-    im_context_use:=False;
-    im_context_skipDelete:=False;
     control:=TWinControl(GetNearestLCLObject(im_context_widget));
     SendMessage(control.Handle,LM_IM_COMPOSITION,GTK_IM_FLAG_END,LPARAM(context));
   end;

Martin Friebe

2021-06-15 13:07

manager   ~0131317

thanks, applied

Issue History

Date Modified Username Field Change
2021-04-08 16:20 Do-wan Kim New Issue
2021-04-08 16:20 Do-wan Kim File Added: synedit_linux_fcitx.patch
2021-04-08 19:10 Martin Friebe Assigned To => Martin Friebe
2021-04-08 19:10 Martin Friebe Status new => assigned
2021-04-09 16:26 CudaText man Note Added: 0130200
2021-04-09 16:43 Do-wan Kim Note Added: 0130201
2021-04-10 12:36 Do-wan Kim Note Added: 0130227
2021-04-10 12:36 Do-wan Kim File Added: synedit_im_fcitx_chn_fixcandidate.patch
2021-04-13 18:47 Martin Friebe Note Added: 0130360
2021-04-14 00:55 Do-wan Kim Note Added: 0130362
2021-04-14 00:55 Do-wan Kim File Added: synedit_fcitx_clean.patch
2021-04-14 01:05 Do-wan Kim Note Edited: 0130362 View Revisions
2021-04-14 01:06 Do-wan Kim Note Edited: 0130362 View Revisions
2021-04-20 09:34 CudaText man Note Added: 0130475
2021-06-06 17:54 Martin Friebe Note Added: 0131187
2021-06-07 03:03 Do-wan Kim Note Added: 0131190
2021-06-08 06:14 Do-wan Kim Note Added: 0131194
2021-06-08 06:14 Do-wan Kim File Added: 38730_fcitx_im.patch
2021-06-08 13:17 CudaText man Note Added: 0131200
2021-06-09 03:39 Do-wan Kim Note Added: 0131209
2021-06-09 03:39 Do-wan Kim File Added: 38730_fcitex_ibus_more_fix.patch
2021-06-15 13:07 Martin Friebe Note Added: 0131317