/* 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 "elementtextpattern.h" #include "diagram.h" #include "qet.h" #include "qetapp.h" #include "qetgraphicsitem/dynamicelementtextitem.h" #include "qetgraphicsitem/element.h" #include "qetgraphicsitem/elementtextitemgroup.h" #include "ui/importelementtextpatterndialog.h" #include "undocommand/addelementtextcommand.h" #include "undocommand/deleteqgraphicsitemcommand.h" #include "shownkinds.h" #include #include #include #include #include #include #include /** @brief ExportElementTextPattern::ExportElementTextPattern Constructor @param elmt */ ExportElementTextPattern::ExportElementTextPattern(Element *elmt) : m_element(elmt) { //Create the conf dir if not yet. QDir dir(QETApp::configDir()+ "/element_texts_pattern"); if(!dir.exists()) { dir.cdUp(); dir.mkdir("element_texts_pattern"); dir.cd("element_texts_pattern"); } //Get the name of conf bool ok; m_name = getName(ok); if(ok == false) return; //Check if a conf with the same name already exist if (QFileInfo::exists(dir.absoluteFilePath(m_name % ".xml"))) { bool r = QMessageBox::question(parentWidget(), QObject::tr("Text configuration"), QObject::tr("A text configuration named <<%1 >> already exists.\n" "Do you want to replace it ?").arg(m_name)); if(r == false) return; } QDomDocument doc = xmlConf(); QET::writeXmlFile(doc, dir.absoluteFilePath(m_name % ".xml")); } /** @brief ExportElementTextConf::getName Open a dialog to let user set the name of the conf and return it @return */ QString ExportElementTextPattern::getName(bool &ok) const { QString text = QInputDialog::getText(parentWidget(), QObject::tr("Configuration name"), QObject::tr("Enter the name of the configuration to create"), QLineEdit::Normal, QString(), &ok); text.replace(" ", "_"); text.replace(".",""); return text; } QWidget *ExportElementTextPattern::parentWidget() const { QWidget *parent = nullptr; if(m_element->scene() && !m_element->scene()->views().isEmpty()) parent = m_element->scene()->views().first(); return parent; } QDomDocument ExportElementTextPattern::xmlConf() const { QDomDocument doc; QDomElement root = doc.createElement("Element_texts_pattern"); root.setAttribute("name", m_name); doc.appendChild(root); QHash H; QDomElement elmt = m_element->toXml(doc, H); QDomElement texts = elmt.firstChildElement("dynamic_texts"); QDomElement groups = elmt.firstChildElement("texts_groups"); if(texts.tagName() == "dynamic_texts") root.appendChild(texts); if(groups.tagName() == "texts_groups") root.appendChild(groups); return doc; } //*******************// //******IMPORT*******// //*******************// ImportElementTextPattern::ImportElementTextPattern(Element *elmt): m_element(elmt) { bool exist = true; QDir dir(QETApp::configDir()+ "/element_texts_pattern"); if(!dir.exists()) exist = false; QStringList entry = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); QStringList result = entry.filter(".xml"); if(result.isEmpty()) exist = false; if(!exist) { QMessageBox::information( parentWidget(), QObject::tr("Text configuration"), QObject::tr("No existing text configuration.")); return; } bool ok=false; bool erase = false; //Remove the .xml extension of the files result.replaceInStrings(".xml", ""); QString name = getName(result, &ok, &erase); if(!ok || name.isEmpty()) return; else apply(name, erase); } /** @brief ImportElementTextPattern::getName Open a dialog to let user select a conf @param list @param ok @param erase @return */ QString ImportElementTextPattern::getName(const QStringList& list, bool *ok, bool *erase) const { return ImportElementTextPatternDialog::getItem( parentWidget(), QObject::tr("Select a text configuration"), QObject::tr("Select the text configuration to add to the element"), list, ok, erase); } QWidget *ImportElementTextPattern::parentWidget() const { QWidget *parent = nullptr; if(m_element->scene() && !m_element->scene()->views().isEmpty()) parent = m_element->scene()->views().first(); return parent; } /** @brief ImportElementTextPattern::apply Apply the user choice @param name : the name of the selected pattern @param erase : erase the existing texts and groups of element. */ void ImportElementTextPattern::apply(QString name, bool erase) const { if(!name.endsWith(".xml")) name.append(".xml"); QFile conf_file(QETApp::configDir() % "/element_texts_pattern/" % name); if(!conf_file.open(QIODevice::ReadOnly | QIODevice::Text)) return; //Load the content of the xml file QDomDocument xml_conf; if(!xml_conf.setContent(&conf_file)) return; QDomElement root = xml_conf.firstChildElement("Element_texts_pattern"); //Get the text QList texts = QET::findInDomElement(root, "dynamic_texts", "dynamic_elmt_text"); if (texts.isEmpty()) return; //Replace the original uuid of texts in the xml description, by a new one for every texts QHash uuid_hash; for(QDomElement text : texts) { QUuid original_uuid(text.attribute("uuid")); QUuid new_uuid = QUuid::createUuid(); text.setAttribute("uuid", new_uuid.toString()); uuid_hash.insert(original_uuid, new_uuid); } //In each group of the xml description, replace the original uuids, by the news created upper. QList groups = QET::findInDomElement(root, "texts_groups", "texts_group"); for(const QDomElement& group : groups) { for(QDomElement text : QET::findInDomElement(group, "texts", "text")) { QUuid original_uuid(text.attribute("uuid")); QUuid new_uuid = uuid_hash.value(original_uuid); if(!new_uuid.isNull()) text.setAttribute("uuid", new_uuid.toString()); } } QUndoStack &undo_stack = m_element->diagram()->undoStack(); undo_stack.beginMacro(QObject::tr("Import the text configuration : %1").arg(name.remove(".xml"))); //erase existing texts and groups if (erase) { for (ElementTextItemGroup *group : m_element->textGroups()) { undo_stack.push(new RemoveTextsGroupCommand(m_element, group)); } for (DynamicElementTextItem *deti : m_element->dynamicTextItems()) { DiagramContent dc; dc.m_element_texts << deti; undo_stack.push(new DeleteQGraphicsItemCommand(m_element->diagram(), dc)); } } //Add the texts to element for(const QDomElement& text : texts) { DynamicElementTextItem *deti = new DynamicElementTextItem(m_element); undo_stack.push(new AddElementTextCommand(m_element, deti)); deti->fromXml(text); } //Add the groups to element for(const QDomElement& xml_group : groups) undo_stack.push(new AddTextsGroupCommand(m_element, xml_group)); undo_stack.endMacro(); } //============================================================================= // ElementTextFormatPattern & ApplyElementTextFormatCommand (Format Painter) //============================================================================= #include "terminal.h" ElementTextFormatPattern ElementTextFormatPattern::s_current_pattern; bool ElementTextFormatPattern::s_has_pattern = false; bool ElementTextFormatPattern::hasPattern() { return s_has_pattern && s_current_pattern.isValid(); } const ElementTextFormatPattern &ElementTextFormatPattern::currentPattern() { return s_current_pattern; } void ElementTextFormatPattern::setCurrentPattern(const ElementTextFormatPattern &pattern) { s_current_pattern = pattern; s_has_pattern = pattern.isValid(); } ElementTextFormatPattern ElementTextFormatPattern::fromElement(Element *element) { ElementTextFormatPattern pattern; if (!element) return pattern; pattern.m_source_element_name = element->name(); // 1. Capture groups for (ElementTextItemGroup *group : element->textGroups()) { TextGroupFormatItem gItem; gItem.name = group->name(); gItem.pos = group->pos(); gItem.rotation = group->rotation(); gItem.alignment = group->alignment(); gItem.verticalAdjustment = group->verticalAdjustment(); gItem.frame = group->frame(); gItem.holdToBottomPage = group->holdToBottomPage(); pattern.m_groups.append(gItem); } // 2. Capture all dynamic texts int idx = 0; // Ungrouped texts for (DynamicElementTextItem *deti : element->dynamicTextItems()) { DynamicTextFormatItem tItem; tItem.textFrom = deti->textFrom(); tItem.infoName = deti->infoName(); tItem.compositeText = deti->toPlainText(); tItem.font = deti->font(); tItem.color = deti->color(); tItem.alignment = deti->alignment(); tItem.pos = deti->pos(); tItem.rotation = deti->rotation(); tItem.keepVisualRotation = deti->keepVisualRotation(); tItem.rotationPointCenter = deti->rotationPointCenter(); tItem.frame = deti->frame(); tItem.textWidth = deti->textWidth(); tItem.visible = ShownKinds::wantsVisible(deti); tItem.groupName = QString(); tItem.index = idx++; tItem.isTerminalText = isTerminalTextItem(deti, element); pattern.m_text_items.append(tItem); } // Grouped texts for (ElementTextItemGroup *group : element->textGroups()) { for (DynamicElementTextItem *deti : group->texts()) { DynamicTextFormatItem tItem; tItem.textFrom = deti->textFrom(); tItem.infoName = deti->infoName(); tItem.compositeText = deti->toPlainText(); tItem.font = deti->font(); tItem.color = deti->color(); tItem.alignment = deti->alignment(); tItem.pos = deti->pos(); tItem.rotation = deti->rotation(); tItem.keepVisualRotation = deti->keepVisualRotation(); tItem.rotationPointCenter = deti->rotationPointCenter(); tItem.frame = deti->frame(); tItem.textWidth = deti->textWidth(); tItem.visible = ShownKinds::wantsVisible(deti); tItem.groupName = group->name(); tItem.index = idx++; tItem.isTerminalText = isTerminalTextItem(deti, element); pattern.m_text_items.append(tItem); } } return pattern; } bool ElementTextFormatPattern::hasTerminalTextFormat() const { for (const DynamicTextFormatItem &item : m_text_items) { if (item.isTerminalText) return true; } return false; } DynamicTextFormatItem ElementTextFormatPattern::terminalTextFormat() const { for (const DynamicTextFormatItem &item : m_text_items) { if (item.isTerminalText) return item; } return DynamicTextFormatItem(); } bool ElementTextFormatPattern::isTerminalTextItem(const DynamicElementTextItem *deti, const Element *element) { if (!deti || !element) return false; if (deti->textFrom() != DynamicElementTextItem::UserText) return false; const QString text = deti->toPlainText().trimmed(); if (text.isEmpty()) return false; const QList terms = element->terminals(); for (const Terminal *t : terms) { if (!t) continue; if (t->name().trimmed() == text) { return true; } } // Secondary check: text is short (<= 6 chars, typical pin designation) // and located close to a terminal (within 40 pixels) if (text.length() <= 6) { for (const Terminal *t : terms) { if (!t) continue; QPointF diff = deti->pos() - t->pos(); if ((diff.x() * diff.x() + diff.y() * diff.y()) <= (40.0 * 40.0)) { return true; } } } return false; } ApplyElementTextFormatCommand::ApplyElementTextFormatCommand( Element *element, const ElementTextFormatPattern &pattern, QUndoCommand *parent) : ApplyElementTextFormatCommand(QList{element}, pattern, parent) { } ApplyElementTextFormatCommand::ApplyElementTextFormatCommand( const QList &elements, const ElementTextFormatPattern &pattern, QUndoCommand *parent) : QUndoCommand(parent) { setText(QObject::tr("Apply text format")); for (Element *element : elements) { if (!element) continue; ElementFormatChange elChange; elChange.element = element; // Collect all existing texts on target element QList targetTexts; targetTexts << element->dynamicTextItems(); for (ElementTextItemGroup *g : element->textGroups()) { targetTexts << g->texts(); } // Setup group changes for (const TextGroupFormatItem &gItem : pattern.groups()) { ElementFormatChange::GroupChange gc; gc.name = gItem.name; ElementTextItemGroup *existingGroup = element->textGroup(gItem.name); if (existingGroup) { gc.group = existingGroup; gc.wasCreated = false; gc.oldPos = existingGroup->pos(); gc.oldRotation = existingGroup->rotation(); gc.oldAlignment = existingGroup->alignment(); gc.oldVAdjust = existingGroup->verticalAdjustment(); gc.oldFrame = existingGroup->frame(); gc.oldHoldBottom = existingGroup->holdToBottomPage(); } else { gc.group = nullptr; gc.wasCreated = true; } gc.newPos = gItem.pos; gc.newRotation = gItem.rotation; gc.newAlignment = gItem.alignment; gc.newVAdjust = gItem.verticalAdjustment; gc.newFrame = gItem.frame; gc.newHoldBottom = gItem.holdToBottomPage; elChange.groupChanges.append(gc); } // Text matching QSet matchedTarget; QSet matchedPatternIndices; // Identify terminal / pole texts on the target element QSet targetTerminalTexts; for (DynamicElementTextItem *tDeti : targetTexts) { if (ElementTextFormatPattern::isTerminalTextItem(tDeti, element)) { targetTerminalTexts.insert(tDeti); } } // Handle Terminal / Pole texts: // They belong to physical terminals/poles of the element (e.g. 1..6, A1/A2, 13/14). // Their positions, rotations, frames and visibilities are NEVER modified! // If the pattern provides a terminal text style (font, color), apply it uniformly to all terminal texts. const bool hasTermFmt = pattern.hasTerminalTextFormat(); const DynamicTextFormatItem termFmt = hasTermFmt ? pattern.terminalTextFormat() : DynamicTextFormatItem(); for (DynamicElementTextItem *tDeti : targetTerminalTexts) { matchedTarget.insert(tDeti); // Exclude from general user text matching // Only record a change if font or color actually changes if (hasTermFmt && (tDeti->font() != termFmt.font || tDeti->color() != termFmt.color)) { ElementFormatChange::TextChange tc; tc.deti = tDeti; tc.wasCreated = false; tc.oldFont = tDeti->font(); tc.oldColor = tDeti->color(); tc.oldAlignment = tDeti->alignment(); tc.oldPos = tDeti->pos(); tc.oldRotation = tDeti->rotation(); tc.oldKeepVisualRotation = tDeti->keepVisualRotation(); tc.oldRotationPointCenter = tDeti->rotationPointCenter(); tc.oldFrame = tDeti->frame(); tc.oldTextWidth = tDeti->textWidth(); tc.oldVisible = ShownKinds::wantsVisible(tDeti); tc.oldGroupName = tDeti->parentGroup() ? tDeti->parentGroup()->name() : QString(); tc.newFont = termFmt.font; tc.newColor = termFmt.color; // Positions, alignments, rotations, frames and visibilities are strictly preserved on terminals! tc.newAlignment = tc.oldAlignment; tc.newPos = tc.oldPos; tc.newRotation = tc.oldRotation; tc.newKeepVisualRotation = tc.oldKeepVisualRotation; tc.newRotationPointCenter = tc.oldRotationPointCenter; tc.newFrame = tc.oldFrame; tc.newTextWidth = tc.oldTextWidth; tc.newVisible = tc.oldVisible; // Always retain visibility! tc.newGroupName = tc.oldGroupName; elChange.textChanges.append(tc); } } // Pass 1: ElementInfo with same infoName for (int i = 0; i < pattern.textItems().size(); ++i) { const DynamicTextFormatItem &pItem = pattern.textItems().at(i); if (pItem.textFrom == DynamicElementTextItem::ElementInfo && !pItem.infoName.isEmpty()) { for (DynamicElementTextItem *tDeti : targetTexts) { if (!matchedTarget.contains(tDeti) && tDeti->textFrom() == DynamicElementTextItem::ElementInfo && tDeti->infoName() == pItem.infoName) { matchedTarget.insert(tDeti); matchedPatternIndices.insert(i); ElementFormatChange::TextChange tc; tc.deti = tDeti; tc.wasCreated = false; tc.oldFont = tDeti->font(); tc.oldColor = tDeti->color(); tc.oldAlignment = tDeti->alignment(); tc.oldPos = tDeti->pos(); tc.oldRotation = tDeti->rotation(); tc.oldKeepVisualRotation = tDeti->keepVisualRotation(); tc.oldRotationPointCenter = tDeti->rotationPointCenter(); tc.oldFrame = tDeti->frame(); tc.oldTextWidth = tDeti->textWidth(); tc.oldVisible = ShownKinds::wantsVisible(tDeti); tc.oldGroupName = tDeti->parentGroup() ? tDeti->parentGroup()->name() : QString(); tc.newFont = pItem.font; tc.newColor = pItem.color; tc.newAlignment = pItem.alignment; tc.newPos = pItem.pos; tc.newRotation = pItem.rotation; tc.newKeepVisualRotation = pItem.keepVisualRotation; tc.newRotationPointCenter = pItem.rotationPointCenter; tc.newFrame = pItem.frame; tc.newTextWidth = pItem.textWidth; tc.newVisible = pItem.visible; tc.newGroupName = pItem.groupName; elChange.textChanges.append(tc); break; } } } } // Pass 2: UserText (non-terminal texts only) for (int i = 0; i < pattern.textItems().size(); ++i) { if (matchedPatternIndices.contains(i)) continue; const DynamicTextFormatItem &pItem = pattern.textItems().at(i); if (pItem.isTerminalText) continue; // Skip terminal texts in generic user text matching if (pItem.textFrom == DynamicElementTextItem::UserText) { DynamicElementTextItem *foundDeti = nullptr; for (DynamicElementTextItem *tDeti : targetTexts) { if (!matchedTarget.contains(tDeti) && tDeti->textFrom() == DynamicElementTextItem::UserText && !pItem.compositeText.isEmpty() && tDeti->toPlainText() == pItem.compositeText) { foundDeti = tDeti; break; } } if (!foundDeti) { for (DynamicElementTextItem *tDeti : targetTexts) { if (!matchedTarget.contains(tDeti) && tDeti->textFrom() == DynamicElementTextItem::UserText) { foundDeti = tDeti; break; } } } if (foundDeti) { matchedTarget.insert(foundDeti); matchedPatternIndices.insert(i); ElementFormatChange::TextChange tc; tc.deti = foundDeti; tc.wasCreated = false; tc.oldFont = foundDeti->font(); tc.oldColor = foundDeti->color(); tc.oldAlignment = foundDeti->alignment(); tc.oldPos = foundDeti->pos(); tc.oldRotation = foundDeti->rotation(); tc.oldKeepVisualRotation = foundDeti->keepVisualRotation(); tc.oldRotationPointCenter = foundDeti->rotationPointCenter(); tc.oldFrame = foundDeti->frame(); tc.oldTextWidth = foundDeti->textWidth(); tc.oldVisible = ShownKinds::wantsVisible(foundDeti); tc.oldGroupName = foundDeti->parentGroup() ? foundDeti->parentGroup()->name() : QString(); tc.newFont = pItem.font; tc.newColor = pItem.color; tc.newAlignment = pItem.alignment; tc.newPos = pItem.pos; tc.newRotation = pItem.rotation; tc.newKeepVisualRotation = pItem.keepVisualRotation; tc.newRotationPointCenter = pItem.rotationPointCenter; tc.newFrame = pItem.frame; tc.newTextWidth = pItem.textWidth; tc.newVisible = pItem.visible; tc.newGroupName = pItem.groupName; elChange.textChanges.append(tc); } } } // Pass 3: CompositeText or other unmatched (non-terminal) for (int i = 0; i < pattern.textItems().size(); ++i) { if (matchedPatternIndices.contains(i)) continue; const DynamicTextFormatItem &pItem = pattern.textItems().at(i); if (pItem.isTerminalText) continue; DynamicElementTextItem *foundDeti = nullptr; for (DynamicElementTextItem *tDeti : targetTexts) { if (!matchedTarget.contains(tDeti) && tDeti->textFrom() == pItem.textFrom) { foundDeti = tDeti; break; } } if (foundDeti) { matchedTarget.insert(foundDeti); matchedPatternIndices.insert(i); ElementFormatChange::TextChange tc; tc.deti = foundDeti; tc.wasCreated = false; tc.oldFont = foundDeti->font(); tc.oldColor = foundDeti->color(); tc.oldAlignment = foundDeti->alignment(); tc.oldPos = foundDeti->pos(); tc.oldRotation = foundDeti->rotation(); tc.oldKeepVisualRotation = foundDeti->keepVisualRotation(); tc.oldRotationPointCenter = foundDeti->rotationPointCenter(); tc.oldFrame = foundDeti->frame(); tc.oldTextWidth = foundDeti->textWidth(); tc.oldVisible = ShownKinds::wantsVisible(foundDeti); tc.oldGroupName = foundDeti->parentGroup() ? foundDeti->parentGroup()->name() : QString(); tc.newFont = pItem.font; tc.newColor = pItem.color; tc.newAlignment = pItem.alignment; tc.newPos = pItem.pos; tc.newRotation = pItem.rotation; tc.newKeepVisualRotation = pItem.keepVisualRotation; tc.newRotationPointCenter = pItem.rotationPointCenter; tc.newFrame = pItem.frame; tc.newTextWidth = pItem.textWidth; tc.newVisible = pItem.visible; tc.newGroupName = pItem.groupName; elChange.textChanges.append(tc); } } // Pass 4: Pattern ElementInfo items that don't exist in target element for (int i = 0; i < pattern.textItems().size(); ++i) { if (matchedPatternIndices.contains(i)) continue; const DynamicTextFormatItem &pItem = pattern.textItems().at(i); if (pItem.textFrom == DynamicElementTextItem::ElementInfo && !pItem.infoName.isEmpty()) { ElementFormatChange::TextChange tc; tc.deti = nullptr; tc.wasCreated = true; tc.newFont = pItem.font; tc.newColor = pItem.color; tc.newAlignment = pItem.alignment; tc.newPos = pItem.pos; tc.newRotation = pItem.rotation; tc.newKeepVisualRotation = pItem.keepVisualRotation; tc.newRotationPointCenter = pItem.rotationPointCenter; tc.newFrame = pItem.frame; tc.newTextWidth = pItem.textWidth; tc.newVisible = pItem.visible; tc.newGroupName = pItem.groupName; tc.oldGroupName = pItem.infoName; // infoName stored temporarily in oldGroupName elChange.textChanges.append(tc); } } // Pass 5 (Destructive hiding of unmatched texts) is completely removed: // Non-matching target texts (such as additional poles on 3-pole/4-pole elements) // are never hidden and retain their exact positions and visibilities! m_changes.append(elChange); } } ApplyElementTextFormatCommand::~ApplyElementTextFormatCommand() { for (ElementFormatChange &elChange : m_changes) { Element *element = elChange.element.data(); for (ElementFormatChange::TextChange &tc : elChange.textChanges) { if (tc.wasCreated && tc.deti) { DynamicElementTextItem *deti = tc.deti.data(); if (!deti) continue; if (deti->parentGroup()) continue; if (!element || !element->dynamicTextItems().contains(deti)) { delete deti; } } } for (ElementFormatChange::GroupChange &gc : elChange.groupChanges) { if (gc.wasCreated && gc.group) { ElementTextItemGroup *group = gc.group.data(); if (!group) continue; if (!element || !element->textGroups().contains(group)) { delete group; } } } } } void ApplyElementTextFormatCommand::redo() { for (ElementFormatChange &elChange : m_changes) { Element *element = elChange.element.data(); if (!element) continue; // 1. Create and update groups for (ElementFormatChange::GroupChange &gc : elChange.groupChanges) { ElementTextItemGroup *group = gc.group.data(); if (!group) { group = element->textGroup(gc.name); if (!group) { group = element->addTextGroup(gc.name); } gc.group = group; } else { if (!element->textGroups().contains(group)) { element->addTextGroup(group); } } if (group) { group->setPos(gc.newPos); group->setRotation(gc.newRotation); group->setAlignment(gc.newAlignment); group->setVerticalAdjustment(gc.newVAdjust); group->setFrame(gc.newFrame); group->setHoldToBottomPage(gc.newHoldBottom); } } // 2. Apply texts for (ElementFormatChange::TextChange &tc : elChange.textChanges) { DynamicElementTextItem *deti = tc.deti.data(); if (tc.wasCreated) { if (!deti) { deti = new DynamicElementTextItem(element); deti->setTextFrom(DynamicElementTextItem::ElementInfo); deti->setInfoName(tc.oldGroupName); element->addDynamicTextItem(deti); tc.deti = deti; } else { deti->setParentItem(element); if (!element->dynamicTextItems().contains(deti)) { element->addDynamicTextItem(deti); } } } if (!deti) continue; // Group membership if (!tc.newGroupName.isEmpty()) { ElementTextItemGroup *targetGroup = element->textGroup(tc.newGroupName); if (targetGroup) { if (deti->parentGroup() != targetGroup) { if (deti->parentGroup()) { element->removeTextFromGroup(deti, deti->parentGroup()); } element->addTextToGroup(deti, targetGroup); } } } else { if (deti->parentGroup()) { element->removeTextFromGroup(deti, deti->parentGroup()); } } // Apply properties deti->setFont(tc.newFont); deti->setColor(tc.newColor); deti->setAlignment(tc.newAlignment); deti->setPos(tc.newPos); deti->setRotation(tc.newRotation); deti->setKeepVisualRotation(tc.newKeepVisualRotation); deti->setRotationPointCenter(tc.newRotationPointCenter); deti->setFrame(tc.newFrame); deti->setTextWidth(tc.newTextWidth); ShownKinds::setVisible(deti, tc.newVisible); deti->update(); } element->update(); if (element->diagram()) { element->diagram()->update(); } } } void ApplyElementTextFormatCommand::undo() { for (ElementFormatChange &elChange : m_changes) { Element *element = elChange.element.data(); if (!element) continue; // 1. Restore texts for (ElementFormatChange::TextChange &tc : elChange.textChanges) { DynamicElementTextItem *deti = tc.deti.data(); if (!deti) continue; if (tc.wasCreated) { if (deti->parentGroup()) { element->removeTextFromGroup(deti, deti->parentGroup()); } element->removeDynamicTextItem(deti); if (deti->scene()) { deti->scene()->removeItem(deti); } continue; } // Restore group membership if (!tc.oldGroupName.isEmpty()) { ElementTextItemGroup *origGroup = element->textGroup(tc.oldGroupName); if (origGroup) { if (deti->parentGroup() != origGroup) { if (deti->parentGroup()) { element->removeTextFromGroup(deti, deti->parentGroup()); } element->addTextToGroup(deti, origGroup); } } } else { if (deti->parentGroup()) { element->removeTextFromGroup(deti, deti->parentGroup()); } } // Restore properties deti->setFont(tc.oldFont); deti->setColor(tc.oldColor); deti->setAlignment(tc.oldAlignment); deti->setPos(tc.oldPos); deti->setRotation(tc.oldRotation); deti->setKeepVisualRotation(tc.oldKeepVisualRotation); deti->setRotationPointCenter(tc.oldRotationPointCenter); deti->setFrame(tc.oldFrame); deti->setTextWidth(tc.oldTextWidth); ShownKinds::setVisible(deti, tc.oldVisible); deti->update(); } // 2. Restore groups for (ElementFormatChange::GroupChange &gc : elChange.groupChanges) { ElementTextItemGroup *group = gc.group.data(); if (!group) continue; if (gc.wasCreated) { element->removeTextGroup(group); continue; } group->setPos(gc.oldPos); group->setRotation(gc.oldRotation); group->setAlignment(gc.oldAlignment); group->setVerticalAdjustment(gc.oldVAdjust); group->setFrame(gc.oldFrame); group->setHoldToBottomPage(gc.oldHoldBottom); } element->update(); if (element->diagram()) { element->diagram()->update(); } } }