mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-06 03:04:13 +02:00
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:
+256
-110
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user