[qet] qet/qet: [5117] Add initial support of texts group

[ Thread Index | Date Index | More lists.tuxfamily.org/qet Archives ]


Revision: 5117
Author:   blacksun
Date:     2017-11-27 20:37:39 +0100 (Mon, 27 Nov 2017)
Log Message:
-----------
Add initial support of texts group

Modified Paths:
--------------
    trunk/qelectrotech.qrc
    trunk/sources/qetgraphicsitem/element.cpp
    trunk/sources/qetgraphicsitem/element.h
    trunk/sources/qetgraphicsitem/qetgraphicsitem.cpp
    trunk/sources/qeticons.cpp
    trunk/sources/qeticons.h
    trunk/sources/ui/diagrampropertieseditordockwidget.cpp
    trunk/sources/ui/dynamicelementtextitemeditor.cpp
    trunk/sources/ui/dynamicelementtextitemeditor.h
    trunk/sources/ui/dynamicelementtextitemeditor.ui
    trunk/sources/ui/dynamicelementtextmodel.cpp
    trunk/sources/ui/dynamicelementtextmodel.h
    trunk/sources/ui/elementpropertieswidget.cpp
    trunk/sources/ui/elementpropertieswidget.h
    trunk/sources/undocommand/addelementtextcommand.cpp

Added Paths:
-----------
    trunk/ico/breeze-icons/scalable/actions/
    trunk/ico/breeze-icons/scalable/actions/16/
    trunk/ico/breeze-icons/scalable/actions/16/object-group.svg
    trunk/sources/qetgraphicsitem/elementtextitemgroup.cpp
    trunk/sources/qetgraphicsitem/elementtextitemgroup.h

Added: trunk/ico/breeze-icons/scalable/actions/16/object-group.svg
===================================================================
--- trunk/ico/breeze-icons/scalable/actions/16/object-group.svg	                        (rev 0)
+++ trunk/ico/breeze-icons/scalable/actions/16/object-group.svg	2017-11-27 19:37:39 UTC (rev 5117)
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#4d4d4d;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 2 2 L 2 14 L 14 14 L 14 2 L 3 2 L 2 2 z M 3 3 L 13 3 L 13 13 L 3 13 L 3 5 L 3 3 z M 6 4 C 4.895 4 4 4.895 4 6 C 4 7.105 4.895 8 6 8 C 7.105 8 8 7.105 8 6 C 8 4.895 7.105 4 6 4 z M 9 4 L 9 8 L 12 8 L 12 4 L 9 4 z M 5 9 L 5 12 L 7 12 L 7 9 L 5 9 z M 10.5 9 L 9 12 L 12 12 L 10.5 9 z "
+     class="ColorScheme-Text"
+     />
+</svg>

Modified: trunk/qelectrotech.qrc
===================================================================
--- trunk/qelectrotech.qrc	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/qelectrotech.qrc	2017-11-27 19:37:39 UTC (rev 5117)
@@ -543,6 +543,7 @@
         <file>ico/breeze-icons/scalable/mimetypes/small/48x48/application-x-qet-element.svgz</file>
         <file>ico/breeze-icons/scalable/mimetypes/small/48x48/application-x-qet-project.svgz</file>
         <file>ico/breeze-icons/scalable/mimetypes/small/48x48/application-x-qet-titleblock.svgz</file>
+        <file>ico/breeze-icons/scalable/actions/16/object-group.svg</file>
         <file>ico/mac_icon/elmt.icns</file>
         <file>ico/mac_icon/qelectrotech.icns</file>
         <file>ico/mac_icon/qet.icns</file>

Modified: trunk/sources/qetgraphicsitem/element.cpp
===================================================================
--- trunk/sources/qetgraphicsitem/element.cpp	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/qetgraphicsitem/element.cpp	2017-11-27 19:37:39 UTC (rev 5117)
@@ -30,6 +30,7 @@
 #include "diagramcontext.h"
 #include "changeelementinformationcommand.h"
 #include "dynamicelementtextitem.h"
+#include "elementtextitemgroup.h"
 
 class ElementXmlRetroCompatibility
 {
@@ -583,6 +584,12 @@
 		}
 	}
 	
+	for (QDomElement qde : QET::findInDomElement(e, "texts_group", ElementTextItemGroup::xmlTaggName()))
+	{
+		ElementTextItemGroup *group = addTextGroup("loaded_from_xml_group");
+		group->fromXml(qde);
+	}
+	
 		//load informations
 	m_element_informations.fromXml(e.firstChildElement("elementInformations"), "elementInformation");
 		/**
@@ -710,7 +717,42 @@
     QDomElement dyn_text = document.createElement("dynamic_texts");
     for (DynamicElementTextItem *deti : m_dynamic_text_list)
         dyn_text.appendChild(deti->toXml(document));
-    element.appendChild(dyn_text);
+	
+	QDomElement texts_group = document.createElement("texts_group");
+	
+		//Dynamic texts owned by groups
+	for(ElementTextItemGroup *group : m_texts_group)
+	{
+			//temporarily remove the texts from group to get the pos relative to element and not group.
+			//Set the alignment to top, because top is not used by groupand so,
+			//each time a text is removed from the group, the alignement is not updated
+		Qt::Alignment al = group->alignment();
+		group->setAlignement(Qt::AlignTop);
+		
+			//Remove the texts from group
+		QList<DynamicElementTextItem *> deti_list = group->texts();
+		for(DynamicElementTextItem *deti : deti_list)
+			group->removeFromGroup(deti);
+		
+			//Save the texts to xml
+		for (DynamicElementTextItem *deti : deti_list)
+			dyn_text.appendChild(deti->toXml(document));
+		
+			//Re add texts to group
+		for(DynamicElementTextItem *deti : deti_list)
+			group->addToGroup(deti);
+		
+			//Restor the alignement
+		group->setAlignement(al);
+		
+			//Save the group to xml
+		texts_group.appendChild(group->toXml(document));
+	}
+	
+		//Append the dynamic texts to element
+	element.appendChild(dyn_text);
+		//Append the texts group to element
+	element.appendChild(texts_group);
 
     return(element);
 }
@@ -726,24 +768,42 @@
     if (deti && !m_dynamic_text_list.contains(deti))
 	{
         m_dynamic_text_list.append(deti);
+		emit textAdded(deti);
 	}
     else
     {
         DynamicElementTextItem *text = new DynamicElementTextItem(this);
         m_dynamic_text_list.append(text);
+		emit textAdded(text);
     }
 }
 
 /**
  * @brief Element::removeDynamicTextItem
- * Remove @deti as dynamic text item of this element.
- * The parent item of deti stay this item.
+ * Remove @deti, no matter if is a child of this element or
+ * a child of a group of this element.
+ * The parent item of deti stay this item and deti is not deleted.
  * @param deti
  */
 void Element::removeDynamicTextItem(DynamicElementTextItem *deti)
 {
     if (m_dynamic_text_list.contains(deti))
+	{
         m_dynamic_text_list.removeOne(deti);
+		emit textRemoved(deti);
+		return;
+	}
+	
+	for(ElementTextItemGroup *group : m_texts_group)
+	{
+		if(group->texts().contains(deti))
+		{
+			removeTextFromGroup(deti, group);
+			m_dynamic_text_list.removeOne(deti);
+			emit textRemoved(deti);
+			return;
+		}
+	}
 }
 
 /**
@@ -751,10 +811,136 @@
  * @return all dynamic text items of this element
  */
 QList<DynamicElementTextItem *> Element::dynamicTextItems() const {
-    return m_dynamic_text_list;
+	return m_dynamic_text_list;
 }
 
 /**
+ * @brief Element::addTextGroup
+ * Create and add an element text item group to this element.
+ * If this element already have a group with the same name,
+ * then @name will renamed to name1 or name2 etc....
+ * @param name : the name of the group
+ * @return the created group.
+ */
+ElementTextItemGroup *Element::addTextGroup(const QString &name)
+{
+	if(m_texts_group.isEmpty())
+	{
+		ElementTextItemGroup *group = new ElementTextItemGroup(name, this);
+		m_texts_group << group;
+		emit textsGroupAdded(group);
+		return group;
+	}
+		
+		//Set a new name if name already exist
+	QString rename = name;
+	int i=1;
+	while (textGroup(rename))
+	{
+		rename = name+QString::number(i);
+		i++;
+	}
+	
+		//Create the group
+	ElementTextItemGroup *group = new ElementTextItemGroup(rename, this);
+	m_texts_group << group;
+	emit textsGroupAdded(group);
+	return group;
+}
+
+/**
+ * @brief Element::removeTextGroup
+ * Remove the text group with name @name
+ * All text owned by the group will be reparented to this element
+ * @param name
+ */
+void Element::removeTextGroup(ElementTextItemGroup *group)
+{
+	if(!m_texts_group.contains(group))
+		return;
+	
+	const QList <QGraphicsItem *> items_list = group->childItems();
+	
+	for(QGraphicsItem *qgi : items_list)
+	{
+		if(qgi->type() == DynamicElementTextItem::Type)
+		{
+			DynamicElementTextItem *deti = static_cast<DynamicElementTextItem *>(qgi);
+			removeTextFromGroup(deti, group);
+		}
+	}
+	
+	m_texts_group.removeOne(group);
+	emit textsGroupAboutToBeRemoved(group);
+	delete group;
+}
+
+/**
+ * @brief Element::textGroup
+ * @param name
+ * @return the text group named @name or nullptr if this element
+ * haven't got a group with this name
+ */
+ElementTextItemGroup *Element::textGroup(const QString &name) const
+{
+	for (ElementTextItemGroup *group : m_texts_group)
+		if(group->name() == name)
+			return group;
+	
+	return nullptr;
+}
+
+/**
+ * @brief Element::textGroups
+ * @return All texts groups of this element
+ */
+QList<ElementTextItemGroup *> Element::textGroups() const
+{
+	return m_texts_group;
+}
+
+/**
+ * @brief Element::addTextToGroup
+ * Add the text @text to the group @group;
+ * If @group isn't owned by this element return false.
+ * The text must be a text of this element.
+ * @return : true if the text was succesfully added to the group.
+ */
+bool Element::addTextToGroup(DynamicElementTextItem *text, ElementTextItemGroup *group)
+{
+	if(!m_dynamic_text_list.contains(text))
+		return false;
+	if(!m_texts_group.contains(group))
+		return false;
+	
+	removeDynamicTextItem(text);
+	group->addToGroup(text);
+	emit textAddedToGroup(text, group);
+	return true;
+}
+
+/**
+ * @brief Element::removeTextFromGroup
+ * Remove the text @text from the group @group, en reparent @text to this element
+ * @return true if text was succesfully removed
+ */
+bool Element::removeTextFromGroup(DynamicElementTextItem *text, ElementTextItemGroup *group)
+{
+	if(!m_texts_group.contains(group))
+		return false;
+	
+	if(group->childItems().contains(text))
+	{
+		group->removeFromGroup(text);
+		emit textRemovedFromGroup(text, group);
+		addDynamicTextItem(text);
+		return true;
+	}
+	
+	return false;
+}
+
+/**
  * @brief Element::AlignedFreeTerminals
  * @return a list of terminal (owned by this element) aligned to other terminal (from other element)
  * The first Terminal of QPair is a Terminal owned by this element,

Modified: trunk/sources/qetgraphicsitem/element.h
===================================================================
--- trunk/sources/qetgraphicsitem/element.h	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/qetgraphicsitem/element.h	2017-11-27 19:37:39 UTC (rev 5117)
@@ -31,6 +31,7 @@
 class NumerotationContext;
 class DiagramTextItem;
 class DynamicElementTextItem;
+class ElementTextItemGroup;
 
 /**
 	This is the base class for electrical elements.
@@ -131,6 +132,12 @@
 		void linkedElementChanged(); //This signal is emited when the linked elements with this element change
 		void elementInfoChange(DiagramContext old_info, DiagramContext new_info);
 		void updateLabel(); //This signal is emited to update element's label
+		void textAdded(DynamicElementTextItem *deti);
+		void textRemoved(DynamicElementTextItem *deti);
+		void textsGroupAdded(ElementTextItemGroup *group);
+		void textsGroupAboutToBeRemoved(ElementTextItemGroup *group);
+		void textAddedToGroup(DynamicElementTextItem *text, ElementTextItemGroup *group);
+		void textRemovedFromGroup(DynamicElementTextItem *text, ElementTextItemGroup *group);
 
 		//METHODS related to information
 	public:
@@ -201,6 +208,12 @@
         void addDynamicTextItem(DynamicElementTextItem *deti = nullptr);
         void removeDynamicTextItem(DynamicElementTextItem *deti);
         QList<DynamicElementTextItem *> dynamicTextItems() const;
+		ElementTextItemGroup *addTextGroup(const QString &name);
+		void removeTextGroup(ElementTextItemGroup *group);
+		ElementTextItemGroup *textGroup(const QString &name) const;
+		QList<ElementTextItemGroup *> textGroups() const;
+		bool addTextToGroup(DynamicElementTextItem *text, ElementTextItemGroup *group);
+		bool removeTextFromGroup(DynamicElementTextItem *text, ElementTextItemGroup *group);
 
 	protected:
 		void drawAxes(QPainter *, const QStyleOptionGraphicsItem *);
@@ -227,6 +240,7 @@
 		bool m_mouse_over;
 		QString m_prefix;
         QList <DynamicElementTextItem *> m_dynamic_text_list;
+		QList <ElementTextItemGroup *> m_texts_group;
 
 };
 

Added: trunk/sources/qetgraphicsitem/elementtextitemgroup.cpp
===================================================================
--- trunk/sources/qetgraphicsitem/elementtextitemgroup.cpp	                        (rev 0)
+++ trunk/sources/qetgraphicsitem/elementtextitemgroup.cpp	2017-11-27 19:37:39 UTC (rev 5117)
@@ -0,0 +1,367 @@
+/*
+	Copyright 2006-2017 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 "elementtextitemgroup.h"
+#include "dynamicelementtextitem.h"
+#include "element.h"
+#include "diagram.h"
+
+#include <QPainter>
+#include <QGraphicsSceneMouseEvent>
+
+bool sorting(QGraphicsItem *qgia, QGraphicsItem *qgib)
+{
+	return qgia->pos().y() < qgib->pos().y();
+}
+
+/**
+ * @brief ElementTextItemGroup::ElementTextItemGroup
+ * @param parent
+ */
+ElementTextItemGroup::ElementTextItemGroup(const QString &name, Element *parent) :
+	QGraphicsItemGroup(parent),
+	m_name(name),
+	m_element(parent)
+{
+	setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable);
+}
+
+ElementTextItemGroup::~ElementTextItemGroup()
+{}
+
+/**
+ * @brief ElementTextItemGroup::addToGroup
+ * @param item
+ */
+void ElementTextItemGroup::addToGroup(QGraphicsItem *item)
+{
+	if(item->type() == DynamicElementTextItem::Type)
+	{
+		item->setFlag(QGraphicsItem::ItemIsSelectable, false);
+		QGraphicsItemGroup::addToGroup(item);	
+		updateAlignement();
+		
+		DynamicElementTextItem *deti = qgraphicsitem_cast<DynamicElementTextItem *>(item);
+		connect(deti, &DynamicElementTextItem::fontSizeChanged,      this, &ElementTextItemGroup::updateAlignement);
+		connect(deti, &DynamicElementTextItem::textChanged,          this, &ElementTextItemGroup::updateAlignement);
+		connect(deti, &DynamicElementTextItem::textFromChanged,      this, &ElementTextItemGroup::updateAlignement);
+		connect(deti, &DynamicElementTextItem::infoNameChanged,      this, &ElementTextItemGroup::updateAlignement);
+		connect(deti, &DynamicElementTextItem::compositeTextChanged, this, &ElementTextItemGroup::updateAlignement);	
+	}
+}
+
+/**
+ * @brief ElementTextItemGroup::removeFromGroup
+ * @param item
+ */
+void ElementTextItemGroup::removeFromGroup(QGraphicsItem *item)
+{
+	QGraphicsItemGroup::removeFromGroup(item);
+	item->setFlag(QGraphicsItem::ItemIsSelectable, true);
+	updateAlignement();
+	
+	if(DynamicElementTextItem *deti = qgraphicsitem_cast<DynamicElementTextItem *>(item))
+	{
+		disconnect(deti, &DynamicElementTextItem::fontSizeChanged,      this, &ElementTextItemGroup::updateAlignement);
+		disconnect(deti, &DynamicElementTextItem::textChanged,          this, &ElementTextItemGroup::updateAlignement);
+		disconnect(deti, &DynamicElementTextItem::textFromChanged,      this, &ElementTextItemGroup::updateAlignement);
+		disconnect(deti, &DynamicElementTextItem::infoNameChanged,      this, &ElementTextItemGroup::updateAlignement);
+		disconnect(deti, &DynamicElementTextItem::compositeTextChanged, this, &ElementTextItemGroup::updateAlignement);
+	}
+}
+
+/**
+ * @brief ElementTextItemGroup::setAlignement
+ * Set the alignement of this group
+ * @param alignement
+ */
+void ElementTextItemGroup::setAlignement(Qt::Alignment alignement)
+{
+	m_alignement = alignement;
+	updateAlignement();
+}
+
+Qt::Alignment ElementTextItemGroup::alignment() const
+{
+	return m_alignement;
+}
+
+/**
+ * @brief ElementTextItemGroup::setAlignement
+ * Update the alignement of the items in this group, according
+ * to the current alignement.
+ * @param alignement
+ */
+void ElementTextItemGroup::updateAlignement()
+{
+	QList <QGraphicsItem *> texts = childItems();
+	if (texts.size() > 1)
+	{
+		prepareGeometryChange();
+		std::sort(texts.begin(), texts.end(), sorting);
+		
+		qreal y_offset =0;
+		
+		if(m_alignement == Qt::AlignLeft)
+		{
+			QPointF ref = texts.first()->pos();
+				
+			for(QGraphicsItem *item : texts)
+			{
+				item->setPos(ref.x(), ref.y()+y_offset);
+				y_offset+=item->boundingRect().height();
+			}
+			return;
+		}
+		else if(m_alignement == Qt::AlignVCenter)
+		{
+			QPointF ref(texts.first()->pos().x() + texts.first()->boundingRect().width()/2,
+						texts.first()->pos().y());
+			
+			for(QGraphicsItem *item : texts)
+			{
+				item->setPos(ref.x() - item->boundingRect().width()/2,
+							 ref.y() + y_offset);
+				y_offset+=item->boundingRect().height();
+			}
+			return;
+				
+		}
+		else if (m_alignement == Qt::AlignRight)
+		{
+			QPointF ref(texts.first()->pos().x() + texts.first()->boundingRect().width(),
+						texts.first()->pos().y());
+			
+			for(QGraphicsItem *item : texts)
+			{
+				item->setPos(ref.x() - item->boundingRect().width(),
+							 ref.y() + y_offset);
+				y_offset+=item->boundingRect().height();
+			}
+			return;
+		}
+	}
+}
+
+/**
+ * @brief ElementTextItemGroup::setName
+ * @param name Set the name of this group
+ */
+void ElementTextItemGroup::setName(QString name)
+{
+	m_name = name;
+}
+
+/**
+ * @brief ElementTextItemGroup::texts
+ * @return Every texts in this group
+ */
+QList<DynamicElementTextItem *> ElementTextItemGroup::texts() const
+{
+	QList<DynamicElementTextItem *> list;
+	for(QGraphicsItem *qgi : childItems())
+	{
+		if(qgi->type() == DynamicElementTextItem::Type)
+			list << static_cast<DynamicElementTextItem *>(qgi);
+	}
+	return list;
+}
+
+/**
+ * @brief ElementTextItemGroup::diagram
+ * @return The diagram of this group, or nullptr if this group is not in a diagram
+ */
+Diagram *ElementTextItemGroup::diagram() const
+{
+	if(scene())
+		return static_cast<Diagram *>(scene());
+	else
+		return nullptr;
+}
+
+/**
+ * @brief ElementTextItemGroup::toXml
+ * Export data of this group to xml
+ * @param dom_document
+ * @return 
+ */
+QDomElement ElementTextItemGroup::toXml(QDomDocument &dom_document) const
+{
+	QDomElement dom_element = dom_document.createElement(this->xmlTaggName());
+	dom_element.setAttribute("name", m_name);
+
+	QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
+	dom_element.setAttribute("alignment", me.valueToKey(m_alignement));
+	
+	QDomElement dom_texts = dom_document.createElement("texts");
+	for(DynamicElementTextItem *deti : texts())
+	{
+		QDomElement text = dom_document.createElement("text");
+		text.setAttribute("uuid", deti->uuid().toString());
+		dom_texts.appendChild(text);
+	}
+	
+	dom_element.appendChild(dom_texts);
+	return dom_element;
+}
+
+/**
+ * @brief ElementTextItemGroup::fromXml
+ * Import data of this group from xml
+ * @param dom_element
+ */
+void ElementTextItemGroup::fromXml(QDomElement &dom_element)
+{
+	if (dom_element.tagName() != xmlTaggName()) {
+		qDebug() << "ElementTextItemGroup::fromXml : Wrong tagg name";
+		return;
+	}
+	
+	m_name = dom_element.attribute("name", "no name");
+	QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
+	m_alignement = Qt::Alignment(me.keyToValue(dom_element.attribute("alignment").toStdString().data()));
+	
+	for(QDomElement text : QET::findInDomElement(dom_element, "texts", "text"))
+	{
+		DynamicElementTextItem *deti = nullptr;
+		QUuid uuid(text.attribute("uuid"));
+		
+		for(DynamicElementTextItem *txt : m_element->dynamicTextItems())
+			if(txt->uuid() == uuid)
+				deti = txt;
+		
+		if (deti)
+			m_element->addTextToGroup(deti, this);
+	}
+}
+
+/**
+ * @brief ElementTextItemGroup::paint
+ * @param painter
+ * @param option
+ * @param widget
+ */
+void ElementTextItemGroup::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+	Q_UNUSED(option);
+	Q_UNUSED(widget);
+	if(isSelected())
+	{
+		painter->save();
+		QPen t;
+		t.setColor(Qt::gray);
+		t.setStyle(Qt::DashDotLine);
+		t.setCosmetic(true);
+		painter->setPen(t);
+		painter->drawRoundRect(boundingRect().adjusted(1, 1, -1, -1), 10, 10);
+		painter->restore();
+	}
+}
+
+/**
+ * @brief ElementTextItemGroup::boundingRect
+ * @return 
+ */
+QRectF ElementTextItemGroup::boundingRect() const
+{
+		//If we refer to the Qt doc, the bounding rect of a QGraphicsItemGroup,
+		//is the bounding of all childrens in the group
+		//When add an item in the group, the bounding rect is good, but
+		//if we move an item already in the group, the bounding rect of the group stay unchanged.
+		//We reimplement this function to avoid this behavior.
+	QRectF rect;
+	for(QGraphicsItem *qgi : childItems())
+	{
+		QRectF r(qgi->pos(), QSize(qgi->boundingRect().width(), qgi->boundingRect().height()));
+		rect = rect.united(r);
+	}
+	return rect;
+}
+
+/**
+ * @brief ElementTextItemGroup::mousePressEvent
+ * @param event
+ */
+void ElementTextItemGroup::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{	
+	if(event->button() == Qt::LeftButton)
+	{
+		m_first_move = true;
+		if(event->modifiers() & Qt::ControlModifier)
+			setSelected(!isSelected());
+	}
+	
+	QGraphicsItemGroup::mousePressEvent(event);
+}
+
+/**
+ * @brief ElementTextItemGroup::mouseMoveEvent
+ * @param event
+ */
+void ElementTextItemGroup::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+	if(isSelected() && event->buttons() & Qt::LeftButton)
+	{
+		if(diagram() && m_first_move)
+			diagram()->beginMoveElementTexts(this);
+		
+		QPointF old_pos = pos();
+		if(m_first_move)
+			m_mouse_to_origin_movement = old_pos - event->buttonDownScenePos(Qt::LeftButton);
+		
+		QPointF expected_pos = event->scenePos() + m_mouse_to_origin_movement;
+		setPos(Diagram::snapToGrid(expected_pos));
+		
+		QPointF effective_movement = pos() - old_pos;
+		if(diagram())
+			diagram()->continueMoveElementTexts(effective_movement);
+	}
+	else
+		event->ignore();
+	
+	if(m_first_move)
+		m_first_move = false;
+}
+
+/**
+ * @brief ElementTextItemGroup::mouseReleaseEvent
+ * @param event
+ */
+void ElementTextItemGroup::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+	if(diagram())
+		diagram()->endMoveElementTexts();
+	
+	if(!(event->modifiers() & Qt::ControlModifier))
+		QGraphicsItemGroup::mouseReleaseEvent(event);
+}
+
+/**
+ * @brief ElementTextItemGroup::keyPressEvent
+ * @param event
+ */
+void ElementTextItemGroup::keyPressEvent(QKeyEvent *event)
+{
+	prepareGeometryChange();
+	if(event->key() == Qt::Key_A)
+		setAlignement(Qt::AlignLeft);
+	else if (event->key() == Qt::Key_Z)
+		setAlignement(Qt::AlignVCenter);
+	else if (event->key() == Qt::Key_E)
+		setAlignement(Qt::AlignRight);
+}
+

Added: trunk/sources/qetgraphicsitem/elementtextitemgroup.h
===================================================================
--- trunk/sources/qetgraphicsitem/elementtextitemgroup.h	                        (rev 0)
+++ trunk/sources/qetgraphicsitem/elementtextitemgroup.h	2017-11-27 19:37:39 UTC (rev 5117)
@@ -0,0 +1,73 @@
+/*
+	Copyright 2006-2017 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/>.
+*/
+#ifndef ELEMENTTEXTITEMGROUP_H
+#define ELEMENTTEXTITEMGROUP_H
+
+#include <QGraphicsItemGroup>
+#include <QObject>
+#include <QDomElement>
+
+class Element;
+class DynamicElementTextItem;
+class Diagram;
+
+/**
+ * @brief The ElementTextItemGroup class
+ * This class represent a group of element text
+ * Texts in the group can be aligned left / center /right
+ */
+class ElementTextItemGroup : public QObject, public  QGraphicsItemGroup
+{
+	Q_OBJECT
+	
+	public:
+		ElementTextItemGroup(const QString &name, Element *parent);
+		~ElementTextItemGroup() override;
+		void addToGroup(QGraphicsItem *item);
+		void removeFromGroup(QGraphicsItem *item);
+		
+		void setAlignement(Qt::Alignment alignement);
+		Qt::Alignment alignment() const;
+		void updateAlignement();
+		void setName(QString name);
+		QString name() const {return m_name;}
+		QList<DynamicElementTextItem *> texts() const;
+		Diagram *diagram() const;
+		
+		QDomElement toXml(QDomDocument &dom_document) const;
+		void fromXml(QDomElement &dom_element);
+		static QString xmlTaggName() {return QString("text_group");}
+		
+		void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+		QRectF boundingRect() const override;
+		
+	protected:
+		void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+		void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
+		void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+		void keyPressEvent(QKeyEvent *event) override;
+
+	private:
+		Qt::Alignment m_alignement = Qt::AlignJustify;
+		QString m_name;
+		bool m_first_move = true;
+		QPointF m_mouse_to_origin_movement;
+		Element *m_element = nullptr;
+};
+
+#endif // ELEMENTTEXTITEMGROUP_H

Modified: trunk/sources/qetgraphicsitem/qetgraphicsitem.cpp
===================================================================
--- trunk/sources/qetgraphicsitem/qetgraphicsitem.cpp	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/qetgraphicsitem/qetgraphicsitem.cpp	2017-11-27 19:37:39 UTC (rev 5117)
@@ -49,10 +49,9 @@
  */
 void QetGraphicsItem::setPos(const QPointF &p) {
 	QPointF pp = Diagram::snapToGrid(p);
-	if (pp == pos() || !is_movable_) return;
-	if (scene() && snap_to_grid_) {
-		QGraphicsItem::setPos(pp);
-	} else QGraphicsItem::setPos(pp);
+	if (pp == pos() || !is_movable_)
+		return;
+	QGraphicsItem::setPos(pp);
 }
 
 /**

Modified: trunk/sources/qeticons.cpp
===================================================================
--- trunk/sources/qeticons.cpp	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/qeticons.cpp	2017-11-27 19:37:39 UTC (rev 5117)
@@ -205,6 +205,7 @@
 		QIcon QETVideo;
 		QIcon super;
 		QIcon sub;
+		QIcon textGroup;
 	}
 }
 
@@ -503,4 +504,5 @@
 	AutoNum             .addFile(":/ico/128x128/plasmagik.png");
     sub                 .addFile(":/ico/22x22/format-text-subscript.png");
     super               .addFile(":/ico/22x22/format-text-superscript.png");
+	textGroup           .addFile(":/ico/breeze-icons/scalable/actions/16/object-group.svg");
 }

Modified: trunk/sources/qeticons.h
===================================================================
--- trunk/sources/qeticons.h	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/qeticons.h	2017-11-27 19:37:39 UTC (rev 5117)
@@ -213,6 +213,7 @@
 		extern QIcon QETVideo;
 		extern QIcon super;
 		extern QIcon sub;
+		extern QIcon textGroup;
 	}
 }
 #endif

Modified: trunk/sources/ui/diagrampropertieseditordockwidget.cpp
===================================================================
--- trunk/sources/ui/diagrampropertieseditordockwidget.cpp	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/ui/diagrampropertieseditordockwidget.cpp	2017-11-27 19:37:39 UTC (rev 5117)
@@ -24,6 +24,7 @@
 #include "qetshapeitem.h"
 #include "shapegraphicsitempropertieswidget.h"
 #include "dynamicelementtextitem.h"
+#include "elementtextitemgroup.h"
 
 /**
  * @brief DiagramPropertiesEditorDockWidget::DiagramPropertiesEditorDockWidget
@@ -90,7 +91,8 @@
 
 	switch (type_)
 	{
-		case Element::Type: {
+		case Element::Type:
+		{
 				//We already edit an element, just update the editor with a new element
 			if (m_edited_qgi_type == type_)
 			{
@@ -101,15 +103,17 @@
 			clear();
 			m_edited_qgi_type = type_;
 			addEditor(new ElementPropertiesWidget(static_cast<Element*>(item), this));
-			break; }
-
-		case DiagramImageItem::Type: {
+			break;
+		}
+		case DiagramImageItem::Type:
+		{
 			clear();
 			m_edited_qgi_type = type_;
 			addEditor(new ImagePropertiesWidget(static_cast<DiagramImageItem*>(item), this));
-			break; }
-
-		case QetShapeItem::Type: {
+			break;
+		}
+		case QetShapeItem::Type:
+		{
 			if (m_edited_qgi_type == type_)
 			{
 				static_cast<ShapeGraphicsItemPropertiesWidget*>(editors().first())->setItem(static_cast<QetShapeItem*>(item));
@@ -119,13 +123,14 @@
 			clear();
 			m_edited_qgi_type = type_;
 			addEditor(new ShapeGraphicsItemPropertiesWidget(static_cast<QetShapeItem*>(item), this));
-			break; }
-			
-		case DynamicElementTextItem::Type: {
+			break;
+		}
+		case DynamicElementTextItem::Type:
+		{
 			DynamicElementTextItem *deti = static_cast<DynamicElementTextItem *>(item);
 			
-				//For dynamic element text, we open the element editor
-				//We already edit an element, just update the editor with a new element
+				//For dynamic element text, we open the element editor to edit it
+				//If we already edit an element, just update the editor with a new element
 			if (m_edited_qgi_type == Element::Type)
 			{
 				static_cast<ElementPropertiesWidget*>(editors().first())->setDynamicText(deti);
@@ -135,8 +140,26 @@
 			clear();
 			m_edited_qgi_type = Element::Type;
 			addEditor(new ElementPropertiesWidget(deti, this));
-			break; }
-
+			break;
+		}
+		case QGraphicsItemGroup::Type:
+		{
+			if(ElementTextItemGroup *group = dynamic_cast<ElementTextItemGroup *>(item))
+			{
+					//For element text item group, we open the element editor to edit it
+					//If we already edit an element, just update the editor with a new element
+				if(m_edited_qgi_type == Element::Type)
+				{
+					static_cast<ElementPropertiesWidget *>(editors().first())->setTextsGroup(group);
+					return;
+				}
+				
+				clear();
+				m_edited_qgi_type = Element::Type;
+				addEditor(new ElementPropertiesWidget(group, this));
+			}
+			break;
+		}
 		default:
 			m_edited_qgi_type = -1;
 			clear();

Modified: trunk/sources/ui/dynamicelementtextitemeditor.cpp
===================================================================
--- trunk/sources/ui/dynamicelementtextitemeditor.cpp	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/ui/dynamicelementtextitemeditor.cpp	2017-11-27 19:37:39 UTC (rev 5117)
@@ -24,6 +24,8 @@
 #include "undocommand/deleteqgraphicsitemcommand.h"
 #include "undocommand/addelementtextcommand.h"
 #include "QPropertyUndoCommand/qpropertyundocommand.h"
+#include "elementtextitemgroup.h"
+#include "deleteqgraphicsitemcommand.h"
 
 #include <QTreeView>
 #include <QUndoCommand>
@@ -33,12 +35,17 @@
     ui(new Ui::DynamicElementTextItemEditor)
 {
     ui->setupUi(this);
+	
     m_tree_view = new QTreeView(this);
     m_tree_view->header()->setDefaultSectionSize(150);
     m_tree_view->setItemDelegate(new DynamicTextItemDelegate(m_tree_view));
 	m_tree_view->setAlternatingRowColors(true);
 	m_tree_view->setEditTriggers(QAbstractItemView::CurrentChanged);
+	m_tree_view->installEventFilter(this);
     ui->verticalLayout->addWidget(m_tree_view);
+	
+	setUpAction();
+	
     setElement(element);
 }
 
@@ -55,12 +62,8 @@
      m_element = element;
     
     DynamicElementTextModel *old_model = m_model;
-    m_model = new DynamicElementTextModel(m_tree_view);
+    m_model = new DynamicElementTextModel(element, m_tree_view);
 	connect(m_model, &DynamicElementTextModel::dataForTextChanged, this, &DynamicElementTextItemEditor::dataEdited);
-    
-    for (DynamicElementTextItem *deti : m_element->dynamicTextItems())
-        m_model->addText(deti);
-    
     m_tree_view->setModel(m_model);
     
     if(old_model)
@@ -76,7 +79,14 @@
 void DynamicElementTextItemEditor::apply()
 {
 	QList <QUndoCommand *> undo_list;
-	for (DynamicElementTextItem *deti : m_element->dynamicTextItems())
+	
+		//Get all dynamic text item of the element
+	QList <DynamicElementTextItem *> deti_list;
+	deti_list << m_element.data()->dynamicTextItems();
+		for(ElementTextItemGroup *group : m_element.data()->textGroups())
+			deti_list << group->texts();
+		
+	for (DynamicElementTextItem *deti : deti_list)
 	{
 		QUndoCommand *undo = m_model->undoForEditedText(deti);
 
@@ -94,9 +104,6 @@
 			delete undo;
 	}
 	
-	for (DynamicElementTextItem *deti : m_element->dynamicTextItems())
-		deti->blockSignals(true);
-	
 	if(!undo_list.isEmpty() && m_element->diagram())
 	{
 		if (undo_list.size() == 1)
@@ -112,9 +119,6 @@
 			us.endMacro();
 		}
 	}
-	
-	for (DynamicElementTextItem *deti : m_element->dynamicTextItems())
-		deti->blockSignals(false);
 }
 
 /**
@@ -133,6 +137,21 @@
 	m_tree_view->setCurrentIndex(index);
 }
 
+/**
+ * @brief DynamicElementTextItemEditor::setCurrentGroup
+ * Expand and select the item for group @group
+ * @param group
+ */
+void DynamicElementTextItemEditor::setCurrentGroup(ElementTextItemGroup *group)
+{
+	QModelIndex index = m_model->indexFromGroup(group);
+	if(!index.isValid())
+		return;
+	
+	m_tree_view->expand(index);
+	m_tree_view->setCurrentIndex(index);
+}
+
 QUndoCommand *DynamicElementTextItemEditor::associatedUndo() const
 {
 	QUndoCommand *parent_undo = new QUndoCommand(tr("Modifier un texte d'élément"));
@@ -149,6 +168,164 @@
 		return nullptr;
 }
 
+/**
+ * @brief DynamicElementTextItemEditor::eventFilter
+ * Reimplemented for intercept the context menu event of the tree view
+ * @param watched
+ * @param event
+ * @return 
+ */
+bool DynamicElementTextItemEditor::eventFilter(QObject *watched, QEvent *event)
+{
+	if(watched == m_tree_view && event->type() == QEvent::ContextMenu)
+	{
+		QContextMenuEvent *qcme = static_cast<QContextMenuEvent *>(event);
+		QModelIndex index = m_tree_view->currentIndex();
+		
+		if(index.isValid())
+		{
+			for(QAction *action : m_actions_list)
+				m_context_menu->removeAction(action);
+			m_add_to_group->menu()->clear();
+			
+				//Pop up a context menu for a group or a text in a group
+			if(m_model->indexIsInGroup(index))
+			{
+				QStandardItem *item = m_model->itemFromIndex(index);
+				if(item)
+				{
+					if(m_model->textFromItem(item)) //User click on a text or a child item of a text
+					{
+						m_context_menu->addAction(m_remove_text_from_group);
+						m_context_menu->addAction(m_remove_current_text);
+					}
+					else//User click on a group item
+						m_context_menu->addAction(m_remove_current_group);
+				}
+			}
+			else //Popup a context menu for a text not owned by a group
+			{
+				if(m_element.data()->textGroups().isEmpty())
+					m_context_menu->addAction(m_new_group);
+				else
+				{
+					m_context_menu->addAction(m_add_to_group);
+					m_context_menu->addAction(m_new_group);
+					m_context_menu->addAction(m_remove_current_text);
+					
+					for(ElementTextItemGroup *grp : m_element.data()->textGroups())
+					{
+						QAction *action = m_add_to_group->menu()->addAction(grp->name());
+						connect(action, &QAction::triggered, m_signal_mapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
+						m_signal_mapper->setMapping(action, grp->name());
+					}
+				}
+			}
+			
+			m_context_menu->popup(qcme->globalPos());
+			return true;
+		}
+	}
+	return AbstractElementPropertiesEditorWidget::eventFilter(watched, event);
+}
+
+void DynamicElementTextItemEditor::setUpAction()
+{
+	m_context_menu = new QMenu(this);
+	
+		//Action add text to a group
+	m_add_to_group = new QAction(tr("Ajouter au groupe"), m_context_menu);
+	m_add_to_group->setMenu(new QMenu(m_context_menu));
+	
+	m_signal_mapper = new QSignalMapper(this);
+	connect(m_signal_mapper, static_cast<void (QSignalMapper::*)(const QString &)>(&QSignalMapper::mapped), this, &DynamicElementTextItemEditor::addCurrentTextToGroup);
+	
+		//Action remove text from a group
+	m_remove_text_from_group = new QAction(tr("Supprimer le texte de ce groupe"), m_context_menu);
+	connect(m_remove_text_from_group, &QAction::triggered, [this]()
+	{
+		QAbstractItemModel *m = this->m_tree_view->model();
+		if(m == nullptr)
+			return;
+		
+		DynamicElementTextModel *model = static_cast<DynamicElementTextModel *>(m);
+		if(model->indexIsInGroup(m_tree_view->currentIndex()))
+		{
+			DynamicElementTextItem *deti = m_model->textFromIndex(m_tree_view->currentIndex());
+			if(deti && deti->parentGroup())
+				m_element.data()->removeTextFromGroup(deti, deti->parentGroup());
+		}
+	});
+	
+		//Action create new group and the connection for open a dialog to edit the name
+		//of the new group
+	m_new_group = new QAction(tr("Nouveau groupe"), m_context_menu);
+	connect(m_new_group, &QAction::triggered, [this]()
+	{
+		QAbstractItemModel *m = this->m_tree_view->model();
+		if(m == nullptr)
+			return;
+		
+		DynamicElementTextModel *model = static_cast<DynamicElementTextModel *>(m);
+		if(model->indexIsInGroup(m_tree_view->currentIndex()))
+			return;
+		
+		DynamicElementTextItem *deti = model->textFromIndex(m_tree_view->currentIndex());
+		if(deti)
+		{
+			Element *element = deti->parentElement();
+			QString name = QInputDialog::getText(this, tr("Nom du groupe"), tr("Entrer le nom du nouveau groupe"));
+			
+			if(name.isEmpty())
+				return;
+			else
+				element->addTextGroup(name);
+		}
+	});
+	
+		//Action remove the selected text
+	m_remove_current_text = new QAction(tr("Supprimer le texte"), m_context_menu);
+	connect(m_remove_current_text, &QAction::triggered, [this]()
+	{
+		QAbstractItemModel *m = this->m_tree_view->model();
+		if(m == nullptr)
+			return;
+		
+		DynamicElementTextModel *model = static_cast<DynamicElementTextModel *>(m);
+		if(DynamicElementTextItem *deti = model->textFromIndex(m_tree_view->currentIndex()))
+		{
+			if(m_element.data()->diagram() && m_element.data()->diagram()->project())
+			{
+				QUndoStack *us =m_element.data()->diagram()->project()->undoStack();
+				DiagramContent dc;
+				dc.m_element_texts << deti;
+				us->push((new DeleteQGraphicsItemCommand(m_element.data()->diagram(), dc)));
+			}
+		}
+	});
+	
+		//Action remove the selected group
+	m_remove_current_group = new QAction(tr("Supprimer le groupe"), m_context_menu);
+	connect(m_remove_current_group, &QAction::triggered, [this]()
+	{
+		QAbstractItemModel *m = this->m_tree_view->model();
+		if(m == nullptr)
+			return;
+		
+		DynamicElementTextModel *model = static_cast<DynamicElementTextModel *>(m);
+		QModelIndex index = m_tree_view->currentIndex();
+		if(model->indexIsInGroup(index) && !model->textFromIndex(index)) //Item is in group and is not a text, so item is the group
+			m_element.data()->removeTextGroup(model->groupFromIndex(index));
+	});
+	
+	m_actions_list << m_add_to_group \
+				   << m_remove_text_from_group \
+				   << m_new_group \
+				   << m_remove_current_text \
+				   << m_remove_current_group;
+	
+}
+
 void DynamicElementTextItemEditor::dataEdited(DynamicElementTextItem *deti)
 {
 	Q_UNUSED(deti)
@@ -157,6 +334,30 @@
 }
 
 /**
+ * @brief DynamicElementTextItemEditor::addCurrentTextToGroup
+ * Add the current selected text to the group named @name
+ * @param name
+ */
+void DynamicElementTextItemEditor::addCurrentTextToGroup(QString name)
+{
+	QModelIndex index = m_tree_view->currentIndex();
+	DynamicElementTextModel *model = static_cast<DynamicElementTextModel *>(m_tree_view->model());
+	
+	DynamicElementTextItem *deti = model->textFromIndex(index);
+	ElementTextItemGroup *group = m_element.data()->textGroup(name);
+	
+	if(deti && group)
+	{
+		if(deti->isSelected())
+		{
+			deti->setSelected(false);
+			group->setSelected(true);
+		}
+		m_element.data()->addTextToGroup(deti, group);
+	}
+}
+
+/**
  * @brief DynamicElementTextItemEditor::on_m_add_text_clicked
  * Add a new dynamic text
  */
@@ -169,8 +370,6 @@
 	if (m_element->diagram())
 	{
 		m_element->diagram()->undoStack().push(new AddElementTextCommand(m_element, deti));
-		m_model->addText(deti);
-		
 		setCurrentText(deti);
 	}
 	else
@@ -193,7 +392,12 @@
 			DiagramContent dc;
 			dc.m_element_texts << deti;
 			m_element->diagram()->undoStack().push(new DeleteQGraphicsItemCommand(m_element->diagram(), dc));
-			m_model->removeText(deti);
 		}
+		return;
     }
+	ElementTextItemGroup *group = m_model->groupFromIndex(m_tree_view->currentIndex());
+	if(group)
+	{
+		m_element.data()->removeTextGroup(group);
+	}
 }

Modified: trunk/sources/ui/dynamicelementtextitemeditor.h
===================================================================
--- trunk/sources/ui/dynamicelementtextitemeditor.h	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/ui/dynamicelementtextitemeditor.h	2017-11-27 19:37:39 UTC (rev 5117)
@@ -24,6 +24,9 @@
 class DynamicElementTextModel;
 class QTreeView;
 class QStandardItem;
+class QMenu;
+class QSignalMapper;
+class ElementTextItemGroup;
 
 namespace Ui {
 	class DynamicElementTextItemEditor;
@@ -42,10 +45,14 @@
 		bool setLiveEdit(bool live_edit) override;
 		void apply() override;
 		void setCurrentText(DynamicElementTextItem *text);
+		void setCurrentGroup(ElementTextItemGroup *group);
 		QUndoCommand *associatedUndo() const override;
+		bool eventFilter(QObject *watched, QEvent *event) override;
 	
 	private:
+		void setUpAction();
 		void dataEdited(DynamicElementTextItem *deti);
+		void addCurrentTextToGroup(QString name);
     
     private slots:
         void on_m_add_text_clicked();
@@ -55,7 +62,14 @@
 		Ui::DynamicElementTextItemEditor *ui;
         QTreeView *m_tree_view = nullptr;
         DynamicElementTextModel *m_model = nullptr;
-        
+		QMenu *m_context_menu = nullptr;
+		QSignalMapper *m_signal_mapper = nullptr;
+		QAction *m_add_to_group = nullptr,
+				*m_remove_text_from_group = nullptr,
+				*m_new_group = nullptr,
+				*m_remove_current_text = nullptr,
+				*m_remove_current_group = nullptr;
+		QList<QAction *> m_actions_list;
 };
 
 #endif // DYNAMICELEMENTTEXTITEMEDITOR_H

Modified: trunk/sources/ui/dynamicelementtextitemeditor.ui
===================================================================
--- trunk/sources/ui/dynamicelementtextitemeditor.ui	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/ui/dynamicelementtextitemeditor.ui	2017-11-27 19:37:39 UTC (rev 5117)
@@ -31,6 +31,9 @@
      </item>
      <item>
       <widget class="QPushButton" name="m_add_text">
+       <property name="toolTip">
+        <string>Ajouter un texte</string>
+       </property>
        <property name="text">
         <string/>
        </property>
@@ -42,6 +45,9 @@
      </item>
      <item>
       <widget class="QPushButton" name="m_remove_text">
+       <property name="toolTip">
+        <string>Supprimer la sélection</string>
+       </property>
        <property name="text">
         <string/>
        </property>

Modified: trunk/sources/ui/dynamicelementtextmodel.cpp
===================================================================
--- trunk/sources/ui/dynamicelementtextmodel.cpp	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/ui/dynamicelementtextmodel.cpp	2017-11-27 19:37:39 UTC (rev 5117)
@@ -29,9 +29,12 @@
 #include "compositetexteditdialog.h"
 #include "terminal.h"
 #include "conductor.h"
+#include "elementtextitemgroup.h"
+#include "qeticons.h"
 
-DynamicElementTextModel::DynamicElementTextModel(QObject *parent) :
-QStandardItemModel(parent)
+DynamicElementTextModel::DynamicElementTextModel(Element *element, QObject *parent) :
+	QStandardItemModel(parent),
+	m_element(element)
 {
     setColumnCount(2);
     setHeaderData(0, Qt::Horizontal, tr("Propriété"), Qt::DisplayRole);
@@ -38,6 +41,19 @@
     setHeaderData(1, Qt::Horizontal, tr("Valeur"), Qt::DisplayRole);
     
 	connect(this, &DynamicElementTextModel::itemChanged, this, &DynamicElementTextModel::itemDataChanged);
+	
+	connect(m_element.data(), &Element::textsGroupAdded,            this, &DynamicElementTextModel::addGroup,            Qt::DirectConnection);
+	connect(m_element.data(), &Element::textsGroupAboutToBeRemoved, this, &DynamicElementTextModel::removeGroup,         Qt::DirectConnection);
+	connect(m_element.data(), &Element::textRemoved,                this, &DynamicElementTextModel::removeText,          Qt::DirectConnection);
+	connect(m_element.data(), &Element::textRemovedFromGroup,       this, &DynamicElementTextModel::removeTextFromGroup, Qt::DirectConnection);
+	connect(m_element.data(), &Element::textAdded,                  this, &DynamicElementTextModel::addText,             Qt::DirectConnection);
+	connect(m_element.data(), &Element::textAddedToGroup,           this, &DynamicElementTextModel::addTextToGroup,      Qt::DirectConnection);
+	
+	for (ElementTextItemGroup *grp : m_element.data()->textGroups())
+		addGroup(grp);
+	
+    for (DynamicElementTextItem *deti : m_element.data()->dynamicTextItems())
+		this->appendRow(itemsForText(deti));
 }
 
 DynamicElementTextModel::~DynamicElementTextModel()
@@ -49,18 +65,43 @@
 }
 
 /**
- * @brief DynamicElementTextModel::addText
+ * @brief DynamicElementTextModel::indexIsInGroup
+ * @param index
+ * @return True if the index represent a group or an item in a group
+ */
+bool DynamicElementTextModel::indexIsInGroup(const QModelIndex &index) const
+{
+	QStandardItem *item = itemFromIndex(index);
+	if(item)
+	{
+		while (item->parent())
+			item = item->parent();
+		
+		if(m_groups_list.values().contains(item))
+			return true;
+		else
+			return false;
+	}
+	return false;
+}
+
+/**
+ * @brief DynamicElementTextModel::itemsForText
  * @param deti
+ * @return The items for the text @deti, if the text@deti is already managed by this model
+ * the returned list is empty
+ * The returned items haven't got the same number of childs if the text is in a group or not.
  */
-void DynamicElementTextModel::addText(DynamicElementTextItem *deti)
+QList<QStandardItem *> DynamicElementTextModel::itemsForText(DynamicElementTextItem *deti)
 {
-    if(m_texts_list.keys().contains(deti))
-        return;
+	QList <QStandardItem *> qsi_list;
+	
+	if(m_texts_list.keys().contains(deti))
+        return qsi_list;
     
-    QList <QStandardItem *> qsi_list;
-
 	QStandardItem *qsi = new QStandardItem(deti->toPlainText());
     qsi->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+	qsi->setIcon(QET::Icons::PartText);
 	
 	
         //Source of text
@@ -174,38 +215,40 @@
 	qsi_list << frame << frame_a;
 	qsi->appendRow(qsi_list);
 	
-		//X pos
-	QStandardItem *x_pos = new QStandardItem(tr("Position X"));
-	x_pos->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+	if(deti->parentGroup() == nullptr)
+	{
+			//X pos
+		QStandardItem *x_pos = new QStandardItem(tr("Position X"));
+		x_pos->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+		
+		QStandardItem *x_pos_a = new QStandardItem;
+		x_pos_a->setData(deti->pos().x(), Qt::EditRole);
+		x_pos_a->setData(DynamicElementTextModel::pos, Qt::UserRole+1);
+		x_pos_a->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);
+		
+		qsi_list.clear();
+		qsi_list << x_pos << x_pos_a;
+		qsi->appendRow(qsi_list);
+		
+			//Y pos
+		QStandardItem *y_pos = new QStandardItem(tr("Position Y"));
+		y_pos->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+		
+		QStandardItem *y_pos_a = new QStandardItem;
+		y_pos_a->setData(deti->pos().y(), Qt::EditRole);
+		y_pos_a->setData(DynamicElementTextModel::pos, Qt::UserRole+1);
+		y_pos_a->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);
+		
+		qsi_list.clear();
+		qsi_list << y_pos << y_pos_a;
+		qsi->appendRow(qsi_list);
+	}
 	
-	QStandardItem *x_pos_a = new QStandardItem;
-	x_pos_a->setData(deti->pos().x(), Qt::EditRole);
-	x_pos_a->setData(DynamicElementTextModel::pos, Qt::UserRole+1);
-	x_pos_a->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);
 	
 	qsi_list.clear();
-	qsi_list << x_pos << x_pos_a;
-	qsi->appendRow(qsi_list);
-	
-		//Y pos
-	QStandardItem *y_pos = new QStandardItem(tr("Position Y"));
-	y_pos->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
-	
-	QStandardItem *y_pos_a = new QStandardItem;
-	y_pos_a->setData(deti->pos().y(), Qt::EditRole);
-	y_pos_a->setData(DynamicElementTextModel::pos, Qt::UserRole+1);
-	y_pos_a->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);
-	
-	qsi_list.clear();
-	qsi_list << y_pos << y_pos_a;
-	qsi->appendRow(qsi_list);
-	
-	
-	qsi_list.clear();
 	QStandardItem *empty_qsi = new QStandardItem(0);
 	empty_qsi->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
 	qsi_list << qsi << empty_qsi;
-    this->appendRow(qsi_list);
 	
     m_texts_list.insert(deti, qsi);
 	blockSignals(true);
@@ -212,9 +255,20 @@
 	enableSourceText(deti, deti->textFrom());
 	blockSignals(false);
 	setConnection(deti, true);
+	
+	return qsi_list;
 }
 
 /**
+ * @brief DynamicElementTextModel::addText
+ * @param deti
+ */
+void DynamicElementTextModel::addText(DynamicElementTextItem *deti)
+{
+	this->appendRow(itemsForText(deti));
+}
+
+/**
  * @brief DynamicElementTextModel::removeText
  * @param deti
  */
@@ -251,15 +305,44 @@
  * @param item
  * @return the text associated with @item. Return value can be nullptr
  * @item can be a child of an item associated with a text
+ * Note can return nullptr
  */
 DynamicElementTextItem *DynamicElementTextModel::textFromItem(QStandardItem *item) const
 {
+		//Item haven't got parent, so they can be only a text or a group
+	if(!item->parent()) 
+	{
+		if(m_texts_list.values().contains(item))
+			return m_texts_list.key(item);
+		else
+			return nullptr;
+	}
+	
+	
+	
 	QStandardItem *text_item = item;
 	while (text_item->parent())
 		text_item = text_item->parent();
 	
-	if (m_texts_list.values().contains(text_item))
+	if (m_texts_list.values().contains(text_item)) //The item is a text
 		return m_texts_list.key(text_item);
+	else if (m_groups_list.values().contains(text_item)) //The item is a group
+	{
+		QStandardItem *previous = item;
+		QStandardItem *top = item;
+			//At the end of the while, previous must be the text
+			//and top the group
+		while(top->parent())
+		{
+			previous = top;
+			top = top->parent();
+		}
+		
+		if(m_texts_list.values().contains(previous))
+			return m_texts_list.key(previous);
+		else
+			return nullptr;
+	}
 	else
 		return nullptr;
 }
@@ -271,10 +354,10 @@
  */
 QModelIndex DynamicElementTextModel::indexFromText(DynamicElementTextItem *text) const
 {
-	if(!m_texts_list.contains(text))
+	if(m_texts_list.contains(text))
+		return m_texts_list.value(text)->index();
+	else
 		return QModelIndex();
-	
-	return m_texts_list.value(text)->index();
 }
 
 /**
@@ -353,12 +436,16 @@
 		quc->setText(tr("Modifier le cadre d'un texte d'élément"));
 	}
 	
-	QPointF p(text_qsi->child(5,1)->data(Qt::EditRole).toDouble(),
-			  text_qsi->child(6,1)->data(Qt::EditRole).toDouble());
-	if(p != deti->pos())
+		//When text is in a group, they're isn't item for position of the text
+	if(text_qsi->child(5,1) && text_qsi->child(6,1))
 	{
-		QPropertyUndoCommand *quc = new QPropertyUndoCommand(deti, "pos", QVariant(deti->pos()), QVariant(p), undo);
-		quc->setText(tr("Déplacer un texte d'élément"));
+		QPointF p(text_qsi->child(5,1)->data(Qt::EditRole).toDouble(),
+				  text_qsi->child(6,1)->data(Qt::EditRole).toDouble());
+		if(p != deti->pos())
+		{
+			QPropertyUndoCommand *quc = new QPropertyUndoCommand(deti, "pos", QVariant(deti->pos()), QVariant(p), undo);
+			quc->setText(tr("Déplacer un texte d'élément"));
+		}
 	}
 	
 	return undo;
@@ -365,6 +452,127 @@
 }
 
 /**
+ * @brief DynamicElementTextModel::AddGroup
+ * Add a text item group to this model
+ * @param group
+ */
+void DynamicElementTextModel::addGroup(ElementTextItemGroup *group)
+{
+	if(m_groups_list.keys().contains(group))
+		return;
+	
+	QStandardItem *grp = new QStandardItem(group->name());
+	grp->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+	grp->setIcon(QET::Icons::textGroup);
+	
+	QStandardItem *empty_qsi = new QStandardItem(0);
+	empty_qsi->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+	
+	QList<QStandardItem *> qsi_list;
+	qsi_list << grp << empty_qsi;
+	
+	this->insertRow(0, qsi_list);
+	m_groups_list.insert(group, grp);
+	
+		//Add the texts of the group
+	for(DynamicElementTextItem *deti : group->texts())
+	{
+		QStandardItem *group_item = m_groups_list.value(group);
+		group_item->appendRow(itemsForText(deti));
+	}
+}
+
+/**
+ * @brief DynamicElementTextModel::removeGroup
+ * Remove the text item group from this model
+ * @param group
+ */
+void DynamicElementTextModel::removeGroup(ElementTextItemGroup *group)
+{
+	if(m_groups_list.keys().contains(group))
+	{
+		QModelIndex group_index = m_groups_list.value(group)->index();
+		this->removeRow(group_index.row(), group_index.parent());
+		m_groups_list.remove(group);
+	}
+}
+
+/**
+ * @brief DynamicElementTextModel::textAddedToGroup
+ * Add the text @text to the group @group
+ * @param deti
+ * @param group
+ */
+void DynamicElementTextModel::addTextToGroup(DynamicElementTextItem *deti, ElementTextItemGroup *group)
+{
+	QStandardItem *group_item = m_groups_list.value(group);
+	group_item->appendRow(itemsForText(deti));
+}
+
+void DynamicElementTextModel::removeTextFromGroup(DynamicElementTextItem *deti, ElementTextItemGroup *group)
+{
+	Q_UNUSED(group)
+	
+	if(m_texts_list.keys().contains(deti))
+	{
+		QStandardItem *text_item = m_texts_list.value(deti);
+		QModelIndex text_index = indexFromItem(text_item);
+		removeRow(text_index.row(), text_index.parent());
+		m_texts_list.remove(deti);
+	}
+}
+
+/**
+ * @brief DynamicElementTextModel::groupFromIndex
+ * @param index
+ * @return the group associated with @index. Return value can be nullptr
+ * @Index can be a child of an index associated with a group
+ */
+ElementTextItemGroup *DynamicElementTextModel::groupFromIndex(const QModelIndex &index) const
+{
+	if(!index.isValid())
+        return nullptr;
+    
+    if (QStandardItem *item = itemFromIndex(index))
+        return groupFromItem(item);
+    else
+        return nullptr;
+}
+
+/**
+ * @brief DynamicElementTextModel::groupFromItem
+ * @param item
+ * @return the group associated with @item. Return value can be nullptr
+ * @item can be a child of an item associated with a group
+ */
+ElementTextItemGroup *DynamicElementTextModel::groupFromItem(QStandardItem *item) const
+{
+	QStandardItem *group_item = item;
+	
+	while (group_item->parent())
+		group_item = group_item->parent();
+	
+	if(m_groups_list.values().contains(group_item))
+		return m_groups_list.key(group_item);
+	else
+		return nullptr;
+}
+
+/**
+ * @brief DynamicElementTextModel::indexFromGroup
+ * @param group
+ * @return The index associated to the group @group
+ * or a default QModelIndex if not match
+ */
+QModelIndex DynamicElementTextModel::indexFromGroup(ElementTextItemGroup *group) const
+{
+	if(m_groups_list.keys().contains(group))
+		return m_groups_list.value(group)->index();
+	else
+		return QModelIndex();
+}
+
+/**
  * @brief DynamicElementTextModel::enableSourceText
  * Enable the good field, according to the current source of text, for the edited text @deti
  * @param deti
@@ -556,8 +764,10 @@
 		}
 		case pos:
 		{
-			qsi->child(5,1)->setData(deti->pos().x(), Qt::EditRole);
-			qsi->child(6,1)->setData(deti->pos().y(), Qt::EditRole);
+			if(qsi->child(5,1))
+				qsi->child(5,1)->setData(deti->pos().x(), Qt::EditRole);
+			if(qsi->child(6,1))
+				qsi->child(6,1)->setData(deti->pos().y(), Qt::EditRole);
 			break;
 		}
 		case frame:

Modified: trunk/sources/ui/dynamicelementtextmodel.h
===================================================================
--- trunk/sources/ui/dynamicelementtextmodel.h	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/ui/dynamicelementtextmodel.h	2017-11-27 19:37:39 UTC (rev 5117)
@@ -23,6 +23,8 @@
 #include "dynamicelementtextitem.h"
 
 class QUndoCommand;
+class ElementTextItemGroup;
+class Element;
 
 /**
  * @brief The DynamicElementTextModel class
@@ -47,20 +49,30 @@
 			frame
         };
         
-		DynamicElementTextModel(QObject *parent = nullptr);
+		DynamicElementTextModel(Element *element, QObject *parent = nullptr);
 		~DynamicElementTextModel() override;
 		
-		void addText(DynamicElementTextItem *deti);
-		void removeText(DynamicElementTextItem *deti);
+		bool indexIsInGroup(const QModelIndex &index) const;
         DynamicElementTextItem *textFromIndex(const QModelIndex &index) const;
         DynamicElementTextItem *textFromItem(QStandardItem *item) const;
 		QModelIndex indexFromText(DynamicElementTextItem *text) const;
 		QUndoCommand *undoForEditedText(DynamicElementTextItem *deti, QUndoCommand *parent_undo = nullptr) const;
 		
+		ElementTextItemGroup *groupFromIndex(const QModelIndex &index) const;
+		ElementTextItemGroup *groupFromItem(QStandardItem *item) const;
+		QModelIndex indexFromGroup(ElementTextItemGroup *group) const;
+		
 	signals:
 		void dataForTextChanged(DynamicElementTextItem *text);
         
     private:
+		QList<QStandardItem *> itemsForText(DynamicElementTextItem *deti);
+		void addText(DynamicElementTextItem *deti);
+		void removeText(DynamicElementTextItem *deti);
+		void addGroup(ElementTextItemGroup *group);
+		void removeGroup(ElementTextItemGroup *group);
+		void addTextToGroup(DynamicElementTextItem *deti, ElementTextItemGroup *group);
+		void removeTextFromGroup(DynamicElementTextItem *deti, ElementTextItemGroup *group);
 		void enableSourceText(DynamicElementTextItem *deti, DynamicElementTextItem::TextFrom tf );
         void itemDataChanged(QStandardItem *qsi);
 		void setConnection(DynamicElementTextItem *deti, bool set);
@@ -67,7 +79,9 @@
 		void updateDataFromText(DynamicElementTextItem *deti, DynamicElementTextModel::ValueType type);
 		
 	private:
+		QPointer<Element> m_element;
 		QHash <DynamicElementTextItem *, QStandardItem *> m_texts_list;
+		QHash <ElementTextItemGroup *, QStandardItem *> m_groups_list;
 		QHash <DynamicElementTextItem *, QList<QMetaObject::Connection>> m_hash_text_connect;
 		bool m_block_dataForTextChanged = false;
 };

Modified: trunk/sources/ui/elementpropertieswidget.cpp
===================================================================
--- trunk/sources/ui/elementpropertieswidget.cpp	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/ui/elementpropertieswidget.cpp	2017-11-27 19:37:39 UTC (rev 5117)
@@ -26,6 +26,7 @@
 #include "qeticons.h"
 #include "dynamicelementtextitemeditor.h"
 #include "dynamicelementtextitem.h"
+#include "elementtextitemgroup.h"
 
 #include <QVBoxLayout>
 #include <QLabel>
@@ -69,6 +70,28 @@
 }
 
 /**
+ * @brief ElementPropertiesWidget::ElementPropertiesWidget
+ * Same as default constructor, the edited element, is the parent element of @group.
+ * The only difference with default constructor, is that the current tab is the tab for dynamic texts,
+ * and the item in the tree that represent @group is expanded and selected.
+ * @param group
+ * @param parent
+ */
+ElementPropertiesWidget::ElementPropertiesWidget(ElementTextItemGroup *group, QWidget *parent) :
+	AbstractElementPropertiesEditorWidget (parent),
+	m_tab (nullptr),
+	m_general_widget(nullptr)
+{
+	if(group->parentItem() && group->parentItem()->type() == Element::Type)
+	{
+		Element *elmt = static_cast<Element *>(group->parentItem());
+		m_diagram = elmt->diagram();
+		buildGui();
+		setTextsGroup(group);
+	}
+}
+
+/**
  * @brief ElementPropertiesWidget::setElement
  * Set @element to be the edited element
  * @param element
@@ -119,6 +142,29 @@
 }
 
 /**
+ * @brief ElementPropertiesWidget::setTextsGroup
+ * Conveniance function : same as call : ElementPropertiesWidget::setElement, with parameter the parent element of @group.
+ * Set the dynamics text tab as current tab, expand and select the item that represent @group
+ * @param group
+ */
+void ElementPropertiesWidget::setTextsGroup(ElementTextItemGroup *group)
+{
+	if(group->parentItem() && group->parentItem()->type() == Element::Type)
+	{
+		setElement(static_cast<Element *>(group->parentItem()));
+		for(AbstractElementPropertiesEditorWidget *aepew : m_list_editor)
+		{
+			if (QString(aepew->metaObject()->className()) == "DynamicElementTextItemEditor")
+			{
+				DynamicElementTextItemEditor *detie = static_cast<DynamicElementTextItemEditor *>(aepew);
+				m_tab->setCurrentWidget(detie);
+				detie->setCurrentGroup(group);
+			}
+		}
+	}
+}
+
+/**
  * @brief ElementPropertiesWidget::apply
  * Apply the new properties by pushing an undo command
  * to the parent project's undo stack of element

Modified: trunk/sources/ui/elementpropertieswidget.h
===================================================================
--- trunk/sources/ui/elementpropertieswidget.h	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/ui/elementpropertieswidget.h	2017-11-27 19:37:39 UTC (rev 5117)
@@ -25,6 +25,7 @@
 class QTabWidget;
 class ElementsLocation;
 class DynamicElementTextItem;
+class ElementTextItemGroup;
 
 
 class ElementPropertiesWidget : public AbstractElementPropertiesEditorWidget
@@ -34,8 +35,10 @@
 	public:
 		explicit ElementPropertiesWidget(Element *elmt, QWidget *parent = nullptr);
 		explicit ElementPropertiesWidget(DynamicElementTextItem *text, QWidget *parent = nullptr);
+		explicit ElementPropertiesWidget(ElementTextItemGroup *group, QWidget *parent = nullptr);
 		void setElement(Element *element) override;
 		void setDynamicText(DynamicElementTextItem *text);
+		void setTextsGroup(ElementTextItemGroup *group);
 		void apply() override;
 		void reset() override;
 		bool setLiveEdit(bool live_edit) override;

Modified: trunk/sources/undocommand/addelementtextcommand.cpp
===================================================================
--- trunk/sources/undocommand/addelementtextcommand.cpp	2017-11-26 22:20:35 UTC (rev 5116)
+++ trunk/sources/undocommand/addelementtextcommand.cpp	2017-11-27 19:37:39 UTC (rev 5117)
@@ -30,6 +30,9 @@
 
 AddElementTextCommand::~AddElementTextCommand()
 {
+	if(m_text->parentGroup())
+		return;
+	
 	if(!m_element->dynamicTextItems().contains(m_text))
 		delete m_text;
 }


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/