diff --git a/sources/undocommand/deleteqgraphicsitemcommand.cpp b/sources/undocommand/deleteqgraphicsitemcommand.cpp
index 661e17608..8991198f0 100644
--- a/sources/undocommand/deleteqgraphicsitemcommand.cpp
+++ b/sources/undocommand/deleteqgraphicsitemcommand.cpp
@@ -16,8 +16,10 @@
along with QElectroTech. If not, see .
*/
#include "deleteqgraphicsitemcommand.h"
+#include "../wiringrules.h"
#include "../diagram.h"
+#include "../qetproject.h"
#include "addgraphicsobjectcommand.h"
#include "../qetdiagrameditor.h"
#include "../qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
@@ -207,17 +209,44 @@ void DeleteQGraphicsItemCommand::setPotentialsOfRemovedElements()
}
ConductorProperties properties = hub_terminal->conductors().first()->properties();
- for (Terminal *t : terminals_to_connect_list)
+
+ //Every terminal to the hub (a star), or, when the project
+ //limits the wires per terminal (discussion #1158), one after
+ //another (a chain): a star gives the hub a wire per other
+ //terminal, which the limit forbids.
+ QList> pairs;
+ const QETProject *project = hub_terminal->diagram() ? hub_terminal->diagram()->project() : nullptr;
+ if (project && WiringRules::chainsWires(project->wiringRules(), WiringRules::masterEnabled()))
+ {
+ QList chain {hub_terminal};
+ chain << terminals_to_connect_list;
+ QList points;
+ for (Terminal *ct : std::as_const(chain)) {
+ points << ct->scenePos();
+ }
+ const QList order = WiringRules::chainOrder(points);
+ for (int i = 1 ; i < order.size() ; ++i) {
+ pairs.append(std::make_pair(chain.at(order.at(i - 1)), chain.at(order.at(i))));
+ }
+ }
+ else
+ {
+ for (Terminal *t : std::as_const(terminals_to_connect_list)) {
+ pairs.append(std::make_pair(hub_terminal, t));
+ }
+ }
+
+ for (const auto &new_pair : std::as_const(pairs))
{
//If a conductor was already created between these two terminals
//in this undo command, from another removed element, we do nothing
bool exist_ = false;
for (std::pair pair : m_connected_terminals)
{
- if (pair.first == hub_terminal && pair.second == t) {
+ if (pair.first == new_pair.first && pair.second == new_pair.second) {
exist_ = true;
continue;
- } else if (pair.first == t && pair.second == hub_terminal) {
+ } else if (pair.first == new_pair.second && pair.second == new_pair.first) {
exist_ = true;
continue;
}
@@ -225,11 +254,11 @@ void DeleteQGraphicsItemCommand::setPotentialsOfRemovedElements()
if (exist_ == false)
{
- m_connected_terminals.append(std::make_pair((Terminal *)hub_terminal, (Terminal *)t));
+ m_connected_terminals.append(new_pair);
qInfo() << "m_connected_terminals" << m_connected_terminals;
- Conductor *new_cond = new Conductor(hub_terminal, t);
+ Conductor *new_cond = new Conductor(new_pair.first, new_pair.second);
new_cond->setProperties(properties);
- new AddGraphicsObjectCommand(new_cond, t->diagram(), QPointF(), this);
+ new AddGraphicsObjectCommand(new_cond, new_pair.second->diagram(), QPointF(), this);
}
}
}
diff --git a/sources/utils/conductorcreator.cpp b/sources/utils/conductorcreator.cpp
index 77f2009ec..2d2ed3344 100644
--- a/sources/utils/conductorcreator.cpp
+++ b/sources/utils/conductorcreator.cpp
@@ -26,6 +26,7 @@
#include "../qetgraphicsitem/element.h"
#include "../qetgraphicsitem/terminal.h"
#include "../ui/potentialselectordialog.h"
+#include "../wiringrules.h"
#include "qgraphicsitem.h"
#include
@@ -48,18 +49,19 @@ ConductorCreator::ConductorCreator(Diagram *d, QList terminals_list)
if (!setUpPropertieToUse()) {
return;
}
- Terminal *hub_terminal = hubTerminal();
-
d->undoStack().beginMacro(QObject::tr("Création de conducteurs"));
+ const bool chain = d->project()
+ && WiringRules::chainsWires(d->project()->wiringRules(), WiringRules::masterEnabled());
QList c_list;
- for (Terminal *t : m_terminals_list)
+ for (const auto &pair : terminalPairs(chain))
{
- if (t == hub_terminal) {
+ //Checked as the chain is built: the wire before this one may
+ //have just filled a terminal.
+ if (chain && !pair.first->canBeLinkedTo(pair.second)) {
continue;
}
-
- Conductor *cond = new Conductor(hub_terminal, t);
+ Conductor *cond = new Conductor(pair.first, pair.second);
cond->setProperties(m_properties);
cond->setSequenceNum(m_sequential_number);
d->undoStack().push(new AddGraphicsObjectCommand(cond, d));
@@ -224,6 +226,41 @@ QList ConductorCreator::existingPotential(const QList &
@brief ConductorCreator::hubTerminal
@return hub_terminal
*/
+/**
+ @brief ConductorCreator::terminalPairs
+ @param chain : the project limits the wires per terminal
+ (discussion #1158, WiringRules::chainsWires())
+ @return the pairs of terminals to wire: all to one hub terminal (a
+ star), or with \p chain one after another (WiringRules::chainOrder()),
+ since a star gives the hub a wire per other terminal.
+*/
+QList> ConductorCreator::terminalPairs(bool chain)
+{
+ QList> pairs;
+ if (chain)
+ {
+ QList points;
+ for (Terminal *t : std::as_const(m_terminals_list)) {
+ points << t->scenePos();
+ }
+ const QList order = WiringRules::chainOrder(points);
+ for (int i = 1 ; i < order.size() ; ++i)
+ {
+ pairs << qMakePair(m_terminals_list.at(order.at(i - 1)),
+ m_terminals_list.at(order.at(i)));
+ }
+ return pairs;
+ }
+
+ Terminal *hub_terminal = hubTerminal();
+ for (Terminal *t : std::as_const(m_terminals_list)) {
+ if (t != hub_terminal) {
+ pairs << qMakePair(hub_terminal, t);
+ }
+ }
+ return pairs;
+}
+
Terminal *ConductorCreator::hubTerminal()
{
Terminal *hub_terminal = m_terminals_list.first();
diff --git a/sources/utils/conductorcreator.h b/sources/utils/conductorcreator.h
index ec287bddd..a8c2c9a6a 100644
--- a/sources/utils/conductorcreator.h
+++ b/sources/utils/conductorcreator.h
@@ -44,6 +44,7 @@ class ConductorCreator
static QList existingPotential(const QList &terminals_list);
bool setUpPropertieToUse();
Terminal *hubTerminal();
+ QList> terminalPairs(bool chain);
QList m_terminals_list;
diff --git a/sources/wiringrules.cpp b/sources/wiringrules.cpp
index 19d7380e4..37cbddc85 100644
--- a/sources/wiringrules.cpp
+++ b/sources/wiringrules.cpp
@@ -174,6 +174,68 @@ bool WiringRules::hasRoom(int limit, int wires)
return limit <= 0 || wires < limit;
}
+/**
+ @brief WiringRules::chainsWires
+ @return true if QElectroTech's own tools that wire several terminals at
+ once must wire them one after another (a chain) rather than all to one
+ of them (a star): a star gives that one terminal a wire per other
+ terminal, which the project's limit forbids.
+*/
+bool WiringRules::chainsWires(const Settings &settings, bool master_enabled)
+{
+ return master_enabled && settings.max_wires > 0;
+}
+
+/**
+ @brief WiringRules::chainOrder
+ The order in which to wire terminals at \a points one after another:
+ from the top left one (smallest x, then smallest y, the same terminal
+ the star used as its hub), each time to the nearest terminal not yet
+ wired. Distance is along the grid (|dx| + |dy|), since wires run
+ horizontally and vertically; a tie goes to the earlier point.
+ @return indexes into \a points, each once
+*/
+QList WiringRules::chainOrder(const QList &points)
+{
+ QList order;
+ if (points.isEmpty()) {
+ return order;
+ }
+
+ int current = 0;
+ for (int i = 1 ; i < points.size() ; ++i) {
+ const QPointF &p = points.at(i);
+ const QPointF &c = points.at(current);
+ if (p.x() < c.x() || (p.x() == c.x() && p.y() < c.y())) {
+ current = i;
+ }
+ }
+
+ QList done(points.size(), false);
+ order << current;
+ done[current] = true;
+ while (order.size() < points.size())
+ {
+ int nearest = -1;
+ qreal nearest_distance = 0;
+ for (int i = 0 ; i < points.size() ; ++i) {
+ if (done.at(i)) {
+ continue;
+ }
+ const QPointF d = points.at(i) - points.at(current);
+ const qreal distance = qAbs(d.x()) + qAbs(d.y());
+ if (nearest < 0 || distance < nearest_distance) {
+ nearest = i;
+ nearest_distance = distance;
+ }
+ }
+ order << nearest;
+ done[nearest] = true;
+ current = nearest;
+ }
+ return order;
+}
+
/**
@brief WiringRules::turnsRuleOn
@param before, after : the rules in force before and after a change
diff --git a/sources/wiringrules.h b/sources/wiringrules.h
index c4eef1db8..02da242b4 100644
--- a/sources/wiringrules.h
+++ b/sources/wiringrules.h
@@ -19,6 +19,9 @@
#ifndef WIRINGRULES_H
#define WIRINGRULES_H
+#include
+#include
+
class QDomElement;
/**
@@ -79,6 +82,9 @@ namespace WiringRules
int limit(const Settings &settings, bool master_enabled, bool is_report);
bool hasRoom(int limit, int wires);
+
+ bool chainsWires(const Settings &settings, bool master_enabled);
+ QList chainOrder(const QList &points);
}
#endif // WIRINGRULES_H
diff --git a/tests/qttest/tst_wiringrules.cpp b/tests/qttest/tst_wiringrules.cpp
index be4d283e2..0cf43ec7d 100644
--- a/tests/qttest/tst_wiringrules.cpp
+++ b/tests/qttest/tst_wiringrules.cpp
@@ -137,6 +137,49 @@ class tst_wiringrules : public QObject
return {};
}
+ // On a fixture with @p rules: a symbol wired to four others is
+ // deleted, so QElectroTech rewires the four to keep the potential.
+ // Returns the most wires any one of them ends with.
+ QString mostWiresAfterDeletingTheHub(const QString &rules)
+ {
+ const QString project = fixtureWith(rules);
+ const QString script_path = m_dir.filePath(QStringLiteral("hub%1.js").arg(m_run));
+ QFile script(script_path);
+ if (project.isEmpty() || !script.open(QIODevice::WriteOnly))
+ return {};
+ // Placed on a diagonal, so no two terminals line up and nothing
+ // is auto-connected.
+ script.write(
+ "var p = 'embed://import/probe/v2_fuse.elmt';\n"
+ "var hub = qet.addElement(0, p, 400, 400);\n"
+ "var others = [];\n"
+ "for (var i = 1; i <= 4; ++i) others.push(qet.addElement(0, p, 400 + 70 * i, 400 + 90 * i));\n"
+ "others.forEach(function (o) { qet.addConductor(0, hub, 0, o, 0); });\n"
+ "qet.deleteElement(0, hub);\n"
+ "var count = {};\n"
+ "qet.conductorUuids(0).forEach(function (u) {\n"
+ " qet.conductorEnds(0, u).forEach(function (e) { count[e] = (count[e] || 0) + 1; }); });\n"
+ "var most = 0;\n"
+ "others.forEach(function (o) { most = Math.max(most, count[o + ' terminal 0'] || 0); });\n"
+ "qet.log('PROBE ' + most);\n");
+ script.close();
+
+ QProcess proc;
+ proc.setProcessEnvironment(sandbox());
+ proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--run"), script_path, project});
+ if (!proc.waitForFinished(60000))
+ return {};
+ const QString out = QString::fromUtf8(proc.readAllStandardOutput()
+ + proc.readAllStandardError());
+ const QString mark = QStringLiteral("PROBE ");
+ for (const QString &line : out.split(QLatin1Char('\n'))) {
+ const int i = line.indexOf(mark);
+ if (i >= 0)
+ return line.mid(i + mark.size()).trimmed();
+ }
+ return {};
+ }
+
private slots:
void initTestCase()
{
@@ -173,6 +216,39 @@ private slots:
QVERIFY(!WiringRules::hasRoom(1, 1));
}
+ void chainsOnlyUnderALimit()
+ {
+ WiringRules::Settings rules;
+ QVERIFY(!WiringRules::chainsWires(rules, true));
+ rules.one_wire_per_report = true;
+ QVERIFY(!WiringRules::chainsWires(rules, true));
+ rules.max_wires = 2;
+ QVERIFY(WiringRules::chainsWires(rules, true));
+ QVERIFY(!WiringRules::chainsWires(rules, false));
+ }
+
+ void chainOrder()
+ {
+ QCOMPARE(WiringRules::chainOrder({}), QList());
+ QCOMPARE(WiringRules::chainOrder({QPointF(5, 5)}), QList({0}));
+
+ // Starts top left, then always the nearest along the grid
+ const QList row {QPointF(300, 0), QPointF(0, 0), QPointF(200, 0), QPointF(100, 0)};
+ QCOMPARE(WiringRules::chainOrder(row), QList({1, 3, 2, 0}));
+
+ // Same x: the higher one starts
+ const QList column {QPointF(0, 100), QPointF(0, 0), QPointF(0, 50)};
+ QCOMPARE(WiringRules::chainOrder(column), QList({1, 2, 0}));
+
+ // Each index exactly once
+ const QList scattered {QPointF(40, 90), QPointF(10, 300), QPointF(220, 10),
+ QPointF(10, 10), QPointF(220, 300)};
+ QList order = WiringRules::chainOrder(scattered);
+ QCOMPARE(order.first(), 3);
+ std::sort(order.begin(), order.end());
+ QCOMPARE(order, QList({0, 1, 2, 3, 4}));
+ }
+
void projectOverridesApplication()
{
WiringRules::Settings application;
@@ -293,6 +369,18 @@ private slots:
// ...and the master switch still turns it off
QCOMPARE(addWireToWiredTerminal(plain, true, 1), QStringLiteral("true"));
}
+
+ void deletingASymbolChainsTheWires()
+ {
+ SKIP_WITHOUT_SCRIPTING;
+ // No rule: the four are wired to one of them, as on master
+ QCOMPARE(mostWiresAfterDeletingTheHub(QString()), QStringLiteral("3"));
+
+ // A limit: one after another, none gets more than two
+ QCOMPARE(mostWiresAfterDeletingTheHub(
+ QStringLiteral("")),
+ QStringLiteral("2"));
+ }
};
QTEST_GUILESS_MAIN(tst_wiringrules)