diff --git a/WDE.Common.Avalonia/Controls/FixedTextBox.cs b/WDE.Common.Avalonia/Controls/FixedTextBox.cs index 5b5117f6f..66e65b181 100644 --- a/WDE.Common.Avalonia/Controls/FixedTextBox.cs +++ b/WDE.Common.Avalonia/Controls/FixedTextBox.cs @@ -1,7 +1,11 @@ using System; +using System.Linq; +using Avalonia; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Styling; +using Avalonia.Threading; namespace WDE.Common.Avalonia.Controls { @@ -19,6 +23,24 @@ protected override void OnTextInput(TextInputEventArgs e) } } + protected override void OnKeyDown(KeyEventArgs e) + { + var keymap = AvaloniaLocator.Current.GetService(); + if (keymap.Paste.Any(g => g.Matches(e))) + { + CustomPaste(); + e.Handled = true; + } + else + base.OnKeyDown(e); + + if (Text == "" && DataValidationErrors.GetHasErrors(this)) + { + Text = "0"; + SelectAll(); + } + } + public virtual void CustomPaste() { Paste(); diff --git a/WDE.Common.Avalonia/Controls/ParameterTextBox.cs b/WDE.Common.Avalonia/Controls/ParameterTextBox.cs index 6e951e804..e89830d7e 100644 --- a/WDE.Common.Avalonia/Controls/ParameterTextBox.cs +++ b/WDE.Common.Avalonia/Controls/ParameterTextBox.cs @@ -1,13 +1,81 @@ using System; +using System.Globalization; +using System.Linq; +using Avalonia; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Styling; +using Avalonia.VisualTree; +using WDE.Common.Utils; namespace WDE.Common.Avalonia.Controls { public class ParameterTextBox : FixedTextBox, IStyleable { private DateTime lastFocusTime; + private bool specialCopying; + public static readonly DirectProperty SpecialCopyingProperty = AvaloniaProperty.RegisterDirect("SpecialCopying", o => o.SpecialCopying, (o, v) => o.SpecialCopying = v); + + public override void CustomPaste() + { + if (specialCopying) + PasteAsync(); + else + base.CustomPaste(); + } + + private async void PasteAsync() + { + var text = await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))).GetTextAsync(); + + if (text is null) + return; + + var coords = CoordsParser.ExtractCoords(text); + if (coords.HasValue) + { + var nextTextBox = FindNext(this); + var nextNextTextBox = FindNext(nextTextBox); + var nextNextNextTextBox = FindNext(nextNextTextBox); + + this.Text = coords.Value.x.ToString(CultureInfo.InvariantCulture); + if (nextTextBox != null) + nextTextBox.Text = coords.Value.y.ToString(CultureInfo.InvariantCulture); + if (nextNextTextBox != null) + nextNextTextBox.Text = coords.Value.z.ToString(CultureInfo.InvariantCulture); + if (nextNextNextTextBox != null && coords.Value.o.HasValue) + nextNextNextTextBox.Text = coords.Value.o.Value.ToString(CultureInfo.InvariantCulture); + } + else + Paste(); + } + + private static T? FindNext(IVisual? start) where T : class, IVisual + { + if (start == null) + return null; + + var parent = start.GetVisualParent(); + if (parent == null) + return null; + + int startChildrenIndex = 0; + while (startChildrenIndex < parent.VisualChildren.Count && + parent.VisualChildren[startChildrenIndex] != start) + startChildrenIndex++; + + startChildrenIndex++; + + for (int i = startChildrenIndex; i < parent.VisualChildren.Count; ++i) + { + var find = parent.VisualChildren[i].FindDescendantOfType(true); + if (find != null) + return find; + } + + return FindNext(parent); + } protected override void OnGotFocus(GotFocusEventArgs e) { @@ -25,5 +93,11 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) } Type IStyleable.StyleKey => typeof(TextBox); + + public bool SpecialCopying + { + get { return specialCopying; } + set { SetAndRaise(SpecialCopyingProperty, ref specialCopying, value); } + } } } \ No newline at end of file diff --git a/WDE.Common.Avalonia/Controls/ParameterValueHolderView.cs b/WDE.Common.Avalonia/Controls/ParameterValueHolderView.cs index bf28aa8c0..8b74b2610 100644 --- a/WDE.Common.Avalonia/Controls/ParameterValueHolderView.cs +++ b/WDE.Common.Avalonia/Controls/ParameterValueHolderView.cs @@ -17,6 +17,9 @@ public class ParameterValueHolderView : TemplatedControl private Func>? specialCommand; public static readonly DirectProperty>?> SpecialCommandProperty = AvaloniaProperty.RegisterDirect>?>("SpecialCommand", o => o.SpecialCommand, (o, v) => o.SpecialCommand = v); + + private bool specialCopying; + public static readonly DirectProperty SpecialCopyingProperty = AvaloniaProperty.RegisterDirect("SpecialCopying", o => o.SpecialCopying, (o, v) => o.SpecialCopying = v); public ICommand? PickCommand { @@ -32,10 +35,16 @@ public Func>? SpecialCommand public ICommand? PickSpecial { get; } + public bool SpecialCopying + { + get => specialCopying; + set => SetAndRaise(SpecialCopyingProperty, ref specialCopying, value); + } + protected override void OnGotFocus(GotFocusEventArgs e) { base.OnGotFocus(e); - if (e.Source == this) + if (ReferenceEquals(e.Source, this)) { Dispatcher.UIThread.Post(() => { diff --git a/WDE.Common.Avalonia/Themes/Generic.axaml b/WDE.Common.Avalonia/Themes/Generic.axaml index bd178a2fe..69aeb2592 100644 --- a/WDE.Common.Avalonia/Themes/Generic.axaml +++ b/WDE.Common.Avalonia/Themes/Generic.axaml @@ -30,6 +30,7 @@ diff --git a/WDE.Common.Test/Utils/CoordsParserTests.cs b/WDE.Common.Test/Utils/CoordsParserTests.cs new file mode 100644 index 000000000..6f11a0f70 --- /dev/null +++ b/WDE.Common.Test/Utils/CoordsParserTests.cs @@ -0,0 +1,71 @@ +using NUnit.Framework; +using WDE.Common.Utils; + +namespace WDE.Common.Test.Utils; + +public class CoordsParserTests +{ + private static float? O => null; + + [Test] + public void TestSpaceXyz() + { + Assert.AreEqual((1f, 2f, 3f, O), CoordsParser.ExtractCoords("1 2 3")); + Assert.AreEqual((1.5f, 2.5f, 3.5f, O), CoordsParser.ExtractCoords("1.5 2.5 3.5")); + Assert.AreEqual((-1.5f, -2.5f, -3.5f, O), CoordsParser.ExtractCoords("-1.5 -2.5 -3.5")); + } + + [Test] + public void TestCommaXyz() + { + Assert.AreEqual((1f, 2f, 3f, O), CoordsParser.ExtractCoords("1, 2, 3")); + } + + [Test] + public void TestSemicolonXyz() + { + Assert.AreEqual((1f, 2f, 3f, O), CoordsParser.ExtractCoords("1; 2; 3")); + } + + [Test] + public void TestLettersXyz() + { + Assert.AreEqual((1f, 2f, 3f, O), CoordsParser.ExtractCoords("X 1 Y 2 Z 3")); + } + + [Test] + public void TestLettersXyzo() + { + Assert.AreEqual((1f, 2f, 3f, 4f), CoordsParser.ExtractCoords("X 1 Y 2 Z 3 O 4")); + } + + [Test] + public void TestLettersColonXyz() + { + Assert.AreEqual((1f, 2f, 3f, O), CoordsParser.ExtractCoords("X: 1 Y: 2 Z: 3")); + } + + [Test] + public void TestLettersColonXyzo() + { + Assert.AreEqual((1f, 2f, 3f, 4f), CoordsParser.ExtractCoords("X: 1 Y: 2 Z: 3 O: 4")); + } + + [Test] + public void TestMixedXyz() + { + Assert.AreEqual((1f, 2f, 3f, 4f), CoordsParser.ExtractCoords("1 2, 3; 4")); + } + + [Test] + public void TestRandomPrefix() + { + Assert.AreEqual((1f, 2f, 3f, 4f), CoordsParser.ExtractCoords("[1] position: 1, 2, 3, 4")); + } + + [Test] + public void TestRandomSuffix() + { + Assert.AreEqual((1f, 2f, 3f, 4f), CoordsParser.ExtractCoords("1, 2, 3, 4[random]")); + } +} \ No newline at end of file diff --git a/WDE.Common.Test/WDE.Common.Test.csproj b/WDE.Common.Test/WDE.Common.Test.csproj new file mode 100644 index 000000000..f5982e1af --- /dev/null +++ b/WDE.Common.Test/WDE.Common.Test.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + false + Debug;Release + AnyCPU + enable + nullable + + + + + + + + + + + + + + diff --git a/WDE.SmartScriptEditor.Avalonia/Editor/Views/Editing/ParameterEditorView.cs b/WDE.SmartScriptEditor.Avalonia/Editor/Views/Editing/ParameterEditorView.cs index 00e43d640..76c755776 100644 --- a/WDE.SmartScriptEditor.Avalonia/Editor/Views/Editing/ParameterEditorView.cs +++ b/WDE.SmartScriptEditor.Avalonia/Editor/Views/Editing/ParameterEditorView.cs @@ -13,6 +13,9 @@ public class ParameterEditorView : TemplatedControl public static readonly AttachedProperty OnEnterPressedProperty = AvaloniaProperty.RegisterAttached("OnEnterPressed", typeof(ParameterEditorView)); + private bool specialCopying; + public static readonly DirectProperty SpecialCopyingProperty = AvaloniaProperty.RegisterDirect("SpecialCopying", o => o.SpecialCopying, (o, v) => o.SpecialCopying = v); + static ParameterEditorView() { OnEnterPressedProperty.Changed.AddClassHandler((box, args) => @@ -26,6 +29,12 @@ static ParameterEditorView() }); } + public bool SpecialCopying + { + get => specialCopying; + set => SetAndRaise(SpecialCopyingProperty, ref specialCopying, value); + } + public static bool GetOnEnterPressed(IAvaloniaObject obj) { return obj.GetValue(OnEnterPressedProperty); diff --git a/WDE.SmartScriptEditor.Avalonia/Editor/Views/Editing/ParametersEditView.axaml b/WDE.SmartScriptEditor.Avalonia/Editor/Views/Editing/ParametersEditView.axaml index 8e0c206f7..6aa1fb2b6 100644 --- a/WDE.SmartScriptEditor.Avalonia/Editor/Views/Editing/ParametersEditView.axaml +++ b/WDE.SmartScriptEditor.Avalonia/Editor/Views/Editing/ParametersEditView.axaml @@ -30,6 +30,7 @@ diff --git a/WDE.SmartScriptEditor.Avalonia/Themes/Generic.axaml b/WDE.SmartScriptEditor.Avalonia/Themes/Generic.axaml index f0f1d998c..a44e1843b 100644 --- a/WDE.SmartScriptEditor.Avalonia/Themes/Generic.axaml +++ b/WDE.SmartScriptEditor.Avalonia/Themes/Generic.axaml @@ -228,6 +228,7 @@ diff --git a/WDE.SmartScriptEditor/Editor/ViewModels/Editing/EditableParameterViewModel.cs b/WDE.SmartScriptEditor/Editor/ViewModels/Editing/EditableParameterViewModel.cs index 34dd85196..9897b496c 100644 --- a/WDE.SmartScriptEditor/Editor/ViewModels/Editing/EditableParameterViewModel.cs +++ b/WDE.SmartScriptEditor/Editor/ViewModels/Editing/EditableParameterViewModel.cs @@ -19,13 +19,17 @@ public class EditableParameterViewModel : EditableParameterViewModel, IEditab private readonly IItemFromListProvider itemFromListProvider; private readonly ICurrentCoreVersion currentCoreVersion; - public EditableParameterViewModel(ParameterValueHolder parameter, string group, IItemFromListProvider itemFromListProvider, ICurrentCoreVersion currentCoreVersion) + public EditableParameterViewModel(ParameterValueHolder parameter, + string group, + IItemFromListProvider itemFromListProvider, + ICurrentCoreVersion currentCoreVersion) { this.itemFromListProvider = itemFromListProvider; this.currentCoreVersion = currentCoreVersion; Group = group; Parameter = parameter; SelectItemAction = new AsyncAutoCommand(SelectItem); + SpecialCopying = typeof(T) == typeof(float); Watch(parameter, p => p.IsUsed, nameof(IsHidden)); Watch(parameter, p => p.ForceHidden, nameof(IsHidden)); @@ -42,6 +46,8 @@ public EditableParameterViewModel(ParameterValueHolder parameter, string grou public string Group { get; } + public bool SpecialCopying { get; } + public ICommand SelectItemAction { get; set; } public string Name => Parameter.Name; diff --git a/WoWDatabaseEditor.Common/WDE.Common/Utils/CoordsParser.cs b/WoWDatabaseEditor.Common/WDE.Common/Utils/CoordsParser.cs new file mode 100644 index 000000000..44c11ecb5 --- /dev/null +++ b/WoWDatabaseEditor.Common/WDE.Common/Utils/CoordsParser.cs @@ -0,0 +1,27 @@ +using System.Text.RegularExpressions; + +namespace WDE.Common.Utils +{ + public class CoordsParser + { + private static Regex waypointRegex = new Regex(@"(-?\d+(?:\.\d+)?)[ ,;Yy:]{1,5}(-?\d+(?:\.\d+)?)[ ,;Zz:]{1,5}(-?\d+(?:\.\d+)?)(?:[ ,;Oo:]{1,5}(-?\d+(?:\.\d+)?))?"); + + public static (float x, float y, float z, float? o)? ExtractCoords(string line) + { + var match = waypointRegex.Match(line); + if (match.Success && + float.TryParse(match.Groups[1].Value, out var x) && + float.TryParse(match.Groups[2].Value, out var y) && + float.TryParse(match.Groups[3].Value, out var z)) + { + float? o = null; + if (match.Groups.Count == 5 && + float.TryParse(match.Groups[4].Value, out var o_)) + o = o_; + return (x, y, z, o); + } + + return null; + } + } +} \ No newline at end of file diff --git a/WoWDatabaseEditor.sln b/WoWDatabaseEditor.sln index 38909ab89..0fd910021 100644 --- a/WoWDatabaseEditor.sln +++ b/WoWDatabaseEditor.sln @@ -51,6 +51,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WDE.RemoteSOAP", "WDE.Remot EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WDE.RemoteSOAP.Test", "WDE.RemoteSOAP.Test\WDE.RemoteSOAP.Test.csproj", "{0CA0846F-F6F3-4B4B-B0BA-8FF9502F9CEA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WDE.Common.Test", "WoWDatabaseEditor\WDE.Common.Test\WDE.Common.Test.csproj", "{87974B90-DAD7-4027-A1F6-4405DC41F733}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "Solution Items", "{E9668961-BD19-4360-8ABE-B9BD55EB52CA}" ProjectSection(SolutionItems) = preProject appveyor.yml = appveyor.yml @@ -508,6 +510,10 @@ Global {DACACBB1-29B3-435E-B1E5-CCE54C76D3A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {DACACBB1-29B3-435E-B1E5-CCE54C76D3A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {DACACBB1-29B3-435E-B1E5-CCE54C76D3A5}.Release|Any CPU.Build.0 = Release|Any CPU + {87974B90-DAD7-4027-A1F6-4405DC41F733}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87974B90-DAD7-4027-A1F6-4405DC41F733}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87974B90-DAD7-4027-A1F6-4405DC41F733}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87974B90-DAD7-4027-A1F6-4405DC41F733}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -579,5 +585,6 @@ Global {96B60B17-8A72-40B5-9D2E-09BF256D7B07} = {4C553308-1140-42F2-B99C-90402248A983} {6E0C8CDD-031A-4318-B612-9F1A575163AC} = {8A37024A-7A68-4BCB-B800-766D92E041EF} {DACACBB1-29B3-435E-B1E5-CCE54C76D3A5} = {8A37024A-7A68-4BCB-B800-766D92E041EF} + {87974B90-DAD7-4027-A1F6-4405DC41F733} = {6C7361F8-036F-4C9A-8002-A94B71005DDB} EndGlobalSection EndGlobal diff --git a/appveyor.yml b/appveyor.yml index f35b3099a..b274e78ff 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -73,6 +73,10 @@ test_script: dotnet test WDE.SqlQueryGenerator.Test/WDE.SqlQueryGenerator.Test.csproj + dotnet test WDE.SqlInterpreter.Test/WDE.SqlInterpreter.Test.csproj + + dotnet test WDE.Common.Test/WDE.Common.Test.csproj + dotnet test WoWDatabaseEditorCore.Test/WoWDatabaseEditorCore.Test.csproj artifacts: - path: WoWDatabaseEditorMacOs.zip