XPlor/app/listpreviewwidget.cpp
njohnson 57ad7c4111 Add new preview widgets and enhance existing viewers
- 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>
2026-01-11 12:08:59 -05:00

272 lines
8.7 KiB
C++

#include "listpreviewwidget.h"
#include <QFileInfo>
ListPreviewWidget::ListPreviewWidget(QWidget *parent)
: QWidget(parent)
{
setupUi();
// Connect to theme changes
connect(&Settings::instance(), &Settings::themeChanged,
this, &ListPreviewWidget::applyTheme);
applyTheme(Settings::instance().theme());
}
void ListPreviewWidget::setupUi()
{
auto *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// Info label at top
mInfoLabel = new QLabel(this);
mInfoLabel->setContentsMargins(8, 4, 8, 4);
mainLayout->addWidget(mInfoLabel);
// Splitter for table and metadata
mSplitter = new QSplitter(Qt::Horizontal, this);
mainLayout->addWidget(mSplitter, 1);
// Table widget for list items
mTableWidget = new QTableWidget(this);
mTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
mTableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
mTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
mTableWidget->horizontalHeader()->setStretchLastSection(true);
mTableWidget->verticalHeader()->setVisible(false);
mTableWidget->setAlternatingRowColors(true);
mSplitter->addWidget(mTableWidget);
// Metadata tree on the right
mMetadataTree = new QTreeWidget(this);
mMetadataTree->setHeaderLabels({"Property", "Value"});
mMetadataTree->setColumnCount(2);
mMetadataTree->header()->setStretchLastSection(true);
mMetadataTree->setMinimumWidth(200);
mSplitter->addWidget(mMetadataTree);
// Set initial splitter sizes (70% table, 30% metadata)
mSplitter->setSizes({700, 300});
// Connect selection change
connect(mTableWidget, &QTableWidget::itemSelectionChanged,
this, &ListPreviewWidget::onItemSelectionChanged);
}
void ListPreviewWidget::setListData(const QVariantList &items, const QString &listName)
{
mItems = items;
mListName = listName;
// Update info label
mInfoLabel->setText(QString("%1 | %2 items")
.arg(listName.isEmpty() ? "List" : listName)
.arg(items.size()));
populateTable(items);
}
void ListPreviewWidget::populateTable(const QVariantList &items)
{
mTableWidget->clear();
mTableWidget->setRowCount(0);
if (items.isEmpty()) {
mTableWidget->setColumnCount(1);
mTableWidget->setHorizontalHeaderLabels({"(empty)"});
return;
}
// Collect all unique keys from all items to determine columns
QStringList columns;
for (const QVariant &item : items) {
if (item.typeId() == QMetaType::QVariantMap) {
const QVariantMap map = item.toMap();
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
const QString &key = it.key();
// Skip internal fields and binary data
if (key.startsWith('_') && key != "_name")
continue;
if (it.value().typeId() == QMetaType::QByteArray)
continue;
if (!columns.contains(key))
columns.append(key);
}
}
}
// If items are simple values (not maps), use a single column
if (columns.isEmpty()) {
columns.append("Value");
}
// Setup columns
mTableWidget->setColumnCount(columns.size());
mTableWidget->setHorizontalHeaderLabels(columns);
// Add rows
mTableWidget->setRowCount(items.size());
for (int row = 0; row < items.size(); ++row) {
const QVariant &item = items[row];
if (item.typeId() == QMetaType::QVariantMap) {
const QVariantMap map = item.toMap();
for (int col = 0; col < columns.size(); ++col) {
const QString &key = columns[col];
QVariant val = map.value(key);
QString displayText;
if (val.typeId() == QMetaType::QVariantMap) {
displayText = QString("{%1 fields}").arg(val.toMap().size());
} else if (val.typeId() == QMetaType::QVariantList) {
displayText = QString("[%1 items]").arg(val.toList().size());
} else if (val.typeId() == QMetaType::QByteArray) {
displayText = QString("<%1 bytes>").arg(val.toByteArray().size());
} else {
displayText = val.toString();
}
auto *tableItem = new QTableWidgetItem(displayText);
mTableWidget->setItem(row, col, tableItem);
}
} else {
// Simple value
auto *tableItem = new QTableWidgetItem(item.toString());
mTableWidget->setItem(row, 0, tableItem);
}
}
// Resize columns to content
mTableWidget->resizeColumnsToContents();
// Clear metadata tree initially
mMetadataTree->clear();
}
void ListPreviewWidget::setMetadata(const QVariantMap &metadata)
{
// This can be used to set overall list metadata
updateMetadataTree(metadata);
}
void ListPreviewWidget::clear()
{
mItems.clear();
mListName.clear();
mTableWidget->clear();
mTableWidget->setRowCount(0);
mTableWidget->setColumnCount(0);
mMetadataTree->clear();
mInfoLabel->setText("");
}
void ListPreviewWidget::onItemSelectionChanged()
{
int row = mTableWidget->currentRow();
if (row >= 0 && row < mItems.size()) {
const QVariant &item = mItems[row];
if (item.typeId() == QMetaType::QVariantMap) {
updateMetadataTree(item.toMap());
} else {
QVariantMap simple;
simple["value"] = item;
simple["index"] = row;
updateMetadataTree(simple);
}
}
}
void ListPreviewWidget::updateMetadataTree(const QVariantMap &item)
{
mMetadataTree->clear();
for (auto it = item.constBegin(); it != item.constEnd(); ++it) {
const QString &key = it.key();
const QVariant &val = it.value();
auto *treeItem = new QTreeWidgetItem(mMetadataTree);
treeItem->setText(0, key);
if (val.typeId() == QMetaType::QVariantMap) {
treeItem->setText(1, QString("{%1 fields}").arg(val.toMap().size()));
// Add nested items
const QVariantMap nested = val.toMap();
for (auto nit = nested.constBegin(); nit != nested.constEnd(); ++nit) {
auto *child = new QTreeWidgetItem(treeItem);
child->setText(0, nit.key());
if (nit.value().typeId() == QMetaType::QByteArray) {
child->setText(1, QString("<%1 bytes>").arg(nit.value().toByteArray().size()));
} else {
child->setText(1, nit.value().toString());
}
}
} else if (val.typeId() == QMetaType::QVariantList) {
treeItem->setText(1, QString("[%1 items]").arg(val.toList().size()));
} else if (val.typeId() == QMetaType::QByteArray) {
treeItem->setText(1, QString("<%1 bytes>").arg(val.toByteArray().size()));
} else {
treeItem->setText(1, val.toString());
}
}
mMetadataTree->expandAll();
mMetadataTree->resizeColumnToContents(0);
}
void ListPreviewWidget::applyTheme(const Theme &theme)
{
// Style info label
mInfoLabel->setStyleSheet(QString(
"QLabel {"
" background-color: %1;"
" color: %2;"
" border-bottom: 1px solid %3;"
"}"
).arg(theme.panelColor, theme.textColorMuted, theme.borderColor));
// Style table widget
mTableWidget->setStyleSheet(QString(
"QTableWidget {"
" background-color: %1;"
" color: %2;"
" border: 1px solid %3;"
" gridline-color: %3;"
"}"
"QTableWidget::item {"
" padding: 4px;"
"}"
"QTableWidget::item:selected {"
" background-color: %4;"
"}"
"QTableWidget::item:alternate {"
" background-color: %5;"
"}"
"QHeaderView::section {"
" background-color: %1;"
" color: %2;"
" border: 1px solid %3;"
" padding: 4px;"
" font-weight: bold;"
"}"
).arg(theme.panelColor, theme.textColor, theme.borderColor,
theme.accentColor, theme.backgroundColor));
// Style metadata tree
mMetadataTree->setStyleSheet(QString(
"QTreeWidget {"
" background-color: %1;"
" color: %2;"
" border: 1px solid %3;"
"}"
"QTreeWidget::item:selected {"
" background-color: %4;"
"}"
"QHeaderView::section {"
" background-color: %1;"
" color: %2;"
" border: 1px solid %3;"
" padding: 4px;"
"}"
).arg(theme.panelColor, theme.textColor, theme.borderColor, theme.accentColor));
}