/*
	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 "elementscene.h"

#include "../NameList/ui/namelistdialog.h"
#include "../NameList/ui/namelistwidget.h"
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../QetGraphicsItemModeler/qetgraphicshandleritem.h"
#include "editorcommands.h"
#include "elementcontent.h"
#include "elementprimitivedecorator.h"
#include "esevent/eseventinterface.h"
#include "graphicspart/partarc.h"
#include "graphicspart/partdynamictextfield.h"
#include "graphicspart/partellipse.h"
#include "graphicspart/partline.h"
#include "graphicspart/partpolygon.h"
#include "graphicspart/partrectangle.h"
#include "graphicspart/partterminal.h"
#include "graphicspart/parttext.h"
#include "ui/qetelementeditor.h"
#include "ui/elementpropertieseditorwidget.h"

#include <QKeyEvent>
#include <algorithm>
#include <cmath>

/**
	@brief ElementScene::ElementScene
	constructor
	\~French Constructeur
	\~ @param editor : Element editor concerned
	\~French L'editeur d'element concerne
	\~ @param parent : Widget parent
	\~French le Widget parent
*/
ElementScene::ElementScene(QETElementEditor *editor, QObject *parent) :
	QGraphicsScene(parent),
	m_qgi_manager(this),
	m_element_editor(editor)
{
	setItemIndexMethod(QGraphicsScene::NoIndex);
	//Set to no index, because they can be the source of the crash with conductor and shape ghost.
	//https://forum.qt.io/topic/71316/qgraphicsscenefinditembsptreevisitor-visit-crashes-due-to-an-obsolete-paintevent-after-qgraphicsscene-removeitem
	//https://stackoverflow.com/questions/38458830/crash-after-qgraphicssceneremoveitem-with-custom-item-class
	//http://www.qtcentre.org/archive/index.php/t-33730.html
	//http://tech-artists.org/t/qt-properly-removing-qgraphicitems/3063

	m_behavior = Normal;
	setItemIndexMethod(NoIndex);
	setGrid(1, 1);
	initPasteArea();
	m_undo_stack.setClean();
	m_decorator_lock = new QMutex();
	connect(&m_undo_stack, SIGNAL(indexChanged(int)),
		this, SLOT(managePrimitivesGroups()));
	connect(this, SIGNAL(selectionChanged()),
			this, SLOT(managePrimitivesGroups()));
}

/**
 * @brief ElementScene::elementData
 * @return the elementdata using by the scene
 */
ElementData ElementScene::elementData() {
	return m_element_data;
}

void ElementScene::setElementData(ElementData data)
{
	bool emit_ = m_element_data.m_informations != data.m_informations;

	m_element_data = data;

	if (emit_)
		emit elementInfoChanged();
}

/**
	@brief ElementScene::~ElementScene
*/
ElementScene::~ElementScene()
{
	//Disconnect to avoid crash, see bug report N° 122.
	disconnect(&m_undo_stack, SIGNAL(indexChanged(int)),
		   this, SLOT(managePrimitivesGroups()));
	delete m_decorator_lock;

	if (m_event_interface)
		delete m_event_interface;

	if (m_decorator)
		delete m_decorator;
}

/**
	@brief ElementScene::mouseMoveEvent
	@param e
*/
void ElementScene::mouseMoveEvent(QGraphicsSceneMouseEvent *e)
{
	if (m_event_interface) {
		if (m_event_interface -> mouseMoveEvent(e)) {
			if (m_event_interface->isFinish()) {
				delete m_event_interface;
				m_event_interface = nullptr;
			}
			return;
		}
	}

	QPointF event_pos = e -> scenePos();
	if (!(e -> modifiers() & Qt::ControlModifier))
		event_pos = snapToGrid(event_pos);

	if (m_behavior == PasteArea) {
		QRectF current_rect(m_paste_area -> rect());
		current_rect.moveCenter(event_pos);
		m_paste_area -> setRect(current_rect);
		return;
	}

	QGraphicsScene::mouseMoveEvent(e);

}

/**
	@brief ElementScene::mousePressEvent
	@param e
*/
void ElementScene::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
	if (m_event_interface) {
		if (m_event_interface -> mousePressEvent(e)) {
			if (m_event_interface->isFinish()) {
				delete m_event_interface;
				m_event_interface = nullptr;
			}
			return;
		}
	}

	QGraphicsScene::mousePressEvent(e);
}

/**
	@brief ElementScene::mouseReleaseEvent
	@param e
*/
void ElementScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *e)
{
	if (m_event_interface) {
		if (m_event_interface -> mouseReleaseEvent(e)) {
			if (m_event_interface->isFinish()) {
				delete m_event_interface;
				m_event_interface = nullptr;
			}
			return;
		}
	}

	if (m_behavior == PasteArea) {
		m_defined_paste_area = m_paste_area -> rect();
		removeItem(m_paste_area);
		emit(pasteAreaDefined(m_defined_paste_area));
		m_behavior = Normal;
		return;
	}

	QGraphicsScene::mouseReleaseEvent(e);
}

/**
	@brief ElementScene::mouseDoubleClickEvent
	@param event
*/
void ElementScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
	if (m_event_interface) {
		if (m_event_interface -> mouseDoubleClickEvent(event)) {
			if (m_event_interface->isFinish()) {
				delete m_event_interface;
				m_event_interface = nullptr;
			}
			return;
		}
	}

	QGraphicsScene::mouseDoubleClickEvent(event);
}

/**
	@brief ElementScene::keyPressEvent
	manage key press event
	@param event
*/
void ElementScene::keyPressEvent(QKeyEvent *event)
{
	if (m_event_interface)
	{
		if (m_event_interface -> keyPressEvent(event))
		{
			if (m_event_interface->isFinish())
			{
				delete m_event_interface;
				m_event_interface = nullptr;
			}
			return;
		}
	}

	if(selectedItems().size() == 1)
	{
		if(selectedItems().first()->type() == PartText::Type)
		{
			PartText *t = static_cast<PartText *>(selectedItems().first());
			if(t->textInteractionFlags() & Qt::TextEditorInteraction)
			{
				QGraphicsScene::keyPressEvent(event);
				return;
			}
		}

		QGraphicsObject *qgo = selectedItems().first()->toGraphicsObject();
		if(qgo)
		{
		QPointF original_pos = qgo->pos();
		QPointF p = qgo->pos();

		if (event->modifiers() & Qt::ControlModifier) {

			int k = event->key();
			if(k == Qt::Key_Right)
				p.rx() += 0.1;
			else if (k == Qt::Key_Left)
				p.rx() -= 0.1;
			else if (k == Qt::Key_Up)
				p.ry() -= 0.1;
			else if (k == Qt::Key_Down)
				p.ry() += 0.1;
				}
			else {

			int k = event->key();
			if(k == Qt::Key_Right)
				p.rx() += 1;
			else if (k == Qt::Key_Left)
				p.rx() -= 1;
			else if (k == Qt::Key_Up)
				p.ry() -= 1;
			else if (k == Qt::Key_Down)
				p.ry() += 1;
			}

			qgo->setPos(p);
			QPropertyUndoCommand *undo =
					new QPropertyUndoCommand(
						qgo,"pos",QVariant(original_pos),QVariant(p));
			undo->setText(tr("Déplacer une primitive"));
			undo->enableAnimation();
			undoStack().push(undo);
			event->accept();
			return;
		}
	}

	QGraphicsScene::keyPressEvent(event);
}

/**
	@brief ElementScene::contextMenuEvent
	Display the context menu event, only if behavior are Normal
	@param event
*/
void ElementScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
	QGraphicsScene::contextMenuEvent(event);
	if(event->isAccepted())
		return;

	if (m_behavior == ElementScene::Normal)
		m_element_editor -> contextMenu(event->screenPos());
}

/**
	@brief ElementScene::drawForeground
	Draws the background of the editor, ie the hotspot indicator.
	\~French Dessine l'arriere-plan de l'editeur, cad l'indicateur de hotspot.
	\~ @param p : The QPainter to use for drawing
	\~French Le QPainter a utiliser pour dessiner
*/
void ElementScene::drawForeground(QPainter *p, const QRectF &)
{
	p -> save();

	// desactive tout antialiasing, sauf pour le texte
	p -> setRenderHint(QPainter::Antialiasing, false);
	p -> setRenderHint(QPainter::TextAntialiasing, true);
	p -> setRenderHint(QPainter::SmoothPixmapTransform, false);

	QPen pen(Qt::red);
	pen.setCosmetic(true);
	p -> setPen(pen);
	p -> setBrush(Qt::NoBrush);
	p -> drawLine(-20, 0, 20, 0);
	p -> drawLine(0, -20, 0, 20);
	p -> restore();
}

/**
	@brief ElementScene::setEventInterface
	Set a new event interface
	@param event_interface
*/
void ElementScene::setEventInterface(ESEventInterface *event_interface)
{
	if (m_event_interface)
	{
		delete m_event_interface;
		//We must re-init because previous interface
		//Reset its own init when deleted
		event_interface->init();
	}
	m_event_interface = event_interface;
}

/**
	@brief ElementScene::clearEventInterface
	Clear the current event interface
*/
void ElementScene::clearEventInterface()
{
	if(m_event_interface)
	{
		delete m_event_interface;
		m_event_interface = nullptr;
	}
}

/**
	@brief ElementScene::setBehavior
	Modify the current behavior of this scene
	\~French Modifie the current behavior of this scene
	\~ @param b
*/
void ElementScene::setBehavior(ElementScene::Behavior b)
{
	m_behavior = b;
}

ElementScene::Behavior ElementScene::behavior() const
{
	return m_behavior;
}

/**
	@brief ElementScene::xGrid
	@return the horizontal size of the grid
	\~French la taille horizontale de la grille
*/
int ElementScene::xGrid() const
{
	return(m_x_grid);
}

/**
	@brief ElementScene::yGrid
	@return vertical grid size
	\~French la taille verticale de la grille
*/
int ElementScene::yGrid() const
{
	return(m_y_grid);
}

/**
	@brief ElementScene::setGrid
	\~ @param x_g : Horizontal grid size
	\~French Taille horizontale de la grille
	\~ @param y_g : Vertical grid size
	\~French Taille verticale de la grille
*/
void ElementScene::setGrid(int x_g, int y_g)
{
	m_x_grid = x_g ? x_g : 1;
	m_y_grid = y_g ? y_g : 1;
}

/**
	@brief ElementScene::toXml
	Export this element as a xml file
	@param all_parts : (true by default)
	if true, export the entire element in xml,
	if false, only export the selected parts.
	@return an xml document that describe the element.
*/
const QDomDocument ElementScene::toXml(bool all_parts)
{
	QRectF size= elementSceneGeometricRect();

	// if the element doesn't contains the origin point of the scene
	// we move the element to the origin for solve this default before saving
	if (!size.contains(0,0) && all_parts)
	{
		centerElementToOrigine();
		//recalcul the size after movement
		size = elementSceneGeometricRect();
	}

	// define the size of the element by the upper multiple of 10
	int upwidth = ((qRound(size.width())/10)*10)+10;
	if ((qRound(size.width())%10) > 6)
		upwidth += 10;

	int upheight = ((qRound(size.height())/10)*10)+10;
	if ((qRound(size.height())%10) > 6)
		upheight += 10;

	// the margin between the real size of the element and the rectangle that delimits
	int xmargin = qRound(upwidth - size.width());
	int ymargin = qRound(upheight - size.height());

	// document XML
	QDomDocument xml_document;

	//Root of xml document
	QDomElement root = xml_document.createElement("definition");
	root.setAttribute("type",        "element");
	root.setAttribute("width",       QString("%1").arg(upwidth));
	root.setAttribute("height",      QString("%1").arg(upheight));
	root.setAttribute("hotspot_x",   QString("%1").arg(
				  -(qRound(size.x() - (xmargin/2)))));
	root.setAttribute("hotspot_y",   QString("%1").arg(
				  -(qRound(size.y() - (ymargin/2)))));

	root.setAttribute("version", QET::version);
	root.setAttribute("link_type", m_element_data.typeToString(m_element_data.m_type));

	//Uuid used to compare two elements
	QDomElement uuid = xml_document.createElement("uuid");
	uuid.setAttribute("uuid", QUuid::createUuid().toString());
	root.appendChild(uuid);

	//names of element
	root.appendChild(m_element_data.m_names_list.toXml(xml_document));

	auto type_ = m_element_data.m_type;
	if (type_ == ElementData::Slave  ||
		type_ == ElementData::Master ||
		type_ == ElementData::Terminale ||
		type_ == ElementData::Thumbnail)
	{
		root.appendChild(m_element_data.kindInfoToXml(xml_document));
	}

	if(type_ == ElementData::Simple ||
	   type_ == ElementData::Master ||
	   type_ == ElementData::Terminale||
		type_ == ElementData::Thumbnail)
	{
		QDomElement element_info = xml_document.createElement("elementInformations");
		m_element_data.m_informations.toXml(element_info, "elementInformation");
		root.appendChild(element_info);
	}

	//complementary information about the element
	QDomElement informations_element = xml_document.createElement("informations");
	root.appendChild(informations_element);
	informations_element.appendChild(xml_document.createTextNode(m_element_data.m_drawing_information));

	QDomElement description = xml_document.createElement("description");

	//the graphic description of the element
	foreach(QGraphicsItem *qgi, zItems())
	{
		//If the export concerns only the selection, the not selected part is ignored
		if (!all_parts && !qgi -> isSelected()) continue;
		if (CustomElementPart *ce = dynamic_cast<CustomElementPart *>(qgi))
		{
			if (ce -> isUseless()) continue;
			description.appendChild(ce -> toXml(xml_document));
		}
	}
	root.appendChild(description);

	xml_document.appendChild(root);
	return(xml_document);
}

/**
	@brief ElementScene::boundingRectFromXml
	@param xml_document : an XML document describing an element
	\~French un document XML decrivant un element
	\~ @return the boundingRect of the element's content
	\~French le boundingRect du contenu de l'element
*/
QRectF ElementScene::boundingRectFromXml(const QDomDocument &xml_document)
{
	// load parts from XML document
	// charge les parties depuis le document XML
	ElementContent loaded_content = loadContent(xml_document);
	if (loaded_content.isEmpty()) return(QRectF());

	// calcule the boundingRect
	// calcule le boundingRect
	QRectF bounding_rect = elementContentBoundingRect(loaded_content);

	// destroy charged parties
	// detruit les parties chargees
	qDeleteAll(loaded_content);

	return(bounding_rect);
}

/**
	@brief ElementScene::fromXml
	Imports the element described in an XML document. If a position is
	specified, the imported elements are positioned in such a way that the
	upper left corner of the smallest rectangle that can surround them all
	(the bounding rect) either at this position.
	\~French Importe l'element decrit dans un document XML. Si une position est
	precisee, les elements importes sont positionnes de maniere a ce que le
	coin superieur gauche du plus petit rectangle pouvant les entourant tous
	(le bounding rect) soit a cette position.
	\~ @param xml_document : an XML document describing the element
	\~French un document XML decrivant l'element
	\~ @param position : The position of the imported parts
	\~French La position des parties importees
	\~ @param consider_informations : If true, additional information
	(dimensions, hotspot, etc.) will be taken into account
	\~French Si vrai, les informations complementaires
	(dimensions, hotspot, etc.) seront prises en compte
	\~ @param content_ptr :
	if this pointer to an ElementContent is different from 0,
	it will be filled with the content added to the element by the fromXml
	\~French si ce pointeur vers un ElementContent est different de 0,
	il sera rempli avec le contenu ajoute a l'element par le fromXml
*/
void ElementScene::fromXml(
		const QDomDocument &xml_document,
		const QPointF &position,
		bool consider_informations,
		ElementContent *content_ptr)
{
	bool state = true;

		//Consider the informations of the element
	if (consider_informations)
	{
			// Root must be an element definition
		QDomElement root = xml_document.documentElement();

		if (root.tagName() == "definition" &&
			root.attribute("type") == "element") {
			m_element_data.fromXml(root);
		}
	}

	if (state)
	{
		ElementContent loaded_content = loadContent(xml_document);
		if (position != QPointF())
			addContentAtPos(loaded_content, position);
		else
			addContent(loaded_content);

		if (content_ptr)
			*content_ptr = loaded_content;
	}
}

/**
	@brief ElementScene::elementSceneGeometricRect
	@return the minimum, margin-less rectangle the element can fit into,
	in scene coordinates.
	It is different from itemsBoundingRect() because it is not supposed
	to imply any margin.
*/
QRectF ElementScene::elementSceneGeometricRect() const
{
	QRectF esgr;
	foreach (QGraphicsItem *qgi, items()) {
		if (qgi->type() == ElementPrimitiveDecorator::Type) continue;
		if (qgi->type() == QGraphicsRectItem::Type) continue;
		if (qgi->type() == PartDynamicTextField::Type) continue;
		if (CustomElementPart *cep = dynamic_cast <CustomElementPart*> (qgi)) {
			esgr |= cep -> sceneGeometricRect();
		}
	}
	return (esgr);
}

/**
	@brief ElementScene::containsTerminals
	@return true if the element has at least one terminal,
	false if it has none.
	\~French true si l'element comporte au moins une borne,
	false s'il n'en a aucune.
*/
bool ElementScene::containsTerminals() const
{
	foreach(QGraphicsItem *qgi,items())
	{
		if (qgraphicsitem_cast<PartTerminal *>(qgi))
		{
			return(true);
		}
	}
	return(false);
}

/**
	@brief ElementScene::undoStack
	@return the undo stack of this element editor
	\~French la pile d'annulations de cet editeur d'element
*/
QUndoStack &ElementScene::undoStack()
{
	return(m_undo_stack);
}

/**
	@brief ElementScene::qgiManager
	@return the QGraphicsItem manager of this item editor
	\~French le gestionnaire de QGraphicsItem de cet editeur d'element
*/
QGIManager &ElementScene::qgiManager()
{
	return(m_qgi_manager);
}

/**
	@brief ElementScene::clipboardMayContainElement
	@return true if the clipboard appears to contain an element
	\~French true si le presse-papier semble contenir un element
*/
bool ElementScene::clipboardMayContainElement()
{
	QString clipboard_text = QApplication::clipboard() -> text().trimmed();
	bool may_be_element = clipboard_text.startsWith("<definition")
			&& clipboard_text.endsWith("</definition>");
	return(may_be_element);
}

/**
	@brief ElementScene::wasCopiedFromThisElement
	\~ @param clipboard_content :
	character string, probably coming from the clipboard.
	\~French chaine de caractere, provenant vraisemblablement du presse-papier.
	\~ @return
	true if clipboard_content has been copied from this element.
	\~French true si clipboard_content a ete copie depuis cet element.
*/
bool ElementScene::wasCopiedFromThisElement(const QString &clipboard_content)
{
	return(clipboard_content == m_last_copied);
}

/**
	@brief ElementScene::cut
	Handles the fact of cutting the selection
	= exporting it in XML to the clipboard then deleting it.
	\~French Gere le fait de couper la selection
	= l'exporter en XML dans le presse-papier puis la supprimer.
*/
void ElementScene::cut()
{
	copy();
	QList<QGraphicsItem *> cut_content = selectedItems();
	clearSelection();
	undoStack().push(new CutPartsCommand(this, cut_content));
}

/**
	@brief ElementScene::copy
	Handles the fact of copying the selection
	= exporting it as XML to the clipboard.
	\~French Gere le fait de copier la selection
	= l'exporter en XML dans lepresse-papier.
*/
void ElementScene::copy()
{
	// accede au presse-papier
	QClipboard *clipboard = QApplication::clipboard();

	// genere la description XML de la selection
	QString clipboard_content = toXml(false).toString(4);

	// met la description XML dans le presse-papier
	if (clipboard -> supportsSelection()) {
		clipboard -> setText(clipboard_content, QClipboard::Selection);
	}
	clipboard -> setText(clipboard_content);

	// retient le dernier contenu copie
	m_last_copied = clipboard_content;
}

/**
	@brief ElementScene::editor
	@return
*/
QETElementEditor* ElementScene::editor() const
{
	return m_element_editor;
}

/**
 * @brief ElementScene::addItems
 * Add items to the scene and emit partsAdded.
 * Prefer always use this method instead of QGraphicsScene::addItem
 * even if you want to add one item, for gain the signal emission
 * @param items
 */
void ElementScene::addItems(QVector<QGraphicsItem *> items)
{
	for (const auto &item : items) {
		addItem(item);
	}

	emit partsAdded();
}

/**
 * @brief ElementScene::removeItems
 * Remove items from the scene and emit partsRemoved.
 * Prefer always use this method instead of QGraphicsScene::removeItem
 * even if you want to remove one item, for gain the signal emission
 * @param items
 */
void ElementScene::removeItems(QVector<QGraphicsItem *> items)
{
	const int previous_selected_count{selectedItems().size()};

		//block signal to avoid multiple emit of selection changed,
		//we emit this signal only once at the end of this function.
	blockSignals(true);
	for (const auto &item : items) {
		removeItem(item);
	}
	blockSignals(false);

	if (previous_selected_count != selectedItems().size()) {
		emit selectionChanged();
	}

	emit partsRemoved();
}

/**
	@brief ElementScene::slot_select
	Select the item in content,
	every others items in the scene are deselected
	@param content
*/
void ElementScene::slot_select(const ElementContent &content)
{
	blockSignals(true);

	/* Before clearing selection,
	 * we must remove the handlers items in @content,
	 * because if in @content there are a selected item,
	 * but also its handlers items, When item is deselected,
	 * the item deletes its handlers items,
	 * then handlers in content doesn't exist anymore and cause segfault
	 */
	QList<QGraphicsItem*> items_list;
	for (QGraphicsItem *qgi : content)
	{
		if(qgi->type() != QetGraphicsHandlerItem::Type)
			items_list << qgi;
	}
	clearSelection();

	foreach(QGraphicsItem *qgi, items_list)
		qgi -> setSelected(true);

	blockSignals(false);
	emit(selectionChanged());
}

/**
	@brief ElementScene::slot_selectAll
	Select all items
*/
void ElementScene::slot_selectAll()
{
	slot_select(items());
}

/**
	@brief ElementScene::slot_deselectAll
	deselect all item
*/
void ElementScene::slot_deselectAll()
{
	slot_select(ElementContent());
}

/**
	@brief ElementScene::slot_invertSelection
	Inverse Selection
	\~French Inverse la selection
*/
void ElementScene::slot_invertSelection()
{
	blockSignals(true);
	foreach(QGraphicsItem *qgi,
			items()) qgi -> setSelected(!qgi -> isSelected());
	blockSignals(false);
	emit(selectionChanged());
}

/**
	@brief ElementScene::slot_delete
	Delete selected items
	\~French Supprime les elements selectionnes
*/
void ElementScene::slot_delete()
{
	const auto selected_items{selectedItems().toVector()};
	if (selected_items.isEmpty()) {
		return;
	}

	m_undo_stack.push(new DeletePartsCommand(this, selected_items));

		// removing items does not trigger QGraphicsScene::selectionChanged()
	emit selectionChanged();
}

/**
	@brief ElementScene::slot_editAuthorInformations
	Starts a dialog to edit the additional information of this element.
	Concretely, this free field is intended to receive information
	on the author of the element, its license, etc.
	\~French Lance un dialogue pour editer les informations complementaires
	de cet element. Concretement, ce champ libre est destine a accueillir
	des informations sur l'auteur de l'element, sa licence, etc.
*/
void ElementScene::slot_editAuthorInformations()
{
	bool is_read_only = m_element_editor && m_element_editor -> isReadOnly();

	// create a dialogue
	// cree un dialogue
	QDialog dialog_author(m_element_editor);
	dialog_author.setModal(true);
#ifdef Q_OS_MACOS
	dialog_author.setWindowFlags(Qt::Sheet);
#endif
	dialog_author.setMinimumSize(400, 260);
	dialog_author.setWindowTitle(
				tr("Éditer les informations sur l'auteur", "window title"));
	QVBoxLayout *dialog_layout = new QVBoxLayout(&dialog_author);

	// adds an explanatory field to the dialogue
	// ajoute un champ explicatif au dialogue
	QLabel *information_label = new QLabel(tr("Vous pouvez utiliser ce champ libre pour mentionner les auteurs de l'élément, sa licence, ou tout autre renseignement que vous jugerez utile."));
	information_label -> setAlignment(Qt::AlignJustify | Qt::AlignVCenter);
	information_label -> setWordWrap(true);
	dialog_layout -> addWidget(information_label);

	// add a QTextEdit to the dialog
	// ajoute un QTextEdit au dialogue
	QTextEdit *text_field = new QTextEdit();
	text_field -> setAcceptRichText(false);
	text_field -> setPlainText(m_element_data.m_drawing_information);
	text_field -> setReadOnly(is_read_only);
	dialog_layout -> addWidget(text_field);

	// ajoute deux boutons au dialogue
	QDialogButtonBox *dialog_buttons = new QDialogButtonBox(
				is_read_only ? QDialogButtonBox::Ok :
						   QDialogButtonBox::Ok
						   | QDialogButtonBox::Cancel);
	dialog_layout -> addWidget(dialog_buttons);
	connect(dialog_buttons, SIGNAL(accepted()),&dialog_author, SLOT(accept()));
	connect(dialog_buttons, SIGNAL(rejected()),&dialog_author, SLOT(reject()));

	// start the dialogue
	// lance le dialogue
	if (dialog_author.exec() == QDialog::Accepted && !is_read_only)
	{
		QString new_infos = text_field -> toPlainText().remove(QChar(13)); // CR-less text
		if (new_infos != m_element_data.m_drawing_information)
		{
			undoStack().push(new ChangeInformationsCommand(
								 this, m_element_data.m_drawing_information, new_infos));
		}
	}
}

/**
	@brief ElementScene::slot_editProperties
	Open dialog to edit the element properties
*/
void  ElementScene::slot_editProperties()
{
	ElementPropertiesEditorWidget epew(m_element_data);
	epew.exec();

	if (m_element_data != epew.editedData())
	{
		undoStack().push(new changeElementDataCommand(this,
													  m_element_data,
													  epew.editedData()));
	}
}

/**
	@brief ElementScene::slot_editNames
	Launch a dialog for edit the names of the edited element
*/
void ElementScene::slot_editNames()
{
	bool is_read_only = m_element_editor && m_element_editor -> isReadOnly();

	NameListDialog dialog_(m_element_editor);

	dialog_.setModal(true);
	dialog_.setMinimumSize(400, 330);
	dialog_.setWindowTitle(tr("Éditer les noms", "window title"));

	dialog_.setInformationText(tr("Vous pouvez spécifier le nom de l'élément dans plusieurs langues."));

	NameListWidget *nlw_ = dialog_.namelistWidget();
	nlw_->setNames(m_element_data.m_names_list);
	nlw_->setReadOnly(is_read_only);

	if (dialog_.exec() == QDialog::Accepted && !is_read_only && !nlw_->isEmpty())
	{
		NamesList new_names = nlw_->names();
		if (new_names != m_element_data. m_names_list) {
			undoStack().push(new ChangeNamesCommand(this,
								m_element_data.m_names_list,
								new_names));
		}
	}
}

/**
	@brief ElementScene::primitives
	@return the list of primitives currently present on the scene.
*/
QList<CustomElementPart *> ElementScene::primitives() const
{
	QList<CustomElementPart *> primitives_list;
	foreach (QGraphicsItem *item, items())
	{
		if (CustomElementPart *primitive = dynamic_cast<CustomElementPart *>(item))
		{
			primitives_list << primitive;
		}
	}
	return(primitives_list);
}

/**
	@brief ElementScene::zItems
	@param options
	@return the parts of the element ordered by increasing zValue
	\~French les parties de l'element ordonnes par zValue croissante
*/
QList<QGraphicsItem *> ElementScene::zItems(ItemOptions options) const
{
	// handle dummy request, i.e. when neither Selected nor NonSelected are set
	if (!(options & ElementScene::Selected)
			&&
			!(options & ElementScene::NonSelected))
	{
		return(QList<QGraphicsItem *>());
	}

	// retrieve all items
	QList<QGraphicsItem *> all_items_list(items());
	QMutableListIterator<QGraphicsItem *> i(all_items_list);

	// remove unrequired items
	if ((options & ElementScene::SelectedOrNot)
			!= ElementScene::SelectedOrNot) {
		bool keep_selected = options & ElementScene::Selected;
		while (i.hasNext()) {
			if (i.next() -> isSelected() != keep_selected) {
				i.remove();
			}
		}
	}

	QList<QGraphicsItem *> terminals;
	QList<QGraphicsItem *> helpers;
	for (i.toFront(); i.hasNext(); )
	{
		i.next();
		QGraphicsItem *qgi = i.value();
		if (
			qgi -> type() == ElementPrimitiveDecorator::Type ||
			qgi -> type() == QGraphicsRectItem::Type ||
			qgi->type() == QetGraphicsHandlerItem::Type
		) {
			i.remove();
			helpers << qgi;
		}
		else if (qgraphicsitem_cast<PartTerminal *>(qgi))
		{
			i.remove();
			terminals << qgi;
		}
	}

	// orders the parts by their zValue
	// ordonne les parties par leur zValue
	if (options & SortByZValue)
	{
		std::sort (
					all_items_list.begin(),
					all_items_list.end(),
					ElementScene::zValueLessThan);
	}

	// possibly add the limits
	// rajoute eventuellement les bornes
	if (options & ElementScene::IncludeTerminals)
	{
		all_items_list += terminals;
	}
	if (options & ElementScene::IncludeHelperItems)
	{
		all_items_list += helpers;
	}
	return(all_items_list);
}

/**
	@brief ElementScene::selectedContent
	@return the selected graphic parts
	\~French les parties graphiques selectionnees
*/
ElementContent ElementScene::selectedContent() const
{
	ElementContent content;
	foreach(QGraphicsItem *qgi, zItems())
	{
		if (qgi -> isSelected()) content << qgi;
	}
	return(content);
}

/**
	@brief ElementScene::getPasteArea
	\~ @param to_paste : Rectangle enclosing the parts to be glued
	\~French Rectangle englobant les parties a coller
	\~ @return the rectangle where you will have to glue these parts
	\~French le rectangle ou il faudra coller ces parties
*/
void ElementScene::getPasteArea(const QRectF &to_paste)
{
	// we draw it on the stage
	// on le dessine sur la scene
	m_paste_area -> setRect(to_paste);
	addItem(m_paste_area);
	//we switch the scene to "zone search for copy / paste" mode
	// on passe la scene en mode "recherche de zone pour copier/coller"
	m_behavior = PasteArea;
}

/**
	@brief ElementScene::reset
	Remove all QGraphicsItems in the scene and clear the undo stack.
*/
void ElementScene::reset()
{
	clearSelection();
	undoStack().clear();

	// We don't add handlers,
	// because it's the role of the primitive or decorator to remove it.
	QList<QGraphicsItem*> items_list;
	for (QGraphicsItem *qgi : items())
	{
		if(qgi->type() != QetGraphicsHandlerItem::Type)
			items_list << qgi;
	}

	for (QGraphicsItem *qgi : items_list)
	{
		removeItem(qgi);
		qgiManager().release(qgi);
	}

	delete m_decorator;
	m_decorator = nullptr;
}

/**
	@brief ElementScene::elementContentBoundingRect
	\~ @param content : Content (= parts) of an element
	\~French Contenu ( = parties) d'un element
	\~ @return the boundingRect of these parts,
	expressed in the coordinates of the scene
	\~French  le boundingRect de ces parties,
	exprime dans les coordonnes de la scene
*/
QRectF ElementScene::elementContentBoundingRect(
		const ElementContent &content) const
{
	QRectF bounding_rect;
	foreach(QGraphicsItem *qgi, content) {
		// skip non-primitives QGraphicsItems (paste area, selection decorator)
		if (qgi -> type() == ElementPrimitiveDecorator::Type) continue;
		if (qgi -> type() == QGraphicsRectItem::Type) continue;
		bounding_rect |= qgi -> sceneBoundingRect();
	}
	return(bounding_rect);
}

/**
	@brief ElementScene::loadContent
	Create and load the content describe in the xml document.
	@param xml_document : xml dom document to analyze
	@return the loaded content
*/
ElementContent ElementScene::loadContent(const QDomDocument &xml_document)
{
	ElementContent loaded_parts;

	// The root is supposed to be an element definition
	QDomElement root = xml_document.documentElement();

	if (root.tagName() != "definition" || root.attribute("type") != "element")
		return(loaded_parts);

	//Load the graphic description of the element
	for (QDomNode node = root.firstChild() ; !node.isNull() ; node = node.nextSibling())
	{
		QDomElement elmts = node.toElement();
		if (elmts.isNull())
			continue;

		if (elmts.tagName() == "description")
		{
			int z = 1;
			for (QDomNode n = node.firstChild() ; !n.isNull() ; n = n.nextSibling())
			{
				QDomElement qde = n.toElement();
				if (qde.isNull())
					continue;
				CustomElementPart *cep = nullptr;
				PartDynamicTextField *pdtf = nullptr;

				if      (qde.tagName() == "line")     cep = new PartLine     (m_element_editor);
				else if (qde.tagName() == "rect")     cep = new PartRectangle(m_element_editor);
				else if (qde.tagName() == "ellipse")  cep = new PartEllipse  (m_element_editor);
				else if (qde.tagName() == "circle")   cep = new PartEllipse  (m_element_editor);
				else if (qde.tagName() == "polygon")  cep = new PartPolygon  (m_element_editor);
				else if (qde.tagName() == "terminal") cep = new PartTerminal (m_element_editor);
				else if (qde.tagName() == "text")     cep = new PartText     (m_element_editor);
				else if (qde.tagName() == "arc")      cep = new PartArc      (m_element_editor);
				else if (qde.tagName() == "dynamic_text") cep = new PartDynamicTextField (m_element_editor);
				//For the input (aka the old text field) we try to convert it to the new partDynamicTextField
				else if (qde.tagName() == "input") cep = pdtf = new PartDynamicTextField(m_element_editor);
				else continue;

				if (QGraphicsItem *qgi = dynamic_cast<QGraphicsItem *>(cep))
				{
					if (!qgi->zValue())
						qgi->setZValue(z++);

					loaded_parts<<qgi;

					if(pdtf)
						pdtf->fromTextFieldXml(qde);
					else
						cep->fromXml(qde);
				}
				else
					delete cep;
			}
		}
	}

	return(loaded_parts);
}

/**
	@brief ElementScene::addContent
	Add content content to this element
	\~French Ajoute le contenu content a cet element
	\~ @param content :
	content (= list of parts) to load
	\~French contenu ( = liste de parties) a charger
	\~ @return Content adds
	\~French Le contenu ajoute
*/
ElementContent ElementScene::addContent(const ElementContent &content)
{
	foreach(QGraphicsItem *part, content) {
		addPrimitive(part);
	}
	return(content);
}

/**
	@brief ElementScene::addContentAtPos
	Add content content to this element
	\~French Ajoute le contenu content a cet element
	\~ @param content :
	content (= list of parts) to load
	\~French contenu ( = liste de parties) a charger
	\~ @param pos :
	Position of the upper left corner of the content after being added
	\~French Position du coin superieur gauche du contenu apres avoir ete ajoute
	\~ @return Content adds
	\~French Le contenu ajoute
*/
ElementContent ElementScene::addContentAtPos(
		const ElementContent &content,
		const QPointF &pos)
{
	// calculate the boundingRect of the content to add
	// calcule le boundingRect du contenu a ajouter
	QRectF bounding_rect = elementContentBoundingRect(content);

	/*   deduced the offset to be applied to the parts to place
	 * them at the point requested
	 *   en deduit le decalage a appliquer aux parties pour les poser
	 * au point demander
	 */
	QPointF offset = pos - bounding_rect.topLeft();

	// add the parts with the correct offset
	// ajoute les parties avec le decalage adequat
	foreach(QGraphicsItem *part, content) {
		part -> setPos(part -> pos() + offset);
		addPrimitive(part);
	}
	return(content);
}

/**
	@brief ElementScene::addPrimitive
	Add a primitive to the scene by wrapping it within an
	ElementPrimitiveDecorator group.
	@param primitive
*/
void ElementScene::addPrimitive(QGraphicsItem *primitive)
{
	if (!primitive) return;
	addItem(primitive);
}

/**
	@brief ElementScene::initPasteArea
	Initializes the paste area
	\~French Initialise la zone de collage
*/
void ElementScene::initPasteArea()
{
	m_paste_area = new QGraphicsRectItem();
	m_paste_area -> setZValue(1000000);

	QPen paste_area_pen;
	paste_area_pen.setStyle(Qt::DashDotLine);
	paste_area_pen.setColor(QColor(30, 56, 86, 255));

	QBrush paste_area_brush;
	paste_area_brush.setStyle(Qt::SolidPattern);
	paste_area_brush.setColor(QColor(90, 167, 255, 64));

	m_paste_area -> setPen(paste_area_pen);
	m_paste_area -> setBrush(paste_area_brush);
}

/**
	@brief ElementScene::snapToGrid
	Rounds the coordinates of the point passed as a parameter
	so that this point is aligned with the grid.
	\~French Arrondit les coordonnees du point passees en parametre
	de facon a ce que ce point soit aligne sur la grille.
	\~ @param point :
	a reference to a QPointF. This object will be modified.
	\~French une reference vers un QPointF. Cet objet sera modifie.
	\~ @return point
*/
QPointF ElementScene::snapToGrid(QPointF point)
{
	point.rx() = qRound(point.x() / m_x_grid) * m_x_grid;
	point.ry() = qRound(point.y() / m_y_grid) * m_y_grid;
	return point;
}

/**
	@brief ElementScene::zValueLessThan
	@param item1 : QGraphicsItem
	@param item2 : QGraphicsItem
	@return true if \a item1's zValue() is less than \a item2's.
*/
bool ElementScene::zValueLessThan(QGraphicsItem *item1, QGraphicsItem *item2)
{
	return(item1-> zValue() < item2 -> zValue());
}

/**
	@brief ElementScene::centerElementToOrigine
	try to center better is possible the element to the scene
	(the calcul isn't optimal but work good)
*/
void ElementScene::centerElementToOrigine()
{
	QRectF size= elementSceneGeometricRect();
	int center_x = qRound(size.center().x());
	int center_y = qRound(size.center().y());

	//define the movement of translation
	int move_x = center_x - (center_x %10);
	if (center_x < 0) move_x -= 10;
	int move_y = center_y - (center_y %10);
	if (center_y < 0) move_y -= 10;

	//move each primitive by @move
	foreach (QGraphicsItem *qgi, items()) {
		if (qgi -> type() == ElementPrimitiveDecorator::Type) continue;
		if (qgi -> type() == QGraphicsRectItem::Type) continue;
		//deselect item for disable decorator
		qgi -> setSelected(false);
		qgi -> moveBy(-(move_x), -(move_y));
	}
	emit (needZoomFit());
}

/**
	@brief ElementScene::managePrimitivesGroups
	Ensure the decorator is adequately shown, hidden or updated so it always
	represents the current selection.
*/
void ElementScene::managePrimitivesGroups()
{
	//this function is not supposed to be reentrant
	if (!m_decorator_lock->tryLock())
		return;

	if (!m_decorator)
	{
		m_decorator = new ElementPrimitiveDecorator();
		connect(m_decorator,
			SIGNAL(actionFinished(ElementEditionCommand*)),
			this, SLOT(stackAction(ElementEditionCommand *)));
		addItem(m_decorator);
		m_decorator -> hide();
	}

	if (m_single_selected_item) {
		m_single_selected_item->removeHandler();
		m_single_selected_item.clear();
	}

		// should we hide the decorator?
	const auto selected_items{zItems(ElementScene::Selected | ElementScene::IncludeTerminals)};
	if (selected_items.size() <= 1)
	{
		m_decorator->hide();

		if (!selected_items.isEmpty())
		{
			if (CustomElementGraphicPart *item_ = dynamic_cast<CustomElementGraphicPart *>(selected_items.first()))
			{
				item_->addHandler();
				m_single_selected_item = item_;
			}
		}
	}
	else
	{
		m_decorator -> setZValue(1000000);
		m_decorator -> setPos(0, 0);
		m_decorator -> setItems(selected_items);
	}
	m_decorator_lock -> unlock();
}

/**
	@brief ElementScene::stackAction
	Push the provided \a command on the undo stack.
	@param command
*/
void ElementScene::stackAction(ElementEditionCommand *command)
{
	if (command -> elementScene()) {
		if (command -> elementScene() != this) return;
	} else {
		command -> setElementScene(this);
	}

	if (!command -> elementView()) {
		foreach (QGraphicsView *view, views()) {
			if (ElementView *element_view =
					dynamic_cast<ElementView *>(view)) {
				command -> setElementView(element_view);
				break;
			}
		}
	}

	undoStack().push(command);
}
