- Add ListPreviewWidget for displaying parsed list data with table view - Add TextViewerWidget for text file preview with syntax highlighting - Add TreeBuilder class to organize parsed data into tree structure - Enhance HexView with selection support, copy functionality, keyboard navigation - Enhance ImagePreviewWidget with additional format support and metadata display - Minor audio preview widget adjustments Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
606 lines
18 KiB
C++
606 lines
18 KiB
C++
#include "hexviewerwidget.h"
|
|
#include <QHeaderView>
|
|
#include <QFontDatabase>
|
|
#include <QScrollBar>
|
|
#include <QPainter>
|
|
#include <QResizeEvent>
|
|
#include <QMouseEvent>
|
|
#include <QKeyEvent>
|
|
#include <QClipboard>
|
|
#include <QApplication>
|
|
|
|
// ============================================================================
|
|
// HexView - Virtualized hex viewer with direct painting
|
|
// ============================================================================
|
|
|
|
HexView::HexView(QWidget *parent)
|
|
: QAbstractScrollArea(parent)
|
|
{
|
|
// Set up monospace font
|
|
mMonoFont = QFont("Consolas", 10);
|
|
if (!QFontDatabase::hasFamily("Consolas")) {
|
|
mMonoFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
|
mMonoFont.setPointSize(10);
|
|
}
|
|
|
|
QFontMetrics fm(mMonoFont);
|
|
mCharWidth = fm.horizontalAdvance('0');
|
|
mLineHeight = fm.height();
|
|
|
|
// Default dark theme colors
|
|
mBgColor = QColor("#1e1e1e");
|
|
mTextColor = QColor("#888888");
|
|
mOffsetColor = QColor("#ad0c0c");
|
|
mNullColor = QColor("#505050");
|
|
mHighColor = QColor("#ad0c0c");
|
|
mPrintableColor = QColor("#c0c0c0");
|
|
mControlColor = QColor("#707070");
|
|
mNonPrintableColor = QColor("#909090");
|
|
mBorderColor = QColor("#333333");
|
|
mSelectionColor = QColor("#264f78"); // Blue selection highlight
|
|
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
viewport()->setAutoFillBackground(false);
|
|
viewport()->setMouseTracking(true);
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
}
|
|
|
|
void HexView::setData(const QByteArray &data)
|
|
{
|
|
mData = data;
|
|
recalculateBytesPerLine();
|
|
updateScrollBars();
|
|
viewport()->update();
|
|
}
|
|
|
|
void HexView::setTheme(const Theme &theme)
|
|
{
|
|
mBgColor = QColor(theme.backgroundColor);
|
|
mTextColor = QColor(theme.textColorMuted);
|
|
mOffsetColor = QColor(theme.accentColor);
|
|
mHighColor = QColor(theme.accentColor);
|
|
mBorderColor = QColor(theme.borderColor);
|
|
|
|
// Derived colors based on theme brightness
|
|
int brightness = mBgColor.lightness();
|
|
if (brightness < 128) {
|
|
// Dark theme
|
|
mNullColor = QColor("#505050");
|
|
mPrintableColor = QColor("#c0c0c0");
|
|
mControlColor = QColor("#707070");
|
|
mNonPrintableColor = QColor("#909090");
|
|
} else {
|
|
// Light theme
|
|
mNullColor = QColor("#a0a0a0");
|
|
mPrintableColor = QColor("#303030");
|
|
mControlColor = QColor("#808080");
|
|
mNonPrintableColor = QColor("#606060");
|
|
}
|
|
|
|
viewport()->update();
|
|
}
|
|
|
|
void HexView::setBytesPerLine(int bytes)
|
|
{
|
|
if (bytes != mBytesPerLine && bytes >= 8) {
|
|
mBytesPerLine = bytes;
|
|
updateScrollBars();
|
|
viewport()->update();
|
|
}
|
|
}
|
|
|
|
void HexView::updateScrollBars()
|
|
{
|
|
if (mData.isEmpty()) {
|
|
verticalScrollBar()->setRange(0, 0);
|
|
horizontalScrollBar()->setRange(0, 0);
|
|
return;
|
|
}
|
|
|
|
int totalLines = (mData.size() + mBytesPerLine - 1) / mBytesPerLine;
|
|
// Account for header (1 line) + gap (4px ~= 0.25 lines)
|
|
int headerHeight = mLineHeight + 4;
|
|
int availableHeight = viewport()->height() - headerHeight;
|
|
int visibleLines = qMax(1, availableHeight / mLineHeight);
|
|
|
|
verticalScrollBar()->setRange(0, qMax(0, totalLines - visibleLines));
|
|
verticalScrollBar()->setPageStep(visibleLines);
|
|
verticalScrollBar()->setSingleStep(1);
|
|
|
|
// Horizontal: offset(10) + hex(3*n) + separator(2) + ascii(n)
|
|
int contentWidth = 10 * mCharWidth + mBytesPerLine * 3 * mCharWidth + 2 * mCharWidth + mBytesPerLine * mCharWidth;
|
|
int viewportWidth = viewport()->width();
|
|
|
|
horizontalScrollBar()->setRange(0, qMax(0, contentWidth - viewportWidth));
|
|
horizontalScrollBar()->setPageStep(viewportWidth);
|
|
}
|
|
|
|
void HexView::recalculateBytesPerLine()
|
|
{
|
|
if (mData.isEmpty()) return;
|
|
|
|
int viewportWidth = viewport()->width();
|
|
if (viewportWidth < 100) return;
|
|
|
|
// Calculate available space for hex bytes
|
|
// Format: XXXXXXXX HH HH HH HH HH HH ... | AAAA...
|
|
// Offset = 10 chars (8 hex + 2 spaces)
|
|
// Each byte = 3 chars hex + 1 char ascii
|
|
// Separator = 2 chars
|
|
|
|
int fixedWidth = 10 * mCharWidth + 2 * mCharWidth; // offset + separator
|
|
int remaining = viewportWidth - fixedWidth;
|
|
|
|
// Each byte needs: 3 chars hex + 1 char ascii = 4 chars
|
|
int bytesPerLine = remaining / (4 * mCharWidth);
|
|
|
|
// Round down to multiple of 8 for clean display
|
|
bytesPerLine = qMax(8, (bytesPerLine / 8) * 8);
|
|
bytesPerLine = qMin(32, bytesPerLine);
|
|
|
|
if (bytesPerLine != mBytesPerLine) {
|
|
mBytesPerLine = bytesPerLine;
|
|
updateScrollBars();
|
|
}
|
|
}
|
|
|
|
void HexView::paintEvent(QPaintEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
|
|
QPainter painter(viewport());
|
|
painter.setFont(mMonoFont);
|
|
painter.setRenderHint(QPainter::TextAntialiasing);
|
|
|
|
// Fill background
|
|
painter.fillRect(viewport()->rect(), mBgColor);
|
|
|
|
if (mData.isEmpty()) {
|
|
painter.setPen(mTextColor);
|
|
painter.drawText(viewport()->rect(), Qt::AlignCenter, "No data");
|
|
return;
|
|
}
|
|
|
|
int xOffset = -horizontalScrollBar()->value();
|
|
int firstLine = verticalScrollBar()->value();
|
|
int visibleLines = viewport()->height() / mLineHeight + 2;
|
|
int totalLines = (mData.size() + mBytesPerLine - 1) / mBytesPerLine;
|
|
|
|
// Calculate column positions (no extra gaps - uniform spacing)
|
|
int offsetX = xOffset;
|
|
int hexX = offsetX + 10 * mCharWidth;
|
|
int asciiX = hexX + mBytesPerLine * 3 * mCharWidth + 2 * mCharWidth;
|
|
|
|
// Draw header line
|
|
int y = mLineHeight;
|
|
painter.setPen(mOffsetColor);
|
|
painter.drawText(offsetX, y - 4, "Offset");
|
|
|
|
// Draw hex column headers (uniform spacing)
|
|
painter.setPen(mTextColor);
|
|
for (int i = 0; i < mBytesPerLine; i++) {
|
|
int x = hexX + i * 3 * mCharWidth;
|
|
painter.drawText(x, y - 4, QString("%1").arg(i, 2, 16, QChar('0')).toUpper());
|
|
}
|
|
|
|
// Draw "Decoded" header
|
|
painter.setPen(mOffsetColor);
|
|
painter.drawText(asciiX, y - 4, "Decoded");
|
|
|
|
// Draw separator line
|
|
painter.setPen(mBorderColor);
|
|
painter.drawLine(0, y, viewport()->width(), y);
|
|
|
|
y += 4; // Small gap after header
|
|
|
|
// Get selection range (normalized so start <= end)
|
|
int selStart = qMin(mSelectionStart, mSelectionEnd);
|
|
int selEnd = qMax(mSelectionStart, mSelectionEnd);
|
|
bool hasSelection = mSelectionStart >= 0 && mSelectionEnd >= 0;
|
|
|
|
// Draw data rows (only visible ones)
|
|
for (int line = 0; line < visibleLines && (firstLine + line) < totalLines; line++) {
|
|
int dataLine = firstLine + line;
|
|
int offset = dataLine * mBytesPerLine;
|
|
int bytesInLine = qMin(mBytesPerLine, mData.size() - offset);
|
|
|
|
y += mLineHeight;
|
|
|
|
// Draw offset
|
|
painter.setPen(mOffsetColor);
|
|
QString offsetStr = QString("%1").arg(offset, 8, 16, QChar('0')).toUpper();
|
|
painter.drawText(offsetX, y, offsetStr);
|
|
|
|
// Draw hex bytes (uniform spacing)
|
|
for (int i = 0; i < mBytesPerLine; i++) {
|
|
int x = hexX + i * 3 * mCharWidth;
|
|
|
|
if (i < bytesInLine) {
|
|
int byteIndex = offset + i;
|
|
quint8 byte = static_cast<quint8>(mData[byteIndex]);
|
|
|
|
// Draw selection background for hex
|
|
if (hasSelection && byteIndex >= selStart && byteIndex <= selEnd) {
|
|
QRect selRect(x - 1, y - mLineHeight + 4, 2 * mCharWidth + 2, mLineHeight);
|
|
painter.fillRect(selRect, mSelectionColor);
|
|
}
|
|
|
|
painter.setPen(getByteColor(byte));
|
|
QString byteStr = QString("%1").arg(byte, 2, 16, QChar('0')).toUpper();
|
|
painter.drawText(x, y, byteStr);
|
|
}
|
|
}
|
|
|
|
// Draw vertical separator
|
|
int sepX = asciiX - mCharWidth;
|
|
painter.setPen(mBorderColor);
|
|
painter.drawLine(sepX, y - mLineHeight + 4, sepX, y + 2);
|
|
|
|
// Draw ASCII/Decoded - show extended ASCII like HxD
|
|
for (int i = 0; i < mBytesPerLine; i++) {
|
|
int x = asciiX + i * mCharWidth;
|
|
|
|
if (i < bytesInLine) {
|
|
int byteIndex = offset + i;
|
|
quint8 byte = static_cast<quint8>(mData[byteIndex]);
|
|
|
|
// Draw selection background for decoded
|
|
if (hasSelection && byteIndex >= selStart && byteIndex <= selEnd) {
|
|
QRect selRect(x, y - mLineHeight + 4, mCharWidth, mLineHeight);
|
|
painter.fillRect(selRect, mSelectionColor);
|
|
}
|
|
|
|
painter.setPen(getAsciiColor(byte));
|
|
// Show actual character for printable ASCII and extended ASCII (Latin-1)
|
|
// Control chars (0x00-0x1F) and DEL (0x7F) show as '.'
|
|
QChar c;
|
|
if (byte < 0x20 || byte == 0x7F) {
|
|
c = '.';
|
|
} else {
|
|
c = QChar::fromLatin1(static_cast<char>(byte));
|
|
}
|
|
painter.drawText(x, y, QString(c));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HexView::resizeEvent(QResizeEvent *event)
|
|
{
|
|
QAbstractScrollArea::resizeEvent(event);
|
|
recalculateBytesPerLine();
|
|
updateScrollBars();
|
|
}
|
|
|
|
void HexView::scrollContentsBy(int dx, int dy)
|
|
{
|
|
Q_UNUSED(dx);
|
|
Q_UNUSED(dy);
|
|
viewport()->update();
|
|
}
|
|
|
|
QColor HexView::getByteColor(quint8 byte) const
|
|
{
|
|
if (byte == 0x00) {
|
|
return mNullColor;
|
|
} else if (byte == 0xFF) {
|
|
return mHighColor;
|
|
} else if (byte >= 0x20 && byte < 0x7F) {
|
|
return mPrintableColor;
|
|
} else if (byte < 0x20) {
|
|
return mControlColor;
|
|
} else {
|
|
return mNonPrintableColor;
|
|
}
|
|
}
|
|
|
|
QColor HexView::getAsciiColor(quint8 byte) const
|
|
{
|
|
if (byte < 0x20 || byte == 0x7F) {
|
|
return mNullColor; // Control characters shown as '.'
|
|
} else if (byte >= 0x20 && byte < 0x7F) {
|
|
return mPrintableColor; // Standard ASCII
|
|
} else {
|
|
return mNonPrintableColor; // Extended ASCII (0x80-0xFF)
|
|
}
|
|
}
|
|
|
|
void HexView::updateColumnPositions()
|
|
{
|
|
mOffsetX = -horizontalScrollBar()->value();
|
|
mHexX = mOffsetX + 10 * mCharWidth;
|
|
mAsciiX = mHexX + mBytesPerLine * 3 * mCharWidth + 2 * mCharWidth;
|
|
mHeaderHeight = mLineHeight + 4;
|
|
}
|
|
|
|
int HexView::byteIndexAtPos(const QPoint &pos, SelectionSource *source) const
|
|
{
|
|
// Check if in header area
|
|
if (pos.y() < mHeaderHeight) {
|
|
return -1;
|
|
}
|
|
|
|
// Calculate which line
|
|
int lineY = pos.y() - mHeaderHeight;
|
|
int line = lineY / mLineHeight + verticalScrollBar()->value();
|
|
int col = -1;
|
|
|
|
// Check if in hex area
|
|
int hexEndX = mHexX + mBytesPerLine * 3 * mCharWidth;
|
|
if (pos.x() >= mHexX && pos.x() < hexEndX) {
|
|
int relX = pos.x() - mHexX;
|
|
col = relX / (3 * mCharWidth);
|
|
if (source) *source = SelectionSource::Hex;
|
|
}
|
|
// Check if in decoded/ASCII area
|
|
else if (pos.x() >= mAsciiX) {
|
|
int relX = pos.x() - mAsciiX;
|
|
col = relX / mCharWidth;
|
|
if (source) *source = SelectionSource::Decoded;
|
|
}
|
|
|
|
if (col < 0 || col >= mBytesPerLine) {
|
|
return -1;
|
|
}
|
|
|
|
int byteIndex = line * mBytesPerLine + col;
|
|
if (byteIndex < 0 || byteIndex >= mData.size()) {
|
|
return -1;
|
|
}
|
|
|
|
return byteIndex;
|
|
}
|
|
|
|
void HexView::clearSelection()
|
|
{
|
|
mSelectionStart = -1;
|
|
mSelectionEnd = -1;
|
|
mSelectionSource = SelectionSource::None;
|
|
viewport()->update();
|
|
}
|
|
|
|
bool HexView::hasSelection() const
|
|
{
|
|
return mSelectionStart >= 0 && mSelectionEnd >= 0 && mSelectionStart != mSelectionEnd;
|
|
}
|
|
|
|
QString HexView::getSelectedHex() const
|
|
{
|
|
if (!hasSelection()) return QString();
|
|
|
|
int start = qMin(mSelectionStart, mSelectionEnd);
|
|
int end = qMax(mSelectionStart, mSelectionEnd);
|
|
|
|
QString result;
|
|
for (int i = start; i <= end && i < mData.size(); i++) {
|
|
result += QString("%1").arg(static_cast<quint8>(mData[i]), 2, 16, QChar('0')).toUpper();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QString HexView::getSelectedDecoded() const
|
|
{
|
|
if (!hasSelection()) return QString();
|
|
|
|
int start = qMin(mSelectionStart, mSelectionEnd);
|
|
int end = qMax(mSelectionStart, mSelectionEnd);
|
|
|
|
QString result;
|
|
for (int i = start; i <= end && i < mData.size(); i++) {
|
|
quint8 byte = static_cast<quint8>(mData[i]);
|
|
if (byte < 0x20 || byte == 0x7F) {
|
|
result += '.';
|
|
} else {
|
|
result += QChar::fromLatin1(static_cast<char>(byte));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void HexView::copyToClipboard()
|
|
{
|
|
if (!hasSelection()) return;
|
|
|
|
QString text;
|
|
if (mSelectionSource == SelectionSource::Hex) {
|
|
text = getSelectedHex();
|
|
} else {
|
|
text = getSelectedDecoded();
|
|
}
|
|
|
|
QApplication::clipboard()->setText(text);
|
|
}
|
|
|
|
void HexView::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
if (event->button() == Qt::LeftButton) {
|
|
updateColumnPositions();
|
|
SelectionSource source = SelectionSource::None;
|
|
int idx = byteIndexAtPos(event->pos(), &source);
|
|
|
|
if (idx >= 0) {
|
|
mSelectionStart = idx;
|
|
mSelectionEnd = idx;
|
|
mSelectionSource = source;
|
|
mSelecting = true;
|
|
setFocus();
|
|
viewport()->update();
|
|
} else {
|
|
clearSelection();
|
|
}
|
|
}
|
|
QAbstractScrollArea::mousePressEvent(event);
|
|
}
|
|
|
|
void HexView::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
if (mSelecting && (event->buttons() & Qt::LeftButton)) {
|
|
updateColumnPositions();
|
|
int idx = byteIndexAtPos(event->pos());
|
|
|
|
if (idx >= 0) {
|
|
mSelectionEnd = idx;
|
|
viewport()->update();
|
|
}
|
|
}
|
|
QAbstractScrollArea::mouseMoveEvent(event);
|
|
}
|
|
|
|
void HexView::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
if (event->button() == Qt::LeftButton) {
|
|
mSelecting = false;
|
|
}
|
|
QAbstractScrollArea::mouseReleaseEvent(event);
|
|
}
|
|
|
|
void HexView::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
if (event->matches(QKeySequence::Copy)) {
|
|
copyToClipboard();
|
|
event->accept();
|
|
return;
|
|
}
|
|
// Ctrl+A to select all
|
|
if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_A) {
|
|
if (!mData.isEmpty()) {
|
|
mSelectionStart = 0;
|
|
mSelectionEnd = mData.size() - 1;
|
|
mSelectionSource = SelectionSource::Hex;
|
|
viewport()->update();
|
|
}
|
|
event->accept();
|
|
return;
|
|
}
|
|
QAbstractScrollArea::keyPressEvent(event);
|
|
}
|
|
|
|
// ============================================================================
|
|
// HexViewerWidget
|
|
// ============================================================================
|
|
|
|
HexViewerWidget::HexViewerWidget(QWidget *parent)
|
|
: QWidget(parent)
|
|
{
|
|
auto *mainLayout = new QVBoxLayout(this);
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
|
mainLayout->setSpacing(0);
|
|
|
|
// Splitter for hex view and metadata
|
|
mSplitter = new QSplitter(Qt::Horizontal, this);
|
|
|
|
// Left side - hex view container
|
|
auto *hexContainer = new QWidget(mSplitter);
|
|
auto *hexLayout = new QVBoxLayout(hexContainer);
|
|
hexLayout->setContentsMargins(0, 0, 0, 0);
|
|
hexLayout->setSpacing(0);
|
|
|
|
// Info label
|
|
mInfoLabel = new QLabel(hexContainer);
|
|
hexLayout->addWidget(mInfoLabel);
|
|
|
|
// Hex view
|
|
mHexView = new HexView(hexContainer);
|
|
hexLayout->addWidget(mHexView, 1);
|
|
|
|
mSplitter->addWidget(hexContainer);
|
|
|
|
// Metadata tree
|
|
mMetadataTree = new QTreeWidget(mSplitter);
|
|
mMetadataTree->setHeaderLabels({"Property", "Value"});
|
|
mMetadataTree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
|
mMetadataTree->header()->setSectionResizeMode(1, QHeaderView::Stretch);
|
|
mMetadataTree->setAlternatingRowColors(true);
|
|
mSplitter->addWidget(mMetadataTree);
|
|
|
|
mSplitter->setSizes({700, 250});
|
|
mainLayout->addWidget(mSplitter);
|
|
|
|
// Connect to theme changes
|
|
connect(&Settings::instance(), &Settings::themeChanged, this, &HexViewerWidget::applyTheme);
|
|
|
|
// Apply current theme
|
|
applyTheme(Settings::instance().theme());
|
|
}
|
|
|
|
void HexViewerWidget::applyTheme(const Theme &theme)
|
|
{
|
|
mCurrentTheme = theme;
|
|
|
|
// Update info label
|
|
mInfoLabel->setStyleSheet(QString(
|
|
"QLabel { background-color: %1; color: %2; padding: 4px 8px; font-size: 11px; }"
|
|
).arg(theme.panelColor, theme.textColorMuted));
|
|
|
|
// Update hex view
|
|
mHexView->setTheme(theme);
|
|
|
|
// Update metadata tree
|
|
mMetadataTree->setStyleSheet(QString(
|
|
"QTreeWidget { background-color: %1; color: %2; border: none; }"
|
|
"QTreeWidget::item:selected { background-color: %3; color: white; }"
|
|
"QTreeWidget::item:alternate { background-color: %4; }"
|
|
"QHeaderView::section { background-color: %4; color: %5; padding: 4px; border: none; }"
|
|
).arg(theme.backgroundColor, theme.textColor, theme.accentColor, theme.panelColor, theme.textColorMuted));
|
|
}
|
|
|
|
void HexViewerWidget::setData(const QByteArray &data, const QString &filename)
|
|
{
|
|
mData = data;
|
|
mFilename = filename;
|
|
|
|
// Update info label
|
|
QString info = QString("%1 | %2 bytes").arg(filename).arg(data.size());
|
|
if (data.size() >= 4) {
|
|
QString magic;
|
|
for (int i = 0; i < qMin(4, data.size()); i++) {
|
|
char c = data[i];
|
|
magic += (c >= 32 && c < 127) ? c : '.';
|
|
}
|
|
info += QString(" | Magic: %1").arg(magic);
|
|
}
|
|
mInfoLabel->setText(info);
|
|
|
|
// Update hex view
|
|
mHexView->setData(data);
|
|
|
|
// Update metadata tree
|
|
mMetadataTree->clear();
|
|
|
|
auto *sizeItem = new QTreeWidgetItem(mMetadataTree);
|
|
sizeItem->setText(0, "File Size");
|
|
sizeItem->setText(1, QString("%1 bytes").arg(data.size()));
|
|
|
|
auto *filenameItem = new QTreeWidgetItem(mMetadataTree);
|
|
filenameItem->setText(0, "Filename");
|
|
filenameItem->setText(1, filename);
|
|
|
|
if (data.size() >= 4) {
|
|
auto *magicItem = new QTreeWidgetItem(mMetadataTree);
|
|
magicItem->setText(0, "Magic Bytes");
|
|
QString hexMagic;
|
|
for (int i = 0; i < qMin(16, data.size()); i++) {
|
|
hexMagic += QString("%1 ").arg(static_cast<quint8>(data[i]), 2, 16, QChar('0')).toUpper();
|
|
}
|
|
magicItem->setText(1, hexMagic.trimmed());
|
|
}
|
|
}
|
|
|
|
void HexViewerWidget::setMetadata(const QVariantMap &metadata)
|
|
{
|
|
// Add metadata from parsed fields (caller provides only visible fields)
|
|
for (auto it = metadata.begin(); it != metadata.end(); ++it) {
|
|
auto *item = new QTreeWidgetItem(mMetadataTree);
|
|
item->setText(0, it.key());
|
|
|
|
QVariant val = it.value();
|
|
if (val.typeId() == QMetaType::QByteArray) {
|
|
QByteArray ba = val.toByteArray();
|
|
item->setText(1, QString("<%1 bytes>").arg(ba.size()));
|
|
} else {
|
|
item->setText(1, val.toString());
|
|
}
|
|
}
|
|
}
|