mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-10-02 05:06:35 -05:00
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:
@@ -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" />
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
281
src/HexManiac.Core/ViewModels/CompleteEditOperation.cs
Normal file
281
src/HexManiac.Core/ViewModels/CompleteEditOperation.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) { }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user