#include "preferenceeditor.h" #include "settings.h" #include "compression.h" #include #include #include #include #include #include #include #include #include #include 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()); // Debug s.setDebugLoggingEnabled(m_debugLoggingCheck->isChecked()); s.setVerboseParsingEnabled(m_verboseParsingCheck->isChecked()); s.setLogToFileEnabled(m_logToFileCheck->isChecked()); // 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; } 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(); } void PreferenceEditor::onBrowseExportDir() { QString dir = QFileDialog::getExistingDirectory(this, "Select Export Directory", m_exportDirEdit->text()); if (!dir.isEmpty()) { m_exportDirEdit->setText(dir); } } 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."); } }