diff --git a/src/Gtk/Perspex.Gtk/ClipboardImpl.cs b/src/Gtk/Perspex.Gtk/ClipboardImpl.cs new file mode 100644 index 0000000000..d220a0c6b5 --- /dev/null +++ b/src/Gtk/Perspex.Gtk/ClipboardImpl.cs @@ -0,0 +1,40 @@ +namespace Perspex.Gtk +{ + using Gdk; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Perspex.Input.Platform; + using Gtk = global::Gtk; + class ClipboardImpl : IClipboard + { + private static Gtk.Clipboard GetClipboard() => Gtk.Clipboard.GetForDisplay(Gdk.Display.Default, new Atom(IntPtr.Zero)); + + public Task GetTextAsync() + { + var clip = GetClipboard(); + var tcs = new TaskCompletionSource(); + clip.RequestText((_, text) => + { + tcs.TrySetResult(text); + }); + return tcs.Task; + } + + public Task SetTextAsync(string text) + { + using (var cl = GetClipboard()) + cl.Text = text; + return Task.FromResult(0); + } + + public Task ClearAsync() + { + using (var cl = GetClipboard()) + cl.Clear(); + return Task.FromResult(0); + } + } +} diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs index 7165046926..8bfecb537b 100644 --- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs +++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs @@ -8,6 +8,7 @@ namespace Perspex.Gtk { using System; using System.Reactive.Disposables; + using Perspex.Input.Platform; using Perspex.Input; using Perspex.Platform; using Splat; @@ -41,6 +42,7 @@ namespace Perspex.Gtk var locator = Locator.CurrentMutable; locator.Register(() => new WindowImpl(), typeof(IWindowImpl)); locator.Register(() => new PopupImpl(), typeof(IPopupImpl)); + locator.Register(() => new ClipboardImpl(), typeof (IClipboard)); locator.Register(() => GtkKeyboardDevice.Instance, typeof(IKeyboardDevice)); locator.Register(() => instance, typeof(IPlatformSettings)); locator.Register(() => instance, typeof(IPlatformThreadingInterface)); diff --git a/src/Gtk/Perspex.Gtk/Perspex.Gtk.csproj b/src/Gtk/Perspex.Gtk/Perspex.Gtk.csproj index ba89098092..b1c83bfbbb 100644 --- a/src/Gtk/Perspex.Gtk/Perspex.Gtk.csproj +++ b/src/Gtk/Perspex.Gtk/Perspex.Gtk.csproj @@ -59,6 +59,7 @@ + diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 40c3542d8c..620059a62b 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -4,7 +4,10 @@ // // ----------------------------------------------------------------------- -namespace Perspex +using Perspex.Input.Platform; + +namespace + Perspex { using System; using System.Reflection; @@ -41,6 +44,9 @@ namespace Perspex /// private DataTemplates dataTemplates; + private readonly Lazy clipboard = + new Lazy(() => (IClipboard) Locator.Current.GetService(typeof(IClipboard))); + /// /// The styler that will be used to apply styles to controls. /// @@ -119,6 +125,8 @@ namespace Perspex private set; } + public IClipboard Clipboard => this.clipboard.Value; + /// /// Gets the application's global styles. /// diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index 4a5adfb2d4..f468b07702 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/src/Perspex.Controls/TextBox.cs @@ -4,6 +4,9 @@ // // ----------------------------------------------------------------------- +using Perspex.Input.Platform; +using Splat; + namespace Perspex.Controls { using System; @@ -143,11 +146,28 @@ namespace Perspex.Controls caretIndex = this.CaretIndex; text = this.Text ?? string.Empty; this.Text = text.Substring(0, caretIndex) + input + text.Substring(caretIndex); - ++this.CaretIndex; + this.CaretIndex += input.Length; this.SelectionStart = this.SelectionEnd = this.CaretIndex; } } + async void Copy() + { + await ((IClipboard) Locator.Current.GetService(typeof (IClipboard))) + .SetTextAsync(this.GetSelection()); + } + + async void Paste() + { + var text = await ((IClipboard) Locator.Current.GetService(typeof (IClipboard))).GetTextAsync(); + if (text == null) + { + return; + } + + HandleTextInput(text); + } + protected override void OnKeyDown(KeyEventArgs e) { string text = this.Text ?? string.Empty; @@ -165,7 +185,20 @@ namespace Perspex.Controls } break; + case Key.C: + if (modifiers == ModifierKeys.Control) + { + this.Copy(); + } + + break; + case Key.V: + if (modifiers == ModifierKeys.Control) + { + this.Paste(); + } + break; case Key.Left: this.MoveHorizontal(-1, modifiers); movement = true; @@ -435,6 +468,19 @@ namespace Perspex.Controls } } + private string GetSelection() + { + var selectionStart = this.SelectionStart; + var selectionEnd = this.SelectionEnd; + var start = Math.Min(selectionStart, selectionEnd); + var end = Math.Max(selectionStart, selectionEnd); + if (start == end || (this.Text?.Length ?? 0) <= end) + { + return ""; + } + return this.Text.Substring(start, end - start); + } + private int GetLine(int caretIndex, IList lines) { int pos = 0; diff --git a/src/Perspex.Input/Perspex.Input.csproj b/src/Perspex.Input/Perspex.Input.csproj index cc8c4195f4..82aaf8b56a 100644 --- a/src/Perspex.Input/Perspex.Input.csproj +++ b/src/Perspex.Input/Perspex.Input.csproj @@ -66,6 +66,7 @@ + diff --git a/src/Perspex.Input/Platform/IClipboard.cs b/src/Perspex.Input/Platform/IClipboard.cs new file mode 100644 index 0000000000..9db769835d --- /dev/null +++ b/src/Perspex.Input/Platform/IClipboard.cs @@ -0,0 +1,17 @@ +namespace Perspex.Input.Platform +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public interface IClipboard + { + Task GetTextAsync(); + + Task SetTextAsync(string text); + + Task ClearAsync(); + } +} diff --git a/src/Windows/Perspex.Win32/ClipboardImpl.cs b/src/Windows/Perspex.Win32/ClipboardImpl.cs new file mode 100644 index 0000000000..6a3dd3a657 --- /dev/null +++ b/src/Windows/Perspex.Win32/ClipboardImpl.cs @@ -0,0 +1,81 @@ +namespace Perspex.Win32 +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Perspex.Input.Platform; + using Perspex.Win32.Interop; + using System.Runtime.InteropServices; + + class ClipboardImpl : IClipboard + { + async Task OpenClipboard() + { + while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero)) + { + await Task.Delay(100); + } + } + + public async Task GetTextAsync() + { + await this.OpenClipboard(); + try + { + IntPtr hText = UnmanagedMethods.GetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT); + if (hText == IntPtr.Zero) + { + return null; + } + + var pText = UnmanagedMethods.GlobalLock(hText); + if (pText == IntPtr.Zero) + { + return null; + } + + var rv = Marshal.PtrToStringUni(pText); + UnmanagedMethods.GlobalUnlock(hText); + return rv; + } + finally + { + UnmanagedMethods.CloseClipboard(); + } + } + + public async Task SetTextAsync(string text) + { + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } + + await this.OpenClipboard(); + try + { + var hGlobal = Marshal.StringToHGlobalUni(text); + UnmanagedMethods.SetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, hGlobal); + } + finally + { + UnmanagedMethods.CloseClipboard(); + } + } + + public async Task ClearAsync() + { + await this.OpenClipboard(); + try + { + UnmanagedMethods.EmptyClipboard(); + } + finally + { + UnmanagedMethods.CloseClipboard(); + } + } + } +} diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index ff8792ccc2..eaab2a9ed6 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -627,6 +627,43 @@ namespace Perspex.Win32.Interop [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool SetWindowText(IntPtr hwnd, string lpString); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool OpenClipboard(IntPtr hWndOwner); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool CloseClipboard(); + + [DllImport("user32.dll")] + public static extern bool EmptyClipboard(); + + [DllImport("user32.dll")] + public static extern IntPtr GetClipboardData(ClipboardFormat uFormat); + + [DllImport("user32.dll")] + public static extern IntPtr SetClipboardData(ClipboardFormat uFormat, IntPtr hMem); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern IntPtr GlobalLock(IntPtr handle); + + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern bool GlobalUnlock(IntPtr handle); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern IntPtr GlobalAlloc(int uFlags, int dwBytes); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern IntPtr GlobalFree(IntPtr hMem); + + + + public enum ClipboardFormat + { + CF_TEXT = 1, + CF_UNICODETEXT = 13 + } + public struct MSG { public IntPtr hwnd; diff --git a/src/Windows/Perspex.Win32/Perspex.Win32.csproj b/src/Windows/Perspex.Win32/Perspex.Win32.csproj index 80946479fe..3ce71ae46c 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -66,6 +66,7 @@ Properties\SharedAssemblyInfo.cs + diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index 2b08fb9182..b753b3263d 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -4,6 +4,8 @@ // // ----------------------------------------------------------------------- +using Perspex.Input.Platform; + namespace Perspex.Win32 { using System; @@ -52,7 +54,7 @@ namespace Perspex.Win32 { var locator = Locator.CurrentMutable; locator.Register(() => new PopupImpl(), typeof(IPopupImpl)); - + locator.Register(() => new ClipboardImpl(), typeof(IClipboard)); locator.Register(() => WindowsKeyboardDevice.Instance, typeof(IKeyboardDevice)); locator.Register(() => WindowsMouseDevice.Instance, typeof(IMouseDevice)); locator.Register(() => instance, typeof(IPlatformSettings));