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
+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>