diff --git a/src/HexManiac.Core/Models/Runs/ArrayRun.cs b/src/HexManiac.Core/Models/Runs/ArrayRun.cs
index 2e2e0ec7..79ecf397 100644
--- a/src/HexManiac.Core/Models/Runs/ArrayRun.cs
+++ b/src/HexManiac.Core/Models/Runs/ArrayRun.cs
@@ -156,6 +156,53 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
}
}
+ public class ColumnHeader {
+ public string ColumnTitle { get; }
+ public int ByteWidth { get; }
+ public ColumnHeader(string title, int byteWidth = 1) => (ColumnTitle, ByteWidth) = (title, byteWidth);
+ }
+
+ ///
+ /// Represents a horizontal row of labels.
+ /// Each entry is meant to label a column.
+ ///
+ public class HeaderRow {
+ public IReadOnlyList ColumnHeaders { get; }
+
+ public HeaderRow(ArrayRun source, int byteStart, int length) {
+ var headers = new List();
+ // we know which 'byte' to start at, but we want to know what 'index' to start at
+ // basically, count off each element to figure out how big it is
+ int currentByte = 0;
+ int startIndex = 0;
+ while (currentByte < byteStart) {
+ currentByte += source.ElementContent[startIndex].Length;
+ startIndex++;
+ }
+ currentByte = 0;
+ for (int i = 0; currentByte < length; i++) {
+ var segment = source.ElementContent[(startIndex + i) % source.ElementContent.Count];
+ headers.Add(new ColumnHeader(segment.Name, segment.Length));
+ currentByte += segment.Length;
+ }
+ ColumnHeaders = headers;
+ }
+
+ public HeaderRow(int start, int length) {
+ var hex = "0123456789ABCDEF";
+ var headers = new ColumnHeader[length];
+ for (int i = 0; i < length; i++) headers[i] = new ColumnHeader(hex[(start + i) % 0x10].ToString());
+ ColumnHeaders = headers;
+ }
+
+ public static IReadOnlyList GetDefaultColumnHeaders(int columnCount) {
+ if (columnCount > 0x10 && columnCount % 0x10 != 0) return new List();
+ if (columnCount < 0x10 && 0x10 % columnCount != 0) return new List();
+ if (columnCount >= 0x10) return new[] { new HeaderRow(0, columnCount) };
+ return Enumerable.Range(0, 0x10 / columnCount).Select(i => new HeaderRow(columnCount * i, columnCount)).ToList();
+ }
+ }
+
public class ArrayRun : BaseRun {
public const char ExtendArray = '+';
public const char ArrayStart = '[';
@@ -386,6 +433,23 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
return new ArrayRun(owner, FormatString, Start, ElementCount, ElementContent, PointerSources, pointerSourcesForInnerElements);
}
+ ///
+ /// Arrays might want custom column headers.
+ /// If so, this method can get them.
+ ///
+ public IReadOnlyList GetColumnHeaders(int columnCount) {
+ // check if it's a multiple of the array width
+ if (columnCount >= ElementLength) {
+ if (columnCount % ElementLength != 0) return null;
+ return new[] { new HeaderRow(this, 0, columnCount) };
+ }
+
+ // check if it's a divisor of the array width
+ if (ElementLength % columnCount != 0) return null;
+ var segments = ElementLength / columnCount;
+ return Enumerable.Range(0, segments).Select(i => new HeaderRow(this, columnCount * i, columnCount)).ToList();
+ }
+
public void AppendTo(IDataModel data, StringBuilder text) {
for (int i = 0; i < ElementCount; i++) {
var offset = Start + i * ElementLength;
diff --git a/src/HexManiac.Core/ViewModels/IViewPort.cs b/src/HexManiac.Core/ViewModels/IViewPort.cs
index f2902e80..1a5ce3c1 100644
--- a/src/HexManiac.Core/ViewModels/IViewPort.cs
+++ b/src/HexManiac.Core/ViewModels/IViewPort.cs
@@ -1,4 +1,5 @@
using HavenSoft.HexManiac.Core.Models;
+using HavenSoft.HexManiac.Core.Models.Runs;
using HavenSoft.HexManiac.Core.ViewModels.Tools;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -16,6 +17,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
int ScrollValue { get; set; }
int MaximumScroll { get; }
ObservableCollection Headers { get; }
+ ObservableCollection ColumnHeaders { get; }
int DataOffset { get; }
ICommand Scroll { get; } // parameter: Direction to scroll
diff --git a/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs b/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs
index 796b3747..f479868a 100644
--- a/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs
+++ b/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs
@@ -1,4 +1,5 @@
using HavenSoft.HexManiac.Core.Models;
+using HavenSoft.HexManiac.Core.Models.Runs;
using HavenSoft.HexManiac.Core.ViewModels.Tools;
using System;
using System.Collections.Generic;
@@ -78,6 +79,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
public bool AnchorTextVisible => false;
+ public ObservableCollection ColumnHeaders { get; }
+
#pragma warning disable 0067 // it's ok if events are never used
public event EventHandler OnError;
public event EventHandler OnMessage;
diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs
index 5bdfe41d..5f6ba5d3 100644
--- a/src/HexManiac.Core/ViewModels/ViewPort.cs
+++ b/src/HexManiac.Core/ViewModels/ViewPort.cs
@@ -70,6 +70,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
public int MaximumScroll => scroll.MaximumScroll;
public ObservableCollection Headers => scroll.Headers;
+ public ObservableCollection ColumnHeaders { get; }
public int DataOffset => scroll.DataIndex;
public ICommand Scroll => scroll.Scroll;
@@ -290,6 +291,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
Model = model;
FileName = fileName;
+ ColumnHeaders = new ObservableCollection();
scroll = new ScrollRegion(model.TryGetUsefulHeader) { DataLength = Model.Count };
scroll.PropertyChanged += ScrollPropertyChanged;
@@ -1225,9 +1227,25 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
}
}
+ if (ColumnHeaders.Count == 0 || ColumnHeaders[0].ColumnHeaders.Count != Width) UpdateColumnHeaders();
+
NotifyCollectionChanged(ResetArgs);
}
+ private void UpdateColumnHeaders() {
+ var index = scroll.ViewPointToDataIndex(new Point(0, 0));
+ var run = Model.GetNextRun(index) as ArrayRun;
+ if (run != null && run.Start > index) run = null; // only use the run if it starts _before_ the screen
+ var headers = run?.GetColumnHeaders(Width) ?? HeaderRow.GetDefaultColumnHeaders(Width);
+
+ for (int i = 0; i < headers.Count; i++) {
+ if (i < ColumnHeaders.Count) ColumnHeaders[i] = headers[i];
+ else ColumnHeaders.Add(headers[i]);
+ }
+
+ while (ColumnHeaders.Count > headers.Count) ColumnHeaders.RemoveAt(ColumnHeaders.Count - 1);
+ }
+
private IDataFormat WrapFormat(IFormattedRun run, IDataFormat format, int dataIndex) {
if (run.PointerSources != null && run.Start == dataIndex) {
var name = Model.GetAnchorFromAddress(-1, run.Start);
diff --git a/src/HexManiac.WPF/Controls/MultiplyConverter.cs b/src/HexManiac.WPF/Controls/MultiplyConverter.cs
new file mode 100644
index 00000000..0381bc08
--- /dev/null
+++ b/src/HexManiac.WPF/Controls/MultiplyConverter.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace HavenSoft.HexManiac.WPF.Controls {
+ public class MultiplyConverter : IValueConverter {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
+ if (value is int number && parameter is double param) return number * param;
+ return 0.0;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/HexManiac.WPF/HexManiac.WPF.csproj b/src/HexManiac.WPF/HexManiac.WPF.csproj
index 9fbc118f..4d941d16 100644
--- a/src/HexManiac.WPF/HexManiac.WPF.csproj
+++ b/src/HexManiac.WPF/HexManiac.WPF.csproj
@@ -72,6 +72,7 @@
+
StartScreen.xaml
diff --git a/src/HexManiac.WPF/Windows/MainWindow.xaml b/src/HexManiac.WPF/Windows/MainWindow.xaml
index 3cd7f9b1..6b41e097 100644
--- a/src/HexManiac.WPF/Windows/MainWindow.xaml
+++ b/src/HexManiac.WPF/Windows/MainWindow.xaml
@@ -13,6 +13,7 @@
+
@@ -257,7 +258,7 @@
-
+
@@ -277,7 +278,38 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+