mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-11 14:44:15 +02:00
bf9b34398d
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.
385 lines
12 KiB
C++
385 lines
12 KiB
C++
/*
|
|
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 "deleteqgraphicsitemcommand.h"
|
|
|
|
#include "../cable/cablemanager.h"
|
|
#include "../diagram.h"
|
|
#include "addgraphicsobjectcommand.h"
|
|
#include "../qetdiagrameditor.h"
|
|
#include "../qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
|
|
#include "../qetgraphicsitem/conductor.h"
|
|
#include "../qetgraphicsitem/conductortextitem.h"
|
|
#include "../qetgraphicsitem/dynamicelementtextitem.h"
|
|
#include "../qetgraphicsitem/element.h"
|
|
#include "../qetgraphicsitem/elementtextitemgroup.h"
|
|
#include "../qetgraphicsitem/terminal.h"
|
|
#include "addelementtextcommand.h"
|
|
#include "../TerminalStrip/realterminal.h"
|
|
#include "../TerminalStrip/physicalterminal.h"
|
|
|
|
/**
|
|
@brief DeleteQGraphicsItemCommand::DeleteQGraphicsItemCommand
|
|
@param diagram : diagram where this undo work
|
|
@param content : content to remove
|
|
@param parent : parent undo
|
|
*/
|
|
DeleteQGraphicsItemCommand::DeleteQGraphicsItemCommand(
|
|
Diagram *diagram,
|
|
const DiagramContent &content,
|
|
QUndoCommand *parent) :
|
|
QUndoCommand(parent),
|
|
m_removed_contents(content),
|
|
m_diagram(diagram)
|
|
{
|
|
//If parent element of a dynamic element text item is also in m_removed_content,
|
|
//we remove it, because when the element will be removed from the scene every child's will also be removed.
|
|
const QSet<DynamicElementTextItem *> elmt_set = m_removed_contents.m_element_texts;
|
|
for(DynamicElementTextItem *deti : elmt_set)
|
|
{
|
|
if (m_removed_contents.m_elements.contains(deti->parentElement()))
|
|
m_removed_contents.m_element_texts.remove(deti);
|
|
|
|
}
|
|
|
|
//When removing a deti we must know its parent item, for re-adding deti as child of the parent
|
|
//when undoing this command
|
|
for(DynamicElementTextItem *deti : m_removed_contents.m_element_texts)
|
|
{
|
|
if(deti->parentGroup())
|
|
m_grp_texts_hash.insert(deti, deti->parentGroup());
|
|
else
|
|
m_elmt_text_hash.insert(deti, deti->parentElement());
|
|
}
|
|
|
|
//If parent element of ElementTextItemGroup is also in m_removed_content,
|
|
//we remove it, because when the element will be removed from the scene every child's will also be removed.
|
|
const QSet<ElementTextItemGroup *> group_set = m_removed_contents.m_texts_groups;
|
|
for(ElementTextItemGroup *group : group_set)
|
|
{
|
|
if(m_removed_contents.m_elements.contains(group->parentElement()))
|
|
m_removed_contents.m_texts_groups.remove(group);
|
|
}
|
|
|
|
//The deletion of the groups is not managed by this undo, but by a RemoveTextsGroupCommand
|
|
for(ElementTextItemGroup *group : m_removed_contents.m_texts_groups) {
|
|
new RemoveTextsGroupCommand(group->parentElement(), group, this);
|
|
}
|
|
|
|
m_removed_contents.m_texts_groups.clear();
|
|
setPotentialsOfRemovedElements();
|
|
|
|
//Get all linkeds table of removed table.
|
|
for (auto table : m_removed_contents.m_tables)
|
|
{
|
|
//Table is already managed, jump to next loop
|
|
if (m_table_scene_hash.contains(table))
|
|
continue;
|
|
|
|
auto first_table = table; //The first table if the table is linked to another
|
|
while(first_table->previousTable())
|
|
first_table = first_table->previousTable();
|
|
auto current_table = first_table;
|
|
|
|
m_table_scene_hash.insert(first_table, first_table->scene());
|
|
while (current_table->nextTable())
|
|
{
|
|
current_table = current_table->nextTable();
|
|
m_table_scene_hash.insert(current_table, current_table->scene());
|
|
}
|
|
}
|
|
|
|
setText(QString(QObject::tr(
|
|
"supprimer %1",
|
|
"undo caption - %1 is a sentence listing the removed content"))
|
|
.arg(m_removed_contents.sentence(DiagramContent::All)));
|
|
//Table is now managed by m_table_scene_hash,
|
|
//we clear the tables of m_removed_content
|
|
m_removed_contents.m_tables.clear();
|
|
m_diagram->qgiManager().manage(m_removed_contents.items(DiagramContent::All));
|
|
}
|
|
|
|
DeleteQGraphicsItemCommand::~DeleteQGraphicsItemCommand()
|
|
{
|
|
m_diagram->qgiManager().release(m_removed_contents.items(DiagramContent::All));
|
|
}
|
|
|
|
/**
|
|
* @brief DeleteQGraphicsItemCommand::hasNonDeletableTerminal
|
|
* Return true if @content have terminal element which can't be deleted.
|
|
* The reason why a terminal can't be deleted is because they have bridge
|
|
* or belong to a physical terminal with more than one level.
|
|
* @param diagram
|
|
* @param content
|
|
* @param dialog
|
|
* @return
|
|
*/
|
|
bool DeleteQGraphicsItemCommand::hasNonDeletableTerminal(const DiagramContent &content)
|
|
{
|
|
if (!content.m_terminal_elements.isEmpty())
|
|
{
|
|
for (const auto &terminal : content.m_terminal_elements)
|
|
{
|
|
if (!terminal.isNull())
|
|
{
|
|
if (terminal->parentTerminalStrip()
|
|
&& (terminal->realTerminal()->isBridged()
|
|
|| terminal->realTerminal()->physicalTerminal()->levelCount() != 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@brief DeleteQGraphicsItemCommand::setPotentialsOfRemovedElements
|
|
This function creates new conductors (if needed) for conserve the electrical potentials
|
|
present at the terminals of each removed elements.
|
|
*/
|
|
void DeleteQGraphicsItemCommand::setPotentialsOfRemovedElements()
|
|
{
|
|
for (Element *elmt : m_removed_contents.m_elements)
|
|
{
|
|
//a list of terminals who have at least two conductors docked in.
|
|
QList<Terminal *> terminals_list;
|
|
for (Terminal *t : elmt->terminals()) {
|
|
if (t->conductors().size() >= 2) {
|
|
terminals_list.append(t);
|
|
}
|
|
}
|
|
if (terminals_list.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
for (Terminal *t : terminals_list)
|
|
{
|
|
//All new created conductors will be docked to hub_terminal
|
|
Terminal *hub_terminal = nullptr;
|
|
QList<Terminal *> terminals_to_connect_list;
|
|
|
|
for (Conductor *c : t->conductors())
|
|
{
|
|
Terminal *other_terminal = c->terminal1 == t ? c->terminal2 : c->terminal1;
|
|
|
|
if (m_removed_contents.items(DiagramContent::Elements).contains(other_terminal->parentElement()))
|
|
{
|
|
other_terminal = terminalInSamePotential(other_terminal, c);
|
|
if (other_terminal == nullptr) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
terminals_to_connect_list.append(other_terminal);
|
|
if (hub_terminal == nullptr) {
|
|
hub_terminal = other_terminal;
|
|
}
|
|
//hub_terminal must be the terminal the more at top left of the diagram.
|
|
else if (other_terminal->scenePos().x() < hub_terminal->scenePos().x()) {
|
|
hub_terminal = other_terminal;
|
|
}
|
|
else if (other_terminal->scenePos().x() == hub_terminal->scenePos().x()) {
|
|
if (other_terminal->scenePos().y() < hub_terminal->scenePos().y()) {
|
|
hub_terminal = other_terminal;
|
|
}
|
|
}
|
|
}
|
|
|
|
terminals_to_connect_list.removeAll(hub_terminal);
|
|
if (hub_terminal == nullptr || terminals_to_connect_list.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
ConductorProperties properties = hub_terminal->conductors().first()->properties();
|
|
for (Terminal *t : terminals_to_connect_list)
|
|
{
|
|
//If a conductor was already created between these two terminals
|
|
//in this undo command, from another removed element, we do nothing
|
|
bool exist_ = false;
|
|
for (std::pair<Terminal *, Terminal *> pair : m_connected_terminals)
|
|
{
|
|
if (pair.first == hub_terminal && pair.second == t) {
|
|
exist_ = true;
|
|
continue;
|
|
} else if (pair.first == t && pair.second == hub_terminal) {
|
|
exist_ = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (exist_ == false)
|
|
{
|
|
m_connected_terminals.append(std::make_pair<Terminal *, Terminal *>((Terminal *)hub_terminal, (Terminal *)t));
|
|
qInfo() << "m_connected_terminals" << m_connected_terminals;
|
|
Conductor *new_cond = new Conductor(hub_terminal, t);
|
|
new_cond->setProperties(properties);
|
|
new AddGraphicsObjectCommand(new_cond, t->diagram(), QPointF(), this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief DeleteQGraphicsItemCommand::terminalInSamePotential
|
|
Return a terminal at the same potential of terminal,
|
|
by traveling through the conductors connected to terminal
|
|
only if the owner element of the terminal
|
|
is not delete by this undo command.
|
|
Return nullptr if a terminal can't be found.
|
|
@param terminal - terminal from search
|
|
@param conductor_to_exclude - a conductor to exlcude from search.
|
|
@return
|
|
*/
|
|
Terminal *DeleteQGraphicsItemCommand::terminalInSamePotential(
|
|
Terminal *terminal,
|
|
Conductor *conductor_to_exclude)
|
|
{
|
|
QList<Conductor *> conductor_list = terminal->conductors();
|
|
conductor_list.removeAll(conductor_to_exclude);
|
|
for(Conductor *c : conductor_list)
|
|
{
|
|
Terminal *other_terminal = c->terminal1 == terminal ? c->terminal2 : c->terminal1;
|
|
if(!m_removed_contents.items(DiagramContent::Elements).contains(other_terminal->parentElement())) {
|
|
return other_terminal;
|
|
}
|
|
}
|
|
//No one of direct conductor of terminal are docked to an element which is not removed
|
|
for(Conductor *c : conductor_list)
|
|
{
|
|
Terminal *other_terminal = c->terminal1 == terminal ? c->terminal2 : c->terminal1;
|
|
Terminal *terminal_to_return = terminalInSamePotential(other_terminal, c);
|
|
if (terminal_to_return != nullptr) {
|
|
return terminal_to_return;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
@brief DeleteQGraphicsItemCommand::undo
|
|
Undo this command
|
|
*/
|
|
void DeleteQGraphicsItemCommand::undo()
|
|
{
|
|
m_diagram->showMe();
|
|
|
|
for(QGraphicsItem *item : m_removed_contents.items())
|
|
m_diagram->addItem(item);
|
|
|
|
//We relink element after every element was added to diagram
|
|
for(Element *e : m_removed_contents.m_elements)
|
|
for(Element *elmt : m_link_hash[e])
|
|
e->linkToElement(elmt);
|
|
|
|
for(DynamicElementTextItem *deti : m_removed_contents.m_element_texts)
|
|
{
|
|
if(m_elmt_text_hash.contains(deti))
|
|
m_elmt_text_hash.value(deti)->addDynamicTextItem(deti);
|
|
else if (m_grp_texts_hash.contains(deti))
|
|
{
|
|
Element *elmt = m_grp_texts_hash.value(deti)->parentElement();
|
|
elmt->addDynamicTextItem(deti);
|
|
elmt->addTextToGroup(deti, m_grp_texts_hash.value(deti));
|
|
}
|
|
}
|
|
|
|
for (auto table : m_table_scene_hash.keys())
|
|
{
|
|
if (!m_table_scene_hash.value(table).isNull()) {
|
|
m_table_scene_hash.value(table)->addItem(table);
|
|
}
|
|
}
|
|
|
|
//Back on the sheet and linked again: the cables which run over
|
|
//this report claim their wire on the other side once more.
|
|
for (Element *e : m_removed_contents.m_elements) {
|
|
if (e && (e->linkType() & Element::AllReport)) {
|
|
CableManager::refreshLabels(m_diagram->project());
|
|
break;
|
|
}
|
|
}
|
|
|
|
QUndoCommand::undo();
|
|
}
|
|
|
|
/**
|
|
@brief DeleteQGraphicsItemCommand::redo
|
|
Redo the delete command
|
|
*/
|
|
void DeleteQGraphicsItemCommand::redo()
|
|
{
|
|
m_diagram -> showMe();
|
|
|
|
for(Conductor *c : m_removed_contents.conductors(DiagramContent::AnyConductor))
|
|
{
|
|
//If option one text per folio is enable, and the text item of
|
|
//current conductor is visible (that mean the conductor have the single displayed text)
|
|
//We call adjustTextItemPosition to other conductor at the same potential to keep
|
|
//a visible text on this potential.
|
|
if (m_diagram -> defaultConductorProperties.m_one_text_per_folio && c -> textItem() -> isVisible())
|
|
{
|
|
QList <Conductor *> conductor_list;
|
|
conductor_list << c -> relatedPotentialConductors(false).values();
|
|
if (conductor_list.count())
|
|
conductor_list.first() -> calculateTextItemPosition();
|
|
}
|
|
}
|
|
|
|
for(Element *e : m_removed_contents.m_elements)
|
|
{
|
|
//Get linked element, for relink it at undo
|
|
if (!e->linkedElements().isEmpty())
|
|
m_link_hash.insert(e, e->linkedElements());
|
|
}
|
|
|
|
for(DynamicElementTextItem *deti : m_removed_contents.m_element_texts)
|
|
{
|
|
if(deti->parentGroup() && deti->parentGroup()->parentElement())
|
|
deti->parentGroup()->parentElement()->removeTextFromGroup(deti, deti->parentGroup());
|
|
|
|
deti->parentElement()->removeDynamicTextItem(deti);
|
|
deti->setParentItem(nullptr);
|
|
}
|
|
|
|
for (auto table : m_table_scene_hash.keys())
|
|
{
|
|
if (!m_table_scene_hash.value(table).isNull()) {
|
|
m_table_scene_hash.value(table)->removeItem(table);
|
|
}
|
|
}
|
|
|
|
for(QGraphicsItem *item : m_removed_contents.items())
|
|
m_diagram->removeItem(item);
|
|
|
|
//A folio report carries a cable core over to the next sheet:
|
|
//taking one away (putting it back is undo, below) changes which
|
|
//wires belong to which cable, so the cable fields are worked
|
|
//out again straight away.
|
|
for (Element *e : m_removed_contents.m_elements) {
|
|
if (e && (e->linkType() & Element::AllReport)) {
|
|
CableManager::refreshLabels(m_diagram->project());
|
|
break;
|
|
}
|
|
}
|
|
|
|
QUndoCommand::redo();
|
|
}
|