2025-09-05 20:44:31 +00:00
|
|
|
#include "preferenceeditor.h"
|
2026-01-08 00:36:50 -05:00
|
|
|
#include "settings.h"
|
2026-01-08 00:54:57 -05:00
|
|
|
#include "compression.h"
|
2026-01-08 00:36:50 -05:00
|
|
|
|
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
|
#include <QGroupBox>
|
|
|
|
|
#include <QDialogButtonBox>
|
|
|
|
|
#include <QFontDatabase>
|
|
|
|
|
#include <QFileDialog>
|
2026-01-08 00:54:57 -05:00
|
|
|
#include <QFileInfo>
|
2026-01-08 00:36:50 -05:00
|
|
|
#include <QFormLayout>
|
|
|
|
|
#include <QFrame>
|
2026-01-08 00:43:52 -05:00
|
|
|
#include <QMessageBox>
|
2025-09-05 20:44:31 +00:00
|
|
|
|
|
|
|
|
PreferenceEditor::PreferenceEditor(QWidget *parent)
|
|
|
|
|
: QDialog(parent)
|
|
|
|
|
{
|
2026-01-08 00:36:50 -05:00
|
|
|
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");
|
2026-01-08 00:54:57 -05:00
|
|
|
m_categoryList->addItem("Tools");
|
2026-01-08 00:36:50 -05:00
|
|
|
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();
|
2026-01-08 00:54:57 -05:00
|
|
|
createToolsPage();
|
2026-01-08 00:36:50 -05:00
|
|
|
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);
|
2026-01-08 00:43:52 -05:00
|
|
|
|
|
|
|
|
// 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);
|
2026-01-08 00:36:50 -05:00
|
|
|
layout->addStretch();
|
|
|
|
|
|
|
|
|
|
m_pageStack->addWidget(page);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-08 00:54:57 -05:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-08 00:36:50 -05:00
|
|
|
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);
|
|
|
|
|
|
2026-01-11 12:09:31 -05:00
|
|
|
// File Type Associations group
|
|
|
|
|
auto *fileTypeGroup = new QGroupBox("File Type Associations", page);
|
|
|
|
|
auto *fileTypeLayout = new QFormLayout(fileTypeGroup);
|
|
|
|
|
fileTypeLayout->setSpacing(10);
|
|
|
|
|
|
|
|
|
|
m_textExtensionsEdit = new QLineEdit(fileTypeGroup);
|
|
|
|
|
m_textExtensionsEdit->setPlaceholderText("txt, xml, json, cfg, lua...");
|
|
|
|
|
m_textExtensionsEdit->setToolTip("Comma-separated list of extensions to open in text viewer");
|
|
|
|
|
fileTypeLayout->addRow("Text Viewer:", m_textExtensionsEdit);
|
|
|
|
|
|
|
|
|
|
m_imageExtensionsEdit = new QLineEdit(fileTypeGroup);
|
|
|
|
|
m_imageExtensionsEdit->setPlaceholderText("tga, dds, png, jpg...");
|
|
|
|
|
m_imageExtensionsEdit->setToolTip("Comma-separated list of extensions to open in image viewer");
|
|
|
|
|
fileTypeLayout->addRow("Image Viewer:", m_imageExtensionsEdit);
|
|
|
|
|
|
|
|
|
|
m_audioExtensionsEdit = new QLineEdit(fileTypeGroup);
|
|
|
|
|
m_audioExtensionsEdit->setPlaceholderText("wav, mp3, ogg...");
|
|
|
|
|
m_audioExtensionsEdit->setToolTip("Comma-separated list of extensions to open in audio player");
|
|
|
|
|
fileTypeLayout->addRow("Audio Player:", m_audioExtensionsEdit);
|
|
|
|
|
|
|
|
|
|
auto *fileTypeDesc = new QLabel(
|
|
|
|
|
"File types not listed above will open in the hex viewer. "
|
|
|
|
|
"Enter extensions without dots, separated by commas.",
|
|
|
|
|
fileTypeGroup);
|
|
|
|
|
fileTypeDesc->setWordWrap(true);
|
|
|
|
|
fileTypeDesc->setStyleSheet("color: #888; font-size: 11px;");
|
|
|
|
|
fileTypeLayout->addRow("", fileTypeDesc);
|
|
|
|
|
|
|
|
|
|
layout->addWidget(fileTypeGroup);
|
|
|
|
|
|
2026-01-08 00:36:50 -05:00
|
|
|
// 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());
|
|
|
|
|
|
2026-01-08 00:54:57 -05:00
|
|
|
// Tools
|
|
|
|
|
QString qbmsPath = s.quickBmsPath();
|
|
|
|
|
m_quickBmsEdit->setText(qbmsPath);
|
|
|
|
|
// Trigger status update
|
|
|
|
|
emit m_quickBmsEdit->textChanged(qbmsPath);
|
|
|
|
|
|
2026-01-08 00:36:50 -05:00
|
|
|
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());
|
2026-01-11 12:09:31 -05:00
|
|
|
|
|
|
|
|
// File Type Associations
|
|
|
|
|
m_textExtensionsEdit->setText(s.textFileExtensions().join(", "));
|
|
|
|
|
m_imageExtensionsEdit->setText(s.imageFileExtensions().join(", "));
|
|
|
|
|
m_audioExtensionsEdit->setText(s.audioFileExtensions().join(", "));
|
2026-01-08 00:36:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PreferenceEditor::saveSettings()
|
|
|
|
|
{
|
|
|
|
|
Settings &s = Settings::instance();
|
|
|
|
|
|
|
|
|
|
// Appearance
|
|
|
|
|
s.setCurrentTheme(m_themeCombo->currentText());
|
|
|
|
|
|
|
|
|
|
// General
|
|
|
|
|
s.setExportDirectory(m_exportDirEdit->text());
|
|
|
|
|
s.setAutoExportOnParse(m_autoExportCheck->isChecked());
|
2026-01-08 00:54:57 -05:00
|
|
|
|
|
|
|
|
// Tools
|
|
|
|
|
QString quickBmsPath = m_quickBmsEdit->text();
|
|
|
|
|
s.setQuickBmsPath(quickBmsPath);
|
|
|
|
|
Compression::setQuickBmsPath(quickBmsPath);
|
|
|
|
|
|
2026-01-08 00:36:50 -05:00
|
|
|
s.setFontFamily(m_fontFamilyCombo->currentText());
|
|
|
|
|
s.setFontSize(m_fontSizeSpin->value());
|
|
|
|
|
s.setViewZoom(m_zoomSpin->value());
|
2025-09-05 20:44:31 +00:00
|
|
|
|
2026-01-08 00:36:50 -05: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
|
|
|
|
2026-01-08 00:36:50 -05: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());
|
|
|
|
|
|
2026-01-11 12:09:31 -05:00
|
|
|
// File Type Associations - parse comma-separated lists
|
|
|
|
|
auto parseExtList = [](const QString& text) -> QStringList {
|
|
|
|
|
QStringList result;
|
|
|
|
|
for (QString ext : text.split(',', Qt::SkipEmptyParts)) {
|
|
|
|
|
ext = ext.trimmed().toLower();
|
|
|
|
|
if (ext.startsWith('.')) ext = ext.mid(1);
|
|
|
|
|
if (!ext.isEmpty()) result.append(ext);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
s.setTextFileExtensions(parseExtList(m_textExtensionsEdit->text()));
|
|
|
|
|
s.setImageFileExtensions(parseExtList(m_imageExtensionsEdit->text()));
|
|
|
|
|
s.setAudioFileExtensions(parseExtList(m_audioExtensionsEdit->text()));
|
|
|
|
|
|
2026-01-08 00:36:50 -05:00
|
|
|
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
|
|
|
}
|
2026-01-08 00:36:50 -05: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;
|
|
|
|
|
}
|
|
|
|
|
)");
|
|
|
|
|
}
|
2025-12-19 23:06:03 -05:00
|
|
|
|
2026-01-08 00:36:50 -05:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2026-01-08 00:36:50 -05:00
|
|
|
void PreferenceEditor::onBrowseExportDir()
|
2025-09-05 20:44:31 +00:00
|
|
|
{
|
2026-01-08 00:36:50 -05: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
|
|
|
}
|
2026-01-08 00:43:52 -05:00
|
|
|
|
2026-01-08 00:54:57 -05: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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-08 00:43:52 -05:00
|
|
|
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.");
|
|
|
|
|
}
|
|
|
|
|
}
|