diff --git a/src/HexManiac.Core/HexManiac.Core.csproj b/src/HexManiac.Core/HexManiac.Core.csproj
index 387d239b..9c149018 100644
--- a/src/HexManiac.Core/HexManiac.Core.csproj
+++ b/src/HexManiac.Core/HexManiac.Core.csproj
@@ -54,6 +54,7 @@
+
diff --git a/src/HexManiac.Core/Models/ModelDelta.cs b/src/HexManiac.Core/Models/ModelDelta.cs
index 22f936a2..6894aee7 100644
--- a/src/HexManiac.Core/Models/ModelDelta.cs
+++ b/src/HexManiac.Core/Models/ModelDelta.cs
@@ -24,6 +24,14 @@ namespace HavenSoft.HexManiac.Core.Models {
public event EventHandler OnNewDataChange;
public bool HasDataChange { get; private set; }
+ public bool HasAnyChange =>
+ HasDataChange ||
+ addedRuns.Any() ||
+ removedRuns.Any() ||
+ addedNames.Any() ||
+ removedNames.Any() ||
+ addedUnmappedPointers.Any() ||
+ removedUnmappedPointers.Any();
public int EarliestChange {
get {
diff --git a/src/HexManiac.Core/ViewModels/ChangeHistory.cs b/src/HexManiac.Core/ViewModels/ChangeHistory.cs
index c5935c24..0de3770f 100644
--- a/src/HexManiac.Core/ViewModels/ChangeHistory.cs
+++ b/src/HexManiac.Core/ViewModels/ChangeHistory.cs
@@ -6,6 +6,7 @@ using System.Windows.Input;
namespace HavenSoft.HexManiac.Core.ViewModels {
public interface IChangeToken {
bool HasDataChange { get; }
+ bool HasAnyChange { get; }
event EventHandler OnNewDataChange;
}
@@ -79,7 +80,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
revert = revertChange;
undo = new StubCommand {
Execute = arg => UndoExecuted(),
- CanExecute = arg => undoStack.Count > 0 || currentChange != null,
+ CanExecute = arg => undoStack.Count > 0 || (currentChange != null && currentChange.HasAnyChange),
};
redo = new StubCommand {
Execute = arg => RedoExecuted(),
@@ -89,7 +90,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
public void ChangeCompleted() {
if (currentChange == null) return;
+ if (!currentChange.HasAnyChange) { currentChange = null; return; }
VerifyRevertNotInProgress();
+
undoStack.Push(currentChange);
currentChange.OnNewDataChange -= OnCurrentTokenDataChanged;
currentChange = null;
diff --git a/src/HexManiac.Core/ViewModels/CompleteEditOperation.cs b/src/HexManiac.Core/ViewModels/CompleteEditOperation.cs
new file mode 100644
index 00000000..93fba446
--- /dev/null
+++ b/src/HexManiac.Core/ViewModels/CompleteEditOperation.cs
@@ -0,0 +1,281 @@
+using HavenSoft.HexManiac.Core.Models;
+using HavenSoft.HexManiac.Core.Models.Runs;
+using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using static HavenSoft.HexManiac.Core.Models.Runs.PCSRun;
+using static HavenSoft.HexManiac.Core.Models.Runs.PointerRun;
+
+namespace HavenSoft.HexManiac.Core.ViewModels {
+ internal class CompleteEditOperation : IDataFormatVisitor {
+ private readonly IDataModel Model;
+ private readonly int memoryLocation;
+ private readonly string CurrentText;
+ private readonly ModelDelta CurrentChange;
+
+ public bool Result { get; private set; } // if true, the edit was completed correctly
+ public int NewDataIndex { get; private set; } // for completed edits, where should the selection move to?
+ public bool DataMoved { get; private set; }
+ public string MessageText { get; private set; } // is there a message to display to the user? For example, when data gets moved.
+ public string ErrorText { get; private set; } // is there an error to display to the user? For example, invalide pointer
+ public HexElement NewCell { get; private set; } // if result is true and this is not null, asign this one value back to the one cell
+ // and refresh the one cell (along with any other UnderEdit cells)
+ // if result is true and this _is_ null, then the entire screen needs to be refreshed.
+
+ public CompleteEditOperation(IDataModel model, int memoryLocation, string currentText, ModelDelta currentChange) {
+ Model = model;
+ this.memoryLocation = memoryLocation;
+ CurrentText = currentText;
+ CurrentChange = currentChange;
+
+ NewDataIndex = memoryLocation;
+ }
+
+ public void Visit(Undefined dataFormat, byte data) => Visit((None)null, data);
+
+ public void Visit(None dataFormat, byte data) {
+ if (CurrentText.StartsWith(PointerStart.ToString())) {
+ if (CurrentText.Last() != PointerEnd && CurrentText.Last() != ' ') return;
+ CompletePointerEdit();
+ Result = true;
+ } else {
+ if (CurrentText.Length < 2) return;
+ CompleteHexEdit(CurrentText);
+ Result = true;
+ }
+ }
+
+ public void Visit(UnderEdit dataFormat, byte data) => throw new NotImplementedException();
+
+ public void Visit(Pointer pointer, byte data) {
+ var run = Model.GetNextRun(memoryLocation);
+ if (run is ArrayRun && CurrentText[0] != PointerStart) {
+ ErrorText = "Pointers in tables cannot be removed without removing the table.";
+ return;
+ }
+
+ Visit((None)null, data);
+ }
+
+ public void Visit(Anchor anchor, byte data) {
+ anchor.OriginalFormat.Visit(this, data);
+ if (NewCell != null) NewCell = new HexElement(NewCell.Value, new Anchor(NewCell.Format, anchor.Name, anchor.Format, anchor.Sources));
+ }
+
+ public void Visit(PCS pcs, byte data) {
+ var currentText = CurrentText;
+ if (currentText.StartsWith(StringDelimeter.ToString())) currentText = currentText.Substring(1);
+ if (pcs.Position != 0 && CurrentText == StringDelimeter.ToString()) {
+ CompleteStringEdit();
+ Result = true;
+ } else if (PCSString.PCS.Any(str => str == currentText)) {
+ CompleteCharacterEdit(pcs);
+ Result = true;
+ }
+ }
+
+ public void Visit(EscapedPCS pcs, byte data) {
+ if (CurrentText.Length < 2) return;
+ CompleteCharacterEdit(pcs);
+ Result = true;
+ }
+
+ public void Visit(ErrorPCS pcs, byte data) => throw new NotImplementedException();
+
+ public void Visit(Ascii ascii, byte data) {
+ CompleteAsciiEdit(ascii);
+ Result = true;
+ }
+
+ public void Visit(Integer integer, byte data) {
+ if (char.IsWhiteSpace(CurrentText.Last())) {
+ CompleteIntegerEdit(integer);
+ Result = true;
+ }
+ }
+
+ public void Visit(IntegerEnum integer, byte data) {
+ // must end in whitespace or must have matching quotation marks (ex. "Mr. Mime")
+ var quoteCount = CurrentText.Count(c => c == '"');
+
+ if (quoteCount == 0 && char.IsWhiteSpace(CurrentText.Last())) {
+ CompleteIntegerEnumEdit(integer);
+ Result = true;
+ } else if (quoteCount == 2) {
+ CompleteIntegerEnumEdit(integer);
+ Result = true;
+ }
+ }
+
+ private void CompleteIntegerEdit(Integer integer) {
+ if (!int.TryParse(CurrentText, out var result)) {
+ ErrorText = $"Could not parse {CurrentText} as a number";
+ return;
+ }
+
+ var run = (ArrayRun)Model.GetNextRun(memoryLocation);
+ var offsets = run.ConvertByteOffsetToArrayOffset(memoryLocation);
+ int length = run.ElementContent[offsets.SegmentIndex].Length;
+ for (int i = 0; i < length; i++) {
+ CurrentChange.ChangeData(Model, offsets.SegmentStart + i, (byte)result);
+ result /= 0x100;
+ }
+
+ if (result != 0) ErrorText = $"Warning: number was too big to fit in the available space.";
+ NewDataIndex = offsets.SegmentStart + length;
+ }
+
+ private void CompleteIntegerEnumEdit(IntegerEnum integer) {
+ var array = (ArrayRun)Model.GetNextRun(memoryLocation);
+ var offsets = array.ConvertByteOffsetToArrayOffset(memoryLocation);
+ var segment = (ArrayRunEnumSegment)array.ElementContent[offsets.SegmentIndex];
+ if (segment.TryParse(Model, CurrentText, out int value)) {
+ Model.WriteMultiByteValue(offsets.SegmentStart, segment.Length, CurrentChange, value);
+ NewDataIndex = offsets.SegmentStart + segment.Length;
+ } else {
+ ErrorText = $"Could not parse {CurrentText}as an enum from the {segment.EnumName} array";
+ }
+ }
+
+ private void CompleteAsciiEdit(Ascii asciiFormat) {
+ var content = (byte)CurrentText[0];
+
+ CurrentChange.ChangeData(Model, memoryLocation, content);
+ NewCell = new HexElement(content, new Ascii(asciiFormat.Source, asciiFormat.Position, CurrentText[0]));
+ NewDataIndex = memoryLocation + 1;
+ }
+
+ private void CompletePointerEdit() {
+ // if they just started a pointer and then clicked off, there's nothing to complete
+ if (CurrentText == PointerStart + " ") return;
+
+ var destination = CurrentText.Substring(1, CurrentText.Length - 2);
+
+ if (destination.Length == 2 && destination.All(ViewPort.AllHexCharacters.Contains)) { CompleteHexEdit(destination); return; }
+
+ Model.ExpandData(CurrentChange, memoryLocation + 3);
+
+ var currentRun = Model.GetNextRun(memoryLocation);
+ bool inArray = currentRun.Start <= memoryLocation && currentRun is ArrayRun;
+ var sources = currentRun.PointerSources;
+
+ if (!inArray) {
+ if (destination != string.Empty) {
+ Model.ClearFormatAndData(CurrentChange, memoryLocation, 4);
+ sources = null;
+ } else if (!(currentRun is NoInfoRun)) {
+ Model.ClearFormat(CurrentChange, memoryLocation, 4);
+ sources = null;
+ }
+ }
+
+ int fullValue;
+ if (destination == string.Empty) {
+ fullValue = Model.ReadPointer(memoryLocation);
+ } else if (destination.All(ViewPort.AllHexCharacters.Contains) && destination.Length <= 7) {
+ while (destination.Length < 6) destination = "0" + destination;
+ fullValue = int.Parse(destination, NumberStyles.HexNumber);
+ } else {
+ fullValue = Model.GetAddressFromAnchor(CurrentChange, memoryLocation, destination);
+ }
+
+ if (fullValue == Pointer.NULL || (0 <= fullValue && fullValue < Model.Count)) {
+ if (inArray) {
+ Model.UpdateArrayPointer(CurrentChange, memoryLocation, fullValue);
+ // Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged);
+ } else {
+ Model.WritePointer(CurrentChange, memoryLocation, fullValue);
+ Model.ObserveRunWritten(CurrentChange, new PointerRun(memoryLocation, sources));
+ }
+
+ NewDataIndex = memoryLocation + 4;
+ } else {
+ ErrorText = $"Address {fullValue.ToString("X2")} is not within the data.";
+ }
+ }
+
+ private void CompleteStringEdit() {
+ int memoryLocation = this.memoryLocation;
+
+ // all the bytes are already correct, just move to the next space
+ var run = Model.GetNextRun(memoryLocation);
+ if (run is PCSRun pcsRun) {
+ while (run.Start + run.Length > memoryLocation) {
+ CurrentChange.ChangeData(Model, memoryLocation, 0xFF);
+ memoryLocation++;
+ NewDataIndex = memoryLocation;
+ var newRunLength = PCSString.ReadString(Model, run.Start, true);
+ Model.ObserveRunWritten(CurrentChange, new PCSRun(run.Start, newRunLength, run.PointerSources));
+ }
+ } else if (run is ArrayRun arrayRun) {
+ var offsets = arrayRun.ConvertByteOffsetToArrayOffset(memoryLocation);
+ CurrentChange.ChangeData(Model, memoryLocation, 0xFF);
+ memoryLocation++;
+ NewDataIndex = memoryLocation;
+ while (offsets.SegmentStart + arrayRun.ElementContent[offsets.SegmentIndex].Length > memoryLocation) {
+ CurrentChange.ChangeData(Model, memoryLocation, 0x00);
+ memoryLocation++;
+ NewDataIndex = memoryLocation;
+ }
+ }
+ }
+
+ private void CompleteCharacterEdit(IDataFormat originalFormat) {
+ var editText = CurrentText;
+ if (editText.StartsWith("\"")) editText = editText.Substring(1);
+ var pcs = originalFormat as PCS;
+ var escaped = originalFormat as EscapedPCS;
+ var run = Model.GetNextRun(memoryLocation);
+
+ var byteValue = escaped != null ?
+ byte.Parse(CurrentText, NumberStyles.HexNumber) :
+ (byte)Enumerable.Range(0, 0x100).First(i => PCSString.PCS[i] == editText);
+
+ var position = pcs != null ? pcs.Position : escaped.Position;
+ HandleLastCharacterChange(memoryLocation, editText, pcs, run, position, byteValue);
+ }
+
+ private void HandleLastCharacterChange(int memoryLocation, string editText, PCS pcs, IFormattedRun run, int position, byte byteValue) {
+ if (run is PCSRun) {
+ // if its the last character being edited on a normal string, try to expand
+ if (run.Length == position + 1) {
+ int extraBytesNeeded = editText == "\\\\" ? 2 : 1;
+ // last character edit: might require relocation
+ var newRun = Model.RelocateForExpansion(CurrentChange, run, run.Length + extraBytesNeeded);
+ if (newRun != run) {
+ MessageText = $"Text was automatically moved to {newRun.Start.ToString("X6")}. Pointers were updated.";
+ memoryLocation += newRun.Start - run.Start;
+ run = newRun;
+ DataMoved = true;
+ }
+
+ CurrentChange.ChangeData(Model, memoryLocation + 1, 0xFF);
+ if (editText == "\\\\") CurrentChange.ChangeData(Model, memoryLocation + 2, 0xFF);
+ run = new PCSRun(run.Start, run.Length + extraBytesNeeded, run.PointerSources);
+ Model.ObserveRunWritten(CurrentChange, run);
+ }
+ } else if (run is ArrayRun arrayRun) {
+ // if the last characet is being edited for an array, truncate
+ var offsets = arrayRun.ConvertByteOffsetToArrayOffset(memoryLocation);
+ if (arrayRun.ElementContent[offsets.SegmentIndex].Length == position + 1) {
+ memoryLocation--; // move back one byte and edit that one instead
+ }
+ } else {
+ Debug.Fail("Why are we completing a character edit on something other than a PCSRun or an Array?");
+ }
+
+ CurrentChange.ChangeData(Model, memoryLocation, byteValue);
+ NewDataIndex = memoryLocation + 1;
+ }
+
+ private void CompleteHexEdit(string currentText) {
+ var byteValue = byte.Parse(currentText, NumberStyles.HexNumber);
+ var run = Model.GetNextRun(memoryLocation);
+ if (!(run is NoInfoRun) || run.Start != memoryLocation) Model.ClearFormat(CurrentChange, memoryLocation, 1);
+ CurrentChange.ChangeData(Model, memoryLocation, byteValue);
+ NewDataIndex = memoryLocation + 1;
+ }
+ }
+}
diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs
index bc5af334..9dca4ed3 100644
--- a/src/HexManiac.Core/ViewModels/ViewPort.cs
+++ b/src/HexManiac.Core/ViewModels/ViewPort.cs
@@ -7,8 +7,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
-using System.Diagnostics;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows.Input;
@@ -1063,214 +1061,95 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
var underEdit = element.Format as UnderEdit;
if (underEdit == null) return false; // no edit to complete
- if (underEdit.CurrentText.StartsWith(PointerStart.ToString())) {
- if (!underEdit.CurrentText.EndsWith(PointerEnd.ToString()) && !underEdit.CurrentText.EndsWith(" ")) return false;
- CompletePointerEdit(point);
- RequestMenuClose?.Invoke(this, EventArgs.Empty);
- return true;
+ if (TryGeneralCompleteEdit(underEdit.CurrentText, point, out bool result)) {
+ return result;
}
- if (underEdit.CurrentText.StartsWith(GotoMarker.ToString())) {
- if (char.IsWhiteSpace(underEdit.CurrentText[underEdit.CurrentText.Length - 1])) {
- var destination = underEdit.CurrentText.Substring(1);
- ClearEdits(point);
- Goto.Execute(destination);
- RequestMenuClose?.Invoke(this, EventArgs.Empty);
- return true;
- } else {
- return false;
- }
- }
- if (underEdit.CurrentText.StartsWith(AnchorStart.ToString())) {
- TryUpdate(ref anchorText, underEdit.CurrentText, nameof(AnchorText));
- if (!char.IsWhiteSpace(underEdit.CurrentText[underEdit.CurrentText.Length - 1])) {
- AnchorTextVisible = true;
- return false;
- }
- // only end the anchor edit if the [] brace count matches
- if (underEdit.CurrentText.Sum(c => c == '[' ? 1 : c == ']' ? -1 : 0) != 0) {
- AnchorTextVisible = true;
- return false;
- }
-
- if (!CompleteAnchorEdit(point)) exitEditEarly = true;
- return true;
- }
+ // normal case: whether or not to accept the edit depends on the existing cell format
var dataIndex = scroll.ViewPointToDataIndex(point);
- if (underEdit.CurrentText == ExtendArray.ToString() && Model.IsAtEndOfArray(dataIndex, out var arrayRun)) {
- var originalArray = arrayRun;
- var errorInfo = Model.CompleteArrayExtension(history.CurrentChange, ref arrayRun);
- if (!errorInfo.HasError) {
- if (arrayRun.Start != originalArray.Start) {
- ScrollFromRunMove(arrayRun.Start + arrayRun.Length, arrayRun.Length, arrayRun);
- }
+ var completeEditOperation = new CompleteEditOperation(Model, dataIndex, underEdit.CurrentText, history.CurrentChange);
+ underEdit.OriginalFormat.Visit(completeEditOperation, element.Value);
+ if (completeEditOperation.Result) {
+ if (completeEditOperation.NewCell != null) {
+ currentView[point.X, point.Y] = completeEditOperation.NewCell;
+ }
+ if (completeEditOperation.DataMoved || completeEditOperation.NewDataIndex > scroll.DataLength) scroll.DataLength = Model.Count;
+ if (!SilentScroll(completeEditOperation.NewDataIndex) && completeEditOperation.NewCell == null) {
RefreshBackingData();
- } else {
- OnError?.Invoke(this, errorInfo.ErrorMessage);
}
- return true;
+ var run = Model.GetNextRun(completeEditOperation.NewDataIndex);
+ if (completeEditOperation.DataMoved) UpdateToolsFromSelection(run.Start);
+ if (run is ArrayRun) Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged);
+ if (run is ArrayRun || run is PCSRun) Tools.Schedule(Tools.StringTool.DataForCurrentRunChanged);
+ if (completeEditOperation.MessageText != null) OnMessage?.Invoke(this, completeEditOperation.MessageText);
+ if (completeEditOperation.ErrorText != null) OnError?.Invoke(this, completeEditOperation.ErrorText);
}
- var originalFormat = underEdit.OriginalFormat;
- if (originalFormat is Anchor) originalFormat = ((Anchor)originalFormat).OriginalFormat;
- if (originalFormat is Ascii) {
- CompleteAsciiEdit(point, underEdit.CurrentText);
- return true;
- } else if (originalFormat is PCS stringFormat) {
- var currentText = underEdit.CurrentText;
- if (currentText.StartsWith(StringDelimeter.ToString())) currentText = currentText.Substring(1);
- if (stringFormat.Position != 0 && underEdit.CurrentText == StringDelimeter.ToString()) {
- CompleteStringEdit(point);
- return true;
- } else if (PCSString.PCS.Any(str => str == currentText)) {
- CompleteCharacterEdit(point);
- return true;
- }
+ return completeEditOperation.Result;
+ }
+ }
- return false;
- } else if (originalFormat is EscapedPCS escaped) {
- if (underEdit.CurrentText.Length < 2) return false;
- CompleteCharacterEdit(point);
- return true;
- } else if (originalFormat is IntegerEnum integerEnum) {
- var currentText = underEdit.CurrentText;
-
- // must end in whitespace or must have matching quotation marks (ex. "Mr. Mime")
- var quoteCount = currentText.Count(c => c == '"');
- if (quoteCount == 2) {
- CompleteIntegerEnumEdit(point, currentText);
- return true;
- } else if (quoteCount == 0 && char.IsWhiteSpace(currentText.Last())) {
- CompleteIntegerEnumEdit(point, currentText);
- return true;
- }
-
- return false;
- } else if (originalFormat is Integer integer) {
- var currentText = underEdit.CurrentText;
- if (char.IsWhiteSpace(currentText.Last())) {
- CompleteIntegerEdit(point, currentText);
- return true;
- }
- return false;
+ ///
+ /// Some edits are valid no matter where you are in the data.
+ /// Try to complete one of those edits here.
+ /// Return true if it's a special edit. Result is true if the edit was completed.
+ ///
+ private bool TryGeneralCompleteEdit(string currentText, Point point, out bool result) {
+ // goto marker
+ if (currentText.StartsWith(GotoMarker.ToString())) {
+ if (char.IsWhiteSpace(currentText[currentText.Length - 1])) {
+ var destination = currentText.Substring(1);
+ ClearEdits(point);
+ Goto.Execute(destination);
+ RequestMenuClose?.Invoke(this, EventArgs.Empty);
+ result = true;
+ } else {
+ result = false;
}
- if (underEdit.CurrentText.Length < 2) return false;
- CompleteHexEdit(point);
return true;
}
- }
- private void CompleteIntegerEdit(Point point, string currentText) {
- var memoryLocation = scroll.ViewPointToDataIndex(point);
- var editFormat = (UnderEdit)currentView[point.X, point.Y].Format;
-
- var integer = (Integer)(editFormat.OriginalFormat is Anchor anchor ? anchor.OriginalFormat : editFormat.OriginalFormat);
- if (!int.TryParse(currentText, out var result)) {
- OnError?.Invoke(this, $"Could not parse {currentText} as a number");
- return;
- }
-
- var run = (ArrayRun)Model.GetNextRun(memoryLocation);
- var offsets = run.ConvertByteOffsetToArrayOffset(memoryLocation);
- int length = run.ElementContent[offsets.SegmentIndex].Length;
- for (int i = 0; i < length; i++) {
- history.CurrentChange.ChangeData(Model, offsets.SegmentStart + i, (byte)result);
- result /= 0x100;
- }
- Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged);
- if (result != 0) OnError?.Invoke(this, $"Warning: number was too big to fit in the available space.");
- if (!SilentScroll(offsets.SegmentStart + length)) ClearEdits(point);
- }
-
- private void CompleteIntegerEnumEdit(Point point, string currentText) {
- var memoryLocation = scroll.ViewPointToDataIndex(point);
- var array = (ArrayRun)Model.GetNextRun(memoryLocation);
- var offsets = array.ConvertByteOffsetToArrayOffset(memoryLocation);
- var segment = (ArrayRunEnumSegment)array.ElementContent[offsets.SegmentIndex];
- if (segment.TryParse(Model, currentText, out int value)) {
- Model.WriteMultiByteValue(offsets.SegmentStart, segment.Length, history.CurrentChange, value);
- Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged);
- if (!SilentScroll(offsets.SegmentStart + segment.Length)) ClearEdits(point);
- } else {
- OnError?.Invoke(this, $"Could not parse {currentText}as an enum from the {segment.EnumName} array");
- ClearEdits(point);
- }
- }
-
- private void CompleteAsciiEdit(Point point, string currentText) {
- var memoryLocation = scroll.ViewPointToDataIndex(point);
- var editFormat = (UnderEdit)currentView[point.X, point.Y].Format;
- var originalFormat = editFormat.OriginalFormat;
- var asciiFormat = originalFormat as Ascii ?? (Ascii)((Anchor)originalFormat).OriginalFormat;
- var content = (byte)currentText[0];
-
- history.CurrentChange.ChangeData(Model, memoryLocation, content);
- currentView[point.X, point.Y] = new HexElement(content, new Ascii(asciiFormat.Source, asciiFormat.Position, currentText[0]));
- SilentScroll(memoryLocation + 1);
- }
-
- private void CompletePointerEdit(Point point) {
- var element = currentView[point.X, point.Y];
- var underEdit = (UnderEdit)element.Format;
-
- // if they just started a pointer and then clicked off, there's nothing to complete
- if (underEdit.CurrentText == PointerStart + " ") {
- ClearEdits(point);
- return;
- }
-
- var index = scroll.ViewPointToDataIndex(point);
- var destination = underEdit.CurrentText.Substring(1, underEdit.CurrentText.Length - 2);
-
- if (destination.Length == 2 && destination.All(AllHexCharacters.Contains)) {
- currentView[point.X, point.Y] = new HexElement(element.Value, new UnderEdit(underEdit.OriginalFormat, destination));
- CompleteHexEdit(point);
- return;
- }
-
- Model.ExpandData(history.CurrentChange, index + 3);
- scroll.DataLength = Model.Count;
-
- var currentRun = Model.GetNextRun(index);
- bool inArray = currentRun.Start <= index && currentRun is ArrayRun;
- var sources = currentRun.PointerSources;
-
- if (!inArray) {
- if (destination != string.Empty) {
- Model.ClearFormatAndData(history.CurrentChange, index, 4);
- sources = null;
- } else if (!(currentRun is NoInfoRun)) {
- Model.ClearFormat(history.CurrentChange, index, 4);
- sources = null;
+ // anchor start
+ if (currentText.StartsWith(AnchorStart.ToString())) {
+ TryUpdate(ref anchorText, currentText, nameof(AnchorText));
+ if (!char.IsWhiteSpace(currentText[currentText.Length - 1])) {
+ AnchorTextVisible = true;
+ result = false;
+ return true;
}
+
+ // only end the anchor edit if the [] brace count matches
+ if (currentText.Sum(c => c == '[' ? 1 : c == ']' ? -1 : 0) != 0) {
+ AnchorTextVisible = true;
+ result = false;
+ return true;
+ }
+
+ if (!CompleteAnchorEdit(point)) exitEditEarly = true;
+ result = true;
+ return true;
}
- int fullValue;
- if (destination == string.Empty) {
- fullValue = Model.ReadPointer(index);
- } else if (destination.All(AllHexCharacters.Contains) && destination.Length <= 7) {
- while (destination.Length < 6) destination = "0" + destination;
- fullValue = int.Parse(destination, NumberStyles.HexNumber);
- } else {
- fullValue = Model.GetAddressFromAnchor(history.CurrentChange, index, destination);
- }
-
- if (fullValue == Pointer.NULL || (0 <= fullValue && fullValue < Model.Count)) {
- if (inArray) {
- Model.UpdateArrayPointer(history.CurrentChange, index, fullValue);
- Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged);
+ // table extension
+ var dataIndex = scroll.ViewPointToDataIndex(point);
+ if (currentText == ExtendArray.ToString() && Model.IsAtEndOfArray(dataIndex, out var arrayRun)) {
+ var originalArray = arrayRun;
+ var errorInfo = Model.CompleteArrayExtension(history.CurrentChange, ref arrayRun);
+ if (!errorInfo.HasError) {
+ if (arrayRun.Start != originalArray.Start) {
+ ScrollFromRunMove(arrayRun.Start + arrayRun.Length, arrayRun.Length, arrayRun);
+ }
+ RefreshBackingData();
} else {
- Model.WritePointer(history.CurrentChange, index, fullValue);
- Model.ObserveRunWritten(history.CurrentChange, new PointerRun(index, sources));
+ OnError?.Invoke(this, errorInfo.ErrorMessage);
}
-
- ClearEdits(point);
- SilentScroll(index + 4);
- } else {
- OnError?.Invoke(this, $"Address {fullValue.ToString("X2")} is not within the data.");
- ClearEdits(point);
+ result = true;
+ return true;
}
+
+ result = default;
+ return false;
}
/// True if it was completed successfully, false if some sort of error occurred and we should abort the remainder of the edit.
@@ -1308,100 +1187,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
return errorInfo.IsWarning;
}
- private void CompleteStringEdit(Point point) {
- // all the bytes are already correct, just move to the next space
- ClearEdits(point);
- var memoryLocation = scroll.ViewPointToDataIndex(point);
- var run = Model.GetNextRun(memoryLocation);
- if (run is PCSRun pcsRun) {
- while (run.Start + run.Length > memoryLocation) {
- history.CurrentChange.ChangeData(Model, memoryLocation, 0xFF);
- memoryLocation++;
- Tools.Schedule(Tools.StringTool.DataForCurrentRunChanged);
- SilentScroll(memoryLocation);
- var newRunLength = PCSString.ReadString(Model, run.Start, true);
- Model.ObserveRunWritten(history.CurrentChange, new PCSRun(run.Start, newRunLength, run.PointerSources));
- }
- } else if (run is ArrayRun arrayRun) {
- var offsets = arrayRun.ConvertByteOffsetToArrayOffset(memoryLocation);
- history.CurrentChange.ChangeData(Model, memoryLocation, 0xFF);
- memoryLocation++;
- Tools.Schedule(Tools.StringTool.DataForCurrentRunChanged);
- Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged);
- SilentScroll(memoryLocation);
- while (offsets.SegmentStart + arrayRun.ElementContent[offsets.SegmentIndex].Length > memoryLocation) {
- history.CurrentChange.ChangeData(Model, memoryLocation, 0x00);
- memoryLocation++;
- SilentScroll(memoryLocation);
- }
- }
- RefreshBackingData();
- }
-
- private void CompleteCharacterEdit(Point point) {
- var memoryLocation = scroll.ViewPointToDataIndex(point);
- var element = currentView[point.X, point.Y];
- var underEdit = (UnderEdit)element.Format;
-
- var editText = underEdit.CurrentText;
- if (editText.StartsWith("\"")) editText = editText.Substring(1);
- var pcs = underEdit.OriginalFormat as PCS;
- pcs = pcs ?? (underEdit.OriginalFormat as Anchor)?.OriginalFormat as PCS;
- var escaped = underEdit.OriginalFormat as EscapedPCS;
- escaped = escaped ?? (underEdit.OriginalFormat as Anchor)?.OriginalFormat as EscapedPCS;
- var run = Model.GetNextRun(memoryLocation);
- var byteValue = escaped != null ?
- byte.Parse(underEdit.CurrentText, NumberStyles.HexNumber) :
- (byte)Enumerable.Range(0, 0x100).First(i => PCSString.PCS[i] == editText);
-
- var position = pcs != null ? pcs.Position : escaped.Position;
- HandleLastCharacterChange(ref memoryLocation, editText, pcs, ref run, position);
-
- history.CurrentChange.ChangeData(Model, memoryLocation, byteValue);
- Tools.Schedule(Tools.StringTool.DataForCurrentRunChanged);
- if (run is ArrayRun) Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged);
- if (!SilentScroll(memoryLocation + 1)) {
- point = scroll.DataIndexToViewPoint(memoryLocation);
- RefreshBackingData(point);
- if (point.X + 1 < Width) {
- RefreshBackingData(new Point(point.X + 1, point.Y));
- } else {
- RefreshBackingData(new Point(0, point.Y + 1));
- }
- }
- }
-
- private void HandleLastCharacterChange(ref int memoryLocation, string editText, PCS pcs, ref IFormattedRun run, int position) {
- if (run is PCSRun) {
- // if its the last character being edited on a normal string, try to expand
- if (run.Length == position + 1) {
- int extraBytesNeeded = editText == "\\\\" ? 2 : 1;
- // last character edit: might require relocation
- var newRun = Model.RelocateForExpansion(history.CurrentChange, run, run.Length + extraBytesNeeded);
- if (newRun != run) {
- OnMessage?.Invoke(this, $"Text was automatically moved to {newRun.Start.ToString("X6")}. Pointers were updated.");
- ScrollFromRunMove(memoryLocation, pcs.Position, newRun);
- memoryLocation += newRun.Start - run.Start;
- run = newRun;
- UpdateToolsFromSelection(run.Start);
- }
-
- history.CurrentChange.ChangeData(Model, memoryLocation + 1, 0xFF);
- if (editText == "\\\\") history.CurrentChange.ChangeData(Model, memoryLocation + 2, 0xFF);
- run = new PCSRun(run.Start, run.Length + extraBytesNeeded, run.PointerSources);
- Model.ObserveRunWritten(history.CurrentChange, run);
- }
- } else if (run is ArrayRun arrayRun) {
- // if the last characet is being edited for an array, truncate
- var offsets = arrayRun.ConvertByteOffsetToArrayOffset(memoryLocation);
- if (arrayRun.ElementContent[offsets.SegmentIndex].Length == position + 1) {
- memoryLocation--; // move back one byte and edit that one instead
- }
- } else {
- Debug.Fail("Why are we completing a character edit on something other than a PCSRun or an Array?");
- }
- }
-
private void ScrollFromRunMove(int originalIndexInData, int indexInOldRun, IFormattedRun newRun) {
scroll.DataLength = Model.Count; // possible length change
var offset = originalIndexInData - scroll.DataIndex;
@@ -1410,20 +1195,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
selection.PropertyChanged += SelectionPropertyChanged;
}
- private void CompleteHexEdit(Point point) {
- var element = currentView[point.X, point.Y];
- var underEdit = (UnderEdit)element.Format;
-
- var byteValue = byte.Parse(underEdit.CurrentText, NumberStyles.HexNumber);
- var memoryLocation = scroll.ViewPointToDataIndex(point);
- var run = Model.GetNextRun(memoryLocation);
- if (!(run is NoInfoRun) || run.Start != memoryLocation) Model.ClearFormat(history.CurrentChange, memoryLocation, 1);
- history.CurrentChange.ChangeData(Model, memoryLocation, byteValue);
- scroll.DataLength = Model.Count;
- ClearEdits(point);
- SilentScroll(memoryLocation + 1);
- }
-
private bool SilentScroll(int memoryLocation) {
var nextPoint = scroll.DataIndexToViewPoint(memoryLocation);
var didScroll = true;
diff --git a/src/HexManiac.Tests/ChangeHistoryTests.cs b/src/HexManiac.Tests/ChangeHistoryTests.cs
index 76f0c59a..4761b197 100644
--- a/src/HexManiac.Tests/ChangeHistoryTests.cs
+++ b/src/HexManiac.Tests/ChangeHistoryTests.cs
@@ -13,6 +13,7 @@ namespace HavenSoft.HexManiac.Tests {
public event EventHandler OnNewDataChange;
#pragma warning restore 0067
public bool HasDataChange => Count > 0;
+ public bool HasAnyChange => true;
public FakeChangeToken() { }
public FakeChangeToken(IEnumerable data) : base(data) { }
}