XPlor/app/preferenceeditor.cpp

646 lines
22 KiB
C++
Raw Normal View History

2025-09-05 20:44:31 +00:00
#include "preferenceeditor.h"
#include "settings.h"
#include "compression.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QDialogButtonBox>
#include <QFontDatabase>
#include <QFileDialog>
#include <QFileInfo>
#include <QFormLayout>
#include <QFrame>
#include <QMessageBox>
2025-09-05 20:44:31 +00:00
PreferenceEditor::PreferenceEditor(QWidget *parent)
: QDialog(parent)
{
setupUi();
loadSettings();
applyStylesheet();
}
void PreferenceEditor::setupUi()
{
setWindowTitle("XPlor Preferences");
setMinimumSize(700, 500);
resize(800, 550);
auto *mainLayout = new QVBoxLayout(this);
mainLayout->setSpacing(10);
mainLayout->setContentsMargins(15, 15, 15, 15);
// Main content area
auto *contentLayout = new QHBoxLayout();
contentLayout->setSpacing(15);
// Category list
m_categoryList = new QListWidget(this);
m_categoryList->setFixedWidth(160);
m_categoryList->setSpacing(2);
m_categoryList->addItem("Appearance");
m_categoryList->addItem("General");
m_categoryList->addItem("Tools");
m_categoryList->addItem("Debug & Logging");
m_categoryList->addItem("Tree Browser");
m_categoryList->addItem("Previews");
m_categoryList->setCurrentRow(0);
connect(m_categoryList, &QListWidget::currentRowChanged, this, &PreferenceEditor::onCategoryChanged);
contentLayout->addWidget(m_categoryList);
// Page stack
m_pageStack = new QStackedWidget(this);
createAppearancePage();
createGeneralPage();
createToolsPage();
createDebugPage();
createTreePage();
createPreviewPage();
contentLayout->addWidget(m_pageStack, 1);
mainLayout->addLayout(contentLayout, 1);
// Separator
auto *separator = new QFrame(this);
separator->setFrameShape(QFrame::HLine);
separator->setStyleSheet("background-color: #3c3c3c;");
mainLayout->addWidget(separator);
// Button box
auto *buttonBox = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply,
this);
connect(buttonBox, &QDialogButtonBox::accepted, this, &PreferenceEditor::onAccept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &PreferenceEditor::onApply);
mainLayout->addWidget(buttonBox);
}
void PreferenceEditor::createAppearancePage()
{
auto *page = new QWidget(this);
auto *layout = new QVBoxLayout(page);
layout->setSpacing(15);
// Title
auto *titleLabel = new QLabel("Appearance", page);
titleLabel->setStyleSheet("font-size: 14px; font-weight: bold; color: #ad0c0c;");
layout->addWidget(titleLabel);
// Theme group
auto *themeGroup = new QGroupBox("Color Theme", page);
auto *themeLayout = new QVBoxLayout(themeGroup);
themeLayout->setSpacing(10);
auto *themeRow = new QHBoxLayout();
auto *themeLabel = new QLabel("Theme:", themeGroup);
themeRow->addWidget(themeLabel);
m_themeCombo = new QComboBox(themeGroup);
for (const QString &themeName : Settings::instance().availableThemes()) {
m_themeCombo->addItem(themeName);
}
connect(m_themeCombo, &QComboBox::currentTextChanged, this, &PreferenceEditor::updateThemePreview);
themeRow->addWidget(m_themeCombo, 1);
themeLayout->addLayout(themeRow);
// Theme preview
m_themePreview = new QLabel(themeGroup);
m_themePreview->setFixedHeight(80);
m_themePreview->setAlignment(Qt::AlignCenter);
themeLayout->addWidget(m_themePreview);
// Theme description
auto *themeDesc = new QLabel(
"Choose a color theme for the application. The theme affects the accent color "
"and overall appearance. Changes will take effect after restarting the application.",
themeGroup);
themeDesc->setWordWrap(true);
themeDesc->setStyleSheet("color: #888; font-size: 11px;");
themeLayout->addWidget(themeDesc);
layout->addWidget(themeGroup);
layout->addStretch();
m_pageStack->addWidget(page);
}
void PreferenceEditor::updateThemePreview()
{
Theme t = Settings::getTheme(m_themeCombo->currentText());
QString previewStyle = QString(
"background: qlineargradient(x1:0, y1:0, x2:1, y2:0, "
"stop:0 %1, stop:0.15 %1, stop:0.15 %2, stop:0.85 %2, stop:0.85 %3, stop:1 %3);"
"border: 2px solid %4; border-radius: 6px; color: %5; font-weight: bold;"
).arg(t.accentColor, t.backgroundColor, t.panelColor, t.borderColor, t.textColor);
m_themePreview->setStyleSheet(previewStyle);
m_themePreview->setText(t.name);
}
void PreferenceEditor::createGeneralPage()
{
auto *page = new QWidget(this);
auto *layout = new QVBoxLayout(page);
layout->setSpacing(15);
// Title
auto *titleLabel = new QLabel("General Settings", page);
titleLabel->setStyleSheet("font-size: 14px; font-weight: bold; color: #ad0c0c;");
layout->addWidget(titleLabel);
// Export settings group
auto *exportGroup = new QGroupBox("Export Settings", page);
auto *exportLayout = new QFormLayout(exportGroup);
exportLayout->setSpacing(10);
auto *dirLayout = new QHBoxLayout();
m_exportDirEdit = new QLineEdit(exportGroup);
m_exportDirEdit->setPlaceholderText("Export directory path...");
dirLayout->addWidget(m_exportDirEdit);
auto *browseBtn = new QPushButton("Browse...", exportGroup);
browseBtn->setFixedWidth(80);
connect(browseBtn, &QPushButton::clicked, this, &PreferenceEditor::onBrowseExportDir);
dirLayout->addWidget(browseBtn);
exportLayout->addRow("Export Directory:", dirLayout);
m_autoExportCheck = new QCheckBox("Automatically export resources when parsing", exportGroup);
exportLayout->addRow("", m_autoExportCheck);
layout->addWidget(exportGroup);
// View settings group
auto *viewGroup = new QGroupBox("View Settings", page);
auto *viewLayout = new QFormLayout(viewGroup);
viewLayout->setSpacing(10);
m_fontFamilyCombo = new QComboBox(viewGroup);
QStringList families = QFontDatabase::families();
for (const QString &family : families) {
if (QFontDatabase::isFixedPitch(family) || family.contains("Mono", Qt::CaseInsensitive) ||
family == "Segoe UI" || family == "Roboto" || family == "Arial") {
m_fontFamilyCombo->addItem(family);
}
}
viewLayout->addRow("Font Family:", m_fontFamilyCombo);
m_fontSizeSpin = new QSpinBox(viewGroup);
m_fontSizeSpin->setRange(8, 24);
m_fontSizeSpin->setSuffix(" pt");
viewLayout->addRow("Font Size:", m_fontSizeSpin);
m_zoomSpin = new QSpinBox(viewGroup);
m_zoomSpin->setRange(50, 200);
m_zoomSpin->setSuffix(" %");
m_zoomSpin->setSingleStep(10);
viewLayout->addRow("View Zoom:", m_zoomSpin);
layout->addWidget(viewGroup);
// Reset group
auto *resetGroup = new QGroupBox("Reset", page);
auto *resetLayout = new QVBoxLayout(resetGroup);
resetLayout->setSpacing(10);
auto *resetDesc = new QLabel(
"Reset all settings to their default values. This will restore the default theme (XPlor Dark) "
"and clear all customizations.",
resetGroup);
resetDesc->setWordWrap(true);
resetDesc->setStyleSheet("color: #888; font-size: 11px;");
resetLayout->addWidget(resetDesc);
auto *resetBtn = new QPushButton("Reset All Settings", resetGroup);
resetBtn->setStyleSheet("background-color: #8a0a0a;");
connect(resetBtn, &QPushButton::clicked, this, &PreferenceEditor::onResetSettings);
resetLayout->addWidget(resetBtn);
layout->addWidget(resetGroup);
layout->addStretch();
m_pageStack->addWidget(page);
}
void PreferenceEditor::createToolsPage()
{
auto *page = new QWidget(this);
auto *layout = new QVBoxLayout(page);
layout->setSpacing(15);
// Title
auto *titleLabel = new QLabel("External Tools", page);
titleLabel->setStyleSheet("font-size: 14px; font-weight: bold; color: #ad0c0c;");
layout->addWidget(titleLabel);
// QuickBMS group
auto *quickbmsGroup = new QGroupBox("QuickBMS", page);
auto *quickbmsLayout = new QVBoxLayout(quickbmsGroup);
quickbmsLayout->setSpacing(10);
auto *quickbmsDesc = new QLabel(
"QuickBMS is used for decompressing certain Xbox 360 formats (LZXTDECODE). "
"If not configured, XPlor will attempt to find it automatically.",
quickbmsGroup);
quickbmsDesc->setWordWrap(true);
quickbmsDesc->setStyleSheet("color: #888; font-size: 11px;");
quickbmsLayout->addWidget(quickbmsDesc);
auto *pathLayout = new QHBoxLayout();
m_quickBmsEdit = new QLineEdit(quickbmsGroup);
m_quickBmsEdit->setPlaceholderText("Path to quickbms.exe...");
pathLayout->addWidget(m_quickBmsEdit);
auto *browseBtn = new QPushButton("Browse...", quickbmsGroup);
browseBtn->setFixedWidth(80);
connect(browseBtn, &QPushButton::clicked, this, &PreferenceEditor::onBrowseQuickBms);
pathLayout->addWidget(browseBtn);
quickbmsLayout->addLayout(pathLayout);
m_quickBmsStatus = new QLabel(quickbmsGroup);
m_quickBmsStatus->setStyleSheet("font-size: 11px;");
quickbmsLayout->addWidget(m_quickBmsStatus);
// Update status when path changes
connect(m_quickBmsEdit, &QLineEdit::textChanged, this, [this](const QString &path) {
if (path.isEmpty()) {
m_quickBmsStatus->setText("No path configured - will auto-detect");
m_quickBmsStatus->setStyleSheet("color: #888; font-size: 11px;");
} else if (QFileInfo::exists(path)) {
m_quickBmsStatus->setText("Found: " + path);
m_quickBmsStatus->setStyleSheet("color: #4caf50; font-size: 11px;");
} else {
m_quickBmsStatus->setText("Not found at specified path");
m_quickBmsStatus->setStyleSheet("color: #f44336; font-size: 11px;");
}
});
layout->addWidget(quickbmsGroup);
layout->addStretch();
m_pageStack->addWidget(page);
}
void PreferenceEditor::createDebugPage()
{
auto *page = new QWidget(this);
auto *layout = new QVBoxLayout(page);
layout->setSpacing(15);
// Title
auto *titleLabel = new QLabel("Debug & Logging", page);
titleLabel->setStyleSheet("font-size: 14px; font-weight: bold; color: #ad0c0c;");
layout->addWidget(titleLabel);
// Logging group
auto *logGroup = new QGroupBox("Logging Options", page);
auto *logLayout = new QVBoxLayout(logGroup);
logLayout->setSpacing(10);
m_debugLoggingCheck = new QCheckBox("Enable debug logging", logGroup);
m_debugLoggingCheck->setToolTip("Log detailed parsing information to the Logs panel.\n"
"Shows parse positions, chunk processing, and tree routing.");
logLayout->addWidget(m_debugLoggingCheck);
m_verboseParsingCheck = new QCheckBox("Verbose parsing output", logGroup);
m_verboseParsingCheck->setToolTip("Show additional details during file parsing.\n"
"Includes field assignments and type information.");
logLayout->addWidget(m_verboseParsingCheck);
m_logToFileCheck = new QCheckBox("Save logs to file", logGroup);
m_logToFileCheck->setToolTip("Write all log entries to xplor.log in the application directory.");
logLayout->addWidget(m_logToFileCheck);
layout->addWidget(logGroup);
// Info label
auto *infoLabel = new QLabel(
"Debug logging provides detailed information about file parsing and "
"data processing. Enable this when troubleshooting issues with file "
"parsing or to understand how data is being processed.\n\n"
"Note: Extensive logging may affect performance with large files.",
page);
infoLabel->setWordWrap(true);
infoLabel->setStyleSheet("color: #888; padding: 10px; background-color: #252526; border-radius: 4px;");
layout->addWidget(infoLabel);
layout->addStretch();
m_pageStack->addWidget(page);
}
void PreferenceEditor::createTreePage()
{
auto *page = new QWidget(this);
auto *layout = new QVBoxLayout(page);
layout->setSpacing(15);
// Title
auto *titleLabel = new QLabel("Tree Browser", page);
titleLabel->setStyleSheet("font-size: 14px; font-weight: bold; color: #ad0c0c;");
layout->addWidget(titleLabel);
// Display group
auto *displayGroup = new QGroupBox("Display Options", page);
auto *displayLayout = new QVBoxLayout(displayGroup);
displayLayout->setSpacing(10);
m_showCountsCheck = new QCheckBox("Show item counts on categories", displayGroup);
m_showCountsCheck->setToolTip("Display the number of children next to category names.");
displayLayout->addWidget(m_showCountsCheck);
m_collapseDefaultCheck = new QCheckBox("Collapse items by default", displayGroup);
m_collapseDefaultCheck->setToolTip("New items are collapsed when added to the tree.");
displayLayout->addWidget(m_collapseDefaultCheck);
layout->addWidget(displayGroup);
// Organization group
auto *orgGroup = new QGroupBox("Organization", page);
auto *orgLayout = new QVBoxLayout(orgGroup);
orgLayout->setSpacing(10);
m_groupByExtCheck = new QCheckBox("Group resources by file extension", orgGroup);
m_groupByExtCheck->setToolTip("Organize resources into subfolders based on file extension (.wav, .tga, etc.)");
orgLayout->addWidget(m_groupByExtCheck);
m_naturalSortCheck = new QCheckBox("Natural sorting (1, 2, 10 instead of 1, 10, 2)", orgGroup);
m_naturalSortCheck->setToolTip("Sort items numerically when they contain numbers.");
orgLayout->addWidget(m_naturalSortCheck);
layout->addWidget(orgGroup);
layout->addStretch();
m_pageStack->addWidget(page);
}
void PreferenceEditor::createPreviewPage()
{
auto *page = new QWidget(this);
auto *layout = new QVBoxLayout(page);
layout->setSpacing(15);
// Title
auto *titleLabel = new QLabel("Preview Settings", page);
titleLabel->setStyleSheet("font-size: 14px; font-weight: bold; color: #ad0c0c;");
layout->addWidget(titleLabel);
// Audio group
auto *audioGroup = new QGroupBox("Audio Preview", page);
auto *audioLayout = new QVBoxLayout(audioGroup);
audioLayout->setSpacing(10);
m_audioAutoPlayCheck = new QCheckBox("Auto-play audio when selected", audioGroup);
audioLayout->addWidget(m_audioAutoPlayCheck);
layout->addWidget(audioGroup);
// Image group
auto *imageGroup = new QGroupBox("Image Preview", page);
auto *imageLayout = new QVBoxLayout(imageGroup);
imageLayout->setSpacing(10);
m_imageShowGridCheck = new QCheckBox("Show transparency grid", imageGroup);
m_imageShowGridCheck->setToolTip("Display a checkered grid behind transparent images.");
imageLayout->addWidget(m_imageShowGridCheck);
m_imageAutoZoomCheck = new QCheckBox("Auto-zoom to fit", imageGroup);
m_imageAutoZoomCheck->setToolTip("Automatically scale images to fit the preview area.");
imageLayout->addWidget(m_imageAutoZoomCheck);
layout->addWidget(imageGroup);
layout->addStretch();
m_pageStack->addWidget(page);
}
void PreferenceEditor::loadSettings()
{
Settings &s = Settings::instance();
// Appearance
int themeIdx = m_themeCombo->findText(s.currentTheme());
if (themeIdx >= 0) m_themeCombo->setCurrentIndex(themeIdx);
updateThemePreview();
// General
m_exportDirEdit->setText(s.exportDirectory());
m_autoExportCheck->setChecked(s.autoExportOnParse());
// Tools
QString qbmsPath = s.quickBmsPath();
m_quickBmsEdit->setText(qbmsPath);
// Trigger status update
emit m_quickBmsEdit->textChanged(qbmsPath);
int fontIdx = m_fontFamilyCombo->findText(s.fontFamily());
if (fontIdx >= 0) m_fontFamilyCombo->setCurrentIndex(fontIdx);
m_fontSizeSpin->setValue(s.fontSize());
m_zoomSpin->setValue(s.viewZoom());
// Debug
m_debugLoggingCheck->setChecked(s.debugLoggingEnabled());
m_verboseParsingCheck->setChecked(s.verboseParsingEnabled());
m_logToFileCheck->setChecked(s.logToFileEnabled());
// Tree
m_showCountsCheck->setChecked(s.showItemCounts());
m_collapseDefaultCheck->setChecked(s.collapseByDefault());
m_groupByExtCheck->setChecked(s.groupByExtension());
m_naturalSortCheck->setChecked(s.naturalSorting());
// Previews
m_audioAutoPlayCheck->setChecked(s.audioAutoPlay());
m_imageShowGridCheck->setChecked(s.imageShowGrid());
m_imageAutoZoomCheck->setChecked(s.imageAutoZoom());
}
void PreferenceEditor::saveSettings()
{
Settings &s = Settings::instance();
// Appearance
s.setCurrentTheme(m_themeCombo->currentText());
// General
s.setExportDirectory(m_exportDirEdit->text());
s.setAutoExportOnParse(m_autoExportCheck->isChecked());
// Tools
QString quickBmsPath = m_quickBmsEdit->text();
s.setQuickBmsPath(quickBmsPath);
Compression::setQuickBmsPath(quickBmsPath);
s.setFontFamily(m_fontFamilyCombo->currentText());
s.setFontSize(m_fontSizeSpin->value());
s.setViewZoom(m_zoomSpin->value());
2025-09-05 20:44:31 +00:00
// Debug
s.setDebugLoggingEnabled(m_debugLoggingCheck->isChecked());
s.setVerboseParsingEnabled(m_verboseParsingCheck->isChecked());
s.setLogToFileEnabled(m_logToFileCheck->isChecked());
2025-09-05 20:44:31 +00:00
// Tree
s.setShowItemCounts(m_showCountsCheck->isChecked());
s.setCollapseByDefault(m_collapseDefaultCheck->isChecked());
s.setGroupByExtension(m_groupByExtCheck->isChecked());
s.setNaturalSorting(m_naturalSortCheck->isChecked());
// Previews
s.setAudioAutoPlay(m_audioAutoPlayCheck->isChecked());
s.setImageShowGrid(m_imageShowGridCheck->isChecked());
s.setImageAutoZoom(m_imageAutoZoomCheck->isChecked());
s.sync();
}
void PreferenceEditor::applyStylesheet()
{
setStyleSheet(R"(
QDialog {
background-color: #1e1e1e;
color: #d4d4d4;
}
QListWidget {
background-color: #252526;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 5px;
}
QListWidget::item {
padding: 8px;
border-radius: 3px;
}
QListWidget::item:selected {
background-color: #ad0c0c;
color: white;
}
QListWidget::item:hover:!selected {
background-color: #2d2d30;
}
QGroupBox {
font-weight: bold;
border: 1px solid #3c3c3c;
border-radius: 4px;
margin-top: 10px;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px;
}
QCheckBox {
spacing: 8px;
}
QCheckBox::indicator {
width: 16px;
height: 16px;
}
QCheckBox::indicator:unchecked {
border: 1px solid #5c5c5c;
background-color: #2d2d30;
border-radius: 3px;
}
QCheckBox::indicator:checked {
background-color: #ad0c0c;
border: 1px solid #ad0c0c;
border-radius: 3px;
2025-09-05 20:44:31 +00:00
}
QLineEdit, QComboBox, QSpinBox {
background-color: #2d2d30;
border: 1px solid #3c3c3c;
border-radius: 3px;
padding: 5px;
min-height: 20px;
}
QLineEdit:focus, QComboBox:focus, QSpinBox:focus {
border-color: #ad0c0c;
}
QComboBox::drop-down {
border: none;
padding-right: 5px;
}
QPushButton {
background-color: #3c3c3c;
border: 1px solid #5c5c5c;
border-radius: 3px;
padding: 6px 15px;
min-width: 70px;
}
QPushButton:hover {
background-color: #4c4c4c;
}
QPushButton:pressed {
background-color: #ad0c0c;
}
QDialogButtonBox QPushButton {
min-width: 80px;
}
)");
}
void PreferenceEditor::onCategoryChanged()
{
m_pageStack->setCurrentIndex(m_categoryList->currentRow());
}
void PreferenceEditor::onApply()
{
saveSettings();
}
void PreferenceEditor::onAccept()
{
saveSettings();
accept();
2025-09-05 20:44:31 +00:00
}
void PreferenceEditor::onBrowseExportDir()
2025-09-05 20:44:31 +00:00
{
QString dir = QFileDialog::getExistingDirectory(this, "Select Export Directory",
m_exportDirEdit->text());
if (!dir.isEmpty()) {
m_exportDirEdit->setText(dir);
}
2025-09-05 20:44:31 +00:00
}
void PreferenceEditor::onBrowseQuickBms()
{
QString startDir = m_quickBmsEdit->text();
if (startDir.isEmpty()) {
startDir = QDir::homePath();
} else {
startDir = QFileInfo(startDir).absolutePath();
}
QString file = QFileDialog::getOpenFileName(
this,
"Locate QuickBMS Executable",
startDir,
"QuickBMS (quickbms.exe);;All Files (*.*)"
);
if (!file.isEmpty()) {
m_quickBmsEdit->setText(file);
}
}
void PreferenceEditor::onResetSettings()
{
QMessageBox::StandardButton reply = QMessageBox::question(
this,
"Reset Settings",
"Are you sure you want to reset all settings to defaults?\n\n"
"This will restore the default theme and clear all customizations.",
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No
);
if (reply == QMessageBox::Yes) {
Settings::instance().resetToDefaults();
loadSettings(); // Reload UI with defaults
QMessageBox::information(this, "Settings Reset", "All settings have been reset to defaults.");
}
}