using HavenSoft.HexManiac.Core.Models; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.DataFormats; using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; namespace HavenSoft.HexManiac.Core.ViewModels.Tools { public enum ElementContentViewModelType { TextField, NumericField, Address, HexField, ComboBox, } public interface IArrayElementViewModel : INotifyPropertyChanged { event EventHandler DataChanged; bool IsInError { get; } string ErrorText { get; } } public interface IFieldArrayElementViewModelStrategy { ElementContentViewModelType Type { get; } void UpdateModelFromViewModel(FieldArrayElementViewModel viewModel); string UpdateViewModelFromModel(FieldArrayElementViewModel viewModel); } public class FieldArrayElementViewModel : ViewModelCore, IArrayElementViewModel { private readonly IFieldArrayElementViewModelStrategy strategy; public event EventHandler DataChanged; public ChangeHistory History { get; } public IDataModel Model { get; } public string Name { get; } public int Start { get; } public int Length { get; } public ElementContentViewModelType Type => strategy.Type; public bool IsInError => errorText != string.Empty; string errorText; public string ErrorText { get => errorText; set { if (TryUpdate(ref errorText, value)) NotifyPropertyChanged(nameof(IsInError)); } } string content; public string Content { get => content; set { if (TryUpdate(ref content, value)) { strategy.UpdateModelFromViewModel(this); DataChanged?.Invoke(this, EventArgs.Empty); } } } public FieldArrayElementViewModel(ChangeHistory history, IDataModel model, string name, int start, int length, IFieldArrayElementViewModelStrategy strategy) { this.strategy = strategy; (History, Model, Name, Start, Length) = (history, model, name, start, length); content = strategy.UpdateViewModelFromModel(this); } public void RefreshControlFromModelChange() { TryUpdate(ref content, strategy.UpdateViewModelFromModel(this), nameof(Content)); } } public class TextFieldStratgy : IFieldArrayElementViewModelStrategy { public ElementContentViewModelType Type => ElementContentViewModelType.TextField; public void UpdateModelFromViewModel(FieldArrayElementViewModel viewModel) { var textBytes = PCSString.Convert(viewModel.Content); while (textBytes.Count < viewModel.Length) textBytes.Add(0x00); if (textBytes.Count > viewModel.Length) textBytes[viewModel.Length - 1] = 0xFF; for (int i = 0; i < viewModel.Length; i++) { viewModel.History.CurrentChange.ChangeData(viewModel.Model, viewModel.Start + i, textBytes[i]); } } public string UpdateViewModelFromModel(FieldArrayElementViewModel viewModel) { var text = PCSString.Convert(viewModel.Model, viewModel.Start, viewModel.Length).Trim(); // take off quotes if (text.StartsWith("\"")) text = text.Substring(1); if (text.EndsWith("\"")) text = text.Substring(0, text.Length - 1); return text; } } public class NumericFieldStrategy : IFieldArrayElementViewModelStrategy { public ElementContentViewModelType Type => ElementContentViewModelType.NumericField; public void UpdateModelFromViewModel(FieldArrayElementViewModel viewModel) { if (int.TryParse(viewModel.Content, out int content)) { viewModel.Model.WriteMultiByteValue(viewModel.Start, viewModel.Length, viewModel.History.CurrentChange, content); } else { viewModel.ErrorText = $"{viewModel.Name} must be an integer."; } } public string UpdateViewModelFromModel(FieldArrayElementViewModel viewModel) { int number = viewModel.Model.ReadMultiByteValue(viewModel.Start, viewModel.Length); return number.ToString(); } } public class AddressFieldStratgy : IFieldArrayElementViewModelStrategy { public ElementContentViewModelType Type => ElementContentViewModelType.Address; public void UpdateModelFromViewModel(FieldArrayElementViewModel viewModel) { var content = viewModel.Content; if (content.StartsWith(PointerRun.PointerStart.ToString())) content = content.Substring(1); if (content.EndsWith(PointerRun.PointerEnd.ToString())) content = content.Substring(0, content.Length - 1); int address; if (!int.TryParse(content, NumberStyles.HexNumber, CultureInfo.CurrentCulture.NumberFormat, out address)) { address = viewModel.Model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, content); } if (address != Pointer.NULL) { viewModel.Model.WritePointer(viewModel.History.CurrentChange, address, viewModel.Start); } else { viewModel.ErrorText = "Address should be hexidecimal or an anchor."; } } public string UpdateViewModelFromModel(FieldArrayElementViewModel viewModel) { var value = viewModel.Model.ReadPointer(viewModel.Start); var text = value.ToString("X2"); while (text.Length < 6) text = "0" + text; return $"{PointerRun.PointerStart}{text}{PointerRun.PointerEnd}"; } } public class HexFieldStratgy : IFieldArrayElementViewModelStrategy { public ElementContentViewModelType Type => ElementContentViewModelType.HexField; public void UpdateModelFromViewModel(FieldArrayElementViewModel viewModel) { if (int.TryParse(viewModel.Content, NumberStyles.HexNumber, CultureInfo.CurrentCulture.NumberFormat, out int hexValue)) { viewModel.Model.WriteMultiByteValue(viewModel.Start, viewModel.Length, viewModel.History.CurrentChange, hexValue); } else { viewModel.ErrorText = "Value should be hexidecimal."; } } public string UpdateViewModelFromModel(FieldArrayElementViewModel viewModel) { int number = viewModel.Model.ReadMultiByteValue(viewModel.Start, viewModel.Length); var text = number.ToString("X2"); return text; } } public class ComboBoxArrayElementViewModel : ViewModelCore, IArrayElementViewModel { private readonly ChangeHistory history; public event EventHandler DataChanged; public IDataModel Model { get; } public string Name { get; } public int Start { get; } public int Length { get; } public ElementContentViewModelType Type => ElementContentViewModelType.ComboBox; public bool IsInError => errorText != string.Empty; string errorText; public string ErrorText { get => errorText; set { if (TryUpdate(ref errorText, value)) NotifyPropertyChanged(nameof(IsInError)); } } private bool containsUniqueOption; public ObservableCollection Options { get; } private int selectedIndex; public int SelectedIndex { get => selectedIndex; set { if (!TryUpdate(ref selectedIndex, value)) return; var run = (ArrayRun)Model.GetNextRun(Start); var offsets = run.ConvertByteOffsetToArrayOffset(Start); var segment = (ArrayRunEnumSegment)run.ElementContent[offsets.SegmentIndex]; // special case: the last option might be a weird value that came in, not normally available in the enum if (containsUniqueOption && selectedIndex == Options.Count - 1 && int.TryParse(Options[selectedIndex], out var parsedValue)) { value = parsedValue; } Model.WriteMultiByteValue(Start, Length, history.CurrentChange, value); DataChanged?.Invoke(this, EventArgs.Empty); } } public ComboBoxArrayElementViewModel(ChangeHistory history, IDataModel model, string name, int start, int length) { (this.history, Model, Name, Start, Length) = (history, model, name, start, length); var run = (ArrayRun)Model.GetNextRun(Start); var offsets = run.ConvertByteOffsetToArrayOffset(start); var segment = (ArrayRunEnumSegment)run.ElementContent[offsets.SegmentIndex]; Options = new ObservableCollection(segment.GetOptions(model)); var value = model.ReadMultiByteValue(start, length); if (value >= Options.Count) { Options.Add(value.ToString()); selectedIndex = Options.Count - 1; containsUniqueOption = true; } else { selectedIndex = model.ReadMultiByteValue(start, length); } } } public class TextStreamArrayElementViewModel : ViewModelCore, IArrayElementViewModel { private readonly ChangeHistory history; private readonly FieldArrayElementViewModel matchingField; private readonly IDataModel model; private readonly string name; private readonly int start; public bool IsInError => !string.IsNullOrEmpty(ErrorText); public string ErrorText { get; private set; } public event EventHandler DataChanged; public event EventHandler<(int originalStart, int newStart)> DataMoved; string content; public string Content { get => content; set { if (TryUpdate(ref content, value)) { var destination = model.ReadPointer(start); var run = model.GetNextRun(destination); var data = PCSString.Convert(content); var newRun = model.RelocateForExpansion(history.CurrentChange, run, data.Count); if (run.Start != newRun.Start) { DataMoved?.Invoke(this, (run.Start, newRun.Start)); matchingField.RefreshControlFromModelChange(); } run = newRun; for (int i = 0; i < data.Count; i++) history.CurrentChange.ChangeData(model, run.Start + i, data[i]); for (int i = data.Count; i < run.Length; i++) history.CurrentChange.ChangeData(model, run.Start + i, 0xFF); DataChanged?.Invoke(this, EventArgs.Empty); model.ObserveRunWritten(history.CurrentChange, new PCSRun(run.Start, data.Count)); } } } public TextStreamArrayElementViewModel(ChangeHistory history, FieldArrayElementViewModel matchingField, IDataModel model, string name, int start) { this.history = history; this.matchingField = matchingField; this.model = model; this.name = name; this.start = start; var destination = model.ReadPointer(start); // by the time we get this far, we're guaranteed that this will be a PCSRun. // if it's not a PCSRun, we shouldn't have asked to construct this object. var run = (PCSRun)model.GetNextRun(destination); content = PCSString.Convert(model, run.Start, run.Length); } } }