AutoNum: a lot of changes

Schemas now get saved with a UUID and do not have to be looked up according to the form. They can be renamed and it gets reflected in existing elements. The GUI for
editing them is now much easier, e.g. forms can be reshuffled. A formula can be applied. Many options now also for conductors and sheets.
This commit is contained in:
Andre Rummler
2026-10-03 17:53:27 +02:00
parent 389fd454da
commit d76f3528ae
52 changed files with 6040 additions and 382 deletions
+256 -110
View File
@@ -23,6 +23,7 @@
#include "autoNum/numerotationcontext.h"
#include "autoNum/numerotationcontextcommands.h"
#include "autoNum/renumberelementscommand.h"
#include "autoNum/elementautonumschemecommand.h"
#include "diagram.h"
#include "qetgraphicsitem/element.h"
#include "qetapp.h"
@@ -35,6 +36,7 @@
#include "ui/importelementdialog.h"
#include "TerminalStrip/terminalstrip.h"
#include "qetxml.h"
#include "qetinformation.h"
#include "qetversion.h"
#include "undocommand/adddiagramcommand.h"
@@ -43,27 +45,6 @@
#include <QTimer>
#include <QtConcurrentRun>
namespace {
/**
* @brief Reset numeric fields of a NumerotationContext so renumbering starts at 1.
* Keeps non-numeric parts (string/idfolio/folio/plant/locmach/elementline/elementcolumn/elementprefix) unchanged.
*/
NumerotationContext resetContextForRenumber(const NumerotationContext &tmpl)
{
NumerotationContext out = tmpl;
for (int i = 0; i < out.size(); ++i) {
const QStringList parts = out.itemAt(i);
if (parts.isEmpty()) continue;
const QString type = parts.at(0);
if (out.keyIsNumber(type)) {
out.replaceValue(i, QStringLiteral("1"));
}
}
return out;
}
} // namespace
#include <QtDebug>
#include <algorithm>
#include <utility>
@@ -884,9 +865,8 @@ void QETProject::setCurrrentElementAutonum(QString autoNum) {
@brief QETProject::renumberElementsBySchemeTitle
Renumber existing elements by element autonumbering scheme title.
Elements do not store the scheme title; they store a "formula" (elementInformations["formula"]).
This method matches elements to schemes by comparing the stored formula with the formula derived
from each scheme's NumerotationContext.
Elements follow a scheme by its uuid (elementInformations["formula_id"]),
see elementsUsingElementAutoNum().
If scheme_title is empty, all schemes are renumbered. Otherwise only that scheme is renumbered.
The operation is undoable.
@@ -894,93 +874,14 @@ void QETProject::setCurrrentElementAutonum(QString autoNum) {
void QETProject::renumberElementsBySchemeTitle(const QString &scheme_title)
{
if (!m_undo_stack) return;
if (isReadOnly()) return;
// Build map: scheme title -> canonical formula
QHash<QString, QString> scheme_formula;
for (const QString &k : m_element_autonum.keys()) {
if (!scheme_title.isEmpty() && k != scheme_title) continue;
scheme_formula.insert(k, autonum::numerotationContextToFormula(m_element_autonum.value(k)));
auto *cmd = ElementAutoNumSchemeCommand::renumber(
this, scheme_title, nullptr,
scheme_title.isEmpty() ? tr("Renumber elements")
: tr("Renumber elements (%1)").arg(scheme_title));
if (cmd) {
m_undo_stack->push(cmd);
}
if (scheme_formula.isEmpty()) return;
// Collect elements per scheme by formula match
QHash<QString, QVector<Element*>> by_key;
for (Diagram *d : diagrams()) {
if (!d) continue;
const auto items = d->items();
for (QGraphicsItem *it : items) {
auto *el = qgraphicsitem_cast<Element*>(it);
if (!el) continue;
if (el->linkType() == Element::Slave || (el->linkType() & Element::AllReport))
continue;
const QString el_formula = el->elementInformations().value(QStringLiteral("formula")).toString();
if (el_formula.isEmpty()) continue;
QString matched_key;
for (auto itf = scheme_formula.constBegin(); itf != scheme_formula.constEnd(); ++itf) {
if (itf.value() == el_formula) { matched_key = itf.key(); break; }
}
if (matched_key.isEmpty()) continue;
by_key[matched_key].append(el);
}
}
if (by_key.isEmpty()) return;
QVector<RenumberElementsCommand::ElementChange> changes;
QHash<QString, NumerotationContext> old_ctx;
QHash<QString, NumerotationContext> new_ctx;
for (auto it = by_key.constBegin(); it != by_key.constEnd(); ++it) {
old_ctx.insert(it.key(), m_element_autonum.value(it.key()));
}
for (auto it = by_key.begin(); it != by_key.end(); ++it) {
const QString key = it.key();
auto &elements = it.value();
std::sort(elements.begin(), elements.end(), [](Element *a, Element *b){ return comparPos(a, b); });
NumerotationContext base_tmpl = m_element_autonum.value(key);
NumerotationContext nc = resetContextForRenumber(base_tmpl);
NumerotationContextCommands ncc(nc);
for (Element *el : elements) {
RenumberElementsCommand::ElementChange ch;
ch.element = el;
ch.old_infos = el->elementInformations();
ch.old_seq = el->sequenceStruct();
ch.old_frozen = el->isFreezeLabel();
ch.new_frozen = ch.old_frozen; // preserve frozen state
const QString formula = ch.old_infos.value(QStringLiteral("formula")).toString();
autonum::sequentialNumbers new_seq;
new_seq.clear();
autonum::setSequential(formula, new_seq, nc, el->diagram(), key);
DiagramContext new_infos = ch.old_infos;
new_infos.addValue(QStringLiteral("label"), autonum::AssignVariables::formulaToLabel(formula, new_seq, el->diagram(), el, nullptr));
ch.new_infos = new_infos;
ch.new_seq = new_seq;
changes.append(ch);
// advance
nc = ncc.next();
ncc = NumerotationContextCommands(nc);
}
new_ctx.insert(key, nc);
}
if (changes.isEmpty()) return;
auto *cmd = new RenumberElementsCommand(
this,
changes,
old_ctx,
new_ctx,
scheme_title.isEmpty() ? tr("Renumber elements") : tr("Renumber elements (%1)").arg(scheme_title));
m_undo_stack->push(cmd);
}
/**
@@ -1043,11 +944,232 @@ void QETProject::addConductorAutoNum(const QString& key, const NumerotationConte
*/
void QETProject::addElementAutoNum(const QString& key, const NumerotationContext& context)
{
addElementAutoNum(key, context, QUuid());
}
/**
@brief QETProject::addElementAutoNum
Add or replace the element numbering scheme @p key.
A scheme that already exists keeps its uuid unless @p id is given;
a new one takes @p id, or a new uuid when @p id is null or already
used by another scheme.
@param key : title of the scheme
@param context : its numerotation context
@param id : its uuid, null to keep or create one
*/
void QETProject::addElementAutoNum(const QString &key,
const NumerotationContext &context,
const QUuid &id)
{
QUuid scheme_id = id;
if (!scheme_id.isNull()) {
const QString owner = elementAutoNumTitle(scheme_id);
if (!owner.isEmpty() && owner != key) {
scheme_id = QUuid();
}
}
if (scheme_id.isNull()) {
scheme_id = m_element_autonum_id.value(key);
}
if (scheme_id.isNull()) {
scheme_id = QUuid::createUuid();
}
m_element_autonum_id.insert(key, scheme_id);
m_element_autonum.insert(key, context);
emit elementAutoNumAdded(key);
emit autoNumContextUpdated();
}
/**
@brief QETProject::elementAutoNumId
@return the uuid of the element numbering scheme @p title, null if
there is no such scheme
*/
QUuid QETProject::elementAutoNumId(const QString &title) const
{
return m_element_autonum_id.value(title);
}
/**
@brief QETProject::elementAutoNumTitle
@return the title of the element numbering scheme with uuid @p id,
empty if there is none
*/
QString QETProject::elementAutoNumTitle(const QUuid &id) const
{
if (id.isNull()) {
return QString();
}
for (auto it = m_element_autonum_id.constBegin();
it != m_element_autonum_id.constEnd(); ++it) {
if (it.value() == id) {
return it.key();
}
}
return QString();
}
/**
@brief QETProject::renameElementAutoNum
Give the element numbering scheme @p old_title the title @p new_title.
It keeps its uuid, so the elements following it are not touched;
the folios' per-scheme maxima of folio sequential numbers and the
project's current scheme follow the new title.
Not undoable by itself: see ElementAutoNumSchemeCommand.
@return false if there is no scheme @p old_title or a scheme
@p new_title already exists
*/
bool QETProject::renameElementAutoNum(const QString &old_title, const QString &new_title)
{
if (old_title == new_title) {
return m_element_autonum.contains(old_title);
}
if (!m_element_autonum.contains(old_title)
|| m_element_autonum.contains(new_title)
|| new_title.isEmpty()) {
return false;
}
m_element_autonum.insert(new_title, m_element_autonum.take(old_title));
m_element_autonum_id.insert(new_title, m_element_autonum_id.take(old_title));
if (m_current_element_autonum == old_title) {
m_current_element_autonum = new_title;
}
for (Diagram *d : std::as_const(m_diagrams_list)) {
if (!d) continue;
for (auto *hash : {&d->m_elmt_unitfolio_max,
&d->m_elmt_tenfolio_max,
&d->m_elmt_hundredfolio_max}) {
if (hash->contains(old_title)) {
hash->insert(new_title, hash->take(old_title));
}
}
}
emit elementAutoNumRemoved(old_title);
emit elementAutoNumAdded(new_title);
emit autoNumContextUpdated();
return true;
}
/**
@brief QETProject::normalizedAutoNumName
@return @p name as it is stored as the title of a numbering scheme
*/
QString QETProject::normalizedAutoNumName(const QString &name)
{
return name.simplified();
}
/**
@brief QETProject::elementAutoNumNameClash
Two element numbering schemes may not have the same name, compared
without regard to case or surrounding white space, so that the name
alone identifies a scheme for the user and for scripts.
@param name : the name to check
@param ignored_title : a scheme not to compare with (the one being
renamed)
@return the title of the existing scheme @p name clashes with, empty
if none
*/
QString QETProject::elementAutoNumNameClash(const QString &name,
const QString &ignored_title) const
{
const QString wanted = normalizedAutoNumName(name);
for (auto it = m_element_autonum.constBegin();
it != m_element_autonum.constEnd(); ++it) {
if (it.key() == ignored_title) {
continue;
}
if (QString::compare(normalizedAutoNumName(it.key()), wanted,
Qt::CaseInsensitive) == 0) {
return it.key();
}
}
return QString();
}
/**
@brief QETProject::elementsUsingElementAutoNum
@return the elements whose label follows the element numbering
scheme @p title, in no particular order
*/
QVector<Element *> QETProject::elementsUsingElementAutoNum(const QString &title) const
{
QVector<Element *> list;
const QUuid id = elementAutoNumId(title);
if (id.isNull()) {
return list;
}
for (Diagram *d : m_diagrams_list) {
if (!d) continue;
const auto items = d->items();
for (QGraphicsItem *it : items) {
auto *el = qgraphicsitem_cast<Element *>(it);
if (!el) continue;
const DiagramContext &info = el->elementInformations();
if (info.value(QETInformation::ELMT_FORMULA).toString().isEmpty()) {
continue;
}
if (QUuid(info.value(QETInformation::ELMT_FORMULA_ID).toString()) == id) {
list << el;
}
}
}
return list;
}
/**
@brief QETProject::linkElementsToElementAutoNums
Called once the diagrams of a file are loaded. Makes every element's
ELMT_FORMULA_ID name an element numbering scheme of this project:
- an id naming one of the schemes is kept;
- an id naming none (the scheme was renamed by a version of
QElectroTech that lost the ids, or the element was pasted from
another project) and, in a file written before the ids existed,
an element with no id at all, are linked to the one scheme whose
formula is the element's formula; with no such scheme, or more than
one, the element is left unlinked.
The label is not touched: nothing it is built from changes.
*/
void QETProject::linkElementsToElementAutoNums()
{
QHash<QString, QStringList> titles_by_formula;
for (auto it = m_element_autonum.constBegin();
it != m_element_autonum.constEnd(); ++it) {
titles_by_formula[autonum::numerotationContextToFormula(it.value())] << it.key();
}
for (Diagram *d : std::as_const(m_diagrams_list)) {
if (!d) continue;
const auto items = d->items();
for (QGraphicsItem *it : items) {
auto *el = qgraphicsitem_cast<Element *>(it);
if (!el) continue;
const DiagramContext &info = el->elementInformations();
const QString formula = info.value(QETInformation::ELMT_FORMULA).toString();
const bool has_id = info.contains(QETInformation::ELMT_FORMULA_ID);
const QUuid id(info.value(QETInformation::ELMT_FORMULA_ID).toString());
if (formula.isEmpty()) {
if (has_id) el->setFormulaSchemeId(QUuid());
continue;
}
if (!elementAutoNumTitle(id).isEmpty()) {
continue;
}
if (!has_id && !m_legacy_element_autonums) {
continue; //A formula typed by hand
}
const QStringList matches = titles_by_formula.value(formula);
el->setFormulaSchemeId(matches.size() == 1
? elementAutoNumId(matches.first())
: QUuid());
}
}
}
/**
@brief QETProject::addFolioAutoNum
Add a new folio numerotation context. If key already exist,
@@ -1077,6 +1199,7 @@ void QETProject::removeConductorAutoNum(const QString& key) {
void QETProject::removeElementAutoNum(const QString& key)
{
m_element_autonum.remove(key);
m_element_autonum_id.remove(key);
emit elementAutoNumRemoved(key);
}
@@ -1934,6 +2057,9 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
readDiagramsXml(xml_project);
const qint64 diagrams_ms = phase_timer.restart();
//Tie the elements to the numbering schemes they follow
linkElementsToElementAutoNums();
//Load the terminal strip
readTerminalStripXml(xml_project);
const qint64 strips_ms = phase_timer.restart();
@@ -2206,7 +2332,22 @@ void QETProject::readDefaultPropertiesXml(QDomDocument &xml_project)
{
NumerotationContext nc;
nc.fromXml(elmt);
m_element_autonum.insert(elmt.attribute(QStringLiteral("title")), nc);
const QString title = elmt.attribute(QStringLiteral("title"));
QUuid id(elmt.attribute(QStringLiteral("id")));
if (id.isNull() || !elementAutoNumTitle(id).isEmpty()) {
//Saved before schemes had an id: derive one, the
//same on every load of the file.
m_legacy_element_autonums = true;
id = derivedItemUuid(QStringLiteral("element_autonum"), title);
}
m_element_autonum.insert(title, nc);
m_element_autonum_id.insert(title, id);
}
//The id is authoritative, the title is kept for older versions
const QString current_title = elementAutoNumTitle(
QUuid(element_autonums.attribute(QStringLiteral("current_autonum_id"))));
if (!current_title.isEmpty()) {
m_current_element_autonum = current_title;
}
}
// Read guides from XML (if missing, e.g. in old projects, list stays empty)
@@ -2372,6 +2513,10 @@ void QETProject::writeDefaultPropertiesXml(QDomElement &xml_element)
//Export Element Autonums
QDomElement element_autonums = xml_document.createElement("element_autonums");
element_autonums.setAttribute("current_autonum", m_current_element_autonum);
if (!elementAutoNumId(m_current_element_autonum).isNull()) {
element_autonums.setAttribute("current_autonum_id",
elementAutoNumId(m_current_element_autonum).toString());
}
element_autonums.setAttribute("freeze_new_elements", m_freeze_new_elements ? "true" : "false");
QStringList element_autonum_keys = elementAutoNum().keys();
element_autonum_keys.sort();
@@ -2379,6 +2524,7 @@ void QETProject::writeDefaultPropertiesXml(QDomElement &xml_element)
QDomElement element_autonum = elementAutoNum(key).toXml(xml_document, "element_autonum");
if (key != "" && elementAutoNumFormula(key) != "") {
element_autonum.setAttribute("title", key);
element_autonum.setAttribute("id", elementAutoNumId(key).toString());
element_autonum.setAttribute("formula", elementAutoNumFormula(key));
element_autonums.appendChild(element_autonum);
}