Browse Source

Merge pull request #105 from kekekeks/text-input

Now using TextInput event, WM_CHAR and GtkIMContext
Steven Kirk 10 years ago
parent
commit
8b252dc87b

+ 33 - 12
src/Gtk/Perspex.Gtk/WindowImpl.cs

@@ -4,6 +4,9 @@
 // </copyright>
 // -----------------------------------------------------------------------
 
+using Gdk;
+using Perspex.Input;
+
 namespace Perspex.Gtk
 {
     using System;
@@ -22,25 +25,33 @@ namespace Perspex.Gtk
 
         private Size clientSize;
 
+        private Gtk.IMContext imContext;
+
+        private uint lastKeyEventTimestamp;
+
         public WindowImpl()
             : base(Gtk.WindowType.Toplevel)
         {
             this.DefaultSize = new Gdk.Size(640, 480);
-            this.Events = Gdk.EventMask.PointerMotionMask | 
-                          Gdk.EventMask.ButtonPressMask | 
-                          Gdk.EventMask.ButtonReleaseMask;
-            this.windowHandle = new PlatformHandle(this.Handle, "GtkWindow");
+            Init();
         }
 
         public WindowImpl(Gtk.WindowType type)
             : base(type)
+        {
+            Init();
+        }
+
+        private void Init()
         {
             this.Events = Gdk.EventMask.PointerMotionMask |
-                          Gdk.EventMask.ButtonPressMask |
-                          Gdk.EventMask.ButtonReleaseMask;
+              Gdk.EventMask.ButtonPressMask |
+              Gdk.EventMask.ButtonReleaseMask;
             this.windowHandle = new PlatformHandle(this.Handle, "GtkWindow");
+            this.imContext =  new Gtk.IMMulticontext();
+            this.imContext.Commit += ImContext_Commit;
         }
-        
+
         public Size ClientSize
         {
             get;
@@ -148,19 +159,29 @@ namespace Perspex.Gtk
             this.Closed();
         }
 
-        protected override bool OnKeyPressEvent(Gdk.EventKey evnt)
+        private bool ProcessKeyEvent(Gdk.EventKey evnt)
         {
-            var keyChar = (char)Gdk.Keyval.ToUnicode ((uint)evnt.Key);
-            var keyText = keyChar == 0 ? string.Empty : new string (keyChar, 1);
+            this.lastKeyEventTimestamp = evnt.Time;
+            if (this.imContext.FilterKeypress(evnt))
+                return true;
             var e = new RawKeyEventArgs(
                 GtkKeyboardDevice.Instance,
                 evnt.Time,
-                RawKeyEventType.KeyDown,
-                GtkKeyboardDevice.ConvertKey(evnt.Key), keyText);
+                evnt.Type == EventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
+                GtkKeyboardDevice.ConvertKey(evnt.Key));
             this.Input(e);
             return true;
         }
 
+        protected override bool OnKeyPressEvent(Gdk.EventKey evnt) => ProcessKeyEvent(evnt);
+
+        protected override bool OnKeyReleaseEvent(EventKey evnt) => ProcessKeyEvent(evnt);
+
+        private void ImContext_Commit(object o, Gtk.CommitArgs args)
+        {
+            this.Input(new RawTextInputEventArgs(GtkKeyboardDevice.Instance, this.lastKeyEventTimestamp, args.Str));
+        }
+
         protected override bool OnExposeEvent(Gdk.EventExpose evnt)
         {
             this.Paint(evnt.Area.ToPerspex(), this.GetHandle(evnt.Window));

+ 2 - 2
src/Perspex.Controls/MenuItemAccessKeyHandler.cs

@@ -53,7 +53,7 @@ namespace Perspex.Controls
 
             this.owner = owner;
 
-            this.owner.AddHandler(InputElement.KeyDownEvent, this.OnKeyDown);
+            this.owner.AddHandler(InputElement.TextInputEvent, this.OnTextInput);
         }
 
         /// <summary>
@@ -90,7 +90,7 @@ namespace Perspex.Controls
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
-        protected virtual void OnKeyDown(object sender, KeyEventArgs e)
+        protected virtual void OnTextInput(object sender, TextInputEventArgs e)
         {
             if (!string.IsNullOrWhiteSpace(e.Text))
             {

+ 17 - 12
src/Perspex.Controls/TextBox.cs

@@ -128,12 +128,27 @@ namespace Perspex.Controls
             this.presenter.HideCaret();
         }
 
+        protected override void OnTextInput(TextInputEventArgs e)
+        {
+            string text = this.Text ?? string.Empty;
+            int caretIndex = this.CaretIndex;
+            if (!string.IsNullOrEmpty(e.Text))
+            {
+                this.DeleteSelection();
+                caretIndex = this.CaretIndex;
+                text = this.Text ?? string.Empty;
+                this.Text = text.Substring(0, caretIndex) + e.Text + text.Substring(caretIndex);
+                ++this.CaretIndex;
+                this.SelectionStart = this.SelectionEnd = this.CaretIndex;
+            }
+            base.OnTextInput(e);
+        }
+
         protected override void OnKeyDown(KeyEventArgs e)
         {
             string text = this.Text ?? string.Empty;
             int caretIndex = this.CaretIndex;
             bool movement = false;
-            bool textEntered = false;
             bool handled = true;
             var modifiers = e.Device.Modifiers;
 
@@ -220,16 +235,6 @@ namespace Perspex.Controls
                     break;
 
                 default:
-                    if (!string.IsNullOrEmpty(e.Text) && !modifiers.HasFlag(ModifierKeys.Control) && !modifiers.HasFlag(ModifierKeys.Alt))
-                    {
-                        this.DeleteSelection();
-                        caretIndex = this.CaretIndex;
-                        text = this.Text ?? string.Empty;
-                        this.Text = text.Substring(0, caretIndex) + e.Text + text.Substring(caretIndex);
-                        ++this.CaretIndex;
-                        textEntered = true;
-                    }
-
                     break;
             }
 
@@ -237,7 +242,7 @@ namespace Perspex.Controls
             {
                 this.SelectionEnd = this.CaretIndex;
             }
-            else if (movement || textEntered)
+            else if (movement)
             {
                 this.SelectionStart = this.SelectionEnd = this.CaretIndex;
             }

+ 1 - 1
src/Perspex.Input/AccessKeyHandler.cs

@@ -150,7 +150,7 @@ namespace Perspex.Input
             {
                 // If any other key is pressed with the Alt key held down, or the main menu is open,
                 // find all controls who have registered that access key.
-                var text = e.Text.ToUpper();
+                var text = e.Key.ToString().ToUpper();
                 var matches = this.registered
                     .Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible)
                     .Select(x => x.Item2);

+ 5 - 0
src/Perspex.Input/IInputElement.cs

@@ -34,6 +34,11 @@ namespace Perspex.Input
         /// </summary>
         event EventHandler<KeyEventArgs> KeyUp;
 
+        /// <summary>
+        /// Occurs when a user typed some text while the control has focus.
+        /// </summary>
+        event EventHandler<TextInputEventArgs> TextInput;
+
         /// <summary>
         /// Occurs when the pointer enters the control.
         /// </summary>

+ 26 - 0
src/Perspex.Input/InputElement.cs

@@ -81,6 +81,14 @@ namespace Perspex.Input
                 "KeyUp",
                 RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
 
+        /// <summary>
+        /// Defines the <see cref="TextInput"/> event.
+        /// </summary>
+        public static readonly RoutedEvent<TextInputEventArgs> TextInputEvent =
+            RoutedEvent.Register<InputElement, TextInputEventArgs>(
+                "TextInput",
+                RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
+
         /// <summary>
         /// Defines the <see cref="PointerEnter"/> event.
         /// </summary>
@@ -136,6 +144,7 @@ namespace Perspex.Input
             LostFocusEvent.AddClassHandler<InputElement>(x => x.OnLostFocus);
             KeyDownEvent.AddClassHandler<InputElement>(x => x.OnKeyDown);
             KeyUpEvent.AddClassHandler<InputElement>(x => x.OnKeyUp);
+            TextInputEvent.AddClassHandler<InputElement>(x => x.OnTextInput);
             PointerEnterEvent.AddClassHandler<InputElement>(x => x.OnPointerEnter);
             PointerLeaveEvent.AddClassHandler<InputElement>(x => x.OnPointerLeave);
             PointerMovedEvent.AddClassHandler<InputElement>(x => x.OnPointerMoved);
@@ -180,6 +189,15 @@ namespace Perspex.Input
             remove { this.RemoveHandler(KeyUpEvent, value); }
         }
 
+        /// <summary>
+        /// Occurs when a user typed some text while the control has focus.
+        /// </summary>
+        public event EventHandler<TextInputEventArgs> TextInput
+        {
+            add { this.AddHandler(TextInputEvent, value); }
+            remove { this.RemoveHandler(TextInputEvent, value); }
+        }
+
         /// <summary>
         /// Occurs when the pointer enters the control.
         /// </summary>
@@ -377,6 +395,14 @@ namespace Perspex.Input
         {
         }
 
+        /// <summary>
+        /// Called before the <see cref="TextInput"/> event occurs.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        protected virtual void OnTextInput(TextInputEventArgs e)
+        {
+        }
+
         /// <summary>
         /// Called before the <see cref="PointerEnter"/> event occurs.
         /// </summary>

+ 0 - 2
src/Perspex.Input/KeyEventArgs.cs

@@ -14,7 +14,5 @@ namespace Perspex.Input
         public IKeyboardDevice Device { get; set; }
 
         public Key Key { get; set; }
-
-        public string Text { get; set; }
     }
 }

+ 34 - 19
src/Perspex.Input/KeyboardDevice.cs

@@ -22,7 +22,7 @@ namespace Perspex.Input
         public KeyboardDevice()
         {
             this.InputManager.RawEventReceived
-                .OfType<RawKeyEventArgs>()
+                .OfType<RawInputEventArgs>()
                 .Where(x => x.Device == this)
                 .Subscribe(this.ProcessRawEvent);
         }
@@ -93,30 +93,45 @@ namespace Perspex.Input
             this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
 
-        private void ProcessRawEvent(RawKeyEventArgs e)
+        private void ProcessRawEvent(RawInputEventArgs e)
         {
             IInputElement element = this.FocusedElement;
 
             if (element != null)
             {
-                switch (e.Type)
+                var keyInput = e as RawKeyEventArgs;
+                if (keyInput != null)
                 {
-                    case RawKeyEventType.KeyDown:
-                    case RawKeyEventType.KeyUp:
-                        var routedEvent = e.Type == RawKeyEventType.KeyDown ?
-                            InputElement.KeyDownEvent : InputElement.KeyUpEvent;
-
-                        KeyEventArgs ev = new KeyEventArgs
-                        {
-                            RoutedEvent = routedEvent,
-                            Device = this,
-                            Key = e.Key,
-                            Text = e.Text,
-                            Source = element,
-                        };
-
-                        element.RaiseEvent(ev);
-                        break;
+                    switch (keyInput.Type)
+                    {
+                        case RawKeyEventType.KeyDown:
+                        case RawKeyEventType.KeyUp:
+                            var routedEvent = keyInput.Type == RawKeyEventType.KeyDown
+                                ? InputElement.KeyDownEvent
+                                : InputElement.KeyUpEvent;
+
+                            KeyEventArgs ev = new KeyEventArgs
+                            {
+                                RoutedEvent = routedEvent,
+                                Device = this,
+                                Key = keyInput.Key,
+                                Source = element,
+                            };
+
+                            element.RaiseEvent(ev);
+                            break;
+                    }
+                }
+                var text = e as RawTextInputEventArgs;
+                if (text != null)
+                {
+                    element.RaiseEvent(new TextInputEventArgs()
+                    {
+                        Device = this,
+                        Text = text.Text,
+                        Source = element,
+                        RoutedEvent = InputElement.TextInputEvent
+                    });
                 }
             }
         }

+ 2 - 0
src/Perspex.Input/Perspex.Input.csproj

@@ -66,6 +66,7 @@
     </Compile>
     <Compile Include="GlobalSuppressions.cs" />
     <Compile Include="AccessKeyHandler.cs" />
+    <Compile Include="Raw\RawTextInputEventArgs.cs" />
     <Compile Include="NavigationMethod.cs" />
     <Compile Include="IInputRoot.cs" />
     <Compile Include="IMainMenu.cs" />
@@ -100,6 +101,7 @@
     <Compile Include="Raw\RawMouseWheelEventArgs.cs" />
     <Compile Include="Raw\RawSizeEventArgs.cs" />
     <Compile Include="KeyboardNavigationMode.cs" />
+    <Compile Include="TextInputEventArgs.cs" />
     <Compile Include="VectorEventArgs.cs" />
     <Compile Include="KeyEventArgs.cs" />
     <Compile Include="MouseDevice.cs" />

+ 2 - 6
src/Perspex.Input/Raw/RawKeyEventArgs.cs

@@ -18,19 +18,15 @@ namespace Perspex.Input.Raw
             IKeyboardDevice device,
             uint timestamp,
             RawKeyEventType type,
-            Key key,
-            string text)
+            Key key)
             : base(device, timestamp)
         {
             this.Key = key;
             this.Type = type;
-            this.Text = text;
         }
 
         public Key Key { get; set; }
-
-        public string Text { get; set; }
-
+        
         public RawKeyEventType Type { get; set; }
     }
 }

+ 13 - 0
src/Perspex.Input/Raw/RawTextInputEventArgs.cs

@@ -0,0 +1,13 @@
+namespace Perspex.Input.Raw
+{
+    public class RawTextInputEventArgs : RawInputEventArgs
+    {
+
+        public string Text { get; set; }
+
+        public RawTextInputEventArgs(IKeyboardDevice device, uint timestamp, string text) : base(device, timestamp)
+        {
+            Text = text;
+        }
+    }
+}

+ 16 - 0
src/Perspex.Input/TextInputEventArgs.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Perspex.Interactivity;
+
+namespace Perspex.Input
+{
+    public class TextInputEventArgs : RoutedEventArgs
+    {
+        public IKeyboardDevice Device { get; set; }
+
+        public string Text { get; set; }
+    }
+}

+ 7 - 4
src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs

@@ -508,10 +508,10 @@ namespace Perspex.Win32.Interop
            IntPtr hInstance,
            IntPtr lpParam);
 
-        [DllImport("user32.dll")]
+        [DllImport("user32.dll", EntryPoint = "DefWindowProcW")]
         public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
 
-        [DllImport("user32.dll")]
+        [DllImport("user32.dll", EntryPoint = "DispatchMessageW")]
         public static extern IntPtr DispatchMessage(ref MSG lpmsg);
 
         [DllImport("user32.dll", SetLastError = true)]
@@ -538,7 +538,7 @@ namespace Perspex.Win32.Interop
         [DllImport("user32.dll")]
         public static extern bool GetKeyboardState(byte[] lpKeyState);
 
-        [DllImport("user32.dll")]
+        [DllImport("user32.dll", EntryPoint = "GetMessageW")]
         public static extern sbyte GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
 
         [DllImport("user32.dll")]
@@ -565,6 +565,9 @@ namespace Perspex.Win32.Interop
         [DllImport("user32.dll")]
         public static extern bool IsWindowEnabled(IntPtr hWnd);
 
+        [DllImport("user32.dll")]
+        public static extern bool IsWindowUnicode(IntPtr hWnd);
+
         [DllImport("user32.dll")]
         public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);
 
@@ -577,7 +580,7 @@ namespace Perspex.Win32.Interop
         [DllImport("user32.dll")]
         public static extern IntPtr PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
 
-        [DllImport("user32.dll", SetLastError = true)]
+        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")]
         public static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx);
 
         [DllImport("user32.dll")]

+ 10 - 5
src/Windows/Perspex.Win32/WindowImpl.cs

@@ -4,6 +4,8 @@
 // </copyright>
 // -----------------------------------------------------------------------
 
+using Perspex.Input;
+
 namespace Perspex.Win32
 {
     using System;
@@ -203,6 +205,8 @@ namespace Perspex.Win32
         [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
         protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
         {
+            bool unicode = UnmanagedMethods.IsWindowUnicode(hWnd);
+
             const double WheelDelta = 120.0;
             uint timestamp = unchecked((uint)UnmanagedMethods.GetMessageTime());
 
@@ -252,8 +256,7 @@ namespace Perspex.Win32
                             WindowsKeyboardDevice.Instance,
                             timestamp,
                             RawKeyEventType.KeyDown,
-                            KeyInterop.KeyFromVirtualKey((int)wParam),
-                            WindowsKeyboardDevice.Instance.StringFromVirtualKey((uint)wParam));
+                            KeyInterop.KeyFromVirtualKey((int)wParam));
                     break;
 
                 case UnmanagedMethods.WindowsMessage.WM_KEYUP:
@@ -263,10 +266,12 @@ namespace Perspex.Win32
                             WindowsKeyboardDevice.Instance,
                             timestamp,
                             RawKeyEventType.KeyUp,
-                            KeyInterop.KeyFromVirtualKey((int)wParam),
-                            WindowsKeyboardDevice.Instance.StringFromVirtualKey((uint)wParam));
+                            KeyInterop.KeyFromVirtualKey((int)wParam));
+                    break;
+                case UnmanagedMethods.WindowsMessage.WM_CHAR:
+                    e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp,
+                        new string((char) wParam.ToInt32(), 1));
                     break;
-
                 case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN:
                     e = new RawMouseEventArgs(
                         WindowsMouseDevice.Instance,

+ 1 - 2
tests/Perspex.Controls.UnitTests/TopLevelTests.cs

@@ -300,8 +300,7 @@ namespace Perspex.Controls.UnitTests
                     new Mock<IKeyboardDevice>().Object,
                     0,
                     RawKeyEventType.KeyDown,
-                    Key.A,
-                    "A");
+                    Key.A);
                 impl.Object.Input(input);
 
                 var inputManagerMock = Mock.Get(InputManager.Instance);