From 46b9fe4ae37c331f8ade510441927fb7e238cd29 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Sun, 7 Apr 2019 13:07:59 -0500 Subject: [PATCH] Adding right click option to see if something is text --- src/HexManiac.Core/Models/PokemonModel.cs | 5 +- src/HexManiac.Core/ViewModels/ViewPort.cs | 59 +++++++++++++++++++---- src/HexManiac.Tests/StringModelTests.cs | 18 +++++++ src/HexManiac.WPF/Controls/HexContent.cs | 10 ++++ 4 files changed, 79 insertions(+), 13 deletions(-) diff --git a/src/HexManiac.Core/Models/PokemonModel.cs b/src/HexManiac.Core/Models/PokemonModel.cs index 89822a28..f7ea7958 100644 --- a/src/HexManiac.Core/Models/PokemonModel.cs +++ b/src/HexManiac.Core/Models/PokemonModel.cs @@ -124,13 +124,12 @@ namespace HavenSoft.HexManiac.Core.Models { } private void WriteStringRuns(Dictionary> pointersForDestination) { - var destinations = pointersForDestination.Keys.OrderBy(i => i).GetEnumerator(); - destinations.MoveNext(); + var noDataChange = new NoDataChangeDeltaModel(); foreach (var destination in pointersForDestination.Keys.OrderBy(i => i)) { var length = PCSString.ReadString(RawData, destination, false); if (length < 2) continue; if (GetNextRun(destination + 1).Start < destination + length) continue; - ObserveRunWritten(new ModelDelta(), new PCSRun(destination, length, pointersForDestination[destination])); + ObserveRunWritten(noDataChange, new PCSRun(destination, length, pointersForDestination[destination])); } } diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index cd198092..60a2dffe 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -11,6 +11,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Windows.Input; using static HavenSoft.HexManiac.Core.ICommandExtensions; using static HavenSoft.HexManiac.Core.Models.Runs.ArrayRun; @@ -29,7 +30,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { private static readonly NotifyCollectionChangedEventArgs ResetArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); private readonly StubCommand clear = new StubCommand(), - copy = new StubCommand(); + copy = new StubCommand(), + isText = new StubCommand(); private HexElement[,] currentView; private bool exitEditEarly; @@ -270,6 +272,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public ICommand Copy => copy; public ICommand Clear => clear; + public ICommand IsText => isText; public HexElement this[Point p] => this[p.X, p.Y]; @@ -354,6 +357,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels { if (usedHistory) UpdateToolsFromSelection(left); }; + isText.CanExecute = CanAlwaysExecute; + isText.Execute = IsTextExecuted; + save.CanExecute = arg => !history.IsSaved; save.Execute = arg => SaveExecuted((IFileSystem)arg); @@ -518,20 +524,24 @@ namespace HavenSoft.HexManiac.Core.ViewModels { // for each of the results, we recognized it as text: see if we need to add a matching string run / pointers private int ConsiderResultsAsTextRuns(IEnumerable searchResults) { int resultsRecognizedAsTextRuns = 0; + var parallelLock = new object(); - foreach (var result in searchResults) { + Parallel.ForEach(searchResults, result => { + // foreach (var result in searchResults) { var nextRun = Model.GetNextRun(result); - if (nextRun.Start < result) continue; - if (nextRun.Start == result && !(nextRun is NoInfoRun)) continue; + if (nextRun.Start < result) return; + if (nextRun.Start == result && !(nextRun is NoInfoRun)) return; var pointers = Model.SearchForPointersToAnchor(history.CurrentChange, result); - if (pointers.Count == 0) continue; + if (pointers.Count == 0) return; var length = PCSString.ReadString(Model, result, true); - if (length < 1) continue; - if (result + length > nextRun.Start && nextRun.Start != result) continue; + if (length < 1) return; + if (result + length > nextRun.Start && nextRun.Start != result) return; var newRun = new PCSRun(result, length, pointers); - Model.ObserveAnchorWritten(history.CurrentChange, string.Empty, newRun); - resultsRecognizedAsTextRuns++; - } + lock (parallelLock) { + Model.ObserveAnchorWritten(history.CurrentChange, string.Empty, newRun); + resultsRecognizedAsTextRuns++; + } + }); return resultsRecognizedAsTextRuns; } @@ -734,6 +744,35 @@ namespace HavenSoft.HexManiac.Core.ViewModels { return point; } + private void IsTextExecuted(object obj) { + var selectionStart = scroll.ViewPointToDataIndex(selection.SelectionStart); + var selectionEnd = scroll.ViewPointToDataIndex(selection.SelectionEnd); + var left = Math.Min(selectionStart, selectionEnd); + var length = Math.Abs(selectionEnd - selectionStart) + 1; + while (Model[left] != 0xFF && PCSString.PCS[Model[left]] != null) { left--; length++; } + left++; length--; + while (true) { + var run = Model.GetNextRun(left); + if (run.Start >= left) break; + length -= left - run.Start; + left = run.Start + run.Length; + } + var startPaces = new List(); + while (length > 0) { + startPaces.Add(left); + while (Model[left] != 0xFF) { left++; length--; } + left++; length--; + var run = Model.GetNextRun(left); + if (!(run is NoInfoRun)) break; + } + var foundCount = ConsiderResultsAsTextRuns(startPaces); + if (foundCount == 0) { + OnError?.Invoke(this, "Failed to automatically find text at that location."); + } else { + RefreshBackingData(); + } + } + private bool ShouldAcceptInput(ref Point point, ref HexElement element, char input) { var underEdit = element.Format as UnderEdit; var innerFormat = underEdit?.OriginalFormat ?? element.Format; diff --git a/src/HexManiac.Tests/StringModelTests.cs b/src/HexManiac.Tests/StringModelTests.cs index ef37a5f8..92f0feee 100644 --- a/src/HexManiac.Tests/StringModelTests.cs +++ b/src/HexManiac.Tests/StringModelTests.cs @@ -419,5 +419,23 @@ namespace HavenSoft.HexManiac.Tests { Assert.NotEqual("anchor", ((Anchor)viewPort[0, 1].Format).Name); Assert.Single(messages); } + + [Fact] + public void CanUseViewPortToAutoFindTextWithoutKnowingAboutPointersToIt() { + var text = PCSString.Convert("This is some text."); + var buffer = Enumerable.Range(0, 0x200).Select(i => (byte)0xFF).ToArray(); + text.CopyTo(buffer, 0x10); + var model = new PokemonModel(buffer); + model.WritePointer(new ModelDelta(), 0x00, 0x10); + + var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 }; + viewPort.SelectionStart = new Point(3, 1); // just a random byte in the middle of the text + var errors = new List(); + viewPort.OnError += (sender, e) => errors.Add(e); + + viewPort.IsText.Execute(); // this line should find the start of the text and add a run, even with no pointer to it + + Assert.IsType(viewPort[3, 1].Format); + } } } diff --git a/src/HexManiac.WPF/Controls/HexContent.cs b/src/HexManiac.WPF/Controls/HexContent.cs index f4af5fb0..85ca434f 100644 --- a/src/HexManiac.WPF/Controls/HexContent.cs +++ b/src/HexManiac.WPF/Controls/HexContent.cs @@ -249,6 +249,7 @@ namespace HavenSoft.HexManiac.WPF.Controls { if (format is PCS pcs) children.AddRange(GetStringChildren(p)); if (ViewPort.IsTable(p)) children.AddRange(GetTableChildren(p)); if (format is Pointer pointer) children.AddRange(GetPointerChildren(p)); + if (format is None none) children.AddRange(GetNoneChildren(p)); if (editableViewPort.FormattedDataIsSelected) children.AddRange(GetClearFormattingChildren(p)); } else { children.AddRange(GetSearchChildren(p)); @@ -410,6 +411,15 @@ namespace HavenSoft.HexManiac.WPF.Controls { yield return CreateFollowLinkButton("Follow Pointer", p); } + private IEnumerable GetNoneChildren(ModelPoint p) { + yield return new Button { + Content = new TextBlock { Text = "This is Text" }, + }.SetEvent(ButtonBase.ClickEvent, (sender, e) => { + ((ViewPort)ViewPort).IsText.Execute(); + recentMenu.IsOpen = false; + }); + } + private IEnumerable GetTableChildren(ModelPoint p) { yield return new Button { Content = new TextBlock { Text = "Open in Table Tool" },