diff --git a/sources/diagram.cpp b/sources/diagram.cpp index aa8e92399..76925c2c1 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -283,10 +283,20 @@ void Diagram::drawBackground(QPainter *p, const QRectF &r) { * if background color is black, * 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. */ QPen pen; - Diagram::background_color == Qt::black? pen.setColor(Qt::white) - : pen.setColor(Qt::black); + 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.setCosmetic(true); p->setPen(pen); diff --git a/sources/diagram.h b/sources/diagram.h index 824b6dc38..516d654b0 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -123,6 +123,7 @@ class Diagram : public QGraphicsScene qreal diagram_qet_version_; bool draw_grid_; + bool m_inverted_lightness = false; bool use_border_; bool draw_guides_; QList m_guides_list; @@ -222,6 +223,7 @@ class Diagram : public QGraphicsScene ExportProperties applyProperties(const ExportProperties &); void setDisplayGrid(bool); bool displayGrid(); + void setInvertedLightness(bool); void setDisplayGuides(bool); bool displayGuides(); void updateProjectGuides(const QList &guides); @@ -355,6 +357,18 @@ inline void Diagram::setDisplayGrid(bool dg) { draw_grid_ = 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 + and export never set this. + @param inverted +*/ +inline void Diagram::setInvertedLightness(bool inverted) { + m_inverted_lightness = inverted; +} + /** @brief Diagram::displayGrid @return draw_grid_ true if the grid is drawn, false otherwise. diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index 643fe7df6..c18559bb0 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -108,6 +108,17 @@ 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 &) {}); connect(&(m_diagram -> border_and_titleblock), &BorderTitleBlock::informationChanged, this, &DiagramView::updateWindowTitle); connect(diagram, &Diagram::findElementRequired, this, &DiagramView::findElementRequired); @@ -1120,8 +1131,10 @@ void DiagramView::paintInverted(const QRect &area) 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), diff --git a/tests/qttest/tst_qetpalette.cpp b/tests/qttest/tst_qetpalette.cpp index 2fd4d0684..f86b16eca 100644 --- a/tests/qttest/tst_qetpalette.cpp +++ b/tests/qttest/tst_qetpalette.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,7 @@ class tst_qetpalette : public QObject void invertLightnessMapsSheetAndInk(); void invertedViewReadsOnDarkSheet(); void invertLightnessSpeed(); + void sceneUpdatesReachARenderedView(); private: static void addPaletteRows(); @@ -529,6 +531,67 @@ void tst_qetpalette::invertedViewReadsOnDarkSheet() qPrintable(QString("the blue box became %1").arg(box.name()))); } +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. + */ + class RenderedView : public QGraphicsView + { + public: + int paints = 0; + using QGraphicsView::QGraphicsView; + 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); + } + }; +} + +/** + 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. +*/ +void tst_qetpalette::sceneUpdatesReachARenderedView() +{ + 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 &) {}); + + RenderedView view(&scene); + view.resize(120, 120); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QTRY_VERIFY(view.paints >= 1); + + for (int round = 1; round <= 3; ++round) + { + const int before = view.paints; + scene.update(); + QTRY_VERIFY2(view.paints > before, qPrintable(QString("scene update %1 was dropped").arg(round))); + } + + const int before = view.paints; + item->setSelected(true); + QTRY_VERIFY2(view.paints > before, "the selection change was dropped"); +} + /** The inversion runs on every repaint of the folio, so a 4K viewport has to cost a few milliseconds. Reported, not asserted: the bound