diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs index 96f0b8f233..91dfe3eef2 100644 --- a/src/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs @@ -4,6 +4,9 @@ // // ----------------------------------------------------------------------- +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)); diff --git a/src/Perspex.Controls/MenuItemAccessKeyHandler.cs b/src/Perspex.Controls/MenuItemAccessKeyHandler.cs index f4bdec0164..6387c7c651 100644 --- a/src/Perspex.Controls/MenuItemAccessKeyHandler.cs +++ b/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); } /// @@ -90,7 +90,7 @@ namespace Perspex.Controls /// /// The event sender. /// The event args. - protected virtual void OnKeyDown(object sender, KeyEventArgs e) + protected virtual void OnTextInput(object sender, TextInputEventArgs e) { if (!string.IsNullOrWhiteSpace(e.Text)) { diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index 3b410b361f..30efed5129 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/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; } diff --git a/src/Perspex.Input/AccessKeyHandler.cs b/src/Perspex.Input/AccessKeyHandler.cs index 6df206bc67..ddb508dda2 100644 --- a/src/Perspex.Input/AccessKeyHandler.cs +++ b/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); diff --git a/src/Perspex.Input/IInputElement.cs b/src/Perspex.Input/IInputElement.cs index fcbaf5eed5..3ce3c5d9d0 100644 --- a/src/Perspex.Input/IInputElement.cs +++ b/src/Perspex.Input/IInputElement.cs @@ -34,6 +34,11 @@ namespace Perspex.Input /// event EventHandler KeyUp; + /// + /// Occurs when a user typed some text while the control has focus. + /// + event EventHandler TextInput; + /// /// Occurs when the pointer enters the control. /// diff --git a/src/Perspex.Input/InputElement.cs b/src/Perspex.Input/InputElement.cs index 1743926e0f..154f71b32a 100644 --- a/src/Perspex.Input/InputElement.cs +++ b/src/Perspex.Input/InputElement.cs @@ -81,6 +81,14 @@ namespace Perspex.Input "KeyUp", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + /// + /// Defines the event. + /// + public static readonly RoutedEvent TextInputEvent = + RoutedEvent.Register( + "TextInput", + RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + /// /// Defines the event. /// @@ -136,6 +144,7 @@ namespace Perspex.Input LostFocusEvent.AddClassHandler(x => x.OnLostFocus); KeyDownEvent.AddClassHandler(x => x.OnKeyDown); KeyUpEvent.AddClassHandler(x => x.OnKeyUp); + TextInputEvent.AddClassHandler(x => x.OnTextInput); PointerEnterEvent.AddClassHandler(x => x.OnPointerEnter); PointerLeaveEvent.AddClassHandler(x => x.OnPointerLeave); PointerMovedEvent.AddClassHandler(x => x.OnPointerMoved); @@ -180,6 +189,15 @@ namespace Perspex.Input remove { this.RemoveHandler(KeyUpEvent, value); } } + /// + /// Occurs when a user typed some text while the control has focus. + /// + public event EventHandler TextInput + { + add { this.AddHandler(TextInputEvent, value); } + remove { this.RemoveHandler(TextInputEvent, value); } + } + /// /// Occurs when the pointer enters the control. /// @@ -377,6 +395,14 @@ namespace Perspex.Input { } + /// + /// Called before the event occurs. + /// + /// The event args. + protected virtual void OnTextInput(TextInputEventArgs e) + { + } + /// /// Called before the event occurs. /// diff --git a/src/Perspex.Input/KeyEventArgs.cs b/src/Perspex.Input/KeyEventArgs.cs index 7ae86d4777..299bb015ef 100644 --- a/src/Perspex.Input/KeyEventArgs.cs +++ b/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; } } } diff --git a/src/Perspex.Input/KeyboardDevice.cs b/src/Perspex.Input/KeyboardDevice.cs index d9f7c2c9f9..2e19cbbd19 100644 --- a/src/Perspex.Input/KeyboardDevice.cs +++ b/src/Perspex.Input/KeyboardDevice.cs @@ -22,7 +22,7 @@ namespace Perspex.Input public KeyboardDevice() { this.InputManager.RawEventReceived - .OfType() + .OfType() .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 + }); } } } diff --git a/src/Perspex.Input/Perspex.Input.csproj b/src/Perspex.Input/Perspex.Input.csproj index f93f2a331e..cc8c4195f4 100644 --- a/src/Perspex.Input/Perspex.Input.csproj +++ b/src/Perspex.Input/Perspex.Input.csproj @@ -66,6 +66,7 @@ + @@ -100,6 +101,7 @@ + diff --git a/src/Perspex.Input/Raw/RawKeyEventArgs.cs b/src/Perspex.Input/Raw/RawKeyEventArgs.cs index 8f17c14410..4f2bccc780 100644 --- a/src/Perspex.Input/Raw/RawKeyEventArgs.cs +++ b/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; } } } diff --git a/src/Perspex.Input/Raw/RawTextInputEventArgs.cs b/src/Perspex.Input/Raw/RawTextInputEventArgs.cs new file mode 100644 index 0000000000..6739bd718d --- /dev/null +++ b/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; + } + } +} diff --git a/src/Perspex.Input/TextInputEventArgs.cs b/src/Perspex.Input/TextInputEventArgs.cs new file mode 100644 index 0000000000..0639e1d1cf --- /dev/null +++ b/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; } + } +} diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 0ffa2fbf5f..ff8792ccc2 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/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")] diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index f4e8051590..e1ca8042b7 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -4,6 +4,8 @@ // // ----------------------------------------------------------------------- +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, diff --git a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs index 0f59636292..d105dd9fb3 100644 --- a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs @@ -300,8 +300,7 @@ namespace Perspex.Controls.UnitTests new Mock().Object, 0, RawKeyEventType.KeyDown, - Key.A, - "A"); + Key.A); impl.Object.Input(input); var inputManagerMock = Mock.Get(InputManager.Instance);