1 /*
2 	Copyright 2006-2019 The QElectroTech Team
3 	This file is part of QElectroTech.
4 
5 	QElectroTech is free software: you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation, either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	QElectroTech is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "titleblocktemplate.h"
19 #include "qet.h"
20 #include "qetapp.h"
21 #include "nameslist.h"
22 #include "createdxf.h"
23 // uncomment the line below to get more debug information
24 //#define TITLEBLOCK_TEMPLATE_DEBUG
25 
26 /**
27 	Constructor
28 	@param parent parent QObject
29 */
TitleBlockTemplate(QObject * parent)30 TitleBlockTemplate::TitleBlockTemplate(QObject *parent) :
31 	QObject(parent)
32 {
33 }
34 
35 /**
36 	Destructor
37 */
~TitleBlockTemplate()38 TitleBlockTemplate::~TitleBlockTemplate() {
39 	loadLogos(QDomElement(), true);
40 	qDeleteAll(registered_cells_);
41 }
42 
43 /**
44 	Create a new cell and associate it with this template, which means that it
45 	will be deleted when this template is destroyed.
46 	@param existing_cell (optional) An existing cell that will be copied
47 	@return A pointer to the newly created cell
48 */
createCell(const TitleBlockCell * existing_cell)49 TitleBlockCell *TitleBlockTemplate::createCell(const TitleBlockCell *existing_cell) {
50 	TitleBlockCell *new_cell = existing_cell ? new TitleBlockCell(*existing_cell) : new TitleBlockCell();
51 	registered_cells_ << new_cell;
52 	return(new_cell);
53 }
54 
55 /**
56 	@param count Number of cells expected in the list
57 	@return a list containing count newly created (and registered) cells
58 	@see createCell()
59 */
createCellsList(int count)60 QList<TitleBlockCell *> TitleBlockTemplate::createCellsList(int count) {
61 	QList<TitleBlockCell *> new_list;
62 	for (int i = 0 ; i < count ; ++ i) new_list << createCell();
63 	return(new_list);
64 }
65 
66 /**
67 	@param cell An existing cell
68 	@return The font that should be used to render this cell according to its properties.
69 */
fontForCell(const TitleBlockCell & cell)70 QFont TitleBlockTemplate::fontForCell(const TitleBlockCell &cell) {
71 	return(QETApp::diagramTextsFont(cell.font_size));
72 }
73 
74 /**
75 	Load a titleblock template from an XML file.
76 	@param filepath A file path to read the template from.
77 	@return true if the reading succeeds, false otherwise.
78 */
loadFromXmlFile(const QString & filepath)79 bool TitleBlockTemplate::loadFromXmlFile(const QString &filepath) {
80 	// open the file
81 	QFile template_file(filepath);
82 	if (!template_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
83 		return(false);
84 	}
85 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
86 	qDebug() << Q_FUNC_INFO << filepath << "opened";
87 #endif
88 
89 	// parse its content as XML
90 	QDomDocument xml_doc;
91 	bool xml_parsing = xml_doc.setContent(&template_file);
92 	if (!xml_parsing) {
93 		return(false);
94 	}
95 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
96 	qDebug() << Q_FUNC_INFO << filepath << "opened and parsed";
97 #endif
98 	return(loadFromXmlElement(xml_doc.documentElement()));
99 }
100 
101 /**
102 	@param xml_element An XML document to read the template from.
103 	@return true if the reading succeeds, false otherwise.
104 */
loadFromXmlElement(const QDomElement & xml_element)105 bool TitleBlockTemplate::loadFromXmlElement(const QDomElement &xml_element) {
106 	// we expect the XML element to be an <titleblocktemplate>
107 	if (xml_element.tagName() != "titleblocktemplate") {
108 		return(false);
109 	}
110 	if (!xml_element.hasAttribute("name")) {
111 		return(false);
112 	}
113 	name_ = xml_element.attribute("name");
114 
115 	loadInformation(xml_element);
116 	loadLogos(xml_element, true);
117 	loadGrid(xml_element);
118 
119 	return(true);
120 }
121 
122 /**
123 	Save the title block template into an XML file.
124 	@param filepath The file path this title block template should be saved to.
125 	@return true if the operation succeeds, false otherwise
126 */
saveToXmlFile(const QString & filepath)127 bool TitleBlockTemplate::saveToXmlFile(const QString &filepath) {
128 	if (filepath.isEmpty()) return(false);
129 
130 	// generate the XML document
131 	QDomDocument doc;
132 	QDomElement e = doc.createElement("root");
133 	bool saving = saveToXmlElement(e);
134 	if (!saving) return(false);
135 	doc.appendChild(e);
136 
137 	return(QET::writeXmlFile(doc, filepath));
138 }
139 
140 /**
141 	Save the title block template as XML.
142 	@param xml_element The XML element this title block template should be saved to.
143 	@return true if the export succeeds, false otherwise
144 */
saveToXmlElement(QDomElement & xml_element) const145 bool TitleBlockTemplate::saveToXmlElement(QDomElement &xml_element) const {
146 	// we are supposed to have at least a name
147 	if (name_.isEmpty()) return(false);
148 
149 	xml_element.setTagName("titleblocktemplate");
150 	xml_element.setAttribute("name", name_);
151 	saveInformation(xml_element);
152 	saveLogos(xml_element);
153 	saveGrid(xml_element);
154 	return(true);
155 }
156 
157 /**
158 	@param xml_element Parent XML element to be used when exporting \a cell
159 	@param cell Cell to export
160 */
exportCellToXml(TitleBlockCell * cell,QDomElement & xml_element) const161 void TitleBlockTemplate::exportCellToXml(TitleBlockCell *cell, QDomElement &xml_element) const {
162 	saveCell(cell, xml_element, true);
163 }
164 
165 /**
166 	@return a deep copy of the current title block template (i.e. title block
167 	cells are duplicated too and associated with their parent template).
168 */
clone() const169 TitleBlockTemplate *TitleBlockTemplate::clone() const {
170 	TitleBlockTemplate *copy = new TitleBlockTemplate();
171 	copy -> name_ = name_;
172 	copy -> information_ = information_;
173 
174 	// this does not really duplicates pixmaps, only the objects that hold a key to the implicitly shared pixmaps
175 	foreach (QString logo_key, bitmap_logos_.keys()) {
176 		copy -> bitmap_logos_[logo_key] = QPixmap(bitmap_logos_[logo_key]);
177 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
178 		qDebug() << Q_FUNC_INFO << "copying " << bitmap_logos_[logo_key] -> cacheKey() << "to" << copy -> bitmap_logos_[logo_key] -> cacheKey();
179 #endif
180 	}
181 
182 	// we have to create new QSvgRenderer objects from the data (no copy constructor)
183 	foreach (QString logo_key, vector_logos_.keys()) {
184 		copy -> vector_logos_[logo_key] = new QSvgRenderer(data_logos_[logo_key]);
185 	}
186 
187 	copy -> data_logos_    = data_logos_;
188 	copy -> storage_logos_ = storage_logos_;
189 	copy -> type_logos_    = type_logos_;
190 	copy -> rows_heights_  = rows_heights_;
191 	copy -> columns_width_ = columns_width_;
192 
193 	// copy cells basically
194 	copy -> cells_ = cells_;
195 	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
196 		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
197 			copy -> cells_[i][j] = copy -> createCell(cells_[i][j]);
198 		}
199 	}
200 
201 	// ensure the copy has no spanner_cell attribute pointing to a cell from the original object
202 	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
203 		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
204 			TitleBlockCell *current_cell = copy -> cells_[i][j];
205 			if (TitleBlockCell *original_cell = current_cell -> spanner_cell) {
206 				int original_cell_row = original_cell -> num_row;
207 				int original_cell_col = original_cell -> num_col;
208 				TitleBlockCell *copy_cell = copy -> cells_[original_cell_col][original_cell_row];
209 				current_cell -> spanner_cell = copy_cell;
210 			}
211 		}
212 	}
213 
214 	return(copy);
215 }
216 
217 /**
218 	Import text informations from a given XML title block template.
219 */
loadInformation(const QDomElement & xml_element)220 void TitleBlockTemplate::loadInformation(const QDomElement &xml_element) {
221 	for (QDomNode n = xml_element.firstChild() ; !n.isNull() ; n = n.nextSibling()) {
222 		if (n.isElement() && n.toElement().tagName() == "information") {
223 			setInformation(n.toElement().text());
224 		}
225 	}
226 }
227 
228 /**
229 	Import the logos from a given XML titleblock template.
230 	@param xml_element An XML element representing an titleblock template.
231 	@param reset true to delete all previously known logos before, false
232 	otherwise.
233 	@return true if the reading succeeds, false otherwise.
234 */
loadLogos(const QDomElement & xml_element,bool reset)235 bool TitleBlockTemplate::loadLogos(const QDomElement &xml_element, bool reset) {
236 	if (reset) {
237 		qDeleteAll(vector_logos_.begin(), vector_logos_.end());
238 		vector_logos_.clear();
239 
240 		// Note: QPixmap are only a key to access the implicitly shared pixmap
241 		bitmap_logos_.clear();
242 
243 		data_logos_.clear();
244 		storage_logos_.clear();
245 	}
246 
247 	// we look for //logos/logo elements
248 	for (QDomNode n = xml_element.firstChild() ; !n.isNull() ; n = n.nextSibling()) {
249 		if (n.isElement() && n.toElement().tagName() == "logos") {
250 			for (QDomNode p = n.firstChild() ; !p.isNull() ; p = p.nextSibling()) {
251 				if (p.isElement() && p.toElement().tagName() == "logo") {
252 					loadLogo(p.toElement());
253 				}
254 			}
255 		}
256 	}
257 
258 	return(true);
259 }
260 
261 /**
262 	Import the logo from a given XML logo description.
263 	@param xml_element An XML element representing a logo within an titleblock
264 	template.
265 	@return true if the reading succeeds, false otherwise.
266 */
loadLogo(const QDomElement & xml_element)267 bool TitleBlockTemplate::loadLogo(const QDomElement &xml_element) {
268 	// we require a name
269 	if (!xml_element.hasAttribute("name")) {
270 		return(false);
271 	}
272 	QString logo_name    = xml_element.attribute("name");
273 	QString logo_type    = xml_element.attribute("type", "png");
274 	QString logo_storage = xml_element.attribute("storage", "base64");
275 
276 	// Both QSvgRenderer and QPixmap read their data from a QByteArray, so
277 	// we convert the available data to that format.
278 	QByteArray logo_data;
279 	if (logo_storage == "xml") {
280 		QDomNodeList svg_nodes = xml_element.elementsByTagName("svg");
281 		if (svg_nodes.isEmpty()) {
282 			return(false);
283 		}
284 		QDomElement svg_element = svg_nodes.at(0).toElement();
285 		QTextStream xml_to_byte_array(&logo_data);
286 		svg_element.save(xml_to_byte_array, 0);
287 	} else if (logo_storage == "base64") {
288 		logo_data = QByteArray::fromBase64(xml_element.text().toLatin1());
289 	} else {
290 		return(false);
291 	}
292 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
293 	qDebug() << Q_FUNC_INFO << logo_name << logo_type << logo_storage;
294 #endif
295 	addLogo(logo_name, &logo_data, logo_type, logo_storage);
296 
297 	return(true);
298 }
299 
300 /**
301 	Import the grid from a given XML titleblock template.
302 	@param xml_element An XML element representing an titleblock template.
303 	@return true if the reading succeeds, false otherwise.
304 */
loadGrid(const QDomElement & xml_element)305 bool TitleBlockTemplate::loadGrid(const QDomElement &xml_element) {
306 	// we parse the first available "grid" XML element
307 	QDomElement grid_element;
308 	for (QDomNode n = xml_element.firstChild() ; !n.isNull() ; n = n.nextSibling()) {
309 		if (n.isElement() && n.toElement().tagName() == "grid") {
310 			grid_element = n.toElement();
311 			break;
312 		}
313 	}
314 
315 	if (!grid_element.hasAttribute("rows") || !grid_element.hasAttribute("cols")) {
316 		return(false);
317 	}
318 
319 	parseRows(grid_element.attribute("rows"));
320 	parseColumns(grid_element.attribute("cols"));
321 	initCells();
322 	loadCells(grid_element);
323 	applyRowColNums();
324 	applyCellSpans();
325 	return(true);
326 }
327 
328 /**
329 	Parse the rows heights
330 	@param rows_string A string describing the rows heights of the titleblock
331 */
parseRows(const QString & rows_string)332 void TitleBlockTemplate::parseRows(const QString &rows_string) {
333 	rows_heights_.clear();
334 	// parse the rows attribute: we expect a serie of absolute heights
335 	QRegExp row_size_format("^([0-9]+)(?:px)?$", Qt::CaseInsensitive);
336 	bool conv_ok;
337 
338 	QStringList rows_descriptions = rows_string.split(QChar(';'), QString::SkipEmptyParts);
339 	foreach (QString rows_description, rows_descriptions) {
340 		if (row_size_format.exactMatch(rows_description)) {
341 			int row_size = row_size_format.capturedTexts().at(1).toInt(&conv_ok);
342 			if (conv_ok) rows_heights_ << row_size;
343 		}
344 	}
345 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
346 	qDebug() << Q_FUNC_INFO << "Rows heights:" << rows_heights_;
347 #endif
348 }
349 
350 /**
351 	Parse the columns widths
352 	@param cols_string A string describing the columns widths of the titleblock
353 */
parseColumns(const QString & cols_string)354 void TitleBlockTemplate::parseColumns(const QString &cols_string) {
355 	columns_width_.clear();
356 	// parse the cols attribute: we expect a serie of absolute or relative widths
357 	QRegExp abs_col_size_format("^([0-9]+)(?:px)?$", Qt::CaseInsensitive);
358 	QRegExp rel_col_size_format("^([rt])([0-9]+)%$", Qt::CaseInsensitive);
359 	bool conv_ok;
360 
361 	QStringList cols_descriptions = cols_string.split(QChar(';'), QString::SkipEmptyParts);
362 	foreach (QString cols_description, cols_descriptions) {
363 		if (abs_col_size_format.exactMatch(cols_description)) {
364 			int col_size = abs_col_size_format.capturedTexts().at(1).toInt(&conv_ok);
365 			if (conv_ok) columns_width_ << TitleBlockDimension(col_size, QET::Absolute);
366 		} else if (rel_col_size_format.exactMatch(cols_description)) {
367 			int col_size = rel_col_size_format.capturedTexts().at(2).toInt(&conv_ok);
368 			QET::TitleBlockColumnLength col_type = rel_col_size_format.capturedTexts().at(1) == "t" ? QET::RelativeToTotalLength : QET::RelativeToRemainingLength;
369 			if (conv_ok) columns_width_ << TitleBlockDimension(col_size, col_type );
370 		}
371 	}
372 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
373 	foreach (TitleBlockColDimension icd, columns_width_) {
374 		qDebug() << Q_FUNC_INFO << QString("%1 [%2]").arg(icd.value).arg(QET::titleBlockColumnLengthToString(icd.type));
375 	}
376 #endif
377 }
378 
379 /**
380 	Analyze an XML element, looking for grid cells. The grid cells are checked
381 	and stored in this object.
382 	@param xml_element XML element to analyze
383 	@return systematically true
384 */
loadCells(const QDomElement & xml_element)385 bool TitleBlockTemplate::loadCells(const QDomElement &xml_element) {
386 	// we are interested by the "logo" and "field" elements
387 	QDomElement grid_element;
388 	for (QDomNode n = xml_element.firstChild() ; !n.isNull() ; n = n.nextSibling()) {
389 		if (!n.isElement()) continue;
390 		QDomElement cell_element = n.toElement();
391 		if (cell_element.tagName() == "field" || cell_element.tagName() == "logo") {
392 			loadCell(cell_element);
393 		}
394 	}
395 	return(true);
396 }
397 
398 /**
399 	Load a cell into this template.
400 	@param cell_element XML element describing a cell within a title block template
401 */
loadCell(const QDomElement & cell_element)402 void TitleBlockTemplate::loadCell(const QDomElement &cell_element) {
403 	TitleBlockCell *loaded_cell;
404 	if (!checkCell(cell_element, &loaded_cell)) return;
405 	loaded_cell -> loadContentFromXml(cell_element);
406 }
407 
408 /**
409 	Export this template's extra information.
410 	@param xml_element XML element under which extra informations will be attached
411 */
saveInformation(QDomElement & xml_element) const412 void TitleBlockTemplate::saveInformation(QDomElement &xml_element) const {
413 	QDomNode information_text_node = xml_element.ownerDocument().createTextNode(information());
414 
415 	QDomElement information_element = xml_element.ownerDocument().createElement("information");
416 	information_element.appendChild(information_text_node);
417 	xml_element.appendChild(information_element);
418 }
419 
420 /**
421 	Export this template's logos as XML
422 	@param xml_element XML Element under which the \<logos\> element will be attached
423 */
saveLogos(QDomElement & xml_element) const424 void TitleBlockTemplate::saveLogos(QDomElement &xml_element) const {
425 	QDomElement logos_element = xml_element.ownerDocument().createElement("logos");
426 	foreach(QString logo_name, type_logos_.keys()) {
427 		QDomElement logo_element = xml_element.ownerDocument().createElement("logo");
428 		saveLogo(logo_name, logo_element);
429 		logos_element.appendChild(logo_element);
430 	}
431 	xml_element.appendChild(logos_element);
432 }
433 
434 /**
435 	Export a specific logo as XML
436 	@param logo_name Name of the logo to be exported
437 	@param xml_element XML element in which the logo will be exported
438 */
saveLogo(const QString & logo_name,QDomElement & xml_element) const439 void TitleBlockTemplate::saveLogo(const QString &logo_name, QDomElement &xml_element) const {
440 	if (!type_logos_.contains(logo_name)) return;
441 
442 	xml_element.setAttribute("name", logo_name);
443 	xml_element.setAttribute("type", type_logos_[logo_name]);
444 	xml_element.setAttribute("storage", storage_logos_[logo_name]);
445 
446 	if (storage_logos_[logo_name] == "xml" && type_logos_[logo_name] == "svg") {
447 		QDomDocument svg_logo;
448 		svg_logo.setContent(data_logos_[logo_name]);
449 		QDomNode svg_logo_element = xml_element.ownerDocument().importNode(svg_logo.documentElement(), true);
450 		xml_element.appendChild(svg_logo_element.toElement());
451 	} else if (storage_logos_[logo_name] == "base64") {
452 		QDomText base64_logo = xml_element.ownerDocument().createTextNode(data_logos_[logo_name].toBase64());
453 		xml_element.appendChild(base64_logo);
454 	}
455 }
456 
457 /**
458 	Export this template's cells grid as XML
459 	@param xml_element XML element under which the \<grid\> element will be attached
460 */
saveGrid(QDomElement & xml_element) const461 void TitleBlockTemplate::saveGrid(QDomElement &xml_element) const {
462 	QDomElement grid_element = xml_element.ownerDocument().createElement("grid");
463 
464 	QString rows_attr, cols_attr;
465 	foreach(int row_height, rows_heights_) rows_attr += QString("%1;").arg(row_height);
466 	foreach(TitleBlockDimension col_width, columns_width_) cols_attr += col_width.toShortString();
467 	grid_element.setAttribute("rows", rows_attr);
468 	grid_element.setAttribute("cols", cols_attr);
469 
470 	saveCells(grid_element);
471 
472 	xml_element.appendChild(grid_element);
473 }
474 
475 /**
476 	Export this template's cells as XML (without the grid-related information, usch as rows and cols)
477 	@param xml_element XML element under which the \<cell\> elements will be attached
478 */
saveCells(QDomElement & xml_element) const479 void TitleBlockTemplate::saveCells(QDomElement &xml_element) const {
480 	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
481 		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
482 			if (cells_[i][j] -> cell_type != TitleBlockCell::EmptyCell) {
483 				saveCell(cells_[i][j], xml_element);
484 			}
485 		}
486 	}
487 }
488 
489 /**
490 	Export a specific cell as XML
491 	@param cell Cell to be exported as XML
492 	@param xml_element XML element under which the \<cell\> element will be attached
493 	@param save_empty If true, the cell will be saved even if it is an empty one
494 */
saveCell(TitleBlockCell * cell,QDomElement & xml_element,bool save_empty) const495 void TitleBlockTemplate::saveCell(TitleBlockCell *cell, QDomElement &xml_element, bool save_empty) const {
496 	if (!cell) return;
497 	if (cell -> spanner_cell) return;
498 	if (!save_empty && cell -> cell_type == TitleBlockCell::EmptyCell) return;
499 
500 
501 	QDomElement cell_elmt = xml_element.ownerDocument().createElement("cell");
502 	xml_element.appendChild(cell_elmt);
503 
504 	// save information dependent from this template
505 	cell_elmt.setAttribute("row", cell -> num_row);
506 	cell_elmt.setAttribute("col", cell -> num_col);
507 	if (cell -> row_span) cell_elmt.setAttribute("rowspan", cell -> row_span);
508 	if (cell -> col_span) cell_elmt.setAttribute("colspan", cell -> col_span);
509 
510 	// save other information
511 	cell -> saveContentToXml(cell_elmt);
512 }
513 
514 /**
515 	Load the essential attributes of a cell: row and column indices and spans.
516 	@param xml_element XML element representing a cell, i.e. either an titleblock
517 	logo or an titleblock field.
518 	@param titleblock_cell_ptr Pointer to a TitleBlockCell object pointer - if non-zero and if
519 	this method returns true, will be filled with the created TitleBlockCell
520 	@return TRUE if the cell appears to be ok, FALSE otherwise
521 */
checkCell(const QDomElement & xml_element,TitleBlockCell ** titleblock_cell_ptr)522 bool TitleBlockTemplate::checkCell(const QDomElement &xml_element, TitleBlockCell **titleblock_cell_ptr) {
523 	int col_count = columns_width_.count(), row_count = rows_heights_.count();
524 
525 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
526 	qDebug() << Q_FUNC_INFO << "begin" << row_count << col_count;
527 #endif
528 
529 	int row_num, col_num, row_span, col_span;
530 	row_num = col_num = -1;
531 	row_span = col_span = 0;
532 
533 	// parse the row and col attributes
534 	if (!QET::attributeIsAnInteger(xml_element, "row", &row_num) || row_num < 0 || row_num >= row_count) {
535 		return(false);
536 	}
537 	if (!QET::attributeIsAnInteger(xml_element, "col", &col_num) || col_num < 0 || col_num >= col_count) {
538 		return(false);
539 	}
540 
541 	// check whether the target cell can be used or not
542 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
543 	qDebug() << Q_FUNC_INFO << "cell access" << col_num << row_num;
544 #endif
545 	TitleBlockCell *cell_ptr = cells_[col_num][row_num];
546 	if (cell_ptr -> cell_type != TitleBlockCell::EmptyCell || cell_ptr -> spanner_cell) {
547 		return(false);
548 	}
549 	// ensure the num_row and num_col attributes are alright
550 	cell_ptr -> num_row = row_num;
551 	cell_ptr -> num_col = col_num;
552 
553 	// parse the rowspan and colspan attributes
554 	if (QET::attributeIsAnInteger(xml_element, "rowspan", &row_span) && row_span > 0) {
555 		cell_ptr -> row_span = row_span;
556 	}
557 
558 	if (QET::attributeIsAnInteger(xml_element, "colspan", &col_span) && col_span > 0) {
559 		cell_ptr -> col_span = col_span;
560 	}
561 	// these attributes are stored "as is" -- whether they can be applied directly or must be restricted will be checked later
562 
563 	if (titleblock_cell_ptr) *titleblock_cell_ptr = cell_ptr;
564 	return(true);
565 }
566 
567 /**
568 	Initialize the internal cells grid with the row and column counts.
569 	Note that this method does nothing if one of the internal lists
570 	columns_width_ and rows_heights_ is empty.
571 */
initCells()572 void TitleBlockTemplate::initCells() {
573 	if (columns_width_.count() < 1 || rows_heights_.count() < 1) return;
574 
575 	cells_.clear();
576 	qDeleteAll(registered_cells_);
577 	registered_cells_.clear();
578 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
579 		cells_ << createColumn();
580 	}
581 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
582 	qDebug() << Q_FUNC_INFO << toString();
583 #endif
584 }
585 
586 /**
587 	@return the name of this template
588 */
name() const589 QString TitleBlockTemplate::name() const {
590 	return(name_);
591 }
592 
593 /**
594 	@return the information field attached to this template
595 */
information() const596 QString TitleBlockTemplate::information() const {
597 	return(information_);
598 }
599 
600 /**
601 	@param info information to be attached to this template
602 */
setInformation(const QString & info)603 void TitleBlockTemplate::setInformation(const QString &info) {
604 	information_ = info;
605 }
606 
607 /**
608 	@param i row index
609 	@return the height of the row at index i
610 */
rowDimension(int i)611 int TitleBlockTemplate::rowDimension(int i) {
612 	int index = (i == -1) ? rows_heights_.count() - 1 : i;
613 	if (index >= 0 && index < rows_heights_.count()) {
614 		return(rows_heights_.at(index));
615 	}
616 	return(-1);
617 }
618 
619 /**
620 	Set the height of a row
621 	@param i row index
622 	@param dimension New height of the row at index i
623 */
setRowDimension(int i,const TitleBlockDimension & dimension)624 void TitleBlockTemplate::setRowDimension(int i, const TitleBlockDimension &dimension) {
625 	int index = (i == -1) ? rows_heights_.count() - 1 : i;
626 	if (index >= 0 || index < rows_heights_.count()) {
627 		rows_heights_[index] = dimension.value;
628 	}
629 }
630 
631 /**
632 	@param i column index
633 	@return the width of the column at index i
634 */
columnDimension(int i)635 TitleBlockDimension TitleBlockTemplate::columnDimension(int i) {
636 	int index = (i == -1) ? columns_width_.count() - 1 : i;
637 	if (index >= 0 && index < columns_width_.count()) {
638 		return(columns_width_.at(index));
639 	}
640 	return(TitleBlockDimension(-1));
641 }
642 
643 /**
644 	Set the width of a column
645 	@param i column index
646 	@param dimension New width of the column at index i
647 */
setColumnDimension(int i,const TitleBlockDimension & dimension)648 void TitleBlockTemplate::setColumnDimension(int i, const TitleBlockDimension &dimension) {
649 	int index = (i == -1) ? columns_width_.count() - 1 : i;
650 	if (index >= 0 || index < columns_width_.count()) {
651 		columns_width_[index] = dimension;
652 	}
653 }
654 
655 /**
656 	@return the number of columns in this template
657 */
columnsCount() const658 int TitleBlockTemplate::columnsCount() const {
659 	return(columns_width_.count());
660 }
661 
662 /**
663 	@return the number of rows in this template
664 */
rowsCount() const665 int TitleBlockTemplate::rowsCount() const {
666 	return(rows_heights_.count());
667 }
668 
669 /**
670 	@param total_width The total width of the titleblock to render
671 	@return the list of the columns widths for this rendering
672 */
columnsWidth(int total_width) const673 QList<int> TitleBlockTemplate::columnsWidth(int total_width) const {
674 	if (total_width < 0) return(QList<int>());
675 
676 	// we first iter to determine the absolute and total-width-related widths
677 	QVector<int> final_widths(columns_width_.count());
678 	int abs_widths_sum = 0, rel_widths_sum = 0;
679 	QList<int> relative_columns;
680 
681 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
682 		TitleBlockDimension icd = columns_width_.at(i);
683 		if (icd.type == QET::Absolute) {
684 			abs_widths_sum += icd.value;
685 			final_widths[i] = icd.value;
686 		} else if (icd.type == QET::RelativeToTotalLength) {
687 			int abs_value = qRound(total_width * icd.value / 100.0);
688 			relative_columns << i;
689 			abs_widths_sum += abs_value;
690 			final_widths[i] = abs_value;
691 		}
692 	}
693 
694 	// we can now deduce the remaining width
695 	int remaining_width = total_width - abs_widths_sum;
696 
697 	// we do a second iteration to build the final widths list
698 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
699 		TitleBlockDimension icd = columns_width_.at(i);
700 		if (icd.type == QET::RelativeToRemainingLength) {
701 			final_widths[i] = qRound(remaining_width * icd.value / 100.0);
702 			relative_columns << i;
703 			rel_widths_sum += final_widths[i];
704 		}
705 	}
706 
707 	// Have we computed widths from percentage for relative columns?
708 	if (relative_columns.count()) {
709 		// Due to the rounding process, we may get a slight difference between the
710 		// sum of the columns widths and the total width.
711 		int difference = total_width - abs_widths_sum - rel_widths_sum;
712 
713 		if (difference) {
714 			// We consider we should not attempt to compensate this difference if it is
715 			// under relative_columns_count * 0.5 (which means that each percent-based
716 			// columns can "bring" up to 0.5px of difference).
717 			qreal max_acceptable_difference = relative_columns.count() * 0.5;
718 
719 			int share = difference > 0 ? 1 : -1;
720 			if (qAbs(difference) <= max_acceptable_difference) {
721 				while (difference) {
722 					foreach (int index, relative_columns) {
723 						final_widths[index] += share;
724 						difference -= share;
725 						if (!difference) break;
726 					}
727 				}
728 			}
729 		}
730 	}
731 	return(final_widths.toList());
732 }
733 
734 /**
735 	@return the heights of all the rows in this template
736 */
rowsHeights() const737 QList<int> TitleBlockTemplate::rowsHeights() const {
738 	return(rows_heights_);
739 }
740 
741 /**
742 	@param a column type
743 	@return the count of \a type columns
744 */
columnTypeCount(QET::TitleBlockColumnLength type)745 int TitleBlockTemplate::columnTypeCount(QET::TitleBlockColumnLength type) {
746 	int count = 0;
747 
748 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
749 		if (columns_width_.at(i).type == type) ++ count;
750 	}
751 
752 	return(count);
753 }
754 
755 /**
756 	@param a column type
757 	@return the sum of values attached to \a type columns
758 */
columnTypeTotal(QET::TitleBlockColumnLength type)759 int TitleBlockTemplate::columnTypeTotal(QET::TitleBlockColumnLength type) {
760 	int total = 0;
761 
762 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
763 		if (columns_width_.at(i).type == type) {
764 			total += columns_width_.at(i).value;
765 		}
766 	}
767 
768 	return(total);
769 }
770 
771 /**
772 	@return the minimum width for this template
773 */
minimumWidth()774 int TitleBlockTemplate::minimumWidth() {
775 	// Abbreviations: ABS: absolute, RTT: relative to total, RTR: relative to
776 	// remaining, TOT: total diagram/TBT width (variable).
777 
778 	// Minimum size may be enforced by ABS and RTT widths:
779 	// TOT >= ((sum(REL)/100)*TOT)+sum(ABS)
780 	// => (1 - (sum(REL)/100))TOT >= sum(ABS)
781 	// => TOT >= sum(ABS) / (1 - (sum(REL)/100))
782 	// => TOT >= sum(ABS) / ((100 - sum(REL))/100))
783 	return(
784 		qRound(
785 			columnTypeTotal(QET::Absolute)
786 			/
787 			((100.0 - columnTypeTotal(QET::RelativeToTotalLength)) / 100.0)
788 		)
789 	);
790 }
791 
792 /**
793 	@return the maximum width for this template, or -1 if it does not have any.
794 */
maximumWidth()795 int TitleBlockTemplate::maximumWidth() {
796 	if (columnTypeCount(QET::Absolute) == columns_width_.count()) {
797 		// The template is composed of absolute widths only,
798 		// therefore it may not extend beyond their sum.
799 		return(columnTypeTotal(QET::Absolute));
800 	}
801 	return(-1);
802 }
803 
804 /**
805 	@return the total effective width of this template
806 	@param total_width The total width initially planned for the rendering
807 */
width(int total_width)808 int TitleBlockTemplate::width(int total_width) {
809 	int width = 0;
810 	foreach (int col_width, columnsWidth(total_width)) {
811 		width += col_width;
812 	}
813 	return(width);
814 }
815 
816 /**
817 	@return the total height of this template
818 */
height() const819 int TitleBlockTemplate::height() const {
820 	int height = 0;
821 	foreach(int row_height, rows_heights_) {
822 		height += row_height;
823 	}
824 	return(height);
825 }
826 
827 /**
828 	Move a row within this template.
829 	@param from Index of the moved row
830 	@param to Arrival index of the moved row
831 */
moveRow(int from,int to)832 bool TitleBlockTemplate::moveRow(int from, int to) {
833 	// checks from and to
834 	if (from >= rows_heights_.count()) return(false);
835 	if (to   >= rows_heights_.count()) return(false);
836 	for (int j = 0 ; j < columns_width_.count() ; ++ j) {
837 		cells_[j].move(from, to);
838 	}
839 	rows_heights_.move(from, to);
840 	rowColsChanged();
841 	return(true);
842 }
843 
844 /**
845 	Add a new 25px-wide row at the provided index.
846 	@param i Index of the added row, -1 meaning "last position"
847 */
addRow(int i)848 void TitleBlockTemplate::addRow(int i) {
849 	insertRow(25, createRow(), i);
850 }
851 
852 /**
853 	@param dimension Size of the row to be added (always absolute, in pixels)
854 	@param column Row to be added
855 	@param i Index of the column after insertion, -1 meaning "last position"
856 */
insertRow(int dimension,const QList<TitleBlockCell * > & row,int i)857 bool TitleBlockTemplate::insertRow(int dimension, const QList<TitleBlockCell *> &row, int i) {
858 	int index = (i == -1) ? rows_heights_.count() : i;
859 
860 	for (int j = 0 ; j < columns_width_.count() ; ++ j) {
861 		cells_[j].insert(index, row[j]);
862 	}
863 	rows_heights_.insert(index, dimension);
864 	rowColsChanged();
865 	return(true);
866 }
867 
868 /**
869 	Removes the row at index i
870 	@param i Index of the column to be removed
871 	@return the removed column
872 */
takeRow(int i)873 QList<TitleBlockCell *> TitleBlockTemplate::takeRow(int i) {
874 	QList<TitleBlockCell *> row;
875 	int index = (i == -1) ? rows_heights_.count() - 1 : i;
876 	if (index < 0 || index >= rows_heights_.count()) return(row);
877 	for (int j = 0 ; j < columns_width_.count() ; ++ j) {
878 		row << cells_[j].takeAt(index);
879 	}
880 	rows_heights_.removeAt(index);
881 	rowColsChanged();
882 	return(row);
883 }
884 
885 /**
886 	@return a new row that fits the current grid
887 */
createRow()888 QList<TitleBlockCell *> TitleBlockTemplate::createRow() {
889 	return(createCellsList(columns_width_.count()));
890 
891 }
892 
893 /**
894 	Move the column at index "from" to index "to".
895 	@param from Source index of the moved column
896 	@param to   Target index of the moved column
897 */
moveColumn(int from,int to)898 bool TitleBlockTemplate::moveColumn(int from, int to) {
899 	// checks from and to
900 	if (from >= columns_width_.count()) return(false);
901 	if (to   >= columns_width_.count()) return(false);
902 	cells_.move(from, to);
903 	columns_width_.move(from, to);
904 	rowColsChanged();
905 	return(true);
906 }
907 
908 /**
909 	Add a new 50px-wide column at the provided index.
910 	@param i Index of the added column, -1 meaning "last position"
911 */
addColumn(int i)912 void TitleBlockTemplate::addColumn(int i) {
913 	insertColumn(TitleBlockDimension(50, QET::Absolute), createColumn(), i);
914 }
915 
916 /**
917 	@param dimension Size of the column to be added
918 	@param column Column to be added
919 	@param i Index of the column after insertion, -1 meaning "last position"
920 */
insertColumn(const TitleBlockDimension & dimension,const QList<TitleBlockCell * > & column,int i)921 bool TitleBlockTemplate::insertColumn(const TitleBlockDimension &dimension, const QList<TitleBlockCell *> &column, int i) {
922 	int index = (i == -1) ? columns_width_.count() : i;
923 	cells_.insert(index, column);
924 	columns_width_.insert(index, dimension);
925 	rowColsChanged();
926 	return(true);
927 }
928 
929 /**
930 	Removes the column at index i
931 	@param i Index of the column to be removed
932 	@return the removed column
933 */
takeColumn(int i)934 QList<TitleBlockCell *> TitleBlockTemplate::takeColumn(int i) {
935 	int index = (i == -1) ? columns_width_.count() - 1 : i;
936 	if (index < 0 || index >= columns_width_.count()) {
937 		return(QList<TitleBlockCell *>());
938 	}
939 	QList<TitleBlockCell *> column = cells_.takeAt(i);
940 	columns_width_.removeAt(i);
941 	rowColsChanged();
942 	return(column);
943 }
944 
945 /**
946 	@return a new column that fits the current grid
947 */
createColumn()948 QList<TitleBlockCell *> TitleBlockTemplate::createColumn() {
949 	return(createCellsList(rows_heights_.count()));
950 }
951 
952 /**
953 	@param row A row number (starting from 0)
954 	@param col A column number (starting from 0)
955 	@return the cell located at (row, col)
956 */
cell(int row,int col) const957 TitleBlockCell *TitleBlockTemplate::cell(int row, int col) const {
958 	if (row >= rows_heights_.count()) return(nullptr);
959 	if (col >= columns_width_.count()) return(nullptr);
960 
961 	return(cells_[col][row]);
962 }
963 
964 /**
965 	@param cell A cell belonging to this title block template
966 	@param ignore_span_state (Optional, defaults to false) If true, will consider
967 	cells theoretically spanned (i.e. row_span and col_span attributes).
968 	Otherwise, will take span_state attribute into account.
969 	@return the set of cells spanned by the provided cell
970 	Note the returned set does not include the spanning, provided cell
971 */
spannedCells(const TitleBlockCell * given_cell,bool ignore_span_state) const972 QSet<TitleBlockCell *> TitleBlockTemplate::spannedCells(const TitleBlockCell *given_cell, bool ignore_span_state) const {
973 	QSet<TitleBlockCell *> set;
974 	if (!given_cell) return(set);
975 	if (!ignore_span_state && given_cell -> span_state == TitleBlockCell::Disabled) return(set);
976 
977 	int final_row_span = ignore_span_state ? given_cell -> row_span : given_cell -> applied_row_span;
978 	int final_col_span = ignore_span_state ? given_cell -> col_span : given_cell -> applied_col_span;
979 	if (!final_row_span && !final_col_span) return(set);
980 
981 	for (int i = given_cell -> num_col ; i <= given_cell -> num_col + final_col_span ; ++ i) {
982 		for (int j = given_cell -> num_row ; j <= given_cell -> num_row + final_row_span ; ++ j) {
983 			if (i == given_cell -> num_col && j == given_cell -> num_row) continue;
984 			TitleBlockCell *current_cell = cell(j, i);
985 			if (current_cell) set << current_cell;
986 		}
987 	}
988 	return(set);
989 }
990 
991 /**
992 	Export the span parameters of all cell in the current grid.
993 */
getAllSpans() const994 QHash<TitleBlockCell *, QPair<int, int> > TitleBlockTemplate::getAllSpans() const {
995 	QHash<TitleBlockCell *, QPair<int, int> > spans;
996 	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
997 		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
998 			spans.insert(
999 				cells_[i][j],
1000 				QPair<int, int>(
1001 					cells_[i][j] -> row_span,
1002 					cells_[i][j] -> col_span
1003 				)
1004 			);
1005 		}
1006 	}
1007 	return(spans);
1008 }
1009 
1010 /**
1011 	Restore a set of span parameters.
1012 */
setAllSpans(const QHash<TitleBlockCell *,QPair<int,int>> & spans)1013 void TitleBlockTemplate::setAllSpans(const QHash<TitleBlockCell *, QPair<int, int> > &spans) {
1014 	foreach (TitleBlockCell *cell, spans.keys()) {
1015 		cell -> row_span = spans[cell].first;
1016 		cell -> col_span = spans[cell].second;
1017 	}
1018 }
1019 
1020 /**
1021 	@param logo_name Logo name to be added / replaced
1022 	@param logo_data Logo data
1023 */
addLogo(const QString & logo_name,QByteArray * logo_data,const QString & logo_type,const QString & logo_storage)1024 bool TitleBlockTemplate::addLogo(const QString &logo_name, QByteArray *logo_data, const QString &logo_type, const QString &logo_storage) {
1025 	if (data_logos_.contains(logo_name)) {
1026 		// we are replacing the logo
1027 		removeLogo(logo_name);
1028 	}
1029 
1030 	// we can now create our image object from the byte array
1031 	if (logo_type == "svg") {
1032 		// SVG format is handled by the QSvgRenderer class
1033 		QSvgRenderer *svg = new QSvgRenderer();
1034 		if (!svg -> load(*logo_data)) {
1035 			return(false);
1036 		}
1037 		vector_logos_.insert(logo_name, svg);
1038 
1039 		// we also memorize the way to store them in the final XML output
1040 		QString final_logo_storage = logo_storage;
1041 		if (logo_storage != "xml" && logo_storage != "base64") {
1042 			final_logo_storage = "xml";
1043 		}
1044 		storage_logos_.insert(logo_name, logo_storage);
1045 	} else {
1046 
1047 		// bitmap formats are handled by the QPixmap class
1048 		QPixmap logo_pixmap;
1049 		logo_pixmap.loadFromData(*logo_data);
1050 		if (!logo_pixmap.width() || !logo_pixmap.height()) {
1051 			return(false);
1052 		}
1053 		bitmap_logos_.insert(logo_name, logo_pixmap);
1054 
1055 		// bitmap logos can only be stored using a base64 encoding
1056 		storage_logos_.insert(logo_name, "base64");
1057 	}
1058 
1059 	// we systematically store the raw data
1060 	data_logos_.insert(logo_name, *logo_data);
1061 	type_logos_.insert(logo_name, logo_type);
1062 
1063 	return(true);
1064 }
1065 
1066 /**
1067 	@param filepath Path of the image file to add as a logo
1068 	@param name Name used to store the logo; if none is provided, the
1069 	basename of the first argument is used.
1070 	@return true if the logo could be deleted, false otherwise
1071 */
addLogoFromFile(const QString & filepath,const QString & name)1072 bool TitleBlockTemplate::addLogoFromFile(const QString &filepath, const QString &name) {
1073 	QFileInfo filepath_info(filepath);
1074 	QString filename = name.isEmpty() ? filepath_info.fileName() : name;
1075 	QString filetype = filepath_info.suffix();
1076 
1077 	// we read the provided logo
1078 	QFile logo_file(filepath);
1079 	if (!logo_file.open(QIODevice::ReadOnly)) return(false);
1080 	QByteArray file_content = logo_file.readAll();
1081 
1082 	// first, we try to add it as an SVG image
1083 	if (addLogo(filename, &file_content, "svg", "xml")) return(true);
1084 
1085 	// we then try to add it as a bitmap image
1086 	return addLogo(filename, &file_content, filepath_info.suffix(), "base64");
1087 }
1088 
1089 /*
1090 	@param logo_name Name used to store the logo
1091 	@param filepath Path the logo will be saved as
1092 	@return true if the logo could be exported, false otherwise
1093 */
saveLogoToFile(const QString & logo_name,const QString & filepath)1094 bool TitleBlockTemplate::saveLogoToFile(const QString &logo_name, const QString &filepath) {
1095 	if (!data_logos_.contains(logo_name)) {
1096 		return(false);
1097 	}
1098 
1099 	QFile target_file(filepath);
1100 	if (!target_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1101 		return(false);
1102 	}
1103 
1104 	target_file.write(data_logos_[logo_name]);
1105 	target_file.close();
1106 	return(true);
1107 }
1108 
1109 /**
1110 	@param logo_name Name of the logo to remove
1111 	@return true if the logo could be deleted, false otherwise
1112 */
removeLogo(const QString & logo_name)1113 bool TitleBlockTemplate::removeLogo(const QString &logo_name) {
1114 	if (!data_logos_.contains(logo_name)) {
1115 		return(false);
1116 	}
1117 
1118 	/// TODO check existing cells using this logo.
1119 	if (vector_logos_.contains(logo_name)) {
1120 		delete vector_logos_.take(logo_name);
1121 	}
1122 	if (bitmap_logos_.contains(logo_name)) {
1123 		bitmap_logos_.remove(logo_name);
1124 	}
1125 	data_logos_.remove(logo_name);
1126 	storage_logos_.remove(logo_name);
1127 	return(true);
1128 }
1129 
1130 /**
1131 	Rename the \a logo_name logo to \a new_name
1132 	@param logo_name Name of the logo to be renamed
1133 	@param new_name New name of the renamed logo
1134 */
renameLogo(const QString & logo_name,const QString & new_name)1135 bool TitleBlockTemplate::renameLogo(const QString &logo_name, const QString &new_name) {
1136 	if (!data_logos_.contains(logo_name) || data_logos_.contains(new_name)) {
1137 		return(false);
1138 	}
1139 
1140 	/// TODO check existing cells using this logo.
1141 	if (vector_logos_.contains(logo_name)) {
1142 		vector_logos_.insert(new_name, vector_logos_.take(logo_name));
1143 	}
1144 	if (bitmap_logos_.contains(logo_name)) {
1145 		bitmap_logos_.insert(new_name, bitmap_logos_.take(logo_name));
1146 	}
1147 	data_logos_.insert(new_name, data_logos_.take(logo_name));
1148 	storage_logos_.insert(new_name, storage_logos_.take(logo_name));
1149 	return(true);
1150 }
1151 
1152 /**
1153 	Set the kind of storage for the \a logo_name logo.
1154 	@param logo_name Name of the logo which kind of storage is to be changed
1155 	@param storage The kind of storage to use for the logo, e.g. "xml" or "base64".
1156 */
setLogoStorage(const QString & logo_name,const QString & storage)1157 void TitleBlockTemplate::setLogoStorage(const QString &logo_name, const QString &storage) {
1158 	if (storage_logos_.contains(logo_name)) {
1159 		storage_logos_[logo_name] = storage;
1160 	}
1161 }
1162 
1163 /**
1164 	@return The names of logos embedded within this title block template.
1165 */
logos() const1166 QList<QString> TitleBlockTemplate::logos() const {
1167 	return(data_logos_.keys());
1168 }
1169 
1170 /**
1171 	@param logo_name Name of a logo embedded within this title block template.
1172 	@return the kind of storage used for the required logo, or a null QString
1173 	if no such logo was found in this template.
1174 */
logoType(const QString & logo_name) const1175 QString TitleBlockTemplate::logoType(const QString &logo_name) const {
1176 	if (type_logos_.contains(logo_name)) {
1177 		return type_logos_[logo_name];
1178 	}
1179 	return(QString());
1180 }
1181 
1182 /**
1183 	@param logo_name Name of a vector logo embedded within this title block template.
1184 	@return the rendering object for the required vector logo, or 0 if no such
1185 	vector logo was found in this template.
1186 */
vectorLogo(const QString & logo_name) const1187 QSvgRenderer *TitleBlockTemplate::vectorLogo(const QString &logo_name) const {
1188 	if (vector_logos_.contains(logo_name)) {
1189 		return vector_logos_[logo_name];
1190 	}
1191 	return(nullptr);
1192 }
1193 
1194 /**
1195 	@param logo_name Name of a logo embedded within this title block template.
1196 	@return the pixmap for the required bitmap logo, or a null pixmap if no
1197 	such bitmap logo was found in this template.
1198 */
bitmapLogo(const QString & logo_name) const1199 QPixmap TitleBlockTemplate::bitmapLogo(const QString &logo_name) const {
1200 	if (bitmap_logos_.contains(logo_name)) {
1201 		return bitmap_logos_[logo_name];
1202 	}
1203 	return(QPixmap());
1204 }
1205 
1206 /**
1207 	Render the titleblock.
1208 	@param painter Painter to use to render the titleblock
1209 	@param diagram_context Diagram context to use to generate the titleblock strings
1210 	@param titleblock_width Width of the titleblock to render
1211 */
render(QPainter & painter,const DiagramContext & diagram_context,int titleblock_width) const1212 void TitleBlockTemplate::render(QPainter &painter, const DiagramContext &diagram_context, int titleblock_width) const {
1213 	QList<int> widths = columnsWidth(titleblock_width);
1214 	int titleblock_height = height();
1215 
1216 	painter.save();
1217 		//Setup the QPainter
1218 	QPen pen(Qt::black);
1219 	pen.setCosmetic(true);
1220 	painter.setPen(pen);
1221 
1222 	// draw the titleblock border
1223 	painter.drawRect(QRect(0, 0, titleblock_width, titleblock_height));
1224 
1225 	// run through each individual cell
1226 	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
1227 		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
1228 			if (cells_[i][j] -> spanner_cell || cells_[i][j] -> cell_type == TitleBlockCell::EmptyCell) continue;
1229 
1230 			// calculate the border rect of the current cell
1231 			int x = lengthRange(0, cells_[i][j] -> num_col, widths);
1232 			int y = lengthRange(0, cells_[i][j] -> num_row, rows_heights_);
1233 
1234 			int row_span = 0, col_span = 0;
1235 			if (cells_[i][j] -> span_state != TitleBlockCell::Disabled) {
1236 				row_span = cells_[i][j] -> applied_row_span;
1237 				col_span = cells_[i][j] -> applied_col_span;
1238 			}
1239 			int w = lengthRange(cells_[i][j] -> num_col, cells_[i][j] -> num_col + 1 + col_span, widths);
1240 			int h = lengthRange(cells_[i][j] -> num_row, cells_[i][j] -> num_row + 1 + row_span, rows_heights_);
1241 			QRect cell_rect(x, y, w, h);
1242 
1243 			renderCell(painter, *cells_[i][j], diagram_context, cell_rect);
1244 		}
1245 	}
1246 	painter.restore();
1247 }
1248 
1249 /**
1250 	Render the titleblock in DXF.
1251 	@param diagram_context Diagram context to use to generate the titleblock strings
1252 	@param titleblock_width Width of the titleblock to render
1253 */
renderDxf(QRectF & title_block_rect,const DiagramContext & diagram_context,int titleblock_width,QString & file_path,int color) const1254 void TitleBlockTemplate::renderDxf(QRectF &title_block_rect, const DiagramContext &diagram_context,
1255 								   int titleblock_width, QString &file_path, int color) const {
1256 	QList<int> widths = columnsWidth(titleblock_width);
1257 
1258 	// draw the titleblock border
1259 	double xCoord    = title_block_rect.topLeft().x();
1260 	double yCoord    = Createdxf::sheetHeight - title_block_rect.bottomLeft().y()*Createdxf::yScale;
1261 	double recWidth  = title_block_rect.width()  * Createdxf::xScale;
1262 	double recHeight = title_block_rect.height() * Createdxf::yScale;
1263 	Createdxf::drawRectangle(file_path, xCoord, yCoord, recWidth, recHeight, color);
1264 
1265 	// run through each individual cell
1266 	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
1267 		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
1268 			if (cells_[i][j] -> spanner_cell || cells_[i][j] -> cell_type == TitleBlockCell::EmptyCell) continue;
1269 
1270 			// calculate the border rect of the current cell
1271 			double x = lengthRange(0, cells_[i][j] -> num_col, widths);
1272 			double y = lengthRange(0, cells_[i][j] -> num_row, rows_heights_);
1273 
1274 			int row_span = 0, col_span = 0;
1275 			if (cells_[i][j] -> span_state != TitleBlockCell::Disabled) {
1276 				row_span = cells_[i][j] -> applied_row_span;
1277 				col_span = cells_[i][j] -> applied_col_span;
1278 			}
1279 			double w = lengthRange(cells_[i][j] -> num_col, cells_[i][j] -> num_col + 1 + col_span, widths);
1280 			double h = lengthRange(cells_[i][j] -> num_row, cells_[i][j] -> num_row + 1 + row_span, rows_heights_);
1281 
1282 			x = xCoord + x*Createdxf::xScale;
1283 			h *= Createdxf::yScale;
1284 			y = yCoord + recHeight - h - y*Createdxf::yScale;
1285 			w *= Createdxf::xScale;
1286 
1287 			Createdxf::drawRectangle(file_path, x, y, w, h, color);
1288 			if (cells_[i][j] -> type() == TitleBlockCell::TextCell) {
1289 				QString final_text = finalTextForCell(*cells_[i][j], diagram_context);
1290 				renderTextCellDxf(file_path, final_text, *cells_[i][j], x, y, w, h, color);
1291 			}
1292 		}
1293 	}
1294 }
1295 
1296 
1297 
1298 /**
1299 	Render a titleblock cell.
1300 	@param painter Painter to use to render the titleblock
1301 	@param diagram_context Diagram context to use to generate the titleblock strings
1302 	@param rect Rectangle the cell must be rendered into.
1303 */
renderCell(QPainter & painter,const TitleBlockCell & cell,const DiagramContext & diagram_context,const QRect & cell_rect) const1304 void TitleBlockTemplate::renderCell(QPainter &painter, const TitleBlockCell &cell, const DiagramContext &diagram_context, const QRect &cell_rect) const {
1305 	// draw the border rect of the current cell
1306 	QPen pen(QBrush(), 0.0, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
1307 	pen.setColor(Qt::black);
1308 	painter.setPen(pen);
1309 	painter.drawRect(cell_rect);
1310 
1311 	painter.save();
1312 	// render the inner content of the current cell
1313 	if (cell.type() == TitleBlockCell::LogoCell) {
1314 		if (!cell.logo_reference.isEmpty()) {
1315 			// the current cell appears to be a logo - we first look for the
1316 			// logo reference in our vector logos list, since they offer a
1317 			// potentially better (or, at least, not resolution-limited) rendering
1318 			if (vector_logos_.contains(cell.logo_reference)) {
1319 				vector_logos_[cell.logo_reference] -> render(&painter, cell_rect);
1320 			} else if (bitmap_logos_.contains(cell.logo_reference)) {
1321 				painter.drawPixmap(cell_rect, bitmap_logos_[cell.logo_reference]);
1322 			}
1323 		}
1324 	} else if (cell.type() == TitleBlockCell::TextCell) {
1325 		QString final_text = finalTextForCell(cell, diagram_context);
1326 		renderTextCell(painter, final_text, cell, cell_rect);
1327 	}
1328 	painter.restore();
1329 
1330 	// draw again the border rect of the current cell, without the brush this time
1331 	painter.setBrush(Qt::NoBrush);
1332 	painter.drawRect(cell_rect);
1333 }
1334 
1335 
1336 
1337 
1338 /**
1339 	@param cell A cell from this template
1340 	@param diagram_context Diagram context to use to generate the final text for the given cell
1341 	@return the final text that has to be drawn in the given cell
1342 */
finalTextForCell(const TitleBlockCell & cell,const DiagramContext & diagram_context) const1343 QString TitleBlockTemplate::finalTextForCell(const TitleBlockCell &cell, const DiagramContext &diagram_context) const {
1344 	QString cell_text = cell.value.name();
1345 	QString cell_label = cell.label.name();
1346 
1347 	cell_text = interpreteVariables(cell_text, diagram_context);
1348 
1349 	if (cell.display_label && !cell.label.isEmpty()) {
1350 		cell_label = interpreteVariables(cell_label, diagram_context);
1351 		cell_text = QString(tr(" %1 : %2", "titleblock content - please let the blank space at the beginning")).arg(cell_label).arg(cell_text);
1352 	} else {
1353 		cell_text = QString(tr(" %1")).arg(cell_text);
1354 	}
1355 	return(cell_text);
1356 }
1357 
1358 /**
1359 	@param string A text containing 0 to n variables, e.g. "%var" or "%{var}"
1360 	@param diagram_context Diagram context to use to interprete variables
1361 	@return the provided string with variables replaced by the values from the diagram context
1362 */
interpreteVariables(const QString & string,const DiagramContext & diagram_context) const1363 QString TitleBlockTemplate::interpreteVariables(const QString &string, const DiagramContext &diagram_context) const {
1364 	QString interpreted_string = string;
1365 	foreach (QString key, diagram_context.keys(DiagramContext::DecreasingLength)) {
1366 		interpreted_string.replace("%{" + key + "}", diagram_context[key].toString());
1367 		interpreted_string.replace("%" + key,        diagram_context[key].toString());
1368 	}
1369 	return(interpreted_string);
1370 }
1371 
1372 /**
1373 	@brief Get list of variables
1374 	@return The list of string with variables
1375 */
listOfVariables()1376 QStringList TitleBlockTemplate::listOfVariables() {
1377 	QStringList list;
1378 	// run through each individual cell
1379 	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
1380 		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
1381 			if (cells_[i][j] -> spanner_cell || cells_[i][j] -> cell_type == TitleBlockCell::EmptyCell) continue;
1382 			// TODO: not works on all cases...
1383 			list << cells_[i][j] -> value.name().replace("%","");
1384 		}
1385 	}
1386 	qDebug() << list;
1387 	return list;
1388 }
1389 
1390 /**
1391 	This method uses a \a painter to render the \a text of a \a cell
1392 	into the \a cell_rect rectangle.
1393 	The alignment, font_size and other cell parameters are taken into account
1394 	when rendering.
1395 	@param painter QPainter used to render the text
1396 	@param text Text to render
1397 	@param cell Cell the rendered text is rattached to
1398 	@param cell_rect Rectangle delimiting the cell area
1399 */
renderTextCell(QPainter & painter,const QString & text,const TitleBlockCell & cell,const QRectF & cell_rect) const1400 void TitleBlockTemplate::renderTextCell(QPainter &painter, const QString &text, const TitleBlockCell &cell, const QRectF &cell_rect) const {
1401 	if (text.isEmpty()) return;
1402 	QFont text_font = TitleBlockTemplate::fontForCell(cell);
1403 	painter.setFont(text_font);
1404 
1405 	if (cell.hadjust) {
1406 		QFontMetricsF font_metrics(text_font);
1407 		QRectF font_rect = font_metrics.boundingRect(QRect(-10000, -10000, 10000, 10000), cell.alignment, text);
1408 
1409 		if (font_rect.width() > cell_rect.width()) {
1410 			qreal ratio = qreal(cell_rect.width()) / qreal(font_rect.width());
1411 			painter.save();
1412 
1413 			painter.translate(cell_rect.topLeft());
1414 			qreal vertical_adjustment = cell_rect.height() * (1 - ratio) / 2.0;
1415 			painter.translate(0.0, vertical_adjustment);
1416 			painter.scale(ratio, ratio);
1417 
1418 			QRectF new_world_cell_rect(cell_rect);
1419 			new_world_cell_rect.moveTo(0, 0.0);
1420 			new_world_cell_rect.setWidth(new_world_cell_rect.width() / ratio);
1421 			painter.drawText(new_world_cell_rect, cell.alignment, text);
1422 
1423 			painter.restore();
1424 			return;
1425 		}
1426 	}
1427 
1428 	// Still here? Let's draw the text normally
1429 	painter.drawText(cell_rect, cell.alignment, text);
1430 }
1431 
1432 
renderTextCellDxf(QString & file_path,const QString & text,const TitleBlockCell & cell,qreal x,qreal y,qreal w,qreal h,int color) const1433 void TitleBlockTemplate::renderTextCellDxf(QString &file_path, const QString &text,
1434 										   const TitleBlockCell &cell,
1435 										   qreal x, qreal y, qreal w, qreal h, int color) const {
1436 	if (text.isEmpty()) return;
1437 	QFont text_font = TitleBlockTemplate::fontForCell(cell);
1438 	double textHeight = text_font.pointSizeF();
1439 	if (textHeight < 0)
1440 		textHeight = text_font.pixelSize();
1441 
1442 	qreal x2 = x + w;
1443 
1444 	int vAlign = 0;
1445 	int hAlign = 0;
1446 	bool hALigned = false;
1447 
1448 	if ( cell.alignment & Qt::AlignRight ) {
1449 		hAlign = 2;
1450 		hALigned = true;
1451 	} else if ( cell.alignment & Qt::AlignHCenter ) {
1452 		hAlign = 1;
1453 		hALigned = true;
1454 		x2 = x + w/2;
1455 	} else if ( cell.alignment & Qt::AlignJustify ) {
1456 		hAlign = 5;
1457 		hALigned = true;
1458 	}
1459 
1460 	if ( cell.alignment & Qt::AlignTop ) {
1461 		vAlign = 3;
1462 		y += h - textHeight*Createdxf::yScale;
1463 		if (!hALigned)
1464 			x2 = x;
1465 	} else if ( cell.alignment & Qt::AlignVCenter ) {
1466 		vAlign = 2;
1467 		y += h/2;
1468 		if (!hALigned)
1469 			x2 = x;
1470 	} else if ( cell.alignment & Qt::AlignBottom ) {}
1471 
1472 
1473 	//painter.setFont(text_font);
1474 
1475 	if (cell.hadjust) {
1476 		QFontMetricsF font_metrics(text_font);
1477 		QRectF font_rect = font_metrics.boundingRect(QRect(-10000, -10000, 10000, 10000), cell.alignment, text);
1478 
1479 		if (font_rect.width()*Createdxf::xScale > w) {
1480 			qreal ratio = qreal(w) / qreal(font_rect.width()*Createdxf::xScale);
1481 			textHeight *= ratio;
1482 		}
1483 	}
1484 
1485 	Createdxf::drawTextAligned(file_path, text, x,
1486 							   y, textHeight*Createdxf::yScale, 0, 0, hAlign, vAlign, x2, color, 0);
1487 
1488 }
1489 
1490 
1491 /**
1492 	Set the spanner_cell attribute of every cell to 0.
1493 */
forgetSpanning()1494 void TitleBlockTemplate::forgetSpanning() {
1495 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
1496 		for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
1497 			cells_[i][j] -> spanner_cell = nullptr;
1498 		}
1499 	}
1500 }
1501 
1502 /**
1503 	Set the spanner_cell attribute of every cell spanned by \a spanning_cell to 0.
1504 	@param modify_cell (Optional, defaults to true) Whether to set row_span and col_span of \a spanning_cell to 0.
1505 */
forgetSpanning(TitleBlockCell * spanning_cell,bool modify_cell)1506 void TitleBlockTemplate::forgetSpanning(TitleBlockCell *spanning_cell, bool modify_cell) {
1507 	if (!spanning_cell) return;
1508 	foreach (TitleBlockCell *spanned_cell, spannedCells(spanning_cell)) {
1509 		spanned_cell -> spanner_cell = nullptr;
1510 	}
1511 	if (modify_cell) {
1512 		spanning_cell -> row_span = 0;
1513 		spanning_cell -> col_span = 0;
1514 		spanning_cell -> applied_row_span = 0;
1515 		spanning_cell -> applied_col_span = 0;
1516 		spanning_cell -> span_state = TitleBlockCell::Enabled;
1517 	}
1518 }
1519 
1520 /**
1521 	Forget any previously applied span, then apply again all spans defined
1522 	by existing cells.
1523 */
applyCellSpans()1524 void TitleBlockTemplate::applyCellSpans() {
1525 	forgetSpanning();
1526 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
1527 		for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
1528 			checkCellSpan(cells_[i][j]);
1529 			applyCellSpan(cells_[i][j]);
1530 		}
1531 	}
1532 }
1533 
1534 /**
1535 	Check whether a given cell can be spanned according to its row_span and
1536 	col_span attributes. the following attributes of \a cell are updated
1537 	according to what is really possible:
1538 	  * applied_col_span
1539 	  * applied_row_span
1540 	  * span_state
1541 	@param cell Cell we want to check
1542 	@return false if no check could be performed, true otherwise
1543 */
checkCellSpan(TitleBlockCell * cell)1544 bool TitleBlockTemplate::checkCellSpan(TitleBlockCell *cell) {
1545 	if (!cell) return(false);
1546 
1547 	cell -> span_state = TitleBlockCell::Enabled;
1548 	cell -> applied_row_span = cell -> row_span;
1549 	cell -> applied_col_span = cell -> col_span;
1550 
1551 	// ensure the cell can span as far as required
1552 	if (cell -> num_col + cell -> col_span >= columnsCount()) {
1553 		cell -> applied_col_span = columnsCount() - 1 - cell -> num_col;
1554 		cell -> span_state = TitleBlockCell::Restricted;
1555 	}
1556 	if (cell -> num_row + cell -> row_span >= rowsCount()) {
1557 		cell -> applied_row_span = rowsCount() - 1 - cell -> num_row;
1558 		cell -> span_state = TitleBlockCell::Restricted;
1559 	}
1560 
1561 	// ensure cells that will be spanned are either empty or free
1562 	for (int i = cell -> num_col ; i <= cell -> num_col + cell -> applied_col_span ; ++ i) {
1563 		for (int j = cell -> num_row ; j <= cell -> num_row + cell -> applied_row_span ; ++ j) {
1564 			if (i == cell -> num_col && j == cell -> num_row) continue;
1565 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
1566 			qDebug() << Q_FUNC_INFO << "span check" << i << j;
1567 #endif
1568 			TitleBlockCell *current_cell = cells_[i][j];
1569 			if (current_cell -> cell_type != TitleBlockCell::EmptyCell || (current_cell -> spanner_cell && current_cell -> spanner_cell != cell)) {
1570 				cell -> span_state = TitleBlockCell::Disabled;
1571 				return(true);
1572 			}
1573 		}
1574 	}
1575 
1576 	return(true);
1577 }
1578 
1579 /**
1580 	Ensure the spans of the provided cell are applied within the grid structure.
1581 	Note: this function does not check whether the spans of the provided cell make sense.
1582 	@param cell Potentially spanning cell
1583 */
applyCellSpan(TitleBlockCell * cell)1584 void TitleBlockTemplate::applyCellSpan(TitleBlockCell *cell) {
1585 	if (!cell || (!cell -> row_span && !cell -> col_span)) return;
1586 	if (cell -> span_state == TitleBlockCell::Disabled) return;
1587 
1588 	// goes through every spanned cell
1589 	for (int i = cell -> num_col ; i <= cell -> num_col + cell -> applied_col_span ; ++ i) {
1590 		for (int j = cell -> num_row ; j <= cell -> num_row + cell -> applied_row_span ; ++ j) {
1591 			// avoid the spanning cell itself
1592 			if (i == cell -> num_col && j == cell -> num_row) continue;
1593 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
1594 			qDebug() << Q_FUNC_INFO << "marking cell at" << j << i <<  "as spanned by cell at" << cell -> num_row <<  cell -> num_col;
1595 #endif
1596 			// marks all spanned cells with the spanning cell
1597 			cells_[i][j] -> spanner_cell = cell;
1598 		}
1599 	}
1600 }
1601 
1602 /**
1603 	Ensure all cells have the right col+row numbers.
1604 */
applyRowColNums()1605 void TitleBlockTemplate::applyRowColNums() {
1606 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
1607 		for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
1608 			cells_[i][j] -> num_col = i;
1609 			cells_[i][j] -> num_row = j;
1610 		}
1611 	}
1612 }
1613 
1614 /**
1615 	Take care of consistency and span-related problematics when
1616 	adding/moving/deleting rows and columns.
1617 */
rowColsChanged()1618 void TitleBlockTemplate::rowColsChanged() {
1619 	applyRowColNums();
1620 	applyCellSpans();
1621 }
1622 
1623 /**
1624 	@return the width between two borders
1625 	@param start start border number
1626 	@param end end border number
1627 */
lengthRange(int start,int end,const QList<int> & lengths_list) const1628 int TitleBlockTemplate::lengthRange(int start, int end, const QList<int> &lengths_list) const {
1629 	if (start > end || start >= lengths_list.count() || end > lengths_list.count()) {
1630 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
1631 		qDebug() << Q_FUNC_INFO << "wont use" << start << "and" << end;
1632 #endif
1633 		return(0);
1634 	}
1635 
1636 	int length = 0;
1637 	for (int i = start ; i < end ; ++i) {
1638 		length += lengths_list[i];
1639 	}
1640 	return(length);
1641 }
1642 
1643