mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-11 06:34:14 +02:00
Add cable management: drawing, cores, numbering, cross-references and cable list
Cables are drawn line objects (toolbar button next to the auto break/reconnect button, click-move-click, right click cancels), not components. The type comes from a CSV catalog following the material list pattern (designation, cores, core colours, editable through the entry dialog with "Nouvelle entrée" / "Modifier l'entrée"). Each core is bound to the wire its colour label stands on, and the cable reference is generated per core and written into the Conductor entry via Conductor::setCableReference(label, cable uuid, slot). The conductor entry is where it has to live because the terminal strip plan (Klemmenplan) consumes it in the next step. Numbering: "Cables" rule under Programmeinstellungen/Neues Projekt/Nummerierung auto (mirrored in project properties), literal "W" fallback without a rule, French gate dialog when no rule exists, Nummerierung menu with the renumber dialog (preferred axis, per-folio counter, rule and counter state written back, hand-typed names ask, whole-project scope). Also included: label block and under-line texts with per-text font and alignment plus the material-list cell margins, free-core placement and right-click removal, claim questions for wires of other cables, multi-selection drag as one undo step, type change with confirmation, cross-references for type "Cable" (%f-%l%c, own font, clickable links in exported PDFs), report propagation of the cable definition, Listes menu (table of contents, material list, terminal strip manager, terminal generator plugin, cable list), cable list with freely selectable columns (installation/localisation of cable, start and end, Blatt start/end, length, used cores...), CSV export dialog with column pick and preview in the Projekt menu, script API (addTable kind "cable_list", exportCableList) and a unit test for the type catalog.
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
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 "addcablecommand.h"
|
||||
|
||||
#include "cablemanager.h"
|
||||
#include "cablepart.h"
|
||||
#include "../autoNum/numerotationcontextcommands.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::AddCableCommand
|
||||
@param cable the cable the line was just given to
|
||||
@param part the line which was just drawn
|
||||
@param diagram the folio it was drawn on
|
||||
@param own_cable true when the cable was created by this very action
|
||||
@param taken the cores this line took away from other cables, so
|
||||
undo can give them back
|
||||
*/
|
||||
AddCableCommand::AddCableCommand(Cable *cable,
|
||||
CablePart *part,
|
||||
Diagram *diagram,
|
||||
bool own_cable,
|
||||
const QList<CableCoreTaken> &taken) :
|
||||
m_cable(cable),
|
||||
m_part(part),
|
||||
m_diagram(diagram),
|
||||
m_taken(taken),
|
||||
m_own_cable(own_cable)
|
||||
{
|
||||
if (!m_cable || !m_part) return;
|
||||
|
||||
m_part_data.uuid = m_part->partUuid();
|
||||
m_part_data.diagram = m_diagram ? m_diagram->uuid() : QUuid();
|
||||
m_part_data.p1 = m_part->firstPoint();
|
||||
m_part_data.p2 = m_part->secondPoint();
|
||||
|
||||
for (const CableCore &core : m_cable->usedCores()) {
|
||||
if (core.part == m_part_data.uuid) {
|
||||
m_cores.append(core);
|
||||
}
|
||||
}
|
||||
|
||||
//A cable this action brought into being takes the next number
|
||||
//the project's numbering rule hands out. The number itself was
|
||||
//worked out before this command existed; what has to happen here
|
||||
//is moving the rule on, and moving it is undoable just like the
|
||||
//cable is -- so undoing gives the number back and the cable
|
||||
//drawn after that takes it again instead of skipping over it.
|
||||
//
|
||||
//Only the rule as it stands now is taken down. Where the rule
|
||||
//stands after this cable is worked out when the cable really
|
||||
//comes into being, because several cables pasted together are
|
||||
//all made before any of them is added, and each of them has to
|
||||
//move the rule on from wherever the one before it left it.
|
||||
if (m_own_cable && m_diagram && project() && project()->hasCableAutoNum())
|
||||
{
|
||||
const QString key = project()->cableCurrentAutoNum();
|
||||
const NumerotationContext current = project()->cableAutoNum(key);
|
||||
if (!current.isEmpty())
|
||||
{
|
||||
m_num_key = key;
|
||||
m_num_before = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::~AddCableCommand
|
||||
Whatever the stack leaves behind has to be cleaned up here: a cable
|
||||
which is not held by the project any more belongs to this command,
|
||||
and so does a line which never made it onto a folio.
|
||||
*/
|
||||
AddCableCommand::~AddCableCommand()
|
||||
{
|
||||
if (m_cable && m_own_cable && !project()->cables().contains(m_cable)) {
|
||||
delete m_cable;
|
||||
}
|
||||
if (m_part && !m_part->scene()) {
|
||||
delete m_part;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::project
|
||||
@return the project this command works in, or nullptr
|
||||
*/
|
||||
QETProject *AddCableCommand::project() const
|
||||
{
|
||||
return m_diagram ? m_diagram->project() : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::redo
|
||||
Put the line on the folio, together with everything the cable holds
|
||||
for it.
|
||||
*/
|
||||
void AddCableCommand::redo()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
if (!proj || !m_cable || !m_part || !m_diagram) return;
|
||||
|
||||
if (m_own_cable) {
|
||||
proj->addCable(m_cable);
|
||||
} else {
|
||||
if (!m_cable->hasPart(m_part_data.uuid)) {
|
||||
m_cable->addPart(m_part_data);
|
||||
}
|
||||
for (const CableCore &core : m_cores) {
|
||||
m_cable->setCore(core);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_part->scene() != m_diagram) {
|
||||
m_diagram->addItem(m_part);
|
||||
}
|
||||
|
||||
//Take the cores away from the cables which held them before this
|
||||
//line claimed those conductors. On the very first run they are
|
||||
//already gone, so nothing happens then; redoing after an undo
|
||||
//has to take them again.
|
||||
for (const CableCoreTaken &taken : std::as_const(m_taken))
|
||||
{
|
||||
if (taken.cable && taken.cable != m_cable) {
|
||||
taken.cable->removeCore(taken.core.core);
|
||||
}
|
||||
}
|
||||
//The cable field of every conductor is worked out again either
|
||||
//way: redoing after an undo puts the cable back into the project
|
||||
//without it having said anything, and the wires it feeds would go
|
||||
//on showing a blank cable field although the cable which
|
||||
//generates that field is right there again.
|
||||
//
|
||||
//And the number this cable was given is spent now: the rule
|
||||
//moves on to the one the next cable will be handed. Worked out
|
||||
//here rather than when the command was made, because a run of
|
||||
//cables pasted together is made from end to end before any of
|
||||
//it is added -- each of them has to move the rule on from where
|
||||
//the one before it left it.
|
||||
if (!m_num_key.isEmpty()) {
|
||||
NumerotationContextCommands ncc(proj->cableAutoNum(m_num_key),
|
||||
m_diagram.data());
|
||||
proj->addCableAutoNum(m_num_key, ncc.next());
|
||||
proj->setCurrentCableAutoNum(m_num_key);
|
||||
}
|
||||
CableManager::refreshLabels(proj);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::undo
|
||||
Take the line back off the folio. An existing cable loses the
|
||||
section and the cores it was carrying on it -- and with them the
|
||||
cable field of the conductors, which CableManager works out again as
|
||||
soon as the cable says it changed.
|
||||
|
||||
The cores this line had taken away from other cables go back to
|
||||
those cables afterwards: without them the conductors they belong to
|
||||
would be left with a blank cable field although the cable which
|
||||
generates that field is still there.
|
||||
*/
|
||||
void AddCableCommand::undo()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
if (!proj || !m_cable || !m_part || !m_diagram) return;
|
||||
|
||||
if (m_part->scene()) {
|
||||
m_diagram->removeItem(m_part);
|
||||
}
|
||||
|
||||
if (m_own_cable) {
|
||||
//The cable keeps everything it knows: it is only taken out of
|
||||
//the project, so redo finds it exactly as it was.
|
||||
proj->removeCable(m_cable);
|
||||
} else {
|
||||
m_cable->removePart(m_part_data.uuid);
|
||||
}
|
||||
|
||||
for (const CableCoreTaken &taken : std::as_const(m_taken))
|
||||
{
|
||||
if (taken.cable && taken.cable != m_cable) {
|
||||
taken.cable->setCore(taken.core);
|
||||
}
|
||||
}
|
||||
//The number this cable was given goes back into the rule, so
|
||||
//the cable drawn after an undo takes that number rather than
|
||||
//the one after it: nothing of it is left spent.
|
||||
if (!m_num_key.isEmpty()) {
|
||||
proj->addCableAutoNum(m_num_key, m_num_before);
|
||||
proj->setCurrentCableAutoNum(m_num_key);
|
||||
}
|
||||
|
||||
//Same as in redo(): worked out again whatever happened above,
|
||||
//so a cable section or a whole cable taken away never leaves
|
||||
//the wires it used to feed with a cable field it still writes.
|
||||
CableManager::refreshLabels(proj);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef ADDCABLECOMMAND_H
|
||||
#define ADDCABLECOMMAND_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include "../autoNum/numerotationcontext.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QUndoCommand>
|
||||
|
||||
class CablePart;
|
||||
class Diagram;
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief Puts a drawn cable line on the folio, or takes it back off.
|
||||
|
||||
Both halves are one single step, because a cable line without its
|
||||
cable is of no use to anybody: undoing has to take the line away and
|
||||
leave nothing behind, and redoing has to put both back exactly as
|
||||
they were.
|
||||
*/
|
||||
class AddCableCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
AddCableCommand(Cable *cable,
|
||||
CablePart *part,
|
||||
Diagram *diagram,
|
||||
bool own_cable,
|
||||
const QList<CableCoreTaken> &taken = QList<CableCoreTaken>());
|
||||
~AddCableCommand() override;
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
QETProject *project() const;
|
||||
|
||||
private:
|
||||
///The cable the line belongs to
|
||||
Cable *m_cable = nullptr;
|
||||
///The line itself, owned by the folio once it is drawn
|
||||
QPointer<CablePart> m_part;
|
||||
QPointer<Diagram> m_diagram;
|
||||
///Which section of the cable this line is
|
||||
CablePartData m_part_data;
|
||||
///The cores the line was giving to the cable, put back by redo
|
||||
QList<CableCore> m_cores;
|
||||
///The cores this line took away from other cables: redo takes
|
||||
///them again, undo gives them back
|
||||
QList<CableCoreTaken> m_taken;
|
||||
///True when this command brought the cable into existence, so it
|
||||
///is the one which has to take it away again
|
||||
bool m_own_cable = false;
|
||||
/**
|
||||
When this cable took a number out of the project's numbering
|
||||
rule, the rule as it was before it did and as it was after:
|
||||
an empty key means the project numbers its cables by no rule
|
||||
and nothing here applies. Handing the number out is part of
|
||||
bringing the cable into being, so undo has to hand it back --
|
||||
otherwise undoing a cable and drawing it once more would
|
||||
leave a gap in the numbering for good.
|
||||
*/
|
||||
QString m_num_key;
|
||||
NumerotationContext m_num_before;
|
||||
};
|
||||
|
||||
#endif // ADDCABLECOMMAND_H
|
||||
@@ -0,0 +1,921 @@
|
||||
/*
|
||||
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 "cable.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
/**
|
||||
@brief The name an alignment is written under in the file, an
|
||||
empty string when the alignment is the plain default (the text
|
||||
ends where it always did, before the line).
|
||||
@param alignment Qt::AlignLeft, Qt::AlignHCenter or Qt::AlignRight
|
||||
@return left, center or right
|
||||
*/
|
||||
QString alignmentName(int alignment)
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case int(Qt::AlignLeft): return QStringLiteral("left");
|
||||
case int(Qt::AlignHCenter): return QStringLiteral("center");
|
||||
case int(Qt::AlignRight): return QStringLiteral("right");
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief The alignment a name written in the file stands for.
|
||||
@param name left, center or right
|
||||
@return the Qt flags, 0 when the name says nothing
|
||||
*/
|
||||
int alignmentFromName(const QString &name)
|
||||
{
|
||||
if (name == QLatin1String("left")) return int(Qt::AlignLeft);
|
||||
if (name == QLatin1String("center")) return int(Qt::AlignHCenter);
|
||||
if (name == QLatin1String("right")) return int(Qt::AlignRight);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::Cable
|
||||
@param parent
|
||||
*/
|
||||
Cable::Cable(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_uuid(QUuid::createUuid())
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::uuid
|
||||
@return the identity of the cable, never empty once constructed
|
||||
*/
|
||||
QUuid Cable::uuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setUuid
|
||||
Only used while reading a project: a cable keeps the identity it was
|
||||
saved with, so the conductors it feeds stay bound to it.
|
||||
@param uuid
|
||||
*/
|
||||
void Cable::setUuid(const QUuid &uuid)
|
||||
{
|
||||
if (uuid.isNull() || uuid == m_uuid) {
|
||||
return;
|
||||
}
|
||||
m_uuid = uuid;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::designation
|
||||
@return the cable number, e.g. 15W1, which heads the label drawn next
|
||||
to the trunk line and opens every core label
|
||||
*/
|
||||
QString Cable::designation() const
|
||||
{
|
||||
return m_designation;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setDesignation
|
||||
@param designation
|
||||
*/
|
||||
void Cable::setDesignation(const QString &designation)
|
||||
{
|
||||
if (m_designation == designation) return;
|
||||
m_designation = designation;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::designationByHand
|
||||
@return true when the name of this cable was typed in rather than
|
||||
worked out of the numbering rule
|
||||
*/
|
||||
bool Cable::designationByHand() const
|
||||
{
|
||||
return m_designation_by_hand;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setDesignationByHand
|
||||
Says where the name of this cable comes from. Nothing of it is
|
||||
drawn, so it changes no label and asks for no redraw.
|
||||
@param by_hand
|
||||
*/
|
||||
void Cable::setDesignationByHand(bool by_hand)
|
||||
{
|
||||
m_designation_by_hand = by_hand;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::bmk
|
||||
@return the BMK of the cable
|
||||
*/
|
||||
QString Cable::bmk() const
|
||||
{
|
||||
return m_bmk;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setBmk
|
||||
@param bmk
|
||||
*/
|
||||
void Cable::setBmk(const QString &bmk)
|
||||
{
|
||||
if (m_bmk == bmk) return;
|
||||
m_bmk = bmk;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::installation
|
||||
@return the installation (Anlage) the cable belongs to
|
||||
*/
|
||||
QString Cable::installation() const
|
||||
{
|
||||
return m_installation;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setInstallation
|
||||
@param installation
|
||||
*/
|
||||
void Cable::setInstallation(const QString &installation)
|
||||
{
|
||||
if (m_installation == installation) return;
|
||||
m_installation = installation;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::location
|
||||
@return the location (Ort) the cable runs through
|
||||
*/
|
||||
QString Cable::location() const
|
||||
{
|
||||
return m_location;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setLocation
|
||||
@param location
|
||||
*/
|
||||
void Cable::setLocation(const QString &location)
|
||||
{
|
||||
if (m_location == location) return;
|
||||
m_location = location;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::length
|
||||
@return the length of the cable as it is written down: a free text,
|
||||
because it may come from the drawing or be typed in by hand
|
||||
*/
|
||||
QString Cable::length() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setLength
|
||||
@param length
|
||||
*/
|
||||
void Cable::setLength(const QString &length)
|
||||
{
|
||||
if (m_length == length) return;
|
||||
m_length = length;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::properties
|
||||
@return every field the user fills in, together with what of it he
|
||||
want to see drawn next to the trunk line
|
||||
*/
|
||||
CableProperties Cable::properties() const
|
||||
{
|
||||
CableProperties properties;
|
||||
properties.designation = m_designation;
|
||||
properties.designation_by_hand = m_designation_by_hand;
|
||||
properties.type = m_type;
|
||||
properties.installation = m_installation;
|
||||
properties.location = m_location;
|
||||
properties.length = m_length;
|
||||
properties.show_designation = m_show_designation;
|
||||
properties.show_type = m_show_type;
|
||||
properties.show_installation = m_show_installation;
|
||||
properties.show_location = m_show_location;
|
||||
properties.show_length = m_show_length;
|
||||
properties.texts = m_texts;
|
||||
properties.text_offset = m_text_offset;
|
||||
properties.core_offset = m_core_offset;
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setProperties
|
||||
Write a whole set of fields at once -- one undo step covers it, and
|
||||
the label next to the trunk line and the conductors it feeds are
|
||||
worked out again once, not ten times.
|
||||
@param properties
|
||||
*/
|
||||
void Cable::setProperties(const CableProperties &properties)
|
||||
{
|
||||
if (properties == this->properties()) return;
|
||||
|
||||
m_designation = properties.designation;
|
||||
m_designation_by_hand = properties.designation_by_hand;
|
||||
m_type = properties.type;
|
||||
m_installation = properties.installation;
|
||||
m_location = properties.location;
|
||||
m_length = properties.length;
|
||||
m_show_designation = properties.show_designation;
|
||||
m_show_type = properties.show_type;
|
||||
m_show_installation = properties.show_installation;
|
||||
m_show_location = properties.show_location;
|
||||
m_show_length = properties.show_length;
|
||||
m_texts = properties.texts;
|
||||
m_text_offset = properties.text_offset;
|
||||
m_core_offset = properties.core_offset;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::type
|
||||
@return the type of the cable, e.g. H07RN-F 4G1,5. The type is copied
|
||||
from the cable type file when the cable is created, so a project
|
||||
opens even when that file is gone or has changed since.
|
||||
*/
|
||||
QString Cable::type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setType
|
||||
@param type
|
||||
*/
|
||||
void Cable::setType(const QString &type)
|
||||
{
|
||||
if (m_type == type) return;
|
||||
m_type = type;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::coreCount
|
||||
@return how many cores the type of this cable has. This is only the
|
||||
size of the type, not the number of cores actually wired: see
|
||||
usedCoreCount().
|
||||
*/
|
||||
int Cable::coreCount() const
|
||||
{
|
||||
return m_core_count;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setCoreCount
|
||||
@param count
|
||||
*/
|
||||
void Cable::setCoreCount(int count)
|
||||
{
|
||||
count = qMax(0, count);
|
||||
if (m_core_count == count) return;
|
||||
m_core_count = count;
|
||||
|
||||
//A type with fewer cores takes the cores it no longer has away
|
||||
//from the conductors which showed them: the label of a core the
|
||||
//cable doesn't own would be a lie.
|
||||
for (int i = m_cores.size() - 1; i >= 0; --i) {
|
||||
if (m_cores.at(i).core >= m_core_count) {
|
||||
m_cores.removeAt(i);
|
||||
}
|
||||
}
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::coreColors
|
||||
@return the colour of each core, in the order of the cable type file
|
||||
*/
|
||||
QStringList Cable::coreColors() const
|
||||
{
|
||||
return m_core_colors;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setCoreColors
|
||||
@param colors
|
||||
*/
|
||||
void Cable::setCoreColors(const QStringList &colors)
|
||||
{
|
||||
if (m_core_colors == colors) return;
|
||||
m_core_colors = colors;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::typeState
|
||||
@return everything the type of this cable decides, taken in one
|
||||
block: changing the type works on that block, and undo puts the very
|
||||
same block back -- name, size, colours and the cores which go with
|
||||
them, so no mark can be left standing on a core the cable lost.
|
||||
*/
|
||||
CableTypeState Cable::typeState() const
|
||||
{
|
||||
CableTypeState state;
|
||||
state.type = m_type;
|
||||
state.core_count = m_core_count;
|
||||
state.core_colors = m_core_colors;
|
||||
state.cores = m_cores;
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::applyTypeState
|
||||
Put back a state taken with typeState().
|
||||
|
||||
The count comes before the cores: a cable which has fewer cores than
|
||||
it had cannot hold the others, and setCores() drops what stands
|
||||
beyond the count -- which is exactly what a type with fewer cores
|
||||
means, and exactly what undo has to take back.
|
||||
@param state
|
||||
*/
|
||||
void Cable::applyTypeState(const CableTypeState &state)
|
||||
{
|
||||
setType(state.type);
|
||||
setCoreColors(state.core_colors);
|
||||
setCoreCount(state.core_count);
|
||||
setCores(state.cores);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::usedCores
|
||||
@return every core wired to a conductor, ordered by core number
|
||||
*/
|
||||
QList<CableCore> Cable::usedCores() const
|
||||
{
|
||||
QList<CableCore> cores = m_cores;
|
||||
std::sort(cores.begin(), cores.end(),
|
||||
[](const CableCore &a, const CableCore &b) {return a.core < b.core;});
|
||||
return cores;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::hasCore
|
||||
@param core
|
||||
@return true when that core is wired to a conductor
|
||||
*/
|
||||
bool Cable::hasCore(int core) const
|
||||
{
|
||||
for (const CableCore &c : m_cores) {
|
||||
if (c.core == core) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::core
|
||||
@param core
|
||||
@return the assignment of that core, an empty one when it is free
|
||||
*/
|
||||
CableCore Cable::core(int core) const
|
||||
{
|
||||
for (const CableCore &c : m_cores) {
|
||||
if (c.core == core) return c;
|
||||
}
|
||||
return CableCore();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setCore
|
||||
Wire a core to a conductor, or move the slash of an already wired
|
||||
core. A core number may only appear once, so the previous holder of
|
||||
that number, if any, is replaced.
|
||||
@param core
|
||||
*/
|
||||
void Cable::setCore(const CableCore &core)
|
||||
{
|
||||
if (core.core < 0 || core.core >= m_core_count) return;
|
||||
|
||||
for (int i = 0; i < m_cores.size(); ++i) {
|
||||
if (m_cores.at(i).core == core.core) {
|
||||
if (m_cores.at(i).conductor == core.conductor
|
||||
&& m_cores.at(i).part == core.part
|
||||
&& m_cores.at(i).position == core.position) {
|
||||
return;
|
||||
}
|
||||
m_cores[i] = core;
|
||||
emit changed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_cores.append(core);
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setCores
|
||||
Write a whole set of assignments in place of the current one, the way
|
||||
undoing a dragged slash has to: every core which was moved, swapped
|
||||
or dropped comes back at once.
|
||||
@param cores
|
||||
*/
|
||||
void Cable::setCores(const QList<CableCore> &cores)
|
||||
{
|
||||
m_cores = cores;
|
||||
for (int i = m_cores.size() - 1; i >= 0; --i) {
|
||||
if (m_cores.at(i).core < 0 || m_cores.at(i).core >= m_core_count) {
|
||||
m_cores.removeAt(i);
|
||||
}
|
||||
}
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::removeCore
|
||||
Wire a core back to nothing: its conductor stops showing the label
|
||||
this cable gave it.
|
||||
@param core
|
||||
*/
|
||||
void Cable::removeCore(int core)
|
||||
{
|
||||
for (int i = 0; i < m_cores.size(); ++i) {
|
||||
if (m_cores.at(i).core == core) {
|
||||
m_cores.removeAt(i);
|
||||
emit changed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::usedCoreCount
|
||||
@return how many cores of this cable are wired to a conductor
|
||||
*/
|
||||
int Cable::usedCoreCount() const
|
||||
{
|
||||
return m_cores.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::freeCoreCount
|
||||
@return how many cores the type offers and no conductor shows yet
|
||||
*/
|
||||
int Cable::freeCoreCount() const
|
||||
{
|
||||
return qMax(0, m_core_count - m_cores.size());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::firstFreeCore
|
||||
@return the lowest core number which is free, -1 when every core of
|
||||
the type is already wired
|
||||
*/
|
||||
int Cable::firstFreeCore() const
|
||||
{
|
||||
for (int i = 0; i < m_core_count; ++i) {
|
||||
if (!hasCore(i)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::colorOfCore
|
||||
@param core
|
||||
@return the colour this core has in the cable type file, an empty
|
||||
string when the type doesn't describe that many cores
|
||||
*/
|
||||
QString Cable::colorOfCore(int core) const
|
||||
{
|
||||
return m_core_colors.value(core);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::labelOfCore
|
||||
The text written into the cable field of the conductor this core is
|
||||
wired to. It is never typed by hand: it is worked out here, from the
|
||||
cable and the core, so renaming the cable or changing its type
|
||||
updates every conductor at once.
|
||||
@param core
|
||||
@return e.g. "15W1:br"
|
||||
*/
|
||||
QString Cable::labelOfCore(int core) const
|
||||
{
|
||||
if (m_designation.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
const QString color = colorOfCore(core);
|
||||
if (color.isEmpty()) {
|
||||
return m_designation;
|
||||
}
|
||||
return m_designation + QLatin1Char(':') + color;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::parts
|
||||
@return every drawn section of this cable, whatever folio it sits on
|
||||
*/
|
||||
QList<CablePartData> Cable::parts() const
|
||||
{
|
||||
return m_parts;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::hasPart
|
||||
@param part
|
||||
@return true when the cable has a section with that identity
|
||||
*/
|
||||
bool Cable::hasPart(const QUuid &part) const
|
||||
{
|
||||
if (part.isNull()) return false;
|
||||
for (const CablePartData &p : m_parts) {
|
||||
if (p.uuid == part) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::part
|
||||
@param part
|
||||
@return the section with that identity, an invalid one when there is none
|
||||
*/
|
||||
CablePartData Cable::part(const QUuid &part) const
|
||||
{
|
||||
for (const CablePartData &p : m_parts) {
|
||||
if (p.uuid == part) return p;
|
||||
}
|
||||
return CablePartData();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::addPart
|
||||
@param part
|
||||
*/
|
||||
void Cable::addPart(const CablePartData &part)
|
||||
{
|
||||
if (part.uuid.isNull() || hasPart(part.uuid)) return;
|
||||
m_parts.append(part);
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::removePart
|
||||
Also drops the cores drawn on that section, since their slash and
|
||||
their colour label live with it.
|
||||
@param part
|
||||
*/
|
||||
void Cable::removePart(const QUuid &part)
|
||||
{
|
||||
bool touched = false;
|
||||
for (int i = m_parts.size() - 1; i >= 0; --i) {
|
||||
if (m_parts.at(i).uuid == part) {
|
||||
m_parts.removeAt(i);
|
||||
touched = true;
|
||||
}
|
||||
}
|
||||
for (int i = m_cores.size() - 1; i >= 0; --i) {
|
||||
if (m_cores.at(i).part == part) {
|
||||
m_cores.removeAt(i);
|
||||
touched = true;
|
||||
}
|
||||
}
|
||||
if (touched) {
|
||||
emit changed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setPartGeometry
|
||||
Moving or lengthening the trunk line is a purely optical change: no
|
||||
core is added, none is removed, nothing is reassigned. Only the line
|
||||
itself moves -- which is why this never emits changed(): the labels
|
||||
the cable writes into the conductors do not depend on it.
|
||||
@param part
|
||||
@param p1
|
||||
@param p2
|
||||
*/
|
||||
void Cable::setPartGeometry(const QUuid &part, const QPointF &p1, const QPointF &p2)
|
||||
{
|
||||
for (int i = 0; i < m_parts.size(); ++i) {
|
||||
if (m_parts.at(i).uuid != part) continue;
|
||||
if (m_parts.at(i).p1 == p1 && m_parts.at(i).p2 == p2) return;
|
||||
m_parts[i].p1 = p1;
|
||||
m_parts[i].p2 = p2;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setPartDiagram
|
||||
@param part
|
||||
@param diagram
|
||||
*/
|
||||
void Cable::setPartDiagram(const QUuid &part, const QUuid &diagram)
|
||||
{
|
||||
for (int i = 0; i < m_parts.size(); ++i) {
|
||||
if (m_parts.at(i).uuid != part) continue;
|
||||
if (m_parts.at(i).diagram == diagram) return;
|
||||
m_parts[i].diagram = diagram;
|
||||
emit changed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::movePartCores
|
||||
Carry the colour labels of one section along when that section is
|
||||
moved as a whole: when a copy is put down, when it is stamped a grid
|
||||
way beside the original, and when the line travels with the rest of
|
||||
a selection rather than on its own.
|
||||
|
||||
Quiet on purpose. Where a slash stands says nothing about the text
|
||||
a conductor shows, so there is nothing to work out again -- and this
|
||||
runs while items travel under the mouse, where one full pass over
|
||||
every wire of the project would cost more than the movement itself.
|
||||
@param part the section which moved
|
||||
@param delta how far it came, in scene coordinates
|
||||
*/
|
||||
void Cable::movePartCores(const QUuid &part, const QPointF &delta)
|
||||
{
|
||||
if (part.isNull() || delta.isNull()) return;
|
||||
|
||||
for (CableCore &core : m_cores)
|
||||
{
|
||||
if (core.part == part) {
|
||||
core.position += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::toXml
|
||||
@param document
|
||||
@return the <cable> element holding every part and every wired core
|
||||
*/
|
||||
QDomElement Cable::toXml(QDomDocument &document) const
|
||||
{
|
||||
QDomElement root = document.createElement(QLatin1String("cable"));
|
||||
root.setAttribute(QLatin1String("uuid"), m_uuid.toString());
|
||||
if (!m_designation.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("designation"), m_designation);
|
||||
}
|
||||
//Only a name which was typed in says so: the ones the numbering
|
||||
//rule handed out are the ordinary case and need no marking
|
||||
if (m_designation_by_hand) {
|
||||
root.setAttribute(QLatin1String("designation_by_hand"), QLatin1String("1"));
|
||||
}
|
||||
if (!m_bmk.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("bmk"), m_bmk);
|
||||
}
|
||||
if (!m_installation.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("installation"), m_installation);
|
||||
}
|
||||
if (!m_location.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("location"), m_location);
|
||||
}
|
||||
if (!m_length.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("length"), m_length);
|
||||
}
|
||||
//Only what was switched off is written: a cable written before
|
||||
//these existed shows everything, like the ones written from now
|
||||
if (!m_show_designation) {
|
||||
root.setAttribute(QLatin1String("show_designation"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_show_type) {
|
||||
root.setAttribute(QLatin1String("show_type"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_show_installation) {
|
||||
root.setAttribute(QLatin1String("show_installation"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_show_location) {
|
||||
root.setAttribute(QLatin1String("show_location"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_show_length) {
|
||||
root.setAttribute(QLatin1String("show_length"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_type.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("type"), m_type);
|
||||
}
|
||||
if (m_core_count > 0) {
|
||||
root.setAttribute(QLatin1String("cores"), QString::number(m_core_count));
|
||||
}
|
||||
|
||||
if (!m_core_colors.isEmpty())
|
||||
{
|
||||
QDomElement colors = document.createElement(QLatin1String("colors"));
|
||||
for (const QString &color : m_core_colors) {
|
||||
QDomElement e = document.createElement(QLatin1String("color"));
|
||||
e.appendChild(document.createTextNode(color));
|
||||
colors.appendChild(e);
|
||||
}
|
||||
root.appendChild(colors);
|
||||
}
|
||||
|
||||
//Only the texts this cable does not write the way the preferences
|
||||
//say are stored, so a cable stays silent as long as it merely
|
||||
//follows the default -- and a later change of that default still
|
||||
//reaches it. The nudges are kept in the same element: they say
|
||||
//where those texts stand rather than what the cable is.
|
||||
if (!m_texts.isEmpty() || !m_text_offset.isNull() || !m_core_offset.isNull())
|
||||
{
|
||||
QDomElement texts = document.createElement(QLatin1String("texts"));
|
||||
bool stored = false;
|
||||
for (auto it = m_texts.cbegin(); it != m_texts.cend(); ++it)
|
||||
{
|
||||
const CableTextFormat &format = it.value();
|
||||
if (format.font.isEmpty() && format.alignment == 0) {
|
||||
continue;
|
||||
}
|
||||
QDomElement e = document.createElement(QLatin1String("text"));
|
||||
e.setAttribute(QLatin1String("key"), it.key());
|
||||
if (!format.font.isEmpty()) {
|
||||
e.setAttribute(QLatin1String("font"), format.font);
|
||||
}
|
||||
const QString align = alignmentName(format.alignment);
|
||||
if (!align.isEmpty()) {
|
||||
e.setAttribute(QLatin1String("align"), align);
|
||||
}
|
||||
texts.appendChild(e);
|
||||
stored = true;
|
||||
}
|
||||
if (!m_text_offset.isNull())
|
||||
{
|
||||
texts.setAttribute(QLatin1String("text_dx"),
|
||||
QString::number(m_text_offset.x(), 'f', 2));
|
||||
texts.setAttribute(QLatin1String("text_dy"),
|
||||
QString::number(m_text_offset.y(), 'f', 2));
|
||||
stored = true;
|
||||
}
|
||||
if (!m_core_offset.isNull())
|
||||
{
|
||||
texts.setAttribute(QLatin1String("core_dx"),
|
||||
QString::number(m_core_offset.x(), 'f', 2));
|
||||
texts.setAttribute(QLatin1String("core_dy"),
|
||||
QString::number(m_core_offset.y(), 'f', 2));
|
||||
stored = true;
|
||||
}
|
||||
if (stored) {
|
||||
root.appendChild(texts);
|
||||
}
|
||||
}
|
||||
|
||||
for (const CablePartData &part : m_parts)
|
||||
{
|
||||
QDomElement e = document.createElement(QLatin1String("part"));
|
||||
e.setAttribute(QLatin1String("uuid"), part.uuid.toString());
|
||||
e.setAttribute(QLatin1String("diagram"), part.diagram.toString());
|
||||
e.setAttribute(QLatin1String("x1"), QString::number(part.p1.x(), 'f', 2));
|
||||
e.setAttribute(QLatin1String("y1"), QString::number(part.p1.y(), 'f', 2));
|
||||
e.setAttribute(QLatin1String("x2"), QString::number(part.p2.x(), 'f', 2));
|
||||
e.setAttribute(QLatin1String("y2"), QString::number(part.p2.y(), 'f', 2));
|
||||
root.appendChild(e);
|
||||
}
|
||||
|
||||
for (const CableCore &core : usedCores())
|
||||
{
|
||||
QDomElement e = document.createElement(QLatin1String("core"));
|
||||
e.setAttribute(QLatin1String("index"), QString::number(core.core));
|
||||
if (!core.part.isNull()) {
|
||||
e.setAttribute(QLatin1String("part"), core.part.toString());
|
||||
}
|
||||
if (!core.conductor.isNull()) {
|
||||
e.setAttribute(QLatin1String("conductor"), core.conductor.toString());
|
||||
}
|
||||
e.setAttribute(QLatin1String("x"), QString::number(core.position.x(), 'f', 2));
|
||||
e.setAttribute(QLatin1String("y"), QString::number(core.position.y(), 'f', 2));
|
||||
root.appendChild(e);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::fromXml
|
||||
@param element a <cable> element
|
||||
*/
|
||||
void Cable::fromXml(const QDomElement &element)
|
||||
{
|
||||
m_uuid = QUuid(element.attribute(QLatin1String("uuid")));
|
||||
if (m_uuid.isNull()) {
|
||||
m_uuid = QUuid::createUuid();
|
||||
}
|
||||
m_designation = element.attribute(QLatin1String("designation"));
|
||||
//A file written before this existed holds names the program
|
||||
//gave, which is what not marking them means
|
||||
m_designation_by_hand = element.attribute(
|
||||
QLatin1String("designation_by_hand")) == QLatin1String("1");
|
||||
m_bmk = element.attribute(QLatin1String("bmk"));
|
||||
m_installation = element.attribute(QLatin1String("installation"));
|
||||
m_location = element.attribute(QLatin1String("location"));
|
||||
m_length = element.attribute(QLatin1String("length"));
|
||||
m_show_designation = element.attribute(
|
||||
QLatin1String("show_designation"), QLatin1String("1")).toInt() != 0;
|
||||
m_show_type = element.attribute(
|
||||
QLatin1String("show_type"), QLatin1String("1")).toInt() != 0;
|
||||
m_show_installation = element.attribute(
|
||||
QLatin1String("show_installation"), QLatin1String("1")).toInt() != 0;
|
||||
m_show_location = element.attribute(
|
||||
QLatin1String("show_location"), QLatin1String("1")).toInt() != 0;
|
||||
m_show_length = element.attribute(
|
||||
QLatin1String("show_length"), QLatin1String("1")).toInt() != 0;
|
||||
m_type = element.attribute(QLatin1String("type"));
|
||||
m_core_count = qMax(0, element.attribute(QLatin1String("cores"), QLatin1String("0")).toInt());
|
||||
|
||||
m_core_colors.clear();
|
||||
const QDomElement colors = element.firstChildElement(QLatin1String("colors"));
|
||||
for (QDomElement e = colors.firstChildElement(QLatin1String("color"));
|
||||
!e.isNull();
|
||||
e = e.nextSiblingElement(QLatin1String("color"))) {
|
||||
m_core_colors.append(e.text());
|
||||
}
|
||||
|
||||
//The texts this cable writes its own way; everything not in here
|
||||
//keeps following what the preferences say.
|
||||
m_texts.clear();
|
||||
m_text_offset = QPointF();
|
||||
m_core_offset = QPointF();
|
||||
const QDomElement texts = element.firstChildElement(QLatin1String("texts"));
|
||||
for (QDomElement e = texts.firstChildElement(QLatin1String("text"));
|
||||
!e.isNull();
|
||||
e = e.nextSiblingElement(QLatin1String("text")))
|
||||
{
|
||||
const QString key = e.attribute(QLatin1String("key"));
|
||||
if (key.isEmpty()) continue;
|
||||
CableTextFormat format;
|
||||
format.font = e.attribute(QLatin1String("font"));
|
||||
format.alignment = alignmentFromName(e.attribute(QLatin1String("align")));
|
||||
if (format.font.isEmpty() && format.alignment == 0) continue;
|
||||
m_texts.insert(key, format);
|
||||
}
|
||||
if (!texts.isNull())
|
||||
{
|
||||
m_text_offset.setX(texts.attribute(QLatin1String("text_dx")).toDouble());
|
||||
m_text_offset.setY(texts.attribute(QLatin1String("text_dy")).toDouble());
|
||||
m_core_offset.setX(texts.attribute(QLatin1String("core_dx")).toDouble());
|
||||
m_core_offset.setY(texts.attribute(QLatin1String("core_dy")).toDouble());
|
||||
}
|
||||
|
||||
m_parts.clear();
|
||||
for (QDomElement e = element.firstChildElement(QLatin1String("part"));
|
||||
!e.isNull();
|
||||
e = e.nextSiblingElement(QLatin1String("part")))
|
||||
{
|
||||
CablePartData part;
|
||||
part.uuid = QUuid(e.attribute(QLatin1String("uuid")));
|
||||
if (part.uuid.isNull()) {
|
||||
continue;
|
||||
}
|
||||
part.diagram = QUuid(e.attribute(QLatin1String("diagram")));
|
||||
part.p1 = QPointF(e.attribute(QLatin1String("x1")).toDouble(),
|
||||
e.attribute(QLatin1String("y1")).toDouble());
|
||||
part.p2 = QPointF(e.attribute(QLatin1String("x2")).toDouble(),
|
||||
e.attribute(QLatin1String("y2")).toDouble());
|
||||
m_parts.append(part);
|
||||
}
|
||||
|
||||
m_cores.clear();
|
||||
for (QDomElement e = element.firstChildElement(QLatin1String("core"));
|
||||
!e.isNull();
|
||||
e = e.nextSiblingElement(QLatin1String("core")))
|
||||
{
|
||||
CableCore core;
|
||||
core.core = e.attribute(QLatin1String("index"), QLatin1String("-1")).toInt();
|
||||
if (core.core < 0) {
|
||||
continue;
|
||||
}
|
||||
core.part = QUuid(e.attribute(QLatin1String("part")));
|
||||
core.conductor = QUuid(e.attribute(QLatin1String("conductor")));
|
||||
core.position = QPointF(e.attribute(QLatin1String("x")).toDouble(),
|
||||
e.attribute(QLatin1String("y")).toDouble());
|
||||
if (hasCore(core.core)) {
|
||||
continue;
|
||||
}
|
||||
m_cores.append(core);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLE_H
|
||||
#define CABLE_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QPointF>
|
||||
#include <QStringList>
|
||||
#include <QUuid>
|
||||
|
||||
class QDomDocument;
|
||||
class QDomElement;
|
||||
|
||||
/**
|
||||
@brief One core of a cable which is wired to a conductor of the drawing.
|
||||
|
||||
A cable owns its assignment: this is the only place in QElectroTech
|
||||
which knows that core number 3 of cable 15W1 lands on such a
|
||||
conductor. The conductor itself only carries the text the assignment
|
||||
generates (15W1:br), written back by CableManager::refreshLabels(),
|
||||
so the text can never drift away from the cable it comes from.
|
||||
*/
|
||||
struct CableCore
|
||||
{
|
||||
/// Core number inside the cable, 0 based, from 0 to coreCount()-1
|
||||
int core = -1;
|
||||
/// The part carrying the slash drawn for this core
|
||||
QUuid part;
|
||||
/// The conductor this core lands on, null when the core is free
|
||||
QUuid conductor;
|
||||
/// Where the slash sits, in the coordinates of its own part
|
||||
QPointF position;
|
||||
|
||||
///Two assignments are the same when they say the same thing, so
|
||||
///undo can tell a gesture which changed something from one which
|
||||
///merely put the slash back where it already was
|
||||
friend bool operator==(const CableCore &a, const CableCore &b)
|
||||
{
|
||||
return a.core == b.core && a.part == b.part
|
||||
&& a.conductor == b.conductor && a.position == b.position;
|
||||
}
|
||||
friend bool operator!=(const CableCore &a, const CableCore &b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief One drawn section of a cable: the trunk line of one folio.
|
||||
|
||||
The geometry is kept as plain data rather than as a pointer to the
|
||||
graphics item, because the folio owns the item and deletes it when the
|
||||
folio is deleted, while the cable outlives it (it may have another
|
||||
part on another folio).
|
||||
*/
|
||||
struct CablePartData
|
||||
{
|
||||
QUuid uuid;
|
||||
QUuid diagram;
|
||||
QPointF p1;
|
||||
QPointF p2;
|
||||
|
||||
bool isHorizontal() const {return qFuzzyCompare(p1.y(), p2.y());}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief How one of the texts of a cable is written.
|
||||
|
||||
Both fields may say nothing at all, and that is the normal case:
|
||||
the cable then writes that text the way the preferences offer it
|
||||
(QETApp::cableTextsFont() and QETApp::cableCoreFont()), so picking
|
||||
another default in the preferences reaches every cable which has
|
||||
not been given a font of its own.
|
||||
|
||||
The texts of a cable are kept in one hash rather than one pair of
|
||||
members each, so that a new text can be added without dragging a
|
||||
new field through the whole code. They are named the way the file
|
||||
writes them:
|
||||
"designation", "type", "installation", "location", "length" for the
|
||||
five texts written next to the trunk line, and "cores" for the
|
||||
colour labels of the cores -- which have no alignment, being turned
|
||||
by a quarter along their own line.
|
||||
*/
|
||||
struct CableTextFormat
|
||||
{
|
||||
///The font, in the stable description QETUtils::fontToString()
|
||||
///writes; empty when this cable follows the preferences
|
||||
QString font;
|
||||
///Qt::AlignLeft, Qt::AlignHCenter or Qt::AlignRight; 0 when the
|
||||
///text goes on ending where it always did, before the line
|
||||
int alignment = 0;
|
||||
|
||||
bool operator==(const CableTextFormat &other) const
|
||||
{
|
||||
return font == other.font && alignment == other.alignment;
|
||||
}
|
||||
bool operator!=(const CableTextFormat &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Everything about a cable the user may type in or tick, and
|
||||
whether what he typed is written on the sheet at all.
|
||||
|
||||
It is kept as a whole so that one undo step can put a whole edit
|
||||
back, and so the selection panel and the dialog always offer exactly
|
||||
the same fields in the same order.
|
||||
*/
|
||||
struct CableProperties
|
||||
{
|
||||
///Name of the cable, e.g. 15W1 -- the head of the label
|
||||
QString designation;
|
||||
/**
|
||||
Where that name comes from: false while the program worked
|
||||
it out of the numbering rule, true from the moment it was
|
||||
typed in. Nothing shows it and no field edits it -- it
|
||||
travels with the fields so that undo puts it back with them,
|
||||
and so that numbering the whole project over again knows
|
||||
which names were never the rule's to begin with and may be
|
||||
left alone.
|
||||
*/
|
||||
bool designation_by_hand = false;
|
||||
///Kind of cable, e.g. H07RN-F 4G1,5
|
||||
QString type;
|
||||
///Installation the cable belongs to
|
||||
QString installation;
|
||||
///Place the cable runs through
|
||||
QString location;
|
||||
///Length, typed in by hand
|
||||
QString length;
|
||||
|
||||
///What of that is written next to the trunk line of the drawing
|
||||
bool show_designation = true;
|
||||
bool show_type = true;
|
||||
bool show_installation = true;
|
||||
bool show_location = true;
|
||||
bool show_length = true;
|
||||
|
||||
///How each text is written -- font and alignment, keyed by the
|
||||
///names listed on CableTextFormat. A text which is not in here
|
||||
///follows what the preferences say, which is where a cable
|
||||
///freshly drawn stands for all of them.
|
||||
QHash<QString, CableTextFormat> texts;
|
||||
|
||||
///How far the label at the left of the trunk line is nudged from
|
||||
///where it has always stood. It is a nudge on the drawing only,
|
||||
///and it moves the whole label as one thing: installation, place
|
||||
///and designation over the line, the type and the length under
|
||||
///it -- while the line, the slashes and the wiring keep standing
|
||||
///exactly where they were.
|
||||
QPointF text_offset;
|
||||
///How far the row of colours is nudged from the slashes it names.
|
||||
///Again on the drawing only: the slashes, the entries they mark
|
||||
///and every wire behind them stay where they are, and so does the
|
||||
///row itself when the trunk line is moved.
|
||||
QPointF core_offset;
|
||||
|
||||
bool operator==(const CableProperties &other) const
|
||||
{
|
||||
return designation == other.designation
|
||||
&& designation_by_hand == other.designation_by_hand
|
||||
&& type == other.type
|
||||
&& installation == other.installation
|
||||
&& location == other.location
|
||||
&& length == other.length
|
||||
&& show_designation == other.show_designation
|
||||
&& show_type == other.show_type
|
||||
&& show_installation == other.show_installation
|
||||
&& show_location == other.show_location
|
||||
&& show_length == other.show_length
|
||||
&& texts == other.texts
|
||||
&& text_offset == other.text_offset
|
||||
&& core_offset == other.core_offset;
|
||||
}
|
||||
bool operator!=(const CableProperties &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Everything about a cable which its type decides, taken as one
|
||||
block so that changing the type is one single step.
|
||||
|
||||
The cores go with the type rather than with the text fields: taking a
|
||||
type with fewer cores takes those cores off the drawing, and undoing
|
||||
has to bring their marks back along with the name and the colours.
|
||||
*/
|
||||
struct CableTypeState
|
||||
{
|
||||
///Kind of cable, e.g. H07RN-F 4G1,5
|
||||
QString type;
|
||||
///How many cores that type has
|
||||
int core_count = 0;
|
||||
///The colour of each of them, in core order
|
||||
QStringList core_colors;
|
||||
///The cores the cable really holds at this moment
|
||||
QList<CableCore> cores;
|
||||
|
||||
bool operator==(const CableTypeState &other) const
|
||||
{
|
||||
return type == other.type
|
||||
&& core_count == other.core_count
|
||||
&& core_colors == other.core_colors
|
||||
&& cores == other.cores;
|
||||
}
|
||||
bool operator!=(const CableTypeState &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The Cable class is the project wide object a drawn cable line
|
||||
stands for.
|
||||
|
||||
It carries what the user fills in -- the type, the number, BMK,
|
||||
installation, location -- together with what QElectroTech works out:
|
||||
how many cores the type has, which colour each core has, which cores
|
||||
are wired to which conductor, and which folios the cable runs on.
|
||||
|
||||
Properties belong to the cable, never to one of its parts: editing
|
||||
the type or the number anywhere changes it for every part.
|
||||
*/
|
||||
class Cable : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Cable(QObject *parent = nullptr);
|
||||
|
||||
//Identity
|
||||
QUuid uuid() const;
|
||||
void setUuid(const QUuid &uuid);
|
||||
|
||||
//Properties the user fills in
|
||||
QString designation() const;
|
||||
void setDesignation(const QString &designation);
|
||||
/**
|
||||
True when the name of this cable was typed in rather
|
||||
than worked out of the numbering rule: numbering the
|
||||
whole project over asks before it takes such a name.
|
||||
*/
|
||||
bool designationByHand() const;
|
||||
void setDesignationByHand(bool by_hand);
|
||||
QString bmk() const;
|
||||
void setBmk(const QString &bmk);
|
||||
QString installation() const;
|
||||
void setInstallation(const QString &installation);
|
||||
QString location() const;
|
||||
void setLocation(const QString &location);
|
||||
QString length() const;
|
||||
void setLength(const QString &length);
|
||||
|
||||
///Every field of the cable, and what of it is drawn on the sheet
|
||||
CableProperties properties() const;
|
||||
void setProperties(const CableProperties &properties);
|
||||
|
||||
//Type, copied from the cable type file when the cable is created
|
||||
QString type() const;
|
||||
void setType(const QString &type);
|
||||
int coreCount() const;
|
||||
void setCoreCount(int count);
|
||||
QStringList coreColors() const;
|
||||
void setCoreColors(const QStringList &colors);
|
||||
///Everything the type of this cable decides, taken as one block
|
||||
CableTypeState typeState() const;
|
||||
///Put back a state taken with typeState(), in one single pass
|
||||
void applyTypeState(const CableTypeState &state);
|
||||
|
||||
//Cores
|
||||
QList<CableCore> usedCores() const;
|
||||
bool hasCore(int core) const;
|
||||
CableCore core(int core) const;
|
||||
void setCore(const CableCore &core);
|
||||
///Write a whole set of assignments in place of another
|
||||
void setCores(const QList<CableCore> &cores);
|
||||
void removeCore(int core);
|
||||
int usedCoreCount() const;
|
||||
int freeCoreCount() const;
|
||||
///The lowest core number which is free, -1 when none is
|
||||
int firstFreeCore() const;
|
||||
///The colour of a core, as written in the cable type file
|
||||
QString colorOfCore(int core) const;
|
||||
///The text a conductor of this core shows, e.g. "15W1:br"
|
||||
QString labelOfCore(int core) const;
|
||||
|
||||
//Parts
|
||||
QList<CablePartData> parts() const;
|
||||
bool hasPart(const QUuid &part) const;
|
||||
CablePartData part(const QUuid &part) const;
|
||||
void addPart(const CablePartData &part);
|
||||
void removePart(const QUuid &part);
|
||||
void setPartGeometry(const QUuid &part, const QPointF &p1, const QPointF &p2);
|
||||
void setPartDiagram(const QUuid &part, const QUuid &diagram);
|
||||
///Carry the colour labels of one section along when that
|
||||
///section is moved as a whole (paste, duplicate, a selection
|
||||
///being moved with everything else on it)
|
||||
void movePartCores(const QUuid &part, const QPointF &delta);
|
||||
|
||||
//Serialisation
|
||||
QDomElement toXml(QDomDocument &document) const;
|
||||
void fromXml(const QDomElement &element);
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
|
||||
private:
|
||||
QUuid m_uuid;
|
||||
QString m_designation;
|
||||
///< where that name comes from (see designationByHand)
|
||||
bool m_designation_by_hand = false;
|
||||
QString m_bmk;
|
||||
QString m_installation;
|
||||
QString m_location;
|
||||
QString m_length;
|
||||
QString m_type;
|
||||
int m_core_count = 0;
|
||||
QStringList m_core_colors;
|
||||
QList<CableCore> m_cores;
|
||||
QList<CablePartData> m_parts;
|
||||
///What the label next to the trunk line is allowed to show
|
||||
bool m_show_designation = true;
|
||||
bool m_show_type = true;
|
||||
bool m_show_installation = true;
|
||||
bool m_show_location = true;
|
||||
bool m_show_length = true;
|
||||
///The texts this cable writes its own way, by the names
|
||||
///listed on CableTextFormat; everything not in here follows
|
||||
///what the preferences say
|
||||
QHash<QString, CableTextFormat> m_texts;
|
||||
///How far the label at the left of the line and the row of
|
||||
///colours are nudged on the drawing: distances nothing else
|
||||
///in the project ever looks at
|
||||
QPointF m_text_offset;
|
||||
QPointF m_core_offset;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief One core which was taken away from another cable.
|
||||
|
||||
A conductor belongs to one cable only, so drawing a second cable
|
||||
across wires a first one already holds takes those cores away from
|
||||
it. That has to be remembered where it is decided, so that undoing
|
||||
the second cable gives the first one its cores back instead of
|
||||
leaving those conductors with a blank cable field forever.
|
||||
*/
|
||||
struct CableCoreTaken
|
||||
{
|
||||
///The cable the core was taken from; null when it is gone
|
||||
QPointer<Cable> cable;
|
||||
///The core exactly as the other cable held it
|
||||
CableCore core;
|
||||
};
|
||||
|
||||
#endif // CABLE_H
|
||||
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
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 "cablecopy.h"
|
||||
|
||||
#include "cablemanager.h"
|
||||
#include "cablepart.h"
|
||||
|
||||
#include "../autoNum/assignvariables.h"
|
||||
#include "../autoNum/numerotationcontext.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QSet>
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@brief CableCopy::write
|
||||
Puts the cable lines selected on @a diagram into @a document, so a
|
||||
copy carries them the way it carries the elements and the texts of
|
||||
the same selection.
|
||||
|
||||
Only the line which was picked is written, together with the whole
|
||||
cable it belongs to: the copy needs everything the cable knows, but
|
||||
only the one section it is standing on.
|
||||
@param diagram the folio being copied
|
||||
@param document the fragment the clipboard is about to hold
|
||||
*/
|
||||
void CableCopy::write(Diagram *diagram, QDomDocument &document)
|
||||
{
|
||||
if (!diagram) return;
|
||||
QDomElement root = document.documentElement();
|
||||
if (root.isNull()) return;
|
||||
|
||||
QList<CablePart *> parts;
|
||||
for (QGraphicsItem *item : diagram->selectedItems()) {
|
||||
if (auto *part = qgraphicsitem_cast<CablePart *>(item)) {
|
||||
parts << part;
|
||||
}
|
||||
}
|
||||
if (parts.isEmpty()) return;
|
||||
|
||||
QDomElement block = document.createElement(QStringLiteral("cables"));
|
||||
bool anything = false;
|
||||
for (CablePart *part : std::as_const(parts))
|
||||
{
|
||||
Cable *cable = part->cable();
|
||||
if (!cable) continue;
|
||||
|
||||
QDomElement element = cable->toXml(document);
|
||||
const QPointF p1 = part->firstPoint();
|
||||
const QPointF p2 = part->secondPoint();
|
||||
element.setAttribute(QStringLiteral("line"), part->partUuid().toString());
|
||||
element.setAttribute(QStringLiteral("x1"), QString::number(p1.x(), 'f', 2));
|
||||
element.setAttribute(QStringLiteral("y1"), QString::number(p1.y(), 'f', 2));
|
||||
element.setAttribute(QStringLiteral("x2"), QString::number(p2.x(), 'f', 2));
|
||||
element.setAttribute(QStringLiteral("y2"), QString::number(p2.y(), 'f', 2));
|
||||
block.appendChild(element);
|
||||
anything = true;
|
||||
}
|
||||
if (anything) root.appendChild(block);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableCopy::read
|
||||
Builds a cable of its own for every line @a root carries, each of
|
||||
them standing exactly where the fragment says it stood.
|
||||
|
||||
The cable keeps everything the source cable knows -- type,
|
||||
installation, place, length, how many cores, which colours -- and it
|
||||
also keeps where the colour labels of the copied section stand, so a
|
||||
copy arrives as a whole: onto a folio which holds no wire at all,
|
||||
into an empty part of a sheet, wherever. What it does not keep is
|
||||
any wire: a copy may not take a wire away from the cable it was
|
||||
copied from, not even for as long as it is being built, so every
|
||||
core is let go while the cable still answers to the source's
|
||||
identity -- no conductor of the source is ever repointed, not even
|
||||
for the length of one signal.
|
||||
|
||||
Which wire each carried core belongs to is worked out later, in
|
||||
CableCopy::wire(), because that depends on where the line has
|
||||
finally been put -- and a copy put where there is no wire simply
|
||||
keeps its labels, to be wired when the line is pulled into place.
|
||||
@param diagram the folio which is about to hold those lines
|
||||
@param root the <diagram> element of a copy fragment
|
||||
@return the lines built, not yet wired
|
||||
*/
|
||||
QList<CablePart *> CableCopy::read(Diagram *diagram, const QDomElement &root)
|
||||
{
|
||||
QList<CablePart *> built;
|
||||
if (!diagram || !diagram->project()) return built;
|
||||
|
||||
const QDomElement block = root.firstChildElement(QStringLiteral("cables"));
|
||||
if (block.isNull()) return built;
|
||||
|
||||
QETProject *project = diagram->project();
|
||||
//One rule for the whole run. Every cable of a paste takes a
|
||||
//number from it and steps it on, so several cables pasted at
|
||||
//once do not all end up under the same number -- although the
|
||||
//project's own rule does not move until those cables really
|
||||
//exist, which is where the commands which add them take over.
|
||||
//A counter bound to the folio starts the folio the copies are
|
||||
//pasted onto at its own beginning, once for the whole run.
|
||||
NumerotationContext rule = autonum::resetFolioCounters(
|
||||
project->cableAutoNum(project->cableCurrentAutoNum()));
|
||||
for (QDomElement element = block.firstChildElement(QStringLiteral("cable"));
|
||||
!element.isNull();
|
||||
element = element.nextSiblingElement(QStringLiteral("cable")))
|
||||
{
|
||||
if (element.attribute(QStringLiteral("x1")).isEmpty()) continue;
|
||||
|
||||
Cable *cable = project->newCable();
|
||||
cable->fromXml(element);
|
||||
|
||||
//The section of the copy, standing where the section it was
|
||||
//copied from stood: the caller moves it to its final place,
|
||||
//and the colour labels come with it (CablePart::itemChange)
|
||||
CablePartData own;
|
||||
own.uuid = QUuid::createUuid();
|
||||
own.diagram = diagram->uuid();
|
||||
own.p1 = QPointF(element.attribute(QStringLiteral("x1")).toDouble(),
|
||||
element.attribute(QStringLiteral("y1")).toDouble());
|
||||
own.p2 = QPointF(element.attribute(QStringLiteral("x2")).toDouble(),
|
||||
element.attribute(QStringLiteral("y2")).toDouble());
|
||||
|
||||
//The colour labels of that one section, carried over whole
|
||||
//and with every wire taken away
|
||||
const QUuid source_part(element.attribute(QStringLiteral("line")));
|
||||
QList<CableCore> carried;
|
||||
for (const CableCore &core : cable->usedCores())
|
||||
{
|
||||
if (core.part != source_part) continue;
|
||||
|
||||
CableCore copy = core;
|
||||
copy.conductor = QUuid();
|
||||
copy.part = own.uuid;
|
||||
carried.append(copy);
|
||||
}
|
||||
//Goes first, while the copy still answers to the source's
|
||||
//identity: that way nothing of the source is left pointing
|
||||
//at a cable which is about to become somebody else
|
||||
cable->setCores(carried);
|
||||
|
||||
//The copy is a cable of its own: nothing of the source's
|
||||
//identity, nothing of its sections and nothing of the wires
|
||||
//it wires may be taken over, only what the user filled in
|
||||
for (const CablePartData &other : cable->parts()) {
|
||||
cable->removePart(other.uuid);
|
||||
}
|
||||
cable->setUuid(QUuid::createUuid());
|
||||
//The copy is numbered by the rule like every other cable.
|
||||
//A project which has no rule gets no number invented for it,
|
||||
//and then what the copy was copied from keeps standing there
|
||||
//rather than being wiped out by an empty one.
|
||||
const QString designation =
|
||||
CableManager::nextDesignation(project, diagram, rule);
|
||||
if (!designation.isEmpty()) {
|
||||
cable->setDesignation(designation);
|
||||
//The copy is numbered by the rule, so whatever the
|
||||
//source was called by hand is not carried over
|
||||
cable->setDesignationByHand(false);
|
||||
}
|
||||
cable->addPart(own);
|
||||
|
||||
auto *item = new CablePart(own);
|
||||
item->setCable(cable);
|
||||
diagram->addItem(item);
|
||||
built << item;
|
||||
}
|
||||
return built;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableCopy::wire
|
||||
Give the cores of every line in @a parts the wire each of them
|
||||
stands on now that the line is where it is going to stay.
|
||||
|
||||
A copy arrives whole: it carries the colour labels it was copied
|
||||
with, and this is the moment they are matched against the wires of
|
||||
the new place. Matching never moves a label: each one goes on
|
||||
standing exactly where it was carried, and the wire it stands on --
|
||||
within reach, as everywhere else -- is the one which takes the cable
|
||||
field. A label standing on no wire at all simply goes on
|
||||
standing there -- that is what makes a copy useful onto a folio
|
||||
which holds no wire yet: the line is there, with its colours, ready
|
||||
to be pulled into place, and pulling it there wires it
|
||||
(CablePart::settleCores).
|
||||
|
||||
A piece of wire belongs to one cable only, and a copy never takes
|
||||
one away: the wires of the folio it lands on which another cable
|
||||
already describes are left to that cable, nothing is asked about
|
||||
them and nothing is written for them. The wires which came along
|
||||
with the copy itself are different -- they are new wires on this
|
||||
folio, carrying the cable field they had back on the folio they
|
||||
come from, where they used to be one of that cable's own wires. If
|
||||
that were still believed here, every one of them would look like a
|
||||
wire another cable holds and the copy would be refused all of them,
|
||||
arriving without a single entry although it is standing on wires of
|
||||
its own. So they are let go of first, exactly like read() lets go
|
||||
of the wires the cores used to name.
|
||||
@param diagram the folio the lines are on
|
||||
@param parts the lines built by CableCopy::read()
|
||||
@param fresh the wires which came with this copy
|
||||
@return one entry per line which is going to stay, with what it
|
||||
had to give back to other cables, ready to be put on the undo stack
|
||||
*/
|
||||
QList<CableCopy::Wired> CableCopy::wire(Diagram *diagram,
|
||||
const QList<CablePart *> &parts,
|
||||
const QList<Conductor *> &fresh)
|
||||
{
|
||||
QList<Wired> wired;
|
||||
if (!diagram || !diagram->project()) {
|
||||
for (CablePart *part : parts) discard(diagram, part);
|
||||
return wired;
|
||||
}
|
||||
QETProject *project = diagram->project();
|
||||
|
||||
//A wire which came with this copy is a new wire here: what it
|
||||
//says about a cable describes the folio it was copied from.
|
||||
//Done by identity rather than by position, because the wire it
|
||||
//was copied from may well stand in this very place -- and that
|
||||
//one goes on belonging to its own cable.
|
||||
for (Conductor *conductor : fresh)
|
||||
{
|
||||
if (!conductor) continue;
|
||||
if (conductor->properties().m_cable_uuid.isNull()) continue;
|
||||
|
||||
|
||||
conductor->setCableReference(QString(), QUuid(), -1);
|
||||
}
|
||||
|
||||
for (CablePart *part : parts)
|
||||
{
|
||||
Cable *cable = part ? part->cable() : nullptr;
|
||||
if (!cable) {
|
||||
discard(diagram, part);
|
||||
continue;
|
||||
}
|
||||
|
||||
//One wire a colour label is dropped on. Two labels may
|
||||
//not be given the same wire, so each one is handed out
|
||||
//once.
|
||||
struct Offered
|
||||
{
|
||||
int core;
|
||||
Conductor *conductor;
|
||||
};
|
||||
QList<Offered> offered;
|
||||
QSet<QUuid> wires;
|
||||
|
||||
const QLineF line(part->firstPoint(), part->secondPoint());
|
||||
const QList<CableCrossing> crossings = CableManager::crossedConductors(diagram, line);
|
||||
for (const CableCore &core : cable->usedCores())
|
||||
{
|
||||
if (core.part != part->partUuid()) continue;
|
||||
if (!core.conductor.isNull()) continue;
|
||||
|
||||
//The wire this label stands on, worked out the way it
|
||||
//is worked out everywhere else (CablePart::wireFor):
|
||||
//the nearest place this line runs across a wire, close
|
||||
//enough for the slash and the wire to read as one
|
||||
//thing. The label itself is not moved to reach it --
|
||||
//a copy arrives with its colours where they were
|
||||
//carried, and no gesture moves one of them, so here
|
||||
//too the entry follows the label rather than the
|
||||
//label being sent off to find a wire. When that wire
|
||||
//has already been handed to another label of the same
|
||||
//copy, the next nearest one this label stands on wins,
|
||||
//rather than leaving it with no wire although one is
|
||||
//under it.
|
||||
QList<CableCrossing> open = crossings;
|
||||
Conductor *conductor = nullptr;
|
||||
while (true)
|
||||
{
|
||||
Conductor *nearest = part->wireFor(open, core, core.position);
|
||||
if (!nearest) break;
|
||||
if (!wires.contains(nearest->uuid())) {
|
||||
conductor = nearest;
|
||||
break;
|
||||
}
|
||||
for (int i = open.size() - 1; i >= 0; --i) {
|
||||
if (open.at(i).conductor == nearest) open.removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!conductor) continue;
|
||||
|
||||
//A wire another cable already describes stays with that
|
||||
//cable. Taking it away would empty a cable behind the
|
||||
//user's back, and asking about it on every paste turned
|
||||
//putting a copy into an existing drawing into a question
|
||||
//to answer first -- so a copy simply takes the wires
|
||||
//nobody holds, and its colour labels standing on the
|
||||
//others name nothing at all. An entry follows its label,
|
||||
//and here it comes to a stop. He can give such a label
|
||||
//one of those wires himself afterwards, by dragging it
|
||||
//there, which is where the question is still asked.
|
||||
const QUuid holder_uuid = conductor->properties().m_cable_uuid;
|
||||
if (!holder_uuid.isNull() && holder_uuid != cable->uuid())
|
||||
{
|
||||
const Cable *previous = CableManager::cableByUuid(project, holder_uuid);
|
||||
if (previous
|
||||
&& previous->hasCore(conductor->properties().m_cable_slot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
wires.insert(conductor->uuid());
|
||||
Offered item;
|
||||
item.core = core.core;
|
||||
item.conductor = conductor;
|
||||
offered.append(item);
|
||||
}
|
||||
|
||||
QList<Bound> bound;
|
||||
for (const Offered &item : std::as_const(offered))
|
||||
{
|
||||
//A wire is one piece of wire: whichever cable held it
|
||||
//before has to let it go.
|
||||
CableManager::claimConductor(project, item.conductor, cable);
|
||||
|
||||
//A cable binds a conductor by its identity, so a
|
||||
//conductor read back from a file written before it had a
|
||||
//persistent one is given one to keep from now on.
|
||||
item.conductor->setUuid(item.conductor->uuid());
|
||||
|
||||
CableCore core = cable->core(item.core);
|
||||
core.conductor = item.conductor->uuid();
|
||||
cable->setCore(core);
|
||||
|
||||
Bound bind;
|
||||
bind.core = item.core;
|
||||
bind.conductor = item.conductor;
|
||||
bound.append(bind);
|
||||
}
|
||||
|
||||
Wired wired_line;
|
||||
wired_line.part = part;
|
||||
wired_line.bound = bound;
|
||||
wired.append(wired_line);
|
||||
}
|
||||
return wired;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableCopy::repoint
|
||||
Work out again which wire each core of @a wired is on, using the
|
||||
identity the wire has right now.
|
||||
|
||||
A copy is put on the undo stack just after it was wired, and the
|
||||
first run of that stack renews the identity of every conductor it
|
||||
pasted (PasteDiagramCommand::redo): a copy must not keep the
|
||||
identity of the conductor it was copied from. The cores of the copy
|
||||
would then go on naming an identity which no longer exists, and the
|
||||
next working out of the labels (CableManager::refreshLabels) would
|
||||
find nothing there and take the cable field away from those wires.
|
||||
|
||||
So the cores are pointed at the very same wires again, with their
|
||||
new identity, and the cable fields are written out from the cables
|
||||
one more time afterwards: what the copy was given as it was put
|
||||
down still names the identity its wires had a moment earlier, and
|
||||
on a folio where those wires have their own new identity that
|
||||
leaves them without an entry although the copy is standing on them.
|
||||
Nothing moves and nothing is asked: an untouched copy is not
|
||||
changed by any of this at all.
|
||||
@param wired the lines CableCopy::wire() just gave wires to
|
||||
*/
|
||||
void CableCopy::repoint(const QList<Wired> &wired)
|
||||
{
|
||||
for (const Wired &line : wired)
|
||||
{
|
||||
Cable *cable = line.part ? line.part->cable() : nullptr;
|
||||
if (!cable) continue;
|
||||
|
||||
for (const Bound &bind : line.bound)
|
||||
{
|
||||
if (!bind.conductor) continue;
|
||||
|
||||
const QUuid now = bind.conductor->uuid();
|
||||
const CableCore core = cable->core(bind.core);
|
||||
if (core.core != bind.core || core.conductor == now) continue;
|
||||
|
||||
|
||||
CableCore updated = core;
|
||||
updated.conductor = now;
|
||||
cable->setCore(updated);
|
||||
}
|
||||
}
|
||||
|
||||
//With every core naming the identity its wire really has now,
|
||||
//the cable fields are written out from the cables once more:
|
||||
//this is what puts the entries a copy was given as it was put
|
||||
//down onto the wires it has actually landed on.
|
||||
Diagram *folio = nullptr;
|
||||
for (const Wired &line : wired) {
|
||||
if (line.part && line.part->diagram()) {
|
||||
folio = line.part->diagram();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (folio && folio->project()) {
|
||||
CableManager::refreshLabels(folio->project());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableCopy::discard
|
||||
Take a line away again whole -- off the folio, out of the project,
|
||||
and deleted -- without putting anything on the undo stack, because
|
||||
nothing really happened: the user said no to the question above.
|
||||
|
||||
Whatever the cable said while it was being built must not be left
|
||||
behind either: the project has to go on claiming exactly what it
|
||||
claimed before, or it would ask to be saved although nothing was
|
||||
changed at all.
|
||||
@param diagram the folio it was built on
|
||||
@param part the line to take away, may be null
|
||||
*/
|
||||
void CableCopy::discard(Diagram *diagram, CablePart *part)
|
||||
{
|
||||
if (!part) return;
|
||||
Cable *cable = part->cable();
|
||||
QETProject *project = diagram ? diagram->project() : nullptr;
|
||||
|
||||
if (part->scene()) part->scene()->removeItem(part);
|
||||
|
||||
if (cable && project && project->cables().contains(cable))
|
||||
{
|
||||
const bool options_modified = project->projectOptionsWereModified();
|
||||
project->removeCable(cable);
|
||||
project->setModified(options_modified);
|
||||
}
|
||||
|
||||
delete part;
|
||||
delete cable;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLECOPY_H
|
||||
#define CABLECOPY_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
|
||||
class CablePart;
|
||||
class Conductor;
|
||||
class Diagram;
|
||||
class QDomDocument;
|
||||
class QDomElement;
|
||||
class QUndoCommand;
|
||||
|
||||
/**
|
||||
@brief Carrying a drawn cable line over into a copy -- through the
|
||||
clipboard (Ctrl+C then Ctrl+V) or as a duplicate (Ctrl+D).
|
||||
|
||||
What travels is the line and everything which made the cable what it
|
||||
was: its type, its installation, the place it runs through, its
|
||||
length, how many cores it has, which colours those have -- and where
|
||||
the colour labels of the copied section stand, so a copy arrives as
|
||||
a whole even onto a folio which holds no wire at all. What does not
|
||||
travel is which wire each core is on: a copy is a cable of its own
|
||||
with a number of its own, and it takes the wire each colour label
|
||||
happens to stand on at the place it is put -- those of the folio it
|
||||
lands on which no other cable describes. A wire another cable
|
||||
already holds is left to that cable, and a label put where there is
|
||||
no wire goes on standing there, to be wired when the line is pulled
|
||||
into place.
|
||||
|
||||
A cable line is not one of the folio's items (the cable owns it),
|
||||
so Diagram::toXml() never writes it: the block travels beside the
|
||||
folio's own XML rather than inside it, and Diagram::fromXml() reads
|
||||
it back only when its caller asks to be handed the lines it built.
|
||||
*/
|
||||
namespace CableCopy
|
||||
{
|
||||
///Put the cable lines selected on @a diagram into @a document
|
||||
void write(Diagram *diagram, QDomDocument &document);
|
||||
|
||||
///Build new cables of their own for the lines @a root holds,
|
||||
///at the coordinates the fragment carries, with the colour
|
||||
///labels of the copied section carried over as they were.
|
||||
///Nothing is asked and no wire is taken yet: that needs the
|
||||
///place the lines have really been put at, which only the
|
||||
///caller knows
|
||||
QList<CablePart *> read(Diagram *diagram, const QDomElement &root);
|
||||
|
||||
///One core of a line, and the wire it was given
|
||||
struct Bound
|
||||
{
|
||||
int core = -1;
|
||||
Conductor *conductor = nullptr;
|
||||
};
|
||||
|
||||
///One line which was built, with what it had to take away
|
||||
struct Wired
|
||||
{
|
||||
QPointer<CablePart> part;
|
||||
QList<CableCoreTaken> taken;
|
||||
///The wires the colour labels of this line were given,
|
||||
///so they can be pointed at them again afterwards
|
||||
QList<Bound> bound;
|
||||
};
|
||||
|
||||
///Give each colour label of a line the wire it stands on now
|
||||
///that the line is where it will stay.
|
||||
///
|
||||
///@a fresh lists the wires which came along with the copy
|
||||
///itself. They are new wires on this folio: the cable field
|
||||
///they carry describes the folio they were copied from, where
|
||||
///they used to be one of that cable's own wires -- and carried
|
||||
///over, that reference would make every one of them look like
|
||||
///a wire some other cable holds, so the copy would be refused
|
||||
///all of them and arrive without a single entry although it is
|
||||
///standing on wires of its own. They are let go of first, the
|
||||
///same way read() lets go of the wires the cores used to name.
|
||||
///
|
||||
///A wire the folio already held, which another cable
|
||||
///describes, is never taken away from that cable: nothing is
|
||||
///asked and nothing is written for it, and the colour label
|
||||
///standing on it simply names nothing until the user drags it
|
||||
///onto a free wire himself, which is where the question is
|
||||
///still asked. A label standing on no wire at all goes on
|
||||
///standing where it is, to be wired when the line is pulled
|
||||
///into place.
|
||||
QList<Wired> wire(Diagram *diagram,
|
||||
const QList<CablePart *> &parts,
|
||||
const QList<Conductor *> &fresh);
|
||||
|
||||
///Point the cores @a wired lists at the wires they were given,
|
||||
///again: putting the copy on the undo stack may have renewed
|
||||
///the identity of a pasted conductor (PasteDiagramCommand::redo),
|
||||
///and a cable binds a wire by its identity. Nothing is asked and
|
||||
///nothing moves here. The cable fields are written out once more
|
||||
///afterwards, from the cables themselves, so the entries a copy
|
||||
///was given as it was put down are on the wires it really
|
||||
///landed on rather than on the identity they had one moment
|
||||
///earlier.
|
||||
void repoint(const QList<Wired> &wired);
|
||||
|
||||
///Take a built line back off the folio without leaving a trace
|
||||
///in the undo history, for a copy which was refused
|
||||
void discard(Diagram *diagram, CablePart *part);
|
||||
}
|
||||
|
||||
#endif // CABLECOPY_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLECREATEDIALOG_H
|
||||
#define CABLECREATEDIALOG_H
|
||||
|
||||
#include "../cablelist/cabletypelist.h"
|
||||
#include "cable.h"
|
||||
#include "cablemanager.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class Diagram;
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTableView;
|
||||
class QTabWidget;
|
||||
class QWidget;
|
||||
class QPushButton;
|
||||
|
||||
/**
|
||||
@brief Filter keeping the rows the user is looking for.
|
||||
|
||||
Every word he types has to be found somewhere in the row, whatever
|
||||
the column it sits in, the same way the material file is searched.
|
||||
On top of that it can hide the rows which are of no use to the cable
|
||||
being drawn -- the checkbox each of the two tabs carries.
|
||||
*/
|
||||
class CableFilterProxy : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static constexpr int UnsuitableRole = Qt::UserRole + 1;
|
||||
|
||||
explicit CableFilterProxy(QObject *parent = nullptr);
|
||||
|
||||
void setTokens(const QStringList &tokens);
|
||||
void setHideUnsuitable(bool hide);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
private:
|
||||
QStringList m_tokens;
|
||||
bool m_hide_unsuitable = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The small window filling in one line of the cable type file.
|
||||
|
||||
It is the cable counterpart of the window the material list opens to
|
||||
add one of its own articles: one field per column of the file, in
|
||||
file order, and a column the program doesn't know still gets its
|
||||
field since it belongs to the user rather than to QElectroTech. The
|
||||
two columns a cable type is read by get the shape they need -- a
|
||||
number which counts, and colours written the way the file separates
|
||||
them.
|
||||
*/
|
||||
class CableTypeEntryDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
@param columns the columns of the cable type file, in file
|
||||
order
|
||||
@param parent
|
||||
*/
|
||||
explicit CableTypeEntryDialog(const QStringList &columns, QWidget *parent = nullptr);
|
||||
|
||||
///The line the form describes, empty fields left out
|
||||
CableTypeRecord record() const;
|
||||
/**
|
||||
Fill the form with a line which is already in the file,
|
||||
for the window where that line is edited rather than
|
||||
added: the window then says so, and every field carries
|
||||
what the file holds about that column.
|
||||
@param values the line as it stands in the file
|
||||
*/
|
||||
void setValues(const CableTypeRecord &values);
|
||||
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
QStringList m_columns;
|
||||
///One field per column, in file order, for the tab key
|
||||
QList<QWidget *> m_fields;
|
||||
///What the window says it is about, changed when editing
|
||||
QLabel *m_intro = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The dialog which opens as soon as a cable line has been drawn.
|
||||
|
||||
It says how many cores were recognised, then lets the user either
|
||||
pick a type from the cable type file -- a brand new cable -- or pick
|
||||
one of the cables the project already holds and give it more cores.
|
||||
Both tabs carry a search field and the checkbox hiding what cannot be
|
||||
used, exactly like the material selection does.
|
||||
|
||||
The very same window opens to change the type of a cable which
|
||||
already exists: it then shows the type tab alone, filled in with what
|
||||
that cable holds, and takes the chosen type onto the cable -- cores
|
||||
reworked, marks redrawn -- as one single undo step. It carries the
|
||||
button filling in a new line of the cable type file, the way the
|
||||
material window carries its own.
|
||||
*/
|
||||
class CableCreateDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CableCreateDialog(Diagram *diagram,
|
||||
const QList<CableCrossing> &crossings,
|
||||
const CablePartData &part,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
The same window, opened to change the type of a cable which
|
||||
already exists rather than to settle a line just drawn.
|
||||
@param diagram the folio whose undo stack takes the change
|
||||
@param cable the cable whose type is being changed
|
||||
@param parent
|
||||
*/
|
||||
CableCreateDialog(Diagram *diagram,
|
||||
Cable *cable,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
///The cable the user settled on, null when he cancelled
|
||||
Cable *cable() const {return m_cable;}
|
||||
///The cores this cable took away from cables the project
|
||||
///already held, so that undo gives them back
|
||||
QList<CableCoreTaken> takenCores() const {return m_taken;}
|
||||
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
void build();
|
||||
void buildNewTab();
|
||||
void buildExistingTab();
|
||||
void buildPropertyRow();
|
||||
void loadTypeFile();
|
||||
void refreshExisting();
|
||||
void selectionChanged();
|
||||
///Fill the name field in and remember what this window put there
|
||||
void fillDesignation(const QString &text, bool by_hand);
|
||||
void assignCrossings(const QList<int> &core_slots);
|
||||
QList<CableCoreTaken> coresTakenFromOthers(const QUuid &target) const;
|
||||
QList<int> nextFreeSlots(int count) const;
|
||||
QModelIndex currentSourceIndex() const;
|
||||
bool currentSelectionIsUnsuitable() const;
|
||||
QString unsuitableNote() const;
|
||||
QStandardItem *cell(const QString &value, bool unsuitable) const;
|
||||
///True while this window changes the type of an existing cable
|
||||
bool changeMode() const {return m_change != nullptr;}
|
||||
///How many cores whatever he picks has to hold at least
|
||||
int neededCores() const;
|
||||
///Where the cable type file stands
|
||||
QString typeFilePath() const;
|
||||
///The button filling in a new line of the cable type file
|
||||
void addTypeRecord();
|
||||
///The button rewriting the line of the cable type file he picked
|
||||
void editTypeRecord();
|
||||
///Take the chosen type onto the cable being changed
|
||||
void acceptTypeChange();
|
||||
|
||||
//ATTRIBUTES
|
||||
private:
|
||||
QPointer<Diagram> m_diagram;
|
||||
QList<CableCrossing> m_crossings;
|
||||
CablePartData m_part;
|
||||
Cable *m_cable = nullptr;
|
||||
///The cable whose type is changed, null while a line is drawn
|
||||
QPointer<Cable> m_change;
|
||||
///The cores other cables lost to this one, given back by undo
|
||||
QList<CableCoreTaken> m_taken;
|
||||
|
||||
QLabel *m_header = nullptr;
|
||||
QTabWidget *m_tabs = nullptr;
|
||||
QDialogButtonBox *m_buttons = nullptr;
|
||||
|
||||
QLineEdit *m_new_search = nullptr;
|
||||
QCheckBox *m_new_hide = nullptr;
|
||||
QTableView *m_new_table = nullptr;
|
||||
QStandardItemModel *m_new_model = nullptr;
|
||||
CableFilterProxy *m_new_proxy = nullptr;
|
||||
QLabel *m_new_note = nullptr;
|
||||
///The button rewriting the chosen line of the file, which
|
||||
///can only work on a line which was picked
|
||||
QPushButton *m_edit_button = nullptr;
|
||||
CableTypeListData m_types;
|
||||
|
||||
QLineEdit *m_existing_search = nullptr;
|
||||
QCheckBox *m_existing_hide = nullptr;
|
||||
QTableView *m_existing_table = nullptr;
|
||||
QStandardItemModel *m_existing_model = nullptr;
|
||||
CableFilterProxy *m_existing_proxy = nullptr;
|
||||
QLabel *m_existing_note = nullptr;
|
||||
QList<Cable *> m_existing;
|
||||
|
||||
QWidget *m_property_row = nullptr;
|
||||
QLineEdit *m_designation = nullptr;
|
||||
/**
|
||||
The name exactly as this window filled it in, and where
|
||||
that name came from: whatever he types over it from then
|
||||
on is a name of his own (see accept())
|
||||
*/
|
||||
QString m_designation_prefill;
|
||||
bool m_designation_prefill_by_hand = false;
|
||||
QLineEdit *m_installation = nullptr;
|
||||
QLineEdit *m_location = nullptr;
|
||||
};
|
||||
|
||||
#endif // CABLECREATEDIALOG_H
|
||||
@@ -0,0 +1,635 @@
|
||||
/*
|
||||
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 "cablemanager.h"
|
||||
|
||||
#include "cable.h"
|
||||
#include "../autoNum/assignvariables.h"
|
||||
#include "../autoNum/numerotationcontextcommands.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/terminal.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QPainterPath>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
@brief Where two segments cross, if they do at all.
|
||||
|
||||
Done by hand rather than with QLineF::intersects() because the
|
||||
collinear case has to be a miss: a cable drawn exactly on top of a
|
||||
conductor follows it, it does not cross it, and counting it would
|
||||
claim a core the cable never reaches.
|
||||
@param a1 start of the first segment
|
||||
@param a2 end of the first segment
|
||||
@param b1 start of the second segment
|
||||
@param b2 end of the second segment
|
||||
@param result receives the crossing point when there is one
|
||||
@return true when the two segments cross within their own length
|
||||
*/
|
||||
bool segmentIntersection(const QPointF &a1, const QPointF &a2,
|
||||
const QPointF &b1, const QPointF &b2,
|
||||
QPointF *result)
|
||||
{
|
||||
const QPointF r = a2 - a1;
|
||||
const QPointF s = b2 - b1;
|
||||
const QPointF u = b1 - a1;
|
||||
|
||||
const qreal denominator = r.x() * s.y() - r.y() * s.x();
|
||||
if (qFuzzyIsNull(denominator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const qreal t = (u.x() * s.y() - u.y() * s.x()) / denominator;
|
||||
const qreal v = (u.x() * r.y() - u.y() * r.x()) / denominator;
|
||||
if (t < 0.0 || t > 1.0 || v < 0.0 || v > 1.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
*result = a1 + t * r;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief How far a point stands from a segment, in scene units
|
||||
*/
|
||||
qreal distanceToSegment(const QPointF &point, const QPointF &a, const QPointF &b)
|
||||
{
|
||||
const QPointF ab = b - a;
|
||||
const qreal length_squared = ab.x() * ab.x() + ab.y() * ab.y();
|
||||
if (qFuzzyIsNull(length_squared)) {
|
||||
return QLineF(point, a).length();
|
||||
}
|
||||
|
||||
const QPointF ap = point - a;
|
||||
qreal t = (ap.x() * ab.x() + ap.y() * ab.y()) / length_squared;
|
||||
t = qBound(0.0, t, 1.0);
|
||||
return QLineF(point, a + t * ab).length();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Whether some cable of the project already stands under this
|
||||
number.
|
||||
|
||||
A number which is taken must not be handed out twice. That happens
|
||||
while a whole run of cables is numbered one after the other -- none
|
||||
of them has been added to the project by anything but its own
|
||||
creation yet -- and it happens when a rule has simply been given a
|
||||
value which some cable carries already.
|
||||
@param project
|
||||
@param designation the number to look for
|
||||
@return true when a cable of that project stands under it
|
||||
*/
|
||||
bool designationIsTaken(QETProject *project, const QString &designation)
|
||||
{
|
||||
if (!project || designation.isEmpty()) return false;
|
||||
|
||||
for (Cable *cable : project->cables())
|
||||
{
|
||||
if (cable && cable->designation() == designation) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@brief CableManager::crossedConductors
|
||||
Tell which conductors a line about to become a cable runs across, in
|
||||
the order they are met along the line. That order is what gives each
|
||||
core its place, and therefore its colour, so it has to be the order
|
||||
of the drawing and not the order the conductors happen to be stored
|
||||
in.
|
||||
@param diagram the folio the line is drawn on
|
||||
@param line the line, in scene coordinates
|
||||
@return the crossings, ordered from the start of the line
|
||||
*/
|
||||
QList<CableCrossing> CableManager::crossedConductors(Diagram *diagram, const QLineF &line)
|
||||
{
|
||||
QList<CableCrossing> crossings;
|
||||
if (!diagram) {
|
||||
return crossings;
|
||||
}
|
||||
|
||||
const QPointF start = line.p1();
|
||||
if (qFuzzyCompare(start, line.p2())) {
|
||||
return crossings;
|
||||
}
|
||||
|
||||
const QPointF direction = line.p2() - start;
|
||||
for (Conductor *conductor : diagram->conductors())
|
||||
{
|
||||
QPointF where;
|
||||
if (!crossingOf(diagram, line, conductor, &where)) continue;
|
||||
|
||||
CableCrossing crossing;
|
||||
crossing.conductor = conductor;
|
||||
crossing.position = where;
|
||||
crossings.append(crossing);
|
||||
}
|
||||
|
||||
std::sort(crossings.begin(), crossings.end(),
|
||||
[&start, &direction](const CableCrossing &a_, const CableCrossing &b_) {
|
||||
const QPointF da = a_.position - start;
|
||||
const QPointF db = b_.position - start;
|
||||
const qreal ta = da.x() * direction.x() + da.y() * direction.y();
|
||||
const qreal tb = db.x() * direction.x() + db.y() * direction.y();
|
||||
return ta < tb;
|
||||
});
|
||||
|
||||
return crossings;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::crossingOf
|
||||
Where one line runs across one conductor, if it does at all.
|
||||
|
||||
Done by hand rather than with QLineF::intersects() because the
|
||||
collinear case has to be a miss: a cable drawn exactly on top of a
|
||||
conductor follows it, it does not cross it, and counting it would
|
||||
claim a core the cable never reaches.
|
||||
@param diagram the folio the line is drawn on
|
||||
@param line the line, in scene coordinates
|
||||
@param conductor the wire being asked about
|
||||
@param result receives the crossing point, in scene coordinates
|
||||
@return true when the line really runs across that wire
|
||||
*/
|
||||
bool CableManager::crossingOf(Diagram *diagram,
|
||||
const QLineF &line,
|
||||
Conductor *conductor,
|
||||
QPointF *result)
|
||||
{
|
||||
if (!conductor || !diagram) return false;
|
||||
|
||||
const QPointF start = line.p1();
|
||||
const QPointF end = line.p2();
|
||||
if (qFuzzyCompare(start, end)) return false;
|
||||
|
||||
//Work in the conductor's own coordinates: its path is stored
|
||||
//there, and a conductor is never where it was when it was
|
||||
//created once the whole drawing has been moved about.
|
||||
const QPointF a = conductor->mapFromScene(start);
|
||||
const QPointF b = conductor->mapFromScene(end);
|
||||
|
||||
const QPainterPath path = conductor->path();
|
||||
for (int i = 0; i + 1 < path.elementCount(); ++i)
|
||||
{
|
||||
const QPainterPath::Element &e1 = path.elementAt(i);
|
||||
const QPainterPath::Element &e2 = path.elementAt(i + 1);
|
||||
//A MoveTo ends the segment before it: the jump to the new
|
||||
//subpath is not drawn, so nothing can cross it.
|
||||
if (e2.type == QPainterPath::MoveToElement) {
|
||||
continue;
|
||||
}
|
||||
QPointF where;
|
||||
if (segmentIntersection(a, b,
|
||||
QPointF(e1.x, e1.y),
|
||||
QPointF(e2.x, e2.y),
|
||||
&where))
|
||||
{
|
||||
if (result) {
|
||||
*result = conductor->mapToScene(where);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::distanceTo
|
||||
How far a point of the sheet stands from a wire, in scene units.
|
||||
@param conductor
|
||||
@param scene_pos
|
||||
@return that distance, which is where the wire passes when the point
|
||||
is beside it rather than in front of one of its ends
|
||||
*/
|
||||
qreal CableManager::distanceTo(Conductor *conductor, const QPointF &scene_pos)
|
||||
{
|
||||
if (!conductor) {
|
||||
return std::numeric_limits<qreal>::max();
|
||||
}
|
||||
|
||||
qreal best = std::numeric_limits<qreal>::max();
|
||||
const QPainterPath path = conductor->path();
|
||||
for (int i = 0; i + 1 < path.elementCount(); ++i)
|
||||
{
|
||||
const QPainterPath::Element &e1 = path.elementAt(i);
|
||||
const QPainterPath::Element &e2 = path.elementAt(i + 1);
|
||||
if (e2.type == QPainterPath::MoveToElement) continue;
|
||||
|
||||
const qreal d = distanceToSegment(
|
||||
scene_pos,
|
||||
conductor->mapToScene(QPointF(e1.x, e1.y)),
|
||||
conductor->mapToScene(QPointF(e2.x, e2.y)));
|
||||
if (d < best) {
|
||||
best = d;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
@brief The end of @a conductor which does not touch @a element.
|
||||
|
||||
Which end that is tells where the wire goes on its own sheet: a wire
|
||||
ending on a report arrow points away from it, and that is the end
|
||||
which says something about where it comes from or where it runs to.
|
||||
@param conductor the wire to look at
|
||||
@param element the report arrow it ends on (may be nullptr)
|
||||
@return the scene position of the other end, (0, 0) when there is none
|
||||
*/
|
||||
QPointF farEndPosition(const Conductor *conductor, Element *element)
|
||||
{
|
||||
const Terminal *far = nullptr;
|
||||
if (conductor->terminal1
|
||||
&& conductor->terminal1->parentElement() != element) {
|
||||
far = conductor->terminal1;
|
||||
} else {
|
||||
far = conductor->terminal2 ? conductor->terminal2 : conductor->terminal1;
|
||||
}
|
||||
return far ? far->scenePos() : QPointF();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Whether wire @a a runs before wire @a b on its own sheet.
|
||||
|
||||
Both are looked at from @a element: what tells them apart is where
|
||||
their other end goes. Putting the wires which come in to a report
|
||||
and the wires which go out of its partner in this order is what
|
||||
pairs a continuation up with the core it belongs to when several
|
||||
cores run through one and the same report.
|
||||
@param a @param b the two wires to compare
|
||||
@param element the report arrow both end on (may be nullptr)
|
||||
@return true when @a a comes first
|
||||
*/
|
||||
bool farBefore(const Conductor *a, const Conductor *b, Element *element)
|
||||
{
|
||||
const QPointF pa = farEndPosition(a, element);
|
||||
const QPointF pb = farEndPosition(b, element);
|
||||
if (pa.y() != pb.y()) return pa.y() < pb.y();
|
||||
if (pa.x() != pb.x()) return pa.x() < pb.x();
|
||||
return a->uuid() < b->uuid();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@brief CableManager::refreshLabels
|
||||
Rewrite the cable field of every conductor of the project from the
|
||||
cables themselves.
|
||||
|
||||
The rule is one-directional and therefore cannot drift: a conductor
|
||||
which some cable claims gets the label that cable generates, a
|
||||
conductor which no cable claims any more -- because the cable was
|
||||
delete, the core was dragged away or the type lost that core -- has
|
||||
the generated label taken away. A conductor which never belonged to
|
||||
a cable keeps whatever it had, since only one with a reference is
|
||||
touched.
|
||||
|
||||
A core which ends on a folio report goes on over there: the wire
|
||||
coming out of the linked arrow shows the same piece of copper, so
|
||||
it belongs to the same core and carries the same cable field. What
|
||||
the links say is worked out from scratch on every pass, and a wire
|
||||
which some cable already claims is never taken away from it, so the
|
||||
reference appears wherever a report carries a core and disappears
|
||||
again by itself as soon as the cable, the core or the link itself
|
||||
is gone. Chains of reports are followed as far as they go, so a
|
||||
cable may run over any number of sheets.
|
||||
@param project
|
||||
*/
|
||||
void CableManager::refreshLabels(QETProject *project)
|
||||
{
|
||||
if (!project) return;
|
||||
|
||||
struct Binding {
|
||||
QUuid cable;
|
||||
int slot = -1;
|
||||
QString label;
|
||||
};
|
||||
|
||||
QHash<QUuid, Binding> bindings;
|
||||
for (Cable *cable : project->cables())
|
||||
{
|
||||
if (!cable) continue;
|
||||
for (const CableCore &core : cable->usedCores())
|
||||
{
|
||||
if (core.conductor.isNull()) continue;
|
||||
Binding binding;
|
||||
binding.cable = cable->uuid();
|
||||
binding.slot = core.core;
|
||||
binding.label = cable->labelOfCore(core.core);
|
||||
bindings.insert(core.conductor, binding);
|
||||
}
|
||||
}
|
||||
|
||||
//A core which ends on a folio report continues on the wire which
|
||||
//leaves the linked arrow on the other sheet. That wire shows the
|
||||
//same piece of copper, so it gets the same reference as the core
|
||||
//it continues. The walk repeats while it finds new wires, so a
|
||||
//chain of reports is followed across any number of sheets, and
|
||||
//it only ever fills a wire no cable has claimed yet: two cores
|
||||
//can never fight over the same wire, and a reference worked out
|
||||
//here is taken away again by the same pass as soon as the cable,
|
||||
//the core or the link does.
|
||||
QHash<QUuid, Conductor *> conductors_by_uuid;
|
||||
QHash<Element *, QList<Conductor *>> endings_on_report;
|
||||
for (Diagram *diagram : project->diagrams())
|
||||
{
|
||||
if (!diagram) continue;
|
||||
for (Conductor *conductor : diagram->conductors())
|
||||
{
|
||||
if (conductor) conductors_by_uuid.insert(conductor->uuid(), conductor);
|
||||
}
|
||||
}
|
||||
|
||||
//Where a wire goes on its own sheet (farBefore, above): putting the
|
||||
//wires which come in and the wires which go out in the same order
|
||||
//is what pairs a continuation up with its core when several cores
|
||||
//run through one and the same report.
|
||||
|
||||
bool grew = true;
|
||||
while (grew)
|
||||
{
|
||||
grew = false;
|
||||
endings_on_report.clear();
|
||||
for (const QUuid &source_uuid : bindings.keys())
|
||||
{
|
||||
Conductor *source = conductors_by_uuid.value(source_uuid);
|
||||
if (!source) continue;
|
||||
|
||||
Terminal *ends[2] = {source->terminal1, source->terminal2};
|
||||
for (Terminal *end : ends)
|
||||
{
|
||||
if (!end) continue;
|
||||
Element *element = end->parentElement();
|
||||
//An arrow taken off the sheet carries nothing over
|
||||
//any more, whatever still links it
|
||||
if (!element || !element->scene()) continue;
|
||||
if (!(element->linkType() & Element::AllReport)) continue;
|
||||
|
||||
QList<Conductor *> &in = endings_on_report[element];
|
||||
if (!in.contains(source)) in.append(source);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = endings_on_report.constBegin();
|
||||
it != endings_on_report.constEnd(); ++it)
|
||||
{
|
||||
Element *element = it.key();
|
||||
QList<Conductor *> in = it.value();
|
||||
std::sort(in.begin(), in.end(),
|
||||
[&](const Conductor *a, const Conductor *b) {
|
||||
return farBefore(a, b, element);
|
||||
});
|
||||
|
||||
for (Element *partner : element->linkedElements())
|
||||
{
|
||||
if (!partner || partner == element || !partner->scene()) continue;
|
||||
|
||||
QList<Conductor *> out = partner->conductors();
|
||||
std::sort(out.begin(), out.end(),
|
||||
[&](const Conductor *a, const Conductor *b) {
|
||||
return farBefore(a, b, partner);
|
||||
});
|
||||
|
||||
//One core coming in may branch out into several wires
|
||||
//over there -- they all show the same piece of copper.
|
||||
//Several cores coming in take one wire each, in the
|
||||
//order they run on their own sheet.
|
||||
const int count = (in.size() == 1)
|
||||
? out.size()
|
||||
: qMin(in.size(), out.size());
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
Conductor *continued = out.at(i);
|
||||
if (!continued || bindings.contains(continued->uuid())) continue;
|
||||
const int from = qMin(i, in.size() - 1);
|
||||
bindings.insert(continued->uuid(),
|
||||
bindings.value(in.at(from)->uuid()));
|
||||
grew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Diagram *diagram : project->diagrams())
|
||||
{
|
||||
if (!diagram) continue;
|
||||
for (Conductor *conductor : diagram->conductors())
|
||||
{
|
||||
if (!conductor) continue;
|
||||
|
||||
const auto it = bindings.constFind(conductor->uuid());
|
||||
if (it != bindings.constEnd())
|
||||
{
|
||||
const ConductorProperties &properties = conductor->properties();
|
||||
conductor->setCableReference(it->label, it->cable, it->slot);
|
||||
}
|
||||
else if (!conductor->properties().m_cable_uuid.isNull())
|
||||
{
|
||||
conductor->setCableReference(QString(), QUuid(), -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::claimConductor
|
||||
Make a conductor a core of @a new_owner, taking it away from whichever
|
||||
cable held it before. A conductor is a single piece of wire: two
|
||||
cables cannot both claim it, and the one that loses it must not go
|
||||
on drawing a label for it.
|
||||
@param project
|
||||
@param conductor
|
||||
@param new_owner the cable which is about to use the conductor
|
||||
*/
|
||||
void CableManager::claimConductor(QETProject *project, Conductor *conductor, Cable *new_owner)
|
||||
{
|
||||
if (!project || !conductor) return;
|
||||
|
||||
const QUuid owner_uuid = conductor->properties().m_cable_uuid;
|
||||
if (owner_uuid.isNull() || (new_owner && owner_uuid == new_owner->uuid())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Cable *previous = cableByUuid(project, owner_uuid)) {
|
||||
previous->removeCore(conductor->properties().m_cable_slot);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::cableByUuid
|
||||
@param project
|
||||
@param uuid
|
||||
@return the cable with that identity, or nullptr
|
||||
*/
|
||||
Cable *CableManager::cableByUuid(QETProject *project, const QUuid &uuid)
|
||||
{
|
||||
if (!project || uuid.isNull()) return nullptr;
|
||||
for (Cable *cable : project->cables()) {
|
||||
if (cable && cable->uuid() == uuid) return cable;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::conductorByUuid
|
||||
@param project
|
||||
@param uuid
|
||||
@return the conductor with that identity, or nullptr. Needed because
|
||||
a cable remembers its cores by identity: only the drawing knows where
|
||||
they ended up.
|
||||
*/
|
||||
Conductor *CableManager::conductorByUuid(QETProject *project, const QUuid &uuid)
|
||||
{
|
||||
if (!project || uuid.isNull()) return nullptr;
|
||||
for (Diagram *diagram : project->diagrams()) {
|
||||
if (!diagram) continue;
|
||||
for (Conductor *conductor : diagram->conductors()) {
|
||||
if (conductor && conductor->uuid() == uuid) return conductor;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::nextDesignation
|
||||
Work out the number of the next cable drawn on a folio.
|
||||
|
||||
When the project has a numbering rule for cables, that rule decides:
|
||||
whatever it holds at this moment is the number, read by the very
|
||||
same machinery the element numbering reads its own. When it has
|
||||
none, the plain letter W is handed out: a project which numbers its
|
||||
cables by no rule gets no numbers invented for it, and W -- the one
|
||||
part of the old numbers which was never a number -- is all such a
|
||||
cable is called until a rule is defined.
|
||||
|
||||
A rule whose counter belongs to one folio is read from the beginning
|
||||
of the folio the cable is drawn on, so that the number this cable
|
||||
gets is the first free one of its own folio rather than whatever the
|
||||
project's rule happens to hold after numbering other folios.
|
||||
|
||||
Nothing here moves the project's own rule on: that belongs to the
|
||||
moment the cable really comes into being.
|
||||
@param project
|
||||
@param diagram the folio the cable is being drawn on
|
||||
@return the number
|
||||
*/
|
||||
QString CableManager::nextDesignation(QETProject *project, Diagram *diagram)
|
||||
{
|
||||
NumerotationContext context;
|
||||
if (project) {
|
||||
context = project->cableAutoNum(project->cableCurrentAutoNum());
|
||||
}
|
||||
//The folio-bound parts are put back to the start of that folio;
|
||||
//the stepping of the overload below then walks past every
|
||||
//number of this folio which is already taken
|
||||
context = autonum::resetFolioCounters(context);
|
||||
return nextDesignation(project, diagram, context);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::nextDesignation
|
||||
Work the number of a cable out of a rule which the caller keeps in
|
||||
@p context -- the project's own rule as it stands, or a copy of it
|
||||
which the caller is numbering a whole run of cables with.
|
||||
|
||||
Whatever the rule holds is the number, unless some cable already
|
||||
stands under it: then @p context is stepped on and the rule is read
|
||||
once more, so that a paste which brings in several cables at once
|
||||
numbers each of them differently although the project's own rule
|
||||
does not move until those cables really exist. A rule which cannot
|
||||
step -- one with nothing in it which counts -- hands out what it
|
||||
says and says no more, for what it says is all it knows.
|
||||
|
||||
A project which numbers its cables by no rule is not read at all
|
||||
and is handed the plain letter W instead: no numbers are invented
|
||||
where none have been defined.
|
||||
@param project
|
||||
@param diagram the folio the cable is being drawn on, which every
|
||||
folio-bound part of a rule is read from
|
||||
@param context the rule to read; stepped on when what it says is
|
||||
already taken
|
||||
@return the number
|
||||
*/
|
||||
QString CableManager::nextDesignation(QETProject *project,
|
||||
Diagram *diagram,
|
||||
NumerotationContext &context)
|
||||
{
|
||||
if (!project || !diagram) return QString();
|
||||
|
||||
const QString key = project->cableCurrentAutoNum();
|
||||
//Whether there was a rule to read at all, taken down before the
|
||||
//loop: stepping the rule on empties it, and that must not look
|
||||
//like a project which never had one.
|
||||
const bool ruled = !key.isEmpty() && !context.isEmpty();
|
||||
QString previous;
|
||||
|
||||
for (int attempt = 0; ruled && attempt < 1000 && !context.isEmpty(); ++attempt)
|
||||
{
|
||||
//The rule is read on a copy: reading it fills in what its
|
||||
//folio-bound parts are worth here and now, and that must
|
||||
//not creep into the rule itself.
|
||||
NumerotationContext evaluated = context;
|
||||
const QString formula = autonum::numerotationContextToFormula(evaluated);
|
||||
autonum::sequentialNumbers seq;
|
||||
autonum::setSequential(formula, seq, evaluated, diagram, key);
|
||||
const QString candidate =
|
||||
autonum::AssignVariables::formulaToLabel(formula, seq, diagram);
|
||||
|
||||
if (candidate.isEmpty()) break;
|
||||
if (!designationIsTaken(project, candidate)) return candidate;
|
||||
|
||||
//Taken already: step the rule on and read it once more.
|
||||
if (candidate == previous) return candidate;
|
||||
previous = candidate;
|
||||
|
||||
NumerotationContextCommands ncc(context, diagram);
|
||||
const NumerotationContext following = ncc.next();
|
||||
if (following.size() != context.size()) break;
|
||||
context = following;
|
||||
}
|
||||
|
||||
//No rule at all: the plain letter W, and nothing more. The old
|
||||
//numbers read folio + W + index, and W is the only part of them
|
||||
//which was never a number -- so it is what a project which has
|
||||
//defined no numbering rule calls its cables, until it defines
|
||||
//one.
|
||||
if (!ruled) {
|
||||
return QStringLiteral("W");
|
||||
}
|
||||
|
||||
//A rule which says nothing usable here: no number at all. The
|
||||
//number is what the rule says, and a rule which says nothing
|
||||
//leaves the field to the user rather than having it filled in
|
||||
//with something it never held.
|
||||
return QString();
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLEMANAGER_H
|
||||
#define CABLEMANAGER_H
|
||||
|
||||
#include <QLineF>
|
||||
#include <QList>
|
||||
#include <QPointF>
|
||||
#include <QUuid>
|
||||
|
||||
class Cable;
|
||||
class Conductor;
|
||||
class Diagram;
|
||||
class NumerotationContext;
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief One place where a drawn cable line crosses a conductor.
|
||||
*/
|
||||
struct CableCrossing
|
||||
{
|
||||
Conductor *conductor = nullptr;
|
||||
/// Scene coordinates of the crossing, where the slash is drawn
|
||||
QPointF position;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The CableManager works out everything which follows from the
|
||||
cables of a project, so nothing has to be maintained by hand.
|
||||
|
||||
It detects which conductors a line about to be drawn crosses, and it
|
||||
turns the cables back into the cable field of every conductor they
|
||||
own -- both directions of one single rule: the cable is the only
|
||||
source of truth.
|
||||
*/
|
||||
class CableManager
|
||||
{
|
||||
public:
|
||||
///How far a colour label may stand from where a line crosses
|
||||
///a wire for that wire to count as the one the label stands
|
||||
///on: one scene unit, and that is already generous. The grid
|
||||
///is there to put a label exactly on the crossing, and it
|
||||
///does: a label drawn on a wire, slid along its own line or
|
||||
///carried there by a paste lands on that crossing to the
|
||||
///last decimal. The single unit only soaks up the rounding
|
||||
///which comes of projecting a label onto its line, and a
|
||||
///terminal which is a hair off the grid. Anything beyond it
|
||||
///is beside the wire rather than on it, so a label one
|
||||
///whole grid step away -- however the grid is set -- gives
|
||||
///up its entry. One number for every place a label is given
|
||||
///to a wire: drawing one, dragging one and wiring a copy
|
||||
///must not disagree about it.
|
||||
static constexpr qreal bind_tolerance = 1.0;
|
||||
|
||||
//Drawing
|
||||
static QList<CableCrossing> crossedConductors(Diagram *diagram, const QLineF &line);
|
||||
static bool crossingOf(Diagram *diagram,
|
||||
const QLineF &line,
|
||||
Conductor *conductor,
|
||||
QPointF *result);
|
||||
static qreal distanceTo(Conductor *conductor, const QPointF &scene_pos);
|
||||
|
||||
//Keeping the drawing in step with the cables
|
||||
static void refreshLabels(QETProject *project);
|
||||
static void claimConductor(QETProject *project, Conductor *conductor, Cable *new_owner);
|
||||
|
||||
//Lookup
|
||||
static Cable *cableByUuid(QETProject *project, const QUuid &uuid);
|
||||
static Conductor *conductorByUuid(QETProject *project, const QUuid &uuid);
|
||||
static QString nextDesignation(QETProject *project, Diagram *diagram);
|
||||
/**
|
||||
Work the number of a next cable out of a rule which is
|
||||
@em not the project's own state but the one @p context
|
||||
holds: the rule is read as it stands there and what it
|
||||
hands out is the number, unless some cable already
|
||||
stands under that number -- which is what happens while
|
||||
a whole run of cables is numbered one after the other
|
||||
before any of them has really been added. Then @p
|
||||
context is stepped on and the rule is read once more.
|
||||
|
||||
When the project numbers its cables by no rule at all,
|
||||
@p context is not read and the plain letter W is handed
|
||||
out instead: no numbers are invented where none have
|
||||
been defined.
|
||||
|
||||
A caller numbering a run of cables keeps the same @p
|
||||
context for all of them, so each of them gets a number
|
||||
of its own; a caller numbering one cable lets it go
|
||||
again afterwards. The project's own rule is never
|
||||
moved on here -- that belongs to the moment the cable
|
||||
comes into being.
|
||||
*/
|
||||
static QString nextDesignation(QETProject *project,
|
||||
Diagram *diagram,
|
||||
NumerotationContext &context);
|
||||
};
|
||||
|
||||
#endif // CABLEMANAGER_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLEPART_H
|
||||
#define CABLEPART_H
|
||||
|
||||
#include "cable.h"
|
||||
#include "cablemanager.h"
|
||||
#include "../qetgraphicsitem/qetgraphicsitem.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
|
||||
class Cable;
|
||||
class CablePartData;
|
||||
class Conductor;
|
||||
class QUndoCommand;
|
||||
class QWidget;
|
||||
|
||||
/**
|
||||
@brief The CablePart class draws one section of a cable: the trunk
|
||||
line of one folio, the number and the type of the cable at its left,
|
||||
and a slash with the colour of a core wherever that core crosses a
|
||||
conductor.
|
||||
|
||||
All of it is a view of the Cable: the geometry lives in the cable
|
||||
itself, so lengthening the trunk line is purely optical. What a core
|
||||
wires is read off where its colour label stands: dropping a label
|
||||
gives that core the wire it lands on, and letting go of a moved line
|
||||
works the entries out the same way, from where the labels have come
|
||||
to rest. A label is never sent off to look for a wire and never
|
||||
moved to reach one -- it goes on standing where the user put it --
|
||||
and no gesture at all takes a core away from its cable.
|
||||
*/
|
||||
class CablePart : public QetGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CablePart(const CablePartData &data, QGraphicsItem *parent = nullptr);
|
||||
explicit CablePart(QGraphicsItem *parent = nullptr);
|
||||
~CablePart() override;
|
||||
|
||||
enum {Type = UserType + 1012};
|
||||
int type() const override {return Type;}
|
||||
|
||||
void setCable(Cable *cable);
|
||||
Cable *cable() const;
|
||||
QUuid partUuid() const;
|
||||
///Bind the item to its cable once the project is loaded
|
||||
void refreshPending();
|
||||
|
||||
void setLine(const QPointF &p1, const QPointF &p2);
|
||||
QLineF line() const;
|
||||
QPointF firstPoint() const {return m_p1;}
|
||||
QPointF secondPoint() const {return m_p2;}
|
||||
|
||||
QRectF boundingRect() const override;
|
||||
QPainterPath shape() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
QString name() const override;
|
||||
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
///Shows the hand over a line which points at another folio
|
||||
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
|
||||
///Opens the dialog editing the properties of the whole cable
|
||||
void editProperty() override;
|
||||
|
||||
/**
|
||||
Give this line the mouse and wait for the next click to put
|
||||
down a core of its cable which is not on the sheet yet: the
|
||||
core follows this line wherever the mouse comes near it and
|
||||
the click leaves it standing there, snapped to the grid like
|
||||
anything else drawn.
|
||||
|
||||
It is always this very section which takes it, never the
|
||||
first section of the cable: a cable running over two folios
|
||||
stands on two lines, and the one whose dialog was asked for
|
||||
is the one being worked on.
|
||||
@param core which core of the cable is being put down
|
||||
*/
|
||||
void startPlacingCore(int core);
|
||||
///Stop waiting for that click, putting nothing down at all
|
||||
void cancelPlacement();
|
||||
|
||||
/**
|
||||
The line which waits for the click putting one of its cores
|
||||
down at this very moment, anywhere in the application, or
|
||||
nullptr when no line does.
|
||||
|
||||
Only one line at a time may wait -- taking the core always
|
||||
belongs to the line whose dialog was asked for -- so Escape
|
||||
and the right button can simply ask here and always act on
|
||||
the right one, however many folios are open.
|
||||
*/
|
||||
static CablePart *placingPart();
|
||||
/**
|
||||
End that wait wherever it stands, without putting anything
|
||||
down anywhere: the core goes back to being a free one.
|
||||
|
||||
Used by the sheet itself, which gets the right button and
|
||||
Escape before any item does (see DiagramView).
|
||||
@return true when a line was waiting for its click
|
||||
*/
|
||||
static bool cancelPlacing();
|
||||
|
||||
/**
|
||||
Which core of this cable has its colour label standing at
|
||||
that very place on the sheet -- asked by the sheet itself
|
||||
when the right button comes down on a line, so that the
|
||||
menu which opens belongs to that one core rather than to
|
||||
the whole folio.
|
||||
@param scene_pos a point of the sheet, in scene coordinates
|
||||
@return the core standing there, -1 when none does
|
||||
*/
|
||||
int coreAt(const QPointF &scene_pos) const;
|
||||
/**
|
||||
Take one core off this line: it names no wire any more and
|
||||
goes back to being a free slot of its cable, listed among
|
||||
the ones which stand nowhere -- the very opposite of
|
||||
putting one down from that list.
|
||||
|
||||
Kept as one undo step, so a single undo puts the label
|
||||
back exactly where it stood.
|
||||
@param core which core standing on this line is taken off
|
||||
@return true when a core was taken off
|
||||
*/
|
||||
bool takeCoreOffLine(int core);
|
||||
|
||||
/**
|
||||
Which wire this line runs across at the very place a colour
|
||||
label stands -- so that the entry of a wire follows the label
|
||||
instead of the label being sent off to find a wire. Also asked
|
||||
by a copy being put down (CableCopy::wire), which must not
|
||||
move any label to reach a wire either.
|
||||
@param crossings every place this line runs across a wire
|
||||
@param core the core being worked out
|
||||
@param at where its label stands, in scene coordinates
|
||||
@return the wire to give the core, or nullptr
|
||||
*/
|
||||
Conductor *wireFor(const QList<CableCrossing> &crossings,
|
||||
const CableCore &core, const QPointF &at) const;
|
||||
|
||||
/**
|
||||
Move this line from outside: the sheet moving a whole
|
||||
selection of elements and lines at once asks every line
|
||||
of the selection to come along, one step after the other,
|
||||
exactly as if the finger had been on the line itself, so
|
||||
that one gesture moves everything which is marked instead
|
||||
of two halves of the selection going different ways.
|
||||
|
||||
The calls come in order -- begin once, continue as often
|
||||
as the gesture needs, settle once for the whole
|
||||
selection, then either done or abort -- and a line which
|
||||
never moves simply has no undo step to hand over.
|
||||
*/
|
||||
void beginLineGesture();
|
||||
///Take one more step of such a move, by that much
|
||||
void continueLineGesture(const QPointF &delta);
|
||||
/**
|
||||
Where this line comes to rest, worked out the same way as
|
||||
after a drag of its own: which wire every colour label now
|
||||
stands on, asked before anything changes hands. The step
|
||||
of this line is handed over rather than pushed, so that
|
||||
the movement as a whole ends up as one single undo step.
|
||||
@param steps receives the undo step of this line, if any
|
||||
@return false when he called the gesture off
|
||||
*/
|
||||
bool settleLineGesture(QList<QUndoCommand *> &steps);
|
||||
///Put this line back where the gesture found it
|
||||
void abortLineGesture();
|
||||
///The gesture is over for this line: forget where it began
|
||||
void doneLineGesture();
|
||||
|
||||
/**
|
||||
One cross reference of this section as the PDF export
|
||||
needs it: where the text of the reference stands on this
|
||||
folio, and on the folio it points at where the section it
|
||||
names stands -- so that exporting makes it a clickable
|
||||
link the way a cross reference of an element already is.
|
||||
|
||||
Both rectangles are in scene coordinates, each of its own
|
||||
folio.
|
||||
*/
|
||||
struct PdfRef
|
||||
{
|
||||
///The folio the reference points at
|
||||
Diagram *diagram = nullptr;
|
||||
///Where the text of the reference stands on this folio
|
||||
QRectF here;
|
||||
///Where the section it names stands on that folio
|
||||
QRectF there;
|
||||
};
|
||||
QList<PdfRef> pdfRefs() const;
|
||||
|
||||
protected:
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
One line of the label at the left of the trunk line:
|
||||
what is written, which field of the cable it comes from
|
||||
and -- worked out from what that field asks for -- the
|
||||
font it is written with, the way it lines up and the
|
||||
rectangle it sits in.
|
||||
|
||||
Every line carries its own font, because every field
|
||||
may have been given one of its own: Anlage, Ort and
|
||||
Désignation stand in one block, and their lines may
|
||||
very well no longer all be the same size once he has
|
||||
set them apart.
|
||||
*/
|
||||
struct LabelLine
|
||||
{
|
||||
QString text;
|
||||
QString key;
|
||||
QFont font;
|
||||
Qt::Alignment align = Qt::AlignRight;
|
||||
qreal width = 0.0;
|
||||
qreal height = 0.0;
|
||||
QRectF rect;
|
||||
};
|
||||
|
||||
/**
|
||||
One line of the label which points at a folio this cable
|
||||
runs on besides this one: that folio, the section of the
|
||||
cable standing there, and the text this project asks for
|
||||
such a reference -- the format chosen for the type
|
||||
"Câble" under Querverweise, written out once per folio,
|
||||
in the order the folios turn.
|
||||
*/
|
||||
struct CrossRef
|
||||
{
|
||||
///The other folio, which always exists while listed
|
||||
Diagram *diagram = nullptr;
|
||||
///Which section of the cable stands on it
|
||||
QUuid part;
|
||||
///The text as it is written on the drawing
|
||||
QString text;
|
||||
};
|
||||
|
||||
QFont textFont() const;
|
||||
///The font the colours of the cores are written with
|
||||
QFont coreFont() const;
|
||||
///The font one named text of this cable is written with
|
||||
QFont fontOf(const QString &key) const;
|
||||
///How one named text of this cable lines up at the line
|
||||
Qt::Alignment alignOf(const QString &key) const;
|
||||
/**
|
||||
How far the whole label at the left of the line is
|
||||
nudged on the drawing: over the line, under it, left
|
||||
or right of the end of it. A nudge only, which moves
|
||||
the texts and never the line.
|
||||
@return the distance in scene units
|
||||
*/
|
||||
QPointF textOffset() const;
|
||||
/**
|
||||
How far the row of colours is nudged on the drawing:
|
||||
again a distance on the texts only, the slashes and
|
||||
the entries behind them staying where they stand.
|
||||
@return the distance in scene units
|
||||
*/
|
||||
QPointF coreOffset() const;
|
||||
/**
|
||||
Where a text of this width starts, at the left end of
|
||||
the trunk line: before the line when it is set to end
|
||||
there (the way it always did), on the line when it is
|
||||
centred, after it when it starts there.
|
||||
@param key the field the text comes from
|
||||
@param width how wide the text is, in scene units
|
||||
@return the x of the left of the text
|
||||
*/
|
||||
qreal textLeft(const QString &key, qreal width) const;
|
||||
bool trunkIsHorizontal() const;
|
||||
QList<LabelLine> labelLines() const;
|
||||
/**
|
||||
The references to the other folios this cable runs on,
|
||||
one line each, stacked under the length of the cable:
|
||||
at the very bottom of everything the line says.
|
||||
@return those lines, in the order crossRefs() lists them
|
||||
*/
|
||||
QList<LabelLine> refLines() const;
|
||||
/**
|
||||
Every other folio this cable runs on, one entry each, in
|
||||
the order the folios turn -- empty when the cable runs on
|
||||
this folio alone, which is the usual case.
|
||||
*/
|
||||
QList<CrossRef> crossRefs() const;
|
||||
/**
|
||||
Which of those references stands at that very place, so
|
||||
that both the hand of the mouse and a double click know
|
||||
which line they are about.
|
||||
@param scene_pos a point of the sheet, in scene coordinates
|
||||
@return the index of that reference, -1 when none stands there
|
||||
*/
|
||||
int crossRefAt(const QPointF &scene_pos) const;
|
||||
/**
|
||||
Send the mouse to the section of the cable which stands
|
||||
on the folio the reference at that place points at -- the
|
||||
very leap an element's cross reference takes, straight to
|
||||
the place to look at.
|
||||
@param scene_pos a point of the sheet, in scene coordinates
|
||||
@return true when the click stood on a reference
|
||||
*/
|
||||
bool goToCrossRef(const QPointF &scene_pos);
|
||||
/**
|
||||
Keep the reference lines up to date with the project: a
|
||||
folio which is added, removed or moved changes what they
|
||||
say, and so does the format chosen under Querverweise.
|
||||
*/
|
||||
void setUpXrefHooks();
|
||||
QString typeText() const;
|
||||
QString lengthText() const;
|
||||
QRectF labelRect() const;
|
||||
qreal underTop() const;
|
||||
QRectF typeRect() const;
|
||||
QRectF lengthRect() const;
|
||||
QRectF coreLabelRect(const QPointF &at, const QString &color) const;
|
||||
QPointF coreLabelPivot(const QPointF &at, const QString &color) const;
|
||||
QPointF coreDrawPosition(const CableCore &core) const;
|
||||
bool coreDrawn(const CableCore &core) const;
|
||||
QPointF dragPosition(const QPointF &scene_pos) const;
|
||||
/**
|
||||
Where the mouse would put down a core which waits to be
|
||||
put down: the mouse projected onto the line itself, at a
|
||||
grid step, and never past either end of the line -- the
|
||||
same coordinates a dragged label would be drawn at, only
|
||||
worked out from the mouse rather than from a gesture.
|
||||
@param scene_pos where the mouse stands, in scene coordinates
|
||||
@return where the slash would be drawn
|
||||
*/
|
||||
QPointF placePosition(const QPointF &scene_pos) const;
|
||||
/**
|
||||
Snap a distance measured along the line to a grid step, in
|
||||
the coordinates of the sheet -- so a horizontal line steps
|
||||
along x and a vertical one along y. Holding Ctrl lets go of
|
||||
the grid, as everywhere else on the sheet.
|
||||
@param along how far from the first end of the line
|
||||
@return that distance at a grid step
|
||||
*/
|
||||
qreal stepAlong(qreal along) const;
|
||||
void drawLabel(QPainter *painter, const QColor &ink) const;
|
||||
void drawCores(QPainter *painter, const QColor &ink) const;
|
||||
///Draw the slash of one core and the colour written beside it
|
||||
void drawCoreMark(QPainter *painter, const QColor &ink,
|
||||
const QPointF &at, const QString &color) const;
|
||||
void syncGeometry();
|
||||
bool labelAt(int core, const QPointF &scene_pos) const;
|
||||
///Answer to the question of taking a wire another cable holds
|
||||
enum class ClaimAnswer {
|
||||
///Take it: the other cable lets it go
|
||||
Take,
|
||||
///Leave it: nothing changes hands, the rest still happens
|
||||
Leave,
|
||||
///Cancel: this whole gesture is called off, as if undone
|
||||
Cancel
|
||||
};
|
||||
/**
|
||||
@param core which core is being let go
|
||||
@param dropped_at where its label comes to rest
|
||||
@param before every core the cable held before this one was
|
||||
added to it, so that the one step of undo takes the addition
|
||||
back as well; nullptr while a core which is already on the
|
||||
sheet is merely moved
|
||||
@return false when the user called the whole thing off
|
||||
*/
|
||||
bool rebindCore(int core, const QPointF &dropped_at,
|
||||
const QList<CableCore> *before = nullptr);
|
||||
/**
|
||||
Put a core which is not on the sheet yet onto this line: it
|
||||
stands where the mouse says, and if a wire runs across that
|
||||
very place it names that wire the same way a dragged label
|
||||
would. Called off means back to being a free core, with one
|
||||
step of undo to take it away again.
|
||||
@param core which core of the cable is being put down
|
||||
@param at where its label is to stand, in scene coordinates
|
||||
@return false when nothing was put down
|
||||
*/
|
||||
bool placeCore(int core, const QPointF &at);
|
||||
/**
|
||||
Send the mouse to this line, to the grid step the mark of
|
||||
the waiting core stands on, so that the very next click is
|
||||
the one which puts the core down -- wherever he asked for
|
||||
the core from, and whether he asks for this very one a
|
||||
second time or not. The mouse goes there only where the
|
||||
line is really in sight: a folio hidden behind another
|
||||
one, or off the edge of the view, is left alone rather
|
||||
than jumped to.
|
||||
*/
|
||||
void sendMouseToLine();
|
||||
void reconcileCores(QList<CableCore> &cores, const QPointF &delta) const;
|
||||
bool settleCores(QList<CableCore> &cores) const;
|
||||
ClaimAnswer mayClaim(Conductor *conductor) const;
|
||||
ClaimAnswer mayClaimSeveral(const QList<Conductor *> &conductors) const;
|
||||
ClaimAnswer askClaim(QWidget *parent,
|
||||
const QString &title,
|
||||
const QString &question) const;
|
||||
|
||||
private:
|
||||
///The one line which waits for the click putting a core down,
|
||||
///null once there is none: taking the core belongs to the line
|
||||
///whose dialog was asked for, never to the first line of the
|
||||
///cable, and the sheet has to be able to cancel that wait
|
||||
///without knowing which scene it stands in
|
||||
static QPointer<CablePart> s_placing;
|
||||
QPointer<Cable> m_cable;
|
||||
///Identity of the cable this part belongs to, kept until the
|
||||
///project has loaded it and setCable() can be called
|
||||
QUuid m_pending_cable;
|
||||
QUuid m_part_uuid;
|
||||
QPointF m_p1;
|
||||
QPointF m_p2;
|
||||
|
||||
///Core being dragged by its slash, -1 when none
|
||||
int m_dragged_core = -1;
|
||||
///Core waiting to be put down by the next click, -1 when none
|
||||
int m_pending_core = -1;
|
||||
///True while the mouse stands close enough to this line for
|
||||
///the waiting core to be put down where it shows
|
||||
bool m_pending_valid = false;
|
||||
///True between the click which put a core down and the second
|
||||
///click Qt counts as a double click, so that a click which was
|
||||
///there to place something does not open the dialog as well
|
||||
bool m_just_placed = false;
|
||||
///Where the dragged slash follows the mouse
|
||||
QPointF m_drag_scene;
|
||||
QPointF m_press_scene;
|
||||
QPointF m_move_p1;
|
||||
QPointF m_move_p2;
|
||||
///True while the trunk line is being dragged, so undo knows
|
||||
///there is a move to take back
|
||||
bool m_line_moved = false;
|
||||
///Which end of the trunk line is being pulled by one of its
|
||||
///blue points: 0 none, 1 the first end, 2 the second
|
||||
int m_stretch_end = 0;
|
||||
///Every core as it stands while the line is being dragged:
|
||||
///every label is carried along by however far the line has
|
||||
///come, so the line, its description and the colours of its
|
||||
///cores move as one thing and no label ever jumps, lands on
|
||||
///a neighbour's wire or disappears under his hand
|
||||
QList<CableCore> m_line_drag_cores;
|
||||
///Every core as it was when the line was picked up, so undo
|
||||
///can put the labels back with the line
|
||||
QList<CableCore> m_cores_before;
|
||||
///Another line he picked up at the same time: a drag takes
|
||||
///the whole selection with it, so each of them is carried
|
||||
///along and each of them goes back to where it stood if he
|
||||
///calls the gesture off
|
||||
struct CarriedLine
|
||||
{
|
||||
CablePart *part = nullptr;
|
||||
///Where that line ran when the gesture began
|
||||
QPointF p1;
|
||||
QPointF p2;
|
||||
///What its cable held when the gesture began
|
||||
QList<CableCore> cores;
|
||||
};
|
||||
QList<CarriedLine> m_carried;
|
||||
|
||||
///True when the sheet has been told that the rest of the
|
||||
///selection comes along with this line (see carrySelection),
|
||||
///so that it is told once per gesture rather than once per
|
||||
///step -- and never again once it had nothing to move
|
||||
bool m_mover_started = false;
|
||||
///How far of this gesture the sheet has already been told
|
||||
QPointF m_told_mover;
|
||||
|
||||
///The lines which keep the references to the other folios of
|
||||
///this cable up to date, taken down and taken again whenever
|
||||
///this section comes to stand in another folio
|
||||
QList<QMetaObject::Connection> m_xref_connections;
|
||||
|
||||
void carryCompanions(const QPointF &delta);
|
||||
/**
|
||||
Everything else he marked at the same time comes along
|
||||
too -- the elements, the texts and the images of the same
|
||||
selection -- by the very step this line has taken, so
|
||||
that one gesture moves all of it instead of the lines
|
||||
going one way and the rest another.
|
||||
|
||||
What this line measures is how far it has come all in
|
||||
altogether, from where the press found it, while the
|
||||
sheet takes a step at a time: so only the part it has not
|
||||
been told yet is handed over.
|
||||
@param total_delta how far this line has come since the press
|
||||
*/
|
||||
void carrySelection(const QPointF &total_delta);
|
||||
bool settleCompanions(QList<QUndoCommand *> &steps);
|
||||
void putCompanionsBack();
|
||||
void readyToSettle(QList<CableCore> &cores) const;
|
||||
};
|
||||
|
||||
#endif // CABLEPART_H
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
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 "cablepropertiesdialog.h"
|
||||
|
||||
#include "cablecreatedialog.h"
|
||||
#include "cablepart.h"
|
||||
#include "cablepropertieswidget.h"
|
||||
#include "editcablecommand.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qeticons.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMenu>
|
||||
#include <QUndoStack>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::CablePropertiesDialog
|
||||
@param cable the cable being edited
|
||||
@param diagram the folio the line was double-clicked on, whose undo
|
||||
stack takes the edit back
|
||||
@param part the very line which was double-clicked: a core put down
|
||||
from this dialog lands on that line, never on the first line of a
|
||||
cable which stands on two folios
|
||||
@param parent
|
||||
*/
|
||||
CablePropertiesDialog::CablePropertiesDialog(Cable *cable,
|
||||
Diagram *diagram,
|
||||
CablePart *part,
|
||||
QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_cable(cable),
|
||||
m_diagram(diagram),
|
||||
m_part(part)
|
||||
{
|
||||
setWindowTitle(tr("Câble"));
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
m_fields = new CablePropertiesWidget(this);
|
||||
layout->addWidget(m_fields);
|
||||
|
||||
//The button beside the type field: it opens the window which
|
||||
//opens when a cable line has just been drawn, in the mode where
|
||||
//it changes the type of this very cable.
|
||||
connect(m_fields, &CablePropertiesWidget::typeChangeRequested,
|
||||
this, &CablePropertiesDialog::changeTypeRequested);
|
||||
|
||||
m_cores = new QLabel(this);
|
||||
m_cores->setWordWrap(true);
|
||||
layout->addWidget(m_cores);
|
||||
|
||||
//The cores the type offers which are not on the sheet yet.
|
||||
//Double-clicking one brings it in: the line it was asked from
|
||||
//takes the mouse and the next click puts the core down there.
|
||||
m_free_label = new QLabel(tr("Âmes non placées :"), this);
|
||||
m_free_cores = new QListWidget(this);
|
||||
m_free_cores->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_free_cores->setAlternatingRowColors(true);
|
||||
m_free_cores->setToolTip(tr("Double-cliquer sur une âme pour la poser sur la "
|
||||
"ligne : le trait suit la souris et le clic la "
|
||||
"laisse à l'endroit visé."));
|
||||
m_free_cores->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_free_cores, &QWidget::customContextMenuRequested,
|
||||
this, &CablePropertiesDialog::freeCoreMenu);
|
||||
connect(m_free_cores, &QListWidget::itemDoubleClicked,
|
||||
this, &CablePropertiesDialog::placeRequested);
|
||||
connect(m_free_cores, &QListWidget::itemActivated,
|
||||
this, &CablePropertiesDialog::placeRequested);
|
||||
layout->addWidget(m_free_label);
|
||||
layout->addWidget(m_free_cores);
|
||||
|
||||
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &CablePropertiesDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &CablePropertiesDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
if (m_cable) {
|
||||
m_fields->setProperties(m_cable->properties());
|
||||
}
|
||||
updateCoreCount();
|
||||
|
||||
fillFreeCores();
|
||||
|
||||
setMinimumWidth(400);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::updateCoreCount
|
||||
Say how many cores this cable has now and how many of them are wired
|
||||
to a conductor. It is said again after the type was changed, since
|
||||
the new type may hold more cores -- or fewer.
|
||||
*/
|
||||
void CablePropertiesDialog::updateCoreCount()
|
||||
{
|
||||
if (!m_cores) return;
|
||||
|
||||
if (!m_cable)
|
||||
{
|
||||
m_cores->setText(tr("Aucun câble."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_cores->setText(tr("%1 âme(s) sur %2 reliée(s) à un conducteur",
|
||||
"how many cores of this cable are wired",
|
||||
m_cable->usedCoreCount())
|
||||
.arg(m_cable->usedCoreCount())
|
||||
.arg(m_cable->coreCount()));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::changeTypeRequested
|
||||
Open the window which opens when a cable line has just been drawn,
|
||||
in the mode where it changes the type of this very cable: he picks
|
||||
the new type there out of the cable type file, and the cable takes
|
||||
it -- cores reworked, marks redrawn -- as one single undo step.
|
||||
|
||||
Whatever he typed into the form is written first, so the change and
|
||||
the typing cannot overwrite each other; and the form is filled in
|
||||
again afterwards, since pressing OK right after would otherwise
|
||||
write the old type back.
|
||||
*/
|
||||
void CablePropertiesDialog::changeTypeRequested()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
apply();
|
||||
|
||||
CableCreateDialog dialog(m_diagram, m_cable, this);
|
||||
if (dialog.exec() != QDialog::Accepted) return;
|
||||
|
||||
m_fields->setProperties(m_cable->properties());
|
||||
updateCoreCount();
|
||||
fillFreeCores();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::fillFreeCores
|
||||
List every core the type of this cable offers and which stands
|
||||
nowhere on the sheet: those are the ones he can still put down. The
|
||||
list is of no use when there is none, so it takes no room then.
|
||||
*/
|
||||
void CablePropertiesDialog::fillFreeCores()
|
||||
{
|
||||
if (!m_free_cores) return;
|
||||
|
||||
m_free_cores->clear();
|
||||
for (int i = 0; m_cable && i < m_cable->coreCount(); ++i)
|
||||
{
|
||||
if (m_cable->hasCore(i)) continue;
|
||||
|
||||
const QString color = m_cable->colorOfCore(i);
|
||||
auto *item = new QListWidgetItem(
|
||||
color.isEmpty() ? tr("Âme %1").arg(i + 1)
|
||||
: tr("Âme %1 — %2").arg(i + 1).arg(color),
|
||||
m_free_cores);
|
||||
item->setData(Qt::UserRole, i);
|
||||
}
|
||||
|
||||
const bool any = m_part && m_free_cores->count() > 0;
|
||||
m_free_label->setVisible(any);
|
||||
m_free_cores->setVisible(any);
|
||||
if (!any) return;
|
||||
|
||||
//As high as the entries it really shows, six of them at most:
|
||||
//a handful of free cores must not turn the dialog into a wall.
|
||||
const int row = qMax(m_free_cores->sizeHintForRow(0), 18);
|
||||
m_free_cores->setFixedHeight(row * qMin(6, m_free_cores->count())
|
||||
+ 2 * m_free_cores->frameWidth() + 4);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::placeRequested
|
||||
He picked one of the cores which stand nowhere: whatever is typed in
|
||||
the form is written first, the dialog closes, and the line he asked
|
||||
it from takes the mouse -- so the core goes onto that very line, and
|
||||
the next click leaves it standing there.
|
||||
@param item the entry he double-clicked
|
||||
*/
|
||||
void CablePropertiesDialog::placeRequested(QListWidgetItem *item)
|
||||
{
|
||||
//A double click reports itself twice on some platforms (once as
|
||||
//activated, once as double-clicked): the first one takes the
|
||||
//mouse away, the second must not ask for it again.
|
||||
if (!item || !m_part || m_placing) return;
|
||||
|
||||
m_placing = true;
|
||||
const int core = item->data(Qt::UserRole).toInt();
|
||||
QPointer<CablePart> part = m_part;
|
||||
|
||||
apply();
|
||||
QDialog::accept();
|
||||
|
||||
part->startPlacingCore(core);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::freeCoreMenu
|
||||
The small menu a right click on a core which stands nowhere opens:
|
||||
one entry, doing what a double click does -- this dialog writes
|
||||
whatever he typed, closes itself, the line he asked it from takes
|
||||
the mouse, the mark shows on that line at once and the mouse is put
|
||||
down there, so that the very next click leaves the core standing on
|
||||
that line.
|
||||
|
||||
@param pos where he right-clicked, in list coordinates
|
||||
*/
|
||||
void CablePropertiesDialog::freeCoreMenu(const QPoint &pos)
|
||||
{
|
||||
QListWidgetItem *item = m_free_cores ? m_free_cores->itemAt(pos) : nullptr;
|
||||
if (!item || !m_part || m_placing) return;
|
||||
|
||||
m_free_cores->setCurrentItem(item);
|
||||
|
||||
QMenu menu(m_free_cores);
|
||||
QAction *place = menu.addAction(tr("Placer cette âme sur la ligne"));
|
||||
place->setIcon(QET::Icons::Cable);
|
||||
|
||||
if (menu.exec(m_free_cores->viewport()->mapToGlobal(pos)) == place) {
|
||||
//Gone before he is sent off: this dialog is closing, the
|
||||
//mouse goes to the line and nothing may be left standing
|
||||
//over the place it is sent to.
|
||||
menu.hide();
|
||||
placeRequested(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::accept
|
||||
Whatever was typed here is written into the cable as one undo step,
|
||||
and the cable works out the cable field of every conductor it feeds
|
||||
again on its own: rename the cable and every drawing which shows it
|
||||
follows.
|
||||
*/
|
||||
void CablePropertiesDialog::accept()
|
||||
{
|
||||
apply();
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::apply
|
||||
Everything the form holds goes into the cable as one single undo
|
||||
step -- and only when something really changed, so that closing the
|
||||
dialog by any means leaves no step of undo to walk back through.
|
||||
*/
|
||||
void CablePropertiesDialog::apply()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
const CableProperties before = m_cable->properties();
|
||||
const CableProperties after = m_fields->properties();
|
||||
if (after != before)
|
||||
{
|
||||
if (m_diagram) {
|
||||
m_diagram->undoStack().push(
|
||||
new ChangeCablePropertiesCommand(m_cable, before, after));
|
||||
} else {
|
||||
m_cable->setProperties(after);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLEPROPERTIESDIALOG_H
|
||||
#define CABLEPROPERTIESDIALOG_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPointer>
|
||||
|
||||
class CablePropertiesWidget;
|
||||
class CablePart;
|
||||
class Diagram;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
|
||||
/**
|
||||
@brief The dialog which edits a cable once it is drawn.
|
||||
|
||||
Everything it holds belongs to the cable itself rather than to the
|
||||
line which was drawn: whatever is typed here applies to every section
|
||||
of that cable, on every folio, and the conductors it feeds get their
|
||||
cable field worked out again as soon as it says it changed.
|
||||
|
||||
Under the count of cores it lists the cores of the type which stand
|
||||
nowhere on the sheet yet: double-clicking one sends it to the very
|
||||
line this dialog was asked from, which takes the mouse until he
|
||||
clicks and leaves the core standing there.
|
||||
*/
|
||||
class CablePropertiesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
@param cable the cable being edited
|
||||
@param diagram the folio whose undo stack takes the edit
|
||||
@param part the very line which was double-clicked, where a
|
||||
core chosen in this dialog is to be put down
|
||||
@param parent
|
||||
*/
|
||||
explicit CablePropertiesDialog(Cable *cable,
|
||||
Diagram *diagram = nullptr,
|
||||
CablePart *part = nullptr,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
///Everything the form holds goes into the cable, as one step
|
||||
void apply();
|
||||
///What the count of cores under the form says right now
|
||||
void updateCoreCount();
|
||||
///He asked for the window choosing another type for this cable
|
||||
void changeTypeRequested();
|
||||
///Lists the cores of the type which stand nowhere yet
|
||||
void fillFreeCores();
|
||||
///He double-clicked one of them: put it down on the line
|
||||
void placeRequested(QListWidgetItem *item);
|
||||
/**
|
||||
The small menu a right click on a core which stands
|
||||
nowhere opens: one entry doing what a double click does
|
||||
-- the dialog closes, the line takes the mouse, the mark
|
||||
shows on it at once and the mouse is put down there.
|
||||
@param pos where he right-clicked, in list coordinates
|
||||
*/
|
||||
void freeCoreMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
Diagram *m_diagram = nullptr;
|
||||
///The line whose dialog this is, and the one a core chosen
|
||||
///here is put down on
|
||||
QPointer<CablePart> m_part;
|
||||
CablePropertiesWidget *m_fields = nullptr;
|
||||
QLabel *m_cores = nullptr;
|
||||
QLabel *m_free_label = nullptr;
|
||||
QListWidget *m_free_cores = nullptr;
|
||||
///True from the moment he picks a core to put down
|
||||
bool m_placing = false;
|
||||
};
|
||||
|
||||
#endif // CABLEPROPERTIESDIALOG_H
|
||||
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
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 "cablepropertieseditorwidget.h"
|
||||
|
||||
#include "cablecreatedialog.h"
|
||||
#include "cablepart.h"
|
||||
#include "cablepropertieswidget.h"
|
||||
#include "editcablecommand.h"
|
||||
#include "../qeticons.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMenu>
|
||||
#include <QSizePolicy>
|
||||
#include <QUndoStack>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::CablePropertiesEditorWidget
|
||||
@param cable the cable being edited
|
||||
@param part the line of that cable this panel was opened by, which
|
||||
is the line a core picked here is put down on
|
||||
@param parent parent widget
|
||||
*/
|
||||
CablePropertiesEditorWidget::CablePropertiesEditorWidget(Cable *cable,
|
||||
CablePart *part,
|
||||
QWidget *parent) :
|
||||
PropertiesEditorWidget(parent),
|
||||
m_fields(new CablePropertiesWidget(this)),
|
||||
m_info(new QLabel(this)),
|
||||
m_free_label(new QLabel(this)),
|
||||
m_free_cores(new QListWidget(this))
|
||||
{
|
||||
m_info->setWordWrap(true);
|
||||
|
||||
//The cores which stand nowhere yet, listed under the count of
|
||||
//wired ones: double-clicking one puts it down on this very line
|
||||
//without any dialog having to be opened for it. The list takes
|
||||
//no keyboard focus, so clicking it never takes the drawing away
|
||||
//from the user -- startPlacingCore() hands the focus to the
|
||||
//sheet itself, which is where Escape has to be heard.
|
||||
m_free_label->setText(tr("Âmes non placées :"));
|
||||
m_free_label->setVisible(false);
|
||||
m_free_cores->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_free_cores->setAlternatingRowColors(true);
|
||||
m_free_cores->setFocusPolicy(Qt::NoFocus);
|
||||
m_free_cores->setToolTip(tr("Double-cliquer sur une âme pour la poser sur la "
|
||||
"ligne : le trait suit la souris et le clic la "
|
||||
"laisse à l'endroit visé."));
|
||||
m_free_cores->setVisible(false);
|
||||
m_free_cores->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_free_cores, &QWidget::customContextMenuRequested,
|
||||
this, &CablePropertiesEditorWidget::freeCoreMenu);
|
||||
connect(m_free_cores, &QListWidget::itemDoubleClicked,
|
||||
this, &CablePropertiesEditorWidget::placeRequested);
|
||||
connect(m_free_cores, &QListWidget::itemActivated,
|
||||
this, &CablePropertiesEditorWidget::placeRequested);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(4);
|
||||
layout->addWidget(m_fields);
|
||||
|
||||
//The button beside the type field: it opens the window which
|
||||
//opens when a cable line has just been drawn, in the mode where
|
||||
//it changes the type of this very cable.
|
||||
connect(m_fields, &CablePropertiesWidget::typeChangeRequested,
|
||||
this, &CablePropertiesEditorWidget::changeTypeRequested);
|
||||
|
||||
layout->addWidget(m_info);
|
||||
layout->addWidget(m_free_label);
|
||||
layout->addWidget(m_free_cores);
|
||||
layout->addStretch(1);
|
||||
|
||||
//Wide enough for the labels, the ticks, the fields, the buttons
|
||||
//setting how a text is written and the two distances nudging
|
||||
//the label and the row of colours -- they sit side by side.
|
||||
setMinimumWidth(250);
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
|
||||
setDisabled(true);
|
||||
setCable(cable, part);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::setCable
|
||||
Set (or change) the cable whose properties are edited.
|
||||
@param cable
|
||||
@param part the line of that cable this panel was opened by: the one
|
||||
a core picked in the list is put down on, and null while no single
|
||||
line is selected -- then nothing can be put down and the list stays
|
||||
out of the way
|
||||
*/
|
||||
void CablePropertiesEditorWidget::setCable(Cable *cable, CablePart *part)
|
||||
{
|
||||
if (!cable) return;
|
||||
m_part = part;
|
||||
if (m_cable && m_cable != cable)
|
||||
disconnect(m_cable, nullptr, this, nullptr);
|
||||
|
||||
m_cable = cable;
|
||||
//Keep the panel in sync when the cable is edited elsewhere (the
|
||||
//dialog reached by double-clicking its line, for one): otherwise
|
||||
//the panel would write back what it held before that edit.
|
||||
connect(m_cable, &Cable::changed,
|
||||
this, &CablePropertiesEditorWidget::updateUi, Qt::UniqueConnection);
|
||||
setEnabled(true);
|
||||
updateUi();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::setLiveEdit
|
||||
In live-edit mode (how the panel uses every editor) an edit is
|
||||
applied as soon as a field is finished, rather than waiting for a
|
||||
call to apply(). On top of that the text itself is written onto the
|
||||
cable with every character, so what he types shows up on the drawing
|
||||
while he is still typing it -- the whole editing of one field stays
|
||||
one single undo step, which is worked out when the field is left.
|
||||
@param live_edit true to enable live edit
|
||||
@return always true
|
||||
*/
|
||||
bool CablePropertiesEditorWidget::setLiveEdit(bool live_edit)
|
||||
{
|
||||
if (m_live_edit == live_edit) return true;
|
||||
m_live_edit = live_edit;
|
||||
|
||||
if (m_live_edit) {
|
||||
m_live_connection = connect(m_fields, &CablePropertiesWidget::propertiesEdited,
|
||||
this, &CablePropertiesEditorWidget::apply);
|
||||
connect(m_fields, &CablePropertiesWidget::propertiesTyped,
|
||||
this, &CablePropertiesEditorWidget::applyLive);
|
||||
} else {
|
||||
disconnect(m_live_connection);
|
||||
disconnect(m_fields, &CablePropertiesWidget::propertiesTyped,
|
||||
this, &CablePropertiesEditorWidget::applyLive);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::applyLive
|
||||
Put what is being typed straight onto the cable, without pushing an
|
||||
undo step: the whole editing of one field is one step, which
|
||||
apply() works out from what the panel showed when the field is left.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::applyLive()
|
||||
{
|
||||
//Ignore the signals sent while the form is being filled in: at
|
||||
//that moment it holds a half loaded state.
|
||||
if (m_updating) return;
|
||||
if (!m_cable) return;
|
||||
|
||||
m_live_cable = m_cable;
|
||||
m_cable->setProperties(m_fields->properties());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::changeTypeRequested
|
||||
Open the window which opens when a cable line has just been drawn,
|
||||
in the mode where it changes the type of this very cable: he picks
|
||||
the new type there out of the cable type file, and the cable takes
|
||||
it -- cores reworked, marks redrawn -- as one single undo step.
|
||||
|
||||
The form is filled in again afterwards rather than through the usual
|
||||
path: the type has just changed under it, and it may not go on
|
||||
offering the one which was left.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::changeTypeRequested()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
CableCreateDialog dialog(m_part ? m_part->diagram() : nullptr,
|
||||
m_cable,
|
||||
this);
|
||||
if (dialog.exec() != QDialog::Accepted) return;
|
||||
|
||||
m_updating = true;
|
||||
m_fields->setProperties(m_cable->properties());
|
||||
m_shown = m_cable->properties();
|
||||
m_updating = false;
|
||||
updateInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::apply
|
||||
Put what was typed onto the cable as one undo step.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::apply()
|
||||
{
|
||||
//Ignore the signals sent while the form is being filled in: at
|
||||
//that moment it holds a half loaded state.
|
||||
if (m_updating) return;
|
||||
if (!m_cable) return;
|
||||
|
||||
if (QUndoCommand *undo = associatedUndo())
|
||||
{
|
||||
auto *project = qobject_cast<QETProject *>(m_cable->parent());
|
||||
if (project && project->undoStack()) {
|
||||
project->undoStack()->push(undo);
|
||||
} else {
|
||||
m_cable->setProperties(m_fields->properties());
|
||||
delete undo;
|
||||
}
|
||||
}
|
||||
m_shown = m_fields->properties();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::associatedUndo
|
||||
@return the edit as one undo step, or nullptr when nothing changed
|
||||
*/
|
||||
QUndoCommand *CablePropertiesEditorWidget::associatedUndo() const
|
||||
{
|
||||
if (!m_cable) return nullptr;
|
||||
|
||||
const CableProperties after = m_fields->properties();
|
||||
if (after == m_shown) return nullptr;
|
||||
return new ChangeCablePropertiesCommand(m_cable, m_shown, after);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::reset
|
||||
Discard the edit in progress, putting back what the panel showed.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::reset()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
m_updating = true;
|
||||
m_fields->setProperties(m_shown);
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::updateInfo
|
||||
Say how many cores of this cable are wired to a conductor.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::updateInfo()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
m_info->setText(tr("%1 âme(s) sur %2 reliée(s) à un conducteur",
|
||||
"how many cores of this cable are wired",
|
||||
m_cable->usedCoreCount())
|
||||
.arg(m_cable->usedCoreCount())
|
||||
.arg(m_cable->coreCount()));
|
||||
|
||||
updateFreeCores();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::updateFreeCores
|
||||
List the cores the type of this cable offers which stand nowhere on
|
||||
the sheet yet: those are the ones he can still put down, and they
|
||||
come down straight from here, on the line this panel was opened by.
|
||||
|
||||
The list is of no use when there is none -- and it cannot work at
|
||||
all when no single line of the cable is the selected one, since a
|
||||
core then has no line to be put down on -- so it takes no room then.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::updateFreeCores()
|
||||
{
|
||||
if (!m_free_cores) return;
|
||||
|
||||
const bool usable = m_cable && m_part && m_part->cable() == m_cable;
|
||||
|
||||
m_free_cores->clear();
|
||||
if (usable)
|
||||
{
|
||||
for (int i = 0; i < m_cable->coreCount(); ++i)
|
||||
{
|
||||
if (m_cable->hasCore(i)) continue;
|
||||
|
||||
const QString color = m_cable->colorOfCore(i);
|
||||
auto *item = new QListWidgetItem(
|
||||
color.isEmpty() ? tr("Âme %1").arg(i + 1)
|
||||
: tr("Âme %1 — %2").arg(i + 1).arg(color),
|
||||
m_free_cores);
|
||||
item->setData(Qt::UserRole, i);
|
||||
}
|
||||
}
|
||||
|
||||
const bool any = usable && m_free_cores->count() > 0;
|
||||
m_free_label->setVisible(any);
|
||||
m_free_cores->setVisible(any);
|
||||
if (!any) return;
|
||||
|
||||
//As high as the entries it really shows, four of them at most:
|
||||
//the dock is small, and a handful of free cores must not turn
|
||||
//it into a wall.
|
||||
const int row = qMax(m_free_cores->sizeHintForRow(0), 18);
|
||||
m_free_cores->setFixedHeight(row * qMin(4, m_free_cores->count())
|
||||
+ 2 * m_free_cores->frameWidth() + 4);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::placeRequested
|
||||
He double-clicked one of the cores which stand nowhere: this very
|
||||
line takes the mouse and waits for the click which puts the core
|
||||
down. There is no dialog to close here -- the panel stays where it
|
||||
is, and it reads the cable back as soon as the core lands.
|
||||
|
||||
@param item the entry he double-clicked
|
||||
*/
|
||||
void CablePropertiesEditorWidget::placeRequested(QListWidgetItem *item)
|
||||
{
|
||||
if (!item || !m_part) return;
|
||||
|
||||
m_part->startPlacingCore(item->data(Qt::UserRole).toInt());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::freeCoreMenu
|
||||
The small menu a right click on a core which stands nowhere opens:
|
||||
one entry, doing what a double click does -- the line takes the
|
||||
mouse, the mark shows on it at once and the mouse is put down there,
|
||||
so the very next click leaves the core standing on that line.
|
||||
|
||||
@param pos where he right-clicked, in list coordinates
|
||||
*/
|
||||
void CablePropertiesEditorWidget::freeCoreMenu(const QPoint &pos)
|
||||
{
|
||||
QListWidgetItem *item = m_free_cores ? m_free_cores->itemAt(pos) : nullptr;
|
||||
if (!item || !m_part) return;
|
||||
|
||||
m_free_cores->setCurrentItem(item);
|
||||
|
||||
QMenu menu(m_free_cores);
|
||||
QAction *place = menu.addAction(tr("Placer cette âme sur la ligne"));
|
||||
place->setIcon(QET::Icons::Cable);
|
||||
|
||||
if (menu.exec(m_free_cores->viewport()->mapToGlobal(pos)) == place) {
|
||||
//Gone before he is sent off: the mouse goes to the line
|
||||
//and nothing may be left standing over the place it is
|
||||
//sent to.
|
||||
menu.hide();
|
||||
placeRequested(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::updateUi
|
||||
Read the cable back into the panel.
|
||||
|
||||
Not while a field is being typed in: writing what was just typed
|
||||
back into that field would take the cursor away from the user with
|
||||
every key, and it would take back the state the whole editing of
|
||||
that field is measured against when the undo step is worked out.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::updateUi()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
if (m_fields->editing() && m_live_cable == m_cable) {
|
||||
updateInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
m_updating = true;
|
||||
m_fields->setProperties(m_cable->properties());
|
||||
m_shown = m_cable->properties();
|
||||
updateInfo();
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::title
|
||||
@return the panel title
|
||||
*/
|
||||
QString CablePropertiesEditorWidget::title() const
|
||||
{
|
||||
return tr("Câble");
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLEPROPERTIESEDITORWIDGET_H
|
||||
#define CABLEPROPERTIESEDITORWIDGET_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include "../PropertiesEditor/propertieseditorwidget.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMetaObject>
|
||||
#include <QPointer>
|
||||
|
||||
class CablePropertiesWidget;
|
||||
class CablePart;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
|
||||
/**
|
||||
@brief The cable shown in the "Propriétés de la sélection" panel.
|
||||
|
||||
Selecting a line of a cable used to bring up nothing at all there:
|
||||
the panel only knew the item types it already had an editor for.
|
||||
Now it holds the very fields the cable is filled in with -- each of
|
||||
them with its own tick saying whether the drawing shows it -- and
|
||||
what stands there is written back live, one undo step per edit.
|
||||
*/
|
||||
class CablePropertiesEditorWidget : public PropertiesEditorWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CablePropertiesEditorWidget(Cable *cable = nullptr,
|
||||
CablePart *part = nullptr,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
void setCable(Cable *cable, CablePart *part = nullptr);
|
||||
|
||||
void apply() override;
|
||||
void reset() override;
|
||||
void updateUi() override;
|
||||
QUndoCommand *associatedUndo() const override;
|
||||
QString title() const override;
|
||||
bool setLiveEdit(bool live_edit) override;
|
||||
|
||||
private:
|
||||
///Write what is being typed onto the cable right away
|
||||
void applyLive();
|
||||
///He asked for the window choosing another type for this cable
|
||||
void changeTypeRequested();
|
||||
///Say how many cores of this cable are wired
|
||||
void updateInfo();
|
||||
/**
|
||||
List the cores which stand nowhere on the sheet yet, under
|
||||
the count of wired ones: those are the ones he can still
|
||||
put down, and double-clicking one puts it down straight
|
||||
from here, on the line this panel was opened for.
|
||||
*/
|
||||
void updateFreeCores();
|
||||
///He double-clicked one of those cores: give it this line
|
||||
void placeRequested(QListWidgetItem *item);
|
||||
/**
|
||||
He right-clicked one of them: a small menu with the one
|
||||
thing which can be done to a core which stands nowhere
|
||||
-- putting it down -- for whoever does not know that a
|
||||
double click does the same.
|
||||
@param pos where he clicked, in list coordinates
|
||||
*/
|
||||
void freeCoreMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
CablePropertiesWidget *m_fields = nullptr;
|
||||
QLabel *m_info = nullptr;
|
||||
///The cores which stand nowhere yet, listed under the count
|
||||
QLabel *m_free_label = nullptr;
|
||||
QListWidget *m_free_cores = nullptr;
|
||||
QPointer<Cable> m_cable;
|
||||
/**
|
||||
The line this panel was opened by, which is the one a core
|
||||
picked here is put down on: taking a core always belongs to
|
||||
the line whose dialog was asked for, never to the first
|
||||
line of the cable. Null while no single line is selected,
|
||||
and then there is nothing to put a core down on and the
|
||||
list takes no room.
|
||||
*/
|
||||
QPointer<CablePart> m_part;
|
||||
///What the panel showed before the edit: the fields which
|
||||
///differ from it are the ones the user changed
|
||||
CableProperties m_shown;
|
||||
///The cable the text is being typed straight into, so that
|
||||
///reading the cable back only skips the very field which is
|
||||
///under the fingers of the user -- and never for another
|
||||
///cable which has just become the selected one
|
||||
QPointer<Cable> m_live_cable;
|
||||
QMetaObject::Connection m_live_connection;
|
||||
bool m_updating = false;
|
||||
};
|
||||
|
||||
#endif // CABLEPROPERTIESEDITORWIDGET_H
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
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 "cablepropertieswidget.h"
|
||||
|
||||
#include "cabletextformatdialog.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../qeticons.h"
|
||||
#include "../utils/qetutils.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSpinBox>
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::CablePropertiesWidget
|
||||
One row per field: its name, the tick saying whether the drawing
|
||||
shows it, the field itself, then the button setting how that one
|
||||
text is written -- one last row for the colours of the cores, which
|
||||
are written all together along the line, and one row per distance
|
||||
he may nudge the whole label and that row of colours with.
|
||||
@param parent
|
||||
*/
|
||||
CablePropertiesWidget::CablePropertiesWidget(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
auto *grid = new QGridLayout(this);
|
||||
grid->setContentsMargins(0, 0, 0, 0);
|
||||
grid->setHorizontalSpacing(6);
|
||||
grid->setVerticalSpacing(3);
|
||||
|
||||
auto *plan = new QLabel(tr("Plan",
|
||||
"column header: the drawing shows this field"), this);
|
||||
plan->setAlignment(Qt::AlignCenter);
|
||||
plan->setToolTip(tr("À cocher pour écrire ce champ sur le plan.",
|
||||
"tooltip of the tick before each field"));
|
||||
grid->addWidget(plan, 0, 1, Qt::AlignCenter);
|
||||
|
||||
int row = 1;
|
||||
addField(grid, row++, QStringLiteral("designation"),
|
||||
tr("Désignation"), &m_designation, &m_show_designation);
|
||||
const int type_row = row;
|
||||
addField(grid, row++, QStringLiteral("type"),
|
||||
tr("Type"), &m_type, &m_show_type);
|
||||
|
||||
//A button beside the type field, opening the very window which
|
||||
//opens when a cable line has just been drawn: the type is
|
||||
//picked from the cable type file there -- with its search, its
|
||||
//filter and its own button for a type the file does not hold
|
||||
//yet -- rather than typed in as plain text.
|
||||
auto *pick_type = new QPushButton(this);
|
||||
pick_type->setFocusPolicy(Qt::NoFocus);
|
||||
pick_type->setIcon(QET::Icons::Cable);
|
||||
pick_type->setToolTip(tr("Choisir le type de ce câble dans la liste des "
|
||||
"types de câbles…"));
|
||||
connect(pick_type, &QPushButton::clicked,
|
||||
this, &CablePropertiesWidget::typeChangeRequested);
|
||||
grid->addWidget(pick_type, type_row, 4, Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
addField(grid, row++, QStringLiteral("installation"),
|
||||
tr("Installation (=)"), &m_installation, &m_show_installation);
|
||||
addField(grid, row++, QStringLiteral("location"),
|
||||
tr("Localisation (+)"), &m_location, &m_show_location);
|
||||
addField(grid, row++, QStringLiteral("length"),
|
||||
tr("Longueur"), &m_length, &m_show_length);
|
||||
|
||||
//The colours of the cores: one setting for every one of them at
|
||||
//once, since they are written in one row along the line and are
|
||||
//read as one row.
|
||||
auto *cores_label = new QLabel(tr("Couleurs des âmes"), this);
|
||||
cores_label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
cores_label->setToolTip(tr("La police des couleurs de toutes les âmes "
|
||||
"de ce câble, écrites le long de la ligne."));
|
||||
grid->addWidget(cores_label, row, 0, Qt::AlignRight | Qt::AlignVCenter);
|
||||
addFormatButton(grid, row, QStringLiteral("cores"));
|
||||
|
||||
//Where that whole label stands: one distance along the line and
|
||||
//one across it, in pixels, moving the label as one thing --
|
||||
//installation, place and designation over the line, type and
|
||||
//length under it -- and the line not at all.
|
||||
addOffsetRow(grid, row + 1, tr("Position du texte"),
|
||||
tr("Décale tout le texte du câble : installation, emplacement "
|
||||
"et désignation d'un côté, type et longueur de l'autre. "
|
||||
"La ligne, les slashes et le câblage restent en place. "
|
||||
"10 px font une case de la grille."),
|
||||
&m_text_offset_x, &m_text_offset_y);
|
||||
|
||||
//And where the row of colours stands from the slashes it names.
|
||||
addOffsetRow(grid, row + 2, tr("Position des couleurs"),
|
||||
tr("Décale les couleurs des âmes par rapport à leurs slashes. "
|
||||
"Les slashes, les entrées et le câblage restent en place. "
|
||||
"10 px font une case de la grille."),
|
||||
&m_core_offset_x, &m_core_offset_y);
|
||||
|
||||
grid->setColumnStretch(2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::addField
|
||||
Build one row of the form.
|
||||
@param grid the form being built
|
||||
@param row where to put it
|
||||
@param key the name this text goes by in the file
|
||||
@param name what the field holds
|
||||
@param edit receives the line edit
|
||||
@param tick receives the tick before it
|
||||
*/
|
||||
void CablePropertiesWidget::addField(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &key,
|
||||
const QString &name,
|
||||
QLineEdit **edit,
|
||||
QCheckBox **tick)
|
||||
{
|
||||
auto *label = new QLabel(name, this);
|
||||
auto *check = new QCheckBox(this);
|
||||
check->setToolTip(tr("À cocher pour écrire ce champ sur le plan.",
|
||||
"tooltip of the tick before each field"));
|
||||
auto *field = new QLineEdit(this);
|
||||
field->setClearButtonEnabled(true);
|
||||
|
||||
connect(field, &QLineEdit::editingFinished,
|
||||
this, &CablePropertiesWidget::propertiesEdited);
|
||||
//Every character too: what is typed is written onto the cable
|
||||
//as it stands, so the drawing follows while he is still typing
|
||||
//rather than only once he leaves the field.
|
||||
connect(field, &QLineEdit::textChanged,
|
||||
this, &CablePropertiesWidget::propertiesTyped);
|
||||
connect(check, &QCheckBox::toggled,
|
||||
this, &CablePropertiesWidget::propertiesEdited);
|
||||
|
||||
grid->addWidget(label, row, 0, Qt::AlignRight | Qt::AlignVCenter);
|
||||
grid->addWidget(check, row, 1, Qt::AlignCenter);
|
||||
grid->addWidget(field, row, 2);
|
||||
addFormatButton(grid, row, key);
|
||||
|
||||
*edit = field;
|
||||
*tick = check;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::addFormatButton
|
||||
The small button standing at the right of a text: it says which
|
||||
size that text is written with, and opens the window where that
|
||||
size -- and the rest of how the text is written -- is set.
|
||||
@param grid the form being built
|
||||
@param row where to put it
|
||||
@param key the name of the text
|
||||
@return the button, so the caller may reach it again
|
||||
*/
|
||||
QPushButton *CablePropertiesWidget::addFormatButton(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &key)
|
||||
{
|
||||
auto *button = new QPushButton(this);
|
||||
button->setFocusPolicy(Qt::NoFocus);
|
||||
connect(button, &QPushButton::clicked, this,
|
||||
[this, key]() {editFormat(key);});
|
||||
|
||||
grid->addWidget(button, row, 3, Qt::AlignRight | Qt::AlignVCenter);
|
||||
m_format_buttons.insert(key, button);
|
||||
refreshFormatButton(key);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::addOffsetRow
|
||||
Build one of the two rows nudging what is drawn: a name at the left
|
||||
of the form, then two distances in pixels -- one measured along the
|
||||
line, one across it -- the way the cross references of a slave offer
|
||||
theirs.
|
||||
|
||||
Both boxes speak of the drawing and nothing else: what he types here
|
||||
moves texts, and the line, the slashes and the wires behind them keep
|
||||
standing exactly where they were.
|
||||
@param grid the form being built
|
||||
@param row where to put it
|
||||
@param name what that distance moves
|
||||
@param tip what it does and does not touch
|
||||
@param dx receives the spin box for the distance along the line
|
||||
@param dy receives the spin box for the distance across it
|
||||
*/
|
||||
void CablePropertiesWidget::addOffsetRow(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &name,
|
||||
const QString &tip,
|
||||
QSpinBox **dx,
|
||||
QSpinBox **dy)
|
||||
{
|
||||
auto *label = new QLabel(name, this);
|
||||
label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
label->setToolTip(tip);
|
||||
|
||||
const auto spin = [this, tip]() {
|
||||
auto *box = new QSpinBox(this);
|
||||
box->setRange(-200, 200);
|
||||
box->setSingleStep(1);
|
||||
box->setSuffix(QStringLiteral(" px"));
|
||||
box->setAlignment(Qt::AlignRight);
|
||||
box->setToolTip(tip);
|
||||
connect(box, &QSpinBox::valueChanged,
|
||||
this, &CablePropertiesWidget::propertiesEdited);
|
||||
return box;
|
||||
};
|
||||
QSpinBox *x_box = spin();
|
||||
QSpinBox *y_box = spin();
|
||||
|
||||
auto *along = new QLabel(tr("X :"), this);
|
||||
along->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
auto *across = new QLabel(tr("Y :"), this);
|
||||
across->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
auto *values = new QHBoxLayout;
|
||||
values->setContentsMargins(0, 0, 0, 0);
|
||||
values->setSpacing(4);
|
||||
values->addWidget(along);
|
||||
values->addWidget(x_box);
|
||||
values->addWidget(across);
|
||||
values->addWidget(y_box);
|
||||
values->addStretch(1);
|
||||
|
||||
grid->addWidget(label, row, 0, Qt::AlignRight | Qt::AlignVCenter);
|
||||
grid->addLayout(values, row, 2);
|
||||
|
||||
*dx = x_box;
|
||||
*dy = y_box;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::editFormat
|
||||
Open the window where one text of the cable is set, and -- when he
|
||||
came out of it having really changed something -- write the answer
|
||||
back into the form. One window is one change, so one step of undo.
|
||||
@param key the name of the text
|
||||
*/
|
||||
void CablePropertiesWidget::editFormat(const QString &key)
|
||||
{
|
||||
CableTextFormatDialog dialog(m_formats.value(key), coresKey(key), this);
|
||||
if (dialog.exec() != QDialog::Accepted) return;
|
||||
|
||||
const CableTextFormat answer = dialog.format();
|
||||
if (m_formats.value(key) == answer) return;
|
||||
|
||||
if (answer == CableTextFormat()) m_formats.remove(key);
|
||||
else m_formats.insert(key, answer);
|
||||
|
||||
refreshFormatButton(key);
|
||||
emit propertiesEdited();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::refreshFormatButton
|
||||
Let the button of one text say which size that text is written with,
|
||||
so that the size is read off the form rather than only in the
|
||||
drawing.
|
||||
@param key the name of the text
|
||||
*/
|
||||
void CablePropertiesWidget::refreshFormatButton(const QString &key)
|
||||
{
|
||||
QPushButton *button = m_format_buttons.value(key);
|
||||
if (!button) return;
|
||||
|
||||
const QFont font = effectiveFont(key);
|
||||
button->setText(tr("%1 pt").arg(QString::number(font.pointSizeF(), 'g', 3)));
|
||||
|
||||
const QString tip = coresKey(key)
|
||||
? tr("Police, taille et style des couleurs des âmes.")
|
||||
: tr("Police, taille, style et alignement de ce texte.");
|
||||
const CableTextFormat format = m_formats.value(key);
|
||||
const bool its_own = !format.font.isEmpty() || format.alignment != 0;
|
||||
button->setToolTip(tip + QLatin1Char(' ') + (its_own
|
||||
? tr("Réglé pour ce câble.")
|
||||
: tr("Valeur définie dans les préférences.")));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::effectiveFont
|
||||
@param key the name of a text of the form
|
||||
@return the font that text really is written with on the drawing:
|
||||
the one this cable asks for, or -- when it asks for none -- the one
|
||||
the preferences offer
|
||||
*/
|
||||
QFont CablePropertiesWidget::effectiveFont(const QString &key) const
|
||||
{
|
||||
const CableTextFormat format = m_formats.value(key);
|
||||
if (!format.font.isEmpty())
|
||||
{
|
||||
QFont font;
|
||||
if (QETUtils::fontFromString(font, format.font)) {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
return coresKey(key) ? QETApp::cableCoreFont() : QETApp::cableTextsFont();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::properties
|
||||
@return everything the form holds right now
|
||||
*/
|
||||
CableProperties CablePropertiesWidget::properties() const
|
||||
{
|
||||
CableProperties properties;
|
||||
properties.designation = m_designation->text().trimmed();
|
||||
//A name which is not the one read from the cable can only have
|
||||
//come from him typing it in: from that moment on the cable
|
||||
//carries a name of his own rather than one the numbering rule
|
||||
//handed out. Putting the very same name back by hand keeps the
|
||||
//cable as it stood, which is what the field says.
|
||||
properties.designation_by_hand = properties.designation == m_designation_text
|
||||
? m_designation_by_hand : true;
|
||||
properties.type = m_type->text().trimmed();
|
||||
properties.installation = m_installation->text().trimmed();
|
||||
properties.location = m_location->text().trimmed();
|
||||
properties.length = m_length->text().trimmed();
|
||||
properties.show_designation = m_show_designation->isChecked();
|
||||
properties.show_type = m_show_type->isChecked();
|
||||
properties.show_installation = m_show_installation->isChecked();
|
||||
properties.show_location = m_show_location->isChecked();
|
||||
properties.show_length = m_show_length->isChecked();
|
||||
properties.texts = m_formats;
|
||||
properties.text_offset = QPointF(m_text_offset_x->value(),
|
||||
m_text_offset_y->value());
|
||||
properties.core_offset = QPointF(m_core_offset_x->value(),
|
||||
m_core_offset_y->value());
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::setProperties
|
||||
Fill the form in. The ticks say what is drawn, so a field which is
|
||||
switched off still keeps the text standing in it -- hiding it on the
|
||||
drawing never throws away what the user typed.
|
||||
@param properties
|
||||
*/
|
||||
void CablePropertiesWidget::setProperties(const CableProperties &properties)
|
||||
{
|
||||
m_designation->setText(properties.designation);
|
||||
//What the field holds now is what the cable said: anything he
|
||||
//types from here on is a name of his own
|
||||
m_designation_text = properties.designation;
|
||||
m_designation_by_hand = properties.designation_by_hand;
|
||||
m_type->setText(properties.type);
|
||||
m_installation->setText(properties.installation);
|
||||
m_location->setText(properties.location);
|
||||
m_length->setText(properties.length);
|
||||
m_show_designation->setChecked(properties.show_designation);
|
||||
m_show_type->setChecked(properties.show_type);
|
||||
m_show_installation->setChecked(properties.show_installation);
|
||||
m_show_location->setChecked(properties.show_location);
|
||||
m_show_length->setChecked(properties.show_length);
|
||||
|
||||
//Filling the form in is not the user nudging anything: those
|
||||
//two rows are muted while they are set, so that loading a cable
|
||||
//can never look like an edit which then has to be undone.
|
||||
const QSignalBlocker block_text_x(m_text_offset_x);
|
||||
const QSignalBlocker block_text_y(m_text_offset_y);
|
||||
const QSignalBlocker block_core_x(m_core_offset_x);
|
||||
const QSignalBlocker block_core_y(m_core_offset_y);
|
||||
m_text_offset_x->setValue(qRound(properties.text_offset.x()));
|
||||
m_text_offset_y->setValue(qRound(properties.text_offset.y()));
|
||||
m_core_offset_x->setValue(qRound(properties.core_offset.x()));
|
||||
m_core_offset_y->setValue(qRound(properties.core_offset.y()));
|
||||
|
||||
//The texts this cable writes its own way. Only what really has
|
||||
//an answer of its own is kept, so that a stray empty entry can
|
||||
//never make the form claim a change it never made.
|
||||
m_formats.clear();
|
||||
for (auto it = properties.texts.cbegin(); it != properties.texts.cend(); ++it)
|
||||
{
|
||||
if (it.value() == CableTextFormat()) continue;
|
||||
m_formats.insert(it.key(), it.value());
|
||||
}
|
||||
for (auto it = m_format_buttons.cbegin(); it != m_format_buttons.cend(); ++it) {
|
||||
refreshFormatButton(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::editing
|
||||
@return true when the keyboard stands in one of the fields of this
|
||||
form. The panel uses it to keep its hands off what is being typed:
|
||||
writing the cable back into the form while he is typing would take
|
||||
the cursor away from him with every key.
|
||||
*/
|
||||
bool CablePropertiesWidget::editing() const
|
||||
{
|
||||
QWidget *focus = QApplication::focusWidget();
|
||||
return focus && (focus == this || isAncestorOf(focus));
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLEPROPERTIESWIDGET_H
|
||||
#define CABLEPROPERTIESWIDGET_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QCheckBox;
|
||||
class QGridLayout;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QSpinBox;
|
||||
|
||||
/**
|
||||
@brief The fields a cable is filled in with, and the tick before each
|
||||
of them saying whether what stands there is written on the drawing.
|
||||
|
||||
It holds no cable of its own: it is a plain form, filled in with
|
||||
setProperties() and read back with properties(). That way the dialog
|
||||
editing one cable and the selection panel of QElectroTech offer
|
||||
exactly the same fields in the same order, and neither of them can
|
||||
drift away from the other.
|
||||
|
||||
Every field is followed by a checkbox: what the checkbox says is
|
||||
written next to the trunk line of the drawing, what it does not say
|
||||
stays in the cable alone and only shows up in lists.
|
||||
|
||||
Behind every field stands a button opening the window where that one
|
||||
text is written: its font, its size, its style and -- for the texts
|
||||
drawn next to the line -- the way it lines up there. The colours of
|
||||
the cores get a button of their own at the foot of the form, all of
|
||||
them at once, because they are written in one row along the line and
|
||||
are never set apart.
|
||||
|
||||
The type field carries one more button, on the right of that row. It
|
||||
does not say how the word is written: it asks for the window which
|
||||
opens when a cable line has just been drawn, where a type is picked
|
||||
out of the cable type file -- with its search, its filter and its own
|
||||
button for a type the file does not hold yet -- and taken onto the
|
||||
cable. Whoever holds this form answers typeChangeRequested().
|
||||
|
||||
At the foot of the form stand the two distances he may nudge: one for
|
||||
the whole label, one for the row of colours. Both are on the drawing
|
||||
only -- the line, the slashes and the wiring stay where they are.
|
||||
*/
|
||||
class CablePropertiesWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CablePropertiesWidget(QWidget *parent = nullptr);
|
||||
|
||||
///What the form holds right now
|
||||
CableProperties properties() const;
|
||||
///Fill the form in
|
||||
void setProperties(const CableProperties &properties);
|
||||
///True while one of the fields is being typed in
|
||||
bool editing() const;
|
||||
|
||||
signals:
|
||||
///Emitted when a field or a tick was really changed by hand
|
||||
void propertiesEdited();
|
||||
///Emitted with every character typed, so the drawing can
|
||||
///follow the text as it is written rather than only once the
|
||||
///field is left
|
||||
void propertiesTyped();
|
||||
///Emitted when he asks for the window choosing the type of
|
||||
///this cable out of the cable type file
|
||||
void typeChangeRequested();
|
||||
|
||||
private:
|
||||
void addField(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &key,
|
||||
const QString &name,
|
||||
QLineEdit **edit,
|
||||
QCheckBox **tick);
|
||||
///The button opening the window which sets one text
|
||||
QPushButton *addFormatButton(QGridLayout *grid, int row, const QString &key);
|
||||
/**
|
||||
Build one row of the two spin boxes nudging what is
|
||||
drawn: one for the label of the cable, one for the row
|
||||
of its colours.
|
||||
@param grid the form being built
|
||||
@param row where to put it
|
||||
@param name what the row moves
|
||||
@param tip what that distance does and does not touch
|
||||
@param dx receives the spin box for the distance along
|
||||
the line
|
||||
@param dy receives the spin box for the distance across
|
||||
it
|
||||
*/
|
||||
void addOffsetRow(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &name,
|
||||
const QString &tip,
|
||||
QSpinBox **dx,
|
||||
QSpinBox **dy);
|
||||
///Open the window setting one text, then say so if it changed
|
||||
void editFormat(const QString &key);
|
||||
///Let the button say which size that text is written with
|
||||
void refreshFormatButton(const QString &key);
|
||||
///The font this text is really written with right now
|
||||
QFont effectiveFont(const QString &key) const;
|
||||
///True when this key names the colours of the cores
|
||||
static bool coresKey(const QString &key) {return key == QLatin1String("cores");}
|
||||
|
||||
private:
|
||||
QLineEdit *m_designation = nullptr;
|
||||
/**
|
||||
The name exactly as it was read from the cable, and
|
||||
whether that name was typed in: anything else he puts
|
||||
into the field from then on is his own work, and the
|
||||
cable is marked as carrying a name he chose (see
|
||||
properties())
|
||||
*/
|
||||
QString m_designation_text;
|
||||
bool m_designation_by_hand = false;
|
||||
QLineEdit *m_type = nullptr;
|
||||
QLineEdit *m_installation = nullptr;
|
||||
QLineEdit *m_location = nullptr;
|
||||
QLineEdit *m_length = nullptr;
|
||||
QCheckBox *m_show_designation = nullptr;
|
||||
QCheckBox *m_show_type = nullptr;
|
||||
QCheckBox *m_show_installation = nullptr;
|
||||
QCheckBox *m_show_location = nullptr;
|
||||
QCheckBox *m_show_length = nullptr;
|
||||
///What each text of this form is written with; a text which
|
||||
///is not in here follows what the preferences say
|
||||
QHash<QString, CableTextFormat> m_formats;
|
||||
///The button opening the window of each text, by name
|
||||
QHash<QString, QPushButton *> m_format_buttons;
|
||||
///How far the label of the cable is nudged on the drawing
|
||||
QSpinBox *m_text_offset_x = nullptr;
|
||||
QSpinBox *m_text_offset_y = nullptr;
|
||||
///How far the row of colours is nudged on the drawing
|
||||
QSpinBox *m_core_offset_x = nullptr;
|
||||
QSpinBox *m_core_offset_y = nullptr;
|
||||
};
|
||||
|
||||
#endif // CABLEPROPERTIESWIDGET_H
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
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 "cablerenumber.h"
|
||||
|
||||
#include "cable.h"
|
||||
#include "../autoNum/assignvariables.h"
|
||||
#include "../autoNum/numerotationcontextcommands.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QSet>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
@brief One cable of the project, and where it stands so that the
|
||||
cables can be laid out in an order everybody can see again.
|
||||
*/
|
||||
struct OrderedCable
|
||||
{
|
||||
Cable *cable = nullptr;
|
||||
///The lowest folio the cable is drawn on
|
||||
int folio = 0;
|
||||
///How far it reaches to the left and how far to the top on
|
||||
///that folio
|
||||
qreal left = 0.0;
|
||||
qreal top = 0.0;
|
||||
///The folio the rule is read on for this cable
|
||||
Diagram *diagram = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Read a rule once, the way the numbering of a cable reads it.
|
||||
|
||||
The rule is read on a copy, since reading it fills in what its
|
||||
folio-bound parts are worth on that one folio, and that must not
|
||||
creep into the rule itself.
|
||||
@param context the rule as it stands at this point of the numbering
|
||||
@param diagram the folio the number is worked out on
|
||||
@param key the name the rule is kept under in the project
|
||||
@return the number, empty when the rule says nothing here
|
||||
*/
|
||||
QString readRule(NumerotationContext context, Diagram *diagram, const QString &key)
|
||||
{
|
||||
const QString formula = autonum::numerotationContextToFormula(context);
|
||||
autonum::sequentialNumbers seq;
|
||||
autonum::setSequential(formula, seq, context, diagram, key);
|
||||
return autonum::AssignVariables::formulaToLabel(formula, seq, diagram);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@brief CableRenumber::byHandCount
|
||||
@param project
|
||||
@return how many cables of that project carry a name typed in by hand
|
||||
*/
|
||||
int CableRenumber::byHandCount(QETProject *project)
|
||||
{
|
||||
if (!project) return 0;
|
||||
|
||||
int count = 0;
|
||||
for (Cable *cable : project->cables())
|
||||
{
|
||||
if (cable && cable->designationByHand()) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableRenumber::plan
|
||||
Work the number of every cable of the project out, without writing
|
||||
anywhere. The cables are put in the order they are numbered in --
|
||||
folio by folio, and on each folio along whichever axis leads -- and
|
||||
the rule is then read from its beginning once for each of them, so
|
||||
that the numbering runs on without a hole.
|
||||
@param project the project whose cables are numbered
|
||||
@param rule the numbering rule to read
|
||||
@param x_axis_first true when the leftmost cable of a folio comes first
|
||||
@param include_by_hand false to leave the names typed in by hand alone
|
||||
@return the numbers, or what stopped the numbering
|
||||
*/
|
||||
CableRenumberPlan CableRenumber::plan(QETProject *project,
|
||||
const NumerotationContext &rule,
|
||||
bool x_axis_first,
|
||||
bool include_by_hand)
|
||||
{
|
||||
CableRenumberPlan result;
|
||||
if (!project)
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber", "Aucun projet n'est ouvert.");
|
||||
return result;
|
||||
}
|
||||
if (project->isReadOnly())
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"Ce projet est en lecture seule : ses câbles ne peuvent pas "
|
||||
"être renumérotés.");
|
||||
return result;
|
||||
}
|
||||
const QString key = project->cableCurrentAutoNum();
|
||||
if (key.isEmpty() || rule.isEmpty())
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"Aucune règle de numérotation des câbles n'est définie pour ce "
|
||||
"projet. Définissez-en une dans cette fenêtre avant de "
|
||||
"renuméroter ses câbles.");
|
||||
return result;
|
||||
}
|
||||
|
||||
//Where every cable stands, and which ones are left alone: their
|
||||
//names stay as they are, so the numbering must not hand the
|
||||
//same number out again
|
||||
QList<OrderedCable> ordered;
|
||||
QSet<QString> taken;
|
||||
for (Cable *cable : project->cables())
|
||||
{
|
||||
if (!cable) continue;
|
||||
if (!include_by_hand && cable->designationByHand())
|
||||
{
|
||||
if (!cable->designation().isEmpty()) {
|
||||
taken.insert(cable->designation());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
OrderedCable entry;
|
||||
entry.cable = cable;
|
||||
|
||||
for (const CablePartData &part : cable->parts())
|
||||
{
|
||||
Diagram *diagram = project->diagramByUuid(part.diagram);
|
||||
if (!diagram) continue;
|
||||
|
||||
const int index = project->folioIndex(diagram);
|
||||
const qreal left = qMin(part.p1.x(), part.p2.x());
|
||||
const qreal top = qMin(part.p1.y(), part.p2.y());
|
||||
|
||||
if (!entry.diagram || index < entry.folio)
|
||||
{
|
||||
entry.folio = index;
|
||||
entry.diagram = diagram;
|
||||
entry.left = left;
|
||||
entry.top = top;
|
||||
} else if (index == entry.folio) {
|
||||
entry.left = qMin(entry.left, left);
|
||||
entry.top = qMin(entry.top, top);
|
||||
}
|
||||
}
|
||||
//A cable which stands on no folio the project still knows
|
||||
//is numbered last rather than first by accident
|
||||
if (!entry.diagram) {
|
||||
entry.folio = std::numeric_limits<int>::max();
|
||||
}
|
||||
ordered.append(entry);
|
||||
}
|
||||
|
||||
//Folio by folio, and on each folio along the axis which leads:
|
||||
//the other axis is only asked when the leading one says two
|
||||
//cables are level, so that the numbering never depends on the
|
||||
//order the cables happen to be stored in
|
||||
std::sort(ordered.begin(), ordered.end(),
|
||||
[x_axis_first](const OrderedCable &a, const OrderedCable &b)
|
||||
{
|
||||
if (a.folio != b.folio) return a.folio < b.folio;
|
||||
|
||||
const qreal a_first = x_axis_first ? a.left : a.top;
|
||||
const qreal b_first = x_axis_first ? b.left : b.top;
|
||||
if (a_first != b_first) return a_first < b_first;
|
||||
|
||||
const qreal a_second = x_axis_first ? a.top : a.left;
|
||||
const qreal b_second = x_axis_first ? b.top : b.left;
|
||||
if (a_second != b_second) return a_second < b_second;
|
||||
|
||||
return a.cable->uuid().toString() < b.cable->uuid().toString();
|
||||
});
|
||||
|
||||
//Read the rule from the beginning, once for each cable: its
|
||||
//counters are put back first, since numbering a project over
|
||||
//starts at the beginning whatever the last number was
|
||||
NumerotationContext context = autonum::resetContextCounters(rule);
|
||||
int current_folio = -1;
|
||||
|
||||
for (const OrderedCable &entry : std::as_const(ordered))
|
||||
{
|
||||
if (!entry.diagram)
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"Un câble n'est sur aucun folio du projet : rien n'a été "
|
||||
"changé.");
|
||||
return result;
|
||||
}
|
||||
|
||||
//A counter bound to the folio counts from the beginning of
|
||||
//that folio: the first cable of a folio gets the first
|
||||
//number of it, and the folios after it start again rather
|
||||
//than carrying the number of the folio before on
|
||||
if (entry.folio != current_folio)
|
||||
{
|
||||
context = autonum::resetFolioCounters(context);
|
||||
current_folio = entry.folio;
|
||||
}
|
||||
|
||||
const QString candidate = readRule(context, entry.diagram, key);
|
||||
if (candidate.isEmpty())
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"La règle en cours ne donne aucun numéro ici : rien n'a été "
|
||||
"changé.");
|
||||
return result;
|
||||
}
|
||||
//A rule which does not count hands out the same number
|
||||
//twice, and two cables of one project may not be called
|
||||
//the same thing
|
||||
if (result.designations.contains(candidate))
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"La règle en cours donne deux fois le même numéro (%1) : "
|
||||
"elle doit compter pour renuméroter un projet entier. "
|
||||
"Rien n'a été changé.")
|
||||
.arg(candidate);
|
||||
return result;
|
||||
}
|
||||
//A name which stays because it was typed in keeps its
|
||||
//number, and the numbering may not hand that number out
|
||||
if (taken.contains(candidate))
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"Le numéro %1 est déjà porté par un câble dont le nom est "
|
||||
"laissé tel quel : rien n'a été changé.")
|
||||
.arg(candidate);
|
||||
return result;
|
||||
}
|
||||
|
||||
taken.insert(candidate);
|
||||
result.cables.append(entry.cable);
|
||||
result.designations.append(candidate);
|
||||
|
||||
//Step the rule on for the next cable; a rule which cannot
|
||||
//step stays where it is and is caught by the check above
|
||||
NumerotationContextCommands ncc(context, entry.diagram);
|
||||
const NumerotationContext following = ncc.next();
|
||||
if (following.size() == context.size()) {
|
||||
context = following;
|
||||
}
|
||||
}
|
||||
|
||||
//Where the rule stands after the last cable has been numbered:
|
||||
//the caller writes that into the project, so numbering goes on
|
||||
//from there instead of from wherever the rule happened to stand
|
||||
//before this numbering ran
|
||||
result.after = context;
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLERENUMBER_H
|
||||
#define CABLERENUMBER_H
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "../autoNum/numerotationcontext.h"
|
||||
|
||||
class Cable;
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief What numbering a whole project would do, worked out before a
|
||||
single cable is written to.
|
||||
|
||||
Working it out first means the answer can be refused as a whole: a
|
||||
rule which hands out the same number twice, or a number some cable
|
||||
which is left alone already carries, stops the whole numbering
|
||||
rather than numbering half the project and then getting stuck.
|
||||
*/
|
||||
struct CableRenumberPlan
|
||||
{
|
||||
///The cables to number, in the order they are numbered in
|
||||
QList<Cable *> cables;
|
||||
///The number each of them gets, one for each cable
|
||||
QStringList designations;
|
||||
///The rule as it stands once the last cable has been numbered:
|
||||
///its counters are where the next number would be taken from
|
||||
NumerotationContext after;
|
||||
///What stopped the numbering, empty while it may be carried out
|
||||
QString error;
|
||||
|
||||
bool ok() const {return error.isEmpty();}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Working out the numbers a whole project should carry.
|
||||
|
||||
Everything here is pure: nothing is written to the project and
|
||||
nothing is put on the undo stack, so a caller may look at the plan
|
||||
and decide against it without anything having happened.
|
||||
*/
|
||||
namespace CableRenumber
|
||||
{
|
||||
/**
|
||||
How many cables of this project carry a name which was typed in
|
||||
rather than handed out by the numbering rule. Those are the ones
|
||||
renumbering asks about before it takes their name away.
|
||||
@param project
|
||||
@return the number of such cables
|
||||
*/
|
||||
int byHandCount(QETProject *project);
|
||||
|
||||
/**
|
||||
Work the number of every cable of the project out of @p rule.
|
||||
|
||||
The cables are laid out first -- folio by folio, and on each
|
||||
folio either from the left to the right or from the top to the
|
||||
bottom, whichever axis leads -- and are then numbered one after
|
||||
the other from the beginning of the rule, so that a hole left by
|
||||
a cable which has been deleted closes behind it.
|
||||
|
||||
@param project the project whose cables are numbered
|
||||
@param rule the numbering rule to read; its counters are put
|
||||
back to 1 first, since numbering starts from the beginning
|
||||
@param x_axis_first true to number the cables of a folio from
|
||||
the left to the right, false to number them from the top to the
|
||||
bottom
|
||||
@param include_by_hand false to leave the cables whose name was
|
||||
typed in exactly as they are
|
||||
@return the numbers, or what stopped the numbering. The plan
|
||||
also carries the rule as it stands once every cable has been
|
||||
given its number, so that the caller may put that into the
|
||||
project: the counters have to stay where the last number was
|
||||
taken from, or the next cable drawn would skip over every
|
||||
number in between
|
||||
*/
|
||||
CableRenumberPlan plan(QETProject *project,
|
||||
const NumerotationContext &rule,
|
||||
bool x_axis_first,
|
||||
bool include_by_hand);
|
||||
}
|
||||
|
||||
#endif // CABLERENUMBER_H
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
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 "cabletextformatdialog.h"
|
||||
|
||||
#include "../qetapp.h"
|
||||
#include "../utils/qetutils.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFontComboBox>
|
||||
#include <QFormLayout>
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
/**
|
||||
@brief The way a text lines up, as the dialog lists it: right
|
||||
first, which is where every text stands unless he says otherwise.
|
||||
@param index the place in the list of the dialog
|
||||
@return the flags the drawing understands
|
||||
*/
|
||||
Qt::Alignment alignmentFromIndex(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 1: return Qt::AlignHCenter;
|
||||
case 2: return Qt::AlignLeft;
|
||||
default: return Qt::AlignRight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief The place in the list of the dialog of an alignment.
|
||||
@param alignment Qt::AlignLeft, Qt::AlignHCenter or Qt::AlignRight
|
||||
@return the index, 0 (right) when nothing is named
|
||||
*/
|
||||
int alignmentIndex(int alignment)
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case int(Qt::AlignHCenter): return 1;
|
||||
case int(Qt::AlignLeft): return 2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Whether two fonts would be read the very same way on the sheet:
|
||||
the family he picks, the size he gives it, and the style he
|
||||
chose among the four offered.
|
||||
@param a the font he has chosen
|
||||
@param b the font the preferences would write it with
|
||||
@return true when nothing of them differs
|
||||
*/
|
||||
bool sameFont(const QFont &a, const QFont &b)
|
||||
{
|
||||
return a.family() == b.family()
|
||||
&& qFuzzyCompare(a.pointSizeF(), b.pointSizeF())
|
||||
&& a.bold() == b.bold()
|
||||
&& a.italic() == b.italic();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::CableTextFormatDialog
|
||||
@param format what this cable writes that text with right now
|
||||
@param cores true for the colours of the cores
|
||||
@param parent
|
||||
*/
|
||||
CableTextFormatDialog::CableTextFormatDialog(const CableTextFormat &format,
|
||||
bool cores,
|
||||
QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_cores(cores)
|
||||
{
|
||||
setWindowTitle(cores ? tr("Couleurs des âmes")
|
||||
: tr("Format du texte"));
|
||||
buildUi();
|
||||
|
||||
//The font this text is written with right now: the one this
|
||||
//cable asks for, or the one the preferences offer when it asks
|
||||
//for none -- which is what he sees on the sheet as he opens this.
|
||||
QFont font;
|
||||
bool restored = false;
|
||||
if (!format.font.isEmpty()) {
|
||||
restored = QETUtils::fontFromString(font, format.font);
|
||||
}
|
||||
if (!restored) {
|
||||
font = cores ? QETApp::cableCoreFont() : QETApp::cableTextsFont();
|
||||
}
|
||||
|
||||
//Where this text stands right now, in the same way: the answer
|
||||
//of the cable itself, or -- when it has none -- the one the
|
||||
//preferences offer, so that he opens this onto what the sheet
|
||||
//really shows rather than onto whatever the default used to be.
|
||||
m_initial_alignment = cores ? 0
|
||||
: (format.alignment ? format.alignment
|
||||
: int(QETApp::cableTextAlignment()));
|
||||
|
||||
m_family->setCurrentFont(font);
|
||||
m_size->setValue(font.pointSizeF());
|
||||
if (font.bold() && font.italic()) m_style->setCurrentIndex(3);
|
||||
else if (font.italic()) m_style->setCurrentIndex(2);
|
||||
else if (font.bold()) m_style->setCurrentIndex(1);
|
||||
else m_style->setCurrentIndex(0);
|
||||
if (m_alignment) {
|
||||
m_alignment->setCurrentIndex(alignmentIndex(m_initial_alignment));
|
||||
}
|
||||
|
||||
//Going back to the preferences means something only while this
|
||||
//cable has an answer of its own: when it already follows them,
|
||||
//there is nothing here to give back.
|
||||
m_reset->setEnabled(!format.font.isEmpty() || format.alignment != 0);
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::buildUi
|
||||
One row per thing he may set, then what it looks like -- because a
|
||||
size means nothing as a number alone.
|
||||
*/
|
||||
void CableTextFormatDialog::buildUi()
|
||||
{
|
||||
auto *form = new QFormLayout;
|
||||
form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
form->setHorizontalSpacing(12);
|
||||
|
||||
m_family = new QFontComboBox(this);
|
||||
m_family->setFontFilters(QFontComboBox::AllFonts);
|
||||
form->addRow(tr("Police :"), m_family);
|
||||
|
||||
m_size = new QDoubleSpinBox(this);
|
||||
m_size->setRange(1.0, 300.0);
|
||||
m_size->setDecimals(1);
|
||||
m_size->setSingleStep(0.5);
|
||||
m_size->setSuffix(tr(" pt"));
|
||||
form->addRow(tr("Taille :"), m_size);
|
||||
|
||||
m_style = new QComboBox(this);
|
||||
m_style->addItem(tr("Normal"));
|
||||
m_style->addItem(tr("Gras"));
|
||||
m_style->addItem(tr("Italique"));
|
||||
m_style->addItem(tr("Gras et italique"));
|
||||
form->addRow(tr("Style :"), m_style);
|
||||
|
||||
//The colours of the cores stand turned by a quarter along their
|
||||
//own line: there is nothing for them to line up with.
|
||||
if (!m_cores)
|
||||
{
|
||||
m_alignment = new QComboBox(this);
|
||||
m_alignment->addItem(tr("Aligné à droite"));
|
||||
m_alignment->addItem(tr("Centré"));
|
||||
m_alignment->addItem(tr("Aligné à gauche"));
|
||||
form->addRow(tr("Alignement :"), m_alignment);
|
||||
}
|
||||
|
||||
m_preview = new QLabel(tr("15W1 · H07V-K 3G1,5"), this);
|
||||
m_preview->setFrameStyle(QFrame::Box | QFrame::Plain);
|
||||
m_preview->setMinimumHeight(30);
|
||||
m_preview->setIndent(6);
|
||||
form->addRow(tr("Aperçu :"), m_preview);
|
||||
|
||||
auto *buttons = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
m_reset = buttons->addButton(tr("Réinitialiser"), QDialogButtonBox::ResetRole);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(m_reset, &QPushButton::clicked, this, [this]() {
|
||||
m_reset_chosen = true;
|
||||
accept();
|
||||
});
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addLayout(form);
|
||||
layout->addWidget(buttons);
|
||||
setLayout(layout);
|
||||
|
||||
connect(m_family, &QFontComboBox::currentFontChanged,
|
||||
this, &CableTextFormatDialog::updatePreview);
|
||||
connect(m_size, &QDoubleSpinBox::valueChanged,
|
||||
this, &CableTextFormatDialog::updatePreview);
|
||||
connect(m_style, &QComboBox::currentIndexChanged,
|
||||
this, &CableTextFormatDialog::updatePreview);
|
||||
if (m_alignment) {
|
||||
connect(m_alignment, &QComboBox::currentIndexChanged,
|
||||
this, &CableTextFormatDialog::updatePreview);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::chosenFont
|
||||
What the dialog offers right now: the family he picked, the size he
|
||||
gave it and the style he chose among the four offered -- rather than
|
||||
whatever that family happened to be called in the list.
|
||||
@return the font
|
||||
*/
|
||||
QFont CableTextFormatDialog::chosenFont() const
|
||||
{
|
||||
QFont font = m_family->currentFont();
|
||||
font.setPointSizeF(m_size->value());
|
||||
const bool italic = m_style->currentIndex() == 2 || m_style->currentIndex() == 3;
|
||||
const bool bold = m_style->currentIndex() == 1 || m_style->currentIndex() == 3;
|
||||
font.setBold(bold);
|
||||
font.setItalic(italic);
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::updatePreview
|
||||
Write the line offered as an example the way it would be written
|
||||
after what he has just set -- the only place where a size becomes
|
||||
something one can judge.
|
||||
*/
|
||||
void CableTextFormatDialog::updatePreview()
|
||||
{
|
||||
m_preview->setFont(chosenFont());
|
||||
if (m_alignment) {
|
||||
m_preview->setAlignment(
|
||||
alignmentFromIndex(m_alignment->currentIndex()) | Qt::AlignVCenter);
|
||||
} else {
|
||||
m_preview->setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::format
|
||||
@return what the cable is to write that text with from now on.
|
||||
|
||||
An empty answer means "the way the preferences say", and the two
|
||||
halves of it are worked out apart from each other: he may set the
|
||||
way one text lines up without taking away its freedom to follow a
|
||||
new default font later, and the other way round. What already
|
||||
stands the way the preferences say is left out, so that a cable
|
||||
which said nothing keeps saying nothing -- and a later change of
|
||||
what those preferences are still reaches it.
|
||||
*/
|
||||
CableTextFormat CableTextFormatDialog::format() const
|
||||
{
|
||||
if (m_reset_chosen) return CableTextFormat();
|
||||
|
||||
const QFont font = chosenFont();
|
||||
const QFont default_font = m_cores ? QETApp::cableCoreFont()
|
||||
: QETApp::cableTextsFont();
|
||||
|
||||
CableTextFormat answer;
|
||||
if (!sameFont(font, default_font)) {
|
||||
answer.font = QETUtils::fontToString(font);
|
||||
}
|
||||
//The colours of the cores stand turned by a quarter: they have
|
||||
//no line to line up with, and saying so would only be noise in
|
||||
//the file.
|
||||
if (!m_cores)
|
||||
{
|
||||
const int alignment = int(alignmentFromIndex(m_alignment->currentIndex()));
|
||||
if (alignment != int(QETApp::cableTextAlignment())) {
|
||||
answer.alignment = alignment;
|
||||
}
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CABLETEXTFORMATDIALOG_H
|
||||
#define CABLETEXTFORMATDIALOG_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QComboBox;
|
||||
class QFontComboBox;
|
||||
class QDoubleSpinBox;
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
|
||||
/**
|
||||
@brief The small window where one text of one cable is set: the font
|
||||
it is written with -- family, size and style -- and, for the texts
|
||||
written next to the trunk line, the way it lines up at the line.
|
||||
|
||||
The colours of the cores come here as well, all of them together
|
||||
because they are drawn together: they have no alignment, being
|
||||
turned by a quarter along their own line.
|
||||
|
||||
Saying nothing is the normal case: the cable then follows what the
|
||||
preferences offer on the "Textes" page, and OK keeps an answer of
|
||||
its own only when he really changed something -- so that picking
|
||||
another default later on still reaches this cable.
|
||||
*/
|
||||
class CableTextFormatDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
@param format what this cable writes that text with right
|
||||
now, an empty format when it merely follows the preferences
|
||||
@param cores true for the colours of the cores: they have
|
||||
no alignment and follow QETApp::cableCoreFont()
|
||||
@param parent
|
||||
*/
|
||||
CableTextFormatDialog(const CableTextFormat &format,
|
||||
bool cores,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
///What the cable is to write that text with from now on
|
||||
CableTextFormat format() const;
|
||||
|
||||
private:
|
||||
void buildUi();
|
||||
void updatePreview();
|
||||
QFont chosenFont() const;
|
||||
|
||||
bool m_cores = false;
|
||||
///True when he asked to go back to following the preferences
|
||||
bool m_reset_chosen = false;
|
||||
///The alignment this text has right now, 0 for the cores
|
||||
int m_initial_alignment = int(Qt::AlignRight);
|
||||
|
||||
QFontComboBox *m_family = nullptr;
|
||||
QDoubleSpinBox *m_size = nullptr;
|
||||
QComboBox *m_style = nullptr;
|
||||
QComboBox *m_alignment = nullptr;
|
||||
QLabel *m_preview = nullptr;
|
||||
QPushButton *m_reset = nullptr;
|
||||
};
|
||||
|
||||
#endif // CABLETEXTFORMATDIALOG_H
|
||||
@@ -0,0 +1,494 @@
|
||||
/*
|
||||
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 "editcablecommand.h"
|
||||
|
||||
#include "cablemanager.h"
|
||||
#include "cablepart.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@brief BatchCommand::BatchCommand
|
||||
@param first step to run first, kept by this command
|
||||
@param second step to run right after it, kept by this command
|
||||
*/
|
||||
BatchCommand::BatchCommand(QUndoCommand *first, QUndoCommand *second) :
|
||||
m_steps({first, second})
|
||||
{
|
||||
m_steps.removeAll(nullptr);
|
||||
setText(QCoreApplication::translate("BatchCommand", "Supprimer"));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BatchCommand::BatchCommand
|
||||
@param steps every step of one single gesture, in the order the
|
||||
user did them, kept by this command
|
||||
@param text what the gesture was, as the undo history shows it
|
||||
*/
|
||||
BatchCommand::BatchCommand(const QList<QUndoCommand *> &steps, const QString &text) :
|
||||
m_steps(steps)
|
||||
{
|
||||
m_steps.removeAll(nullptr);
|
||||
setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BatchCommand::~BatchCommand
|
||||
The steps it holds go with it: they were never given to anything else.
|
||||
*/
|
||||
BatchCommand::~BatchCommand()
|
||||
{
|
||||
for (QUndoCommand *step : std::as_const(m_steps)) {
|
||||
delete step;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BatchCommand::redo
|
||||
Every step, in the order the user did them.
|
||||
*/
|
||||
void BatchCommand::redo()
|
||||
{
|
||||
for (QUndoCommand *step : std::as_const(m_steps)) {
|
||||
if (step) step->redo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BatchCommand::undo
|
||||
Every step taken back in the reverse order, so what one step changed
|
||||
is still there when the next one is undone.
|
||||
*/
|
||||
void BatchCommand::undo()
|
||||
{
|
||||
for (auto it = m_steps.crbegin(); it != m_steps.crend(); ++it) {
|
||||
if (*it) (*it)->undo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MoveCablePartCommand::MoveCablePartCommand
|
||||
@param cable the cable the section belongs to
|
||||
@param part the line being dragged
|
||||
@param before_p1
|
||||
@param before_p2
|
||||
@param after_p1
|
||||
@param after_p2
|
||||
@param before_cores where every colour label stood before the drag
|
||||
@param after_cores where it stands after it
|
||||
*/
|
||||
MoveCablePartCommand::MoveCablePartCommand(Cable *cable,
|
||||
CablePart *part,
|
||||
const QPointF &before_p1,
|
||||
const QPointF &before_p2,
|
||||
const QPointF &after_p1,
|
||||
const QPointF &after_p2,
|
||||
const QList<CableCore> &before_cores,
|
||||
const QList<CableCore> &after_cores) :
|
||||
m_cable(cable),
|
||||
m_part(part),
|
||||
m_before_p1(before_p1),
|
||||
m_before_p2(before_p2),
|
||||
m_after_p1(after_p1),
|
||||
m_after_p2(after_p2),
|
||||
m_before_cores(before_cores),
|
||||
m_after_cores(after_cores)
|
||||
{
|
||||
setText(QCoreApplication::translate("MoveCablePartCommand", "Déplacer un câble"));
|
||||
}
|
||||
|
||||
void MoveCablePartCommand::undo()
|
||||
{
|
||||
apply(m_before_p1, m_before_p2, m_before_cores);
|
||||
}
|
||||
|
||||
void MoveCablePartCommand::redo()
|
||||
{
|
||||
apply(m_after_p1, m_after_p2, m_after_cores);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MoveCablePartCommand::apply
|
||||
Put the line where it belongs and the colour labels with it. The
|
||||
positions are written last: they are stored in scene coordinates, so
|
||||
they only make sense once the line itself is back in place.
|
||||
@param p1
|
||||
@param p2
|
||||
@param cores
|
||||
*/
|
||||
void MoveCablePartCommand::apply(const QPointF &p1,
|
||||
const QPointF &p2,
|
||||
const QList<CableCore> &cores)
|
||||
{
|
||||
if (m_part) {
|
||||
m_part->setLine(p1, p2);
|
||||
}
|
||||
if (m_cable) {
|
||||
m_cable->setCores(cores);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCablePropertiesCommand::ChangeCablePropertiesCommand
|
||||
@param cable
|
||||
@param before every field as it was before the edit
|
||||
@param after every field as the user left it
|
||||
*/
|
||||
ChangeCablePropertiesCommand::ChangeCablePropertiesCommand(Cable *cable,
|
||||
const CableProperties &before,
|
||||
const CableProperties &after) :
|
||||
m_cable(cable),
|
||||
m_before(before),
|
||||
m_after(after)
|
||||
{
|
||||
setText(QCoreApplication::translate("ChangeCablePropertiesCommand",
|
||||
"Modifier les propriétés d'un câble"));
|
||||
}
|
||||
|
||||
void ChangeCablePropertiesCommand::undo()
|
||||
{
|
||||
if (m_cable) {
|
||||
m_cable->setProperties(m_before);
|
||||
}
|
||||
}
|
||||
|
||||
void ChangeCablePropertiesCommand::redo()
|
||||
{
|
||||
if (m_cable) {
|
||||
m_cable->setProperties(m_after);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RenumberCablesCommand::RenumberCablesCommand
|
||||
@param cables the cables to number, in the order they are numbered in
|
||||
@param designations the number each of them gets
|
||||
@param project the project whose numbering rule moves along with
|
||||
the numbering
|
||||
@param num_key the name that rule is kept under in the project
|
||||
@param num_before where its counters stood before the numbering ran
|
||||
@param num_after where the numbering leaves them
|
||||
*/
|
||||
RenumberCablesCommand::RenumberCablesCommand(const QList<Cable *> &cables,
|
||||
const QStringList &designations,
|
||||
QETProject *project,
|
||||
const QString &num_key,
|
||||
const NumerotationContext &num_before,
|
||||
const NumerotationContext &num_after) :
|
||||
m_project(project),
|
||||
m_num_key(num_key),
|
||||
m_num_before(num_before),
|
||||
m_num_after(num_after)
|
||||
{
|
||||
for (int i = 0; i < cables.size() && i < designations.size(); ++i)
|
||||
{
|
||||
Cable *cable = cables.at(i);
|
||||
if (!cable) continue;
|
||||
|
||||
m_cables.append(cable);
|
||||
m_before.append(cable->designation());
|
||||
m_by_hand_before.append(cable->designationByHand());
|
||||
m_after.append(designations.at(i));
|
||||
//A number the rule has just handed out is the rule's, so
|
||||
//nothing here stays marked as having been typed in
|
||||
m_by_hand_after.append(false);
|
||||
}
|
||||
|
||||
setText(QCoreApplication::translate("RenumberCablesCommand",
|
||||
"Renumérotter %n câble(s)",
|
||||
"", m_cables.size()));
|
||||
}
|
||||
|
||||
void RenumberCablesCommand::undo()
|
||||
{
|
||||
apply(false);
|
||||
}
|
||||
|
||||
void RenumberCablesCommand::redo()
|
||||
{
|
||||
apply(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RenumberCablesCommand::apply
|
||||
@param use_after true to write the numbers the rule handed out,
|
||||
false to put the ones back which were there before
|
||||
*/
|
||||
void RenumberCablesCommand::apply(bool use_after)
|
||||
{
|
||||
for (int i = 0; i < m_cables.size(); ++i)
|
||||
{
|
||||
Cable *cable = m_cables.at(i).data();
|
||||
if (!cable) continue;
|
||||
|
||||
//Where the name comes from is written first: a number the
|
||||
//rule has just handed out is the rule's whatever the cable
|
||||
//was called before, and undo has to say so again
|
||||
cable->setDesignationByHand(use_after ? m_by_hand_after.at(i)
|
||||
: m_by_hand_before.at(i));
|
||||
cable->setDesignation(use_after ? m_after.at(i) : m_before.at(i));
|
||||
}
|
||||
|
||||
//Where the numbering rule itself stands moves along with the
|
||||
//numbering: once it has run, its counters are where the last
|
||||
//number was taken from, so the next cable drawn goes on from
|
||||
//there rather than skipping over every number in between. Undo
|
||||
//puts them back like the names, since numbering a project over
|
||||
//is one single act.
|
||||
if (m_project && !m_num_key.isEmpty())
|
||||
{
|
||||
const NumerotationContext context = use_after ? m_num_after
|
||||
: m_num_before;
|
||||
//An empty rule is never written: losing the rule altogether
|
||||
//is not something numbering may do, not even on the way back
|
||||
if (!context.isEmpty())
|
||||
{
|
||||
m_project->addCableAutoNum(m_num_key, context);
|
||||
m_project->setCurrentCableAutoNum(m_num_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCableTypeCommand::ChangeCableTypeCommand
|
||||
@param cable
|
||||
@param before the type, the count, the colours and the cores as
|
||||
they were before the change
|
||||
@param after the very same four as the chosen type says
|
||||
*/
|
||||
ChangeCableTypeCommand::ChangeCableTypeCommand(Cable *cable,
|
||||
const CableTypeState &before,
|
||||
const CableTypeState &after) :
|
||||
m_cable(cable),
|
||||
m_before(before),
|
||||
m_after(after)
|
||||
{
|
||||
setText(QCoreApplication::translate("ChangeCableTypeCommand",
|
||||
"Changer le type du câble"));
|
||||
}
|
||||
|
||||
void ChangeCableTypeCommand::undo()
|
||||
{
|
||||
apply(m_before);
|
||||
}
|
||||
|
||||
void ChangeCableTypeCommand::redo()
|
||||
{
|
||||
apply(m_after);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCableTypeCommand::apply
|
||||
One block in one pass: name, colours, count, then the cores. The
|
||||
count has to stand before the cores, since a cable with fewer cores
|
||||
cannot hold the others -- and the other way round, undoing a type
|
||||
with fewer cores has to open the room before the cores come back
|
||||
into it.
|
||||
@param state the block to write
|
||||
*/
|
||||
void ChangeCableTypeCommand::apply(const CableTypeState &state)
|
||||
{
|
||||
if (m_cable) {
|
||||
m_cable->applyTypeState(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCableCoresCommand::ChangeCableCoresCommand
|
||||
@param cable
|
||||
@param before every core the cable wired before the change
|
||||
@param after every core it wires after it
|
||||
@param text what undo says about this step, empty for a core which
|
||||
is merely moved
|
||||
*/
|
||||
ChangeCableCoresCommand::ChangeCableCoresCommand(Cable *cable,
|
||||
const QList<CableCore> &before,
|
||||
const QList<CableCore> &after,
|
||||
const QString &text) :
|
||||
m_cable(cable),
|
||||
m_before(before),
|
||||
m_after(after)
|
||||
{
|
||||
setText(text.isEmpty()
|
||||
? QCoreApplication::translate("ChangeCableCoresCommand",
|
||||
"Déplacer une âme de câble")
|
||||
: text);
|
||||
}
|
||||
|
||||
void ChangeCableCoresCommand::undo()
|
||||
{
|
||||
apply(m_before);
|
||||
}
|
||||
|
||||
void ChangeCableCoresCommand::redo()
|
||||
{
|
||||
apply(m_after);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCableCoresCommand::apply
|
||||
Write one whole set of assignments in place of another. The cable
|
||||
says it changed, which is what makes the project work out the cable
|
||||
field of every conductor it feeds again, so the drawing follows.
|
||||
@param cores
|
||||
*/
|
||||
void ChangeCableCoresCommand::apply(const QList<CableCore> &cores)
|
||||
{
|
||||
if (m_cable) {
|
||||
m_cable->setCores(cores);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::RemoveCableCommand
|
||||
@param diagram the folios the lines are drawn on
|
||||
@param parts the lines the user asked to delete
|
||||
*/
|
||||
RemoveCableCommand::RemoveCableCommand(Diagram *diagram, const QList<CablePart *> &parts) :
|
||||
m_diagram(diagram)
|
||||
{
|
||||
setText(QCoreApplication::translate("RemoveCableCommand", "Supprimer un câble"));
|
||||
|
||||
for (CablePart *part : parts)
|
||||
{
|
||||
if (!part) continue;
|
||||
|
||||
Entry entry;
|
||||
entry.part = part;
|
||||
entry.cable = part->cable();
|
||||
entry.data.uuid = part->partUuid();
|
||||
entry.data.diagram = m_diagram ? m_diagram->uuid() : QUuid();
|
||||
entry.data.p1 = part->firstPoint();
|
||||
entry.data.p2 = part->secondPoint();
|
||||
m_entries.append(entry);
|
||||
|
||||
if (entry.cable && !m_cables.contains(entry.cable)) {
|
||||
m_cables.append(entry.cable);
|
||||
m_cores.insert(entry.cable.data(), entry.cable->usedCores());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::~RemoveCableCommand
|
||||
Whatever the stack leaves behind has to be cleaned up here: a cable
|
||||
which is not held by the project any more, and a line which never
|
||||
made it back onto a folio, belong to this command.
|
||||
*/
|
||||
RemoveCableCommand::~RemoveCableCommand()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
for (const QPointer<Cable> &cable : std::as_const(m_cables))
|
||||
{
|
||||
if (cable && (!proj || !proj->cables().contains(cable))) {
|
||||
delete cable;
|
||||
}
|
||||
}
|
||||
for (const Entry &entry : std::as_const(m_entries))
|
||||
{
|
||||
if (entry.part && !entry.part->scene()) {
|
||||
delete entry.part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::project
|
||||
@return the project this command works in, or nullptr
|
||||
*/
|
||||
QETProject *RemoveCableCommand::project() const
|
||||
{
|
||||
return m_diagram ? m_diagram->project() : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::redo
|
||||
Take the lines off their folios. Each cable loses the sections which
|
||||
are gone together with the cores drawn on them, and a cable which has
|
||||
no section left anywhere leaves the project: a cable nothing draws is
|
||||
no cable of this project.
|
||||
*/
|
||||
void RemoveCableCommand::redo()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
|
||||
for (const Entry &entry : std::as_const(m_entries))
|
||||
{
|
||||
if (entry.cable) {
|
||||
entry.cable->removePart(entry.data.uuid);
|
||||
}
|
||||
if (entry.part && entry.part->scene() && m_diagram) {
|
||||
m_diagram->removeItem(entry.part);
|
||||
}
|
||||
}
|
||||
|
||||
if (proj)
|
||||
{
|
||||
for (const QPointer<Cable> &cable : std::as_const(m_cables))
|
||||
{
|
||||
if (cable && cable->parts().isEmpty() && proj->cables().contains(cable)) {
|
||||
proj->removeCable(cable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CableManager::refreshLabels(proj);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::undo
|
||||
Put the lines back on their folios, the sections back in their cable
|
||||
and the cores back on those sections: exactly the state the delete
|
||||
action took away.
|
||||
*/
|
||||
void RemoveCableCommand::undo()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
|
||||
if (proj)
|
||||
{
|
||||
for (const QPointer<Cable> &cable : std::as_const(m_cables))
|
||||
{
|
||||
if (cable && !proj->cables().contains(cable)) {
|
||||
proj->addCable(cable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const Entry &entry : std::as_const(m_entries))
|
||||
{
|
||||
if (entry.cable)
|
||||
{
|
||||
if (!entry.cable->hasPart(entry.data.uuid)) {
|
||||
entry.cable->addPart(entry.data);
|
||||
}
|
||||
for (const CableCore &core : m_cores.value(entry.cable.data())) {
|
||||
entry.cable->setCore(core);
|
||||
}
|
||||
}
|
||||
if (entry.part && !entry.part->scene() && m_diagram) {
|
||||
m_diagram->addItem(entry.part);
|
||||
}
|
||||
}
|
||||
|
||||
CableManager::refreshLabels(proj);
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef EDITCABLECOMMAND_H
|
||||
#define EDITCABLECOMMAND_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include "../autoNum/numerotationcontext.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QUndoCommand>
|
||||
|
||||
class CablePart;
|
||||
class Diagram;
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief Puts a dragged cable line back where it was, or where the user
|
||||
just dragged it to.
|
||||
|
||||
The colour labels of its cores follow the line -- they are drawn on
|
||||
it -- and come back with it when the move is undone. Which core lands
|
||||
on which conductor never depends on where the trunk line stands, so
|
||||
the assignments are only carried along by this step, never changed.
|
||||
*/
|
||||
class MoveCablePartCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
MoveCablePartCommand(Cable *cable,
|
||||
CablePart *part,
|
||||
const QPointF &before_p1,
|
||||
const QPointF &before_p2,
|
||||
const QPointF &after_p1,
|
||||
const QPointF &after_p2,
|
||||
const QList<CableCore> &before_cores,
|
||||
const QList<CableCore> &after_cores);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
void apply(const QPointF &p1,
|
||||
const QPointF &p2,
|
||||
const QList<CableCore> &cores);
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
QPointer<CablePart> m_part;
|
||||
QPointF m_before_p1;
|
||||
QPointF m_before_p2;
|
||||
QPointF m_after_p1;
|
||||
QPointF m_after_p2;
|
||||
QList<CableCore> m_before_cores;
|
||||
QList<CableCore> m_after_cores;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Rewrites which cores a cable wires, the way dragging the slash
|
||||
of a core does: the whole set of assignments is taken before and
|
||||
after the gesture, so undoing puts every one of them back -- the core
|
||||
which was moved, but also the one it swapped colours with.
|
||||
*/
|
||||
class ChangeCableCoresCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@param cable the cable whose assignments are rewritten
|
||||
@param before every core it held before
|
||||
@param after every core it holds after
|
||||
@param text what undo says about this step: an empty string
|
||||
for the usual step of moving a core around, since putting a
|
||||
core down for the first time is not a move at all
|
||||
*/
|
||||
ChangeCableCoresCommand(Cable *cable,
|
||||
const QList<CableCore> &before,
|
||||
const QList<CableCore> &after,
|
||||
const QString &text = QString());
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
void apply(const QList<CableCore> &cores);
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
QList<CableCore> m_before;
|
||||
QList<CableCore> m_after;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Writes what the user typed into the fields of a cable -- and
|
||||
what he ticks off again -- as one single undo step.
|
||||
|
||||
All the fields belong to the cable rather than to one line of it, so
|
||||
one change puts every line of that cable, on every folio, right.
|
||||
*/
|
||||
class ChangeCablePropertiesCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
ChangeCablePropertiesCommand(Cable *cable,
|
||||
const CableProperties &before,
|
||||
const CableProperties &after);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
CableProperties m_before;
|
||||
CableProperties m_after;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Gives every cable of a project the number the numbering rule
|
||||
says it should have, laid out in the order the cables are met along
|
||||
the sheets.
|
||||
|
||||
That is how a project is numbered again after a cable has been taken
|
||||
away: the hole its number leaves closes because the cables behind it
|
||||
move up one place, and a rule which has just been changed reaches
|
||||
every cable of the project at once.
|
||||
|
||||
One command covers them all -- numbering a whole project is one
|
||||
single act, so undo takes all of it back together. What the
|
||||
numbering rule itself holds is part of that act: once the cables
|
||||
are numbered, the counters of the rule stand where the last number
|
||||
was taken from, and undo puts them back where they stood.
|
||||
*/
|
||||
class RenumberCablesCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@param cables the cables to number, in the order they are
|
||||
numbered in
|
||||
@param designations the number each of them gets, one for
|
||||
each cable
|
||||
@param project the project whose numbering rule moves along
|
||||
with the numbering
|
||||
@param num_key the name that rule is kept under in the
|
||||
project
|
||||
@param num_before where its counters stood before the
|
||||
numbering ran
|
||||
@param num_after where the numbering leaves them
|
||||
*/
|
||||
RenumberCablesCommand(const QList<Cable *> &cables,
|
||||
const QStringList &designations,
|
||||
QETProject *project,
|
||||
const QString &num_key,
|
||||
const NumerotationContext &num_before,
|
||||
const NumerotationContext &num_after);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
void apply(bool use_after);
|
||||
|
||||
private:
|
||||
QList<QPointer<Cable>> m_cables;
|
||||
QStringList m_before;
|
||||
QStringList m_after;
|
||||
QList<bool> m_by_hand_before;
|
||||
QList<bool> m_by_hand_after;
|
||||
QETProject *m_project = nullptr;
|
||||
QString m_num_key;
|
||||
NumerotationContext m_num_before;
|
||||
NumerotationContext m_num_after;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Changes which type a cable is -- its name, how many cores it
|
||||
has and the colour of each of them -- together with the cores it
|
||||
may lose on the way, as one single step.
|
||||
|
||||
The cores belong to this step rather than to a step of their own:
|
||||
a type with fewer cores takes marks off the drawing, and undoing has
|
||||
to bring them back along with the name and the colours, never half
|
||||
of it.
|
||||
*/
|
||||
class ChangeCableTypeCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@param cable the cable whose type is changed
|
||||
@param before the state the cable was in
|
||||
@param after the state it is put into
|
||||
*/
|
||||
ChangeCableTypeCommand(Cable *cable,
|
||||
const CableTypeState &before,
|
||||
const CableTypeState &after);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
void apply(const CableTypeState &state);
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
CableTypeState m_before;
|
||||
CableTypeState m_after;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Holds the steps of one single gesture together, so a single
|
||||
undo takes all of them back -- an ordinary deletion with the cable
|
||||
deletions which go with it, or a drag which moved several cable
|
||||
lines at once.
|
||||
|
||||
It keeps its steps rather than handing them to QUndoCommand as
|
||||
children: Qt6.11 has no addChild() any more.
|
||||
*/
|
||||
class BatchCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
BatchCommand(QUndoCommand *first, QUndoCommand *second);
|
||||
BatchCommand(const QList<QUndoCommand *> &steps, const QString &text);
|
||||
~BatchCommand() override;
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
QList<QUndoCommand *> m_steps;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Takes one or more drawn cable lines off their folios -- and the
|
||||
cable itself with them when no line of it is left anywhere.
|
||||
|
||||
Undo puts everything back: the lines on their folios, the sections in
|
||||
the cable, the cores on those sections, and the cable in the project
|
||||
when it had to leave it.
|
||||
*/
|
||||
class RemoveCableCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
RemoveCableCommand(Diagram *diagram, const QList<CablePart *> &parts);
|
||||
~RemoveCableCommand() override;
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
QETProject *project() const;
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
///The line as it is drawn on the folio
|
||||
QPointer<CablePart> part;
|
||||
///Where the cable holds that section
|
||||
CablePartData data;
|
||||
///The cable the section belongs to
|
||||
QPointer<Cable> cable;
|
||||
};
|
||||
|
||||
QPointer<Diagram> m_diagram;
|
||||
QList<Entry> m_entries;
|
||||
///Every cable which loses a section here, with all the cores
|
||||
///it wires at this moment: removing a section drops the cores
|
||||
///drawn on it, and undo has to bring them back
|
||||
QList<QPointer<Cable>> m_cables;
|
||||
QHash<Cable *, QList<CableCore>> m_cores;
|
||||
};
|
||||
|
||||
#endif // EDITCABLECOMMAND_H
|
||||
Reference in New Issue
Block a user