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
+12
View File
@@ -324,6 +324,18 @@ set(QET_SRC_FILES
${QET_DIR}/sources/autoNum/numerotationcontext.h
${QET_DIR}/sources/autoNum/renumberelementscommand.cpp
${QET_DIR}/sources/autoNum/renumberelementscommand.h
${QET_DIR}/sources/autoNum/autonumschemecommand.cpp
${QET_DIR}/sources/autoNum/autonumschemecommand.h
${QET_DIR}/sources/autoNum/elementautonumschemecommand.cpp
${QET_DIR}/sources/autoNum/elementautonumschemecommand.h
${QET_DIR}/sources/autoNum/ui/counterwarning.cpp
${QET_DIR}/sources/autoNum/ui/counterwarning.h
${QET_DIR}/sources/autoNum/ui/pastenumberingimport.cpp
${QET_DIR}/sources/autoNum/ui/pastenumberingimport.h
${QET_DIR}/sources/autoNum/ui/renumberpreviewdialog.cpp
${QET_DIR}/sources/autoNum/ui/renumberpreviewdialog.h
${QET_DIR}/sources/undocommand/freezeelementlabelcommand.cpp
${QET_DIR}/sources/undocommand/freezeelementlabelcommand.h
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.cpp
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.h
${QET_DIR}/sources/autoNum/ui/autonumberingmanagementw.cpp
+21 -1
View File
@@ -1444,9 +1444,17 @@ OPS = {
"add_autonum": ("addAutoNum", [("kind", "str"), ("name", "str"),
("parts", "list")]),
"remove_autonum": ("removeAutoNum", [("kind", "str"), ("name", "str")]),
"rename_autonum": ("renameAutoNum", [("kind", "str"), ("name", "str"),
("new_name", "str")]),
"use_conductor_autonum": ("useConductorAutoNum", [("folio", "folio"), ("name", "str")]),
"use_element_autonum": ("useElementAutoNum", [("name", "str")]),
"number_element": ("numberElement", [("folio", "folio"), ("element", "elmt")]),
"renumber_element_autonum": ("renumberElementAutoNum", [("name", "str")]),
"free_element_numbers": ("freeElementNumbers", [("folio", "folio"), ("element", "elmt")]),
"assign_element_number": ("assignElementNumber", [("folio", "folio"), ("element", "elmt"),
("number", "num")]),
"assign_element_autonum": ("assignElementAutoNum", [("name", "str"), ("folio", "folio"),
("element", "elmt"), ("overwrite", "bool")]),
# The text fields drawn on a symbol. Indexed within the element's own
# list, which follows its definition and shifts on delete (and undo of a
# delete puts the field back at the end).
@@ -1959,7 +1967,7 @@ def _build_script(operations: list, output: str) -> str:
raise ValueError(f"operation {i}: unknown folio property "
f"{op.get('property')!r}; expected one of "
f"{', '.join(FOLIO_PROPERTIES)}")
if name in ("add_autonum", "remove_autonum") and op.get("kind") not in AUTONUM_KINDS:
if name in ("add_autonum", "remove_autonum", "rename_autonum") and op.get("kind") not in AUTONUM_KINDS:
raise ValueError(f"operation {i}: unknown kind {op.get('kind')!r}; "
f"expected one of {', '.join(AUTONUM_KINDS)}")
if name == "search_and_replace":
@@ -4500,6 +4508,18 @@ TOOLS = [
"use_conductor_autonum then makes new conductors on a folio "
"take their number from it, so define and select it BEFORE the "
"add_conductor ops it should number. For elements, "
"renumber_element_autonum numbers an element context's elements again "
"(an element with a frozen label keeps its label, and nobody else gets it); "
"free_element_numbers lists the numbers an element of an element context may "
"be given by hand, assign_element_number gives it one of them "
"(it keeps following the context, its counter moves past the number); "
"assign_element_autonum makes one element follow a named element context "
"(refused when it holds a formula, frozen label or follows "
"another one, unless overwrite is true); "
"rename_autonum renames a context of any kind, what follows it (elements, or "
"the folios of a conductor or folio context) keeps following it; "
"remove_autonum refuses a context which elements (element kind) or "
"folios (conductor and folio kinds) still follow. "
"use_element_autonum selects the context and number_element applies "
"it to one element AFTER it is placed (add_element does not number "
"what it places); slaves and reports are refused, since they take "
+7
View File
@@ -441,6 +441,7 @@ class EditValidation(unittest.TestCase):
"sort_terminal_strip": [{"op": "sort_terminal_strip", "strip": 0}],
"add_autonum": [{"op": "add_autonum", "kind": "conductor", "name": "W", "parts": ["string:W"]}],
"remove_autonum": [{"op": "remove_autonum", "kind": "conductor", "name": "W"}],
"rename_autonum": [{"op": "rename_autonum", "kind": "element", "name": "EL", "new_name": "EL2"}],
"use_conductor_autonum": [f, {"op": "use_conductor_autonum", "folio": "$f", "name": "W"}],
"use_element_autonum": [{"op": "use_element_autonum", "name": "EL"}],
"insert_folio": [{"op": "insert_folio", "id": "i", "position": 0}],
@@ -458,6 +459,12 @@ class EditValidation(unittest.TestCase):
"duplicate_elements": el + [{"op": "duplicate_elements", "id": "d", "folio": "$f",
"elements": ["$e"], "to_folio": "$f", "x": 50, "y": 50}],
"number_element": el + [{"op": "number_element", "folio": "$f", "element": "$e"}],
"renumber_element_autonum": [{"op": "renumber_element_autonum", "name": "EL"}],
"free_element_numbers": el + [{"op": "free_element_numbers", "folio": "$f", "element": "$e"}],
"assign_element_number": el + [{"op": "assign_element_number", "folio": "$f",
"element": "$e", "number": 3}],
"assign_element_autonum": el + [{"op": "assign_element_autonum", "name": "EL", "folio": "$f",
"element": "$e", "overwrite": False}],
"add_table": [f, {"op": "add_table", "id": "t", "folio": "$f", "kind": "nomenclature",
"name": "BOM", "query": "SELECT label FROM element_nomenclature_view"}],
"set_table_position": [f, {"op": "add_table", "id": "t", "folio": "$f", "kind": "nomenclature",
+297
View File
@@ -0,0 +1,297 @@
/*
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 "autonumschemecommand.h"
#include "../diagram.h"
#include "../qetproject.h"
#include <algorithm>
AutoNumSchemeCommand::AutoNumSchemeCommand(QETProject *project, Kind kind) :
m_project(project),
m_kind(kind)
{}
/// @return the titles of the schemes of @p kind, sorted
QStringList AutoNumSchemeCommand::titles(const QETProject *project, Kind kind)
{
if (!project) {
return {};
}
QStringList list = kind == Kind::Conductor ? project->conductorAutoNum().keys()
: project->folioAutoNum().keys();
list.sort(Qt::CaseInsensitive);
return list;
}
bool AutoNumSchemeCommand::contains(const QETProject *project, Kind kind, const QString &title)
{
if (!project) {
return false;
}
return kind == Kind::Conductor ? project->conductorAutoNum().contains(title)
: project->folioAutoNum().contains(title);
}
NumerotationContext AutoNumSchemeCommand::contextOf(const QETProject *project, Kind kind, const QString &title)
{
if (!project) {
return NumerotationContext();
}
return kind == Kind::Conductor ? project->conductorAutoNum(title)
: project->folioAutoNum(title);
}
/**
@return the title of the scheme @p name clashes with, empty if none:
the same name, whatever the case or the white space
@param ignored_title : the scheme being renamed
*/
QString AutoNumSchemeCommand::nameClash(const QETProject *project, Kind kind,
const QString &name, const QString &ignored_title)
{
const QString wanted = QETProject::normalizedAutoNumName(name);
for (const QString &title : titles(project, kind)) {
if (title == ignored_title) {
continue;
}
if (QString::compare(QETProject::normalizedAutoNumName(title), wanted,
Qt::CaseInsensitive) == 0) {
return title;
}
}
return QString();
}
/// @return why @p name cannot be the name of a scheme, translated, empty if it can
QString AutoNumSchemeCommand::nameProblem(const QETProject *project, Kind kind,
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.");
}
const QString clash = nameClash(project, kind, name, ignored_title);
if (!clash.isEmpty()) {
return tr("Une numérotation nommée « %1 » existe déjà.").arg(clash);
}
return QString();
}
/**
@return the folios which follow the scheme @p title
@param references : every folio which names it, for a folio numbering,
even if its number has been written into the folio field already (see
the class description)
*/
QList<Diagram *> AutoNumSchemeCommand::usersOf(const QETProject *project, Kind kind,
const QString &title, bool references)
{
QList<Diagram *> users;
if (!project || title.isEmpty()) {
return users;
}
for (Diagram *diagram : project->diagrams()) {
if (kind == Kind::Conductor) {
if (diagram->conductorsAutonumName() == title) {
users << diagram;
}
} else if (diagram->border_and_titleblock.autoPageNum() == title
&& (references || diagram->border_and_titleblock.folio().contains(QLatin1String("%autonum")))) {
users << diagram;
}
}
return users;
}
AutoNumSchemeCommand *AutoNumSchemeCommand::create(QETProject *project, Kind kind,
const QString &title,
const NumerotationContext &context,
bool make_current)
{
if (!project || !nameProblem(project, kind, title).isEmpty()) {
return nullptr;
}
auto *cmd = new AutoNumSchemeCommand(project, kind);
cmd->m_after = Scheme{QETProject::normalizedAutoNumName(title), context};
cmd->m_current_before = project->conductorCurrentAutoNum();
cmd->m_current_after = (kind == Kind::Conductor && make_current)
? cmd->m_after->title
: cmd->m_current_before;
cmd->setText(tr("Créer la numérotation %1").arg(cmd->m_after->title));
return cmd;
}
/**
Rename the scheme @p old_title to @p new_title and give it @p context.
@param make_current : for conductors, make it the scheme new conductors
take; otherwise it stays so if it was
@return nullptr if there is no such scheme, the new name is not
acceptable, or nothing changes
*/
AutoNumSchemeCommand *AutoNumSchemeCommand::edit(QETProject *project, Kind kind,
const QString &old_title,
const QString &new_title,
const NumerotationContext &context,
bool make_current)
{
if (!project || !contains(project, kind, old_title)) {
return nullptr;
}
if (!nameProblem(project, kind, new_title, old_title).isEmpty()) {
return nullptr;
}
auto *cmd = new AutoNumSchemeCommand(project, kind);
cmd->m_before = Scheme{old_title, contextOf(project, kind, old_title)};
cmd->m_after = Scheme{QETProject::normalizedAutoNumName(new_title), context};
cmd->m_current_before = project->conductorCurrentAutoNum();
cmd->m_current_after = kind == Kind::Conductor
? ((make_current || cmd->m_current_before == old_title)
? cmd->m_after->title : cmd->m_current_before)
: cmd->m_current_before;
if (cmd->m_before->title != cmd->m_after->title) {
cmd->m_users = [&] {
QList<QPointer<Diagram>> list;
for (Diagram *d : usersOf(project, kind, old_title, true)) list << d;
return list;
}();
}
bool same_definition = cmd->m_before->context.size() == cmd->m_after->context.size();
for (int i = 0 ; same_definition && i < cmd->m_before->context.size() ; ++i) {
same_definition = cmd->m_before->context[i] == cmd->m_after->context[i];
}
if (cmd->m_before->title == cmd->m_after->title && same_definition
&& cmd->m_current_before == cmd->m_current_after) {
delete cmd;
return nullptr;
}
if (cmd->m_before->title != cmd->m_after->title && same_definition) {
cmd->setText(tr("Renommer la numérotation %1 en %2")
.arg(cmd->m_before->title, cmd->m_after->title));
} else {
cmd->setText(tr("Modifier la numérotation %1").arg(cmd->m_after->title));
}
return cmd;
}
/**
Remove the scheme @p title.
@return nullptr if there is no such scheme or a folio still follows it
*/
AutoNumSchemeCommand *AutoNumSchemeCommand::remove(QETProject *project, Kind kind,
const QString &title)
{
if (!project || !contains(project, kind, title)) {
return nullptr;
}
if (!usersOf(project, kind, title).isEmpty()) {
return nullptr;
}
auto *cmd = new AutoNumSchemeCommand(project, kind);
cmd->m_before = Scheme{title, contextOf(project, kind, title)};
cmd->m_current_before = project->conductorCurrentAutoNum();
cmd->m_current_after = (kind == Kind::Conductor && cmd->m_current_before == title)
? QString() : cmd->m_current_before;
cmd->setText(tr("Supprimer la numérotation %1").arg(title));
return cmd;
}
/// Let the windows which list the schemes read them again
void AutoNumSchemeCommand::announce(bool added, bool removed)
{
if (m_kind == Kind::Conductor) {
if (removed) m_project->conductorAutoNumRemoved();
if (added) m_project->conductorAutoNumAdded();
} else {
if (removed) m_project->folioAutoNumRemoved();
if (added) m_project->folioAutoNumAdded();
}
}
/// Give the scheme @p from the title @p to, and the folios which follow it
void AutoNumSchemeCommand::rename(const QString &from, const QString &to)
{
const NumerotationContext context = contextOf(m_project, m_kind, from);
if (m_kind == Kind::Conductor) {
m_project->removeConductorAutoNum(from);
m_project->addConductorAutoNum(to, context);
if (m_project->conductorCurrentAutoNum() == from) {
m_project->setCurrentConductorAutoNum(to);
}
for (Diagram *d : m_project->diagrams()) {
//The highest folio number given, kept by scheme title
for (auto *hash : {&d->m_cnd_unitfolio_max, &d->m_cnd_tenfolio_max, &d->m_cnd_hundredfolio_max}) {
if (hash->contains(from)) {
hash->insert(to, hash->take(from));
}
}
}
} else {
m_project->removeFolioAutoNum(from);
m_project->addFolioAutoNum(to, context);
}
for (const QPointer<Diagram> &d : std::as_const(m_users)) {
if (!d) continue;
if (m_kind == Kind::Conductor) {
d->setConductorsAutonumName(to);
} else {
d->border_and_titleblock.setAutoPageNum(to);
}
}
}
void AutoNumSchemeCommand::redo()
{
if (!m_project) return;
if (m_before && m_after && m_before->title != m_after->title) {
rename(m_before->title, m_after->title);
} else if (m_before && !m_after) {
if (m_kind == Kind::Conductor) m_project->removeConductorAutoNum(m_before->title);
else m_project->removeFolioAutoNum(m_before->title);
}
if (m_after) {
if (m_kind == Kind::Conductor) m_project->addConductorAutoNum(m_after->title, m_after->context);
else m_project->addFolioAutoNum(m_after->title, m_after->context);
}
if (m_kind == Kind::Conductor) {
m_project->setCurrentConductorAutoNum(m_current_after);
}
announce(m_after.has_value(), m_before.has_value() && (!m_after || m_after->title != m_before->title));
}
void AutoNumSchemeCommand::undo()
{
if (!m_project) return;
if (m_before && m_after && m_before->title != m_after->title) {
rename(m_after->title, m_before->title);
} else if (!m_before && m_after) {
if (m_kind == Kind::Conductor) m_project->removeConductorAutoNum(m_after->title);
else m_project->removeFolioAutoNum(m_after->title);
}
if (m_before) {
if (m_kind == Kind::Conductor) m_project->addConductorAutoNum(m_before->title, m_before->context);
else m_project->addFolioAutoNum(m_before->title, m_before->context);
}
if (m_kind == Kind::Conductor) {
m_project->setCurrentConductorAutoNum(m_current_before);
}
announce(m_before.has_value(), m_after.has_value() && (!m_before || m_after->title != m_before->title));
}
+111
View File
@@ -0,0 +1,111 @@
/*
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/>.
*/
#ifndef AUTONUMSCHEMECOMMAND_H
#define AUTONUMSCHEMECOMMAND_H
#include "numerotationcontext.h"
#include <QCoreApplication>
#include <QList>
#include <QPointer>
#include <QUndoCommand>
#include <optional>
class Diagram;
class QETProject;
/**
@brief The AutoNumSchemeCommand class
Undoable creation, edition (rename and/or new definition) and removal of
one conductor or folio numbering scheme of a project.
A folio refers to the scheme it follows by its title, as the project file
has always had it (the title of the conductor numbering a folio reads,
the title of the folio numbering its title block follows). So a rename
moves those references with it, in the same undo step, and a scheme which
a folio still follows cannot be removed: the folio would be left reading
a numbering which does not exist.
A folio numbering is applied once: the number is written into the folio
field of the title block, in place of %autonum, and the title block keeps
the name of the numbering it came from. So a folio really depends on a
folio numbering only while its folio field still holds %autonum, and
only then can the numbering not be removed; every title block which
names it has its name moved by a rename.
Conductors keep the formula they were numbered with, and the numbers
folios show are worked out from the scheme when they are drawn: a new
definition changes what is numbered from then on, nothing is renumbered.
Counter changes made by numbering something are not edits of the scheme
and do not come here.
*/
class AutoNumSchemeCommand : public QUndoCommand
{
Q_DECLARE_TR_FUNCTIONS(AutoNumSchemeCommand)
public:
enum class Kind { Conductor, Folio };
struct Scheme
{
QString title;
NumerotationContext context;
};
static QStringList titles(const QETProject *project, Kind kind);
static bool contains(const QETProject *project, Kind kind, const QString &title);
static NumerotationContext contextOf(const QETProject *project, Kind kind, const QString &title);
static QString nameClash(const QETProject *project, Kind kind,
const QString &name, const QString &ignored_title = QString());
static QString nameProblem(const QETProject *project, Kind kind,
const QString &name, const QString &ignored_title = QString());
static QList<Diagram *> usersOf(const QETProject *project, Kind kind,
const QString &title, bool references = false);
static AutoNumSchemeCommand *create(QETProject *project, Kind kind,
const QString &title,
const NumerotationContext &context,
bool make_current = false);
static AutoNumSchemeCommand *edit(QETProject *project, Kind kind,
const QString &old_title,
const QString &new_title,
const NumerotationContext &context,
bool make_current = false);
static AutoNumSchemeCommand *remove(QETProject *project, Kind kind,
const QString &title);
void undo() override;
void redo() override;
private:
AutoNumSchemeCommand(QETProject *project, Kind kind);
void rename(const QString &from, const QString &to);
void announce(bool added, bool removed);
QETProject *m_project = nullptr;
Kind m_kind;
std::optional<Scheme> m_before;
std::optional<Scheme> m_after;
QString m_current_before;
QString m_current_after;
QList<QPointer<Diagram>> m_users; ///< the folios which follow the scheme being renamed
};
#endif // AUTONUMSCHEMECOMMAND_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,243 @@
/*
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/>.
*/
#ifndef ELEMENTAUTONUMSCHEMECOMMAND_H
#define ELEMENTAUTONUMSCHEMECOMMAND_H
#include "numerotationcontext.h"
#include "renumberelementscommand.h"
#include <QCoreApplication>
#include <QDomDocument>
#include <QDomElement>
#include <QList>
#include <QMap>
#include <QSet>
#include <QUndoCommand>
#include <QUuid>
#include <QVector>
#include <optional>
class Element;
class QETProject;
/**
@brief The ElementAutoNumSchemeCommand class
Undoable creation, edition (rename and/or new definition) and removal
of one element numbering scheme of a project, together with what it
does to the elements following the scheme.
Elements follow a scheme by its uuid (QETInformation::ELMT_FORMULA_ID),
so a rename touches no element. A new definition with a different
formula rewrites the formula of every element following the scheme:
when its sequential parts are the same (only the fixed text or the
folio parts changed), each element keeps its numbers; otherwise the
elements are numbered again, in folio and position order, as
QETProject::renumberElementsBySchemeTitle() does.
A scheme whose formula would change cannot be edited while an element
with a frozen label follows it: that element would keep a label and a
formula the scheme no longer defines (see editBlockedBy(); edit()
refuses). A rename, or a change of the counter, touches no formula and
is allowed. renumber() leaves a frozen element exactly as it is,
label, formula and numbers, and does not give its label to another
element: the numbering goes past it (see reservedLabels()).
Counter changes made by placing elements are not edits of the scheme:
they stay with SetAutoNumContextCommand.
Build one with create(), edit() or remove(); each returns nullptr when
the change is refused or changes nothing. Show changedElementCount()
to the user before pushing it when it is not zero.
*/
class ElementAutoNumSchemeCommand : public QUndoCommand
{
Q_DECLARE_TR_FUNCTIONS(ElementAutoNumSchemeCommand)
public:
struct Scheme
{
QString title;
QUuid id;
NumerotationContext context;
};
static ElementAutoNumSchemeCommand *create(
QETProject *project,
const QString &title,
const NumerotationContext &context,
const QUuid &id = QUuid(),
bool make_current = true);
static ElementAutoNumSchemeCommand *edit(
QETProject *project,
const QString &old_title,
const QString &new_title,
const NumerotationContext &context,
bool make_current = true);
static ElementAutoNumSchemeCommand *remove(
QETProject *project,
const QString &title);
static QString nameProblem(const QETProject *project,
const QString &name,
const QString &ignored_title = QString());
static bool sameSequentialParts(const QString &formula_a,
const QString &formula_b);
static NumerotationContext resetForRenumber(const NumerotationContext &context);
static QVector<RenumberElementsCommand::ElementChange> renumberChanges(
const QString &hash_key,
const NumerotationContext &context,
QVector<Element *> elements,
const QString &formula,
NumerotationContext *final_context,
bool from_start = true,
const QUuid &scheme_id = QUuid(),
const QSet<QString> &reserved = QSet<QString>());
static QSet<QString> reservedLabels(const QETProject *project,
const QString &title,
const QVector<Element *> &changing);
static RenumberElementsCommand *renumber(QETProject *project,
const QString &title,
QVector<Element *> *frozen,
const QString &text);
static QVector<Element *> frozenFollowers(const QETProject *project,
const QString &title);
static QVector<Element *> editBlockedBy(const QETProject *project,
const QString &title,
const NumerotationContext &context);
/// Why assign() leaves an element as it is
enum class Skipped {
None,
FollowsAlready, ///< already follows this scheme
Frozen, ///< its label is frozen
OtherFormula, ///< follows another scheme or has a formula of its own
Linked ///< slave or report: takes its label from its master
};
static Skipped skipReason(const QETProject *project,
const QString &title,
const Element *element);
/// What assign() would do with each of the given elements
struct AssignPlan
{
QVector<Element *> todo; ///< given the scheme
QVector<Element *> frozen; ///< a frozen label: left alone unless overwrite
QVector<Element *> otherFormula; ///< follow or hold another formula: left alone unless overwrite
int followsAlready = 0; ///< follow this scheme already
int linked = 0; ///< slave or report: take their label from their master
};
static AssignPlan assignPlan(const QETProject *project,
const QString &title,
const QVector<Element *> &elements);
static RenumberElementsCommand *assign(
QETProject *project,
const QString &title,
const QVector<Element *> &elements,
bool overwrite,
int *skipped = nullptr,
QUndoCommand *parent = nullptr);
/// What a numbered element's frozen state becomes
enum class FreezeRule {
Unfrozen, ///< free: the user asked for this numbering
NewElementPolicy ///< as an element just placed: frozen if the folio or project freezes new ones
};
static RenumberElementsCommand *numberElements(
QETProject *project,
const QString &title,
const QVector<Element *> &elements,
const QString &text,
FreezeRule freeze = FreezeRule::Unfrozen,
QUndoCommand *parent = nullptr);
static QString schemeForFormula(const QETProject *project, const QString &formula);
static void writeCopiedSchemes(QDomDocument &document,
QDomElement &root,
const QETProject *project,
const QVector<Element *> &copied);
static QList<Scheme> copiedSchemes(const QDomElement &root);
static QList<Scheme> missingForPaste(const QETProject *project,
const QList<Scheme> &copied,
const QList<Element *> &pasted);
/// How the elements of a scheme are numbered by hand (see numberSupport())
struct NumberSupport
{
bool supported = false;
int partIndex = -1; ///< the one number part of the definition
QString type; ///< "unit", "ten" or "hundred"
int increase = 1;
};
struct GapRange
{
int from = 0;
int to = 0;
};
/// A counter which would meet numbers already in use
struct CounterConflict
{
int counter = 0; ///< the next number it would hand out
int highest = 0; ///< the highest number in use
int count = 0; ///< how many elements have a number of at least counter
};
static std::optional<CounterConflict> counterConflict(const QETProject *project,
const QString &title,
const NumerotationContext &proposed);
static QSet<QString> labelsHeldBesides(const QETProject *project, const Element *element);
static NumberSupport numberSupport(const NumerotationContext &context);
static std::optional<int> numberOf(const NumberSupport &support, const Element *element);
static QList<GapRange> gapRanges(const QETProject *project, const QString &title);
static QList<int> freeNumbers(const QETProject *project, const QString &title,
const Element *element, int limit = 500);
static QString labelForNumber(const QETProject *project, const QString &title,
Element *element, int number);
static RenumberElementsCommand *assignNumber(QETProject *project,
Element *element,
int number,
QString *problem = nullptr,
QUndoCommand *parent = nullptr);
static QMap<QString, QVector<Element *>> pastedSchemes(
const QETProject *project,
const QList<Element *> &elements);
static void linkPasted(const QETProject *project,
const QList<Element *> &elements);
static int numberPasted(QETProject *project,
const QMap<QString, QVector<Element *>> &schemes,
QUndoCommand *parent);
/// Elements whose label and formula change
int changedElementCount() const {return m_changes.size();}
const QVector<RenumberElementsCommand::ElementChange> &changes() const {return m_changes;}
std::optional<Scheme> before() const {return m_before;}
std::optional<Scheme> after() const {return m_after;}
void undo() override;
void redo() override;
private:
explicit ElementAutoNumSchemeCommand(QETProject *project);
QETProject *m_project = nullptr;
std::optional<Scheme> m_before;
std::optional<Scheme> m_after;
QString m_current_before;
QString m_current_after;
QVector<RenumberElementsCommand::ElementChange> m_changes;
};
#endif // ELEMENTAUTONUMSCHEMECOMMAND_H
+21 -15
View File
@@ -26,8 +26,9 @@ RenumberElementsCommand::RenumberElementsCommand(
QVector<ElementChange> changes,
QHash<QString, NumerotationContext> old_ctx,
QHash<QString, NumerotationContext> new_ctx,
const QString &text)
: QUndoCommand(text)
const QString &text,
QUndoCommand *parent)
: QUndoCommand(text, parent)
, project_(project)
, changes_(std::move(changes))
, old_ctx_(std::move(old_ctx))
@@ -61,18 +62,23 @@ void RenumberElementsCommand::apply(bool use_new)
// Apply per-element changes
for (const ElementChange &c : changes_) {
if (!c.element) continue;
const bool frozen = use_new ? c.new_frozen : c.old_frozen;
const auto &infos = use_new ? c.new_infos : c.old_infos;
const auto &seq = use_new ? c.new_seq : c.old_seq;
// Temporarily unfreeze so that label/infos update correctly.
const bool was_frozen = c.element->isFreezeLabel();
if (was_frozen) c.element->freezeLabel(false);
c.element->rSequenceStruct() = seq;
c.element->setElementInformations(infos);
c.element->freezeLabel(frozen);
applyChange(c, use_new);
}
}
void RenumberElementsCommand::applyChange(const ElementChange &c, bool use_new)
{
if (!c.element) return;
const bool frozen = use_new ? c.new_frozen : c.old_frozen;
const auto &infos = use_new ? c.new_infos : c.old_infos;
const auto &seq = use_new ? c.new_seq : c.old_seq;
// Temporarily unfreeze so that label/infos update correctly.
const bool was_frozen = c.element->isFreezeLabel();
if (was_frozen) c.element->freezeLabel(false);
c.element->rSequenceStruct() = seq;
c.element->setElementInformations(infos);
c.element->freezeLabel(frozen);
}
+7 -1
View File
@@ -51,11 +51,17 @@ public:
QVector<ElementChange> changes,
QHash<QString, NumerotationContext> old_ctx,
QHash<QString, NumerotationContext> new_ctx,
const QString &text);
const QString &text,
QUndoCommand *parent = nullptr);
const QVector<ElementChange> &changes() const { return changes_; }
void undo() override;
void redo() override;
/// Apply one change's old (@p use_new false) or new state.
static void applyChange(const ElementChange &change, bool use_new);
private:
void apply(bool use_new);
@@ -19,6 +19,11 @@
#include "../../diagram.h"
#include "../../diagramview.h"
#include "../../qetgraphicsitem/element.h"
#include "../../qeticons.h"
#include "../elementautonumschemecommand.h"
#include "counterwarning.h"
#include "renumberpreviewdialog.h"
#include "../../qetapp.h"
#include "../../shortcutmanager.h"
#include "../../titleblockproperties.h"
@@ -29,7 +34,10 @@
#include "../../undocommand/changetitleblockcommand.h"
#include <QComboBox>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QSignalBlocker>
#include <QSpinBox>
@@ -43,9 +51,127 @@ AutoNumberingDockWidget::AutoNumberingDockWidget(QWidget *parent) :
ui(new Ui::AutoNumberingDockWidget)
{
ui->setupUi(this);
//The element row gets a button to the left of its reset button
//which gives the numbering shown to the selected elements
QPushButton *reset = ui->m_element_reset_start_pb;
ui->gridLayout->removeWidget(reset);
auto *actions = new QWidget(this);
auto *actions_layout = new QHBoxLayout(actions);
actions_layout->setContentsMargins(0, 0, 0, 0);
actions_layout->setSpacing(2);
m_element_apply_pb = new QPushButton(QET::Icons::DialogOk, QString(), actions);
m_element_apply_pb->setObjectName(QStringLiteral("m_element_apply_pb"));
m_element_apply_pb->setMaximumWidth(24);
m_element_apply_pb->setToolTip(tr("Appliquer cette numérotation aux éléments sélectionnés"));
reset->setParent(actions);
actions_layout->addWidget(m_element_apply_pb);
actions_layout->addWidget(reset);
ui->gridLayout->addWidget(actions, 3, 2);
connect(m_element_apply_pb, &QPushButton::clicked,
this, &AutoNumberingDockWidget::applyElementSchemeToSelection);
connect(ui->m_element_cb, qOverload<int>(&QComboBox::currentIndexChanged),
this, &AutoNumberingDockWidget::updateApplyEnabled);
m_element_apply_pb->setEnabled(false);
this->setDisabled(true);
}
/**
@brief AutoNumberingDockWidget::applyElementSchemeToSelection
Give the element numbering shown in the list to the elements selected
in the current folio: each takes its formula and the next number, in
folio and position order, and the counter moves on.
What would change is shown first. An element which holds another
numbering, or whose label is frozen, is left as it is, unless the user
asks to replace them too (a frozen one is then given back). One undo
step.
*/
void AutoNumberingDockWidget::applyElementSchemeToSelection()
{
if (!m_project || m_project->isReadOnly()) {
return;
}
const QString caption = tr("Appliquer la numérotation");
const QString title = ui->m_element_cb->currentText();
DiagramView *view = m_project_view ? m_project_view->currentDiagram() : nullptr;
Diagram *diagram = view ? view->diagram() : nullptr;
if (!diagram || !m_project->elementAutoNum().contains(title)) {
QMessageBox::information(this, caption,
tr("Choisissez une numérotation d'éléments du projet."));
return;
}
QVector<Element *> selected;
const auto items = diagram->selectedItems();
for (QGraphicsItem *item : items) {
if (auto *el = qgraphicsitem_cast<Element *>(item)) {
selected << el;
}
}
if (selected.isEmpty()) {
QMessageBox::information(this, caption,
tr("Sélectionnez d'abord des éléments dans le folio."));
return;
}
const auto plan = ElementAutoNumSchemeCommand::assignPlan(m_project, title, selected);
QVector<RenumberPreviewDialog::LeftAlone> left;
for (Element *el : plan.frozen) {
left.append({el, tr("(figé)")});
}
for (Element *el : plan.otherFormula) {
left.append({el, tr("(autre numérotation)")});
}
const int replaceable = static_cast<int>(left.size());
auto *cmd = ElementAutoNumSchemeCommand::assign(m_project, title, selected, false);
if (!cmd && !replaceable) {
QMessageBox::information(
this, caption,
tr("Rien à appliquer : les éléments sélectionnés suivent déjà "
"la numérotation « %1 », ou prennent leur nom d'un autre élément.").arg(title));
return;
}
const QString intro = cmd
? tr("%n élément(s) vont recevoir la numérotation « %1 », dans l'ordre des folios "
"et des positions.", "", static_cast<int>(plan.todo.size())).arg(title)
: tr("Aucun élément ne peut recevoir la numérotation « %1 » sans remplacer "
"ce qu'il a.").arg(title);
const auto answer = RenumberPreviewDialog::ask(
this, caption, intro,
cmd ? cmd->changes() : QVector<RenumberElementsCommand::ElementChange>(),
left,
replaceable
? tr("Remplacer aussi les %n élément(s) laissés comme ils sont", "", replaceable)
: QString());
if (answer == RenumberPreviewDialog::Answer::Cancel) {
delete cmd;
return;
}
int applied = cmd ? static_cast<int>(cmd->changes().size()) : 0;
if (answer == RenumberPreviewDialog::Answer::GoAndReplace) {
delete cmd;
cmd = ElementAutoNumSchemeCommand::assign(m_project, title, selected, true);
applied = cmd ? static_cast<int>(cmd->changes().size()) : 0;
}
if (cmd) {
m_project->undoStack()->push(cmd);
}
const int not_given = static_cast<int>(selected.size()) - applied;
if (not_given > 0) {
QMessageBox::information(
this, caption,
tr("%n élément(s) numérotés.", "", applied)
+ QLatin1Char('\n')
+ tr("%n élément(s) sélectionnés sont restés comme ils sont.", "", not_given));
}
}
/**
@brief AutoNumberingDockWidget::~AutoNumberingDockWidget
Destructor
@@ -117,6 +243,8 @@ void AutoNumberingDockWidget::setProject(QETProject *project,
disconnect(m_project, &QETProject::conductorAutoNumRemoved, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
disconnect(m_project, &QETProject::conductorAutoNumAdded, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
disconnect(m_project_view, &ProjectView::diagramActivated, this, &AutoNumberingDockWidget::setConductorActive);
disconnect(m_project_view, &ProjectView::diagramActivated, this, &AutoNumberingDockWidget::followSelectionOf);
followSelectionOf(nullptr);
//Element Signals
disconnect(m_project, &QETProject::elementAutoNumRemoved, this, &AutoNumberingDockWidget::elementAutoNumChanged);
@@ -142,6 +270,8 @@ void AutoNumberingDockWidget::setProject(QETProject *project,
connect(m_project, &QETProject::conductorAutoNumRemoved, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
connect(m_project, &QETProject::conductorAutoNumAdded, this, &AutoNumberingDockWidget::conductorAutoNumChanged);
connect(m_project_view, &ProjectView::diagramActivated, this, &AutoNumberingDockWidget::setConductorActive);
connect(m_project_view, &ProjectView::diagramActivated, this, &AutoNumberingDockWidget::followSelectionOf);
followSelectionOf(m_project_view->currentDiagram());
//Element Signals
connect(m_project, &QETProject::elementAutoNumRemoved, this, &AutoNumberingDockWidget::elementAutoNumChanged);
@@ -207,6 +337,56 @@ void AutoNumberingDockWidget::setContext()
this->setActive();
}
/**
@brief AutoNumberingDockWidget::followSelectionOf
Enable the apply button only while elements are selected in the folio
shown: it gives a numbering to the selected elements, and with none
selected there is nothing for it to do.
@param view : the folio now shown, nullptr for none
*/
void AutoNumberingDockWidget::followSelectionOf(DiagramView *view)
{
Diagram *diagram = view ? view->diagram() : nullptr;
if (m_selection_diagram) {
disconnect(m_selection_diagram, &QGraphicsScene::selectionChanged,
this, &AutoNumberingDockWidget::updateApplyEnabled);
}
m_selection_diagram = diagram;
if (m_selection_diagram) {
connect(m_selection_diagram, &QGraphicsScene::selectionChanged,
this, &AutoNumberingDockWidget::updateApplyEnabled);
}
updateApplyEnabled();
}
/**
@brief AutoNumberingDockWidget::updateApplyEnabled
The apply button needs a numbering to give and elements selected.
*/
void AutoNumberingDockWidget::updateApplyEnabled()
{
if (!m_element_apply_pb) {
return;
}
bool elements_selected = false;
if (m_selection_diagram) {
const auto items = m_selection_diagram->selectedItems();
for (QGraphicsItem *item : items) {
if (qgraphicsitem_cast<Element *>(item)) {
elements_selected = true;
break;
}
}
}
m_element_apply_pb->setEnabled(
elements_selected && m_project && !m_project->isReadOnly()
&& ui->m_element_cb->count() > 0);
m_element_apply_pb->setToolTip(
elements_selected
? tr("Appliquer cette numérotation aux éléments sélectionnés")
: tr("Sélectionnez d'abord des éléments dans le folio"));
}
/**
@brief AutoNumberingDockWidget::setConductorActive
@param dv: activated diagramview
@@ -539,6 +719,12 @@ void AutoNumberingDockWidget::applyValueField(QComboBox *combo_box, QLineEdit *l
}
context.replaceValue(index, typed);
//An element counter put back on numbers in use: ask first
if (category == AutoNumCategory::Element
&& !CounterWarning::confirm(this, m_project, combo_box->currentText(), context)) {
refreshRow(category);
return;
}
storeContext(combo_box, category, context);
refreshRow(category);
}
@@ -684,6 +870,12 @@ void AutoNumberingDockWidget::resetAutoNum(QComboBox *combo_box, AutoNumCategory
context.replaceValue(i, QStringLiteral("a"));
}
//Back to the first number, over numbers in use: ask first
if (category == AutoNumCategory::Element
&& !CounterWarning::confirm(this, m_project, combo_box->currentText(), context)) {
return;
}
storeContext(combo_box, category, context);
refreshRow(category);
}
@@ -22,9 +22,11 @@
#include "../../qetproject.h"
#include <QDockWidget>
#include <QPointer>
class QComboBox;
class QLineEdit;
class QPushButton;
class QSpinBox;
namespace Ui {
@@ -58,6 +60,9 @@ class AutoNumberingDockWidget : public QDockWidget
void refreshValueFields();
void on_m_configure_pb_clicked();
void applyElementSchemeToSelection();
void updateApplyEnabled();
void followSelectionOf(DiagramView *view);
void on_m_conductor_reset_start_pb_clicked();
void on_m_element_reset_start_pb_clicked();
@@ -126,6 +131,8 @@ class AutoNumberingDockWidget : public QDockWidget
/// this replaces refreshValueField() at each of them.
void refreshRow(AutoNumCategory category);
QPushButton *m_element_apply_pb = nullptr;
QPointer<Diagram> m_selection_diagram; ///< the folio whose selection enables the apply button
Ui::AutoNumberingDockWidget *ui;
QETProject* m_project = nullptr;
ProjectView* m_project_view = nullptr;
@@ -16,8 +16,11 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "autonumberingmanagementw.h"
#include "../elementautonumschemecommand.h"
#include "renumberpreviewdialog.h"
#include "../../diagram.h"
#include "../../qetgraphicsitem/element.h"
#include "../../qetproject.h"
#include "../numerotationcontextcommands.h"
#include "formulaautonumberingw.h"
@@ -122,7 +125,42 @@ void AutoNumberingManagementW::on_m_renumber_elements_pb_clicked()
RenumberElementsDialog dlg(titles, this);
if (dlg.exec() != QDialog::Accepted) return;
project_->renumberElementsBySchemeTitle(dlg.selectedSchemeTitle());
const QString title = dlg.selectedSchemeTitle();
QVector<Element *> frozen;
auto *cmd = ElementAutoNumSchemeCommand::renumber(
project_, title, &frozen,
title.isEmpty() ? tr("Renuméroter les éléments")
: tr("Renuméroter les éléments (%1)").arg(title));
if (!cmd)
{
QMessageBox::information(
this, tr("Renuméroter les éléments"),
frozen.isEmpty()
? tr("Aucun élément ne suit cette numérotation.")
: tr("Les %n élément(s) qui suivent cette numérotation ont un nom "
"figé : rien n'est renuméroté.", "", frozen.size()));
return;
}
const int renumbered = static_cast<int>(cmd->changes().size());
const int changed = RenumberPreviewDialog::changedLabelCount(cmd->changes());
if (!RenumberPreviewDialog::confirm(
this, tr("Renuméroter les éléments"),
tr("%n élément(s) vont être renumérotés, à partir du premier numéro, "
"dans l'ordre des folios et des positions.", "", renumbered),
cmd->changes(), frozen)) {
delete cmd;
return;
}
project_->undoStack()->push(cmd);
QString summary = tr("%n élément(s) renumérotés, dont %1 avec un nouveau nom.", "", renumbered)
.arg(changed);
if (!frozen.isEmpty()) {
summary += QLatin1Char('\n') + tr("%n élément(s) au nom figé n'ont pas été touchés, "
"et leur numéro n'a pas été redonné.", "", frozen.size());
}
QMessageBox::information(this, tr("Renuméroter les éléments"), summary);
}
/**
+50
View File
@@ -0,0 +1,50 @@
/*
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 "counterwarning.h"
#include "../elementautonumschemecommand.h"
#include "../../qetmessagebox.h"
/**
@brief CounterWarning::confirm
@param title : the element numbering
@param proposed : its definition, with the counter to give it
@return true if the counter meets no number in use, or the user goes
on; false if the user gives up (and, with nobody to ask, always)
*/
bool CounterWarning::confirm(QWidget *parent,
const QETProject *project,
const QString &title,
const NumerotationContext &proposed)
{
const auto conflict = ElementAutoNumSchemeCommand::counterConflict(project, title, proposed);
if (!conflict) {
return true;
}
const auto answer = QET::QetMessageBox::question(
parent,
tr("Compteur de la numérotation"),
tr("Le prochain numéro serait %1, mais %n élément(s) de la numérotation « %2 » "
"ont déjà un numéro égal ou supérieur (jusqu'à %3).", "", conflict->count)
.arg(conflict->counter).arg(title).arg(conflict->highest)
+ tr("\nLes nouveaux éléments sauteront les numéros déjà pris : ils ne recevront "
"pas forcément les numéros à partir de %1.\n\nContinuer ?").arg(conflict->counter),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel);
return answer == QMessageBox::Yes;
}
+46
View File
@@ -0,0 +1,46 @@
/*
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/>.
*/
#ifndef COUNTERWARNING_H
#define COUNTERWARNING_H
#include "../numerotationcontext.h"
#include <QCoreApplication>
class QETProject;
class QWidget;
/**
@brief The CounterWarning class
Asks before the counter of an element numbering is set to a number which
elements of it have already: the next elements skip the numbers in use,
so the next number is not the one asked for. The user may go on or give
up.
*/
class CounterWarning
{
Q_DECLARE_TR_FUNCTIONS(CounterWarning)
public:
static bool confirm(QWidget *parent,
const QETProject *project,
const QString &title,
const NumerotationContext &proposed);
};
#endif // COUNTERWARNING_H
@@ -0,0 +1,94 @@
/*
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 "pastenumberingimport.h"
#include "../../diagram.h"
#include "../../diagramcommands.h"
#include "../../qetmessagebox.h"
#include "../../qetproject.h"
#include <QSettings>
/// @return the numberings a copy carries, which pasted elements follow
QList<ElementAutoNumSchemeCommand::Scheme> PasteNumberingImport::copiedBy(const QDomDocument &clipboard)
{
return ElementAutoNumSchemeCommand::copiedSchemes(clipboard.documentElement());
}
/**
@brief PasteNumberingImport::push
Push the paste of @p content on the undo stack of @p diagram, after
asking whether to import the numberings of @p copied which the pasted
elements follow and the project does not have.
@param parent : parent of the question
@param copied : the numberings the copy carries
*/
void PasteNumberingImport::push(QWidget *parent,
Diagram *diagram,
const DiagramContent &content,
const QList<ElementAutoNumSchemeCommand::Scheme> &copied)
{
QETProject *project = diagram->project();
auto *paste = new PasteDiagramCommand(diagram, content);
QList<ElementAutoNumSchemeCommand::Scheme> missing;
if (project && !copied.isEmpty()
&& QSettings().value("diagramcommands/autonumber-pasted-elements", true).toBool()) {
missing = ElementAutoNumSchemeCommand::missingForPaste(project, copied, content.m_elements);
}
if (missing.isEmpty()) {
diagram->undoStack().push(paste);
return;
}
QStringList names;
for (const auto &scheme : std::as_const(missing)) {
names << QStringLiteral("« %1 »").arg(scheme.title);
}
const auto answer = QET::QetMessageBox::question(
parent,
tr("Numérotation absente de ce projet"),
tr("Les éléments collés suivent %n numérotation(s) qui n'existe(nt) pas dans ce projet : %1.\n\n"
"Les importer ? Les éléments recevront alors les prochains numéros, à partir de 1. "
"Sinon ils seront collés sans numérotation.", "", static_cast<int>(missing.size()))
.arg(names.join(QStringLiteral(", "))),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
if (answer != QMessageBox::Yes) {
diagram->undoStack().push(paste);
return;
}
diagram->undoStack().beginMacro(paste->text());
for (const auto &scheme : std::as_const(missing))
{
//Under its own name, or one which is free here
QString title = scheme.title;
for (int n = 2 ; !project->elementAutoNumNameClash(title).isEmpty() ; ++n) {
title = QStringLiteral("%1 (%2)").arg(scheme.title).arg(n);
}
if (auto *cmd = ElementAutoNumSchemeCommand::create(
project, title,
ElementAutoNumSchemeCommand::resetForRenumber(scheme.context),
scheme.id, false)) {
diagram->undoStack().push(cmd);
}
}
diagram->undoStack().push(paste);
diagram->undoStack().endMacro();
}
+57
View File
@@ -0,0 +1,57 @@
/*
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/>.
*/
#ifndef PASTENUMBERINGIMPORT_H
#define PASTENUMBERINGIMPORT_H
#include "../elementautonumschemecommand.h"
#include "../../diagramcontent.h"
#include <QCoreApplication>
#include <QList>
class Diagram;
class QWidget;
/**
@brief The PasteNumberingImport class
Pastes elements copied from another project, which may follow element
numberings this project does not have.
Such an element would be left with a formula which names no numbering
here, and with a label nothing explains. So the user is asked, once
for all the numberings missing, whether to import them. Imported, they
number the pasted elements from their first number (a number of the other
project means nothing here); not imported, the elements are pasted
without a numbering.
The import and the paste are one undo step.
*/
class PasteNumberingImport
{
Q_DECLARE_TR_FUNCTIONS(PasteNumberingImport)
public:
static void push(QWidget *parent,
Diagram *diagram,
const DiagramContent &content,
const QList<ElementAutoNumSchemeCommand::Scheme> &copied);
static QList<ElementAutoNumSchemeCommand::Scheme> copiedBy(const QDomDocument &clipboard);
};
#endif // PASTENUMBERINGIMPORT_H
@@ -0,0 +1,187 @@
/*
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 "renumberpreviewdialog.h"
#include "../../diagram.h"
#include "../../qetgraphicsitem/element.h"
#include "../../qetinformation.h"
#include <QBrush>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFont>
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include <QTableWidget>
#include <QVBoxLayout>
namespace {
QString labelOf(const DiagramContext &info)
{
return info.value(QETInformation::ELMT_LABEL).toString();
}
QString folioOf(const Element *element)
{
return element && element->diagram()
? QString::number(element->diagram()->folioIndex() + 1)
: QString();
}
} // namespace
/**
@brief RenumberPreviewDialog::changedLabelCount
@return how many of @p changes give their element another label
*/
int RenumberPreviewDialog::changedLabelCount(
const QVector<RenumberElementsCommand::ElementChange> &changes)
{
int count = 0;
for (const auto &change : changes) {
if (labelOf(change.old_infos) != labelOf(change.new_infos)) {
++count;
}
}
return count;
}
/**
@brief RenumberPreviewDialog::ask
@param intro : text above the list
@param changes : what the operation would do to elements
@param left_alone : the elements it leaves as they are, each with the
reason shown in place of its new name
@param replace_text : when not empty, a third button of that text
which goes on and also replaces the elements left alone
@return what the user chose
*/
RenumberPreviewDialog::Answer RenumberPreviewDialog::ask(
QWidget *parent,
const QString &title,
const QString &intro,
const QVector<RenumberElementsCommand::ElementChange> &changes,
const QVector<LeftAlone> &left_alone,
const QString &replace_text)
{
QDialog dialog(parent);
dialog.setWindowTitle(title);
dialog.resize(640, 460);
auto *layout = new QVBoxLayout(&dialog);
const int changed = changedLabelCount(changes);
QString text = intro;
if (changes.size() > changed) {
text += QLatin1Char('\n') + tr("%n élément(s) gardent le nom qu'ils ont.", "", changes.size() - changed);
}
if (!left_alone.isEmpty()) {
text += QLatin1Char('\n') + tr("%n élément(s) restent comme ils sont.", "", left_alone.size());
}
auto *intro_label = new QLabel(text, &dialog);
intro_label->setWordWrap(true);
layout->addWidget(intro_label);
auto *table = new QTableWidget(&dialog);
table->setColumnCount(4);
table->setHorizontalHeaderLabels({tr("Folio"), tr("Élément"), tr("Nom actuel"), tr("Nouveau nom")});
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setSelectionBehavior(QAbstractItemView::SelectRows);
table->verticalHeader()->setVisible(false);
table->horizontalHeader()->setStretchLastSection(true);
table->setAlternatingRowColors(true);
const auto add_row = [table](const QStringList &cells, bool grey) {
const int row = table->rowCount();
table->insertRow(row);
for (int column = 0 ; column < cells.size() ; ++column) {
auto *item = new QTableWidgetItem(cells.at(column));
if (grey) {
QFont font = item->font();
font.setItalic(true);
item->setFont(font);
item->setForeground(QBrush(Qt::gray));
}
table->setItem(row, column, item);
}
};
for (const auto &change : changes) {
const QString before = labelOf(change.old_infos);
const QString after = labelOf(change.new_infos);
if (before != after) {
add_row({folioOf(change.element),
change.element ? change.element->name() : QString(),
before, after}, false);
}
}
for (const LeftAlone &left : left_alone) {
if (left.element) {
add_row({folioOf(left.element), left.element->name(),
labelOf(left.element->elementInformations()), left.note}, true);
}
}
table->resizeColumnsToContents();
layout->addWidget(table, 1);
//With no label to change, OK would do nothing: only Cancel (and, where
//there is one, the button which replaces what is left alone) is offered
const bool something_to_do = !changes.isEmpty();
auto *buttons = new QDialogButtonBox(
something_to_do ? (QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
: QDialogButtonBox::Cancel, &dialog);
Answer answer = Answer::Cancel;
QPushButton *replace = nullptr;
if (!replace_text.isEmpty()) {
replace = buttons->addButton(replace_text, QDialogButtonBox::ActionRole);
}
QObject::connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
QObject::connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
if (replace) {
QObject::connect(replace, &QPushButton::clicked, &dialog, [&]() {
answer = Answer::GoAndReplace;
dialog.accept();
});
}
layout->addWidget(buttons);
if (dialog.exec() == QDialog::Accepted && answer != Answer::GoAndReplace) {
answer = Answer::Go;
}
return answer;
}
/**
@brief RenumberPreviewDialog::confirm
@param intro : text above the list
@param changes : what the operation would do to elements
@param frozen : the elements it leaves as they are, a frozen label
@return true if the user agrees to go on
*/
bool RenumberPreviewDialog::confirm(
QWidget *parent,
const QString &title,
const QString &intro,
const QVector<RenumberElementsCommand::ElementChange> &changes,
const QVector<Element *> &frozen)
{
QVector<LeftAlone> left;
for (Element *el : frozen) {
left.append({el, tr("(figé)")});
}
return ask(parent, title, intro, changes, left) == Answer::Go;
}
@@ -0,0 +1,68 @@
/*
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/>.
*/
#ifndef RENUMBERPREVIEWDIALOG_H
#define RENUMBERPREVIEWDIALOG_H
#include "../renumberelementscommand.h"
#include <QCoreApplication>
#include <QVector>
class Element;
class QWidget;
/**
@brief The RenumberPreviewDialog class
Shows what a numbering operation is about to do to the labels of
elements -- each changed element with its label now and the one it
would get -- and the elements with a frozen label it leaves as they are,
and asks to go on or not. Nothing is changed by the dialog itself.
*/
class RenumberPreviewDialog
{
Q_DECLARE_TR_FUNCTIONS(RenumberPreviewDialog)
public:
/// An element an operation leaves as it is, and why
struct LeftAlone
{
Element *element = nullptr;
QString note;
};
enum class Answer { Cancel, Go, GoAndReplace };
static Answer ask(
QWidget *parent,
const QString &title,
const QString &intro,
const QVector<RenumberElementsCommand::ElementChange> &changes,
const QVector<LeftAlone> &left_alone,
const QString &replace_text = QString());
static bool confirm(
QWidget *parent,
const QString &title,
const QString &intro,
const QVector<RenumberElementsCommand::ElementChange> &changes,
const QVector<Element *> &frozen);
static int changedLabelCount(
const QVector<RenumberElementsCommand::ElementChange> &changes);
};
#endif // RENUMBERPREVIEWDIALOG_H
+163 -49
View File
@@ -18,13 +18,17 @@
#include "selectautonumw.h"
#include "../assignvariables.h"
#include "../numerotationcontextcommands.h"
#include "formulaautonumberingw.h"
#include "numparteditorw.h"
#include "ui_formulaautonumberingw.h"
#include "ui_selectautonumw.h"
#include "../../qeticons.h"
#include <QHBoxLayout>
#include <QMessageBox>
#include <QPushButton>
#include <QToolButton>
/**
@brief SelectAutonumW::SelectAutonumW
@@ -39,6 +43,24 @@ SelectAutonumW::SelectAutonumW(int type, QWidget *parent) :
{
ui->setupUi(this);
ui->m_comboBox->lineEdit()->setClearButtonEnabled(true);
//The add button goes under the last part, at the left; each part
//has its own remove and move buttons at its right. The one remove
//button above the parts, which could only remove the last one, is
//not needed any more.
ui->remove_button->hide();
ui->horizontalLayout->removeWidget(ui->add_button);
m_add_row = new QWidget(this);
auto *add_layout = new QHBoxLayout(m_add_row);
add_layout->setContentsMargins(0, 0, 0, 0);
ui->add_button->setParent(m_add_row);
add_layout->addWidget(ui->add_button);
add_layout->addStretch();
ui->editor_layout->addWidget(m_add_row);
ui->buttonBox->button(QDialogButtonBox::Reset)->setText(tr("Annuler"));
ui->buttonBox->button(QDialogButtonBox::Reset)->setToolTip(
tr("Revenir à la définition enregistrée"));
if (m_edited_type == 0)
{
m_feaw = new FormulaAutonumberingW();
@@ -103,28 +125,125 @@ void SelectAutonumW::setContext(const NumerotationContext &context)
{
m_context = context;
qDeleteAll(num_part_list_);
num_part_list_.clear();
clearPartRows();
if (m_context.size() == 0) { //@context contain nothing, build a default numPartEditor
on_add_button_clicked();
insertPartRow(new NumPartEditorW(m_edited_type, this));
}
else {
for (int i=0; i<m_context.size(); ++i) { //build with the content of @context
NumPartEditorW *part= new NumPartEditorW(m_context, i, m_edited_type, this);
connect(part, &NumPartEditorW::changed, this, [this]() { applyEnable(); });
num_part_list_ << part;
ui -> editor_layout -> addWidget(part);
insertPartRow(new NumPartEditorW(m_context, i, m_edited_type, this));
}
}
num_part_list_.size() == 1 ?
ui -> remove_button -> setDisabled(true):
ui -> remove_button -> setEnabled (true);
updatePartButtons();
applyEnable(false);
}
/**
@brief SelectAutonumW::insertPartRow
Show @p part as the last part of the definition, with the buttons which
remove it and move it up or down at its right.
*/
void SelectAutonumW::insertPartRow(NumPartEditorW *part)
{
connect(part, &NumPartEditorW::changed, this, [this]() { applyEnable(); });
PartRow r;
r.part = part;
r.row = new QWidget(this);
auto *layout = new QHBoxLayout(r.row);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(2);
part->setParent(r.row);
layout->addWidget(part, 1);
const auto make_button = [&](const QIcon &icon, const QString &tip) {
auto *button = new QToolButton(r.row);
button->setIcon(icon);
button->setToolTip(tip);
button->setAutoRaise(true);
layout->addWidget(button);
return button;
};
r.up = make_button(QET::Icons::GoUp, tr("Monter cette variable"));
r.down = make_button(QET::Icons::GoDown, tr("Descendre cette variable"));
r.remove = make_button(QET::Icons::EditDelete, tr("Supprimer cette variable"));
QWidget *row = r.row;
connect(r.up, &QToolButton::clicked, this, [this, row]() { movePartRow(row, -1); });
connect(r.down, &QToolButton::clicked, this, [this, row]() { movePartRow(row, +1); });
connect(r.remove, &QToolButton::clicked, this, [this, row]() { removePartRow(row); });
m_rows << r;
num_part_list_ << part;
//Before the add button, which stays under the last part
ui->editor_layout->insertWidget(ui->editor_layout->count() - 1, r.row);
updatePartButtons();
}
/// Take away every part of the definition shown
void SelectAutonumW::clearPartRows()
{
for (const PartRow &r : std::as_const(m_rows)) {
delete r.row; //with the part, which it holds
}
m_rows.clear();
num_part_list_.clear();
}
/**
@brief SelectAutonumW::updatePartButtons
The first part cannot move up, the last cannot move down, and the only
one cannot be removed: those buttons are greyed out.
*/
void SelectAutonumW::updatePartButtons()
{
for (int i = 0 ; i < m_rows.size() ; ++i) {
m_rows.at(i).up->setEnabled(i > 0);
m_rows.at(i).down->setEnabled(i < m_rows.size() - 1);
m_rows.at(i).remove->setEnabled(m_rows.size() > 1);
}
}
/// Remove the part shown in @p row; the definition keeps at least one
void SelectAutonumW::removePartRow(QWidget *row)
{
if (m_rows.size() <= 1) {
return;
}
for (int i = 0 ; i < m_rows.size() ; ++i) {
if (m_rows.at(i).row == row) {
const PartRow r = m_rows.takeAt(i);
num_part_list_.removeAll(r.part);
delete r.row; //with the part, which it holds
break;
}
}
updatePartButtons();
applyEnable();
}
/// Move the part shown in @p row up (-1) or down (+1)
void SelectAutonumW::movePartRow(QWidget *row, int step)
{
for (int i = 0 ; i < m_rows.size() ; ++i) {
if (m_rows.at(i).row != row) {
continue;
}
const int to = i + step;
if (to < 0 || to >= m_rows.size()) {
return;
}
m_rows.swapItemsAt(i, to);
num_part_list_.swapItemsAt(i, to);
ui->editor_layout->removeWidget(row);
ui->editor_layout->insertWidget(1 + to, row); //after the header of the columns
break;
}
updatePartButtons();
applyEnable();
}
/**
@brief SelectAutonumW::toNumContext
@return the content to num_part_list to NumerotationContext
@@ -143,12 +262,8 @@ NumerotationContext SelectAutonumW::toNumContext() const
*/
void SelectAutonumW::on_add_button_clicked()
{
applyEnable(false);
NumPartEditorW *part = new NumPartEditorW(m_edited_type, this);
connect(part, &NumPartEditorW::changed, this, [this]() { applyEnable(); });
num_part_list_ << part;
ui -> editor_layout -> addWidget(part);
ui -> remove_button -> setEnabled(true);
insertPartRow(new NumPartEditorW(m_edited_type, this));
applyEnable();
}
/**
@@ -157,16 +272,9 @@ void SelectAutonumW::on_add_button_clicked()
*/
void SelectAutonumW::on_remove_button_clicked()
{
//remove if @num_part_list contains more than one item
if (num_part_list_.size() > 1) {
NumPartEditorW *part = num_part_list_.takeLast();
// deliberately not disconnecting as not possible to resolve with lambda and will happen automatically when the object "part" is destroyed.
delete part;
if (num_part_list_.size() == 1) {
ui -> remove_button -> setDisabled(true);
}
if (!m_rows.isEmpty()) {
removePartRow(m_rows.last().row);
}
applyEnable();
}
/**
@@ -292,6 +400,8 @@ void SelectAutonumW::on_buttonBox_clicked(QAbstractButton *button)
*/
void SelectAutonumW::applyEnable(bool b)
{
//Apply and Cancel both stand for a change: grey until there is one
ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(b);
if (b){
bool valid= true;
foreach (NumPartEditorW *npe, num_part_list_)
@@ -331,28 +441,6 @@ void SelectAutonumW::contextToFormula()
}
}
/**
@brief SelectAutonumW::on_m_next_pb_clicked
Increase NumerotationContext
*/
void SelectAutonumW::on_m_next_pb_clicked()
{
NumerotationContextCommands ncc (toNumContext());
setContext(ncc.next());
applyEnable(true);
}
/**
@brief SelectAutonumW::on_m_previous_pb_clicked
Decrease NumerotationContext
*/
void SelectAutonumW::on_m_previous_pb_clicked()
{
NumerotationContextCommands ncc (toNumContext());
setContext(ncc.previous());
applyEnable(true);
}
void SelectAutonumW::on_m_comboBox_currentTextChanged(const QString &arg1)
{
Q_UNUSED(arg1);
@@ -363,3 +451,29 @@ void SelectAutonumW::on_m_remove_pb_clicked()
{
emit removeClicked();
}
/**
@brief SelectAutonumW::setExplicitNaming
Make the list of numberings a plain choice: no typing a name into it
(an easy way to create a numbering by accident, or one with an empty
name), but a button to create a numbering and one to rename the shown
one, which emit newClicked() and renameClicked() for the owner to ask
for the name.
*/
void SelectAutonumW::setExplicitNaming()
{
ui->m_comboBox->setEditable(false);
auto *row = ui->horizontalLayout_2;
const int index = row->indexOf(ui->m_remove_pb);
auto *new_pb = new QPushButton(QET::Icons::Add, QString(), this);
new_pb->setToolTip(tr("Nouvelle numérotation…"));
connect(new_pb, &QPushButton::clicked, this, &SelectAutonumW::newClicked);
auto *rename_pb = new QPushButton(QET::Icons::EditRename, QString(), this);
rename_pb->setToolTip(tr("Renommer la numérotation…"));
connect(rename_pb, &QPushButton::clicked, this, &SelectAutonumW::renameClicked);
row->insertWidget(index, rename_pb);
row->insertWidget(index, new_pb);
}
+23 -3
View File
@@ -27,6 +27,8 @@ class NumPartEditorW;
class QAbstractButton;
class FormulaAutonumberingW;
class QComboBox;
class QToolButton;
class QHBoxLayout;
namespace Ui {
class SelectAutonumW;
@@ -52,10 +54,14 @@ class SelectAutonumW : public QWidget
void contextToFormula ();
QString formula();
QComboBox *contextComboBox() const;
void setExplicitNaming();
signals:
void applyPressed();
void removeClicked();
/// Only with setExplicitNaming()
void newClicked();
void renameClicked();
//SLOT
private slots:
@@ -63,16 +69,30 @@ class SelectAutonumW : public QWidget
void on_remove_button_clicked();
void on_buttonBox_clicked(QAbstractButton *);
void applyEnable (bool = true);
void on_m_next_pb_clicked();
void on_m_previous_pb_clicked();
void on_m_comboBox_currentTextChanged(const QString &arg1);
//ATTRIBUTES
void on_m_remove_pb_clicked();
private:
/// A part of the definition, with the buttons at its right
struct PartRow
{
QWidget *row = nullptr;
NumPartEditorW *part = nullptr;
QToolButton *up = nullptr;
QToolButton *down = nullptr;
QToolButton *remove = nullptr;
};
void insertPartRow(NumPartEditorW *part);
void removePartRow(QWidget *row);
void movePartRow(QWidget *row, int step);
void updatePartButtons();
void clearPartRows();
Ui::SelectAutonumW *ui;
QList <NumPartEditorW *> num_part_list_;
QList <PartRow> m_rows;
QWidget *m_add_row = nullptr; ///< holds the add button, under the last part
NumerotationContext m_context;
FormulaAutonumberingW *m_feaw;
FormulaAutonumberingW *m_fcaw;
+3 -26
View File
@@ -158,32 +158,6 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="m_previous_pb">
<property name="toolTip">
<string>Précédent</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="arrow-left"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="m_next_pb">
<property name="toolTip">
<string>Suivant</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="arrow-right"/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
@@ -227,6 +201,9 @@
</item>
<item>
<widget class="QLabel" name="value_label">
<property name="toolTip">
<string>Pour un numéro : le prochain numéro attribué. Pour un texte : le texte fixe.</string>
</property>
<property name="text">
<string>Valeur</string>
</property>
+2
View File
@@ -133,6 +133,8 @@ class BorderTitleBlock : public QObject
return (additional_fields_); }
/// @return the value of the title block
QString autoPageNum() const { return(btb_auto_page_num_); }
/// The title of the folio numbering this folio follows, nothing else changes
void setAutoPageNum(const QString &title) { btb_auto_page_num_ = title; }
/// @return the value of the total number of folios
int folioTotal() const { return(folio_total_);}
+8
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "diagram.h"
#include "autoNum/elementautonumschemecommand.h"
#include "ElementsCollection/elementcollectionhandler.h"
#include "TerminalStrip/GraphicsItem/terminalstripitem.h"
@@ -1290,6 +1291,13 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
std::stable_sort(list_conductors.begin(), list_conductors.end(),
[](Conductor *a, Conductor *b) { return conductorSortKey(a) < conductorSortKey(b); });
// A copy carries the numberings its elements follow: pasted into
// another project, which does not know them, it can offer to import
// them (see PasteNumberingImport)
if (is_copy_command) {
ElementAutoNumSchemeCommand::writeCopiedSchemes(document, dom_root, m_project, list_elements);
}
// correspondence table between the addresses of the terminals and their ids
// table de correspondance entre les adresses des bornes et leurs ids
QHash<Terminal *, int> table_adr_id;
+73
View File
@@ -17,8 +17,10 @@
*/
#include "diagramcommands.h"
#include "autoNum/elementautonumschemecommand.h"
#include "diagram.h"
#include "itemgroups.h"
#include "qetproject.h"
#include "qetgraphicsitem/conductortextitem.h"
#include "qetgraphicsitem/diagramimageitem.h"
#include "qetgraphicsitem/dynamicelementtextitem.h"
@@ -29,6 +31,19 @@
#include "qetinformation.h"
#include "qgimanager.h"
namespace {
/// Is @p element among those the numberings in @p schemes are given to?
bool pasted_schemes_has(const QMap<QString, QVector<Element *>> &schemes, const Element *element)
{
for (auto it = schemes.constBegin() ; it != schemes.constEnd() ; ++it) {
if (it.value().contains(const_cast<Element *>(element))) {
return true;
}
}
return false;
}
} // namespace
/**
@brief PasteDiagramCommand::PasteDiagramCommand
Constructor
@@ -64,6 +79,10 @@ void PasteDiagramCommand::undo()
{
diagram -> showMe();
//The numbering of the pasted elements, if any: gives the numbers
//back to the numberings they came from
QUndoCommand::undo();
foreach(QGraphicsItem *item, content.items(filter))
diagram->removeItem(item);
}
@@ -98,6 +117,18 @@ void PasteDiagramCommand::redo()
e->initLink(elmts_list);
}
//The numberings the pasted elements follow, known now: the label
//erasing below empties their formula. Pasted elements get the next
//number of their numbering, as placed ones do, instead of the
//label of the element they were copied from.
const bool autonumber = m_autonumber && settings.value(
"diagramcommands/autonumber-pasted-elements", true).toBool();
QETProject *project = diagram->project();
QMap<QString, QVector<Element *>> pasted_schemes;
if (project && autonumber) {
pasted_schemes = ElementAutoNumSchemeCommand::pastedSchemes(project, elmts_list);
}
//make new uuid for every pasted conductor, because old uuid are
//the uuid of the copied conductor
const QList <Conductor *> all_pasted_conductors = content.conductors();
@@ -221,6 +252,44 @@ void PasteDiagramCommand::redo()
}
}
//What the pasted elements keep of their numbering names a
//numbering of this project, or none (a paste from another
//project, or from a file written before the ids)
if (project) {
ElementAutoNumSchemeCommand::linkPasted(project, elmts_list);
if (autonumber)
{
//A formula which names no numbering of this project is
//not kept: it would stand for a numbering which does
//not exist here, with a label which is not its result.
//The label is what the erase preference made of it.
for (Element *e : elmts_list)
{
const DiagramContext &info = e->elementInformations();
if (e->linkType() == Element::Slave || (e->linkType() & Element::AllReport)
|| info.value(QETInformation::ELMT_FORMULA).toString().isEmpty()
|| !project->elementAutoNumTitle(QUuid(info.value(QETInformation::ELMT_FORMULA_ID).toString())).isEmpty()
|| pasted_schemes_has(pasted_schemes, e)) {
continue;
}
DiagramContext dc = info;
dc.addValue(QETInformation::ELMT_FORMULA, QString());
for (DynamicElementTextItem *deti : e->dynamicTextItems())
deti->m_block_alignment = true;
for (auto *group : e->textGroups())
group->blockAlignmentUpdate(true);
e->setElementInformations(dc);
for (DynamicElementTextItem *deti : e->dynamicTextItems())
deti->m_block_alignment = false;
for (auto *group : e->textGroups())
group->blockAlignmentUpdate(false);
}
}
if (!pasted_schemes.isEmpty()) {
ElementAutoNumSchemeCommand::numberPasted(project, pasted_schemes, this);
}
}
//Pasted groups become new groups: the members of one source
//group all get the same new uuid, never the source's, or the
//copy would join the original's group. After the elements got
@@ -250,6 +319,10 @@ void PasteDiagramCommand::redo()
}
}
//The numbering of the pasted elements, if any, on the first redo
//as well as on the next ones
QUndoCommand::redo();
const QList<QGraphicsItem *> qgis_list = content.items();
for (QGraphicsItem *qgi : qgis_list)
qgi -> setSelected(true);
+8
View File
@@ -42,6 +42,12 @@ class PasteDiagramCommand : public QUndoCommand {
public:
void undo() override;
void redo() override;
/**
Whether pasted elements which follow an element numbering are
numbered by the paste (default true, if the preference allows it).
Off for a caller which numbers them itself.
*/
void setAutoNumbering(bool enabled) {m_autonumber = enabled;}
// attributes
private:
@@ -53,6 +59,8 @@ class PasteDiagramCommand : public QUndoCommand {
int filter;
/// prevent the first call to redo()
bool first_redo;
/// number the pasted elements which follow an element numbering
bool m_autonumber = true;
};
/**
@@ -274,7 +274,10 @@ void DiagramEventAddMacro::addMacro(QPointF final_pos)
QSettings settings;
bool saved_erase = settings.value("diagramcommands/erase-label-on-copy", true).toBool();
settings.setValue("diagramcommands/erase-label-on-copy", false);
m_diagram->undoStack().push(new PasteDiagramCommand(m_diagram, pasted_content));
//and does not number them either: the labels are the ones saved
auto *paste = new PasteDiagramCommand(m_diagram, pasted_content);
paste->setAutoNumbering(false);
m_diagram->undoStack().push(paste);
settings.setValue("diagramcommands/erase-label-on-copy", saved_erase);
}
}
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "diagrameventaddpaste.h"
#include "../autoNum/ui/pastenumberingimport.h"
#include "../diagram.h"
#include "../diagramcommands.h"
@@ -74,6 +75,7 @@
//Load items at their original XML coordinates.
m_diagram->fromXml(document_xml, QPointF(), false, &m_content);
m_copied_schemes = PasteNumberingImport::copiedBy(document_xml);
if (db) {
db->blockSignals(false);
@@ -385,7 +387,10 @@ void DiagramEventAddPaste::commit()
m_finished = true;
m_running = false;
m_diagram->undoStack().push(new PasteDiagramCommand(m_diagram, m_content));
//Asks whether to import the numberings the copy brings, if the
//project has not got them
PasteNumberingImport::push(m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
m_diagram, m_content, m_copied_schemes);
emit finish();
}
@@ -20,6 +20,7 @@
#include "diagrameventinterface.h"
#include "../diagramcontent.h"
#include "../autoNum/elementautonumschemecommand.h"
#include <QHash>
#include <QPointer>
@@ -90,6 +91,8 @@ class DiagramEventAddPaste : public DiagramEventInterface
void removeItems();
DiagramContent m_content;
/// The numberings the pasted copy carries
QList<ElementAutoNumSchemeCommand::Scheme> m_copied_schemes;
///Each movable item's position relative to the group's top left,
///taken once so repeated moves cannot accumulate rounding drift.
QHash<QGraphicsItem *, QPointF> m_relative_pos;
+5 -1
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "diagramview.h"
#include "autoNum/ui/pastenumberingimport.h"
#include "cellruler.h"
#include "lastusedstyle.h"
#include "utils/colordialogdoubleclick.h"
@@ -571,7 +572,10 @@ void DiagramView::paste(const QPointF &pos, QClipboard::Mode clipboard_mode) {
if (content_pasted.count())
{
m_diagram -> clearSelection();
m_diagram -> undoStack().push(new PasteDiagramCommand(m_diagram, content_pasted));
//Asks whether to import the numberings the copy brings, if the
//project has not got them
PasteNumberingImport::push(this, m_diagram, content_pasted,
PasteNumberingImport::copiedBy(document_xml));
adjustSceneRect();
}
}
+21
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "elementspanelwidget.h"
#include "autoNum/elementautonumschemecommand.h"
#include "diagram.h"
#include "qetgraphicsitem/conductor.h"
#include "editor/ui/qetelementeditor.h"
@@ -699,6 +700,14 @@ void ElementsPanelWidget::duplicateDiagram()
elmt->initLink(new_elements);
}
// The numberings the copies follow, known before the label
// erasing below empties their formula
const bool autonumber = QSettings().value(
"diagramcommands/autonumber-pasted-elements", true).toBool();
const QMap<QString, QVector<Element *>> copy_schemes = autonumber
? ElementAutoNumSchemeCommand::pastedSchemes(project, new_elements)
: QMap<QString, QVector<Element *>>();
for (QGraphicsItem *item : new_diagram->items()) {
if (Element *elmt = dynamic_cast<Element *>(item)) {
// The XML round-trip kept the source elements' uuids. Give the
@@ -835,6 +844,18 @@ void ElementsPanelWidget::duplicateDiagram()
}
}
// The copies follow the numberings of the project: they get the
// next numbers, as pasted elements do. This duplication is not
// undoable, so neither is the numbering: it is done at once.
ElementAutoNumSchemeCommand::linkPasted(project, new_elements);
if (!copy_schemes.isEmpty())
{
QUndoCommand numbering;
if (ElementAutoNumSchemeCommand::numberPasted(project, copy_schemes, &numbering)) {
numbering.redo();
}
}
// Groups too: a group of the copy is its own, so selecting it
// is the same on both folios but the database tells them apart.
QHash<QUuid, QUuid> renewed_groups;
+1 -1
View File
@@ -427,7 +427,7 @@ void ProjectPrintWindow::printDiagram(Diagram *diagram, bool fit_page, QPainter
lines << QETInformation::translatedInfoKey(key) + ": " + info.value(key).toString();
}
for (const QString &key : info.keys()) {
if (key == "formula") continue;
if (key == "formula" || key == QETInformation::ELMT_FORMULA_ID) continue;
QString translated = QETInformation::translatedInfoKey(key);
if (lines.contains(translated + ": " + info.value(key).toString())) continue;
if (info.value(key).toString().isEmpty()) continue;
+70 -2
View File
@@ -20,6 +20,7 @@
#include "../qetproject.h"
#include "../PropertiesEditor/propertieseditordialog.h"
#include "../autoNum/assignvariables.h"
#include "../autoNum/elementautonumschemecommand.h"
#include "../autoNum/numerotationcontextcommands.h"
#include "../diagram.h"
#include "../diagramcommands.h"
@@ -1495,8 +1496,41 @@ void Element::setGroupIndexForElement(Element *elmt, int index)
If new information is different of current infotmation emit elementInfoChange
@param dc
*/
namespace {
/**
An element follows a numbering scheme only through a formula: an
information context whose formula is emptied (paste with labels
erased, the formula deleted by hand...) no longer names one.
*/
void dropOrphanFormulaId(DiagramContext &dc)
{
if (dc.contains(QETInformation::ELMT_FORMULA_ID)
&& dc.value(QETInformation::ELMT_FORMULA).toString().isEmpty()) {
dc.remove(QETInformation::ELMT_FORMULA_ID);
}
}
} // namespace
/**
@brief Element::setFormulaSchemeId
Make this element follow the element numbering scheme with uuid @p id,
or none if @p id is null, without touching anything else: no label
update, no signal, no undo. For QETProject when it ties freshly loaded
elements to their schemes.
*/
void Element::setFormulaSchemeId(const QUuid &id)
{
if (id.isNull()) {
m_data.m_informations.remove(QETInformation::ELMT_FORMULA_ID);
} else {
m_data.m_informations.addValue(QETInformation::ELMT_FORMULA_ID,
id.toString(), false);
}
}
void Element::setElementInformations(DiagramContext dc)
{
dropOrphanFormulaId(dc);
if (m_data.m_informations == dc) {
return;
}
@@ -1549,6 +1583,7 @@ ElementData Element::elementData() const
*/
void Element::setElementData(ElementData data)
{
dropOrphanFormulaId(data.m_informations);
auto old_info = m_data.m_informations;
auto old_plc = m_data.m_type == ElementData::Master && m_data.m_master_type == ElementData::PLC
? m_data.plcMasterData() : ElementData::PlcMasterData();
@@ -1776,15 +1811,48 @@ void Element::setUpFormula(bool code_letter, QUndoCommand *parent_undo)
QString element_currentAutoNum = diagram()
->project()
->elementCurrentAutoNum();
const QUuid scheme_id = diagram()->project()->elementAutoNumId(element_currentAutoNum);
setFormulaSchemeId(formula.isEmpty() ? QUuid() : scheme_id);
NumerotationContext nc = diagram()
->project()
->elementAutoNum(element_currentAutoNum);
NumerotationContextCommands ncc (nc);
//The number given is the first whose label no other element of
//the project carries: a counter set back, or an element which
//was given a number by hand, must not make a second element
//with the same label. The counter then goes on from there.
NumerotationContext given = nc;
if (!formula.isEmpty())
{
const QSet<QString> held = ElementAutoNumSchemeCommand::labelsHeldBesides(
diagram()->project(), this);
for (int attempt = 0 ; !held.isEmpty() && attempt < 100000 ; ++attempt)
{
autonum::sequentialNumbers probe;
autonum::setSequential(formula, probe, given, diagram(), element_currentAutoNum);
const QString candidate = autonum::AssignVariables::formulaToLabel(
formula, probe, diagram(), this, nullptr);
if (candidate.isEmpty() || !held.contains(candidate)) {
break;
}
NumerotationContextCommands step (given);
const NumerotationContext advanced = step.next();
bool moved = advanced.size() != given.size();
for (int i = 0 ; !moved && i < given.size() ; ++i) {
moved = advanced.itemAt(i) != given.itemAt(i);
}
if (!moved) {
break; //a numbering which cannot go on: nothing to skip to
}
given = advanced;
}
}
NumerotationContextCommands ncc (given);
m_autoNum_seq.clear();
autonum::setSequential(formula,
m_autoNum_seq,
nc,
given,
diagram(),
element_currentAutoNum);
+1
View File
@@ -150,6 +150,7 @@ class Element : public QetGraphicsItem
autonum::sequentialNumbers& rSequenceStruct()
{return m_autoNum_seq;}
void setUpFormula(bool code_letter = true, QUndoCommand *parent_undo = nullptr);
void setFormulaSchemeId(const QUuid &id);
void setPrefix(QString);
QString getPrefix() const;
void freezeLabel(bool freeze);
+7
View File
@@ -40,6 +40,13 @@ namespace QETInformation
/** Default information related to element **/
static QString ELMT_LABEL = "label";
static QString ELMT_FORMULA = "formula";
/**
Uuid of the project's element numbering scheme the element's
formula follows. Internal: never shown, edited, exported or
printed as an information of its own; it only exists alongside a
non-empty ELMT_FORMULA (see Element::setElementInformations()).
*/
static QString ELMT_FORMULA_ID = "formula_id";
static QString ELMT_COMMENT = "comment";
static QString ELMT_FUNCTION = "function";
static QString ELMT_DESCRIPTION = "description";
+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);
}
+26 -3
View File
@@ -39,11 +39,14 @@
#include <QHash>
#include <QSet>
#include <QUuid>
#include <QVector>
#include <QFuture>
#include <array>
class Diagram;
class Element;
class ElementsLocation;
class QETResult;
class TitleBlockTemplate;
@@ -179,12 +182,26 @@ class QETProject : public QObject
QString elementCurrentAutoNum() const;
void setCurrrentElementAutonum(QString autoNum);
//Identity of the element numbering schemes. The title is the
//name shown to the user and the lookup key of the API; the uuid
//is what an element's ELMT_FORMULA_ID refers to, so a scheme can
//be renamed or edited without its elements losing track of it.
void addElementAutoNum(const QString &key,
const NumerotationContext &context,
const QUuid &id);
QUuid elementAutoNumId(const QString &title) const;
QString elementAutoNumTitle(const QUuid &id) const;
bool renameElementAutoNum(const QString &old_title, const QString &new_title);
QString elementAutoNumNameClash(const QString &name,
const QString &ignored_title = QString()) const;
static QString normalizedAutoNumName(const QString &name);
QVector<Element *> elementsUsingElementAutoNum(const QString &title) const;
/**
* @brief Renumber existing elements by element autonumbering scheme.
*
* Elements do not store the scheme title but they store the corresponding formula.
* This operation matches elements to schemes by comparing the stored formula
* with the formula derived from the scheme's NumerotationContext.
* Elements follow a scheme by its uuid (QETInformation::ELMT_FORMULA_ID),
* see elementsUsingElementAutoNum().
*
* If @p scheme_title is empty, all schemes are renumbered.
* If @p scheme_title is non-empty, only that scheme is renumbered.
@@ -374,7 +391,13 @@ class QETProject : public QObject
QHash <QString, NumerotationContext> m_folio_autonum;
/// Element Auto Numbering
QHash <QString, NumerotationContext> m_element_autonum; //Title and NumContext hash
/// Title -> uuid of each element numbering scheme
QHash <QString, QUuid> m_element_autonum_id;
QString m_current_element_autonum;
/// True when the loaded file had element numbering schemes
/// saved without an id (written before ids existed)
bool m_legacy_element_autonums = false;
void linkElementsToElementAutoNums();
bool m_auto_conductor = true;
WireHops::Mode m_wire_hops = WireHops::Mode::None;
WiringRules::Settings m_wiring_rules;
+266 -7
View File
@@ -68,6 +68,8 @@
#include "../TerminalStrip/physicalterminal.h"
#include "../TerminalStrip/realterminal.h"
#include "../TerminalStrip/terminalstrip.h"
#include "../autoNum/autonumschemecommand.h"
#include "../autoNum/elementautonumschemecommand.h"
#include "../autoNum/assignvariables.h"
#include "../autoNum/numerotationcontext.h"
#include "../borderproperties.h"
@@ -609,6 +611,11 @@ bool QetScriptApi::setInfoKey(int folioIndex, const QString &elementUuid,
log(QStringLiteral("qet.%1: empty information key").arg(caller));
return false;
}
if (key == QETInformation::ELMT_FORMULA_ID) {
log(QStringLiteral("qet.%1: \"%2\" is internal, follow a numbering with numberElement()")
.arg(caller, key));
return false;
}
Element *element = findElement(folioIndex, elementUuid);
if (!element) return false;
@@ -616,6 +623,10 @@ bool QetScriptApi::setInfoKey(int folioIndex, const QString &elementUuid,
if (old_info.value(key).toString() == value) return true; // nothing to push
DiagramContext new_info = old_info;
new_info.addValue(key, value);
// A formula written by hand follows no numbering scheme
if (key == QETInformation::ELMT_FORMULA) {
new_info.remove(QETInformation::ELMT_FORMULA_ID);
}
auto *cmd = new ChangeElementInformationCommand(element, old_info, new_info);
m_project->undoStack()->push(cmd);
@@ -3110,9 +3121,108 @@ bool QetScriptApi::addAutoNum(const QString &kind, const QString &name, const QS
}
}
if (kind == QLatin1String("conductor")) m_project->addConductorAutoNum(name, context);
else if (kind == QLatin1String("element")) m_project->addElementAutoNum(name, context);
else m_project->addFolioAutoNum(name, context);
if (kind == QLatin1String("element")) {
// Through the undo stack, and an existing scheme is edited
// rather than replaced: the elements following it follow
// the new definition (see ElementAutoNumSchemeCommand)
ElementAutoNumSchemeCommand *cmd = nullptr;
if (m_project->elementAutoNum().contains(name)) {
const int frozen = ElementAutoNumSchemeCommand::editBlockedBy(m_project, name, context).size();
if (frozen) {
log(QStringLiteral("qet.addAutoNum: %1 element(s) with a frozen label follow '%2', "
"its formula cannot change").arg(frozen).arg(name));
return false;
}
if (const auto conflict = ElementAutoNumSchemeCommand::counterConflict(m_project, name, context)) {
log(QStringLiteral("qet.addAutoNum: the counter %1 of '%2' is at or below %3 number(s) in use "
"(up to %4); new elements will skip them")
.arg(conflict->counter).arg(name).arg(conflict->count).arg(conflict->highest));
}
cmd = ElementAutoNumSchemeCommand::edit(m_project, name, name, context, false);
} else {
const QString problem = ElementAutoNumSchemeCommand::nameProblem(m_project, name);
if (!problem.isEmpty()) {
log(QStringLiteral("qet.addAutoNum: %1").arg(problem));
return false;
}
cmd = ElementAutoNumSchemeCommand::create(m_project, name, context, QUuid(), false);
}
if (cmd) m_project->undoStack()->push(cmd);
return true;
}
// Conductor and folio numberings: undoable too, an existing one is
// edited, and a new name is unique and not empty
const auto scheme_kind = kind == QLatin1String("conductor")
? AutoNumSchemeCommand::Kind::Conductor
: AutoNumSchemeCommand::Kind::Folio;
AutoNumSchemeCommand *cmd = nullptr;
if (AutoNumSchemeCommand::contains(m_project, scheme_kind, name)) {
cmd = AutoNumSchemeCommand::edit(m_project, scheme_kind, name, name, context);
} else {
const QString problem = AutoNumSchemeCommand::nameProblem(m_project, scheme_kind, name);
if (!problem.isEmpty()) {
log(QStringLiteral("qet.addAutoNum: %1").arg(problem));
return false;
}
cmd = AutoNumSchemeCommand::create(m_project, scheme_kind, name, context);
}
if (cmd) m_project->undoStack()->push(cmd);
return true;
}
/**
@brief QetScriptApi::renameAutoNum
Rename a numbering context. What follows it follows it under its new
name: the elements for an element numbering, the folios for a conductor
or folio numbering. Undoable.
*/
bool QetScriptApi::renameAutoNum(const QString &kind, const QString &name, const QString &newName)
{
if (!m_project) return false;
if (m_project->isReadOnly()) {
log(QStringLiteral("qet.renameAutoNum: project is read-only"));
return false;
}
if (kind != QLatin1String("element") && kind != QLatin1String("conductor")
&& kind != QLatin1String("folio")) {
log(QStringLiteral("qet.renameAutoNum: unknown kind '%1'; expected conductor, element or folio").arg(kind));
return false;
}
if (kind != QLatin1String("element"))
{
// The folios which follow it follow it under its new name
const auto scheme_kind = kind == QLatin1String("conductor")
? AutoNumSchemeCommand::Kind::Conductor
: AutoNumSchemeCommand::Kind::Folio;
if (!AutoNumSchemeCommand::contains(m_project, scheme_kind, name)) {
log(QStringLiteral("qet.renameAutoNum: no %1 auto-numbering named '%2'").arg(kind, name));
return false;
}
const QString problem = AutoNumSchemeCommand::nameProblem(m_project, scheme_kind, newName, name);
if (!problem.isEmpty()) {
log(QStringLiteral("qet.renameAutoNum: %1").arg(problem));
return false;
}
auto *cmd = AutoNumSchemeCommand::edit(
m_project, scheme_kind, name, newName,
AutoNumSchemeCommand::contextOf(m_project, scheme_kind, name));
if (cmd) m_project->undoStack()->push(cmd);
return true;
}
if (!m_project->elementAutoNum().contains(name)) {
log(QStringLiteral("qet.renameAutoNum: no element auto-numbering named '%1'").arg(name));
return false;
}
const QString problem = ElementAutoNumSchemeCommand::nameProblem(m_project, newName, name);
if (!problem.isEmpty()) {
log(QStringLiteral("qet.renameAutoNum: %1").arg(problem));
return false;
}
auto *cmd = ElementAutoNumSchemeCommand::edit(
m_project, name, newName, m_project->elementAutoNum(name), false);
if (cmd) m_project->undoStack()->push(cmd);
return true;
}
@@ -3129,9 +3239,30 @@ bool QetScriptApi::removeAutoNum(const QString &kind, const QString &name)
log(QStringLiteral("qet.removeAutoNum: no %1 auto-numbering named '%2'").arg(kind, name));
return false;
}
if (kind == QLatin1String("conductor")) m_project->removeConductorAutoNum(name);
else if (kind == QLatin1String("element")) m_project->removeElementAutoNum(name);
else m_project->removeFolioAutoNum(name);
if (kind == QLatin1String("element")) {
const int used = m_project->elementsUsingElementAutoNum(name).size();
if (used) {
log(QStringLiteral("qet.removeAutoNum: %1 element(s) follow '%2', it cannot be removed")
.arg(used).arg(name));
return false;
}
if (auto *cmd = ElementAutoNumSchemeCommand::remove(m_project, name)) {
m_project->undoStack()->push(cmd);
}
return true;
}
const auto scheme_kind = kind == QLatin1String("conductor")
? AutoNumSchemeCommand::Kind::Conductor
: AutoNumSchemeCommand::Kind::Folio;
const int used = AutoNumSchemeCommand::usersOf(m_project, scheme_kind, name).size();
if (used) {
log(QStringLiteral("qet.removeAutoNum: %1 folio(s) follow '%2', it cannot be removed")
.arg(used).arg(name));
return false;
}
if (auto *cmd = AutoNumSchemeCommand::remove(m_project, scheme_kind, name)) {
m_project->undoStack()->push(cmd);
}
return true;
}
@@ -3732,7 +3863,8 @@ bool QetScriptApi::numberElement(int folioIndex, const QString &elementUuid)
element->setUpFormula(true);
const DiagramContext new_info = element->elementInformations();
if (new_info.value(QETInformation::ELMT_LABEL) == old_info.value(QETInformation::ELMT_LABEL)
&& new_info.value(QStringLiteral("formula")) == old_info.value(QStringLiteral("formula"))) {
&& new_info.value(QStringLiteral("formula")) == old_info.value(QStringLiteral("formula"))
&& new_info.value(QETInformation::ELMT_FORMULA_ID) == old_info.value(QETInformation::ELMT_FORMULA_ID)) {
stack->endMacro();
return false;
}
@@ -3742,6 +3874,129 @@ bool QetScriptApi::numberElement(int folioIndex, const QString &elementUuid)
return true;
}
/**
@brief QetScriptApi::renumberElementAutoNum
Number again, from the first number, the elements following the
element auto-numbering @p name, in folio and position order, as the
"Renumber" button of the project's numberings does. Undoable, one
step.
An element whose label is frozen is left as it is, and its label is
not given to another element.
@return how many elements were left as they are because their label
is frozen, -1 if there is no such numbering or the project is read-only
*/
int QetScriptApi::renumberElementAutoNum(const QString &name)
{
if (!m_project) return -1;
const QString caller = QStringLiteral("renumberElementAutoNum");
if (m_project->isReadOnly()) {
log(QStringLiteral("qet.%1: project is read-only").arg(caller));
return -1;
}
if (!m_project->elementAutoNum().contains(name)) {
log(QStringLiteral("qet.%1: no element auto-numbering named '%2'").arg(caller, name));
return -1;
}
QVector<Element *> frozen;
if (auto *cmd = ElementAutoNumSchemeCommand::renumber(
m_project, name, &frozen,
QObject::tr("Renuméroter les éléments (%1)").arg(name))) {
m_project->undoStack()->push(cmd);
}
return static_cast<int>(frozen.size());
}
/**
@brief QetScriptApi::freeElementNumbers
The numbers an element which follows an element auto-numbering may be
given by hand with assignElementNumber(): from 1 to a few past the
highest in use, those no other element of the numbering carries and which
would not give it the label of another element. Empty if the element
follows no auto-numbering, or one whose numbers are not a single sequence
(a folio number, a cycle or letters in the definition).
*/
QVariantList QetScriptApi::freeElementNumbers(int folioIndex, const QString &elementUuid)
{
QVariantList list;
if (!m_project) return list;
Element *element = findElement(folioIndex, elementUuid);
if (!element) return list;
const QString title = m_project->elementAutoNumTitle(
QUuid(element->elementInformations().value(QETInformation::ELMT_FORMULA_ID).toString()));
for (int n : ElementAutoNumSchemeCommand::freeNumbers(m_project, title, element)) {
list << n;
}
return list;
}
/**
@brief QetScriptApi::assignElementNumber
Give an element which follows an element auto-numbering the number
@p number of it, which must be free (see freeElementNumbers()): its label
is the formula worked out with that number and it keeps following the
numbering. If the counter of the numbering is at or below @p number it
moves past it. Undoable, one step.
*/
bool QetScriptApi::assignElementNumber(int folioIndex, const QString &elementUuid, int number)
{
if (!m_project) return false;
const QString caller = QStringLiteral("assignElementNumber");
if (m_project->isReadOnly()) {
log(QStringLiteral("qet.%1: project is read-only").arg(caller));
return false;
}
Element *element = findElement(folioIndex, elementUuid);
if (!element) return false;
QString problem;
auto *cmd = ElementAutoNumSchemeCommand::assignNumber(m_project, element, number, &problem);
if (!cmd) {
log(QStringLiteral("qet.%1: %2").arg(caller, problem));
return false;
}
m_project->undoStack()->push(cmd);
return true;
}
/**
@brief QetScriptApi::assignElementAutoNum
Make an element follow the element auto-numbering @p name: it gets
the formula and the next number of that numbering, which moves on.
The same command as picking the numbering in the element's
information window. Undoable, one step.
The element is left alone, and false returned, when it follows that
numbering 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 is true. A label typed by hand, with no formula, is
replaced.
*/
bool QetScriptApi::assignElementAutoNum(const QString &name, int folioIndex,
const QString &elementUuid, bool overwrite)
{
if (!m_project) return false;
const QString caller = QStringLiteral("assignElementAutoNum");
if (m_project->isReadOnly()) {
log(QStringLiteral("qet.%1: project is read-only").arg(caller));
return false;
}
if (!m_project->elementAutoNum().contains(name)) {
log(QStringLiteral("qet.%1: no element auto-numbering named '%2'").arg(caller, name));
return false;
}
Element *element = findElement(folioIndex, elementUuid);
if (!element) return false;
auto *cmd = ElementAutoNumSchemeCommand::assign(m_project, name, {element}, overwrite);
if (!cmd) {
log(QStringLiteral("qet.%1: the element was left as it is").arg(caller));
return false;
}
m_project->undoStack()->push(cmd);
return true;
}
/**
@brief QetScriptApi::duplicateElements
Copy elements, and the conductors between them, to another place.
@@ -3751,6 +4006,10 @@ bool QetScriptApi::numberElement(int folioIndex, const QString &elementUuid)
elements are selected for the moment and the previous selection restored
before returning; the paste is Diagram::fromXml() at the position,
followed by one PasteDiagramCommand so it is a single undo step.
As for a paste in the editor, the copies of elements which follow an
element numbering get the next numbers of it (the preference "number
pasted elements"), and the numbering moves on; undoing gives them back.
@return the uuids of the new elements, or an empty list on failure
*/
QStringList QetScriptApi::duplicateElements(int fromFolioIndex, const QStringList &elementUuids,
+6
View File
@@ -523,9 +523,15 @@ class QetScriptApi : public QObject
Q_INVOKABLE QStringList autoNums(const QString &kind) const;
Q_INVOKABLE bool addAutoNum(const QString &kind, const QString &name, const QStringList &parts);
Q_INVOKABLE bool removeAutoNum(const QString &kind, const QString &name);
Q_INVOKABLE bool renameAutoNum(const QString &kind, const QString &name, const QString &newName);
Q_INVOKABLE bool useConductorAutoNum(int folioIndex, const QString &name);
Q_INVOKABLE bool useElementAutoNum(const QString &name);
Q_INVOKABLE bool numberElement(int folioIndex, const QString &elementUuid);
Q_INVOKABLE int renumberElementAutoNum(const QString &name);
Q_INVOKABLE QVariantList freeElementNumbers(int folioIndex, const QString &elementUuid);
Q_INVOKABLE bool assignElementNumber(int folioIndex, const QString &elementUuid, int number);
Q_INVOKABLE bool assignElementAutoNum(const QString &name, int folioIndex,
const QString &elementUuid, bool overwrite);
// -- images, embedded in the project --
Q_INVOKABLE QStringList images(int folioIndex) const;
@@ -131,6 +131,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
}
ui->m_use_gesture_trackpad->setChecked(settings.value("diagramview/gestures", false).toBool());
ui->m_save_label_paste->setChecked(settings.value("diagramcommands/erase-label-on-copy", true).toBool());
ui->m_autonumber_pasted->setChecked(settings.value("diagramcommands/autonumber-pasted-elements", true).toBool());
ui->m_enable_scripting->setChecked(QetSettings::scriptingEnabled());
#ifdef QET_HAS_SCRIPTING
if (QetSettings::scriptingForcedByEnvironment()) {
@@ -322,6 +323,7 @@ void GeneralConfigurationPage::applyConf()
//DIAGRAM COMMAND
settings.setValue("diagramcommands/erase-label-on-copy", ui->m_save_label_paste->isChecked());
settings.setValue("diagramcommands/autonumber-pasted-elements", ui->m_autonumber_pasted->isChecked());
//SCRIPTING
//Left alone while the environment forces it on: the box is disabled
@@ -327,6 +327,16 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="m_autonumber_pasted">
<property name="text">
<string>Numéroter automatiquement les éléments collés ou dupliqués qui suivent une numérotation</string>
</property>
<property name="toolTip">
<string>Un élément collé reçoit le numéro suivant de sa numérotation au lieu du nom de l'élément copié, et la numérotation avance.</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="m_save_label_paste">
<property name="text">
@@ -354,7 +364,7 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="7" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -1373,6 +1383,7 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
<tabstop>m_use_windows_mode_rb</tabstop>
<tabstop>m_use_tab_mode_rb</tabstop>
<tabstop>m_save_label_paste</tabstop>
<tabstop>m_autonumber_pasted</tabstop>
<tabstop>m_use_folio_label</tabstop>
<tabstop>m_border_0</tabstop>
<tabstop>m_autosave_sb</tabstop>
+692 -118
View File
@@ -17,6 +17,10 @@
*/
#include "projectconfigpages.h"
#include "../autoNum/autonumschemecommand.h"
#include "../autoNum/elementautonumschemecommand.h"
#include "../autoNum/ui/counterwarning.h"
#include "../autoNum/ui/renumberpreviewdialog.h"
#include "../autoNum/numerotationcontext.h"
#include "../autoNum/ui/autonumberingmanagementw.h"
#include "../autoNum/ui/folioautonumbering.h"
@@ -28,6 +32,8 @@
#include "../qetproject.h"
#include "../wiringrules.h"
#include "../wiringruleswarning.h"
#include "../qetgraphicsitem/element.h"
#include "../diagram.h"
#include "../borderpropertieswidget.h"
#include "../conductorpropertieswidget.h"
#include "../diagramcontextwidget.h"
@@ -38,6 +44,7 @@
//#include "ui_autonumberingmanagementw.h"
#include <QtWidgets>
#include <optional>
/**
Constructor
@@ -450,6 +457,33 @@ QIcon ProjectAutoNumConfigPage::icon() const
void ProjectAutoNumConfigPage::applyProjectConf()
{}
namespace {
/**
A tab of a numbering list over a table of the folios which follow the
numbering shown
*/
QWidget *schemeTab(SelectAutonumW *saw, QGroupBox *&box, QTableWidget *&table)
{
auto *tab = new QWidget();
auto *layout = new QVBoxLayout(tab);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(saw);
box = new QGroupBox(tab);
auto *box_layout = new QVBoxLayout(box);
table = new QTableWidget(0, 2, box);
table->setHorizontalHeaderLabels({QObject::tr("Folio"), QObject::tr("Titre")});
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setSelectionBehavior(QAbstractItemView::SelectRows);
table->verticalHeader()->setVisible(false);
table->horizontalHeader()->setStretchLastSection(true);
table->setAlternatingRowColors(true);
box_layout->addWidget(table);
layout->addWidget(box, 1);
return tab;
}
} // namespace
/**
@brief ProjectAutoNumConfigPage::initWidgets
Init some widget of this page
@@ -457,6 +491,7 @@ void ProjectAutoNumConfigPage::applyProjectConf()
void ProjectAutoNumConfigPage::initWidgets()
{
QTabWidget *tab_widget = new QTabWidget(this);
m_tab_widget = tab_widget;
//Management tab
m_amw = new AutoNumberingManagementW(project());
@@ -464,15 +499,44 @@ void ProjectAutoNumConfigPage::initWidgets()
//Conductor tab
m_saw_conductor = new SelectAutonumW(1);
tab_widget->addTab(m_saw_conductor, tr("Conducteurs"));
m_saw_conductor->setExplicitNaming();
tab_widget->addTab(schemeTab(m_saw_conductor, m_conductor_users_box, m_conductor_users), tr("Conducteurs"));
//Element tab
m_saw_element = new SelectAutonumW(0);
tab_widget->addTab(m_saw_element, tr("Eléments"));
m_saw_element->setExplicitNaming();
auto *element_tab = new QWidget(this);
auto *element_layout = new QVBoxLayout(element_tab);
element_layout->setContentsMargins(0, 0, 0, 0);
element_layout->addWidget(m_saw_element);
//The elements which follow the numbering shown: what an edit or a
//renumbering would touch, and which of them stop it
m_element_users_box = new QGroupBox(element_tab);
auto *users_layout = new QVBoxLayout(m_element_users_box);
m_element_users = new QTableWidget(0, 5, m_element_users_box);
m_element_users->setHorizontalHeaderLabels({tr("N°"), tr("Nom"), tr("Folio"), tr("Élément"), tr("Figé")});
m_element_users->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_element_users->setSelectionBehavior(QAbstractItemView::SelectRows);
m_element_users->verticalHeader()->setVisible(false);
m_element_users->horizontalHeader()->setStretchLastSection(true);
m_element_users->setAlternatingRowColors(true);
users_layout->addWidget(m_element_users);
m_assign_number_pb = new QPushButton(tr("Attribuer un numéro libre…"), m_element_users_box);
m_assign_number_pb->setToolTip(tr("Donner à l'élément sélectionné un numéro que personne n'a : "
"il garde sa numérotation, seul son numéro change."));
m_assign_number_pb->setEnabled(false);
users_layout->addWidget(m_assign_number_pb, 0, Qt::AlignLeft);
connect(m_assign_number_pb, &QPushButton::clicked, this, &ProjectAutoNumConfigPage::assignFreeNumber);
connect(m_element_users, &QTableWidget::itemSelectionChanged,
this, &ProjectAutoNumConfigPage::updateAssignNumberButton);
element_layout->addWidget(m_element_users_box, 1);
tab_widget->addTab(element_tab, tr("Eléments"));
//Folio Tab
m_saw_folio = new SelectAutonumW(2);
tab_widget->addTab(m_saw_folio, tr("Folios"));
m_saw_folio->setExplicitNaming();
tab_widget->addTab(schemeTab(m_saw_folio, m_folio_users_box, m_folio_users), tr("Folios"));
//AutoNumbering Tab
m_faw = new FolioAutonumberingW(project());
@@ -509,16 +573,13 @@ void ProjectAutoNumConfigPage::readValuesFromProject()
m_saw_folio->contextComboBox()->clear();
//Conductor Tab
const QStringList strlc(m_project->conductorAutoNum().keys());
m_saw_conductor->contextComboBox()->addItems(strlc);
refreshSchemes(SchemeKind::Conductor, m_project->conductorCurrentAutoNum());
//Element Tab
const QStringList strle(m_project->elementAutoNum().keys());
m_saw_element->contextComboBox()->addItems(strle);
refreshElementSchemes(m_project->elementCurrentAutoNum());
//Folio Tab
const QStringList strlf(m_project->folioAutoNum().keys());
m_saw_folio->contextComboBox()->addItems(strlf);
refreshSchemes(SchemeKind::Folio, QString());
//Folio AutoNumbering Tab
m_faw->setContext(m_project->folioAutoNum().keys());
@@ -547,16 +608,22 @@ void ProjectAutoNumConfigPage::buildConnections()
//Conductor Tab
connect(m_saw_conductor, &SelectAutonumW::applyPressed, this, &ProjectAutoNumConfigPage::saveContextConductor);
connect(m_saw_conductor, &SelectAutonumW::removeClicked, this, &ProjectAutoNumConfigPage::removeContextConductor);
connect(m_saw_conductor, &SelectAutonumW::newClicked, this, &ProjectAutoNumConfigPage::newContextConductor);
connect(m_saw_conductor, &SelectAutonumW::renameClicked, this, &ProjectAutoNumConfigPage::renameContextConductor);
connect(m_saw_conductor->contextComboBox(), &QComboBox::textActivated, this, &ProjectAutoNumConfigPage::updateContextConductor);
//Element Tab
connect(m_saw_element, &SelectAutonumW::applyPressed, this, &ProjectAutoNumConfigPage::saveContextElement);
connect(m_saw_element, &SelectAutonumW::removeClicked, this, &ProjectAutoNumConfigPage::removeContextElement);
connect(m_saw_element, &SelectAutonumW::newClicked, this, &ProjectAutoNumConfigPage::newContextElement);
connect(m_saw_element, &SelectAutonumW::renameClicked, this, &ProjectAutoNumConfigPage::renameContextElement);
connect(m_saw_element->contextComboBox(), &QComboBox::textActivated, this, &ProjectAutoNumConfigPage::updateContextElement);
//Folio Tab
connect(m_saw_folio, &SelectAutonumW::applyPressed, this, &ProjectAutoNumConfigPage::saveContextFolio);
connect(m_saw_folio, &SelectAutonumW::removeClicked, this, &ProjectAutoNumConfigPage::removeContextFolio);
connect(m_saw_folio, &SelectAutonumW::newClicked, this, &ProjectAutoNumConfigPage::newContextFolio);
connect(m_saw_folio, &SelectAutonumW::renameClicked, this, &ProjectAutoNumConfigPage::renameContextFolio);
connect(m_saw_folio->contextComboBox(), &QComboBox::textActivated, this, &ProjectAutoNumConfigPage::updateContextFolio);
// Auto Folio Numbering
@@ -572,18 +639,12 @@ void ProjectAutoNumConfigPage::buildConnections()
@param str : key of context stored in project
*/
void ProjectAutoNumConfigPage::updateContextConductor(const QString& str) {
if (str == tr("Nom de la nouvelle numérotation")) m_saw_conductor -> setContext(NumerotationContext());
else m_saw_conductor ->setContext(m_project->conductorAutoNum(str));
m_saw_conductor->setContext(AutoNumSchemeCommand::contextOf(m_project, SchemeKind::Conductor, str));
refreshSchemeUsers(SchemeKind::Conductor);
}
/**
@brief ProjectAutoNumConfigPage::updateContext_folio
Display the current selected context for folio
@param str : key of context stored in project
*/
void ProjectAutoNumConfigPage::updateContextFolio(const QString& str) {
if (str == tr("Nom de la nouvelle numérotation")) m_saw_folio -> setContext(NumerotationContext());
else m_saw_folio ->setContext(m_project->folioAutoNum(str));
m_saw_folio->setContext(AutoNumSchemeCommand::contextOf(m_project, SchemeKind::Folio, str));
refreshSchemeUsers(SchemeKind::Folio);
}
/**
@@ -593,7 +654,7 @@ void ProjectAutoNumConfigPage::updateContextFolio(const QString& str) {
*/
void ProjectAutoNumConfigPage::updateContextElement(const QString& str)
{
if (str == tr("Nom de la nouvelle numérotation"))
if (str.isEmpty() || !m_project->elementAutoNum().contains(str))
{
m_saw_element->setContext(NumerotationContext());
}
@@ -601,6 +662,306 @@ void ProjectAutoNumConfigPage::updateContextElement(const QString& str)
{
m_saw_element->setContext(m_project->elementAutoNum(str));
}
refreshElementUsers();
}
/**
@brief ProjectAutoNumConfigPage::refreshElementUsers
List, in folio and position order, the elements which follow the
element numbering shown, with their label and whether it is frozen.
*/
void ProjectAutoNumConfigPage::refreshElementUsers()
{
if (!m_element_users || !m_element_users_box) {
return;
}
const QString title = m_saw_element->contextComboBox()->currentText();
QVector<Element *> users = m_project->elementsUsingElementAutoNum(title);
std::sort(users.begin(), users.end(),
[](Element *a, Element *b) { return comparPos(a, b); });
//With a single sequence of numbers the elements are listed by number,
//and the numbers no element has between the lowest and the highest
//are shown too: where an element was deleted or given another number
using Support = ElementAutoNumSchemeCommand::NumberSupport;
const Support support = m_project->elementAutoNum().contains(title)
? ElementAutoNumSchemeCommand::numberSupport(m_project->elementAutoNum().value(title))
: Support();
struct Entry {
int number = 0;
int to = 0; ///< the end of a range of free numbers
Element *element = nullptr;
};
QVector<Entry> entries;
QVector<Entry> unnumbered;
for (Element *el : std::as_const(users)) {
const auto number = support.supported ? ElementAutoNumSchemeCommand::numberOf(support, el)
: std::nullopt;
if (support.supported && !number) {
unnumbered << Entry{0, 0, el};
} else {
entries << Entry{number.value_or(0), 0, el};
}
}
if (support.supported) {
for (const auto &gap : ElementAutoNumSchemeCommand::gapRanges(m_project, title)) {
entries << Entry{gap.from, gap.to, nullptr};
}
std::stable_sort(entries.begin(), entries.end(),
[](const Entry &a, const Entry &b) { return a.number < b.number; });
entries += unnumbered;
}
int frozen = 0;
int gaps = 0;
m_element_users->setRowCount(0);
m_element_rows.clear();
for (const Entry &entry : std::as_const(entries))
{
const int row = m_element_users->rowCount();
m_element_users->insertRow(row);
m_element_rows << QPointer<Element>(entry.element);
QStringList cells;
if (entry.element)
{
const bool is_frozen = entry.element->isFreezeLabel();
if (is_frozen) ++frozen;
const auto number = support.supported ? ElementAutoNumSchemeCommand::numberOf(support, entry.element)
: std::nullopt;
cells = QStringList{
number ? QString::number(*number) : QString(),
entry.element->elementInformations().value(QStringLiteral("label")).toString(),
entry.element->diagram() ? QString::number(entry.element->diagram()->folioIndex() + 1) : QString(),
entry.element->name(),
is_frozen ? tr("figé") : QString()};
}
else
{
++gaps;
cells = QStringList{
entry.to > entry.number ? QStringLiteral("%1 – %2").arg(entry.number).arg(entry.to)
: QString::number(entry.number),
tr("— libre —"), QString(),
tr("aucun élément n'a ce numéro (supprimé ou renuméroté)"), QString()};
}
for (int column = 0 ; column < cells.size() ; ++column) {
auto *item = new QTableWidgetItem(cells.at(column));
if (!entry.element) {
QFont font = item->font();
font.setItalic(true);
item->setFont(font);
item->setForeground(QBrush(Qt::gray));
}
m_element_users->setItem(row, column, item);
}
}
m_element_users->setColumnHidden(0, !support.supported);
m_element_users->resizeColumnsToContents();
m_element_users_box->setTitle(
users.isEmpty()
? tr("Aucun élément ne suit cette numérotation")
: tr("%n élément(s) suivent cette numérotation, dont %1 au nom figé", "", users.size())
.arg(frozen)
+ (gaps ? tr(" ; %n numéro(s) sans élément", "", gaps) : QString()));
updateAssignNumberButton();
}
/**
@brief ProjectAutoNumConfigPage::updateAssignNumberButton
The button which gives an element a free number needs an element row
selected, in a numbering whose numbers are one sequence.
*/
void ProjectAutoNumConfigPage::updateAssignNumberButton()
{
if (!m_assign_number_pb) {
return;
}
const int row = m_element_users->currentRow();
const Element *element = row >= 0 ? m_element_rows.value(row).data() : nullptr;
const QString title = m_saw_element->contextComboBox()->currentText();
const bool supported = m_project->elementAutoNum().contains(title)
&& ElementAutoNumSchemeCommand::numberSupport(m_project->elementAutoNum().value(title)).supported;
m_assign_number_pb->setEnabled(element && supported && !m_project->isReadOnly());
m_assign_number_pb->setToolTip(
!supported
? tr("Cette numérotation a plusieurs numéros (ou un numéro par folio) : "
"on ne peut pas en choisir un à la main.")
: tr("Donner à l'élément sélectionné un numéro que personne n'a : "
"il garde sa numérotation, seul son numéro change."));
}
/**
@brief ProjectAutoNumConfigPage::assignFreeNumber
Give the element selected in the table a free number, chosen in the list
of those which are, each with the label it would give. The element keeps
following the numbering. One undo step.
*/
void ProjectAutoNumConfigPage::assignFreeNumber()
{
const int row = m_element_users->currentRow();
Element *element = row >= 0 ? m_element_rows.value(row).data() : nullptr;
const QString title = m_saw_element->contextComboBox()->currentText();
const QString caption = tr("Attribuer un numéro libre");
if (!element || m_project->isReadOnly()) {
return;
}
if (element->isFreezeLabel()) {
QMessageBox::information(this, caption,
tr("Le nom de cet élément est figé : dégelez-le d'abord."));
return;
}
const QList<int> numbers = ElementAutoNumSchemeCommand::freeNumbers(m_project, title, element);
if (numbers.isEmpty()) {
QMessageBox::information(this, caption, tr("Aucun numéro n'est libre."));
return;
}
QStringList items;
for (int n : numbers) {
items << tr("%1 → %2").arg(n).arg(ElementAutoNumSchemeCommand::labelForNumber(
m_project, title, element, n));
}
bool ok = false;
const QString chosen = QInputDialog::getItem(
this, caption,
tr("Numéro libre pour l'élément « %1 » (%2) :")
.arg(element->elementInformations().value(QStringLiteral("label")).toString(), element->name()),
items, 0, false, &ok);
if (!ok) {
return;
}
QString problem;
auto *cmd = ElementAutoNumSchemeCommand::assignNumber(
m_project, element, numbers.at(items.indexOf(chosen)), &problem);
if (!cmd) {
QMessageBox::warning(this, caption, problem);
return;
}
m_project->undoStack()->push(cmd);
updateContextElement(title); // the counter may have moved; lists the elements again
}
/**
@brief ProjectAutoNumConfigPage::refreshElementSchemes
Fill the list of element numberings from the project and show
@p selected, or the first one if there is no such numbering
*/
void ProjectAutoNumConfigPage::refreshElementSchemes(const QString &selected)
{
QComboBox *cb = m_saw_element->contextComboBox();
const QSignalBlocker blocker(cb);
cb->clear();
QStringList titles(m_project->elementAutoNum().keys());
titles.sort(Qt::CaseInsensitive);
cb->addItems(titles);
const int index = cb->findText(selected);
cb->setCurrentIndex(index >= 0 ? index : 0);
updateContextElement(cb->currentText());
}
/**
@brief ProjectAutoNumConfigPage::askElementSchemeName
Ask the user for the name of an element numbering until it is an
acceptable one or the user gives up.
@param title : title of the dialog
@param name : name proposed first
@param ignored_title : the numbering being renamed, if any
@return the name, empty if the user cancelled
*/
QString ProjectAutoNumConfigPage::askElementSchemeName(const QString &title,
QString name,
const QString &ignored_title)
{
for (;;)
{
bool ok = false;
name = QInputDialog::getText(this, title, tr("Nom de la numérotation :"),
QLineEdit::Normal, name, &ok);
if (!ok) {
return QString();
}
const QString problem = ElementAutoNumSchemeCommand::nameProblem(
m_project, name, ignored_title);
if (problem.isEmpty()) {
return QETProject::normalizedAutoNumName(name);
}
QMessageBox::warning(this, title, problem);
}
}
/**
@brief ProjectAutoNumConfigPage::pushElementSchemeCommand
Push @p cmd on the project's undo stack, after the user agreed when it
changes the label of elements.
@return true if pushed; @p cmd is deleted otherwise
*/
bool ProjectAutoNumConfigPage::pushElementSchemeCommand(ElementAutoNumSchemeCommand *cmd)
{
if (!cmd) {
return false;
}
if (const int changed = cmd->changedElementCount())
{
if (!RenumberPreviewDialog::confirm(
this,
tr("Modifier la numérotation"),
tr("%n élément(s) suivent cette numérotation et vont "
"changer de formule et de nom.", "", changed),
cmd->changes(), {})) {
delete cmd;
return false;
}
}
m_project->undoStack()->push(cmd);
return true;
}
/**
@brief ProjectAutoNumConfigPage::newContextElement
Create an element numbering under a name asked to the user, with a
default definition, and show it to be defined.
*/
void ProjectAutoNumConfigPage::newContextElement()
{
if (m_project->isReadOnly()) {
return;
}
const QString name = askElementSchemeName(tr("Nouvelle numérotation"),
QString(), QString());
if (name.isEmpty()) {
return;
}
m_saw_element->setContext(NumerotationContext());
if (pushElementSchemeCommand(ElementAutoNumSchemeCommand::create(
m_project, name,
m_saw_element->toNumContext(),
QUuid(), false))) {
refreshElementSchemes(name);
}
}
/**
@brief ProjectAutoNumConfigPage::renameContextElement
Rename the shown element numbering; the elements following it keep
following it.
*/
void ProjectAutoNumConfigPage::renameContextElement()
{
const QString old_title = m_saw_element->contextComboBox()->currentText();
if (m_project->isReadOnly() || !m_project->elementAutoNum().contains(old_title)) {
return;
}
const QString name = askElementSchemeName(tr("Renommer la numérotation"),
old_title, old_title);
if (name.isEmpty() || name == old_title) {
return;
}
if (pushElementSchemeCommand(ElementAutoNumSchemeCommand::edit(
m_project, old_title, name,
m_project->elementAutoNum(old_title),
false))) {
refreshElementSchemes(name);
}
}
/**
@@ -609,28 +970,49 @@ void ProjectAutoNumConfigPage::updateContextElement(const QString& str)
*/
void ProjectAutoNumConfigPage::saveContextElement()
{
// If the text is the default text "Name of new numerotation" save the edited context
// With the the name "No name"
if (m_saw_element->contextComboBox()->currentText() == tr("Nom de la nouvelle numérotation"))
{
QString title(tr("Sans nom"));
m_project->addElementAutoNum (title, m_saw_element -> toNumContext());
m_project->setCurrrentElementAutonum(title);
m_saw_element->contextComboBox()->addItem(tr("Sans nom"));
if (m_project->isReadOnly()) {
return;
}
// If the text isn't yet to the autonum of the project, add this new item to the combo box.
else if ( !m_project -> elementAutoNum().contains( m_saw_element->contextComboBox()->currentText()))
const QString title = m_saw_element->contextComboBox()->currentText();
ElementAutoNumSchemeCommand *cmd = nullptr;
QString shown = title;
if (!m_project->elementAutoNum().contains(title))
{
m_project->addElementAutoNum(m_saw_element->contextComboBox()->currentText(), m_saw_element->toNumContext());
m_project->setCurrrentElementAutonum(m_saw_element->contextComboBox()->currentText());
m_saw_element->contextComboBox()->addItem(m_saw_element->contextComboBox()->currentText());
//No numbering yet: the definition needs a name
shown = askElementSchemeName(tr("Nouvelle numérotation"),
QString(), QString());
if (shown.isEmpty()) {
return;
}
cmd = ElementAutoNumSchemeCommand::create(
m_project, shown, m_saw_element->toNumContext());
}
// Else, the text already exist in the autonum of the project, just update the context
else
{
m_project->addElementAutoNum (m_saw_element->contextComboBox() -> currentText(), m_saw_element -> toNumContext());
m_project->setCurrrentElementAutonum(m_saw_element->contextComboBox()->currentText());
const NumerotationContext wanted = m_saw_element->toNumContext();
if (const int frozen = ElementAutoNumSchemeCommand::editBlockedBy(
m_project, title, wanted).size())
{
QMessageBox::warning(
this, tr("Modifier la numérotation"),
tr("La numérotation « %1 » ne peut pas être modifiée : "
"%n élément(s) au nom figé la suivent.\n"
"Dégelez-les d'abord (voir la liste), ou renommez seulement la "
"numérotation.", "", frozen).arg(title));
refreshElementSchemes(title); //Back to the definition the project has
return;
}
//A counter put back on numbers in use: the next elements skip them
if (!CounterWarning::confirm(this, m_project, title, wanted))
{
refreshElementSchemes(title); //Back to the definition the project has
return;
}
cmd = ElementAutoNumSchemeCommand::edit(
m_project, title, title, wanted);
}
if (pushElementSchemeCommand(cmd)) {
refreshElementSchemes(shown);
}
}
@@ -736,7 +1118,7 @@ void ProjectAutoNumConfigPage::importFromProject()
bool exists = false;
switch (i) {
case 0: exists = m_project->conductorAutoNum().contains(title); break;
case 1: exists = m_project->elementAutoNum().contains(title); break;
case 1: exists = !m_project->elementAutoNumNameClash(title).isEmpty(); break;
default: exists = m_project->folioAutoNum().contains(title); break;
}
@@ -750,6 +1132,7 @@ void ProjectAutoNumConfigPage::importFromProject()
item->setCheckState(exists ? Qt::Unchecked : Qt::Checked);
item->setData(Qt::UserRole, i);
item->setData(Qt::UserRole + 1, title);
item->setData(Qt::UserRole + 3, entry.attribute(QStringLiteral("id")));
NumerotationContext nc;
nc.fromXml(entry);
@@ -778,7 +1161,8 @@ void ProjectAutoNumConfigPage::importFromProject()
return;
}
int imported = 0, skipped = 0, conductors = 0;
int imported = 0, skipped = 0, conductors = 0, relabelled = 0, blocked = 0;
m_project->undoStack()->beginMacro(tr("Importer des numérotations"));
for (int row = 0 ; row < list->count() ; ++row)
{
QListWidgetItem *item = list->item(row);
@@ -791,9 +1175,9 @@ void ProjectAutoNumConfigPage::importFromProject()
bool exists = false;
switch (category) {
case 0: exists = m_project->conductorAutoNum().contains(title); break;
case 1: exists = m_project->elementAutoNum().contains(title); break;
default: exists = m_project->folioAutoNum().contains(title); break;
case 0: exists = !AutoNumSchemeCommand::nameClash(m_project, SchemeKind::Conductor, title).isEmpty(); break;
case 1: exists = !m_project->elementAutoNumNameClash(title).isEmpty(); break;
default: exists = !AutoNumSchemeCommand::nameClash(m_project, SchemeKind::Folio, title).isEmpty(); break;
}
if (exists && !overwrite_cb->isChecked()) {
++skipped;
@@ -804,14 +1188,54 @@ void ProjectAutoNumConfigPage::importFromProject()
contexts.at(item->data(Qt::UserRole + 2).toInt());
switch (category) {
case 0:
m_project->addConductorAutoNum(title, nc);
{
const QString clash = AutoNumSchemeCommand::nameClash(m_project, SchemeKind::Conductor, title);
auto *cmd = clash.isEmpty()
? AutoNumSchemeCommand::create(m_project, SchemeKind::Conductor, title, nc)
: AutoNumSchemeCommand::edit(m_project, SchemeKind::Conductor, clash, clash, nc);
if (cmd) m_project->undoStack()->push(cmd);
++conductors;
break;
case 1: m_project->addElementAutoNum(title, nc); break;
default: m_project->addFolioAutoNum(title, nc); break;
}
case 1:
{
//Undoable, and an existing numbering is edited, not
//replaced: the elements following it follow the
//imported definition. A new one keeps the id it has
//in the other project when free here, so elements
//pasted from there follow it too.
const QString clash = m_project->elementAutoNumNameClash(title);
if (!clash.isEmpty()
&& !ElementAutoNumSchemeCommand::editBlockedBy(m_project, clash, nc).isEmpty()) {
//Elements with a frozen label follow it: kept as it is
++blocked;
continue;
}
ElementAutoNumSchemeCommand *cmd = clash.isEmpty()
? ElementAutoNumSchemeCommand::create(
m_project, title, nc,
QUuid(item->data(Qt::UserRole + 3).toString()), false)
: ElementAutoNumSchemeCommand::edit(
m_project, clash, clash, nc, false);
if (cmd) {
relabelled += cmd->changedElementCount();
m_project->undoStack()->push(cmd);
}
break;
}
default:
{
const QString clash = AutoNumSchemeCommand::nameClash(m_project, SchemeKind::Folio, title);
auto *cmd = clash.isEmpty()
? AutoNumSchemeCommand::create(m_project, SchemeKind::Folio, title, nc)
: AutoNumSchemeCommand::edit(m_project, SchemeKind::Folio, clash, clash, nc);
if (cmd) m_project->undoStack()->push(cmd);
break;
}
}
++imported;
}
m_project->undoStack()->endMacro();
readValuesFromProject();
if (conductors) {
@@ -820,10 +1244,17 @@ void ProjectAutoNumConfigPage::importFromProject()
QMessageBox::information(
this, tr("Import terminé"),
skipped ? tr("%1 numérotation(s) importée(s), "
"%2 conservée(s) telles quelles.")
.arg(imported).arg(skipped)
: tr("%1 numérotation(s) importée(s).").arg(imported));
(skipped ? tr("%1 numérotation(s) importée(s), "
"%2 conservée(s) telles quelles.")
.arg(imported).arg(skipped)
: tr("%1 numérotation(s) importée(s).").arg(imported))
+ (blocked ? QStringLiteral("\n")
+ tr("%n numérotation(s) suivie(s) par des éléments au nom figé "
"n'ont pas été remplacées.", "", blocked)
: QString())
+ (relabelled ? QStringLiteral("\n")
+ tr("%n élément(s) ont changé de formule.", "", relabelled)
: QString()));
}
/**
@@ -832,15 +1263,23 @@ void ProjectAutoNumConfigPage::importFromProject()
*/
void ProjectAutoNumConfigPage::removeContextElement()
{
//if default text, return
if (m_saw_element->contextComboBox()->currentText() == tr("Nom de la nouvelle numérotation"))
const QString title = m_saw_element->contextComboBox()->currentText();
if (m_project->isReadOnly() || !m_project->elementAutoNum().contains(title)) {
return;
m_project->removeElementAutoNum (m_saw_element->contextComboBox()->currentText());
m_saw_element->contextComboBox()->removeItem (m_saw_element->contextComboBox()->currentIndex());
// removeItem() removes the current selection programmatically but
// textActivated() does not react to (by design, see buildConnections()).
// Refresh the displayed pattern explicitly so it matches the new selection.
updateContextElement(m_saw_element->contextComboBox()->currentText());
}
if (const int used = m_project->elementsUsingElementAutoNum(title).size())
{
QMessageBox::information(
this, tr("Supprimer la numérotation"),
tr("%n élément(s) suivent la numérotation « %1 », elle ne "
"peut pas être supprimée.\nDonnez-leur une autre "
"numérotation ou un nom fixe d'abord.", "", used)
.arg(title));
return;
}
if (pushElementSchemeCommand(ElementAutoNumSchemeCommand::remove(m_project, title))) {
refreshElementSchemes(m_project->elementCurrentAutoNum());
}
}
/**
@@ -849,52 +1288,16 @@ void ProjectAutoNumConfigPage::removeContextElement()
*/
void ProjectAutoNumConfigPage::saveContextConductor()
{
// If the text is the default text "Name of new numerotation" save the edited context
// With the the name "No name"
if (m_saw_conductor->contextComboBox()-> currentText() == tr("Nom de la nouvelle numérotation"))
{
m_project->addConductorAutoNum (tr("Sans nom"), m_saw_conductor -> toNumContext());
project()->setCurrentConductorAutoNum(tr("Sans nom"));
m_saw_conductor->contextComboBox()-> addItem(tr("Sans nom"));
}
// If the text isn't yet to the autonum of the project, add this new item to the combo box.
else if ( !m_project -> conductorAutoNum().contains( m_saw_conductor->contextComboBox()->currentText()))
{
project()->addConductorAutoNum(m_saw_conductor->contextComboBox()->currentText(), m_saw_conductor->toNumContext());
project()->setCurrentConductorAutoNum(m_saw_conductor->contextComboBox()->currentText());
m_saw_conductor->contextComboBox()-> addItem(m_saw_conductor->contextComboBox()->currentText());
}
// Else, the text already exist in the autonum of the project, just update the context
else
{
project()->setCurrentConductorAutoNum(m_saw_conductor->contextComboBox()->currentText());
m_project->addConductorAutoNum (m_saw_conductor->contextComboBox()-> currentText(), m_saw_conductor -> toNumContext());
}
project()->conductorAutoNumAdded();
saveScheme(SchemeKind::Conductor);
}
/**
@brief ProjectAutoNumConfigPage::saveContext_folio
Save the current displayed folio context in project
@brief ProjectAutoNumConfigPage::saveContextFolio
Apply the definition shown to the folio numbering shown
*/
void ProjectAutoNumConfigPage::saveContextFolio()
{
// If the text is the default text "Name of new numerotation" save the edited context
// With the the name "No name"
if (m_saw_folio->contextComboBox() -> currentText() == tr("Nom de la nouvelle numérotation")) {
m_project->addFolioAutoNum (tr("Sans nom"), m_saw_folio -> toNumContext());
m_saw_folio->contextComboBox() -> addItem(tr("Sans nom"));
}
// If the text isn't yet to the autonum of the project, add this new item to the combo box.
else if ( !m_project -> folioAutoNum().contains( m_saw_folio->contextComboBox()->currentText())) {
project()->addFolioAutoNum(m_saw_folio->contextComboBox()->currentText(), m_saw_folio->toNumContext());
m_saw_folio->contextComboBox() -> addItem(m_saw_folio->contextComboBox()->currentText());
}
// Else, the text already exist in the autonum of the project, just update the context
else {
m_project->addFolioAutoNum (m_saw_folio->contextComboBox() -> currentText(), m_saw_folio -> toNumContext());
}
project()->folioAutoNumAdded();
saveScheme(SchemeKind::Folio);
}
/**
@@ -1024,32 +1427,201 @@ void ProjectAutoNumConfigPage::applyManagement()
*/
void ProjectAutoNumConfigPage::removeContextConductor()
{
//if default text, return
if ( m_saw_conductor->contextComboBox()-> currentText() == tr("Nom de la nouvelle numérotation") ) return;
m_project -> removeConductorAutoNum (m_saw_conductor->contextComboBox()-> currentText() );
m_saw_conductor->contextComboBox()-> removeItem (m_saw_conductor->contextComboBox()-> currentIndex() );
// removeItem() removes the current selection programmatically but
// textActivated() does not react to (by design, see buildConnections()).
// Refresh the displayed pattern explicitly so it matches the new selection.
updateContextConductor(m_saw_conductor->contextComboBox()->currentText());
project()->conductorAutoNumRemoved();
removeScheme(SchemeKind::Conductor);
}
/**
@brief ProjectAutoNumConfigPage::removeContext_folio
Remove from project the current folio numerotation context
@brief ProjectAutoNumConfigPage::removeContextFolio
Remove from project the folio numerotation context shown
*/
void ProjectAutoNumConfigPage::removeContextFolio()
{
//if default text, return
if ( m_saw_folio->contextComboBox() -> currentText() == tr("Nom de la nouvelle numérotation") ) return;
m_project -> removeFolioAutoNum (m_saw_folio->contextComboBox() -> currentText() );
m_saw_folio->contextComboBox() -> removeItem (m_saw_folio->contextComboBox() -> currentIndex() );
// removeItem() removes the current selection programmatically but
// textActivated() does not react to (by design, see buildConnections()).
// Refresh the displayed pattern explicitly so it matches the new selection.
updateContextFolio(m_saw_folio->contextComboBox()->currentText());
project()->folioAutoNumRemoved();
removeScheme(SchemeKind::Folio);
}
void ProjectAutoNumConfigPage::newContextConductor() {newScheme(SchemeKind::Conductor);}
void ProjectAutoNumConfigPage::renameContextConductor() {renameScheme(SchemeKind::Conductor);}
void ProjectAutoNumConfigPage::newContextFolio() {newScheme(SchemeKind::Folio);}
void ProjectAutoNumConfigPage::renameContextFolio() {renameScheme(SchemeKind::Folio);}
SelectAutonumW *ProjectAutoNumConfigPage::sawFor(SchemeKind kind) const
{
return kind == SchemeKind::Conductor ? m_saw_conductor : m_saw_folio;
}
/**
@brief ProjectAutoNumConfigPage::refreshSchemes
Fill the list of numberings of @p kind from the project and show
@p selected, or the first one if there is no such numbering
*/
void ProjectAutoNumConfigPage::refreshSchemes(SchemeKind kind, const QString &selected)
{
QComboBox *cb = sawFor(kind)->contextComboBox();
{
const QSignalBlocker blocker(cb);
cb->clear();
cb->addItems(AutoNumSchemeCommand::titles(m_project, kind));
const int index = cb->findText(selected);
cb->setCurrentIndex(index >= 0 ? index : 0);
}
sawFor(kind)->setContext(AutoNumSchemeCommand::contextOf(m_project, kind, cb->currentText()));
refreshSchemeUsers(kind);
}
/**
@brief ProjectAutoNumConfigPage::refreshSchemeUsers
List the folios which follow the numbering of @p kind shown
*/
void ProjectAutoNumConfigPage::refreshSchemeUsers(SchemeKind kind)
{
QTableWidget *table = kind == SchemeKind::Conductor ? m_conductor_users : m_folio_users;
QGroupBox *box = kind == SchemeKind::Conductor ? m_conductor_users_box : m_folio_users_box;
if (!table || !box) {
return;
}
const QString title = sawFor(kind)->contextComboBox()->currentText();
//A folio numbering is named by the title blocks it numbered, whose
//folio field holds the number since: they are what is listed
const QList<Diagram *> users = AutoNumSchemeCommand::usersOf(
m_project, kind, title, kind == SchemeKind::Folio);
table->setRowCount(0);
for (Diagram *d : users) {
const int row = table->rowCount();
table->insertRow(row);
table->setItem(row, 0, new QTableWidgetItem(QString::number(d->folioIndex() + 1)));
table->setItem(row, 1, new QTableWidgetItem(d->border_and_titleblock.title()));
}
table->resizeColumnsToContents();
if (kind == SchemeKind::Folio) {
box->setTitle(users.isEmpty()
? tr("Aucun folio ne nomme cette numérotation")
: tr("%n folio(s) nomment cette numérotation", "", users.size()));
} else {
box->setTitle(users.isEmpty()
? tr("Aucun folio ne suit cette numérotation")
: tr("%n folio(s) suivent cette numérotation", "", users.size()));
}
}
QString ProjectAutoNumConfigPage::askSchemeName(SchemeKind kind, const QString &title,
QString name, const QString &ignored_title)
{
for (;;)
{
bool ok = false;
name = QInputDialog::getText(this, title, tr("Nom de la numérotation :"),
QLineEdit::Normal, name, &ok);
if (!ok) {
return QString();
}
const QString problem = AutoNumSchemeCommand::nameProblem(m_project, kind, name, ignored_title);
if (problem.isEmpty()) {
return QETProject::normalizedAutoNumName(name);
}
QMessageBox::warning(this, title, problem);
}
}
/**
@brief ProjectAutoNumConfigPage::newScheme
Create a numbering under a name asked to the user, and show it to be defined.
*/
void ProjectAutoNumConfigPage::newScheme(SchemeKind kind)
{
if (m_project->isReadOnly()) {
return;
}
const QString name = askSchemeName(kind, tr("Nouvelle numérotation"), QString(), QString());
if (name.isEmpty()) {
return;
}
sawFor(kind)->setContext(NumerotationContext());
if (auto *cmd = AutoNumSchemeCommand::create(m_project, kind, name,
sawFor(kind)->toNumContext(), true)) {
m_project->undoStack()->push(cmd);
refreshSchemes(kind, name);
}
}
/**
@brief ProjectAutoNumConfigPage::renameScheme
Rename the numbering shown; the folios which follow it follow it under its new name.
*/
void ProjectAutoNumConfigPage::renameScheme(SchemeKind kind)
{
const QString old_title = sawFor(kind)->contextComboBox()->currentText();
if (m_project->isReadOnly() || !AutoNumSchemeCommand::contains(m_project, kind, old_title)) {
return;
}
const QString name = askSchemeName(kind, tr("Renommer la numérotation"), old_title, old_title);
if (name.isEmpty() || name == old_title) {
return;
}
if (auto *cmd = AutoNumSchemeCommand::edit(
m_project, kind, old_title, name,
AutoNumSchemeCommand::contextOf(m_project, kind, old_title))) {
m_project->undoStack()->push(cmd);
refreshSchemes(kind, name);
}
}
/**
@brief ProjectAutoNumConfigPage::saveScheme
Give the numbering shown the definition shown; with no numbering yet,
create one under a name asked to the user. A conductor numbering applied
becomes the one new conductors take.
*/
void ProjectAutoNumConfigPage::saveScheme(SchemeKind kind)
{
if (m_project->isReadOnly()) {
return;
}
const QString title = sawFor(kind)->contextComboBox()->currentText();
const NumerotationContext wanted = sawFor(kind)->toNumContext();
QString shown = title;
AutoNumSchemeCommand *cmd = nullptr;
if (!AutoNumSchemeCommand::contains(m_project, kind, title))
{
shown = askSchemeName(kind, tr("Nouvelle numérotation"), QString(), QString());
if (shown.isEmpty()) {
return;
}
cmd = AutoNumSchemeCommand::create(m_project, kind, shown, wanted, true);
}
else
{
cmd = AutoNumSchemeCommand::edit(m_project, kind, title, title, wanted, true);
}
if (cmd) {
m_project->undoStack()->push(cmd);
}
refreshSchemes(kind, shown);
}
/**
@brief ProjectAutoNumConfigPage::removeScheme
Remove the numbering shown, unless a folio still follows it
*/
void ProjectAutoNumConfigPage::removeScheme(SchemeKind kind)
{
const QString title = sawFor(kind)->contextComboBox()->currentText();
if (m_project->isReadOnly() || !AutoNumSchemeCommand::contains(m_project, kind, title)) {
return;
}
if (const int used = AutoNumSchemeCommand::usersOf(m_project, kind, title).size())
{
QMessageBox::information(
this, tr("Supprimer la numérotation"),
tr("%n folio(s) suivent la numérotation « %1 », elle ne peut pas être "
"supprimée.\nDonnez-leur une autre numérotation d'abord.", "", used)
.arg(title));
return;
}
if (auto *cmd = AutoNumSchemeCommand::remove(m_project, kind, title)) {
m_project->undoStack()->push(cmd);
refreshSchemes(kind, kind == SchemeKind::Conductor ? m_project->conductorCurrentAutoNum() : QString());
}
}
/**
@@ -1059,5 +1631,7 @@ void ProjectAutoNumConfigPage::removeContextFolio()
*/
void ProjectAutoNumConfigPage::changeToTab(int i)
{
qDebug()<<"Q_UNUSED"<<i;
if (m_tab_widget && i >= 0 && i < m_tab_widget->count()) {
m_tab_widget->setCurrentIndex(i);
}
}
@@ -18,6 +18,9 @@
#ifndef PROJECTCONFIGPAGES_H
#define PROJECTCONFIGPAGES_H
#include "configpage.h"
#include <QPointer>
#include <QVector>
#include "../../autoNum/autonumschemecommand.h"
class QLabel;
class QComboBox;
@@ -33,6 +36,11 @@ class DiagramContextWidget;
class ReportPropertieWidget;
class XRefPropertiesWidget;
class SelectAutonumW;
class QTabWidget;
class QGroupBox;
class Element;
class QTableWidget;
class ElementAutoNumSchemeCommand;
class FolioAutonumberingW;
class FormulaAutonumberingW;
class AutoNumberingManagementW;
@@ -168,11 +176,37 @@ class ProjectAutoNumConfigPage : public ProjectConfigPage {
void updateContextElement(const QString&);//element
void saveContextElement();
void removeContextElement();
void newContextElement();
void renameContextElement();
void refreshElementSchemes(const QString &selected);
void refreshElementUsers();
void assignFreeNumber();
void updateAssignNumberButton();
void newContextConductor();
void renameContextConductor();
void newContextFolio();
void renameContextFolio();
QString askElementSchemeName(const QString &title, QString name,
const QString &ignored_title);
bool pushElementSchemeCommand(ElementAutoNumSchemeCommand *cmd);
void importFromProject();
void applyAutoNum();
void applyManagement();
private:
//Conductor and folio numberings
using SchemeKind = AutoNumSchemeCommand::Kind;
SelectAutonumW *sawFor(SchemeKind kind) const;
void refreshSchemes(SchemeKind kind, const QString &selected);
void refreshSchemeUsers(SchemeKind kind);
QString askSchemeName(SchemeKind kind, const QString &title, QString name,
const QString &ignored_title);
void newScheme(SchemeKind kind);
void renameScheme(SchemeKind kind);
void saveScheme(SchemeKind kind);
void removeScheme(SchemeKind kind);
signals:
void setAutoNum(QString);
void setAutoNum(int,int);
@@ -184,6 +218,15 @@ class ProjectAutoNumConfigPage : public ProjectConfigPage {
SelectAutonumW *m_saw_conductor;
SelectAutonumW *m_saw_folio;
SelectAutonumW *m_saw_element;
QTabWidget *m_tab_widget = nullptr;
QGroupBox *m_conductor_users_box = nullptr;
QTableWidget *m_conductor_users = nullptr;
QGroupBox *m_folio_users_box = nullptr;
QTableWidget *m_folio_users = nullptr;
QGroupBox *m_element_users_box = nullptr;
QTableWidget *m_element_users = nullptr;
QPushButton *m_assign_number_pb = nullptr;
QVector<QPointer<Element>> m_element_rows; ///< the element of each row of the table, none for a gap
FolioAutonumberingW *m_faw;
AutoNumberingManagementW *m_amw;
QPushButton *m_import_pb = nullptr;
+451 -5
View File
@@ -18,7 +18,13 @@
#include "elementinfowidget.h"
#include "../qet.h"
#include <QCheckBox>
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include "../autoNum/elementautonumschemecommand.h"
#include "../undocommand/freezeelementlabelcommand.h"
#include "../diagram.h"
#include "../materiallist/materiallist.h"
#include "../materiallist/materialselectiondialog.h"
@@ -28,6 +34,7 @@
#include "../dataBase/projectdatabase.h"
#include "../qetinformation.h"
#include "../qetproject.h"
#include "../ui/projectpropertiesdialog.h"
#include "../ui_elementinfowidget.h"
#include "../undocommand/changeelementinformationcommand.h"
#include "customelementinfopartwidget.h"
@@ -113,13 +120,76 @@ void ElementInfoWidget::apply()
*/
QUndoCommand* ElementInfoWidget::associatedUndo() const
{
const auto new_info = currentInfo();
auto new_info = currentInfo();
const auto old_info = m_element -> elementInformations();
if (old_info != new_info)
return (new ChangeElementInformationCommand(m_element, old_info, new_info));
const bool was_frozen = m_element->isFreezeLabel();
const bool want_frozen = m_freeze_cb ? m_freeze_cb->isChecked() : was_frozen;
return nullptr;
//A numbering chosen in the list is given to the element the way
//a placed one gets it: its formula, and the next number of the
//numbering, which moves on. That is a command of its own, first,
//so the other edited fields are changed on top of its result.
const QString chosen = chosenScheme();
const bool assign_wanted = !chosen.isEmpty()
&& chosen != followedScheme()
&& m_element->diagram();
//A number picked among the free ones, for an element which keeps its numbering
const bool number_wanted = !assign_wanted && m_number_cb && m_number_row
&& !m_number_row->isHidden() && m_number_cb->isEnabled()
&& m_number_cb->currentData().toInt() > 0
&& m_number_cb->currentData().toInt() != m_number_current
&& m_element->diagram();
if (!assign_wanted && !number_wanted && was_frozen == want_frozen)
{
if (old_info != new_info)
return (new ChangeElementInformationCommand(m_element, old_info, new_info));
return nullptr;
}
auto *macro = new QUndoCommand(
QObject::tr("Modifier les informations de l'élément : %1")
.arg(m_element->name()));
DiagramContext base_info = old_info;
bool state_frozen = was_frozen;
if (assign_wanted || number_wanted)
{
RenumberElementsCommand *assign = assign_wanted
? ElementAutoNumSchemeCommand::assign(
m_element->diagram()->project(), chosen,
{m_element.data()}, true, nullptr, macro)
: ElementAutoNumSchemeCommand::assignNumber(
m_element->diagram()->project(), m_element.data(),
m_number_cb->currentData().toInt(), nullptr, macro);
if (assign && !assign->changes().isEmpty())
{
const auto &change = assign->changes().first();
base_info = change.new_infos;
state_frozen = change.new_frozen;
for (const QString &key : {QETInformation::ELMT_FORMULA,
QETInformation::ELMT_FORMULA_ID,
QETInformation::ELMT_LABEL}) {
if (base_info.contains(key)) {
new_info.addValue(key, base_info.value(key), key != QETInformation::ELMT_FORMULA_ID);
}
}
}
}
if (base_info != new_info) {
new ChangeElementInformationCommand(m_element, base_info, new_info, macro);
}
if (state_frozen != want_frozen) {
new FreezeElementLabelCommand(m_element, state_frozen, want_frozen, macro);
}
if (macro->childCount() == 0) {
delete macro;
return nullptr;
}
return macro;
}
/**
@@ -177,6 +247,12 @@ void ElementInfoWidget::enableLiveEdit()
if (m_exclude_from_bom_cb) {
connect(m_exclude_from_bom_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
if (m_freeze_cb) {
connect(m_freeze_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
if (m_number_cb) {
connect(m_number_cb, QOverload<int>::of(&QComboBox::activated), this, &ElementInfoWidget::apply);
}
}
/**
@@ -192,6 +268,12 @@ void ElementInfoWidget::disableLiveEdit()
if (m_potential_isolating_cb) {
disconnect(m_potential_isolating_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
if (m_freeze_cb) {
disconnect(m_freeze_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
if (m_number_cb) {
disconnect(m_number_cb, QOverload<int>::of(&QComboBox::activated), this, &ElementInfoWidget::apply);
}
if (m_exclude_from_bom_cb) {
disconnect(m_exclude_from_bom_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
@@ -226,6 +308,9 @@ void ElementInfoWidget::buildInterface()
}
setupMaterialButtons();
setupSchemeRow();
setupNumberRow();
setupFreezeRow();
m_add_custom_property_btn = new QPushButton(tr("Ajouter une propriété personnalisée"), this);
connect(m_add_custom_property_btn, &QPushButton::clicked, this, [this]() { addCustomProperty(); });
@@ -273,7 +358,8 @@ QStringList ElementInfoWidget::predefinedKeys() const
? QETInformation::terminalElementInfoKeys()
: QETInformation::elementInfoKeys();
keys << QStringLiteral("auto_num_locked")
keys << QETInformation::ELMT_FORMULA_ID
<< QStringLiteral("auto_num_locked")
<< QStringLiteral("potential_isolating")
<< QStringLiteral("exclude_from_bom");
@@ -573,6 +659,12 @@ void ElementInfoWidget::updateUi()
for (ElementInfoPartWidget *eipw : m_eipw_list) {
eipw -> setText (element_info[eipw->key()].toString());
}
refreshSchemeRow();
if (m_freeze_cb) {
m_freeze_cb->setChecked(m_element->isFreezeLabel());
updateFreezeRow();
}
refreshNumberRow();
updateSuggestions();
// Rebuild the custom-property rows to match whatever
@@ -658,8 +750,362 @@ DiagramContext ElementInfoWidget::currentInfo() const
if (m_exclude_from_bom_cb) {
info_.addValue(QStringLiteral("exclude_from_bom"), m_exclude_from_bom_cb->isChecked() ? QStringLiteral("true") : QStringLiteral("false"));
}
//The element keeps following its numbering scheme as long as its
//formula is left as it is; a formula edited by hand is its own.
const DiagramContext &elmt_info = m_element->elementInformations();
if (elmt_info.contains(QETInformation::ELMT_FORMULA_ID)
&& !info_.value(QETInformation::ELMT_FORMULA).toString().isEmpty()
&& info_.value(QETInformation::ELMT_FORMULA) == elmt_info.value(QETInformation::ELMT_FORMULA)) {
info_.addValue(QETInformation::ELMT_FORMULA_ID,
elmt_info.value(QETInformation::ELMT_FORMULA_ID), false);
}
return info_;
}
/**
@brief ElementInfoWidget::setupSchemeRow
Add above the formula the list of the numberings of the project, the
formula of the element being the one of the numbering picked there:
no formula to type, a numbering to choose, and a button to open the
numberings without leaving this window.
*/
void ElementInfoWidget::setupSchemeRow()
{
ElementInfoPartWidget *formula = infoPartWidgetForKey(QETInformation::ELMT_FORMULA);
if (!formula || !m_element || !m_element->diagram()) {
return;
}
m_scheme_row = new QWidget(this);
auto *layout = new QHBoxLayout(m_scheme_row);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(new QLabel(tr("Numérotation automatique"), m_scheme_row));
m_scheme_cb = new QComboBox(m_scheme_row);
m_scheme_cb->setObjectName(QStringLiteral("m_scheme_cb"));
m_scheme_cb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
layout->addWidget(m_scheme_cb, 1);
auto *open = new QPushButton(tr("…"), m_scheme_row);
open->setToolTip(tr("Ouvrir les numérotations d'éléments du projet"));
open->setMaximumWidth(32);
layout->addWidget(open);
ui->scroll_vlayout->insertWidget(ui->scroll_vlayout->indexOf(formula), m_scheme_row);
//The formula is shown, not typed: it comes from the numbering
formula->setDisabled(true);
connect(m_scheme_cb, QOverload<int>::of(&QComboBox::activated),
this, &ElementInfoWidget::schemeChosen);
connect(m_scheme_cb, QOverload<int>::of(&QComboBox::activated),
this, &ElementInfoWidget::updateNumberRow);
connect(open, &QPushButton::clicked, this, &ElementInfoWidget::openSchemePage);
}
/**
@brief ElementInfoWidget::setupFreezeRow
Add below the label a check box which freezes it: a frozen label is
not touched by the numbering of elements, and its number is not given
to another element.
*/
void ElementInfoWidget::setupFreezeRow()
{
ElementInfoPartWidget *label = infoPartWidgetForKey(QETInformation::ELMT_LABEL);
if (!label || !m_element) {
return;
}
m_freeze_cb = new QCheckBox(tr("Figer le nom"), this);
m_freeze_cb->setToolTip(tr("Un nom figé n'est pas changé par la numérotation automatique, "
"et son numéro n'est pas donné à un autre élément."));
ui->scroll_vlayout->insertWidget(ui->scroll_vlayout->indexOf(label) + 1, m_freeze_cb);
connect(m_freeze_cb, &QCheckBox::toggled, this, &ElementInfoWidget::updateNumberRow);
}
/**
@brief ElementInfoWidget::updateFreezeRow
Freezing means something for a label a formula gives: the box can be
ticked when there is one, and always unticked, so that a frozen
element can be given back.
*/
void ElementInfoWidget::updateFreezeRow()
{
ElementInfoPartWidget *formula = infoPartWidgetForKey(QETInformation::ELMT_FORMULA);
if (!m_freeze_cb || !formula) {
return;
}
m_freeze_cb->setEnabled(!formula->text().isEmpty() || m_freeze_cb->isChecked());
updateNumberRow();
}
/**
@brief ElementInfoWidget::setupNumberRow
Add under the numbering a list of the numbers the element may be given
by hand: its own, then the free ones, each with the label it would give.
For an element which follows a numbering whose numbers are one sequence.
*/
void ElementInfoWidget::setupNumberRow()
{
if (!m_scheme_row || !m_element || !m_element->diagram()) {
return;
}
m_number_row = new QWidget(this);
auto *layout = new QHBoxLayout(m_number_row);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(new QLabel(tr("Numéro"), m_number_row));
m_number_cb = new QComboBox(m_number_row);
m_number_cb->setObjectName(QStringLiteral("m_number_cb"));
m_number_cb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_number_cb->setToolTip(tr("Seuls les numéros libres sont proposés : un élément qui doit garder son "
"numéro réel peut le retrouver si personne ne l'a."));
layout->addWidget(m_number_cb, 1);
ui->scroll_vlayout->insertWidget(ui->scroll_vlayout->indexOf(m_scheme_row) + 1, m_number_row);
m_number_row->hide();
connect(m_number_cb, QOverload<int>::of(&QComboBox::activated), this, &ElementInfoWidget::numberChosen);
}
/**
@brief ElementInfoWidget::refreshNumberRow
Fill the list of numbers: the element's own, then the free ones. Hidden
when the element follows no numbering, or one whose numbers are not one
sequence.
*/
void ElementInfoWidget::refreshNumberRow()
{
if (!m_number_cb || !m_element || !m_element->diagram()) {
return;
}
QETProject *project = m_element->diagram()->project();
const QString title = followedScheme();
const auto support = title.isEmpty()
? ElementAutoNumSchemeCommand::NumberSupport()
: ElementAutoNumSchemeCommand::numberSupport(project->elementAutoNum().value(title));
const QSignalBlocker blocker(m_number_cb);
m_number_cb->clear();
m_number_current = -1;
if (!support.supported) {
m_number_row->hide();
return;
}
if (const auto own = ElementAutoNumSchemeCommand::numberOf(support, m_element)) {
m_number_current = *own;
m_number_cb->addItem(tr("%1 (actuel)").arg(*own), *own);
} else {
m_number_cb->addItem(tr("— (numéro inconnu)"), 0);
}
for (int n : ElementAutoNumSchemeCommand::freeNumbers(project, title, m_element)) {
if (n == m_number_current) continue;
m_number_cb->addItem(tr("%1 → %2").arg(n).arg(
ElementAutoNumSchemeCommand::labelForNumber(project, title, m_element, n)), n);
}
m_number_cb->setCurrentIndex(0);
m_number_row->show();
updateNumberRow();
}
/**
@brief ElementInfoWidget::updateNumberRow
A number is chosen only for an element which keeps its numbering and
whose name is not frozen.
*/
void ElementInfoWidget::updateNumberRow()
{
if (!m_number_cb || !m_number_row || m_number_row->isHidden()) {
return;
}
const bool keeps = chosenScheme() == followedScheme() && !chosenScheme().isEmpty();
const bool frozen = m_freeze_cb ? m_freeze_cb->isChecked() : m_element->isFreezeLabel();
m_number_cb->setEnabled(keeps && !frozen);
m_number_cb->setToolTip(
frozen ? tr("Le nom est figé : dégelez-le pour changer son numéro.")
: !keeps ? tr("Le numéro se choisit quand l'élément garde sa numérotation.")
: tr("Seuls les numéros libres sont proposés : un élément qui doit garder son "
"numéro réel peut le retrouver si personne ne l'a."));
}
/**
@brief ElementInfoWidget::numberChosen
Show the label the number picked gives, in the field of the label.
*/
void ElementInfoWidget::numberChosen()
{
ElementInfoPartWidget *label = infoPartWidgetForKey(QETInformation::ELMT_LABEL);
if (!label || !m_element || !m_element->diagram()) {
return;
}
const int number = m_number_cb->currentData().toInt();
if (number <= 0 || number == m_number_current) {
label->setText(m_element->elementInformations().value(QETInformation::ELMT_LABEL).toString());
return;
}
label->setText(ElementAutoNumSchemeCommand::labelForNumber(
m_element->diagram()->project(), followedScheme(), m_element, number));
}
/**
@brief ElementInfoWidget::followedScheme
@return the title of the numbering the element follows, empty if none
*/
QString ElementInfoWidget::followedScheme() const
{
if (!m_element || !m_element->diagram()) {
return QString();
}
return m_element->diagram()->project()->elementAutoNumTitle(
QUuid(m_element->elementInformations()
.value(QETInformation::ELMT_FORMULA_ID).toString()));
}
/**
@brief ElementInfoWidget::chosenScheme
@return the title of the numbering picked in the list, empty if the
element is to follow none
*/
QString ElementInfoWidget::chosenScheme() const
{
const QString data = m_scheme_cb ? m_scheme_cb->currentData().toString() : QString();
return data == QLatin1String("\x01") ? QString() : data;
}
/**
@brief ElementInfoWidget::refreshSchemeRow
Fill the list with the numberings of the project and show the one the
element follows. An element with a formula of its own, followed from
no numbering, gets an entry for it, so that it is neither hidden nor
lost by looking at it.
*/
void ElementInfoWidget::refreshSchemeRow()
{
if (!m_scheme_cb || !m_element || !m_element->diagram()) {
return;
}
QETProject *project = m_element->diagram()->project();
const QSignalBlocker blocker(m_scheme_cb);
m_scheme_cb->clear();
m_scheme_cb->addItem(tr("Aucune (nom saisi à la main)"), QString());
QStringList titles(project->elementAutoNum().keys());
titles.sort(Qt::CaseInsensitive);
for (const QString &title : titles) {
m_scheme_cb->addItem(title, title);
}
const QString followed = followedScheme();
if (!followed.isEmpty()) {
m_scheme_cb->setCurrentIndex(m_scheme_cb->findData(followed));
m_scheme_index = m_scheme_cb->currentIndex();
return;
}
const QString formula = m_element->elementInformations()
.value(QETInformation::ELMT_FORMULA).toString();
if (!formula.isEmpty()) {
//Same data as "none" would clash: the entry is for display only
m_scheme_cb->insertItem(1, tr("Formule propre : %1").arg(formula), QStringLiteral("\x01"));
m_scheme_cb->setCurrentIndex(1);
}
m_scheme_index = m_scheme_cb->currentIndex();
}
/**
@brief ElementInfoWidget::schemeChosen
The user picked an entry of the list.
The formula shown is the one of the numbering picked; the label is
typed only when there is no formula. Leaving a numbering, or the
element's own formula, for "none" also empties the label: what stays
would pass for a number the numbering gave, and could be given again
to another element. Going back to what the element has at present
gives its label back.
An element whose label is frozen is changed only if the user agrees.
*/
void ElementInfoWidget::schemeChosen()
{
ElementInfoPartWidget *formula = infoPartWidgetForKey(QETInformation::ELMT_FORMULA);
ElementInfoPartWidget *label = infoPartWidgetForKey(QETInformation::ELMT_LABEL);
if (!formula || !label || !m_element || !m_element->diagram()) {
return;
}
const DiagramContext info = m_element->elementInformations();
const QString own_formula = info.value(QETInformation::ELMT_FORMULA).toString();
const bool own_entry = m_scheme_cb->currentData().toString() == QLatin1String("\x01");
const QString chosen = chosenScheme();
const bool unchanged = own_entry
|| (!chosen.isEmpty() && chosen == followedScheme())
|| (chosen.isEmpty() && own_formula.isEmpty());
const bool frozen_now = m_freeze_cb ? m_freeze_cb->isChecked() : m_element->isFreezeLabel();
if (!unchanged && frozen_now)
{
const auto answer = QET::QetMessageBox::question(
this,
tr("Nom figé"),
tr("Le nom de cet élément est figé.\n"
"Changer sa numérotation le remplacera ou l'effacera.\n\nContinuer ?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (answer != QMessageBox::Yes) {
m_scheme_cb->setCurrentIndex(m_scheme_index);
return;
}
}
m_scheme_index = m_scheme_cb->currentIndex();
//The user agreed: the element is no longer protected
if (!unchanged && m_freeze_cb) {
m_freeze_cb->setChecked(false);
}
//Setting the fields one by one must not apply each step
const bool live = m_live_edit;
if (live) disableLiveEdit();
if (unchanged) {
formula->setText(own_formula);
label->setText(info.value(QETInformation::ELMT_LABEL).toString());
} else if (chosen.isEmpty()) {
formula->setText(QString());
if (!own_formula.isEmpty()) {
label->setText(QString());
}
} else {
formula->setText(m_element->diagram()->project()->elementAutoNumFormula(chosen));
}
updateFreezeRow();
if (live) {
enableLiveEdit();
apply();
}
}
/**
@brief ElementInfoWidget::openSchemePage
Open the numberings of the project, on the one of elements, then show
again the list, which may have changed.
*/
void ElementInfoWidget::openSchemePage()
{
if (!m_element || !m_element->diagram()) {
return;
}
const QString before = chosenScheme();
ProjectPropertiesDialog ppd(m_element->diagram()->project(), this);
ppd.setCurrentPage(ProjectPropertiesDialog::Autonum);
ppd.changeToElement();
ppd.exec();
refreshSchemeRow();
const int index = m_scheme_cb->findData(before);
if (index >= 0) {
m_scheme_cb->setCurrentIndex(index);
}
}
/**
@brief ElementInfoWidget::firstActivated
Slot activated when this widget is show.
+21
View File
@@ -29,7 +29,9 @@ class ElementInfoPartWidget;
class CustomElementInfoPartWidget;
class ChangeElementInformationCommand;
class QCheckBox;
class QComboBox;
class QPushButton;
class QWidget;
struct MaterialRecord;
namespace Ui {
@@ -71,6 +73,18 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget
ElementInfoPartWidget *infoPartWidgetForKey(const QString &key) const;
QStringList predefinedKeys() const;
void updateSuggestions();
void setupSchemeRow();
void setupFreezeRow();
void setupNumberRow();
void refreshNumberRow();
void updateNumberRow();
void numberChosen();
void updateFreezeRow();
void refreshSchemeRow();
void schemeChosen();
void openSchemePage();
QString chosenScheme() const;
QString followedScheme() const;
private slots:
void firstActivated();
@@ -86,6 +100,13 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget
QPushButton *m_add_custom_property_btn = nullptr;
QCheckBox *m_potential_isolating_cb = nullptr;
QCheckBox *m_exclude_from_bom_cb = nullptr;
QComboBox *m_scheme_cb = nullptr;
QCheckBox *m_freeze_cb = nullptr;
QWidget *m_number_row = nullptr;
QComboBox *m_number_cb = nullptr;
int m_number_current = -1; ///< the number the element has, -1 if unknown
int m_scheme_index = 0;
QWidget *m_scheme_row = nullptr;
bool m_first_activation;
bool m_ui_builded = false;
};
+31 -36
View File
@@ -17,6 +17,7 @@
*/
#include "multipastedialog.h"
#include "../autoNum/elementautonumschemecommand.h"
#include "../qetproject.h"
#include "../conductorautonumerotation.h"
#include "../diagram.h"
@@ -105,22 +106,28 @@ void MultiPasteDialog::on_m_button_box_accepted()
{
m_diagram->undoStack().beginMacro(tr("Multi-collage"));
QSettings settings;
bool erase_label = settings.value("diagramcommands/erase-label-on-copy", true).toBool();
//Ensure when 'auto_num' is checked, the settings 'save_label' is to true.
//Because in the class PasteDiagramCommand, if the settings 'save_label' is to false,
//the function redo of PasteDiagramCommand, clear the formula and the label of the pasted element
//and so the auto_num below do nothing (there is not a formula to compare)
if(ui->m_auto_num_cb->isChecked())
settings.setValue("diagramcommands/erase-label-on-copy", false);
QETProject *project = m_diagram->project();
//The element numberings the copies follow, read now: the paste
//erases the formulas of its elements, unless the preference
//keeps them, and the numbering below is meant to work either way
QList<QMap<QString, QVector<Element *>>> copy_schemes;
for(const DiagramContent &dc : std::as_const(m_pasted_content_list))
{
copy_schemes << (ui->m_auto_num_cb->isChecked()
? ElementAutoNumSchemeCommand::pastedSchemes(project, dc.m_elements)
: QMap<QString, QVector<Element *>>());
}
m_diagram->clearSelection();
m_diagram->undoStack().push(new PasteDiagramCommand(m_diagram, m_pasted_content));
//The copies are numbered by this dialog, copy by copy, below
auto *paste = new PasteDiagramCommand(m_diagram, m_pasted_content);
paste->setAutoNumbering(false);
m_diagram->undoStack().push(paste);
for(DiagramContent dc : m_pasted_content_list)
for(int copy = 0 ; copy < m_pasted_content_list.size() ; ++copy)
{
const DiagramContent &dc = m_pasted_content_list.at(copy);
QList<Element *> pasted_elements = dc.m_elements;
//Sort the list element by there pos (top -> bottom)
std::sort(pasted_elements.begin(), pasted_elements.end(), [](Element *a, Element *b){return (a->pos().y() < b->pos().y());});
@@ -147,32 +154,21 @@ void MultiPasteDialog::on_m_button_box_accepted()
}
}
//Set up the label of element
//Instead of use the current autonum of project,
//we try to fetch the same formula of the pasted element, in the several autonum of the project
//for apply the good formula for each elements
if(ui->m_auto_num_cb->isChecked())
//Number the elements of this copy with the numberings they
//follow: the next numbers, in the order of the Renumber
//button, the counters moving on, so that the next copy goes
//on from there. The project's current numbering is not
//changed, and labels which other elements keep are not given.
if(!copy_schemes.at(copy).isEmpty())
{
for(Element *elmt : pasted_elements)
{
QString formula = elmt->elementInformations()["formula"].toString();
if(!formula.isEmpty())
{
QHash <QString, NumerotationContext> autonums = m_diagram->project()->elementAutoNum();
QHashIterator<QString, NumerotationContext> hash_iterator(autonums);
while(hash_iterator.hasNext())
{
hash_iterator.next();
if(autonum::numerotationContextToFormula(hash_iterator.value()) == formula)
{
m_diagram->project()->setCurrrentElementAutonum(hash_iterator.key());
elmt->setUpFormula();
}
}
}
}
auto *numbering = new QUndoCommand(tr("Numéroter les éléments collés"));
ElementAutoNumSchemeCommand::numberPasted(project, copy_schemes.at(copy), numbering);
if(numbering->childCount())
m_diagram->undoStack().push(numbering);
else
delete numbering;
}
//Like elements, we compare formula of pasted conductor with the autonums available in the project.
if(ui->m_auto_num_cond_cb->isChecked())
{
@@ -216,7 +212,6 @@ void MultiPasteDialog::on_m_button_box_accepted()
m_diagram->adjustSceneRect();
m_accept = true;
settings.setValue("diagramcommands/erase-label-on-copy", erase_label);
m_diagram->undoStack().endMacro();
}
}
+12
View File
@@ -93,3 +93,15 @@ void ProjectPropertiesDialog::changeToFolio()
m_properties_dialog->pages.at(2));
autoNumPage->changeToTab(3);
}
/**
@brief ProjectPropertiesDialog::changeToElement
Change the current displayed tab to the element numberings tab.
*/
void ProjectPropertiesDialog::changeToElement()
{
ProjectAutoNumConfigPage *autoNumPage =
static_cast <ProjectAutoNumConfigPage*>(
m_properties_dialog->pages.at(2));
autoNumPage->changeToTab(2);
}
+1
View File
@@ -41,6 +41,7 @@ class ProjectPropertiesDialog : public QObject {
void exec();
void setCurrentPage(ProjectPropertiesDialog::Page);
void changeToFolio();
void changeToElement();
private:
ConfigDialog *m_properties_dialog;
@@ -0,0 +1,46 @@
/*
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 "freezeelementlabelcommand.h"
#include "../qetgraphicsitem/element.h"
FreezeElementLabelCommand::FreezeElementLabelCommand(Element *element,
bool old_frozen,
bool new_frozen,
QUndoCommand *parent) :
QUndoCommand(new_frozen ? QObject::tr("Figer le nom de l'élément")
: QObject::tr("Dégeler le nom de l'élément"),
parent),
m_element(element),
m_old_frozen(old_frozen),
m_new_frozen(new_frozen)
{}
void FreezeElementLabelCommand::undo()
{
if (m_element) {
m_element->freezeLabel(m_old_frozen);
}
}
void FreezeElementLabelCommand::redo()
{
if (m_element) {
m_element->freezeLabel(m_new_frozen);
}
}
@@ -0,0 +1,49 @@
/*
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/>.
*/
#ifndef FREEZEELEMENTLABELCOMMAND_H
#define FREEZEELEMENTLABELCOMMAND_H
#include <QPointer>
#include <QUndoCommand>
class Element;
/**
@brief The FreezeElementLabelCommand class
Freezes or unfreezes the label of an element: a frozen label is not
touched by the numbering of elements, and its number is not given to
another element.
*/
class FreezeElementLabelCommand : public QUndoCommand
{
public:
FreezeElementLabelCommand(Element *element,
bool old_frozen,
bool new_frozen,
QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
QPointer<Element> m_element;
bool m_old_frozen;
bool m_new_frozen;
};
#endif // FREEZEELEMENTLABELCOMMAND_H
+27
View File
@@ -633,6 +633,21 @@ target_compile_definitions(tst_derivedwireuuid PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
# Element numbering schemes carry an id and elements follow them by it:
# a legacy file gets stable ids on --resave; through --run, rename, edit,
# refused removal and undo/redo keep the elements following the scheme.
if(QET_HAS_SCRIPTING)
add_executable(
tst_elementautonumids
tst_elementautonumids.cpp)
add_test(NAME tst_elementautonumids COMMAND tst_elementautonumids)
add_dependencies(tst_elementautonumids qelectrotech)
target_link_libraries(tst_elementautonumids PRIVATE Qt::Test Qt::Xml)
target_compile_definitions(tst_elementautonumids PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
endif()
# Saving a project that was just saved changes nothing: runs the real
# binary's --resave twice on every project in examples/, and on one whose
# title block holds a single-space value (#973).
@@ -714,3 +729,15 @@ add_executable(
add_test(NAME tst_xrefpos COMMAND tst_xrefpos)
target_include_directories(tst_xrefpos PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_xrefpos PRIVATE Qt::Test Qt::Widgets Qt::Xml pugixml::pugixml)
# Qt 6.4.2 writes the attributes of an element in an order that changes from
# one process to the next, so a project saved twice by two runs of the binary
# differs in bytes only (seen on projects without any numbering, too). A fixed
# hash seed makes the order stable; it changes nothing for a newer Qt.
if(Qt6_VERSION VERSION_LESS 6.5.0)
foreach(qet_test tst_resaveunchanged tst_elementautonumids)
if(TEST ${qet_test})
set_tests_properties(${qet_test} PROPERTIES ENVIRONMENT "QT_HASH_SEED=0")
endif()
endforeach()
endif()
+976
View File
@@ -0,0 +1,976 @@
/*
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/>.
*/
// Element numbering schemes have an id, and elements follow a scheme by it
// (elementInformation "formula_id"). Runs the real binary on
// examples/industrial.qet, written before the ids existed: its schemes
// "Equipment" and "XV" are followed by elements, and it also holds
// elements whose formula no scheme defines any more (%prefixV1:%sequ_1...,
// left behind by earlier edits of a scheme), which must stay unlinked.
#include <QtTest>
#include <QDomDocument>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QProcessEnvironment>
#include <QTemporaryDir>
#include <QUuid>
class tst_elementautonumids : public QObject
{
Q_OBJECT
QTemporaryDir m_dir;
int m_run = 0;
QString m_preferences; // contents of the settings file of the next run
QProcessEnvironment environment()
{
const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run++));
QDir().mkpath(home);
if (!m_preferences.isEmpty()) {
QDir().mkpath(home + QStringLiteral("/config/QElectroTech"));
QFile settings(home + QStringLiteral("/config/QElectroTech/QElectroTech.conf"));
if (settings.open(QIODevice::WriteOnly)) {
settings.write(m_preferences.toUtf8());
}
}
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
env.insert(QStringLiteral("HOME"), home);
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
env.insert(QStringLiteral("TMPDIR"), m_dir.path());
return env;
}
QString resave(const QString &in)
{
const QString out = m_dir.filePath(QStringLiteral("out%1.qet").arg(m_run));
QProcess proc;
proc.setProcessEnvironment(environment());
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--resave"), in, out});
if (!proc.waitForFinished(180000) || proc.exitCode() != 0) return {};
return out;
}
QJsonObject run(const QString &script, const QString &project)
{
const QString path = m_dir.filePath(QStringLiteral("probe%1.js").arg(m_run));
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return {};
f.write(script.toUtf8());
f.close();
QProcess proc;
proc.setProcessEnvironment(environment());
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--run"), path, project});
if (!proc.waitForFinished(180000)) return {};
const QString out = QString::fromUtf8(proc.readAllStandardOutput()
+ proc.readAllStandardError());
const QString mark = QStringLiteral("PROBE ");
for (const QString &line : out.split(QLatin1Char('\n'))) {
const int i = line.indexOf(mark);
if (i >= 0)
return QJsonDocument::fromJson(line.mid(i + mark.size()).toUtf8()).object();
}
qWarning().noquote() << out;
return {};
}
static QByteArray read(const QString &path)
{
QFile f(path);
return f.open(QIODevice::ReadOnly) ? f.readAll() : QByteArray();
}
struct Saved
{
QHash<QString, QUuid> scheme_id; // title -> id
QHash<QString, QString> scheme_formula; // title -> formula
QString current, current_id;
// per element: formula and formula_id
QList<QPair<QString, QString>> elements;
};
static Saved parse(const QByteArray &xml)
{
Saved s;
QDomDocument doc;
if (!doc.setContent(xml)) return s;
const QDomNodeList groups = doc.elementsByTagName(QStringLiteral("element_autonums"));
if (!groups.isEmpty()) {
const QDomElement g = groups.at(0).toElement();
s.current = g.attribute(QStringLiteral("current_autonum"));
s.current_id = g.attribute(QStringLiteral("current_autonum_id"));
const QDomNodeList schemes = g.elementsByTagName(QStringLiteral("element_autonum"));
for (int i = 0 ; i < schemes.count() ; ++i) {
const QDomElement e = schemes.at(i).toElement();
s.scheme_id.insert(e.attribute(QStringLiteral("title")),
QUuid(e.attribute(QStringLiteral("id"))));
s.scheme_formula.insert(e.attribute(QStringLiteral("title")),
e.attribute(QStringLiteral("formula")));
}
}
const QDomNodeList elements = doc.elementsByTagName(QStringLiteral("element"));
for (int i = 0 ; i < elements.count() ; ++i) {
const QDomElement infos = elements.at(i).toElement()
.firstChildElement(QStringLiteral("elementInformations"));
if (infos.isNull()) continue;
QString formula, id;
for (QDomElement info = infos.firstChildElement(QStringLiteral("elementInformation"));
!info.isNull();
info = info.nextSiblingElement(QStringLiteral("elementInformation"))) {
if (info.attribute(QStringLiteral("name")) == QLatin1String("formula"))
formula = info.text();
else if (info.attribute(QStringLiteral("name")) == QLatin1String("formula_id"))
id = info.text();
}
s.elements << qMakePair(formula, id);
}
return s;
}
// industrial.qet with the element labelled @p label frozen
QString withFrozen(const QString &label)
{
QFile in(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
if (!in.open(QIODevice::ReadOnly)) return {};
QDomDocument doc;
if (!doc.setContent(&in)) return {};
int frozen = 0;
const QDomNodeList elements = doc.elementsByTagName(QStringLiteral("element"));
for (int i = 0 ; i < elements.count() ; ++i) {
QDomElement e = elements.at(i).toElement();
const QDomElement infos = e.firstChildElement(QStringLiteral("elementInformations"));
for (QDomElement info = infos.firstChildElement(QStringLiteral("elementInformation"));
!info.isNull();
info = info.nextSiblingElement(QStringLiteral("elementInformation"))) {
if (info.attribute(QStringLiteral("name")) == QLatin1String("label")
&& info.text() == label) {
e.setAttribute(QStringLiteral("freezeLabel"), QStringLiteral("true"));
++frozen;
}
}
}
if (frozen != 1) return {};
const QString out = m_dir.filePath(QStringLiteral("frozen%1.qet").arg(m_run));
QFile f(out);
if (!f.open(QIODevice::WriteOnly)) return {};
f.write(doc.toByteArray());
return out;
}
static QStringList diagramAttributes(const QByteArray &xml, const QString &attribute)
{
QStringList values;
QDomDocument doc;
if (!doc.setContent(xml)) return values;
const QDomNodeList diagrams = doc.elementsByTagName(QStringLiteral("diagram"));
for (int i = 0 ; i < diagrams.count() ; ++i)
values << diagrams.at(i).toElement().attribute(attribute);
return values;
}
// qet.autoNums() lists the numberings as "name: formula='...'"
static QStringList titlesOf(const QJsonValue &list)
{
QStringList titles;
for (const QJsonValue &entry : list.toArray()) {
const QString text = entry.toString();
const int cut = text.indexOf(QLatin1String(": formula="));
titles << (cut >= 0 ? text.left(cut) : text);
}
titles.sort();
return titles;
}
static int linkedTo(const Saved &s, const QString &title)
{
int n = 0;
for (const auto &e : s.elements)
if (QUuid(e.second) == s.scheme_id.value(title)) ++n;
return n;
}
private slots:
void initTestCase()
{
QVERIFY(m_dir.isValid());
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
}
// A file without ids: every scheme gets one, the same on every load;
// an element is linked to the one scheme with its formula, and only then.
void legacyFileGetsIds()
{
const QString project = QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet");
const QString first = resave(project);
QVERIFY2(!first.isEmpty(), "--resave failed");
const QString again = resave(project);
QVERIFY2(!again.isEmpty(), "second --resave of the original failed");
//Two separate loads write attributes in their own order, so
//compare what they saved, not the bytes
const Saved s = parse(read(first));
const Saved s_again = parse(read(again));
QVERIFY2(s.scheme_id == s_again.scheme_id,
"the ids derived for a legacy file differ between loads");
QVERIFY2(s.elements == s_again.elements,
"the elements were linked differently on another load");
QVERIFY(!s.scheme_id.isEmpty());
QSet<QUuid> ids;
for (auto it = s.scheme_id.constBegin() ; it != s.scheme_id.constEnd() ; ++it) {
QVERIFY2(!it.value().isNull(), qPrintable(it.key() + QStringLiteral(" has no id")));
ids << it.value();
}
QCOMPARE(ids.size(), s.scheme_id.size());
QMultiHash<QString, QString> titles_by_formula;
for (auto it = s.scheme_formula.constBegin() ; it != s.scheme_formula.constEnd() ; ++it)
titles_by_formula.insert(it.value(), it.key());
for (const auto &e : s.elements) {
const QStringList titles = titles_by_formula.values(e.first);
if (!e.first.isEmpty() && titles.size() == 1) {
QCOMPARE(QUuid(e.second), s.scheme_id.value(titles.first()));
} else {
QVERIFY2(e.second.isEmpty(),
qPrintable(QStringLiteral("element with formula '%1' is linked")
.arg(e.first)));
}
}
QVERIFY(linkedTo(s, QStringLiteral("Equipment")) > 0);
QVERIFY(linkedTo(s, QStringLiteral("XV")) > 0);
const QString second = resave(first);
QVERIFY2(!second.isEmpty(), "--resave of the resaved file failed");
QVERIFY2(read(first) == read(second), "the second save changed the file");
}
// Rename, refused rename and removal, an edit that keeps the numbers,
// an edit that renumbers, and undo/redo: through the scripting API,
// which goes through the same ElementAutoNumSchemeCommand as the UI.
void renameEditRemoveUndo()
{
const QString saved = m_dir.filePath(QStringLiteral("scripted.qet"));
const QString script = QStringLiteral(R"JS(
function linked(formula) {
var r = [];
for (var f = 0; f < qet.folioCount(); ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i)
if (qet.elementInfo(f, u[i], 'formula') === formula) r.push([f, u[i]]);
}
return r;
}
function label(e) { return qet.elementInfo(e[0], e[1], 'label'); }
function fid(e) { return qet.elementInfo(e[0], e[1], 'formula_id'); }
var xv = linked('%prefix2:%sequ_1');
var eq = linked('%id%prefix%sequ_1');
var r = {xv: xv.length, eq: eq.length};
var e0 = xv[0];
r.id0 = fid(e0);
r.label0 = label(e0);
r.renamed = qet.renameAutoNum('element', 'XV', 'Terminals XV');
r.idAfterRename = fid(e0);
r.clash = qet.renameAutoNum('element', 'Terminals XV', ' equipment ');
r.empty = qet.renameAutoNum('element', 'Terminals XV', ' ');
r.removeUsed = qet.removeAutoNum('element', 'Terminals XV');
r.removeUnused = qet.removeAutoNum('element', 'TB "XPE"');
r.edited = qet.addAutoNum('element', 'Terminals XV', ['elementprefix', 'string:X', 'unit:1']);
r.formulaAfterEdit = qet.elementInfo(e0[0], e0[1], 'formula');
r.labelAfterEdit = label(e0);
r.idAfterEdit = fid(e0);
qet.undo();
r.formulaAfterUndo = qet.elementInfo(e0[0], e0[1], 'formula');
r.labelAfterUndo = label(e0);
qet.redo();
r.labelAfterRedo = label(e0);
r.renumbered = qet.addAutoNum('element', 'Equipment', ['idfolio', 'elementprefix', 'string:-', 'ten:1']);
r.eqStillLinked = linked('%id%prefix-%seqt_1').length;
r.typed = qet.setElementInfo(eq[0][0], eq[0][1], 'formula', '%id%prefix%sequ_1');
r.typedId = fid(eq[0]);
r.internal = qet.setElementInfo(eq[1][0], eq[1][1], 'formula_id', '{00000000-0000-0000-0000-000000000001}');
r.names = qet.autoNums('element');
r.save = qet.save(%1);
qet.log('PROBE ' + JSON.stringify(r));
)JS").arg(QStringLiteral("'") + saved + QStringLiteral("'"));
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
QVERIFY(r.value(QStringLiteral("xv")).toInt() > 0);
QVERIFY(r.value(QStringLiteral("eq")).toInt() > 1);
const QString id0 = r.value(QStringLiteral("id0")).toString();
QVERIFY(!QUuid(id0).isNull());
// a rename touches no element
QVERIFY(r.value(QStringLiteral("renamed")).toBool());
QCOMPARE(r.value(QStringLiteral("idAfterRename")).toString(), id0);
// names are unique ignoring case and surrounding spaces, never empty
QVERIFY(!r.value(QStringLiteral("clash")).toBool());
QVERIFY(!r.value(QStringLiteral("empty")).toBool());
// a scheme elements follow cannot be removed, an unused one can
QVERIFY(!r.value(QStringLiteral("removeUsed")).toBool());
QVERIFY(r.value(QStringLiteral("removeUnused")).toBool());
// same sequential parts: new formula, same number, still linked
QVERIFY(r.value(QStringLiteral("edited")).toBool());
QCOMPARE(r.value(QStringLiteral("formulaAfterEdit")).toString(),
QStringLiteral("%prefixX%sequ_1"));
QCOMPARE(r.value(QStringLiteral("idAfterEdit")).toString(), id0);
QString expected = r.value(QStringLiteral("label0")).toString();
const int colon = expected.lastIndexOf(QStringLiteral("2:"));
QVERIFY(colon >= 0);
expected.replace(colon, 2, QStringLiteral("X"));
QCOMPARE(r.value(QStringLiteral("labelAfterEdit")).toString(), expected);
// one undo step, both ways
QCOMPARE(r.value(QStringLiteral("formulaAfterUndo")).toString(),
QStringLiteral("%prefix2:%sequ_1"));
QCOMPARE(r.value(QStringLiteral("labelAfterUndo")).toString(),
r.value(QStringLiteral("label0")).toString());
QCOMPARE(r.value(QStringLiteral("labelAfterRedo")).toString(), expected);
// other sequential parts: renumbered, every element still linked
QVERIFY(r.value(QStringLiteral("renumbered")).toBool());
QCOMPARE(r.value(QStringLiteral("eqStillLinked")).toInt(),
r.value(QStringLiteral("eq")).toInt());
// a formula written by hand follows no scheme, formula_id is internal
QVERIFY(r.value(QStringLiteral("typed")).toBool());
QVERIFY(r.value(QStringLiteral("typedId")).toString().isEmpty());
QVERIFY(!r.value(QStringLiteral("internal")).toBool());
QVERIFY(r.value(QStringLiteral("save")).toBool());
const Saved s = parse(read(saved));
QVERIFY(s.scheme_id.contains(QStringLiteral("Terminals XV")));
QVERIFY(!s.scheme_id.contains(QStringLiteral("XV")));
QVERIFY(!s.scheme_id.contains(QStringLiteral("TB \"XPE\"")));
QCOMPARE(s.scheme_id.value(QStringLiteral("Terminals XV")), QUuid(id0));
QCOMPARE(linkedTo(s, QStringLiteral("Terminals XV")), r.value(QStringLiteral("xv")).toInt());
QCOMPARE(linkedTo(s, QStringLiteral("Equipment")), r.value(QStringLiteral("eq")).toInt() - 1);
}
// Giving a numbering to elements that have none (what picking it in the
// element's information window does): formula, id and the next number,
// the counter moving on, refused where something would be lost, and one
// undo step that gives the number back.
void assignScheme()
{
const QString script = QStringLiteral(R"JS(
function find(pred, max) {
var r = [];
for (var f = 0; f < qet.folioCount() && r.length < max; ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length && r.length < max; ++i)
if (pred(f, u[i])) r.push([f, u[i]]);
}
return r;
}
function info(e, k) { return qet.elementInfo(e[0], e[1], k); }
var r = {};
var candidates = find(function (f, u) { return qet.elementInfo(f, u, 'formula') === ''; }, 40);
var done = [];
for (var i = 0; i < candidates.length && done.length < 3; ++i)
if (qet.assignElementAutoNum('Equipment', candidates[i][0], candidates[i][1], false)) done.push(candidates[i]);
r.assigned = done.length;
var a = done[0], b = done[1], c = done[2];
r.formulaA = info(a, 'formula');
r.idA = info(a, 'formula_id');
r.labelA = info(a, 'label');
r.labelB = info(b, 'label');
r.labelC = info(c, 'label');
r.again = qet.assignElementAutoNum('Equipment', a[0], a[1], false);
r.other = qet.assignElementAutoNum('XV', a[0], a[1], false);
r.otherOverwrite = qet.assignElementAutoNum('XV', a[0], a[1], true);
r.formulaAfterOverwrite = info(a, 'formula');
r.idAfterOverwrite = info(a, 'formula_id');
r.unknown = qet.assignElementAutoNum('Nope', a[0], a[1], true);
qet.undo();
r.formulaAfterUndo = info(a, 'formula');
r.idAfterUndo = info(a, 'formula_id');
r.save = qet.save(%1);
qet.log('PROBE ' + JSON.stringify(r));
)JS").arg(QStringLiteral("'") + m_dir.filePath(QStringLiteral("assigned.qet")) + QStringLiteral("'"));
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
QCOMPARE(r.value(QStringLiteral("assigned")).toInt(), 3);
QCOMPARE(r.value(QStringLiteral("formulaA")).toString(), QStringLiteral("%id%prefix%sequ_1"));
QVERIFY(!QUuid(r.value(QStringLiteral("idA")).toString()).isNull());
// the saved counter of "Equipment" is 9: the next numbers, in order
// (the label is the folio id, the prefix, then the number)
QVERIFY2(r.value(QStringLiteral("labelA")).toString().endsWith(QLatin1Char('9')),
qPrintable(r.value(QStringLiteral("labelA")).toString()));
QVERIFY2(r.value(QStringLiteral("labelB")).toString().endsWith(QStringLiteral("10")),
qPrintable(r.value(QStringLiteral("labelB")).toString()));
QVERIFY2(r.value(QStringLiteral("labelC")).toString().endsWith(QStringLiteral("11")),
qPrintable(r.value(QStringLiteral("labelC")).toString()));
QVERIFY(!r.value(QStringLiteral("again")).toBool());
QVERIFY(!r.value(QStringLiteral("other")).toBool());
QVERIFY(r.value(QStringLiteral("otherOverwrite")).toBool());
QCOMPARE(r.value(QStringLiteral("formulaAfterOverwrite")).toString(),
QStringLiteral("%prefix2:%sequ_1"));
QVERIFY(r.value(QStringLiteral("idAfterOverwrite")).toString()
!= r.value(QStringLiteral("idA")).toString());
QVERIFY(!r.value(QStringLiteral("unknown")).toBool());
// one undo gives the element its previous numbering back
QCOMPARE(r.value(QStringLiteral("formulaAfterUndo")).toString(),
QStringLiteral("%id%prefix%sequ_1"));
QCOMPARE(r.value(QStringLiteral("idAfterUndo")).toString(),
r.value(QStringLiteral("idA")).toString());
QVERIFY(r.value(QStringLiteral("save")).toBool());
const Saved saved = parse(read(m_dir.filePath(QStringLiteral("assigned.qet"))));
// three elements gained the numbering, whose counter moved past them
QVERIFY(linkedTo(saved, QStringLiteral("Equipment")) >= 3);
}
// An element whose label is frozen is left as it is by a renumbering, and
// the numbering goes past its number instead of giving it to another
// element. On industrial.qet the element labelled 5F3 is frozen: without
// that, the numbering would give 5F3 to another element of folio 5.
void frozenElementsAreSkipped()
{
const QString body = QStringLiteral(R"JS(
function labels() {
var r = {};
for (var f = 0; f < qet.folioCount(); ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i)
if (qet.elementInfo(f, u[i], 'formula') === '%id%prefix%sequ_1')
r[f + '/' + u[i]] = qet.elementInfo(f, u[i], 'label');
}
return r;
}
var before = labels();
var r = {before: before};
r.frozen = qet.renumberElementAutoNum('Equipment');
r.after = labels();
r.missing = qet.renumberElementAutoNum('Nope');
qet.undo();
r.undone = labels();
qet.log('PROBE ' + JSON.stringify(r));
)JS");
// control: nothing frozen, 5F3 goes to another element
const QJsonObject c = run(body, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!c.isEmpty(), "the control script logged nothing");
QCOMPARE(c.value(QStringLiteral("frozen")).toInt(), 0);
const QJsonObject cb = c.value(QStringLiteral("before")).toObject();
const QJsonObject ca = c.value(QStringLiteral("after")).toObject();
QString holder;
int given = 0;
for (auto it = cb.constBegin() ; it != cb.constEnd() ; ++it) {
if (it.value().toString() == QLatin1String("5F3")) holder = it.key();
}
QVERIFY2(!holder.isEmpty(), "no element is labelled 5F3 in industrial.qet");
for (auto it = ca.constBegin() ; it != ca.constEnd() ; ++it) {
if (it.value().toString() == QLatin1String("5F3") && it.key() != holder) ++given;
}
QVERIFY2(given == 1, "the control renumbering does not give 5F3 to another element: "
"this test would prove nothing");
// with the element frozen
const QString project = withFrozen(QStringLiteral("5F3"));
QVERIFY2(!project.isEmpty(), "cannot freeze the element");
const QJsonObject r = run(body, project);
QVERIFY2(!r.isEmpty(), "the script logged nothing");
QCOMPARE(r.value(QStringLiteral("frozen")).toInt(), 1);
QCOMPARE(r.value(QStringLiteral("missing")).toInt(), -1);
const QJsonObject before = r.value(QStringLiteral("before")).toObject();
const QJsonObject after = r.value(QStringLiteral("after")).toObject();
QCOMPARE(after.size(), before.size());
QCOMPARE(after.value(holder).toString(), QStringLiteral("5F3"));
QSet<QString> seen;
int changed = 0;
for (auto it = after.constBegin() ; it != after.constEnd() ; ++it) {
const QString label = it.value().toString();
QVERIFY2(!seen.contains(label), qPrintable(QStringLiteral("two elements are labelled %1").arg(label)));
seen << label;
if (before.value(it.key()).toString() != label) ++changed;
}
QVERIFY(changed > 0); // the others were numbered again
// one undo gives every label back
QCOMPARE(r.value(QStringLiteral("undone")).toObject(), before);
}
// A scheme whose formula would change cannot be edited while an element
// with a frozen label follows it. A rename, and a change of the counter
// (same formula), still can: they touch no label.
void frozenElementsBlockAnEdit()
{
const QString project = withFrozen(QStringLiteral("5F3"));
QVERIFY2(!project.isEmpty(), "cannot freeze the element");
const QString script = QStringLiteral(R"JS(
function find(label) {
for (var f = 0; f < qet.folioCount(); ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i)
if (qet.elementInfo(f, u[i], 'label') === label) return [f, u[i]];
}
return null;
}
var frozen = find('5F3'), other = find('5F4');
var r = {oldFormula: qet.elementInfo(frozen[0], frozen[1], 'formula')};
r.refused = qet.addAutoNum('element', 'Equipment', ['idfolio', 'elementprefix', 'unit:1', 'string:x']);
r.frozenFormula = qet.elementInfo(frozen[0], frozen[1], 'formula');
r.frozenLabel = qet.elementInfo(frozen[0], frozen[1], 'label');
r.otherFormula = qet.elementInfo(other[0], other[1], 'formula');
r.otherLabel = qet.elementInfo(other[0], other[1], 'label');
r.counter = qet.addAutoNum('element', 'Equipment', ['idfolio', 'elementprefix', 'unit:40']);
r.otherAfterCounter = qet.elementInfo(other[0], other[1], 'label');
r.renamed = qet.renameAutoNum('element', 'Equipment', 'Equipment 2');
r.frozenId = qet.elementInfo(frozen[0], frozen[1], 'formula_id');
r.names = qet.autoNums('element');
qet.log('PROBE ' + JSON.stringify(r));
)JS");
const QJsonObject r = run(script, project);
QVERIFY2(!r.isEmpty(), "the script logged nothing");
QVERIFY(!r.value(QStringLiteral("refused")).toBool());
QCOMPARE(r.value(QStringLiteral("frozenFormula")).toString(),
r.value(QStringLiteral("oldFormula")).toString());
QCOMPARE(r.value(QStringLiteral("frozenLabel")).toString(), QStringLiteral("5F3"));
QCOMPARE(r.value(QStringLiteral("otherFormula")).toString(), r.value(QStringLiteral("oldFormula")).toString());
QCOMPARE(r.value(QStringLiteral("otherLabel")).toString(), QStringLiteral("5F4"));
// same formula: allowed, and no label moves
QVERIFY(r.value(QStringLiteral("counter")).toBool());
QCOMPARE(r.value(QStringLiteral("otherAfterCounter")).toString(), QStringLiteral("5F4"));
// a rename is allowed, the frozen element still follows the numbering
QVERIFY(r.value(QStringLiteral("renamed")).toBool());
QVERIFY(!QUuid(r.value(QStringLiteral("frozenId")).toString()).isNull());
QVERIFY(QJsonDocument(r.value(QStringLiteral("names")).toArray()).toJson().contains("Equipment 2"));
}
// Duplicating elements which follow a numbering gives the copies the next
// numbers of it instead of the labels of the originals, and the numbering
// moves on; one undo takes the copies away and gives the numbers back, one
// redo brings the same numbers again.
void duplicatesAreNumbered()
{
const QString script = QStringLiteral(R"JS(
function labelsOf(f) {
var r = [], u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i)
if (qet.elementInfo(f, u[i], 'formula') === '%id%prefix%sequ_1') r.push(qet.elementInfo(f, u[i], 'label'));
return r;
}
function find(label) {
for (var f = 0; f < qet.folioCount(); ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i)
if (qet.elementInfo(f, u[i], 'label') === label) return [f, u[i]];
}
return null;
}
var a = find('8F1'), b = find('8F2');
var r = {before: labelsOf(a[0])};
var created = qet.duplicateElements(a[0], [a[1], b[1]], a[0], 0, 60);
r.count = created.length;
r.labels = created.map(function (u) { return qet.elementInfo(a[0], u, 'label'); });
r.formulas = created.map(function (u) { return qet.elementInfo(a[0], u, 'formula'); });
r.ids = created.map(function (u) { return qet.elementInfo(a[0], u, 'formula_id'); });
r.originalId = qet.elementInfo(a[0], a[1], 'formula_id');
r.afterAll = labelsOf(a[0]);
qet.undo();
r.afterUndo = labelsOf(a[0]);
qet.redo();
r.afterRedo = labelsOf(a[0]);
qet.undo();
// the counter went back: copying again gives the same numbers
var again = qet.duplicateElements(a[0], [a[1], b[1]], a[0], 0, 60);
r.again = again.map(function (u) { return qet.elementInfo(a[0], u, 'label'); });
qet.log('PROBE ' + JSON.stringify(r));
)JS");
m_preferences.clear();
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
QCOMPARE(r.value(QStringLiteral("count")).toInt(), 2);
const QJsonArray labels = r.value(QStringLiteral("labels")).toArray();
const QJsonArray before = r.value(QStringLiteral("before")).toArray();
QCOMPARE(labels.size(), 2);
for (const QJsonValue &label : labels) {
QVERIFY2(!label.toString().isEmpty(), "a copy has no label");
QVERIFY2(!before.contains(label), qPrintable(QStringLiteral("a copy has the label %1 of another element").arg(label.toString())));
}
QVERIFY(labels.at(0) != labels.at(1));
QVERIFY(labels.at(0).toString().startsWith(QLatin1String("8F")));
const QJsonArray formulas = r.value(QStringLiteral("formulas")).toArray();
const QJsonArray ids = r.value(QStringLiteral("ids")).toArray();
for (int i = 0 ; i < 2 ; ++i) {
QCOMPARE(formulas.at(i).toString(), QStringLiteral("%id%prefix%sequ_1"));
QCOMPARE(ids.at(i).toString(), r.value(QStringLiteral("originalId")).toString());
}
// the originals are untouched, and every label is unique
const QJsonArray all = r.value(QStringLiteral("afterAll")).toArray();
QCOMPARE(all.size(), before.size() + 2);
QSet<QString> unique;
for (const QJsonValue &label : all) unique << label.toString();
QCOMPARE(unique.size(), all.size());
for (const QJsonValue &label : before) QVERIFY(all.contains(label));
// undo, redo, undo and copy again: the same numbers
QCOMPARE(r.value(QStringLiteral("afterUndo")).toArray(), before);
QCOMPARE(r.value(QStringLiteral("afterRedo")).toArray().size(), all.size());
QCOMPARE(r.value(QStringLiteral("again")).toArray(), labels);
}
// The preference "number pasted elements" is off: a copy is what a copy
// was, whatever the label preference says. Erasing (the default) leaves
// it with no label and no numbering; keeping leaves it with the label of
// the original, still tied to the same numbering.
void duplicatesAreNotNumberedWhenSwitchedOff()
{
const QString script = QStringLiteral(R"JS(
function find(label) {
for (var f = 0; f < qet.folioCount(); ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i)
if (qet.elementInfo(f, u[i], 'label') === label) return [f, u[i]];
}
return null;
}
var a = find('8F1');
var created = qet.duplicateElements(a[0], [a[1]], a[0], 0, 60);
var r = {label: qet.elementInfo(a[0], created[0], 'label'),
formula: qet.elementInfo(a[0], created[0], 'formula'),
id: qet.elementInfo(a[0], created[0], 'formula_id')};
qet.log('PROBE ' + JSON.stringify(r));
)JS");
m_preferences = QStringLiteral("[diagramcommands]\nautonumber-pasted-elements=false\n");
const QJsonObject erased = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!erased.isEmpty(), "the script logged nothing");
QVERIFY(erased.value(QStringLiteral("label")).toString().isEmpty());
QVERIFY(erased.value(QStringLiteral("formula")).toString().isEmpty());
QVERIFY(erased.value(QStringLiteral("id")).toString().isEmpty());
m_preferences = QStringLiteral("[diagramcommands]\nautonumber-pasted-elements=false\nerase-label-on-copy=false\n");
const QJsonObject kept = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!kept.isEmpty(), "the script logged nothing");
QCOMPARE(kept.value(QStringLiteral("label")).toString(), QStringLiteral("8F1"));
QCOMPARE(kept.value(QStringLiteral("formula")).toString(), QStringLiteral("%id%prefix%sequ_1"));
QVERIFY(!QUuid(kept.value(QStringLiteral("id")).toString()).isNull());
m_preferences.clear();
}
// Conductor numberings: names are unique and never empty, a rename moves
// the folios which read the numbering with it (26 folios of
// industrial.qet read "Wire"), a numbering which folios read cannot be
// removed, an unused one can, and every step is one undo.
void conductorNumberings()
{
const QString saved = m_dir.filePath(QStringLiteral("conductors.qet"));
const QString script = QStringLiteral(R"JS(
var r = {};
r.names = qet.autoNums('conductor');
r.clash = qet.addAutoNum('conductor', ' wire ', ['unit:1']);
r.empty = qet.addAutoNum('conductor', ' ', ['unit:1']);
r.added = qet.addAutoNum('conductor', 'Spare', ['string:S', 'unit:1']);
r.renameClash = qet.renameAutoNum('conductor', 'Spare', 'WIRE');
r.removeUsed = qet.removeAutoNum('conductor', 'Wire');
r.renamed = qet.renameAutoNum('conductor', 'Wire', 'Cable');
r.afterRename = qet.autoNums('conductor');
r.missing = qet.renameAutoNum('conductor', 'Nope', 'X');
r.removeSpare = qet.removeAutoNum('conductor', 'Spare');
r.afterRemove = qet.autoNums('conductor');
qet.undo(); // the removal
r.afterUndoRemove = qet.autoNums('conductor');
qet.undo(); qet.undo(); // the rename, then the creation
qet.redo(); qet.redo(); // the creation, then the rename
r.afterRedo = qet.autoNums('conductor');
r.save = qet.save(%1);
qet.log('PROBE ' + JSON.stringify(r));
)JS").arg(QStringLiteral("'") + saved + QStringLiteral("'"));
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
const auto names = [&](const char *key) {
return titlesOf(r.value(QLatin1String(key)));
};
QCOMPARE(names("names"), QStringList{QStringLiteral("Wire")});
QVERIFY(!r.value(QStringLiteral("clash")).toBool()); // " wire " is "Wire"
QVERIFY(!r.value(QStringLiteral("empty")).toBool());
QVERIFY(r.value(QStringLiteral("added")).toBool());
QVERIFY(!r.value(QStringLiteral("renameClash")).toBool()); // "WIRE" is "Wire"
QVERIFY(!r.value(QStringLiteral("removeUsed")).toBool()); // 26 folios read it
QVERIFY(r.value(QStringLiteral("renamed")).toBool());
QCOMPARE(names("afterRename"), (QStringList{QStringLiteral("Cable"), QStringLiteral("Spare")}));
QVERIFY(!r.value(QStringLiteral("missing")).toBool());
QVERIFY(r.value(QStringLiteral("removeSpare")).toBool()); // no folio reads it
QCOMPARE(names("afterRemove"), QStringList{QStringLiteral("Cable")});
QCOMPARE(names("afterUndoRemove"), (QStringList{QStringLiteral("Cable"), QStringLiteral("Spare")}));
QCOMPARE(names("afterRedo"), (QStringList{QStringLiteral("Cable"), QStringLiteral("Spare")}));
// the folios read the numbering under its new name
QVERIFY(r.value(QStringLiteral("save")).toBool());
const QByteArray xml = read(saved);
const QStringList followed = diagramAttributes(xml, QStringLiteral("conductorAutonum"));
QCOMPARE(followed.count(QStringLiteral("Cable")), 26);
QCOMPARE(followed.count(QStringLiteral("Wire")), 0);
}
// Folio numberings. A folio numbering is applied once: the number is
// written into the folio field and the title block keeps the name (49 of
// the 50 folios of industrial.qet name "Name of the new numbering", none
// has a folio field which still holds %autonum). A rename moves every name,
// removing a numbering which folios only name is allowed, and undo and
// redo give back the same names.
void folioNumberings()
{
const QString renamed_file = m_dir.filePath(QStringLiteral("folio-renamed.qet"));
const QString undone_file = m_dir.filePath(QStringLiteral("folio-undone.qet"));
const QString script = QStringLiteral(R"JS(
var r = {};
r.names = qet.autoNums('folio');
r.clash = qet.addAutoNum('folio', 'name of the new numbering', ['unit:1']);
r.empty = qet.addAutoNum('folio', ' ', ['unit:1']);
r.renamed = qet.renameAutoNum('folio', 'Name of the new numbering', 'Folios');
r.afterRename = qet.autoNums('folio');
r.saveRenamed = qet.save(%1);
r.removed = qet.removeAutoNum('folio', 'Folios');
r.afterRemove = qet.autoNums('folio');
qet.undo(); // the removal
r.afterUndoRemove = qet.autoNums('folio');
qet.undo(); // the rename
r.afterUndoRename = qet.autoNums('folio');
r.saveUndone = qet.save(%2);
qet.redo();
r.afterRedo = qet.autoNums('folio');
qet.log('PROBE ' + JSON.stringify(r));
)JS").arg(QStringLiteral("'") + renamed_file + QStringLiteral("'"),
QStringLiteral("'") + undone_file + QStringLiteral("'"));
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
const auto names = [&](const char *key) {
return titlesOf(r.value(QLatin1String(key)));
};
const QString old_name = QStringLiteral("Name of the new numbering");
QCOMPARE(names("names"), QStringList{old_name});
QVERIFY(!r.value(QStringLiteral("clash")).toBool());
QVERIFY(!r.value(QStringLiteral("empty")).toBool());
QVERIFY(r.value(QStringLiteral("renamed")).toBool());
QCOMPARE(names("afterRename"), QStringList{QStringLiteral("Folios")});
QVERIFY(r.value(QStringLiteral("removed")).toBool());
QCOMPARE(names("afterRemove"), QStringList());
QCOMPARE(names("afterUndoRemove"), QStringList{QStringLiteral("Folios")});
QCOMPARE(names("afterUndoRename"), QStringList{old_name});
QCOMPARE(names("afterRedo"), QStringList{QStringLiteral("Folios")});
QVERIFY(r.value(QStringLiteral("saveRenamed")).toBool());
QStringList named = diagramAttributes(read(renamed_file), QStringLiteral("auto_page_num"));
QCOMPARE(named.size(), 50);
QCOMPARE(named.count(QStringLiteral("Folios")), 49);
QCOMPARE(named.count(old_name), 0);
QVERIFY(r.value(QStringLiteral("saveUndone")).toBool());
named = diagramAttributes(read(undone_file), QStringLiteral("auto_page_num"));
QCOMPARE(named.count(old_name), 49);
QCOMPARE(named.count(QStringLiteral("Folios")), 0);
}
// Free numbers. An element which follows a numbering of one sequence may be
// given a number nobody has, and keeps following the numbering: the case of
// an element which, after a reshuffle, must have its real-life number back.
// Only free numbers are offered and accepted; one undo gives the number back;
// a number past the counter moves the counter, so that the next element is
// not given the same label.
void freeNumbers()
{
const QString script = QStringLiteral(R"JS(
function find(label) {
for (var f = 0; f < qet.folioCount(); ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i)
if (qet.elementInfo(f, u[i], 'label') === label) return [f, u[i]];
}
return null;
}
function number(label) { var m = /(\d+)$/.exec(label); return m ? parseInt(m[1]) : -1; }
function followers() {
var r = [];
for (var f = 0; f < qet.folioCount(); ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i)
if (qet.elementInfo(f, u[i], 'formula') === '%id%prefix%sequ_1') r.push(number(qet.elementInfo(f, u[i], 'label')));
}
return r.sort(function (a, b) { return a - b; });
}
var e = find('8F2'), other = find('8F1');
var r = {used: followers()};
r.before = qet.elementInfo(e[0], e[1], 'label');
r.own = number(r.before);
r.free = qet.freeElementNumbers(e[0], e[1]);
r.numberOfOther = number(qet.elementInfo(other[0], other[1], 'label'));
r.idBefore = qet.elementInfo(e[0], e[1], 'formula_id');
r.assignUsed = qet.assignElementNumber(e[0], e[1], r.numberOfOther); // taken: refused
r.assignZero = qet.assignElementNumber(e[0], e[1], 0);
r.assignFree = qet.assignElementNumber(e[0], e[1], r.free[0]);
r.after = qet.elementInfo(e[0], e[1], 'label');
r.formulaAfter = qet.elementInfo(e[0], e[1], 'formula');
r.idAfter = qet.elementInfo(e[0], e[1], 'formula_id');
r.freeAfter = qet.freeElementNumbers(e[0], e[1]); // its old number is free now
qet.undo();
r.undone = qet.elementInfo(e[0], e[1], 'label');
// a number past the counter: the counter moves past it
var high = r.free[r.free.length - 1];
r.high = high;
r.assignHigh = qet.assignElementNumber(e[0], e[1], high);
r.highLabel = qet.elementInfo(e[0], e[1], 'label');
var plain = null;
for (var f = 0; f < qet.folioCount() && !plain; ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i)
if (qet.elementInfo(f, u[i], 'formula') === '') { plain = [f, u[i]]; break; }
}
// an element which follows nothing has no numbers to choose from, nor can it be given one
r.noFree = qet.freeElementNumbers(plain[0], plain[1]).length;
r.assignNothing = qet.assignElementNumber(plain[0], plain[1], 5);
// the next element of the numbering is past the number given by hand
r.assignNext = qet.assignElementAutoNum('Equipment', plain[0], plain[1], false);
r.nextNumber = number(qet.elementInfo(plain[0], plain[1], 'label'));
qet.log('PROBE ' + JSON.stringify(r));
)JS");
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
const QJsonArray used = r.value(QStringLiteral("used")).toArray();
const QJsonArray free = r.value(QStringLiteral("free")).toArray();
QVERIFY(!free.isEmpty());
const int own = r.value(QStringLiteral("own")).toInt();
for (const QJsonValue &n : free)
QVERIFY2(!used.contains(n) || n.toInt() == own, "a number in use is offered");
QVERIFY(!free.contains(r.value(QStringLiteral("numberOfOther"))));
QVERIFY(!r.value(QStringLiteral("assignUsed")).toBool());
QVERIFY(!r.value(QStringLiteral("assignZero")).toBool());
QVERIFY(r.value(QStringLiteral("assignFree")).toBool());
const QString after = r.value(QStringLiteral("after")).toString();
QVERIFY(after != r.value(QStringLiteral("before")).toString());
QVERIFY2(after.endsWith(QString::number(free.at(0).toInt())), qPrintable(after));
QVERIFY(after.startsWith(QLatin1String("8F")));
QCOMPARE(r.value(QStringLiteral("formulaAfter")).toString(), QStringLiteral("%id%prefix%sequ_1"));
QCOMPARE(r.value(QStringLiteral("idAfter")).toString(), r.value(QStringLiteral("idBefore")).toString());
// the number it had is free for another element now
QVERIFY(r.value(QStringLiteral("freeAfter")).toArray().size() >= free.size());
QCOMPARE(r.value(QStringLiteral("undone")).toString(), r.value(QStringLiteral("before")).toString());
// a number past the counter moves the counter on
QVERIFY(r.value(QStringLiteral("assignHigh")).toBool());
QVERIFY(r.value(QStringLiteral("assignNext")).toBool());
QVERIFY2(r.value(QStringLiteral("nextNumber")).toInt() > r.value(QStringLiteral("high")).toInt(),
"the next element was given a number which is not past the one given by hand");
QCOMPARE(r.value(QStringLiteral("noFree")).toInt(), 0);
QVERIFY(!r.value(QStringLiteral("assignNothing")).toBool());
}
// A numbering whose counter is set back over numbers in use does not hand
// them out again: placing an element takes the first number whose label
// nobody carries, and the counter goes on from there; one undo gives the
// counter back. A label held by an element which does not follow the
// numbering counts too.
void placementSkipsLabelsInUse()
{
const QString script = QStringLiteral(R"JS(
function labels() {
var r = [];
for (var f = 0; f < qet.folioCount(); ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i) {
var l = qet.elementInfo(f, u[i], 'label');
if (l !== '') r.push(l);
}
}
return r;
}
// number the first element without a formula which accepts one (a slave or a report does not)
function numberOne(skip) {
for (var f = 0; f < qet.folioCount(); ++f) {
var u = qet.elementUuids(f);
for (var i = 0; i < u.length; ++i) {
if (qet.elementInfo(f, u[i], 'formula') !== '' || skip.indexOf(u[i]) >= 0) continue;
if (qet.numberElement(f, u[i])) return [f, u[i]];
}
}
return null;
}
var r = {};
// the counter of "Equipment" back at 1, over the 29 elements which have numbers: allowed
r.back = qet.addAutoNum('element', 'Equipment', ['idfolio', 'elementprefix', 'unit:1']);
r.use = qet.useElementAutoNum('Equipment');
var held = labels();
var a = numberOne([]);
r.placedA = a !== null;
r.labelA = qet.elementInfo(a[0], a[1], 'label');
r.heldA = held.indexOf(r.labelA) >= 0;
var b = numberOne([a[1]]);
r.placedB = b !== null;
r.labelB = qet.elementInfo(b[0], b[1], 'label');
r.heldB = held.indexOf(r.labelB) >= 0 || r.labelB === r.labelA;
r.formulaA = qet.elementInfo(a[0], a[1], 'formula');
// one undo takes B's number back, the next undo A's
qet.undo();
r.afterUndoB = qet.elementInfo(b[0], b[1], 'label');
qet.undo();
r.afterUndoA = qet.elementInfo(a[0], a[1], 'label');
r.again = qet.numberElement(a[0], a[1]);
r.labelAgain = qet.elementInfo(a[0], a[1], 'label');
qet.log('PROBE ' + JSON.stringify(r));
)JS");
const QJsonObject r = run(script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY2(!r.isEmpty(), "the script logged nothing");
QVERIFY(r.value(QStringLiteral("back")).toBool());
QVERIFY(r.value(QStringLiteral("use")).toBool());
QVERIFY(r.value(QStringLiteral("placedA")).toBool());
QVERIFY(r.value(QStringLiteral("placedB")).toBool());
const QString a = r.value(QStringLiteral("labelA")).toString();
const QString b = r.value(QStringLiteral("labelB")).toString();
QVERIFY2(!a.isEmpty() && !b.isEmpty(), "an element was not given a label");
QVERIFY2(!r.value(QStringLiteral("heldA")).toBool(), qPrintable(QStringLiteral("%1 is held by another element").arg(a)));
QVERIFY2(!r.value(QStringLiteral("heldB")).toBool(), qPrintable(QStringLiteral("%1 is held by another element").arg(b)));
QVERIFY(a != b);
QCOMPARE(r.value(QStringLiteral("formulaA")).toString(), QStringLiteral("%id%prefix%sequ_1"));
// undoing gives the numbers back, and numbering again gives the same label
QVERIFY(r.value(QStringLiteral("afterUndoB")).toString() != b);
QVERIFY(r.value(QStringLiteral("afterUndoA")).toString() != a);
QVERIFY(r.value(QStringLiteral("again")).toBool());
QCOMPARE(r.value(QStringLiteral("labelAgain")).toString(), a);
}
};
QTEST_APPLESS_MAIN(tst_elementautonumids)
#include "tst_elementautonumids.moc"
+3
View File
@@ -122,6 +122,9 @@ private slots:
// (#973), and a value with accents comes back as it went in.
void singleSpaceValueKept()
{
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
QSKIP("QDomDocument::PreserveSpacingOnlyNodes needs Qt 6.5 (see QETProject::openFile)");
#endif
QByteArray xml = read(QStringLiteral(QET_EXAMPLES_DIR "/Projet_vierge.qet"));
QVERIFY(xml.contains("<properties>"));
xml.replace("<properties>",