From a6fd42ac5f564c1546827bb9a3ded5c8ffea257f Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sun, 13 Sep 2026 09:56:25 +1200 Subject: [PATCH] Import automatic numbering rules from another project Forum #3186 / issue #850: a user who has built up conductor and element numbering rules in one project has no way to reuse them in the next one. The only answer today is to open both .qet files in a text editor and copy the XML across by hand. Adds an "Import from another project..." button to the auto-numbering page of the project properties dialog. It offers every numbering found in the chosen file, per category, with names that already exist here unticked by default and a "replace same-named numberings" option for when that is what the user wants. The source file is parsed as plain XML rather than opened as a QETProject. Opening it would run the whole load path, including the modal dialog raised for a file written by a different version of QElectroTech -- a dialog the user has no reason to see, since nothing but the block is being read. Two supporting changes: - readValuesFromProject() clears the three combo boxes before filling them. It only ran once before; it now runs again after an import, and without the clear every name appeared twice. - FolioAutonumberingW::setContext() likewise replaces its list instead of appending to it. It has a single caller, the line above. This deliberately does not attempt the project-template feature also raised on the forum thread. That needs decisions about where templates live and what else they carry, and is better settled in a discussion first. Co-Authored-By: Claude Opus 5 --- sources/autoNum/ui/folioautonumbering.cpp | 4 + sources/ui/configpage/projectconfigpages.cpp | 218 ++++++++++++++++++- sources/ui/configpage/projectconfigpages.h | 2 + 3 files changed, 223 insertions(+), 1 deletion(-) diff --git a/sources/autoNum/ui/folioautonumbering.cpp b/sources/autoNum/ui/folioautonumbering.cpp index 69e413039..a39f4d770 100644 --- a/sources/autoNum/ui/folioautonumbering.cpp +++ b/sources/autoNum/ui/folioautonumbering.cpp @@ -54,6 +54,10 @@ FolioAutonumberingW::~FolioAutonumberingW() */ void FolioAutonumberingW::setContext(QList autonums) { + // Replace the list rather than append to it: this is called again + // whenever the project's numberings change (import, for instance), + // and appending gave a second copy of every name. + ui->m_autonums_cb->clear(); foreach (QString str, autonums) { ui->m_autonums_cb->addItem(str);} } diff --git a/sources/ui/configpage/projectconfigpages.cpp b/sources/ui/configpage/projectconfigpages.cpp index ab31a9aa1..0d38d6f76 100644 --- a/sources/ui/configpage/projectconfigpages.cpp +++ b/sources/ui/configpage/projectconfigpages.cpp @@ -23,6 +23,7 @@ #include "../autoNum/ui/formulaautonumberingw.h" #include "../autoNum/ui/selectautonumw.h" #include "../project/projectpropertieshandler.h" +#include "../qet.h" #include "../qeticons.h" #include "../qetproject.h" #include "../borderpropertieswidget.h" @@ -354,8 +355,19 @@ void ProjectAutoNumConfigPage::initWidgets() m_faw = new FolioAutonumberingW(project()); tab_widget->addTab(m_faw, tr("Numérotation auto des folios")); - QHBoxLayout *main_layout = new QHBoxLayout(); + m_import_pb = new QPushButton( + tr("Importer depuis un autre projet..."), this); + m_import_pb->setToolTip( + tr("Reprendre les numérotations automatiques " + "enregistrées dans un autre projet")); + + QHBoxLayout *button_layout = new QHBoxLayout(); + button_layout->addStretch(); + button_layout->addWidget(m_import_pb); + + QVBoxLayout *main_layout = new QVBoxLayout(); main_layout->addWidget(tab_widget); + main_layout->addLayout(button_layout); setLayout(main_layout); buildConnections(); @@ -367,6 +379,12 @@ void ProjectAutoNumConfigPage::initWidgets() */ void ProjectAutoNumConfigPage::readValuesFromProject() { + // This is called again after an import, so start from an empty + // combo box instead of appending a second copy of every name. + m_saw_conductor->contextComboBox()->clear(); + m_saw_element->contextComboBox()->clear(); + m_saw_folio->contextComboBox()->clear(); + //Conductor Tab const QStringList strlc(m_project->conductorAutoNum().keys()); m_saw_conductor->contextComboBox()->addItems(strlc); @@ -389,6 +407,9 @@ void ProjectAutoNumConfigPage::readValuesFromProject() */ void ProjectAutoNumConfigPage::adjustReadOnly() { + if (m_import_pb && m_project) { + m_import_pb->setDisabled(m_project->isReadOnly()); + } } /** @@ -417,6 +438,9 @@ void ProjectAutoNumConfigPage::buildConnections() // Auto Folio Numbering connect(m_faw, &FolioAutonumberingW::applyPressed, this, &ProjectAutoNumConfigPage::applyAutoNum); + + //Import from another project + connect(m_import_pb, &QPushButton::clicked, this, &ProjectAutoNumConfigPage::importFromProject); } /** @@ -487,6 +511,198 @@ void ProjectAutoNumConfigPage::saveContextElement() } } +/** + @brief ProjectAutoNumConfigPage::importFromProject + Read the automatic numbering rules stored in another .qet project and + copy the ones the user selects into this project. + + The source file is parsed as plain XML rather than opened as a + QETProject: opening it would run the whole load path, including the + modal dialog raised when the file was written by a different version + of QElectroTech. +*/ +void ProjectAutoNumConfigPage::importFromProject() +{ + if (!m_project || m_project->isReadOnly()) { + return; + } + + const QString path = QFileDialog::getOpenFileName( + this, + tr("Importer les numérotations d'un projet"), + m_project->currentDir(), + tr("Projet QElectroTech (*.qet)")); + if (path.isEmpty()) { + return; + } + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox::warning(this, tr("Import impossible"), + tr("Impossible d'ouvrir %1").arg(path)); + return; + } + + QDomDocument doc; + if (!doc.setContent(&file)) { + QMessageBox::warning(this, tr("Import impossible"), + tr("%1 n'est pas un projet QElectroTech valide.") + .arg(QFileInfo(path).fileName())); + return; + } + file.close(); + + const QDomNodeList newdiagrams = + doc.elementsByTagName(QStringLiteral("newdiagrams")); + if (newdiagrams.isEmpty()) { + QMessageBox::information(this, tr("Aucune numérotation"), + tr("Ce projet ne contient aucune numérotation automatique.")); + return; + } + const QDomElement root = newdiagrams.at(0).toElement(); + + // tag of the group, tag of one entry, label shown to the user + struct Category { + QString group_tag; + QString item_tag; + QString label; + }; + const QList categories { + {QStringLiteral("conductors_autonums"), + QStringLiteral("conductor_autonum"), tr("Conducteurs")}, + {QStringLiteral("element_autonums"), + QStringLiteral("element_autonum"), tr("Eléments")}, + {QStringLiteral("folio_autonums"), + QStringLiteral("folio_autonum"), tr("Folios")} + }; + + QDialog dialog(this); + dialog.setWindowTitle(tr("Numérotations à importer")); + QVBoxLayout *layout = new QVBoxLayout(&dialog); + layout->addWidget(new QLabel( + tr("Numérotations trouvées dans %1 :") + .arg(QFileInfo(path).fileName()), &dialog)); + + QListWidget *list = new QListWidget(&dialog); + layout->addWidget(list); + + // NumerotationContext is not a QVariant type, so the list item + // carries an index into this instead of the context itself. + QList contexts; + for (int i = 0 ; i < categories.count() ; ++i) + { + const Category &category = categories.at(i); + QDomElement group; + for (QDomNode n = root.firstChild() ; !n.isNull() ; n = n.nextSibling()) { + if (n.toElement().tagName() == category.group_tag) { + group = n.toElement(); + break; + } + } + if (group.isNull()) { + continue; + } + + for (QDomElement entry : QET::findInDomElement(group, category.item_tag)) + { + const QString title = entry.attribute(QStringLiteral("title")); + if (title.isEmpty()) { + continue; + } + + bool exists = false; + switch (i) { + 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; + } + + QListWidgetItem *item = new QListWidgetItem( + exists ? tr("%1 : %2 (existe déjà)") + .arg(category.label, title) + : QStringLiteral("%1 : %2") + .arg(category.label, title), + list); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(exists ? Qt::Unchecked : Qt::Checked); + item->setData(Qt::UserRole, i); + item->setData(Qt::UserRole + 1, title); + + NumerotationContext nc; + nc.fromXml(entry); + item->setData(Qt::UserRole + 2, contexts.count()); + contexts << nc; + } + } + + if (contexts.isEmpty()) { + QMessageBox::information(this, tr("Aucune numérotation"), + tr("Ce projet ne contient aucune numérotation automatique.")); + return; + } + + QCheckBox *overwrite_cb = new QCheckBox( + tr("Remplacer les numérotations de même nom"), &dialog); + layout->addWidget(overwrite_cb); + + QDialogButtonBox *buttons = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog); + layout->addWidget(buttons); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + if (dialog.exec() != QDialog::Accepted) { + return; + } + + int imported = 0, skipped = 0, conductors = 0; + for (int row = 0 ; row < list->count() ; ++row) + { + QListWidgetItem *item = list->item(row); + if (item->checkState() != Qt::Checked) { + continue; + } + + const int category = item->data(Qt::UserRole).toInt(); + const QString title = item->data(Qt::UserRole + 1).toString(); + + 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; + } + if (exists && !overwrite_cb->isChecked()) { + ++skipped; + continue; + } + + const NumerotationContext &nc = + contexts.at(item->data(Qt::UserRole + 2).toInt()); + switch (category) { + case 0: + m_project->addConductorAutoNum(title, nc); + ++conductors; + break; + case 1: m_project->addElementAutoNum(title, nc); break; + default: m_project->addFolioAutoNum(title, nc); break; + } + ++imported; + } + + readValuesFromProject(); + if (conductors) { + m_project->conductorAutoNumAdded(); + } + + 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)); +} + /** @brief ProjectAutoNumConfigPage::removeContextElement Remove from project the current element numerotation context diff --git a/sources/ui/configpage/projectconfigpages.h b/sources/ui/configpage/projectconfigpages.h index 79649689a..fa9929e6b 100644 --- a/sources/ui/configpage/projectconfigpages.h +++ b/sources/ui/configpage/projectconfigpages.h @@ -156,6 +156,7 @@ class ProjectAutoNumConfigPage : public ProjectConfigPage { void updateContextElement(const QString&);//element void saveContextElement(); void removeContextElement(); + void importFromProject(); void applyAutoNum(); void applyManagement(); @@ -173,6 +174,7 @@ class ProjectAutoNumConfigPage : public ProjectConfigPage { SelectAutonumW *m_saw_element; FolioAutonumberingW *m_faw; AutoNumberingManagementW *m_amw; + QPushButton *m_import_pb = nullptr; };