From 50792ba1ad6360a9b74972d84cc928a4fdf62ccc Mon Sep 17 00:00:00 2001 From: Jeff Patterson Date: Sat, 19 Sep 2026 15:41:50 -0500 Subject: [PATCH] Repaint the whole dark canvas when the application palette changes After a live light/dark switch only the sheet changed colors; the viewport around it kept the previous palette. Qt repaints a widget on an application palette change only when the widget's own palette changed with it, and under the folio tab widget's style sheet it does not, so the only repaints came from the scene and covered the scene rectangle alone. PaletteGraphicsView now watches the application object for ApplicationPaletteChange, the one receiver Qt always notifies, and repaints its whole viewport. The test paletteViewFollowsTheApplicationUnderAStyleSheet now also requires a full-viewport repaint after each switch, before anything asks the view for a rendering. --- sources/palettegraphicsview.cpp | 20 ++++++++++++++++++++ sources/palettegraphicsview.h | 1 + tests/qttest/tst_qetpalette.cpp | 20 +++++++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/sources/palettegraphicsview.cpp b/sources/palettegraphicsview.cpp index bb05d7657..04eb77039 100644 --- a/sources/palettegraphicsview.cpp +++ b/sources/palettegraphicsview.cpp @@ -20,6 +20,7 @@ #include "qetpalette.h" #include +#include #include #include #include @@ -29,11 +30,13 @@ PaletteGraphicsView::PaletteGraphicsView(QWidget *parent) : QGraphicsView(parent) { + qApp->installEventFilter(this); } PaletteGraphicsView::PaletteGraphicsView(QGraphicsScene *scene, QWidget *parent) : QGraphicsView(scene, parent) { + qApp->installEventFilter(this); listenToScene(scene); } @@ -78,6 +81,23 @@ void PaletteGraphicsView::listenToScene(QGraphicsScene *scene) connect(scene, &QGraphicsScene::changed, this, [](const QList &) {}); } +/** + @brief PaletteGraphicsView::eventFilter + Repaint the whole viewport when the application palette changes. Qt + sends that change to the application object and then repaints only + the widgets whose own palette changed with it, which under a style + sheet is not the case (see invertsLightness()): the scene would then + repaint only what it updates itself, and the viewport around the + sheet would keep the colors of the previous palette. The filter sits + on the application object, the one receiver Qt always notifies. +*/ +bool PaletteGraphicsView::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == qApp && event->type() == QEvent::ApplicationPaletteChange) + viewport()->update(); + return QGraphicsView::eventFilter(watched, event); +} + /** @brief PaletteGraphicsView::paintEvent Paints as QGraphicsView on a light palette, inverted on a dark one. diff --git a/sources/palettegraphicsview.h b/sources/palettegraphicsview.h index 13ad26077..c8d512e30 100644 --- a/sources/palettegraphicsview.h +++ b/sources/palettegraphicsview.h @@ -52,6 +52,7 @@ class PaletteGraphicsView : public QGraphicsView bool invertsLightness() const; protected: + bool eventFilter(QObject *watched, QEvent *event) override; void paintEvent(QPaintEvent *event) override; /** Called with true right before the scene is rendered for an diff --git a/tests/qttest/tst_qetpalette.cpp b/tests/qttest/tst_qetpalette.cpp index bf20fe33d..5a6f4fddb 100644 --- a/tests/qttest/tst_qetpalette.cpp +++ b/tests/qttest/tst_qetpalette.cpp @@ -546,12 +546,22 @@ namespace { { public: int paints = 0; + QList paint_rects; QList inverted_calls; using PaletteGraphicsView::PaletteGraphicsView; + /// True when a paint since index \a from covered the whole viewport. + bool fullyRepaintedSince(int from) const + { + for (int i = from; i < paint_rects.size(); ++i) + if (paint_rects.at(i).contains(viewport()->rect())) + return true; + return false; + } protected: void paintEvent(QPaintEvent *event) override { ++paints; + paint_rects << event->rect(); PaletteGraphicsView::paintEvent(event); } void paintingInverted(bool inverted) override @@ -728,7 +738,11 @@ void tst_qetpalette::paletteViewDrawsTheRubberBand() such a sheet). The view's own palette() is therefore stale after a live switch, and the view must follow the application palette instead: inside a tab widget with a style sheet, switching the - application to dark and back still changes the sheet. + application to dark and back still changes the sheet. Qt also skips + the repaint of a widget whose palette did not change, so the view + has to repaint its whole viewport by itself on each switch, without + anyone asking for a rendering: otherwise the area around the sheet + keeps the old colors. */ void tst_qetpalette::paletteViewFollowsTheApplicationUnderAStyleSheet() { @@ -748,13 +762,17 @@ void tst_qetpalette::paletteViewFollowsTheApplicationUnderAStyleSheet() QVERIFY(QTest::qWaitForWindowExposed(&tabs)); QCOMPARE(sheetColor(view->viewport()->grab().toImage()), QColor(Qt::white)); + int since = view->paint_rects.size(); QApplication::setPalette(QET::Palette::fusionDark()); QTRY_VERIFY(view->invertsLightness()); + QTRY_VERIFY2(view->fullyRepaintedSince(since), "no full repaint after the switch to dark"); const QColor base = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Base); QCOMPARE(sheetColor(view->viewport()->grab().toImage()), base); + since = view->paint_rects.size(); QApplication::setPalette(QET::Palette::fusionLight()); QTRY_VERIFY(!view->invertsLightness()); + QTRY_VERIFY2(view->fullyRepaintedSince(since), "no full repaint after the switch to light"); QCOMPARE(sheetColor(view->viewport()->grab().toImage()), QColor(Qt::white)); }