/* Copyright 2006-2012 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. QElectroTech is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ #include "templateview.h" #include "templatevisualcell.h" #include "gridlayoutanimation.h" #include "helpercell.h" #include "splittedhelpercell.h" #include "templatecommands.h" #include "templatecellsset.h" #include "dimensionwidget.h" #include "qeticons.h" #define ROW_OFFSET 2 #define COL_OFFSET 1 #define DEFAULT_PREVIEW_WIDTH 600 #define DEFAULT_PREVIEW_HELPER_CELL_HEIGHT 15 #define DEFAULT_COLS_HELPER_CELLS_HEIGHT 15 #define DEFAULT_ROWS_HELPER_CELLS_WIDTH 50 /** Constructor @param parent Parent QWidget. */ TitleBlockTemplateView::TitleBlockTemplateView(QWidget *parent) : QGraphicsView(parent), tbtemplate_(0), tbgrid_(0), form_(0), preview_width_(DEFAULT_PREVIEW_WIDTH), apply_columns_widths_count_(0), apply_rows_heights_count_(0), first_activation_(true), read_only_(false) { init(); } /** Constructor @param parent Parent QWidget. */ TitleBlockTemplateView::TitleBlockTemplateView(QGraphicsScene *scene, QWidget *parent) : QGraphicsView(scene, parent), tbtemplate_(0), tbgrid_(0), preview_width_(DEFAULT_PREVIEW_WIDTH), apply_columns_widths_count_(0), apply_rows_heights_count_(0), first_activation_(true), read_only_(false) { init(); } /** Destructor */ TitleBlockTemplateView::~TitleBlockTemplateView() { } /** @param tbtemplate Title block template to be rendered by this view. If set to zero, the View will render nothing. */ void TitleBlockTemplateView::setTitleBlockTemplate(TitleBlockTemplate *tbtemplate) { loadTemplate(tbtemplate); zoomFit(); } /** @return The title block template object rendered by this view. */ TitleBlockTemplate *TitleBlockTemplateView::titleBlockTemplate() const { return(tbtemplate_); } /** Emits the selectedCellsChanged() signal with the currently selected cells. */ void TitleBlockTemplateView::selectionChanged() { emit(selectedCellsChanged(selectedCells())); } /** Zoom in by zoomFactor(). @see zoomFactor() */ void TitleBlockTemplateView::zoomIn() { scale(zoomFactor(), zoomFactor()); } /** Zoom out by zoomFactor(). @see zoomFactor() */ void TitleBlockTemplateView::zoomOut() { qreal zoom_factor = 1.0/zoomFactor(); scale(zoom_factor, zoom_factor); } /** Fit the rendered title block template in this view. */ void TitleBlockTemplateView::zoomFit() { adjustSceneRect(); fitInView(scene() -> sceneRect(), Qt::KeepAspectRatio); } /** Reset the zoom level. */ void TitleBlockTemplateView::zoomReset() { adjustSceneRect(); resetMatrix(); } /** Export currently selected cells to the clipboard before setting them as empty. @return the list of cells copied to the clipboard */ QList TitleBlockTemplateView::cut() { if (!tbtemplate_) return(QList()); QList copied_cells = copy(); if (!copied_cells.isEmpty()) { CutTemplateCellsCommand *cut_command = new CutTemplateCellsCommand(tbtemplate_); cut_command -> setCutCells(copied_cells); requestGridModification(cut_command); } return(copied_cells); } /** Export currently selected cells to the clipboard. @return the list of cells copied to the clipboard */ QList TitleBlockTemplateView::copy() { if (!tbtemplate_) return(QList()); QList copied_cells = selectedCells(); QDomDocument xml_export; QDomElement tbtpartial = xml_export.createElement("titleblocktemplate-partial"); xml_export.appendChild(tbtpartial); foreach (TitleBlockCell *cell, copied_cells) { tbtemplate_ -> exportCellToXml(cell, tbtpartial); tbtpartial.setAttribute("row", cell -> num_row); tbtpartial.setAttribute("col", cell -> num_col); tbtpartial.setAttribute("row_span", cell -> row_span); tbtpartial.setAttribute("col_span", cell -> col_span); } QClipboard *clipboard = QApplication::clipboard(); clipboard -> setText(xml_export.toString()); return(copied_cells); } /** @return true if the content of the clipboard looks interesting */ bool TitleBlockTemplateView::mayPaste() { // retrieve the clipboard content QClipboard *clipboard = QApplication::clipboard(); return(clipboard -> text().contains(" TitleBlockTemplateView::pastedCells() { QList pasted_cells; // retrieve the clipboard content and parse it as XML QClipboard *clipboard = QApplication::clipboard(); QDomDocument xml_import; if (!xml_import.setContent(clipboard -> text().trimmed())) { return(pasted_cells); } // ensure the XML document describes cells that can be pasted if (xml_import.documentElement().tagName() != "titleblocktemplate-partial") { return(pasted_cells); } // load pasted cells QDomElement paste_root = xml_import.documentElement(); for (QDomElement e = paste_root.firstChildElement() ; !e.isNull() ; e = e.nextSiblingElement()) { if (e.tagName() == "empty" || e.tagName() == "field" || e.tagName() == "logo") { TitleBlockCell cell; cell.loadContentFromXml(e); int row_num = -1, col_num = -1, row_span = -1, col_span = -1; if (!QET::attributeIsAnInteger(e, "row", &row_num) || row_num < 0) { continue; } if (!QET::attributeIsAnInteger(e, "col", &col_num) || col_num < 0) { continue; } cell.num_row = row_num; cell.num_col = col_num; // parse the rowspan and colspan attributes if (QET::attributeIsAnInteger(e, "rowspan", &row_span) && row_span > 0) { cell.row_span = row_span; } if (QET::attributeIsAnInteger(e, "colspan", &col_span) && col_span > 0) { cell.col_span = col_span; } pasted_cells << cell; } } return(pasted_cells); } /** Import the cells described in the clipboard. */ void TitleBlockTemplateView::paste() { if (!tbtemplate_) return; QList pasted_cells = pastedCells(); if (!pasted_cells.count()) return; // the top left cell among the selected ones will be used to position the pasted cells TitleBlockTemplateVisualCell *selected_cell = selectedCellsSet().topLeftCell(); if (!selected_cell) return; TitleBlockCell *erased_cell = selected_cell -> cell(); if (!erased_cell) return; // change num_row and num_col attributes of pasted cells so they get positionned relatively to selected_cell normalizeCells(pasted_cells, erased_cell -> num_row, erased_cell -> num_col); PasteTemplateCellsCommand *paste_command = new PasteTemplateCellsCommand(tbtemplate_); foreach (TitleBlockCell cell, pasted_cells) { TitleBlockCell *erased_cell = tbtemplate_ -> cell(cell.num_row, cell.num_col); if (!erased_cell) continue; paste_command -> addCell(erased_cell, *erased_cell, cell); } requestGridModification(paste_command); } /** Add a column right after the last one. */ void TitleBlockTemplateView::addColumnAtEnd() { if (read_only_) return; requestGridModification(ModifyTemplateGridCommand::addColumn(tbtemplate_, tbtemplate_ -> columnsCount())); } /** Add a row right after the last one. */ void TitleBlockTemplateView::addRowAtEnd() { if (read_only_) return; requestGridModification(ModifyTemplateGridCommand::addRow(tbtemplate_, tbtemplate_ -> rowsCount())); } /** Add a column right before the last index selected when calling the context menu. */ void TitleBlockTemplateView::addColumnBefore() { if (read_only_) return; int index = lastContextMenuCellIndex(); if (index == -1) return; requestGridModification(ModifyTemplateGridCommand::addColumn(tbtemplate_, index)); } /** Add a row right before the last index selected when calling the context menu. */ void TitleBlockTemplateView::addRowBefore() { if (read_only_) return; int index = lastContextMenuCellIndex(); if (index == -1) return; requestGridModification(ModifyTemplateGridCommand::addRow(tbtemplate_, index)); } /** Add a column right after the last index selected when calling the context menu. */ void TitleBlockTemplateView::addColumnAfter() { if (read_only_) return; int index = lastContextMenuCellIndex(); if (index == -1) return; requestGridModification(ModifyTemplateGridCommand::addColumn(tbtemplate_, index + 1)); } /** Add a row right after the last index selected when calling the context menu. */ void TitleBlockTemplateView::addRowAfter() { if (read_only_) return; int index = lastContextMenuCellIndex(); if (index == -1) return; requestGridModification(ModifyTemplateGridCommand::addRow(tbtemplate_, index + 1)); } /** Edit the width of a column. @param cell (optional) HelperCell of the column to be modified. If 0, this method uses the last index selected when calling the context menu. */ void TitleBlockTemplateView::editColumn(HelperCell *cell) { int index = cell ? cell -> index : lastContextMenuCellIndex(); if (index == -1) return; TitleBlockDimension dimension_before = tbtemplate_ -> columnDimension(index); TitleBlockDimensionWidget dialog(true, this); dialog.setReadOnly(read_only_); dialog.setWindowTitle(tr("Changer la largeur de la colonne", "window title when changing a column with")); dialog.label() -> setText(tr("Largeur :", "text before the spinbox to change a column width")); dialog.setValue(dimension_before); int user_answer = dialog.exec(); if (!read_only_ && user_answer == QDialog::Accepted) { ModifyTemplateDimension *command = new ModifyTemplateDimension(tbtemplate_); command -> setType(false); command -> setIndex(index); command -> setDimensionBefore(dimension_before); command -> setDimensionAfter(dialog.value()); requestGridModification(command); } } /** Edit the height of a row. @param cell (optional) HelperCell of the row to be modified. If 0, this method uses the last index selected when calling the context menu. */ void TitleBlockTemplateView::editRow(HelperCell *cell) { int index = cell ? cell -> index : lastContextMenuCellIndex(); if (index == -1) return; TitleBlockDimension dimension_before = TitleBlockDimension(tbtemplate_ -> rowDimension(index)); TitleBlockDimensionWidget dialog(false, this); dialog.setReadOnly(read_only_); dialog.setWindowTitle(tr("Changer la hauteur de la ligne", "window title when changing a row height")); dialog.label() -> setText(tr("Hauteur :", "text before the spinbox to change a row height")); dialog.setValue(dimension_before); int user_answer = dialog.exec(); if (!read_only_ && user_answer == QDialog::Accepted) { ModifyTemplateDimension *command = new ModifyTemplateDimension(tbtemplate_); command -> setType(true); command -> setIndex(index); command -> setDimensionBefore(dimension_before); command -> setDimensionAfter(dialog.value()); requestGridModification(command); } } /** Remove the column at the last index selected when calling the context menu. */ void TitleBlockTemplateView::deleteColumn() { int index = lastContextMenuCellIndex(); if (index == -1) return; requestGridModification(ModifyTemplateGridCommand::deleteColumn(tbtemplate_, index)); } /** Remove the row at the last index selected when calling the context menu. */ void TitleBlockTemplateView::deleteRow() { int index = lastContextMenuCellIndex(); if (index == -1) return; requestGridModification(ModifyTemplateGridCommand::deleteRow(tbtemplate_, index)); } /** Merge the selected cells. */ void TitleBlockTemplateView::mergeSelectedCells() { // retrieve the selected cells TitleBlockTemplateCellsSet selected_cells = selectedCellsSet(); MergeCellsCommand *merge_command = new MergeCellsCommand(selected_cells, tbtemplate_); if (merge_command -> isValid()) requestGridModification(merge_command); } /** Split the selected cell. */ void TitleBlockTemplateView::splitSelectedCell() { // retrieve the selected cells TitleBlockTemplateCellsSet selected_cells = selectedCellsSet(); SplitCellsCommand *split_command = new SplitCellsCommand(selected_cells, tbtemplate_); if (split_command -> isValid()) requestGridModification(split_command); } /** Reimplement the way the background is drawn to render the title block template. */ void TitleBlockTemplateView::drawBackground(QPainter *painter, const QRectF &rect) { QGraphicsView::drawBackground(painter, rect); if (!tbtemplate_) return; // TODO shouldn't we draw a large uniform rect? } /** @return the selected logical cells, not including the spanned ones. */ QList TitleBlockTemplateView::selectedCells() const { return(selectedCellsSet().cells(false).toList()); } /** @return the selected visual cells. */ TitleBlockTemplateCellsSet TitleBlockTemplateView::selectedCellsSet() const { return(makeCellsSetFromGraphicsItems(scene() -> selectedItems())); } /** @return the visual cells contained in the \a rect @param rect Rectangle in the coordinates of the QGraphicsWidget representing the title block template. */ TitleBlockTemplateCellsSet TitleBlockTemplateView::cells(const QRectF &rect) const { QPolygonF mapped_rect(form_ -> mapToScene(rect)); QList items = scene() -> items(mapped_rect, Qt::IntersectsItemShape); return(makeCellsSetFromGraphicsItems(items)); } /** @param can_merge If non-zero, will be changed to reflect whether selected cells may be merged @param can_merge If non-zero, will be changed to reflect whether selected cells may be splitted @param count If non-zero, will be changed to reflect the number of selected cells */ void TitleBlockTemplateView::analyzeSelectedCells(bool *can_merge, bool *can_split, int *count) { if (!can_merge && !can_split) return; if (!tbtemplate_) { if (can_merge) *can_merge = false; if (can_split) *can_split = false; return; } // retrieve the selected cells TitleBlockTemplateCellsSet selected_cells = selectedCellsSet(); if (can_merge) { *can_merge = MergeCellsCommand::canMerge(selected_cells, tbtemplate_); } if (can_split) { *can_split = SplitCellsCommand::canSplit(selected_cells, tbtemplate_); } if (count) { *count = selectedCellsSet().count(); } } /** @return the current size of the rendered title block template */ QSizeF TitleBlockTemplateView::templateSize() const { return(QSizeF(templateWidth(), templateHeight())); } /** @return the current width of the rendered title block template */ qreal TitleBlockTemplateView::templateWidth() const { if (!tbtemplate_) return(0); qreal width = DEFAULT_ROWS_HELPER_CELLS_WIDTH; // the rendered width may exceed the initially planned preview width width += qMax(preview_width_, tbtemplate_ -> width(preview_width_)); return(width); } /** @return the current height of the rendered title block template */ qreal TitleBlockTemplateView::templateHeight() const { if (!tbtemplate_) return(0); qreal height = DEFAULT_PREVIEW_HELPER_CELL_HEIGHT; height += DEFAULT_COLS_HELPER_CELLS_HEIGHT; height += tbtemplate_ -> height(); return(height); } /** Handles mouse wheel-related actions @param e QWheelEvent describing the wheel event */ void TitleBlockTemplateView::wheelEvent(QWheelEvent *e) { // si la touche Ctrl est enfoncee, on zoome / dezoome if (e -> modifiers() & Qt::ControlModifier) { if (e -> delta() > 0) { zoomIn(); } else { zoomOut(); } } else { QAbstractScrollArea::wheelEvent(e); } } /** @return the zoom factor used by zoomIn() and zoomOut(). */ qreal TitleBlockTemplateView::zoomFactor() const { return(1.1); } /** Initialize this view (actions, signals/slots connections, etc.) */ void TitleBlockTemplateView::init() { add_column_before_ = new QAction(QET::Icons::EditTableInsertColumnLeft, tr("Ajouter une colonne (avant)", "context menu"), this); add_row_before_ = new QAction(QET::Icons::EditTableInsertRowAbove, tr("Ajouter une ligne (avant)", "context menu"), this); add_column_after_ = new QAction(QET::Icons::EditTableInsertColumnRight, tr("Ajouter une colonne (apr\350s)", "context menu"), this); add_row_after_ = new QAction(QET::Icons::EditTableInsertRowUnder, tr("Ajouter une ligne (apr\350s)", "context menu"), this); edit_column_dim_ = new QAction( tr("Modifier les dimensions de cette colonne", "context menu"), this); edit_row_dim_ = new QAction( tr("Modifier les dimensions de cette ligne", "context menu"), this); delete_column_ = new QAction(QET::Icons::EditTableDeleteColumn, tr("Supprimer cette colonne", "context menu"), this); delete_row_ = new QAction(QET::Icons::EditTableDeleteRow, tr("Supprimer cette ligne", "context menu"), this); change_preview_width_ = new QAction( tr("Modifier la largeur de cet aper\347u", "context menu"), this); connect(add_column_before_, SIGNAL(triggered()), this, SLOT(addColumnBefore())); connect(add_row_before_, SIGNAL(triggered()), this, SLOT(addRowBefore())); connect(add_column_after_, SIGNAL(triggered()), this, SLOT(addColumnAfter())); connect(add_row_after_, SIGNAL(triggered()), this, SLOT(addRowAfter())); connect(edit_column_dim_, SIGNAL(triggered()), this, SLOT(editColumn())); connect(edit_row_dim_, SIGNAL(triggered()), this, SLOT(editRow())); connect(delete_column_, SIGNAL(triggered()), this, SLOT(deleteColumn())); connect(delete_row_, SIGNAL(triggered()), this, SLOT(deleteRow())); connect(change_preview_width_, SIGNAL(triggered()), this, SLOT(changePreviewWidth())); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setBackgroundBrush(QBrush(QColor(248, 255, 160))); connect(scene(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); } /** Apply the columns widths currently specified by the edited title block template. @param animate true to animate the change, false otherwise. */ void TitleBlockTemplateView::applyColumnsWidths(bool animate) { // the first column is dedicated to helper cells showing the rows height tbgrid_ -> setColumnFixedWidth(0, DEFAULT_ROWS_HELPER_CELLS_WIDTH); tbgrid_ -> setColumnSpacing(0, 0); // we apply the other columns width based on the title block template data QList widths = tbtemplate_ -> columnsWidth(preview_width_); int total_applied_width = 0; for (int i = 0 ; i < widths.count() ; ++ i) { int applied_width = qMax(0, widths.at(i)); tbgrid_ -> setColumnSpacing(COL_OFFSET + i, 0); if (!animate) { // no animation on first call tbgrid_ -> setColumnFixedWidth(COL_OFFSET + i, widths.at(i)); } else { GridLayoutAnimation *animation = new GridLayoutAnimation(tbgrid_, form_); animation -> setIndex(COL_OFFSET + i); animation -> setActsOnRows(false); animation -> setStartValue(QVariant(tbgrid_ -> columnMinimumWidth(COL_OFFSET + i))); animation -> setEndValue(QVariant(1.0 * applied_width)); animation -> setDuration(500); connect(animation, SIGNAL(finished()), this, SLOT(updateColumnsHelperCells())); animation -> start(QAbstractAnimation::DeleteWhenStopped); } total_applied_width += applied_width; } if (!animate) updateColumnsHelperCells(); ++ apply_columns_widths_count_; // we systematically parameter some cells total_width_helper_cell_ -> split_size = 0; tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, widths.count()); removeItem(extra_cells_width_helper_cell_); if (total_applied_width < preview_width_) { // preview_width is greater than the sum of cells widths // we add an extra column with a helper cell tbgrid_ -> addItem(extra_cells_width_helper_cell_, ROW_OFFSET - 1, COL_OFFSET + widths.count(), tbtemplate_ -> rowsCount() + 1, 1); tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, widths.count() + 1); tbgrid_ -> setColumnFixedWidth(COL_OFFSET + widths.count(), preview_width_ - total_applied_width); extra_cells_width_helper_cell_ -> label = QString( tr("[%1px]","content of the extra cell added when the total width of cells is less than the preview width") ).arg(preview_width_ - total_applied_width); } else if (total_applied_width > preview_width_) { // preview width is smaller than the sum of cells widths // we draw an extra header within th "preview width" cell. tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, widths.count()); total_width_helper_cell_ -> split_background_color = QColor(Qt::red); total_width_helper_cell_ -> split_foreground_color = QColor(Qt::black); total_width_helper_cell_ -> split_label = QString( tr("[%1px]", "content of the extra helper cell added when the total width of cells is greather than the preview width") ).arg(total_applied_width - preview_width_); total_width_helper_cell_ -> split_size = total_applied_width - preview_width_; } updateDisplayedMinMaxWidth(); } /** Apply the rows heights currently specified by the edited title block template. @param animate true to animate the change, false otherwise. */ void TitleBlockTemplateView::applyRowsHeights(bool animate) { // the first row is dedicated to a helper cell showing the total width tbgrid_ -> setRowFixedHeight(0, DEFAULT_PREVIEW_HELPER_CELL_HEIGHT); tbgrid_ -> setRowSpacing(0, 0); // the second row is dedicated to helper cells showing the columns width tbgrid_ -> setRowFixedHeight(1, DEFAULT_COLS_HELPER_CELLS_HEIGHT); tbgrid_ -> setRowSpacing(1, 0); QList heights = tbtemplate_ -> rowsHeights(); for (int i = 0 ; i < heights.count() ; ++ i) { tbgrid_ -> setRowSpacing(ROW_OFFSET + i, 0); if (!animate) { // no animation on first call tbgrid_ -> setRowFixedHeight(ROW_OFFSET + i, heights.at(i)); } else { GridLayoutAnimation *animation = new GridLayoutAnimation(tbgrid_, form_); animation -> setIndex(ROW_OFFSET + i); animation -> setActsOnRows(true); animation -> setStartValue(QVariant(tbgrid_ -> rowMinimumHeight(ROW_OFFSET + i))); animation -> setEndValue(QVariant(1.0 * heights.at(i))); animation -> setDuration(500); connect(animation, SIGNAL(finished()), this, SLOT(updateRowsHelperCells())); animation -> start(QAbstractAnimation::DeleteWhenStopped); } } if (!animate) updateRowsHelperCells(); ++ apply_rows_heights_count_; } /** Update the content (type and value) of rows helper cells. */ void TitleBlockTemplateView::updateRowsHelperCells() { int row_count = tbtemplate_ -> rowsCount(); QList heights = tbtemplate_ -> rowsHeights(); for (int i = 0 ; i < row_count ; ++ i) { HelperCell *current_row_cell = static_cast(tbgrid_ -> itemAt(ROW_OFFSET + i, 0)); if (current_row_cell) { current_row_cell -> setType(QET::Absolute); // rows always have absolute heights current_row_cell -> label = QString(tr("%1px", "format displayed in rows helper cells")).arg(heights.at(i)); } } } /** Update the content (type and value) of columns helper cells. */ void TitleBlockTemplateView::updateColumnsHelperCells() { int col_count = tbtemplate_ -> columnsCount(); for (int i = 0 ; i < col_count ; ++ i) { TitleBlockDimension current_col_dim = tbtemplate_ -> columnDimension(i); HelperCell *current_col_cell = static_cast(tbgrid_ -> itemAt(1, COL_OFFSET + i)); if (current_col_cell) { current_col_cell -> setType(current_col_dim.type); current_col_cell -> label = current_col_dim.toString(); } } } /** Add the cells (both helper cells and regular visual cells) to the scene to get a visual representation of the edited title block template. */ void TitleBlockTemplateView::addCells() { int col_count = tbtemplate_ -> columnsCount(); int row_count = tbtemplate_ -> rowsCount(); // we add a big cell to show the total width total_width_helper_cell_ = new SplittedHelperCell(); total_width_helper_cell_ -> setType(QET::Absolute); updateTotalWidthLabel(); total_width_helper_cell_ -> orientation = Qt::Horizontal; total_width_helper_cell_ -> setActions(QList() << change_preview_width_); connect(total_width_helper_cell_, SIGNAL(contextMenuTriggered(HelperCell *)), this, SLOT(updateLastContextMenuCell(HelperCell *))); connect(total_width_helper_cell_, SIGNAL(doubleClicked(HelperCell*)), this, SLOT(changePreviewWidth())); tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, col_count); // we also initialize an extra helper cells that shows the preview width is // too long for the current cells widths extra_cells_width_helper_cell_ = new HelperCell(); extra_cells_width_helper_cell_ -> background_color = QColor(Qt::red); // we add one cell per column to show their respective width for (int i = 0 ; i < col_count ; ++ i) { TitleBlockDimension current_col_dim = tbtemplate_ -> columnDimension(i); HelperCell *current_col_cell = new HelperCell(); current_col_cell -> setType(current_col_dim.type); current_col_cell -> label = current_col_dim.toString(); current_col_cell -> setActions(columnsActions()); current_col_cell -> orientation = Qt::Horizontal; current_col_cell -> index = i; connect(current_col_cell, SIGNAL(contextMenuTriggered(HelperCell *)), this, SLOT(updateLastContextMenuCell(HelperCell *))); connect(current_col_cell, SIGNAL(doubleClicked(HelperCell*)), this, SLOT(editColumn(HelperCell *))); tbgrid_ -> addItem(current_col_cell, 1, COL_OFFSET + i, 1, 1); } // we add one cell per row to show their respective height QList heights = tbtemplate_ -> rowsHeights(); for (int i = 0 ; i < row_count ; ++ i) { HelperCell *current_row_cell = new HelperCell(); current_row_cell -> setType(QET::Absolute); // rows always have absolute heights current_row_cell -> label = QString(tr("%1px")).arg(heights.at(i)); current_row_cell -> orientation = Qt::Vertical; current_row_cell -> index = i; current_row_cell -> setActions(rowsActions()); connect(current_row_cell, SIGNAL(contextMenuTriggered(HelperCell *)), this, SLOT(updateLastContextMenuCell(HelperCell *))); connect(current_row_cell, SIGNAL(doubleClicked(HelperCell*)), this, SLOT(editRow(HelperCell *))); tbgrid_ -> addItem(current_row_cell, ROW_OFFSET + i, 0, 1, 1); } // eventually we add the cells composing the titleblock template for (int i = 0 ; i < col_count ; ++ i) { for (int j = 0 ; j < row_count ; ++ j) { TitleBlockCell *cell = tbtemplate_ -> cell(j, i); if (cell -> spanner_cell) continue; TitleBlockTemplateVisualCell *cell_item = new TitleBlockTemplateVisualCell(); cell_item -> setTemplateCell(tbtemplate_, cell); int row_span = 0, col_span = 0; if (cell -> span_state != TitleBlockCell::Disabled) { row_span = cell -> applied_row_span; col_span = cell -> applied_col_span; } tbgrid_ -> addItem(cell_item, ROW_OFFSET + j, COL_OFFSET + i, row_span + 1, col_span + 1); } } } /** Refresh the regular cells. */ void TitleBlockTemplateView::refresh() { int col_count = tbtemplate_ -> columnsCount(); int row_count = tbtemplate_ -> rowsCount(); if (row_count < 1 || col_count < 1) return; for (int i = 0 ; i < col_count ; ++ i) { for (int j = 0 ; j < row_count ; ++ j) { if (QGraphicsLayoutItem *item = tbgrid_ -> itemAt(ROW_OFFSET + j, COL_OFFSET + i)) { if (QGraphicsItem *qgi = dynamic_cast(item)) { qgi -> update(); } } } } } /** Ask the user a new width for the preview */ void TitleBlockTemplateView::changePreviewWidth() { TitleBlockDimensionWidget dialog(false, this); dialog.setWindowTitle(tr("Changer la largeur de l'aper\347u")); dialog.label() -> setText(tr("Largeur de l'aper\347u :")); dialog.setValue(TitleBlockDimension(preview_width_)); if (dialog.exec() == QDialog::Accepted) { setPreviewWidth(dialog.value().value); } } /** Fill the layout with empty cells where needed. */ void TitleBlockTemplateView::fillWithEmptyCells() { int col_count = tbtemplate_ -> columnsCount(); int row_count = tbtemplate_ -> rowsCount(); if (row_count < 1 || col_count < 1) return; for (int i = 0 ; i < col_count ; ++ i) { for (int j = 0 ; j < row_count ; ++ j) { if (tbgrid_ -> itemAt(ROW_OFFSET + j, COL_OFFSET + i)) continue; TitleBlockTemplateVisualCell *cell_item = new TitleBlockTemplateVisualCell(); if (TitleBlockCell *target_cell = tbtemplate_ -> cell(j, i)) { qDebug() << Q_FUNC_INFO << "target_cell" << target_cell; cell_item -> setTemplateCell(tbtemplate_, target_cell); } tbgrid_ -> addItem(cell_item, ROW_OFFSET + j, COL_OFFSET + i); } } } /** @param event Object describing the received event */ bool TitleBlockTemplateView::event(QEvent *event) { if (first_activation_ && event -> type() == QEvent::WindowActivate) { QTimer::singleShot(250, this, SLOT(zoomFit())); first_activation_ = false; } return(QGraphicsView::event(event)); } /** Given a cells list, change their position so the top left one is at row \a x and column \a y. @param cells Cells list */ void TitleBlockTemplateView::normalizeCells(QList &cells, int x, int y) const { if (!cells.count()) return; int min_row = cells.at(0).num_row; int min_col = cells.at(0).num_col; for (int i = 1 ; i < cells.count() ; ++ i) { if (cells.at(i).num_row < min_row) min_row = cells.at(i).num_row; if (cells.at(i).num_col < min_col) min_col = cells.at(i).num_col; } for (int i = 0 ; i < cells.count() ; ++ i) { cells[i].num_row = cells[i].num_row - min_row + x; cells[i].num_col = cells[i].num_col - min_col + y; } } /** Load the \a tbt title block template. If a different template was previously loaded, it is deleted. */ void TitleBlockTemplateView::loadTemplate(TitleBlockTemplate *tbt) { if (tbgrid_) { scene() -> removeItem(form_); // also deletes TemplateCellPreview because, according to the // documentation, QGraphicsGridLayout takes ownership of the items. form_ -> deleteLater(); } if (tbtemplate_ && tbtemplate_ != tbt) { delete tbtemplate_; } tbtemplate_ = tbt; // initialize a grid layout with no margin tbgrid_ = new QGraphicsGridLayout(); tbgrid_ -> setContentsMargins(0, 0, 0, 0); // add cells defined by the title block template in this layout addCells(); // fill potential holes in the grid with empty cells fillWithEmptyCells(); // apply rows and columns dimensions applyColumnsWidths(false); applyRowsHeights(false); // assign the layout to a basic QGraphicsWidget form_ = new QGraphicsWidget(); // enforce the layout direction to avoid reversing the template rendering form_ -> setLayoutDirection(Qt::LeftToRight); form_ -> setLayout(tbgrid_); scene() -> addItem(form_); adjustSceneRect(); } /** @return the list of rows-specific actions. */ QList TitleBlockTemplateView::rowsActions() const { return QList() << add_row_before_<< edit_row_dim_ << add_row_after_ << delete_row_; } /** @return the list of columns-specific actions. */ QList TitleBlockTemplateView::columnsActions() const { return QList() << add_column_before_ << edit_column_dim_ << add_column_after_ << delete_column_; } /** Update the displayed layout. Call this function to refresh the display after the rendered title block template has been "deeply" modified, e.g. rows/columns have been added/modified or cells were merged/splitted. */ void TitleBlockTemplateView::updateLayout() { // TODO we should try to update the grid instead of deleting-and-reloading it loadTemplate(tbtemplate_); } /** Update the displayed layout. Call this function when the dimensions of rows changed. */ void TitleBlockTemplateView::rowsDimensionsChanged() { applyRowsHeights(); } /** Update the displayed layout. Call this function when the dimensions of columns changed. */ void TitleBlockTemplateView::columnsDimensionsChanged() { applyColumnsWidths(); } /** Update the tooltip that displays the minimum and/or maximum width of the template. */ void TitleBlockTemplateView::updateDisplayedMinMaxWidth() { if (!tbtemplate_) return; int min_width = tbtemplate_ -> minimumWidth(); int max_width = tbtemplate_ -> maximumWidth(); QString min_max_width_sentence; if (max_width != -1) { min_max_width_sentence = QString( tr( "Longueur minimale\240: %1px\nLongueur maximale\240: %2px\n", "tooltip showing the minimum and/or maximum width of the edited template" ) ).arg(min_width).arg(max_width); } else { min_max_width_sentence = QString( tr( "Longueur minimale\240: %1px\n", "tooltip showing the minimum width of the edited template" ) ).arg(min_width); } total_width_helper_cell_ -> setToolTip(makePrettyToolTip(min_max_width_sentence)); } /** @param read_only whether this view should be read only. */ void TitleBlockTemplateView::setReadOnly(bool read_only) { if (read_only_ == read_only) return; read_only_ = read_only; add_column_before_ -> setEnabled(!read_only_); add_row_before_ -> setEnabled(!read_only_); add_column_after_ -> setEnabled(!read_only_); add_row_after_ -> setEnabled(!read_only_); delete_column_ -> setEnabled(!read_only_); delete_row_ -> setEnabled(!read_only_); } /** Set the new preview width to width @param width new preview width */ void TitleBlockTemplateView::setPreviewWidth(int width) { if (preview_width_ == width) return; int former_preview_width = preview_width_; preview_width_ = width; if (tbgrid_) { applyColumnsWidths(); updateTotalWidthLabel(); //adjustSceneRect(); centerOn(form_); } emit(previewWidthChanged(former_preview_width, preview_width_)); } /** Update the label of the helper cell that indicates the preview width. */ void TitleBlockTemplateView::updateTotalWidthLabel() { if (!total_width_helper_cell_) return; total_width_helper_cell_ -> label = QString( tr( "Largeur totale pour cet aper\347u : %1px", "displayed at the top of the preview when editing a title block template" ) ).arg(preview_width_); } /** Emit the gridModificationRequested() signal with \a command after having set its view component. @see TitleBlockTemplateCommand::setView() @param command A command object modifying the rendered title block template. */ void TitleBlockTemplateView::requestGridModification(TitleBlockTemplateCommand *command) { if (!command) return; command -> setView(this); emit(gridModificationRequested(command)); } /** @return the last index selected when triggering the context menu. @see updateLastContextMenuCell */ int TitleBlockTemplateView::lastContextMenuCellIndex() const { if (last_context_menu_cell_) { return(last_context_menu_cell_ -> index); } return(-1); } /** @param item an item supposed to be contained in the grid layout. @return the flat index if this item, or -1 if it could not be found. */ int TitleBlockTemplateView::indexOf(QGraphicsLayoutItem *item) { for (int i = 0 ; i < tbgrid_ -> count() ; ++i) { if (item == tbgrid_ -> itemAt(i)) return(i); } return(-1); } /** Removes an item from the grid layout @param item an item supposed to be contained in the grid layout. */ void TitleBlockTemplateView::removeItem(QGraphicsLayoutItem *item) { int index = indexOf(item); if (index != -1) { tbgrid_ -> removeAt(index); // trick: we also have to remove the item from the scene if (QGraphicsScene *current_scene = scene()) { if (QGraphicsItem *qgi = item -> graphicsItem()) { current_scene -> removeItem(qgi); } } } } /** @param a list of QGraphicsItem @return the corresponding TitleBlockTemplateCellsSet */ TitleBlockTemplateCellsSet TitleBlockTemplateView::makeCellsSetFromGraphicsItems(const QList &items) const { TitleBlockTemplateCellsSet set(this); foreach (QGraphicsItem *item, items) { if (TitleBlockTemplateVisualCell *cell_view = dynamic_cast(item)) { if (cell_view -> cell() && cell_view -> cell() -> num_row != -1) { set << cell_view; } } } return(set); } /* @param a text string @return an HTML string that can be passed to setToolTip() */ QString TitleBlockTemplateView::makePrettyToolTip(const QString &string) { QString css_style = QString("white-space: pre;"); QString final_tooltip_content = QString( "
%2
" ).arg(css_style).arg(string); return(final_tooltip_content); } /** Stores \a last_context_menu_cell as being the last helper cell the context menu was triggered on. */ void TitleBlockTemplateView::updateLastContextMenuCell(HelperCell *last_context_menu_cell) { last_context_menu_cell_ = last_context_menu_cell; } /** Adjusts the bounding rect of the scene. */ void TitleBlockTemplateView::adjustSceneRect() { QRectF old_scene_rect = scene() -> sceneRect(); // rectangle including everything on the scene QRectF bounding_rect(QPointF(0, 0), templateSize()); scene() -> setSceneRect(bounding_rect); // met a jour la scene scene() -> update(old_scene_rect.united(bounding_rect)); }