Refactor ViewPort Part 4

Pull the completion code into a new type.
This is actualy way better organized now. But a lot of code had to move, so I'm not confident that I didn't introduce any bugs.
This commit is contained in:
Benjamin Popp
2019-05-10 23:23:28 -05:00
parent 4d5bd4a057
commit 4fa76b1af2
6 changed files with 365 additions and 300 deletions

View File

@@ -54,6 +54,7 @@
<Compile Include="Models\Runs\AsciiRun.cs" />
<Compile Include="Models\Runs\HeaderRow.cs" />
<Compile Include="ViewModels\AutoCompleteSelectionItem.cs" />
<Compile Include="ViewModels\CompleteEditOperation.cs" />
<Compile Include="ViewModels\ContinueCellEdit.cs" />
<Compile Include="ViewModels\ConvertCellToText.cs" />
<Compile Include="ViewModels\DataClear.cs" />

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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;
/// <summary>
/// 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.
/// </summary>
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;
}
/// <returns>True if it was completed successfully, false if some sort of error occurred and we should abort the remainder of the edit.</returns>
@@ -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;

View File

@@ -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<int> data) : base(data) { }
}