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
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;			//
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
[email protected]
http://lists.ximian.com/mailman/listinfo/mono-devel-list

Reply via email to