Recent

Author Topic: Synedit at UIM input method module.  (Read 7661 times)

parcel

  • Full Member
  • ***
  • Posts: 135
Synedit at UIM input method module.
« on: June 18, 2014, 07:21:24 am »
I try to solve problem asian word inputing in synedit control, but there is a lot of problem.
Most of them caused by 'key-press-evnent' handler.
I have use UIM input method module in linux mint 17 64bit.

synedit with candidate window will not work, key press raised in widget.

Code: [Select]
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, SynEdit, Forms, Controls, Graphics, Dialogs,
  StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Memo1: TMemo;
    SynEdit1: TSynEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

uses glib2, gdk2, gtk2, Gtk2Globals, pango;

{$R *.lfm}

var
  ntext:PGtkIMContext;
  ipos:Integer;
  preeditstr:string;

procedure preeditstart(context:PGtkIMContext; Data:Pointer); cdecl;
begin
  Inc(ipos);
  Form1.Memo1.Lines.Add(IntToStr(ipos)+' preedit-start');
end;

procedure preeditchanged(context:PGtkIMContext; Data:Pointer); cdecl;
var
  str:Pgchar;
  pangoattr:PPangoAttrList;
  pos:pgint;
begin
  Form1.Memo1.Lines.Add(IntToStr(ipos)+' preedit-changed');
  gtk_im_context_get_preedit_string(context,@str,pangoattr,nil);
  preeditstr:=str;
  g_free(str);
  pango_attr_list_unref(pangoattr);
  Form1.Memo1.Lines.Add(IntToStr(ipos)+' '+preeditstr);
  im_context_string:=preeditstr;
end;

procedure preeditend(context:PGtkIMContext; Data:Pointer); cdecl;
begin
  Form1.Memo1.Lines.Add(IntToStr(ipos)+' preedit-end');
  preeditstr:='';
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
  gtk_im_context_reset(im_context);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ipos:=0;
  g_signal_connect(G_OBJECT(im_context),'preedit-start',G_CALLBACK(@preeditstart),nil);
  g_signal_connect(G_OBJECT(im_context),'preedit-changed',G_CALLBACK(@preeditchanged),nil);
  g_signal_connect(G_OBJECT(im_context),'preedit-end',G_CALLBACK(@preeditend),nil);
end;

end.         

with above source, composition string out successfully in synedit.

What is the best way of skipping candidate window key(hanja candidate window) code in synedit control's widget?


Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 5699
    • wiki
Re: Synedit at UIM input method module.
« Reply #1 on: June 18, 2014, 03:09:10 pm »
Can you point me to documentation for those functions?

I am not sure, if gtk should send key down events to synedit, if the pre-compose win is open, that may need to be fixed in the LCL widgetset code.

'key-press-evnent' is in lcl-gtk. I'll see if I can get the maintainer to join us here. (I only do SynEdit, but not gtk)

Otherwise it would need some checks in synedit (hardcoded in KeyDown/Press)

Once you have the composition, look at unit LazSynIMM (windows)

It shows how to insert the text.
procedure LazSynImeFull.WMImeComposition(var Msg: TMessage);


zeljko

  • Hero Member
  • *****
  • Posts: 1080
    • http://wiki.lazarus.freepascal.org/User:Zeljan
Re: Synedit at UIM input method module.
« Reply #2 on: June 18, 2014, 03:23:46 pm »
@parcel, what version of lazarus do you use ?

parcel

  • Full Member
  • ***
  • Posts: 135
Re: Synedit at UIM input method module.
« Reply #3 on: June 18, 2014, 03:34:39 pm »
 I'm also newbie in gtk.  :-[
 I did check gtk2proc.inc 'key-press-event' signal but 'commit' not work property.

'preedit' signals match windows IME-compositon messages. I can get input from above code, but it's insert all between-composition characters.
 widgetset GTK 'key-press-event' signal handler break apart input method composition, and only alphabet character appears.

 I guess it's not synedit problem.

parcel

  • Full Member
  • ***
  • Posts: 135
Re: Synedit at UIM input method module.
« Reply #4 on: June 19, 2014, 01:55:53 pm »
@parcel, what version of lazarus do you use ?
It is svn trunk version.

I made small patch.
 it works but composition window is not shown.
and not work with breaking composition mode by mouse click.
and candidate window is not work.

Code: [Select]
Index: lcl/interfaces/gtk2/gtk2globals.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2globals.pp (revision 45577)
+++ lcl/interfaces/gtk2/gtk2globals.pp (working copy)
@@ -75,6 +75,7 @@
   im_context: PGtkIMContext = nil;
   im_context_widget: PGtkWidget = nil;
   im_context_string: string = '';
+  im_context_use: Boolean = False; //DW
 
 procedure ResetDefaultIMContext;
 
@@ -421,6 +422,7 @@
   end;
   im_context_widget:=nil;
   im_context_string:='';
+  im_context_use:=False; //DW
 end;
 
 procedure AddCharsetEncoding(CharSet: Byte; CharSetReg, CharSetCod: CharSetStr;
Index: lcl/interfaces/gtk2/gtk2proc.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2proc.inc (revision 45577)
+++ lcl/interfaces/gtk2/gtk2proc.inc (working copy)
@@ -1990,7 +1990,8 @@
       end;
       Exit;
     end;
-    Result := (AEvent^.Length > 0) or (GetSpecialChar <> #0);
+    //DW
+    Result := ((not im_context_use) and (AEvent^.Length > 0)) or (GetSpecialChar <> #0);
   end;
   
   function KeyAlreadyHandledByGtk: boolean;
Index: lcl/interfaces/gtk2/gtk2widgetset.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2widgetset.inc (revision 45577)
+++ lcl/interfaces/gtk2/gtk2widgetset.inc (working copy)
@@ -221,6 +221,32 @@
   im_context_string:=Str;
 end;
 
+//DW
+procedure gtk_predit_start_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+begin
+  im_context_use:=True;
+end;
+
+procedure gtk_predit_end_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+var
+  WinControl:TCustomControl;
+  chutf8:TUTF8Char;
+begin
+  im_context_use:=False;
+  { at preedit ending, "commit => preedit-end => commit => keypress"
+    it send last composition character to keyboard event.
+  }
+  if (im_context_widget<>nil) and (im_context_string<>'') and
+     gtk_widget_is_focus(im_context_widget) and
+     (GetNearestLCLObject(im_context_widget) is TCustomControl) then
+      begin
+        WinControl:=GetNearestLCLObject(im_context_widget) as TCustomControl;
+        chutf8:=UTF8Copy(im_context_string,1,1);
+        WinControl.IntfUTF8KeyPress(chutf8,1,False);
+        im_context_string:='';
+      end;
+end;
+
 {$IfNDef GTK2_2}
 procedure gtkTreeSelectionCountSelectedRows({%H-}model : PGtkTreeModel; {%H-}path : PGtkTreePath;
                                   {%H-}iter : PGtkTreeIter; data : PGint); cdecl;
@@ -985,6 +1011,11 @@
   im_context:=gtk_im_multicontext_new;
   g_signal_connect (G_OBJECT (im_context), 'commit',
     G_CALLBACK (@gtk_commit_cb), nil);
+  //DW
+  g_signal_connect (G_OBJECT (im_context), 'preedit-start',
+    G_CALLBACK (@gtk_predit_start_cb), nil);
+  g_signal_connect (G_OBJECT (im_context), 'preedit-end',
+    G_CALLBACK (@gtk_predit_end_cb), nil);
   {$IFDEF HASX}
   if IsNoTransientWM then
   begin
« Last Edit: June 20, 2014, 12:04:53 am by parcel »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 5699
    • wiki
Re: Synedit at UIM input method module.
« Reply #5 on: June 19, 2014, 02:21:53 pm »
This probably needs to be splitted between SynEdit and gtk.

CanSendChar needs to be patched in gtk.

Collecting the composition string is better done in SynEdit, or at least to allow SynEdit to do it, and handle in gtk as fallback.

SynEdit could display the composition in the actual editor, without an compose window being shown.

parcel

  • Full Member
  • ***
  • Posts: 135
Re: Synedit at UIM input method module.
« Reply #6 on: June 20, 2014, 12:55:41 am »
Currently I found there is no way to distinguish preedit-changed and commit signal in control side
Also thers is no flag for composition completion.


parcel

  • Full Member
  • ***
  • Posts: 135
Re: Synedit at UIM input method module.
« Reply #7 on: June 20, 2014, 04:31:36 am »
I finally made it,
but some problem remains.  :-[

work fine for me.  :D

Code: [Select]
Index: components/synedit/synedit.pp
===================================================================
--- components/synedit/synedit.pp (revision 45577)
+++ components/synedit/synedit.pp (working copy)
@@ -562,6 +562,8 @@
     FOnMouseLink: TSynMouseLinkEvent;
     FPendingFoldState: String;
 
+    FgtkCommit: Boolean;
+
     procedure AquirePrimarySelection;
     function GetChangeStamp: int64;
     function GetCharsInWindow: Integer;
@@ -1131,6 +1133,9 @@
     property OnSpecialLineColors: TSpecialLineColorsEvent read FOnSpecialLineColors write SetSpecialLineColors;  deprecated;
     property OnSpecialLineMarkup: TSpecialLineMarkupEvent read FOnSpecialLineMarkup write SetSpecialLineMarkup;
     property OnStatusChange: TStatusChangeEvent read fOnStatusChange write fOnStatusChange;
+  public
+    function gtk_PreeditCommit(const str: PChar; flag: Integer): Boolean;
+       override;
   end;
 
   TSynEdit = class(TCustomSynEdit)
@@ -4010,6 +4015,29 @@
   Result := FMarkupManager.Count;
 end;
 
+// for gtk input method. if return false, key skipped.
+function TCustomSynEdit.gtk_PreeditCommit(const str: PChar; flag: Integer
+  ): Boolean;
+begin
+  Result:=inherited gtk_PreeditCommit(str, flag);
+  if (flag=0) or (flag=2) then
+    FgtkCommit:=flag=0
+    else begin
+      if not fReadOnly then begin
+        if not FgtkCommit then begin
+          // delete on last char
+          if BiDiMode=bdLeftToRight then
+            CommandProcessor(ecSelLeft,'',nil)
+            else
+              CommandProcessor(ecSelRight,'',nil);
+        end;
+        CommandProcessor(ecChar,str,nil);
+        FgtkCommit:=flag=3;
+        Result:=False;
+      end;
+    end;
+end;
+
 procedure TCustomSynEdit.PasteFromClipboard;
 var
   ClipHelper: TSynClipboardStream;
Index: lcl/controls.pp
===================================================================
--- lcl/controls.pp (revision 45577)
+++ lcl/controls.pp (working copy)
@@ -1634,6 +1634,10 @@
     property HelpType: THelpType read FHelpType write FHelpType default htContext;
     property HelpKeyword: String read FHelpKeyword write SetHelpKeyword stored IsHelpKeyWordStored;
     property HelpContext: THelpContext read FHelpContext write SetHelpContext stored IsHelpContextStored default 0;
+  //{$ifdef LCLGTK2}
+  public
+    function gtk_PreeditCommit(const str:PChar;flag:Integer):Boolean; virtual;
+  //{$endif}
   end;
 
 
Index: lcl/include/control.inc
===================================================================
--- lcl/include/control.inc (revision 45577)
+++ lcl/include/control.inc (working copy)
@@ -5596,7 +5596,18 @@
   Result := UseRightToLeftReading;
 end;
 
+//{$ifdef LCLGTK2}
 {------------------------------------------------------------------------------
+       TControl.gtk_PreeditCommit
+------------------------------------------------------------------------------}
+
+function TControl.gtk_PreeditCommit(const str: PChar; flag: Integer): Boolean;
+begin
+  Result:=True;
+end;
+//{$endif}
+
+{------------------------------------------------------------------------------
        TControl.UseRightToLeftAlignment
 ------------------------------------------------------------------------------}
 
Index: lcl/interfaces/gtk2/gtk2globals.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2globals.pp (revision 45577)
+++ lcl/interfaces/gtk2/gtk2globals.pp (working copy)
@@ -75,6 +75,8 @@
   im_context: PGtkIMContext = nil;
   im_context_widget: PGtkWidget = nil;
   im_context_string: string = '';
+  im_context_use: Boolean = False; //DW
+  im_context_skipkey: Boolean = False;
 
 procedure ResetDefaultIMContext;
 
@@ -421,6 +423,8 @@
   end;
   im_context_widget:=nil;
   im_context_string:='';
+  im_context_use:=False; //DW
+  im_context_skipkey:=False;
 end;
 
 procedure AddCharsetEncoding(CharSet: Byte; CharSetReg, CharSetCod: CharSetStr;
Index: lcl/interfaces/gtk2/gtk2proc.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2proc.inc (revision 45577)
+++ lcl/interfaces/gtk2/gtk2proc.inc (working copy)
@@ -1990,7 +1990,8 @@
       end;
       Exit;
     end;
-    Result := (AEvent^.Length > 0) or (GetSpecialChar <> #0);
+    //DW
+    Result := ((not im_context_use) and (AEvent^.Length > 0)) or (GetSpecialChar <> #0);
   end;
   
   function KeyAlreadyHandledByGtk: boolean;
@@ -2423,7 +2424,12 @@
         begin
           OldCharacter := Character;
           // send the key after navigation keys were handled
-          Result := TWinControl(LCLObject).IntfUTF8KeyPress(Character, 1, SysKey);
+          if not im_context_skipkey  then
+            Result := TWinControl(LCLObject).IntfUTF8KeyPress(Character, 1, SysKey)
+              else begin
+                Result:=True;
+                im_context_skipkey:=false;
+              end;
           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 (revision 45577)
+++ lcl/interfaces/gtk2/gtk2widgetset.inc (working copy)
@@ -216,11 +216,67 @@
 
 procedure gtk_commit_cb ({%H-}context: PGtkIMContext; const Str: Pgchar;
   {%H-}Data: Pointer); cdecl;
+var
+  Control:TControl;
 begin
   //DebugLn(['gtk_commit_cb ',dbgstr(Str),'="',Str,'"']);
   im_context_string:=Str;
+  if im_context_use and (im_context_widget<>nil) and (im_context_string<>'') and
+     gtk_widget_is_focus(im_context_widget) then
+      begin
+        Control:=TControl(GetNearestLCLObject(im_context_widget));
+        im_context_skipkey:=not Control.gtk_PreeditCommit(PChar(im_context_string),3);
+      end;
 end;
 
+//DW
+procedure gtk_preedit_start_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+var
+  Control:TControl;
+begin
+  im_context_use:=True;
+  if (im_context_widget<>nil) and
+     gtk_widget_is_focus(im_context_widget) then
+      begin
+        Control:=TControl(GetNearestLCLObject(im_context_widget));
+        im_context_skipkey:=not Control.gtk_PreeditCommit(PChar(''),0);
+      end;
+end;
+
+procedure gtk_preedit_end_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+var
+  Control:TControl;
+begin
+  im_context_use:=False;
+  if (im_context_widget<>nil) and
+     gtk_widget_is_focus(im_context_widget) then
+      begin
+        Control:=TControl(GetNearestLCLObject(im_context_widget));
+        im_context_skipkey := not Control.gtk_PreeditCommit(PChar(''),2);
+      end;
+end;
+
+procedure gtk_preedit_changed_cb({%H-}context:PGtkIMContext; {%H-}Data:Pointer); cdecl;
+var
+  str:Pgchar;
+  pangoattr:PPangoAttrList;
+  pos:gint;
+  control:TControl;
+  compostr:string;
+begin
+  if im_context_use and (im_context_widget<>nil) and
+    gtk_widget_is_focus(im_context_widget) then
+  begin
+    control:=TControl(GetNearestLCLObject(im_context_widget));
+    pos:=1;
+    gtk_im_context_get_preedit_string(context,@str,pangoattr,@pos);
+    compostr:=str;
+    g_free(str);
+    pango_attr_list_unref(pangoattr);
+    im_context_skipkey:=not control.gtk_PreeditCommit(PChar(compostr),1);
+  end;
+end;
+
 {$IfNDef GTK2_2}
 procedure gtkTreeSelectionCountSelectedRows({%H-}model : PGtkTreeModel; {%H-}path : PGtkTreePath;
                                   {%H-}iter : PGtkTreeIter; data : PGint); cdecl;
@@ -985,6 +1041,13 @@
   im_context:=gtk_im_multicontext_new;
   g_signal_connect (G_OBJECT (im_context), 'commit',
     G_CALLBACK (@gtk_commit_cb), nil);
+  //DW
+  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);
   {$IFDEF HASX}
   if IsNoTransientWM then
   begin

(update) missing var initialization.

(update) no TControl Hack.
« Last Edit: June 23, 2014, 09:21:32 am by parcel »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 5699
    • wiki
Re: Synedit at UIM input method module.
« Reply #8 on: June 20, 2014, 04:05:58 pm »
Currently I found there is no way to distinguish preedit-changed and commit signal in control side
Also thers is no flag for composition completion.

Then how does it work?

I know on windows, pressing a key/key-sequence will get you a char, and continue typing can change this char.

Are all chars on gtk final, once they are generated? or can they still change?

parcel

  • Full Member
  • ***
  • Posts: 135
Re: Synedit at UIM input method module.
« Reply #9 on: June 23, 2014, 09:27:48 am »
Currently I found there is no way to distinguish preedit-changed and commit signal in control side
Also thers is no flag for composition completion.

Then how does it work?

I know on windows, pressing a key/key-sequence will get you a char, and continue typing can change this char.

Are all chars on gtk final, once they are generated? or can they still change?

I check compositon completion by commit signal. send delete last utf8 char and send utf8 char to control by every preedit-changed signal and commit.
It's not pretty but simple  :)

I reported it to mantis.
http://bugs.freepascal.org/view.php?id=26369

Mike.Cornflake

  • Hero Member
  • *****
  • Posts: 1249
Re: Synedit at UIM input method module.
« Reply #10 on: June 23, 2014, 09:34:46 am »
It's not pretty but simple  :)
That's GTK for you :)
Lazarus Trunk/FPC Trunk on Windows [7, 10]
  Have you tried searching this forum or the wiki?:   http://wiki.lazarus.freepascal.org/Alternative_Main_Page
  BOOKS! (Free and otherwise): http://wiki.lazarus.freepascal.org/Pascal_and_Lazarus_Books_and_Magazines

parcel

  • Full Member
  • ***
  • Posts: 135
Re: Synedit at UIM input method module.
« Reply #11 on: July 06, 2014, 07:35:25 am »
It's not pretty but simple  :)
That's GTK for you :)
:)

Minor update, remove adding last char by pressing non-composition keys in compositon mode.