Files
qelectrotech-source-mirror/sources/titleblocktemplate.cpp
T
Shane Ringrose ba6320bff8 Folio properties: auto-add a title block's custom variables (#271)
When a title block template uses custom variables (e.g. %{department},
%{owner}), the user previously had to declare each one by hand in the
folio properties 'Custom' tab before a value could be entered. Now the
template's undefined custom variables are added automatically, so the
user only fills in the values.

- listOfVariables() now extracts %{name} placeholders with a regex
  (deduplicated) instead of a crude '%' strip that returned '{name}'.
- The folio properties widget merges the template's custom variables into
  the Custom tab both on open (setProperties) and when the template is
  changed, preserving any values already entered and skipping the
  standard fields (title, author, date, ...) which have their own inputs.

Fixes #271 (variable auto-population; the revision-history request in the
thread is a separate feature).
2026-06-12 05:45:08 +12:00

2142 lines
59 KiB
C++

/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "titleblocktemplate.h"
#include "NameList/nameslist.h"
#include "createdxf.h"
#include "qet.h"
#include "qetapp.h"
// uncomment the line below to get more debug information
//#define TITLEBLOCK_TEMPLATE_DEBUG
#include <QRegularExpression>
#include <QRegularExpressionMatch>
/**
@brief TitleBlockTemplate::TitleBlockTemplate
Constructor
@param parent parent QObject
*/
TitleBlockTemplate::TitleBlockTemplate(QObject *parent) :
QObject(parent)
{
}
/**
@brief TitleBlockTemplate::~TitleBlockTemplate
Destructor
*/
TitleBlockTemplate::~TitleBlockTemplate()
{
loadLogos(QDomElement(), true);
qDeleteAll(registered_cells_);
}
/**
@brief TitleBlockTemplate::createCell
Create a new cell and associate it with this template,
which means that it will be deleted when this template is destroyed.
@param existing_cell (optional) An existing cell that will be copied
@return A pointer to the newly created cell
*/
TitleBlockCell *TitleBlockTemplate::createCell(
const TitleBlockCell *existing_cell) {
TitleBlockCell *new_cell =
existing_cell ? new TitleBlockCell(*existing_cell)
: new TitleBlockCell();
registered_cells_ << new_cell;
return(new_cell);
}
/**
@brief TitleBlockTemplate::createCellsList
@param count :
Number of cells expected in the list
@return a list containing count newly created (and registered) cells
@see createCell()
*/
QList<TitleBlockCell *> TitleBlockTemplate::createCellsList(int count) {
QList<TitleBlockCell *> new_list;
for (int i = 0 ; i < count ; ++ i) new_list << createCell();
return(new_list);
}
/**
@brief TitleBlockTemplate::fontForCell
@param cell : An existing cell
@return The font that should be used to render this cell
according to its properties.
*/
QFont TitleBlockTemplate::fontForCell(const TitleBlockCell &cell) {
return(QETApp::diagramTextsFont(cell.font_size));
}
/**
@brief TitleBlockTemplate::loadFromXmlFile
Load a titleblock template from an XML file.
@param filepath : A file path to read the template from.
@return true if the reading succeeds, false otherwise.
*/
bool TitleBlockTemplate::loadFromXmlFile(const QString &filepath) {
// open the file
QFile template_file(filepath);
if (!template_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return(false);
}
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO << filepath << "opened";
#endif
// parse its content as XML
QDomDocument xml_doc;
bool xml_parsing = xml_doc.setContent(&template_file);
if (!xml_parsing) {
return(false);
}
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO << filepath << "opened and parsed";
#endif
return(loadFromXmlElement(xml_doc.documentElement()));
}
/**
@brief TitleBlockTemplate::loadFromXmlElement
@param xml_element : An XML document to read the template from.
@return true if the reading succeeds, false otherwise.
*/
bool TitleBlockTemplate::loadFromXmlElement(const QDomElement &xml_element) {
// we expect the XML element to be an <titleblocktemplate>
if (xml_element.tagName() != "titleblocktemplate") {
return(false);
}
if (!xml_element.hasAttribute("name")) {
return(false);
}
name_ = xml_element.attribute("name");
loadInformation(xml_element);
loadLogos(xml_element, true);
loadGrid(xml_element);
return(true);
}
/**
@brief TitleBlockTemplate::saveToXmlFile
Save the title block template into an XML file.
@param filepath :
The file path this title block template should be saved to.
@return true if the operation succeeds, false otherwise
*/
bool TitleBlockTemplate::saveToXmlFile(const QString &filepath) {
if (filepath.isEmpty()) return(false);
// generate the XML document
QDomDocument doc;
QDomElement e = doc.createElement("root");
bool saving = saveToXmlElement(e);
if (!saving) return(false);
doc.appendChild(e);
return(QET::writeXmlFile(doc, filepath));
}
/**
@brief TitleBlockTemplate::saveToXmlElement
Save the title block template as XML.
@param xml_element :
The XML element this title block template should be saved to.
@return true if the export succeeds, false otherwise
*/
bool TitleBlockTemplate::saveToXmlElement(QDomElement &xml_element) const
{
// we are supposed to have at least a name
if (name_.isEmpty()) return(false);
xml_element.setTagName("titleblocktemplate");
xml_element.setAttribute("name", name_);
saveInformation(xml_element);
saveLogos(xml_element);
saveGrid(xml_element);
return(true);
}
/**
@brief TitleBlockTemplate::exportCellToXml
@param cell : Cell to export
@param xml_element :
Parent XML element to be used when exporting \a cell
*/
void TitleBlockTemplate::exportCellToXml(TitleBlockCell *cell,
QDomElement &xml_element) const
{
saveCell(cell, xml_element, true);
}
/**
@brief TitleBlockTemplate::clone
@return a deep copy of the current title block template
(i.e. title block cells are duplicated too
and associated with their parent template).
*/
TitleBlockTemplate *TitleBlockTemplate::clone() const
{
TitleBlockTemplate *copy = new TitleBlockTemplate();
copy -> name_ = name_;
copy -> information_ = information_;
// this does not really duplicates pixmaps,
// only the objects that hold a key to the implicitly shared pixmaps
foreach (QString logo_key, bitmap_logos_.keys()) {
copy -> bitmap_logos_[logo_key] =
QPixmap(bitmap_logos_[logo_key]);
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO
<< "copying "
<< bitmap_logos_[logo_key] -> cacheKey()
<< "to"
<< copy -> bitmap_logos_[logo_key] -> cacheKey();
#endif
}
// we have to create new QSvgRenderer objects from the data
// (no copy constructor)
foreach (QString logo_key, vector_logos_.keys()) {
copy -> vector_logos_[logo_key] =
new QSvgRenderer(data_logos_[logo_key]);
}
copy -> data_logos_ = data_logos_;
copy -> storage_logos_ = storage_logos_;
copy -> type_logos_ = type_logos_;
copy -> rows_heights_ = rows_heights_;
copy -> columns_width_ = columns_width_;
// copy cells basically
copy -> cells_ = cells_;
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
copy -> cells_[i][j] = copy -> createCell(cells_[i][j]);
}
}
// ensure the copy has no spanner_cell attribute pointing to a cell
// from the original object
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
TitleBlockCell *current_cell = copy -> cells_[i][j];
if (TitleBlockCell *original_cell
= current_cell -> spanner_cell) {
int original_cell_row = original_cell -> num_row;
int original_cell_col = original_cell -> num_col;
TitleBlockCell *copy_cell =
copy -> cells_[original_cell_col]
[original_cell_row];
current_cell -> spanner_cell = copy_cell;
}
}
}
return(copy);
}
/**
@brief TitleBlockTemplate::loadInformation
Import text informations from a given XML title block template.
@param xml_element
*/
void TitleBlockTemplate::loadInformation(const QDomElement &xml_element) {
for (QDomNode n = xml_element.firstChild() ;
!n.isNull() ;
n = n.nextSibling()) {
if (n.isElement() && n.toElement().tagName() == "information") {
setInformation(n.toElement().text());
}
}
}
/**
@brief TitleBlockTemplate::loadLogos
Import the logos from a given XML titleblock template.
@param xml_element : An XML element representing an titleblock template.
@param reset : true to delete all previously known logos before,
false otherwise.
@return true if the reading succeeds, false otherwise.
*/
bool TitleBlockTemplate::loadLogos(const QDomElement &xml_element, bool reset) {
if (reset) {
qDeleteAll(vector_logos_.begin(), vector_logos_.end());
vector_logos_.clear();
// Note:
// QPixmap are only a key to access the implicitly shared pixmap
bitmap_logos_.clear();
data_logos_.clear();
storage_logos_.clear();
}
// we look for //logos/logo elements
for (QDomNode n = xml_element.firstChild() ;
!n.isNull() ;
n = n.nextSibling()) {
if (n.isElement() && n.toElement().tagName() == "logos") {
for (QDomNode p = n.firstChild() ;
!p.isNull() ;
p = p.nextSibling()) {
if (p.isElement() && p.toElement().tagName()
== "logo") {
loadLogo(p.toElement());
}
}
}
}
return(true);
}
/**
@brief TitleBlockTemplate::loadLogo
Import the logo from a given XML logo description.
@param xml_element :
An XML element representing a logo within an titleblock template.
@return true if the reading succeeds, false otherwise.
*/
bool TitleBlockTemplate::loadLogo(const QDomElement &xml_element) {
// we require a name
if (!xml_element.hasAttribute("name")) {
return(false);
}
QString logo_name = xml_element.attribute("name");
QString logo_type = xml_element.attribute("type", "png");
QString logo_storage = xml_element.attribute("storage", "base64");
// Both QSvgRenderer and QPixmap read their data from a QByteArray, so
// we convert the available data to that format.
QByteArray logo_data;
if (logo_storage == "xml") {
QDomNodeList svg_nodes = xml_element.elementsByTagName("svg");
if (svg_nodes.isEmpty()) {
return(false);
}
QDomElement svg_element = svg_nodes.at(0).toElement();
QTextStream xml_to_byte_array(&logo_data);
svg_element.save(xml_to_byte_array, 0);
} else if (logo_storage == "base64") {
logo_data = QByteArray::fromBase64(xml_element.text().toLatin1());
} else {
return(false);
}
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO << logo_name << logo_type << logo_storage;
#endif
addLogo(logo_name, &logo_data, logo_type, logo_storage);
return(true);
}
/**
Import the grid from a given XML titleblock template.
@param xml_element An XML element representing an titleblock template.
@return true if the reading succeeds, false otherwise.
*/
bool TitleBlockTemplate::loadGrid(const QDomElement &xml_element) {
// we parse the first available "grid" XML element
QDomElement grid_element;
for (QDomNode n = xml_element.firstChild() ;
!n.isNull() ;
n = n.nextSibling()) {
if (n.isElement() && n.toElement().tagName() == "grid") {
grid_element = n.toElement();
break;
}
}
if (!grid_element.hasAttribute("rows")
|| !grid_element.hasAttribute("cols")) {
return(false);
}
parseRows(grid_element.attribute("rows"));
parseColumns(grid_element.attribute("cols"));
initCells();
loadCells(grid_element);
applyRowColNums();
applyCellSpans();
return(true);
}
/**
@brief TitleBlockTemplate::parseRows
Parse the rows heights
@param rows_string :
A string describing the rows heights of the titleblock
*/
void TitleBlockTemplate::parseRows(const QString &rows_string) {
rows_heights_.clear();
// parse the rows attribute: we expect a serie of absolute heights
QRegularExpression row_size_format
("^([0-9]+)(?:px)?$",
QRegularExpression::CaseInsensitiveOption);
bool conv_ok;
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
QStringList rows_descriptions =
rows_string.split(QChar(';'), QString::SkipEmptyParts);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.14 or later")
#endif
QStringList rows_descriptions =
rows_string.split(QChar(';'), Qt::SkipEmptyParts);
#endif
foreach (QString rows_description, rows_descriptions) {
QRegularExpressionMatch match;
match = row_size_format.match(rows_description);
if (match.hasMatch()) {
int row_size =
match.captured(1).toInt(&conv_ok);
if (conv_ok)
rows_heights_ << row_size;
}
}
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO << "Rows heights:" << rows_heights_;
#endif
}
/**
@brief TitleBlockTemplate::parseColumns
Parse the columns widths
@param cols_string :
A string describing the columns widths of the titleblock
*/
void TitleBlockTemplate::parseColumns(const QString &cols_string) {
columns_width_.clear();
// parse the cols attribute: we expect a serie of absolute or relative widths
QRegularExpression abs_col_size_format,rel_col_size_format;
abs_col_size_format.setPattern("^([0-9]+)(?:px)?$");
abs_col_size_format.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
rel_col_size_format.setPattern("^([rt])([0-9]+)%$");
rel_col_size_format.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
bool conv_ok;
qDebug() <<"is QRegularExpression ok?";
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
QStringList cols_descriptions =
cols_string.split(QChar(';'), QString::SkipEmptyParts);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.14 or later")
#endif
QStringList cols_descriptions =
cols_string.split(QChar(';'), Qt::SkipEmptyParts);
#endif
foreach (QString cols_description, cols_descriptions) {
QRegularExpressionMatch match_abc,match_rel;
match_abc = abs_col_size_format.match(cols_description);
match_rel = rel_col_size_format.match(cols_description);
if (match_abc.hasMatch()) {
int col_size = match_abc.captured(1).toInt(&conv_ok);
if (conv_ok)
columns_width_ << TitleBlockDimension(
col_size,
QET::Absolute);
} else if (match_rel.hasMatch()) {
int col_size = match_rel.captured(2).toInt(&conv_ok);
QET::TitleBlockColumnLength col_type = match_rel.captured(1)
== "t"
? QET::RelativeToTotalLength
: QET::RelativeToRemainingLength;
if (conv_ok)
columns_width_ << TitleBlockDimension(
col_size,
col_type );
}
}
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
foreach (TitleBlockColDimension icd, columns_width_) {
qDebug() << Q_FUNC_INFO
<< QString("%1 [%2]").arg(icd.value).arg(
QET::titleBlockColumnLengthToString(icd.type));
}
#endif
}
/**
@brief TitleBlockTemplate::loadCells
Analyze an XML element, looking for grid cells. The grid cells are checked
and stored in this object.
@param xml_element XML element to analyze
@return systematically true
*/
bool TitleBlockTemplate::loadCells(const QDomElement &xml_element) {
// we are interested by the "logo" and "field" elements
QDomElement grid_element;
for (QDomNode n = xml_element.firstChild() ;
!n.isNull() ;
n = n.nextSibling()) {
if (!n.isElement()) continue;
QDomElement cell_element = n.toElement();
if (cell_element.tagName() == "field"
|| cell_element.tagName() == "logo") {
loadCell(cell_element);
}
}
return(true);
}
/**
@brief TitleBlockTemplate::loadCell
Load a cell into this template.
@param cell_element :
XML element describing a cell within a title block template
*/
void TitleBlockTemplate::loadCell(const QDomElement &cell_element) {
TitleBlockCell *loaded_cell;
if (!checkCell(cell_element, &loaded_cell)) return;
loaded_cell -> loadContentFromXml(cell_element);
}
/**
@brief TitleBlockTemplate::saveInformation
Export this template's extra information.
@param xml_element :
XML element under which extra informations will be attached
*/
void TitleBlockTemplate::saveInformation(QDomElement &xml_element) const
{
QDomNode information_text_node =
xml_element.ownerDocument().createTextNode(information());
QDomElement information_element =
xml_element.ownerDocument().createElement("information");
information_element.appendChild(information_text_node);
xml_element.appendChild(information_element);
}
/**
@brief TitleBlockTemplate::saveLogos
Export this template's logos as XML
@param xml_element :
XML Element under which the \<logos\> element will be attached
*/
void TitleBlockTemplate::saveLogos(QDomElement &xml_element) const
{
QDomElement logos_element =
xml_element.ownerDocument().createElement("logos");
foreach(QString logo_name, type_logos_.keys()) {
QDomElement logo_element =
xml_element.ownerDocument().createElement("logo");
saveLogo(logo_name, logo_element);
logos_element.appendChild(logo_element);
}
xml_element.appendChild(logos_element);
}
/**
@brief TitleBlockTemplate::saveLogo
Export a specific logo as XML
@param logo_name : Name of the logo to be exported
@param xml_element : XML element in which the logo will be exported
*/
void TitleBlockTemplate::saveLogo(const QString &logo_name,
QDomElement &xml_element) const
{
if (!type_logos_.contains(logo_name)) return;
xml_element.setAttribute("name", logo_name);
xml_element.setAttribute("type", type_logos_[logo_name]);
xml_element.setAttribute("storage", storage_logos_[logo_name]);
if (storage_logos_[logo_name] == "xml"
&& type_logos_[logo_name] == "svg") {
QDomDocument svg_logo;
svg_logo.setContent(data_logos_[logo_name]);
QDomNode svg_logo_element =
xml_element.ownerDocument().importNode(
svg_logo.documentElement(), true);
xml_element.appendChild(svg_logo_element.toElement());
} else if (storage_logos_[logo_name] == "base64") {
QDomText base64_logo =
xml_element.ownerDocument().createTextNode(
data_logos_[logo_name].toBase64());
xml_element.appendChild(base64_logo);
}
}
/**
@brief TitleBlockTemplate::saveGrid
Export this template's cells grid as XML
@param xml_element :
XML element under which the \<grid\> element will be attached
*/
void TitleBlockTemplate::saveGrid(QDomElement &xml_element) const
{
QDomElement grid_element =
xml_element.ownerDocument().createElement("grid");
QString rows_attr, cols_attr;
foreach(int row_height, rows_heights_)
rows_attr += QString("%1;").arg(row_height);
foreach(TitleBlockDimension col_width, columns_width_)
cols_attr += col_width.toShortString();
grid_element.setAttribute("rows", rows_attr);
grid_element.setAttribute("cols", cols_attr);
saveCells(grid_element);
xml_element.appendChild(grid_element);
}
/**
@brief TitleBlockTemplate::saveCells
Export this template's cells as XML
(without the grid-related information, usch as rows and cols)
@param xml_element :
XML element under which the \<cell\> elements will be attached
*/
void TitleBlockTemplate::saveCells(QDomElement &xml_element) const
{
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
if (cells_[i][j] -> cell_type
!= TitleBlockCell::EmptyCell) {
saveCell(cells_[i][j], xml_element);
}
}
}
}
/**
@brief TitleBlockTemplate::saveCell
Export a specific cell as XML
@param cell : Cell to be exported as XML
@param xml_element :
XML element under which the \<cell\> element will be attached
@param save_empty :
If true, the cell will be saved even if it is an empty one
*/
void TitleBlockTemplate::saveCell(TitleBlockCell *cell,
QDomElement &xml_element,
bool save_empty) const
{
if (!cell) return;
if (cell -> spanner_cell) return;
if (!save_empty && cell -> cell_type == TitleBlockCell::EmptyCell)
return;
QDomElement cell_elmt =
xml_element.ownerDocument().createElement("cell");
xml_element.appendChild(cell_elmt);
// save information dependent from this template
cell_elmt.setAttribute("row", cell -> num_row);
cell_elmt.setAttribute("col", cell -> num_col);
if (cell -> row_span) cell_elmt.setAttribute("rowspan",
cell -> row_span);
if (cell -> col_span) cell_elmt.setAttribute("colspan",
cell -> col_span);
// save other information
cell -> saveContentToXml(cell_elmt);
}
/**
@brief TitleBlockTemplate::checkCell
Load the essential attributes of a cell:
row and column indices and spans.
@param xml_element :
XML element representing a cell, i.e. either an titleblock
logo or an titleblock field.
@param titleblock_cell_ptr :
Pointer to a TitleBlockCell object pointer - if non-zero and if
this method returns true, will be filled with the created TitleBlockCell
@return TRUE if the cell appears to be ok, FALSE otherwise
*/
bool TitleBlockTemplate::checkCell(const QDomElement &xml_element,
TitleBlockCell **titleblock_cell_ptr) {
int col_count = columns_width_.count(),
row_count = rows_heights_.count();
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO << "begin" << row_count << col_count;
#endif
int row_num, col_num, row_span, col_span;
row_num = col_num = -1;
row_span = col_span = 0;
// parse the row and col attributes
if (!QET::attributeIsAnInteger(xml_element, "row", &row_num)
|| row_num < 0
|| row_num >= row_count) {
return(false);
}
if (!QET::attributeIsAnInteger(xml_element, "col", &col_num)
|| col_num < 0
|| col_num >= col_count) {
return(false);
}
// check whether the target cell can be used or not
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO << "cell access" << col_num << row_num;
#endif
TitleBlockCell *cell_ptr = cells_[col_num][row_num];
if (cell_ptr -> cell_type != TitleBlockCell::EmptyCell
|| cell_ptr -> spanner_cell) {
return(false);
}
// ensure the num_row and num_col attributes are alright
cell_ptr -> num_row = row_num;
cell_ptr -> num_col = col_num;
// parse the rowspan and colspan attributes
if (QET::attributeIsAnInteger(xml_element, "rowspan", &row_span)
&& row_span > 0) {
cell_ptr -> row_span = row_span;
}
if (QET::attributeIsAnInteger(xml_element, "colspan", &col_span)
&& col_span > 0) {
cell_ptr -> col_span = col_span;
}
// these attributes are stored "as is" -- whether they can be applied
// directly or must be restricted will be checked later
if (titleblock_cell_ptr) *titleblock_cell_ptr = cell_ptr;
return(true);
}
/**
@brief TitleBlockTemplate::initCells
Initialize the internal cells grid with the row and column counts.
Note that this method does nothing if one of the internal lists
columns_width_ and rows_heights_ is empty.
*/
void TitleBlockTemplate::initCells()
{
if (columns_width_.count() < 1 || rows_heights_.count() < 1) return;
cells_.clear();
qDeleteAll(registered_cells_);
registered_cells_.clear();
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
cells_ << createColumn();
}
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO << toString();
#endif
}
/**
@brief TitleBlockTemplate::name
@return the name of this template
*/
QString TitleBlockTemplate::name() const
{
return(name_);
}
/**
@brief TitleBlockTemplate::information
@return the information field attached to this template
*/
QString TitleBlockTemplate::information() const
{
return(information_);
}
/**
@brief TitleBlockTemplate::setInformation
@param info information to be attached to this template
*/
void TitleBlockTemplate::setInformation(const QString &info) {
information_ = info;
}
/**
@brief TitleBlockTemplate::rowDimension
@param i : row index
@return the height of the row at index i
*/
int TitleBlockTemplate::rowDimension(int i) {
int index = (i == -1) ? rows_heights_.count() - 1 : i;
if (index >= 0 && index < rows_heights_.count()) {
return(rows_heights_.at(index));
}
return(-1);
}
/**
@brief TitleBlockTemplate::setRowDimension
Set the height of a row
@param i : row index
@param dimension : New height of the row at index i
*/
void TitleBlockTemplate::setRowDimension(int i,
const TitleBlockDimension &dimension) {
int index = (i == -1) ? rows_heights_.count() - 1 : i;
if (index >= 0 || index < rows_heights_.count()) {
rows_heights_[index] = dimension.value;
}
}
/**
@brief TitleBlockTemplate::columnDimension
@param i : column index
@return the width of the column at index i
*/
TitleBlockDimension TitleBlockTemplate::columnDimension(int i) {
int index = (i == -1) ? columns_width_.count() - 1 : i;
if (index >= 0 && index < columns_width_.count()) {
return(columns_width_.at(index));
}
return(TitleBlockDimension(-1));
}
/**
@brief TitleBlockTemplate::setColumnDimension
Set the width of a column
@param i : column index
@param dimension : New width of the column at index i
*/
void TitleBlockTemplate::setColumnDimension(
int i,
const TitleBlockDimension &dimension) {
int index = (i == -1) ? columns_width_.count() - 1 : i;
if (index >= 0 || index < columns_width_.count()) {
columns_width_[index] = dimension;
}
}
/**
@brief TitleBlockTemplate::columnsCount
@return the number of columns in this template
*/
int TitleBlockTemplate::columnsCount() const
{
return(columns_width_.count());
}
/**
@brief TitleBlockTemplate::rowsCount
@return the number of rows in this template
*/
int TitleBlockTemplate::rowsCount() const
{
return(rows_heights_.count());
}
/**
@brief TitleBlockTemplate::columnsWidth
@param total_width : The total width of the titleblock to render
@return the list of the columns widths for this rendering
*/
QList<int> TitleBlockTemplate::columnsWidth(int total_width) const
{
if (total_width < 0) return(QList<int>());
// we first iter to determine the absolute and total-width-related widths
QVector<int> final_widths(columns_width_.count());
int abs_widths_sum = 0, rel_widths_sum = 0;
QList<int> relative_columns;
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
TitleBlockDimension icd = columns_width_.at(i);
if (icd.type == QET::Absolute) {
abs_widths_sum += icd.value;
final_widths[i] = icd.value;
} else if (icd.type == QET::RelativeToTotalLength) {
int abs_value = qRound(total_width * icd.value / 100.0);
relative_columns << i;
abs_widths_sum += abs_value;
final_widths[i] = abs_value;
}
}
// we can now deduce the remaining width
int remaining_width = total_width - abs_widths_sum;
// we do a second iteration to build the final widths list
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
TitleBlockDimension icd = columns_width_.at(i);
if (icd.type == QET::RelativeToRemainingLength) {
final_widths[i] =
qRound(remaining_width * icd.value / 100.0);
relative_columns << i;
rel_widths_sum += final_widths[i];
}
}
// Have we computed widths from percentage for relative columns?
if (relative_columns.count()) {
// Due to the rounding process,
// we may get a slight difference between the
// sum of the columns widths and the total width.
int difference = total_width - abs_widths_sum - rel_widths_sum;
if (difference) {
// We consider we should not attempt to compensate
// this difference if it is under
// relative_columns_count * 0.5
// (which means that each percent-based
// columns can "bring" up to 0.5px of difference).
qreal max_acceptable_difference =
relative_columns.count() * 0.5;
int share = difference > 0 ? 1 : -1;
if (qAbs(difference) <= max_acceptable_difference) {
while (difference) {
foreach (int index, relative_columns) {
final_widths[index] += share;
difference -= share;
if (!difference) break;
}
}
}
}
}
return(final_widths.toList());
}
/**
@brief TitleBlockTemplate::rowsHeights
@return the heights of all the rows in this template
*/
QList<int> TitleBlockTemplate::rowsHeights() const
{
return(rows_heights_);
}
/**
@brief TitleBlockTemplate::columnTypeCount
@param type : a column type
@return the count of \a type columns
*/
int TitleBlockTemplate::columnTypeCount(QET::TitleBlockColumnLength type) {
int count = 0;
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
if (columns_width_.at(i).type == type) ++ count;
}
return(count);
}
/**
@brief TitleBlockTemplate::columnTypeTotal
@param type : a column type
@return the sum of values attached to \a type columns
*/
int TitleBlockTemplate::columnTypeTotal(QET::TitleBlockColumnLength type) {
int total = 0;
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
if (columns_width_.at(i).type == type) {
total += columns_width_.at(i).value;
}
}
return(total);
}
/**
@return the minimum width for this template
*/
int TitleBlockTemplate::minimumWidth()
{
// Abbreviations: ABS: absolute, RTT: relative to total, RTR:
// relative to remaining,
// TOT: total diagram/TBT width (variable).
// Minimum size may be enforced by ABS and RTT widths:
// TOT >= ((sum(REL)/100)*TOT)+sum(ABS)
// => (1 - (sum(REL)/100))TOT >= sum(ABS)
// => TOT >= sum(ABS) / (1 - (sum(REL)/100))
// => TOT >= sum(ABS) / ((100 - sum(REL))/100))
return(
qRound(
columnTypeTotal(QET::Absolute)
/
((100.0 - columnTypeTotal(QET::RelativeToTotalLength))
/ 100.0)
)
);
}
/**
@brief TitleBlockTemplate::maximumWidth
@return the maximum width for this template,
or -1 if it does not have any.
*/
int TitleBlockTemplate::maximumWidth()
{
if (columnTypeCount(QET::Absolute) == columns_width_.count()) {
// The template is composed of absolute widths only,
// therefore it may not extend beyond their sum.
return(columnTypeTotal(QET::Absolute));
}
return(-1);
}
/**
@brief TitleBlockTemplate::width
@param total_width : The total width initially planned for the rendering
@return the total effective width of this template
*/
int TitleBlockTemplate::width(int total_width) {
int width = 0;
foreach (int col_width, columnsWidth(total_width)) {
width += col_width;
}
return(width);
}
/**
@brief TitleBlockTemplate::height
@return the total height of this template
*/
int TitleBlockTemplate::height() const
{
int height = 0;
foreach(int row_height, rows_heights_) {
height += row_height;
}
return(height);
}
/**
@brief TitleBlockTemplate::moveRow
Move a row within this template.
@param from : Index of the moved row
@param to : Arrival index of the moved row
@return true on row Changed or false
*/
bool TitleBlockTemplate::moveRow(int from, int to) {
// checks from and to
if (from >= rows_heights_.count()) return(false);
if (to >= rows_heights_.count()) return(false);
for (int j = 0 ; j < columns_width_.count() ; ++ j) {
cells_[j].move(from, to);
}
rows_heights_.move(from, to);
rowColsChanged();
return(true);
}
/**
@brief TitleBlockTemplate::addRow
Add a new 25px-wide row at the provided index.
@param i : Index of the added row, -1 meaning "last position"
*/
void TitleBlockTemplate::addRow(int i) {
insertRow(25, createRow(), i);
}
/**
@brief TitleBlockTemplate::insertRow
@param dimension :
dimension Size of the row to be added (always absolute, in pixels)
@param row :
column Row to be added
@param i :
Index of the column after insertion, -1 meaning "last position"
@return true
*/
bool TitleBlockTemplate::insertRow(int dimension,
const QList<TitleBlockCell *> &row,
int i) {
int index = (i == -1) ? rows_heights_.count() : i;
for (int j = 0 ; j < columns_width_.count() ; ++ j) {
cells_[j].insert(index, row[j]);
}
rows_heights_.insert(index, dimension);
rowColsChanged();
return(true);
}
/**
@brief TitleBlockTemplate::takeRow
Removes the row at index i
@param i : Index of the column to be removed
@return the removed column
*/
QList<TitleBlockCell *> TitleBlockTemplate::takeRow(int i) {
QList<TitleBlockCell *> row;
int index = (i == -1) ? rows_heights_.count() - 1 : i;
if (index < 0 || index >= rows_heights_.count()) return(row);
for (int j = 0 ; j < columns_width_.count() ; ++ j) {
row << cells_[j].takeAt(index);
}
rows_heights_.removeAt(index);
rowColsChanged();
return(row);
}
/**
@brief TitleBlockTemplate::createRow
@return a new row that fits the current grid
*/
QList<TitleBlockCell *> TitleBlockTemplate::createRow()
{
return(createCellsList(columns_width_.count()));
}
/**
@brief TitleBlockTemplate::moveColumn
Move the column at index "from" to index "to".
@param from : Source index of the moved column
@param to : Target index of the moved column
@return true or false
*/
bool TitleBlockTemplate::moveColumn(int from, int to) {
// checks from and to
if (from >= columns_width_.count()) return(false);
if (to >= columns_width_.count()) return(false);
cells_.move(from, to);
columns_width_.move(from, to);
rowColsChanged();
return(true);
}
/**
@brief TitleBlockTemplate::addColumn
Add a new 50px-wide column at the provided index.
@param i : Index of the added column, -1 meaning "last position"
*/
void TitleBlockTemplate::addColumn(int i) {
insertColumn(TitleBlockDimension(50, QET::Absolute), createColumn(), i);
}
/**
@brief TitleBlockTemplate::insertColumn
@param dimension : Size of the column to be added
@param column : Column to be added
@param i :
Index of the column after insertion, -1 meaning "last position"
@return true
*/
bool TitleBlockTemplate::insertColumn(const TitleBlockDimension &dimension,
const QList<TitleBlockCell *> &column,
int i) {
int index = (i == -1) ? columns_width_.count() : i;
cells_.insert(index, column);
columns_width_.insert(index, dimension);
rowColsChanged();
return(true);
}
/**
@brief TitleBlockTemplate::takeColumn
Removes the column at index i
@param i : Index of the column to be removed
@return the removed column
*/
QList<TitleBlockCell *> TitleBlockTemplate::takeColumn(int i) {
int index = (i == -1) ? columns_width_.count() - 1 : i;
if (index < 0 || index >= columns_width_.count()) {
return(QList<TitleBlockCell *>());
}
QList<TitleBlockCell *> column = cells_.takeAt(i);
columns_width_.removeAt(i);
rowColsChanged();
return(column);
}
/**
@brief TitleBlockTemplate::createColumn
@return a new column that fits the current grid
*/
QList<TitleBlockCell *> TitleBlockTemplate::createColumn()
{
return(createCellsList(rows_heights_.count()));
}
/**
@brief TitleBlockTemplate::cell
@param row : A row number (starting from 0)
@param col : A column number (starting from 0)
@return the cell located at (row, col)
*/
TitleBlockCell *TitleBlockTemplate::cell(int row, int col) const
{
if (row >= rows_heights_.count()) return(nullptr);
if (col >= columns_width_.count()) return(nullptr);
return(cells_[col][row]);
}
/**
@brief TitleBlockTemplate::spannedCells
@param given_cell :
cell A cell belonging to this title block template
@param ignore_span_state :
(Optional, defaults to false) If true, will consider
cells theoretically spanned (i.e. row_span and col_span attributes).
Otherwise, will take span_state attribute into account.
@return the set of cells spanned by the provided cell
Note the returned set does not include the spanning, provided cell
*/
QSet<TitleBlockCell *> TitleBlockTemplate::spannedCells(
const TitleBlockCell *given_cell,
bool ignore_span_state) const
{
QSet<TitleBlockCell *> set;
if (!given_cell) return(set);
if (!ignore_span_state && given_cell -> span_state
== TitleBlockCell::Disabled)
return(set);
int final_row_span = ignore_span_state
? given_cell -> row_span
: given_cell -> applied_row_span;
int final_col_span = ignore_span_state
? given_cell -> col_span
: given_cell -> applied_col_span;
if (!final_row_span && !final_col_span) return(set);
for (int i = given_cell -> num_col ;
i <= given_cell -> num_col + final_col_span ;
++ i) {
for (int j = given_cell -> num_row ;
j <= given_cell -> num_row + final_row_span ;
++ j) {
if (i == given_cell -> num_col && j
== given_cell -> num_row)
continue;
TitleBlockCell *current_cell = cell(j, i);
if (current_cell) set << current_cell;
}
}
return(set);
}
/**
@brief TitleBlockTemplate::getAllSpans
Export the span parameters of all cell in the current grid.
@return
*/
QHash<TitleBlockCell *, QPair<int, int> > TitleBlockTemplate::getAllSpans(
) const
{
QHash<TitleBlockCell *, QPair<int, int> > spans;
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
spans.insert(
cells_[i][j],
QPair<int, int>(
cells_[i][j] -> row_span,
cells_[i][j] -> col_span
)
);
}
}
return(spans);
}
/**
@brief TitleBlockTemplate::setAllSpans
Restore a set of span parameters.
@param spans :
*/
void TitleBlockTemplate::setAllSpans(const QHash<TitleBlockCell *,
QPair<int, int> > &spans) {
foreach (TitleBlockCell *cell, spans.keys()) {
cell -> row_span = spans[cell].first;
cell -> col_span = spans[cell].second;
}
}
/**
@brief TitleBlockTemplate::addLogo
@param logo_name :
Logo name to be added / replaced
@param logo_data :
Logo data
@param logo_type :
@param logo_storage :
@return true or false
*/
bool TitleBlockTemplate::addLogo(const QString &logo_name,
QByteArray *logo_data,
const QString &logo_type,
const QString &logo_storage) {
if (data_logos_.contains(logo_name)) {
// we are replacing the logo
removeLogo(logo_name);
}
// we can now create our image object from the byte array
if (logo_type == "svg") {
// SVG format is handled by the QSvgRenderer class
QSvgRenderer *svg = new QSvgRenderer();
if (!svg -> load(*logo_data)) {
return(false);
}
vector_logos_.insert(logo_name, svg);
// we also memorize the way to store them in the final XML output
QString final_logo_storage = logo_storage;
if (logo_storage != "xml" && logo_storage != "base64") {
final_logo_storage = "xml";
}
storage_logos_.insert(logo_name, logo_storage);
} else {
// bitmap formats are handled by the QPixmap class
QPixmap logo_pixmap;
logo_pixmap.loadFromData(*logo_data);
if (!logo_pixmap.width() || !logo_pixmap.height()) {
return(false);
}
bitmap_logos_.insert(logo_name, logo_pixmap);
// bitmap logos can only be stored using a base64 encoding
storage_logos_.insert(logo_name, "base64");
}
// we systematically store the raw data
data_logos_.insert(logo_name, *logo_data);
type_logos_.insert(logo_name, logo_type);
return(true);
}
/**
@brief TitleBlockTemplate::addLogoFromFile
@param filepath :
Path of the image file to add as a logo
@param name :
Name used to store the logo; if none is provided, the
basename of the first argument is used.
@return true if the logo could be deleted, false otherwise
*/
bool TitleBlockTemplate::addLogoFromFile(const QString &filepath,
const QString &name) {
QFileInfo filepath_info(filepath);
QString filename = name.isEmpty() ? filepath_info.fileName() : name;
QString filetype = filepath_info.suffix();
// we read the provided logo
QFile logo_file(filepath);
if (!logo_file.open(QIODevice::ReadOnly)) return(false);
QByteArray file_content = logo_file.readAll();
// first, we try to add it as an SVG image
if (addLogo(filename, &file_content, "svg", "xml")) return(true);
// we then try to add it as a bitmap image
return addLogo(filename,
&file_content,
filepath_info.suffix(),
"base64");
}
/**
@brief TitleBlockTemplate::saveLogoToFile
@param logo_name :
Name used to store the logo
@param filepath :
Path the logo will be saved as
@return true if the logo could be exported, false otherwise
*/
bool TitleBlockTemplate::saveLogoToFile(const QString &logo_name,
const QString &filepath) {
if (!data_logos_.contains(logo_name)) {
return(false);
}
QFile target_file(filepath);
if (!target_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return(false);
}
target_file.write(data_logos_[logo_name]);
target_file.close();
return(true);
}
/**
@brief TitleBlockTemplate::removeLogo
@param logo_name : Name of the logo to remove
@return true if the logo could be deleted, false otherwise
*/
bool TitleBlockTemplate::removeLogo(const QString &logo_name) {
if (!data_logos_.contains(logo_name)) {
return(false);
}
#if TODO_LIST
#pragma message("@TODO check existing cells using this logo.")
#endif
/// TODO check existing cells using this logo.
if (vector_logos_.contains(logo_name)) {
delete vector_logos_.take(logo_name);
}
if (bitmap_logos_.contains(logo_name)) {
bitmap_logos_.remove(logo_name);
}
data_logos_.remove(logo_name);
storage_logos_.remove(logo_name);
return(true);
}
/**
@brief TitleBlockTemplate::renameLogo
Rename the \a logo_name logo to \a new_name
@param logo_name : Name of the logo to be renamed
@param new_name : New name of the renamed logo
@return
*/
bool TitleBlockTemplate::renameLogo(const QString &logo_name,
const QString &new_name) {
if (!data_logos_.contains(logo_name)
|| data_logos_.contains(new_name)) {
return(false);
}
#if TODO_LIST
#pragma message("@TODO check existing cells using this logo.")
#endif
/// TODO check existing cells using this logo.
if (vector_logos_.contains(logo_name)) {
vector_logos_.insert(new_name, vector_logos_.take(logo_name));
}
if (bitmap_logos_.contains(logo_name)) {
bitmap_logos_.insert(new_name, bitmap_logos_.take(logo_name));
}
data_logos_.insert(new_name, data_logos_.take(logo_name));
storage_logos_.insert(new_name, storage_logos_.take(logo_name));
return(true);
}
/**
@brief TitleBlockTemplate::setLogoStorage
Set the kind of storage for the \a logo_name logo.
@param logo_name :
Name of the logo which kind of storage is to be changed
@param storage :
The kind of storage to use for the logo, e.g. "xml" or "base64".
*/
void TitleBlockTemplate::setLogoStorage(const QString &logo_name,
const QString &storage) {
if (storage_logos_.contains(logo_name)) {
storage_logos_[logo_name] = storage;
}
}
/**
@brief TitleBlockTemplate::logos
@return The names of logos embedded within this title block template.
*/
QList<QString> TitleBlockTemplate::logos() const
{
return(data_logos_.keys());
}
/**
@brief TitleBlockTemplate::logoType
@param logo_name :
Name of a logo embedded within this title block template.
@return the kind of storage used for the required logo,
or a null QString if no such logo was found in this template.
*/
QString TitleBlockTemplate::logoType(const QString &logo_name) const
{
if (type_logos_.contains(logo_name)) {
return type_logos_[logo_name];
}
return(QString());
}
/**
@brief TitleBlockTemplate::vectorLogo
@param logo_name :
Name of a vector logo embedded within this title block template.
@return the rendering object for the required vector logo,
or 0 if no such vector logo was found in this template.
*/
QSvgRenderer *TitleBlockTemplate::vectorLogo(const QString &logo_name) const
{
if (vector_logos_.contains(logo_name)) {
return vector_logos_[logo_name];
}
return(nullptr);
}
/**
@brief TitleBlockTemplate::bitmapLogo
@param logo_name :
Name of a logo embedded within this title block template.
@return the pixmap for the required bitmap logo, or a null pixmap if no
such bitmap logo was found in this template.
*/
QPixmap TitleBlockTemplate::bitmapLogo(const QString &logo_name) const
{
if (bitmap_logos_.contains(logo_name)) {
return bitmap_logos_[logo_name];
}
return(QPixmap());
}
/**
@brief TitleBlockTemplate::render
Render the titleblock.
@param painter :
Painter to use to render the titleblock
@param diagram_context :
Diagram context to use to generate the titleblock strings
@param titleblock_width :
Width of the titleblock to render
*/
void TitleBlockTemplate::render(QPainter &painter,
const DiagramContext &diagram_context,
int titleblock_width) const
{
QList<int> widths = columnsWidth(titleblock_width);
int titleblock_height = height();
painter.save();
//Setup the QPainter
QPen pen(Qt::black);
painter.setPen(pen);
// draw the titleblock border
painter.drawRect(QRect(0, 0, titleblock_width, titleblock_height));
// run through each individual cell
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
if (cells_[i][j] -> spanner_cell
|| cells_[i][j] -> cell_type
== TitleBlockCell::EmptyCell)
continue;
// calculate the border rect of the current cell
int x = lengthRange(0, cells_[i][j] -> num_col, widths);
int y = lengthRange(0,
cells_[i][j] -> num_row,
rows_heights_);
int row_span = 0, col_span = 0;
if (cells_[i][j] -> span_state
!= TitleBlockCell::Disabled) {
row_span = cells_[i][j] -> applied_row_span;
col_span = cells_[i][j] -> applied_col_span;
}
int w = lengthRange(cells_[i][j] -> num_col,
cells_[i][j] -> num_col + 1 + col_span,
widths);
int h = lengthRange(cells_[i][j] -> num_row,
cells_[i][j] -> num_row + 1 + row_span,
rows_heights_);
QRect cell_rect(x, y, w, h);
renderCell(painter, *cells_[i][j],
diagram_context,
cell_rect);
}
}
painter.restore();
}
/**
@brief TitleBlockTemplate::renderDxf
Render the titleblock in DXF.
@param title_block_rect :
@param diagram_context :
Diagram context to use to generate the titleblock strings
@param titleblock_width :
Width of the titleblock to render
@param file_path :
@param color :
*/
void TitleBlockTemplate::renderDxf(QRectF &title_block_rect,
const DiagramContext &diagram_context,
int titleblock_width,
QString &file_path,
int color) const
{
QList<int> widths = columnsWidth(titleblock_width);
// draw the titleblock border
double xCoord = title_block_rect.topLeft().x()*Createdxf::xScale;
double yCoord =
Createdxf::sheetHeight
- title_block_rect.bottomLeft().y()
*Createdxf::yScale;
double recWidth = title_block_rect.width() * Createdxf::xScale;
double recHeight = title_block_rect.height() * Createdxf::yScale;
Createdxf::drawRectangle(file_path,
xCoord,
yCoord,
recWidth,
recHeight,
color);
// run through each individual cell
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
if (cells_[i][j] -> spanner_cell
|| cells_[i][j] -> cell_type
== TitleBlockCell::EmptyCell)
continue;
// calculate the border rect of the current cell
double x = lengthRange(0, cells_[i][j] -> num_col,
widths);
double y = lengthRange(0, cells_[i][j] -> num_row,
rows_heights_);
int row_span = 0, col_span = 0;
if (cells_[i][j] -> span_state
!= TitleBlockCell::Disabled) {
row_span = cells_[i][j] -> applied_row_span;
col_span = cells_[i][j] -> applied_col_span;
}
double w = lengthRange(cells_[i][j] -> num_col,
cells_[i][j] -> num_col + 1 + col_span,
widths);
double h = lengthRange(cells_[i][j] -> num_row,
cells_[i][j] -> num_row + 1 + row_span,
rows_heights_);
x = xCoord + x*Createdxf::xScale;
h *= Createdxf::yScale;
y = yCoord + recHeight - h - y*Createdxf::yScale;
w *= Createdxf::xScale;
Createdxf::drawRectangle(file_path, x, y, w, h, color);
if (cells_[i][j] -> type() == TitleBlockCell::TextCell)
{
QString final_text =
finalTextForCell(*cells_[i][j],
diagram_context);
renderTextCellDxf(file_path,
final_text,
*cells_[i][j],
x,
y,
w,
h,
color);
}
}
}
}
/**
@brief TitleBlockTemplate::renderCell
Render a titleblock cell.
@param painter :
Painter to use to render the titleblock
@param cell :
@param diagram_context :
Diagram context to use to generate the titleblock strings
@param cell_rect :
Rectangle the cell must be rendered into.
*/
void TitleBlockTemplate::renderCell(QPainter &painter,
const TitleBlockCell &cell,
const DiagramContext &diagram_context,
const QRect &cell_rect) const
{
// draw the border rect of the current cell
QPen pen(QBrush(), 1, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
pen.setColor(Qt::black);
painter.setPen(pen);
painter.drawRect(cell_rect);
painter.save();
// render the inner content of the current cell
if (cell.type() == TitleBlockCell::LogoCell) {
if (!cell.logo_reference.isEmpty()) {
/* the current cell appears to be a logo
* - we first look for the logo reference
* in our vector logos list,
* since they offer a potentially better
* (or, at least, not resolution-limited) rendering
*/
if (vector_logos_.contains(cell.logo_reference)) {
vector_logos_[cell.logo_reference] -> render(
&painter,
cell_rect);
} else if (bitmap_logos_.contains(cell.logo_reference)) {
painter.drawPixmap(cell_rect,
bitmap_logos_[cell.logo_reference]);
}
}
} else if (cell.type() == TitleBlockCell::TextCell) {
QString final_text = finalTextForCell(cell, diagram_context);
renderTextCell(painter, final_text, cell, cell_rect);
}
painter.restore();
// draw again the border rect of the current cell, without the brush this time
painter.setBrush(Qt::NoBrush);
painter.drawRect(cell_rect);
}
/**
@brief TitleBlockTemplate::finalTextForCell
@param cell :
A cell from this template
@param diagram_context :
Diagram context to use to generate the final text for the given cell
@return the final text that has to be drawn in the given cell
*/
QString TitleBlockTemplate::finalTextForCell(
const TitleBlockCell &cell,
const DiagramContext &diagram_context) const
{
QString cell_text = cell.value.name();
QString cell_label = cell.label.name();
cell_text = interpreteVariables(cell_text, diagram_context);
if (cell.display_label && !cell.label.isEmpty()) {
cell_label = interpreteVariables(cell_label, diagram_context);
cell_text = QString(tr(" %1 : %2", "titleblock content - please let the blank space at the beginning")).arg(cell_label).arg(cell_text);
} else {
cell_text = QString(tr(" %1")).arg(cell_text);
}
return(cell_text);
}
/**
@brief TitleBlockTemplate::interpreteVariables
@param string :
A text containing 0 to n variables, e.g. "%var" or "%{var}"
@param diagram_context :
Diagram context to use to interprete variables
@return the provided string with variables replaced by the values
from the diagram context
*/
QString TitleBlockTemplate::interpreteVariables(
const QString &string,
const DiagramContext &diagram_context) const
{
QString interpreted_string = string;
foreach (QString key,
diagram_context.keys(DiagramContext::DecreasingLength)) {
interpreted_string.replace("%{" % key % "}",
diagram_context[key].toString());
interpreted_string.replace("%" % key,
diagram_context[key].toString());
}
return(interpreted_string);
}
/**
@brief TitleBlockTemplate::listOfVariables
Get list of variables
@return The list of string with variables
*/
QStringList TitleBlockTemplate::listOfVariables()
{
QStringList list;
// Match every "%{name}" placeholder. The bare "%name" form can't be
// extracted reliably without the variable list, and templates use the
// braced form, so only that is collected here.
static const QRegularExpression rx(QStringLiteral("%\\{([^}]+)\\}"));
// run through each individual cell
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
if (cells_[i][j] -> spanner_cell
|| cells_[i][j] -> cell_type
== TitleBlockCell::EmptyCell)
continue;
const QString cell_value = cells_[i][j] -> value.name();
auto it = rx.globalMatch(cell_value);
while (it.hasNext()) {
const QString name = it.next().captured(1);
if (!name.isEmpty() && !list.contains(name))
list << name;
}
}
}
return list;
}
/**
@brief TitleBlockTemplate::renderTextCell
This method uses a \a painter to render the \a text of a \a cell
into the \a cell_rect rectangle.
The alignment, font_size and other cell parameters are taken into account
when rendering.
@param painter :
QPainter used to render the text
@param text :
Text to render
@param cell :
Cell the rendered text is rattached to
@param cell_rect :
Rectangle delimiting the cell area
*/
void TitleBlockTemplate::renderTextCell(QPainter &painter,
const QString &text,
const TitleBlockCell &cell,
const QRectF &cell_rect) const
{
if (text.isEmpty()) return;
QFont text_font = TitleBlockTemplate::fontForCell(cell);
painter.setFont(text_font);
if (cell.hadjust) {
QFontMetricsF font_metrics(text_font);
QRectF font_rect = font_metrics.boundingRect(
QRect(-10000, -10000, 10000, 10000),
cell.alignment,
text);
if (font_rect.width() > cell_rect.width()) {
qreal ratio = qreal(cell_rect.width())
/ qreal(font_rect.width());
painter.save();
painter.translate(cell_rect.topLeft());
qreal vertical_adjustment =
cell_rect.height() * (1 - ratio) / 2.0;
painter.translate(0.0, vertical_adjustment);
painter.scale(ratio, ratio);
QRectF new_world_cell_rect(cell_rect);
new_world_cell_rect.moveTo(0, 0.0);
new_world_cell_rect.setWidth(new_world_cell_rect.width()
/ ratio);
painter.drawText(new_world_cell_rect,
cell.alignment,
text);
painter.restore();
return;
}
}
// Still here? Let's draw the text normally
painter.drawText(cell_rect, cell.alignment, text);
}
/**
@brief TitleBlockTemplate::renderTextCellDxf
@param file_path
@param text
@param cell
@param x
@param y
@param w
@param h
@param color
*/
void TitleBlockTemplate::renderTextCellDxf(
QString &file_path,
const QString &text,
const TitleBlockCell &cell,
qreal x,
qreal y,
qreal w,
qreal h,
int color) const
{
if (text.isEmpty()) return;
QFont text_font = TitleBlockTemplate::fontForCell(cell);
double textHeight = text_font.pointSizeF();
if (textHeight < 0)
textHeight = text_font.pixelSize();
qreal x2 = x + w;
qreal y1 = y;
int vAlign = 0;
int hAlign = 0;
x2 = x; // default
if ( cell.alignment & Qt::AlignTop )
{
vAlign = 3;
y1 = y + h - (textHeight*Createdxf::yScale / 8);
}
else if ( cell.alignment & Qt::AlignVCenter )
{
vAlign = 2;
y1 = y + h/2;
}
else if ( cell.alignment & Qt::AlignBottom )
{
y1 = y + (textHeight*Createdxf::yScale / 8);
}
if ( cell.alignment & Qt::AlignRight )
{
hAlign = 2;
x2 = x + w;
}
else if ( cell.alignment & Qt::AlignHCenter )
{
hAlign = 1;
x2 = x + w/2;
}
else if (cell.alignment & Qt::AlignJustify )
{
hAlign = 5;
vAlign = 0;
x2 = x + w;
y1 = y + textHeight*Createdxf::yScale / 8;
}
//painter.setFont(text_font);
qreal ratio = 1.0;
if (cell.hadjust)
{
// Scale font width to fit string in cell width w
// As DXF font aspect ratio is implementation dependent we add a fudge-factor based on tests with AutoCAD
int len = text.length() * textHeight * Createdxf::xScale * 1.2;
if(len > w)
ratio = (w/len);
}
// x offset value below currently set heuristically based on appearance...
Createdxf::drawTextAligned(
file_path,
text,
x - 2*Createdxf::xScale,
y1,
textHeight*Createdxf::yScale,
0,
0,
hAlign,
vAlign,
x2,
ratio,
color);
}
/**
@brief TitleBlockTemplate::forgetSpanning
Set the spanner_cell attribute of every cell to 0.
*/
void TitleBlockTemplate::forgetSpanning()
{
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
cells_[i][j] -> spanner_cell = nullptr;
}
}
}
/**
@brief TitleBlockTemplate::forgetSpanning
Set the spanner_cell attribute of every cell
spanned by \a spanning_cell to 0.
@param spanning_cell :
@param modify_cell :
(Optional, defaults to true)
Whether to set row_span and col_span of \a spanning_cell to 0.
*/
void TitleBlockTemplate::forgetSpanning(TitleBlockCell *spanning_cell,
bool modify_cell) {
if (!spanning_cell) return;
foreach (TitleBlockCell *spanned_cell, spannedCells(spanning_cell)) {
spanned_cell -> spanner_cell = nullptr;
}
if (modify_cell) {
spanning_cell -> row_span = 0;
spanning_cell -> col_span = 0;
spanning_cell -> applied_row_span = 0;
spanning_cell -> applied_col_span = 0;
spanning_cell -> span_state = TitleBlockCell::Enabled;
}
}
/**
@brief TitleBlockTemplate::applyCellSpans
Forget any previously applied span,
then apply again all spans defined by existing cells.
*/
void TitleBlockTemplate::applyCellSpans()
{
forgetSpanning();
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
checkCellSpan(cells_[i][j]);
applyCellSpan(cells_[i][j]);
}
}
}
/**
@brief TitleBlockTemplate::checkCellSpan
Check whether a given cell can be spanned according to its row_span and
col_span attributes. the following attributes of \a cell are updated
according to what is really possible:
* applied_col_span
* applied_row_span
* span_state
@param cell :
Cell we want to check
@return false if no check could be performed, true otherwise
*/
bool TitleBlockTemplate::checkCellSpan(TitleBlockCell *cell) {
if (!cell) return(false);
cell -> span_state = TitleBlockCell::Enabled;
cell -> applied_row_span = cell -> row_span;
cell -> applied_col_span = cell -> col_span;
// ensure the cell can span as far as required
if (cell -> num_col + cell -> col_span >= columnsCount()) {
cell -> applied_col_span = columnsCount() - 1 - cell -> num_col;
cell -> span_state = TitleBlockCell::Restricted;
}
if (cell -> num_row + cell -> row_span >= rowsCount()) {
cell -> applied_row_span = rowsCount() - 1 - cell -> num_row;
cell -> span_state = TitleBlockCell::Restricted;
}
// ensure cells that will be spanned are either empty or free
for (int i = cell -> num_col ;
i <= cell -> num_col + cell -> applied_col_span ;
++ i) {
for (int j = cell -> num_row ;
j <= cell -> num_row + cell -> applied_row_span ;
++ j) {
if (i == cell -> num_col && j == cell -> num_row)
continue;
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO << "span check" << i << j;
#endif
TitleBlockCell *current_cell = cells_[i][j];
if (current_cell -> cell_type
!= TitleBlockCell::EmptyCell
|| (
current_cell -> spanner_cell
&& current_cell -> spanner_cell
!= cell
)
) {
cell -> span_state = TitleBlockCell::Disabled;
return(true);
}
}
}
return(true);
}
/**
@brief TitleBlockTemplate::applyCellSpan
Ensure the spans of the provided cell are applied
within the grid structure.
@note: this function does not check
whether the spans of the provided cell make sense.
@param cell :
Potentially spanning cell
*/
void TitleBlockTemplate::applyCellSpan(TitleBlockCell *cell)
{
if (!cell || (!cell -> row_span && !cell -> col_span)) return;
if (cell -> span_state == TitleBlockCell::Disabled) return;
// goes through every spanned cell
for (int i = cell -> num_col ;
i <= cell -> num_col + cell -> applied_col_span ;
++ i) {
for (int j = cell -> num_row ;
j <= cell -> num_row + cell -> applied_row_span ;
++ j) {
// avoid the spanning cell itself
if (i == cell -> num_col && j == cell -> num_row)
continue;
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO
<< "marking cell at"
<< j
<< i
<< "as spanned by cell at"
<< cell -> num_row
<< cell -> num_col;
#endif
// marks all spanned cells with the spanning cell
cells_[i][j] -> spanner_cell = cell;
}
}
}
/**
@brief TitleBlockTemplate::applyRowColNums
Ensure all cells have the right col+row numbers.
*/
void TitleBlockTemplate::applyRowColNums()
{
for (int i = 0 ; i < columns_width_.count() ; ++ i) {
for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
cells_[i][j] -> num_col = i;
cells_[i][j] -> num_row = j;
}
}
}
/**
@brief TitleBlockTemplate::rowColsChanged
Take care of consistency and span-related problematics when
adding/moving/deleting rows and columns.
*/
void TitleBlockTemplate::rowColsChanged()
{
applyRowColNums();
applyCellSpans();
}
/**
@brief TitleBlockTemplate::lengthRange
@param start :
start border number
@param end :
end border number
@param lengths_list :
@return the width between two borders
*/
int TitleBlockTemplate::lengthRange(
int start, int end, const QList<int> &lengths_list) const
{
if (start > end
|| start >= lengths_list.count()
|| end > lengths_list.count()) {
#ifdef TITLEBLOCK_TEMPLATE_DEBUG
qDebug() << Q_FUNC_INFO << "wont use" << start << "and" << end;
#endif
return(0);
}
int length = 0;
for (int i = start ; i < end ; ++i) {
length += lengths_list[i];
}
return(length);
}