mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-20 07:14:13 +02:00
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:
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 *);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user