diff --git a/sources/diagram.cpp b/sources/diagram.cpp index 41696a615..a60b411db 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -813,6 +813,7 @@ void Diagram::invalidateMovedElements() { conductors_to_move.clear(); conductors_to_update.clear(); texts_to_move.clear(); + elements_texts_to_move.clear(); } /** @@ -825,6 +826,8 @@ void Diagram::fetchMovedElements() { elements_to_move << elmt; } else if (IndependentTextItem *iti = qgraphicsitem_cast(item)) { texts_to_move << iti; + } else if (ElementTextItem *eti = qgraphicsitem_cast(item)) { + elements_texts_to_move << eti; } } @@ -847,13 +850,14 @@ void Diagram::fetchMovedElements() { } } } + moved_elements_fetched = true; } /** - Deplace les elements, conducteurs et textes selectionnes en gerant au - mieux les conducteurs (seuls les conducteurs dont un seul des elements - est deplace sont recalcules, les autres sont deplaces). + Deplace les elements, conducteurs et textes independants selectionnes en + gerant au mieux les conducteurs (seuls les conducteurs dont un seul des + elements est deplace sont recalcules, les autres sont deplaces). @param diff Translation a effectuer @param dontmove QGraphicsItem (optionnel) a ne pas deplacer ; note : ce parametre ne concerne que les elements et les champs de texte. @@ -887,6 +891,26 @@ void Diagram::moveElements(const QPointF &diff, QGraphicsItem *dontmove) { } } +/** + Deplace les champs de textes selectionnes ET rattaches a un element + @param diff Translation a effectuer, exprimee dans les coordonnees de la + scene + @param dontmove ElementTextItem (optionnel) a ne pas deplacer +*/ +void Diagram::moveElementsTexts(const QPointF &diff, ElementTextItem *dontmove) { + // inutile de deplacer les autres textes s'il n'y a pas eu de mouvement concret + if (diff.isNull()) return; + current_movement += diff; + + // deplace les champs de texte rattaches a un element + foreach(ElementTextItem *eti, elementTextsToMove()) { + if (dontmove && eti == dontmove) continue; + QPointF applied_movement = eti -> mapMovementToParent(eti-> mapMovementFromScene(diff)); + eti -> setPos(eti -> pos() + applied_movement); + } + +} + /** Permet de savoir si un element est utilise sur un schema @param location Emplacement d'un element diff --git a/sources/diagram.h b/sources/diagram.h index fdf3997b9..c5b0eae83 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -30,6 +30,7 @@ class DiagramPosition; class DiagramTextItem; class Element; class ElementsLocation; +class ElementTextItem; class IndependentTextItem; class QETProject; class Terminal; @@ -80,6 +81,7 @@ class Diagram : public QGraphicsScene { QSet conductors_to_move; QHash conductors_to_update; QSet texts_to_move; + QSet elements_texts_to_move; QGIManager *qgi_manager; QUndoStack *undo_stack; bool draw_terminals; @@ -158,12 +160,14 @@ class Diagram : public QGraphicsScene { const QSet &conductorsToMove(); const QHash &conductorsToUpdate(); const QSet &independentTextsToMove(); + const QSet &elementTextsToMove(); QSet selectedTexts() const; QSet selectedConductors() const; DiagramContent content() const; DiagramContent selectedContent(); bool canRotateSelection() const; void moveElements(const QPointF &, QGraphicsItem * = 0); + void moveElementsTexts(const QPointF &, ElementTextItem * = 0); bool usesElement(const ElementsLocation &); QUndoStack &undoStack(); @@ -292,6 +296,12 @@ inline const QSet &Diagram::independentTextsToMove() { return(texts_to_move); } +/// @return la liste des textes rattaches a un element qui sont a deplacer +inline const QSet &Diagram::elementTextsToMove() { + if (!moved_elements_fetched) fetchMovedElements(); + return(elements_texts_to_move); +} + /// @return la pile d'annulations de ce schema inline QUndoStack &Diagram::undoStack() { return(*undo_stack); diff --git a/sources/diagramcommands.cpp b/sources/diagramcommands.cpp index 76e250899..8a678954d 100644 --- a/sources/diagramcommands.cpp +++ b/sources/diagramcommands.cpp @@ -19,6 +19,7 @@ #include "element.h" #include "conductor.h" #include "diagram.h" +#include "elementtextitem.h" #include "independenttextitem.h" #include "qgimanager.h" #include "diagram.h" @@ -366,6 +367,64 @@ void MoveElementsCommand::move(const QPointF &actual_movement) { } } +/** + Constructeur + @param diagram Schema sur lequel on deplace des champs de texte + @param texts Liste des textes deplaces + @param m translation subie par les elements + @param parent QUndoCommand parent +*/ +MoveElementsTextsCommand::MoveElementsTextsCommand( + Diagram *diagram, + const QSet &texts, + const QPointF &m, + QUndoCommand *parent +) : + QUndoCommand(parent), + diagram(diagram), + texts_to_move(texts), + movement(m), + first_redo(true) +{ + QString moved_content_sentence = QET::ElementsAndConductorsSentence(0, 0, texts_to_move.count()); + + setText( + QString( + QObject::tr( + "d\351placer %1", + "undo caption - %1 is a sentence listing the moved content" + ).arg(moved_content_sentence) + ) + ); +} + +/// Destructeur +MoveElementsTextsCommand::~MoveElementsTextsCommand() { +} + +/// annule le deplacement +void MoveElementsTextsCommand::undo() { + move(-movement); +} + +/// refait le deplacement +void MoveElementsTextsCommand::redo() { + if (first_redo) first_redo = false; + else move(movement); +} + +/** + deplace les elements et conducteurs + @param actual_movement translation a effectuer sur les elements et conducteurs +*/ +void MoveElementsTextsCommand::move(const QPointF &actual_movement) { + // deplace les textes + foreach(ElementTextItem *text, texts_to_move) { + QPointF applied_movement = text -> mapMovementToParent(text -> mapMovementFromScene(actual_movement)); + text -> setPos(text -> pos() + applied_movement); + } +} + /** Constructeur @param dti Champ de texte modifie diff --git a/sources/diagramcommands.h b/sources/diagramcommands.h index bec30cef8..3e9d8429f 100644 --- a/sources/diagramcommands.h +++ b/sources/diagramcommands.h @@ -27,6 +27,7 @@ class Diagram; class DiagramTextItem; class Element; +class ElementTextItem; class IndependentTextItem; /** @@ -201,6 +202,36 @@ class MoveElementsCommand : public QUndoCommand { bool first_redo; }; +/** + Cette classe represente l'action de deplacer des champs de texte rattaches + a des elements sur un schema +*/ +class MoveElementsTextsCommand : public QUndoCommand { + // constructeurs, destructeur + public: + MoveElementsTextsCommand(Diagram *, const QSet &, const QPointF &m, QUndoCommand * = 0); + virtual ~MoveElementsTextsCommand(); + private: + MoveElementsTextsCommand(const MoveElementsTextsCommand &); + + // methodes + public: + virtual void undo(); + virtual void redo(); + virtual void move(const QPointF &); + + // attributs + private: + /// schema sur lequel on deplace les elements + Diagram *diagram; + /// liste des champs de texte a deplacer + QSet texts_to_move; + /// mouvement effectue + QPointF movement; + /// booleen pour ne pas executer le premier redo() + bool first_redo; +}; + /** Cette classe represente la modification d'un champ de texte */ diff --git a/sources/diagramtextitem.cpp b/sources/diagramtextitem.cpp index 4be308548..536e8369a 100644 --- a/sources/diagramtextitem.cpp +++ b/sources/diagramtextitem.cpp @@ -33,7 +33,7 @@ DiagramTextItem::DiagramTextItem(QGraphicsItem *parent, Diagram *parent_diagram) { setDefaultTextColor(Qt::black); setFont(QETApp::diagramTextsFont()); - setFlags(QGraphicsItem::ItemIsSelectable|QGraphicsItem::ItemIsMovable); + setFlags(QGraphicsItem::ItemIsSelectable|QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemSendsGeometryChanges); connect(this, SIGNAL(lostFocus()), this, SLOT(setNonFocusable())); } @@ -51,7 +51,7 @@ DiagramTextItem::DiagramTextItem(const QString &text, QGraphicsItem *parent, Dia { setDefaultTextColor(Qt::black); setFont(QETApp::diagramTextsFont()); - setFlags(QGraphicsItem::ItemIsSelectable|QGraphicsItem::ItemIsMovable); + setFlags(QGraphicsItem::ItemIsSelectable|QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemSendsGeometryChanges); connect(this, SIGNAL(lostFocus()), this, SLOT(setNonFocusable())); } @@ -98,6 +98,82 @@ void DiagramTextItem::rotateBy(const qreal &added_rotation) { applyRotation(applied_added_rotation); } +/** + Traduit en coordonnees de la scene un mouvement / vecteur initialement + exprime en coordonnees locales. + @param movement Vecteur exprime en coordonnees locales + @return le meme vecteur, exprime en coordonnees de la scene +*/ +QPointF DiagramTextItem::mapMovementToScene(const QPointF &movement) const { + // on definit deux points en coordonnees locales + QPointF local_origin(0.0, 0.0); + QPointF local_movement_point(movement); + + // on les mappe sur la scene + QPointF scene_origin(mapToScene(local_origin)); + QPointF scene_movement_point(mapToScene(local_movement_point)); + + // on calcule le vecteur represente par ces deux points + return(scene_movement_point - scene_origin); +} + +/** + Traduit en coordonnees locales un mouvement / vecteur initialement + exprime en coordonnees de la scene. + @param movement Vecteur exprime en coordonnees de la scene + @return le meme vecteur, exprime en coordonnees locales +*/ +QPointF DiagramTextItem::mapMovementFromScene(const QPointF &movement) const { + // on definit deux points sur la scene + QPointF scene_origin(0.0, 0.0); + QPointF scene_movement_point(movement); + + // on les mappe sur ce QGraphicsItem + QPointF local_origin(mapFromScene(scene_origin)); + QPointF local_movement_point(mapFromScene(scene_movement_point)); + + // on calcule le vecteur represente par ces deux points + return(local_movement_point - local_origin); +} + +/** + Traduit en coordonnees de l'item parent un mouvement / vecteur initialement + exprime en coordonnees locales. + @param movement Vecteur exprime en coordonnees locales + @return le meme vecteur, exprime en coordonnees du parent +*/ +QPointF DiagramTextItem::mapMovementToParent(const QPointF &movement) const { + // on definit deux points en coordonnees locales + QPointF local_origin(0.0, 0.0); + QPointF local_movement_point(movement); + + // on les mappe sur la scene + QPointF parent_origin(mapToParent(local_origin)); + QPointF parent_movement_point(mapToParent(local_movement_point)); + + // on calcule le vecteur represente par ces deux points + return(parent_movement_point - parent_origin); +} + +/** + Traduit en coordonnees locales un mouvement / vecteur initialement + exprime en coordonnees du parent. + @param movement Vecteur exprime en coordonnees du parent + @return le meme vecteur, exprime en coordonnees locales +*/ +QPointF DiagramTextItem::mapMovementFromParent(const QPointF &movement) const { + // on definit deux points sur le parent + QPointF parent_origin(0.0, 0.0); + QPointF parent_movement_point(movement); + + // on les mappe sur ce QGraphicsItem + QPointF local_origin(mapFromParent(parent_origin)); + QPointF local_movement_point(mapFromParent(parent_movement_point)); + + // on calcule le vecteur represente par ces deux points + return(local_movement_point - local_origin); +} + /** Gere les changements dont ce champ de texte est informe */ diff --git a/sources/diagramtextitem.h b/sources/diagramtextitem.h index 217977fed..42bb710df 100644 --- a/sources/diagramtextitem.h +++ b/sources/diagramtextitem.h @@ -52,6 +52,10 @@ class DiagramTextItem : public QGraphicsTextItem { qreal rotationAngle() const; void setRotationAngle(const qreal &); void rotateBy(const qreal &); + QPointF mapMovementToScene(const QPointF &) const; + QPointF mapMovementFromScene(const QPointF &) const; + QPointF mapMovementToParent(const QPointF &) const; + QPointF mapMovementFromParent(const QPointF &) const; protected: virtual QVariant itemChange(GraphicsItemChange, const QVariant &); diff --git a/sources/elementtextitem.cpp b/sources/elementtextitem.cpp index 2564ea748..c5fb65afc 100644 --- a/sources/elementtextitem.cpp +++ b/sources/elementtextitem.cpp @@ -32,8 +32,7 @@ ElementTextItem::ElementTextItem(Element *parent_element, Diagram *parent_diagra original_rotation_angle_(0.0) { // par defaut, les DiagramTextItem sont Selectable et Movable - // on desactive Movable pour les textes des elements - setFlag(QGraphicsItem::ItemIsMovable, false); + // cela nous convient, on ne touche pas a ces flags // ajuste la position du QGraphicsItem lorsque le QTextDocument change connect(document(), SIGNAL(blockCountChanged(int)), this, SLOT(adjustItemPosition(int))); @@ -52,8 +51,7 @@ ElementTextItem::ElementTextItem(const QString &text, Element *parent_element, D original_rotation_angle_(0.0) { // par defaut, les DiagramTextItem sont Selectable et Movable - // on desactive Movable pour les textes des elements - setFlag(QGraphicsItem::ItemIsMovable, false); + // cela nous convient, on ne touche pas a ces flags // ajuste la position du QGraphicsItem lorsque le QTextDocument change connect(document(), SIGNAL(blockCountChanged(int)), this, SLOT(adjustItemPosition(int))); @@ -118,6 +116,15 @@ void ElementTextItem::fromXml(const QDomElement &e) { qFuzzyCompare(qreal(e.attribute("y").toDouble()), _pos.y()) ) { setPlainText(e.attribute("text")); + + qreal user_pos_x, user_pos_y; + if ( + QET::attributeIsAReal(e, "userx", &user_pos_x) && + QET::attributeIsAReal(e, "usery", &user_pos_y) + ) { + setPos(user_pos_x, user_pos_y); + } + qreal xml_rotation_angle; if (QET::attributeIsAReal(e, "userrotation", &xml_rotation_angle)) { setRotationAngle(xml_rotation_angle); @@ -131,12 +138,21 @@ void ElementTextItem::fromXml(const QDomElement &e) { */ QDomElement ElementTextItem::toXml(QDomDocument &document) const { QDomElement result = document.createElement("input"); + result.setAttribute("x", QString("%1").arg(originalPos().x())); result.setAttribute("y", QString("%1").arg(originalPos().y())); + + if (pos() != originalPos()) { + result.setAttribute("userx", QString("%1").arg(pos().x())); + result.setAttribute("usery", QString("%1").arg(pos().y())); + } + result.setAttribute("text", toPlainText()); + if (rotationAngle() != originalRotationAngle()) { result.setAttribute("userrotation", QString("%1").arg(rotationAngle())); } + return(result); } @@ -180,7 +196,7 @@ qreal ElementTextItem::originalRotationAngle() const { */ void ElementTextItem::adjustItemPosition(int new_block_count) { Q_UNUSED(new_block_count); - setPos(originalPos()); + setPos(known_position_); } /** @@ -199,3 +215,80 @@ void ElementTextItem::applyRotation(const qreal &angle) { QGraphicsTextItem::setTransform(rotation, true); } + +/** + Gere les mouvements de souris lies au champ de texte +*/ +void ElementTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { + if (textInteractionFlags() & Qt::TextEditable) { + DiagramTextItem::mouseMoveEvent(e); + } else if ((flags() & QGraphicsItem::ItemIsMovable) && (e -> buttons() & Qt::LeftButton)) { + QPointF old_pos = pos(); + /* + Utiliser e -> pos() directement aurait pour effet de positionner + l'origine du champ de texte a la position indiquee par le curseur, + ce qui n'est pas l'effet recherche + Au lieu de cela, on applique a la position actuelle le vecteur + definissant le mouvement effectue depuis la derniere position + cliquee avec le bouton gauche + */ + QPointF movement = e -> pos() - e -> buttonDownPos(Qt::LeftButton); + + /* + Les methodes pos() et setPos() travaillent toujours avec les + coordonnees de l'item parent (ou de la scene s'il n'y a pas d'item + parent). On n'oublie donc pas de mapper le mouvement fraichement + calcule sur l'item parent avant de l'appliquer. + */ + QPointF parent_movement = mapMovementToParent(movement); + setPos(pos() + parent_movement); + + /* + Comme setPos() n'est pas oblige d'appliquer exactement la valeur + qu'on lui fournit, on calcule le mouvement reellement applique. + */ + QPointF effective_movement = pos() - old_pos; + QPointF scene_effective_movement = mapMovementToScene(mapMovementFromParent(effective_movement)); + + if (Diagram *diagram_ptr = diagram()) { + diagram_ptr -> moveElementsTexts(scene_effective_movement, this); + } + } else e -> ignore(); +} + +/** + Gere le relachement de souris + Cette methode cree un objet d'annulation pour le deplacement +*/ +void ElementTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { + if (Diagram *diagram_ptr = diagram()) { + if ((flags() & QGraphicsItem::ItemIsMovable) && (!diagram_ptr -> current_movement.isNull())) { + diagram_ptr -> undoStack().push( + new MoveElementsTextsCommand( + diagram_ptr, + diagram_ptr -> elementTextsToMove(), + diagram_ptr -> current_movement + ) + ); + diagram_ptr -> current_movement = QPointF(); + } + diagram_ptr -> invalidateMovedElements(); + } + if (!(e -> modifiers() & Qt::ControlModifier)) { + QGraphicsTextItem::mouseReleaseEvent(e); + } +} + +/** + Gere les changements intervenant sur ce champ de texte + @param change Type de changement + @param value Valeur numerique relative au changement +*/ +QVariant ElementTextItem::itemChange(GraphicsItemChange change, const QVariant &value) { + if (change == QGraphicsItem::ItemPositionHasChanged || change == QGraphicsItem::ItemSceneHasChanged) { + // memorise la nouvelle position "officielle" du champ de texte + // cette information servira a le recentrer en cas d'ajout / retrait de lignes + known_position_ = pos(); + } + return(DiagramTextItem::itemChange(change, value)); +} diff --git a/sources/elementtextitem.h b/sources/elementtextitem.h index 52059d1b7..806abf900 100644 --- a/sources/elementtextitem.h +++ b/sources/elementtextitem.h @@ -45,6 +45,7 @@ class ElementTextItem : public DiagramTextItem { Element *parent_element_; bool follow_parent_rotations; QPointF original_position; + QPointF known_position_; qreal original_rotation_angle_; // methodes @@ -70,6 +71,9 @@ class ElementTextItem : public DiagramTextItem { protected: virtual void applyRotation(const qreal &); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *); + virtual QVariant itemChange(GraphicsItemChange, const QVariant &); }; /**