Condensed array display + per-scope column widths + MIT license

- Array element structs render without { } braces (condensed display)
- [N] separators show element indices within arrays
- Per-scope column width calculation (nested elements use tighter spacing)
- Array headers show struct[N] for struct arrays
- [N] separators are not interactive (no hover/click highlight)
- Dynamic type column width (min 8, max 14)
- PE32+ sample data with full headers, DataDirectory[16], SectionHeaders[4]
- Added MIT license

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
IChooChoose
2026-02-05 06:26:00 -07:00
committed by sysadmin
parent 04252a3c96
commit 4d35db224e
11 changed files with 727 additions and 430 deletions

View File

@@ -235,16 +235,19 @@ struct Node {
QString name;
uint64_t parentId = 0; // 0 = root (no parent)
int offset = 0;
int arrayLen = 0;
int arrayLen = 1; // Array: element count
int strLen = 64;
bool collapsed = false;
uint64_t refId = 0; // Pointer32/64: id of Struct to expand at *ptr
NodeKind elementKind = NodeKind::UInt8; // Array: element type
int viewIndex = 0; // Array: current view offset (transient)
int byteSize() const {
switch (kind) {
case NodeKind::UTF8: return strLen;
case NodeKind::UTF16: return strLen * 2;
case NodeKind::Padding: return qMax(1, arrayLen);
case NodeKind::Array: return arrayLen * sizeForKind(elementKind);
default: return sizeForKind(kind);
}
}
@@ -260,6 +263,7 @@ struct Node {
o["strLen"] = strLen;
o["collapsed"] = collapsed;
o["refId"] = QString::number(refId);
o["elementKind"] = kindToString(elementKind);
return o;
}
static Node fromJson(const QJsonObject& o) {
@@ -269,12 +273,19 @@ struct Node {
n.name = o["name"].toString();
n.parentId = o["parentId"].toString("0").toULongLong();
n.offset = o["offset"].toInt(0);
n.arrayLen = o["arrayLen"].toInt(0);
n.arrayLen = o["arrayLen"].toInt(1);
n.strLen = o["strLen"].toInt(64);
n.collapsed = o["collapsed"].toBool(false);
n.refId = o["refId"].toString("0").toULongLong();
n.elementKind = kindFromString(o["elementKind"].toString("UInt8"));
return n;
}
// Helper: is this a string-like array (char[] or wchar_t[])?
bool isStringArray() const {
return kind == NodeKind::Array &&
(elementKind == NodeKind::UInt8 || elementKind == NodeKind::UInt16);
}
};
// ── NodeTree ──
@@ -415,7 +426,7 @@ struct NodeTree {
// ── LineMeta ──
enum class LineKind : uint8_t {
Header, Field, Continuation, Footer
Header, Field, Continuation, Footer, ArrayElementSeparator
};
struct LineMeta {
@@ -428,15 +439,23 @@ struct LineMeta {
bool foldCollapsed = false;
bool isContinuation = false;
bool isRootHeader = false; // true for top-level struct headers (base address editable)
bool isArrayHeader = false; // true for array headers (has <idx/count> nav)
LineKind lineKind = LineKind::Field;
NodeKind nodeKind = NodeKind::Int32;
NodeKind elementKind = NodeKind::UInt8; // Array element type
int arrayViewIdx = 0; // Array: current view index
int arrayCount = 0; // Array: total element count
int arrayElementIdx = -1; // Index of this element within parent array (-1 if not array element)
QString offsetText;
uint32_t markerMask = 0;
int effectiveTypeW = 14; // Per-line type column width used for rendering
int effectiveNameW = 22; // Per-line name column width used for rendering
};
// ── Layout Info ──
struct LayoutInfo {
int typeW = 14; // Effective type column width (default = kColType)
int nameW = 22; // Effective name column width (default = kColName)
};
@@ -476,30 +495,32 @@ struct ColumnSpan {
bool valid = false;
};
enum class EditTarget { Name, Type, Value, BaseAddress };
enum class EditTarget { Name, Type, Value, BaseAddress, ArrayIndex, ArrayCount };
// Column layout constants (shared with format.cpp span computation)
inline constexpr int kFoldCol = 3; // 3-char fold indicator prefix per line
inline constexpr int kColType = 10;
inline constexpr int kColType = 14; // Max type column width (fits "uint64_t[999]")
inline constexpr int kColName = 22;
inline constexpr int kColValue = 32;
inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits
inline constexpr int kColBaseAddr = 12; // "0x" + up to 10 hex digits (40-bit address)
inline constexpr int kSepWidth = 2;
inline constexpr int kMinTypeW = 8; // Minimum type column width (fits "uint64_t")
inline constexpr int kMaxTypeW = 14; // Maximum type column width (fits "uint64_t[999]")
inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview)
inline constexpr int kMaxNameW = 22; // Maximum name column width (= kColName)
inline ColumnSpan typeSpanFor(const LineMeta& lm) {
inline ColumnSpan typeSpanFor(const LineMeta& lm, int typeW = kColType) {
if (lm.lineKind != LineKind::Field || lm.isContinuation) return {};
int ind = kFoldCol + lm.depth * 3;
return {ind, ind + kColType, true};
return {ind, ind + typeW, true};
}
inline ColumnSpan nameSpanFor(const LineMeta& lm, int nameW = kColName) {
inline ColumnSpan nameSpanFor(const LineMeta& lm, int typeW = kColType, int nameW = kColName) {
if (lm.isContinuation || lm.lineKind != LineKind::Field) return {};
int ind = kFoldCol + lm.depth * 3;
int start = ind + kColType + kSepWidth;
int start = ind + typeW + kSepWidth;
// Hex/Padding: ASCII preview takes the name column position (8 chars)
if (isHexPreview(lm.nodeKind))
@@ -508,8 +529,9 @@ inline ColumnSpan nameSpanFor(const LineMeta& lm, int nameW = kColName) {
return {start, start + nameW, true};
}
inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int nameW = kColName) {
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer) return {};
inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int typeW = kColType, int nameW = kColName) {
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer ||
lm.lineKind == LineKind::ArrayElementSeparator) return {};
int ind = kFoldCol + lm.depth * 3;
// Hex/Padding layout: [Type][sep][ASCII(8)][sep][hex bytes(23)]
@@ -518,20 +540,20 @@ inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int nameW
if (lm.isContinuation) {
int prefixW = isHexPad
? (kColType + kSepWidth + 8 + kSepWidth)
: (kColType + nameW + 4);
? (typeW + kSepWidth + 8 + kSepWidth)
: (typeW + nameW + 4);
int start = ind + prefixW;
return {start, start + valWidth, true};
}
if (lm.lineKind != LineKind::Field) return {};
int start = isHexPad
? (ind + kColType + kSepWidth + 8 + kSepWidth)
: (ind + kColType + kSepWidth + nameW + kSepWidth);
? (ind + typeW + kSepWidth + 8 + kSepWidth)
: (ind + typeW + kSepWidth + nameW + kSepWidth);
return {start, start + valWidth, true};
}
inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int nameW = kColName) {
inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW = kColType, int nameW = kColName) {
if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer) return {};
int ind = kFoldCol + lm.depth * 3;
@@ -541,13 +563,13 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int nameW =
int start;
if (lm.isContinuation) {
int prefixW = isHexPad
? (kColType + kSepWidth + 8 + kSepWidth)
: (kColType + nameW + 4);
? (typeW + kSepWidth + 8 + kSepWidth)
: (typeW + nameW + 4);
start = ind + prefixW + valWidth;
} else {
start = isHexPad
? (ind + kColType + kSepWidth + 8 + kSepWidth + valWidth)
: (ind + kColType + kSepWidth + nameW + kSepWidth + valWidth);
? (ind + typeW + kSepWidth + 8 + kSepWidth + valWidth)
: (ind + typeW + kSepWidth + nameW + kSepWidth + valWidth);
}
return {start, lineLength, start < lineLength};
}
@@ -579,6 +601,39 @@ inline ColumnSpan baseAddressFullSpanFor(const LineMeta& lm, const QString& line
return {baseIdx, endPos, true};
}
// ── Array navigation spans ──
// Line format: "uint32_t[16] name { <0/16>"
inline ColumnSpan arrayPrevSpanFor(const LineMeta& lm, const QString& lineText) {
if (!lm.isArrayHeader) return {};
int lt = lineText.lastIndexOf('<');
if (lt < 0) return {};
return {lt, lt + 1, true};
}
inline ColumnSpan arrayIndexSpanFor(const LineMeta& lm, const QString& lineText) {
if (!lm.isArrayHeader) return {};
int lt = lineText.lastIndexOf('<');
int slash = lineText.indexOf('/', lt);
if (lt < 0 || slash < 0) return {};
return {lt + 1, slash, true};
}
inline ColumnSpan arrayCountSpanFor(const LineMeta& lm, const QString& lineText) {
if (!lm.isArrayHeader) return {};
int slash = lineText.lastIndexOf('/');
int gt = lineText.indexOf('>', slash);
if (slash < 0 || gt < 0) return {};
return {slash + 1, gt, true};
}
inline ColumnSpan arrayNextSpanFor(const LineMeta& lm, const QString& lineText) {
if (!lm.isArrayHeader) return {};
int gt = lineText.lastIndexOf('>');
if (gt < 0) return {};
return {gt, gt + 1, true};
}
// ── ViewState ──
struct ViewState {
@@ -592,7 +647,8 @@ struct ViewState {
namespace fmt {
using TypeNameFn = QString (*)(NodeKind);
void setTypeNameProvider(TypeNameFn fn);
QString typeName(NodeKind kind);
QString typeName(NodeKind kind, int colType = kColType);
QString typeNameRaw(NodeKind kind); // Unpadded type name for width calculation
QString fmtInt8(int8_t v);
QString fmtInt16(int16_t v);
QString fmtInt32(int32_t v);
@@ -608,11 +664,13 @@ namespace fmt {
QString fmtPointer64(uint64_t v);
QString fmtNodeLine(const Node& node, const Provider& prov,
uint64_t addr, int depth, int subLine = 0,
const QString& comment = {}, int colName = kColName);
const QString& comment = {}, int colType = kColType, int colName = kColName);
QString fmtOffsetMargin(int64_t relativeOffset, bool isContinuation);
QString fmtStructHeader(const Node& node, int depth);
QString fmtStructHeaderWithBase(const Node& node, int depth, uint64_t baseAddress);
QString fmtStructFooter(const Node& node, int depth, int totalSize = -1);
QString fmtArrayHeader(const Node& node, int depth, int viewIdx);
QString arrayTypeName(NodeKind elemKind, int count);
QString validateBaseAddress(const QString& text);
QString indent(int depth);
QString readValue(const Node& node, const Provider& prov,