Logo Search packages:      
Sourcecode: qelectrotech version File versions  Download package

diagram.cpp

/*
      Copyright 2006-2009 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 <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include "qetapp.h"
#include "conductor.h"
#include "customelement.h"
#include "diagram.h"
#include "elementtextitem.h"
#include "exportdialog.h"
#include "ghostelement.h"
#include "diagramcommands.h"
#include "diagramcontent.h"
#include "diagramposition.h"

const int   Diagram::xGrid  = 10;
const int   Diagram::yGrid  = 10;
const qreal Diagram::margin = 5.0;

/**
      Constructeur
      @param parent Le QObject parent du schema
*/
00038 Diagram::Diagram(QObject *parent) :
      QGraphicsScene(parent),
      draw_grid(true),
      use_border(true),
      moved_elements_fetched(false),
      draw_terminals(true),
      project_(0),
      read_only_(false)
{
      undo_stack = new QUndoStack();
      qgi_manager = new QGIManager(this);
      setBackgroundBrush(Qt::white);
      conductor_setter = new QGraphicsLineItem(0, 0);
      conductor_setter -> setZValue(1000000);
      QPen t;
      t.setColor(Qt::black);
      t.setWidthF(1.5);
      t.setStyle(Qt::DashLine);
      conductor_setter -> setPen(t);
      conductor_setter -> setLine(QLineF(QPointF(0.0, 0.0), QPointF(0.0, 0.0)));
}

/**
      Destructeur
*/
00063 Diagram::~Diagram() {
      // suppression de la liste des annulations - l'undo stack fait appel au qgimanager pour supprimer certains elements
      delete undo_stack;
      // suppression du QGIManager - tous les elements qu'il connait sont supprimes
      delete qgi_manager;
      
      // recense les items supprimables
      QList<QGraphicsItem *> deletable_items;
      foreach(QGraphicsItem *qgi, items()) {
            if (qgi -> parentItem()) continue;
            if (qgraphicsitem_cast<Conductor *>(qgi)) continue;
            deletable_items << qgi;
      }

      // suppression des items supprimables
      foreach(QGraphicsItem *qgi_d, deletable_items) {
            delete qgi_d;
      }
}

/**
      Dessine l'arriere-plan du schema, cad la grille.
      @param p Le QPainter a utiliser pour dessiner
      @param r Le rectangle de la zone a dessiner
*/
00088 void Diagram::drawBackground(QPainter *p, const QRectF &r) {
      p -> save();
      
      // desactive tout antialiasing, sauf pour le texte
      p -> setRenderHint(QPainter::Antialiasing, false);
      p -> setRenderHint(QPainter::TextAntialiasing, true);
      p -> setRenderHint(QPainter::SmoothPixmapTransform, false);
      
      // dessine un fond blanc
      p -> setPen(Qt::NoPen);
      p -> setBrush(Qt::white);
      p -> drawRect(r);
      
      if (draw_grid) {
            // dessine les points de la grille
            p -> setPen(Qt::black);
            p -> setBrush(Qt::NoBrush);
            qreal limite_x = r.x() + r.width();
            qreal limite_y = r.y() + r.height();
            
            int g_x = (int)ceil(r.x());
            while (g_x % xGrid) ++ g_x;
            int g_y = (int)ceil(r.y());
            while (g_y % yGrid) ++ g_y;
            
            for (int gx = g_x ; gx < limite_x ; gx += xGrid) {
                  for (int gy = g_y ; gy < limite_y ; gy += yGrid) {
                        p -> drawPoint(gx, gy);
                  }
            }
      }
      
      if (use_border) border_and_inset.draw(p, margin, margin);
      p -> restore();
}

/**
      Gere les enfoncements de touches du clavier
      @param e QKeyEvent decrivant l'evenement clavier
*/
00128 void Diagram::keyPressEvent(QKeyEvent *e) {
      if (!isReadOnly()) {
            QPointF movement;
            switch(e -> key()) {
                  case Qt::Key_Left:  movement = QPointF(-xGrid, 0.0); break;
                  case Qt::Key_Right: movement = QPointF(+xGrid, 0.0); break;
                  case Qt::Key_Up:    movement = QPointF(0.0, -yGrid); break;
                  case Qt::Key_Down:  movement = QPointF(0.0, +yGrid); break;
            }
            if (!movement.isNull() && !focusItem()) {
                  moveElements(movement);
            }
      }
      QGraphicsScene::keyPressEvent(e);
}

/**
      Gere les relachements de touches du clavier
      @param e QKeyEvent decrivant l'evenement clavier
*/
00148 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()
            ) {
                  // 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();
            }
      }
      QGraphicsScene::keyReleaseEvent(e);
}

/**
      Exporte le schema vers une image
      @return Une QImage representant le schema
*/
00169 bool Diagram::toPaintDevice(QPaintDevice &pix, int width, int height, Qt::AspectRatioMode aspectRatioMode) {
      // determine la zone source =  contenu du schema + marges
      QRectF source_area;
      if (!use_border) {
            source_area = itemsBoundingRect();
            source_area.translate(-margin, -margin);
            source_area.setWidth (source_area.width () + 2.0 * margin);
            source_area.setHeight(source_area.height() + 2.0 * margin);
      } else {
            source_area = QRectF(
                  0.0,
                  0.0,
                  border_and_inset.borderWidth () + 2.0 * margin,
                  border_and_inset.borderHeight() + 2.0 * margin
            );
      }
      
      // si les dimensions ne sont pas precisees, l'image est exportee a l'echelle 1:1
      QSize image_size = (width == -1 && height == -1) ? source_area.size().toSize() : QSize(width, height);
      
      // prepare le rendu
      QPainter p;
      if (!p.begin(&pix)) return(false);
      
      // rendu antialiase
      p.setRenderHint(QPainter::Antialiasing, true);
      p.setRenderHint(QPainter::TextAntialiasing, true);
      p.setRenderHint(QPainter::SmoothPixmapTransform, true);
      
      // deselectionne tous les elements
      QList<QGraphicsItem *> selected_elmts = selectedItems();
      foreach (QGraphicsItem *qgi, selected_elmts) qgi -> setSelected(false);
      
      // effectue le rendu lui-meme
      render(&p, QRect(QPoint(0, 0), image_size), source_area, aspectRatioMode);
      p.end();
      
      // restaure les elements selectionnes
      foreach (QGraphicsItem *qgi, selected_elmts) qgi -> setSelected(true);
      
      return(true);
}

/**
      Permet de connaitre les dimensions qu'aura l'image generee par la methode toImage()
      @return La taille de l'image generee par toImage()
*/
00216 QSize Diagram::imageSize() const {
      // determine la zone source =  contenu du schema + marges
      qreal image_width, image_height;
      if (!use_border) {
            QRectF items_rect = itemsBoundingRect();
            image_width  = items_rect.width();
            image_height = items_rect.height();
      } else {
            image_width  = border_and_inset.borderWidth();
            image_height = border_and_inset.borderHeight();
      }
      
      image_width  += 2.0 * margin;
      image_height += 2.0 * margin;
      
      // renvoie la taille de la zone source
      return(QSizeF(image_width, image_height).toSize());
}

/**
      @return true si le schema est considere comme vide, false sinon.
      Un schema vide ne contient ni element, ni conducteur, ni champ de texte
*/
00239 bool Diagram::isEmpty() const {
      return(!items().count());
}

/**
      Exporte tout ou partie du schema 
      @param diagram Booleen (a vrai par defaut) indiquant si le XML genere doit
      representer tout le schema ou seulement les elements selectionnes
      @return Un Document XML (QDomDocument)
*/
00249 QDomDocument Diagram::toXml(bool diagram) {
      // document
      QDomDocument document;
      
      // racine de l'arbre XML
      QDomElement racine = document.createElement("diagram");
      
      // proprietes du schema
      if (diagram) {
            if (!border_and_inset.author().isNull())    racine.setAttribute("author",   border_and_inset.author());
            if (!border_and_inset.date().isNull())      racine.setAttribute("date",     border_and_inset.date().toString("yyyyMMdd"));
            if (!border_and_inset.title().isNull())     racine.setAttribute("title",    border_and_inset.title());
            if (!border_and_inset.fileName().isNull())  racine.setAttribute("filename", border_and_inset.fileName());
            if (!border_and_inset.folio().isNull())     racine.setAttribute("folio",    border_and_inset.folio());
            racine.setAttribute("cols",    border_and_inset.nbColumns());
            racine.setAttribute("colsize", QString("%1").arg(border_and_inset.columnsWidth()));
            racine.setAttribute("rows",    border_and_inset.nbRows());
            racine.setAttribute("rowsize", QString("%1").arg(border_and_inset.rowsHeight()));
            // attribut datant de la version 0.1 - laisse pour retrocompatibilite
            racine.setAttribute("height",  QString("%1").arg(border_and_inset.diagramHeight()));
            racine.setAttribute("displaycols", border_and_inset.columnsAreDisplayed() ? "true" : "false");
            racine.setAttribute("displayrows", border_and_inset.rowsAreDisplayed()    ? "true" : "false");
            
            // type de conducteur par defaut
            QDomElement default_conductor = document.createElement("defaultconductor");
            defaultConductorProperties.toXml(default_conductor);
            racine.appendChild(default_conductor);
      }
      document.appendChild(racine);
      
      // si le schema ne contient pas d'element (et donc pas de conducteurs), on retourne de suite le document XML
      if (items().isEmpty()) return(document);
      
      // creation de trois listes : une qui contient les elements, une qui contient les conducteurs, une qui contient les champs de texte
      QList<Element *> list_elements;
      QList<Conductor *> list_conductors;
      QList<DiagramTextItem *> list_texts;
      
      // Determine les elements a "XMLiser"
      foreach(QGraphicsItem *qgi, items()) {
            if (Element *elmt = qgraphicsitem_cast<Element *>(qgi)) {
                  if (diagram) list_elements << elmt;
                  else if (elmt -> isSelected()) list_elements << elmt;
            } else if (Conductor *f = qgraphicsitem_cast<Conductor *>(qgi)) {
                  if (diagram) list_conductors << f;
                  // lorsqu'on n'exporte pas tout le diagram, il faut retirer les conducteurs non selectionnes
                  // et pour l'instant, les conducteurs non selectionnes sont les conducteurs dont un des elements n'est pas relie
                  else if (f -> terminal1 -> parentItem() -> isSelected() && f -> terminal2 -> parentItem() -> isSelected()) list_conductors << f;
            } else if (DiagramTextItem *dti = qgraphicsitem_cast<DiagramTextItem *>(qgi)) {
                  if (!dti -> parentItem()) {
                        if (diagram) list_texts << dti;
                        else if (dti -> isSelected()) list_texts << dti;
                  }
            }
      }
      
      // table de correspondance entre les adresses des bornes et leurs ids
      QHash<Terminal *, int> table_adr_id;
      
      // enregistrement des elements
      if (!list_elements.isEmpty()) {
            QDomElement elements = document.createElement("elements");
            foreach(Element *elmt, list_elements) {
                  elements.appendChild(elmt -> toXml(document, table_adr_id));
            }
            racine.appendChild(elements);
      }
      
      // enregistrement des conducteurs
      if (!list_conductors.isEmpty()) {
            QDomElement conductors = document.createElement("conductors");
            foreach(Conductor *cond, list_conductors) {
                  conductors.appendChild(cond -> toXml(document, table_adr_id));
            }
            racine.appendChild(conductors);
      }
      
      // enregistrement des champs de texte
      if (!list_texts.isEmpty()) {
            QDomElement inputs = document.createElement("inputs");
            foreach(DiagramTextItem *dti, list_texts) {
                  inputs.appendChild(dti -> toXml(document));
            }
            racine.appendChild(inputs);
      }
      
      // on retourne le document XML ainsi genere
      return(document);
}

/**
      Importe le schema decrit dans un document XML. Si une position est
      precisee, les elements importes sont positionnes de maniere a ce que le
      coin superieur gauche du plus petit rectangle pouvant les entourant tous
      (le bounding rect) soit a cette position.
      @param document Le document XML a analyser
      @param position La position du schema importe
      @param consider_informations Si vrai, les informations complementaires
      (auteur, titre, ...) seront prises en compte
      @param content_ptr si ce pointeur vers un DiagramContent est different de 0,
      il sera rempli avec le contenu ajoute au schema par le fromXml
      @return true si l'import a reussi, false sinon
*/
00352 bool Diagram::fromXml(QDomDocument &document, QPointF position, bool consider_informations, DiagramContent *content_ptr) {
      QDomElement root = document.documentElement();
      return(fromXml(root, position, consider_informations, content_ptr));
}

/**
      Importe le schema decrit dans un element XML. Cette methode delegue son travail a Diagram::fromXml
      Si l'import reussit, cette methode initialise egalement le document XML
      interne permettant de bien gerer l'enregistrement de ce schema dans le
      projet auquel il appartient.
      @see Diagram::fromXml
      @param document Le document XML a analyser
      @param position La position du schema importe
      @param consider_informations Si vrai, les informations complementaires
      (auteur, titre, ...) seront prises en compte
      @param content_ptr si ce pointeur vers un DiagramContent est different de 0,
      il sera rempli avec le contenu ajoute au schema par le fromXml
      @return true si l'import a reussi, false sinon
      
*/
00372 bool Diagram::initFromXml(QDomElement &document, QPointF position, bool consider_informations, DiagramContent *content_ptr) {
      // import le contenu et les proprietes du schema depuis l'element XML fourni en parametre
      bool from_xml = fromXml(document, position, consider_informations, content_ptr);
      
      // initialise le document XML interne a partir de l'element XML fourni en parametre
      if (from_xml) {
            xml_document.clear();
            xml_document.appendChild(xml_document.importNode(document, true));
            // a ce stade, le document XML interne contient le code XML qui a ete importe, et non pas une version re-exporte par la methode toXml()
      }
      return(from_xml);
}

/**
      Importe le schema decrit dans un element XML. Si une position est
      precisee, les elements importes sont positionnes de maniere a ce que le
      coin superieur gauche du plus petit rectangle pouvant les entourant tous
      (le bounding rect) soit a cette position.
      @param document Le document XML a analyser
      @param position La position du schema importe
      @param consider_informations Si vrai, les informations complementaires
      (auteur, titre, ...) seront prises en compte
      @param content_ptr si ce pointeur vers un DiagramContent est different de 0,
      il sera rempli avec le contenu ajoute au schema par le fromXml
      @return true si l'import a reussi, false sinon
*/
00398 bool Diagram::fromXml(QDomElement &document, QPointF position, bool consider_informations, DiagramContent *content_ptr) {
      QDomElement root = document;
      // le premier element doit etre un schema
      if (root.tagName() != "diagram") return(false);
      
      // lecture des attributs de ce schema
      if (consider_informations) {
            border_and_inset.setAuthor(root.attribute("author"));
            border_and_inset.setTitle(root.attribute("title"));
            border_and_inset.setDate(QDate::fromString(root.attribute("date"), "yyyyMMdd"));
            border_and_inset.setFileName(root.attribute("filename"));
            border_and_inset.setFolio(root.attribute("folio"));
            
            bool ok;
            // nombre de colonnes
            int nb_cols = root.attribute("cols").toInt(&ok);
            if (ok) border_and_inset.setNbColumns(nb_cols);
            
            // taille des colonnes
            double col_size = root.attribute("colsize").toDouble(&ok);
            if (ok) border_and_inset.setColumnsWidth(col_size);
            
            // retrocompatibilite : les schemas enregistres avec la 0.1 ont un attribut "height"
            if (root.hasAttribute("rows") && root.hasAttribute("rowsize")) {
                  // nombre de lignes
                  int nb_rows = root.attribute("rows").toInt(&ok);
                  if (ok) border_and_inset.setNbRows(nb_rows);
                  
                  // taille des lignes
                  double row_size = root.attribute("rowsize").toDouble(&ok);
                  if (ok) border_and_inset.setRowsHeight(row_size);
            } else {
                  // hauteur du schema
                  double height = root.attribute("height").toDouble(&ok);
                  if (ok) border_and_inset.setDiagramHeight(height);
            }
            
            // affichage des lignes et colonnes
            border_and_inset.displayColumns(root.attribute("displaycols") != "false");
            border_and_inset.displayRows(root.attribute("displayrows") != "false");
            
            border_and_inset.adjustInsetToColumns();
            
            // repere le permier element "defaultconductor"
            for (QDomNode node = root.firstChild() ; !node.isNull() ; node = node.nextSibling()) {
                  QDomElement elmts = node.toElement();
                  if(elmts.isNull() || elmts.tagName() != "defaultconductor") continue;
                  defaultConductorProperties.fromXml(elmts);
                  break;
            }
            
      }
      
      // si la racine n'a pas d'enfant : le chargement est fini (schema vide)
      if (root.firstChild().isNull()) {
            write(document);
            return(true);
      }
      
      // chargement de tous les elements du fichier XML
      QList<Element *> added_elements;
      QHash<int, Terminal *> table_adr_id;
      foreach (QDomElement e, QET::findInDomElement(root, "elements", "element")) {
            if (!Element::valideXml(e)) continue;
            
            // cree un element dont le type correspond a l'id type
            QString type_id = e.attribute("type");
            ElementsLocation element_location = ElementsLocation(type_id);
            if (type_id.startsWith("embed://")) element_location.setProject(project_);
            
            CustomElement *nvel_elmt = new CustomElement(element_location);
            if (nvel_elmt -> isNull()) {
                  QString debug_message = QString("Diagram::fromXml() : Le chargement de la description de l'element %1 a echoue avec le code d'erreur %2").arg(element_location.path()).arg(nvel_elmt -> state());
                  qDebug() << qPrintable(debug_message);
                  delete nvel_elmt;
                  
                  qDebug() << "Diagram::fromXml() : Utilisation d'un GhostElement en lieu et place de cet element.";
                  nvel_elmt = new GhostElement(element_location);
            }
            
            // charge les caracteristiques de l'element
            if (nvel_elmt -> fromXml(e, table_adr_id)) {
                  // ajout de l'element au schema et a la liste des elements ajoutes
                  addElement(nvel_elmt);
                  added_elements << nvel_elmt;
            } else {
                  delete nvel_elmt;
                  qDebug() << "Diagram::fromXml() : Le chargement des parametres d'un element a echoue";
            }
      }
      
      // chargement de tous les textes du fichiers XML
      QList<DiagramTextItem *> added_texts;
      foreach (QDomElement f, QET::findInDomElement(root, "inputs", "input")) {
            DiagramTextItem *dti = new DiagramTextItem(0, this);
            dti -> fromXml(f);
            addDiagramTextItem(dti);
            added_texts << dti;
      }
      
      // gere la translation des nouveaux elements et texte si celle-ci est demandee
      if (position != QPointF()) {
            // determine quel est le coin superieur gauche du rectangle entourant les elements ajoutes
            qreal minimum_x = 0, minimum_y = 0;
            bool init = false;
            QList<QGraphicsItem *> added_items;
            foreach (Element *added_element, added_elements) added_items << added_element;
            foreach (DiagramTextItem *added_text, added_texts) added_items << added_text;
            foreach (QGraphicsItem *item, added_items) {
                  QPointF csg = item -> mapToScene(item -> boundingRect()).boundingRect().topLeft();
                  qreal px = csg.x();
                  qreal py = csg.y();
                  if (!init) {
                        minimum_x = px;
                        minimum_y = py;
                        init = true;
                  } else {
                        if (px < minimum_x) minimum_x = px;
                        if (py < minimum_y) minimum_y = py;
                  }
            }
            qreal diff_x = position.x() - minimum_x;
            qreal diff_y = position.y() - minimum_y;
            foreach (Element *added_element, added_elements) {
                  added_element -> setPos(added_element -> pos().x() + diff_x, added_element -> pos().y() + diff_y);
            }
            foreach (DiagramTextItem *added_text, added_texts) {
                  added_text -> setPos(added_text -> pos().x() + diff_x, added_text -> pos().y() + diff_y);
            }
      }
      
      // chargement de tous les Conducteurs du fichier XML
      QList<Conductor *> added_conductors;
      foreach (QDomElement f, QET::findInDomElement(root, "conductors", "conductor")) {
            if (!Conductor::valideXml(f)) continue;
            // verifie que les bornes que le conducteur relie sont connues
            int id_p1 = f.attribute("terminal1").toInt();
            int id_p2 = f.attribute("terminal2").toInt();
            if (table_adr_id.contains(id_p1) && table_adr_id.contains(id_p2)) {
                  // pose le conducteur... si c'est possible
                  Terminal *p1 = table_adr_id.value(id_p1);
                  Terminal *p2 = table_adr_id.value(id_p2);
                  if (p1 != p2) {
                        bool can_add_conductor = true;
                        bool cia = ((Element *)p2 -> parentItem()) -> internalConnections();
                        if (!cia) {
                              foreach(QGraphicsItem *item, p2 -> parentItem() -> children()) {
                                    if (item == p1) can_add_conductor = false;
                              }
                        }
                        if (can_add_conductor) {
                              Conductor *c = new Conductor(table_adr_id.value(id_p1), table_adr_id.value(id_p2), 0, this);
                              c -> fromXml(f);
                              added_conductors << c;
                        }
                  }
            } else qDebug() << "Diagram::fromXml() : Le chargement du conducteur" << id_p1 << id_p2 << "a echoue";
      }
      
      // remplissage des listes facultatives
      if (content_ptr) {
            content_ptr -> elements         = added_elements;
            content_ptr -> conductorsToMove = added_conductors;
            content_ptr -> textFields       = added_texts;
      }
      
      return(true);
}

/**
      Enregistre le schema XML dans son document XML interne et emet le signal
      written().
*/
00571 void Diagram::write() {
      qDebug() << qPrintable(QString("Diagram::write() : saving changes from diagram \"%1\" [%2]").arg(title()).arg(QET::pointerString(this)));
      write(toXml().documentElement());
      undoStack().setClean();
}

/**
      Enregistre un element XML dans son document XML interne et emet le signal
      written().
      @param element xml a enregistrer
*/
00582 void Diagram::write(const QDomElement &element) {
      xml_document.clear();
      xml_document.appendChild(xml_document.importNode(element, true));
      emit(written());
}

/**
      @return true si la fonction write a deja ete appele (pour etre plus exact :
      si le document XML utilise en interne n'est pas vide), false sinon
*/
00592 bool Diagram::wasWritten() const {
      return(!xml_document.isNull());
}

/**
      @return le schema en XML tel qu'il doit etre enregistre dans le fichier projet
      @param xml_doc document XML a utiliser pour creer l'element
*/
00600 QDomElement Diagram::writeXml(QDomDocument &xml_doc) const {
      // si le schema n'a pas ete enregistre explicitement, on n'ecrit rien
      if (!wasWritten()) return(QDomElement());
      
      QDomElement diagram_elmt = xml_document.documentElement();
      QDomNode new_node = xml_doc.importNode(diagram_elmt, true);
      return(new_node.toElement());
}

/**
      Ajoute un element sur le schema
      @param element Element a ajouter
*/
00613 void Diagram::addElement(Element *element) {
      if (!element || isReadOnly()) return;
      
      // ajoute l'element au schema
      if (element -> scene() != this) {
            addItem(element);
      }
      
      // surveille les modifications de ses champs de texte
      foreach(ElementTextItem *eti, element -> texts()) {
            connect(
                  eti,
                  SIGNAL(diagramTextChanged(DiagramTextItem *, const QString &, const QString &)),
                  this,
                  SLOT(diagramTextChanged(DiagramTextItem *, const QString &, const QString &))
            );
      }
}

/**
      Ajoute un conducteur sur le schema
      @param conductor Conducteur a ajouter
*/
00636 void Diagram::addConductor(Conductor *conductor) {
      if (!conductor || isReadOnly()) return;
      
      // ajoute le conducteur au schema
      if (conductor -> scene() != this) {
            addItem(conductor);
            conductor -> terminal1 -> addConductor(conductor);
            conductor -> terminal2 -> addConductor(conductor);
      }
}

/**
      Aoute un champ de texte independant sur le schema
      @param dti Champ de texte a ajouter
*/
00651 void Diagram::addDiagramTextItem(DiagramTextItem *dti) {
      if (!dti || isReadOnly()) return;
      
      // ajoute le champ de texte au schema
      if (dti -> scene() != this) {
            addItem(dti);
      }
      
      // surveille les modifications apportees au champ de texte
      connect(
            dti,
            SIGNAL(diagramTextChanged(DiagramTextItem *, const QString &, const QString &)),
            this,
            SLOT(diagramTextChanged(DiagramTextItem *, const QString &, const QString &))
      );
}

/**
      Enleve un element du schema
      @param element Element a enlever
*/
00672 void Diagram::removeElement(Element *element) {
      if (!element || isReadOnly()) return;
      
      // enleve l'element au schema
      removeItem(element);
      
      // arrete la surveillance des modifications de ses champs de texte
      foreach(ElementTextItem *eti, element -> texts()) {
            disconnect(
                  eti,
                  SIGNAL(diagramTextChanged(DiagramTextItem *, const QString &, const QString &)),
                  this,
                  SLOT(diagramTextChanged(DiagramTextItem *, const QString &, const QString &))
            );
      }
}

/**
      Enleve un conducteur du schema
      @param conductor Conducteur a enlever
*/
00693 void Diagram::removeConductor(Conductor *conductor) {
      if (!conductor || isReadOnly()) return;
      
      // detache le conducteur sans le detruire
      conductor -> terminal1 -> removeConductor(conductor);
      conductor -> terminal2 -> removeConductor(conductor);
      
      // enleve le conducteur du schema
      removeItem(conductor);
}

/**
      Enleve un champ de texte independant du schema
      @param dti Champ de texte a enlever
*/
00708 void Diagram::removeDiagramTextItem(DiagramTextItem *dti) {
      if (!dti || isReadOnly()) return;
      
      // enleve le champ de texte au schema
      removeItem(dti);
      
      // arrete la surveillance des modifications apportees au champ de texte
      disconnect(
            dti,
            SIGNAL(diagramTextChanged(DiagramTextItem *, const QString &, const QString &)),
            this,
            SLOT(diagramTextChanged(DiagramTextItem *, const QString &, const QString &))
      );
}

/**
      Gere le fait qu'un texte du schema ait ete modifie
      @param text_item Texte modifie
      @param old_text Ancien texte
      @param new_text Nouveau texte
*/
00729 void Diagram::diagramTextChanged(DiagramTextItem *text_item, const QString &old_text, const QString &new_text) {
      if (!text_item) return;
      undo_stack -> push(new ChangeDiagramTextCommand(text_item, old_text, new_text));
}

/**
      Selectionne tous les objets du schema
*/
00737 void Diagram::selectAll() {
      if (items().isEmpty()) return;
      
      blockSignals(true);
      foreach(QGraphicsItem *qgi, items()) qgi -> setSelected(true);
      blockSignals(false);
      emit(selectionChanged());
}

/**
      Deslectionne tous les objets selectionnes
*/
00749 void Diagram::deselectAll() {
      if (items().isEmpty()) return;
      
      clearSelection();
}

/**
      Inverse l'etat de selection de tous les objets du schema
*/
00758 void Diagram::invertSelection() {
      if (items().isEmpty()) return;
      
      blockSignals(true);
      foreach (QGraphicsItem *item, items()) item -> setSelected(!item -> isSelected());
      blockSignals(false);
      emit(selectionChanged());
}

/**
      @return Le rectangle (coordonnees par rapport a la scene) delimitant le bord du schema
*/
00770 QRectF Diagram::border() const {
      return(
            QRectF(
                  margin,
                  margin,
                  border_and_inset.borderWidth(),
                  border_and_inset.borderHeight()
            )
      );
}

/**
      @return le titre du cartouche
*/
00784 QString Diagram::title() const {
      return(border_and_inset.title());
}

/**
      @return la liste des elements de ce schema
*/
00791 QList<CustomElement *> Diagram::customElements() const {
      QList<CustomElement *> elements_list;
      foreach(QGraphicsItem *qgi, items()) {
            if (CustomElement *elmt = qgraphicsitem_cast<CustomElement *>(qgi)) {
                  elements_list << elmt;
            }
      }
      return(elements_list);
}

/**
      Oublie la liste des elements et conducteurs en mouvement
*/
00804 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();
}

/**
      Reconstruit la liste des elements et conducteurs en mouvement
*/
00816 void Diagram::fetchMovedElements() {
      // recupere les elements deplaces
      foreach (QGraphicsItem *item, selectedItems()) {
            if (Element *elmt = qgraphicsitem_cast<Element *>(item)) {
                  elements_to_move << elmt;
            } else if (DiagramTextItem *t = qgraphicsitem_cast<DiagramTextItem *>(item)) {
                  if (!t -> parentItem()) texts_to_move << t;
            }
      }
      
      // 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(static_cast<Element *>(other_terminal -> parentItem()))) {
                              conductors_to_move << conductor;
                        } else {
                              conductors_to_update.insert(conductor, terminal);
                        }
                  }
            }
      }
      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).
      @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.
*/
00856 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
      const QHash<Conductor *, Terminal *> &conductors_modify = conductorsToUpdate();
      foreach(Conductor *conductor, conductors_modify.keys()) {
            conductor -> updateWithNewPos(QRectF(), conductors_modify[conductor], conductors_modify[conductor] -> amarrageConductor());
      }
      
      // deplace les champs de texte
      foreach(DiagramTextItem *dti, textsToMove()) {
            if (dontmove && dti == dontmove) continue;
            dti -> setPos(dti -> pos() + diff);
      }
}

/**
      Permet de savoir si un element est utilise sur un schema
      @param location Emplacement d'un element
      @return true si l'element location est utilise sur ce schema, false sinon
*/
00891 bool Diagram::usesElement(const ElementsLocation &location) {
      foreach(CustomElement *element, customElements()) {
            if (element -> location() == location) {
                  return(true);
            }
      }
      return(false);
}

/**
      @param pos Position cartesienne (ex : 10.3, 45.2) a transformer en position
      dans la grille (ex : B2)
      @return la position dans la grille correspondant a pos
*/
00905 DiagramPosition Diagram::convertPosition(const QPointF &pos) {
      // decale la position pour prendre en compte les marges en haut a gauche du schema
      QPointF final_pos = pos - QPointF(margin, margin);
      
      // delegue le calcul au BorderInset
      DiagramPosition diagram_position = border_and_inset.convertPosition(final_pos);
      
      // embarque la position cartesienne
      diagram_position.setPosition(pos);
      
      return(diagram_position);
}

/**
      Definit s'il faut afficher ou non les bornes
      @param dt true pour afficher les bornes, false sinon
*/
00922 void Diagram::setDrawTerminals(bool dt) {
      foreach(QGraphicsItem *qgi, items()) {
            if (Terminal *t = qgraphicsitem_cast<Terminal *>(qgi)) {
                  t -> setVisible(dt);
            }
      }
}

/**
      @return la liste des conducteurs selectionnes sur le schema
*/
00933 QSet<Conductor *> Diagram::selectedConductors() const {
      QSet<Conductor *> conductors_set;
      foreach(QGraphicsItem *qgi, selectedItems()) {
            if (Conductor *c = qgraphicsitem_cast<Conductor *>(qgi)) {
                  conductors_set << c;
            }
      }
      return(conductors_set);
}

/// @return true si le presse-papier semble contenir un schema
00944 bool Diagram::clipboardMayContainDiagram() {
      QString clipboard_text = QApplication::clipboard() -> text().trimmed();
      bool may_be_diagram = clipboard_text.startsWith("<diagram") && clipboard_text.endsWith("</diagram>");
      return(may_be_diagram);
}

/**
      @return le projet auquel ce schema appartient ou 0 s'il s'agit d'un schema
      independant.
*/
00954 QETProject *Diagram::project() const {
      return(project_);
}

/**
      @param project le nouveau projet auquel ce schema appartient ou 0 s'il
      s'agit d'un schema independant. Indiquer 0 pour rendre ce schema independant.
*/
00962 void Diagram::setProject(QETProject *project) {
      project_ = project;
}

/**
      @return true si le schema est en lecture seule
*/
00969 bool Diagram::isReadOnly() const {
      return(read_only_);
}

/**
      @param read_only true pour passer le schema en lecture seule, false sinon
*/
00976 void Diagram::setReadOnly(bool read_only) {
      if (read_only_ != read_only) {
            read_only_ = read_only;
            emit(readOnlyChanged(read_only_));
      }
}

/**
      @return Le contenu du schema. Les conducteurs sont tous places dans
      conductorsToMove.
*/
00987 DiagramContent Diagram::content() const {
      DiagramContent dc;
      foreach(QGraphicsItem *qgi, items()) {
            if (Element *e = qgraphicsitem_cast<Element *>(qgi)) {
                  dc.elements << e;
            } else if (DiagramTextItem *dti = qgraphicsitem_cast<DiagramTextItem *>(qgi)) {
                  dc.textFields << dti;
            } else if (Conductor *c = qgraphicsitem_cast<Conductor *>(qgi)) {
                  dc.conductorsToMove << c;
            }
      }
      return(dc);
}

/**
      @return le contenu selectionne du schema.
*/
01004 DiagramContent Diagram::selectedContent() {
      invalidateMovedElements();
      DiagramContent dc;
      dc.elements           = elementsToMove().toList();
      dc.textFields         = textsToMove().toList();
      dc.conductorsToMove   = conductorsToMove().toList();
      dc.conductorsToUpdate = conductorsToUpdate();
      
      // recupere les conducteurs selectionnes isoles (= non deplacables mais supprimables)
      foreach(QGraphicsItem *qgi, items()) {
            if (Conductor *c = qgraphicsitem_cast<Conductor *>(qgi)) {
                  if (
                        c -> isSelected() &&\
                        !c -> terminal1 -> parentItem() -> isSelected() &&\
                        !c -> terminal2 -> parentItem() -> isSelected()
                  ) {
                        dc.otherConductors << c;
                  }
            }
      }
      invalidateMovedElements();
      return(dc);
}

/**
      @return true s'il est possible de tourner les elements selectionnes.
      Concretement, cette methode retourne true s'il y a des elements selectionnes
      et qu'au moins l'un d'entre eux peut etre pivote.
*/
01033 bool Diagram::canRotateSelection() const {
      foreach(QGraphicsItem * qgi, selectedItems()) {
            if (Element *e = qgraphicsitem_cast<Element *>(qgi)) {
                  // l'element est-il pivotable ?
                  if (e -> orientation().current() != e -> orientation().next()) {
                        return(true);
                  }
            }
      }
      return(false);
}

Generated by  Doxygen 1.6.0   Back to index