Major UI improvements across the application: MainWindow: - Integrate undo stack for field editing - Dirty state tracking with tab title indicators (*) - Save/SaveAs prompts on tab close with unsaved changes - Export menu integration in tab context menu - Tab management: close all, close left/right of current tab - Connect export system to tree widget signals XTreeWidget: - Add context menus for tree items - Quick export action for immediate saves - Export dialog action for format options - Raw data export for any item - Batch export for containers XTreeWidgetItem: - Add modified state tracking with visual indicator - Support for marking items as dirty ImagePreviewWidget: - Enhanced image display and navigation - Improved zoom and pan controls TreeBuilder: - Better handling of nested data structures - Improved child node generation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
215 lines
7.7 KiB
C++
215 lines
7.7 KiB
C++
#include "xtreewidget.h"
|
|
#include "exportmanager.h"
|
|
#include "qheaderview.h"
|
|
#include "qmenu.h"
|
|
#include "logmanager.h"
|
|
#include "xtreewidgetitem.h"
|
|
#include "../libs/dsl/dslkeys.h"
|
|
|
|
#include <QFileDialog>
|
|
|
|
XTreeWidget::XTreeWidget(QWidget *parent)
|
|
: QTreeWidget(parent) {
|
|
|
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
|
setSelectionMode(QTreeWidget::SingleSelection);
|
|
setColumnCount(3);
|
|
header()->hide();
|
|
setMinimumWidth(350);
|
|
setSortingEnabled(false); // Preserve parse order instead of sorting alphabetically
|
|
setIconSize(QSize(16, 16));
|
|
|
|
header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
|
|
|
connect(this, &XTreeWidget::itemSelectionChanged, this, &XTreeWidget::ItemSelectionChanged);
|
|
connect(this, &XTreeWidget::customContextMenuRequested, this, &XTreeWidget::PrepareContextMenu);
|
|
}
|
|
|
|
int XTreeWidget::countExportableChildren(QTreeWidgetItem* parent) const {
|
|
int count = 0;
|
|
std::function<void(QTreeWidgetItem*)> countRecursive = [&](QTreeWidgetItem* item) {
|
|
QString kind = item->data(0, Qt::UserRole + 1).toString();
|
|
if (kind == "INSTANCE") {
|
|
count++;
|
|
}
|
|
for (int i = 0; i < item->childCount(); ++i) {
|
|
countRecursive(item->child(i));
|
|
}
|
|
};
|
|
|
|
for (int i = 0; i < parent->childCount(); ++i) {
|
|
countRecursive(parent->child(i));
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int XTreeWidget::countExportableChildrenByType(QTreeWidgetItem* parent, int contentType) const {
|
|
int count = 0;
|
|
ExportManager& exp = ExportManager::instance();
|
|
|
|
std::function<void(QTreeWidgetItem*)> countRecursive = [&](QTreeWidgetItem* item) {
|
|
QString kind = item->data(0, Qt::UserRole + 1).toString();
|
|
if (kind == "INSTANCE") {
|
|
QVariantMap vars = item->data(0, Qt::UserRole + 3).toMap();
|
|
if (exp.detectContentType(vars) == contentType) {
|
|
count++;
|
|
}
|
|
}
|
|
for (int i = 0; i < item->childCount(); ++i) {
|
|
countRecursive(item->child(i));
|
|
}
|
|
};
|
|
|
|
for (int i = 0; i < parent->childCount(); ++i) {
|
|
countRecursive(parent->child(i));
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void XTreeWidget::PrepareContextMenu(const QPoint &pos) {
|
|
auto activeItem = itemAt(pos);
|
|
if (!activeItem) { return; }
|
|
if (activeItem->text(0).isEmpty()) { return; }
|
|
|
|
QString kind = activeItem->data(0, Qt::UserRole + 1).toString();
|
|
|
|
QMenu *contextMenu = new QMenu(this);
|
|
|
|
if (kind == "INSTANCE") {
|
|
prepareInstanceContextMenu(contextMenu, activeItem);
|
|
} else if (kind == "CATEGORY" || kind == "SUBCATEGORY" || kind == "EXTENSION_GROUP") {
|
|
prepareContainerContextMenu(contextMenu, activeItem);
|
|
} else {
|
|
// No context menu for other item types
|
|
delete contextMenu;
|
|
return;
|
|
}
|
|
|
|
contextMenu->exec(mapToGlobal(pos));
|
|
delete contextMenu;
|
|
}
|
|
|
|
void XTreeWidget::prepareInstanceContextMenu(QMenu* menu, QTreeWidgetItem* item) {
|
|
QVariantMap vars = item->data(0, Qt::UserRole + 3).toMap();
|
|
ExportManager& exp = ExportManager::instance();
|
|
auto contentType = exp.detectContentType(vars);
|
|
|
|
// Quick Export - uses saved defaults
|
|
QAction* quickAction = menu->addAction("Quick Export");
|
|
quickAction->setShortcut(QKeySequence("Ctrl+Shift+E"));
|
|
connect(quickAction, &QAction::triggered, this, [this, item]() {
|
|
emit quickExportRequested(item);
|
|
});
|
|
|
|
// Export with dialog - full options
|
|
QAction* dialogAction = menu->addAction("Export...");
|
|
dialogAction->setShortcut(QKeySequence("Ctrl+E"));
|
|
connect(dialogAction, &QAction::triggered, this, [this, item]() {
|
|
emit exportDialogRequested(item);
|
|
});
|
|
|
|
menu->addSeparator();
|
|
|
|
// Raw export - always available
|
|
QAction* rawAction = menu->addAction("Export Raw Data...");
|
|
connect(rawAction, &QAction::triggered, this, [this, item]() {
|
|
emit exportRequested("raw", item);
|
|
});
|
|
|
|
menu->addSeparator();
|
|
|
|
// Format-specific exports based on content type
|
|
if (contentType == ExportManager::Image) {
|
|
QMenu* imgMenu = menu->addMenu("Export Image As");
|
|
for (const QString& fmt : exp.supportedImageFormats()) {
|
|
QAction* a = imgMenu->addAction(fmt.toUpper());
|
|
connect(a, &QAction::triggered, this, [this, item, fmt]() {
|
|
emit exportRequested(fmt, item);
|
|
});
|
|
}
|
|
}
|
|
else if (contentType == ExportManager::Audio) {
|
|
QMenu* audMenu = menu->addMenu("Export Audio As");
|
|
for (const QString& fmt : exp.supportedAudioFormats()) {
|
|
QAction* a = audMenu->addAction(fmt.toUpper());
|
|
// Disable non-WAV formats if FFmpeg not available
|
|
a->setEnabled(fmt == "wav" || exp.hasFFmpeg());
|
|
if (!a->isEnabled()) {
|
|
a->setText(a->text() + " (requires FFmpeg)");
|
|
}
|
|
connect(a, &QAction::triggered, this, [this, item, fmt]() {
|
|
emit exportRequested(fmt, item);
|
|
});
|
|
}
|
|
}
|
|
else if (contentType == ExportManager::Text) {
|
|
QAction* txtAction = menu->addAction("Export as Text...");
|
|
connect(txtAction, &QAction::triggered, this, [this, item]() {
|
|
emit exportRequested("txt", item);
|
|
});
|
|
}
|
|
|
|
menu->addSeparator();
|
|
|
|
// Clipboard operations
|
|
QAction* copyAction = menu->addAction("Copy to Clipboard");
|
|
connect(copyAction, &QAction::triggered, this, [this, item]() {
|
|
emit exportRequested("clipboard", item);
|
|
});
|
|
}
|
|
|
|
void XTreeWidget::prepareContainerContextMenu(QMenu* menu, QTreeWidgetItem* item) {
|
|
int totalCount = countExportableChildren(item);
|
|
|
|
if (totalCount == 0) {
|
|
QAction* emptyAction = menu->addAction("No exportable items");
|
|
emptyAction->setEnabled(false);
|
|
return;
|
|
}
|
|
|
|
// Export all children
|
|
QAction* exportAllAction = menu->addAction(QString("Export All Children... (%1 items)").arg(totalCount));
|
|
connect(exportAllAction, &QAction::triggered, this, [this, item]() {
|
|
emit batchExportRequested(item);
|
|
});
|
|
|
|
menu->addSeparator();
|
|
|
|
// Export by type
|
|
int imageCount = countExportableChildrenByType(item, ExportManager::Image);
|
|
int audioCount = countExportableChildrenByType(item, ExportManager::Audio);
|
|
int textCount = countExportableChildrenByType(item, ExportManager::Text);
|
|
|
|
if (imageCount > 0) {
|
|
QAction* imgAction = menu->addAction(QString("Export All Images... (%1)").arg(imageCount));
|
|
connect(imgAction, &QAction::triggered, this, [this, item]() {
|
|
emit batchExportByTypeRequested(item, ExportManager::Image);
|
|
});
|
|
}
|
|
|
|
if (audioCount > 0) {
|
|
QAction* audAction = menu->addAction(QString("Export All Audio... (%1)").arg(audioCount));
|
|
connect(audAction, &QAction::triggered, this, [this, item]() {
|
|
emit batchExportByTypeRequested(item, ExportManager::Audio);
|
|
});
|
|
}
|
|
|
|
if (textCount > 0) {
|
|
QAction* txtAction = menu->addAction(QString("Export All Text... (%1)").arg(textCount));
|
|
connect(txtAction, &QAction::triggered, this, [this, item]() {
|
|
emit batchExportByTypeRequested(item, ExportManager::Text);
|
|
});
|
|
}
|
|
}
|
|
|
|
void XTreeWidget::ItemSelectionChanged() {
|
|
if (selectedItems().isEmpty()) { return; }
|
|
|
|
XTreeWidgetItem *selectedItem = dynamic_cast<XTreeWidgetItem*>(selectedItems().first());
|
|
if (!selectedItem) { return; }
|
|
if (selectedItem->text(0).isEmpty()) { return; }
|
|
QString selectedText = selectedItem->text(0);
|
|
|
|
emit ItemSelected(selectedText, selectedItem);
|
|
}
|