#include "settings.h" #include "logmanager.h" #include #include #include #include // Built-in themes static const QMap s_themes = { {"XPlor Dark", { "XPlor Dark", "#ad0c0c", // accentColor (red) "#8a0a0a", // accentColorDark "#c41010", // accentColorLight "#1e1e1e", // backgroundColor "#2d2d30", // panelColor "#3c3c3c", // borderColor "#d4d4d4", // textColor "#888888" // textColorMuted }}, {"Midnight Blue", { "Midnight Blue", "#0078d4", // accentColor (blue) "#005a9e", // accentColorDark "#1890ff", // accentColorLight "#1a1a2e", // backgroundColor "#16213e", // panelColor "#0f3460", // borderColor "#e0e0e0", // textColor "#7f8c8d" // textColorMuted }}, {"Forest Green", { "Forest Green", "#2e7d32", // accentColor (green) "#1b5e20", // accentColorDark "#43a047", // accentColorLight "#1a1f1a", // backgroundColor "#2d332d", // panelColor "#3c4a3c", // borderColor "#d4d8d4", // textColor "#88918a" // textColorMuted }}, {"Purple Haze", { "Purple Haze", "#7b1fa2", // accentColor (purple) "#4a148c", // accentColorDark "#9c27b0", // accentColorLight "#1a1a1e", // backgroundColor "#2d2d35", // panelColor "#3c3c4a", // borderColor "#d4d4dc", // textColor "#8888a0" // textColorMuted }}, {"Orange Sunset", { "Orange Sunset", "#e65100", // accentColor (orange) "#bf360c", // accentColorDark "#ff6d00", // accentColorLight "#1e1a18", // backgroundColor "#302820", // panelColor "#4a3c30", // borderColor "#d8d4d0", // textColor "#908880" // textColorMuted }}, {"Classic Dark", { "Classic Dark", "#505050", // accentColor (gray) "#404040", // accentColorDark "#606060", // accentColorLight "#1e1e1e", // backgroundColor "#252526", // panelColor "#3c3c3c", // borderColor "#d4d4d4", // textColor "#888888" // textColorMuted }} }; Settings& Settings::instance() { static Settings instance; return instance; } Settings::Settings(QObject *parent) : QObject(parent) , m_settings(QSettings::NativeFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()) { // Set up debug checker for LogManager LogManager::instance().setDebugChecker([this]() { return debugLoggingEnabled(); }); // Set up log-to-file checker for LogManager LogManager::instance().setLogToFileChecker([this]() { return logToFileEnabled(); }); } void Settings::sync() { m_settings.sync(); } // Theme QString Settings::currentTheme() const { return m_settings.value("Appearance/Theme", "XPlor Dark").toString(); } void Settings::setCurrentTheme(const QString& themeName) { if (s_themes.contains(themeName)) { m_settings.setValue("Appearance/Theme", themeName); emit themeChanged(s_themes[themeName]); emit settingsChanged(); } } Theme Settings::theme() const { return getTheme(currentTheme()); } QStringList Settings::availableThemes() const { return s_themes.keys(); } Theme Settings::getTheme(const QString& name) { if (s_themes.contains(name)) { return s_themes[name]; } return s_themes["XPlor Dark"]; } // General QString Settings::exportDirectory() const { QString defaultPath = QCoreApplication::applicationDirPath() + "/exports"; return m_settings.value("General/ExportDirectory", defaultPath).toString(); } void Settings::setExportDirectory(const QString& path) { m_settings.setValue("General/ExportDirectory", path); emit settingsChanged(); } bool Settings::autoExportOnParse() const { return m_settings.value("General/AutoExportOnParse", true).toBool(); } void Settings::setAutoExportOnParse(bool enable) { m_settings.setValue("General/AutoExportOnParse", enable); emit settingsChanged(); } // Tools QString Settings::quickBmsPath() const { QString path = m_settings.value("Tools/QuickBmsPath").toString(); if (path.isEmpty() || !QFileInfo::exists(path)) { path = findQuickBms(); } return path; } void Settings::setQuickBmsPath(const QString& path) { m_settings.setValue("Tools/QuickBmsPath", path); emit settingsChanged(); } QString Settings::findQuickBms() { // Common locations to search for QuickBMS QStringList searchPaths = { QCoreApplication::applicationDirPath() + "/quickbms.exe", QCoreApplication::applicationDirPath() + "/tools/quickbms.exe", QCoreApplication::applicationDirPath() + "/quickbms/quickbms.exe", "C:/Program Files/QuickBMS/quickbms.exe", "C:/Program Files (x86)/QuickBMS/quickbms.exe", "C:/QuickBMS/quickbms.exe", QDir::homePath() + "/QuickBMS/quickbms.exe", "E:/Software/QuickBMS/quickbms.exe", // Legacy path }; // Also check PATH environment QString pathEnv = qEnvironmentVariable("PATH"); QStringList pathDirs = pathEnv.split(';', Qt::SkipEmptyParts); for (const QString& dir : pathDirs) { searchPaths.append(dir + "/quickbms.exe"); } for (const QString& path : searchPaths) { if (QFileInfo::exists(path)) { return QDir::cleanPath(path); } } return QString(); // Not found } QString Settings::pythonPath() const { QString path = m_settings.value("Tools/PythonPath").toString(); if (path.isEmpty() || !QFileInfo::exists(path)) { path = findPython(); } return path; } void Settings::setPythonPath(const QString& path) { m_settings.setValue("Tools/PythonPath", path); emit settingsChanged(); } QString Settings::findPython() { // Common locations to search for Python QStringList searchPaths = { "python", "python3", "C:/Python312/python.exe", "C:/Python311/python.exe", "C:/Python310/python.exe", "C:/Program Files/Python312/python.exe", "C:/Program Files/Python311/python.exe", QDir::homePath() + "/AppData/Local/Programs/Python/Python312/python.exe", QDir::homePath() + "/AppData/Local/Programs/Python/Python311/python.exe", "/usr/bin/python3", "/usr/bin/python", }; QString pathEnv = qEnvironmentVariable("PATH"); #ifdef Q_OS_WIN QStringList pathDirs = pathEnv.split(';', Qt::SkipEmptyParts); #else QStringList pathDirs = pathEnv.split(':', Qt::SkipEmptyParts); #endif for (const QString& dir : pathDirs) { searchPaths.append(dir + "/python.exe"); searchPaths.append(dir + "/python3.exe"); } for (const QString& path : searchPaths) { if (QFileInfo::exists(path)) { return QDir::cleanPath(path); } } return QString(); } QString Settings::ffmpegPath() const { QString path = m_settings.value("Tools/FFmpegPath").toString(); if (path.isEmpty() || !QFileInfo::exists(path)) { path = findFFmpeg(); } return path; } void Settings::setFFmpegPath(const QString& path) { m_settings.setValue("Tools/FFmpegPath", path); emit settingsChanged(); } QString Settings::findFFmpeg() { // Common locations to search for FFmpeg QStringList searchPaths = { "ffmpeg", "ffmpeg.exe", "C:/ffmpeg/bin/ffmpeg.exe", "C:/Program Files/ffmpeg/bin/ffmpeg.exe", "C:/Program Files (x86)/ffmpeg/bin/ffmpeg.exe", QDir::homePath() + "/ffmpeg/bin/ffmpeg.exe", "/usr/bin/ffmpeg", "/usr/local/bin/ffmpeg", }; QString pathEnv = qEnvironmentVariable("PATH"); #ifdef Q_OS_WIN QStringList pathDirs = pathEnv.split(';', Qt::SkipEmptyParts); #else QStringList pathDirs = pathEnv.split(':', Qt::SkipEmptyParts); #endif for (const QString& dir : pathDirs) { searchPaths.append(dir + "/ffmpeg.exe"); searchPaths.append(dir + "/ffmpeg"); } for (const QString& path : searchPaths) { if (QFileInfo::exists(path)) { return QDir::cleanPath(path); } } return QString(); } QString Settings::scriptsDirectory() const { QString defaultPath = QCoreApplication::applicationDirPath() + "/scripts"; return m_settings.value("Tools/ScriptsDirectory", defaultPath).toString(); } void Settings::setScriptsDirectory(const QString& path) { m_settings.setValue("Tools/ScriptsDirectory", path); emit settingsChanged(); } // Debug/Logging bool Settings::debugLoggingEnabled() const { return m_settings.value("Debug/LoggingEnabled", false).toBool(); } void Settings::setDebugLoggingEnabled(bool enable) { m_settings.setValue("Debug/LoggingEnabled", enable); m_settings.sync(); // Ensure immediate persistence emit debugLoggingChanged(enable); emit settingsChanged(); // Provide immediate feedback in log panel if (enable) { LogManager::instance().addEntry("[SETTINGS] Debug logging ENABLED - parse a file to see debug output"); } else { LogManager::instance().addEntry("[SETTINGS] Debug logging DISABLED"); } } bool Settings::verboseParsingEnabled() const { return m_settings.value("Debug/VerboseParsing", false).toBool(); } void Settings::setVerboseParsingEnabled(bool enable) { m_settings.setValue("Debug/VerboseParsing", enable); m_settings.sync(); // Ensure immediate persistence emit settingsChanged(); // Provide immediate feedback in log panel if (enable) { LogManager::instance().addEntry("[SETTINGS] Verbose parsing ENABLED"); } else { LogManager::instance().addEntry("[SETTINGS] Verbose parsing DISABLED"); } } bool Settings::logToFileEnabled() const { return m_settings.value("Debug/LogToFile", false).toBool(); } void Settings::setLogToFileEnabled(bool enable) { m_settings.setValue("Debug/LogToFile", enable); m_settings.sync(); // Ensure immediate persistence emit settingsChanged(); // Provide immediate feedback in log panel if (enable) { LogManager::instance().addEntry("[SETTINGS] Log to file ENABLED"); } else { LogManager::instance().addEntry("[SETTINGS] Log to file DISABLED"); } } // View QString Settings::fontFamily() const { return m_settings.value("View/FontFamily", "Segoe UI").toString(); } void Settings::setFontFamily(const QString& family) { m_settings.setValue("View/FontFamily", family); emit settingsChanged(); } int Settings::fontSize() const { return m_settings.value("View/FontSize", 10).toInt(); } void Settings::setFontSize(int size) { m_settings.setValue("View/FontSize", size); emit settingsChanged(); } int Settings::viewZoom() const { return m_settings.value("View/Zoom", 100).toInt(); } void Settings::setViewZoom(int zoom) { m_settings.setValue("View/Zoom", zoom); emit settingsChanged(); } // Tree Widget bool Settings::showItemCounts() const { return m_settings.value("Tree/ShowItemCounts", true).toBool(); } void Settings::setShowItemCounts(bool show) { m_settings.setValue("Tree/ShowItemCounts", show); emit settingsChanged(); } bool Settings::collapseByDefault() const { return m_settings.value("Tree/CollapseByDefault", true).toBool(); } void Settings::setCollapseByDefault(bool collapse) { m_settings.setValue("Tree/CollapseByDefault", collapse); emit settingsChanged(); } bool Settings::groupByExtension() const { return m_settings.value("Tree/GroupByExtension", false).toBool(); } void Settings::setGroupByExtension(bool group) { m_settings.setValue("Tree/GroupByExtension", group); emit settingsChanged(); } bool Settings::naturalSorting() const { return m_settings.value("Tree/NaturalSorting", true).toBool(); } void Settings::setNaturalSorting(bool enable) { m_settings.setValue("Tree/NaturalSorting", enable); emit settingsChanged(); } // Hex Viewer int Settings::hexBytesPerLine() const { return m_settings.value("HexViewer/BytesPerLine", 16).toInt(); } void Settings::setHexBytesPerLine(int bytes) { m_settings.setValue("HexViewer/BytesPerLine", bytes); emit settingsChanged(); } bool Settings::hexShowAscii() const { return m_settings.value("HexViewer/ShowAscii", true).toBool(); } void Settings::setHexShowAscii(bool show) { m_settings.setValue("HexViewer/ShowAscii", show); emit settingsChanged(); } // Audio Preview bool Settings::audioAutoPlay() const { return m_settings.value("Audio/AutoPlay", false).toBool(); } void Settings::setAudioAutoPlay(bool enable) { m_settings.setValue("Audio/AutoPlay", enable); emit settingsChanged(); } // Image Preview bool Settings::imageShowGrid() const { return m_settings.value("Image/ShowGrid", false).toBool(); } void Settings::setImageShowGrid(bool show) { m_settings.setValue("Image/ShowGrid", show); emit settingsChanged(); } bool Settings::imageAutoZoom() const { return m_settings.value("Image/AutoZoom", true).toBool(); } void Settings::setImageAutoZoom(bool enable) { m_settings.setValue("Image/AutoZoom", enable); emit settingsChanged(); } // File Type Associations static const QStringList s_defaultTextExtensions = { "txt", "xml", "json", "csv", "cfg", "ini", "log", "html", "htm", "css", "js", "lua", "py", "sh", "bat", "md", "yaml", "yml", "gsc", "csc", "arena", "vision" }; static const QStringList s_defaultImageExtensions = { "tga", "dds", "png", "jpg", "jpeg", "bmp", "xbtex", "iwi" }; static const QStringList s_defaultAudioExtensions = { "wav", "wave", "mp3", "ogg", "flac", "raw" }; static const QStringList s_defaultListExtensions = { "str" }; QStringList Settings::textFileExtensions() const { QStringList exts = m_settings.value("FileTypes/Text").toStringList(); if (exts.isEmpty()) { return s_defaultTextExtensions; } return exts; } void Settings::setTextFileExtensions(const QStringList& extensions) { m_settings.setValue("FileTypes/Text", extensions); emit settingsChanged(); } QStringList Settings::imageFileExtensions() const { QStringList exts = m_settings.value("FileTypes/Image").toStringList(); if (exts.isEmpty()) { return s_defaultImageExtensions; } return exts; } void Settings::setImageFileExtensions(const QStringList& extensions) { m_settings.setValue("FileTypes/Image", extensions); emit settingsChanged(); } QStringList Settings::audioFileExtensions() const { QStringList exts = m_settings.value("FileTypes/Audio").toStringList(); if (exts.isEmpty()) { return s_defaultAudioExtensions; } return exts; } void Settings::setAudioFileExtensions(const QStringList& extensions) { m_settings.setValue("FileTypes/Audio", extensions); emit settingsChanged(); } QStringList Settings::listFileExtensions() const { QStringList exts = m_settings.value("FileTypes/List").toStringList(); if (exts.isEmpty()) { return s_defaultListExtensions; } return exts; } void Settings::setListFileExtensions(const QStringList& extensions) { m_settings.setValue("FileTypes/List", extensions); emit settingsChanged(); } QString Settings::viewerForExtension(const QString& extension) const { QString ext = extension.toLower(); if (ext.startsWith('.')) { ext = ext.mid(1); } if (textFileExtensions().contains(ext, Qt::CaseInsensitive)) { return "text"; } if (imageFileExtensions().contains(ext, Qt::CaseInsensitive)) { return "image"; } if (audioFileExtensions().contains(ext, Qt::CaseInsensitive)) { return "audio"; } if (listFileExtensions().contains(ext, Qt::CaseInsensitive)) { return "list"; } return "hex"; // Default to hex viewer } void Settings::setViewerForExtension(const QString& extension, const QString& viewer) { QString ext = extension.toLower(); if (ext.startsWith('.')) { ext = ext.mid(1); } // Remove from all lists first QStringList textExts = textFileExtensions(); QStringList imageExts = imageFileExtensions(); QStringList audioExts = audioFileExtensions(); QStringList listExts = listFileExtensions(); textExts.removeAll(ext); imageExts.removeAll(ext); audioExts.removeAll(ext); listExts.removeAll(ext); // Add to appropriate list if (viewer == "text") { textExts.append(ext); } else if (viewer == "image") { imageExts.append(ext); } else if (viewer == "audio") { audioExts.append(ext); } else if (viewer == "list") { listExts.append(ext); } // "hex" means don't add to any list setTextFileExtensions(textExts); setImageFileExtensions(imageExts); setAudioFileExtensions(audioExts); setListFileExtensions(listExts); } // Export Settings QString Settings::defaultImageExportFormat() const { return m_settings.value("Export/ImageFormat", "png").toString(); } void Settings::setDefaultImageExportFormat(const QString& format) { m_settings.setValue("Export/ImageFormat", format); emit settingsChanged(); } QString Settings::defaultAudioExportFormat() const { return m_settings.value("Export/AudioFormat", "wav").toString(); } void Settings::setDefaultAudioExportFormat(const QString& format) { m_settings.setValue("Export/AudioFormat", format); emit settingsChanged(); } int Settings::imageJpegQuality() const { return m_settings.value("Export/JpegQuality", 90).toInt(); } void Settings::setImageJpegQuality(int quality) { m_settings.setValue("Export/JpegQuality", qBound(1, quality, 100)); emit settingsChanged(); } int Settings::imagePngCompression() const { return m_settings.value("Export/PngCompression", 6).toInt(); } void Settings::setImagePngCompression(int level) { m_settings.setValue("Export/PngCompression", qBound(0, level, 9)); emit settingsChanged(); } int Settings::audioMp3Bitrate() const { return m_settings.value("Export/Mp3Bitrate", 256).toInt(); } void Settings::setAudioMp3Bitrate(int bitrate) { m_settings.setValue("Export/Mp3Bitrate", bitrate); emit settingsChanged(); } int Settings::audioOggQuality() const { return m_settings.value("Export/OggQuality", 5).toInt(); } void Settings::setAudioOggQuality(int quality) { m_settings.setValue("Export/OggQuality", qBound(-1, quality, 10)); emit settingsChanged(); } int Settings::audioFlacCompression() const { return m_settings.value("Export/FlacCompression", 5).toInt(); } void Settings::setAudioFlacCompression(int level) { m_settings.setValue("Export/FlacCompression", qBound(0, level, 8)); emit settingsChanged(); } bool Settings::exportRememberSettings() const { return m_settings.value("Export/RememberSettings", true).toBool(); } void Settings::setExportRememberSettings(bool remember) { m_settings.setValue("Export/RememberSettings", remember); emit settingsChanged(); } QString Settings::batchExportDirectory() const { return m_settings.value("Export/BatchDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).toString(); } void Settings::setBatchExportDirectory(const QString& path) { m_settings.setValue("Export/BatchDirectory", path); emit settingsChanged(); } bool Settings::batchExportPreserveStructure() const { return m_settings.value("Export/PreserveStructure", true).toBool(); } void Settings::setBatchExportPreserveStructure(bool preserve) { m_settings.setValue("Export/PreserveStructure", preserve); emit settingsChanged(); } QString Settings::batchExportConflictResolution() const { return m_settings.value("Export/ConflictResolution", "number").toString(); } void Settings::setBatchExportConflictResolution(const QString& resolution) { m_settings.setValue("Export/ConflictResolution", resolution); emit settingsChanged(); } // Window State QByteArray Settings::windowGeometry() const { return m_settings.value("Window/Geometry").toByteArray(); } void Settings::setWindowGeometry(const QByteArray& geometry) { m_settings.setValue("Window/Geometry", geometry); } QByteArray Settings::windowState() const { return m_settings.value("Window/State").toByteArray(); } void Settings::setWindowState(const QByteArray& state) { m_settings.setValue("Window/State", state); } // Recent Files QStringList Settings::recentFiles() const { return m_settings.value("RecentFiles/List").toStringList(); } void Settings::addRecentFile(const QString& path) { QStringList files = recentFiles(); files.removeAll(path); files.prepend(path); while (files.size() > 10) { files.removeLast(); } m_settings.setValue("RecentFiles/List", files); } void Settings::clearRecentFiles() { m_settings.setValue("RecentFiles/List", QStringList()); } void Settings::resetToDefaults() { // Clear all settings m_settings.clear(); // Apply default theme immediately emit themeChanged(s_themes["XPlor Dark"]); emit settingsChanged(); sync(); }