/* 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 . */ #include "editcablecommand.h" #include "cablemanager.h" #include "cablepart.h" #include "../diagram.h" #include "../qetproject.h" #include #include /** @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 &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 &before_cores, const QList &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 &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 &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 &before, const QList &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 &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 &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 : 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 : 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 : 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); }