Lazarus
Programming => Widgetset => GTK => Topic started by: parcel 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.
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?
-
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);
-
@parcel, what version of lazarus do you use ?
-
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, 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.
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
-
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.
-
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.
-
I finally made it,
but some problem remains. :-[
work fine for me. :D
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.
-
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?
-
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 (http://bugs.freepascal.org/view.php?id=26369)
-
It's not pretty but simple :)
That's GTK for you :)
-
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.