diff --git a/app/settings.cpp b/app/settings.cpp new file mode 100644 index 0000000..5b1cb77 --- /dev/null +++ b/app/settings.cpp @@ -0,0 +1,370 @@ +#include "settings.h" +#include "logmanager.h" +#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(); + }); +} + +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(); +} + +// 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); + emit debugLoggingChanged(enable); + emit settingsChanged(); +} + +bool Settings::verboseParsingEnabled() const +{ + return m_settings.value("Debug/VerboseParsing", false).toBool(); +} + +void Settings::setVerboseParsingEnabled(bool enable) +{ + m_settings.setValue("Debug/VerboseParsing", enable); + emit settingsChanged(); +} + +bool Settings::logToFileEnabled() const +{ + return m_settings.value("Debug/LogToFile", false).toBool(); +} + +void Settings::setLogToFileEnabled(bool enable) +{ + m_settings.setValue("Debug/LogToFile", enable); + emit settingsChanged(); +} + +// 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(); +} + +// 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()); +} diff --git a/app/settings.h b/app/settings.h new file mode 100644 index 0000000..73f80aa --- /dev/null +++ b/app/settings.h @@ -0,0 +1,124 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include +#include +#include + +// Theme definition +struct Theme { + QString name; + QString accentColor; + QString accentColorDark; + QString accentColorLight; + QString backgroundColor; + QString panelColor; + QString borderColor; + QString textColor; + QString textColorMuted; +}; + +class Settings : public QObject +{ + Q_OBJECT + +public: + static Settings& instance(); + + // Theme + QString currentTheme() const; + void setCurrentTheme(const QString& themeName); + Theme theme() const; + QStringList availableThemes() const; + static Theme getTheme(const QString& name); + + // General + QString exportDirectory() const; + void setExportDirectory(const QString& path); + + bool autoExportOnParse() const; + void setAutoExportOnParse(bool enable); + + // Debug/Logging + bool debugLoggingEnabled() const; + void setDebugLoggingEnabled(bool enable); + + bool verboseParsingEnabled() const; + void setVerboseParsingEnabled(bool enable); + + bool logToFileEnabled() const; + void setLogToFileEnabled(bool enable); + + // View + QString fontFamily() const; + void setFontFamily(const QString& family); + + int fontSize() const; + void setFontSize(int size); + + int viewZoom() const; + void setViewZoom(int zoom); + + // Tree Widget + bool showItemCounts() const; + void setShowItemCounts(bool show); + + bool collapseByDefault() const; + void setCollapseByDefault(bool collapse); + + bool groupByExtension() const; + void setGroupByExtension(bool group); + + bool naturalSorting() const; + void setNaturalSorting(bool enable); + + // Hex Viewer + int hexBytesPerLine() const; + void setHexBytesPerLine(int bytes); + + bool hexShowAscii() const; + void setHexShowAscii(bool show); + + // Audio Preview + bool audioAutoPlay() const; + void setAudioAutoPlay(bool enable); + + // Image Preview + bool imageShowGrid() const; + void setImageShowGrid(bool show); + + bool imageAutoZoom() const; + void setImageAutoZoom(bool enable); + + // Window State + QByteArray windowGeometry() const; + void setWindowGeometry(const QByteArray& geometry); + + QByteArray windowState() const; + void setWindowState(const QByteArray& state); + + // Recent Files + QStringList recentFiles() const; + void addRecentFile(const QString& path); + void clearRecentFiles(); + + // Sync to disk + void sync(); + +signals: + void debugLoggingChanged(bool enabled); + void themeChanged(const Theme& theme); + void settingsChanged(); + +private: + explicit Settings(QObject *parent = nullptr); + ~Settings() = default; + Settings(const Settings&) = delete; + Settings& operator=(const Settings&) = delete; + + QSettings m_settings; +}; + +#endif // SETTINGS_H diff --git a/app/splashscreen.cpp b/app/splashscreen.cpp new file mode 100644 index 0000000..e0c904f --- /dev/null +++ b/app/splashscreen.cpp @@ -0,0 +1,219 @@ +#include "splashscreen.h" +#include "settings.h" + +#include +#include +#include +#include +#include +#include + +SplashScreen::SplashScreen(QWidget *parent) + : QSplashScreen() +{ + Q_UNUSED(parent); + + // Load theme colors + loadThemeColors(); + + // Create transparent pixmap + QPixmap pixmap(WIDTH, HEIGHT); + pixmap.fill(Qt::transparent); + setPixmap(pixmap); + + setWindowFlags(Qt::SplashScreen | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + setAttribute(Qt::WA_TranslucentBackground); + + // Center on screen + if (QScreen *screen = QApplication::primaryScreen()) { + QRect screenGeometry = screen->geometry(); + int x = (screenGeometry.width() - WIDTH) / 2; + int y = (screenGeometry.height() - HEIGHT) / 2; + move(x, y); + } +} + +void SplashScreen::loadThemeColors() +{ + Theme theme = Settings::instance().theme(); + mPrimaryColor = QColor(theme.accentColor); + mBgColor = QColor(theme.backgroundColor); + mPanelColor = QColor(theme.panelColor); + mTextColor = QColor(theme.textColor); + mTextColorMuted = QColor(theme.textColorMuted); + mBorderColor = QColor(theme.borderColor); +} + +void SplashScreen::setStatus(const QString &message) +{ + mStatus = message; + repaint(); + QApplication::processEvents(); +} + +void SplashScreen::setProgress(int value, int max) +{ + mProgress = value; + mProgressMax = max; + repaint(); + QApplication::processEvents(); +} + +void SplashScreen::setWaitForInteraction(bool wait) +{ + mWaitForInteraction = wait; +} + +void SplashScreen::finish(QWidget *mainWindow) +{ + // Always show the main window + if (mainWindow) { + mainWindow->show(); + } + + if (mWaitForInteraction && !mInteractionReceived) { + // Keep splash visible on top until user interacts + mPendingMainWindow = mainWindow; + raise(); + activateWindow(); + mStatus = "Click or press any key to continue..."; + repaint(); + return; + } + + // Normal behavior - close splash + close(); +} + +void SplashScreen::mousePressEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + mInteractionReceived = true; + + if (mWaitForInteraction && mPendingMainWindow) { + close(); // Just close the splash + } +} + +void SplashScreen::keyPressEvent(QKeyEvent *event) +{ + Q_UNUSED(event); + mInteractionReceived = true; + + if (mWaitForInteraction && mPendingMainWindow) { + close(); // Just close the splash + } +} + +void SplashScreen::drawContents(QPainter *painter) +{ + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setRenderHint(QPainter::TextAntialiasing, true); + + // Create clipping path for rounded corners + QPainterPath clipPath; + clipPath.addRoundedRect(0, 0, WIDTH, HEIGHT, 12, 12); + painter->setClipPath(clipPath); + + // Draw background + painter->setPen(Qt::NoPen); + painter->setBrush(mBgColor); + painter->drawRect(0, 0, WIDTH, HEIGHT); + + // Draw accent stripe at top (clipped to rounded corners) + painter->setBrush(mPrimaryColor); + painter->drawRect(0, 0, WIDTH, 8); + + // Draw logo/title area + QFont titleFont("Segoe UI", 36, QFont::Bold); + painter->setFont(titleFont); + painter->setPen(mTextColor); + + // App name with primary color accent + QString appName = "XPlor"; + QFontMetrics titleMetrics(titleFont); + int titleWidth = titleMetrics.horizontalAdvance(appName); + int titleX = (WIDTH - titleWidth) / 2; + int titleY = 80; + + // Draw "X" in primary color + painter->setPen(mPrimaryColor); + painter->drawText(titleX, titleY, "X"); + + // Draw "Plor" in text color + painter->setPen(mTextColor); + int xWidth = titleMetrics.horizontalAdvance("X"); + painter->drawText(titleX + xWidth, titleY, "Plor"); + + // Tagline + QFont taglineFont("Segoe UI", 11); + painter->setFont(taglineFont); + painter->setPen(mTextColorMuted); + QString tagline = "Binary File Format Explorer"; + QFontMetrics taglineMetrics(taglineFont); + int taglineWidth = taglineMetrics.horizontalAdvance(tagline); + painter->drawText((WIDTH - taglineWidth) / 2, titleY + 25, tagline); + + // Version info + QFont versionFont("Segoe UI", 10); + painter->setFont(versionFont); + painter->setPen(mTextColorMuted); + + QString version = QString("Version %1").arg(QCoreApplication::applicationVersion()); + QFontMetrics versionMetrics(versionFont); + int versionWidth = versionMetrics.horizontalAdvance(version); + painter->drawText((WIDTH - versionWidth) / 2, titleY + 50, version); + + // Company/copyright - use QCoreApplication values + QFont copyrightFont("Segoe UI", 9); + painter->setFont(copyrightFont); + painter->setPen(mTextColorMuted); + QString orgName = QCoreApplication::organizationName(); + QFontMetrics copyrightMetrics(copyrightFont); + int copyrightWidth = copyrightMetrics.horizontalAdvance(orgName); + painter->drawText((WIDTH - copyrightWidth) / 2, HEIGHT - 45, orgName); + + QString year = QString::number(QDate::currentDate().year()); + int yearWidth = copyrightMetrics.horizontalAdvance(year); + painter->drawText((WIDTH - yearWidth) / 2, HEIGHT - 30, year); + + // Progress bar background + int progressX = 40; + int progressY = HEIGHT - 80; + int progressWidth = WIDTH - 80; + int progressHeight = 6; + + painter->setPen(Qt::NoPen); + painter->setBrush(mPanelColor); + painter->drawRoundedRect(progressX, progressY, progressWidth, progressHeight, 3, 3); + + // Progress bar fill + if (mProgressMax > 0 && mProgress > 0) { + int fillWidth = (progressWidth * mProgress) / mProgressMax; + if (fillWidth > 0) { + // Gradient from primary to lighter + QLinearGradient gradient(progressX, 0, progressX + fillWidth, 0); + gradient.setColorAt(0, mPrimaryColor); + gradient.setColorAt(1, mPrimaryColor.lighter(120)); + painter->setBrush(gradient); + painter->drawRoundedRect(progressX, progressY, fillWidth, progressHeight, 3, 3); + } + } + + // Status text + if (!mStatus.isEmpty()) { + QFont statusFont("Segoe UI", 9); + painter->setFont(statusFont); + painter->setPen(mTextColorMuted); + + QFontMetrics statusMetrics(statusFont); + QString elidedStatus = statusMetrics.elidedText(mStatus, Qt::ElideMiddle, progressWidth); + painter->drawText(progressX, progressY - 8, elidedStatus); + } + + // Draw subtle border (disable clipping first) + painter->setClipping(false); + painter->setPen(QPen(mBorderColor, 1)); + painter->setBrush(Qt::NoBrush); + painter->drawRoundedRect(0, 0, WIDTH - 1, HEIGHT - 1, 12, 12); +} diff --git a/app/splashscreen.h b/app/splashscreen.h new file mode 100644 index 0000000..4f5f105 --- /dev/null +++ b/app/splashscreen.h @@ -0,0 +1,50 @@ +#ifndef SPLASHSCREEN_H +#define SPLASHSCREEN_H + +#include +#include +#include +#include +#include +#include + +class SplashScreen : public QSplashScreen +{ + Q_OBJECT + +public: + explicit SplashScreen(QWidget *parent = nullptr); + + void setStatus(const QString &message); + void setProgress(int value, int max = 100); + void setWaitForInteraction(bool wait); + void finish(QWidget *mainWindow); + +protected: + void drawContents(QPainter *painter) override; + void mousePressEvent(QMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +private: + void loadThemeColors(); + + QString mStatus; + int mProgress = 0; + int mProgressMax = 100; + bool mWaitForInteraction = false; + bool mInteractionReceived = false; + QWidget *mPendingMainWindow = nullptr; + + // Theme colors + QColor mPrimaryColor; + QColor mBgColor; + QColor mPanelColor; + QColor mTextColor; + QColor mTextColorMuted; + QColor mBorderColor; + + static constexpr int WIDTH = 480; + static constexpr int HEIGHT = 300; +}; + +#endif // SPLASHSCREEN_H