on-the-spot Input method editing in mono WinForms

View: New views
1 Messages — Rating Filter:   Alert me  

on-the-spot Input method editing in mono WinForms

by tom_hindle :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hi,

I need to implement in a custom control on-the-spot Input method
editing. The Control is driven by WinForm events - although all the
drawing is completely custom.

I have had a first stab at adding support to mono's X11 classes to allow
a custom control to get at this information. I've attached the (not
finished) initial patch for comments and suggestions.

I have also attached a small test program which I use for testing this.
For which I run it with:
XMODIFIERS="@im=SCIM"
MONO_WINFORMS_XIM_STYLE=on-the-spot mono TestApp.exe

I have been testing with Scim using 'Amharic' and 'Chinese (Simplified)
- Wubi' (note: mono winforms seems to have problems rending Amharic -
but it's good enough for testing).

What I would like to know is what are the changes of accepting a patch
which allows custom controls to hook into mono's XIM use?

I have currently put externally access-able classes in a
System.Windows.Forms.X11 namespace, it this the right thing to do?

Another thing which I think may be necessary is the ability to have a
control specific xim or a least to be able to switch xim in application
code ( currently it seems to be one per applications) This would allow
some controls to do on-the-spot and some to do an alternative method
like off-the-spot.
 
Thanks
Tom

[IM-on-the-spot.patch]

Index: class/Managed.Windows.Forms/System.Windows.Forms/X11Keyboard.cs
===================================================================
--- class/Managed.Windows.Forms/System.Windows.Forms/X11Keyboard.cs (revision 144586)
+++ class/Managed.Windows.Forms/System.Windows.Forms/X11Keyboard.cs (working copy)
@@ -32,11 +32,91 @@
 //
 using System;
 using System.Collections;
+using System.Diagnostics;
 using System.Drawing;
 using System.Text;
 using System.Globalization;
 using System.Runtime.InteropServices;
 
+namespace System.Windows.Forms.X11
+{
+ public class XIM
+ {
+ /// <summary>
+ /// Allows user to request preedit notifications.
+ /// </summary>
+ public interface IPreedit
+ {
+ int PreeditStart(IntPtr xic, IntPtr clientData, IntPtr callData);
+ int PreeditDone(IntPtr xic, IntPtr clientData, IntPtr callData);
+ int PreeditDraw (IntPtr xic, IntPtr clientData, IntPtr callData);
+ int PreeditCaret (IntPtr xic, IntPtr clientData, IntPtr callData);
+ }
+
+ /// <summary>
+ /// Allow Applications to register interest in X Input methods preedit callbacks
+ /// TODO: Generalize this to allow specifiying window/control
+ /// </summary>
+ public static void RegisterPreeditNotification(IPreedit preedit)
+ {
+ System.Windows.Forms.XplatUIX11.Keyboard.RegisterPreeditNotification(preedit);
+ }
+
+ /// <summary>
+ /// Converts IntPtr if XIMPreeditDrawCallbackStruct into an easier to use form
+ /// </summary>
+ public class PreeditDrawInfo
+ {
+ public PreeditDrawInfo(IntPtr ximPreeditDrawPtr)
+ {
+ if (ximPreeditDrawPtr != IntPtr.Zero)
+ {
+ XIMPreeditDrawCallbackStruct preeditStruct = (XIMPreeditDrawCallbackStruct) Marshal.PtrToStructure (ximPreeditDrawPtr, typeof (XIMPreeditDrawCallbackStruct));
+ this.Caret = preeditStruct.Caret;
+ this.ChangeFirst = preeditStruct.ChangeFirst;
+ this.ChangeLength = preeditStruct.ChangeLength;
+ if (preeditStruct.Text != IntPtr.Zero)
+ {
+ XIMText text = (XIMText) Marshal.PtrToStructure(preeditStruct.Text, typeof(XIMText));
+ {
+ this.Length = text.Length;
+ if (text.Feedback != IntPtr.Zero)
+ {
+ XIMFeedbackStruct Feedback = (XIMFeedbackStruct) Marshal.PtrToStructure(text.Feedback, typeof(XIMFeedbackStruct));
+ this.Feedback = Feedback.FeedbackMask;
+ }
+ this.EncodingIsWChar = text.EncodingIsWChar;
+ if (text.String != IntPtr.Zero)
+ {
+ if (text.EncodingIsWChar)
+ this.String = Marshal.PtrToStringUni(text.String);
+ else
+ this.String = Marshal.PtrToStringAuto(text.String);
+ }
+ }
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return String.Format("PreeditDrawInfo: Carret = {0} ChangeFirst = {1} ChangeLength = {2} Length = {3} Feedback = {4} EncodingIsWChar = {5} String = {6}",
+                     Caret, ChangeFirst, ChangeLength, Length, Feedback, EncodingIsWChar, String);
+ }
+
+ public int Caret = 0;
+ public int ChangeFirst = 0;
+ public int ChangeLength = 0;
+ public ushort Length = 0;
+ public ulong Feedback = 0;
+ public bool EncodingIsWChar = false;
+ public String String = string.Empty;
+ }
+
+ }
+
+}
+
 namespace System.Windows.Forms {
 
  internal class X11Keyboard : IDisposable {
@@ -63,6 +143,8 @@
 
  private int NumLockMask;
  private int AltGrMask;
+
+ private System.Windows.Forms.X11.XIM.IPreedit ximPreedit;
 
  public X11Keyboard (IntPtr display, IntPtr clientWindow)
  {
@@ -1001,9 +1083,14 @@
  }
  }
 
+ public void RegisterPreeditNotification(System.Windows.Forms.X11.XIM.IPreedit preedit)
+ {
+ ximPreedit = preedit;
+ }
+
  private IntPtr CreateOnTheSpotXic (IntPtr window, IntPtr xim)
  {
- callbackContext = new XIMCallbackContext (window);
+ callbackContext = new XIMCallbackContext (window, ximPreedit);
  return callbackContext.CreateXic (window, xim);
  }
 
@@ -1012,8 +1099,10 @@
  XIMCallback startCB, doneCB, drawCB, caretCB;
  IntPtr pStartCB = IntPtr.Zero, pDoneCB = IntPtr.Zero, pDrawCB = IntPtr.Zero, pCaretCB = IntPtr.Zero;
  IntPtr pStartCBN = IntPtr.Zero, pDoneCBN = IntPtr.Zero, pDrawCBN = IntPtr.Zero, pCaretCBN = IntPtr.Zero;
+
+ System.Windows.Forms.X11.XIM.IPreedit preeditApplicationCallback; // allows user application to register for preedit methods.
 
- public XIMCallbackContext (IntPtr clientWindow)
+ public XIMCallbackContext (IntPtr clientWindow, System.Windows.Forms.X11.XIM.IPreedit preedit)
  {
  startCB = new XIMCallback (IntPtr.Zero, DoPreeditStart);
  doneCB = new XIMCallback (IntPtr.Zero, DoPreeditDone);
@@ -1027,6 +1116,8 @@
  pDoneCBN = Marshal.StringToHGlobalAnsi (XNames.XNPreeditDoneCallback);
  pDrawCBN = Marshal.StringToHGlobalAnsi (XNames.XNPreeditDrawCallback);
  pCaretCBN = Marshal.StringToHGlobalAnsi (XNames.XNPreeditCaretCallback);
+
+ preeditApplicationCallback = preedit;
  }
 
  ~XIMCallbackContext ()
@@ -1052,27 +1143,33 @@
 
  int DoPreeditStart (IntPtr xic, IntPtr clientData, IntPtr callData)
  {
- Console.WriteLine ("DoPreeditStart");
+ Debug.WriteLine ("DoPreeditStart");
+ if (preeditApplicationCallback != null)
+ return preeditApplicationCallback.PreeditStart(xic, clientData, callData);
  return 100;
  }
 
  int DoPreeditDone (IntPtr xic, IntPtr clientData, IntPtr callData)
  {
- Console.WriteLine ("DoPreeditDone");
+ Debug.WriteLine ("DoPreeditDone");
+ if (preeditApplicationCallback != null)
+ return preeditApplicationCallback.PreeditDone(xic, clientData, callData);
  return 0;
  }
 
  int DoPreeditDraw (IntPtr xic, IntPtr clientData, IntPtr callData)
  {
- Console.WriteLine ("DoPreeditDraw");
- //XIMPreeditDrawCallbackStruct cd = (XIMPreeditDrawCallbackStruct) Marshal.PtrToStructure (callData, typeof (XIMPreeditDrawCallbackStruct));
+ Debug.WriteLine ("DoPreeditDraw");
+ if (preeditApplicationCallback != null)
+ return preeditApplicationCallback.PreeditDraw(xic, clientData, callData);
  return 0;
  }
 
  int DoPreeditCaret (IntPtr xic, IntPtr clientData, IntPtr callData)
  {
- Console.WriteLine ("DoPreeditCaret");
- //XIMPreeditCaretCallbackStruct cd = (XIMPreeditCaretCallbackStruct) Marshal.PtrToStructure (callData, typeof (XIMPreeditCaretCallbackStruct));
+ Debug.WriteLine ("DoPreeditCaret");
+ if (preeditApplicationCallback != null)
+ return preeditApplicationCallback.PreeditCaret(xic, clientData, callData);
  return 0;
  }
 
Index: class/Managed.Windows.Forms/System.Windows.Forms/X11Structs.cs
===================================================================
--- class/Managed.Windows.Forms/System.Windows.Forms/X11Structs.cs (revision 144586)
+++ class/Managed.Windows.Forms/System.Windows.Forms/X11Structs.cs (working copy)
@@ -1702,11 +1702,26 @@
  gch.Free ();
  }
  }
+
+ internal enum XIMFeedback
+ {
+ Reverse = 1,
+ Underline = 2,
+ Highlight = 4,
+ Primary = 8,
+ Secondary = 16,
+ Tertiary = 32,
+ }
 
+ internal struct XIMFeedbackStruct
+ {
+ public ulong FeedbackMask; // one or more of XIMFeedback enum
+ }
+
  internal struct XIMText
  {
  public ushort Length;
- public IntPtr Feedback;
+ public IntPtr Feedback; // to XIMFeedbackStruct
  public bool EncodingIsWChar;
  public IntPtr String; // it could be either char* or wchar_t*
  }
Index: class/Managed.Windows.Forms/System.Windows.Forms/XplatUIX11.cs
===================================================================
--- class/Managed.Windows.Forms/System.Windows.Forms/XplatUIX11.cs (revision 144586)
+++ class/Managed.Windows.Forms/System.Windows.Forms/XplatUIX11.cs (working copy)
@@ -112,7 +112,7 @@
  private static bool wake_waiting;
  private static object wake_waiting_lock = new object ();
  #endif //
- private static X11Keyboard Keyboard; //
+ internal static X11Keyboard Keyboard; //
  private static X11Dnd Dnd;
  private static Socket listen; //
  private static Socket wake; //


[TestApp.cs]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.X11;
using System.Collections;

/// <summary>
/// Proof on concept implementation of a user defined TextBox that does on-the-spot
/// input method editing.
/// This a number of know flaws - eg. doesn't end preedit when user moves cursor with mouse.
/// Need to run with MONO_WINFORMS_XIM_STYLE=on-the-spot mono
/// </summary>
public class OnTheSpotTextBox : TextBox, XIM.IPreedit
{
        string backup; // store text before preedit.
       
#region IPreedit implementation
        public int PreeditStart(IntPtr xic, IntPtr clientData, IntPtr callData)
        {
                backup = Text;
                return 100;
        }
       
        public int PreeditDone(IntPtr xic, IntPtr clientData, IntPtr callData)
        {
                Clear();
                AppendText(backup);
                return 0;
        }
       
        public int PreeditDraw (IntPtr xic, IntPtr clientData, IntPtr callData)
        {
                XIM.PreeditDrawInfo info = new XIM.PreeditDrawInfo(callData);
                if (info.ChangeLength > 0)
                {
                        Clear();
                        AppendText(backup);
                }
                if (info.Caret > 0)
                {
                        AppendText(info.String);
                }
                return 0;
        }
       
        public int PreeditCaret (IntPtr xic, IntPtr clientData, IntPtr callData)
        {
                return 0;
        }
#endregion
       
}

#region Testing OnTheSpotTextBox

public class Form1 : Form
{
    private OnTheSpotTextBox textBox1;
       
        private ComboBox comboBox1;

    public Form1()
    {
        InitializeComponent();
    }
       
        private IEnumerable Names(Graphics graphics)
        {
                var families =  FontFamily.GetFamilies(graphics);
                foreach(FontFamily f in families)
                {
                        string s = f.GetName(0);
                        yield return s;
                }
        }
       
         private void ComboBox1_SelectedIndexChanged(object sender,
        System.EventArgs e)
    {
                textBox1.Font = new Font((string)comboBox1.SelectedItem, 10);
        }
       
    private void InitializeComponent()
    {
        this.textBox1 = new OnTheSpotTextBox();
                System.Windows.Forms.X11.XIM.RegisterPreeditNotification(textBox1);
                this.comboBox1 = new System.Windows.Forms.ComboBox();
                comboBox1.Sorted = true;
        this.SuspendLayout();
        //
        // textBox1
        //
                var graphics = textBox1.CreateGraphics();
                               
                foreach (string name in Names(graphics))
                        comboBox1.Items.Add(name);
               
                 this.comboBox1.SelectedIndexChanged +=
            new System.EventHandler(ComboBox1_SelectedIndexChanged);
               
                this.textBox1.Font = new Font("AR PL UMing CN", 10);
        this.textBox1.AcceptsReturn = true;
        this.textBox1.AcceptsTab = true;        
        this.textBox1.Multiline = true;
        this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
                this.textBox1.Top = 30;
                this.textBox1.Height = 300;
                this.textBox1.Width = 300;
               
               
               
        //
        // Form1
        //
        this.ClientSize = new System.Drawing.Size(284, 264);
        this.Controls.Add(this.textBox1);
                this.Controls.Add(this.comboBox1);
        this.Text = "TextBox Example";
        this.ResumeLayout(false);
        this.PerformLayout();

    }

    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

#endregion


_______________________________________________
Mono-devel-list mailing list
Mono-devel-list@...
http://lists.ximian.com/mailman/listinfo/mono-devel-list