mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-15 10:04:13 +02:00
Merge master into feat/qetz-db-truth-v2
The .qetz files take this branch's side: it already holds #1473, and adds the folio parts to join(). qetscriptapi.cpp: #1456 gave setConductorProperty() the voltage that crosses a function separated terminal element; here that function hands its work to applyConductorProperties(), so the change goes there, with the properties captured before the pushes. The database path for folios not built already gives up at such a terminal, so it builds the folio and takes the same route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -271,6 +271,15 @@ qet_add_app_test(tst_rotateselectionimage tst_rotateselectionimage.cpp)
|
||||
# it selected and records no undo step (#1414); the key goes to the view.
|
||||
qet_add_app_test(tst_imagehandleescape tst_imagehandleescape.cpp)
|
||||
|
||||
# SVG export with smaller pictures (--svg-small-pictures, the export dialog's
|
||||
# option): a picture drawn at a tenth of its size is stored at four times
|
||||
# that, a small one whole; without the option every picture is whole.
|
||||
qet_add_app_test(tst_svgsmallpictures tst_svgsmallpictures.cpp)
|
||||
add_dependencies(tst_svgsmallpictures qelectrotech)
|
||||
target_link_libraries(tst_svgsmallpictures PRIVATE Qt::Svg)
|
||||
target_compile_definitions(tst_svgsmallpictures PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||
|
||||
# TextResizeHandles: the corner handles that change the width of a text box,
|
||||
# on a plain QGraphicsTextItem with the "textWidth" property QET texts have.
|
||||
add_executable(
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<project title="A large picture shown small" uuid="{3f6a2c91-8d47-4b5e-a0c3-91e2d7b4f600}" version="0.200.1">
|
||||
<properties>
|
||||
<property name="saveddate" show="1">10/2/26</property>
|
||||
<property name="saveddate-eu" show="1">02-10-2026</property>
|
||||
<property name="saveddate-us" show="1">2026-10-02</property>
|
||||
<property name="savedfilename" show="1">svg_small_pictures</property>
|
||||
<property name="savedtime" show="1">18:39</property>
|
||||
</properties>
|
||||
<usage enabled="true" time_spent="0"/>
|
||||
<newdiagrams>
|
||||
<border cols="17" colsize="60" displaycols="true" displayrows="true" rows="8" rowsize="80"/>
|
||||
<inset author="" auto_page_num="" date="null" displayAt="bottom" filename="" folio="" indexrev="" locmach="" plant="" title="" version=""/>
|
||||
<conductors bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
|
||||
<report label="%f-%l%c"/>
|
||||
<xrefs>
|
||||
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" switchprefix="" type="coil" xrefpos="AlignBottom"/>
|
||||
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" switchprefix="" type="commutator" xrefpos="AlignBottom"/>
|
||||
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" switchprefix="" type="plc" xrefpos="AlignBottom"/>
|
||||
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" switchprefix="" type="protection" xrefpos="AlignBottom"/>
|
||||
</xrefs>
|
||||
<conductors_autonums auto_break_conductors="false" current_autonum="" freeze_new_conductors="false"/>
|
||||
<folio_autonums/>
|
||||
<element_autonums current_autonum="" freeze_new_elements="false"/>
|
||||
<guides/>
|
||||
</newdiagrams>
|
||||
<diagram author="" auto_page_num="" cols="17" colsize="60" date="null" displayAt="bottom" displaycols="true" displayrows="true" filename="" folio="" freezeNewConductor="false" freezeNewElement="false" height="660" indexrev="" locmach="" order="1" plant="" rows="8" rowsize="80" title="" uuid="{3f6a2c91-8d47-4b5e-a0c3-91e2d7b4f610}" version="0.200.1-dev">
|
||||
<defaultconductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
|
||||
<inputs/>
|
||||
<images>
|
||||
<image z="0" rotation="0" y="100" x="100" size="0.1" is_movable="1">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</image>
|
||||
<image z="0" rotation="0" y="300" x="100" size="1" is_movable="1">iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAAP0lEQVR42u3PMQoAIAwEwRVC1F/7dFuxFoKwqS7XHNNgJSR0OMP1Pu+DQckFs2pYsWLFihUrVqxYsWLFir8Rb7mhAuQBqxaIAAAAAElFTkSuQmCC</image>
|
||||
</images>
|
||||
</diagram>
|
||||
<collection>
|
||||
<category name="import">
|
||||
<names>
|
||||
<name lang="en">Imported elements</name>
|
||||
</names>
|
||||
</category>
|
||||
</collection>
|
||||
</project>
|
||||
@@ -29,6 +29,10 @@ class FakeWire : public QObject
|
||||
ConductorProperties properties() const { return m_properties; }
|
||||
void setProperties(const ConductorProperties &p) { m_properties = p; }
|
||||
QSet<FakeWire *> relatedPotentialConductors() const { return m_potential; }
|
||||
// The wires of the other side of a function separated terminal
|
||||
// element: same electrical node, but only the voltage /
|
||||
// protocol crosses.
|
||||
QSet<FakeWire *> functionSeparatedConductors() const { return m_separated; }
|
||||
QPointF pos() const { return m_pos; }
|
||||
|
||||
// Joins wires into one potential; like the real one, a wire's
|
||||
@@ -41,10 +45,24 @@ class FakeWire : public QObject
|
||||
w->m_potential.insert(o);
|
||||
}
|
||||
|
||||
// Marks two groups as separated by a function separated
|
||||
// terminal element, without joining their potentials.
|
||||
static void separate(const QList<FakeWire *> &one,
|
||||
const QList<FakeWire *> &other)
|
||||
{
|
||||
for (FakeWire *w : one)
|
||||
for (FakeWire *o : other)
|
||||
{
|
||||
w->m_separated.insert(o);
|
||||
o->m_separated.insert(w);
|
||||
}
|
||||
}
|
||||
|
||||
ConductorProperties m_properties;
|
||||
|
||||
private:
|
||||
QSet<FakeWire *> m_potential;
|
||||
QSet<FakeWire *> m_separated;
|
||||
QPointF m_pos;
|
||||
};
|
||||
|
||||
@@ -102,6 +120,143 @@ class tst_conductormultiedit : public QObject
|
||||
(QSet<FakeWire *> {&a, &b, &c, &d}));
|
||||
}
|
||||
|
||||
// A function separated terminal element: the other side of the
|
||||
// terminal is reached too, but only the voltage / protocol
|
||||
// crosses it -- not the function, not the number.
|
||||
void functionSeparatedGetsVoltageProtocolOnly()
|
||||
{
|
||||
FakeWire a("1", "fnA", "", {}), b("2", "fnB", "", {}),
|
||||
c("3", "fnC", "", {});
|
||||
FakeWire::join({&a, &b});
|
||||
FakeWire::separate({&a}, {&c});
|
||||
|
||||
QSet<FakeWire *> separated;
|
||||
const Wires t = ConductorMultiEdit::targets(Wires {&a}, true,
|
||||
&separated);
|
||||
QCOMPARE(QSet<FakeWire *>(t.begin(), t.end()),
|
||||
(QSet<FakeWire *> {&a, &b, &c}));
|
||||
QCOMPARE(separated, (QSet<FakeWire *> {&c}));
|
||||
// A caller that does not know about the rule gets the
|
||||
// separated side left out, as before
|
||||
QCOMPARE(ConductorMultiEdit::targets(Wires {&a}, true),
|
||||
(Wires {&a, &b}));
|
||||
|
||||
const ConductorProperties shown = a.properties();
|
||||
ConductorProperties after = edited(shown,
|
||||
&ConductorProperties::m_function, "PWR");
|
||||
after.m_tension_protocol = "24V";
|
||||
|
||||
QUndoStack stack;
|
||||
stack.push(ConductorMultiEdit::undo(t, shown, after, separated));
|
||||
QCOMPARE(stack.count(), 1);
|
||||
|
||||
for (FakeWire *w : {&a, &b})
|
||||
{
|
||||
QCOMPARE(w->m_properties.m_function, QStringLiteral("PWR"));
|
||||
QCOMPARE(w->m_properties.m_tension_protocol,
|
||||
QStringLiteral("24V"));
|
||||
}
|
||||
QCOMPARE(c.m_properties.m_function, QStringLiteral("fnC"));
|
||||
QCOMPARE(c.m_properties.text, QStringLiteral("3"));
|
||||
QCOMPARE(c.m_properties.m_tension_protocol, QStringLiteral("24V"));
|
||||
|
||||
stack.undo();
|
||||
QVERIFY(c.properties().m_tension_protocol.isEmpty());
|
||||
QCOMPARE(c.m_properties.m_function, QStringLiteral("fnC"));
|
||||
}
|
||||
|
||||
// The explicitly picked side of a separation: selecting one
|
||||
// wire on each side and editing them edits them both, in
|
||||
// full -- however the selection happens to be walked (#review)
|
||||
void selectedWireIsNeverSeparated()
|
||||
{
|
||||
FakeWire a("1", "fnA", "", {}), c("3", "fnC", "", {});
|
||||
FakeWire::separate({&a}, {&c});
|
||||
|
||||
const ConductorProperties original_a = a.properties();
|
||||
const ConductorProperties original_c = c.properties();
|
||||
const ConductorProperties after = edited(original_a,
|
||||
&ConductorProperties::m_function, "PWR");
|
||||
|
||||
for (const Wires &selection : {Wires {&a, &c}, Wires {&c, &a}})
|
||||
{
|
||||
//Each order starts from the same, unedited state
|
||||
a.setProperties(original_a);
|
||||
c.setProperties(original_c);
|
||||
|
||||
QSet<FakeWire *> separated;
|
||||
const Wires t = ConductorMultiEdit::targets(selection, true,
|
||||
&separated);
|
||||
//c is picked, so it is not "the far side" of anything
|
||||
QCOMPARE(separated, (QSet<FakeWire *> {}));
|
||||
|
||||
QUndoStack stack;
|
||||
QUndoCommand *undo = ConductorMultiEdit::undo(t, original_a,
|
||||
after, separated);
|
||||
QVERIFY(undo);
|
||||
stack.push(undo);
|
||||
QCOMPARE(a.m_properties.m_function, QStringLiteral("PWR"));
|
||||
QCOMPARE(c.m_properties.m_function, QStringLiteral("PWR"));
|
||||
}
|
||||
}
|
||||
|
||||
// A wire that belongs to the potential as well -- on any path,
|
||||
// however far that path runs -- takes the full edit, even when a
|
||||
// separation also reaches it. The fake's potential is one hop
|
||||
// deep, unlike the real one, so the shared potential is stated
|
||||
// in full here.
|
||||
void wireReachableBothWaysIsNotSeparated()
|
||||
{
|
||||
FakeWire a("1", "fnA", "", {}), b("2", "fnB", "", {}),
|
||||
c("3", "fnC", "", {});
|
||||
FakeWire::join({&a, &b, &c});
|
||||
FakeWire::separate({&a}, {&c});
|
||||
|
||||
QSet<FakeWire *> separated;
|
||||
const Wires t = ConductorMultiEdit::targets(Wires {&a}, true,
|
||||
&separated);
|
||||
QCOMPARE(QSet<FakeWire *>(t.begin(), t.end()),
|
||||
(QSet<FakeWire *> {&a, &b, &c}));
|
||||
QCOMPARE(separated, (QSet<FakeWire *> {}));
|
||||
|
||||
const ConductorProperties shown = a.properties();
|
||||
QUndoStack stack;
|
||||
stack.push(ConductorMultiEdit::undo(t, shown, edited(shown,
|
||||
&ConductorProperties::m_function, "PWR"), separated));
|
||||
QCOMPARE(c.m_properties.m_function, QStringLiteral("PWR"));
|
||||
}
|
||||
|
||||
// The far side only takes the voltage when the edit changed it:
|
||||
// editing the function of a 24 V wire must leave the 0 V one
|
||||
// at 0 V, not drag it along (#review)
|
||||
void separatedWireKeepsVoltageWhenNotEdited()
|
||||
{
|
||||
FakeWire a("1", "fnA", "", {}), c("3", "fnC", "", {});
|
||||
FakeWire::separate({&a}, {&c});
|
||||
a.m_properties.m_tension_protocol = "24V";
|
||||
c.m_properties.m_tension_protocol = "0V";
|
||||
|
||||
QSet<FakeWire *> separated;
|
||||
const Wires t = ConductorMultiEdit::targets(Wires {&a}, true,
|
||||
&separated);
|
||||
const ConductorProperties shown = a.properties();
|
||||
QUndoStack stack;
|
||||
|
||||
//Only the function changes: the far side changes nothing
|
||||
stack.push(ConductorMultiEdit::undo(t, shown, edited(shown,
|
||||
&ConductorProperties::m_function, "PWR"), separated));
|
||||
QCOMPARE(c.m_properties.m_function, QStringLiteral("fnC"));
|
||||
QCOMPARE(c.m_properties.m_tension_protocol, QStringLiteral("0V"));
|
||||
|
||||
//Now the voltage changes too: it crosses, and only it
|
||||
const ConductorProperties shown_now = a.properties();
|
||||
stack.push(ConductorMultiEdit::undo(t, shown_now, edited(shown_now,
|
||||
&ConductorProperties::m_tension_protocol, "30V"), separated));
|
||||
QCOMPARE(c.m_properties.m_tension_protocol, QStringLiteral("30V"));
|
||||
QCOMPARE(c.m_properties.m_function, QStringLiteral("fnC"));
|
||||
QCOMPARE(a.m_properties.m_tension_protocol, QStringLiteral("30V"));
|
||||
}
|
||||
|
||||
// One edit of three wires: each takes the function, keeps its
|
||||
// own number and cable; one undo restores all three exactly
|
||||
void undoRedoThreeWires()
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
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 "qetapptest.h"
|
||||
#include "qetgraphicsitem/diagramimageitem.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDomDocument>
|
||||
#include <QPicture>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QSvgGenerator>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
/**
|
||||
SVG export with "smaller pictures" (ExportProperties::svg_small_pictures,
|
||||
--svg-small-pictures) stores each picture at four times the size it is
|
||||
drawn at when that is smaller than the picture, and at full resolution
|
||||
otherwise. fixtures/svg_small_pictures.qet has an 800x600 picture drawn
|
||||
at a tenth of its size and a 40x30 one at full size.
|
||||
*/
|
||||
class tst_svgsmallpictures : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QString m_fixture;
|
||||
|
||||
/// Pixel size of each picture embedded in @p svg, smallest first
|
||||
static QList<QSize> embeddedSizes(const QByteArray &svg)
|
||||
{
|
||||
static const QRegularExpression data(
|
||||
QStringLiteral("data:image/png;base64,([A-Za-z0-9+/=\\s]+)"));
|
||||
QList<QSize> sizes;
|
||||
auto it = data.globalMatch(QString::fromLatin1(svg));
|
||||
while (it.hasNext())
|
||||
sizes << QImage::fromData(QByteArray::fromBase64(
|
||||
it.next().captured(1).toLatin1())).size();
|
||||
std::sort(sizes.begin(), sizes.end(), [](const QSize &a, const QSize &b) {
|
||||
return a.width() < b.width();
|
||||
});
|
||||
return sizes;
|
||||
}
|
||||
|
||||
QByteArray exportWithCli(const QStringList &extra)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(env);
|
||||
proc.start(QStringLiteral(QET_TEST_BINARY_PATH),
|
||||
QStringList{QStringLiteral("--export-svg"), m_fixture, dir.path()} + extra);
|
||||
if (!proc.waitForFinished(60000) || proc.exitCode() != 0)
|
||||
return {};
|
||||
const QStringList files = QDir(dir.path()).entryList({QStringLiteral("*.svg")});
|
||||
QFile svg(QDir(dir.path()).filePath(files.value(0)));
|
||||
return svg.open(QIODevice::ReadOnly) ? svg.readAll() : QByteArray();
|
||||
}
|
||||
|
||||
/// As ExportDialog::generateSvg(): the folio drawn into a QPicture,
|
||||
/// played into the SVG generator
|
||||
static QByteArray exportLikeTheDialog(Diagram *diagram, bool small)
|
||||
{
|
||||
const QSize size = diagram->imageSize();
|
||||
QPicture picture;
|
||||
diagram->setSmallPictures(small);
|
||||
diagram->toPaintDevice(picture, size.width(), size.height());
|
||||
diagram->setSmallPictures(false);
|
||||
|
||||
QByteArray svg;
|
||||
QBuffer buffer(&svg);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QSvgGenerator generator;
|
||||
generator.setSize(size);
|
||||
generator.setOutputDevice(&buffer);
|
||||
QPainter painter(&generator);
|
||||
picture.play(&painter);
|
||||
painter.end();
|
||||
return svg;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
m_fixture = QFINDTESTDATA("fixtures/svg_small_pictures.qet");
|
||||
QVERIFY2(!m_fixture.isEmpty(), "fixture project not found");
|
||||
QVERIFY(QetAppTest::editor());
|
||||
}
|
||||
|
||||
void cliFullResolutionByDefault()
|
||||
{
|
||||
const QByteArray svg = exportWithCli({});
|
||||
QVERIFY2(!svg.isEmpty(), "--export-svg failed");
|
||||
QCOMPARE(embeddedSizes(svg), (QList<QSize>{QSize(40, 30), QSize(800, 600)}));
|
||||
}
|
||||
|
||||
void cliSmallPictures()
|
||||
{
|
||||
const QByteArray svg = exportWithCli({QStringLiteral("--svg-small-pictures")});
|
||||
QVERIFY2(!svg.isEmpty(), "--export-svg --svg-small-pictures failed");
|
||||
// 800 x 600 drawn at 0.1, times 4; the 40 x 30 one is kept whole
|
||||
QCOMPARE(embeddedSizes(svg), (QList<QSize>{QSize(40, 30), QSize(320, 240)}));
|
||||
}
|
||||
|
||||
void dialogSmallPicturesThenFullAgain()
|
||||
{
|
||||
QFile file(m_fixture);
|
||||
QVERIFY(file.open(QIODevice::ReadOnly));
|
||||
QDomDocument document;
|
||||
QVERIFY(document.setContent(&file));
|
||||
const QDomElement image_xml = document.elementsByTagName(
|
||||
QStringLiteral("image")).at(0).toElement();
|
||||
Diagram *diagram = QetAppTest::diagram();
|
||||
auto *picture = new DiagramImageItem();
|
||||
QVERIFY(picture->fromXml(image_xml));
|
||||
diagram->addItem(picture);
|
||||
|
||||
QCOMPARE(embeddedSizes(exportLikeTheDialog(diagram, true)),
|
||||
QList<QSize>{QSize(320, 240)});
|
||||
// The option lasts for one export only
|
||||
QVERIFY(!diagram->smallPictures());
|
||||
QCOMPARE(embeddedSizes(exportLikeTheDialog(diagram, false)),
|
||||
QList<QSize>{QSize(800, 600)});
|
||||
delete picture;
|
||||
}
|
||||
};
|
||||
|
||||
QET_APP_TEST_MAIN(tst_svgsmallpictures)
|
||||
#include "tst_svgsmallpictures.moc"
|
||||
Reference in New Issue
Block a user