mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-05 18:54:14 +02:00
1292 lines
42 KiB
C++
1292 lines
42 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 "elementautonumschemecommand.h"
|
|
|
|
#include "assignvariables.h"
|
|
#include "numerotationcontextcommands.h"
|
|
#include "../diagram.h"
|
|
#include "../qetgraphicsitem/element.h"
|
|
#include "../qetinformation.h"
|
|
#include "../qetproject.h"
|
|
|
|
#include <QRegularExpression>
|
|
|
|
#include <algorithm>
|
|
#include <optional>
|
|
|
|
namespace {
|
|
bool sameContext(const NumerotationContext &a, const NumerotationContext &b)
|
|
{
|
|
if (a.size() != b.size()) return false;
|
|
for (int i = 0; i < a.size(); ++i) {
|
|
if (a[i] != b[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
ElementAutoNumSchemeCommand::ElementAutoNumSchemeCommand(QETProject *project) :
|
|
m_project(project)
|
|
{}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::nameProblem
|
|
@return why @p name cannot be the name of an element numbering scheme
|
|
of @p project, translated, or an empty string if it can
|
|
@param ignored_title : the scheme being renamed, if any
|
|
*/
|
|
QString ElementAutoNumSchemeCommand::nameProblem(const QETProject *project,
|
|
const QString &name,
|
|
const QString &ignored_title)
|
|
{
|
|
if (QETProject::normalizedAutoNumName(name).isEmpty()) {
|
|
return tr("Le nom de la numérotation ne peut pas être vide.");
|
|
}
|
|
if (!project) {
|
|
return QString();
|
|
}
|
|
const QString clash = project->elementAutoNumNameClash(name, ignored_title);
|
|
if (!clash.isEmpty()) {
|
|
return tr("Une numérotation nommée « %1 » existe déjà.").arg(clash);
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::sameSequentialParts
|
|
@return true if both formulas use the same sequential numbers
|
|
(%sequ_1, %seqtf_2...), so the numbers an element holds for one still
|
|
fit the other
|
|
*/
|
|
bool ElementAutoNumSchemeCommand::sameSequentialParts(const QString &formula_a,
|
|
const QString &formula_b)
|
|
{
|
|
static const QRegularExpression rx(QStringLiteral("%seq[a-z]+_\\d+"));
|
|
const auto tokens = [](const QString &formula) {
|
|
QStringList list;
|
|
auto it = rx.globalMatch(formula);
|
|
while (it.hasNext()) {
|
|
list << it.next().captured(0);
|
|
}
|
|
list.sort();
|
|
list.removeDuplicates();
|
|
return list;
|
|
};
|
|
return tokens(formula_a) == tokens(formula_b);
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::resetForRenumber
|
|
@return @p context with every number part set back to 1, the other
|
|
parts unchanged, to number elements again from the start
|
|
*/
|
|
NumerotationContext ElementAutoNumSchemeCommand::resetForRenumber(const NumerotationContext &context)
|
|
{
|
|
NumerotationContext out = context;
|
|
for (int i = 0; i < out.size(); ++i) {
|
|
const QStringList parts = out.itemAt(i);
|
|
if (parts.isEmpty()) continue;
|
|
if (out.keyIsNumber(parts.at(0))) {
|
|
out.replaceValue(i, QStringLiteral("1"));
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::frozenFollowers
|
|
@return the elements with a frozen label which follow the element
|
|
numbering scheme @p title
|
|
*/
|
|
QVector<Element *> ElementAutoNumSchemeCommand::frozenFollowers(
|
|
const QETProject *project,
|
|
const QString &title)
|
|
{
|
|
QVector<Element *> list;
|
|
if (!project) {
|
|
return list;
|
|
}
|
|
const auto followers = project->elementsUsingElementAutoNum(title);
|
|
for (Element *el : followers) {
|
|
if (el->isFreezeLabel()) {
|
|
list << el;
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::editBlockedBy
|
|
@return the elements with a frozen label which stop the element
|
|
numbering scheme @p title from being given the definition @p context:
|
|
none when its formula stays the same (a rename, a change of the
|
|
counter), otherwise every frozen follower
|
|
*/
|
|
QVector<Element *> ElementAutoNumSchemeCommand::editBlockedBy(
|
|
const QETProject *project,
|
|
const QString &title,
|
|
const NumerotationContext &context)
|
|
{
|
|
if (!project || !project->elementAutoNum().contains(title)) {
|
|
return {};
|
|
}
|
|
if (project->elementAutoNumFormula(title)
|
|
== autonum::numerotationContextToFormula(context)) {
|
|
return {};
|
|
}
|
|
return frozenFollowers(project, title);
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::renumberChanges
|
|
Number @p elements again, in folio and position order, with @p context
|
|
started from 1, and give them @p formula.
|
|
@param hash_key : key of the scheme in the folios' maxima of folio
|
|
sequential numbers (its title)
|
|
@param final_context : if not null, receives the context after the
|
|
last element, to store as the scheme's counter
|
|
@return one change per element
|
|
*/
|
|
QVector<RenumberElementsCommand::ElementChange> ElementAutoNumSchemeCommand::renumberChanges(
|
|
const QString &hash_key,
|
|
const NumerotationContext &context,
|
|
QVector<Element *> elements,
|
|
const QString &formula,
|
|
NumerotationContext *final_context,
|
|
bool from_start,
|
|
const QUuid &scheme_id,
|
|
const QSet<QString> &reserved)
|
|
{
|
|
QVector<RenumberElementsCommand::ElementChange> changes;
|
|
std::sort(elements.begin(), elements.end(),
|
|
[](Element *a, Element *b){ return comparPos(a, b); });
|
|
|
|
NumerotationContext nc = from_start ? resetForRenumber(context) : context;
|
|
for (Element *el : std::as_const(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;
|
|
|
|
//The next number which does not give the element a label an
|
|
//element left as it is already has. A numbering whose number
|
|
//parts cannot move on (fixed text only) has nothing to skip
|
|
//to: it keeps its label.
|
|
autonum::sequentialNumbers new_seq;
|
|
QString label;
|
|
for (int attempt = 0 ; ; ++attempt)
|
|
{
|
|
new_seq = autonum::sequentialNumbers();
|
|
autonum::setSequential(formula, new_seq, nc, el->diagram(), hash_key);
|
|
label = autonum::AssignVariables::formulaToLabel(
|
|
formula, new_seq, el->diagram(), el, nullptr);
|
|
if (label.isEmpty() || !reserved.contains(label) || attempt >= 100000) {
|
|
break;
|
|
}
|
|
NumerotationContextCommands skip(nc);
|
|
const NumerotationContext advanced = skip.next();
|
|
if (sameContext(advanced, nc)) {
|
|
break;
|
|
}
|
|
nc = advanced;
|
|
}
|
|
|
|
DiagramContext new_infos = ch.old_infos;
|
|
new_infos.addValue(QETInformation::ELMT_FORMULA, formula);
|
|
if (!scheme_id.isNull()) {
|
|
new_infos.addValue(QETInformation::ELMT_FORMULA_ID,
|
|
scheme_id.toString(), false);
|
|
}
|
|
new_infos.addValue(QETInformation::ELMT_LABEL, label);
|
|
ch.new_infos = new_infos;
|
|
ch.new_seq = new_seq;
|
|
changes << ch;
|
|
|
|
NumerotationContextCommands ncc(nc);
|
|
nc = ncc.next();
|
|
}
|
|
if (final_context) {
|
|
*final_context = nc;
|
|
}
|
|
return changes;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::reservedLabels
|
|
@return the labels the elements following the scheme @p title keep
|
|
while @p changing change: numbers an operation on @p changing must not
|
|
give again, or two elements would carry the same label
|
|
*/
|
|
QSet<QString> ElementAutoNumSchemeCommand::reservedLabels(
|
|
const QETProject *project,
|
|
const QString &title,
|
|
const QVector<Element *> &changing)
|
|
{
|
|
QSet<QString> labels;
|
|
if (!project) {
|
|
return labels;
|
|
}
|
|
const QSet<const Element *> moving(changing.constBegin(), changing.constEnd());
|
|
const auto followers = project->elementsUsingElementAutoNum(title);
|
|
for (const Element *el : followers)
|
|
{
|
|
if (moving.contains(el)
|
|
|| el->linkType() == Element::Slave
|
|
|| (el->linkType() & Element::AllReport)) {
|
|
continue;
|
|
}
|
|
const QString label = el->elementInformations().value(QETInformation::ELMT_LABEL).toString();
|
|
if (!label.isEmpty()) {
|
|
labels << label;
|
|
}
|
|
}
|
|
return labels;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::renumber
|
|
Number again, from the first number, the elements following the
|
|
element numbering scheme @p title, or every scheme if @p title is
|
|
empty, in folio and position order. One undo step.
|
|
|
|
An element whose label is frozen is left as it is, and its label is not
|
|
given to another element: the numbering goes past it.
|
|
@param frozen : if not null, receives the elements left as they are
|
|
because their label is frozen
|
|
@param text : caption of the undo step
|
|
@return the command, nullptr if there is nothing to number
|
|
*/
|
|
RenumberElementsCommand *ElementAutoNumSchemeCommand::renumber(
|
|
QETProject *project,
|
|
const QString &title,
|
|
QVector<Element *> *frozen,
|
|
const QString &text)
|
|
{
|
|
if (frozen) frozen->clear();
|
|
if (!project || project->isReadOnly()) {
|
|
return nullptr;
|
|
}
|
|
|
|
QVector<RenumberElementsCommand::ElementChange> changes;
|
|
QHash<QString, NumerotationContext> old_ctx;
|
|
QHash<QString, NumerotationContext> new_ctx;
|
|
|
|
QStringList titles = title.isEmpty() ? QStringList(project->elementAutoNum().keys())
|
|
: QStringList{title};
|
|
titles.sort(Qt::CaseInsensitive);
|
|
for (const QString &key : std::as_const(titles))
|
|
{
|
|
if (!project->elementAutoNum().contains(key)) {
|
|
continue;
|
|
}
|
|
|
|
QVector<Element *> elements;
|
|
const auto followers = project->elementsUsingElementAutoNum(key);
|
|
for (Element *el : followers)
|
|
{
|
|
if (el->linkType() == Element::Slave || (el->linkType() & Element::AllReport)) {
|
|
continue;
|
|
}
|
|
if (el->isFreezeLabel()) {
|
|
if (frozen) *frozen << el;
|
|
continue;
|
|
}
|
|
elements << el;
|
|
}
|
|
if (elements.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
const NumerotationContext tmpl = project->elementAutoNum(key);
|
|
NumerotationContext final_ctx;
|
|
changes << renumberChanges(key, tmpl, elements,
|
|
autonum::numerotationContextToFormula(tmpl),
|
|
&final_ctx, true, QUuid(),
|
|
reservedLabels(project, key, elements));
|
|
old_ctx.insert(key, tmpl);
|
|
new_ctx.insert(key, final_ctx);
|
|
}
|
|
if (changes.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
return new RenumberElementsCommand(project, changes, old_ctx, new_ctx, text);
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::numberSupport
|
|
Giving an element a number by hand makes sense when the numbers of a
|
|
scheme are one sequence: a definition with exactly one number part, a
|
|
"unit", "ten" or "hundred" (a folio number, a cycle or letters would make
|
|
"which number is free" depend on more than one value).
|
|
*/
|
|
ElementAutoNumSchemeCommand::NumberSupport ElementAutoNumSchemeCommand::numberSupport(
|
|
const NumerotationContext &context)
|
|
{
|
|
NumberSupport support;
|
|
int numbers = 0;
|
|
for (int i = 0 ; i < context.size() ; ++i)
|
|
{
|
|
const QStringList part = context.itemAt(i);
|
|
const QString type = part.value(0);
|
|
if (type == QLatin1String("unit") || type == QLatin1String("ten")
|
|
|| type == QLatin1String("hundred")) {
|
|
++numbers;
|
|
support.partIndex = i;
|
|
support.type = type;
|
|
support.increase = std::max(1, part.value(2).toInt());
|
|
} else if (type == QLatin1String("unitfolio") || type == QLatin1String("tenfolio")
|
|
|| type == QLatin1String("hundredfolio") || type == QLatin1String("alpha")
|
|
|| type == QLatin1String("wrap")) {
|
|
return NumberSupport();
|
|
}
|
|
}
|
|
support.supported = numbers == 1;
|
|
if (!support.supported) {
|
|
return NumberSupport();
|
|
}
|
|
return support;
|
|
}
|
|
|
|
/// @return the number @p element was given by the scheme, if it knows it
|
|
std::optional<int> ElementAutoNumSchemeCommand::numberOf(const NumberSupport &support,
|
|
const Element *element)
|
|
{
|
|
if (!support.supported || !element) {
|
|
return std::nullopt;
|
|
}
|
|
const autonum::sequentialNumbers seq = element->sequenceStruct();
|
|
const QStringList &list = support.type == QLatin1String("unit") ? seq.unit
|
|
: support.type == QLatin1String("ten") ? seq.ten
|
|
: seq.hundred;
|
|
bool ok = false;
|
|
const int number = list.value(0).toInt(&ok);
|
|
if (!ok) {
|
|
return std::nullopt;
|
|
}
|
|
return number;
|
|
}
|
|
|
|
namespace {
|
|
/// The elements of @p project which follow the scheme @p title and are not slaves or reports
|
|
QVector<Element *> numberedFollowers(const QETProject *project, const QString &title)
|
|
{
|
|
QVector<Element *> list;
|
|
const auto followers = project->elementsUsingElementAutoNum(title);
|
|
for (Element *el : followers) {
|
|
if (el->linkType() != Element::Slave && !(el->linkType() & Element::AllReport)) {
|
|
list << el;
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
} // namespace
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::labelsHeldBesides
|
|
@return the labels the elements of @p project carry, @p element apart:
|
|
those a number given to @p element must not turn into
|
|
*/
|
|
QSet<QString> ElementAutoNumSchemeCommand::labelsHeldBesides(const QETProject *project,
|
|
const Element *element)
|
|
{
|
|
QSet<QString> labels;
|
|
if (!project) {
|
|
return labels;
|
|
}
|
|
for (Diagram *d : project->diagrams()) {
|
|
for (QGraphicsItem *item : d->items()) {
|
|
if (auto *el = qgraphicsitem_cast<Element *>(item)) {
|
|
if (el != element) {
|
|
labels << el->elementInformations().value(QETInformation::ELMT_LABEL).toString();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
labels.remove(QString());
|
|
return labels;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::counterConflict
|
|
Giving the numbering @p title the counter of @p proposed, with the same
|
|
formula it has, makes its next element meet numbers in use when elements
|
|
of the numbering have a number of at least that counter. Those numbers
|
|
are skipped when the element is placed, but the next number is not the
|
|
one asked for, which is worth telling.
|
|
@return what the counter would meet, nothing if the counter does not
|
|
change, the formula does, or the numbers are not a single sequence
|
|
*/
|
|
std::optional<ElementAutoNumSchemeCommand::CounterConflict> ElementAutoNumSchemeCommand::counterConflict(
|
|
const QETProject *project, const QString &title, const NumerotationContext &proposed)
|
|
{
|
|
if (!project || !project->elementAutoNum().contains(title)) {
|
|
return std::nullopt;
|
|
}
|
|
const NumerotationContext stored = project->elementAutoNum().value(title);
|
|
const NumberSupport support = numberSupport(stored);
|
|
if (!support.supported || proposed.size() != stored.size()
|
|
|| autonum::numerotationContextToFormula(proposed) != autonum::numerotationContextToFormula(stored)) {
|
|
return std::nullopt;
|
|
}
|
|
const int counter = proposed.itemAt(support.partIndex).value(1).toInt();
|
|
if (counter == stored.itemAt(support.partIndex).value(1).toInt()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
CounterConflict conflict;
|
|
conflict.counter = counter;
|
|
for (const Element *el : numberedFollowers(project, title)) {
|
|
if (const auto number = numberOf(support, el)) {
|
|
if (*number >= counter) {
|
|
++conflict.count;
|
|
conflict.highest = std::max(conflict.highest, *number);
|
|
}
|
|
}
|
|
}
|
|
if (!conflict.count) {
|
|
return std::nullopt;
|
|
}
|
|
return conflict;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::gapRanges
|
|
@return the numbers between the lowest and the highest which the elements
|
|
following the scheme @p title carry, that none of them carries: where an
|
|
element was deleted or given another number. Empty for a scheme whose
|
|
elements cannot be given a number by hand (see numberSupport()).
|
|
*/
|
|
QList<ElementAutoNumSchemeCommand::GapRange> ElementAutoNumSchemeCommand::gapRanges(
|
|
const QETProject *project, const QString &title)
|
|
{
|
|
QList<GapRange> ranges;
|
|
if (!project || !project->elementAutoNum().contains(title)) {
|
|
return ranges;
|
|
}
|
|
const NumberSupport support = numberSupport(project->elementAutoNum().value(title));
|
|
if (!support.supported) {
|
|
return ranges;
|
|
}
|
|
QSet<int> used;
|
|
for (const Element *el : numberedFollowers(project, title)) {
|
|
if (const auto number = numberOf(support, el)) {
|
|
used << *number;
|
|
}
|
|
}
|
|
if (used.size() < 2) {
|
|
return ranges;
|
|
}
|
|
const int low = *std::min_element(used.cbegin(), used.cend());
|
|
const int high = *std::max_element(used.cbegin(), used.cend());
|
|
for (int n = low + 1 ; n < high ; ++n)
|
|
{
|
|
if (used.contains(n)) {
|
|
continue;
|
|
}
|
|
if (!ranges.isEmpty() && ranges.last().to == n - 1) {
|
|
ranges.last().to = n;
|
|
} else {
|
|
ranges << GapRange{n, n};
|
|
}
|
|
}
|
|
return ranges;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::labelForNumber
|
|
@return the label @p element would have with the number @p number of the
|
|
scheme @p title, empty if the scheme cannot give a number by hand
|
|
*/
|
|
QString ElementAutoNumSchemeCommand::labelForNumber(const QETProject *project,
|
|
const QString &title,
|
|
Element *element,
|
|
int number)
|
|
{
|
|
if (!project || !element || !element->diagram() || !project->elementAutoNum().contains(title)) {
|
|
return QString();
|
|
}
|
|
NumerotationContext context = project->elementAutoNum().value(title);
|
|
const NumberSupport support = numberSupport(context);
|
|
if (!support.supported) {
|
|
return QString();
|
|
}
|
|
context.replaceValue(support.partIndex, QString::number(number));
|
|
const QString formula = autonum::numerotationContextToFormula(context);
|
|
autonum::sequentialNumbers seq;
|
|
autonum::setSequential(formula, seq, context, element->diagram(), title);
|
|
return autonum::AssignVariables::formulaToLabel(formula, seq, element->diagram(), element, nullptr);
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::freeNumbers
|
|
@return the numbers @p element of the scheme @p title may be given by
|
|
hand, in order: from 1 to a few past the highest in use or the counter,
|
|
those no other element of the scheme carries and which would not give it
|
|
the label of any other element. At most @p limit.
|
|
*/
|
|
QList<int> ElementAutoNumSchemeCommand::freeNumbers(const QETProject *project,
|
|
const QString &title,
|
|
const Element *element,
|
|
int limit)
|
|
{
|
|
QList<int> numbers;
|
|
if (!project || !element || !project->elementAutoNum().contains(title)) {
|
|
return numbers;
|
|
}
|
|
const NumerotationContext context = project->elementAutoNum().value(title);
|
|
const NumberSupport support = numberSupport(context);
|
|
if (!support.supported) {
|
|
return numbers;
|
|
}
|
|
|
|
QSet<int> used;
|
|
int highest = context.itemAt(support.partIndex).value(1).toInt();
|
|
for (const Element *el : numberedFollowers(project, title)) {
|
|
if (el == element) continue;
|
|
if (const auto number = numberOf(support, el)) {
|
|
used << *number;
|
|
highest = std::max(highest, *number);
|
|
}
|
|
}
|
|
|
|
QSet<QString> labels;
|
|
for (Diagram *d : project->diagrams()) {
|
|
for (QGraphicsItem *item : d->items()) {
|
|
if (auto *el = qgraphicsitem_cast<Element *>(item)) {
|
|
if (el != element) {
|
|
labels << el->elementInformations().value(QETInformation::ELMT_LABEL).toString();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
labels.remove(QString());
|
|
|
|
for (int n = 1 ; n <= highest + 10 && numbers.size() < limit ; ++n)
|
|
{
|
|
if (used.contains(n)) {
|
|
continue;
|
|
}
|
|
const QString label = labelForNumber(project, title, const_cast<Element *>(element), n);
|
|
if (!label.isEmpty() && labels.contains(label)) {
|
|
continue;
|
|
}
|
|
numbers << n;
|
|
}
|
|
return numbers;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::assignNumber
|
|
Give @p element, which follows an element numbering scheme, the number
|
|
@p number of it, which must be free (see freeNumbers()): its label is the
|
|
formula worked out with that number and it keeps following the scheme.
|
|
When the scheme's counter is at or below @p number it moves to the next
|
|
number after it, so that the next element is not given the same label.
|
|
One undo step.
|
|
@param problem : if not null, receives why nothing is done, translated
|
|
@return the command, nullptr if @p number cannot be given
|
|
*/
|
|
RenumberElementsCommand *ElementAutoNumSchemeCommand::assignNumber(QETProject *project,
|
|
Element *element,
|
|
int number,
|
|
QString *problem,
|
|
QUndoCommand *parent)
|
|
{
|
|
const auto fail = [&](const QString &text) -> RenumberElementsCommand * {
|
|
if (problem) *problem = text;
|
|
return nullptr;
|
|
};
|
|
if (!project || !element || !element->diagram()) {
|
|
return fail(tr("Cet élément n'est dans aucun folio."));
|
|
}
|
|
const DiagramContext info = element->elementInformations();
|
|
const QString title = project->elementAutoNumTitle(
|
|
QUuid(info.value(QETInformation::ELMT_FORMULA_ID).toString()));
|
|
if (title.isEmpty()) {
|
|
return fail(tr("Cet élément ne suit aucune numérotation."));
|
|
}
|
|
const NumerotationContext context = project->elementAutoNum().value(title);
|
|
const NumberSupport support = numberSupport(context);
|
|
if (!support.supported) {
|
|
return fail(tr("La numérotation « %1 » n'a pas un seul numéro : on ne peut pas en choisir un à la main.").arg(title));
|
|
}
|
|
if (number < 1) {
|
|
return fail(tr("Le numéro doit être au moins 1."));
|
|
}
|
|
if (!freeNumbers(project, title, element, 100000).contains(number)) {
|
|
return fail(tr("Le numéro %1 n'est pas libre.").arg(number));
|
|
}
|
|
|
|
const QString formula = autonum::numerotationContextToFormula(context);
|
|
NumerotationContext at = context;
|
|
at.replaceValue(support.partIndex, QString::number(number));
|
|
autonum::sequentialNumbers seq;
|
|
autonum::setSequential(formula, seq, at, element->diagram(), title);
|
|
|
|
RenumberElementsCommand::ElementChange change;
|
|
change.element = element;
|
|
change.old_infos = info;
|
|
change.old_seq = element->sequenceStruct();
|
|
change.old_frozen = element->isFreezeLabel();
|
|
change.new_frozen = change.old_frozen;
|
|
change.new_infos = info;
|
|
change.new_infos.addValue(QETInformation::ELMT_FORMULA, formula);
|
|
change.new_infos.addValue(QETInformation::ELMT_FORMULA_ID,
|
|
project->elementAutoNumId(title).toString(), false);
|
|
change.new_infos.addValue(QETInformation::ELMT_LABEL,
|
|
autonum::AssignVariables::formulaToLabel(
|
|
formula, seq, element->diagram(), element, nullptr));
|
|
change.new_seq = seq;
|
|
|
|
NumerotationContext counter = context;
|
|
if (counter.itemAt(support.partIndex).value(1).toInt() <= number) {
|
|
counter.replaceValue(support.partIndex, QString::number(number + support.increase));
|
|
}
|
|
return new RenumberElementsCommand(project, {change},
|
|
{{title, context}}, {{title, counter}},
|
|
tr("Attribuer le numéro %1").arg(number), parent);
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::skipReason
|
|
@return why assign() would leave @p element alone, None if it would
|
|
give it the scheme @p title
|
|
*/
|
|
ElementAutoNumSchemeCommand::Skipped ElementAutoNumSchemeCommand::skipReason(
|
|
const QETProject *project,
|
|
const QString &title,
|
|
const Element *element)
|
|
{
|
|
if (!project || !element) {
|
|
return Skipped::Linked;
|
|
}
|
|
if (element->linkType() == Element::Slave
|
|
|| (element->linkType() & Element::AllReport)) {
|
|
return Skipped::Linked;
|
|
}
|
|
const DiagramContext &info = element->elementInformations();
|
|
const QString formula = info.value(QETInformation::ELMT_FORMULA).toString();
|
|
const QUuid wanted = project->elementAutoNumId(title);
|
|
if (!formula.isEmpty()) {
|
|
if (!wanted.isNull()
|
|
&& QUuid(info.value(QETInformation::ELMT_FORMULA_ID).toString()) == wanted) {
|
|
return Skipped::FollowsAlready;
|
|
}
|
|
return Skipped::OtherFormula;
|
|
}
|
|
if (element->isFreezeLabel()) {
|
|
return Skipped::Frozen;
|
|
}
|
|
return Skipped::None;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::assignPlan
|
|
Sort @p elements by what assign() would do with them. A frozen label
|
|
is told apart from a formula held already: both are left alone unless
|
|
the user agrees to replace them.
|
|
*/
|
|
ElementAutoNumSchemeCommand::AssignPlan ElementAutoNumSchemeCommand::assignPlan(
|
|
const QETProject *project,
|
|
const QString &title,
|
|
const QVector<Element *> &elements)
|
|
{
|
|
AssignPlan plan;
|
|
for (Element *el : elements)
|
|
{
|
|
switch (skipReason(project, title, el)) {
|
|
case Skipped::None:
|
|
plan.todo << el;
|
|
break;
|
|
case Skipped::Frozen:
|
|
plan.frozen << el;
|
|
break;
|
|
case Skipped::OtherFormula:
|
|
plan.otherFormula << el;
|
|
break;
|
|
case Skipped::FollowsAlready:
|
|
++plan.followsAlready;
|
|
break;
|
|
case Skipped::Linked:
|
|
++plan.linked;
|
|
break;
|
|
}
|
|
}
|
|
return plan;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::numberElements
|
|
Give every one of @p elements (a slave or a report excepted) the
|
|
element numbering scheme @p title: its formula and the next number of
|
|
the scheme, in folio and position order, the scheme's counter moving
|
|
on. The numbering goes past the labels the other elements which follow
|
|
the scheme keep. One undo step.
|
|
@param freeze : what the frozen state of the elements becomes
|
|
@param parent : parent undo command, if the numbering is a part of one
|
|
@return the command, nullptr if there is nothing to number
|
|
*/
|
|
RenumberElementsCommand *ElementAutoNumSchemeCommand::numberElements(
|
|
QETProject *project,
|
|
const QString &title,
|
|
const QVector<Element *> &elements,
|
|
const QString &text,
|
|
FreezeRule freeze,
|
|
QUndoCommand *parent)
|
|
{
|
|
if (!project || !project->elementAutoNum().contains(title)) {
|
|
return nullptr;
|
|
}
|
|
QVector<Element *> todo;
|
|
for (Element *el : elements) {
|
|
if (el && el->linkType() != Element::Slave && !(el->linkType() & Element::AllReport)) {
|
|
todo << el;
|
|
}
|
|
}
|
|
if (todo.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
const NumerotationContext context = project->elementAutoNum().value(title);
|
|
NumerotationContext final_context;
|
|
QVector<RenumberElementsCommand::ElementChange> changes = renumberChanges(
|
|
title, context, todo,
|
|
autonum::numerotationContextToFormula(context),
|
|
&final_context, false, project->elementAutoNumId(title),
|
|
reservedLabels(project, title, todo));
|
|
for (auto &change : changes) {
|
|
change.new_frozen = freeze == FreezeRule::NewElementPolicy
|
|
&& change.element && change.element->diagram()
|
|
&& (change.element->diagram()->freezeNewElements()
|
|
|| project->isFreezeNewElements());
|
|
}
|
|
|
|
return new RenumberElementsCommand(
|
|
project, changes,
|
|
{{title, context}}, {{title, final_context}},
|
|
text, parent);
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::assign
|
|
Make @p elements follow the element numbering scheme @p title: each
|
|
takes its formula and the next number of the scheme, in folio and
|
|
position order, and the scheme's counter moves on. One undo step.
|
|
|
|
An element is left alone (see assignPlan()) when it follows the scheme
|
|
already, is a slave or a report, or when something would be lost: a
|
|
frozen label, or a formula it follows or holds already, unless
|
|
@p overwrite. An element with a label typed by hand and no formula is
|
|
given the scheme; its label is replaced by the number.
|
|
@param skipped : if not null, receives how many elements were left alone
|
|
@return the command, nullptr if there is nothing to give
|
|
*/
|
|
RenumberElementsCommand *ElementAutoNumSchemeCommand::assign(
|
|
QETProject *project,
|
|
const QString &title,
|
|
const QVector<Element *> &elements,
|
|
bool overwrite,
|
|
int *skipped,
|
|
QUndoCommand *parent)
|
|
{
|
|
if (skipped) *skipped = 0;
|
|
if (!project || !project->elementAutoNum().contains(title)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const AssignPlan plan = assignPlan(project, title, elements);
|
|
QVector<Element *> todo = plan.todo;
|
|
int left = plan.followsAlready + plan.linked;
|
|
if (overwrite) {
|
|
todo += plan.frozen;
|
|
todo += plan.otherFormula;
|
|
} else {
|
|
left += plan.frozen.size() + plan.otherFormula.size();
|
|
}
|
|
if (skipped) *skipped = left;
|
|
if (todo.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
return numberElements(project, title, todo,
|
|
tr("Appliquer la numérotation %1").arg(title),
|
|
FreezeRule::Unfrozen, parent);
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::schemeForFormula
|
|
@return the title of the element numbering scheme of @p project which
|
|
gives @p formula, empty if there is none. When several schemes have the
|
|
same formula (the same parts, other counters), the project's current one
|
|
if it is among them, otherwise the first by name: the label is worked
|
|
out afresh in either case, only the counter which goes on differs.
|
|
*/
|
|
QString ElementAutoNumSchemeCommand::schemeForFormula(const QETProject *project,
|
|
const QString &formula)
|
|
{
|
|
if (!project || formula.isEmpty()) {
|
|
return QString();
|
|
}
|
|
QStringList matches;
|
|
const auto titles = project->elementAutoNum().keys();
|
|
for (const QString &title : titles) {
|
|
if (project->elementAutoNumFormula(title) == formula) {
|
|
matches << title;
|
|
}
|
|
}
|
|
if (matches.isEmpty()) {
|
|
return QString();
|
|
}
|
|
if (matches.contains(project->elementCurrentAutoNum())) {
|
|
return project->elementCurrentAutoNum();
|
|
}
|
|
matches.sort(Qt::CaseInsensitive);
|
|
return matches.first();
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::followedScheme
|
|
@return the title of the scheme of @p project which an element with the
|
|
information @p info follows, empty if none.
|
|
|
|
The id names it, but only counts when the scheme has the formula the
|
|
element has: an id which comes from another project (a copy of the
|
|
project edited since, two projects which both have a numbering of the
|
|
same name...) may name a scheme here which is not the same numbering.
|
|
*/
|
|
QString ElementAutoNumSchemeCommand::followedScheme(const QETProject *project,
|
|
const DiagramContext &info)
|
|
{
|
|
if (!project) {
|
|
return QString();
|
|
}
|
|
const QString title = project->elementAutoNumTitle(
|
|
QUuid(info.value(QETInformation::ELMT_FORMULA_ID).toString()));
|
|
if (title.isEmpty()
|
|
|| project->elementAutoNumFormula(title) != info.value(QETInformation::ELMT_FORMULA).toString()) {
|
|
return QString();
|
|
}
|
|
return title;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::writeCopiedSchemes
|
|
Add to the copy @p root the definition of every element numbering scheme
|
|
of @p project which the @p copied elements follow, so that a paste into
|
|
another project, which knows nothing of it, can offer to import it.
|
|
*/
|
|
void ElementAutoNumSchemeCommand::writeCopiedSchemes(QDomDocument &document,
|
|
QDomElement &root,
|
|
const QETProject *project,
|
|
const QVector<Element *> &copied)
|
|
{
|
|
if (!project) {
|
|
return;
|
|
}
|
|
QSet<QUuid> done;
|
|
QDomElement list;
|
|
for (const Element *el : copied)
|
|
{
|
|
const QUuid id(el->elementInformations().value(QETInformation::ELMT_FORMULA_ID).toString());
|
|
const QString title = project->elementAutoNumTitle(id);
|
|
if (title.isEmpty() || done.contains(id)) {
|
|
continue;
|
|
}
|
|
done << id;
|
|
if (list.isNull()) {
|
|
list = document.createElement(QStringLiteral("copied_element_autonums"));
|
|
root.appendChild(list);
|
|
}
|
|
NumerotationContext context = project->elementAutoNum().value(title);
|
|
QDomElement scheme = context.toXml(document, QStringLiteral("element_autonum"));
|
|
scheme.setAttribute(QStringLiteral("title"), title);
|
|
scheme.setAttribute(QStringLiteral("id"), id.toString());
|
|
scheme.setAttribute(QStringLiteral("formula"), autonum::numerotationContextToFormula(context));
|
|
list.appendChild(scheme);
|
|
}
|
|
}
|
|
|
|
/// @return the numbering schemes a copy carries (see writeCopiedSchemes())
|
|
QList<ElementAutoNumSchemeCommand::Scheme> ElementAutoNumSchemeCommand::copiedSchemes(
|
|
const QDomElement &root)
|
|
{
|
|
QList<Scheme> schemes;
|
|
const QDomElement list = root.firstChildElement(QStringLiteral("copied_element_autonums"));
|
|
for (QDomElement e = list.firstChildElement(QStringLiteral("element_autonum"));
|
|
!e.isNull();
|
|
e = e.nextSiblingElement(QStringLiteral("element_autonum")))
|
|
{
|
|
Scheme scheme;
|
|
scheme.title = e.attribute(QStringLiteral("title"));
|
|
scheme.id = QUuid(e.attribute(QStringLiteral("id")));
|
|
scheme.context.fromXml(e);
|
|
if (!scheme.title.isEmpty() && !scheme.id.isNull() && !scheme.context.isEmpty()) {
|
|
schemes << scheme;
|
|
}
|
|
}
|
|
return schemes;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::missingForPaste
|
|
@return those of the @p copied schemes which the @p pasted elements
|
|
follow and which @p project has no use for: it has neither their id nor
|
|
a scheme with their formula (see schemeForFormula()), so the pasted
|
|
elements would follow no numbering. Slaves and reports follow none.
|
|
*/
|
|
QList<ElementAutoNumSchemeCommand::Scheme> ElementAutoNumSchemeCommand::missingForPaste(
|
|
const QETProject *project,
|
|
const QList<Scheme> &copied,
|
|
const QList<Element *> &pasted)
|
|
{
|
|
QList<Scheme> missing;
|
|
if (!project) {
|
|
return missing;
|
|
}
|
|
QSet<QUuid> followed;
|
|
for (const Element *el : pasted) {
|
|
if (el->linkType() == Element::Slave || (el->linkType() & Element::AllReport)) {
|
|
continue;
|
|
}
|
|
followed << QUuid(el->elementInformations().value(QETInformation::ELMT_FORMULA_ID).toString());
|
|
}
|
|
for (const Scheme &scheme : copied)
|
|
{
|
|
const QString formula = autonum::numerotationContextToFormula(scheme.context);
|
|
const QString by_id = project->elementAutoNumTitle(scheme.id);
|
|
//Present: under its id with the same formula, or under another id
|
|
//(another name, other project) with the same formula
|
|
const bool present = (!by_id.isEmpty() && project->elementAutoNumFormula(by_id) == formula)
|
|
|| !schemeForFormula(project, formula).isEmpty();
|
|
if (!followed.contains(scheme.id) || present) {
|
|
continue;
|
|
}
|
|
missing << scheme;
|
|
}
|
|
return missing;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::pastedSchemes
|
|
@return the element numbering schemes of @p project which freshly
|
|
pasted @p elements follow, by title, each with its elements.
|
|
|
|
An element follows a scheme when its formula_id names one, or, when
|
|
it names none (the element comes from another project, or from a file
|
|
written before the ids), when one has its formula (see
|
|
schemeForFormula()). A slave or a report follows none: it takes its
|
|
label from its master.
|
|
*/
|
|
QMap<QString, QVector<Element *>> ElementAutoNumSchemeCommand::pastedSchemes(
|
|
const QETProject *project,
|
|
const QList<Element *> &elements)
|
|
{
|
|
QMap<QString, QVector<Element *>> schemes;
|
|
if (!project || project->elementAutoNum().isEmpty()) {
|
|
return schemes;
|
|
}
|
|
|
|
for (Element *el : elements)
|
|
{
|
|
if (!el || el->linkType() == Element::Slave || (el->linkType() & Element::AllReport)) {
|
|
continue;
|
|
}
|
|
const DiagramContext &info = el->elementInformations();
|
|
const QString formula = info.value(QETInformation::ELMT_FORMULA).toString();
|
|
if (formula.isEmpty()) {
|
|
continue;
|
|
}
|
|
QString title = followedScheme(project, info);
|
|
if (title.isEmpty()) {
|
|
title = schemeForFormula(project, formula);
|
|
}
|
|
if (!title.isEmpty()) {
|
|
schemes[title] << el;
|
|
}
|
|
}
|
|
return schemes;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::linkPasted
|
|
Make the formula_id of pasted @p elements name a scheme of @p project,
|
|
as QETProject does for the elements of a file it loads: an id which
|
|
names one with the element's formula is kept (see followedScheme());
|
|
otherwise the scheme with the element's formula (see schemeForFormula()),
|
|
else none. Labels are not touched.
|
|
*/
|
|
void ElementAutoNumSchemeCommand::linkPasted(const QETProject *project,
|
|
const QList<Element *> &elements)
|
|
{
|
|
if (!project) {
|
|
return;
|
|
}
|
|
for (Element *el : elements)
|
|
{
|
|
if (!el) continue;
|
|
const DiagramContext &info = el->elementInformations();
|
|
const QString formula = info.value(QETInformation::ELMT_FORMULA).toString();
|
|
if (formula.isEmpty()) {
|
|
if (info.contains(QETInformation::ELMT_FORMULA_ID)) {
|
|
el->setFormulaSchemeId(QUuid());
|
|
}
|
|
continue;
|
|
}
|
|
if (!followedScheme(project, info).isEmpty()) {
|
|
continue;
|
|
}
|
|
const QString title = schemeForFormula(project, formula);
|
|
el->setFormulaSchemeId(title.isEmpty() ? QUuid() : project->elementAutoNumId(title));
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::numberPasted
|
|
Number the pasted elements of @p schemes (see pastedSchemes()) the way
|
|
elements placed one after the other are: each scheme gives its next
|
|
numbers, in folio and position order, and its counter moves on. One
|
|
numbering command per scheme, children of @p parent, which runs them
|
|
with its own redo(), so that undoing the paste gives the numbers back.
|
|
@return how many elements are numbered
|
|
*/
|
|
int ElementAutoNumSchemeCommand::numberPasted(
|
|
QETProject *project,
|
|
const QMap<QString, QVector<Element *>> &schemes,
|
|
QUndoCommand *parent)
|
|
{
|
|
int count = 0;
|
|
for (auto it = schemes.constBegin() ; it != schemes.constEnd() ; ++it)
|
|
{
|
|
if (numberElements(project, it.key(), it.value(),
|
|
tr("Numéroter les éléments collés (%1)").arg(it.key()),
|
|
FreezeRule::NewElementPolicy, parent)) {
|
|
count += it.value().size();
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::create
|
|
A new element numbering scheme.
|
|
@param id : its uuid; a new one if null or already used
|
|
@param make_current : make it the scheme given to new elements
|
|
@return nullptr if @p title is not an acceptable name
|
|
*/
|
|
ElementAutoNumSchemeCommand *ElementAutoNumSchemeCommand::create(
|
|
QETProject *project,
|
|
const QString &title,
|
|
const NumerotationContext &context,
|
|
const QUuid &id,
|
|
bool make_current)
|
|
{
|
|
if (!project || !nameProblem(project, title).isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto *cmd = new ElementAutoNumSchemeCommand(project);
|
|
Scheme after;
|
|
after.title = QETProject::normalizedAutoNumName(title);
|
|
after.id = (id.isNull() || !project->elementAutoNumTitle(id).isEmpty())
|
|
? QUuid::createUuid()
|
|
: id;
|
|
after.context = context;
|
|
cmd->m_after = after;
|
|
cmd->m_current_before = project->elementCurrentAutoNum();
|
|
cmd->m_current_after = make_current ? after.title : cmd->m_current_before;
|
|
cmd->setText(tr("Créer la numérotation d'éléments %1").arg(after.title));
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::edit
|
|
Rename the element numbering scheme @p old_title to @p new_title and
|
|
give it @p context. The elements following it follow it still (see
|
|
the class description).
|
|
@param make_current : make it the scheme given to new elements; if
|
|
false, it stays so if it was
|
|
@return nullptr if there is no scheme @p old_title, @p new_title is not
|
|
an acceptable name, an element with a frozen label follows the scheme
|
|
and its formula would change (see editBlockedBy()), or nothing changes
|
|
*/
|
|
ElementAutoNumSchemeCommand *ElementAutoNumSchemeCommand::edit(
|
|
QETProject *project,
|
|
const QString &old_title,
|
|
const QString &new_title,
|
|
const NumerotationContext &context,
|
|
bool make_current)
|
|
{
|
|
if (!project || !project->elementAutoNum().contains(old_title)) {
|
|
return nullptr;
|
|
}
|
|
if (!nameProblem(project, new_title, old_title).isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
if (!editBlockedBy(project, old_title, context).isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
Scheme before;
|
|
before.title = old_title;
|
|
before.id = project->elementAutoNumId(old_title);
|
|
before.context = project->elementAutoNum(old_title);
|
|
|
|
Scheme after;
|
|
after.title = QETProject::normalizedAutoNumName(new_title);
|
|
after.id = before.id;
|
|
after.context = context;
|
|
|
|
auto *cmd = new ElementAutoNumSchemeCommand(project);
|
|
cmd->m_before = before;
|
|
cmd->m_current_before = project->elementCurrentAutoNum();
|
|
cmd->m_current_after = (make_current || cmd->m_current_before == old_title)
|
|
? after.title
|
|
: cmd->m_current_before;
|
|
|
|
const QString old_formula = autonum::numerotationContextToFormula(before.context);
|
|
const QString new_formula = autonum::numerotationContextToFormula(after.context);
|
|
if (old_formula != new_formula)
|
|
{
|
|
QVector<Element *> elements;
|
|
const auto followers = project->elementsUsingElementAutoNum(old_title);
|
|
for (Element *el : followers)
|
|
{
|
|
if (el->linkType() == Element::Slave || (el->linkType() & Element::AllReport)) {
|
|
continue;
|
|
}
|
|
elements << el;
|
|
}
|
|
if (sameSequentialParts(old_formula, new_formula))
|
|
{
|
|
for (Element *el : elements)
|
|
{
|
|
RenumberElementsCommand::ElementChange ch;
|
|
ch.element = el;
|
|
ch.old_infos = el->elementInformations();
|
|
ch.old_seq = el->sequenceStruct();
|
|
ch.new_seq = ch.old_seq;
|
|
ch.old_frozen = el->isFreezeLabel();
|
|
ch.new_frozen = ch.old_frozen;
|
|
|
|
autonum::sequentialNumbers seq = ch.old_seq;
|
|
ch.new_infos = ch.old_infos;
|
|
ch.new_infos.addValue(QETInformation::ELMT_FORMULA, new_formula);
|
|
ch.new_infos.addValue(QETInformation::ELMT_LABEL,
|
|
autonum::AssignVariables::formulaToLabel(
|
|
new_formula, seq, el->diagram(), el, nullptr));
|
|
cmd->m_changes << ch;
|
|
}
|
|
}
|
|
else if (!elements.isEmpty())
|
|
{
|
|
cmd->m_changes = renumberChanges(old_title, after.context,
|
|
elements, new_formula,
|
|
&after.context, true, QUuid(),
|
|
reservedLabels(project, old_title, elements));
|
|
}
|
|
}
|
|
cmd->m_after = after;
|
|
|
|
const bool renamed = before.title != after.title;
|
|
const bool redefined = !sameContext(before.context, after.context);
|
|
if (!renamed && !redefined && cmd->m_changes.isEmpty()
|
|
&& cmd->m_current_before == cmd->m_current_after) {
|
|
delete cmd;
|
|
return nullptr;
|
|
}
|
|
|
|
if (renamed && !redefined) {
|
|
cmd->setText(tr("Renommer la numérotation d'éléments %1 en %2")
|
|
.arg(before.title, after.title));
|
|
} else {
|
|
cmd->setText(tr("Modifier la numérotation d'éléments %1").arg(after.title));
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
@brief ElementAutoNumSchemeCommand::remove
|
|
Remove the element numbering scheme @p title.
|
|
@return nullptr if there is no such scheme or elements still follow
|
|
it: removing it would leave them with a formula nothing defines
|
|
*/
|
|
ElementAutoNumSchemeCommand *ElementAutoNumSchemeCommand::remove(
|
|
QETProject *project,
|
|
const QString &title)
|
|
{
|
|
if (!project || !project->elementAutoNum().contains(title)) {
|
|
return nullptr;
|
|
}
|
|
if (!project->elementsUsingElementAutoNum(title).isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto *cmd = new ElementAutoNumSchemeCommand(project);
|
|
Scheme before;
|
|
before.title = title;
|
|
before.id = project->elementAutoNumId(title);
|
|
before.context = project->elementAutoNum(title);
|
|
cmd->m_before = before;
|
|
cmd->m_current_before = project->elementCurrentAutoNum();
|
|
cmd->m_current_after = cmd->m_current_before == title
|
|
? QString()
|
|
: cmd->m_current_before;
|
|
cmd->setText(tr("Supprimer la numérotation d'éléments %1").arg(title));
|
|
return cmd;
|
|
}
|
|
|
|
void ElementAutoNumSchemeCommand::redo()
|
|
{
|
|
if (!m_project) return;
|
|
|
|
if (m_before && m_after) {
|
|
m_project->renameElementAutoNum(m_before->title, m_after->title);
|
|
} else if (m_before) {
|
|
m_project->removeElementAutoNum(m_before->title);
|
|
}
|
|
if (m_after) {
|
|
m_project->addElementAutoNum(m_after->title, m_after->context, m_after->id);
|
|
}
|
|
m_project->setCurrrentElementAutonum(m_current_after);
|
|
|
|
for (const auto &change : std::as_const(m_changes)) {
|
|
RenumberElementsCommand::applyChange(change, true);
|
|
}
|
|
}
|
|
|
|
void ElementAutoNumSchemeCommand::undo()
|
|
{
|
|
if (!m_project) return;
|
|
|
|
for (const auto &change : std::as_const(m_changes)) {
|
|
RenumberElementsCommand::applyChange(change, false);
|
|
}
|
|
|
|
if (m_before && m_after) {
|
|
m_project->renameElementAutoNum(m_after->title, m_before->title);
|
|
} else if (m_after) {
|
|
m_project->removeElementAutoNum(m_after->title);
|
|
}
|
|
if (m_before) {
|
|
m_project->addElementAutoNum(m_before->title, m_before->context, m_before->id);
|
|
}
|
|
m_project->setCurrrentElementAutonum(m_current_before);
|
|
}
|