/*
	Copyright 2006-2023 The QElectroTech Team
	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 <http://www.gnu.org/licenses/>.
*/
#include "templateview.h"

#include "../qeticons.h"
#include "dimensionwidget.h"
#include "gridlayoutanimation.h"
#include "helpercell.h"
#include "splittedhelpercell.h"
#include "templatecellsset.h"
#include "templatecommands.h"
#include "templatevisualcell.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_(nullptr),
	tbgrid_(nullptr),
	form_(nullptr),
	preview_width_(DEFAULT_PREVIEW_WIDTH),
	apply_columns_widths_count_(0),
	apply_rows_heights_count_(0),
	first_activation_(true),
	read_only_(false)
{
	init();
}

/**
	@brief TitleBlockTemplateView::TitleBlockTemplateView
	@param scene
	@param parent Parent QWidget.
*/
TitleBlockTemplateView::TitleBlockTemplateView(
		QGraphicsScene *scene,QWidget *parent) :
	QGraphicsView(scene, parent),
	tbtemplate_(nullptr),
	tbgrid_(nullptr),
	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();
	resetTransform();
}

/**
	Export currently selected cells to the clipboard before setting them as
	empty.
	@return the list of cells copied to the clipboard
*/
QList<TitleBlockCell *> TitleBlockTemplateView::cut()
{
	if (!tbtemplate_) return(QList<TitleBlockCell *>());
	QList<TitleBlockCell *> 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<TitleBlockCell *> TitleBlockTemplateView::copy()
{
	if (!tbtemplate_) return(QList<TitleBlockCell *>());
	QList<TitleBlockCell *> 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("<titleblocktemplate-partial"));
}

/**
	@return a list containing the pasted cells
*/
QList<TitleBlockCell> TitleBlockTemplateView::pastedCells()
{
	QList<TitleBlockCell> 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<TitleBlockCell> 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 positioned 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 TODO_LIST
#pragma message("@TODO shouldn't we draw a large uniform rect?")
#endif
	if (!tbtemplate_) return; // TODO shouldn't we draw a large uniform rect?
}

/**
	@return the selected logical cells, not including the spanned ones.
*/
QList<TitleBlockCell *> TitleBlockTemplateView::selectedCells() const
{
	return(selectedCellsSet().cells(false).values());
}

/**
	@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<QGraphicsItem *> items = scene() -> items(mapped_rect, Qt::IntersectsItemShape);
	return(makeCellsSetFromGraphicsItems(items));
}

/**
	@brief TitleBlockTemplateView::analyzeSelectedCells
	@param can_merge :
	If non-zero, will be changed to reflect whether selected cells may be merged
	@param can_split :
	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<int>(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 -> angleDelta().y() > 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ès)",           "context menu"), this);
	add_row_after_        = new QAction(QET::Icons::EditTableInsertRowUnder,    tr("Ajouter une ligne (après)",             "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çu",     "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<int> 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_ -> setLabel(
			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 greater 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<int> 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<int> heights = tbtemplate_ -> rowsHeights();
	for (int i = 0 ; i < row_count ; ++ i) {
		HelperCell *current_row_cell = static_cast<HelperCell *>(tbgrid_ -> itemAt(ROW_OFFSET + i, 0));
		if (current_row_cell) {
			current_row_cell -> setType(QET::Absolute); // rows always have absolute heights
			current_row_cell -> setLabel(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<HelperCell *>(tbgrid_ -> itemAt(1, COL_OFFSET + i));
		if (current_col_cell) {
			current_col_cell -> setType(current_col_dim.type);
			current_col_cell -> setLabel(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<QAction *>() << 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 -> setLabel(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<int> 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 -> setLabel(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<QGraphicsItem *>(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çu"));
	dialog.label() -> setText(tr("Largeur de l'aperçu :"));
	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));
}

/**
	@brief TitleBlockTemplateView::normalizeCells
	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
	@param x : row
	@param y : column
*/
void TitleBlockTemplateView::normalizeCells(
		QList<TitleBlockCell> &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<QAction *> TitleBlockTemplateView::rowsActions() const
{
	return QList<QAction *>() << add_row_before_<< edit_row_dim_ << add_row_after_ << delete_row_;
}

/**
	@return the list of columns-specific actions.
*/
QList<QAction *> TitleBlockTemplateView::columnsActions() const
{
	return QList<QAction *>() << 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()
{
#if TODO_LIST
#pragma message("@TODO we should try to update the grid instead of deleting-and-reloading it")
#endif
	// 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 : %1px\nLongueur maximale : %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 : %1px\n",
				"tooltip showing the minimum width of the edited template"
			)
		).arg(min_width);
	}

	// the tooltip may also display the split label for readability purpose
	if (total_width_helper_cell_ -> split_size) {
		min_max_width_sentence += "---\n";
		min_max_width_sentence += total_width_helper_cell_ -> split_label;
	}

	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çu : %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);
			}
		}
	}
}

/**
	@brief TitleBlockTemplateView::makeCellsSetFromGraphicsItems
	@param items : a list of QGraphicsItem
	@return the corresponding TitleBlockTemplateCellsSet
*/
TitleBlockTemplateCellsSet TitleBlockTemplateView::makeCellsSetFromGraphicsItems(
		const QList<QGraphicsItem *> &items) const
{
	TitleBlockTemplateCellsSet set(this);
	foreach (QGraphicsItem *item, items) {
		if (TitleBlockTemplateVisualCell *cell_view = dynamic_cast<TitleBlockTemplateVisualCell *>(item)) {
			if (cell_view -> cell() && cell_view -> cell() -> num_row != -1) {
				set << cell_view;
			}
		}
	}
	return(set);
}

/**
	@brief TitleBlockTemplateView::makePrettyToolTip
	@param string : 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(
		"<div style=\"%1\">%2</div>"
	).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));
}

