diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index 6ad06fb94..3fec32275 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -1808,6 +1808,10 @@ void QETApp::useSystemPalette(bool use) { file.close(); } } + // Widgets with their own style sheet keep the palette they were + // polished with; after a live light/dark switch they would stay in + // the old colors (see QET::Palette::refreshStyleSheets). + QET::Palette::refreshStyleSheets(); } /** diff --git a/sources/qetpalette.cpp b/sources/qetpalette.cpp index 9cf56c7cd..8b080eeff 100644 --- a/sources/qetpalette.cpp +++ b/sources/qetpalette.cpp @@ -17,10 +17,11 @@ */ #include "qetpalette.h" +#include #include #include #include - +#include #include namespace { @@ -296,3 +297,14 @@ QPixmap QET::Palette::forPalette(const QPixmap &pixmap, const QPalette &palette) result.setDevicePixelRatio(pixmap.devicePixelRatio()); return result; } + +void QET::Palette::refreshStyleSheets() +{ + // Setting the same sheet again is not a no-op: QWidget::setStyleSheet() + // asks QStyleSheetStyle to repolish the widget, which recomputes its + // palette from the application palette now in force. + const QWidgetList widgets = QApplication::allWidgets(); + for (QWidget *widget : widgets) + if (!widget->styleSheet().isEmpty()) + widget->setStyleSheet(widget->styleSheet()); +} diff --git a/sources/qetpalette.h b/sources/qetpalette.h index 536b660d2..e1a5cabcd 100644 --- a/sources/qetpalette.h +++ b/sources/qetpalette.h @@ -135,6 +135,18 @@ namespace QET { left alone either way. */ QPixmap forPalette(const QPixmap &pixmap, const QPalette &palette); + + /** + Make every widget that carries a style sheet take the current + application palette. QApplication::setPalette() reaches plain + widgets, but a widget with a style sheet keeps the palette + QStyleSheetStyle resolved when the sheet was applied, so after a + live light/dark switch it is drawn in the old colors (the folio + tab bar and its buttons, the element info widgets, several + configuration pages). Re-applying each widget's own sheet makes + QStyleSheetStyle resolve it again. Call after setPalette(). + */ + void refreshStyleSheets(); } } diff --git a/sources/qetsbom.cpp b/sources/qetsbom.cpp new file mode 100644 index 000000000..b9ac01a88 --- /dev/null +++ b/sources/qetsbom.cpp @@ -0,0 +1,53 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "qetsbom.h" + +#include +#include +#include + +// Generated by cmake/generate_sbom.cmake at configure time from whatever +// find_package()/FetchContent resolution this build actually used. +#include "sbom_data.h" + +QList QetSbom::components() +{ + QList list; + + const QJsonDocument doc = QJsonDocument::fromJson(QByteArray(qet_sbom_json)); + const QJsonArray components = doc.object().value("components").toArray(); + + for (const QJsonValue &value : components) + { + const QJsonObject obj = value.toObject(); + list.append({ + obj.value("name").toString(), + obj.value("version").toString(), + obj.value("license").toString(), + obj.value("homepage").toString(), + obj.value("source").toString() + }); + } + + return list; +} + +QString QetSbom::rawJson() +{ + return QString::fromUtf8(qet_sbom_json); +} diff --git a/sources/qetsbom.h b/sources/qetsbom.h new file mode 100644 index 000000000..d4ec5bcd5 --- /dev/null +++ b/sources/qetsbom.h @@ -0,0 +1,40 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef QETSBOM_H +#define QETSBOM_H + +#include +#include + +/// One third-party component resolved for this specific build, as recorded +/// by cmake/generate_sbom.cmake at configure time. +struct QetSbomComponent +{ + QString name; + QString version; + QString license; + QString homepage; + QString source; ///< "system", "fetched" or "submodule" +}; + +namespace QetSbom { + QList components(); + QString rawJson(); +} + +#endif diff --git a/tests/qttest/tst_qetpalette.cpp b/tests/qttest/tst_qetpalette.cpp index 4ced9d5a1..5c504e66a 100644 --- a/tests/qttest/tst_qetpalette.cpp +++ b/tests/qttest/tst_qetpalette.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "inkcontrast.h" #include "ElementsCollection/elementpreviewdelegate.h" @@ -84,6 +85,7 @@ class tst_qetpalette : public QObject void paletteViewDrawsTheRubberBand(); void paletteViewFollowsTheApplicationUnderAStyleSheet(); void paletteViewFillsWhatTheSceneLeavesBlank(); + void styleSheetWidgetsFollowPaletteChange(); private: static void addPaletteRows(); @@ -815,6 +817,64 @@ void tst_qetpalette::invertLightnessSpeed() } } +/** + A widget with a style sheet keeps the palette QStyleSheetStyle + resolved when the sheet was applied: after QApplication::setPalette() + it is still drawn in the old colors, which is what the folio tab bar + showed after a live light/dark switch. refreshStyleSheets() brings + it in line. Both directions are checked. +*/ +void tst_qetpalette::styleSheetWidgetsFollowPaletteChange() +{ + QApplication::setStyle(QStyleFactory::create("Fusion")); + QApplication::setPalette(QET::Palette::fusionLight()); + + QWidget top; + auto *layout = new QHBoxLayout(&top); + auto *tabs = new QTabWidget; + tabs->addTab(new QWidget, "1"); + tabs->setStyleSheet("QTabBar::scroller {width: 0px;}"); // as sources/projectview.cpp + auto *button = new QToolButton; + button->setText("+"); + button->setAutoRaise(true); + tabs->setCornerWidget(button, Qt::TopRightCorner); + auto *plain = new QLabel("plain"); + plain->setAutoFillBackground(true); + layout->addWidget(tabs); + layout->addWidget(plain); + top.resize(300, 120); + top.show(); + QVERIFY(QTest::qWaitForWindowExposed(&top)); + + // The most frequent color of a widget's rendering: its background. + auto background = [](QWidget *w) { + const QImage image = w->grab().toImage(); + QHash histogram; + for (int y = 0; y < image.height(); ++y) + for (int x = 0; x < image.width(); ++x) + ++histogram[image.pixel(x, y)]; + QRgb best = 0; + int count = -1; + for (auto it = histogram.cbegin(); it != histogram.cend(); ++it) + if (it.value() > count) { count = it.value(); best = it.key(); } + return best; + }; + auto window = [](const QPalette &p) { return p.color(QPalette::Active, QPalette::Window).rgb(); }; + + for (const QPalette &palette : {QET::Palette::fusionDark(), QET::Palette::fusionLight()}) + { + QApplication::setPalette(palette); + QTest::qWait(50); + QCOMPARE(background(plain), window(palette)); + // Qt leaves the style-sheet widget behind; this is the defect. + QVERIFY2(background(button) != window(palette), "Qt now updates style-sheet widgets itself; refreshStyleSheets() is redundant"); + + QET::Palette::refreshStyleSheets(); + QTest::qWait(50); + QCOMPARE(background(button), window(palette)); + } +} + int main(int argc, char **argv) { // Widgets are painted for real, on Qt's offscreen platform so the test