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 "templateview.h"
19 #include "templatevisualcell.h"
20 #include "gridlayoutanimation.h"
21 #include "helpercell.h"
22 #include "splittedhelpercell.h"
23 #include "templatecommands.h"
24 #include "templatecellsset.h"
25 #include "dimensionwidget.h"
26 #include "qeticons.h"
27 #define ROW_OFFSET 2
28 #define COL_OFFSET 1
29 #define DEFAULT_PREVIEW_WIDTH 600
30 #define DEFAULT_PREVIEW_HELPER_CELL_HEIGHT 15
31 #define DEFAULT_COLS_HELPER_CELLS_HEIGHT   15
32 #define DEFAULT_ROWS_HELPER_CELLS_WIDTH    50
33 
34 
35 
36 /**
37 	Constructor
38 	@param parent Parent QWidget.
39 */
TitleBlockTemplateView(QWidget * parent)40 TitleBlockTemplateView::TitleBlockTemplateView(QWidget *parent) :
41 	QGraphicsView(parent),
42 	tbtemplate_(nullptr),
43 	tbgrid_(nullptr),
44 	form_(nullptr),
45 	preview_width_(DEFAULT_PREVIEW_WIDTH),
46 	apply_columns_widths_count_(0),
47 	apply_rows_heights_count_(0),
48 	first_activation_(true),
49 	read_only_(false)
50 {
51 	init();
52 }
53 
54 /**
55 	Constructor
56 	@param parent Parent QWidget.
57 */
TitleBlockTemplateView(QGraphicsScene * scene,QWidget * parent)58 TitleBlockTemplateView::TitleBlockTemplateView(QGraphicsScene *scene, QWidget *parent) :
59 	QGraphicsView(scene, parent),
60 	tbtemplate_(nullptr),
61 	tbgrid_(nullptr),
62 	preview_width_(DEFAULT_PREVIEW_WIDTH),
63 	apply_columns_widths_count_(0),
64 	apply_rows_heights_count_(0),
65 	first_activation_(true),
66 	read_only_(false)
67 {
68 	init();
69 }
70 
71 /**
72 	Destructor
73 */
~TitleBlockTemplateView()74 TitleBlockTemplateView::~TitleBlockTemplateView() {
75 }
76 
77 /**
78 	@param tbtemplate Title block template to be rendered by this view.
79 	If set to zero, the View will render nothing.
80 */
setTitleBlockTemplate(TitleBlockTemplate * tbtemplate)81 void TitleBlockTemplateView::setTitleBlockTemplate(TitleBlockTemplate *tbtemplate) {
82 	loadTemplate(tbtemplate);
83 	zoomFit();
84 }
85 
86 /**
87 	@return The title block template object rendered by this view.
88 */
titleBlockTemplate() const89 TitleBlockTemplate *TitleBlockTemplateView::titleBlockTemplate() const {
90 	return(tbtemplate_);
91 }
92 
93 /**
94 	Emits the selectedCellsChanged() signal with the currently selected cells.
95 */
selectionChanged()96 void TitleBlockTemplateView::selectionChanged() {
97 	emit(selectedCellsChanged(selectedCells()));
98 }
99 
100 /**
101 	Zoom in by zoomFactor().
102 	@see zoomFactor()
103 */
zoomIn()104 void TitleBlockTemplateView::zoomIn() {
105 	scale(zoomFactor(), zoomFactor());
106 }
107 
108 /**
109 	Zoom out by zoomFactor().
110 	@see zoomFactor()
111 */
zoomOut()112 void TitleBlockTemplateView::zoomOut() {
113 	qreal zoom_factor = 1.0/zoomFactor();
114 	scale(zoom_factor, zoom_factor);
115 }
116 
117 /**
118 	Fit the rendered title block template in this view.
119 */
zoomFit()120 void TitleBlockTemplateView::zoomFit() {
121 	adjustSceneRect();
122 	fitInView(scene() -> sceneRect(), Qt::KeepAspectRatio);
123 }
124 
125 /**
126 	Reset the zoom level.
127 */
zoomReset()128 void TitleBlockTemplateView::zoomReset() {
129 	adjustSceneRect();
130 	resetMatrix();
131 }
132 
133 /**
134 	Export currently selected cells to the clipboard before setting them as
135 	empty.
136 	@return the list of cells copied to the clipboard
137 */
cut()138 QList<TitleBlockCell *> TitleBlockTemplateView::cut() {
139 	if (!tbtemplate_) return(QList<TitleBlockCell *>());
140 	QList<TitleBlockCell *> copied_cells = copy();
141 
142 	if (!copied_cells.isEmpty()) {
143 		CutTemplateCellsCommand *cut_command = new CutTemplateCellsCommand(tbtemplate_);
144 		cut_command -> setCutCells(copied_cells);
145 		requestGridModification(cut_command);
146 	}
147 	return(copied_cells);
148 }
149 
150 /**
151 	Export currently selected cells to the clipboard.
152 	@return the list of cells copied to the clipboard
153 */
copy()154 QList<TitleBlockCell *> TitleBlockTemplateView::copy() {
155 	if (!tbtemplate_) return(QList<TitleBlockCell *>());
156 	QList<TitleBlockCell *> copied_cells = selectedCells();
157 
158 	QDomDocument xml_export;
159 	QDomElement tbtpartial = xml_export.createElement("titleblocktemplate-partial");
160 	xml_export.appendChild(tbtpartial);
161 	foreach (TitleBlockCell *cell, copied_cells) {
162 		tbtemplate_ -> exportCellToXml(cell, tbtpartial);
163 		tbtpartial.setAttribute("row", cell -> num_row);
164 		tbtpartial.setAttribute("col", cell -> num_col);
165 		tbtpartial.setAttribute("row_span", cell -> row_span);
166 		tbtpartial.setAttribute("col_span", cell -> col_span);
167 	}
168 
169 	QClipboard *clipboard = QApplication::clipboard();
170 	clipboard -> setText(xml_export.toString());
171 
172 	return(copied_cells);
173 }
174 
175 /**
176 	@return true if the content of the clipboard looks interesting
177 */
mayPaste()178 bool TitleBlockTemplateView::mayPaste() {
179 	// retrieve the clipboard content
180 	QClipboard *clipboard = QApplication::clipboard();
181 	return(clipboard -> text().contains("<titleblocktemplate-partial"));
182 }
183 
184 /**
185 	@return a list containing the pasted cells
186 */
pastedCells()187 QList<TitleBlockCell> TitleBlockTemplateView::pastedCells() {
188 	QList<TitleBlockCell> pasted_cells;
189 
190 	// retrieve the clipboard content and parse it as XML
191 	QClipboard *clipboard = QApplication::clipboard();
192 	QDomDocument xml_import;
193 
194 	if (!xml_import.setContent(clipboard -> text().trimmed())) {
195 		return(pasted_cells);
196 	}
197 
198 	// ensure the XML document describes cells that can be pasted
199 	if (xml_import.documentElement().tagName() != "titleblocktemplate-partial") {
200 		return(pasted_cells);
201 	}
202 
203 	// load pasted cells
204 	QDomElement paste_root = xml_import.documentElement();
205 	for (QDomElement e = paste_root.firstChildElement() ; !e.isNull() ; e = e.nextSiblingElement()) {
206 		if (e.tagName() == "empty" || e.tagName() == "field" || e.tagName() == "logo") {
207 			TitleBlockCell cell;
208 			cell.loadContentFromXml(e);
209 			int row_num = -1, col_num = -1, row_span = -1, col_span = -1;
210 			if (!QET::attributeIsAnInteger(e, "row", &row_num) || row_num < 0) {
211 				continue;
212 			}
213 			if (!QET::attributeIsAnInteger(e, "col", &col_num) || col_num < 0) {
214 				continue;
215 			}
216 			cell.num_row = row_num;
217 			cell.num_col = col_num;
218 
219 			// parse the rowspan and colspan attributes
220 			if (QET::attributeIsAnInteger(e, "rowspan", &row_span) && row_span > 0) {
221 				cell.row_span = row_span;
222 			}
223 
224 			if (QET::attributeIsAnInteger(e, "colspan", &col_span) && col_span > 0) {
225 				cell.col_span = col_span;
226 			}
227 			pasted_cells << cell;
228 		}
229 	}
230 	return(pasted_cells);
231 }
232 
233 /**
234 	Import the cells described in the clipboard.
235 */
paste()236 void TitleBlockTemplateView::paste() {
237 	if (!tbtemplate_) return;
238 	QList<TitleBlockCell> pasted_cells = pastedCells();
239 	if (!pasted_cells.count()) return;
240 
241 	// the top left cell among the selected ones will be used to position the pasted cells
242 	TitleBlockTemplateVisualCell *selected_cell = selectedCellsSet().topLeftCell();
243 	if (!selected_cell) return;
244 	TitleBlockCell *erased_cell = selected_cell -> cell();
245 	if (!erased_cell) return;
246 
247 	// change num_row and num_col attributes of pasted cells so they get positionned relatively to selected_cell
248 	normalizeCells(pasted_cells, erased_cell -> num_row, erased_cell -> num_col);
249 
250 	PasteTemplateCellsCommand *paste_command = new PasteTemplateCellsCommand(tbtemplate_);
251 	foreach (TitleBlockCell cell, pasted_cells) {
252 		TitleBlockCell *erased_cell = tbtemplate_ -> cell(cell.num_row, cell.num_col);
253 		if (!erased_cell) continue;
254 		paste_command -> addCell(erased_cell, *erased_cell, cell);
255 	}
256 
257 	requestGridModification(paste_command);
258 }
259 
260 /**
261 	Add a column right after the last one.
262 */
addColumnAtEnd()263 void TitleBlockTemplateView::addColumnAtEnd() {
264 	if (read_only_) return;
265 	requestGridModification(ModifyTemplateGridCommand::addColumn(tbtemplate_, tbtemplate_ -> columnsCount()));
266 }
267 
268 /**
269 	Add a row right after the last one.
270 */
addRowAtEnd()271 void TitleBlockTemplateView::addRowAtEnd() {
272 	if (read_only_) return;
273 	requestGridModification(ModifyTemplateGridCommand::addRow(tbtemplate_, tbtemplate_ -> rowsCount()));
274 }
275 
276 /**
277 	Add a column right before the last index selected when calling the context
278 	menu.
279 */
addColumnBefore()280 void TitleBlockTemplateView::addColumnBefore() {
281 	if (read_only_) return;
282 	int index = lastContextMenuCellIndex();
283 	if (index == -1) return;
284 	requestGridModification(ModifyTemplateGridCommand::addColumn(tbtemplate_, index));
285 }
286 
287 /**
288 	Add a row right before the last index selected when calling the context
289 	menu.
290 */
addRowBefore()291 void TitleBlockTemplateView::addRowBefore() {
292 	if (read_only_) return;
293 	int index = lastContextMenuCellIndex();
294 	if (index == -1) return;
295 	requestGridModification(ModifyTemplateGridCommand::addRow(tbtemplate_, index));
296 }
297 
298 /**
299 	Add a column right after the last index selected when calling the context
300 	menu.
301 */
addColumnAfter()302 void TitleBlockTemplateView::addColumnAfter() {
303 	if (read_only_) return;
304 	int index = lastContextMenuCellIndex();
305 	if (index == -1) return;
306 	requestGridModification(ModifyTemplateGridCommand::addColumn(tbtemplate_, index + 1));
307 }
308 
309 /**
310 	Add a row right after the last index selected when calling the context
311 	menu.
312 */
addRowAfter()313 void TitleBlockTemplateView::addRowAfter() {
314 	if (read_only_) return;
315 	int index = lastContextMenuCellIndex();
316 	if (index == -1) return;
317 	requestGridModification(ModifyTemplateGridCommand::addRow(tbtemplate_, index + 1));
318 }
319 
320 /**
321 	Edit the width of a column.
322 	@param cell (optional) HelperCell of the column to be modified. If 0, this
323 	method uses the last index selected when calling the context menu.
324 */
editColumn(HelperCell * cell)325 void TitleBlockTemplateView::editColumn(HelperCell *cell) {
326 	int index = cell ? cell -> index : lastContextMenuCellIndex();
327 	if (index == -1) return;
328 
329 	TitleBlockDimension dimension_before = tbtemplate_ -> columnDimension(index);
330 	TitleBlockDimensionWidget dialog(true, this);
331 	dialog.setReadOnly(read_only_);
332 	dialog.setWindowTitle(tr("Changer la largeur de la colonne", "window title when changing a column with"));
333 	dialog.label() -> setText(tr("Largeur :", "text before the spinbox to change a column width"));
334 	dialog.setValue(dimension_before);
335 	int user_answer = dialog.exec();
336 	if (!read_only_ && user_answer == QDialog::Accepted) {
337 		ModifyTemplateDimension *command = new ModifyTemplateDimension(tbtemplate_);
338 		command -> setType(false);
339 		command -> setIndex(index);
340 		command -> setDimensionBefore(dimension_before);
341 		command -> setDimensionAfter(dialog.value());
342 		requestGridModification(command);
343 	}
344 }
345 
346 /**
347 	Edit the height of a row.
348 	@param cell (optional) HelperCell of the row to be modified. If 0, this
349 	method uses the last index selected when calling the context menu.
350 */
editRow(HelperCell * cell)351 void TitleBlockTemplateView::editRow(HelperCell *cell) {
352 	int index = cell ? cell -> index : lastContextMenuCellIndex();
353 	if (index == -1) return;
354 
355 	TitleBlockDimension dimension_before = TitleBlockDimension(tbtemplate_ -> rowDimension(index));
356 	TitleBlockDimensionWidget dialog(false, this);
357 	dialog.setReadOnly(read_only_);
358 	dialog.setWindowTitle(tr("Changer la hauteur de la ligne", "window title when changing a row height"));
359 	dialog.label() -> setText(tr("Hauteur :", "text before the spinbox to change a row height"));
360 	dialog.setValue(dimension_before);
361 	int user_answer = dialog.exec();
362 	if (!read_only_ && user_answer == QDialog::Accepted) {
363 		ModifyTemplateDimension *command = new ModifyTemplateDimension(tbtemplate_);
364 		command -> setType(true);
365 		command -> setIndex(index);
366 		command -> setDimensionBefore(dimension_before);
367 		command -> setDimensionAfter(dialog.value());
368 		requestGridModification(command);
369 	}
370 }
371 
372 /**
373 	Remove the column at the last index selected when calling the context menu.
374 */
deleteColumn()375 void TitleBlockTemplateView::deleteColumn() {
376 	int index = lastContextMenuCellIndex();
377 	if (index == -1) return;
378 	requestGridModification(ModifyTemplateGridCommand::deleteColumn(tbtemplate_, index));
379 }
380 
381 /**
382 	Remove the row at the last index selected when calling the context menu.
383 */
deleteRow()384 void TitleBlockTemplateView::deleteRow() {
385 	int index = lastContextMenuCellIndex();
386 	if (index == -1) return;
387 	requestGridModification(ModifyTemplateGridCommand::deleteRow(tbtemplate_, index));
388 }
389 
390 /**
391 	Merge the selected cells.
392 */
mergeSelectedCells()393 void TitleBlockTemplateView::mergeSelectedCells() {
394 	// retrieve the selected cells
395 	TitleBlockTemplateCellsSet selected_cells = selectedCellsSet();
396 
397 	MergeCellsCommand *merge_command = new MergeCellsCommand(selected_cells, tbtemplate_);
398 	if (merge_command -> isValid()) requestGridModification(merge_command);
399 }
400 
401 /**
402 	Split the selected cell.
403 */
splitSelectedCell()404 void TitleBlockTemplateView::splitSelectedCell() {
405 	// retrieve the selected cells
406 	TitleBlockTemplateCellsSet selected_cells = selectedCellsSet();
407 
408 	SplitCellsCommand *split_command = new SplitCellsCommand(selected_cells, tbtemplate_);
409 	if (split_command -> isValid()) requestGridModification(split_command);
410 }
411 
412 /**
413 	Reimplement the way the background is drawn to render the title block
414 	template.
415 */
drawBackground(QPainter * painter,const QRectF & rect)416 void TitleBlockTemplateView::drawBackground(QPainter *painter, const QRectF &rect) {
417 	QGraphicsView::drawBackground(painter, rect);
418 	if (!tbtemplate_) return; // TODO shouldn't we draw a large uniform rect?
419 }
420 
421 /**
422 	@return the selected logical cells, not including the spanned ones.
423 */
selectedCells() const424 QList<TitleBlockCell *> TitleBlockTemplateView::selectedCells() const {
425 	return(selectedCellsSet().cells(false).toList());
426 }
427 
428 /**
429 	@return the selected visual cells.
430 */
selectedCellsSet() const431 TitleBlockTemplateCellsSet TitleBlockTemplateView::selectedCellsSet() const {
432 	return(makeCellsSetFromGraphicsItems(scene() -> selectedItems()));
433 }
434 
435 /**
436 	@return the visual cells contained in the \a rect
437 	@param rect Rectangle in the coordinates of the QGraphicsWidget
438 	representing the title block template.
439 */
cells(const QRectF & rect) const440 TitleBlockTemplateCellsSet TitleBlockTemplateView::cells(const QRectF &rect) const {
441 	QPolygonF mapped_rect(form_ -> mapToScene(rect));
442 	QList<QGraphicsItem *> items = scene() -> items(mapped_rect, Qt::IntersectsItemShape);
443 	return(makeCellsSetFromGraphicsItems(items));
444 }
445 
446 /**
447 	@param can_merge If non-zero, will be changed to reflect whether selected cells may be merged
448 	@param can_merge If non-zero, will be changed to reflect whether selected cells may be splitted
449 	@param count     If non-zero, will be changed to reflect the number of selected cells
450 */
analyzeSelectedCells(bool * can_merge,bool * can_split,int * count)451 void TitleBlockTemplateView::analyzeSelectedCells(bool *can_merge, bool *can_split, int *count) {
452 	if (!can_merge && !can_split) return;
453 
454 	if (!tbtemplate_) {
455 		if (can_merge) *can_merge = false;
456 		if (can_split) *can_split = false;
457 		return;
458 	}
459 
460 	// retrieve the selected cells
461 	TitleBlockTemplateCellsSet selected_cells = selectedCellsSet();
462 
463 	if (can_merge) {
464 		*can_merge = MergeCellsCommand::canMerge(selected_cells, tbtemplate_);
465 	}
466 	if (can_split) {
467 		*can_split = SplitCellsCommand::canSplit(selected_cells, tbtemplate_);
468 	}
469 	if (count) {
470 		*count = selectedCellsSet().count();
471 	}
472 }
473 
474 /**
475 	@return the current size of the rendered title block template
476 */
templateSize() const477 QSizeF TitleBlockTemplateView::templateSize() const {
478 	return(QSizeF(templateWidth(), templateHeight()));
479 }
480 
481 /**
482 	@return the current width of the rendered title block template
483 */
templateWidth() const484 qreal TitleBlockTemplateView::templateWidth() const {
485 	if (!tbtemplate_) return(0);
486 
487 	qreal width = DEFAULT_ROWS_HELPER_CELLS_WIDTH;
488 	// the rendered width may exceed the initially planned preview width
489 	width += qMax<int>(preview_width_, tbtemplate_ -> width(preview_width_));
490 
491 	return(width);
492 }
493 
494 /**
495 	@return the current height of the rendered title block template
496 */
templateHeight() const497 qreal TitleBlockTemplateView::templateHeight() const {
498 	if (!tbtemplate_) return(0);
499 
500 	qreal height = DEFAULT_PREVIEW_HELPER_CELL_HEIGHT;
501 	height += DEFAULT_COLS_HELPER_CELLS_HEIGHT;
502 	height += tbtemplate_ -> height();
503 
504 	return(height);
505 }
506 
507 /**
508 	Handles mouse wheel-related actions
509 	@param e QWheelEvent describing the wheel event
510 */
wheelEvent(QWheelEvent * e)511 void TitleBlockTemplateView::wheelEvent(QWheelEvent *e) {
512 	// si la touche Ctrl est enfoncee, on zoome / dezoome
513 	if (e -> modifiers() & Qt::ControlModifier) {
514 		if (e -> delta() > 0) {
515 			zoomIn();
516 		} else {
517 			zoomOut();
518 		}
519 	} else {
520 		QAbstractScrollArea::wheelEvent(e);
521 	}
522 }
523 
524 /**
525 	@return the zoom factor used by zoomIn() and zoomOut().
526 */
zoomFactor() const527 qreal TitleBlockTemplateView::zoomFactor() const {
528 	return(1.1);
529 }
530 
531 /**
532 	Initialize this view (actions, signals/slots connections, etc.)
533 */
init()534 void TitleBlockTemplateView::init() {
535 	add_column_before_    = new QAction(QET::Icons::EditTableInsertColumnLeft,  tr("Ajouter une colonne (avant)",              "context menu"), this);
536 	add_row_before_       = new QAction(QET::Icons::EditTableInsertRowAbove,    tr("Ajouter une ligne (avant)",                "context menu"), this);
537 	add_column_after_     = new QAction(QET::Icons::EditTableInsertColumnRight, tr("Ajouter une colonne (après)",           "context menu"), this);
538 	add_row_after_        = new QAction(QET::Icons::EditTableInsertRowUnder,    tr("Ajouter une ligne (après)",             "context menu"), this);
539 	edit_column_dim_      = new QAction(                                        tr("Modifier les dimensions de cette colonne", "context menu"), this);
540 	edit_row_dim_         = new QAction(                                        tr("Modifier les dimensions de cette ligne",   "context menu"), this);
541 	delete_column_        = new QAction(QET::Icons::EditTableDeleteColumn,      tr("Supprimer cette colonne",                  "context menu"), this);
542 	delete_row_           = new QAction(QET::Icons::EditTableDeleteRow,         tr("Supprimer cette ligne",                    "context menu"), this);
543 	change_preview_width_ = new QAction(                                        tr("Modifier la largeur de cet aperçu",     "context menu"), this);
544 
545 	connect(add_column_before_,    SIGNAL(triggered()), this, SLOT(addColumnBefore()));
546 	connect(add_row_before_,       SIGNAL(triggered()), this, SLOT(addRowBefore()));
547 	connect(add_column_after_,     SIGNAL(triggered()), this, SLOT(addColumnAfter()));
548 	connect(add_row_after_,        SIGNAL(triggered()), this, SLOT(addRowAfter()));
549 	connect(edit_column_dim_,      SIGNAL(triggered()), this, SLOT(editColumn()));
550 	connect(edit_row_dim_,         SIGNAL(triggered()), this, SLOT(editRow()));
551 	connect(delete_column_,        SIGNAL(triggered()), this, SLOT(deleteColumn()));
552 	connect(delete_row_,           SIGNAL(triggered()), this, SLOT(deleteRow()));
553 	connect(change_preview_width_, SIGNAL(triggered()), this, SLOT(changePreviewWidth()));
554 
555 	setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
556 	setBackgroundBrush(QBrush(QColor(248, 255, 160)));
557 
558 	connect(scene(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged()));
559 }
560 
561 /**
562 	Apply the columns widths currently specified by the edited title block
563 	template.
564 	@param animate true to animate the change, false otherwise.
565 */
applyColumnsWidths(bool animate)566 void TitleBlockTemplateView::applyColumnsWidths(bool animate) {
567 	// the first column is dedicated to helper cells showing the rows height
568 	tbgrid_ -> setColumnFixedWidth(0, DEFAULT_ROWS_HELPER_CELLS_WIDTH);
569 	tbgrid_ -> setColumnSpacing(0, 0);
570 
571 	// we apply the other columns width based on the title block template data
572 	QList<int> widths = tbtemplate_ -> columnsWidth(preview_width_);
573 	int total_applied_width = 0;
574 	for (int i = 0 ; i < widths.count() ; ++ i) {
575 		int applied_width = qMax(0, widths.at(i));
576 		tbgrid_ -> setColumnSpacing(COL_OFFSET + i, 0);
577 		if (!animate) {
578 			// no animation on first call
579 			tbgrid_ -> setColumnFixedWidth(COL_OFFSET + i, widths.at(i));
580 		} else {
581 			GridLayoutAnimation *animation = new GridLayoutAnimation(tbgrid_, form_);
582 			animation -> setIndex(COL_OFFSET + i);
583 			animation -> setActsOnRows(false);
584 			animation -> setStartValue(QVariant(tbgrid_ -> columnMinimumWidth(COL_OFFSET + i)));
585 			animation -> setEndValue(QVariant(1.0 * applied_width));
586 			animation -> setDuration(500);
587 			connect(animation, SIGNAL(finished()), this, SLOT(updateColumnsHelperCells()));
588 			animation -> start(QAbstractAnimation::DeleteWhenStopped);
589 		}
590 		total_applied_width += applied_width;
591 	}
592 	if (!animate) updateColumnsHelperCells();
593 	++ apply_columns_widths_count_;
594 
595 	// we systematically parameter some cells
596 	total_width_helper_cell_ -> split_size = 0;
597 	tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, widths.count());
598 	removeItem(extra_cells_width_helper_cell_);
599 
600 	if (total_applied_width < preview_width_) {
601 		// preview_width is greater than the sum of cells widths
602 		// we add an extra column with a helper cell
603 		tbgrid_ -> addItem(extra_cells_width_helper_cell_, ROW_OFFSET - 1, COL_OFFSET + widths.count(), tbtemplate_ -> rowsCount() + 1, 1);
604 		tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, widths.count() + 1);
605 		tbgrid_ -> setColumnFixedWidth(COL_OFFSET + widths.count(), preview_width_ - total_applied_width);
606 		extra_cells_width_helper_cell_ -> setLabel(
607 			QString(
608 				tr("[%1px]","content of the extra cell added when the total width of cells is less than the preview width")
609 			).arg(preview_width_ - total_applied_width)
610 		);
611 	} else if (total_applied_width > preview_width_) {
612 		// preview width is smaller than the sum of cells widths
613 		// we draw an extra header within th "preview width" cell.
614 		tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, widths.count());
615 		total_width_helper_cell_ -> split_background_color = QColor(Qt::red);
616 		total_width_helper_cell_ -> split_foreground_color = QColor(Qt::black);
617 		total_width_helper_cell_ -> split_label = QString(
618 			tr("[%1px]", "content of the extra helper cell added when the total width of cells is greather than the preview width")
619 		).arg(total_applied_width - preview_width_);
620 		total_width_helper_cell_ -> split_size = total_applied_width - preview_width_;
621 	}
622 
623 	updateDisplayedMinMaxWidth();
624 }
625 
626 /**
627 	Apply the rows heights currently specified by the edited title block
628 	template.
629 	@param animate true to animate the change, false otherwise.
630 */
applyRowsHeights(bool animate)631 void TitleBlockTemplateView::applyRowsHeights(bool animate) {
632 	// the first row is dedicated to a helper cell showing the total width
633 	tbgrid_ -> setRowFixedHeight(0, DEFAULT_PREVIEW_HELPER_CELL_HEIGHT);
634 	tbgrid_ -> setRowSpacing(0, 0);
635 	// the second row is dedicated to helper cells showing the columns width
636 	tbgrid_ -> setRowFixedHeight(1, DEFAULT_COLS_HELPER_CELLS_HEIGHT);
637 	tbgrid_ -> setRowSpacing(1, 0);
638 
639 	QList<int> heights = tbtemplate_ -> rowsHeights();
640 	for (int i = 0 ; i < heights.count() ; ++ i) {
641 		tbgrid_ -> setRowSpacing(ROW_OFFSET + i, 0);
642 		if (!animate) {
643 			// no animation on first call
644 			tbgrid_ -> setRowFixedHeight(ROW_OFFSET + i, heights.at(i));
645 		} else {
646 			GridLayoutAnimation *animation = new GridLayoutAnimation(tbgrid_, form_);
647 			animation -> setIndex(ROW_OFFSET + i);
648 			animation -> setActsOnRows(true);
649 			animation -> setStartValue(QVariant(tbgrid_ -> rowMinimumHeight(ROW_OFFSET + i)));
650 			animation -> setEndValue(QVariant(1.0 * heights.at(i)));
651 			animation -> setDuration(500);
652 			connect(animation, SIGNAL(finished()), this, SLOT(updateRowsHelperCells()));
653 			animation -> start(QAbstractAnimation::DeleteWhenStopped);
654 		}
655 
656 	}
657 	if (!animate) updateRowsHelperCells();
658 	++ apply_rows_heights_count_;
659 }
660 
661 /**
662 	Update the content (type and value) of rows helper cells.
663 */
updateRowsHelperCells()664 void TitleBlockTemplateView::updateRowsHelperCells() {
665 	int row_count = tbtemplate_ -> rowsCount();
666 	QList<int> heights = tbtemplate_ -> rowsHeights();
667 	for (int i = 0 ; i < row_count ; ++ i) {
668 		HelperCell *current_row_cell = static_cast<HelperCell *>(tbgrid_ -> itemAt(ROW_OFFSET + i, 0));
669 		if (current_row_cell) {
670 			current_row_cell -> setType(QET::Absolute); // rows always have absolute heights
671 			current_row_cell -> setLabel(QString(tr("%1px", "format displayed in rows helper cells")).arg(heights.at(i)));
672 		}
673 	}
674 }
675 
676 /**
677 	Update the content (type and value) of columns helper cells.
678 */
updateColumnsHelperCells()679 void TitleBlockTemplateView::updateColumnsHelperCells() {
680 	int col_count = tbtemplate_ -> columnsCount();
681 	for (int i = 0 ; i < col_count ; ++ i) {
682 		TitleBlockDimension current_col_dim = tbtemplate_ -> columnDimension(i);
683 		HelperCell *current_col_cell = static_cast<HelperCell *>(tbgrid_ -> itemAt(1, COL_OFFSET + i));
684 		if (current_col_cell) {
685 			current_col_cell -> setType(current_col_dim.type);
686 			current_col_cell -> setLabel(current_col_dim.toString());
687 		}
688 	}
689 }
690 
691 /**
692 	Add the cells (both helper cells and regular visual cells) to the scene to
693 	get a visual representation of the edited title block template.
694 */
addCells()695 void TitleBlockTemplateView::addCells() {
696 	int col_count = tbtemplate_ -> columnsCount();
697 	int row_count = tbtemplate_ -> rowsCount();
698 
699 	// we add a big cell to show the total width
700 	total_width_helper_cell_ = new SplittedHelperCell();
701 	total_width_helper_cell_ -> setType(QET::Absolute);
702 	updateTotalWidthLabel();
703 	total_width_helper_cell_ -> orientation = Qt::Horizontal;
704 	total_width_helper_cell_ -> setActions(QList<QAction *>() << change_preview_width_);
705 	connect(total_width_helper_cell_, SIGNAL(contextMenuTriggered(HelperCell *)), this, SLOT(updateLastContextMenuCell(HelperCell *)));
706 	connect(total_width_helper_cell_, SIGNAL(doubleClicked(HelperCell*)),         this, SLOT(changePreviewWidth()));
707 	tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, col_count);
708 
709 	// we also initialize an extra helper cells that shows the preview width is
710 	// too long for the current cells widths
711 	extra_cells_width_helper_cell_ = new HelperCell();
712 	extra_cells_width_helper_cell_ -> background_color = QColor(Qt::red);
713 
714 	// we add one cell per column to show their respective width
715 	for (int i = 0 ; i < col_count ; ++ i) {
716 		TitleBlockDimension current_col_dim = tbtemplate_ -> columnDimension(i);
717 		HelperCell *current_col_cell = new HelperCell();
718 		current_col_cell -> setType(current_col_dim.type);
719 		current_col_cell -> setLabel(current_col_dim.toString());
720 		current_col_cell -> setActions(columnsActions());
721 		current_col_cell -> orientation = Qt::Horizontal;
722 		current_col_cell -> index = i;
723 		connect(current_col_cell, SIGNAL(contextMenuTriggered(HelperCell *)), this, SLOT(updateLastContextMenuCell(HelperCell *)));
724 		connect(current_col_cell, SIGNAL(doubleClicked(HelperCell*)),         this, SLOT(editColumn(HelperCell *)));
725 		tbgrid_ -> addItem(current_col_cell, 1, COL_OFFSET + i, 1, 1);
726 	}
727 
728 	// we add one cell per row to show their respective height
729 	QList<int> heights = tbtemplate_ -> rowsHeights();
730 	for (int i = 0 ; i < row_count ; ++ i) {
731 		HelperCell *current_row_cell = new HelperCell();
732 		current_row_cell -> setType(QET::Absolute); // rows always have absolute heights
733 		current_row_cell -> setLabel(QString(tr("%1px")).arg(heights.at(i)));
734 		current_row_cell -> orientation = Qt::Vertical;
735 		current_row_cell -> index = i;
736 		current_row_cell -> setActions(rowsActions());
737 		connect(current_row_cell, SIGNAL(contextMenuTriggered(HelperCell *)), this, SLOT(updateLastContextMenuCell(HelperCell *)));
738 		connect(current_row_cell, SIGNAL(doubleClicked(HelperCell*)),         this, SLOT(editRow(HelperCell *)));
739 		tbgrid_ -> addItem(current_row_cell, ROW_OFFSET + i, 0, 1, 1);
740 	}
741 
742 	// eventually we add the cells composing the titleblock template
743 	for (int i = 0 ; i < col_count ; ++ i) {
744 		for (int j = 0 ; j < row_count ; ++ j) {
745 			TitleBlockCell *cell = tbtemplate_ -> cell(j, i);
746 			if (cell -> spanner_cell) continue;
747 			TitleBlockTemplateVisualCell *cell_item = new TitleBlockTemplateVisualCell();
748 			cell_item -> setTemplateCell(tbtemplate_, cell);
749 
750 			int row_span = 0, col_span = 0;
751 			if (cell -> span_state != TitleBlockCell::Disabled) {
752 				row_span = cell -> applied_row_span;
753 				col_span = cell -> applied_col_span;
754 			}
755 			tbgrid_ -> addItem(cell_item, ROW_OFFSET + j, COL_OFFSET + i, row_span + 1, col_span + 1);
756 		}
757 	}
758 }
759 
760 /**
761 	Refresh the regular cells.
762 */
refresh()763 void TitleBlockTemplateView::refresh() {
764 	int col_count = tbtemplate_ -> columnsCount();
765 	int row_count = tbtemplate_ -> rowsCount();
766 	if (row_count < 1 || col_count < 1) return;
767 
768 	for (int i = 0 ; i < col_count ; ++ i) {
769 		for (int j = 0 ; j < row_count ; ++ j) {
770 			if (QGraphicsLayoutItem *item = tbgrid_ -> itemAt(ROW_OFFSET + j, COL_OFFSET + i)) {
771 				if (QGraphicsItem *qgi = dynamic_cast<QGraphicsItem *>(item)) {
772 					qgi -> update();
773 				}
774 			}
775 		}
776 	}
777 }
778 
779 /**
780 	Ask the user a new width for the preview
781 */
changePreviewWidth()782 void TitleBlockTemplateView::changePreviewWidth() {
783 	TitleBlockDimensionWidget dialog(false, this);
784 	dialog.setWindowTitle(tr("Changer la largeur de l'aperçu"));
785 	dialog.label() -> setText(tr("Largeur de l'aperçu :"));
786 	dialog.setValue(TitleBlockDimension(preview_width_));
787 	if (dialog.exec() == QDialog::Accepted) {
788 		setPreviewWidth(dialog.value().value);
789 	}
790 }
791 
792 /**
793 	Fill the layout with empty cells where needed.
794 */
fillWithEmptyCells()795 void TitleBlockTemplateView::fillWithEmptyCells() {
796 	int col_count = tbtemplate_ -> columnsCount();
797 	int row_count = tbtemplate_ -> rowsCount();
798 	if (row_count < 1 || col_count < 1) return;
799 
800 	for (int i = 0 ; i < col_count ; ++ i) {
801 		for (int j = 0 ; j < row_count ; ++ j) {
802 			if (tbgrid_ -> itemAt(ROW_OFFSET + j, COL_OFFSET + i)) continue;
803 			TitleBlockTemplateVisualCell *cell_item = new TitleBlockTemplateVisualCell();
804 			if (TitleBlockCell *target_cell = tbtemplate_ -> cell(j, i)) {
805 				qDebug() << Q_FUNC_INFO << "target_cell" << target_cell;
806 				cell_item -> setTemplateCell(tbtemplate_, target_cell);
807 			}
808 			tbgrid_ -> addItem(cell_item, ROW_OFFSET + j, COL_OFFSET + i);
809 		}
810 	}
811 }
812 
813 /**
814 	@param event Object describing the received event
815 */
event(QEvent * event)816 bool TitleBlockTemplateView::event(QEvent *event) {
817 	if (first_activation_ && event -> type() == QEvent::WindowActivate) {
818 		QTimer::singleShot(250, this, SLOT(zoomFit()));
819 		first_activation_ = false;
820 	}
821 	return(QGraphicsView::event(event));
822 }
823 
824 /**
825 	Given a cells list, change their position so the top left one is at row \a x and column \a y.
826 	@param cells Cells list
827 */
normalizeCells(QList<TitleBlockCell> & cells,int x,int y) const828 void TitleBlockTemplateView::normalizeCells(QList<TitleBlockCell> &cells, int x, int y) const {
829 	if (!cells.count()) return;
830 
831 	int min_row = cells.at(0).num_row;
832 	int min_col = cells.at(0).num_col;
833 	for (int i = 1 ; i < cells.count() ; ++ i) {
834 		if (cells.at(i).num_row < min_row) min_row = cells.at(i).num_row;
835 		if (cells.at(i).num_col < min_col) min_col = cells.at(i).num_col;
836 	}
837 	for (int i = 0 ; i < cells.count() ; ++ i) {
838 		cells[i].num_row = cells[i].num_row - min_row + x;
839 		cells[i].num_col = cells[i].num_col - min_col + y;
840 	}
841 }
842 
843 /**
844 	Load the \a tbt title block template.
845 	If a different template was previously loaded, it is deleted.
846 
847 */
loadTemplate(TitleBlockTemplate * tbt)848 void TitleBlockTemplateView::loadTemplate(TitleBlockTemplate *tbt) {
849 	if (tbgrid_) {
850 		scene() -> removeItem(form_);
851 		// also deletes TemplateCellPreview because, according to the
852 		// documentation, QGraphicsGridLayout takes ownership of the items.
853 		form_ -> deleteLater();
854 	}
855 	if (tbtemplate_ && tbtemplate_ != tbt) {
856 		delete tbtemplate_;
857 	}
858 
859 	tbtemplate_ = tbt;
860 
861 	// initialize a grid layout with no margin
862 	tbgrid_ = new QGraphicsGridLayout();
863 	tbgrid_ -> setContentsMargins(0, 0, 0, 0);
864 	// add cells defined by the title block template in this layout
865 	addCells();
866 	// fill potential holes in the grid with empty cells
867 	fillWithEmptyCells();
868 	// apply rows and columns dimensions
869 	applyColumnsWidths(false);
870 	applyRowsHeights(false);
871 
872 	// assign the layout to a basic QGraphicsWidget
873 	form_ = new QGraphicsWidget();
874 	// enforce the layout direction to avoid reversing the template rendering
875 	form_ -> setLayoutDirection(Qt::LeftToRight);
876 	form_ -> setLayout(tbgrid_);
877 	scene() -> addItem(form_);
878 	adjustSceneRect();
879 }
880 
881 /**
882 	@return the list of rows-specific actions.
883 */
rowsActions() const884 QList<QAction *> TitleBlockTemplateView::rowsActions() const {
885 	return QList<QAction *>() << add_row_before_<< edit_row_dim_ << add_row_after_ << delete_row_;
886 }
887 
888 /**
889 	@return the list of columns-specific actions.
890 */
columnsActions() const891 QList<QAction *> TitleBlockTemplateView::columnsActions() const {
892 	return QList<QAction *>() << add_column_before_ << edit_column_dim_ << add_column_after_ << delete_column_;
893 }
894 
895 /**
896 	Update the displayed layout. Call this function to refresh the display
897 	after the rendered title block template has been "deeply" modified, e.g.
898 	rows/columns have been added/modified or cells were merged/splitted.
899 */
updateLayout()900 void TitleBlockTemplateView::updateLayout() {
901 	// TODO we should try to update the grid instead of deleting-and-reloading it
902 	loadTemplate(tbtemplate_);
903 }
904 
905 /**
906 	Update the displayed layout. Call this function when the dimensions of
907 	rows changed.
908 */
rowsDimensionsChanged()909 void TitleBlockTemplateView::rowsDimensionsChanged() {
910 	applyRowsHeights();
911 }
912 
913 /**
914 	Update the displayed layout. Call this function when the dimensions of
915 	columns changed.
916 */
columnsDimensionsChanged()917 void TitleBlockTemplateView::columnsDimensionsChanged() {
918 	applyColumnsWidths();
919 }
920 
921 /**
922 	Update the tooltip that displays the minimum and/or maximum width of the
923 	template.
924 */
updateDisplayedMinMaxWidth()925 void TitleBlockTemplateView::updateDisplayedMinMaxWidth() {
926 	if (!tbtemplate_) return;
927 	int min_width = tbtemplate_ -> minimumWidth();
928 	int max_width = tbtemplate_ -> maximumWidth();
929 
930 	QString min_max_width_sentence;
931 	if (max_width != -1) {
932 		min_max_width_sentence = QString(
933 			tr(
934 				"Longueur minimale : %1px\nLongueur maximale : %2px\n",
935 				"tooltip showing the minimum and/or maximum width of the edited template"
936 			)
937 		).arg(min_width).arg(max_width);
938 	} else {
939 		min_max_width_sentence = QString(
940 			tr(
941 				"Longueur minimale : %1px\n",
942 				"tooltip showing the minimum width of the edited template"
943 			)
944 		).arg(min_width);
945 	}
946 
947 	// the tooltip may also display the split label for readability purpose
948 	if (total_width_helper_cell_ -> split_size) {
949 		min_max_width_sentence += "---\n";
950 		min_max_width_sentence += total_width_helper_cell_ -> split_label;
951 	}
952 
953 	total_width_helper_cell_ -> setToolTip(makePrettyToolTip(min_max_width_sentence));
954 }
955 
956 /**
957 	@param read_only whether this view should be read only.
958 
959 */
setReadOnly(bool read_only)960 void TitleBlockTemplateView::setReadOnly(bool read_only) {
961 	if (read_only_ == read_only) return;
962 
963 	read_only_ = read_only;
964 	add_column_before_ -> setEnabled(!read_only_);
965 	add_row_before_    -> setEnabled(!read_only_);
966 	add_column_after_  -> setEnabled(!read_only_);
967 	add_row_after_     -> setEnabled(!read_only_);
968 	delete_column_     -> setEnabled(!read_only_);
969 	delete_row_        -> setEnabled(!read_only_);
970 }
971 
972 /**
973 	Set the new preview width to width
974 	@param width new preview width
975 */
setPreviewWidth(int width)976 void TitleBlockTemplateView::setPreviewWidth(int width) {
977 	if (preview_width_ == width) return;
978 	int former_preview_width = preview_width_;
979 	preview_width_ = width;
980 	if (tbgrid_) {
981 		applyColumnsWidths();
982 		updateTotalWidthLabel();
983 		//adjustSceneRect();
984 		centerOn(form_);
985 	}
986 	emit(previewWidthChanged(former_preview_width, preview_width_));
987 }
988 
989 /**
990 	Update the label of the helper cell that indicates the preview width.
991 */
updateTotalWidthLabel()992 void TitleBlockTemplateView::updateTotalWidthLabel() {
993 	if (!total_width_helper_cell_) return;
994 	total_width_helper_cell_ -> label = QString(
995 		tr(
996 			"Largeur totale pour cet aperçu : %1px",
997 			"displayed at the top of the preview when editing a title block template"
998 		)
999 	).arg(preview_width_);
1000 }
1001 
1002 /**
1003 	Emit the gridModificationRequested() signal with \a command after having set
1004 	its view component.
1005 	@see TitleBlockTemplateCommand::setView()
1006 	@param command A command object modifying the rendered title block template.
1007 */
requestGridModification(TitleBlockTemplateCommand * command)1008 void TitleBlockTemplateView::requestGridModification(TitleBlockTemplateCommand *command) {
1009 	if (!command) return;
1010 	command -> setView(this);
1011 	emit(gridModificationRequested(command));
1012 }
1013 
1014 /**
1015 	@return the last index selected when triggering the context menu.
1016 	@see updateLastContextMenuCell
1017 */
lastContextMenuCellIndex() const1018 int TitleBlockTemplateView::lastContextMenuCellIndex() const {
1019 	if (last_context_menu_cell_) {
1020 		return(last_context_menu_cell_ -> index);
1021 	}
1022 	return(-1);
1023 }
1024 
1025 /**
1026 	@param item an item supposed to be contained in the grid layout.
1027 	@return the flat index if this item, or -1 if it could not be found.
1028 */
indexOf(QGraphicsLayoutItem * item)1029 int TitleBlockTemplateView::indexOf(QGraphicsLayoutItem *item) {
1030 	for (int i = 0 ; i < tbgrid_ -> count() ; ++i) {
1031 		if (item == tbgrid_ -> itemAt(i)) return(i);
1032 	}
1033 	return(-1);
1034 }
1035 
1036 /**
1037 	Removes an item from the grid layout
1038 	@param item an item supposed to be contained in the grid layout.
1039 */
removeItem(QGraphicsLayoutItem * item)1040 void TitleBlockTemplateView::removeItem(QGraphicsLayoutItem *item) {
1041 	int index = indexOf(item);
1042 	if (index != -1) {
1043 		tbgrid_ -> removeAt(index);
1044 		// trick: we also have to remove the item from the scene
1045 		if (QGraphicsScene *current_scene = scene()) {
1046 			if (QGraphicsItem *qgi = item -> graphicsItem()) {
1047 				current_scene -> removeItem(qgi);
1048 			}
1049 		}
1050 	}
1051 }
1052 
1053 /**
1054 	@param a list of QGraphicsItem
1055 	@return the corresponding TitleBlockTemplateCellsSet
1056 */
makeCellsSetFromGraphicsItems(const QList<QGraphicsItem * > & items) const1057 TitleBlockTemplateCellsSet TitleBlockTemplateView::makeCellsSetFromGraphicsItems(const QList<QGraphicsItem *> &items) const {
1058 	TitleBlockTemplateCellsSet set(this);
1059 	foreach (QGraphicsItem *item, items) {
1060 		if (TitleBlockTemplateVisualCell *cell_view = dynamic_cast<TitleBlockTemplateVisualCell *>(item)) {
1061 			if (cell_view -> cell() && cell_view -> cell() -> num_row != -1) {
1062 				set << cell_view;
1063 			}
1064 		}
1065 	}
1066 	return(set);
1067 }
1068 
1069 /*
1070 	@param a text string
1071 	@return an HTML string that can be passed to setToolTip()
1072 */
makePrettyToolTip(const QString & string)1073 QString TitleBlockTemplateView::makePrettyToolTip(const QString &string) {
1074 	QString css_style = QString("white-space: pre;");
1075 
1076 	QString final_tooltip_content = QString(
1077 		"<div style=\"%1\">%2</div>"
1078 	).arg(css_style).arg(string);
1079 
1080 	return(final_tooltip_content);
1081 }
1082 
1083 /**
1084 	Stores \a last_context_menu_cell as being the last helper cell the context
1085 	menu was triggered on.
1086 */
updateLastContextMenuCell(HelperCell * last_context_menu_cell)1087 void TitleBlockTemplateView::updateLastContextMenuCell(HelperCell *last_context_menu_cell) {
1088 	last_context_menu_cell_ = last_context_menu_cell;
1089 }
1090 
1091 /**
1092 	Adjusts the bounding rect of the scene.
1093 */
adjustSceneRect()1094 void TitleBlockTemplateView::adjustSceneRect() {
1095 	QRectF old_scene_rect = scene() -> sceneRect();
1096 
1097 	// rectangle including everything on the scene
1098 	QRectF bounding_rect(QPointF(0, 0), templateSize());
1099 	scene() -> setSceneRect(bounding_rect);
1100 
1101 	// met a jour la scene
1102 	scene() -> update(old_scene_rect.united(bounding_rect));
1103 }
1104 
1105