using HavenSoft.HexManiac.Core.Models; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.DataFormats; using HavenSoft.HexManiac.Core.ViewModels.Tools; using HavenSoft.HexManiac.Core.ViewModels.Visitors; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Windows.Input; namespace HavenSoft.HexManiac.Core.ViewModels { public class DiffViewPort : ViewModelCore, IViewPort { public const int MaxInnerTabWidth = 32; private readonly IList left, right; private readonly int leftWidth, rightWidth; private readonly List startOfNextSegment; private HexElement[,] cell; public int ChildCount => left.Count; public DiffViewPort(IEnumerable leftChildren, IEnumerable rightChildren) { left = leftChildren.ToList(); right = rightChildren.ToList(); EqualizeScroll(left, right); Debug.Assert(left.Count == right.Count, "Diff views must have the same number of diff elements on each side!"); // combine similar children for (int i = 0; i < left.Count - 1; i++) { if (CompositeChildViewPort.TryCombine(left[i], left[i + 1], out var leftResult) && CompositeChildViewPort.TryCombine(right[i], right[i + 1], out var rightResult)) { left[i] = leftResult; right[i] = rightResult; left.RemoveAt(i + 1); right.RemoveAt(i + 1); i -= 1; } } leftWidth = left.Count == 0 ? 16 : Math.Min(MaxInnerTabWidth, left.Max(child => child.Width)); rightWidth = right.Count == 0 ? 16 : Math.Min(MaxInnerTabWidth, right.Max(child => child.Width)); startOfNextSegment = new List(); startOfNextSegment.Add(0); for (int i = 0; i < Math.Min(left.Count, right.Count); i++) { startOfNextSegment.Add(startOfNextSegment[i] + 1 + Math.Max(left[i].Height, right[i].Height)); } } public int LeftHeight(int index) => left[index].Height; public int RightHeight(int index) => right[index].Height; #region IViewPort public HexElement this[int x, int y] { get { if (x < 0 || y < 0 || x >= cell.GetLength(0) || y >= cell.GetLength(1)) return new HexElement(default, default, DataFormats.Undefined.Instance); return cell[x, y]; } } public double ToolPanelWidth { get; set; } = 500; public string FileName => string.Empty; public string FullFileName => Name; public int Width { get => leftWidth + rightWidth + 1; set { } } private int height; public int Height { get => height; set => Set(ref height, value, Refresh); } public bool AutoAdjustDataWidth { get; set; } public bool StretchData { get; set; } public bool AllowMultipleElementsPerLine { get; set; } public bool UseCustomHeaders { get; set; } public int MinimumScroll => 0; private int scrollValue; public int ScrollValue { get => scrollValue; set => Set(ref scrollValue, value.LimitToRange(MinimumScroll, MaximumScroll), Refresh); } public int MaximumScroll => startOfNextSegment[startOfNextSegment.Count - 1] - 1; public ObservableCollection Headers { get; } = new ObservableCollection(); public ObservableCollection ColumnHeaders => null; public int DataOffset => 0; private StubCommand scrollCommand; public ICommand Scroll => StubCommand(ref scrollCommand, ExecuteScroll); private void ExecuteScroll(Direction direction) { if (direction == Direction.Up) ScrollValue -= 1; if (direction == Direction.Up) ScrollValue += 1; if (direction == Direction.PageUp) ScrollValue -= height; if (direction == Direction.PageDown) ScrollValue += height; } public double Progress => 0; public bool UpdateInProgress => false; public string SelectedAddress { get => string.Empty; set { } } public string SelectedBytes => string.Empty; public string AnchorText { get; set; } public bool AnchorTextVisible => false; public IDataModel Model => null; public bool HasTools => false; public ChangeHistory ChangeHistory { get; } public IToolTrayViewModel Tools => null; public byte[] FindBytes { get; set; } public string Name => left[0].Parent.Name.Trim('*') + " -> " + right[0].Parent.Name.Trim('*'); public bool IsMetadataOnlyChange => false; public ICommand Save { get; } = new StubCommand(); public ICommand SaveAs => null; public ICommand ExportBackup => null; public ICommand Undo => null; public ICommand Redo => null; public ICommand Copy => null; public ICommand DeepCopy => null; public ICommand Clear => null; public ICommand SelectAll => null; public ICommand Goto => null; public ICommand ResetAlignment => null; public ICommand Back => null; public ICommand Forward => null; private StubCommand close; public ICommand Close => StubCommand(ref close, () => Closed?.Invoke(this, EventArgs.Empty)); public ICommand Diff => null; public ICommand DiffLeft => null; public ICommand DiffRight => null; public bool CanDuplicate => false; public void Duplicate() { } public event EventHandler PreviewScrollChanged; public event EventHandler OnError; public event EventHandler OnMessage; public event EventHandler ClearMessage; public event EventHandler Closed; public event EventHandler RequestTabChange; public event EventHandler RequestCloseOtherViewports; public event EventHandler RequestDelayedWork; public event EventHandler RequestMenuClose; public event EventHandler RequestDiff; public event EventHandler RequestCanDiff; event EventHandler ITabContent.RequestCanCreatePatch { add { } remove { } } event EventHandler ITabContent.RequestCreatePatch { add { } remove { } } public event PropertyChangedEventHandler PropertyChanged; public event NotifyCollectionChangedEventHandler CollectionChanged; public void ConsiderReload(IFileSystem fileSystem) => throw new NotImplementedException(); public IChildViewPort CreateChildView(int startAddress, int endAddress) { throw new NotImplementedException(); } public void ExpandSelection(int x, int y) { if (x < leftWidth) { RequestTabChange(this, left[0].Parent); var (childIndex, childLine) = ConvertLine(y); var child = left[childIndex]; var targetAddress = child.DataOffset + child.Width * childLine; left[0].Parent.Goto.Execute(targetAddress + x); } else if (x > leftWidth + 1) { RequestTabChange(this, right[0].Parent); var (childIndex, childLine) = ConvertLine(y); var child = right[childIndex]; var targetAddress = child.DataOffset + child.Width * childLine; right[0].Parent.Goto.Execute(targetAddress + x - leftWidth - 1); } } public IReadOnlyList<(int start, int end)> Find(string search, bool matchExactCase = false) => Array.Empty<(int, int)>(); public bool CanFindFreeSpace => false; public void FindFreeSpace(IFileSystem fs) { } public void FindAllSources(int x, int y) { } public void FollowLink(int x, int y) { } public IReadOnlyList GetContextMenuItems(Point point, IFileSystem fileSystem) { var results = new List(); results.Add(new ContextItem("Copy Address", arg => { var fileSystem = (IFileSystem)arg; var (childIndex, childLine) = ConvertLine(point.Y); var address = left[childIndex].DataOffset + childLine * left[childIndex].Width + point.X; if (point.X > leftWidth) address = right[childIndex].DataOffset + childLine * right[childIndex].Width + point.X - leftWidth - 1; fileSystem.CopyText = address.ToAddress(); })); return results; } public bool IsSelected(Point point) { var (childIndex, childLine) = ConvertLine(point.Y); if (childIndex >= left.Count) return false; if (point.X < leftWidth) return left[childIndex].IsSelected(new Point(point.X, childLine)); return right[childIndex].IsSelected(new Point(point.X - leftWidth - 1, childLine)); } public bool IsTable(Point point) => false; private void Refresh(int unused) => FillCells(); public void Refresh() => FillCells(); public bool TryImport(LoadedFile file, IFileSystem fileSystem) => false; #endregion private void EqualizeScroll(IList left, IList right) { for (int i = 0; i < left.Count; i++) { var leftRun = left[i].Model.GetNextRun(left[i].DataOffset); var rightRun = right[i].Model.GetNextRun(right[i].DataOffset); var leftIsFormatted = leftRun.Start <= left[i].DataOffset && (leftRun is ITableRun || leftRun is IStreamRun); var rightIsFormatted = rightRun.Start <= right[i].DataOffset && (rightRun is ITableRun || rightRun is IStreamRun); if (!leftIsFormatted && rightIsFormatted) { EqualizeScroll(right[i], left[i]); } else { EqualizeScroll(left[i], right[i]); } } } private void EqualizeScroll(IChildViewPort example, IChildViewPort change) { while (change.DataOffset > example.DataOffset) change.Scroll.Execute(Direction.Left); while (change.DataOffset < example.DataOffset) change.Scroll.Execute(Direction.Right); change.AutoAdjustDataWidth = false; change.PreferredWidth = example.PreferredWidth; change.Width = example.Width; change.Height = example.Height; } private (int childIndex, int childLine) ConvertLine(int parentLine) { var scrollLine = parentLine + scrollValue; if (scrollLine < 0) return (0, scrollLine); int index = startOfNextSegment.BinarySearch(scrollLine); if (index >= 0) return (index, 0); index = ~index - 1; return (index, scrollLine - startOfNextSegment[index]); } private void FillCells() { Headers.Clear(); if (Width < 0 || Height < 0) return; cell = new HexElement[Width, Height]; var defaultCell = new HexElement(default, default, Undefined.Instance); var (childIndex, childLine) = ConvertLine(0); for (int y = 0; y < Height; y++) { var childIsValid = childIndex < left.Count; for (int x = 0; x < leftWidth; x++) { cell[x, y] = childIsValid ? left[childIndex][x, childLine] : defaultCell; } cell[leftWidth, y] = defaultCell; for (int x = 0; x < rightWidth; x++) { cell[leftWidth + 1 + x, y] = childIsValid ? right[childIndex][x, childLine] : defaultCell; } var hasHeader = childIsValid && left[childIndex].Headers.Count > childLine; Headers.Add(hasHeader ? left[childIndex].Headers[childLine] : string.Empty); childLine += 1; if (childIsValid && y + scrollValue == startOfNextSegment[childIndex + 1] - 1) { (childIndex, childLine) = (childIndex + 1, 0); } } CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } }