Let a script list, embed and apply a folio's title block template

titleBlockTemplates()          embedded + common/company/custom, by name
  embedTitleBlockTemplate(name)  copy one into the project's own collection
  setFolioProperty(f,"template",name)  embed-if-needed, then apply
  folioProperty(f,"template")

Not the trivial addition to the existing title-block-field list it looked
like at first. Diagram::setTitleBlockTemplate() resolves a name only
against QETProject::embeddedTitleBlockTemplatesCollection() -- the exact
same copy-into-the-project step addElement() already goes through for
elements, and for the same reason: a project opened on another machine
must not depend on files only this one has. embedTitleBlockTemplate()
does that copy through get/setTemplateXmlDescription(), the same round
trip the template editor itself uses to save one -- not scripting-specific
code, and unlike defining an auto-numbering context, not undoable, for the
same reason that isn't: the application does both through direct
collection/project calls with no undo command of their own.

Two things found only by testing, not by reading:

- "default" is a real template name in the common collection, and setting
  a folio's template to it is legitimate -- but
  BorderTitleBlock::titleBlockTemplateName() normalises a template
  literally named "default" back to "", indistinguishable from no
  override, since that is genuinely what "no override" renders with. The
  first version compared the raw name and reported success as failure;
  fixed by comparing against that same normalised form, which folioProperty()
  now also documents.
- QElectroTech resolves the common template collection from a compiled-in
  path (here, an absolute /usr/share/qelectrotech/titleblocks, not
  relative to the binary), and --common-tbt-dir, the CLI override, is
  read by QETApp::parseArguments() -- which the --run headless path never
  reaches, confirmed by the CLI itself swallowing the flag as a stray
  positional argument. There is no QSettings fallback the way
  commonElementsDir() has. So testing this at all needed the path to
  genuinely exist; no environment trick from inside the process reaches
  it.

Verified: 10 common templates listed; DIN_A4 embedded and applied,
folioProperty reading it back; re-applying the same name a no-op success;
an unknown name refused; "default" applied and correctly read back as ""
per the note above; both folios exported to PNG and visually compared --
plain default rendering vs. DIN_A4's logo, revision table and field
layout, genuinely different, not just an API call returning true. The
choice survives a save and reload. Qt 6.10.2, ctest 12/12, coherence gate
clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-22 11:10:19 +12:00
parent cd27912605
commit a8f883601d
2 changed files with 125 additions and 2 deletions
+100 -2
View File
@@ -28,6 +28,7 @@
#include "../qetgraphicsitem/element.h"
#include "../qetmessagebox.h"
#include "../dataBase/projectdatabase.h"
#include "../qetapp.h"
#include "../qetproject.h"
#include "../qetresult.h"
#include "../qetgraphicsitem/conductor.h"
@@ -42,6 +43,7 @@
#include "../autoNum/assignvariables.h"
#include "../autoNum/numerotationcontext.h"
#include "../borderproperties.h"
#include "../titleblock/templatescollection.h"
#include "../diagramcommands.h"
#include "../qetgraphicsitem/terminal.h"
#include "../qetgraphicsitem/terminalelement.h"
@@ -1504,13 +1506,17 @@ QString *titleBlockField(TitleBlockProperties &p, const QString &name)
// QElectroTech writes on every save, so a value set here reports success
// and is overwritten -- measured: set "V9-USER", read back "0.200.1-dev".
if (name == QLatin1String("folio")) return &p.folio;
// Not "template" either: template_name resolves against the project's
// embedded collection, not free text, so it goes through
// setFolioProperty()'s own branch (embedTitleBlockTemplate() first)
// rather than this direct field lookup.
return nullptr;
}
const QStringList &titleBlockFieldNames()
{
static const QStringList n{QStringLiteral("title"), QStringLiteral("author"),
QStringLiteral("filename"), QStringLiteral("plant"), QStringLiteral("locmach"),
QStringLiteral("indexrev"), QStringLiteral("folio")};
QStringLiteral("indexrev"), QStringLiteral("folio"), QStringLiteral("template")};
return n;
}
} // namespace
@@ -1520,7 +1526,10 @@ QString QetScriptApi::folioProperty(int folioIndex, const QString &property) con
if (!m_project) return QString();
const QList<Diagram *> diagrams = m_project->diagrams();
if (folioIndex < 0 || folioIndex >= diagrams.count()) return QString();
TitleBlockProperties p = diagrams.at(folioIndex)->border_and_titleblock.exportTitleBlock();
Diagram *diagram = diagrams.at(folioIndex);
if (property == QLatin1String("template"))
return diagram->border_and_titleblock.titleBlockTemplateName();
TitleBlockProperties p = diagram->border_and_titleblock.exportTitleBlock();
QString *field = titleBlockField(p, property);
return field ? *field : QString();
}
@@ -1544,6 +1553,27 @@ bool QetScriptApi::setFolioProperty(int folioIndex, const QString &property, con
if (folioIndex < 0 || folioIndex >= diagrams.count()) return false;
Diagram *diagram = diagrams.at(folioIndex);
// Not a TitleBlockProperties field like the others below: the template
// is named by whatever the project's embedded collection calls it, not
// by a value stored on this folio's own properties, so it has to be
// embedded (or already present) before Diagram::setTitleBlockTemplate()
// -- the same public slot BorderTitleBlock's own needTitleBlockTemplate
// signal calls -- can find it.
if (property == QLatin1String("template")) {
// BorderTitleBlock::titleBlockTemplateName() normalises a template
// literally named "default" back to "" -- indistinguishable, once
// set, from no override at all (a template named "default" ships
// in the common collection and is genuinely what "no override"
// renders with). Compare against that same normalised form, or a
// script setting "default" would see this report failure although
// the application applied it correctly -- measured: it did.
const QString normalised = (value == QLatin1String("default")) ? QString() : value;
if (diagram->border_and_titleblock.titleBlockTemplateName() == normalised) return true;
if (!embedTitleBlockTemplate(value)) return false;
diagram->setTitleBlockTemplate(value);
return diagram->border_and_titleblock.titleBlockTemplateName() == normalised;
}
TitleBlockProperties old_p = diagram->border_and_titleblock.exportTitleBlock();
TitleBlockProperties new_p = old_p;
QString *field = titleBlockField(new_p, property);
@@ -2427,6 +2457,74 @@ int QetScriptApi::insertFolio(int position)
return m_project->diagrams().indexOf(diagram);
}
/**
@brief QetScriptApi::titleBlockTemplates
Every title block template this project can use right now (embedded)
or could embed and then use (common, company, custom), each name
suffixed with which. A name can appear more than once, under different
sources -- embedding does not remove it from where it came from, and a
project can have its own embedded copy of a name the common collection
also has, which then shadows it (Diagram::setTitleBlockTemplate() only
ever looks in the embedded one).
*/
QStringList QetScriptApi::titleBlockTemplates() const
{
QStringList list;
if (!m_project) return list;
auto describe = [&list](TitleBlockTemplatesCollection *c, const QString &source) {
if (!c) return;
const QStringList names = c->templates();
for (const QString &n : names) {
list << QStringLiteral("%1 (%2)").arg(n, source);
}
};
describe(m_project->embeddedTitleBlockTemplatesCollection(), QStringLiteral("embedded"));
describe(QETApp::commonTitleBlockTemplatesCollection(), QStringLiteral("common"));
describe(QETApp::companyTitleBlockTemplatesCollection(), QStringLiteral("company"));
describe(QETApp::customTitleBlockTemplatesCollection(), QStringLiteral("custom"));
return list;
}
/**
@brief QetScriptApi::embedTitleBlockTemplate
Copy a template's XML into the project's own embedded collection, from
the first of common/company/custom that has it -- the same
get/setTemplateXmlDescription() round trip the template editor itself
uses to save one, not scripting-specific code. A no-op, reporting
success, if the project already has an embedded copy of that name: the
embedded one is what Diagram::setTitleBlockTemplate() will use either
way, so re-embedding would only discard a project-specific edit to it
for no reason.
*/
bool QetScriptApi::embedTitleBlockTemplate(const QString &name)
{
if (!m_project) return false;
if (m_project->isReadOnly()) {
log(QStringLiteral("qet.embedTitleBlockTemplate: project is read-only"));
return false;
}
if (name.isEmpty()) {
log(QStringLiteral("qet.embedTitleBlockTemplate: empty name"));
return false;
}
auto *embedded = m_project->embeddedTitleBlockTemplatesCollection();
if (embedded->templates().contains(name)) return true;
const QList<TitleBlockTemplatesCollection *> sources{
QETApp::commonTitleBlockTemplatesCollection(),
QETApp::companyTitleBlockTemplatesCollection(),
QETApp::customTitleBlockTemplatesCollection()};
for (TitleBlockTemplatesCollection *source : sources)
{
if (!source || !source->templates().contains(name)) continue;
const QDomElement xml = source->getTemplateXmlDescription(name);
if (xml.isNull()) continue;
return embedded->setTemplateXmlDescription(name, xml);
}
log(QStringLiteral("qet.embedTitleBlockTemplate: no collection has a template named '%1'").arg(name));
return false;
}
int QetScriptApi::addFolio()
{
if (!m_project) return -1;