XPlor/app/exportdialog.cpp
njohnson d7488c5fa9 Add comprehensive export system with format-specific dialogs
Implement a unified export system for extracting data from parsed files:

ExportManager (singleton):
- Centralized export handling for all content types
- Content type detection (image, audio, video, text, binary)
- Batch export support with progress tracking

Format-specific export dialogs:
- ImageExportDialog: PNG, JPEG, BMP, TGA with quality options
- AudioExportDialog: WAV, MP3, OGG with FFmpeg integration
- BinaryExportDialog: Raw data export with optional decompression
- BatchExportDialog: Recursive export with filtering options
- Base ExportDialog class for common functionality

Settings additions:
- FFmpeg path configuration with auto-detection
- Search common install locations and PATH

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 20:54:38 -05:00

222 lines
6.6 KiB
C++

#include "exportdialog.h"
#include "exportmanager.h"
#include "settings.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QComboBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QFileDialog>
#include <QFileInfo>
#include <QStandardPaths>
ExportDialog::ExportDialog(ContentType type, QWidget *parent)
: QDialog(parent)
, mContentType(type)
, mPreviewContainer(nullptr)
, mOptionsContainer(nullptr)
, mFormatCombo(nullptr)
, mOutputPath(nullptr)
, mBrowseButton(nullptr)
, mButtonBox(nullptr)
, mRememberSettings(nullptr)
, mInfoLabel(nullptr)
{
// Set dialog title based on content type
QString title;
switch (type) {
case Image: title = "Export Image"; break;
case Audio: title = "Export Audio"; break;
case Video: title = "Export Video"; break;
case Text: title = "Export Text"; break;
case Binary: title = "Export Data"; break;
}
setWindowTitle(title);
setMinimumWidth(500);
setModal(true);
setupCommonUI();
}
void ExportDialog::setupCommonUI()
{
mMainLayout = new QVBoxLayout(this);
// Content area: preview on left, options on right
mContentLayout = new QHBoxLayout();
// Left side: preview container
mLeftLayout = new QVBoxLayout();
mPreviewContainer = new QWidget(this);
mPreviewContainer->setMinimumSize(256, 256);
mPreviewContainer->setMaximumSize(300, 300);
mPreviewContainer->setStyleSheet("background-color: #1e1e1e; border: 1px solid #3e3e3e;");
mLeftLayout->addWidget(mPreviewContainer);
// Info label below preview
mInfoLabel = new QLabel(this);
mInfoLabel->setAlignment(Qt::AlignCenter);
mLeftLayout->addWidget(mInfoLabel);
mLeftLayout->addStretch();
mContentLayout->addLayout(mLeftLayout);
// Right side: format and options
mRightLayout = new QVBoxLayout();
// Format selection
QHBoxLayout* formatLayout = new QHBoxLayout();
QLabel* formatLabel = new QLabel("Format:", this);
mFormatCombo = new QComboBox(this);
formatLayout->addWidget(formatLabel);
formatLayout->addWidget(mFormatCombo, 1);
mRightLayout->addLayout(formatLayout);
// Options container (subclasses add format-specific options here)
mOptionsContainer = new QGroupBox("Options", this);
QVBoxLayout* optionsLayout = new QVBoxLayout(mOptionsContainer);
optionsLayout->setContentsMargins(8, 8, 8, 8);
mRightLayout->addWidget(mOptionsContainer);
mRightLayout->addStretch();
mContentLayout->addLayout(mRightLayout, 1);
mMainLayout->addLayout(mContentLayout);
// Output path
QHBoxLayout* pathLayout = new QHBoxLayout();
QLabel* outputLabel = new QLabel("Output:", this);
mOutputPath = new QLineEdit(this);
mBrowseButton = new QPushButton("Browse...", this);
pathLayout->addWidget(outputLabel);
pathLayout->addWidget(mOutputPath, 1);
pathLayout->addWidget(mBrowseButton);
mMainLayout->addLayout(pathLayout);
// Remember settings checkbox
mRememberSettings = new QCheckBox("Remember these settings", this);
mRememberSettings->setChecked(Settings::instance().exportRememberSettings());
mMainLayout->addWidget(mRememberSettings);
// Dialog buttons
mButtonBox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok, this);
mButtonBox->button(QDialogButtonBox::Ok)->setText("Export");
mMainLayout->addWidget(mButtonBox);
// Connect signals
connect(mBrowseButton, &QPushButton::clicked, this, &ExportDialog::onBrowseClicked);
connect(mFormatCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ExportDialog::onFormatComboChanged);
connect(mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void ExportDialog::setData(const QByteArray& data, const QString& suggestedName)
{
mData = data;
mSuggestedName = suggestedName;
// Set default output path
QString dir;
switch (mContentType) {
case Image:
dir = ExportManager::instance().lastExportDir("image");
if (dir.isEmpty()) dir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
break;
case Audio:
dir = ExportManager::instance().lastExportDir("audio");
if (dir.isEmpty()) dir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
break;
default:
dir = ExportManager::instance().lastExportDir("raw");
if (dir.isEmpty()) dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
break;
}
// Get base name without extension
QString baseName = QFileInfo(suggestedName).baseName();
if (baseName.isEmpty()) baseName = "export";
// Get default format
QString format = mFormatCombo->currentText().toLower();
if (format.isEmpty() && mFormatCombo->count() > 0) {
format = mFormatCombo->itemText(0).toLower();
}
mOutputPath->setText(dir + "/" + baseName + "." + format);
// Update preview (subclass implementation)
updatePreview();
}
void ExportDialog::setMetadata(const QVariantMap& metadata)
{
mMetadata = metadata;
}
QString ExportDialog::selectedFormat() const
{
return mFormatCombo->currentText().toLower();
}
QString ExportDialog::outputPath() const
{
return mOutputPath->text();
}
bool ExportDialog::rememberSettings() const
{
return mRememberSettings->isChecked();
}
void ExportDialog::onFormatChanged(const QString& format)
{
// Update output path extension
QString path = mOutputPath->text();
QFileInfo fi(path);
QString newPath = fi.path() + "/" + fi.baseName() + "." + format.toLower();
mOutputPath->setText(newPath);
}
void ExportDialog::onBrowseClicked()
{
QString filter;
QString format = selectedFormat();
// Build filter based on content type
switch (mContentType) {
case Image:
filter = QString("%1 Files (*.%2)").arg(format.toUpper()).arg(format);
break;
case Audio:
filter = QString("%1 Files (*.%2)").arg(format.toUpper()).arg(format);
break;
case Text:
filter = "Text Files (*.txt);;All Files (*)";
break;
case Binary:
default:
filter = "Binary Files (*.bin *.dat);;All Files (*)";
break;
}
QString path = QFileDialog::getSaveFileName(this, "Export", mOutputPath->text(), filter);
if (!path.isEmpty()) {
mOutputPath->setText(path);
}
}
void ExportDialog::onFormatComboChanged(int index)
{
Q_UNUSED(index);
QString format = mFormatCombo->currentText();
onFormatChanged(format);
}