diff --git a/ico/breeze-icons/scalable/actions/16/object-group.svg b/ico/breeze-icons/scalable/actions/16/object-group.svg new file mode 100644 index 000000000..a6e5b811b --- /dev/null +++ b/ico/breeze-icons/scalable/actions/16/object-group.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/qelectrotech.qrc b/qelectrotech.qrc index 0c0fef062..1729d58ba 100644 --- a/qelectrotech.qrc +++ b/qelectrotech.qrc @@ -543,6 +543,7 @@ ico/breeze-icons/scalable/mimetypes/small/48x48/application-x-qet-element.svgz ico/breeze-icons/scalable/mimetypes/small/48x48/application-x-qet-project.svgz ico/breeze-icons/scalable/mimetypes/small/48x48/application-x-qet-titleblock.svgz + ico/breeze-icons/scalable/actions/16/object-group.svg ico/mac_icon/elmt.icns ico/mac_icon/qelectrotech.icns ico/mac_icon/qet.icns diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index dc1f03f1f..0559ca463 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -30,6 +30,7 @@ #include "diagramcontext.h" #include "changeelementinformationcommand.h" #include "dynamicelementtextitem.h" +#include "elementtextitemgroup.h" class ElementXmlRetroCompatibility { @@ -583,6 +584,12 @@ bool Element::fromXml(QDomElement &e, QHash &table_id_adr, bool } } + 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 Element::toXml(QDomDocument &document, QHash &table 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 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 @@ void Element::addDynamicTextItem(DynamicElementTextItem *deti) 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,7 +811,133 @@ void Element::removeDynamicTextItem(DynamicElementTextItem *deti) * @return all dynamic text items of this element */ QList 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 items_list = group->childItems(); + + for(QGraphicsItem *qgi : items_list) + { + if(qgi->type() == DynamicElementTextItem::Type) + { + DynamicElementTextItem *deti = static_cast(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 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; } /** diff --git a/sources/qetgraphicsitem/element.h b/sources/qetgraphicsitem/element.h index 715b76cda..251a76821 100644 --- a/sources/qetgraphicsitem/element.h +++ b/sources/qetgraphicsitem/element.h @@ -31,6 +31,7 @@ class Conductor; class NumerotationContext; class DiagramTextItem; class DynamicElementTextItem; +class ElementTextItemGroup; /** This is the base class for electrical elements. @@ -131,6 +132,12 @@ class Element : public QetGraphicsItem 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 @@ class Element : public QetGraphicsItem void addDynamicTextItem(DynamicElementTextItem *deti = nullptr); void removeDynamicTextItem(DynamicElementTextItem *deti); QList dynamicTextItems() const; + ElementTextItemGroup *addTextGroup(const QString &name); + void removeTextGroup(ElementTextItemGroup *group); + ElementTextItemGroup *textGroup(const QString &name) const; + QList textGroups() const; + bool addTextToGroup(DynamicElementTextItem *text, ElementTextItemGroup *group); + bool removeTextFromGroup(DynamicElementTextItem *text, ElementTextItemGroup *group); protected: void drawAxes(QPainter *, const QStyleOptionGraphicsItem *); @@ -227,6 +240,7 @@ class Element : public QetGraphicsItem bool m_mouse_over; QString m_prefix; QList m_dynamic_text_list; + QList m_texts_group; }; diff --git a/sources/qetgraphicsitem/elementtextitemgroup.cpp b/sources/qetgraphicsitem/elementtextitemgroup.cpp new file mode 100644 index 000000000..071ad809e --- /dev/null +++ b/sources/qetgraphicsitem/elementtextitemgroup.cpp @@ -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 . +*/ +#include "elementtextitemgroup.h" +#include "dynamicelementtextitem.h" +#include "element.h" +#include "diagram.h" + +#include +#include + +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(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(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 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 ElementTextItemGroup::texts() const +{ + QList list; + for(QGraphicsItem *qgi : childItems()) + { + if(qgi->type() == DynamicElementTextItem::Type) + list << static_cast(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(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(); + 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(); + 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); +} + diff --git a/sources/qetgraphicsitem/elementtextitemgroup.h b/sources/qetgraphicsitem/elementtextitemgroup.h new file mode 100644 index 000000000..478d23c1d --- /dev/null +++ b/sources/qetgraphicsitem/elementtextitemgroup.h @@ -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 . +*/ +#ifndef ELEMENTTEXTITEMGROUP_H +#define ELEMENTTEXTITEMGROUP_H + +#include +#include +#include + +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 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 diff --git a/sources/qetgraphicsitem/qetgraphicsitem.cpp b/sources/qetgraphicsitem/qetgraphicsitem.cpp index 4ea4e8853..e32cd811a 100644 --- a/sources/qetgraphicsitem/qetgraphicsitem.cpp +++ b/sources/qetgraphicsitem/qetgraphicsitem.cpp @@ -49,10 +49,9 @@ Diagram* QetGraphicsItem::diagram() const{ */ 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); } /** diff --git a/sources/qeticons.cpp b/sources/qeticons.cpp index 64e160b3d..53a36a4a1 100644 --- a/sources/qeticons.cpp +++ b/sources/qeticons.cpp @@ -205,6 +205,7 @@ namespace QET { QIcon QETVideo; QIcon super; QIcon sub; + QIcon textGroup; } } @@ -503,4 +504,5 @@ void QET::Icons::initIcons() { 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"); } diff --git a/sources/qeticons.h b/sources/qeticons.h index dd0a34aa7..8201d8e32 100644 --- a/sources/qeticons.h +++ b/sources/qeticons.h @@ -213,6 +213,7 @@ namespace QET { extern QIcon QETVideo; extern QIcon super; extern QIcon sub; + extern QIcon textGroup; } } #endif diff --git a/sources/ui/diagrampropertieseditordockwidget.cpp b/sources/ui/diagrampropertieseditordockwidget.cpp index 5cde748a4..ddac9a65a 100644 --- a/sources/ui/diagrampropertieseditordockwidget.cpp +++ b/sources/ui/diagrampropertieseditordockwidget.cpp @@ -24,6 +24,7 @@ #include "qetshapeitem.h" #include "shapegraphicsitempropertieswidget.h" #include "dynamicelementtextitem.h" +#include "elementtextitemgroup.h" /** * @brief DiagramPropertiesEditorDockWidget::DiagramPropertiesEditorDockWidget @@ -90,7 +91,8 @@ void DiagramPropertiesEditorDockWidget::selectionChanged() 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 @@ void DiagramPropertiesEditorDockWidget::selectionChanged() clear(); m_edited_qgi_type = type_; addEditor(new ElementPropertiesWidget(static_cast(item), this)); - break; } - - case DiagramImageItem::Type: { + break; + } + case DiagramImageItem::Type: + { clear(); m_edited_qgi_type = type_; addEditor(new ImagePropertiesWidget(static_cast(item), this)); - break; } - - case QetShapeItem::Type: { + break; + } + case QetShapeItem::Type: + { if (m_edited_qgi_type == type_) { static_cast(editors().first())->setItem(static_cast(item)); @@ -119,13 +123,14 @@ void DiagramPropertiesEditorDockWidget::selectionChanged() clear(); m_edited_qgi_type = type_; addEditor(new ShapeGraphicsItemPropertiesWidget(static_cast(item), this)); - break; } - - case DynamicElementTextItem::Type: { + break; + } + case DynamicElementTextItem::Type: + { DynamicElementTextItem *deti = static_cast(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(editors().first())->setDynamicText(deti); @@ -135,8 +140,26 @@ void DiagramPropertiesEditorDockWidget::selectionChanged() clear(); m_edited_qgi_type = Element::Type; addEditor(new ElementPropertiesWidget(deti, this)); - break; } - + break; + } + case QGraphicsItemGroup::Type: + { + if(ElementTextItemGroup *group = dynamic_cast(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(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(); diff --git a/sources/ui/dynamicelementtextitemeditor.cpp b/sources/ui/dynamicelementtextitemeditor.cpp index ba21294cd..b669d110b 100644 --- a/sources/ui/dynamicelementtextitemeditor.cpp +++ b/sources/ui/dynamicelementtextitemeditor.cpp @@ -24,6 +24,8 @@ #include "undocommand/deleteqgraphicsitemcommand.h" #include "undocommand/addelementtextcommand.h" #include "QPropertyUndoCommand/qpropertyundocommand.h" +#include "elementtextitemgroup.h" +#include "deleteqgraphicsitemcommand.h" #include #include @@ -33,12 +35,17 @@ DynamicElementTextItemEditor::DynamicElementTextItemEditor(Element *element, QWi 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 @@ void DynamicElementTextItemEditor::setElement(Element *element) 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 @@ bool DynamicElementTextItemEditor::setLiveEdit(bool live_edit) void DynamicElementTextItemEditor::apply() { QList undo_list; - for (DynamicElementTextItem *deti : m_element->dynamicTextItems()) + + //Get all dynamic text item of the element + QList 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 @@ void DynamicElementTextItemEditor::apply() 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 @@ void DynamicElementTextItemEditor::apply() us.endMacro(); } } - - for (DynamicElementTextItem *deti : m_element->dynamicTextItems()) - deti->blockSignals(false); } /** @@ -133,6 +137,21 @@ void DynamicElementTextItemEditor::setCurrentText(DynamicElementTextItem *text) 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 @@ QUndoCommand *DynamicElementTextItemEditor::associatedUndo() const 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(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(&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(&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(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(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(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(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) @@ -156,6 +333,30 @@ void DynamicElementTextItemEditor::dataEdited(DynamicElementTextItem *deti) apply(); } +/** + * @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(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 @@ void DynamicElementTextItemEditor::on_m_add_text_clicked() 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 @@ void DynamicElementTextItemEditor::on_m_remove_text_clicked() 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); + } } diff --git a/sources/ui/dynamicelementtextitemeditor.h b/sources/ui/dynamicelementtextitemeditor.h index 2562ee504..b3b01958a 100644 --- a/sources/ui/dynamicelementtextitemeditor.h +++ b/sources/ui/dynamicelementtextitemeditor.h @@ -24,6 +24,9 @@ class DynamicElementTextItem; class DynamicElementTextModel; class QTreeView; class QStandardItem; +class QMenu; +class QSignalMapper; +class ElementTextItemGroup; namespace Ui { class DynamicElementTextItemEditor; @@ -42,10 +45,14 @@ class DynamicElementTextItemEditor : public AbstractElementPropertiesEditorWidge 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 @@ class DynamicElementTextItemEditor : public AbstractElementPropertiesEditorWidge 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 m_actions_list; }; #endif // DYNAMICELEMENTTEXTITEMEDITOR_H diff --git a/sources/ui/dynamicelementtextitemeditor.ui b/sources/ui/dynamicelementtextitemeditor.ui index 303071ca8..66d645f1e 100644 --- a/sources/ui/dynamicelementtextitemeditor.ui +++ b/sources/ui/dynamicelementtextitemeditor.ui @@ -31,6 +31,9 @@ + + Ajouter un texte + @@ -42,6 +45,9 @@ + + Supprimer la sélection + diff --git a/sources/ui/dynamicelementtextmodel.cpp b/sources/ui/dynamicelementtextmodel.cpp index 49a90a3bf..11c6170ee 100644 --- a/sources/ui/dynamicelementtextmodel.cpp +++ b/sources/ui/dynamicelementtextmodel.cpp @@ -29,15 +29,31 @@ #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); 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 @@ DynamicElementTextModel::~DynamicElementTextModel() } /** - * @brief DynamicElementTextModel::addText - * @param deti + * @brief DynamicElementTextModel::indexIsInGroup + * @param index + * @return True if the index represent a group or an item in a group */ -void DynamicElementTextModel::addText(DynamicElementTextItem *deti) +bool DynamicElementTextModel::indexIsInGroup(const QModelIndex &index) const { - if(m_texts_list.keys().contains(deti)) - return; - - QList qsi_list; + 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. + */ +QList DynamicElementTextModel::itemsForText(DynamicElementTextItem *deti) +{ + QList qsi_list; + + if(m_texts_list.keys().contains(deti)) + return qsi_list; + QStandardItem *qsi = new QStandardItem(deti->toPlainText()); qsi->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + qsi->setIcon(QET::Icons::PartText); //Source of text @@ -174,44 +215,57 @@ void DynamicElementTextModel::addText(DynamicElementTextItem *deti) 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); - - 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); + 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); + } 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); 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)); } /** @@ -251,15 +305,44 @@ DynamicElementTextItem *DynamicElementTextModel::textFromIndex(const QModelIndex * @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 @@ DynamicElementTextItem *DynamicElementTextModel::textFromItem(QStandardItem *ite */ 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,17 +436,142 @@ QUndoCommand *DynamicElementTextModel::undoForEditedText(DynamicElementTextItem 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; } +/** + * @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 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 @@ -556,8 +764,10 @@ void DynamicElementTextModel::updateDataFromText(DynamicElementTextItem *deti, V } 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: diff --git a/sources/ui/dynamicelementtextmodel.h b/sources/ui/dynamicelementtextmodel.h index 896d3150b..d4e166447 100644 --- a/sources/ui/dynamicelementtextmodel.h +++ b/sources/ui/dynamicelementtextmodel.h @@ -23,6 +23,8 @@ #include "dynamicelementtextitem.h" class QUndoCommand; +class ElementTextItemGroup; +class Element; /** * @brief The DynamicElementTextModel class @@ -47,27 +49,39 @@ class DynamicElementTextModel : public QStandardItemModel 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 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); void updateDataFromText(DynamicElementTextItem *deti, DynamicElementTextModel::ValueType type); private: + QPointer m_element; QHash m_texts_list; + QHash m_groups_list; QHash > m_hash_text_connect; bool m_block_dataForTextChanged = false; }; diff --git a/sources/ui/elementpropertieswidget.cpp b/sources/ui/elementpropertieswidget.cpp index ceef69a18..9ab360a59 100644 --- a/sources/ui/elementpropertieswidget.cpp +++ b/sources/ui/elementpropertieswidget.cpp @@ -26,6 +26,7 @@ #include "qeticons.h" #include "dynamicelementtextitemeditor.h" #include "dynamicelementtextitem.h" +#include "elementtextitemgroup.h" #include #include @@ -68,6 +69,28 @@ ElementPropertiesWidget::ElementPropertiesWidget(DynamicElementTextItem *text, Q } } +/** + * @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(group->parentItem()); + m_diagram = elmt->diagram(); + buildGui(); + setTextsGroup(group); + } +} + /** * @brief ElementPropertiesWidget::setElement * Set @element to be the edited element @@ -118,6 +141,29 @@ void ElementPropertiesWidget::setDynamicText(DynamicElementTextItem *text) } } +/** + * @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(group->parentItem())); + for(AbstractElementPropertiesEditorWidget *aepew : m_list_editor) + { + if (QString(aepew->metaObject()->className()) == "DynamicElementTextItemEditor") + { + DynamicElementTextItemEditor *detie = static_cast(aepew); + m_tab->setCurrentWidget(detie); + detie->setCurrentGroup(group); + } + } + } +} + /** * @brief ElementPropertiesWidget::apply * Apply the new properties by pushing an undo command diff --git a/sources/ui/elementpropertieswidget.h b/sources/ui/elementpropertieswidget.h index b3e835c60..affd18c4a 100644 --- a/sources/ui/elementpropertieswidget.h +++ b/sources/ui/elementpropertieswidget.h @@ -25,6 +25,7 @@ class Diagram; class QTabWidget; class ElementsLocation; class DynamicElementTextItem; +class ElementTextItemGroup; class ElementPropertiesWidget : public AbstractElementPropertiesEditorWidget @@ -34,8 +35,10 @@ class ElementPropertiesWidget : public AbstractElementPropertiesEditorWidget 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; diff --git a/sources/undocommand/addelementtextcommand.cpp b/sources/undocommand/addelementtextcommand.cpp index 3ebccf455..acbf972ae 100644 --- a/sources/undocommand/addelementtextcommand.cpp +++ b/sources/undocommand/addelementtextcommand.cpp @@ -30,6 +30,9 @@ AddElementTextCommand::AddElementTextCommand(Element *element, DynamicElementTex AddElementTextCommand::~AddElementTextCommand() { + if(m_text->parentGroup()) + return; + if(!m_element->dynamicTextItems().contains(m_text)) delete m_text; }