commit 5cadf173c7b73460b62409c81568fc8999177d52 Author: xavierqet Date: Fri Oct 27 15:47:22 2006 +0000 Import initial git-svn-id: svn+ssh://svn.tuxfamily.org/svnroot/qet/qet/trunk@1 bfdf4180-ca20-0410-9c96-a3a8aa849046 diff --git a/aboutqet.cpp b/aboutqet.cpp new file mode 100644 index 000000000..a69d073ff --- /dev/null +++ b/aboutqet.cpp @@ -0,0 +1,122 @@ +#include "aboutqet.h" + +/** + Constructeur + @param parent Le QWidget parent de la boite de dialogue +*/ +AboutQET::AboutQET(QWidget *parent) : QDialog(parent) { + // Titre, taille, comportement... + setWindowTitle(tr("\300 propos de QElectrotech")); + setMinimumWidth(680); + setMinimumHeight(350); + setModal(true); + + // Trois onglets + QTabWidget *onglets = new QTabWidget(this); + onglets -> addTab(ongletAPropos(), tr("\300 &propos")); + onglets -> addTab(ongletAuteurs(), tr("A&uteurs")); + onglets -> addTab(ongletLicence(), tr("&Accord de licence")); + + // Un bouton pour fermer la boite de dialogue + QDialogButtonBox *boutons = new QDialogButtonBox(QDialogButtonBox::Close); + connect(boutons, SIGNAL(accepted()), this, SLOT(accept())); + connect(boutons, SIGNAL(rejected()), this, SLOT(accept())); + + // Le tout dans une disposition verticale + QVBoxLayout *disposition = new QVBoxLayout(); + disposition -> addWidget(titre()); + disposition -> addWidget(onglets); + disposition -> addWidget(boutons); + setLayout(disposition); +} + +/** + @return Le titre QElectroTech avec son icone +*/ +QWidget *AboutQET::titre() { + QWidget *icone_et_titre = new QWidget(); + // icone + QLabel *icone = new QLabel(); + icone -> setPixmap(QIcon(":/ico/qelectrotech.png").pixmap(48, 48)); + // label "QElectroTech" + QLabel *titre = new QLabel("QElectroTech"); + titre -> setTextFormat(Qt::RichText); + // le tout dans une grille + QGridLayout *dispo_horiz = new QGridLayout(); + dispo_horiz -> addWidget(icone, 0, 0); + dispo_horiz -> addWidget(titre, 0, 1); + dispo_horiz -> setColumnStretch(0, 1); + dispo_horiz -> setColumnStretch(1, 100); + icone_et_titre -> setLayout(dispo_horiz); + return(icone_et_titre); +} + +/** + @return Le widget contenu par l'onglet « A propos » +*/ +QWidget *AboutQET::ongletAPropos() { + QLabel *apropos = new QLabel( + tr( + "QElectroTech, une application de " + "r\351alisation de sch\351mas \351lectriques.\n\n\251 2006 Les " + "d\351veloppeurs de QElectroTech\n\nMerde on n'a pas de site web" + ) + ); + apropos -> setAlignment(Qt::AlignCenter); + return(apropos); +} + +/** + @return Le widget contenu par l'onglet « Auteurs » +*/ +QWidget *AboutQET::ongletAuteurs() { + QLabel *auteurs = new QLabel( + tr( + "Id\351e originale : Beno\356t Ansieau \n\n" + "Programmation : Xavier Guerrin " + ) + ); + auteurs -> setAlignment(Qt::AlignCenter); + return(auteurs); +} + +/** + @return Le widget contenu par l'onglet « Accord de Licence » +*/ +QWidget *AboutQET::ongletLicence() { + QWidget *licence = new QWidget(); + // label + QLabel *titre_licence = new QLabel(tr("Ce programme est sous licence GNU/GPL.")); + + // Recuperation du texte de la GNU/GPL dans un fichier externe + QFile *fichier_gpl = new QFile("./gnugpl.txt"); + QString txt_gpl; + // verifie que le fichier existe + if (!fichier_gpl -> exists()) { + txt_gpl = QString(tr("Le fichier texte contenant la licence GNU/GPL est introuvable - bon bah de toute fa\347on, vous la connaissez par coeur non ?")); + } else { + // ouvre le fichier en mode texte et en lecture seule + if (!fichier_gpl -> open(QIODevice::ReadOnly | QIODevice::Text)) { + txt_gpl = QString(tr("Le fichier texte contenant la licence GNU/GPL existe mais n'a pas pu \352tre ouvert - bon bah de toute fa\347on, vous la connaissez par coeur non ?")); + } else { + // charge le contenu du fichier dans une QString + QTextStream in(fichier_gpl); + txt_gpl = QString(""); + while (!in.atEnd()) txt_gpl += in.readLine()+"\n"; + // ferme le fichier + fichier_gpl -> close(); + } + } + + // texte de la GNU/GPL dans une zone de texte scrollable non editable + QTextEdit *texte_licence = new QTextEdit(); + texte_licence -> setPlainText(txt_gpl); + texte_licence -> setReadOnly(true); + + // le tout dans une disposition verticale + QVBoxLayout *dispo_licence = new QVBoxLayout(); + dispo_licence -> addWidget(titre_licence); + dispo_licence -> addWidget(texte_licence); + licence -> setLayout(dispo_licence); + return(licence); +} diff --git a/aboutqet.h b/aboutqet.h new file mode 100644 index 000000000..bc5123ca7 --- /dev/null +++ b/aboutqet.h @@ -0,0 +1,18 @@ +#ifndef ABOUTQET_H + #define ABOUTQET_H + #include + /** + Cette classe represente la boite de dialogue + « A propos de QElectroTech » + */ + class AboutQET : public QDialog { + Q_OBJECT + public: + AboutQET(QWidget * = 0); + private: + QWidget *titre(); + QWidget *ongletAPropos(); + QWidget *ongletAuteurs(); + QWidget *ongletLicence(); + }; +#endif diff --git a/borne.cpp b/borne.cpp new file mode 100644 index 000000000..1741b5c7f --- /dev/null +++ b/borne.cpp @@ -0,0 +1,412 @@ +#include "borne.h" +#include "schema.h" +#include "element.h" +#include "conducteur.h" + +/** + 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 Borne::initialise(QPointF pf, Borne::Orientation o) { + // definition du pount d'amarrage pour un conducteur + amarrage_conducteur = pf; + + // definition de l'orientation de la borne (par defaut : sud) + if (o < Borne::Nord || o > Borne::Ouest) sens = Borne::Sud; + else sens = o; + + // calcul de la position du point d'amarrage a l'element + amarrage_elmt = amarrage_conducteur; + switch(sens) { + case Borne::Nord : amarrage_elmt += QPointF(0, TAILLE_BORNE); break; + case Borne::Est : amarrage_elmt += QPointF(-TAILLE_BORNE, 0); break; + case Borne::Ouest : amarrage_elmt += QPointF(TAILLE_BORNE, 0); break; + case Borne::Sud : + default : amarrage_elmt += QPointF(0, -TAILLE_BORNE); + } + + // par defaut : pas de conducteur + + // QRectF null + br = new QRectF(); + borne_precedente = NULL; + // divers + setAcceptsHoverEvents(true); + setAcceptedMouseButtons(Qt::LeftButton); + hovered = false; + setToolTip("Borne"); + couleur_neutre = QColor(Qt::blue); + couleur_autorise = QColor(Qt::darkGreen); + couleur_prudence = QColor("#ff8000"); + couleur_interdit = QColor(Qt::red); + couleur_hovered = couleur_neutre; +} + +/** + Constructeur par defaut +*/ +Borne::Borne() : QGraphicsItem(0, 0) { + initialise(QPointF(0.0, 0.0), Borne::Sud); + schema_scene = 0; +} + +/** + 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 +*/ +Borne::Borne(QPointF pf, Borne::Orientation o, Element *e, Schema *s) : QGraphicsItem(e, s) { + initialise(pf, o); + schema_scene = s; +} + +/** + 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 +*/ +Borne::Borne(qreal pf_x, qreal pf_y, Borne::Orientation o, Element *e, Schema *s) : QGraphicsItem(e, s) { + initialise(QPointF(pf_x, pf_y), o); +} + +/** + Destructeur +*/ +Borne::~Borne() { + 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 Borne. +*/ +Borne::Orientation Borne::orientation() const { + //true pour une orientation verticale, false pour une orientation horizontale + if (Element *elt = qgraphicsitem_cast(parentItem())) { + if (elt -> orientation()) return(sens); + else { + Borne::Orientation retour; + switch(sens) { + case Borne::Nord : retour = Borne::Ouest; break; + case Borne::Est : retour = Borne::Nord; break; + case Borne::Ouest : retour = Borne::Sud; break; + case Borne::Sud : + default : retour = Borne::Est; + } + return(retour); + } + } else return(sens); +} + +/** + Attribue un conducteur a la borne + @param f Le conducteur a rattacher a cette borne +*/ +bool Borne::addConducteur(Conducteur *f) { + // pointeur 0 refuse + if (!f) return(false); + + // une seule des deux bornes du conducteur doit etre this + Q_ASSERT_X((f -> borne1 == this ^ f -> borne2 == this), "Borne::addConducteur", "Le conducteur devrait etre relie exactement une fois a la borne en cours"); + + // determine l'autre borne a laquelle cette borne va etre relie grace au conducteur + Borne *autre_borne = (f -> borne1 == this) ? f -> borne2 : f -> borne1; + + // verifie que la borne n'est pas deja reliee avec l'autre borne + bool deja_liees = false; + foreach (Conducteur* conducteur, liste_conducteurs) { + if (conducteur -> borne1 == autre_borne || conducteur -> borne2 == autre_borne) 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_conducteurs.append(f); + return(true); +} + +void Borne::removeConducteur(Conducteur *f) { + int index = liste_conducteurs.indexOf(f); + if (index == -1) return; + liste_conducteurs.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 Borne::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_conducteur); + 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 -> drawEllipse(((int)f.x())-2, ((int)f.y())-2, 5, 5); + else p -> drawPoint(f); + + p -> restore(); +} + +/** + @return Le rectangle (en precision flottante) delimitant la borne et ses alentours. +*/ +QRectF Borne::boundingRect() const { + if (br -> isNull()) { + qreal afx = amarrage_conducteur.x(); + qreal afy = amarrage_conducteur.y(); + qreal aex = amarrage_elmt.x(); + qreal aey = amarrage_elmt.y(); + QPointF origine; + origine = (afx <= aex && afy <= aey ? amarrage_conducteur : 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 Borne::hoverEnterEvent(QGraphicsSceneHoverEvent *) { + hovered = true; + update(); +} + +/** + Gere les mouvements de la souris sur la zone de la Borne. +*/ +void Borne::hoverMoveEvent(QGraphicsSceneHoverEvent *) { +} + +/** + Gere le fait que la souris sorte de la zone de la Borne. +*/ +void Borne::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 Borne::mousePressEvent(QGraphicsSceneMouseEvent *e) { + if (Schema *s = qobject_cast(scene())) { + s -> setDepart(mapToScene(QPointF(amarrage_conducteur))); + s -> setArrivee(e -> scenePos()); + s -> poseConducteur(true); + setCursor(Qt::CrossCursor); + } + //QGraphicsItem::mouseReleaseEvent(e); +} + +/** + Gere le fait qu'on bouge la souris sur la Borne. + @param e L'evenement souris correspondant +*/ +void Borne::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 (borne_precedente != NULL) { + if (borne_precedente == this) hovered = true; + else borne_precedente -> hovered = false; + borne_precedente -> couleur_hovered = borne_precedente -> couleur_neutre; + borne_precedente -> update(); + } + + // si la scene est un Schema, on actualise le poseur de conducteur + if (Schema *s = qobject_cast(scene())) s -> setArrivee(e -> scenePos()); + + // on recupere la liste des qgi sous le pointeur + QList qgis = scene() -> items(e -> scenePos()); + + /* le qgi le plus haut + = le poseur de conducteur + = 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()), "Borne::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 (Borne *p = qgraphicsitem_cast(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 -> nbConducteurs()) { + // 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 (Conducteur *f, liste_conducteurs) { + if (f -> borne1 == p || f -> borne2 == 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; + } + borne_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 Borne::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { + setCursor(Qt::ArrowCursor); + borne_precedente = NULL; + couleur_hovered = couleur_neutre; + // verifie que la scene est bien un Schema + if (Schema *s = qobject_cast(scene())) { + // on arrete de dessiner l'apercu du conducteur + s -> poseConducteur(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 + Borne *p = qgraphicsitem_cast(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 (Conducteur *f, liste_conducteurs) if (f -> borne1 == p || f -> borne2 == p) return; + // autrement, on pose un conducteur + new Conducteur(this, (Borne *)qgi, 0, scene()); + } +} + +/** + Met a jour l'eventuel conducteur relie a la Borne. +*/ +void Borne::updateConducteur() { + if (scene()) { + foreach (Conducteur *conducteur, liste_conducteurs) if (!conducteur -> isDestroyed()) conducteur -> update(QRectF()/*scene()->sceneRect()*/); + } +} + +/** + @return La liste des conducteurs lies a cette borne +*/ +QList Borne::conducteurs() const { + return(liste_conducteurs); +} + +/** + Methode d'export en XML + @param doc Le Document XML a utiliser pour creer l'element XML + @return un QDomElement representant cette borne +*/ +QDomElement Borne::toXml(QDomDocument &doc) { + QDomElement qdo = doc.createElement("borne"); + 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 Borne::valideXml(QDomElement &borne) { + // verifie le nom du tag + if (borne.tagName() != "borne") return(false); + + // verifie la presence des attributs minimaux + if (!borne.hasAttribute("x")) return(false); + if (!borne.hasAttribute("y")) return(false); + if (!borne.hasAttribute("orientation")) return(false); + + bool conv_ok; + // parse l'abscisse + borne.attribute("x").toDouble(&conv_ok); + if (!conv_ok) return(false); + + // parse l'ordonnee + borne.attribute("y").toDouble(&conv_ok); + if (!conv_ok) return(false); + + // parse l'id + borne.attribute("id").toInt(&conv_ok); + if (!conv_ok) return(false); + + // parse l'orientation + int borne_or = borne.attribute("orientation").toInt(&conv_ok); + if (!conv_ok) return(false); + if (borne_or != Borne::Nord && borne_or != Borne::Sud && borne_or != Borne::Est && borne_or != Borne::Ouest) 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 Borne::fromXml(QDomElement &borne) { + return ( + borne.attribute("x").toDouble() == amarrage_elmt.x() &&\ + borne.attribute("y").toDouble() == amarrage_elmt.y() &&\ + borne.attribute("orientation").toInt() == sens + ); +} diff --git a/borne.h b/borne.h new file mode 100644 index 000000000..a7f58782d --- /dev/null +++ b/borne.h @@ -0,0 +1,81 @@ +#ifndef BORNE_H + #define BORNE_H + #define TAILLE_BORNE 4 + #include + #include + class Conducteur; + class Element; + class Schema; + /** + Classe modelisant la « borne » d'un appareil, c'est-a-dire un + branchement possible pour un Conducteur. + */ + class Borne : public QGraphicsItem { + public: + // enum definissant l'orientation de la borne + enum Orientation {Nord, Sud, Est, Ouest}; + + // permet de caster un QGraphicsItem en Borne avec qgraphicsitem_cast + enum { Type = UserType + 1002 }; + virtual int type() const { return Type; } + + // constructeurs + Borne(); + Borne(QPointF, Borne::Orientation, Element * = 0, Schema * = 0); + Borne(qreal, qreal, Borne::Orientation, Element * = 0, Schema * = 0); + + // destructeur + ~Borne(); + + // implementation des methodes virtuelles pures de QGraphicsItem + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + QRectF boundingRect() const; + + // methodes de manipulation des conducteurs lies a cette borne + bool addConducteur(Conducteur *); + void removeConducteur(Conducteur *); + inline int nbConducteurs() { return(liste_conducteurs.size()); } + + // methodes de lecture + QList conducteurs() const; + Borne::Orientation orientation() const; + inline QPointF amarrageConducteur() const { return(mapToScene(amarrage_conducteur)); } + void updateConducteur(); + + // methodes relatives a l'import/export au format XML + static bool valideXml(QDomElement &); + bool fromXml (QDomElement &); + QDomElement toXml (QDomDocument &); + + // methodes de gestion des evenements + void hoverEnterEvent (QGraphicsSceneHoverEvent *); + void hoverMoveEvent (QGraphicsSceneHoverEvent *); + void hoverLeaveEvent (QGraphicsSceneHoverEvent *); + void mousePressEvent (QGraphicsSceneMouseEvent *); + void mouseMoveEvent (QGraphicsSceneMouseEvent *); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *); + + private: + // pointeur vers la QGraphicsScene de type Schema (evite quelques casts en interne) + Schema *schema_scene; + // coordonnees des points d'amarrage + QPointF amarrage_conducteur; + QPointF amarrage_elmt; + // orientation de la borne + Borne::Orientation sens; + // liste des conducteurs lies a cette borne + QList liste_conducteurs; + // pointeur vers un rectangle correspondant au bounding rect ; permet de ne calculer le bounding rect qu'une seule fois ; le pointeur c'est parce que le compilo exige une methode const + QRectF *br; + Borne *borne_precedente; + bool hovered; + // methode initialisant les differents membres de la borne + void initialise(QPointF, Borne::Orientation); + // differentes couleurs utilisables pour l'effet "hover" + QColor couleur_hovered; + QColor couleur_neutre; + QColor couleur_autorise; + QColor couleur_prudence; + QColor couleur_interdit; + }; +#endif diff --git a/conducteur.cpp b/conducteur.cpp new file mode 100644 index 000000000..5ffac6300 --- /dev/null +++ b/conducteur.cpp @@ -0,0 +1,198 @@ +#include +#include "conducteur.h" +#include "element.h" + +/** + Constructeur + @param p1 Premiere Borne auquel le conducteur est lie + @param p2 Seconde Borne auquel le conducteur est lie + @param parent Element parent du conducteur (0 par defaut) + @param scene QGraphicsScene auquelle appartient le conducteur +*/ +Conducteur::Conducteur(Borne *p1, Borne* p2, Element *parent, QGraphicsScene *scene) : QGraphicsPathItem(parent, scene) { + // bornes que le conducteur relie + borne1 = p1; + borne2 = p2; + // ajout du conducteur a la liste de conducteurs de chacune des deux bornes + bool ajout_p1 = borne1 -> addConducteur(this); + bool ajout_p2 = borne2 -> addConducteur(this); + // en cas d'echec de l'ajout (conducteur deja existant notamment) + if (!ajout_p1 || !ajout_p2) return; + destroyed = false; + // le conducteur est represente par un trait fin + QPen t; + t.setWidthF(1.0); + setPen(t); + // calcul du rendu du conducteur + calculeConducteur(); +} + +/** + Met a jour la representation graphique du conducteur. + @param rect Rectangle a mettre a jour +*/ +void Conducteur::update(const QRectF &rect = QRectF()) { + calculeConducteur(); + QGraphicsPathItem::update(rect); +} + +/** + Met a jour la representation graphique du conducteur. + @param x abscisse du rectangle a mettre a jour + @param y ordonnee du rectangle a mettre a jour + @param width longueur du rectangle a mettre a jour + @param height hauteur du rectangle a mettre a jour +*/ +void Conducteur::update(qreal x, qreal y, qreal width, qreal height) { + calculeConducteur(); + QGraphicsPathItem::update(x, y, width, height); +} + +/** + Destructeur du Conducteur. Avant d'etre detruit, le conducteur se decroche des bornes + auxquelles il est lie. +*/ +/*Conducteur::~Conducteur() { + +}*/ + +/** + Met a jour le QPainterPath constituant le conducteur pour obtenir + un conducteur uniquement compose de droites reliant les deux bornes. +*/ +void Conducteur::calculeConducteur() { + QPainterPath t; + + QPointF p1 = borne1 -> amarrageConducteur(); + QPointF p2 = borne2 -> amarrageConducteur(); + + QPointF depart, arrivee; + Borne::Orientation ori_depart, ori_arrivee; + // distingue le depart de l'arrivee : le trajet se fait toujours de gauche a droite + if (p1.x() <= p2.x()) { + depart = mapFromScene(p1); + arrivee = mapFromScene(p2); + ori_depart = borne1 -> orientation(); + ori_arrivee = borne2 -> orientation(); + } else { + depart = mapFromScene(p2); + arrivee = mapFromScene(p1); + ori_depart = borne2 -> orientation(); + ori_arrivee = borne1 -> orientation(); + } + + // debut du trajet + t.moveTo(depart); + if (depart.y() < arrivee.y()) { + // trajet descendant + if ((ori_depart == Borne::Nord && (ori_arrivee == Borne::Sud || ori_arrivee == Borne::Ouest)) || (ori_depart == Borne::Est && ori_arrivee == Borne::Ouest)) { + // cas « 3 » + qreal ligne_inter_x = (depart.x() + arrivee.x()) / 2.0; + t.lineTo(ligne_inter_x, depart.y()); + t.lineTo(ligne_inter_x, arrivee.y()); + } else if ((ori_depart == Borne::Sud && (ori_arrivee == Borne::Nord || ori_arrivee == Borne::Est)) || (ori_depart == Borne::Ouest && ori_arrivee == Borne::Est)) { + // cas « 4 » + qreal ligne_inter_y = (depart.y() + arrivee.y()) / 2.0; + t.lineTo(depart.x(), ligne_inter_y); + t.lineTo(arrivee.x(), ligne_inter_y); + } else if ((ori_depart == Borne::Nord || ori_depart == Borne::Est) && (ori_arrivee == Borne::Nord || ori_arrivee == Borne::Est)) { + t.lineTo(arrivee.x(), depart.y()); // cas « 2 » + } else t.lineTo(depart.x(), arrivee.y()); // cas « 1 » + } else { + // trajet montant + if ((ori_depart == Borne::Ouest && (ori_arrivee == Borne::Est || ori_arrivee == Borne::Sud)) || (ori_depart == Borne::Nord && ori_arrivee == Borne::Sud)) { + // cas « 3 » + qreal ligne_inter_y = (depart.y() + arrivee.y()) / 2.0; + t.lineTo(depart.x(), ligne_inter_y); + t.lineTo(arrivee.x(), ligne_inter_y); + } else if ((ori_depart == Borne::Est && (ori_arrivee == Borne::Ouest || ori_arrivee == Borne::Nord)) || (ori_depart == Borne::Sud && ori_arrivee == Borne::Nord)) { + // cas « 4 » + qreal ligne_inter_x = (depart.x() + arrivee.x()) / 2.0; + t.lineTo(ligne_inter_x, depart.y()); + t.lineTo(ligne_inter_x, arrivee.y()); + } else if ((ori_depart == Borne::Ouest || ori_depart == Borne::Nord) && (ori_arrivee == Borne::Ouest || ori_arrivee == Borne::Nord)) { + t.lineTo(depart.x(), arrivee.y()); // cas « 2 » + } else t.lineTo(arrivee.x(), depart.y()); // cas « 1 » + } + // fin du trajet + t.lineTo(arrivee); + setPath(t); +} + +/** + Dessine le conducteur sans antialiasing. + @param qp Le QPainter a utiliser pour dessiner le conducteur + @param qsogi Les options de style pour le conducteur + @param qw Le QWidget sur lequel on dessine +*/ +void Conducteur::paint(QPainter *qp, const QStyleOptionGraphicsItem *qsogi, QWidget *qw) { + qp -> save(); + qp -> setRenderHint(QPainter::Antialiasing, false); + qp -> setRenderHint(QPainter::TextAntialiasing, false); + qp -> setRenderHint(QPainter::SmoothPixmapTransform, false); + QGraphicsPathItem::paint(qp, qsogi, qw); + qp -> restore(); +} + +/** + Indique si deux orientations de Borne sont sur le meme axe (Vertical / Horizontal). + @param a La premiere orientation de Borne + @param b La seconde orientation de Borne + @return Un booleen a true si les deux orientations de bornes sont sur le meme axe +*/ +bool Conducteur::surLeMemeAxe(Borne::Orientation a, Borne::Orientation b) { + if ((a == Borne::Nord || a == Borne::Sud) && (b == Borne::Nord || b == Borne::Sud)) return(true); + else if ((a == Borne::Est || a == Borne::Ouest) && (b == Borne::Est || b == Borne::Ouest)) return(true); + else return(false); +} + +/** + Indique si une orientation de borne est horizontale (Est / Ouest). + @param a L'orientation de borne + @return True si l'orientation de borne est horizontale, false sinon +*/ +bool Conducteur::estHorizontale(Borne::Orientation a) { + return(a == Borne::Est || a == Borne::Ouest); +} + +/** + Indique si une orientation de borne est verticale (Nord / Sud). + @param a L'orientation de borne + @return True si l'orientation de borne est verticale, false sinon +*/ +bool Conducteur::estVerticale(Borne::Orientation a) { + return(a == Borne::Nord || a == Borne::Sud); +} + +/** + Methode de preparation a la destruction du conducteur ; le conducteur se detache de ses deux bornes +*/ +void Conducteur::destroy() { + destroyed = true; + borne1 -> removeConducteur(this); + borne2 -> removeConducteur(this); +} + +/** + Methode de validation d'element XML + @param e Un element XML sense represente un Conducteur + @return true si l'element XML represente bien un Conducteur ; false sinon +*/ +bool Conducteur::valideXml(QDomElement &e){ + // verifie le nom du tag + if (e.tagName() != "conducteur") return(false); + + // verifie la presence des attributs minimaux + if (!e.hasAttribute("borne1")) return(false); + if (!e.hasAttribute("borne2")) return(false); + + bool conv_ok; + // parse l'abscisse + e.attribute("borne1").toInt(&conv_ok); + if (!conv_ok) return(false); + + // parse l'ordonnee + e.attribute("borne2").toInt(&conv_ok); + if (!conv_ok) return(false); + return(true); +} diff --git a/conducteur.h b/conducteur.h new file mode 100644 index 000000000..8be86593e --- /dev/null +++ b/conducteur.h @@ -0,0 +1,36 @@ +#ifndef CONDUCTEUR_H + #define CONDUCTEUR_H + #include + #include "borne.h" + class Element; + /** + Cette classe represente un conducteur. Un conducteur relie deux bornes d'element. + */ + class Conducteur : public QGraphicsPathItem { + public: + enum { Type = UserType + 1001 }; + virtual int type() const { return Type; } + Conducteur(Borne *, Borne *, Element * = 0, QGraphicsScene * = 0); + //virtual ~Conducteur(); + + void destroy(); + bool isDestroyed() const { return(destroyed); } + void update(const QRectF & rect); + void update(qreal x, qreal y, qreal width, qreal height); + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + static bool valideXml(QDomElement &); + + ///Premiere borne a laquelle le fil est rattache + Borne *borne1; + ///Deuxieme borne a laquelle le fil est rattache + Borne *borne2; + private: + /// booleen indiquant si le fil est encore valide + bool destroyed; + + void calculeConducteur(); + bool surLeMemeAxe(Borne::Orientation, Borne::Orientation); + bool estHorizontale(Borne::Orientation a); + bool estVerticale(Borne::Orientation a); + }; +#endif diff --git a/contacteur.cpp b/contacteur.cpp new file mode 100644 index 000000000..fca3861bd --- /dev/null +++ b/contacteur.cpp @@ -0,0 +1,56 @@ +#include "contacteur.h" + +/** + Constructeur + @param parent Le QObject parent de l'element. + @param scene La scene sur laquelle l'element est affiche +*/ +Contacteur::Contacteur(QGraphicsItem *parent, Schema *scene) : ElementFixe(parent, scene) { + // taille et hotspot + setSize(15, 70); + setHotspot(QPoint(10, 5)); + + // ajout de deux bornes a l'element + new Borne(0, 0, Borne::Nord, this, scene); + new Borne(0, 60, Borne::Sud, this, scene); +} + +/** + @return Le nombre actuel de bornes de l'element +*/ +int Contacteur::nbBornes() const { + return(2); +} + +/** + Fonction qui effectue le rendu graphique du contacteur + @param p Le QPainter a utiliser pour dessiner l'element + @param o Les options de dessin +*/ +void Contacteur::paint(QPainter *p, const QStyleOptionGraphicsItem *) { + // traits de couleur noire + QPen t; + t.setColor(Qt::black); + t.setWidthF(1.0); + t.setJoinStyle(Qt::MiterJoin); + p -> setPen(t); + + // une ligne eventuellement antialiasee + p -> drawLine(-5, 19, 0, 40); + + // deux lignes JAMAIS antialiasees (annulation des renderhints) + p -> save(); + p -> setRenderHint(QPainter::Antialiasing, false); + p -> setRenderHint(QPainter::TextAntialiasing, false); + p -> setRenderHint(QPainter::SmoothPixmapTransform, false); + p -> drawLine(0, 0, 0, 20); + p -> drawLine(0, 40, 0, 60); + p -> restore(); +} + +/** + @return l'ID du type "Contacteur" +*/ +QString Contacteur::typeId() { + return(QString("0")); +} diff --git a/contacteur.h b/contacteur.h new file mode 100644 index 000000000..4fdcc5999 --- /dev/null +++ b/contacteur.h @@ -0,0 +1,16 @@ +#ifndef CONTACTEUR_H + #define CONTACTEUR_H + #include "elementfixe.h" + /** + Cette classe herite de la classe Element Fixe pour definir un + contacteur + */ + class Contacteur : public ElementFixe { + public: + Contacteur(QGraphicsItem * = 0, Schema * = 0); + virtual int nbBornes() const; + void paint(QPainter *, const QStyleOptionGraphicsItem *); + QString typeId(); + QString nom() { return("Contacteur"); } + }; +#endif diff --git a/del.cpp b/del.cpp new file mode 100644 index 000000000..e81eca866 --- /dev/null +++ b/del.cpp @@ -0,0 +1,63 @@ +#include "del.h" +#include +#include + +/** + Constructeur + @param parent Le QObject parent de l'element. + @param scene La scene sur laquelle l'element est affiche + */ +DEL::DEL(QGraphicsItem *parent, Schema *scene) : ElementFixe(parent, scene) { + // taille et hotspot + setSize(30, 70); + setHotspot(QPoint(15, 5)); + + // ajout de deux bornes a l'element + new Borne(0, 0, Borne::Nord, this, scene); + new Borne(0, 60, Borne::Sud, this, scene); + + peut_relier_ses_propres_bornes = true; +} + +/** + @return Le nombre actuel de bornes de l'element +*/ +int DEL::nbBornes() const { + return(2); +} + +/** + Fonction qui effectue le rendu graphique de la DEL + @param p Le QPainter a utiliser pour dessiner l'element + @param o Les options de dessin +*/ +void DEL::paint(QPainter *p, const QStyleOptionGraphicsItem *) { + // traits de couleur noire + QPen t; + t.setColor(Qt::black); + t.setWidthF(1.0); + p -> setPen(t); + + // un cercle a fond blanc + p -> setBrush(QBrush(Qt::white, Qt::SolidPattern)); + p -> drawEllipse(-10, 20, 20, 20); + p -> setBrush(Qt::NoBrush); + // deux lignes eventuellement antialiasees + p -> drawLine(-7, 23, 7, 37); + p -> drawLine( 7, 23, -7, 37); + // deux lignes JAMAIS antialiasees + p -> save(); + p -> setRenderHint(QPainter::Antialiasing, false); + p -> setRenderHint(QPainter::TextAntialiasing, false); + p -> setRenderHint(QPainter::SmoothPixmapTransform, false); + p -> drawLine(0, 0, 0, 20); + p -> drawLine(0, 40, 0, 60); + p -> restore(); +} + +/** + @return l'ID du type "DEL" +*/ +QString DEL::typeId() { + return(QString("1")); +} diff --git a/del.h b/del.h new file mode 100644 index 000000000..e6e6dd91d --- /dev/null +++ b/del.h @@ -0,0 +1,16 @@ +#ifndef DEL_H + #define DEL_H + #include "elementfixe.h" + /** + Cette classe herite de la classe Element Fixe pour definir une + Diode ElectroLuminescente + */ + class DEL : public ElementFixe { + public: + DEL(QGraphicsItem * = 0, Schema * = 0); + virtual int nbBornes() const; + void paint(QPainter *, const QStyleOptionGraphicsItem *); + QString typeId(); + QString nom() { return("DEL"); } + }; +#endif diff --git a/element.cpp b/element.cpp new file mode 100644 index 000000000..9e3fe17d2 --- /dev/null +++ b/element.cpp @@ -0,0 +1,276 @@ +#include "element.h" +#include "schema.h" +#include + +/*** Methodes publiques ***/ + +/** + Constructeur pour un element sans scene ni parent +*/ +Element::Element(QGraphicsItem *parent, Schema *scene) : QGraphicsItem(parent, scene) { + sens = true; + peut_relier_ses_propres_bornes = false; +} + +/** + Methode principale de dessin de l'element + @param painter Le QPainter utilise pour dessiner l'elment + @param options Les options de style a prendre en compte + @param widget Le widget sur lequel on dessine +*/ +void Element::paint(QPainter *painter, const QStyleOptionGraphicsItem *options, QWidget *) { + // Dessin de l'element lui-meme + paint(painter, options); + + // Dessin du cadre de selection si necessaire + if (isSelected()) drawSelection(painter, options); +} + +/** + @return Le rectangle delimitant le contour de l'element +*/ +QRectF Element::boundingRect() const { + return(QRectF(QPointF(-hotspot_coord.x(), -hotspot_coord.y()), dimensions)); +} + +/** + Definit la taille de l'element sur le schema. Les tailles doivent etre + des multiples de 10 ; si ce n'est pas le cas, les dimensions indiquees + seront arrrondies aux dizaines superieures. + @param wid Largeur de l'element + @param hei Hauteur de l'element + @return La taille finale de l'element +*/ +QSize Element::setSize(int wid, int hei) { + prepareGeometryChange(); + // chaque dimension indiquee est arrondie a la dizaine superieure + while (wid % 10) ++ wid; + while (hei % 10) ++ hei; + // les dimensions finales sont conservees et retournees + return(dimensions = QSize(wid, hei)); +} + +/** + Definit le hotspot de l'element par rapport au coin superieur gauche de son rectangle delimitant. + Necessite que la taille ait deja ete definie + @param hsx Abscisse du hotspot + @param hsy Ordonnee du hotspot +*/ +QPoint Element::setHotspot(QPoint hs) { + // la taille doit avoir ete definie + prepareGeometryChange(); + if (dimensions.isNull()) hotspot_coord = QPoint(0, 0); + else { + // les coordonnees indiquees ne doivent pas depasser les dimensions de l'element + int hsx = hs.x() > dimensions.width() ? dimensions.width() : hs.x(); + int hsy = hs.y() > dimensions.height() ? dimensions.height() : hs.y(); + hotspot_coord = QPoint(hsx, hsy); + } + return(hotspot_coord); +} + +/** + @return Le hotspot courant de l'element +*/ +QPoint Element::hotspot() const { + return(hotspot_coord); +} + +/** + Selectionne l'element +*/ +void Element::select() { + setSelected(true); +} + +/** + Deselectionne l'element +*/ +void Element::deselect() { + setSelected(false); +} + +/** + @return La pixmap de l'element +*/ +QPixmap Element::pixmap() { + if (apercu.isNull()) updatePixmap(); // on genere la pixmap si ce n'est deja fait + return(apercu); +} + +/** + @todo distinguer les bornes avec un cast dynamique +*/ +QVariant Element::itemChange(GraphicsItemChange change, const QVariant &value) { + if (change == QGraphicsItem::ItemPositionChange || change == QGraphicsItem::ItemSelectedChange) { + foreach(QGraphicsItem *qgi, children()) { + if (Borne *p = qgraphicsitem_cast(qgi)) p -> updateConducteur(); + } + } + return(QGraphicsItem::itemChange(change, value)); +} + +/** + @return L'orientation en cours de l'element : true pour une orientation verticale, false pour une orientation horizontale +*/ +bool Element::orientation() const { + return(sens); +} + +/** + Inverse l'orientation de l'element + @return La nouvelle orientation : true pour une orientation verticale, false pour une orientation horizontale +*/ +bool Element::invertOrientation() { + // inversion du sens + sens = !sens; + // on cache temporairement l'element pour eviter un bug graphique + hide(); + // rotation en consequence et rafraichissement de l'element graphique + rotate(sens ? 90.0 : -90.0); + // on raffiche l'element, on le reselectionne et on le rafraichit + show(); + select(); + update(); + return(sens); +} + +/*** Methodes protegees ***/ + +/** + Dessine un petit repere (axes x et y) relatif a l'element + @param painter Le QPainter a utiliser pour dessiner les axes + @param options Les options de style a prendre en compte +*/ +void Element::drawAxes(QPainter *painter, const QStyleOptionGraphicsItem *) { + painter -> setPen(Qt::blue); + painter -> drawLine(0, 0, 10, 0); + painter -> drawLine(7,-3, 10, 0); + painter -> drawLine(7, 3, 10, 0); + painter -> setPen(Qt::red); + painter -> drawLine(0, 0, 0, 10); + painter -> drawLine(0, 10,-3, 7); + painter -> drawLine(0, 10, 3, 7); +} + +/*** Methodes privees ***/ + +/** + Dessine le cadre de selection de l'element de maniere systematiquement non antialiasee. + @param qp Le QPainter a utiliser pour dessiner les bornes. + @param options Les options de style a prendre en compte + */ +void Element::drawSelection(QPainter *painter, const QStyleOptionGraphicsItem *) { + painter -> save(); + // Annulation des renderhints + painter -> setRenderHint(QPainter::Antialiasing, false); + painter -> setRenderHint(QPainter::TextAntialiasing, false); + painter -> setRenderHint(QPainter::SmoothPixmapTransform, false); + // Dessin du cadre de selection en gris + QPen t; + t.setColor(Qt::gray); + t.setStyle(Qt::DashDotLine); + painter -> setPen(t); + // Le dessin se fait a partir du rectangle delimitant + painter -> drawRoundRect(boundingRect(), 10, 10); + painter -> restore(); +} + +/** + Fonction initialisant et dessinant la pixmap de l'element. +*/ +void Element::updatePixmap() { + // Pixmap transparente faisant la taille de base de l'element + apercu = QPixmap(dimensions); + apercu.fill(QColor(255, 255, 255, 0)); + // QPainter sur la pixmap, avec antialiasing + QPainter p(&apercu); + p.setRenderHint(QPainter::Antialiasing, true); + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + // Translation de l'origine du repere de la pixmap + p.translate(hotspot_coord); + // L'element se dessine sur la pixmap + paint(&p, 0); +} + +/** + Change la position de l'element en veillant a ce que l'element + reste sur la grille du Schema auquel il appartient. + @param p Nouvelles coordonnees de l'element +*/ +void Element::setPos(const QPointF &p) { + if (p == pos()) return; + // pas la peine de positionner sur la grille si l'element n'est pas sur un Schema + if (scene()) { + // arrondit l'abscisse a 10 px pres + int p_x = qRound(p.x() / 10.0) * 10; + // arrondit l'ordonnee a 10 px pres + int p_y = qRound(p.y() / 10.0) * 10; + QGraphicsItem::setPos(p_x, p_y); + } else QGraphicsItem::setPos(p); + // actualise les bornes / conducteurs + foreach(QGraphicsItem *qgi, children()) { + if (Borne *p = qgraphicsitem_cast(qgi)) p -> updateConducteur(); + } +} + +/** + Change la position de l'element en veillant a ce que l'element + reste sur la grille du Schema auquel il appartient. + @param x Nouvelle abscisse de l'element + @param y Nouvelle ordonnee de l'element +*/ +void Element::setPos(qreal x, qreal y) { + setPos(QPointF(x, y)); +} + +/** + Gere les mouvements de souris lies a l'element, notamment +*/ +void Element::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { + /*&& (flags() & ItemIsMovable)*/ // on le sait qu'il est movable + if (e -> buttons() & Qt::LeftButton) { + QPointF oldPos = pos(); + setPos(mapToParent(e->pos()) - matrix().map(e->buttonDownPos(Qt::LeftButton))); + QPointF diff = pos() - oldPos; + + // Recupere la liste des elements selectionnes + QList selectedItems; + if (scene()) { + selectedItems = scene() -> selectedItems(); + } else if (QGraphicsItem *parent = parentItem()) { + while (parent && parent->isSelected()) selectedItems << parent; + } + + // Deplace tous les elements selectionnes + foreach (QGraphicsItem *item, selectedItems) { + if (!item->parentItem() || !item->parentItem()->isSelected()) + if (item != this) item->setPos(item->pos() + diff); + } + } else e -> ignore(); +} + +/** + Permet de savoir si un element XML (QDomElement) represente bien un element + @param e Le QDomElement a valide + @return true si l'element XML est un Element, false sinon +*/ +bool Element::valideXml(QDomElement &e) { + // verifie le nom du tag + if (e.tagName() != "element") return(false); + + // verifie la presence des attributs minimaux + if (!e.hasAttribute("type")) return(false); + if (!e.hasAttribute("x")) return(false); + if (!e.hasAttribute("y")) return(false); + + bool conv_ok; + // parse l'abscisse + e.attribute("x").toDouble(&conv_ok); + if (!conv_ok) return(false); + + // parse l'ordonnee + e.attribute("y").toDouble(&conv_ok); + if (!conv_ok) return(false); + return(true); +} diff --git a/element.h b/element.h new file mode 100644 index 000000000..0227ef0ca --- /dev/null +++ b/element.h @@ -0,0 +1,50 @@ +#ifndef ELEMENT_H + #define ELEMENT_H + #include + #include "borne.h" + class Schema; + class Element : public QGraphicsItem { + public: + enum { Type = UserType + 1000 }; + virtual int type() const { return Type; } + Element(QGraphicsItem * = 0, Schema * = 0); + + virtual int nbBornes() const = 0; + virtual int nbBornesMin() const = 0; + virtual int nbBornesMax() const = 0; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *) = 0; + virtual QString typeId() = 0; + + virtual QString nom() = 0; + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + QRectF boundingRect() const; + QSize setSize(int, int); + QPoint setHotspot(QPoint); + QPoint hotspot() const; + void select(); + void deselect(); + QPixmap pixmap(); + QVariant itemChange(GraphicsItemChange, const QVariant &); + bool orientation() const; + bool invertOrientation(); + void setPos(const QPointF &); + void setPos(qreal, qreal); + bool connexionsInternesAcceptees() { return(peut_relier_ses_propres_bornes); } + static bool valideXml(QDomElement &); + virtual bool fromXml(QDomElement &, QHash&) = 0; + + protected: + void drawAxes(QPainter *, const QStyleOptionGraphicsItem *); + void mouseMoveEvent(QGraphicsSceneMouseEvent *); + bool peut_relier_ses_propres_bornes; + + private: + void drawSelection(QPainter *, const QStyleOptionGraphicsItem *); + void updatePixmap(); + bool sens; + QSize dimensions; + QPoint hotspot_coord; + QPixmap apercu; + QMenu menu; + }; +#endif diff --git a/elementfixe.cpp b/elementfixe.cpp new file mode 100644 index 000000000..c6f252a26 --- /dev/null +++ b/elementfixe.cpp @@ -0,0 +1,82 @@ +#include "elementfixe.h" +/** + Constructeur +*/ +ElementFixe::ElementFixe(QGraphicsItem *parent, Schema *scene) : Element(parent, scene) { +} + +/** + @return Le nombre minimal de bornes que l'element peut avoir +*/ +int ElementFixe::nbBornesMin() const { + return(nbBornes()); +} + +/** + @return Le nombre maximal de bornes que l'element peut avoir +*/ +int ElementFixe::nbBornesMax() const { + return(nbBornes()); +} + +/** + Methode d'import XML. Cette methode est appelee lors de l'import de contenu XML (coller, import, ouverture de fichier...) afin que l'element puisse gerer lui-meme l'importation de ses bornes. Ici, comme cette classe est caracterisee par un nombre fixe de bornes, l'implementation exige de retrouver exactement ses bornes dans le fichier XML. + @param e L'element XML a analyser. + @param table_id_adr Reference vers la table de correspondance entre les IDs du fichier XML et les adresses en memoire. Si l'import reussit, il faut y ajouter les bons couples (id, adresse). + @return true si l'import a reussi, false sinon + +*/ +bool ElementFixe::fromXml(QDomElement &e, QHash &table_id_adr) { + /* + les bornes vont maintenant etre recensees pour associer leurs id à leur adresse reelle + ce recensement servira lors de la mise en place des fils + */ + + QList liste_bornes; + // parcours des enfants de l'element + for (QDomNode enfant = e.firstChild() ; !enfant.isNull() ; enfant = enfant.nextSibling()) { + // on s'interesse a l'element XML "bornes" + QDomElement bornes = enfant.toElement(); + if (bornes.isNull() || bornes.tagName() != "bornes") continue; + // parcours des enfants de l'element XML "bornes" + for (QDomNode node_borne = bornes.firstChild() ; !node_borne.isNull() ; node_borne = node_borne.nextSibling()) { + // on s'interesse a l'element XML "borne" + QDomElement borne = node_borne.toElement(); + if (!borne.isNull() && Borne::valideXml(borne)) liste_bornes.append(borne); + } + } + + QHash priv_id_adr; + int bornes_non_trouvees = 0; + foreach(QGraphicsItem *qgi, children()) { + if (Borne *p = qgraphicsitem_cast(qgi)) { + bool borne_trouvee = false; + foreach(QDomElement qde, liste_bornes) { + if (p -> fromXml(qde)) { + priv_id_adr.insert(qde.attribute("id").toInt(), p); + borne_trouvee = true; + break; + } + } + if (!borne_trouvee) ++ bornes_non_trouvees; + } + } + + if (bornes_non_trouvees > 0) { + return(false); + } else { + // verifie que les associations id / adr n'entrent pas en conflit avec table_id_adr + foreach(int id_trouve, priv_id_adr.keys()) { + if (table_id_adr.contains(id_trouve)) { + // cet element possede un id qui est deja reference (= conflit) + return(false); + } + } + // copie des associations id / adr + foreach(int id_trouve, priv_id_adr.keys()) { + table_id_adr.insert(id_trouve, priv_id_adr.value(id_trouve)); + } + } + + return(true); +} diff --git a/elementfixe.h b/elementfixe.h new file mode 100644 index 000000000..89f117f79 --- /dev/null +++ b/elementfixe.h @@ -0,0 +1,15 @@ +#ifndef ELEMENTFIXE_H + #define ELEMENTFIXE_H + #include "element.h" + class ElementFixe : public Element { + public: + ElementFixe(QGraphicsItem * = 0, Schema * = 0); + int nbBornesMin() const; + int nbBornesMax() const; + virtual bool fromXml(QDomElement &, QHash&); + virtual int nbBornes() const = 0; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *) = 0; + virtual QString typeId() = 0; + virtual QString nom() = 0; + }; +#endif diff --git a/elementperso.cpp b/elementperso.cpp new file mode 100644 index 000000000..da87a5997 --- /dev/null +++ b/elementperso.cpp @@ -0,0 +1,196 @@ +#include "elementperso.h" + +ElementPerso::ElementPerso(QString &nom_fichier, QGraphicsItem *qgi, Schema *s, int *etat) : ElementFixe(qgi, s) { + nomfichier = nom_fichier; + nb_bornes = 0; + // pessimisme inside : par defaut, ca foire + elmt_etat = -1; + + // le fichier doit exister + QString chemin_elements = "elements/"; + nomfichier = chemin_elements + nom_fichier; + if (!QFileInfo(nomfichier).exists()) { + if (etat != NULL) *etat = 1; + elmt_etat = 1; + return; + } + + // le fichier doit etre lisible + QFile fichier(nomfichier); + if (!fichier.open(QIODevice::ReadOnly)) { + if (etat != NULL) *etat = 2; + elmt_etat = 2; + return; + } + + // le fichier doit etre un document XML + QDomDocument document_xml; + if (!document_xml.setContent(&fichier)) { + if (etat != NULL) *etat = 3; + elmt_etat = 3; + return; + } + + // la racine est supposee etre une definition d'element + QDomElement racine = document_xml.documentElement(); + if (racine.tagName() != "definition" || racine.attribute("type") != "element") { + if (etat != NULL) *etat = 4; + elmt_etat = 4; + return; + } + + // ces attributs doivent etre presents et valides + int w, h, hot_x, hot_y; + if ( + racine.attribute("nom") == QString("") ||\ + !attributeIsAnInteger(racine, QString("width"), &w) ||\ + !attributeIsAnInteger(racine, QString("height"), &h) ||\ + !attributeIsAnInteger(racine, QString("hotspot_x"), &hot_x) ||\ + !attributeIsAnInteger(racine, QString("hotspot_y"), &hot_y) + ) { + if (etat != NULL) *etat = 5; + elmt_etat = 5; + return; + } + + // on peut d'ores et deja specifier le nom, la taille et le hotspot + priv_nom = racine.attribute("nom"); + setSize(w, h); + setHotspot(QPoint(hot_x, hot_y)); + + // la definition est supposee avoir des enfants + if (racine.firstChild().isNull()) { + if (etat != NULL) *etat = 6; + elmt_etat = 6; + return; + } + + // parcours des enfants de la definition + int nb_elements_parses = 0; + QPainter qp; + qp.begin(&dessin); + QPen t; + t.setColor(Qt::black); + t.setWidthF(1.0); + t.setJoinStyle(Qt::MiterJoin); + qp.setPen(t); + for (QDomNode node = racine.firstChild() ; !node.isNull() ; node = node.nextSibling()) { + QDomElement elmts = node.toElement(); + if(elmts.isNull()) continue; + if (parseElement(elmts, qp, s)) ++ nb_elements_parses; + else { + if (etat != NULL) *etat = 7; + elmt_etat = 7; + return; + } + } + qp.end(); + + // il doit y avoir au moins un element charge + if (!nb_elements_parses) { + if (etat != NULL) *etat = 8; + elmt_etat = 8; + return; + } + + // fermeture du fichier + fichier.close(); + + if (etat != NULL) *etat = 0; + elmt_etat = 0; +} + +int ElementPerso::nbBornes() const { + return(nb_bornes); +} + +void ElementPerso::paint(QPainter *qp, const QStyleOptionGraphicsItem *) { + dessin.play(qp); +} + +bool ElementPerso::parseElement(QDomElement &e, QPainter &qp, Schema *s) { + if (e.tagName() == "borne") return(parseBorne(e, s)); + else if (e.tagName() == "ligne") return(parseLigne(e, qp)); + else if (e.tagName() == "cercle") return(parseCercle(e, qp)); + else if (e.tagName() == "polygone") return(parsePolygone(e, qp)); + else return(true); // on n'est pas chiant, on ignore l'element inconnu +} + +bool ElementPerso::parseLigne(QDomElement &e, QPainter &qp) { + // verifie la presence et la validite des attributs obligatoires + int x1, y1, x2, y2; + if (!attributeIsAnInteger(e, QString("x1"), &x1)) return(false); + if (!attributeIsAnInteger(e, QString("y1"), &y1)) return(false); + if (!attributeIsAnInteger(e, QString("x2"), &x2)) return(false); + if (!attributeIsAnInteger(e, QString("y2"), &y2)) return(false); + /// @todo : gerer l'antialiasing (mieux que ca !) et le type de trait + setQPainterAntiAliasing(&qp, e.attribute("antialias") == "true"); + qp.drawLine(x1, y1, x2, y2); + return(true); +} + +bool ElementPerso::parseCercle(QDomElement &e, QPainter &qp) { + // verifie la presence des attributs obligatoires + int cercle_x, cercle_y, cercle_r; + if (!attributeIsAnInteger(e, QString("x"), &cercle_x)) return(false); + if (!attributeIsAnInteger(e, QString("y"), &cercle_y)) return(false); + if (!attributeIsAnInteger(e, QString("rayon"), &cercle_r)) return(false); + /// @todo : gerer l'antialiasing (mieux que ca !) et le type de trait + setQPainterAntiAliasing(&qp, e.attribute("antialias") == "true"); + qp.drawEllipse(cercle_x, cercle_y, cercle_r, cercle_r); + return(true); +} + +bool ElementPerso::parsePolygone(QDomElement &e, QPainter &qp) { + int i = 1; + while(true) { + if (attributeIsAnInteger(e, QString("x%1").arg(i)) && attributeIsAnInteger(e, QString("y%1").arg(i))) ++ i; + else break; + } + if (i < 3) return(false); + QPointF points[i-1]; + for (int j = 1 ; j < i ; ++ j) { + points[j-1] = QPointF( + e.attribute(QString("x%1").arg(j)).toDouble(), + e.attribute(QString("y%1").arg(j)).toDouble() + ); + } + setQPainterAntiAliasing(&qp, e.attribute("antialias") == "true"); + qp.drawPolygon(points, i-1); + return(true); +} + +bool ElementPerso::parseBorne(QDomElement &e, Schema *s) { + // verifie la presence et la validite des attributs obligatoires + int bornex, borney; + Borne::Orientation borneo; + if (!attributeIsAnInteger(e, QString("x"), &bornex)) return(false); + if (!attributeIsAnInteger(e, QString("y"), &borney)) return(false); + if (!e.hasAttribute("orientation")) return(false); + if (e.attribute("orientation") == "n") borneo = Borne::Nord; + else if (e.attribute("orientation") == "s") borneo = Borne::Sud; + else if (e.attribute("orientation") == "e") borneo = Borne::Est; + else if (e.attribute("orientation") == "o") borneo = Borne::Ouest; + else return(false); + new Borne(bornex, borney, borneo, this, s); + ++ nb_bornes; + return(true); +} + +void ElementPerso::setQPainterAntiAliasing(QPainter *qp, bool aa) { + qp -> setRenderHint(QPainter::Antialiasing, aa); + qp -> setRenderHint(QPainter::TextAntialiasing, aa); + qp -> setRenderHint(QPainter::SmoothPixmapTransform, aa); +} + +int ElementPerso::attributeIsAnInteger(QDomElement &e, QString nom_attribut, int *entier) { + // verifie la presence de l'attribut + if (!e.hasAttribute(nom_attribut)) return(false); + // verifie la validite de l'attribut + bool ok; + int tmp = e.attribute(nom_attribut).toInt(&ok); + if (!ok) return(false); + if (entier != NULL) *entier = tmp; + return(true); +} + diff --git a/elementperso.h b/elementperso.h new file mode 100644 index 000000000..1506351a1 --- /dev/null +++ b/elementperso.h @@ -0,0 +1,30 @@ +#ifndef ELEMENTPERSO_H + #define ELEMENTPERSO_H + #include "elementfixe.h" + #include + class ElementPerso : public ElementFixe { + public: + ElementPerso(QString &, QGraphicsItem * = 0, Schema * = 0, int * = NULL); + virtual int nbBornes() const; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *); + QString typeId() { return(nomfichier); } + QString fichier() { return(nomfichier); } + bool isNull() { return(elmt_etat != 0); } + int etat() { return(elmt_etat); } + QString nom() { return(priv_nom); } + + private: + int elmt_etat; // contient le code d'erreur si l'instanciation a echoue ou 0 si l'instanciation s'est bien passe + QString priv_nom; + QString nomfichier; + QPicture dessin; + bool parseElement(QDomElement &, QPainter &, Schema *); + bool parseLigne(QDomElement &, QPainter &); + bool parseCercle(QDomElement &, QPainter &); + bool parsePolygone(QDomElement &, QPainter &); + bool parseBorne(QDomElement &, Schema *); + void setQPainterAntiAliasing(QPainter *, bool); + int attributeIsAnInteger(QDomElement &, QString, int * = NULL); + int nb_bornes; + }; +#endif diff --git a/elements/contacteur.elmt b/elements/contacteur.elmt new file mode 100644 index 000000000..0969a5ed1 --- /dev/null +++ b/elements/contacteur.elmt @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/elements/del.elmt b/elements/del.elmt new file mode 100644 index 000000000..db3dad19d --- /dev/null +++ b/elements/del.elmt @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/elements/entree.elmt b/elements/entree.elmt new file mode 100644 index 000000000..d5baf8a66 --- /dev/null +++ b/elements/entree.elmt @@ -0,0 +1,6 @@ + + + + + + diff --git a/entree.cpp b/entree.cpp new file mode 100644 index 000000000..cc42dee84 --- /dev/null +++ b/entree.cpp @@ -0,0 +1,62 @@ +#include "entree.h" + +/** + Constructeur + @param parent Le QObject parent de l'element. + @param scene La scene sur laquelle l'element est affiche +*/ +Entree::Entree(QGraphicsItem *parent, Schema *scene) : ElementFixe(parent, scene) { + // taille et hotspot + setSize(20, 40); + setHotspot(QPoint(10, 15)); + + // ajout d'une borne a l'element + new Borne(0, 15, Borne::Sud, this, scene); +} + +/** + @return Le nombre actuel de bornes de l'element +*/ +int Entree::nbBornes() const { + return(1); +} + +/** + Fonction qui effectue le rendu graphique du contacteur + @param p Le QPainter a utiliser pour dessiner l'element + @param o Les options de dessin +*/ +void Entree::paint(QPainter *p, const QStyleOptionGraphicsItem *) { + // traits de couleur noire + QPen t; + t.setColor(Qt::black); + t.setWidthF(1.0); + t.setJoinStyle(Qt::MiterJoin); + p -> setPen(t); + p -> setBrush(Qt::black); + + // Dessin du triangle + static const QPointF points[3] = { + QPointF(-7.5, -13), + QPointF( 7.5, -13), + QPointF( 0.0, 0.0) + }; + + p -> drawPolygon(points, 3); + p -> setBrush(Qt::NoBrush); + + // une ligne JAMAIS antialiasee (annulation des renderhints) + p -> save(); + p -> setRenderHint(QPainter::Antialiasing, false); + p -> setRenderHint(QPainter::TextAntialiasing, false); + p -> setRenderHint(QPainter::SmoothPixmapTransform, false); + p -> drawLine(0, 0, 0, 13); + p -> restore(); +} + +/** + @return l'ID du type "Contacteur" +*/ +QString Entree::typeId() { + return(QString("2")); +} diff --git a/entree.h b/entree.h new file mode 100644 index 000000000..a74233bb6 --- /dev/null +++ b/entree.h @@ -0,0 +1,16 @@ +#ifndef ENTREE_H + #define ENTREE_H + #include "elementfixe.h" + /** + Cette classe herite de la classe Element Fixe pour definir une + entree. + */ + class Entree : public ElementFixe{ + public: + Entree(QGraphicsItem * = 0, Schema * = 0); + virtual int nbBornes() const; + void paint(QPainter *, const QStyleOptionGraphicsItem *); + QString typeId(); + QString nom() { return("Entr\351e"); } + }; +#endif diff --git a/gnugpl.txt b/gnugpl.txt new file mode 100644 index 000000000..c0d0af8d8 --- /dev/null +++ b/gnugpl.txt @@ -0,0 +1,342 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ico/button_cancel.png b/ico/button_cancel.png new file mode 100644 index 000000000..96919575a Binary files /dev/null and b/ico/button_cancel.png differ diff --git a/ico/button_ok.png b/ico/button_ok.png new file mode 100644 index 000000000..31c064ba9 Binary files /dev/null and b/ico/button_ok.png differ diff --git a/ico/configure.png b/ico/configure.png new file mode 100644 index 000000000..95bd319ce Binary files /dev/null and b/ico/configure.png differ diff --git a/ico/copy.png b/ico/copy.png new file mode 100644 index 000000000..c3ff243b8 Binary files /dev/null and b/ico/copy.png differ diff --git a/ico/cut.png b/ico/cut.png new file mode 100644 index 000000000..7ec355a0d Binary files /dev/null and b/ico/cut.png differ diff --git a/ico/delete.png b/ico/delete.png new file mode 100644 index 000000000..6fb193f06 Binary files /dev/null and b/ico/delete.png differ diff --git a/ico/editdelete.png b/ico/editdelete.png new file mode 100644 index 000000000..6d0d29d76 Binary files /dev/null and b/ico/editdelete.png differ diff --git a/ico/entrer_fs.png b/ico/entrer_fs.png new file mode 100644 index 000000000..a8c35c4ed Binary files /dev/null and b/ico/entrer_fs.png differ diff --git a/ico/exit.png b/ico/exit.png new file mode 100644 index 000000000..a77152b5b Binary files /dev/null and b/ico/exit.png differ diff --git a/ico/export.png b/ico/export.png new file mode 100644 index 000000000..381bfc053 Binary files /dev/null and b/ico/export.png differ diff --git a/ico/fileclose.png b/ico/fileclose.png new file mode 100644 index 000000000..edf5f76f9 Binary files /dev/null and b/ico/fileclose.png differ diff --git a/ico/import.png b/ico/import.png new file mode 100644 index 000000000..32baf9c54 Binary files /dev/null and b/ico/import.png differ diff --git a/ico/masquer.png b/ico/masquer.png new file mode 100644 index 000000000..1059c25c5 Binary files /dev/null and b/ico/masquer.png differ diff --git a/ico/move.png b/ico/move.png new file mode 100644 index 000000000..b99d3e477 Binary files /dev/null and b/ico/move.png differ diff --git a/ico/new.png b/ico/new.png new file mode 100644 index 000000000..84c407153 Binary files /dev/null and b/ico/new.png differ diff --git a/ico/open.png b/ico/open.png new file mode 100644 index 000000000..037c2da98 Binary files /dev/null and b/ico/open.png differ diff --git a/ico/paste.png b/ico/paste.png new file mode 100644 index 000000000..f6a1db8f4 Binary files /dev/null and b/ico/paste.png differ diff --git a/ico/pivoter.png b/ico/pivoter.png new file mode 100644 index 000000000..36e35b7d0 Binary files /dev/null and b/ico/pivoter.png differ diff --git a/ico/print.png b/ico/print.png new file mode 100644 index 000000000..d6defce21 Binary files /dev/null and b/ico/print.png differ diff --git a/ico/qelectrotech.png b/ico/qelectrotech.png new file mode 100644 index 000000000..87c00bd53 Binary files /dev/null and b/ico/qelectrotech.png differ diff --git a/ico/qelectrotech.svg b/ico/qelectrotech.svg new file mode 100644 index 000000000..7ef9c8a35 --- /dev/null +++ b/ico/qelectrotech.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + QET + diff --git a/ico/qet.png b/ico/qet.png new file mode 100644 index 000000000..0ff7d7625 Binary files /dev/null and b/ico/qet.png differ diff --git a/ico/qet.svg b/ico/qet.svg new file mode 100644 index 000000000..876268e18 --- /dev/null +++ b/ico/qet.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/ico/qt.png b/ico/qt.png new file mode 100644 index 000000000..a616ea095 Binary files /dev/null and b/ico/qt.png differ diff --git a/ico/redo.png b/ico/redo.png new file mode 100644 index 000000000..6aacd4f85 Binary files /dev/null and b/ico/redo.png differ diff --git a/ico/restaurer.png b/ico/restaurer.png new file mode 100644 index 000000000..7ff886a99 Binary files /dev/null and b/ico/restaurer.png differ diff --git a/ico/save.png b/ico/save.png new file mode 100644 index 000000000..41b3f4367 Binary files /dev/null and b/ico/save.png differ diff --git a/ico/saveas.png b/ico/saveas.png new file mode 100644 index 000000000..3e28d5d35 Binary files /dev/null and b/ico/saveas.png differ diff --git a/ico/select.png b/ico/select.png new file mode 100644 index 000000000..06fa1aa75 Binary files /dev/null and b/ico/select.png differ diff --git a/ico/sortir_fs.png b/ico/sortir_fs.png new file mode 100644 index 000000000..9310a4713 Binary files /dev/null and b/ico/sortir_fs.png differ diff --git a/ico/toolbars.png b/ico/toolbars.png new file mode 100644 index 000000000..653479bf0 Binary files /dev/null and b/ico/toolbars.png differ diff --git a/ico/undo.png b/ico/undo.png new file mode 100644 index 000000000..d4b682bb2 Binary files /dev/null and b/ico/undo.png differ diff --git a/ico/viewmag+.png b/ico/viewmag+.png new file mode 100644 index 000000000..d2b139c8b Binary files /dev/null and b/ico/viewmag+.png differ diff --git a/ico/viewmag-.png b/ico/viewmag-.png new file mode 100644 index 000000000..2ae727f35 Binary files /dev/null and b/ico/viewmag-.png differ diff --git a/ico/viewmag.png b/ico/viewmag.png new file mode 100644 index 000000000..bee286ab4 Binary files /dev/null and b/ico/viewmag.png differ diff --git a/ico/viewmagfit.png b/ico/viewmagfit.png new file mode 100644 index 000000000..001ea7aab Binary files /dev/null and b/ico/viewmagfit.png differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 000000000..c11e7d589 --- /dev/null +++ b/main.cpp @@ -0,0 +1,21 @@ +#include +#include +#include "qetapp.h" + +/** + Fonction principale du programme QElectroTech + @param argc nombre de parametres + @param argv parametres +*/ +int main(int argc, char **argv) { + // Creation de l'application + QApplication app(argc, argv); + // Traducteur + QTranslator trad; + //trad.load("qet_en"); + app.installTranslator(&trad); + // Creation et affichage du QETApp : QElectroTechApplication + (new QETApp()) -> show(); + // Execution de l'application + return(app.exec()); +} diff --git a/panelappareils.cpp b/panelappareils.cpp new file mode 100644 index 000000000..ce39f6678 --- /dev/null +++ b/panelappareils.cpp @@ -0,0 +1,111 @@ +#include "panelappareils.h" +#include "contacteur.h" +#include "del.h" +#include "entree.h" +#include "elementperso.h" + +/** + Constructeur + @param parent Le QWidget parent du panel d'appareils + @todo : definir une classe heritant de QListWidgetItem et automatiser tout ca +*/ +PanelAppareils::PanelAppareils(QWidget *parent) : QListWidget(parent) { + + // selection unique + setSelectionMode(QAbstractItemView::SingleSelection); + + // drag'n drop autorise + setDragEnabled(true); + setAcceptDrops(false); + setDropIndicatorShown(false); + + // style, mouvement et taille des elements + setIconSize(QSize(50, 50)); + setMovement(QListView::Free); + setViewMode(QListView::ListMode); + + // donnees + /*Element *del = new DEL(0,0); + Element *contacteur = new Contacteur(0,0); + Element *entree = new Entree(0, 0);*/ + + QListWidgetItem *qlwi; + QString whats_this = tr("Ceci est un \351l\351ment que vous pouvez ins\351rer dans votre sch\351ma par cliquer-d\351placer"); + QString tool_tip = tr("Cliquer-d\351posez cet \351l\351ment sur le sch\351ma pour ins\351rer un \351l\351ment "); + + // remplissage de la liste + QDir dossier_elements("elements/"); + QStringList filtres; + filtres << "*.elmt"; + QStringList fichiers = dossier_elements.entryList(filtres, QDir::Files, QDir::Name); + foreach(QString fichier, fichiers) { + int etat; + ElementPerso *elmt_perso = new ElementPerso(fichier, 0, 0, &etat); + if (etat != 0) { + qDebug() << "Le chargement du composant" << fichier << "a echoue avec le code d'erreur" << etat; + continue; + } + qlwi = new QListWidgetItem(QIcon(elmt_perso -> pixmap()), elmt_perso -> nom(), this); + qlwi -> setStatusTip(tool_tip + "\253 " + elmt_perso -> nom() + " \273"); + qlwi -> setToolTip(elmt_perso -> nom()); + qlwi -> setWhatsThis(whats_this); + qlwi -> setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); + qlwi -> setData(42, fichier); + } + + // force du noir sur une alternance de blanc (comme le schema) et de bleu clair + QPalette qp = palette(); + setAlternatingRowColors(true); + qp.setColor(QPalette::Text, Qt::black); + qp.setColor(QPalette::Base, Qt::white); + //qp.setColor(QPalette::AlternateBase, QColor(240, 255, 255)); + setPalette(qp); +} + +/** + Gere le mouvement lors d'un drag'n drop +*/ +void PanelAppareils::dragMoveEvent(QDragMoveEvent */*e*/) { +} + +/** + Gere le depot lors d'un drag'n drop +*/ +void PanelAppareils::dropEvent(QDropEvent */*e*/) { +} + +/** + Gere le debut des drag'n drop + @param supportedActions Les actions supportees + @todo virer les lignes type «if ("tel appareil") construire TelAppareil» => trouver un moyen d'automatiser ca + */ +void PanelAppareils::startDrag(Qt::DropActions /*supportedActions*/) { + // objet QDrag pour realiser le drag'n drop + QDrag *drag = new QDrag(this); + + // donnees qui seront transmises par le drag'n drop + QMimeData *mimeData = new QMimeData(); + + // appareil temporaire pour fournir un apercu + Element *appar; + int etat; + QString nom_fichier = currentItem() -> data(42).toString(); + appar = new ElementPerso(nom_fichier, 0, 0, &etat); + if (etat != 0) { + delete appar; + return; + } + + mimeData -> setText(nom_fichier); + drag -> setMimeData(mimeData); + + // accrochage d'une pixmap representant l'appareil au pointeur + drag -> setPixmap(appar -> pixmap()); + drag -> setHotSpot(appar -> hotspot()); + + // realisation du drag'n drop + drag -> start(Qt::CopyAction); + + // suppression de l'appareil temporaire + delete appar; +} diff --git a/panelappareils.h b/panelappareils.h new file mode 100644 index 000000000..f8313c61a --- /dev/null +++ b/panelappareils.h @@ -0,0 +1,18 @@ +#ifndef PANELAPPAREILS_H + #define PANELAPPAREILS_H + #include + /** + Cette classe represente le panel d'appareils (en tant qu'element + graphique) dans lequel l'utilisateur choisit les composants de + son choix et les depose sur le schema par drag'n drop. + */ + class PanelAppareils : public QListWidget { + Q_OBJECT + public: + PanelAppareils(QWidget * = 0); + public slots: + void dragMoveEvent(QDragMoveEvent *); + void dropEvent(QDropEvent *); + void startDrag(Qt::DropActions); + }; +#endif diff --git a/qelectrotech.pro b/qelectrotech.pro new file mode 100644 index 000000000..ea591eeec --- /dev/null +++ b/qelectrotech.pro @@ -0,0 +1,40 @@ +###################################################################### +# Automatically generated by qmake (2.01a) dim. oct. 8 23:57:43 2006 +###################################################################### + +TEMPLATE = app +TARGET = +DEPENDPATH += . +INCLUDEPATH += . + +# Input +HEADERS += aboutqet.h \ + borne.h \ + conducteur.h \ + contacteur.h \ + del.h \ + element.h \ + elementfixe.h \ + elementperso.h \ + entree.h \ + panelappareils.h \ + qetapp.h \ + schema.h \ + schemavue.h +SOURCES += aboutqet.cpp \ + borne.cpp \ + conducteur.cpp \ + contacteur.cpp \ + del.cpp \ + element.cpp \ + elementfixe.cpp \ + elementperso.cpp \ + entree.cpp \ + main.cpp \ + panelappareils.cpp \ + qetapp.cpp \ + schema.cpp \ + schemavue.cpp +RESOURCES += qelectrotech.qrc +TRANSLATIONS += qet_en.ts +QT += xml diff --git a/qelectrotech.qrc b/qelectrotech.qrc new file mode 100644 index 000000000..6b2c63667 --- /dev/null +++ b/qelectrotech.qrc @@ -0,0 +1,38 @@ + + + ico/qet.png + ico/qelectrotech.png + ico/button_cancel.png + ico/button_ok.png + ico/configure.png + ico/copy.png + ico/cut.png + ico/delete.png + ico/editdelete.png + ico/entrer_fs.png + ico/exit.png + ico/export.png + ico/fileclose.png + ico/import.png + ico/masquer.png + ico/move.png + ico/new.png + ico/open.png + ico/paste.png + ico/pivoter.png + ico/print.png + ico/qt.png + ico/redo.png + ico/restaurer.png + ico/saveas.png + ico/save.png + ico/select.png + ico/sortir_fs.png + ico/toolbars.png + ico/undo.png + ico/viewmagfit.png + ico/viewmag-.png + ico/viewmag.png + ico/viewmag+.png + + diff --git a/qet_en.qm b/qet_en.qm new file mode 100644 index 000000000..de38ea5fc Binary files /dev/null and b/qet_en.qm differ diff --git a/qet_en.ts b/qet_en.ts new file mode 100644 index 000000000..75397e801 --- /dev/null +++ b/qet_en.ts @@ -0,0 +1,500 @@ + + + + AboutQET + + + À propos de QElectrotech + About QElectroTech + + + + À &propos + &About + + + + A&uteurs + A&uthors + + + + &Accord de licence + &License Agreement + + + + QElectroTech, une application de réalisation de schémas électriques. + +© 2006 Les développeurs de QElectroTech + +Merde on n'a pas de site web + QElectroTech is a program to design electric schemas. + +© 2006 QElectroTech Developers + +Whoops, we have no website + + + + Idée originale : Benoît Ansieau <benoit.ansieau@gmail.com> + +Programmation : Xavier Guerrin <xavier.guerrin@gmail.com> + Original idea : Benoît Ansieau <benoit.ansieau@gmail.com> + +Programming : Xavier Guerrin <xavier.guerrin@gmail.com> + + + + Ce programme est sous licence GNU/GPL. + This program is under the GNU/GPL license. + + + + Le fichier texte contenant la licence GNU/GPL est introuvable - bon bah de toute façon, vous la connaissez par c&oelig;ur non ? + The text file containing the GNU/GPL license could not be found - however, you know it by heart, don't you ? + + + + Le fichier texte contenant la licence GNU/GPL existe mais n'a pas pu être ouvert - bon bah de toute façon, vous la connaissez par c&oelig;ur non ? + The text file containing the GNU/GPL license exists but could not be opened - however, you know it by heart, don't you ? + + + + PanelAppareils + + + Ceci est un élément que vous pouvez insérer dans votre schéma par cliquer-déplacer + This is a device you can drag'n drop onto your plan + + + + Cliquer-déposez cet élément sur le schéma pour insérer + Drag this device to the plan to insert + + + + Contacteur + Contact + + + + un contacteur + a contact + + + + Voyant DEL + LED light + + + + une DEL + a LED light + + + + Diode Electroluminescente + Light-Emitting Diode + + + + Entrée + Input + + + + une entrée + an input + + + + QETApp + + + QElectroTech + QElectroTech + + + + QElectrotech : dimensions de la zone de dessin : + QElectrotech : Size of the drawing area : + + + + x + x + + + + &Masquer + &Hide + + + + &Quitter + &Quit + + + + &Restaurer + &Show + + + + &Fichier + &File + + + + &Édition + &Edit + + + + Afficha&ge + Displ&ay + + + + O&utils + &Tools + + + + &Configuration + &Settings + + + + &Aide + &Help + + + + &Nouveau + &New + + + + &Ouvrir + &Open + + + + &Enregistrer + &Save + + + + Enregistrer sous + Save as + + + + &Importer + &Import + + + + Ctrl+Shift+I + + + + + E&xporter + &Export + + + + Ctrl+Shift+X + + + + + Ctrl+Q + + + + + Annu&ler + &Undo + + + + Re&faire + &Redo + + + + Co&uper + Cu&t + + + + Cop&ier + &Copy + + + + C&oller + &Paste + + + + Tout sélectionner + Select All + + + + Désélectionner tout + Select none + + + + Ctrl+Shift+A + + + + + Inverser la sélection + Invert selection + + + + Ctrl+I + + + + + Cacher &la barre d'outils + Hide Too&lbar + + + + &Mode plein écran + &Fullscreen Mode + + + + Ctrl+Shift+F + + + + + Configurer les &barres d'outils + Configure tool&bars + + + + &Configurer QElectroTech + &Configure QElectroTech + + + + À &propos de QElectroTech + A&bout QElectroTech + + + + À propos de &Qt + About &Qt + + + + Désactiver l'&antialiasing + Render without &Antialiasing + + + + Activer l'&antialiasing + Render with &Antialiasing + + + + QElectrotech + QElectrotech + + + + Panel d'appareils + Elements Panel + + + + Imprimer + Print + + + + Zoom avant + Zoom In + + + + Zoom arrière + Zoom Out + + + + Zoom adapté + Fit in view + + + + Pas de zoom + Reset zoom + + + + Mode Selection + Selection Mode + + + + Passer en &mode plein écran + F&ullScreen Screen Mode + + + + Sortir du &mode plein écran + Exit F&ullScreen Screen Mode + + + + Reduire QElectroTech dans le systray + Minimize QElectroTech to the sytray + + + + Restaurer QElectroTech + Restore QElectroTech + + + + P + P + + + + Ctrl+9 + + + + + Ctrl+0 + + + + + Afficher + Display + + + + Outils + Tools + + + + Exporter vers le fichier + Export to file + + + + Image PNG (*.png) + PNG Picture (*.png) + + + + Schema QelectroTech (*.qet) + QElectroTech Schema (*.qet) + + + + Erreur + Error + + + + Impossible d'ecrire dans ce fichier + Can't write to the file + + + + Ouvrir un fichier + Open a file + + + + Schema QelectroTech (*.qet);;Fichiers XML (*.xml);;Tous les fichiers (*) + QelectroTech Schema (*.qet);;XML Files (*.xml);;All Files (*) + + + + Impossible de lire ce fichier + Can't read that file + + + + Ce fichier n'est pas un document XML valide. + This file is not a valid XML Document. + + + + Enregistrer le schéma en cours ? + Save the current schema ? + + + + Voulez-vous enregistrer le schéma en cours ? + Do you wish to save the current schema ? + + + + Supprimer + Delete + + + + Pivoter + Rotate + + + + Mode Visualisation + View Mode + + + + Schema + + + Contacteur + Contact + + + + Voyant DEL + LED light + + + + SchemaVue + + + Contacteur + Contact + + + + Voyant DEL + LED light + + + + Entrée + Input + + + diff --git a/qetapp.cpp b/qetapp.cpp new file mode 100644 index 000000000..22082565a --- /dev/null +++ b/qetapp.cpp @@ -0,0 +1,733 @@ +#include "qetapp.h" +#include "schemavue.h" +#include "schema.h" +#include "panelappareils.h" +#include "aboutqet.h" + +/** + constructeur + @param parent le widget parent de la fenetre principale + */ +QETApp::QETApp(QWidget *parent) : QMainWindow(parent) { + // mise en place de l'interface MDI au centre de l'application + setCentralWidget(&workspace); + + // mise en place du signalmapper + connect(&windowMapper, SIGNAL(mapped(QWidget *)), &workspace, SLOT(setActiveWindow(QWidget *))); + + // recupere les arguments passes au programme + QStringList args = QCoreApplication::arguments(); + + // recupere les chemins de fichiers parmi les arguments + QStringList fichiers; + for (int i = 1 ; i < args.size() ; ++ i) { + if (QFileInfo(args.at(i)).exists()) fichiers << args.at(i); + } + + // si des chemins de fichiers valides sont passes en arguments + QList schema_vues; + if (fichiers.size()) { + // alors on ouvre ces fichiers + foreach(QString fichier, fichiers) { + SchemaVue *sv = new SchemaVue(this); + if (sv -> ouvrir(fichier)) schema_vues << sv; + else delete sv; + } + } + + // si aucun schema n'a ete ouvert jusqu'a maintenant, on ouvre un nouveau schema + if (!schema_vues.size()) schema_vues << new SchemaVue(this); + + // ajout de tous les SchemaVue necessaires + foreach (SchemaVue *sv, schema_vues) addSchemaVue(sv); + + // titre de la fenetre + setWindowTitle(tr("QElectroTech")); + + // icone de la fenetre + setWindowIcon(QIcon(":/ico/qet.png")); + + // barre de statut de la fenetre + statusBar() -> showMessage(tr("QElectrotech")); + + // ajout du panel d'Appareils en tant que QDockWidget + qdw_pa = new QDockWidget(tr("Panel d'appareils"), this); + qdw_pa -> setAllowedAreas(Qt::AllDockWidgetAreas); + qdw_pa -> setFeatures(QDockWidget::AllDockWidgetFeatures); + qdw_pa -> setMinimumWidth(160); + qdw_pa -> setWidget(pa = new PanelAppareils(qdw_pa)); + addDockWidget(Qt::LeftDockWidgetArea, qdw_pa); + + // mise en place des actions + actions(); + + // mise en place de la barre d'outils + toolbar(); + + // mise en place des menus + menus(); + + // systray de l'application + if (QSystemTrayIcon::isSystemTrayAvailable()) { + qsti = new QSystemTrayIcon(QIcon(":/ico/qet.png"), this); + qsti -> setToolTip(tr("QElectroTech")); + connect(qsti, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(systray(QSystemTrayIcon::ActivationReason))); + menu_systray = new QMenu(tr("QElectroTech")); + menu_systray -> addAction(masquer_appli); + menu_systray -> addAction(quitter_qet); + qsti -> setContextMenu(menu_systray); + qsti -> show(); + } + + // la fenetre est maximisee par defaut + setMinimumWidth(500); + setMinimumHeight(350); + setWindowState(Qt::WindowMaximized); + + // connexions signaux / slots pour une interface sensee + connect(&workspace, SIGNAL(windowActivated(QWidget *)), this, SLOT(slot_updateActions())); + connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(slot_updateActions())); +} + +/** + Gere les evenements relatifs au QSystemTrayIcon + @param raison un entier representant l'evenement survenu sur le systray +*/ +void QETApp::systray(QSystemTrayIcon::ActivationReason raison) { + if (!QSystemTrayIcon::isSystemTrayAvailable()) return; + switch(raison) { + case QSystemTrayIcon::Context: + // affichage du menu + (qsti -> contextMenu()) -> show(); + break; + case QSystemTrayIcon::DoubleClick: + case QSystemTrayIcon::Trigger: + // reduction ou restauration de l'application + if (isVisible()) systrayReduire(); else systrayRestaurer(); + break; + case QSystemTrayIcon::Unknown: + default: // ne rien faire + break; + } +} + +/** + Reduit l'application dans le systray +*/ +void QETApp::systrayReduire() { + // on sauvegarde la position et les dimensions de l'application + wg = saveGeometry(); + // on cache l'application + hide(); + // on ajoute le menu "Restaurer" et on enlève le menu "Masquer" + menu_systray -> insertAction(masquer_appli, restaurer_appli); + menu_systray -> removeAction(masquer_appli); +} + +/** + Restaure l'application reduite dans le systray +*/ +void QETApp::systrayRestaurer() { + // on restaure la position et les dimensions de l'application + restoreGeometry(wg); + // on affiche l'application + show(); + // on ajoute le menu "Masquer" et on enlève le menu "Restaurer" + menu_systray -> insertAction(restaurer_appli, masquer_appli); + menu_systray -> removeAction(restaurer_appli); +} + +/** + Permet de quitter l'application lors de la fermeture de la fenetre principale +*/ +void QETApp::closeEvent(QCloseEvent *) { + quitter(); +} + +/** + Gere la sortie de l'application + @todo gerer les eventuelles fermetures de fichiers +*/ +void QETApp::quitter() { + if (!schemaEnCours()) qApp -> quit(); + else { + bool peut_quitter = true; + foreach(QWidget *fenetre, workspace.windowList()) { + if (qobject_cast(fenetre)) { + workspace.setActiveWindow(fenetre); + if (!fermer()) { + peut_quitter = false; + break; + } + } + } + if (peut_quitter) qApp -> quit(); + } + +} + +/** + Fait passer la fenetre en mode plein ecran au mode normal et vice-versa +*/ +void QETApp::toggleFullScreen() { + setWindowState(windowState() ^ Qt::WindowFullScreen); +} + +/** + Active ou desactive l'antialiasing sur le rendu graphique du Schema +*/ +void QETApp::toggleAntialiasing() { + SchemaVue *sv = schemaEnCours(); + if (!sv) return; + sv -> setAntialiasing(!sv -> antialiased()); + toggle_aa -> setText(sv -> antialiased() ? tr("D\351sactiver l'&antialiasing") : tr("Activer l'&antialiasing")); +} + +/** + Dialogue « A propos de QElectroTech » + Le dialogue en question est cree lors du premier appel de cette fonction. + En consequence, sa premiere apparition n'est pas immediate. Par la suite, + le dialogue n'a pas a etre recree et il apparait instantanement. Il est + detruit en meme temps que son parent (ici, la QETApp). +*/ +void QETApp::aPropos() { + static AboutQET *apqet = new AboutQET(this); + apqet -> exec(); +} + +/** + Mise en place des actions +*/ +void QETApp::actions() { + // icones et labels + nouveau_fichier = new QAction(QIcon(":/ico/new.png"), tr("&Nouveau"), this); + ouvrir_fichier = new QAction(QIcon(":/ico/open.png"), tr("&Ouvrir"), this); + fermer_fichier = new QAction(QIcon(":/ico/fileclose.png"), tr("&Fermer"), this); + enr_fichier = new QAction(QIcon(":/ico/save.png"), tr("&Enregistrer"), this); + enr_fichier_sous = new QAction(QIcon(":/ico/saveas.png"), tr("Enregistrer sous"), this); + importer = new QAction(QIcon(":/ico/import.png"), tr("&Importer"), this); + exporter = new QAction(QIcon(":/ico/export.png"), tr("E&xporter"), this); + imprimer = new QAction(QIcon(":/ico/print.png"), tr("Imprimer"), this); + quitter_qet = new QAction(QIcon(":/ico/exit.png"), tr("&Quitter"), this); + + annuler = new QAction(QIcon(":/ico/undo.png"), tr("Annu&ler"), this); + refaire = new QAction(QIcon(":/ico/redo.png"), tr("Re&faire"), this); + couper = new QAction(QIcon(":/ico/cut.png"), tr("Co&uper"), this); + copier = new QAction(QIcon(":/ico/copy.png"), tr("Cop&ier"), this); + coller = new QAction(QIcon(":/ico/paste.png"), tr("C&oller"), this); + sel_tout = new QAction( tr("Tout s\351lectionner"), this); + sel_rien = new QAction( tr("D\351s\351lectionner tout"), this); + sel_inverse = new QAction( tr("Inverser la s\351lection"), this); + supprimer = new QAction(QIcon(":/ico/delete.png"), tr("Supprimer"), this); + pivoter = new QAction(QIcon(":/ico/pivoter.png"), tr("Pivoter"), this); + + toggle_aa = new QAction( tr("D\351sactiver l'&antialiasing"), this); + zoom_avant = new QAction(QIcon(":/ico/viewmag+.png"), tr("Zoom avant"), this); + zoom_arriere = new QAction(QIcon(":/ico/viewmag-.png"), tr("Zoom arri\350re"), this); + zoom_adapte = new QAction(QIcon(":/ico/viewmagfit.png"), tr("Zoom adapt\351"), this); + zoom_reset = new QAction(QIcon(":/ico/viewmag.png"), tr("Pas de zoom"), this); + + mode_selection = new QAction(QIcon(":/ico/select.png"), tr("Mode Selection"), this); + mode_visualise = new QAction(QIcon(":/ico/move.png"), tr("Mode Visualisation"), this); + + entrer_pe = new QAction(QIcon(":/ico/entrer_fs.png"), tr("Passer en &mode plein \351cran"), this); + sortir_pe = new QAction(QIcon(":/ico/sortir_fs.png"), tr("Sortir du &mode plein \351cran"), this); + configurer = new QAction(QIcon(":/ico/configure.png"), tr("&Configurer QElectroTech"), this); + + f_mosaique = new QAction( tr("&Mosa\357que"), this); + f_cascade = new QAction( tr("&Cascade"), this); + f_reorganise = new QAction( tr("Arranger les fen\352tres réduites"),this); + f_suiv = new QAction( tr("Fen\352tre suivante"), this); + f_prec = new QAction( tr("Fen\352tre pr\351c\351dente"), this); + + a_propos_de_qet = new QAction(QIcon(":/ico/qet.png"), tr("\300 &propos de QElectroTech"), this); + a_propos_de_qt = new QAction(QIcon(":/ico/qt.png"), tr("\300 propos de &Qt"), this); + + masquer_appli = new QAction(QIcon(":/ico/masquer.png"), tr("&Masquer"), this); + restaurer_appli = new QAction(QIcon(":/ico/restaurer.png"), tr("&Restaurer"), this); + + // info-bulles / indications dans la barre de statut + masquer_appli -> setToolTip(tr("Reduire QElectroTech dans le systray")); + restaurer_appli -> setToolTip(tr("Restaurer QElectroTech")); + + // raccourcis clavier + nouveau_fichier -> setShortcut(QKeySequence::New); + ouvrir_fichier -> setShortcut(QKeySequence::Open); + fermer_fichier -> setShortcut(QKeySequence::Close); + enr_fichier -> setShortcut(QKeySequence::Save); + importer -> setShortcut(QKeySequence(tr("Ctrl+Shift+I"))); + exporter -> setShortcut(QKeySequence(tr("Ctrl+Shift+X"))); + imprimer -> setShortcut(QKeySequence(QKeySequence::Print)); + quitter_qet -> setShortcut(QKeySequence(tr("Ctrl+Q"))); + + annuler -> setShortcut(QKeySequence::Undo); + refaire -> setShortcut(QKeySequence::Redo); + couper -> setShortcut(QKeySequence::Cut); + copier -> setShortcut(QKeySequence::Copy); + coller -> setShortcut(QKeySequence::Paste); + sel_tout -> setShortcut(QKeySequence::SelectAll); + sel_rien -> setShortcut(QKeySequence(tr("Ctrl+Shift+A"))); + sel_inverse -> setShortcut(QKeySequence(tr("Ctrl+I"))); + supprimer -> setShortcut(QKeySequence::Delete); + pivoter -> setShortcut(QKeySequence(tr("P"))); + + zoom_avant -> setShortcut(QKeySequence::ZoomIn); + zoom_arriere -> setShortcut(QKeySequence::ZoomOut); + zoom_adapte -> setShortcut(QKeySequence(tr("Ctrl+9"))); + zoom_reset -> setShortcut(QKeySequence(tr("Ctrl+0"))); + + entrer_pe -> setShortcut(QKeySequence(tr("Ctrl+Shift+F"))); + sortir_pe -> setShortcut(QKeySequence(tr("Ctrl+Shift+F"))); + + // + f_mosaique -> setStatusTip(tr("Dispose les fen\352tres en mosa\357que")); + f_cascade -> setStatusTip(tr("Dispose les fen\352tres en cascade")); + f_reorganise -> setStatusTip(tr("Aligne les fen\352tres réduites")); + f_suiv -> setStatusTip(tr("Active la fen\352tre suivante")); + f_prec -> setStatusTip(tr("Active la fen\352tre pr\351c\351dente")); + + // traitements speciaux + mode_selection -> setCheckable(true); + mode_visualise -> setCheckable(true); + mode_selection -> setChecked(true); + + QActionGroup *grp_visu_sel = new QActionGroup(this); + grp_visu_sel -> addAction(mode_selection); + grp_visu_sel -> addAction(mode_visualise); + grp_visu_sel -> setExclusive(true); + + // connexion a des slots + connect(quitter_qet, SIGNAL(triggered()), this, SLOT(quitter()) ); + connect(sel_tout, SIGNAL(triggered()), this, SLOT(slot_selectAll()) ); + connect(sel_rien, SIGNAL(triggered()), this, SLOT(slot_selectNothing()) ); + connect(sel_inverse, SIGNAL(triggered()), this, SLOT(slot_selectInvert()) ); + connect(supprimer, SIGNAL(triggered()), this, SLOT(slot_supprimer()) ); + connect(pivoter, SIGNAL(triggered()), this, SLOT(slot_pivoter()) ); + connect(entrer_pe, SIGNAL(triggered()), this, SLOT(toggleFullScreen()) ); + connect(sortir_pe, SIGNAL(triggered()), this, SLOT(toggleFullScreen()) ); + connect(mode_selection, SIGNAL(triggered()), this, SLOT(slot_setSelectionMode()) ); + connect(mode_visualise, SIGNAL(triggered()), this, SLOT(slot_setVisualisationMode())); + connect(a_propos_de_qet, SIGNAL(triggered()), this, SLOT(aPropos()) ); + connect(a_propos_de_qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()) ); + connect(masquer_appli, SIGNAL(triggered()), this, SLOT(systrayReduire ()) ); + connect(restaurer_appli, SIGNAL(triggered()), this, SLOT(systrayRestaurer()) ); + connect(zoom_avant, SIGNAL(triggered()), this, SLOT(slot_zoomPlus()) ); + connect(zoom_arriere, SIGNAL(triggered()), this, SLOT(slot_zoomMoins()) ); + connect(zoom_adapte, SIGNAL(triggered()), this, SLOT(slot_zoomFit()) ); + connect(zoom_reset, SIGNAL(triggered()), this, SLOT(slot_zoomReset()) ); + connect(imprimer, SIGNAL(triggered()), this, SLOT(dialogue_imprimer()) ); + connect(exporter, SIGNAL(triggered()), this, SLOT(dialogue_exporter()) ); + connect(enr_fichier_sous, SIGNAL(triggered()), this, SLOT(dialogue_enregistrer_sous())); + connect(enr_fichier, SIGNAL(triggered()), this, SLOT(enregistrer()) ); + connect(nouveau_fichier, SIGNAL(triggered()), this, SLOT(nouveau()) ); + connect(ouvrir_fichier, SIGNAL(triggered()), this, SLOT(ouvrir()) ); + connect(fermer_fichier, SIGNAL(triggered()), this, SLOT(fermer()) ); + connect(couper, SIGNAL(triggered()), this, SLOT(slot_couper()) ); + connect(copier, SIGNAL(triggered()), this, SLOT(slot_copier()) ); + connect(coller, SIGNAL(triggered()), this, SLOT(slot_coller()) ); + connect(toggle_aa, SIGNAL(triggered()), this, SLOT(toggleAntialiasing()) ); + connect(f_mosaique, SIGNAL(triggered()), &workspace, SLOT(tile())); + connect(f_cascade, SIGNAL(triggered()), &workspace, SLOT(cascade())); + connect(f_reorganise, SIGNAL(triggered()), &workspace, SLOT(arrangeIcons())); + connect(f_suiv, SIGNAL(triggered()), &workspace, SLOT(activateNextWindow())); + connect(f_prec, SIGNAL(triggered()), &workspace, SLOT(activatePreviousWindow())); +} + +/** + Mise en place des menus +*/ +void QETApp::menus() { + QMenu *menu_fichier = menuBar() -> addMenu(tr("&Fichier")); + QMenu *menu_edition = menuBar() -> addMenu(tr("&\311dition")); + QMenu *menu_affichage = menuBar() -> addMenu(tr("Afficha&ge")); + QMenu *menu_outils = menuBar() -> addMenu(tr("O&utils")); + QMenu *menu_config = menuBar() -> addMenu(tr("&Configuration")); + menu_fenetres = menuBar() -> addMenu(tr("Fe&n\352tres")); + QMenu *menu_aide = menuBar() -> addMenu(tr("&Aide")); + + // tear off feature rulezz... pas ^^ mais bon... + menu_fichier -> setTearOffEnabled(true); + menu_edition -> setTearOffEnabled(true); + menu_affichage -> setTearOffEnabled(true); + menu_outils -> setTearOffEnabled(true); + menu_config -> setTearOffEnabled(true); + menu_aide -> setTearOffEnabled(true); + + // menu Fichier + menu_fichier -> addAction(nouveau_fichier); + menu_fichier -> addAction(ouvrir_fichier); + menu_fichier -> addAction(enr_fichier); + menu_fichier -> addAction(enr_fichier_sous); + menu_fichier -> addAction(fermer_fichier); + menu_fichier -> addSeparator(); + menu_fichier -> addAction(importer); + menu_fichier -> addAction(exporter); + menu_fichier -> addSeparator(); + menu_fichier -> addAction(imprimer); + menu_fichier -> addSeparator(); + menu_fichier -> addAction(quitter_qet); + + // menu Edition + menu_edition -> addAction(annuler); + menu_edition -> addAction(refaire); + menu_edition -> addSeparator(); + menu_edition -> addAction(couper); + menu_edition -> addAction(copier); + menu_edition -> addAction(coller); + menu_edition -> addSeparator(); + menu_edition -> addAction(sel_tout); + menu_edition -> addAction(sel_rien); + menu_edition -> addAction(sel_inverse); + menu_edition -> addSeparator(); + menu_edition -> addAction(supprimer); + menu_edition -> addAction(pivoter); + + // menu Affichage > Afficher + QMenu *menu_aff_aff = new QMenu(tr("Afficher")); + menu_aff_aff -> addAction(barre_outils -> toggleViewAction()); + menu_aff_aff -> addAction(qdw_pa -> toggleViewAction()); + + // menu Affichage + menu_affichage -> addMenu(menu_aff_aff); + menu_affichage -> addSeparator(); + menu_affichage -> addAction(toggle_aa); + menu_affichage -> addSeparator(); + menu_affichage -> addAction(zoom_avant); + menu_affichage -> addAction(zoom_arriere); + menu_affichage -> addAction(zoom_adapte); + menu_affichage -> addAction(zoom_reset); + + // menu Outils + menu_outils -> addAction(mode_selection); + menu_outils -> addAction(mode_visualise); + + // menu Configuration + menu_config -> addAction(entrer_pe); + menu_config -> addAction(configurer); + + // menu Fenêtres + slot_updateMenuFenetres(); + + // menu Aide + menu_aide -> addAction(a_propos_de_qet); + menu_aide -> addAction(a_propos_de_qt); +} + +/** + Mise en place de la barre d'outils +*/ +void QETApp::toolbar() { + barre_outils = new QToolBar(tr("Outils"), this); + + // Modes selection / visualisation + barre_outils -> addAction(mode_selection); + barre_outils -> addAction(mode_visualise); + barre_outils -> addSeparator(); + barre_outils -> addAction(annuler); + barre_outils -> addAction(refaire); + barre_outils -> addSeparator(); + barre_outils -> addAction(couper); + barre_outils -> addAction(copier); + barre_outils -> addAction(coller); + barre_outils -> addSeparator(); + barre_outils -> addAction(supprimer); + barre_outils -> addAction(pivoter); + barre_outils -> addSeparator(); + barre_outils -> addAction(zoom_avant); + barre_outils -> addAction(zoom_arriere); + barre_outils -> addAction(zoom_adapte); + barre_outils -> addAction(zoom_reset); + + // ajout de la barre d'outils a la fenetre principale + addToolBar(Qt::TopToolBarArea, barre_outils); +} + +/** + gere l'impression +*/ +void QETApp::dialogue_imprimer() { + QPrinter *qprin = new QPrinter(); + QPrintDialog *qpd = new QPrintDialog(qprin, this); + qpd -> exec(); +} + +void QETApp::dialogue_exporter() { + QString nom_fichier = QFileDialog::getSaveFileName( + this, + tr("Exporter vers le fichier"), + QDir::homePath(), + tr("Image PNG (*.png)") + ); + if (nom_fichier != "") { + if (!nom_fichier.endsWith(".png", Qt::CaseInsensitive)) nom_fichier += ".png"; + QFile fichier(nom_fichier); + QImage image = schemaEnCours() -> scene -> toImage(); + image.save(&fichier, "PNG"); + fichier.close(); + } +} + +/** + Methode enregistrant le schema dans le dernier nom de fichier connu. + Si aucun nom de fichier n'est connu, cette methode appelle la methode enregistrer_sous + @return true si l'enregistrement a reussi, false sinon +*/ +bool QETApp::enregistrer() { + if (!schemaEnCours()) return(false); + return(schemaEnCours() -> enregistrer()); +} + +/** + Cette methode demande un nom de fichier a l'utilisateur pour enregistrer le schema + Si aucun nom n'est entre, elle renvoie faux. + Si le nom ne se termine pas par l'extension .qet, celle-ci est ajoutee. + Si l'enregistrement reussit, le nom du fichier est conserve et la fonction renvoie true. + Sinon, faux est renvoye. + @return true si l'enregistrement a reussi, false sinon + @todo detecter le chemin du bureau automatiquement +*/ +bool QETApp::dialogue_enregistrer_sous() { + if (!schemaEnCours()) return(false); + return(schemaEnCours() -> enregistrer_sous()); +} + +/** + Cette methode cree un nouveau schema. + @return true si tout s'est bien passe ; false si vous executez cette fonction dans un univers non cartesien (en fait y'a pas de return(false) :p) +*/ +bool QETApp::nouveau() { + addSchemaVue(new SchemaVue(this)); + return(true); +} + +/** + Cette fonction demande un nom de fichier a ouvrir a l'utilisateur + @return true si l'ouverture a reussi, false sinon +*/ +bool QETApp::ouvrir() { + // demande un nom de fichier a ouvrir a l'utilisateur + QString nom_fichier = QFileDialog::getOpenFileName( + this, + tr("Ouvrir un fichier"), + QDir::homePath(), + tr("Schema QelectroTech (*.qet);;Fichiers XML (*.xml);;Tous les fichiers (*)") + ); + if (nom_fichier == "") return(false); + + // verifie que le fichier n'est pas deja ouvert + QString chemin_fichier = QFileInfo(nom_fichier).canonicalFilePath(); + foreach (QWidget *fenetre, workspace.windowList()) { + SchemaVue *fenetre_en_cours = qobject_cast(fenetre); + if (QFileInfo(fenetre_en_cours -> nom_fichier).canonicalFilePath() == chemin_fichier) { + workspace.setActiveWindow(fenetre); + return(false); + } + } + + // ouvre le fichier + SchemaVue *sv = new SchemaVue(this); + int code_erreur; + if (sv -> ouvrir(nom_fichier, &code_erreur)) { + addSchemaVue(sv); + return(true); + } else { + QString message_erreur; + switch(code_erreur) { + case 1: message_erreur = tr("Ce fichier n'existe pas."); break; + case 2: message_erreur = tr("Impossible de lire ce fichier."); break; + case 3: message_erreur = tr("Ce fichier n'est pas un document XML valide."); break; + case 4: message_erreur = tr("Une erreur s'est produite lors de l'ouverture du fichier."); break; + } + QMessageBox::warning(this, tr("Erreur"), message_erreur); + delete sv; + return(false); + } +} + +/** + Ferme le document courant + @return true si la fermeture du fichier a reussi, false sinon + @todo detecter les modifications et ne demander que si besoin est +*/ +bool QETApp::fermer() { + SchemaVue *sv = schemaEnCours(); + if (!sv) return(false); + bool fermeture_schema = sv -> close(); + if (fermeture_schema) delete sv; + return(fermeture_schema); +} + +/** + @return Le SchemaVue qui a le focus dans l'interface MDI +*/ +SchemaVue *QETApp::schemaEnCours() { + return(qobject_cast(workspace.activeWindow())); +} + +void QETApp::slot_couper() { + if(schemaEnCours()) schemaEnCours() -> couper(); +} + +void QETApp::slot_copier() { + if(schemaEnCours()) schemaEnCours() -> copier(); +} + +void QETApp::slot_coller() { + if(schemaEnCours()) schemaEnCours() -> coller(); +} + +void QETApp::slot_zoomPlus() { + if(schemaEnCours()) schemaEnCours() -> zoomPlus(); +} + +void QETApp::slot_zoomMoins() { + if(schemaEnCours()) schemaEnCours() -> zoomMoins(); +} + +void QETApp::slot_zoomFit() { + if(schemaEnCours()) schemaEnCours() -> zoomFit(); +} + +void QETApp::slot_zoomReset() { + if(schemaEnCours()) schemaEnCours() -> zoomReset(); +} + +void QETApp::slot_selectAll() { + if(schemaEnCours()) schemaEnCours() -> selectAll(); +} + +void QETApp::slot_selectNothing() { + if(schemaEnCours()) schemaEnCours() -> selectNothing(); +} + +void QETApp::slot_selectInvert() { + if(schemaEnCours()) schemaEnCours() -> selectInvert(); +} + +void QETApp::slot_supprimer() { + if(schemaEnCours()) schemaEnCours() -> supprimer(); +} + +void QETApp::slot_pivoter() { + if(schemaEnCours()) schemaEnCours() -> pivoter(); +} + +void QETApp::slot_setSelectionMode() { + if(schemaEnCours()) schemaEnCours() -> setSelectionMode(); +} + +void QETApp::slot_setVisualisationMode() { + if(schemaEnCours()) schemaEnCours() -> setVisualisationMode(); +} + +/** + gere les actions ayant besoin d'un document ouvert +*/ +void QETApp::slot_updateActions() { + SchemaVue *sv = schemaEnCours(); + bool document_ouvert = (sv != 0); + + // actions ayant juste besoin d'un document ouvert + fermer_fichier -> setEnabled(document_ouvert); + enr_fichier -> setEnabled(document_ouvert); + enr_fichier_sous -> setEnabled(document_ouvert); + importer -> setEnabled(document_ouvert); + exporter -> setEnabled(document_ouvert); + imprimer -> setEnabled(document_ouvert); + sel_tout -> setEnabled(document_ouvert); + sel_rien -> setEnabled(document_ouvert); + sel_inverse -> setEnabled(document_ouvert); + zoom_avant -> setEnabled(document_ouvert); + zoom_arriere -> setEnabled(document_ouvert); + zoom_adapte -> setEnabled(document_ouvert); + zoom_reset -> setEnabled(document_ouvert); + toggle_aa -> setEnabled(document_ouvert); + + // actions ayant aussi besoin d'un historique des actions + annuler -> setEnabled(document_ouvert); + refaire -> setEnabled(document_ouvert); + + // actions ayant aussi besoin d'elements selectionnes + bool elements_selectionnes = document_ouvert ? (sv -> scene -> selectedItems().size() > 0) : false; + couper -> setEnabled(elements_selectionnes); + copier -> setEnabled(elements_selectionnes); + supprimer -> setEnabled(elements_selectionnes); + pivoter -> setEnabled(elements_selectionnes); + + // action ayant aussi besoin d'un presse-papier plein + bool peut_coller = QApplication::clipboard() -> text() != QString(); + coller -> setEnabled(document_ouvert && peut_coller); + + // actions ayant aussi besoin d'un document ouvert et de la connaissance de son mode + if (!document_ouvert) { + mode_selection -> setEnabled(false); + mode_visualise -> setEnabled(false); + } else { + switch((int)(sv -> dragMode())) { + case QGraphicsView::NoDrag: + mode_selection -> setEnabled(false); + mode_visualise -> setEnabled(false); + break; + case QGraphicsView::ScrollHandDrag: + mode_selection -> setEnabled(true); + mode_visualise -> setEnabled(true); + mode_selection -> setChecked(false); + mode_visualise -> setChecked(true); + break; + case QGraphicsView::RubberBandDrag: + mode_selection -> setEnabled(true); + mode_visualise -> setEnabled(true); + mode_selection -> setChecked(true); + mode_visualise -> setChecked(false); + break; + } + } + + // actions ayant besoin de la connaissance de son mode + if (document_ouvert) toggle_aa -> setText(sv -> antialiased() ? tr("D\351sactiver l'&antialiasing") : tr("Activer l'&antialiasing")); + + slot_updateMenuFenetres(); +} + +void QETApp::addSchemaVue(SchemaVue *sv) { + if (!sv) return; + SchemaVue *s_v = schemaEnCours(); + bool maximise = ((!s_v) || (s_v -> windowState() & Qt::WindowMaximized)); + QWidget *p = workspace.addWindow(sv); + connect(sv, SIGNAL(selectionChanged()), this, SLOT(slot_updateActions())); + connect(sv, SIGNAL(modeChanged()), this, SLOT(slot_updateActions())); + if (maximise) p -> showMaximized(); + else p -> show(); +} + +void QETApp::slot_updateMenuFenetres() { + // nettoyage du menu + menu_fenetres -> clear(); + + // actions de fermeture + menu_fenetres -> addAction(fermer_fichier); + //menu_fenetres -> addAction(closeAllAct); + + // actions de reorganisation des fenetres + menu_fenetres -> addSeparator(); + menu_fenetres -> addAction(f_mosaique); + menu_fenetres -> addAction(f_cascade); + menu_fenetres -> addAction(f_reorganise); + + // actiosn de deplacement entre les fenetres + menu_fenetres -> addSeparator(); + menu_fenetres -> addAction(f_suiv); + menu_fenetres -> addAction(f_prec); + + // liste des fenetres + QList fenetres = workspace.windowList(); + if (!fenetres.isEmpty()) menu_fenetres -> addSeparator(); + for (int i = 0 ; i < fenetres.size() ; ++ i) { + SchemaVue *sv = qobject_cast(fenetres.at(i)); + QAction *action = menu_fenetres -> addAction(sv -> windowTitle().left(sv -> windowTitle().length()-3)); + action -> setCheckable(true); + action -> setChecked(sv == schemaEnCours()); + connect(action, SIGNAL(triggered()), &windowMapper, SLOT(map())); + windowMapper.setMapping(action, sv); + } +} diff --git a/qetapp.h b/qetapp.h new file mode 100644 index 000000000..f20dbf61b --- /dev/null +++ b/qetapp.h @@ -0,0 +1,122 @@ +#ifndef QETAPP_H + #define QETAPP_H + #include + class SchemaVue; + class PanelAppareils; + /** + Cette classe represente la fenetre principale de QElectroTech et, + ipso facto, la plus grande partie de l'interface graphique de QElectroTech. + Il s'agit d'un objet QMainWindow avec un objet « Schema » en guise de widget central + et un « Panel d'Appareils » en guise de widget « Dock ». + */ + class QETApp : public QMainWindow { + Q_OBJECT + public: + QETApp(QWidget *parent=0); + void closeEvent(QCloseEvent * event ); + void addSchemaVue(SchemaVue *); + + public slots: + void systray(QSystemTrayIcon::ActivationReason raison); + void systrayReduire(); + void systrayRestaurer(); + void quitter(); + void toggleFullScreen(); + void toggleAntialiasing(); + void aPropos(); + void dialogue_imprimer(); + void dialogue_exporter(); + bool dialogue_enregistrer_sous(); + bool enregistrer(); + bool nouveau(); + bool ouvrir(); + bool fermer(); + + protected: + // Actions faisables au travers de menus dans l'application QElectroTech + QAction *mode_selection; + QAction *mode_visualise; + QAction *nouveau_fichier; + QAction *ouvrir_fichier; + QAction *fermer_fichier; + QAction *enr_fichier; + QAction *enr_fichier_sous; + QAction *importer; + QAction *exporter; + QAction *imprimer; + QAction *quitter_qet; + QAction *annuler; + QAction *refaire; + QAction *couper; + QAction *copier; + QAction *coller; + QAction *sel_tout; + QAction *sel_rien; + QAction *sel_inverse; + QAction *supprimer; + QAction *selectionner; + QAction *pivoter; + QAction *poser_fil; + QAction *masquer_appli; + QAction *restaurer_appli; + QAction *zoom_avant; + QAction *zoom_arriere; + QAction *zoom_adapte; + QAction *zoom_reset; + QAction *a_propos_de_qet; + QAction *a_propos_de_qt; + QAction *configurer; + QAction *entrer_pe; + QAction *sortir_pe; + QAction *toggle_aa; + QAction *f_mosaique; + QAction *f_cascade; + QAction *f_reorganise; + QAction *f_prec; + QAction *f_suiv; + + void actions(); + // menus variables + QAction *menu_systray_masquer_restaurer; + + private: + QWorkspace workspace; + SchemaVue *schemaEnCours(); + QSignalMapper windowMapper; + /// Dock pour le Panel d'Appareils + QDockWidget *qdw_pa; + /// Panel d'Appareils + PanelAppareils *pa; + /// Elements de menus pour l'icone du systray + QMenu *menu_systray; + QAction *systray_masquer; + QAction * config_fullscreen; + QAction *systray_quitter; + QMenu *menu_fenetres; + /// Icone dans le systray + QSystemTrayIcon *qsti; + /// Geometrie de la fenetre principale + QByteArray wg; + void menus(); + void toolbar(); + QToolBar *barre_outils; + + private slots: + void slot_couper(); + void slot_copier(); + void slot_coller(); + void slot_zoomPlus(); + void slot_zoomMoins(); + void slot_zoomFit(); + void slot_zoomReset(); + void slot_selectAll(); + void slot_selectNothing(); + void slot_selectInvert(); + void slot_supprimer(); + void slot_pivoter(); + void slot_setSelectionMode(); + void slot_setVisualisationMode(); + void slot_updateActions(); + void slot_updateMenuFenetres(); + }; +#endif diff --git a/schema.cpp b/schema.cpp new file mode 100644 index 000000000..600b65088 --- /dev/null +++ b/schema.cpp @@ -0,0 +1,322 @@ +#include +#include "conducteur.h" +#include "contacteur.h" +#include "elementperso.h" +#include "schema.h" + +/** + Constructeur + @param parent Le QObject parent du schema +*/ +Schema::Schema(QObject *parent) : QGraphicsScene(parent) { + setBackgroundBrush(Qt::white); + poseur_de_conducteur = new QGraphicsLineItem(0, 0); + poseur_de_conducteur -> setZValue(1000000); + QPen t; + t.setColor(Qt::black); + t.setWidthF(1.5); + t.setStyle(Qt::DashLine); + poseur_de_conducteur -> setPen(t); + poseur_de_conducteur -> setLine(QLineF(QPointF(0.0, 0.0), QPointF(0.0, 0.0))); + doit_dessiner_grille = true; + connect(this, SIGNAL(changed(const QList &)), this, SLOT(slot_checkSelectionChange())); +} + +/** + 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 +*/ +void Schema::drawBackground(QPainter *p, const QRectF &r) { + p -> save(); + + // desactive tout antialiasing + p -> setRenderHint(QPainter::Antialiasing, false); + p -> setRenderHint(QPainter::TextAntialiasing, false); + p -> setRenderHint(QPainter::SmoothPixmapTransform, false); + + // dessine un fond blanc + p -> setPen(Qt::NoPen); + p -> setBrush(Qt::white); + p -> drawRect(r); + + if (doit_dessiner_grille) { + // 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 % GRILLE_X) ++ g_x; + int g_y = (int)ceil(r.y()); + while (g_y % GRILLE_Y) ++ g_y; + + for (int gx = g_x ; gx < limite_x ; gx += GRILLE_X) { + for (int gy = g_y ; gy < limite_y ; gy += GRILLE_Y) { + p -> drawPoint(gx, gy); + } + } + + p -> drawLine(0, 0, 0, 10); + p -> drawLine(0, 0, 10, 0); + } + p -> restore(); +} + +QImage Schema::toImage() { + + QRectF vue = itemsBoundingRect(); + // la marge = 5 % de la longueur necessaire + qreal marge = 0.05 * vue.width(); + vue.translate(-marge, -marge); + vue.setWidth(vue.width() + 2.0 * marge); + vue.setHeight(vue.height() + 2.0 * marge); + QSize dimensions_image = vue.size().toSize(); + + QImage pix = QImage(dimensions_image, QImage::Format_RGB32); + QPainter p; + bool painter_ok = p.begin(&pix); + if (!painter_ok) return(QImage()); + + // rendu antialiase + p.setRenderHint(QPainter::Antialiasing, true); + p.setRenderHint(QPainter::TextAntialiasing, true); + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + + render(&p, pix.rect(), vue, Qt::KeepAspectRatio); + p.end(); + return(pix); +} + +/** + Exporte tout ou partie du schema + @param schema 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) +*/ +QDomDocument Schema::toXml(bool schema) { + // document + QDomDocument document; + + // racine de l'arbre XML + QDomElement racine = document.createElement("schema"); + + // proprietes du schema + if (schema) { + if (!auteur.isNull()) racine.setAttribute("auteur", auteur); + if (!date.isNull()) racine.setAttribute("date", date.toString("yyyyMMdd")); + if (!titre.isNull()) racine.setAttribute("titre", titre); + } + 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 deux listes : une qui contient les elements, une qui contient les conducteurs + QList liste_elements; + QList liste_conducteurs; + + + // Determine les elements a « XMLiser » + foreach(QGraphicsItem *qgi, items()) { + if (Element *elmt = qgraphicsitem_cast(qgi)) { + if (schema) liste_elements << elmt; + else if (elmt -> isSelected()) liste_elements << elmt; + } else if (Conducteur *f = qgraphicsitem_cast(qgi)) { + if (schema) liste_conducteurs << f; + // lorsqu'on n'exporte pas tout le schema, 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 -> borne1 -> parentItem() -> isSelected() && f -> borne2 -> parentItem() -> isSelected()) liste_conducteurs << f; + } + } + + // enregistrement des elements + if (liste_elements.isEmpty()) return(document); + int id_borne = 0; + // table de correspondance entre les adresses des bornes et leurs ids + QHash table_adr_id; + QDomElement elements = document.createElement("elements"); + foreach(Element *elmt, liste_elements) { + QDomElement element = document.createElement("element"); + + // type, position, selection et orientation + element.setAttribute("type", QFileInfo(elmt -> typeId()).fileName()); + element.setAttribute("x", elmt -> pos().x()); + element.setAttribute("y", elmt -> pos().y()); + if (elmt -> isSelected()) element.setAttribute("selected", "selected"); + element.setAttribute("sens", elmt -> orientation() ? "true" : "false"); + + // enregistrements des bornes de chaque appareil + QDomElement bornes = document.createElement("bornes"); + // pour chaque enfant de l'element + foreach(QGraphicsItem *child, elmt -> children()) { + // si cet enfant est une borne + if (Borne *p = qgraphicsitem_cast(child)) { + // alors on enregistre la borne + QDomElement borne = p -> toXml(document); + borne.setAttribute("id", id_borne); + table_adr_id.insert(p, id_borne ++); + bornes.appendChild(borne); + } + } + element.appendChild(bornes); + + /** + @todo appeler une methode virtuelle de la classe Element qui permettra + aux developpeurs d'elements de personnaliser l'enregistrement des elements + */ + elements.appendChild(element); + } + racine.appendChild(elements); + + // enregistrement des conducteurs + if (liste_conducteurs.isEmpty()) return(document); + QDomElement conducteurs = document.createElement("conducteurs"); + foreach(Conducteur *f, liste_conducteurs) { + QDomElement conducteur = document.createElement("conducteur"); + conducteur.setAttribute("borne1", table_adr_id.value(f -> borne1)); + conducteur.setAttribute("borne2", table_adr_id.value(f -> borne2)); + conducteurs.appendChild(conducteur); + } + racine.appendChild(conducteurs); + + // on retourne le document XML ainsi genere + return(document); +} + +void Schema::reset() { + /// @todo implementer cette fonction +} + +/** + 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 + @return true si l'import a reussi, false sinon +*/ +bool Schema::fromXml(QDomDocument &document, QPointF position) { + QDomElement racine = document.documentElement(); + // le premier element doit etre un schema + if (racine.tagName() != "schema") return(false); + // lecture des attributs de ce schema + auteur = racine.attribute("auteur"); + titre = racine.attribute("titre"); + date = QDate::fromString(racine.attribute("date"), "yyyyMMdd"); + + // si la racine n'a pas d'enfant : le chargement est fini (schema vide) + if (racine.firstChild().isNull()) return(true); + + // chargement de tous les Elements du fichier XML + QList elements_ajoutes; + //uint nb_elements = 0; + QHash< int, Borne *> table_adr_id; + QHash< int, Borne *> &ref_table_adr_id = table_adr_id; + for (QDomNode node = racine.firstChild() ; !node.isNull() ; node = node.nextSibling()) { + // on s'interesse a l'element XML "elements" (= groupe d'elements) + QDomElement elmts = node.toElement(); + if(elmts.isNull() || elmts.tagName() != "elements") continue; + // parcours des enfants de l'element XML "elements" + for (QDomNode n = elmts.firstChild() ; !n.isNull() ; n = n.nextSibling()) { + // on s'interesse a l'element XML "element" (elements eux-memes) + QDomElement e = n.toElement(); + if (e.isNull() || !Element::valideXml(e)) continue; + Element *element_ajoute; + if ((element_ajoute = elementFromXml(e, ref_table_adr_id)) != NULL) elements_ajoutes << element_ajoute; + else qDebug("Le chargement d'un element a echoue"); + } + } + + // aucun Element n'a ete ajoute - inutile de chercher des conducteurs - le chargement est fini + if (!elements_ajoutes.size()) return(true); + + // gere la translation des nouveaux elements 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; + foreach (Element *elmt_ajoute, elements_ajoutes) { + QPointF csg = elmt_ajoute -> mapToScene(elmt_ajoute -> 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 *elmt_ajoute, elements_ajoutes) { + elmt_ajoute -> setPos(elmt_ajoute -> pos().x() + diff_x, elmt_ajoute -> pos().y() + diff_y); + } + } + + // chargement de tous les Conducteurs du fichier XML + for (QDomNode node = racine.firstChild() ; !node.isNull() ; node = node.nextSibling()) { + // on s'interesse a l'element XML "conducteurs" (= groupe de conducteurs) + QDomElement conducteurs = node.toElement(); + if(conducteurs.isNull() || conducteurs.tagName() != "conducteurs") continue; + // parcours des enfants de l'element XML "conducteurs" + for (QDomNode n = conducteurs.firstChild() ; !n.isNull() ; n = n.nextSibling()) { + // on s'interesse a l'element XML "element" (elements eux-memes) + QDomElement f = n.toElement(); + if (f.isNull() || !Conducteur::valideXml(f)) continue; + // verifie que les bornes que le conducteur relie sont connues + int id_p1 = f.attribute("borne1").toInt(); + int id_p2 = f.attribute("borne2").toInt(); + if (table_adr_id.contains(id_p1) && table_adr_id.contains(id_p2)) { + // pose le conducteur... si c'est possible + Borne *p1 = table_adr_id.value(id_p1); + Borne *p2 = table_adr_id.value(id_p2); + if (p1 != p2) { + bool peut_poser_conducteur = true; + bool cia = ((Element *)p2 -> parentItem()) -> connexionsInternesAcceptees(); + if (!cia) foreach(QGraphicsItem *item, p2 -> parentItem() -> children()) if (item == p1) peut_poser_conducteur = false; + if (peut_poser_conducteur) new Conducteur(table_adr_id.value(id_p1), table_adr_id.value(id_p2), 0, this); + } + } else qDebug() << "Le chargement du conducteur" << id_p1 << id_p2 << "a echoue"; + } + } + return(true); +} + +/** + Ajoute au schema l'Element correspondant au QDomElement passe en parametre + @param e QDomElement a analyser + @param table_id_adr Table de correspondance entre les entiers et les bornes + @return true si l'ajout a parfaitement reussi, false sinon +*/ +Element *Schema::elementFromXml(QDomElement &e, QHash &table_id_adr) { + // cree un element dont le type correspond à l'id type + QString type = e.attribute("type"); + int etat; + Element *nvel_elmt = new ElementPerso(type, 0, 0, &etat); + /*switch(e.attribute("type").toInt()) { + case 0: nvel_elmt = new Contacteur(); break; + case 1: nvel_elmt = new DEL(); break; + case 2: nvel_elmt = new Entree(); break; + }*/ + if (etat != 0) return(false); + bool retour = nvel_elmt -> fromXml(e, table_id_adr); + if (!retour) { + delete nvel_elmt; + } else { + // ajout de l'element au schema + addItem(nvel_elmt); + nvel_elmt -> setPos(e.attribute("x").toDouble(), e.attribute("y").toDouble()); + nvel_elmt -> setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); + if (e.attribute("sens") == "false") nvel_elmt -> invertOrientation(); + nvel_elmt -> setSelected(e.attribute("selected") == "selected"); + } + return(retour ? nvel_elmt : NULL); +} + +void Schema::slot_checkSelectionChange() { + static QList cache_selecteditems = QList(); + QList selecteditems = selectedItems(); + if (cache_selecteditems != selecteditems) emit(selectionChanged()); + cache_selecteditems = selecteditems; +} diff --git a/schema.h b/schema.h new file mode 100644 index 000000000..693126691 --- /dev/null +++ b/schema.h @@ -0,0 +1,46 @@ +#ifndef SCHEMA_H + #define SCHEMA_H + #define GRILLE_X 10 + #define GRILLE_Y 10 + #include + #include + class Element; + class Borne; + class Schema : public QGraphicsScene { + Q_OBJECT + public: + Schema(QObject * = 0); + void drawBackground(QPainter *, const QRectF &); + inline void poseConducteur(bool pf) { + if (pf) { + if (!poseur_de_conducteur -> scene()) addItem(poseur_de_conducteur); + } else { + if (poseur_de_conducteur -> scene()) removeItem(poseur_de_conducteur); + } + } + inline void setDepart (QPointF d) { poseur_de_conducteur -> setLine(QLineF(d, poseur_de_conducteur -> line().p2())); } + inline void setArrivee(QPointF a) { poseur_de_conducteur -> setLine(QLineF(poseur_de_conducteur -> line().p1(), a)); } + QImage toImage(); + QDomDocument toXml(bool = true); + bool fromXml(QDomDocument &, QPointF = QPointF()); + void reset(); + QGraphicsItem *getElementById(uint id); + + private: + QGraphicsLineItem *poseur_de_conducteur; + bool doit_dessiner_grille; + // elements du cartouche + QString auteur; + QDate date; + QString titre; + QString folio; // vraiment necessaire ce truc ? + QString nom_fichier; // meme remarque + Element *elementFromXml(QDomElement &e, QHash &); + + private slots: + void slot_checkSelectionChange(); + + signals: + void selectionChanged(); + }; +#endif diff --git a/schemavue.cpp b/schemavue.cpp new file mode 100644 index 000000000..cea1ab8cf --- /dev/null +++ b/schemavue.cpp @@ -0,0 +1,431 @@ +#include "schemavue.h" +#include "schema.h" +#include "elementperso.h" +#include "contacteur.h" +#include "del.h" +#include "entree.h" + +/** + Initialise le SchemaVue +*/ +void SchemaVue::initialise() { + setInteractive(true); + setAntialiasing(true); + setScene(scene = new Schema(this)); + setDragMode(RubberBandDrag); + setAcceptDrops(true); + setWindowTitle(tr("Nouveau sch\351ma") + "[*]"); + connect(scene, SIGNAL(selectionChanged()), this, SLOT(slot_selectionChanged())); +} + +/** + Constructeur par defaut +*/ +SchemaVue::SchemaVue() : QGraphicsView() { + initialise(); +} + +/** + Constructeur + @param parent Le QWidegt parent de cette vue de schema +*/ +SchemaVue::SchemaVue(QWidget *parent) : QGraphicsView(parent) { + initialise(); +} + +/** + Permet de savoir si le rendu graphique du SchemaVue est antialiase ou non. + @return Un booleen indiquant si le SchemaVue est antialiase +*/ +bool SchemaVue::antialiased() const { + return(antialiasing); +} + +/** + Active ou desactive l'antialiasing pour le rendu graphique du SchemaVue. + @param aa un booleen indiquant si le SchemaVue doit etre antialiase ou non +*/ +void SchemaVue::setAntialiasing(bool aa) { + antialiasing = aa; + setRenderHint(QPainter::Antialiasing, aa); + setRenderHint(QPainter::TextAntialiasing, aa); + setRenderHint(QPainter::SmoothPixmapTransform, aa); + repaint(); +} + +/** + appelle la methode select sur tous les elements de la liste d'elements + @todo modifier selectAll pour l'integration des conducteurs +*/ +void SchemaVue::selectAll() { + if (scene -> items().isEmpty()) return; + foreach (QGraphicsItem *item, scene -> items()) item -> setSelected(true); +} + +/** + appelle la methode deselect sur tous les elements de la liste d'elements + @todo modifier selectNothing pour l'integration des conducteurs +*/ +void SchemaVue::selectNothing() { + if (scene -> items().isEmpty()) return; + foreach (QGraphicsItem *item, scene -> items()) item -> setSelected(false); +} + +/** + Inverse l'etat de selection de tous les elements de la liste d'elements + @todo modifier selectInvert pour l'integration des conducteurs + */ +void SchemaVue::selectInvert() { + if (scene -> items().isEmpty()) return; + foreach (QGraphicsItem *item, scene -> items()) item -> setSelected(!item -> isSelected()); +} + +/** + Supprime les composants selectionnes +*/ +void SchemaVue::supprimer() { + QList garbage_elmt; + QList garbage_conducteurs; + + // useless but careful : creating two lists : one for wires, one for elements + foreach (QGraphicsItem *qgi, scene -> selectedItems()) { + if (!garbage_elmt.contains(qgi)) garbage_elmt.append(qgi); + // pour chaque enfant de l'element + foreach (QGraphicsItem *child, qgi -> children()) { + // si cet enfant est une borne + if (Borne *p = qgraphicsitem_cast(child)) { + // alors chaque conducteur de la borne est recense + foreach (Conducteur *f, p -> conducteurs()) { + if (!garbage_conducteurs.contains(f)) garbage_conducteurs.append(f); + } + } + } + } + scene -> clearSelection(); + + // "destroying" the wires, removing them from the scene and stocking them into the « garbage » + foreach (QGraphicsItem *qgi, garbage_conducteurs) { + if (Conducteur *f = qgraphicsitem_cast(qgi)) { + f -> destroy(); + scene -> removeItem(f); + throwToGarbage(f); + } + } + + // removing the elements from the scene and stocking them into the « garbage » + foreach (QGraphicsItem *qgi, garbage_elmt) { + scene -> removeItem(qgi); + throwToGarbage(qgi); + } + resetCachedContent(); + QTimer::singleShot(5000, this, SLOT(flushGarbage())); +} + +/** + Envoie un item vers le "garbage" pour qu'il soit supprime plus tard + @param qgi L'item a supprimer +*/ +void SchemaVue::throwToGarbage(QGraphicsItem *qgi) { + // pas de doublon dans le garbage (sinon ca va sentir la segfault) + bool qgi_deja_dans_le_garbage = false; + foreach(QGraphicsItem *gbg_qgi, garbage) { + if ((void *)gbg_qgi == (void *)qgi) { + qgi_deja_dans_le_garbage = true; + break; + } + } + if (!qgi_deja_dans_le_garbage) garbage.append(qgi); +} + +/** + Supprime tous les elements du "garbage" +*/ +void SchemaVue::flushGarbage() { + foreach(QGraphicsItem *qgi, garbage) { + delete(qgi); + garbage.removeAll(qgi); + } +} + +/** + Pivote les composants selectionnes +*/ +void SchemaVue::pivoter() { + if (scene -> selectedItems().isEmpty()) return; + foreach (QGraphicsItem *item, scene -> selectedItems()) { + if (Element *elt = qgraphicsitem_cast(item)) { + elt -> invertOrientation(); + elt -> update(); + } + } +} + +/** + accepte ou refuse le drag'n drop en fonction du type de donnees entrant + @param e le QDragEnterEvent correspondant au drag'n drop tente + @todo trouver un MIME Type plus adapte +*/ +void SchemaVue::dragEnterEvent(QDragEnterEvent *e) { + if (e -> mimeData() -> hasFormat("text/plain")) e -> acceptProposedAction(); + else e-> ignore(); +} + +/** + gere les dragleaveevent + @param e le QDragEnterEvent correspondant au drag'n drop sortant +*/ +void SchemaVue::dragLeaveEvent(QDragLeaveEvent *) {} + +/** + accepte ou refuse le drag'n drop en fonction du type de donnees entrant + @param e le QDragMoveEvent correspondant au drag'n drop tente +*/ +void SchemaVue::dragMoveEvent(QDragMoveEvent *e) { + if (e -> mimeData() -> hasFormat("text/plain")) e -> acceptProposedAction(); + else e-> ignore(); +} + +/** + gere les depots (drop) acceptes sur le Schema + @param e le QDropEvent correspondant au drag'n drop effectue + @todo Ajouter directement l'objet Element a la scene lorsque le drag'n drop aura ete ameliore +*/ +void SchemaVue::dropEvent(QDropEvent *e) { + QString fichier = e -> mimeData() -> text(); + int etat; + Element *el = new ElementPerso(fichier, 0, 0, &etat); + if (etat != 0) delete el; + else { + scene -> addItem(el); + el -> setPos(mapToScene(e -> pos().x(), e -> pos().y())); + el -> setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); + } +} + +/** + Passe le Schema en mode visualisation +*/ +void SchemaVue::setVisualisationMode() { + setDragMode(ScrollHandDrag); + emit(modeChanged()); +} + +/** + Passe le Schema en mode Selection +*/ +void SchemaVue::setSelectionMode() { + setDragMode(RubberBandDrag); + setCursor(Qt::ArrowCursor); + emit(modeChanged()); +} + +/** + Agrandit le schema (+33% = inverse des -25 % de zoomMoins()) +*/ +void SchemaVue::zoomPlus() { + scale(4.0/3.0, 4.0/3.0); +} + +/** + Retrecit le schema (-25% = inverse des +33 % de zoomPlus()) +*/ +void SchemaVue::zoomMoins() { + scale(0.75, 0.75); +} + +/** + Agrandit ou rectrecit le schema de facon a ce que tous les elements du + schema soient visibles a l'ecran. S'il n'y a aucun element sur le schema, + le zoom est reinitialise +*/ +void SchemaVue::zoomFit() { + if (scene -> items().isEmpty()) { + zoomReset(); + return; + } + QRectF vue = scene -> itemsBoundingRect(); + // la marge = 5 % de la longueur necessaire + qreal marge = 0.05 * vue.width(); + vue.translate(-marge, -marge); + vue.setWidth(vue.width() + 2.0 * marge); + vue.setHeight(vue.height() + 2.0 * marge); + fitInView(vue, Qt::KeepAspectRatio); +} + +/** + Reinitialise le zoom +*/ +void SchemaVue::zoomReset() { + resetMatrix(); +} + +/** + copie les elements selectionnes du schema dans le presse-papier puis les supprime +*/ +void SchemaVue::couper() { + copier(); + supprimer(); +} + +/** + copie les elements selectionnes du schema dans le presse-papier +*/ +void SchemaVue::copier() { + QClipboard *presse_papier = QApplication::clipboard(); + QString contenu_presse_papier = scene -> toXml(false).toString(4); + if (presse_papier -> supportsSelection()) presse_papier -> setText(contenu_presse_papier, QClipboard::Selection); + presse_papier -> setText(contenu_presse_papier); +} + +/** + importe les elements contenus dans le presse-papier dans le schema +*/ +void SchemaVue::coller() { + QString texte_presse_papier; + QDomDocument document_xml; + if ((texte_presse_papier = QApplication::clipboard() -> text()) == QString()) return; + if (!document_xml.setContent(texte_presse_papier)) return; + scene -> fromXml(document_xml); +} + +/** + gere les clics et plus particulierement le clic du milieu (= coller pour X11) +*/ +void SchemaVue::mousePressEvent(QMouseEvent *e) { + if (e -> buttons() == Qt::MidButton) { + QString texte_presse_papier; + QDomDocument document_xml; + if ((texte_presse_papier = QApplication::clipboard() -> text(QClipboard::Selection)) == QString()) return; + if (!document_xml.setContent(texte_presse_papier)) return; + scene -> fromXml(document_xml, mapToScene(e -> pos())); + } + QGraphicsView::mousePressEvent(e); +} + +/** + Ouvre un fichier *.qet dans cette SchemaVue + @param nom_fichier Nom du fichier a ouvrir + @param erreur Si le pointeur est specifie, cet entier est mis a 0 en cas de reussite de l'ouverture, 1 si le fichier n'existe pas, 2 si le fichier n'est pas lisible, 3 si le fichier n'est pas un element XML, 4 si l'ouverture du fichier a echoue pour une autre raison (c'est pas ca qui manque ^^) + @return true si l'ouverture a reussi, false sinon +*/ +bool SchemaVue::ouvrir(QString n_fichier, int *erreur) { + // verifie l'existence du fichier + if (!QFileInfo(n_fichier).exists()) { + if (erreur != NULL) *erreur = 1; + return(false); + } + + // ouvre le fichier + QFile fichier(n_fichier); + if (!fichier.open(QIODevice::ReadOnly)) { + if (erreur != NULL) *erreur = 2; + return(false); + } + + // lit son contenu dans un QDomDocument + QDomDocument document; + if (!document.setContent(&fichier)) { + if (erreur != NULL) *erreur = 3; + fichier.close(); + return(false); + } + fichier.close(); + + // construit le schema a partir du QDomDocument + QDomDocument &doc = document; + if (scene -> fromXml(doc)) { + if (erreur != NULL) *erreur = 0; + nom_fichier = n_fichier; + setWindowTitle(nom_fichier + "[*]"); + return(true); + } else { + if (erreur != NULL) *erreur = 4; + return(false); + } +} + +void SchemaVue::slot_selectionChanged() { + emit(selectionChanged()); +} + +void SchemaVue::closeEvent(QCloseEvent *event) { + // demande d'abord a l'utilisateur s'il veut enregistrer le schema en cours + QMessageBox::StandardButton reponse = QMessageBox::question( + this, + tr("Enregistrer le sch\351ma en cours ?"), + tr("Voulez-vous enregistrer le sch\351ma en cours ?"), + QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, + QMessageBox::Cancel + ); + bool retour; + switch(reponse) { + case QMessageBox::Cancel: retour = false; break; // l'utilisateur annule : echec de la fermeture + case QMessageBox::Yes: retour = enregistrer(); break; // l'utilisateur dit oui : la reussite depend de l'enregistrement + default: retour = true; // l'utilisateur dit non ou ferme le dialogue: c'est reussi + } + if (retour) event -> accept(); + else event -> ignore(); +} + +/** + Methode enregistrant le schema dans le dernier nom de fichier connu. + Si aucun nom de fichier n'est connu, cette methode appelle la methode enregistrer_sous + @return true si l'enregistrement a reussi, false sinon +*/ +bool SchemaVue::enregistrer() { + if (nom_fichier == QString()) return(enregistrer_sous()); + else return(private_enregistrer(nom_fichier)); +} + +/** + Cette methode demande un nom de fichier a l'utilisateur pour enregistrer le schema + Si aucun nom n'est entre, elle renvoie faux. + Si le nom ne se termine pas par l'extension .qet, celle-ci est ajoutee. + Si l'enregistrement reussit, le nom du fichier est conserve et la fonction renvoie true. + Sinon, faux est renvoye. + @return true si l'enregistrement a reussi, false sinon + @todo detecter le chemin du bureau automatiquement +*/ +bool SchemaVue::enregistrer_sous() { + // demande un nom de fichier a l'utilisateur pour enregistrer le schema + QString n_fichier = QFileDialog::getSaveFileName( + this, + tr("Enregistrer sous"), + QDir::homePath(), + tr("Schema QelectroTech (*.qet)") + ); + // si aucun nom n'est entre, renvoie faux. + if (n_fichier == "") return(false); + // si le nom ne se termine pas par l'extension .qet, celle-ci est ajoutee + if (!n_fichier.endsWith(".qet", Qt::CaseInsensitive)) n_fichier += ".qet"; + // tente d'enregistrer le fichier + bool resultat_enregistrement = private_enregistrer(n_fichier); + // si l'enregistrement reussit, le nom du fichier est conserve + if (resultat_enregistrement) { + nom_fichier = n_fichier; + setWindowTitle(nom_fichier + "[*]"); + } + // retourne un booleen representatif de la reussite de l'enregistrement + return(resultat_enregistrement); +} + +/** + Methode privee gerant l'enregistrement du fichier XML. S'il n'est pas possible + d'ecrire dans le fichier, cette fonction affiche un message d'erreur et renvoie false. + Autrement, elle renvoie true. + @param nom_fichier Nom du fichier dans lequel l'arbre XML doit etre ecrit + @return true si l'enregistrement a reussi, false sinon +*/ +bool SchemaVue::private_enregistrer(QString &n_fichier) { + QFile fichier(n_fichier); + if (!fichier.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::warning(this, tr("Erreur"), tr("Impossible d'ecrire dans ce fichier")); + return(false); + } + QTextStream out(&fichier); + out.setCodec("UTF-8"); + out << scene -> toXml().toString(4); + fichier.close(); + return(true); +} + diff --git a/schemavue.h b/schemavue.h new file mode 100644 index 000000000..ecd8c6135 --- /dev/null +++ b/schemavue.h @@ -0,0 +1,69 @@ +#ifndef SCHEMAVUE_H + #define SCHEMAVUE_H + #include + class Schema; + #include "element.h" + #include "conducteur.h" + #define TAILLE_GRILLE 10 + /** + Classe representant un SchemaVue electrique + @todo creer une structure capable de retenir les differents composants du SchemaVue : elements, fils, indications eventuelles => revoir les SchemaVues + */ + class SchemaVue : public QGraphicsView { + Q_OBJECT + public: + // constructeurs + SchemaVue(); + SchemaVue(QWidget * = 0); + + // nouveaux attributs + Schema *scene; + + // methodes publiques + bool antialiased() const; + void setAntialiasing(bool); + bool ouvrir(QString, int * = NULL); + void closeEvent(QCloseEvent *); + QString nom_fichier; + bool enregistrer(); + bool enregistrer_sous(); + + private: + bool private_enregistrer(QString &); + void initialise(); + bool antialiasing; // booleen indiquant s'il faut effectuer un antialiasing sur le rendu graphique du SchemaVue + QList garbage; + + void throwToGarbage(QGraphicsItem *); + void mousePressEvent(QMouseEvent *); + void dragEnterEvent(QDragEnterEvent *); + void dragLeaveEvent(QDragLeaveEvent *); + void dragMoveEvent(QDragMoveEvent *); + void dropEvent(QDropEvent *); + + signals: + void selectionChanged(); + void antialiasingChanged(); + void modeChanged(); + + public slots: + void selectNothing(); + void selectAll(); + void selectInvert(); + void supprimer(); + void pivoter(); + void setVisualisationMode(); + void setSelectionMode(); + void zoomPlus(); + void zoomMoins(); + void zoomFit(); + void zoomReset(); + void couper(); + void copier(); + void coller(); + + private slots: + void flushGarbage(); + void slot_selectionChanged(); + }; +#endif