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