From 5cadf173c7b73460b62409c81568fc8999177d52 Mon Sep 17 00:00:00 2001 From: xavierqet Date: Fri, 27 Oct 2006 15:47:22 +0000 Subject: [PATCH] Import initial git-svn-id: svn+ssh://svn.tuxfamily.org/svnroot/qet/qet/trunk@1 bfdf4180-ca20-0410-9c96-a3a8aa849046 --- aboutqet.cpp | 122 +++++++ aboutqet.h | 18 + borne.cpp | 412 ++++++++++++++++++++++ borne.h | 81 +++++ conducteur.cpp | 198 +++++++++++ conducteur.h | 36 ++ contacteur.cpp | 56 +++ contacteur.h | 16 + del.cpp | 63 ++++ del.h | 16 + element.cpp | 276 +++++++++++++++ element.h | 50 +++ elementfixe.cpp | 82 +++++ elementfixe.h | 15 + elementperso.cpp | 196 +++++++++++ elementperso.h | 30 ++ elements/contacteur.elmt | 8 + elements/del.elmt | 9 + elements/entree.elmt | 6 + entree.cpp | 62 ++++ entree.h | 16 + gnugpl.txt | 342 ++++++++++++++++++ ico/button_cancel.png | Bin 0 -> 883 bytes ico/button_ok.png | Bin 0 -> 769 bytes ico/configure.png | Bin 0 -> 1055 bytes ico/copy.png | Bin 0 -> 777 bytes ico/cut.png | Bin 0 -> 804 bytes ico/delete.png | Bin 0 -> 951 bytes ico/editdelete.png | Bin 0 -> 892 bytes ico/entrer_fs.png | Bin 0 -> 785 bytes ico/exit.png | Bin 0 -> 830 bytes ico/export.png | Bin 0 -> 626 bytes ico/fileclose.png | Bin 0 -> 1027 bytes ico/import.png | Bin 0 -> 851 bytes ico/masquer.png | Bin 0 -> 609 bytes ico/move.png | Bin 0 -> 235 bytes ico/new.png | Bin 0 -> 686 bytes ico/open.png | Bin 0 -> 1080 bytes ico/paste.png | Bin 0 -> 979 bytes ico/pivoter.png | Bin 0 -> 749 bytes ico/print.png | Bin 0 -> 684 bytes ico/qelectrotech.png | Bin 0 -> 6772 bytes ico/qelectrotech.svg | 13 + ico/qet.png | Bin 0 -> 8682 bytes ico/qet.svg | 12 + ico/qt.png | Bin 0 -> 2134 bytes ico/redo.png | Bin 0 -> 736 bytes ico/restaurer.png | Bin 0 -> 679 bytes ico/save.png | Bin 0 -> 838 bytes ico/saveas.png | Bin 0 -> 1104 bytes ico/select.png | Bin 0 -> 411 bytes ico/sortir_fs.png | Bin 0 -> 772 bytes ico/toolbars.png | Bin 0 -> 1160 bytes ico/undo.png | Bin 0 -> 683 bytes ico/viewmag+.png | Bin 0 -> 810 bytes ico/viewmag-.png | Bin 0 -> 800 bytes ico/viewmag.png | Bin 0 -> 815 bytes ico/viewmagfit.png | Bin 0 -> 846 bytes main.cpp | 21 ++ panelappareils.cpp | 111 ++++++ panelappareils.h | 18 + qelectrotech.pro | 40 +++ qelectrotech.qrc | 38 ++ qet_en.qm | Bin 0 -> 8076 bytes qet_en.ts | 500 ++++++++++++++++++++++++++ qetapp.cpp | 733 +++++++++++++++++++++++++++++++++++++++ qetapp.h | 122 +++++++ schema.cpp | 322 +++++++++++++++++ schema.h | 46 +++ schemavue.cpp | 431 +++++++++++++++++++++++ schemavue.h | 69 ++++ 71 files changed, 4586 insertions(+) create mode 100644 aboutqet.cpp create mode 100644 aboutqet.h create mode 100644 borne.cpp create mode 100644 borne.h create mode 100644 conducteur.cpp create mode 100644 conducteur.h create mode 100644 contacteur.cpp create mode 100644 contacteur.h create mode 100644 del.cpp create mode 100644 del.h create mode 100644 element.cpp create mode 100644 element.h create mode 100644 elementfixe.cpp create mode 100644 elementfixe.h create mode 100644 elementperso.cpp create mode 100644 elementperso.h create mode 100644 elements/contacteur.elmt create mode 100644 elements/del.elmt create mode 100644 elements/entree.elmt create mode 100644 entree.cpp create mode 100644 entree.h create mode 100644 gnugpl.txt create mode 100644 ico/button_cancel.png create mode 100644 ico/button_ok.png create mode 100644 ico/configure.png create mode 100644 ico/copy.png create mode 100644 ico/cut.png create mode 100644 ico/delete.png create mode 100644 ico/editdelete.png create mode 100644 ico/entrer_fs.png create mode 100644 ico/exit.png create mode 100644 ico/export.png create mode 100644 ico/fileclose.png create mode 100644 ico/import.png create mode 100644 ico/masquer.png create mode 100644 ico/move.png create mode 100644 ico/new.png create mode 100644 ico/open.png create mode 100644 ico/paste.png create mode 100644 ico/pivoter.png create mode 100644 ico/print.png create mode 100644 ico/qelectrotech.png create mode 100644 ico/qelectrotech.svg create mode 100644 ico/qet.png create mode 100644 ico/qet.svg create mode 100644 ico/qt.png create mode 100644 ico/redo.png create mode 100644 ico/restaurer.png create mode 100644 ico/save.png create mode 100644 ico/saveas.png create mode 100644 ico/select.png create mode 100644 ico/sortir_fs.png create mode 100644 ico/toolbars.png create mode 100644 ico/undo.png create mode 100644 ico/viewmag+.png create mode 100644 ico/viewmag-.png create mode 100644 ico/viewmag.png create mode 100644 ico/viewmagfit.png create mode 100644 main.cpp create mode 100644 panelappareils.cpp create mode 100644 panelappareils.h create mode 100644 qelectrotech.pro create mode 100644 qelectrotech.qrc create mode 100644 qet_en.qm create mode 100644 qet_en.ts create mode 100644 qetapp.cpp create mode 100644 qetapp.h create mode 100644 schema.cpp create mode 100644 schema.h create mode 100644 schemavue.cpp create mode 100644 schemavue.h 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 0000000000000000000000000000000000000000..96919575afdbc31e7f3539f8ad1cb709f813a7ee GIT binary patch literal 883 zcmV-(1C0EMP) zA_&6)0*DFSP!7Ey22LGc2Ehndfqy`k+}6i59NAC-8CeEF32_E_Q&9tu%TMt`3QO-fLy5X|L*x}gl;e}M7--+vkY{R77h z*kFJFVgv;=BO@aNC`>`#2e}kvD9CVzUq2W=o!`swY~|?>Knom!Vb6fg(6{%VG2B1# z?gP+)j%J@2E`ZDg2q3t@AOH$ekoQ3@8t||i!|HrUj&Ra`>S3(-a&<~GZGTb@x8mpl& zg8>4F1y?$SMikil$c8d}2L;V21P{DMd`;hlc5XWhMpz(eDVaBN=XJ{r~?1fB<5G zI2|bPm*K(x-~R}vQ>&jq_CE$kkoR9>#@Pb~h~W$X0R%7TLFMULafb76d_@1hb{F~& zET!I`;=^5R9%W`QIKsg2{w2^|APi)mKLRa)KxP625K82M%Tr(&f=ekJi60pN4FU%X z)TN;C{QxrEcCQx|_#6(w3K*JSaE=A6H00G2EtI8B0zyJ_{_}Csa6@>r*002ov JPDHLkV1jeQWgY+k literal 0 HcmV?d00001 diff --git a/ico/button_ok.png b/ico/button_ok.png new file mode 100644 index 0000000000000000000000000000000000000000..31c064ba9221ee125c0e45fd78c5c5aee61df26e GIT binary patch literal 769 zcmV+c1OEJpP)cHeO`Ol5zyLImnGt9T1H%VqW(G%q0AggA&F~&9`H#VoA)Q3SX95jmWT@xj;7a)R z^BV(@{Q!sq8Il<;00a;d!!L#n;(X!)41XB*GfZYMAj-f=Km(^R%;#X{xbI--kf0)> z!T>bq0>f_xLy(~$B>({gGI%4<76vm1GXbE)euf@AhW7vs>}8nG%FKFS(^xaXBh-W8 z%8e@wmu_A<0CbomLngxqm{NcMVqoZE5MbzF_^Sy{dctr5O#t#j!dhU7L{~0=gA&dn81P~-iG%@UF4rO*=QfFfLbMX(u|4sitF#HF) z;6Km>z%XRs1V)NE0|Sd03j-quP|FJjhChpe>VE!Tz);7Kh|OSt0Ac}?KN&Xuz4X`N zul!#I26Z6c4d{z4z+hnq(q=$D2y*}p144#}K=u-_oP$4LH|iN6a)YQh#7^a2TZY_83Gt?0om_>hAsuh{$++A)u014Kkn1^4&({_*ki{s#vI#oXNG@BRJ!JR2Jo2NM$z00II4 zG}hMN*7*4P)cN`R0Q>v<0P^zn02db$03#zD00M{!XvlJ)nWmpVe`UCO^$x?+r!OX* zIdkpghYz2@Hpt0IG4Swkdj9zFgZbC5Uku;A|72ieV`Gq#5(CQv1Q3&qjCioDtoX`b zzkcif{ri`JgM+==+S<^DmzNvpt>0ipKyS#t|MZjl>(}oLpFVzJ;N|6H(9ux>%K-!s z3mY5rH&<6%MPQ(Vf4!IgG^xblzj4+9&+XLuk21Q4^BnC74V{~3HWG~^jXLgw-0|Nj64_V)My>gw$P_xJb!1_=-V1MJ2C8Rd}x8TjQ701*dC^Y#Yx^6BZv@#xt9 z0R##E0*Em*w3Ok?moGpKKfO6QIqZ3Pxir3h{rN&jOwusSLBK@j)g%U~-}e|qfYyEE zWMz27$ouE+ZRX;yUw=&c{OvCTKmf6T{K3e`#K6kRvK1K0TR(mJ#`%wt>ATp!z2^Tf z-rT07^-+kIoeS(=?*9x7%1l34{{H9eyK8D>l|NF+q!{#cX#>&jV&(6RAq?rHyXAtH3#q^Jf+2i?Z#+LvA#KM3B z{AXZf`1O}*%8@e+Y`^|9^q9J_GBUjT!NBm~3j^by{|vgqzZoA3ayJ455KaTYQN+x^ z08Cqb`;IXD`T6}vpN;BYW(E#m;(h;{feDz%xS9S60R#|U17L;#y*6pr5e9~z&;Iv0 zTd^=Ru>WUx|B`{>$|L4W00BhM0C0K-TKbO>Xvi&2hI=o6)Cuu0u-|*ne*NtarW}9( Z0|4(~c&K0)VI}|o002ovPDHLkV1f~|8dU%Q literal 0 HcmV?d00001 diff --git a/ico/copy.png b/ico/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..c3ff243b85f2d80a576da5225b81ce0793057841 GIT binary patch literal 777 zcmV+k1NQuhP)oe`{G-n1L>1#4rFLfG`YT1giP__YZ@fo(hAR znKpx_rV>z$QPI-UfPt5nhXLIs00G2;%>ZVgGd_L##_;|7F9tq7ZU#$BJv&iRfmy(C z-~=k+1{$*c-@kvefnH+(2p|jtzJ6t3c>n$jFdV)x7#S%6ZDj)qD+9%qKY#uT45Pn5 zH9rD@VZ#qJum>Q3Pz?C-gMs1Ow;$kG0J(yl9U;xl%>axcPKMX7KQMg${23TF9Nj;E zeij1=Ab1#ny!PwYZw3|?CZHq>$lna;0nft12n=9W21!X#1{D=K1}-j+LVy5*8}REF z$p6gXFu(;svB1vG${;Q-%pff-0yL170U&@_Kmmbl0}~SqFcv_HF(L@)yN<0EYv}D}Vklf^C8% zb4IW?kpVye!3_Y}qO2^-ps%k6POks|{|0MhWn}<|#lL^7K>8mL|AD4hCa^d_0I?t^ zVPFblIC}IvG+q4vdGqE25c3O^4|WxV{THnM%a`we00ImEj`sC{+hY1`00000NkvXX Hu0mjfs4zfj literal 0 HcmV?d00001 diff --git a/ico/cut.png b/ico/cut.png new file mode 100644 index 0000000000000000000000000000000000000000..7ec355a0dcf91ad072161b6eb1857943b12dcd4a GIT binary patch literal 804 zcmV+<1Ka$GP)HV(FCI$h{l5;NJpZPL>W`I33Ha2@@ zVq!ipIyznTFQ42C!1LUl?CdO~EGKt+eSIHK4GxZ<0RVb008Nt?Ns%go8#D*0EYJ5ARLY(9FD=~3($ga;H=$l?Y7y>MO#~e;JzY+LNSCwQFy)nhO)Av z4y*OBeQIiP(&bw1-B$#^KLkZl;Bb^Y6a)h|JUsE)Jq4vvku-5b{lf^cP>E<=S}>eBpU zThDtg302k5-~VY&6g}4nf}C+W?`k`91ORkhD6rK!Qm>1T5!Psg`DU~*M&3$SCExLmwjIsw+ zrE755gC#nFtWU(WKx=*WO4o4sPlUWIDK8mLlk;wjXX#bsZ>mj#Sy9C~buQaNI)qYM zLj>jeT(U+^kjJICavFel_l#QsDx%X;?1_@dqsk-kfgHVjQT(p-FMd_dE0Gk!Gl^yK zUGnnQWYQ_<)1`7!YutS_09f#Gy)32G6_`_vDnS^;HRf3$N!%^(`PJUIwH7l$dDSb58;QzC19k@=Ur~Tqnwl^V3xcZ^{sh zkns$_y#bE~Ao9C1w6Td7D4F(wqN;#llNs12vi~arkm*L6N zSKt%_l3TN84Te5|0AeE6#o&l!W&|7hk{J^~HD zcb$Q2aSH?2kxdM|^E<$Xg7AmzW`^%pF2IOo0*5m;01!Y-=vnw5pD@Eer*MYA?4L1;k zza>{N{PGF~=0YZjp^Qvm3$Ouz075aC5eYIqxXHly{5itlk6#(smn~pm{PYo+R1i7x z-+%0p2M|D5{lR>ECj;AzX1JmM{{a)vhi?qbr_M46_<1rgefk7W+u$6@h&?C(0*DEl zp{z)T{w(fb_%?Ml!+%CD28M4y`OB9W`29T@7(af*o2dW-2-DzgE8vFyD(PhS?H$JO zS5uGS+nSvW|M~eLi63Y<+p)uV4F(7x41<4HOk(&aBE#^jw3FeFPZ(VMubw``r_B(< zzf76I@WaC!ufYHT#DbpO7`S*C{?6G7j-Y?Q90$UXybUy5SC8TCy{AAXkORz#kgSe9 z@&Ez|#b7p8MACs72+Femp_Z@!BkKQuCNLXV9Dyxh{DZxQ0tg@$NU6odP+Hl>KurJ; ZU;r@5PZawLhx`Bl002ovPDHLkV1f)SrDgyC literal 0 HcmV?d00001 diff --git a/ico/editdelete.png b/ico/editdelete.png new file mode 100644 index 0000000000000000000000000000000000000000..6d0d29d760d37e78d0ce975bc20f681369335a84 GIT binary patch literal 892 zcmV-?1B3jDP)g`JI!TT@Mjg8>Qr2O9R{*FT28e;63<-ha%%!_CQc_rdf1PoBLDWoKtU zfUFiEfS4Gt07gbe2B1X@j7^5sJtOdUV~VRZq>b|#=V{`~&KplhJP@aqRKFhBof zc=!A{11l>V!>JQT;c5W_2;qW%e;F9ApJHIRd6wZHkoyPdk3X!e3}5cuV_@nkVqo37 zj)9en8|W=o24-evxHf zQ|=hUZ*4OMCNXJXAR}B05I_h6B$XK$J1ZC%UVmixYh=Ojk6W05_3RA>VINlp z_H95HadJUzN4NkWfZ(yfa%2Ys>(mYgh8J%benloTd|tSd;cM?KhW~v049u@yF$fgr zGBCY*!SEj#jmQ8XfDm3Vb7Ek0@?-cfqr&hjr4XDSe*5||d|17afvc>5;cI#p!#`f2 zVZgM3>;-@TVu7V=Zb3c{w_fL{F1!2R_X0}Bf)N+1FR z5X;9;pT4$rPX5Eh!~)F+|A0PZ0&4yTHsC)rXZ#0q{sT2LF?{(1OqNgp5MTgah5&9K SO7D080000q1azL6lnT zrh&HLA6zIJSBeX9A#6erHF})4Ou%yf-y?5y9Ad zcpkxc1W^LC01vQpK-PgL1HfXxY_+vKsL$(>lzuWccS60ti+U?}lPPHG&^SiIkg#NV zG-bGa)A&HZg@;p<&5I*C@ll^f>o;j~{f5@nX)SdcXHFw^I{EgUCf|Ikja8qh?%1in z-F>U9`|1YJ>tih>3HtJ1BnXjZ%Lwpts|v<}R;twIFQVh`e^5%))7dzlisA&!!S4|3 zBfIvJJoppwKotaNeg6Z&nO}(y9HO~lBid-Rhw5we0lwacSE=BJwj;N1WBOL00-`yj zvGsE@QA)!nnNI4srx`ex18@%Mu2L3|!7b?E7S!eIQ$QSa4Q)e)wt+&aIv=%3P6m7h zTwzDGxWugeb}i1P=x5}qXOYPWL5Y9AL;c7x(l|m)0kiPo<|vKBM{(k4MlqVDU`*kl zx&TH-m>z#g`PMDWbc5*2FQ}|ri)8t$!RQ}!pB`o+OX-ZK@OB>uYl|6RP#`=tOxE`) z(bX$d)~`id?M-5T{*j3+rRUr)n1MC8{o!ncz(CO5O?l-2;i83fzJ850U_I~*cphk6 zqbrFp1FNyUy_8EfCj6O; P00000NkvXXu0mjfXQyoH literal 0 HcmV?d00001 diff --git a/ico/exit.png b/ico/exit.png new file mode 100644 index 0000000000000000000000000000000000000000..a77152b5b6ee870d7c0dd6d21217dd6456a4ce98 GIT binary patch literal 830 zcmV-E1Ht@>P)5 zlQC?QR}_W6``+_!`?sye5L<#sE(vyqgdmj(kp)I1rig(7Au_TscjyiS69Wq)D-{wF z5*~<1%vPEVAp>q{>x6{H)NzRG*uVY1_ZZSxUAfa8?m0U5o^$0VV{ZOXDm~1lQhC0i zI{kioZFBRk{5&gv@II6X0e6pMs~0?vE7%_f6SpU8GPC=rwhhsKa;H1fuKkBcJuySu0$;Kb$2jL*(8 z==W(h8YmIQ&z>cion>on4Ov|!qZHa&y!ROIJ>5ow%$YMx+_=HXnHhe4{+!P8GN-dy zQi%kcfVp&u@2_0p&nO~TSwTe+-g}gFj^54=K8`6|xWF$@pCT__a5|33&dt%AoW!)- z1l20LuU;{A@gh56i1hnd=TOc$jMj|J&l7dKY(IWX^56j1YEis)jmd=t{{Hw8zqf}x zdq&*rk*-wm%_hz{jP;(h*5uEhXJ>t#@3yxYb~;EdhbV;t;{AQZTD-M%>vam{GMy;G zdXI6=kq811K}5(HgBlKzFJDHk$o@VO$43?dR)iRE&Y@y!Nf^Vy#s;U(oud~7B(%n8 zJt8>HfKDb!S1Nqo*kC-BBDVJEhzYIfzJ5(Ili|djJDC3n{#jY!kEJDYi3CO|Qj3e2 zFr-zhkv0bF9L8FU(VDncWBcVx<`x(E-5Au9C%Aj}NO?~-mm|1+n{Z))57jD@@7^KF zB-UEYu>zwty$2820xGv|F}!}AgZJ-Ai;$h3#)Kgss#T&#k0>RRD3K!tv9;*qtVa=Y z|31GhFQez@8Jn5G0e>wmvH#``rS)}6L4XngAhs6M9Sqvt!61J!2u8b)y?u*bT}6~) z2uvgrl$1jL2i|J6=ne*L`BCI%Aq*b|T7T0!53Fr}K0Lhp6)*fWl%x)x#sB~S07*qo IM6N<$f{(R(NB{r; literal 0 HcmV?d00001 diff --git a/ico/export.png b/ico/export.png new file mode 100644 index 0000000000000000000000000000000000000000..381bfc05392cc6a760e17a0f3a7f9bfc8bcadaa6 GIT binary patch literal 626 zcmV-&0*(ENP)5s!0< z_ksSP)YloH*;AlX4=ZOwJjR!2Y*i6h%#pX$EjE5H!;L-DUsBDZwH^ zBdU?0M(qNlVL@?TA`+-;M@m-!UIIQC<8fl4)g+}BMkJacRgJ5JWJ(K&rqs1u2RNBT z*m$tYqlfF%wNOgoL0t@rcYE zG$)T35U&h}6=snbjTj>_M$95+Ha#OwJl^~35yUCyqmo-UTFCP9)UsW1FtM*{h?@hb zYDZBxZr@y@*DF!A%Yh5PMen^QgaBLt)TL1LhxD3})s-b04W-eDfC2_6IU6|}qrm_v zOQr^9pJ{XRSzFauTx^kZA|*&EO)pNCDWyzR)l`)u#+~-uRsC1}1-gaal@pe+p8x;= M07*qoM6N<$f=Un=0{{R3 literal 0 HcmV?d00001 diff --git a/ico/fileclose.png b/ico/fileclose.png new file mode 100644 index 0000000000000000000000000000000000000000..edf5f76f9b6966f72b98e0300a1f2b5675cecfe6 GIT binary patch literal 1027 zcmV+e1pNDnP)1}Oiau>V0r)ohy|hf ze+~-^TSs_!5VyO#J%f&p5(76k8`ywfzy2}YxbcW#>(+hND_3q@^YP=G3MM9|DIk3S z0mKOM0?=l6W@gsS2?>#`?(Vh>EG&!+KY#uP84ER>fq|8k6)4WkaP;VThW`E;zdwC? z=L_WT1PCBz5fNp+-@kv)c5tv)u(!7a`u7immX;jDn>QaB9zT8sbpCfB`vZfjsx$)^ z7aPzZ4hEoQ%oi_SRsyozrEP%$$MEjm2Zom~KQO4M z$S`nnvVk>8N{TQjC`f?4($}|?Va=ND3=bbZVfgXm8_=6SUIGLV3otN*7=a=6`t=*2 zz%PbJkDdZu@|Gbn(T{z;&8o~^;=sVEDKkonn zhzS^yTY!FH`19uvFyZhqw6zs7Xllwq{k#~caXQ0?58oO1_&69^TXTQ`s>=Y3HLw9d z7i<9tASPfeU;Y05>oJh?K%su)#sg5IVwf>wEyJ!|M;R_$xW+JX;u41M-+wXOyY~!8 zLo5Tj`WVowR{;WuQCZmzsNt_W2M5n)b#+Zvad8O-2?=3_+qWNp6A~zZfBpK+pslS6 z4CoKQwDFkX%9ZoKzkK=R3rx~G0RjjZjq;$-y!-3dkN2NHf3at0XJZ2<6b2CyL10vJ z0>gxhL0DLr;r;uMz!-YSaQ*t#uV21=ECp&_4)P{I0D%nvr)frpQ^2@A_wwa)#ZR9; z$$k0q6=X0l2EH-ezkd&ynQk!Lx^?~Nj~`!ifSQ-V@(@4(fxQ6DiJ)u?^1mQ3kTigb xeAw7ng+O5gOds!nA-)9|`rz!0oPz-Z3;@VckhxK7)4Kow002ovPDHLkV1nb_%*Ox# literal 0 HcmV?d00001 diff --git a/ico/import.png b/ico/import.png new file mode 100644 index 0000000000000000000000000000000000000000..32baf9c5493b7d38327b9f49909dae5ff68453ec GIT binary patch literal 851 zcmV-Z1FZasP)R0=-8r4p-E z5Fd+z3$aokt5REqVi#5^#U`{gi4O#E zKj;5A@PEwvA0L1D^Dn1nw?6*j9sdcq_miJDetU4{L?&C3q8rFtZy!7WH2!tRy8pGa zukRY`dZVUwk3ecg^YYmQQ&1dI_nteDM1z5nn+ z8^fFX(+myw5}731o*&;eT=1X#;qX+(iH$)U&|>lFFFwCr`yIg|)`loHM6nQrnn$+| zGPB%FU4aThjvtx3Rk@inj#4)Wre|Fyw)b#vcY?O=Buf=YFF|TibLG0?#rZl*tBqXAMPwHv(%C90y-J*NiHx8s^(x80 zr+DnLqqLzC2;D6<%ORvzEw1Km!kVU5hob9qZoPt@+iB@)B$2SGcG#WjTy9pNoI3rUb+eKQ!2Y+VY8da literal 0 HcmV?d00001 diff --git a/ico/masquer.png b/ico/masquer.png new file mode 100644 index 0000000000000000000000000000000000000000..1059c25c506b01d7cf83fd9b6640f27bcac1077d GIT binary patch literal 609 zcmV-n0-pVeP)-y7ePp8S8ieprw zP)SyVB1=+KqR~YyE9yE{=(zV=K0Wm9GgI#SPP^5OBSSwDqEP5ZhA7PUQ6xk`z9tCT z5{xaF?86pt)owQ@yK|@OL{)fn-(%@nfCE?pqCsU4+kgc-YaWf80aU@_$Gm~IeR&%0 zUCJ*5EI;u0z8Ro3BXMQH17P8m%ab<&&tJN{_(;WWOc*-N93}_|J&#V;qaFm53cVa& zREqV3KZs~nXDk3JMhwF$!4evW1+5Jtk{e(~4gO`as1zHAIV@rZD}ep^*SJBN>XH1l z9|GVwRRFX$Z0*(Ud455Bk<9--JTCX+?YE0&R($6 zawrllI%qLjpfUr3#fPRH{*_B1;utehUB5-tAk~ISXhNaguhwK0fQu-wBO# v&7T0y1KT#RSHy|B(6TMF?^J<4@D1QE$Cy9Y0ri2s00000NkvXXu0mjfQ}Y8w literal 0 HcmV?d00001 diff --git a/ico/move.png b/ico/move.png new file mode 100644 index 0000000000000000000000000000000000000000..b99d3e477d4fab7f9ea55369e4248ec584bf2ca2 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fEa{HEjtmSN`?>!lvI6;>1s;*b z3=G_YAk0{w59uTH%ye$9#0y{}W zK~y-)jgw7l6G0rtf3un0O`4jtDKTwDw5@t5DD?#q5ycj(MK8Sw1wVraG2kcg(xc$T zi+Zqvhg?MP1qBbK9!d|26%=i4t!X!H+9u5=o827`HR%Rz^?^UkFvIWv^2`XO6r-b~ z$#`P->-LVl&4wXcgb*R+gXcrF>X#8_Y1x`-O>`bOIy`vdD*?&cqAl4ud*#yk%bF(Y zhqeuc0=sg#64_=w_cNQF$h?>w7#=+SzII1r2Sp+fjY1;Pgbb;+ivvT+jg`eWndehy z8UX2Z+H#z7UwPXBtm%)GCvPHWo^jyR0-@z8uC~8T zKDi*`br+=6%7+St=oA^QC>XkGMmkFl;ok#|ejGLT5g5Gb|jzh4W- zA(p>QIbR@l@(99=gZ~3xXexdE>{uNj1j;Y~0V)7+mBRA`O8J3Q;5nXhE3VrxMAHP* z03#6LsEKjt7EN6(6vwCVmgaF+3z%U|n|?ZeEdba|p=luH7Qi$?*cNM_Y)bkFD8>B7 z2>FGg^Q}O$3{>@pwBPtx&tX*eJ={tKYkZcLzGK{db#A?Qx>ss}zSK@{K?u;pChh$< zFv9{P++;U=CfBXXVzEf@Gi|30yHYQ@4#?-S#ruyk)}{|ArBp+AfjtVSl-4|eU)UDn UIrjytJ^%m!07*qoM6N<$f=r$-GXMYp literal 0 HcmV?d00001 diff --git a/ico/open.png b/ico/open.png new file mode 100644 index 0000000000000000000000000000000000000000..037c2da98d1c629fd33957baa64edc60197ef767 GIT binary patch literal 1080 zcmV-81jqY{P)mwqz<1P}`@lm7u-_Wk>BJ5^%+ZkvZGVS)t0! zQI3z3gPGyiuYU}Vb_%h}R$p1k!oqau&p$@6MF0WBqM^#0m>4g@@b(h}!`D9y4B8s( z3_q_w^hpYolhoD}Wcc=tf#J)Szd$E3FgV-F@E$t#qVw;?H{n3*AAt-22p}dlR)#Nv z0!$2?91IM<9)4iZ{{Nn#DPKoYS6h(b_iqMHl}znhny{{;vj7NEfl%s?A{uyZo3I`f9%-1|=qzkmK_`1AKK!(X5aKml{* z_D6>MAD9{BbmbYI{a|GfG?C$ZbNr>lpRZ5P0t67t-~ay@Sb(-1xcH4h^gkf9L(b$NR4`G5q_n7a#xtK>)u0 z1pfX10Pyqx0O{iN0E>Ge0QUL(00kEX0Mz3F0PfoV0P5-g0ORQY0PE-Y0Pp1O0P)-A z0Qu$e<^Toy3IGTG%mRpo1!(=Tv)>ss<#`y`g_#&mTxVc-``{nLgXhc)ckljVc=G56 z!?UNq7(P6F%kb;ndyo@=A;$QRf$8@nhW~%B0R#{e1JL;wF1};1Gv;Kt0hE9IosHot z(17Bh=mEkYL9e@C0dHCojgQhAQ zgQOS-13yrW79RtHkPI`!$9KOOq?rFO2>$%WAi?~X;Wse({sktKpI`qlK6(6)=k>F{ zml+rt{{RFK%l$i_8Jvyz8Qg$|fLy}>H2a4n12_SF`}&XJrv(ecH(j%TvAHNx{-T3?rD0v*1#25eqh>^u`|9ew25gu-KCI(>C15?dku-||G_yzPg2>)dG y1=REh$o}&Sltce8yt@Bq5yKybWRPV50R{k_prT25zPY3=4LAWY}_*mqF>xHHII5{xfiL zvNJqn@L;Ikb)R9m@dAbnPG+wyTnr3A19k%Sz5^Km5I`&p|NnkxVED=T`}+?DS)5dYVA zhMzzg2NMAX7c(XXhVRdT(k~cZ-havP>FG;`pHIFqod2fC@J)b$fkT3Um*xHazki@! z00P@FAN4U3=F&+zZw4g z`OUz__=kbz?|%lH_~sl2i_&HWW>yw}0Ac~V;~&s?W+3zbe+GsRcNrKy-3L32k&Tn# zH_)wY%#1)o7#P@@7#YsL_|5SA#a{-=u&$ctFE3mG2q1_746Hy``~$l43B(obz%b$h z;yGDG-`K+Q}{ zV9notGBA7u#?`ye{~6wX`oq8q6zAmts{7Bt2oONfKm>-)XQ1Z4z(8bTh8O}08?ZqT zD}MX{N&^k!=452x1nT1W3UV3)13&;l4EXa2h~EOk0%*`*pvpf$>wvKWjvWSw86Yq4 zvID)r3<(p~pAdrq0*Hl&o0XH1lbeA-LJz3%3lRSRM(GcRe_!|+*ce1XZeaKfG!y83 zhQEJ-(m)0)BiMIf01!Yd0qLvD92NFg|Ni?Im@0okLJa7(zu$lVf3K{;?~l4CsKB>o3E} zzBYgWVua;Nka=iIU};_ml43zYh9U>b*KYs<3;>)ZC+|Q#8AJd8002ovPDHLkV1hns BtAhXl literal 0 HcmV?d00001 diff --git a/ico/pivoter.png b/ico/pivoter.png new file mode 100644 index 0000000000000000000000000000000000000000..36e35b7d06cb422f30315f21317b15d80679e140 GIT binary patch literal 749 zcmVL zl3z$uQ5?sAcX#ZX&g-(>IVNT(&L$2r(rGCLC6xv)r4|^155*Ss&>yH4MwE&5Qb_vP zLp|sqq=Lw3D3tY5locaw)YQ4u?YiANckk}q?sj_GSlf%y7tX`?aL$JxpK~yT5d2T% znMKnRBs-h(hLxMJs&xTM=KT`-;)yQAM8P9+YxlTkb>Ff9SZV_EAZIsAcP>bpu=5*5Q5{k=dSlT zc;VQCB--5<;#BS9rqmf(`z~VS^8&7NdxK7PRAOfV`%f1L35aod| z()eh8P2hr)Id2qWz_*-OuGwa&t*+EH2RICrc>voDdIPB>Y_ng(R=I02#>T~D0+J%H zXmvIsiB|z|0FnqG@pTxAau+(c?%!#f9BJ9tbh?Pl!~NcQZ**nG_H)eg0=9H}9hGO- z1>)dTb*4as5a12B%$a@9xu^Gghd$0Vo_gGp7EN}!?1oEmL|Hx!<^XnFG-vM>G+k3? zb{WSk-ep!jZ`Ine-eU9X`fQ2WiM;QpGrLT39+Dy<-}#uxiMsOYF>TjGNBVv;?93r# zCH<8P+n!extF=QKt*)74eN(L8<5*pqeIk9hA#Dhdiu#D7LrIeL3LI(~`j<))*B=EZ fuRPo^;9v6xrt)k;e-NK600000NkvXXu0mjf29{X= literal 0 HcmV?d00001 diff --git a/ico/print.png b/ico/print.png new file mode 100644 index 0000000000000000000000000000000000000000..d6defce21cce92643bac58f0802db1909272e4a1 GIT binary patch literal 684 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 E05Jx+D*ylh literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0ff7d762508c06d42fea233a33303039fbf7fa52 GIT binary patch literal 8682 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 m{(trpga7Z36v8!j0&1)@HKRk)jKHrlAVpa + + + + + + + + + + + diff --git a/ico/qt.png b/ico/qt.png new file mode 100644 index 0000000000000000000000000000000000000000..a616ea0959af1ca770de7e765f7ef2360957fa09 GIT binary patch literal 2134 zcmV-c2&wmpP)T1xknlwkl*Ggk z4PVp{L*j!Epv0h&VpJ4cUM+Ukl==n_l=DF2B92q9XXsQ6~bN_!)~x=o4B-u01AfSnJwQp|>>-0`q9 zH6Z8Td)>Hn*t7lzggA)}=fc3o{q~Z>)9}sFqtt&{EHu&kypAFZ3 z{yWXcx{=wDAU{1Q^T+mkc_BpUTp(cTKXID_>}cz?H!WR=+bu+dLu*0X1(6MFf)%CD_$zMtMo6eMOKux`%gax_6O%N$>qM;f*xqIFZ ztIjfjP&o(z0uTZ$A+Ri)C=QiE$jnTQsvCf%iN^%Jy$QN{S_qVfCIl3{t<98cdTGj? z-RVmq)(%PoD=Gr9Dq(TuQ(8TN}fbO=I?r(LZR$ab%-92pIb_0cS1v?sQfdMeqGJp_5 z1xj0BETUG7eJ^>;97_^2U06XYVKs(wBZSo^EI&gvm*I_r- zr70E1D8cwpz{p{nc&wGfuNS%I<_J^c1%CgNEE`wd(Atw;pS=6_2iSY$IflpH;HK+t z!O5egs%s!zW`RHjgi4}~LQBoqp*YP=ok$N_dx~6nBlxyU$*WQGJz@#N)%}Xlb8vIr zEMyID?a7i@+(LVDZSvM_chcUSVgKP5>1ppHnYL-~NXPoR)+c~f%K<_UseFu)Ob*qt zbt^(gsQY;~-=rwj%eaAyAmrVb6|es}ic|ra-Xh6pJJZ89xonm3@#74S9ip$Vm)4FL zPdxH8-@W${M9gE$m0Nr6yX(;_fE~*Kys%23>L}w;UI+;#EaZ!{tjtsK%J`v+ZAo5x z-X%ZYO~gua@YOPHYYG(R%M`OVmt2)3o^kP1-LI)^vEWWri^Ul{vp`eY!52k$^u${W zz{oOyx~veWDxt1%6+!@rWdKS`q%s7d#p=yANB1lMFg4`jx{7ChWa#V5 zaC~|aOE|>iP3(R$&yIUsq)d?PTtw=CMxmpDVrV?iqJAPfe@wl4Kyh&JwfR8$8c@qq zF`zL>tw^K|#bS|ED#f9HhCK9(7P{6haB}J-yB>6y9QHW2FGDPr;a88HBGYC_byrbZ zpPkPbEri$=P~7;tmmk~uyO+_xU}G^_4siM@kO9jpOTGoXCv@uAf2yI3G9jC1Tu0g6S~an zKzGqqR0~>3xfL%iw2weTpnG zcw~3H^~R|gnvPdcRbk632)vo(;j1LRkF}~{(sm}+64?;^MDe*~iNNjVQRVc>YOFNJ^#bY>}ETnL3b?5=Fi3;>YPz{qHuE{IsXu3{P<^dxl^5{)$yrspA@UG?!Ph9sC0%kpZE| Sri63=0000y~n literal 0 HcmV?d00001 diff --git a/ico/restaurer.png b/ico/restaurer.png new file mode 100644 index 0000000000000000000000000000000000000000..7ff886a991b5b838eb0aca9d8b09b86a1773b89f GIT binary patch literal 679 zcmV;Y0$BZtP)H0NB4(3F(MoVrR39QD7kz;!h^QcNBh;o% z+k$8l?n=ak^Z~LNs!3;*Nb@CU{%`j_Pm9SnGV5&4xfjm4&$;({=btVX^Vw8~S|JG? zap;Hx+vLa*H4@@z@4k&3E)H%cj1wy8_C25tE)`E&|vAx;!)6>*BQ!t>WYro9CQJJ}cI>JLs8Iyu>uLPQY}oNKbR z!t|_%k17X2mEUIFHt%S~S2;KU*gk#7EI8L(n|}qU>h=U$n?Xg9W9JYNR28Ftsi3N$ zK)8x5e8lB@$W+DvNU|^R*T14+vVVYTv!enXU+&M*1R!_y;q4=E|OHH@X|%RbdjVo z#d>X+&t_z2g5Bn=+g89^D`RqVnEoN)I{>DCh+I-8y*Yqc-AwqjR7YI%b2ul6b2uxk ztOm3!-@-prVg5}Iy~7UxsLp(&w^-x)U==exxAC|%zk!V{HnP|_Y3kVGVoNo9nMcdx zOney0KkR;W17KqI%9HyW&+2#Ij^gp32qp>{uhZNE&}DnOb!;I9_yuGxomd;J%+3G+ N002ovPDHLkV1kTjD~A98 literal 0 HcmV?d00001 diff --git a/ico/save.png b/ico/save.png new file mode 100644 index 0000000000000000000000000000000000000000..41b3f436790fc9d996a410069fbd3c54e51279b8 GIT binary patch literal 838 zcmV-M1G)T(P)6{{8>=@8ADv%Z~mJ$)5fH z#KlKoK03a7@9qEM+79gQtT&gPo-|gSM&^Rs(+g_`&ex z;bVs1zkmG&@c{w|V!$_$KYuYi`}K>##zYlp`(K9F@4qrUd;JH@{s(l;?|=UozI^`5 zaPstZhEJb9F#x>-HV_~HhjWYrAP59e^xyxM6jX-An2oTbA+8jY-@VKR+y|kTQqRQ1 zn87{&D&gRA$BvFQLm{5rTKfQ>0D>3*4E4`IL;eFH9}hbND=Q-dGb01Tf1r!L|NF(j z`12 z@&hRPpJCzZJq*__U4a__5J1pCgv9aBAAcEMz5EGu5zuB}lzazi28x3W`14!<#qn7=*akf!_HB=70YV(F8N-`}gk*OpHuG{#Rf?ego=)!~#G7L1WJ0tjNjr!TA@UcO;?$Gsl>91cufEcLhKO@73*RL7=eg6d1#mEZM3lKnzpuphg_qfNz_+N;D z@izlEHwQS8{Qdh6Yzrt!gVewh&eyLWfvNB_kj45An3k7RCwBA zoIig*!-o%_89sje3Z_4O`U+%!0pr*2e=_LsU0|4OeSm>MNow6Ej(FpHn>Q}|aQ%8O z6Z8K!3_t)7KurJtGw}WS^OyhEuiyN?fBymFpFe-`|M>BfpW)9p{zLcV_;6{{8>= z@8ADv%Z~mJ$)5fH#KlKoJ{Z3}^Z(y9w*UVQG5`NJoA>|b1jGMKtkw@07&N&V7_=Dx z0*LAJ=O4d6eE!Yw{Wk-{iSu_D9z1!)@b~Y31{nDJb{dcb^1li){CdsDaF)%Ufs30@ z7-%~yNE{%5SiXM!#_;*;4+c)w{|ufE`V4lK+6>yNQV`vr?lLg^-p|1BOM-#nI|swL zccKgzzezLv`*rp&0|VoKkQV>~h~?|I9}M5V{bG3b>lcHKi7L?czrfJ{%JBFl1B2*? z*$iTWK#M*IF#P|-!m#V34#S%-z_9xF2kO3G3;+Sd^5x5ShM&LwFg$$vf#ECApzl9^ zGJN^|kAdOaGlpo>(?CT)*ZgN?IDY*n!_haQ48Oj-2O97Xq#5XfKa2nY!~zQS&p<=| z10f#|I|C~#Bf~$Slp@Co261T~28Q=+4FBH$WjOj?n&B4mWDX&%O?hgr|*F*32;oke*25TJ~WHr@!qQpYtNnn8t|79 zC<%-OhVK9Y1PsI<4Bx;1Wcc~xFT<;sK<9r0#>o3u3@WUO3=GU%3=BV?Fr0bH!0;UC z@3${r0@Z$H`2Qamh762g%K!q1>D%`o3?DxOlP@p{|M|nn@cHvEaI*gSQwAt)&2a4C zKZcwCbQnIqe-8|!uMEF_{eT+401C;k009Ja!FPr?Z{9Hoaj`Re14b!`{{5Rm2&7 z{rtr6`~M#X7X1JQp6`DczJL3|@Sh!MALAc}Pw(FW?feMxKQM8yF@P{Y05S6Mx!+@A z{4d17_?v;7n**4k{xSRoSp^J!P<;OfW>*Le^zhe@48MPW2FkI%V_;y?2Qoeb1Q-Am Wk?nT%u9W`(0000!lvI6;RN#5=* z3}zr`QTOW+P=vFyz3})sQ60r& z5(&5Z?@F$a-FTR@`d{2Zxs=b`xwqq9aWN$GC>3SQIwq{I=PPr3)hesluXVTF1aGc+ zc}jahp^Wx|)X1uijuN>I;jfrBNF8MQ;C}h%2UcNK^{LagZqow>8-u5-pUXO@geCwl CexSVo literal 0 HcmV?d00001 diff --git a/ico/sortir_fs.png b/ico/sortir_fs.png new file mode 100644 index 0000000000000000000000000000000000000000..9310a4713f11321df6698e0dba4afbedef51ed5b GIT binary patch literal 772 zcmV+f1N;1mP)< z`)|VKt60F{@gSn4qi1Lqio{cs)K893?e$=?xdN?J&^?0$Q&Y(N z0`l7xyo(V)6$nGj+;if|3)F^&k%@7{+LpUu)fhY=vyWl>c4(CFAFM+)^Gg+y-abSX zQ?2sqf zBjqw`T51gB9zCQuGmS44DSXu3HZU`>*g#+R{B0; z{V$VLIy&I!cMt>_AeI!{^9kYJy)?ca#SQeL)z^e+*4>05weu6?yVq0cEJFVda&gph z1MKa^X4g`2BYxz2#OBb4Aeu*?ylNR{@a{wO?-Y)V5ycVr|MOs-12BAkWE?L&grP%3 zv)A*E2*Ygt$QfSD++*>z@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ<{z*hZRCwBA{Qv(y12q6Z0I`6G-rmIw($eBU)_*~E zcD56mno83D{{8*`_U*?P&z`-S`1$kq`M-bv`O3(M(D>uWZw6CSEe0zqJqAXg1V8{W zK?Hv>Jb3Vw;pfj^U%q|&Iq&=T?_8Riiaa(p1}Y{dT9a8=n0tYiu!1yz41-~001!Z6 zgP53@SigPy!Epcn)8AjdeC<4V@O;71qZdB^{ri_eNlAvm&`>S?^XG5DA3l6x`1tV~ z1CY(|{{1J0FJHdF4FU)tCMdleXoudx?_ctUkS(s(tfBMP&_3L+rPanT9@bYpp=;$cH4FU)taA32sF@JM)wN(TL?ur*L z-W+sxHp^63R{(nBE6@NIhIj8iFg$+t{<+Yfo1dI{cQJedx`^YeHc)3c+#rAe0vmMm z=EDb8R{C1r-nLq=U%%5*QIQ3P!AGFXKVYCTGrWHNp5e>4f8w5;dv%1FK3w_7_=kaw z;WJ8L0|XG*Yrt@sxO3-mU^s9vfDHNi^(VuxUw?qH`G?`|-6srhKKx~H(ER2sEb@EX z_y4T9fB!Qx{Q36}q?Hxs7=Qo(NdUJ01Au^l0I8|D&Dhx9{p#xPI{*Iw1orm$0P5=P z0QdL!00s#V00ZpC02$?x02%n@4*(GdN%Qsw^z!NH$MNXc{{aLE{|*==00M{+l(NIZ z%79V%1*q|-Hzy~DJufep2GCV6gv2Bb!yE)mWL`~TkotX(K?G>?H%?ZDM~u9G?%rlD z{`&RDq|e{}GNi72&j1iWEHFPZGBPo+va)Oi#>&=DpT2SaV`Ta+_HVEG|BE-bDQSHa z;$`OotK$adC}pM}EPwxV_FcKk#=yqTFbO0M5I{`mc?Os`85o(_ze{p`V(^l_eDcGa z-}U$2a{p$KMM^XD%(T>%p)!>6~;85EfQ3W4+j1Q3eX zn1LEVE?|aYkVcROFb49Om>9tr#0SYUFtIXRdBl7Pq!%E7P+YWO%Z_g^UcTgFVP*XV zjQZaP4j=#j{N+oq%YOX$$sjBw$hd6fT8@{m-!KD%iQ(&)FTcOMXUYWW1qdKUY87k% a0R{jM2BDHjQt{;g0000M=gcj66L@uj?AEkqPN5xVR zaS*O^7Xvvc4ka#b4ylV!&|F(%tBvMtF6NTlheJx*(xzIpgWvj_Y+)xb6ZhGgqzW_1tfQaeNTq*9Z3)LMeuJkUn^kh1~4Lf|3MBN{1YPP;PF!- R9xng@002ovPDHLkV1hU=C2s%# literal 0 HcmV?d00001 diff --git a/ico/viewmag+.png b/ico/viewmag+.png new file mode 100644 index 0000000000000000000000000000000000000000..d2b139c8b734845720c9a9371cceebbccbaca920 GIT binary patch literal 810 zcmV+_1J(SAP)eEK7>?11jeBHy-*Yp9*>I@7Ke5MXmpyufmpSQONJ;gNP+-H z85pBbBnfUshAc@46^39K**Yfxql`$DQlwHD%*`)C5_q^>3Iv|Pwi!4=Uw+08F)rP0Ej#fNfZIgLOPvYaRe+bERSew8cfTCVOp>Nn3e@fDFmK} z#PgV%m_#y}9&iNAMq};YMt{r_0&pCGZQHQ_If%#N7=HT^Ye{3l2|$Yq!VB4KYSlE2 z+F&T)Rulz{a-f7D8ja#b-)rP@6y*V$f3bS3x1BWa zA1gZhrRPLa`+Uuwd{ATewuX9dA1W#b0P?@5yLAI(z27-p6bO|DuI_2v7bz+BVCKzp z{ryPn=~#@vO!qcd+;jrGdpyy;-uuVa(%by}NPH$PcngcGOT%H=ZN|1vM^n{oaXdu; zAW)&$0I)coB2SV-p3T6HSF@{Ub{V%;6aD(ZQw^7d(%qt-H*tUffcy|RSN?a(zv@nD o-cjH$+2ZRFgO!a_>9X&C0BKDGm#86@jQ{`u07*qoM6N<$g3JtG6#xJL literal 0 HcmV?d00001 diff --git a/ico/viewmag-.png b/ico/viewmag-.png new file mode 100644 index 0000000000000000000000000000000000000000..2ae727f351dc9e73b353f63ae84ecbd57f74b264 GIT binary patch literal 800 zcmV+*1K<3KP)4`=S==qNpiBy4KNQredbB?_W~qNuDzxUeXd5DKDN zw1}XEv}k34E`p4}T4>9VEP~RW5cV2l#;I{;u6OP{?xRH*C9|lrJRCmGIRpTPhDQB@ z#5y^b1_Y6Aq5_yeNY=LOiGpEVE}M9N{P>Whb)UIS8NoEB~U6u^(axRT)AKr z=4SxRE(ZvJb1JCSXn}wqD?$N;wIC$MpauOGPo{-KnNU3M7dP- zR&9{a8wMc+t_x6sKoS{5MnM3`q6jX_faAb4OWCS`AL)fDW6^-^*syE|4glM6Kq-YJ zir}J%PqQDP>*hpNz+57E@%7BxIYIzIAaGq5?jM0vGKI;<&#
    1qHPsf(sdr9#fO zt+v{5$g8R<7!^PXK_ZdBgRw`bR4CSj=!(;^cZ~;HJ&EC``tLae0NUHTcwIwHi>B4| zYavf7mzh#3R?-U@V~kP$JnXUdHIb>k8?-OZx6ktr=Q2X~kz2_W{#7^jM^3yPIj9>i z&bc3Nwz~Ja!}qT2imU+urcC1YU+E73(l}}MA-80iCET5UGQD02gnApdcA(k3zay7V zngiSG&(h|u$mnTpcYGU<#kcWTJd@VrDLs4L8i%n27=O2DZVZZ3sjn%?U*8a06^$xh zJK6j>QHT-c8m?ApeaY;DzT7;vr7pB?ZJ>YW;f^zsZOC^Q8=Y6yY%;cW+;rE@x_k9Ea`um@$oWMFH zNw~@J^eE-PI6^io(;CYf#=T+=Kf1fm7U3e&*Egv0620cr%Hpcur^&Jej^h9aaBK@( z8yjCXSJrOpTjoe_? zfZuxo9AIA$Xl0t;ucAEYM_3C$U<_J7g`$Y?`8=euDtsJ3hg$^>#BNbMGDLwv5(F^H zz!-%hN$@H%WJyA}G7Qr!w7CHoWkjNsBAd-)X?Ycrz{BfNAn**1!@v;&Ti{VFmC`Tah3_PPC07RaLB#MA-BbO^|xB^x)>yyT|0n4^vS~hF|mTiMl3W4V# z@jMd0XQAu4F;~E1GIjUs^!G(V0FEPY90$%WLpqhl#G4P;*3FC?fJQ1BGlfES)3VH# zP&nvS6a|cOpoAcqOyc>-OO#3!wLw~bqiLr4LbE41_+H8#SX|?JE)S&AJ2B)5AU@uX_;|ZpN<2Cr zx$Df%#WHa}@u5-|L@xp@NX9m4!tXI#AQf)Ufb*mPH8w$WMT)wSW6R tXmefnj=GLj1giJ>hs01_M`IzTyA?002ovPDHLkV1n&wZe#!e literal 0 HcmV?d00001 diff --git a/ico/viewmagfit.png b/ico/viewmagfit.png new file mode 100644 index 0000000000000000000000000000000000000000..001ea7aabe4df1a5ce99a36f236de8af5269d9d7 GIT binary patch literal 846 zcmV-U1F`&xP)@&9+e?>pzrnOo*eXOfI~sW#Fk8Y-y^aiv*I5kc^Rf+*63bWufH z7Yc%i>+Xu{x(K)s5sXEqT@@8lNhz(ZDQGh7%ru!ylT0!>b3GR~DlyKxe);h}FOL8? zfBxH)VXEV{ZGLQM`J^;Zh7irb4=%Pn?~8ti+b2%E+-Jy$bLZw#+LrGn-Sp|yNYahP zYz)JI2%<1xr`h~%`~K#qtsQ^<)TuXv{ebF+eld|qp3df6*UhA`EKPq9(Czk+(!zkb-Qo8G*V_F_Knzd9inUYT_7hWGBQU#M*wC*S^Rtqve!XL$=){JSKt z0Y7)n{I@V)zCQl$voAe&_R!?P#QK#VxpuYk-BL|{_{sOHWvPqI0pEze(L`_(LFAXX zz883YB3a&TwVc#w;hFKnlbBxR(e1`|;cza!Aa#+iJ7=im%18X((Tz*U=%!!7-wTwM zb~{gG^g_K-H&bJU>4&DKVu@hw$en6?TIwP%`Xz3)wi>7xqQ`-ZU*dlszx+q5H!8&9 z{Yp*c4nF*}O6AS#E9x<+i(J-4mUWR;-~zC+Kf*tAZPn{MXsWAs|E$g2u33l5{fS$D Y0VyLF=MWlp;{X5v07*qoM6N<$g5l1OzyJUM literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..de38ea5fcd7d78ba887072f7430a9b1c60c8fb8a GIT binary patch literal 8076 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 literal 0 HcmV?d00001 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