#include "palette_grid_widget.h" #include "../theme_manager.h" #include #include #include #include #include #include #include static QList allRoles() { QList roles; for (int i = 0; i < QPalette::NColorRoles; ++i) { auto r = static_cast(i); if (r != QPalette::NoRole) { roles << r; } } return roles; } static const QList ALL_GROUPS = {QPalette::Active, QPalette::Disabled, QPalette::Inactive}; static const QMap ROLE_DESCRIPTIONS = { {QPalette::Window, QT_TR_NOOP("Main window / dialog background")}, {QPalette::WindowText, QT_TR_NOOP("Text drawn on Window")}, {QPalette::Base, QT_TR_NOOP("Background for text input widgets")}, {QPalette::Text, QT_TR_NOOP("Text in input widgets")}, {QPalette::Button, QT_TR_NOOP("Button background")}, {QPalette::ButtonText, QT_TR_NOOP("Button label text")}, {QPalette::BrightText, QT_TR_NOOP("High-contrast text (e.g. checked items)")}, {QPalette::Highlight, QT_TR_NOOP("Selection / focus highlight")}, {QPalette::HighlightedText, QT_TR_NOOP("Text on top of Highlight")}, {QPalette::Link, QT_TR_NOOP("Unvisited hyperlink")}, {QPalette::LinkVisited, QT_TR_NOOP("Visited hyperlink")}, {QPalette::AlternateBase, QT_TR_NOOP("Alternating row background in views")}, {QPalette::ToolTipBase, QT_TR_NOOP("Tooltip background")}, {QPalette::ToolTipText, QT_TR_NOOP("Tooltip text")}, {QPalette::PlaceholderText, QT_TR_NOOP("Placeholder / hint text in inputs")}, {QPalette::Light, QT_TR_NOOP("Lighter than Button (3-D highlight)")}, {QPalette::Midlight, QT_TR_NOOP("Between Button and Light")}, {QPalette::Mid, QT_TR_NOOP("Between Button and Dark")}, {QPalette::Dark, QT_TR_NOOP("Darker than Button (3-D shadow)")}, {QPalette::Shadow, QT_TR_NOOP("Very dark shadow colour")}, }; static const QMap APP_ROLE_DESCRIPTIONS = { {AppColor::AccentStrong, QT_TR_NOOP("Vivid primary accent (e.g. home-tab button gradient start)")}, {AppColor::AccentSoft, QT_TR_NOOP("Lightened, desaturated accent (e.g. home-tab button gradient end)")}, }; PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent) { scroll = new QScrollArea(this); scroll->setWidgetResizable(true); scroll->setFrameShape(QFrame::NoFrame); gridHost = new QWidget; buildGrid(gridHost); refreshChromePalettes(); scroll->setWidget(gridHost); layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(scroll); } void PaletteGridWidget::buildGrid(QWidget *host) { QMetaEnum roleEnum = QMetaEnum::fromType(); auto *grid = new QGridLayout(host); grid->setSpacing(3); grid->setContentsMargins(12, 8, 12, 8); grid->setColumnStretch(0, 1); grid->setColumnStretch(1, 0); grid->setColumnStretch(2, 0); grid->setColumnStretch(3, 0); // Column headers const QStringList groupHeaders = {tr("Active"), tr("Disabled"), tr("Inactive")}; const QStringList groupTips = { tr("Normal interactive state"), tr("Widget is disabled / not interactive"), tr("Window is in background / unfocused"), }; for (int col = 0; col < 3; ++col) { auto *label = new QLabel(groupHeaders[col], host); label->setAlignment(Qt::AlignCenter); label->setToolTip(groupTips[col]); QFont f = label->font(); f.setBold(true); label->setFont(f); label->setAutoFillBackground(true); label->setContentsMargins(4, 4, 4, 4); grid->addWidget(label, 0, col + 1); headerLabels.push_back(label); } // Role rows const auto roles = allRoles(); for (int row = 0; row < roles.size(); ++row) { auto role = roles[row]; const char *name = roleEnum.valueToKey(role); // Alternating row shade if (row % 2 == 0) { for (int col = 0; col < 4; ++col) { auto *shade = new QWidget(host); shade->setAutoFillBackground(true); grid->addWidget(shade, row + 1, col); rowShadeWidgets.push_back(shade); } } auto *label = new QLabel(QString(name), host); label->setToolTip(ROLE_DESCRIPTIONS.value(role, {})); label->setContentsMargins(4, 2, 8, 2); grid->addWidget(label, row + 1, 0); for (int col = 0; col < 3; ++col) { auto group = ALL_GROUPS[col]; auto *btn = new ColorButton(host); connect(btn, &ColorButton::colorChanged, this, [this] { emit paletteChanged(); }); colorButtons[group][role] = btn; grid->addWidget(btn, row + 1, col + 1, Qt::AlignHCenter | Qt::AlignVCenter); } } // Application color section: one ColorButton per role below the role grid. // These are not tied to a color group, so a single button spans the row. QMetaEnum appEnum = QMetaEnum::fromType(); const int appHeaderRow = roles.size() + 1; auto *appHeader = new QLabel(tr("App colors"), host); appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette")); QFont appHeaderFont = appHeader->font(); appHeaderFont.setBold(true); appHeader->setFont(appHeaderFont); appHeader->setAutoFillBackground(true); appHeader->setContentsMargins(4, 4, 4, 4); grid->addWidget(appHeader, appHeaderRow, 0, 1, 4); headerLabels.append(appHeader); for (int i = 0; i < appEnum.keyCount(); ++i) { auto role = static_cast(appEnum.value(i)); const int row = appHeaderRow + 1 + i; if (i % 2 == 0) { for (int col = 0; col < 4; ++col) { auto *shade = new QWidget(host); shade->setAutoFillBackground(true); grid->addWidget(shade, row, col); rowShadeWidgets.push_back(shade); } } auto *label = new QLabel(QString(appEnum.valueToKey(role)), host); label->setToolTip(APP_ROLE_DESCRIPTIONS.value(role, {})); label->setContentsMargins(4, 2, 8, 2); grid->addWidget(label, row, 0); auto *btn = new ColorButton(host); connect(btn, &ColorButton::colorChanged, this, [this] { emit paletteChanged(); }); appColorButtons[role] = btn; grid->addWidget(btn, row, 1, Qt::AlignHCenter | Qt::AlignVCenter); } } void PaletteGridWidget::changeEvent(QEvent *e) { if (e->type() == QEvent::PaletteChange) { QTimer::singleShot(0, this, &PaletteGridWidget::refreshChromePalettes); } QWidget::changeEvent(e); } void PaletteGridWidget::refreshChromePalettes() { const QPalette base = qApp->palette(); const QColor alt = base.color(QPalette::AlternateBase); // Header labels for (auto *label : headerLabels) { QPalette lp = label->palette(); lp.setColor(QPalette::Window, alt); label->setPalette(lp); label->update(); } // Alternating row backgrounds for (auto *shade : rowShadeWidgets) { QPalette sp = shade->palette(); sp.setColor(QPalette::Window, alt); shade->setPalette(sp); shade->update(); } } void PaletteGridWidget::loadPalette(const PaletteConfig &cfg) { for (auto group : ALL_GROUPS) { for (auto role : allRoles()) { QColor color = cfg.colors.value(group).value(role); if (!color.isValid()) { color = qApp->palette().color(group, role); } colorButtons[group][role]->setColor(color); } } QMetaEnum appEnum = QMetaEnum::fromType(); for (int i = 0; i < appEnum.keyCount(); ++i) { auto role = static_cast(appEnum.value(i)); QColor color = cfg.appColors.value(role); if (!color.isValid()) { color = themeManager->appColor(role); } appColorButtons[role]->setColor(color); } } PaletteConfig PaletteGridWidget::currentPaletteConfig() const { PaletteConfig cfg; for (auto group : ALL_GROUPS) { for (auto role : allRoles()) { cfg.colors[group][role] = colorButtons[group][role]->getColor(); } } QMetaEnum appEnum = QMetaEnum::fromType(); for (int i = 0; i < appEnum.keyCount(); ++i) { auto role = static_cast(appEnum.value(i)); cfg.appColors[role] = appColorButtons[role]->getColor(); } return cfg; }