mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-04 18:04:14 +02:00
Merge pull request #1272 from ispyisail/feature/wires-per-terminal
Add an optional limit on wires per terminal (discussion #1158)
This commit is contained in:
@@ -179,6 +179,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/wiringlistexport.cpp
|
||||
${QET_DIR}/sources/wirehops.h
|
||||
${QET_DIR}/sources/wirehops.cpp
|
||||
${QET_DIR}/sources/wiringrules.h
|
||||
${QET_DIR}/sources/wiringrules.cpp
|
||||
${QET_DIR}/sources/conductorrouter.h
|
||||
${QET_DIR}/sources/conductorrouter.cpp
|
||||
${QET_DIR}/sources/ui/wiringlistdialog.h
|
||||
@@ -797,6 +799,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/ui/importelementtextpatterndialog.h
|
||||
${QET_DIR}/sources/ui/jumptoelementdialog.cpp
|
||||
${QET_DIR}/sources/ui/jumptoelementdialog.h
|
||||
${QET_DIR}/sources/ui/wiringruleswarning.cpp
|
||||
${QET_DIR}/sources/ui/wiringruleswarning.h
|
||||
${QET_DIR}/sources/ui/inditextpropertieswidget.cpp
|
||||
${QET_DIR}/sources/ui/inditextpropertieswidget.h
|
||||
${QET_DIR}/sources/ui/linksingleelementwidget.cpp
|
||||
|
||||
@@ -1345,7 +1345,8 @@ bool Element::removeTextFromGroup(DynamicElementTextItem *text,
|
||||
The first Terminal of QPair is a Terminal owned by this element,
|
||||
this terminal haven't got any conductor docked.
|
||||
The second Terminal of QPair is a Terminal owned by an other element,
|
||||
which is aligned with the first Terminal. The second Terminal can have or not docked conductors.
|
||||
which is aligned with the first Terminal. The second Terminal can have or not docked conductors,
|
||||
but no more than the project's wires-per-terminal limit allows (discussion #1158).
|
||||
*/
|
||||
QList <QPair <Terminal *, Terminal *> > Element::AlignedFreeTerminals() const
|
||||
{
|
||||
@@ -1357,7 +1358,9 @@ QList <QPair <Terminal *, Terminal *> > Element::AlignedFreeTerminals() const
|
||||
{
|
||||
Terminal *other_terminal =
|
||||
terminal -> alignedWithTerminal();
|
||||
if (other_terminal)
|
||||
if (other_terminal
|
||||
&& terminal->hasRoomForWire()
|
||||
&& other_terminal->hasRoomForWire())
|
||||
list << qMakePair(terminal, other_terminal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "conductortextitem.h"
|
||||
#include "../wiringrules.h"
|
||||
|
||||
#include <QToolTip>
|
||||
|
||||
#include <QtCore/qnumeric.h>
|
||||
#include <utility>
|
||||
@@ -610,7 +613,22 @@ void Terminal::mouseReleaseEvent(QGraphicsSceneMouseEvent *e)
|
||||
other_terminal -> m_hovered = false;
|
||||
|
||||
//We stop her if we can't link this terminal with other terminal
|
||||
if (!canBeLinkedTo(other_terminal)) return;
|
||||
if (!canBeLinkedTo(other_terminal))
|
||||
{
|
||||
//Say why when it is the wire limit (discussion #1158): a red
|
||||
//terminal alone does not tell the user what to do instead.
|
||||
const Terminal *full = !hasRoomForWire() ? this
|
||||
: !other_terminal->hasRoomForWire() ? other_terminal
|
||||
: nullptr;
|
||||
if (full) {
|
||||
QToolTip::showText(e->screenPos(),
|
||||
tr("Cette borne a déjà %n conducteur(s), la limite du projet. "
|
||||
"Ajoutez une borne pour raccorder un conducteur de plus.",
|
||||
"wire refused by the wires-per-terminal limit",
|
||||
full->wireLimit()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Create conductor
|
||||
Conductor *new_conductor = new Conductor(this, other_terminal);
|
||||
@@ -692,6 +710,7 @@ bool Terminal::isLinkedTo(Terminal *other_terminal) {
|
||||
Reasons for not linable:
|
||||
- \p other_terminal is this terminal
|
||||
- this terminal is already connected to \p other_terminal
|
||||
- either terminal already has as many wires as the project allows
|
||||
@param other_terminal
|
||||
@return true if this terminal can be linked to other_terminal,
|
||||
otherwise false
|
||||
@@ -701,9 +720,40 @@ bool Terminal::canBeLinkedTo(Terminal *other_terminal)
|
||||
if (other_terminal == this || isLinkedTo(other_terminal))
|
||||
return false;
|
||||
|
||||
if (!hasRoomForWire() || !other_terminal->hasRoomForWire())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Terminal::wireLimit
|
||||
@return the most wires this terminal may take under its project's
|
||||
wiring rules (discussion #1158), 0 for no limit.
|
||||
*/
|
||||
int Terminal::wireLimit() const
|
||||
{
|
||||
const Diagram *parent_diagram = diagram();
|
||||
if (!parent_diagram || !parent_diagram->project()) {
|
||||
return 0;
|
||||
}
|
||||
const Element *element = parentElement();
|
||||
const bool is_report = element
|
||||
&& (element->linkType() & (Element::NextReport | Element::PreviousReport));
|
||||
return WiringRules::limit(parent_diagram->project()->wiringRules(),
|
||||
WiringRules::masterEnabled(),
|
||||
is_report);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Terminal::hasRoomForWire
|
||||
@return true if one more wire may be connected to this terminal
|
||||
*/
|
||||
bool Terminal::hasRoomForWire() const
|
||||
{
|
||||
return WiringRules::hasRoom(wireLimit(), conductorsCount());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Terminal::conductors
|
||||
@return La liste des conducteurs lies a cette borne
|
||||
|
||||
@@ -92,6 +92,8 @@ class Terminal : public QGraphicsObject
|
||||
void updateConductor();
|
||||
bool isLinkedTo(Terminal *);
|
||||
bool canBeLinkedTo(Terminal *);
|
||||
int wireLimit() const;
|
||||
bool hasRoomForWire() const;
|
||||
|
||||
// methods related to XML import/export
|
||||
static bool valideXml(QDomElement &);
|
||||
|
||||
@@ -1257,6 +1257,37 @@ void QETProject::setWireHops(WireHops::Mode mode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::wiringRules
|
||||
@return how many wires a terminal of this project may take
|
||||
(discussion #1158): the project's own rules when it sets them,
|
||||
otherwise the application's (Settings > General).
|
||||
*/
|
||||
WiringRules::Settings QETProject::wiringRules() const {
|
||||
return WiringRules::effective(m_wiring_rules, WiringRules::applicationSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::projectWiringRules
|
||||
@return the rules as the project stores them: Settings::own false when
|
||||
it follows the application's.
|
||||
*/
|
||||
WiringRules::Settings QETProject::projectWiringRules() const {
|
||||
return m_wiring_rules;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::setWiringRules
|
||||
Set the project's own wiring rules, or (Settings::own false) make it
|
||||
follow the application's. Only wires drawn from now on are affected:
|
||||
none already drawn is removed.
|
||||
@param rules
|
||||
*/
|
||||
void QETProject::setWiringRules(const WiringRules::Settings &rules)
|
||||
{
|
||||
m_wiring_rules = rules;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::autoBreakConductor
|
||||
@return true if use of auto break conductor is authorized.
|
||||
@@ -1367,6 +1398,7 @@ QDomDocument QETProject::toXml()
|
||||
// local, non-transmitted usage tracking (time spent on this project)
|
||||
writeUsageXml(project_root);
|
||||
writeWireHopsXml(project_root);
|
||||
writeWiringRulesXml(project_root);
|
||||
|
||||
// Properties for news diagrams
|
||||
QDomElement new_diagrams_properties = xml_doc.createElement("newdiagrams");
|
||||
@@ -1877,6 +1909,7 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
|
||||
//Load the local, non-transmitted usage tracking
|
||||
readUsageXml(xml_project);
|
||||
readWireHopsXml(xml_project);
|
||||
readWiringRulesXml(xml_project);
|
||||
|
||||
//Load the default properties for the new diagrams
|
||||
readDefaultPropertiesXml(xml_project);
|
||||
@@ -2070,6 +2103,17 @@ void QETProject::readWireHopsXml(QDomDocument &xml_project)
|
||||
: WireHops::fromString(crossings.attribute(QStringLiteral("hop")));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::readWiringRulesXml
|
||||
Read the <wiring_rules> element of the project, if any.
|
||||
A project without it sets no rule.
|
||||
@param xml_project : the xml description of the project
|
||||
*/
|
||||
void QETProject::readWiringRulesXml(QDomDocument &xml_project)
|
||||
{
|
||||
m_wiring_rules = WiringRules::fromXml(xml_project.documentElement());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::readDefaultPropertiesXml
|
||||
load default properties for new diagram, found in the xml of this project
|
||||
@@ -2232,6 +2276,16 @@ void QETProject::writeWireHopsXml(QDomElement &xml_element)
|
||||
xml_element.appendChild(crossings);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::writeWiringRulesXml
|
||||
Export the project's wiring rules as a <wiring_rules> child of
|
||||
\a xml_element, only when one is set.
|
||||
*/
|
||||
void QETProject::writeWiringRulesXml(QDomElement &xml_element)
|
||||
{
|
||||
WiringRules::toXml(m_wiring_rules, xml_element);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::writeDefaultPropertiesXml
|
||||
Export all defaults properties used by a new diagram and his content
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "titleblock/templatescollection.h"
|
||||
#include "titleblockproperties.h"
|
||||
#include "wirehops.h"
|
||||
#include "wiringrules.h"
|
||||
#include "diagram.h"
|
||||
#ifdef BUILD_WITHOUT_KF
|
||||
# include "ui/nokde/kautosavefile.h"
|
||||
@@ -211,6 +212,9 @@ class QETProject : public QObject
|
||||
void setAutoConductor (bool ac);
|
||||
WireHops::Mode wireHops() const;
|
||||
void setWireHops(WireHops::Mode mode);
|
||||
WiringRules::Settings wiringRules() const;
|
||||
WiringRules::Settings projectWiringRules() const;
|
||||
void setWiringRules(const WiringRules::Settings &rules);
|
||||
void setAutoBreakConductor (bool abc);
|
||||
void setAutoElement (bool ae);
|
||||
void autoFolioNumberingNewFolios ();
|
||||
@@ -308,11 +312,13 @@ class QETProject : public QObject
|
||||
void readTerminalStripXml(const QDomDocument &xml_project);
|
||||
void readUsageXml(QDomDocument &xml_project);
|
||||
void readWireHopsXml(QDomDocument &xml_project);
|
||||
void readWiringRulesXml(QDomDocument &xml_project);
|
||||
|
||||
void writeProjectPropertiesXml(QDomElement &);
|
||||
void writeDefaultPropertiesXml(QDomElement &);
|
||||
void writeUsageXml(QDomElement &);
|
||||
void writeWireHopsXml(QDomElement &);
|
||||
void writeWiringRulesXml(QDomElement &);
|
||||
void addDiagram(Diagram *diagram, int pos = -1);
|
||||
void detachDiagram(Diagram *diagram);
|
||||
void writeBackup();
|
||||
@@ -371,6 +377,7 @@ class QETProject : public QObject
|
||||
QString m_current_element_autonum;
|
||||
bool m_auto_conductor = true;
|
||||
WireHops::Mode m_wire_hops = WireHops::Mode::None;
|
||||
WiringRules::Settings m_wiring_rules;
|
||||
bool m_auto_break_conductor = false;
|
||||
XmlElementCollection *m_elements_collection = nullptr;
|
||||
bool m_freeze_new_elements = false;
|
||||
|
||||
@@ -973,6 +973,11 @@ bool QetScriptApi::addConductor(int folioIndex,
|
||||
log(QStringLiteral("qet.addConductor: those two terminals are already wired together"));
|
||||
return false;
|
||||
}
|
||||
if (!t1->hasRoomForWire() || !t2->hasRoomForWire()) {
|
||||
log(QStringLiteral("qet.addConductor: a terminal already has as many wires as the "
|
||||
"project's wires-per-terminal limit allows"));
|
||||
return false;
|
||||
}
|
||||
if (!t1->canBeLinkedTo(t2)) {
|
||||
log(QStringLiteral("qet.addConductor: those two terminals cannot be linked"));
|
||||
return false;
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "../../utils/qetutils.h"
|
||||
#include "../../qetmessagebox.h"
|
||||
#include "../../textgrid.h"
|
||||
#include "../../wiringrules.h"
|
||||
#include "../wiringruleswarning.h"
|
||||
#include "../../editor/terminalnamecheck.h"
|
||||
#include "../../ElementsCollection/qetlabelsfile.h"
|
||||
#include "../prefixconfigurationdialog.h"
|
||||
@@ -113,6 +115,20 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
|
||||
ui->m_use_windows_mode_rb->setChecked(true);
|
||||
ui->m_zoom_out_beyond_folio->setChecked(settings.value("diagrameditor/zoom-out-beyond-of-folio", false).toBool());
|
||||
ui->m_conductor_properties_panel->setChecked(settings.value("diagrameditor/conductor_properties_panel", false).toBool());
|
||||
ui->m_wiring_rules_cb->setChecked(WiringRules::masterEnabled());
|
||||
{
|
||||
//The rules every project follows unless it sets its own (#1158)
|
||||
const WiringRules::Settings rules = WiringRules::applicationSettings();
|
||||
ui->m_wiring_max_wires_sb->setValue(rules.max_wires);
|
||||
ui->m_wiring_one_wire_per_report_cb->setChecked(rules.one_wire_per_report);
|
||||
auto enable = [this](bool on) {
|
||||
ui->m_wiring_max_wires_label->setEnabled(on);
|
||||
ui->m_wiring_max_wires_sb->setEnabled(on);
|
||||
ui->m_wiring_one_wire_per_report_cb->setEnabled(on);
|
||||
};
|
||||
enable(ui->m_wiring_rules_cb->isChecked());
|
||||
connect(ui->m_wiring_rules_cb, &QCheckBox::toggled, this, enable);
|
||||
}
|
||||
ui->m_use_gesture_trackpad->setChecked(settings.value("diagramview/gestures", false).toBool());
|
||||
ui->m_save_label_paste->setChecked(settings.value("diagramcommands/erase-label-on-copy", true).toBool());
|
||||
ui->m_enable_scripting->setChecked(QetSettings::scriptingEnabled());
|
||||
@@ -334,6 +350,17 @@ void GeneralConfigurationPage::applyConf()
|
||||
settings.setValue("diagrameditor/highlight-integrated-elements", ui->m_highlight_integrated_elements->isChecked());
|
||||
settings.setValue("diagrameditor/zoom-out-beyond-of-folio", ui->m_zoom_out_beyond_folio->isChecked());
|
||||
settings.setValue("diagrameditor/conductor_properties_panel", ui->m_conductor_properties_panel->isChecked());
|
||||
WiringRules::setMasterEnabled(ui->m_wiring_rules_cb->isChecked());
|
||||
{
|
||||
const WiringRules::Settings before = WiringRules::applicationSettings();
|
||||
WiringRules::Settings rules = before;
|
||||
rules.max_wires = ui->m_wiring_max_wires_sb->value();
|
||||
rules.one_wire_per_report = ui->m_wiring_one_wire_per_report_cb->isChecked();
|
||||
WiringRules::setApplicationSettings(rules);
|
||||
if (WiringRules::masterEnabled() && WiringRules::turnsRuleOn(before, rules)) {
|
||||
WiringRulesWarning::show(this);
|
||||
}
|
||||
}
|
||||
settings.setValue("diagrameditor/autosave-interval", ui->m_autosave_sb->value());
|
||||
|
||||
settings.setValue("diagrameditor/grid_display_startup", ui->grid_startup_cb->isChecked());
|
||||
|
||||
@@ -70,6 +70,76 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="m_wiring_rules_cb">
|
||||
<property name="toolTip">
|
||||
<string>Décoché, aucune règle de conducteurs par borne n'est appliquée, quels que soient les réglages des projets.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Appliquer les règles de conducteurs par borne des projets</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="m_wiring_max_wires_layout">
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="m_wiring_max_wires_label">
|
||||
<property name="text">
|
||||
<string>Nombre maximal de conducteurs par borne :</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="m_wiring_max_wires_sb">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Pour tous les projets, sauf ceux qui ont leurs propres réglages. Un nouveau conducteur qui dépasserait ce nombre sur une borne est refusé ; les conducteurs déjà dessinés ne sont pas modifiés. 4 correspond à deux embouts doubles, un de chaque côté de la vis.</string>
|
||||
</property>
|
||||
<property name="specialValueText">
|
||||
<string>Sans limite</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="m_wiring_max_wires_spacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="m_wiring_report_layout">
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="m_wiring_one_wire_per_report_cb">
|
||||
<property name="toolTip">
|
||||
<string>Un renvoi de folio est un point virtuel : il ne reçoit qu'un conducteur, celui qui continue sur l'autre folio.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Un seul conducteur par renvoi de folio</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
@@ -1297,6 +1367,9 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
<tabstop>m_use_gesture_trackpad</tabstop>
|
||||
<tabstop>m_zoom_out_beyond_folio</tabstop>
|
||||
<tabstop>m_conductor_properties_panel</tabstop>
|
||||
<tabstop>m_wiring_rules_cb</tabstop>
|
||||
<tabstop>m_wiring_max_wires_sb</tabstop>
|
||||
<tabstop>m_wiring_one_wire_per_report_cb</tabstop>
|
||||
<tabstop>m_use_windows_mode_rb</tabstop>
|
||||
<tabstop>m_use_tab_mode_rb</tabstop>
|
||||
<tabstop>m_save_label_paste</tabstop>
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "../qet.h"
|
||||
#include "../qeticons.h"
|
||||
#include "../qetproject.h"
|
||||
#include "../wiringrules.h"
|
||||
#include "../wiringruleswarning.h"
|
||||
#include "../borderpropertieswidget.h"
|
||||
#include "../conductorpropertieswidget.h"
|
||||
#include "../diagramcontextwidget.h"
|
||||
@@ -176,6 +178,23 @@ void ProjectMainConfigPage::applyProjectConf()
|
||||
modified_project = true;
|
||||
}
|
||||
|
||||
//Following the application's rules stores nothing in the project
|
||||
WiringRules::Settings wiring_rules;
|
||||
if (!use_application_rules_cb_ -> isChecked()) {
|
||||
wiring_rules.own = true;
|
||||
wiring_rules.max_wires = max_wires_sb_ -> value();
|
||||
wiring_rules.one_wire_per_report = one_wire_per_report_cb_ -> isChecked();
|
||||
}
|
||||
if (m_project -> projectWiringRules() != wiring_rules) {
|
||||
const WiringRules::Settings before = m_project -> wiringRules();
|
||||
m_project -> setWiringRules(wiring_rules);
|
||||
modified_project = true;
|
||||
if (WiringRules::masterEnabled()
|
||||
&& WiringRules::turnsRuleOn(before, m_project -> wiringRules())) {
|
||||
WiringRulesWarning::show(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (modified_project) {
|
||||
m_project -> setModified(true);
|
||||
}
|
||||
@@ -225,6 +244,39 @@ void ProjectMainConfigPage::initWidgets()
|
||||
wire_hops_cb_ -> setToolTip(tr("Dessine un petit arc là où deux conducteurs se croisent sans être reliés. "
|
||||
"Seul le dessin change : aucun élément n'est ajouté et aucun conducteur n'est coupé.",
|
||||
"tooltip"));
|
||||
|
||||
//How many wires a terminal may take (discussion #1158)
|
||||
wiring_rules_gb_ = new QGroupBox(tr("Conducteurs par borne", "group box title"));
|
||||
use_application_rules_cb_ = new QCheckBox(tr("Utiliser les réglages de l'application", "checkbox label"));
|
||||
use_application_rules_cb_ -> setToolTip(tr("Les réglages de Configurer QElectroTech > Général s'appliquent. "
|
||||
"Décochez pour donner à ce projet ses propres réglages, enregistrés dans le projet.",
|
||||
"tooltip"));
|
||||
connect(use_application_rules_cb_, &QCheckBox::toggled, this, [this](bool use) {
|
||||
//Show the values that will apply: the application's, or the
|
||||
//application's as a starting point for the project's own
|
||||
if (use) {
|
||||
const WiringRules::Settings application = WiringRules::applicationSettings();
|
||||
max_wires_sb_ -> setValue(application.max_wires);
|
||||
one_wire_per_report_cb_ -> setChecked(application.one_wire_per_report);
|
||||
}
|
||||
updateWiringRulesWidgets();
|
||||
});
|
||||
max_wires_label_ = new QLabel(tr("Nombre maximal de conducteurs par borne :", "label when configuring"));
|
||||
max_wires_sb_ = new QSpinBox();
|
||||
max_wires_sb_ -> setRange(0, 99);
|
||||
max_wires_sb_ -> setSpecialValueText(tr("Sans limite", "wires per terminal"));
|
||||
max_wires_sb_ -> setToolTip(tr("Un nouveau conducteur qui dépasserait ce nombre sur une borne est refusé. "
|
||||
"Les conducteurs déjà dessinés ne sont pas modifiés. "
|
||||
"4 correspond à deux embouts doubles, un de chaque côté de la vis.",
|
||||
"tooltip"));
|
||||
one_wire_per_report_cb_ = new QCheckBox(tr("Un seul conducteur par renvoi de folio", "checkbox label"));
|
||||
one_wire_per_report_cb_ -> setToolTip(tr("Un renvoi de folio est un point virtuel : il ne reçoit qu'un conducteur, "
|
||||
"celui qui continue sur l'autre folio.",
|
||||
"tooltip"));
|
||||
wiring_rules_off_label_ = new QLabel(tr("Ces règles sont désactivées pour tous les projets "
|
||||
"(Configurer QElectroTech > Général).",
|
||||
"informative label"));
|
||||
wiring_rules_off_label_ -> setWordWrap(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,6 +309,18 @@ void ProjectMainConfigPage::initLayout()
|
||||
wire_hops_layout0 -> addWidget(wire_hops_cb_);
|
||||
wire_hops_layout0 -> addStretch();
|
||||
main_layout0 -> addLayout(wire_hops_layout0);
|
||||
main_layout0 -> addSpacing(10);
|
||||
|
||||
QVBoxLayout *wiring_rules_layout = new QVBoxLayout(wiring_rules_gb_);
|
||||
wiring_rules_layout -> addWidget(use_application_rules_cb_);
|
||||
QHBoxLayout *max_wires_layout = new QHBoxLayout();
|
||||
max_wires_layout -> addWidget(max_wires_label_);
|
||||
max_wires_layout -> addWidget(max_wires_sb_);
|
||||
max_wires_layout -> addStretch();
|
||||
wiring_rules_layout -> addLayout(max_wires_layout);
|
||||
wiring_rules_layout -> addWidget(one_wire_per_report_cb_);
|
||||
wiring_rules_layout -> addWidget(wiring_rules_off_label_);
|
||||
main_layout0 -> addWidget(wiring_rules_gb_);
|
||||
|
||||
setLayout(main_layout0);
|
||||
this -> setMinimumWidth(680);
|
||||
@@ -280,6 +344,34 @@ void ProjectMainConfigPage::readValuesFromProject()
|
||||
|
||||
const int wire_hops_index = wire_hops_cb_ -> findData(WireHops::toString(m_project -> wireHops()));
|
||||
wire_hops_cb_ -> setCurrentIndex(qMax(0, wire_hops_index));
|
||||
|
||||
//The rules that apply: the project's own or the application's
|
||||
const WiringRules::Settings wiring_rules = m_project -> wiringRules();
|
||||
{
|
||||
const QSignalBlocker blocker(use_application_rules_cb_);
|
||||
use_application_rules_cb_ -> setChecked(!wiring_rules.own);
|
||||
}
|
||||
max_wires_sb_ -> setValue(wiring_rules.max_wires);
|
||||
one_wire_per_report_cb_ -> setChecked(wiring_rules.one_wire_per_report);
|
||||
updateWiringRulesWidgets();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ProjectMainConfigPage::updateWiringRulesWidgets
|
||||
Enable the wiring rule fields only when they can change something: the
|
||||
master switch is on and the project sets its own rules.
|
||||
*/
|
||||
void ProjectMainConfigPage::updateWiringRulesWidgets()
|
||||
{
|
||||
//The master switch wins over every project: say so rather than
|
||||
//let the user set a rule that does nothing.
|
||||
const bool master = WiringRules::masterEnabled();
|
||||
const bool own = master && !use_application_rules_cb_ -> isChecked();
|
||||
wiring_rules_off_label_ -> setVisible(!master);
|
||||
use_application_rules_cb_ -> setEnabled(master);
|
||||
max_wires_label_ -> setEnabled(own);
|
||||
max_wires_sb_ -> setEnabled(own);
|
||||
one_wire_per_report_cb_ -> setEnabled(own);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,6 +399,7 @@ void ProjectMainConfigPage::adjustReadOnly()
|
||||
usage_enabled_cb_ -> setDisabled(is_read_only);
|
||||
usage_reset_pb_ -> setDisabled(is_read_only);
|
||||
wire_hops_cb_ -> setDisabled(is_read_only);
|
||||
wiring_rules_gb_ -> setDisabled(is_read_only);
|
||||
}
|
||||
|
||||
//######################################################################################//
|
||||
|
||||
@@ -24,6 +24,8 @@ class QComboBox;
|
||||
class QLineEdit;
|
||||
class QCheckBox;
|
||||
class QPushButton;
|
||||
class QSpinBox;
|
||||
class QGroupBox;
|
||||
class QETProject;
|
||||
class BorderPropertiesWidget;
|
||||
class ConductorPropertiesWidget;
|
||||
@@ -114,6 +116,7 @@ class ProjectMainConfigPage : public ProjectConfigPage {
|
||||
|
||||
private slots:
|
||||
void resetUsageTracker();
|
||||
void updateWiringRulesWidgets();
|
||||
|
||||
// attributes
|
||||
protected:
|
||||
@@ -128,6 +131,12 @@ class ProjectMainConfigPage : public ProjectConfigPage {
|
||||
QPushButton *usage_reset_pb_;
|
||||
QLabel *wire_hops_label_;
|
||||
QComboBox *wire_hops_cb_;
|
||||
QGroupBox *wiring_rules_gb_;
|
||||
QCheckBox *use_application_rules_cb_;
|
||||
QLabel *max_wires_label_;
|
||||
QSpinBox *max_wires_sb_;
|
||||
QCheckBox *one_wire_per_report_cb_;
|
||||
QLabel *wiring_rules_off_label_;
|
||||
};
|
||||
|
||||
class ProjectAutoNumConfigPage : public ProjectConfigPage {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "wiringruleswarning.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMessageBox>
|
||||
|
||||
/**
|
||||
@brief WiringRulesWarning::show
|
||||
Warn, when a wires-per-terminal rule is turned on (discussion #1158,
|
||||
review of #1272), that the rules are experimental: they count wires on
|
||||
a terminal as QElectroTech models them today, and would have to be
|
||||
redesigned if wires and conductors become separate objects. A plain
|
||||
warning, as the element editor gives for unnamed terminals: OK goes on.
|
||||
@param parent
|
||||
*/
|
||||
void WiringRulesWarning::show(QWidget *parent)
|
||||
{
|
||||
QMessageBox::warning(
|
||||
parent,
|
||||
QApplication::translate("WiringRulesWarning", "Avertissement"),
|
||||
QApplication::translate(
|
||||
"WiringRulesWarning",
|
||||
"<b>Les règles de conducteurs par borne sont une fonction expérimentale.</b>"
|
||||
"<br><br>Elles comptent les conducteurs tels que QElectroTech les représente "
|
||||
"aujourd'hui. Elles pourraient changer, et vos réglages devoir être refaits, "
|
||||
"si les fils et les conducteurs deviennent des objets distincts dans une "
|
||||
"version future."
|
||||
"<br><br>Toutes ces règles peuvent être désactivées dans Configurer "
|
||||
"QElectroTech > Général."));
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#ifndef WIRINGRULESWARNING_H
|
||||
#define WIRINGRULESWARNING_H
|
||||
|
||||
class QWidget;
|
||||
|
||||
namespace WiringRulesWarning
|
||||
{
|
||||
void show(QWidget *parent);
|
||||
}
|
||||
|
||||
#endif // WIRINGRULESWARNING_H
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "wiringrules.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
#include <QSettings>
|
||||
|
||||
namespace {
|
||||
const QString element_name = QStringLiteral("wiring_rules");
|
||||
const QString max_wires_attribute = QStringLiteral("max_wires_per_terminal");
|
||||
const QString report_attribute = QStringLiteral("one_wire_per_report");
|
||||
|
||||
//The master switch is read on every wire drawn, so it is read from
|
||||
//the settings once and kept; setMasterEnabled() keeps both in step.
|
||||
int master_cache = -1;
|
||||
//Same for the application's rules
|
||||
bool application_cached = false;
|
||||
WiringRules::Settings application_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::masterEnabled
|
||||
@return false when the user turned every rule of this feature off for
|
||||
every project (Settings > General). On by default: a project still has
|
||||
to turn a rule on before anything changes.
|
||||
*/
|
||||
bool WiringRules::masterEnabled()
|
||||
{
|
||||
if (master_cache < 0) {
|
||||
master_cache = QSettings().value(settings_key, true).toBool() ? 1 : 0;
|
||||
}
|
||||
return master_cache == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::setMasterEnabled
|
||||
Save the master switch.
|
||||
@param enabled
|
||||
*/
|
||||
void WiringRules::setMasterEnabled(bool enabled)
|
||||
{
|
||||
QSettings().setValue(settings_key, enabled);
|
||||
master_cache = enabled ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::applicationSettings
|
||||
@return the rules set for every project in Settings > General; a
|
||||
project that sets its own (Settings::own) uses those instead.
|
||||
*/
|
||||
WiringRules::Settings WiringRules::applicationSettings()
|
||||
{
|
||||
if (!application_cached) {
|
||||
const QSettings settings;
|
||||
application_cache = Settings();
|
||||
application_cache.max_wires = qMax(0, settings.value(max_wires_key, 0).toInt());
|
||||
application_cache.one_wire_per_report = settings.value(one_wire_per_report_key, false).toBool();
|
||||
application_cached = true;
|
||||
}
|
||||
return application_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::setApplicationSettings
|
||||
Save the rules set for every project.
|
||||
@param settings
|
||||
*/
|
||||
void WiringRules::setApplicationSettings(const Settings &settings)
|
||||
{
|
||||
QSettings qsettings;
|
||||
qsettings.setValue(max_wires_key, qMax(0, settings.max_wires));
|
||||
qsettings.setValue(one_wire_per_report_key, settings.one_wire_per_report);
|
||||
application_cache = settings;
|
||||
application_cache.own = false;
|
||||
application_cached = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::effective
|
||||
@return the rules that apply to a project: its own when it sets them,
|
||||
otherwise the application's.
|
||||
*/
|
||||
WiringRules::Settings WiringRules::effective(const Settings &project, const Settings &application)
|
||||
{
|
||||
Settings result = project.own ? project : application;
|
||||
result.own = project.own;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::fromXml
|
||||
@param project_root : the root element of a project
|
||||
@return the rules stored in the <wiring_rules> child of \a project_root,
|
||||
which the project then uses instead of the application's (Settings::own);
|
||||
the default, which follows the application, when there is none.
|
||||
*/
|
||||
WiringRules::Settings WiringRules::fromXml(const QDomElement &project_root)
|
||||
{
|
||||
Settings settings;
|
||||
const QDomElement rules = project_root.firstChildElement(element_name);
|
||||
if (rules.isNull()) {
|
||||
return settings;
|
||||
}
|
||||
settings.own = true;
|
||||
settings.max_wires = qMax(0, rules.attribute(max_wires_attribute, QStringLiteral("0")).toInt());
|
||||
settings.one_wire_per_report = rules.attribute(report_attribute) == QLatin1String("true");
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::toXml
|
||||
Write \a settings as a <wiring_rules> child of \a project_root, only
|
||||
when the project sets its own rules: a project that follows the
|
||||
application's saves exactly as before.
|
||||
*/
|
||||
void WiringRules::toXml(const Settings &settings, QDomElement &project_root)
|
||||
{
|
||||
if (!settings.own) {
|
||||
return;
|
||||
}
|
||||
QDomElement rules = project_root.ownerDocument().createElement(element_name);
|
||||
if (settings.max_wires > 0) {
|
||||
rules.setAttribute(max_wires_attribute, settings.max_wires);
|
||||
}
|
||||
if (settings.one_wire_per_report) {
|
||||
rules.setAttribute(report_attribute, QStringLiteral("true"));
|
||||
}
|
||||
project_root.appendChild(rules);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::limit
|
||||
@param settings : the project's rules
|
||||
@param master_enabled : the master switch, masterEnabled()
|
||||
@param is_report : the terminal belongs to a folio report
|
||||
@return the most wires the terminal may take, 0 for no limit.
|
||||
*/
|
||||
int WiringRules::limit(const Settings &settings, bool master_enabled, bool is_report)
|
||||
{
|
||||
if (!master_enabled) {
|
||||
return 0;
|
||||
}
|
||||
if (is_report && settings.one_wire_per_report) {
|
||||
return 1;
|
||||
}
|
||||
return settings.max_wires;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::hasRoom
|
||||
@param limit : WiringRules::limit()
|
||||
@param wires : the wires the terminal already has
|
||||
@return true if one more wire may be connected
|
||||
*/
|
||||
bool WiringRules::hasRoom(int limit, int wires)
|
||||
{
|
||||
return limit <= 0 || wires < limit;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief WiringRules::turnsRuleOn
|
||||
@param before, after : the rules in force before and after a change
|
||||
@return true if the change turns on a rule that was off: QElectroTech
|
||||
then warns that the rules are experimental (review of #1272).
|
||||
*/
|
||||
bool WiringRules::turnsRuleOn(const Settings &before, const Settings &after)
|
||||
{
|
||||
return (after.max_wires > 0 && before.max_wires <= 0)
|
||||
|| (after.one_wire_per_report && !before.one_wire_per_report);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#ifndef WIRINGRULES_H
|
||||
#define WIRINGRULES_H
|
||||
|
||||
class QDomElement;
|
||||
|
||||
/**
|
||||
@brief The WiringRules namespace
|
||||
How many wires a terminal may take (discussion #1158). A real terminal
|
||||
takes one or two wires, sometimes four with double ferrules; a folio
|
||||
report is a virtual point that should carry one. When a project sets a
|
||||
limit, a new wire that would go past it is refused. Wires already in
|
||||
the drawing are never touched.
|
||||
|
||||
The rules are set once for the application (Settings > General,
|
||||
applicationSettings()) and apply to every project; a project can set
|
||||
its own instead (Settings::own), saved in the project so it travels
|
||||
with the drawing. Every rule is off by default, and the application
|
||||
has a master switch (masterEnabled()) that turns all of them off in
|
||||
every project at once. Kept free of any QGraphicsItem so the rules can
|
||||
be tested on their own.
|
||||
*/
|
||||
namespace WiringRules
|
||||
{
|
||||
///The QSettings key of the master switch
|
||||
constexpr const char *settings_key = "diagrameditor/wiring_rules_enabled";
|
||||
///The QSettings keys of the application's rules
|
||||
constexpr const char *max_wires_key = "diagrameditor/wiring_rules_max_wires";
|
||||
constexpr const char *one_wire_per_report_key = "diagrameditor/wiring_rules_one_wire_per_report";
|
||||
|
||||
///A set of rules: the application's, or a project's. The default is
|
||||
///"no rule".
|
||||
struct Settings
|
||||
{
|
||||
///For a project: it uses these values rather than the
|
||||
///application's. Ignored in the application's own settings.
|
||||
bool own = false;
|
||||
///Most wires a terminal may take, 0 for no limit
|
||||
int max_wires = 0;
|
||||
///A folio report takes one wire only
|
||||
bool one_wire_per_report = false;
|
||||
|
||||
bool isDefault() const { return *this == Settings(); }
|
||||
bool operator==(const Settings &other) const {
|
||||
return own == other.own
|
||||
&& max_wires == other.max_wires
|
||||
&& one_wire_per_report == other.one_wire_per_report;
|
||||
}
|
||||
bool operator!=(const Settings &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
bool masterEnabled();
|
||||
void setMasterEnabled(bool enabled);
|
||||
|
||||
Settings applicationSettings();
|
||||
void setApplicationSettings(const Settings &settings);
|
||||
Settings effective(const Settings &project, const Settings &application);
|
||||
bool turnsRuleOn(const Settings &before, const Settings &after);
|
||||
|
||||
Settings fromXml(const QDomElement &project_root);
|
||||
void toXml(const Settings &settings, QDomElement &project_root);
|
||||
|
||||
int limit(const Settings &settings, bool master_enabled, bool is_report);
|
||||
bool hasRoom(int limit, int wires);
|
||||
}
|
||||
|
||||
#endif // WIRINGRULES_H
|
||||
@@ -468,6 +468,21 @@ target_link_libraries(tst_wirehops PRIVATE Qt::Test Qt::Gui)
|
||||
target_compile_definitions(tst_wirehops PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||
|
||||
# Wires per terminal (discussion #1158): the rules in wiringrules.cpp, and
|
||||
# through the real binary, that --resave keeps the project setting, adds
|
||||
# none to a project without it, and that a wire past the limit is refused
|
||||
# unless the master switch is off.
|
||||
add_executable(
|
||||
tst_wiringrules
|
||||
tst_wiringrules.cpp
|
||||
${QET_DIR}/sources/wiringrules.cpp)
|
||||
add_test(NAME tst_wiringrules COMMAND tst_wiringrules)
|
||||
add_dependencies(tst_wiringrules qelectrotech)
|
||||
target_include_directories(tst_wiringrules PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_wiringrules PRIVATE Qt::Test Qt::Xml)
|
||||
target_compile_definitions(tst_wiringrules PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||
|
||||
# The routing behind qet.routeConductor(): conductorrouter.cpp needs only
|
||||
# Qt Core, so the geometry is tested without a scene.
|
||||
add_executable(
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <QtTest>
|
||||
|
||||
#include <QDir>
|
||||
#include <QDomDocument>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include "wiringrules.h"
|
||||
|
||||
// The checks that drive QElectroTech through a script (--run) need a
|
||||
// build with scripting; without it --run is not an option and the
|
||||
// process never exits (QET_HAS_SCRIPTING, top-level CMakeLists.txt).
|
||||
#ifdef QET_HAS_SCRIPTING
|
||||
# define SKIP_WITHOUT_SCRIPTING
|
||||
#else
|
||||
# define SKIP_WITHOUT_SCRIPTING QSKIP("built without scripting: --run is not available")
|
||||
#endif
|
||||
|
||||
// How many wires a terminal may take (discussion #1158). The rules are
|
||||
// tested on their own; the project setting and the refusal are tested
|
||||
// through the real binary: --resave keeps a <wiring_rules> element and
|
||||
// adds none to a project without it, and qet.addConductor() is refused
|
||||
// past the limit unless the master switch is off.
|
||||
class tst_wiringrules : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
int m_run = 0;
|
||||
|
||||
// Environment of one sandboxed run; @p master_off writes the master
|
||||
// switch off into that run's own settings, @p application_max_wires
|
||||
// the limit every project follows unless it sets its own.
|
||||
QProcessEnvironment sandbox(bool master_off = false, int application_max_wires = 0)
|
||||
{
|
||||
const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run));
|
||||
const QString tmp = m_dir.filePath(QStringLiteral("tmp%1").arg(m_run));
|
||||
const QString settings = m_dir.filePath(QStringLiteral("settings%1").arg(m_run));
|
||||
++m_run;
|
||||
QDir().mkpath(home);
|
||||
QDir().mkpath(tmp);
|
||||
QDir().mkpath(settings + QStringLiteral("/QElectroTech"));
|
||||
if (master_off || application_max_wires) {
|
||||
QFile ini(settings + QStringLiteral("/QElectroTech/QElectroTech.ini"));
|
||||
if (ini.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
ini.write(QStringLiteral("[diagrameditor]\nwiring_rules_enabled=%1\nwiring_rules_max_wires=%2\n")
|
||||
.arg(master_off ? QStringLiteral("false") : QStringLiteral("true"))
|
||||
.arg(application_max_wires).toUtf8());
|
||||
}
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
|
||||
env.insert(QStringLiteral("HOME"), home);
|
||||
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/.config"));
|
||||
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/.local/share"));
|
||||
env.insert(QStringLiteral("QET_SETTINGS_DIR"), settings);
|
||||
env.insert(QStringLiteral("TMPDIR"), tmp);
|
||||
return env;
|
||||
}
|
||||
|
||||
// The fixture with @p rules inserted before <newdiagrams>, as a new file
|
||||
QString fixtureWith(const QString &rules)
|
||||
{
|
||||
QFile source(QFINDTESTDATA("fixtures/qet_bug_repro_resaved.qet"));
|
||||
if (!source.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return {};
|
||||
QString text = QString::fromUtf8(source.readAll());
|
||||
const int newdiagrams = text.indexOf(QLatin1String("<newdiagrams"));
|
||||
if (newdiagrams < 0)
|
||||
return {};
|
||||
text.insert(newdiagrams, rules + QStringLiteral("\n "));
|
||||
const QString path = m_dir.filePath(QStringLiteral("fixture%1.qet").arg(m_run));
|
||||
QFile out(path);
|
||||
if (!out.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
return {};
|
||||
out.write(text.toUtf8());
|
||||
return path;
|
||||
}
|
||||
|
||||
// Runs --resave on @p in, returns the saved file's text (empty on failure)
|
||||
QString resave(const QString &in)
|
||||
{
|
||||
const QString out = m_dir.filePath(QStringLiteral("out%1.qet").arg(m_run));
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(sandbox());
|
||||
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--resave"), in, out});
|
||||
if (!proc.waitForFinished(120000) || proc.exitCode() != 0)
|
||||
return {};
|
||||
QFile file(out);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return {};
|
||||
return QString::fromUtf8(file.readAll());
|
||||
}
|
||||
|
||||
// Wires a free terminal to a terminal that already has a wire, through
|
||||
// qet.addConductor(); returns what the script printed after "PROBE ".
|
||||
QString addWireToWiredTerminal(const QString &project, bool master_off,
|
||||
int application_max_wires = 0)
|
||||
{
|
||||
const QString script_path = m_dir.filePath(QStringLiteral("probe%1.js").arg(m_run));
|
||||
QFile script(script_path);
|
||||
if (!script.open(QIODevice::WriteOnly))
|
||||
return {};
|
||||
// The first end of the first conductor is a terminal with a wire.
|
||||
// Any terminal of another element with no wire is the other end.
|
||||
script.write(
|
||||
"var end = qet.conductorEnds(0, qet.conductorUuids(0)[0])[0].split(' terminal ');\n"
|
||||
"var wired = {};\n"
|
||||
"qet.conductorUuids(0).forEach(function (u) {\n"
|
||||
" qet.conductorEnds(0, u).forEach(function (e) { wired[e] = true; }); });\n"
|
||||
"var free = null;\n"
|
||||
"qet.elementUuids(0).forEach(function (el) {\n"
|
||||
" if (free || el == end[0]) return;\n"
|
||||
" var n = qet.elementTerminals(0, el).length;\n"
|
||||
" for (var i = 0; i < n && !free; ++i)\n"
|
||||
" if (!wired[el + ' terminal ' + i]) free = [el, i]; });\n"
|
||||
"qet.log('PROBE ' + (free ? qet.addConductor(0, end[0], parseInt(end[1]), free[0], free[1])\n"
|
||||
" : 'nofree'));\n");
|
||||
script.close();
|
||||
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(sandbox(master_off, application_max_wires));
|
||||
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()
|
||||
{
|
||||
QVERIFY(m_dir.isValid());
|
||||
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
|
||||
}
|
||||
|
||||
void limit()
|
||||
{
|
||||
WiringRules::Settings rules;
|
||||
QCOMPARE(WiringRules::limit(rules, true, false), 0);
|
||||
QCOMPARE(WiringRules::limit(rules, true, true), 0);
|
||||
|
||||
rules.max_wires = 4;
|
||||
QCOMPARE(WiringRules::limit(rules, true, false), 4);
|
||||
QCOMPARE(WiringRules::limit(rules, true, true), 4);
|
||||
|
||||
rules.one_wire_per_report = true;
|
||||
QCOMPARE(WiringRules::limit(rules, true, true), 1);
|
||||
QCOMPARE(WiringRules::limit(rules, true, false), 4);
|
||||
|
||||
// The master switch turns every rule off
|
||||
QCOMPARE(WiringRules::limit(rules, false, false), 0);
|
||||
QCOMPARE(WiringRules::limit(rules, false, true), 0);
|
||||
}
|
||||
|
||||
void hasRoom()
|
||||
{
|
||||
QVERIFY(WiringRules::hasRoom(0, 0));
|
||||
QVERIFY(WiringRules::hasRoom(0, 19));
|
||||
QVERIFY(WiringRules::hasRoom(2, 1));
|
||||
QVERIFY(!WiringRules::hasRoom(2, 2));
|
||||
QVERIFY(!WiringRules::hasRoom(2, 3));
|
||||
QVERIFY(!WiringRules::hasRoom(1, 1));
|
||||
}
|
||||
|
||||
void projectOverridesApplication()
|
||||
{
|
||||
WiringRules::Settings application;
|
||||
application.max_wires = 2;
|
||||
application.one_wire_per_report = true;
|
||||
|
||||
// A project that follows the application gets its rules
|
||||
WiringRules::Settings follows;
|
||||
QCOMPARE(WiringRules::effective(follows, application).max_wires, 2);
|
||||
QVERIFY(WiringRules::effective(follows, application).one_wire_per_report);
|
||||
QVERIFY(!WiringRules::effective(follows, application).own);
|
||||
|
||||
// Its own rules win, "no limit" included
|
||||
WiringRules::Settings own;
|
||||
own.own = true;
|
||||
QCOMPARE(WiringRules::effective(own, application).max_wires, 0);
|
||||
QVERIFY(!WiringRules::effective(own, application).one_wire_per_report);
|
||||
QVERIFY(WiringRules::effective(own, application).own);
|
||||
own.max_wires = 6;
|
||||
QCOMPARE(WiringRules::effective(own, application).max_wires, 6);
|
||||
}
|
||||
|
||||
void warnsWhenARuleIsTurnedOn()
|
||||
{
|
||||
WiringRules::Settings off;
|
||||
WiringRules::Settings limit;
|
||||
limit.max_wires = 4;
|
||||
WiringRules::Settings report;
|
||||
report.one_wire_per_report = true;
|
||||
|
||||
QVERIFY(WiringRules::turnsRuleOn(off, limit));
|
||||
QVERIFY(WiringRules::turnsRuleOn(off, report));
|
||||
QVERIFY(WiringRules::turnsRuleOn(limit, [&]{ auto s = limit; s.one_wire_per_report = true; return s; }()));
|
||||
// Changing a limit that was already on, or turning rules off: no warning
|
||||
QVERIFY(!WiringRules::turnsRuleOn(limit, [&]{ auto s = limit; s.max_wires = 2; return s; }()));
|
||||
QVERIFY(!WiringRules::turnsRuleOn(limit, off));
|
||||
QVERIFY(!WiringRules::turnsRuleOn(off, off));
|
||||
}
|
||||
|
||||
void xmlRoundTrip()
|
||||
{
|
||||
QDomDocument doc;
|
||||
QDomElement root = doc.createElement(QStringLiteral("project"));
|
||||
doc.appendChild(root);
|
||||
|
||||
// Following the application: nothing written, and reading
|
||||
// nothing follows the application
|
||||
WiringRules::Settings follows;
|
||||
follows.max_wires = 3; // not the project's: not written
|
||||
WiringRules::toXml(follows, root);
|
||||
QVERIFY(root.firstChildElement().isNull());
|
||||
QVERIFY(WiringRules::fromXml(root).isDefault());
|
||||
|
||||
// Its own rules, even "none", are written and read back as its own
|
||||
WiringRules::Settings none;
|
||||
none.own = true;
|
||||
WiringRules::toXml(none, root);
|
||||
QCOMPARE(root.firstChildElement().tagName(), QStringLiteral("wiring_rules"));
|
||||
QVERIFY(WiringRules::fromXml(root) == none);
|
||||
root.removeChild(root.firstChildElement());
|
||||
|
||||
WiringRules::Settings rules;
|
||||
rules.own = true;
|
||||
rules.max_wires = 2;
|
||||
rules.one_wire_per_report = true;
|
||||
WiringRules::toXml(rules, root);
|
||||
QCOMPARE(root.firstChildElement().tagName(), QStringLiteral("wiring_rules"));
|
||||
QVERIFY(WiringRules::fromXml(root) == rules);
|
||||
}
|
||||
|
||||
void savedSettingSurvivesResave()
|
||||
{
|
||||
const QString plain = resave(QFINDTESTDATA("fixtures/qet_bug_repro_resaved.qet"));
|
||||
QVERIFY2(!plain.isEmpty(), "--resave failed");
|
||||
QVERIFY(!plain.contains(QLatin1String("wiring_rules")));
|
||||
|
||||
const QString with_rules = fixtureWith(
|
||||
QStringLiteral("<wiring_rules max_wires_per_terminal=\"2\" one_wire_per_report=\"true\"/>"));
|
||||
QVERIFY(!with_rules.isEmpty());
|
||||
const QString saved = resave(with_rules);
|
||||
QVERIFY2(!saved.isEmpty(), "--resave failed");
|
||||
QVERIFY2(saved.contains(QLatin1String("max_wires_per_terminal=\"2\""))
|
||||
&& saved.contains(QLatin1String("one_wire_per_report=\"true\"")),
|
||||
"the setting was lost on save");
|
||||
|
||||
// A project's own "no rule" is kept too: it overrides the application's
|
||||
const QString own_none = fixtureWith(QStringLiteral("<wiring_rules/>"));
|
||||
QVERIFY(!own_none.isEmpty());
|
||||
const QString saved_none = resave(own_none);
|
||||
QVERIFY2(!saved_none.isEmpty(), "--resave failed");
|
||||
QVERIFY2(saved_none.contains(QLatin1String("<wiring_rules/>")),
|
||||
"a project's own rules were lost on save");
|
||||
}
|
||||
|
||||
void wirePastTheLimitIsRefused()
|
||||
{
|
||||
SKIP_WITHOUT_SCRIPTING;
|
||||
// Without a rule the wire is drawn, as on master
|
||||
QCOMPARE(addWireToWiredTerminal(QFINDTESTDATA("fixtures/qet_bug_repro_resaved.qet"), false),
|
||||
QStringLiteral("true"));
|
||||
|
||||
// One wire per terminal: the terminal is full
|
||||
const QString limited = fixtureWith(
|
||||
QStringLiteral("<wiring_rules max_wires_per_terminal=\"1\"/>"));
|
||||
QVERIFY(!limited.isEmpty());
|
||||
QCOMPARE(addWireToWiredTerminal(limited, false), QStringLiteral("false"));
|
||||
|
||||
// The master switch off: the project's rule does nothing
|
||||
QCOMPARE(addWireToWiredTerminal(limited, true), QStringLiteral("true"));
|
||||
|
||||
// The application's limit applies to a project that sets none...
|
||||
const QString plain = QFINDTESTDATA("fixtures/qet_bug_repro_resaved.qet");
|
||||
QCOMPARE(addWireToWiredTerminal(plain, false, 1), QStringLiteral("false"));
|
||||
// ...not to one that sets its own, here "no limit"
|
||||
const QString own_none = fixtureWith(QStringLiteral("<wiring_rules/>"));
|
||||
QVERIFY(!own_none.isEmpty());
|
||||
QCOMPARE(addWireToWiredTerminal(own_none, false, 1), QStringLiteral("true"));
|
||||
// ...and the master switch still turns it off
|
||||
QCOMPARE(addWireToWiredTerminal(plain, true, 1), QStringLiteral("true"));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(tst_wiringrules)
|
||||
|
||||
#include "tst_wiringrules.moc"
|
||||
Reference in New Issue
Block a user