diff --git a/sources/conductor.cpp b/sources/conductor.cpp index 2d2e1a54d..a1d5cfa2c 100644 --- a/sources/conductor.cpp +++ b/sources/conductor.cpp @@ -419,6 +419,13 @@ void Conductor::paint(QPainter *qp, const QStyleOptionGraphicsItem *options, QWi qp -> save(); qp -> setRenderHint(QPainter::Antialiasing, false); + /* + qp -> save(); + qp -> setPen(Qt::blue); + qp -> drawPath(variableShape(60.0).simplified()); + qp -> restore(); + */ + // determine la couleur du conducteur QColor final_conductor_color(properties_.color); if (isSelected()) { @@ -514,6 +521,13 @@ Diagram *Conductor::diagram() const { return(qobject_cast(scene())); } +/** + @return le champ de texte associe a ce conducteur +*/ +ConductorTextItem *Conductor::textItem() const { + return(text_item); +} + /** Methode de validation d'element XML @param e Un element XML sense represente un Conducteur @@ -569,6 +583,10 @@ void Conductor::mousePressEvent(QGraphicsSceneMouseEvent *e) { } segment = segment -> nextSegment(); } + if (moving_segment || moving_point) { + // en cas de debut de modification de conducteur, on memorise la position du champ de texte + before_mov_text_pos_ = text_item -> pos(); + } } QGraphicsPathItem::mousePressEvent(e); if (e -> modifiers() & Qt::ControlModifier) { @@ -733,9 +751,39 @@ QRectF Conductor::boundingRect() const { } /** - @return La forme / zone "cliquable" du conducteur + @return La forme / zone "cliquable" du conducteur (epaisseur : 5.0px). + @see variableShape() */ QPainterPath Conductor::shape() const { + return(variableShape(5.0)); +} + +/** + @return la distance en dessous de laquelle on considere qu'un point est a + proximite du trajet du conducteur. La valeur est actuellement fixee a + 60.0px. +*/ +qreal Conductor::nearDistance() const { + return(60.0); +} + +/** + @return la zone dans laquelle dont on considere que tous les points sont a + proximite du trajet du conducteur. + @see nearDistance() + @see variableShape() +*/ +QPainterPath Conductor::nearShape() const { + return(variableShape(nearDistance())); +} + +/** + @return la forme du conducteur + @param thickness la moitie de l'epaisseur voulue pour cette forme +*/ +QPainterPath Conductor::variableShape(const qreal &thickness) const { + qreal my_thickness = qAbs(thickness); + QList points = segmentsToPoints(); QPainterPath area; QPointF previous_point; @@ -764,15 +812,24 @@ QPainterPath Conductor::shape() const { qreal p2_x = point2 -> x(); qreal p2_y = point2 -> y(); area.setFillRule(Qt::OddEvenFill); - area.addRect(p1_x - 5.0, p1_y - 5.0, 10.0 + p2_x - p1_x, 10.0 + p2_y - p1_y); + area.addRect(p1_x - my_thickness, p1_y - my_thickness, my_thickness * 2.0 + p2_x - p1_x, my_thickness * 2.0 + p2_y - p1_y); } previous_point = point; area.setFillRule(Qt::WindingFill); - area.addRect(point.x() - 5.0, point.y() - 5.0, 10.0, 10.0); + area.addRect(point.x() - my_thickness, point.y() - my_thickness, my_thickness * 2.0, my_thickness * 2.0 ); } return(area); } +/** + @param point un point, exprime dans les coordonnees du conducteur + @return true si le point est a proximite du conducteur, c-a-d a moins de + 60px du conducteur. +*/ +bool Conductor::isNearConductor(const QPointF &point) { + return(variableShape(60.1).contains(point)); +} + /** Renvoie une valeur donnee apres l'avoir bornee entre deux autres valeurs, en y ajoutant une marge interne. @@ -886,6 +943,14 @@ bool Conductor::fromXml(QDomElement &e) { // recupere la "configuration" du conducteur properties_.fromXml(e); readProperties(); + qreal user_pos_x, user_pos_y; + if ( + QET::attributeIsAReal(e, "userx", &user_pos_x) && + QET::attributeIsAReal(e, "usery", &user_pos_y) + ) { + text_item -> forceMovedByUser(true); + text_item -> setPos(user_pos_x, user_pos_y); + } text_item -> setRotationAngle(e.attribute("rotation").toDouble()); // parcourt les elements XML "segment" et en extrait deux listes de longueurs @@ -984,6 +1049,10 @@ QDomElement Conductor::toXml(QDomDocument &d, QHash &table_adr_ if (text_item -> rotationAngle()) { e.setAttribute("rotation", QString("%1").arg(text_item -> rotationAngle())); } + if (text_item -> wasMovedByUser()) { + e.setAttribute("userx", QString("%1").arg(text_item -> pos().x())); + e.setAttribute("usery", QString("%1").arg(text_item -> pos().y())); + } return(e); } @@ -1043,9 +1112,20 @@ ConductorSegment *Conductor::middleSegment() { @see middleSegment() */ void Conductor::calculateTextItemPosition() { - if (properties_.type != ConductorProperties::Multi) return; if (!text_item) return; - text_item -> setPos(middleSegment() -> middle()); + + if (text_item -> wasMovedByUser()) { + // le champ de texte a ete deplace par l'utilisateur : + // on verifie qu'il est encore a proximite du conducteur + QPointF text_item_pos = text_item -> pos(); + QPainterPath near_shape = nearShape(); + if (!near_shape.contains(text_item_pos)) { + text_item -> setPos(movePointIntoPolygon(text_item_pos, near_shape)); + } + } else { + // positionnement automatique basique + text_item -> setPos(middleSegment() -> middle()); + } } /** @@ -1058,7 +1138,14 @@ void Conductor::saveProfile(bool undo) { conductor_profiles[current_path_type].fromConductor(this); Diagram *dia = diagram(); if (undo && dia) { - dia -> undoStack().push(new ChangeConductorCommand(this, old_profile, conductor_profiles[current_path_type], current_path_type)); + ChangeConductorCommand *undo_object = new ChangeConductorCommand( + this, + old_profile, + conductor_profiles[current_path_type], + current_path_type + ); + undo_object -> setConductorTextItemMove(before_mov_text_pos_, text_item -> pos()); + dia -> undoStack().push(undo_object); } } @@ -1138,6 +1225,15 @@ void Conductor::readProperties() { text_item -> setVisible(properties_.type == ConductorProperties::Multi); } +/** + S'assure que le texte du conducteur est a une position raisonnable + Cette methode ne fait rien si ce conducteur n'affiche pas son champ de + texte. +*/ +void Conductor::adjustTextItemPosition() { + calculateTextItemPosition(); +} + /** Met a jour les proprietes du conducteur apres modification du champ de texte affiche */ @@ -1378,3 +1474,55 @@ void Conductor::deleteSegments() { segments = NULL; } } + +/** + @param point Un point situe a l'exterieur du polygone + @param polygon Le polygone dans lequel on veut rapatrier le point + @return la position du point, une fois ramene dans le polygone, ou plus + exactement sur le bord du polygone +*/ +QPointF Conductor::movePointIntoPolygon(const QPointF &point, const QPainterPath &polygon) { + // decompose le polygone en lignes et points + QList polygons = polygon.simplified().toSubpathPolygons(); + QList lines; + QList points; + foreach(QPolygonF polygon, polygons) { + if (polygon.count() <= 1) continue; + + // on recense les lignes et les points + for (int i = 1 ; i < polygon.count() ; ++ i) { + lines << QLineF(polygon.at(i - 1), polygon.at(i)); + points << polygon.at(i -1); + } + } + + // on fait des projetes orthogonaux du point sur les differents segments du + // polygone, en les triant par longueur croissante + QMap intersections; + foreach (QLineF line, lines) { + QPointF intersection_point; + if (QET::orthogonalProjection(point, line, &intersection_point)) { + intersections.insert(QLineF(intersection_point, point).length(), intersection_point); + } + } + if (intersections.count()) { + // on determine la plus courte longueur pour un projete orthogonal + QPointF the_point = intersections[intersections.keys().first()]; + return(the_point); + } else { + // determine le coin du polygone le plus proche du point exterieur + qreal minimum_length = -1; + int point_index = -1; + for (int i = 0 ; i < points.count() ; ++ i) { + qreal length = qAbs(QLineF(points.at(i), point).length()); + if (minimum_length < 0 || length < minimum_length) { + minimum_length = length; + point_index = i; + } + } + // on connait desormais le coin le plus proche du texte + + // aucun projete orthogonal n'a donne quoi que ce soit, on met le texte sur un des coins du polygone + return(points.at(point_index)); + } +} diff --git a/sources/conductor.h b/sources/conductor.h index 7a92b0f93..696719a82 100644 --- a/sources/conductor.h +++ b/sources/conductor.h @@ -22,7 +22,7 @@ #include "conductorprofile.h" #include "conductorproperties.h" class ConductorSegment; -class DiagramTextItem; +class ConductorTextItem; class Element; typedef QPair ConductorBend; typedef QHash ConductorProfilesGroup; @@ -61,10 +61,15 @@ class Conductor : public QObject, public QGraphicsPathItem { /// @return true si ce conducteur est detruit bool isDestroyed() const { return(destroyed); } Diagram *diagram() const; + ConductorTextItem *textItem() const; void updatePath(const QRectF & = QRectF()); void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); QRectF boundingRect() const; virtual QPainterPath shape() const; + virtual qreal nearDistance() const; + virtual QPainterPath nearShape() const; + virtual QPainterPath variableShape(const qreal &) const; + virtual bool isNearConductor(const QPointF &); qreal length(); ConductorSegment *middleSegment(); bool containsPoint(const QPointF &) const; @@ -81,10 +86,11 @@ class Conductor : public QObject, public QGraphicsPathItem { void setProfiles(const ConductorProfilesGroup &); ConductorProfilesGroup profiles() const; void readProperties(); + void adjustTextItemPosition(); public slots: void displayedTextChanged(); - + protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *); virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *); @@ -100,7 +106,7 @@ class Conductor : public QObject, public QGraphicsPathItem { /// booleen indiquant si le fil est encore valide bool destroyed; /// champ de texte editable pour les conducteurs non unifilaires - DiagramTextItem *text_item; + ConductorTextItem *text_item; /// segments composant le conducteur ConductorSegment *segments; /// attributs lies aux manipulations a la souris @@ -110,6 +116,7 @@ class Conductor : public QObject, public QGraphicsPathItem { int moved_point; qreal previous_z_value; ConductorSegment *moved_segment; + QPointF before_mov_text_pos_; /// booleen indiquant si le conducteur a ete modifie manuellement par l'utilisateur bool modified_path; /// booleen indiquant s'il faut sauver le profil courant au plus tot @@ -146,5 +153,6 @@ class Conductor : public QObject, public QGraphicsPathItem { static qreal conductor_bound(qreal, qreal, qreal, qreal = 0.0); static qreal conductor_bound(qreal, qreal, bool); static Qt::Corner movementType(const QPointF &, const QPointF &); + static QPointF movePointIntoPolygon(const QPointF &, const QPainterPath &); }; #endif diff --git a/sources/conductortextitem.cpp b/sources/conductortextitem.cpp index 964c010b1..eb778059a 100644 --- a/sources/conductortextitem.cpp +++ b/sources/conductortextitem.cpp @@ -17,6 +17,7 @@ */ #include "conductortextitem.h" #include "conductor.h" +#include "diagramcommands.h" /** Constructeur @@ -25,11 +26,11 @@ */ ConductorTextItem::ConductorTextItem(Conductor *parent_conductor, Diagram *parent_diagram) : DiagramTextItem(parent_conductor, parent_diagram), - parent_conductor_(parent_conductor) + parent_conductor_(parent_conductor), + moved_by_user_(false) { // par defaut, les DiagramTextItem sont Selectable et Movable - // on desactive Movable pour les textes des conducteurs - setFlag(QGraphicsItem::ItemIsMovable, false); + // cela nous convient, on ne touche pas a ces flags } /** @@ -40,11 +41,11 @@ ConductorTextItem::ConductorTextItem(Conductor *parent_conductor, Diagram *paren */ ConductorTextItem::ConductorTextItem(const QString &text, Conductor *parent_conductor, Diagram *parent_diagram) : DiagramTextItem(text, parent_conductor, parent_diagram), - parent_conductor_(parent_conductor) + parent_conductor_(parent_conductor), + moved_by_user_(false) { // par defaut, les DiagramTextItem sont Selectable et Movable - // on desactive Movable pour les textes des conducteurs - setFlag(QGraphicsItem::ItemIsMovable, false); + // cela nous convient, on ne touche pas a ces flags } /** @@ -68,8 +69,16 @@ Conductor *ConductorTextItem::parentConductor() const { @param e L'element XML representant le champ de texte */ void ConductorTextItem::fromXml(const QDomElement &e) { - setPos(e.attribute("x").toDouble(), e.attribute("y").toDouble()); 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); + } + setRotationAngle(e.attribute("rotation").toDouble()); } @@ -79,11 +88,95 @@ void ConductorTextItem::fromXml(const QDomElement &e) { */ QDomElement ConductorTextItem::toXml(QDomDocument &document) const { QDomElement result = document.createElement("input"); - result.setAttribute("x", QString("%1").arg(pos().x())); - result.setAttribute("y", QString("%1").arg(pos().y())); + result.setAttribute("userx", QString("%1").arg(pos().x())); + result.setAttribute("usery", QString("%1").arg(pos().y())); result.setAttribute("text", toPlainText()); if (rotationAngle()) { result.setAttribute("rotation", QString("%1").arg(rotationAngle())); } return(result); } + +/** + @return true si ce champ de texte a ete explictement deplace par + l'utilisateur, false sinon +*/ +bool ConductorTextItem::wasMovedByUser() const { + return(moved_by_user_); +} + +/** + @param moved_by_user true pour que la position du texte soit consideree + comme ayant ete definie par l'utilisateur (et donc soit sauvegardee), false + pour remettre le texte a sa position originelle +*/ +void ConductorTextItem::forceMovedByUser(bool moved_by_user) { + if (moved_by_user == moved_by_user_) return; + + moved_by_user_ = moved_by_user; + if (!moved_by_user && parent_conductor_) { + parent_conductor_ -> adjustTextItemPosition(); + } + +} + +/** + Gere les clics de souris lies au champ de texte + @param e Objet decrivant l'evenement souris +*/ +void ConductorTextItem::mousePressEvent(QGraphicsSceneMouseEvent *e) { + if ((flags() & QGraphicsItem::ItemIsMovable) && (e -> buttons() & Qt::LeftButton)) { + before_mov_pos_ = pos(); + } + DiagramTextItem::mousePressEvent(e); +} + +/** + Gere les mouvements de souris lies au champ de texte + @param e Objet decrivant l'evenement souris +*/ +void ConductorTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { + if (textInteractionFlags() & Qt::TextEditable) { + QGraphicsTextItem::mouseMoveEvent(e); + } else if ((flags() & QGraphicsItem::ItemIsMovable) && (e -> buttons() & Qt::LeftButton)) { + QPointF old_pos = pos(); + QPointF intended_pos = mapToParent(e -> pos()) - matrix().map(e -> buttonDownPos(Qt::LeftButton)); + + // si ce texte est attache a un conducteur, alors ses mouvements seront + // limites a une certaine distance du trace de ce conducteur + if (parent_conductor_) { + if (parent_conductor_ -> isNearConductor(intended_pos)) { + setPos(intended_pos); + } + } + + } else e -> ignore(); +} + +/** + Gere le relachement de souris + Cette methode cree un objet d'annulation pour le deplacement + @param e Objet decrivant l'evenement souris +*/ +void ConductorTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { + if (flags() & QGraphicsItem::ItemIsMovable) { + if (Diagram *diagram_ptr = diagram()) { + // on cree un objet d'annulation correspondant au deplacement qui s'acheve + QPointF applied_movement = pos() - before_mov_pos_; + + if (!applied_movement.isNull()) { + // on cree un objet d'annulation seulement pour ce champ de texte + MoveConductorsTextsCommand *undo_object = new MoveConductorsTextsCommand(diagram_ptr); + undo_object -> addTextMovement(this, before_mov_pos_, pos(), moved_by_user_); + + // on active le flag indiquant que ce champ de texte a ete explicitement repositionne par l'utilisateur + moved_by_user_ = true; + + diagram_ptr -> undoStack().push(undo_object); + } + } + } + if (!(e -> modifiers() & Qt::ControlModifier)) { + QGraphicsTextItem::mouseReleaseEvent(e); + } +} diff --git a/sources/conductortextitem.h b/sources/conductortextitem.h index b6cf44d3e..a86fab285 100644 --- a/sources/conductortextitem.h +++ b/sources/conductortextitem.h @@ -47,9 +47,18 @@ class ConductorTextItem : public DiagramTextItem { // methodes public: virtual int type() const { return Type; } + virtual bool wasMovedByUser() const; + virtual void forceMovedByUser(bool); + + protected: + virtual void mousePressEvent(QGraphicsSceneMouseEvent *); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *); // attributs private: Conductor *parent_conductor_; + bool moved_by_user_; + QPointF before_mov_pos_; }; #endif diff --git a/sources/diagram.cpp b/sources/diagram.cpp index a5f0454ad..9163f7310 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -24,6 +24,8 @@ #include "diagramcontent.h" #include "diagramposition.h" #include "elementtextitem.h" +#include "elementsmover.h" +#include "elementtextsmover.h" #include "exportdialog.h" #include "ghostelement.h" #include "independenttextitem.h" @@ -41,7 +43,6 @@ Diagram::Diagram(QObject *parent) : QGraphicsScene(parent), draw_grid(true), use_border(true), - moved_elements_fetched(false), draw_terminals(true), draw_colored_conductors_(true), project_(0), @@ -58,6 +59,10 @@ Diagram::Diagram(QObject *parent) : t.setStyle(Qt::DashLine); conductor_setter -> setPen(t); conductor_setter -> setLine(QLineF(QPointF(0.0, 0.0), QPointF(0.0, 0.0))); + + // initialise les objets gerant les deplacements + elements_mover_ = new ElementsMover(); // deplacements d'elements/conducteurs/textes + element_texts_mover_ = new ElementTextsMover(); // deplacements d'ElementTextItem } /** @@ -69,6 +74,10 @@ Diagram::~Diagram() { // suppression du QGIManager - tous les elements qu'il connait sont supprimes delete qgi_manager; + // suppression des objets gerant les deplacements + delete elements_mover_; + delete element_texts_mover_; + // recense les items supprimables QList deletable_items; foreach(QGraphicsItem *qgi, items()) { @@ -140,7 +149,8 @@ void Diagram::keyPressEvent(QKeyEvent *e) { case Qt::Key_Down: movement = QPointF(0.0, +yGrid); break; } if (!movement.isNull() && !focusItem()) { - moveElements(movement); + beginMoveElements(); + continueMoveElements(movement); } } QGraphicsScene::keyPressEvent(e); @@ -154,14 +164,11 @@ void Diagram::keyReleaseEvent(QKeyEvent *e) { if (!isReadOnly()) { // detecte le relachement d'une touche de direction ( = deplacement d'elements) if ( - (e -> key() == Qt::Key_Left || e -> key() == Qt::Key_Right ||\ - e -> key() == Qt::Key_Up || e -> key() == Qt::Key_Down) &&\ - !current_movement.isNull() && !e -> isAutoRepeat() + (e -> key() == Qt::Key_Left || e -> key() == Qt::Key_Right || + e -> key() == Qt::Key_Up || e -> key() == Qt::Key_Down) && + !e -> isAutoRepeat() ) { - // cree un objet d'annulation pour le mouvement qui vient de se finir - undoStack().push(new MoveElementsCommand(this, selectedContent(), current_movement)); - invalidateMovedElements(); - current_movement = QPointF(); + endMoveElements(); } } QGraphicsScene::keyReleaseEvent(e); @@ -561,9 +568,9 @@ bool Diagram::fromXml(QDomElement &document, QPointF position, bool consider_inf // remplissage des listes facultatives if (content_ptr) { - content_ptr -> elements = added_elements; - content_ptr -> conductorsToMove = added_conductors; - content_ptr -> textFields = added_texts; + content_ptr -> elements = added_elements.toSet(); + content_ptr -> conductorsToMove = added_conductors.toSet(); + content_ptr -> textFields = added_texts.toSet(); } return(true); @@ -804,110 +811,59 @@ QList Diagram::customElements() const { } /** - Oublie la liste des elements et conducteurs en mouvement + Initialise un deplacement d'elements, conducteurs et champs de texte sur le + schema. + @param driver_item Item deplace par la souris et ne necessitant donc pas + d'etre deplace lors des appels a continueMovement. + @see ElementsMover */ -void Diagram::invalidateMovedElements() { - if (!moved_elements_fetched) return; - moved_elements_fetched = false; - elements_to_move.clear(); - conductors_to_move.clear(); - conductors_to_update.clear(); - texts_to_move.clear(); - elements_texts_to_move.clear(); +int Diagram::beginMoveElements(QGraphicsItem *driver_item) { + return(elements_mover_ -> beginMovement(this, driver_item)); } /** - Reconstruit la liste des elements et conducteurs en mouvement + Prend en compte un mouvement composant un deplacement d'elements, + conducteurs et champs de texte + @param movement mouvement a ajouter au deplacement en cours + @see ElementsMover */ -void Diagram::fetchMovedElements() { - // recupere les elements deplaces - foreach (QGraphicsItem *item, selectedItems()) { - if (Element *elmt = qgraphicsitem_cast(item)) { - 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; - } - } - - // pour chaque element deplace, determine les conducteurs qui seront modifies - foreach(Element *elmt, elements_to_move) { - foreach(Terminal *terminal, elmt -> terminals()) { - foreach(Conductor *conductor, terminal -> conductors()) { - Terminal *other_terminal; - if (conductor -> terminal1 == terminal) { - other_terminal = conductor -> terminal2; - } else { - other_terminal = conductor -> terminal1; - } - // si les deux elements du conducteur sont deplaces - if (elements_to_move.contains(other_terminal -> parentElement())) { - conductors_to_move << conductor; - } else { - conductors_to_update << conductor; - } - } - } - } - - moved_elements_fetched = true; +void Diagram::continueMoveElements(const QPointF &movement) { + elements_mover_ -> continueMovement(movement); } /** - 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. + Finalise un deplacement d'elements, conducteurs et champs de texte + @see ElementsMover */ -void Diagram::moveElements(const QPointF &diff, QGraphicsItem *dontmove) { - // inutile de deplacer les autres elements s'il n'y a pas eu de mouvement concret - if (diff.isNull()) return; - current_movement += diff; - - // deplace les elements selectionnes - foreach(Element *element, elementsToMove()) { - if (dontmove && element == dontmove) continue; - element -> setPos(element -> pos() + diff); - } - - // deplace certains conducteurs - foreach(Conductor *conductor, conductorsToMove()) { - conductor -> setPos(conductor -> pos() + diff); - } - - // recalcule les autres conducteurs - foreach(Conductor *conductor, conductorsToUpdate()) { - conductor -> updatePath(); - } - - // deplace les champs de texte - foreach(DiagramTextItem *dti, independentTextsToMove()) { - if (dontmove && dti == dontmove) continue; - dti -> setPos(dti -> pos() + diff); - } +void Diagram::endMoveElements() { + elements_mover_ -> endMovement(); } /** - 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 + Initialise un deplacement d'ElementTextItems + @param driver_item Item deplace par la souris et ne necessitant donc pas + d'etre deplace lors des appels a continueMovement. + @see ElementTextsMover */ -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); - } - +int Diagram::beginMoveElementTexts(QGraphicsItem *driver_item) { + return(element_texts_mover_ -> beginMovement(this, driver_item)); +} + +/** + Prend en compte un mouvement composant un deplacement d'ElementTextItems + @param movement mouvement a ajouter au deplacement en cours + @see ElementTextsMover +*/ +void Diagram::continueMoveElementTexts(const QPointF &movement) { + element_texts_mover_ -> continueMovement(movement); +} + +/** + Finalise un deplacement d'ElementTextItems + @see ElementTextsMover +*/ +void Diagram::endMoveElementTexts() { + element_texts_mover_ -> endMovement(); } /** @@ -1084,18 +1040,17 @@ DiagramContent Diagram::content() const { @return le contenu selectionne du schema. */ DiagramContent Diagram::selectedContent() { - invalidateMovedElements(); DiagramContent dc; - dc.elements = elementsToMove().toList(); - dc.textFields = independentTextsToMove().toList(); - dc.conductorsToMove = conductorsToMove().toList(); - dc.conductorsToUpdate = conductorsToUpdate().toList(); - // recupere les conducteurs selectionnes isoles (= non deplacables mais supprimables) - foreach(QGraphicsItem *qgi, items()) { - if (Conductor *c = qgraphicsitem_cast(qgi)) { + // recupere les elements deplaces + foreach (QGraphicsItem *item, selectedItems()) { + if (Element *elmt = qgraphicsitem_cast(item)) { + dc.elements << elmt; + } else if (IndependentTextItem *iti = qgraphicsitem_cast(item)) { + dc.textFields << iti; + } else if (Conductor *c = qgraphicsitem_cast(item)) { + // recupere les conducteurs selectionnes isoles (= non deplacables mais supprimables) if ( - c -> isSelected() &&\ !c -> terminal1 -> parentItem() -> isSelected() &&\ !c -> terminal2 -> parentItem() -> isSelected() ) { @@ -1103,7 +1058,27 @@ DiagramContent Diagram::selectedContent() { } } } - invalidateMovedElements(); + + // pour chaque element deplace, determine les conducteurs qui seront modifies + foreach(Element *elmt, dc.elements) { + foreach(Terminal *terminal, elmt -> terminals()) { + foreach(Conductor *conductor, terminal -> conductors()) { + Terminal *other_terminal; + if (conductor -> terminal1 == terminal) { + other_terminal = conductor -> terminal2; + } else { + other_terminal = conductor -> terminal1; + } + // si les deux elements du conducteur sont deplaces + if (dc.elements.contains(other_terminal -> parentElement())) { + dc.conductorsToMove << conductor; + } else { + dc.conductorsToUpdate << conductor; + } + } + } + } + return(dc); } diff --git a/sources/diagram.h b/sources/diagram.h index dc1f19af4..1d9bd050d 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -30,7 +30,9 @@ class DiagramPosition; class DiagramTextItem; class Element; class ElementsLocation; +class ElementsMover; class ElementTextItem; +class ElementTextsMover; class IndependentTextItem; class QETProject; class Terminal; @@ -63,8 +65,6 @@ class Diagram : public QGraphicsScene { ConductorProperties defaultConductorProperties; /// Dimensions et cartouches du schema BorderInset border_and_inset; - /// Mouvement en cours lors d'un deplacement d'elements et conducteurs - QPointF current_movement; /// taille de la grille en abscisse static const int xGrid; /// taille de la grille en ordonnee @@ -74,14 +74,10 @@ class Diagram : public QGraphicsScene { private: QGraphicsLineItem *conductor_setter; + ElementsMover *elements_mover_; + ElementTextsMover *element_texts_mover_; bool draw_grid; bool use_border; - bool moved_elements_fetched; - QSet elements_to_move; - QSet conductors_to_move; - QSet conductors_to_update; - QSet texts_to_move; - QSet elements_texts_to_move; QGIManager *qgi_manager; QUndoStack *undo_stack; bool draw_terminals; @@ -154,20 +150,17 @@ class Diagram : public QGraphicsScene { bool isEmpty() const; QList customElements() const; - void invalidateMovedElements(); - void fetchMovedElements(); - const QSet &elementsToMove(); - const QSet &conductorsToMove(); - const QSet &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); + int beginMoveElements(QGraphicsItem * = 0); + void continueMoveElements(const QPointF &); + void endMoveElements(); + int beginMoveElementTexts(QGraphicsItem * = 0); + void continueMoveElementTexts(const QPointF &); + void endMoveElementTexts(); bool usesElement(const ElementsLocation &); QUndoStack &undoStack(); @@ -272,36 +265,6 @@ inline Diagram::BorderOptions Diagram::borderOptions() { return(retour); } -/// @return la liste des elements a deplacer -inline const QSet &Diagram::elementsToMove() { - if (!moved_elements_fetched) fetchMovedElements(); - return(elements_to_move); -} - -/// @return la liste des conducteurs a deplacer -inline const QSet &Diagram::conductorsToMove() { - if (!moved_elements_fetched) fetchMovedElements(); - return(conductors_to_move); -} - -/// @return la liste des conducteurs a modifier (typiquement les conducteurs dont seul un element est deplace) -inline const QSet &Diagram::conductorsToUpdate() { - if (!moved_elements_fetched) fetchMovedElements(); - return(conductors_to_update); -} - -/// @return la liste des textes a deplacer -inline const QSet &Diagram::independentTextsToMove() { - if (!moved_elements_fetched) fetchMovedElements(); - 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 a6c3f3f04..5517d8101 100644 --- a/sources/diagramcommands.cpp +++ b/sources/diagramcommands.cpp @@ -18,6 +18,7 @@ #include "diagramcommands.h" #include "element.h" #include "conductor.h" +#include "conductortextitem.h" #include "diagram.h" #include "elementtextitem.h" #include "independenttextitem.h" @@ -357,12 +358,37 @@ void MoveElementsCommand::move(const QPointF &actual_movement) { conductor -> updatePath(); } + // repositionne les textes des conducteurs mis a jour + foreach(ConductorTextItem *text_item, moved_conductor_texts_.keys()) { + // determine s'il s'agit d'un undo ou d'un redo + qreal coef = actual_movement.x() / movement.x(); + // -1 : undo, 1 : redo + QPointF desired_pos = coef > 0 ? moved_conductor_texts_[text_item].second : moved_conductor_texts_[text_item].first; + text_item -> setPos(desired_pos); + } + // deplace les textes foreach(DiagramTextItem *text, content_to_move.textFields) { text -> setPos(text -> pos() + actual_movement); } } +/** + Ajoute un champ de texte de conducteur a deplacer + @param text_item Champ de texte a deplacer + @param old_pos Position du champ de texte avant le deplacement + @param new_pos Position du champ de texte apres le deplacement +*/ +void MoveElementsCommand::addConductorTextItemMovement(ConductorTextItem *text_item, const QPointF &old_pos, const QPointF &new_pos) { + if (moved_conductor_texts_.contains(text_item)) return; + if (!text_item -> wasMovedByUser()) return; + if (new_pos == old_pos) return; + moved_conductor_texts_.insert( + text_item, + qMakePair(old_pos, new_pos) + ); +} + /** Constructeur @param diagram Schema sur lequel on deplace des champs de texte @@ -421,6 +447,89 @@ void MoveElementsTextsCommand::move(const QPointF &actual_movement) { } } +/** + Constructeur + @param diagram Schema sur lequel on deplace des champs de texte + @param texts Textes deplaces : chaque ConductorTextItem est associe a un + couple de position : avant et apres le deplacement + @param m translation subie par les elements + @param parent QUndoCommand parent +*/ +MoveConductorsTextsCommand::MoveConductorsTextsCommand( + Diagram *diagram, + QUndoCommand *parent +) : + QUndoCommand(parent), + diagram(diagram), + first_redo(true) +{ +} + +/// Destructeur +MoveConductorsTextsCommand::~MoveConductorsTextsCommand() { +} + +/// annule le deplacement +void MoveConductorsTextsCommand::undo() { + foreach(ConductorTextItem *cti, texts_to_move_.keys()) { + QPointF movement = texts_to_move_[cti].first; + bool was_already_moved = texts_to_move_[cti].second; + + cti -> forceMovedByUser(was_already_moved); + if (was_already_moved) { + cti -> setPos(cti -> pos() - movement); + } + } +} + +/// refait le deplacement +void MoveConductorsTextsCommand::redo() { + if (first_redo) { + first_redo = false; + } else { + foreach(ConductorTextItem *cti, texts_to_move_.keys()) { + QPointF movement = texts_to_move_[cti].first; + + cti -> forceMovedByUser(true); + cti -> setPos(cti -> pos() + movement); + } + } +} + +/** + Ajout un mouvement de champ de texte a cet objet + @param text_item Champ de texte deplace ; si celui-ci est deja connu de l'objet d'annulation, il sera ignore + @param old_pos Position du champ de texte avant le mouvement + @param new_pos Position du champ de texte apres le mouvement + @param alread_moved true si le champ de texte etait deja a une position personnalisee par l'utilisateur, false sinon +*/ +void MoveConductorsTextsCommand::addTextMovement(ConductorTextItem *text_item, const QPointF &old_pos, const QPointF &new_pos, bool already_moved) { + // si le champ de texte est deja connu de l'objet d'annulation, il sera ignore + if (texts_to_move_.contains(text_item)) return; + + // on memorise le champ de texte, en l'associant au mouvement effectue et a son etat avant le deplacement + texts_to_move_.insert(text_item, qMakePair(new_pos - old_pos, already_moved)); + + // met a jour la description de l'objet d'annulation + regenerateTextLabel(); +} + +/** + Genere la description de l'objet d'annulation +*/ +void MoveConductorsTextsCommand::regenerateTextLabel() { + 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) + ) + ); +} + /** Constructeur @param dti Champ de texte modifie @@ -621,12 +730,28 @@ ChangeConductorCommand::~ChangeConductorCommand() { /// Annule la modification du conducteur void ChangeConductorCommand::undo() { conductor -> setProfile(old_profile, path_type); + conductor -> textItem() -> setPos(text_pos_before_mov_); } /// Refait la modification du conducteur void ChangeConductorCommand::redo() { - if (first_redo) first_redo = false; - else conductor -> setProfile(new_profile, path_type); + if (first_redo) { + first_redo = false; + } else { + conductor -> setProfile(new_profile, path_type); + conductor -> textItem() -> setPos(text_pos_after_mov_); + } +} + +/** + Integre dans cet objet d'annulation le repositionnement du champ de texte + du conducteur + @param pos_before Position du texte avant la modification du conducteur + @param pos_after Position du texte apres la modification du conducteur +*/ +void ChangeConductorCommand::setConductorTextItemMove(const QPointF &pos_before, const QPointF &pos_after) { + text_pos_before_mov_ = pos_before; + text_pos_after_mov_ = pos_after; } /** diff --git a/sources/diagramcommands.h b/sources/diagramcommands.h index 3e9d8429f..44c87b883 100644 --- a/sources/diagramcommands.h +++ b/sources/diagramcommands.h @@ -189,6 +189,7 @@ class MoveElementsCommand : public QUndoCommand { virtual void undo(); virtual void redo(); virtual void move(const QPointF &); + virtual void addConductorTextItemMovement(ConductorTextItem *, const QPointF &, const QPointF &); // attributs private: @@ -198,6 +199,16 @@ class MoveElementsCommand : public QUndoCommand { DiagramContent content_to_move; /// mouvement effectue QPointF movement; + /** + Deplacer des elements ou champs de texte entraine des conducteurs. + Soit ces conducteurs sont betement deplaces, soit leur trajet est + recalcule. + Si leur trajet est recalcule, les champs de texte dont la position a ete + personnalisee par l'utilisateur + Liste des champs de texte de conducteurs dont la position a ete modifiee + par des mises + */ + QHash > moved_conductor_texts_; /// booleen pour ne pas executer le premier redo() bool first_redo; }; @@ -232,6 +243,37 @@ class MoveElementsTextsCommand : public QUndoCommand { bool first_redo; }; +/** + Cette classe represente l'action de deplacer des champs de texte rattaches + a des conducteurs sur un schema +*/ +class MoveConductorsTextsCommand : public QUndoCommand { + // constructeurs, destructeur + public: + MoveConductorsTextsCommand(Diagram *, QUndoCommand * = 0); + virtual ~MoveConductorsTextsCommand(); + private: + MoveConductorsTextsCommand(const MoveConductorsTextsCommand &); + + // methodes + public: + virtual void undo(); + virtual void redo(); + virtual void addTextMovement(ConductorTextItem *, const QPointF &, const QPointF &, bool = false); + + private: + void regenerateTextLabel(); + + // attributs + private: + /// schema sur lequel on deplace les elements + Diagram *diagram; + /// liste des champs de texte a deplacer + QHash > texts_to_move_; + /// booleen pour ne pas executer le premier redo() + bool first_redo; +}; + /** Cette classe represente la modification d'un champ de texte */ @@ -331,6 +373,7 @@ class ChangeConductorCommand : public QUndoCommand { public: virtual void undo(); virtual void redo(); + virtual void setConductorTextItemMove(const QPointF &, const QPointF &); // attributs private: @@ -342,6 +385,10 @@ class ChangeConductorCommand : public QUndoCommand { ConductorProfile new_profile; /// Type de trajet Qt::Corner path_type; + /// Position du champ de texte avant le changement + QPointF text_pos_before_mov_; + /// Position du champ de texte apres le changement + QPointF text_pos_after_mov_; /// booleen pour ne pas executer le premier redo() bool first_redo; }; diff --git a/sources/diagramcontent.cpp b/sources/diagramcontent.cpp index 3f2859484..ecd04a2cd 100644 --- a/sources/diagramcontent.cpp +++ b/sources/diagramcontent.cpp @@ -50,16 +50,16 @@ DiagramContent::~DiagramContent() { @return tous les conducteurs */ QList DiagramContent::conductors(int filter) const { - QList result; + QSet result; if (filter & ConductorsToMove) result += conductorsToMove; if (filter & ConductorsToUpdate) result += conductorsToUpdate; if (filter & OtherConductors) result += otherConductors; if (filter & SelectedOnly) { foreach(Conductor *conductor, result) { - if (!conductor -> isSelected()) result.removeOne(conductor); + if (!conductor -> isSelected()) result.remove(conductor); } } - return(result); + return(result.toList()); } /** diff --git a/sources/diagramcontent.h b/sources/diagramcontent.h index 4791763e7..e7705f3d5 100644 --- a/sources/diagramcontent.h +++ b/sources/diagramcontent.h @@ -50,15 +50,15 @@ class DiagramContent { }; /// Elements de texte du schema - QList elements; + QSet elements; /// Champs de texte independants du schema - QList textFields; + QSet textFields; /// Conducteurs a mettre a jour du schema - QList conductorsToUpdate; + QSet conductorsToUpdate; /// Conducteurs a deplacer du schema - QList conductorsToMove; + QSet conductorsToMove; /// Conducteurs isoles (ni a deplacer, ni a mettre a jour) - QList otherConductors; + QSet otherConductors; QList conductors(int = AnyConductor) const; QList items(int = All) const; diff --git a/sources/diagramtextitem.cpp b/sources/diagramtextitem.cpp index 63ed6d500..23f4d1456 100644 --- a/sources/diagramtextitem.cpp +++ b/sources/diagramtextitem.cpp @@ -198,6 +198,9 @@ void DiagramTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o void DiagramTextItem::focusInEvent(QFocusEvent *e) { QGraphicsTextItem::focusInEvent(e); + // empeche le deplacement du texte pendant son edition + setFlag(QGraphicsItem::ItemIsMovable, false); + // memorise le texte avant que l'utilisateur n'y touche previous_text_ = toPlainText(); // cela permettra de determiner si l'utilisateur a modifie le texte a la fin de l'edition @@ -223,6 +226,9 @@ void DiagramTextItem::focusOutEvent(QFocusEvent *e) { // hack a la con pour etre re-entrant setTextInteractionFlags(Qt::NoTextInteraction); + + // autorise de nouveau le deplacement du texte + setFlag(QGraphicsItem::ItemIsMovable, true); QTimer::singleShot(0, this, SIGNAL(lostFocus())); } @@ -242,55 +248,6 @@ void DiagramTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { } } -/** - Gere le clic sur le champ de texte -*/ -void DiagramTextItem::mousePressEvent(QGraphicsSceneMouseEvent *e) { - if (e -> modifiers() & Qt::ControlModifier) { - setSelected(!isSelected()); - } - QGraphicsTextItem::mousePressEvent(e); -} - -/** - Gere les mouvements de souris lies au champ de texte -*/ -void DiagramTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { - if (textInteractionFlags() & Qt::TextEditable) { - QGraphicsTextItem::mouseMoveEvent(e); - } else if ((flags() & QGraphicsItem::ItemIsMovable) && (e -> buttons() & Qt::LeftButton)) { - QPointF oldPos = pos(); - setPos(mapToParent(e -> pos()) - matrix().map(e -> buttonDownPos(Qt::LeftButton))); - if (Diagram *diagram_ptr = diagram()) { - diagram_ptr -> moveElements(pos() - oldPos, this); - } - } else e -> ignore(); -} - -/** - Gere le relachement de souris - Cette methode a ete reimplementee pour tenir a jour la liste des elements - et conducteurs a deplacer au niveau du schema. -*/ -void DiagramTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { - if (Diagram *diagram_ptr = diagram()) { - if ((flags() & QGraphicsItem::ItemIsMovable) && (!diagram_ptr -> current_movement.isNull())) { - diagram_ptr -> undoStack().push( - new MoveElementsCommand( - diagram_ptr, - diagram_ptr -> selectedContent(), - diagram_ptr -> current_movement - ) - ); - diagram_ptr -> current_movement = QPointF(); - } - diagram_ptr -> invalidateMovedElements(); - } - if (!(e -> modifiers() & Qt::ControlModifier)) { - QGraphicsTextItem::mouseReleaseEvent(e); - } -} - /** Effetue la rotation du texte en elle-meme Pour les DiagramTextItem, la rotation s'effectue autour du point (0, 0). diff --git a/sources/diagramtextitem.h b/sources/diagramtextitem.h index f52256eed..db775d8ab 100644 --- a/sources/diagramtextitem.h +++ b/sources/diagramtextitem.h @@ -62,9 +62,6 @@ class DiagramTextItem : public QGraphicsTextItem { virtual void focusInEvent(QFocusEvent *); virtual void focusOutEvent(QFocusEvent *); virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *); - virtual void mousePressEvent(QGraphicsSceneMouseEvent *); - virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *); - virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *); virtual void applyRotation(const qreal &); // signaux diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index ac7214541..0fcfcd358 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -686,7 +686,7 @@ void DiagramView::editSelectionProperties() { // cas d'un element selectionne if (selection.elements.count() == 1) { - editElement(selection.elements.at(0)); + editElement(selection.elements.toList().at(0)); return; } diff --git a/sources/element.cpp b/sources/element.cpp index c89e5c1c0..e909f297a 100644 --- a/sources/element.cpp +++ b/sources/element.cpp @@ -30,7 +30,8 @@ Element::Element(QGraphicsItem *parent, Diagram *scene) : QObject(), QGraphicsItem(parent, scene), internal_connections(false), - must_highlight_(false) + must_highlight_(false), + first_move_(true) { setZValue(10); } @@ -315,9 +316,11 @@ void Element::setPos(qreal x, qreal y) { } /** - Gere l'enfoncement d'un bouton de la souris + Gere le clic sur l'element + @param e Objet decrivant l'evenement souris */ void Element::mousePressEvent(QGraphicsSceneMouseEvent *e) { + first_move_ = true; if (e -> modifiers() & Qt::ControlModifier) { setSelected(!isSelected()); } @@ -330,12 +333,33 @@ void Element::mousePressEvent(QGraphicsSceneMouseEvent *e) { */ void Element::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { if (isSelected() && e -> buttons() & Qt::LeftButton) { - QPointF oldPos = pos(); + // l'element est en train d'etre deplace + Diagram *diagram_ptr = diagram(); + if (diagram_ptr) { + if (first_move_) { + // il s'agit du premier mouvement du deplacement, on le signale + // au schema parent + diagram_ptr -> beginMoveElements(this); + } + } + + // on applique le mouvement impose par la souris + QPointF old_pos = pos(); setPos(mapToParent(e -> pos()) - matrix().map(e -> buttonDownPos(Qt::LeftButton))); - if (Diagram *diagram_ptr = diagram()) { - diagram_ptr -> moveElements(pos() - oldPos, this); + + // on calcule le mouvement reellement applique par setPos() + QPointF effective_movement = pos() - old_pos; + + if (diagram_ptr) { + // on signale le mouvement ainsi applique au schema parent, qui + // l'appliquera aux autres items selectionnes selon son bon vouloir + diagram_ptr -> continueMoveElements(effective_movement); } } else e -> ignore(); + + if (first_move_) { + first_move_ = false; + } } /** @@ -344,20 +368,10 @@ void Element::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { et conducteurs a deplacer au niveau du schema. */ void Element::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { - Diagram *diagram_ptr = diagram(); - if (diagram_ptr) { - if (!diagram_ptr -> current_movement.isNull()) { - diagram_ptr -> undoStack().push( - new MoveElementsCommand( - diagram_ptr, - diagram_ptr -> selectedContent(), - diagram_ptr -> current_movement - ) - ); - diagram_ptr -> current_movement = QPointF(); - } - diagram_ptr -> invalidateMovedElements(); + if (Diagram *diagram_ptr = diagram()) { + diagram_ptr -> endMoveElements(); } + if (!(e -> modifiers() & Qt::ControlModifier)) { QGraphicsItem::mouseReleaseEvent(e); } diff --git a/sources/element.h b/sources/element.h index 896a30a29..daba9a336 100644 --- a/sources/element.h +++ b/sources/element.h @@ -129,6 +129,7 @@ class Element : public QObject, public QGraphicsItem { private: bool internal_connections; bool must_highlight_; + bool first_move_; void drawSelection(QPainter *, const QStyleOptionGraphicsItem *); void drawHighlight(QPainter *, const QStyleOptionGraphicsItem *); void updatePixmap(); diff --git a/sources/elementsmover.cpp b/sources/elementsmover.cpp new file mode 100644 index 000000000..1e9851f9b --- /dev/null +++ b/sources/elementsmover.cpp @@ -0,0 +1,178 @@ +/* + Copyright 2006-2010 Xavier Guerrin + 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 "elementsmover.h" +#include "conductor.h" +#include "conductortextitem.h" +#include "diagram.h" +#include "diagramcommands.h" +#include "element.h" +#include "independenttextitem.h" + +/** + Constructeur +*/ +ElementsMover::ElementsMover() : + movement_running_(false), + current_movement_(), + diagram_(0), + movement_driver_(0), + moved_content_() +{ + +} + +/** + Destructeur +*/ +ElementsMover::~ElementsMover() { +} + +/** + @return true si ce gestionnaire de deplacement est pret a etre utilise, + false sinon. Un gestionnaire de deplacement est pret a etre utilise a partir + du moment ou le mouvement precedemment gere n'est plus en cours. +*/ +bool ElementsMover::isReady() const { + return(!movement_running_); +} + +/** + Demarre un nouveau mouvement d'element + @param diagram Schema sur lequel se deroule le deplacement + @param driver_item Item deplace par la souris et ne necessitant donc pas + d'etre deplace lors des appels a continueMovement. + @return le nombre d'items concernes par le deplacement, ou -1 si le + mouvement n'a pas ete initie +*/ +int ElementsMover::beginMovement(Diagram *diagram, QGraphicsItem *driver_item) { + // il ne doit pas y avoir de mouvement en cours + if (movement_running_) return(-1); + + // on s'assure que l'on dispose d'un schema pour travailler + if (!diagram) return(-1); + diagram_ = diagram; + + // on prend en compte le driver_item + movement_driver_ = driver_item; + + // au debut du mouvement, le deplacement est nul + current_movement_ = QPointF(0.0, 0.0); + + // on stocke dans cet objet les items concernes par le deplacement + moved_content_ = diagram -> selectedContent(); + + // on a egalement besoin de retenir la position des champs de textes + // rattaches a un conducteur (ConductorTextItem) si cette position a ete + // personnalisee. + // ceci n'est necessaire que pour les conducteurs dont le trajet sera + // recalcule a cause du mouvement + foreach(Conductor *conductor, moved_content_.conductorsToUpdate) { + if (ConductorTextItem *text_item = conductor -> textItem()) { + if (text_item -> wasMovedByUser()) { + updated_conductors_text_pos_.insert( + text_item, + text_item -> pos() + ); + } + } + } + // on s'assure qu'il y a quelque chose a deplacer + if (!moved_content_.count()) return(-1); + + // a ce stade, on dispose de toutes les informations necessaires pour + // prendre en compte les mouvements + + // il y a desormais un mouvement en cours + movement_running_ = true; + + return(moved_content_.count()); +} + +/** + Ajoute un mouvement au deplacement en cours. Cette methode + @param movement mouvement a ajouter au deplacement en cours +*/ +void ElementsMover::continueMovement(const QPointF &movement) { + // un mouvement doit avoir ete initie + if (!movement_running_) return; + + // inutile de faire quoi que ce soit s'il n'y a pas eu de mouvement concret + if (movement.isNull()) return; + + // prise en compte du mouvement + current_movement_ += movement; + + // deplace les elements selectionnes + foreach(Element *element, moved_content_.elements) { + if (movement_driver_ && element == movement_driver_) continue; + element -> setPos(element -> pos() + movement); + } + + // deplace certains conducteurs + foreach(Conductor *conductor, moved_content_.conductorsToMove) { + conductor -> setPos(conductor -> pos() + movement); + } + + // recalcule les autres conducteurs + foreach(Conductor *conductor, moved_content_.conductorsToUpdate) { + conductor -> updatePath(); + } + + // deplace les champs de texte + foreach(IndependentTextItem *text_field, moved_content_.textFields) { + if (movement_driver_ && text_field == movement_driver_) continue; + text_field -> setPos(text_field -> pos() + movement); + } +} + +/** + Termine le deplacement en creant un objet d'annulation et en l'ajoutant a + la QUndoStack du schema concerne. + @see Diagram::undoStack() +*/ +void ElementsMover::endMovement() { + // un mouvement doit avoir ete initie + if (!movement_running_) return; + + // inutile de faire quoi que ce soit s'il n'y a pas eu de mouvement concret + if (!current_movement_.isNull()) { + // cree un objet d'annulation pour le mouvement ainsi realise + MoveElementsCommand *undo_object = new MoveElementsCommand( + diagram_, + moved_content_, + current_movement_ + ); + + // ajoute les informations necessaires au repositionnement des champs + // de textes des conducteurs + foreach(ConductorTextItem *text_item, updated_conductors_text_pos_.keys()) { + if (text_item -> pos() != updated_conductors_text_pos_[text_item]) { + undo_object -> addConductorTextItemMovement( + text_item, + updated_conductors_text_pos_[text_item], + text_item -> pos() + ); + } + } + + diagram_ -> undoStack().push(undo_object); + } + + // il n'y a plus de mouvement en cours + movement_running_ = false; +} diff --git a/sources/elementsmover.h b/sources/elementsmover.h new file mode 100644 index 000000000..235d8616c --- /dev/null +++ b/sources/elementsmover.h @@ -0,0 +1,52 @@ +/* + Copyright 2006-2010 Xavier Guerrin + 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 ELEMENTS_MOVER_H +#define ELEMENTS_MOVER_H +#include +#include "diagramcontent.h" +class ConductorTextItem; +class Diagram; +/** + Cette classe permet de gerer le deplacement des differents items composant + un schema electrique sur ce schema. +*/ +class ElementsMover { + // constructeurs, destructeur + public: + ElementsMover(); + virtual ~ElementsMover(); + private: + ElementsMover(const ElementsMover &); + + // methodes + public: + bool isReady() const; + int beginMovement(Diagram *, QGraphicsItem * = 0); + void continueMovement(const QPointF &); + void endMovement(); + + // attributs + private: + bool movement_running_; + QPointF current_movement_; + Diagram *diagram_; + QGraphicsItem *movement_driver_; + DiagramContent moved_content_; + QHash updated_conductors_text_pos_; +}; +#endif diff --git a/sources/elementtextitem.cpp b/sources/elementtextitem.cpp index bd1a49fcc..b56167db4 100644 --- a/sources/elementtextitem.cpp +++ b/sources/elementtextitem.cpp @@ -218,6 +218,18 @@ void ElementTextItem::applyRotation(const qreal &angle) { QGraphicsTextItem::setTransform(rotation, true); } +/** + Gere le clic sur le champ de texte + @param e Objet decrivant l'evenement souris +*/ +void ElementTextItem::mousePressEvent(QGraphicsSceneMouseEvent *e) { + first_move_ = true; + if (e -> modifiers() & Qt::ControlModifier) { + setSelected(!isSelected()); + } + DiagramTextItem::mousePressEvent(e); +} + /** Gere les mouvements de souris lies au champ de texte @param e Objet decrivant l'evenement souris @@ -246,13 +258,17 @@ void ElementTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { QPointF parent_movement = mapMovementToParent(movement); setPos(pos() + parent_movement); - if (Diagram *diagram_ptr = diagram()) { - int moved_texts_count = diagram_ptr -> elementTextsToMove().count(); - // s'il n'y a qu'un seul texte deplace, on met en valeur l'element parent - if (moved_texts_count == 1 && parent_element_ && first_move_) { - parent_element_ -> setHighlighted(true); - parent_element_ -> update(); - first_move_ = false; + Diagram *diagram_ptr = diagram(); + if (diagram_ptr) { + if (first_move_) { + // on signale le debut d'un deplacement d'ElementTextItems au schema parent + int moved_texts_count = diagram_ptr -> beginMoveElementTexts(this); + + // s'il n'y a qu'un seul texte deplace, on met en valeur l'element parent + if (moved_texts_count == 1 && parent_element_) { + parent_element_ -> setHighlighted(true); + parent_element_ -> update(); + } } /* @@ -264,9 +280,13 @@ void ElementTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { QPointF scene_effective_movement = mapMovementToScene(mapMovementFromParent(effective_movement)); // on applique le mouvement subi aux autres textes a deplacer - diagram_ptr -> moveElementsTexts(scene_effective_movement, this); + diagram_ptr -> continueMoveElementTexts(scene_effective_movement); } } else e -> ignore(); + + if (first_move_) { + first_move_ = false; + } } /** @@ -276,28 +296,14 @@ void ElementTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { */ void ElementTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { if (Diagram *diagram_ptr = diagram()) { - int moved_texts_count = diagram_ptr -> elementTextsToMove().count(); - - // s'il n'y a qu'un seul texte deplace, on arrete de mettre en valeur l'element parent - if (moved_texts_count == 1) { - first_move_ = true; - if (parent_element_) { + // on arrete de mettre en valeur l'element parent + if (parent_element_) { + if (parent_element_ -> isHighlighted()) { parent_element_ -> setHighlighted(false); } } - // on cree un objet d'annulation correspondant au deplacement qui s'acheve - 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(); + diagram_ptr -> endMoveElementTexts(); } if (!(e -> modifiers() & Qt::ControlModifier)) { QGraphicsTextItem::mouseReleaseEvent(e); diff --git a/sources/elementtextitem.h b/sources/elementtextitem.h index d3a3cb6ff..6ee7f8bd7 100644 --- a/sources/elementtextitem.h +++ b/sources/elementtextitem.h @@ -72,6 +72,7 @@ class ElementTextItem : public DiagramTextItem { protected: virtual void applyRotation(const qreal &); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *); virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *); virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *); virtual QVariant itemChange(GraphicsItemChange, const QVariant &); diff --git a/sources/elementtextsmover.cpp b/sources/elementtextsmover.cpp new file mode 100644 index 000000000..4399c98c8 --- /dev/null +++ b/sources/elementtextsmover.cpp @@ -0,0 +1,141 @@ +/* + Copyright 2006-2010 Xavier Guerrin + 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 "elementtextsmover.h" +#include "conductor.h" +#include "elementtextitem.h" +#include "diagram.h" +#include "diagramcommands.h" +#include "element.h" +#include "independenttextitem.h" + +/** + Constructeur +*/ +ElementTextsMover::ElementTextsMover() : + movement_running_(false), + current_movement_(), + diagram_(0), + movement_driver_(0), + moved_texts_() +{ + +} + +/** + Destructeur +*/ +ElementTextsMover::~ElementTextsMover() { +} + +/** + @return true si ce gestionnaire de deplacement est pret a etre utilise, + false sinon. Un gestionnaire de deplacement est pret a etre utilise a partir + du moment ou le mouvement precedemment gere n'est plus en cours. +*/ +bool ElementTextsMover::isReady() const { + return(!movement_running_); +} + +/** + Demarre un nouveau mouvement d'ElementTextItems + @param diagram Schema sur lequel se deroule le deplacement + @param driver_item Item deplace par la souris et ne necessitant donc pas + d'etre deplace lors des appels a continueMovement. + @return le nombre d'items concernes par le deplacement, ou -1 si le + mouvement n'a pas ete initie +*/ +int ElementTextsMover::beginMovement(Diagram *diagram, QGraphicsItem *driver_item) { + // il ne doit pas y avoir de mouvement en cours + if (movement_running_) return(-1); + + // on s'assure que l'on dispose d'un schema pour travailler + if (!diagram) return(-1); + diagram_ = diagram; + + // on prend en compte le driver_item + movement_driver_ = driver_item; + + // au debut du mouvement, le deplacement est nul + current_movement_ = QPointF(0.0, 0.0); + + // on stocke dans cet objet les items concernes par le deplacement + moved_texts_.clear(); + foreach(QGraphicsItem *item, diagram -> selectedItems()) { + if (ElementTextItem *text_item = qgraphicsitem_cast(item)) { + moved_texts_ << text_item; + } + } + + // on s'assure qu'il y a quelque chose a deplacer + if (!moved_texts_.count()) return(-1); + + // a ce stade, on dispose de toutes les informations necessaires pour + // prendre en compte les mouvements + + // il y a desormais un mouvement en cours + movement_running_ = true; + + return(moved_texts_.count()); +} + +/** + Ajoute un mouvement au deplacement en cours. Cette methode + @param movement mouvement a ajouter au deplacement en cours +*/ +void ElementTextsMover::continueMovement(const QPointF &movement) { + // un mouvement doit avoir ete initie + if (!movement_running_) return; + + // inutile de faire quoi que ce soit s'il n'y a pas eu de mouvement concret + if (movement.isNull()) return; + + // prise en compte du mouvement + current_movement_ += movement; + + // deplace les elements selectionnes + foreach(ElementTextItem *text_item, moved_texts_) { + if (movement_driver_ && text_item == movement_driver_) continue; + QPointF applied_movement = text_item -> mapMovementToParent(text_item-> mapMovementFromScene(movement)); + text_item -> setPos(text_item -> pos() + applied_movement); + } +} + +/** + Termine le deplacement en creant un objet d'annulation et en l'ajoutant a + la QUndoStack du schema concerne. + @see Diagram::undoStack() +*/ +void ElementTextsMover::endMovement() { + // un mouvement doit avoir ete initie + if (!movement_running_) return; + + // inutile de faire quoi que ce soit s'il n'y a pas eu de mouvement concret + if (!current_movement_.isNull()) { + // cree un objet d'annulation pour le mouvement ainsi realise + MoveElementsTextsCommand*undo_object = new MoveElementsTextsCommand( + diagram_, + moved_texts_, + current_movement_ + ); + + diagram_ -> undoStack().push(undo_object); + } + + // il n'y a plus de mouvement en cours + movement_running_ = false; +} diff --git a/sources/elementtextsmover.h b/sources/elementtextsmover.h new file mode 100644 index 000000000..49634ad37 --- /dev/null +++ b/sources/elementtextsmover.h @@ -0,0 +1,51 @@ +/* + Copyright 2006-2010 Xavier Guerrin + 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 ELEMENT_TEXTS_MOVER_H +#define ELEMENT_TEXTS_MOVER_H +#include +#include "diagramcontent.h" +class ElementTextItem; +class Diagram; +/** + Cette classe permet de gerer le deplacement des ElementTextItems d'un schema + electrique. +*/ +class ElementTextsMover { + // constructeurs, destructeur + public: + ElementTextsMover(); + virtual ~ElementTextsMover(); + private: + ElementTextsMover(const ElementTextsMover &); + + // methodes + public: + bool isReady() const; + int beginMovement(Diagram *, QGraphicsItem * = 0); + void continueMovement(const QPointF &); + void endMovement(); + + // attributs + private: + bool movement_running_; + QPointF current_movement_; + Diagram *diagram_; + QGraphicsItem *movement_driver_; + QSet moved_texts_; +}; +#endif diff --git a/sources/independenttextitem.cpp b/sources/independenttextitem.cpp index c99168ccc..cfa045533 100644 --- a/sources/independenttextitem.cpp +++ b/sources/independenttextitem.cpp @@ -23,7 +23,8 @@ @param parent_diagram Le schema auquel est rattache le champ de texte */ IndependentTextItem::IndependentTextItem(Diagram *parent_diagram) : - DiagramTextItem(0, parent_diagram) + DiagramTextItem(0, parent_diagram), + first_move_(true) { } @@ -33,7 +34,8 @@ IndependentTextItem::IndependentTextItem(Diagram *parent_diagram) : @param parent_diagram Le schema auquel est rattache le champ de texte */ IndependentTextItem::IndependentTextItem(const QString &text, Diagram *parent_diagram) : - DiagramTextItem(text, 0, parent_diagram) + DiagramTextItem(text, 0, parent_diagram), + first_move_(true) { } @@ -68,3 +70,68 @@ QDomElement IndependentTextItem::toXml(QDomDocument &document) const { return(result); } +/** + Gere le clic sur le champ de texte + @param e Objet decrivant l'evenement souris +*/ +void IndependentTextItem::mousePressEvent(QGraphicsSceneMouseEvent *e) { + first_move_ = true; + if (e -> modifiers() & Qt::ControlModifier) { + setSelected(!isSelected()); + } + DiagramTextItem::mousePressEvent(e); +} + +/** + Gere les mouvements de souris lies au champ de texte + @param e Objet decrivant l'evenement souris +*/ +void IndependentTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { + if (textInteractionFlags() & Qt::TextEditable) { + DiagramTextItem::mouseMoveEvent(e); + } else if ((flags() & QGraphicsItem::ItemIsMovable) && isSelected() && (e -> buttons() & Qt::LeftButton)) { + // le champ de texte est en train d'etre deplace + Diagram *diagram_ptr = diagram(); + if (diagram_ptr) { + if (first_move_) { + // il s'agit du premier mouvement du deplacement, on le signale + // au schema parent + diagram_ptr -> beginMoveElements(this); + } + } + + // on applique le mouvement impose par la souris + QPointF old_pos = pos(); + setPos(mapToParent(e -> pos()) - matrix().map(e -> buttonDownPos(Qt::LeftButton))); + + // on calcule le mouvement reellement applique par setPos() + QPointF effective_movement = pos() - old_pos; + + if (diagram_ptr) { + // on signale le mouvement ainsi applique au schema parent, qui + // l'appliquera aux autres items selectionnes selon son bon vouloir + diagram_ptr -> continueMoveElements(effective_movement); + } + } else e -> ignore(); + + if (first_move_) { + first_move_ = false; + } +} + +/** + Gere le relachement de souris + Cette methode a ete reimplementee pour tenir a jour la liste des elements + et conducteurs a deplacer au niveau du schema. + @param e Objet decrivant l'evenement souris +*/ +void IndependentTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { + if (flags() & QGraphicsItem::ItemIsMovable) { + if (Diagram *diagram_ptr = diagram()) { + diagram_ptr -> endMoveElements(); + } + } + if (!(e -> modifiers() & Qt::ControlModifier)) { + DiagramTextItem::mouseReleaseEvent(e); + } +} diff --git a/sources/independenttextitem.h b/sources/independenttextitem.h index a65453e2c..a590bcc53 100644 --- a/sources/independenttextitem.h +++ b/sources/independenttextitem.h @@ -44,5 +44,13 @@ class IndependentTextItem : public DiagramTextItem { virtual int type() const { return Type; } virtual void fromXml(const QDomElement &); virtual QDomElement toXml(QDomDocument &) const; + + protected: + virtual void mousePressEvent(QGraphicsSceneMouseEvent *); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *); + + private: + bool first_move_; }; #endif diff --git a/sources/qet.cpp b/sources/qet.cpp index 3424390e4..4c79a87a7 100644 --- a/sources/qet.cpp +++ b/sources/qet.cpp @@ -104,6 +104,53 @@ QET::Orientation QET::previousOrientation(QET::Orientation o) { return((QET::Orientation)(o - 1)); } +/** + @param line Un segment de droite + @param point Un point + @return true si le point appartient au segment de droite, false sinon +*/ +bool QET::lineContainsPoint(const QLineF &line, const QPointF &point) { + QLineF point_line(line.p1(), point); + if (point_line.unitVector() != line.unitVector()) return(false); + return(point_line.length() <= line.length()); +} + +/** + @param point Un point donne + @param line Un segment de droite donnee + @param intersection si ce pointeur est different de 0, le QPointF ainsi + designe contiendra les coordonnees du projete orthogonal, meme si celui-ci + n'appartient pas au segment de droite + @return true si le projete orthogonal du point sur la droite appartient au + segment de droite. +*/ +bool QET::orthogonalProjection(const QPointF &point, const QLineF &line, QPointF *intersection) { + // recupere le vecteur normal de `line' + QLineF line_normal_vector(line.normalVector()); + QPointF normal_vector(line_normal_vector.dx(), line_normal_vector.dy()); + + // cree une droite perpendiculaire a `line' passant par `point' + QLineF perpendicular_line(point, point + normal_vector); + + // determine le point d'intersection des deux droites = le projete orthogonal + QPointF intersection_point; + QLineF::IntersectType it = line.intersect(perpendicular_line, &intersection_point); + + // ne devrait pas arriver (mais bon...) + if (it == QLineF::NoIntersection) return(false); + + // fournit le point d'intersection a l'appelant si necessaire + if (intersection) { + *intersection = intersection_point; + } + + // determine si le point d'intersection appartient au segment de droite + if (QET::lineContainsPoint(line, intersection_point)) { + return(true); + } + return(false); +} + /** Permet de savoir si l'attribut nom_attribut d'un element XML e est bien un entier. Si oui, sa valeur est copiee dans entier. diff --git a/sources/qet.h b/sources/qet.h index fece103a9..0434ccb1a 100644 --- a/sources/qet.h +++ b/sources/qet.h @@ -104,6 +104,8 @@ namespace QET { bool surLeMemeAxe(QET::Orientation, QET::Orientation); bool estHorizontale(QET::Orientation); bool estVerticale(QET::Orientation); + bool lineContainsPoint(const QLineF &, const QPointF &); + bool orthogonalProjection(const QPointF &, const QLineF &, QPointF * = 0); bool attributeIsAnInteger(const QDomElement &, QString , int * = NULL); bool attributeIsAReal(const QDomElement &, QString , qreal * = NULL); QString ElementsAndConductorsSentence(int, int, int = 0);