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