xavierqet b91b418c95 Correction d'un bug dans la modification des conducteurs apres un deplacement d'elements
Implementation partielle de l'alignement des conducteurs sur la grille. Lors de la modification manuelle des conducteurs, les segments se fixent sur la grille. L'ancien comportement peut etre obtenu en maintenant la touche Shift enfoncee.
Optimisation des fonctions "Selectionner tout" et "Deselectionner tout"
Remplacement des #define par des attributs static const


git-svn-id: svn+ssh://svn.tuxfamily.org/svnroot/qet/qet/trunk@168 bfdf4180-ca20-0410-9c96-a3a8aa849046
2007-10-10 22:35:32 +00:00

447 lines
14 KiB
C++

#include "terminal.h"
#include "diagram.h"
#include "element.h"
#include "conductor.h"
#include "diagramcommands.h"
QColor Terminal::couleur_neutre = QColor(Qt::blue);
QColor Terminal::couleur_autorise = QColor(Qt::darkGreen);
QColor Terminal::couleur_prudence = QColor("#ff8000");
QColor Terminal::couleur_interdit = QColor(Qt::red);
const qreal Terminal::terminalSize = 4.0;
/**
Fonction privee pour initialiser la borne.
@param pf position du point d'amarrage pour un conducteur
@param o orientation de la borne : Qt::Horizontal ou Qt::Vertical
*/
void Terminal::initialise(QPointF pf, QET::Orientation o) {
// definition du pount d'amarrage pour un conducteur
amarrage_conductor = pf;
// definition de l'orientation de la terminal (par defaut : sud)
if (o < QET::North || o > QET::West) sens = QET::South;
else sens = o;
// calcul de la position du point d'amarrage a l'element
amarrage_elmt = amarrage_conductor;
switch(sens) {
case QET::North: amarrage_elmt += QPointF(0, Terminal::terminalSize); break;
case QET::East : amarrage_elmt += QPointF(-Terminal::terminalSize, 0); break;
case QET::West : amarrage_elmt += QPointF(Terminal::terminalSize, 0); break;
case QET::South:
default : amarrage_elmt += QPointF(0, -Terminal::terminalSize);
}
// par defaut : pas de conducteur
// QRectF null
br = new QRectF();
terminal_precedente = NULL;
// divers
setAcceptsHoverEvents(true);
setAcceptedMouseButtons(Qt::LeftButton);
hovered = false;
setToolTip(QObject::tr("Borne"));
}
/**
Constructeur par defaut
*/
Terminal::Terminal() :
QGraphicsItem(0, 0),
couleur_hovered(Terminal::couleur_neutre)
{
initialise(QPointF(0.0, 0.0), QET::South);
}
/**
initialise une borne
@param pf position du point d'amarrage pour un conducteur
@param o orientation de la borne : Qt::Horizontal ou Qt::Vertical
@param e Element auquel cette borne appartient
@param s Scene sur laquelle figure cette borne
*/
Terminal::Terminal(QPointF pf, QET::Orientation o, Element *e, Diagram *s) :
QGraphicsItem(e, s),
couleur_hovered(Terminal::couleur_neutre)
{
initialise(pf, o);
}
/**
initialise une borne
@param pf_x Abscisse du point d'amarrage pour un conducteur
@param pf_y Ordonnee du point d'amarrage pour un conducteur
@param o orientation de la borne : Qt::Horizontal ou Qt::Vertical
@param e Element auquel cette borne appartient
@param s Scene sur laquelle figure cette borne
*/
Terminal::Terminal(qreal pf_x, qreal pf_y, QET::Orientation o, Element *e, Diagram *s) :
QGraphicsItem(e, s),
couleur_hovered(Terminal::couleur_neutre)
{
initialise(QPointF(pf_x, pf_y), o);
}
/**
Destructeur
La destruction de la borne entraine la destruction des conducteurs
associes.
*/
Terminal::~Terminal() {
//qDebug() << "Terminal::~Terminal" << (void *)this;
foreach(Conductor *c, liste_conductors) delete c;
delete br;
}
/**
Permet de connaitre l'orientation de la borne. Si le parent de la borne
est bien un Element, cette fonction renvoie l'orientation par rapport a
la scene de la borne, en tenant compte du fait que l'element ait pu etre
pivote. Sinon elle renvoie son sens normal.
@return L'orientation actuelle de la Terminal.
*/
QET::Orientation Terminal::orientation() const {
if (Element *elt = qgraphicsitem_cast<Element *>(parentItem())) {
// orientations actuelle et par defaut de l'element
QET::Orientation ori_cur = elt -> orientation().current();
QET::Orientation ori_def = elt -> orientation().defaultOrientation();
if (ori_cur == ori_def) return(sens);
else {
// calcul l'angle de rotation implique par l'orientation de l'element parent
// angle de rotation de la borne sur la scene, divise par 90
int angle = ori_cur - ori_def + sens;
while (angle >= 4) angle -= 4;
return((QET::Orientation)angle);
}
} else return(sens);
}
/**
Attribue un conductor a la borne
@param f Le conducteur a rattacher a cette borne
*/
bool Terminal::addConductor(Conductor *f) {
// pointeur 0 refuse
if (!f) return(false);
// une seule des deux bornes du conducteur doit etre this
Q_ASSERT_X((f -> terminal1 == this ^ f -> terminal2 == this), "Terminal::addConductor", "Le conductor devrait etre relie exactement une fois a la terminal en cours");
// determine l'autre borne a laquelle cette borne va etre relie grace au conducteur
Terminal *autre_terminal = (f -> terminal1 == this) ? f -> terminal2 : f -> terminal1;
// verifie que la borne n'est pas deja reliee avec l'autre borne
bool deja_liees = false;
foreach (Conductor* conductor, liste_conductors) {
if (conductor -> terminal1 == autre_terminal || conductor -> terminal2 == autre_terminal) deja_liees = true;
}
// si les deux bornes sont deja reliees, on refuse d'ajouter le conducteur
if (deja_liees) return(false);
// sinon on ajoute le conducteur
liste_conductors.append(f);
return(true);
}
/**
Enleve un conducteur donne a la borne
@param f Conducteur a enlever
*/
void Terminal::removeConductor(Conductor *f) {
//qDebug() << "Terminal::removeConductor" << (void *)this;
int index = liste_conductors.indexOf(f);
if (index == -1) return;
liste_conductors.removeAt(index);
}
/**
Fonction de dessin des bornes
@param p Le QPainter a utiliser
@param options Les options de dessin
@param widget Le widget sur lequel on dessine
*/
void Terminal::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) {
p -> save();
//annulation des renderhints
p -> setRenderHint(QPainter::Antialiasing, false);
p -> setRenderHint(QPainter::TextAntialiasing, false);
p -> setRenderHint(QPainter::SmoothPixmapTransform, false);
// on travaille avec les coordonnees de l'element parent
QPointF f = mapFromParent(amarrage_conductor);
QPointF e = mapFromParent(amarrage_elmt);
QPen t;
t.setWidthF(1.0);
// dessin de la borne en rouge
t.setColor(Qt::red);
p -> setPen(t);
p -> drawLine(f, e);
// dessin du point d'amarrage au conducteur en bleu
t.setColor(couleur_hovered);
p -> setPen(t);
p -> setBrush(couleur_hovered);
if (hovered) {
p -> setRenderHint(QPainter::Antialiasing, true);
p -> drawEllipse(QRectF(f.x() - 2.5, f.y() - 2.5, 5.0, 5.0));
} else p -> drawPoint(f);
p -> restore();
}
/**
@return Le rectangle (en precision flottante) delimitant la borne et ses alentours.
*/
QRectF Terminal::boundingRect() const {
if (br -> isNull()) {
qreal afx = amarrage_conductor.x();
qreal afy = amarrage_conductor.y();
qreal aex = amarrage_elmt.x();
qreal aey = amarrage_elmt.y();
QPointF origine;
origine = (afx <= aex && afy <= aey ? amarrage_conductor : amarrage_elmt);
origine += QPointF(-3.0, -3.0);
qreal w = qAbs((int)(afx - aex)) + 7;
qreal h = qAbs((int)(afy - aey)) + 7;
*br = QRectF(origine, QSizeF(w, h));
}
return(*br);
}
/**
Gere l'entree de la souris sur la zone de la Borne.
*/
void Terminal::hoverEnterEvent(QGraphicsSceneHoverEvent *) {
hovered = true;
update();
}
/**
Gere les mouvements de la souris sur la zone de la Borne.
*/
void Terminal::hoverMoveEvent(QGraphicsSceneHoverEvent *) {
}
/**
Gere le fait que la souris sorte de la zone de la Borne.
*/
void Terminal::hoverLeaveEvent(QGraphicsSceneHoverEvent *) {
hovered = false;
update();
}
/**
Gere le fait qu'on enfonce un bouton de la souris sur la Borne.
@param e L'evenement souris correspondant
*/
void Terminal::mousePressEvent(QGraphicsSceneMouseEvent *e) {
if (Diagram *s = diagram()) {
s -> setConductorStart(mapToScene(QPointF(amarrage_conductor)));
s -> setConductorStop(e -> scenePos());
s -> setConductor(true);
//setCursor(Qt::CrossCursor);
}
}
/**
Gere le fait qu'on bouge la souris sur la Borne.
@param e L'evenement souris correspondant
*/
void Terminal::mouseMoveEvent(QGraphicsSceneMouseEvent *e) {
// pendant la pose d'un conducteur, on adopte un autre curseur
//setCursor(Qt::CrossCursor);
// d'un mouvement a l'autre, il faut retirer l'effet hover de la borne precedente
if (terminal_precedente != NULL) {
if (terminal_precedente == this) hovered = true;
else terminal_precedente -> hovered = false;
terminal_precedente -> couleur_hovered = terminal_precedente -> couleur_neutre;
terminal_precedente -> update();
}
Diagram *s = diagram();
if (!s) return;
// si la scene est un Diagram, on actualise le poseur de conducteur
s -> setConductorStop(e -> scenePos());
// on recupere la liste des qgi sous le pointeur
QList<QGraphicsItem *> qgis = s -> items(e -> scenePos());
/* le qgi le plus haut
= le poseur de conductor
= le premier element de la liste
= la liste ne peut etre vide
= on prend le deuxieme element de la liste
*/
Q_ASSERT_X(!(qgis.isEmpty()), "Terminal::mouseMoveEvent", "La liste d'items ne devrait pas etre vide");
// s'il y a autre chose que le poseur de conducteur dans la liste
if (qgis.size() > 1) {
// on prend le deuxieme element de la liste
QGraphicsItem *qgi = qgis.at(1);
// si le qgi est une borne...
if (Terminal *p = qgraphicsitem_cast<Terminal *>(qgi)) {
// ...on lui applique l'effet hover approprie
if (p == this) {
// effet si l'on hover sur la borne de depart
couleur_hovered = couleur_interdit;
} else if (p -> parentItem() == parentItem()) {
// effet si l'on hover sur une borne du meme appareil
if (((Element *)parentItem()) -> connexionsInternesAcceptees())
p -> couleur_hovered = p -> couleur_autorise;
else p -> couleur_hovered = p -> couleur_interdit;
} else if (p -> nbConductors()) {
// si la borne a deja un conducteur
// verifie que cette borne n'est pas deja reliee a l'autre borne
bool deja_reliee = false;
foreach (Conductor *f, liste_conductors) {
if (f -> terminal1 == p || f -> terminal2 == p) {
deja_reliee = true;
break;
}
}
// interdit si les bornes sont deja reliees, prudence sinon
p -> couleur_hovered = deja_reliee ? p -> couleur_interdit : p -> couleur_prudence;
} else {
// effet si on peut poser le conducteur
p -> couleur_hovered = p -> couleur_autorise;
}
terminal_precedente = p;
p -> hovered = true;
p -> update();
}
}
}
/**
Gere le fait qu'on relache la souris sur la Borne.
@param e L'evenement souris correspondant
*/
void Terminal::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) {
//setCursor(Qt::ArrowCursor);
terminal_precedente = NULL;
couleur_hovered = couleur_neutre;
// verifie que la scene est bien un Diagram
if (Diagram *s = diagram()) {
// on arrete de dessiner l'apercu du conducteur
s -> setConductor(false);
// on recupere l'element sous le pointeur lors du MouseReleaseEvent
QGraphicsItem *qgi = s -> itemAt(e -> scenePos());
// s'il n'y a rien, on arrete la
if (!qgi) return;
// idem si l'element obtenu n'est pas une borne
Terminal *p = qgraphicsitem_cast<Terminal *>(qgi);
if (!p) return;
// on remet la couleur de hover a sa valeur par defaut
p -> couleur_hovered = p -> couleur_neutre;
// idem s'il s'agit de la borne actuelle
if (p == this) return;
// idem s'il s'agit d'une borne de l'element actuel et que l'element n'a pas le droit de relier ses propres bornes
bool cia = ((Element *)parentItem()) -> connexionsInternesAcceptees();
if (!cia) foreach(QGraphicsItem *item, parentItem() -> children()) if (item == p) return;
// derniere verification : verifier que cette borne n'est pas deja reliee a l'autre borne
foreach (Conductor *f, liste_conductors) if (f -> terminal1 == p || f -> terminal2 == p) return;
// autrement, on pose un conducteur
s -> undoStack().push(new AddConductorCommand(s, new Conductor(this, p)));
}
}
/**
Met a jour l'eventuel conducteur relie a la Terminal.
@param newpos Position de l'element parent a prendre en compte
*/
void Terminal::updateConductor(QPointF newpos) {
if (!scene() || !parentItem()) return;
foreach (Conductor *conductor, liste_conductors) {
if (conductor -> isDestroyed()) continue;
if (newpos == QPointF()) conductor -> update(QRectF());
else {
// determine la translation subie par l'element parent
QPointF translation = newpos - parentItem() -> pos();
// rafraichit le conducteur en tenant compte de la translation
conductor -> updateWithNewPos(QRectF(), this, amarrageConductor() + translation);
}
}
}
/**
@return La liste des conducteurs lies a cette borne
*/
QList<Conductor *> Terminal::conductors() const {
return(liste_conductors);
}
/**
Methode d'export en XML
@param doc Le Document XML a utiliser pour creer l'element XML
@return un QDomElement representant cette borne
*/
QDomElement Terminal::toXml(QDomDocument &doc) const {
QDomElement qdo = doc.createElement("terminal");
qdo.setAttribute("x", amarrage_elmt.x());
qdo.setAttribute("y", amarrage_elmt.y());
qdo.setAttribute("orientation", sens);
return(qdo);
}
/**
Permet de savoir si un element XML represente une borne
@param e Le QDomElement a analyser
@return true si le QDomElement passe en parametre est une borne, false sinon
*/
bool Terminal::valideXml(QDomElement &terminal) {
// verifie le nom du tag
if (terminal.tagName() != "terminal") return(false);
// verifie la presence des attributs minimaux
if (!terminal.hasAttribute("x")) return(false);
if (!terminal.hasAttribute("y")) return(false);
if (!terminal.hasAttribute("orientation")) return(false);
bool conv_ok;
// parse l'abscisse
terminal.attribute("x").toDouble(&conv_ok);
if (!conv_ok) return(false);
// parse l'ordonnee
terminal.attribute("y").toDouble(&conv_ok);
if (!conv_ok) return(false);
// parse l'id
terminal.attribute("id").toInt(&conv_ok);
if (!conv_ok) return(false);
// parse l'orientation
int terminal_or = terminal.attribute("orientation").toInt(&conv_ok);
if (!conv_ok) return(false);
if (terminal_or != QET::North && terminal_or != QET::South && terminal_or != QET::East && terminal_or != QET::West) return(false);
// a ce stade, la borne est syntaxiquement correcte
return(true);
}
/**
Permet de savoir si un element XML represente cette borne. Attention, l'element XML n'est pas verifie
@param e Le QDomElement a analyser
@return true si la borne "se reconnait" (memes coordonnes, meme orientation), false sinon
*/
bool Terminal::fromXml(QDomElement &terminal) {
return (
terminal.attribute("x").toDouble() == amarrage_elmt.x() &&\
terminal.attribute("y").toDouble() == amarrage_elmt.y() &&\
terminal.attribute("orientation").toInt() == sens
);
}
/// @return le Diagram auquel cette borne appartient, ou 0 si cette borne est independant
Diagram *Terminal::diagram() const {
return(qobject_cast<Diagram *>(scene()));
}