XPlor/app/batchexportdialog.cpp

468 lines
15 KiB
C++
Raw Permalink Normal View History

#include "batchexportdialog.h"
#include "settings.h"
#include "exportdialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QHeaderView>
#include <QProgressBar>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QFileDialog>
#include <QStandardPaths>
BatchExportDialog::BatchExportDialog(QWidget *parent)
: QDialog(parent)
, mExporting(false)
, mCancelled(false)
, mItemTree(nullptr)
, mSelectionLabel(nullptr)
, mOutputPath(nullptr)
, mBrowseButton(nullptr)
, mPreserveStructure(nullptr)
, mConflictCombo(nullptr)
, mImageFormatCombo(nullptr)
, mAudioFormatCombo(nullptr)
, mProgressBar(nullptr)
, mProgressLabel(nullptr)
, mButtonBox(nullptr)
, mExportButton(nullptr)
, mCancelButton(nullptr)
{
setWindowTitle("Batch Export");
setMinimumSize(600, 500);
setModal(true);
setupUI();
}
void BatchExportDialog::setupUI()
{
QVBoxLayout* mainLayout = new QVBoxLayout(this);
// Item tree with checkboxes
mItemTree = new QTreeWidget(this);
mItemTree->setHeaderLabels({"Name", "Type", "Size"});
mItemTree->setRootIsDecorated(true);
mItemTree->setAlternatingRowColors(true);
mItemTree->header()->setStretchLastSection(false);
mItemTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
mItemTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
mItemTree->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
mainLayout->addWidget(mItemTree, 1);
connect(mItemTree, &QTreeWidget::itemChanged, this, &BatchExportDialog::onItemChanged);
// Selection buttons
QHBoxLayout* selectionLayout = new QHBoxLayout();
QPushButton* selectAllBtn = new QPushButton("Select All", this);
QPushButton* selectNoneBtn = new QPushButton("Select None", this);
QPushButton* selectImagesBtn = new QPushButton("Images Only", this);
QPushButton* selectAudioBtn = new QPushButton("Audio Only", this);
mSelectionLabel = new QLabel("Selected: 0 of 0", this);
selectionLayout->addWidget(selectAllBtn);
selectionLayout->addWidget(selectNoneBtn);
selectionLayout->addWidget(selectImagesBtn);
selectionLayout->addWidget(selectAudioBtn);
selectionLayout->addStretch();
selectionLayout->addWidget(mSelectionLabel);
mainLayout->addLayout(selectionLayout);
connect(selectAllBtn, &QPushButton::clicked, this, &BatchExportDialog::onSelectAll);
connect(selectNoneBtn, &QPushButton::clicked, this, &BatchExportDialog::onSelectNone);
connect(selectImagesBtn, &QPushButton::clicked, this, &BatchExportDialog::onSelectImages);
connect(selectAudioBtn, &QPushButton::clicked, this, &BatchExportDialog::onSelectAudio);
// Output options group
QGroupBox* optionsGroup = new QGroupBox("Export Options", this);
QGridLayout* optionsLayout = new QGridLayout(optionsGroup);
// Output directory
optionsLayout->addWidget(new QLabel("Output:", this), 0, 0);
mOutputPath = new QLineEdit(this);
mOutputPath->setText(Settings::instance().batchExportDirectory());
if (mOutputPath->text().isEmpty()) {
mOutputPath->setText(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
}
optionsLayout->addWidget(mOutputPath, 0, 1);
mBrowseButton = new QPushButton("Browse...", this);
optionsLayout->addWidget(mBrowseButton, 0, 2);
connect(mBrowseButton, &QPushButton::clicked, this, &BatchExportDialog::onBrowseClicked);
// Preserve structure
mPreserveStructure = new QCheckBox("Preserve folder structure", this);
mPreserveStructure->setChecked(Settings::instance().batchExportPreserveStructure());
optionsLayout->addWidget(mPreserveStructure, 1, 0, 1, 3);
// Conflict resolution
optionsLayout->addWidget(new QLabel("Conflicts:", this), 2, 0);
mConflictCombo = new QComboBox(this);
mConflictCombo->addItem("Append number", "number");
mConflictCombo->addItem("Overwrite", "overwrite");
mConflictCombo->addItem("Skip", "skip");
QString savedResolution = Settings::instance().batchExportConflictResolution();
for (int i = 0; i < mConflictCombo->count(); ++i) {
if (mConflictCombo->itemData(i).toString() == savedResolution) {
mConflictCombo->setCurrentIndex(i);
break;
}
}
optionsLayout->addWidget(mConflictCombo, 2, 1, 1, 2);
// Format options
optionsLayout->addWidget(new QLabel("Image format:", this), 3, 0);
mImageFormatCombo = new QComboBox(this);
mImageFormatCombo->addItem("PNG", "png");
mImageFormatCombo->addItem("JPG", "jpg");
mImageFormatCombo->addItem("BMP", "bmp");
mImageFormatCombo->addItem("TGA", "tga");
mImageFormatCombo->addItem("TIFF", "tiff");
QString savedImageFormat = Settings::instance().defaultImageExportFormat();
for (int i = 0; i < mImageFormatCombo->count(); ++i) {
if (mImageFormatCombo->itemData(i).toString() == savedImageFormat) {
mImageFormatCombo->setCurrentIndex(i);
break;
}
}
optionsLayout->addWidget(mImageFormatCombo, 3, 1, 1, 2);
optionsLayout->addWidget(new QLabel("Audio format:", this), 4, 0);
mAudioFormatCombo = new QComboBox(this);
mAudioFormatCombo->addItem("WAV", "wav");
mAudioFormatCombo->addItem("MP3", "mp3");
mAudioFormatCombo->addItem("OGG", "ogg");
mAudioFormatCombo->addItem("FLAC", "flac");
QString savedAudioFormat = Settings::instance().defaultAudioExportFormat();
for (int i = 0; i < mAudioFormatCombo->count(); ++i) {
if (mAudioFormatCombo->itemData(i).toString() == savedAudioFormat) {
mAudioFormatCombo->setCurrentIndex(i);
break;
}
}
optionsLayout->addWidget(mAudioFormatCombo, 4, 1, 1, 2);
mainLayout->addWidget(optionsGroup);
// Progress section
mProgressBar = new QProgressBar(this);
mProgressBar->setVisible(false);
mainLayout->addWidget(mProgressBar);
mProgressLabel = new QLabel(this);
mProgressLabel->setVisible(false);
mainLayout->addWidget(mProgressLabel);
// Dialog buttons
mButtonBox = new QDialogButtonBox(this);
mExportButton = mButtonBox->addButton("Export All", QDialogButtonBox::AcceptRole);
mCancelButton = mButtonBox->addButton(QDialogButtonBox::Cancel);
mainLayout->addWidget(mButtonBox);
connect(mExportButton, &QPushButton::clicked, this, &BatchExportDialog::onExportClicked);
connect(mCancelButton, &QPushButton::clicked, this, &QDialog::reject);
}
void BatchExportDialog::setItems(const QList<BatchExportItem>& items)
{
mItems = items;
populateTree();
updateSelectionCount();
// Update title with item count
setWindowTitle(QString("Batch Export (%1 items)").arg(items.size()));
}
void BatchExportDialog::populateTree()
{
mItemTree->clear();
mItemTree->blockSignals(true);
// Group items by path
QMap<QString, QTreeWidgetItem*> folderItems;
for (int i = 0; i < mItems.size(); ++i) {
const BatchExportItem& item = mItems[i];
// Determine parent folder
QString folder;
QString name = item.name;
int lastSlash = item.path.lastIndexOf('/');
if (lastSlash > 0) {
folder = item.path.left(lastSlash);
}
QTreeWidgetItem* parent = nullptr;
if (!folder.isEmpty()) {
if (!folderItems.contains(folder)) {
// Create folder item
QTreeWidgetItem* folderItem = new QTreeWidgetItem(mItemTree);
folderItem->setText(0, folder);
folderItem->setCheckState(0, Qt::Checked);
folderItem->setFlags(folderItem->flags() | Qt::ItemIsAutoTristate);
folderItem->setData(0, Qt::UserRole, -1); // -1 indicates folder
folderItems[folder] = folderItem;
}
parent = folderItems[folder];
}
// Create item
QTreeWidgetItem* treeItem = parent ? new QTreeWidgetItem(parent) : new QTreeWidgetItem(mItemTree);
treeItem->setText(0, name);
treeItem->setCheckState(0, item.selected ? Qt::Checked : Qt::Unchecked);
treeItem->setData(0, Qt::UserRole, i); // Store index
// Type column
QString typeStr;
switch (item.contentType) {
case ExportDialog::Image: typeStr = "Image"; break;
case ExportDialog::Audio: typeStr = "Audio"; break;
case ExportDialog::Video: typeStr = "Video"; break;
case ExportDialog::Text: typeStr = "Text"; break;
default: typeStr = "Binary"; break;
}
treeItem->setText(1, typeStr);
// Size column
qint64 size = item.data.size();
QString sizeStr;
if (size >= 1024 * 1024) {
sizeStr = QString("%1 MB").arg(size / (1024.0 * 1024.0), 0, 'f', 1);
} else if (size >= 1024) {
sizeStr = QString("%1 KB").arg(size / 1024.0, 0, 'f', 0);
} else {
sizeStr = QString("%1 B").arg(size);
}
treeItem->setText(2, sizeStr);
}
mItemTree->expandAll();
mItemTree->blockSignals(false);
}
QString BatchExportDialog::outputDirectory() const
{
return mOutputPath->text();
}
bool BatchExportDialog::preserveStructure() const
{
return mPreserveStructure->isChecked();
}
QString BatchExportDialog::conflictResolution() const
{
return mConflictCombo->currentData().toString();
}
QString BatchExportDialog::imageFormat() const
{
return mImageFormatCombo->currentData().toString();
}
QString BatchExportDialog::audioFormat() const
{
return mAudioFormatCombo->currentData().toString();
}
QList<BatchExportItem> BatchExportDialog::selectedItems() const
{
QList<BatchExportItem> selected;
std::function<void(QTreeWidgetItem*)> collectSelected = [&](QTreeWidgetItem* item) {
int idx = item->data(0, Qt::UserRole).toInt();
if (idx >= 0 && idx < mItems.size()) {
if (item->checkState(0) == Qt::Checked) {
selected.append(mItems[idx]);
}
}
for (int i = 0; i < item->childCount(); ++i) {
collectSelected(item->child(i));
}
};
for (int i = 0; i < mItemTree->topLevelItemCount(); ++i) {
collectSelected(mItemTree->topLevelItem(i));
}
return selected;
}
void BatchExportDialog::updateProgress(int current, int total, const QString& currentItem)
{
mProgressBar->setMaximum(total);
mProgressBar->setValue(current);
mProgressLabel->setText(QString("Exporting: %1").arg(currentItem));
}
void BatchExportDialog::onExportCompleted(int succeeded, int failed, int skipped)
{
mExporting = false;
mProgressBar->setVisible(false);
mProgressLabel->setVisible(false);
mExportButton->setEnabled(true);
mExportButton->setText("Export All");
// Show results
mProgressLabel->setText(QString("Completed: %1 succeeded, %2 failed, %3 skipped")
.arg(succeeded).arg(failed).arg(skipped));
mProgressLabel->setVisible(true);
if (failed == 0) {
accept();
}
}
void BatchExportDialog::onSelectAll()
{
mItemTree->blockSignals(true);
for (int i = 0; i < mItemTree->topLevelItemCount(); ++i) {
QTreeWidgetItem* item = mItemTree->topLevelItem(i);
item->setCheckState(0, Qt::Checked);
}
mItemTree->blockSignals(false);
updateSelectionCount();
}
void BatchExportDialog::onSelectNone()
{
mItemTree->blockSignals(true);
for (int i = 0; i < mItemTree->topLevelItemCount(); ++i) {
QTreeWidgetItem* item = mItemTree->topLevelItem(i);
item->setCheckState(0, Qt::Unchecked);
}
mItemTree->blockSignals(false);
updateSelectionCount();
}
void BatchExportDialog::onSelectImages()
{
mItemTree->blockSignals(true);
std::function<void(QTreeWidgetItem*)> setByType = [&](QTreeWidgetItem* item) {
int idx = item->data(0, Qt::UserRole).toInt();
if (idx >= 0 && idx < mItems.size()) {
bool isImage = mItems[idx].contentType == ExportDialog::Image;
item->setCheckState(0, isImage ? Qt::Checked : Qt::Unchecked);
}
for (int i = 0; i < item->childCount(); ++i) {
setByType(item->child(i));
}
};
for (int i = 0; i < mItemTree->topLevelItemCount(); ++i) {
setByType(mItemTree->topLevelItem(i));
}
mItemTree->blockSignals(false);
updateSelectionCount();
}
void BatchExportDialog::onSelectAudio()
{
mItemTree->blockSignals(true);
std::function<void(QTreeWidgetItem*)> setByType = [&](QTreeWidgetItem* item) {
int idx = item->data(0, Qt::UserRole).toInt();
if (idx >= 0 && idx < mItems.size()) {
bool isAudio = mItems[idx].contentType == ExportDialog::Audio;
item->setCheckState(0, isAudio ? Qt::Checked : Qt::Unchecked);
}
for (int i = 0; i < item->childCount(); ++i) {
setByType(item->child(i));
}
};
for (int i = 0; i < mItemTree->topLevelItemCount(); ++i) {
setByType(mItemTree->topLevelItem(i));
}
mItemTree->blockSignals(false);
updateSelectionCount();
}
void BatchExportDialog::onBrowseClicked()
{
QString dir = QFileDialog::getExistingDirectory(this, "Select Output Directory",
mOutputPath->text());
if (!dir.isEmpty()) {
mOutputPath->setText(dir);
}
}
void BatchExportDialog::onExportClicked()
{
if (mExporting) {
mCancelled = true;
return;
}
// Save settings
Settings::instance().setBatchExportDirectory(mOutputPath->text());
Settings::instance().setBatchExportPreserveStructure(mPreserveStructure->isChecked());
Settings::instance().setBatchExportConflictResolution(mConflictCombo->currentData().toString());
// Start export
mExporting = true;
mCancelled = false;
mExportButton->setText("Cancel");
mProgressBar->setVisible(true);
mProgressBar->setValue(0);
mProgressLabel->setVisible(true);
// Signal acceptance - MainWindow will handle actual export
accept();
}
void BatchExportDialog::onItemChanged(QTreeWidgetItem* item, int column)
{
Q_UNUSED(item);
Q_UNUSED(column);
updateSelectionCount();
}
void BatchExportDialog::updateSelectionCount()
{
int selected = 0;
int total = 0;
std::function<void(QTreeWidgetItem*)> count = [&](QTreeWidgetItem* item) {
int idx = item->data(0, Qt::UserRole).toInt();
if (idx >= 0) {
total++;
if (item->checkState(0) == Qt::Checked) {
selected++;
}
}
for (int i = 0; i < item->childCount(); ++i) {
count(item->child(i));
}
};
for (int i = 0; i < mItemTree->topLevelItemCount(); ++i) {
count(mItemTree->topLevelItem(i));
}
mSelectionLabel->setText(QString("Selected: %1 of %2").arg(selected).arg(total));
mExportButton->setEnabled(selected > 0);
}
int BatchExportDialog::countByType(int contentType) const
{
int count = 0;
for (const BatchExportItem& item : mItems) {
if (item.contentType == contentType) {
count++;
}
}
return count;
}