Move the dark canvas into PaletteGraphicsView and test it directly

The inverted painting, the rubber band replay and the changed()
receiver lived in DiagramView, which the unit tests cannot link, so the
update-flag regression was only covered through a stand-in view. They
now live in PaletteGraphicsView, a QGraphicsView subclass with no
other dependency, and DiagramView derives from it. The view tells a
subclass through paintingInverted(bool) when it renders for an
inverted display; DiagramView forwards that to the diagram. The grid
dot rule moves out of Diagram::drawBackground into
QET::Palette::gridDotColor().

tst_qetpalette now links the real class: gridDotColorSoftensInvertedDots,
paletteViewFollowsThePalette (light sheet, dark sheet at text contrast
with a red box still red and the paintingInverted calls in order, back
to light), paletteViewKeepsSceneUpdatesFlowing (three whole-scene
updates and a selection each repaint, scene set after construction),
paletteViewDrawsTheRubberBand.
This commit is contained in:
Jeff Patterson
2026-09-19 14:28:23 -05:00
parent b8c9e670c4
commit eaf15faaa3
11 changed files with 413 additions and 137 deletions
+2
View File
@@ -248,6 +248,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/qet.h
${QET_DIR}/sources/qeticons.cpp
${QET_DIR}/sources/qeticons.h
${QET_DIR}/sources/palettegraphicsview.cpp
${QET_DIR}/sources/palettegraphicsview.h
${QET_DIR}/sources/qetpalette.cpp
${QET_DIR}/sources/qetpalette.h
${QET_DIR}/sources/qetstyle.cpp
+3 -11
View File
@@ -26,6 +26,7 @@
#include "diagramposition.h"
#include "factory/elementfactory.h"
#include "qetapp.h"
#include "qetpalette.h"
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
#include "qetgraphicsitem/conductor.h"
#include "qetgraphicsitem/conductortextitem.h"
@@ -284,19 +285,10 @@ void Diagram::drawBackground(QPainter *p, const QRectF &r) {
* then grid spots shall be white,
* else they shall be black in color.
* A view that shows the sheet with its lightness inverted
* would turn black dots as bright as the ink, so it gets
* dots a third of the way from the sheet color to black,
* which come out as a soft gray.
* gets softer dots, see QET::Palette::gridDotColor.
*/
QPen pen;
if (Diagram::background_color == Qt::black)
pen.setColor(Qt::white);
else if (m_inverted_lightness)
pen.setColor(QColor(Diagram::background_color.red() * 2 / 3,
Diagram::background_color.green() * 2 / 3,
Diagram::background_color.blue() * 2 / 3));
else
pen.setColor(Qt::black);
pen.setColor(QET::Palette::gridDotColor(Diagram::background_color, m_inverted_lightness));
pen.setCosmetic(true);
p->setPen(pen);
+2 -2
View File
@@ -360,8 +360,8 @@ inline void Diagram::setDisplayGrid(bool dg) {
/**
@brief Diagram::setInvertedLightness
Tell the diagram whether the view painting it will show the result
with its lightness inverted (DiagramView::paintInverted on a dark
palette). drawBackground draws a softer grid in that case. Printing
with its lightness inverted (PaletteGraphicsView on a dark palette).
drawBackground draws a softer grid in that case. Printing
and export never set this.
@param inverted
*/
+7 -88
View File
@@ -29,7 +29,6 @@
#include "qetgraphicsitem/conductortextitem.h"
#include "qetgraphicsitem/independenttextitem.h"
#include "qeticons.h"
#include "qetpalette.h"
#include "titleblock/integrationmovetemplateshandler.h"
#include "ui/diagrampropertiesdialog.h"
#include "ui/multipastedialog.h"
@@ -44,9 +43,6 @@
#include <QDropEvent>
#include <QPainter>
#include <QPointer>
#include <QStyleHintReturnMask>
#include <QStyleOptionRubberBand>
#include <QtMath>
/**
Constructeur
@@ -54,7 +50,7 @@
@param parent Le QWidget parent de cette vue de schema
*/
DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
QGraphicsView (parent),
PaletteGraphicsView (parent),
m_diagram (diagram)
{
grabGesture(Qt::PinchGesture);
@@ -108,17 +104,6 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
connect(m_diagram, &Diagram::showDiagram, this, &DiagramView::showDiagram);
connect(m_diagram, &QGraphicsScene::sceneRectChanged, this, &DiagramView::adjustSceneRect);
/* On a dark palette this view paints the scene into an image (see
* paintInverted). QGraphicsView delivers scene updates straight to
* its viewport when nobody listens to QGraphicsScene::changed(),
* and in that mode the scene clears its "update everything" flag
* only when the items are painted straight onto the viewport,
* which never happens here: from the second update on, grid and
* background toggles and even a selection would wait for an
* unrelated repaint. With a receiver connected the scene sends its
* updates through the signal, and clears the flag before it
* emits. Any receiver does; this one has nothing to do. */
connect(m_diagram, &QGraphicsScene::changed, this, [](const QList<QRectF> &) {});
connect(&(m_diagram -> border_and_titleblock), &BorderTitleBlock::informationChanged, this, &DiagramView::updateWindowTitle);
connect(diagram, &Diagram::findElementRequired, this, &DiagramView::findElementRequired);
@@ -1099,76 +1084,13 @@ bool DiagramView::event(QEvent *e) {
}
/**
@brief DiagramView::canvasIsInverted
@return true when the folio must be drawn with its lightness inverted,
i.e. when the application palette is dark. The document keeps its own
colors; only the screen rendering changes, so printing and exporting
stay black on white.
@brief DiagramView::paintingInverted
Reimplemented from PaletteGraphicsView: tell the diagram it is being
drawn for an inverted display, so it softens its grid.
*/
bool DiagramView::canvasIsInverted() const
void DiagramView::paintingInverted(bool inverted)
{
return QET::Palette::isDark(qApp->palette());
}
/**
@brief DiagramView::paintInverted
Render \a area of the viewport into an off-screen image, invert the
lightness of that image and blit it to the viewport. Inverting the
finished rendering turns the white sheet black and the black ink white
in one pass, and keeps the hue of colored conductors and elements.
@param area the part of the viewport to repaint, in viewport coordinates
*/
void DiagramView::paintInverted(const QRect &area)
{
const QRect rect = area.intersected(viewport()->rect());
if (rect.isEmpty())
return;
const qreal ratio = viewport()->devicePixelRatioF();
QImage buffer(qCeil(rect.width() * ratio), qCeil(rect.height() * ratio),
QImage::Format_RGB32);
buffer.setDevicePixelRatio(ratio);
QPainter buffer_painter(&buffer);
buffer_painter.setRenderHints(renderHints());
m_diagram->setInvertedLightness(true);
render(&buffer_painter, QRectF(QPointF(0, 0), QSizeF(rect.size())),
rect, Qt::IgnoreAspectRatio);
m_diagram->setInvertedLightness(false);
buffer_painter.end();
QET::Palette::invertLightness(buffer, palette().color(QPalette::Base),
palette().color(QPalette::Text));
QPainter painter(viewport());
painter.drawImage(rect.topLeft(), buffer);
drawRubberBand(painter);
}
/**
@brief DiagramView::drawRubberBand
Draw the selection rubber band the way QGraphicsView::paintEvent does.
Rendering the view into an off-screen image skips it, so it is drawn
here instead, after the inversion, and keeps the palette colors.
@param painter a painter on the viewport
*/
void DiagramView::drawRubberBand(QPainter &painter)
{
const QRect band = rubberBandRect();
if (band.isNull())
return;
QStyleOptionRubberBand option;
option.initFrom(viewport());
option.rect = band;
option.shape = QRubberBand::Rectangle;
QStyleHintReturnMask mask;
if (viewport()->style()->styleHint(QStyle::SH_RubberBand_Mask, &option,
viewport(), &mask))
painter.setClipRegion(mask.region, Qt::IntersectClip);
viewport()->style()->drawControl(QStyle::CE_RubberBand, &option,
&painter, viewport());
m_diagram->setInvertedLightness(inverted);
}
/**
@@ -1178,10 +1100,7 @@ void DiagramView::drawRubberBand(QPainter &painter)
*/
void DiagramView::paintEvent(QPaintEvent *event)
{
if (canvasIsInverted())
paintInverted(event->rect());
else
QGraphicsView::paintEvent(event);
PaletteGraphicsView::paintEvent(event);
if (m_free_rubberbanding && m_free_rubberband.count() >= 3)
{
+3 -6
View File
@@ -22,7 +22,7 @@
#include "titleblock/templatelocation.h"
#include <QClipboard>
#include <QGraphicsView>
#include "palettegraphicsview.h"
class Conductor;
class Diagram;
@@ -30,13 +30,12 @@ class QETDiagramEditor;
class DVEventInterface;
class QInputEvent;
class QGestureEvent;
class QPainter;
/**
This class provides a widget to render an electric diagram in an editable,
interactive way.
*/
class DiagramView : public QGraphicsView
class DiagramView : public PaletteGraphicsView
{
Q_OBJECT
@@ -85,6 +84,7 @@ class DiagramView : public QGraphicsView
///Set for one call only, by the Escape handler, to let focus leave the view.
bool m_releasing_focus = false;
void paintEvent(QPaintEvent *event) override;
void paintingInverted(bool inverted) override;
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
@@ -98,9 +98,6 @@ class DiagramView : public QGraphicsView
virtual bool selectedItemHasFocus();
private:
bool canvasIsInverted() const;
void paintInverted(const QRect &area);
void drawRubberBand(QPainter &painter);
void handleElementDrop(QDropEvent *);
void handleTitleBlockDrop(QDropEvent *);
void handleTextDrop(QDropEvent *);
+148
View File
@@ -0,0 +1,148 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
#include "palettegraphicsview.h"
#include "qetpalette.h"
#include <QPainter>
#include <QPaintEvent>
#include <QStyleHintReturnMask>
#include <QStyleOptionRubberBand>
#include <QtMath>
PaletteGraphicsView::PaletteGraphicsView(QWidget *parent) :
QGraphicsView(parent)
{
}
PaletteGraphicsView::PaletteGraphicsView(QGraphicsScene *scene, QWidget *parent) :
QGraphicsView(scene, parent)
{
listenToScene(scene);
}
/**
@brief PaletteGraphicsView::setScene
Same as QGraphicsView::setScene, and keeps the scene's updates flowing
(see the class description).
*/
void PaletteGraphicsView::setScene(QGraphicsScene *scene)
{
QGraphicsView::setScene(scene);
listenToScene(scene);
}
/**
@brief PaletteGraphicsView::invertsLightness
@return true when the scene is shown with its lightness inverted,
i.e. when the view's palette is dark.
*/
bool PaletteGraphicsView::invertsLightness() const
{
return QET::Palette::isDark(palette());
}
void PaletteGraphicsView::paintingInverted(bool inverted)
{
Q_UNUSED(inverted)
}
/**
@brief PaletteGraphicsView::listenToScene
Connect a receiver to the scene's changed() signal. Any receiver does;
this one has nothing to do. The connection dies with the view.
*/
void PaletteGraphicsView::listenToScene(QGraphicsScene *scene)
{
if (scene)
connect(scene, &QGraphicsScene::changed, this, [](const QList<QRectF> &) {});
}
/**
@brief PaletteGraphicsView::paintEvent
Paints as QGraphicsView on a light palette, inverted on a dark one.
*/
void PaletteGraphicsView::paintEvent(QPaintEvent *event)
{
if (invertsLightness())
paintInverted(event->rect());
else
QGraphicsView::paintEvent(event);
}
/**
@brief PaletteGraphicsView::paintInverted
Render \a area of the viewport into an off-screen image, invert the
lightness of that image between the palette's Base and Text colors and
blit it to the viewport. Inverting the finished rendering turns the
white sheet dark and the black ink light in one pass, and keeps the
hue of colored strokes.
@param area the part of the viewport to repaint, in viewport coordinates
*/
void PaletteGraphicsView::paintInverted(const QRect &area)
{
const QRect rect = area.intersected(viewport()->rect());
if (rect.isEmpty())
return;
const qreal ratio = viewport()->devicePixelRatioF();
QImage buffer(qCeil(rect.width() * ratio), qCeil(rect.height() * ratio),
QImage::Format_RGB32);
buffer.setDevicePixelRatio(ratio);
QPainter buffer_painter(&buffer);
buffer_painter.setRenderHints(renderHints());
paintingInverted(true);
render(&buffer_painter, QRectF(QPointF(0, 0), QSizeF(rect.size())),
rect, Qt::IgnoreAspectRatio);
paintingInverted(false);
buffer_painter.end();
QET::Palette::invertLightness(buffer, palette().color(QPalette::Base),
palette().color(QPalette::Text));
QPainter painter(viewport());
painter.drawImage(rect.topLeft(), buffer);
drawRubberBand(painter);
}
/**
@brief PaletteGraphicsView::drawRubberBand
Draw the selection rubber band the way QGraphicsView::paintEvent does.
Rendering the view into an off-screen image skips it, so it is drawn
here instead, after the inversion, in the palette colors.
@param painter a painter on the viewport
*/
void PaletteGraphicsView::drawRubberBand(QPainter &painter)
{
const QRect band = rubberBandRect();
if (band.isNull())
return;
QStyleOptionRubberBand option;
option.initFrom(viewport());
option.rect = band;
option.shape = QRubberBand::Rectangle;
QStyleHintReturnMask mask;
if (viewport()->style()->styleHint(QStyle::SH_RubberBand_Mask, &option,
viewport(), &mask))
painter.setClipRegion(mask.region, Qt::IntersectClip);
viewport()->style()->drawControl(QStyle::CE_RubberBand, &option,
&painter, viewport());
}
+70
View File
@@ -0,0 +1,70 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
#ifndef PALETTE_GRAPHICS_VIEW_H
#define PALETTE_GRAPHICS_VIEW_H
#include <QGraphicsView>
class QPainter;
/**
A QGraphicsView that shows its scene with inverted lightness while its
palette is dark: white becomes the palette's Base color, black its Text
color, and colored strokes keep their hue. The scene itself is left as
drawn, so printing and exporting it still give black on white. On a
light palette the view paints exactly as QGraphicsView does.
The view paints through QGraphicsView::render() into an image and blits
the inverted image. QGraphicsView delivers scene updates straight to
its viewport when nobody listens to QGraphicsScene::changed(), and in
that mode the scene clears its "update everything" flag only when the
items are painted straight onto the viewport, which never happens
here: from the second QGraphicsScene::update() on, every scene update
and every item update would wait for an unrelated repaint. So the view
listens to changed() on every scene it is given, which makes the scene
clear the flag before it emits. Set the scene through this class, not
through a QGraphicsView pointer.
*/
class PaletteGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
explicit PaletteGraphicsView(QWidget *parent = nullptr);
explicit PaletteGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
void setScene(QGraphicsScene *scene);
bool invertsLightness() const;
protected:
void paintEvent(QPaintEvent *event) override;
/**
Called with true right before the scene is rendered for an
inverted display and with false right after, so a scene can
adapt what it draws (a softer grid, for instance). Does
nothing by default.
*/
virtual void paintingInverted(bool inverted);
private:
void listenToScene(QGraphicsScene *scene);
void paintInverted(const QRect &area);
void drawRubberBand(QPainter &painter);
};
#endif
+9
View File
@@ -119,6 +119,15 @@ void QET::Palette::invertLightness(QImage &image, const QColor &sheet,
}
}
QColor QET::Palette::gridDotColor(const QColor &sheet, bool inverted)
{
if (sheet == QColor(Qt::black))
return Qt::white;
if (inverted)
return QColor(sheet.red() * 2 / 3, sheet.green() * 2 / 3, sheet.blue() * 2 / 3);
return Qt::black;
}
double QET::Palette::contrastRatio(const QColor &a, const QColor &b)
{
double lighter = relativeLuminance(a);
+10
View File
@@ -67,6 +67,16 @@ namespace QET {
void invertLightness(QImage &image, const QColor &sheet = Qt::black,
const QColor &ink = Qt::white);
/**
The color of the grid dots on a sheet of color \a sheet: black,
or white on a black sheet. With \a inverted the sheet is about to
be shown with its lightness inverted (PaletteGraphicsView), where
black dots would come out as bright as the ink; the dots are then
a third of the way from the sheet color to black, which shows as
a soft gray.
*/
QColor gridDotColor(const QColor &sheet, bool inverted);
/**
WCAG 2 contrast ratio between two opaque colors, from 1 (equal)
to 21 (black on white). Normal text needs at least 4.5, large
+2
View File
@@ -95,6 +95,8 @@ add_executable(
tst_qetpalette
tst_qetpalette.cpp
inkcontrast.h
${QET_DIR}/sources/palettegraphicsview.cpp
${QET_DIR}/sources/palettegraphicsview.h
${QET_DIR}/sources/qetpalette.cpp
${QET_DIR}/sources/qetpalette.h
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.cpp
+157 -30
View File
@@ -36,6 +36,7 @@
#include "inkcontrast.h"
#include "ElementsCollection/elementpreviewdelegate.h"
#include "palettegraphicsview.h"
#include "qetpalette.h"
using QET::Palette::contrastRatio;
@@ -76,7 +77,10 @@ class tst_qetpalette : public QObject
void invertLightnessMapsSheetAndInk();
void invertedViewReadsOnDarkSheet();
void invertLightnessSpeed();
void sceneUpdatesReachARenderedView();
void gridDotColorSoftensInvertedDots();
void paletteViewFollowsThePalette();
void paletteViewKeepsSceneUpdatesFlowing();
void paletteViewDrawsTheRubberBand();
private:
static void addPaletteRows();
@@ -533,52 +537,148 @@ void tst_qetpalette::invertedViewReadsOnDarkSheet()
namespace {
/**
A view that paints the way DiagramView does on a dark palette:
the exposed rectangle goes through QGraphicsView::render() into an
image, which is then blitted, so Qt never paints the items straight
onto the viewport.
A PaletteGraphicsView that counts its paints and records the
paintingInverted() calls it receives.
*/
class RenderedView : public QGraphicsView
class ProbeView : public PaletteGraphicsView
{
public:
int paints = 0;
using QGraphicsView::QGraphicsView;
QList<bool> inverted_calls;
using PaletteGraphicsView::PaletteGraphicsView;
protected:
void paintEvent(QPaintEvent *event) override
{
++paints;
const QRect rect = event->rect().intersected(viewport()->rect());
QImage buffer(rect.size(), QImage::Format_RGB32);
QPainter buffer_painter(&buffer);
render(&buffer_painter, QRectF(QPointF(0, 0), QSizeF(rect.size())), rect);
buffer_painter.end();
QPainter painter(viewport());
painter.drawImage(rect.topLeft(), buffer);
PaletteGraphicsView::paintEvent(event);
}
void paintingInverted(bool inverted) override
{
inverted_calls << inverted;
}
};
/**
A small folio: a white sheet with a black line and a red box.
Returns the box, which is selectable.
*/
QGraphicsRectItem *fillSheet(QGraphicsScene &scene)
{
scene.setSceneRect(0, 0, 200, 120);
scene.setBackgroundBrush(Qt::white);
scene.addLine(10, 60, 190, 60, QPen(Qt::black, 2));
QGraphicsRectItem *box = scene.addRect(20, 20, 40, 20, QPen(Qt::NoPen), QBrush(QColor(200, 0, 0)));
box->setFlag(QGraphicsItem::ItemIsSelectable);
return box;
}
/// The view sized to its scene, without frame or scroll bars.
void showAsSheet(QGraphicsView &view)
{
view.setFrameShape(QFrame::NoFrame);
view.setAlignment(Qt::AlignLeft | Qt::AlignTop);
view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.resize(200, 120);
view.show();
}
/// The most frequent color of an image: the sheet.
QColor sheetColor(const QImage &image)
{
QHash<QRgb, int> 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;
for (auto it = histogram.cbegin(); it != histogram.cend(); ++it)
if (it.value() > histogram.value(best)) best = it.key();
return QColor(best);
}
}
/**
QGraphicsView clears the scene's "update everything" flag only when
it paints the items straight onto its viewport, and while the flag is
set every further QGraphicsScene::update() and item update is dropped.
A view that paints through render() therefore needs a receiver on
QGraphicsScene::changed(), which makes the scene clear the flag before
it emits. This checks that with the receiver, three whole-scene
updates and a selection each repaint the view.
Grid dots are black, white on a black sheet, and a third of the way
from the sheet color to black when the sheet is about to be shown
inverted, so that they do not come out as bright as the ink.
*/
void tst_qetpalette::sceneUpdatesReachARenderedView()
void tst_qetpalette::gridDotColorSoftensInvertedDots()
{
QGraphicsScene scene(0, 0, 100, 100);
QGraphicsRectItem *item = scene.addRect(10, 10, 30, 30, QPen(Qt::black), QBrush(Qt::white));
item->setFlag(QGraphicsItem::ItemIsSelectable);
QObject::connect(&scene, &QGraphicsScene::changed, &scene, [](const QList<QRectF> &) {});
QCOMPARE(QET::Palette::gridDotColor(Qt::white, false), QColor(Qt::black));
QCOMPARE(QET::Palette::gridDotColor(Qt::white, true), QColor(170, 170, 170));
QCOMPARE(QET::Palette::gridDotColor(Qt::darkGray, true), QColor(85, 85, 85));
QCOMPARE(QET::Palette::gridDotColor(Qt::black, false), QColor(Qt::white));
QCOMPARE(QET::Palette::gridDotColor(Qt::black, true), QColor(Qt::white));
}
RenderedView view(&scene);
view.resize(120, 120);
view.show();
/**
On a light palette the view shows the sheet as drawn and never tells
the scene it inverts. On a dark palette, set while the view is
showing, the sheet comes out as Base, the black line at text contrast,
the red box still red, and the scene hears paintingInverted(true)
before and (false) after. Back on a light palette the sheet is white
again.
*/
void tst_qetpalette::paletteViewFollowsThePalette()
{
QApplication::setStyle(QStyleFactory::create("Fusion"));
QApplication::setPalette(QET::Palette::fusionLight());
QGraphicsScene scene;
fillSheet(scene);
ProbeView view(&scene);
showAsSheet(view);
QVERIFY(QTest::qWaitForWindowExposed(&view));
const QImage light = view.viewport()->grab().toImage();
QCOMPARE(sheetColor(light), QColor(Qt::white));
QVERIFY(view.inverted_calls.isEmpty());
QApplication::setPalette(QET::Palette::fusionDark());
QTRY_VERIFY(view.invertsLightness());
const QImage dark = view.viewport()->grab().toImage();
const QColor base = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Base);
const QColor text = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Text);
QCOMPARE(sheetColor(dark), base);
const double contrast = inkContrast(dark, dark.rect());
QVERIFY2(contrast >= QET::Palette::contrastRatio(base, text) - 0.5,
qPrintable(QString("ink reads %1:1 on the dark sheet").arg(contrast)));
const QColor box = dark.pixelColor(40, 30);
QVERIFY2(box.hslHue() == 0 && box.hslSaturationF() > 0.3 && box.red() > box.blue() + 60,
qPrintable(QString("the red box became %1").arg(box.name())));
QVERIFY(!view.inverted_calls.isEmpty());
QCOMPARE(view.inverted_calls.first(), true);
QCOMPARE(view.inverted_calls.last(), false);
QCOMPARE(view.inverted_calls.count(true), view.inverted_calls.count(false));
QApplication::setPalette(QET::Palette::fusionLight());
QTRY_VERIFY(!view.invertsLightness());
QCOMPARE(sheetColor(view.viewport()->grab().toImage()), QColor(Qt::white));
}
/**
QGraphicsView clears the scene's "update everything" flag only when it
paints the items straight onto its viewport, and while the flag is set
every further QGraphicsScene::update() and item update is dropped. On
a dark palette the view paints through render() instead, so it listens
to the scene's changed() signal, which makes the scene clear the flag
before it emits. Three whole-scene updates and a selection must each
repaint the view, with the scene set after construction as
DiagramView does it.
*/
void tst_qetpalette::paletteViewKeepsSceneUpdatesFlowing()
{
QApplication::setStyle(QStyleFactory::create("Fusion"));
QApplication::setPalette(QET::Palette::fusionDark());
QGraphicsScene scene;
QGraphicsRectItem *box = fillSheet(scene);
ProbeView view;
view.setScene(&scene);
showAsSheet(view);
QVERIFY(QTest::qWaitForWindowExposed(&view));
QTRY_VERIFY(view.paints >= 1);
QVERIFY(view.invertsLightness());
for (int round = 1; round <= 3; ++round)
{
@@ -588,10 +688,37 @@ void tst_qetpalette::sceneUpdatesReachARenderedView()
}
const int before = view.paints;
item->setSelected(true);
box->setSelected(true);
QTRY_VERIFY2(view.paints > before, "the selection change was dropped");
}
/**
render() skips Qt's selection rubber band, so the view draws it after
the inversion: while a drag on the sheet is in progress, the dragged
area no longer shows the bare sheet.
*/
void tst_qetpalette::paletteViewDrawsTheRubberBand()
{
QApplication::setStyle(QStyleFactory::create("Fusion"));
QApplication::setPalette(QET::Palette::fusionDark());
QGraphicsScene scene;
fillSheet(scene);
ProbeView view(&scene);
view.setDragMode(QGraphicsView::RubberBandDrag);
showAsSheet(view);
QVERIFY(QTest::qWaitForWindowExposed(&view));
const QColor base = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Base);
QCOMPARE(view.viewport()->grab().toImage().pixelColor(170, 100), base);
QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(120, 80));
QTest::mouseMove(view.viewport(), QPoint(190, 115));
QTRY_VERIFY(!view.rubberBandRect().isNull());
const QColor inside = view.viewport()->grab().toImage().pixelColor(170, 100);
QVERIFY2(inside != base, qPrintable(QString("no rubber band drawn, pixel is %1").arg(inside.name())));
QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(190, 115));
}
/**
The inversion runs on every repaint of the folio, so a 4K viewport
has to cost a few milliseconds. Reported, not asserted: the bound