From 264d2bba603f69539d59fdfaf1524ef1d95f8b55 Mon Sep 17 00:00:00 2001 From: test <grave54122@gmail.com> Date: Sun, 30 Mar 2025 13:13:59 +0000 Subject: [PATCH] TP2 --- src/src/twisk/MainTwisk.java | 4 + src/src/twisk/exceptions/ArcException.java | 15 ++ src/src/twisk/exceptions/TwiskException.java | 15 ++ src/src/twisk/modele/ActiviteIG.java | 6 +- src/src/twisk/modele/ArcIG.java | 45 ++++ src/src/twisk/modele/CourbeIG.java | 62 +++++ src/src/twisk/modele/EtapeIG.java | 88 ++++++- src/src/twisk/modele/LigneDroiteIG.java | 16 ++ src/src/twisk/modele/MondeIG.java | 237 ++++++++++++++++++- src/src/twisk/modele/PointDeControleIG.java | 57 +++++ src/src/twisk/outils/TailleComposants.java | 45 ++++ src/src/twisk/ressources/css/style.css | 99 ++++++++ src/src/twisk/test/modele/MondeIGTest.java | 79 ++++++- src/src/twisk/vues/VueActiviteIG.java | 19 +- src/src/twisk/vues/VueArcIG.java | 23 ++ src/src/twisk/vues/VueCourbeIG.java | 93 ++++++++ src/src/twisk/vues/VueLigneDroiteIG.java | 80 +++++++ src/src/twisk/vues/VueMondeIG.java | 69 +++++- src/src/twisk/vues/VueOutils.java | 4 + src/src/twisk/vues/VuePointDeControleIG.java | 68 ++++++ 20 files changed, 1097 insertions(+), 27 deletions(-) create mode 100644 src/src/twisk/exceptions/ArcException.java create mode 100644 src/src/twisk/exceptions/TwiskException.java create mode 100644 src/src/twisk/modele/ArcIG.java create mode 100644 src/src/twisk/modele/CourbeIG.java create mode 100644 src/src/twisk/modele/LigneDroiteIG.java create mode 100644 src/src/twisk/modele/PointDeControleIG.java create mode 100644 src/src/twisk/outils/TailleComposants.java create mode 100644 src/src/twisk/ressources/css/style.css create mode 100644 src/src/twisk/vues/VueArcIG.java create mode 100644 src/src/twisk/vues/VueCourbeIG.java create mode 100644 src/src/twisk/vues/VueLigneDroiteIG.java create mode 100644 src/src/twisk/vues/VuePointDeControleIG.java diff --git a/src/src/twisk/MainTwisk.java b/src/src/twisk/MainTwisk.java index 3044754..785b687 100644 --- a/src/src/twisk/MainTwisk.java +++ b/src/src/twisk/MainTwisk.java @@ -30,6 +30,10 @@ public class MainTwisk extends Application { // Création de la scène Scene scene = new Scene(root, 800, 600); + // Chargement du fichier CSS + String cssPath = getClass().getResource("/twisk/ressources/css/style.css").toExternalForm(); + scene.getStylesheets().add(cssPath); + // Configuration de la fenêtre principale primaryStage.setTitle("TwiskIG"); primaryStage.setScene(scene); diff --git a/src/src/twisk/exceptions/ArcException.java b/src/src/twisk/exceptions/ArcException.java new file mode 100644 index 0000000..b17d143 --- /dev/null +++ b/src/src/twisk/exceptions/ArcException.java @@ -0,0 +1,15 @@ +package twisk.exceptions; + +/** + * Exception levée lors d'un problème avec les arcs + */ +public class ArcException extends TwiskException { + + /** + * Constructeur + * @param message message d'erreur + */ + public ArcException(String message) { + super(message); + } +} diff --git a/src/src/twisk/exceptions/TwiskException.java b/src/src/twisk/exceptions/TwiskException.java new file mode 100644 index 0000000..8980414 --- /dev/null +++ b/src/src/twisk/exceptions/TwiskException.java @@ -0,0 +1,15 @@ +package twisk.exceptions; + +/** + * Exception de base pour toutes les exceptions spécifiques à Twisk + */ +public class TwiskException extends Exception { + + /** + * Constructeur + * @param message message d'erreur + */ + public TwiskException(String message) { + super(message); + } +} diff --git a/src/src/twisk/modele/ActiviteIG.java b/src/src/twisk/modele/ActiviteIG.java index 3ddd57b..bc4d491 100644 --- a/src/src/twisk/modele/ActiviteIG.java +++ b/src/src/twisk/modele/ActiviteIG.java @@ -1,8 +1,12 @@ package twisk.modele; +import twisk.outils.TailleComposants; + public class ActiviteIG extends EtapeIG { public ActiviteIG(String nom) { - super(nom, 100, 60); // Dimensions par défaut pour une activité + // Utilisation des tailles définies dans TailleComposants + super(nom, TailleComposants.getInstance().getLargeurActivite(), + TailleComposants.getInstance().getHauteurActivite()); } } diff --git a/src/src/twisk/modele/ArcIG.java b/src/src/twisk/modele/ArcIG.java new file mode 100644 index 0000000..c1557ee --- /dev/null +++ b/src/src/twisk/modele/ArcIG.java @@ -0,0 +1,45 @@ +package twisk.modele; + +/** + * Classe abstraite représentant un arc dans l'interface graphique + */ +public abstract class ArcIG { + protected final PointDeControleIG p1; + protected final PointDeControleIG p2; + protected final String id; + + /** + * Constructeur + * @param p1 premier point de contrôle + * @param p2 deuxième point de contrôle + */ + public ArcIG(PointDeControleIG p1, PointDeControleIG p2) { + this.p1 = p1; + this.p2 = p2; + this.id = FabriqueIdentifiant.getInstance().getIdentifiantUnique(); + } + + /** + * Retourne le premier point de contrôle + * @return premier point de contrôle + */ + public PointDeControleIG getP1() { + return p1; + } + + /** + * Retourne le deuxième point de contrôle + * @return deuxième point de contrôle + */ + public PointDeControleIG getP2() { + return p2; + } + + /** + * Retourne l'identifiant de l'arc + * @return identifiant + */ + public String getId() { + return id; + } +} diff --git a/src/src/twisk/modele/CourbeIG.java b/src/src/twisk/modele/CourbeIG.java new file mode 100644 index 0000000..47add4d --- /dev/null +++ b/src/src/twisk/modele/CourbeIG.java @@ -0,0 +1,62 @@ +package twisk.modele; + +/** + * Classe représentant une courbe dans l'interface graphique + */ +public class CourbeIG extends ArcIG { + private final int controlX1; + private final int controlY1; + private final int controlX2; + private final int controlY2; + + /** + * Constructeur + * @param p1 premier point de contrôle + * @param p2 deuxième point de contrôle + * @param controlX1 coordonnée X du premier point de contrôle intermédiaire + * @param controlY1 coordonnée Y du premier point de contrôle intermédiaire + * @param controlX2 coordonnée X du deuxième point de contrôle intermédiaire + * @param controlY2 coordonnée Y du deuxième point de contrôle intermédiaire + */ + public CourbeIG(PointDeControleIG p1, PointDeControleIG p2, + int controlX1, int controlY1, + int controlX2, int controlY2) { + super(p1, p2); + this.controlX1 = controlX1; + this.controlY1 = controlY1; + this.controlX2 = controlX2; + this.controlY2 = controlY2; + } + + /** + * Retourne la coordonnée X du premier point de contrôle intermédiaire + * @return coordonnée X + */ + public int getControlX1() { + return controlX1; + } + + /** + * Retourne la coordonnée Y du premier point de contrôle intermédiaire + * @return coordonnée Y + */ + public int getControlY1() { + return controlY1; + } + + /** + * Retourne la coordonnée X du deuxième point de contrôle intermédiaire + * @return coordonnée X + */ + public int getControlX2() { + return controlX2; + } + + /** + * Retourne la coordonnée Y du deuxième point de contrôle intermédiaire + * @return coordonnée Y + */ + public int getControlY2() { + return controlY2; + } +} diff --git a/src/src/twisk/modele/EtapeIG.java b/src/src/twisk/modele/EtapeIG.java index fc57098..66514b0 100644 --- a/src/src/twisk/modele/EtapeIG.java +++ b/src/src/twisk/modele/EtapeIG.java @@ -1,12 +1,17 @@ package twisk.modele; -public abstract class EtapeIG { - private String nom; - private final String identifiant; - private int posX; - private int posY; - private int largeur; - private int hauteur; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public abstract class EtapeIG implements Iterable<PointDeControleIG> { + protected String nom; + protected String identifiant; + protected int posX; + protected int posY; + protected int largeur; + protected int hauteur; + protected List<PointDeControleIG> pointsDeControle; public EtapeIG(String nom, int largeur, int hauteur) { this.nom = nom; @@ -16,8 +21,69 @@ public abstract class EtapeIG { this.posY = (int) (Math.random() * 500); this.largeur = largeur; this.hauteur = hauteur; + + // Initialisation de la liste des points de contrôle + this.pointsDeControle = new ArrayList<>(4); + + // Création des 4 points de contrôle (un au milieu de chaque côté) + creerPointsDeControle(); + } + + /** + * Crée les 4 points de contrôle pour l'étape + */ + private void creerPointsDeControle() { + // Point sur le côté supérieur (milieu) + pointsDeControle.add(new PointDeControleIG( + posX + largeur / 2, + posY, + "p" + identifiant + "N", // N pour Nord + this + )); + + // Point sur le côté droit (milieu) + pointsDeControle.add(new PointDeControleIG( + posX + largeur, + posY + hauteur / 2, + "p" + identifiant + "E", // E pour Est + this + )); + + // Point sur le côté inférieur (milieu) + pointsDeControle.add(new PointDeControleIG( + posX + largeur / 2, + posY + hauteur, + "p" + identifiant + "S", // S pour Sud + this + )); + + // Point sur le côté gauche (milieu) + pointsDeControle.add(new PointDeControleIG( + posX, + posY + hauteur / 2, + "p" + identifiant + "O", // O pour Ouest + this + )); + } + + /** + * Met à jour la position des points de contrôle quand l'étape est déplacée + */ + public void majPointsDeControle() { + // Vider la liste actuelle + pointsDeControle.clear(); + + // Recréer les points de contrôle avec les nouvelles coordonnées + creerPointsDeControle(); + } + + // Implémentation de l'interface Iterable + @Override + public Iterator<PointDeControleIG> iterator() { + return pointsDeControle.iterator(); } + // Getters et setters existants public String getNom() { return nom; } @@ -36,6 +102,8 @@ public abstract class EtapeIG { public void setPosX(int posX) { this.posX = posX; + // Mettre à jour les points de contrôle + majPointsDeControle(); } public int getPosY() { @@ -44,6 +112,8 @@ public abstract class EtapeIG { public void setPosY(int posY) { this.posY = posY; + // Mettre à jour les points de contrôle + majPointsDeControle(); } public int getLargeur() { @@ -52,6 +122,8 @@ public abstract class EtapeIG { public void setLargeur(int largeur) { this.largeur = largeur; + // Mettre à jour les points de contrôle + majPointsDeControle(); } public int getHauteur() { @@ -60,5 +132,7 @@ public abstract class EtapeIG { public void setHauteur(int hauteur) { this.hauteur = hauteur; + // Mettre à jour les points de contrôle + majPointsDeControle(); } } diff --git a/src/src/twisk/modele/LigneDroiteIG.java b/src/src/twisk/modele/LigneDroiteIG.java new file mode 100644 index 0000000..f4b42a1 --- /dev/null +++ b/src/src/twisk/modele/LigneDroiteIG.java @@ -0,0 +1,16 @@ +package twisk.modele; + +/** + * Classe représentant une ligne droite dans l'interface graphique + */ +public class LigneDroiteIG extends ArcIG { + + /** + * Constructeur + * @param p1 premier point de contrôle + * @param p2 deuxième point de contrôle + */ + public LigneDroiteIG(PointDeControleIG p1, PointDeControleIG p2) { + super(p1, p2); + } +} diff --git a/src/src/twisk/modele/MondeIG.java b/src/src/twisk/modele/MondeIG.java index edc5540..55e3ead 100644 --- a/src/src/twisk/modele/MondeIG.java +++ b/src/src/twisk/modele/MondeIG.java @@ -1,20 +1,26 @@ package twisk.modele; +import twisk.exceptions.ArcException; import twisk.outils.Observable; import twisk.outils.Observateur; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import javafx.geometry.Point2D; +import java.util.*; public class MondeIG implements Iterable<EtapeIG>, Observable { private final Map<String, EtapeIG> etapes; private int compteurActivites; private ArrayList<Observateur> observateurs; + private ArrayList<ArcIG> arcs; + private PointDeControleIG pointSelectionne; + private List<Point2D> pointsCliques; + private static final int NB_POINTS_COURBE = 4; public MondeIG() { etapes = new HashMap<>(); + arcs = new ArrayList<>(); + pointSelectionne = null; + pointsCliques = new ArrayList<>(); compteurActivites = 1; observateurs = new ArrayList<>(); @@ -22,6 +28,10 @@ public class MondeIG implements Iterable<EtapeIG>, Observable { ajouter("Activite"); } + /** + * Ajoute une étape du type spécifié + * @param type le type d'étape à ajouter + */ public void ajouter(String type) { if ("Activite".equals(type)) { String nom = "Activite " + compteurActivites++; @@ -37,15 +47,105 @@ public class MondeIG implements Iterable<EtapeIG>, Observable { // Autres types d'étapes pourront être ajoutés ici dans le futur } + /** + * Ajoute un arc entre deux points de contrôle + * @param pt1 premier point de contrôle + * @param pt2 deuxième point de contrôle + * @throws ArcException si l'arc ne peut pas être ajouté + */ + public void ajouter(PointDeControleIG pt1, PointDeControleIG pt2) throws ArcException { + // Contrainte 4: Vérifier que les points sont différents + if (pt1 == pt2) { + throw new ArcException("Impossible de créer un arc: les deux points sont identiques"); + } + + // Contrainte 1: Vérifier que les points ne sont pas sur la même étape + if (pt1.getEtape().equals(pt2.getEtape())) { + throw new ArcException("Impossible de créer un arc: les deux points appartiennent à la même étape"); + } + + // Contrainte 2: Vérifier qu'il n'existe pas déjà un arc entre ces deux points + for (ArcIG arc : arcs) { + if ((arc.getP1().equals(pt1) && arc.getP2().equals(pt2))) { + throw new ArcException("Impossible de créer un arc: un arc existe déjà entre ces deux points"); + } + } + + // Contrainte 3: Vérifier qu'il n'existe pas déjà un arc dans le sens inverse + for (ArcIG arc : arcs) { + if ((arc.getP1().equals(pt2) && arc.getP2().equals(pt1)) || + (arc.getP1().getEtape().equals(pt2.getEtape()) && arc.getP2().getEtape().equals(pt1.getEtape()))) { + throw new ArcException("Impossible de créer un arc: un arc existe déjà dans le sens inverse"); + } + } + + // Contrainte 5: Vérifier que le point de départ n'est pas déjà utilisé comme source d'un autre arc + for (ArcIG arc : arcs) { + if (arc.getP1().equals(pt1)) { + throw new ArcException("Impossible de créer un arc: le point de départ est déjà utilisé comme source d'un autre arc"); + } + } + + // Contrainte 6: Vérifier que le point d'arrivée n'est pas déjà utilisé comme destination d'un autre arc + for (ArcIG arc : arcs) { + if (arc.getP2().equals(pt2)) { + throw new ArcException("Impossible de créer un arc: le point d'arrivée est déjà utilisé comme destination d'un autre arc"); + } + } + + // Toutes les contraintes sont satisfaites, on peut ajouter l'arc + ArcIG arc = new LigneDroiteIG(pt1, pt2); + arcs.add(arc); + notifierObservateurs(); + } + + /** + * Gère la sélection d'un point de contrôle + * @param pt le point de contrôle sélectionné + * @throws ArcException si l'arc ne peut pas être créé + */ + public void selectionnerPointDeControle(PointDeControleIG pt) throws ArcException { + if (pointSelectionne == null) { + // Premier point sélectionné + pointSelectionne = pt; + } else { + // Deuxième point sélectionné, on tente de créer un arc + PointDeControleIG premier = pointSelectionne; + // Réinitialiser la sélection avant de tenter d'ajouter l'arc + pointSelectionne = null; + // Tenter d'ajouter l'arc (peut lever une ArcException) + ajouter(premier, pt); + } + } + + + + /** + * Retourne l'étape correspondant à l'identifiant + * @param identifiant l'identifiant de l'étape + * @return l'étape correspondante + */ public EtapeIG getEtape(String identifiant) { return etapes.get(identifiant); } + /** + * Retourne un itérateur sur les étapes + * @return itérateur sur les étapes + */ @Override public Iterator<EtapeIG> iterator() { return etapes.values().iterator(); } + /** + * Retourne un itérateur sur les arcs + * @return itérateur sur les arcs + */ + public Iterator<ArcIG> iteratorArcs() { + return arcs.iterator(); + } + @Override public void ajouterObservateur(Observateur o) { observateurs.add(o); @@ -57,4 +157,133 @@ public class MondeIG implements Iterable<EtapeIG>, Observable { o.reagir(); } } + /** + * Gère un clic sur le monde + * @param x coordonnée x du clic + * @param y coordonnée y du clic + * @throws ArcException si l'arc ne peut pas être créé + */ + public void clic(double x, double y) throws ArcException { + // Vérifier si le clic est sur un point de contrôle + PointDeControleIG pointClique = null; + + // Parcourir toutes les étapes et leurs points de contrôle + for (EtapeIG etape : etapes.values()) { + for (PointDeControleIG pt : etape) { + // Vérifier si le clic est sur ce point (distance < 10 pixels) + double distance = Math.sqrt(Math.pow(pt.getPosX() - x, 2) + Math.pow(pt.getPosY() - y, 2)); + if (distance < 10) { + pointClique = pt; + break; + } + } + if (pointClique != null) { + break; + } + } + + // Si le clic est sur un point de contrôle + if (pointClique != null) { + // Ajouter le point à la liste des points cliqués + pointsCliques.add(new Point2D(pointClique.getPosX(), pointClique.getPosY())); + + // Si c'est le premier point, le mémoriser comme point sélectionné + if (pointsCliques.size() == 1) { + pointSelectionne = pointClique; + } + // Si c'est le deuxième point et que le premier était un point de contrôle, + // créer une ligne droite + else if (pointsCliques.size() == 2 && pointSelectionne != null) { + // Créer une ligne droite + try { + ajouter(pointSelectionne, pointClique); + } catch (ArcException e) { + // Réinitialiser en cas d'erreur + pointSelectionne = null; + pointsCliques.clear(); + throw e; + } + + // Réinitialiser + pointSelectionne = null; + pointsCliques.clear(); + } + // Si c'est le quatrième point et que le premier était un point de contrôle, + // créer une courbe + else if (pointsCliques.size() == NB_POINTS_COURBE && pointSelectionne != null) { + // Récupérer les points intermédiaires + Point2D p2 = pointsCliques.get(1); + Point2D p3 = pointsCliques.get(2); + + // Vérifier les contraintes comme dans la méthode ajouter + if (pointSelectionne == pointClique) { + pointSelectionne = null; + pointsCliques.clear(); + throw new ArcException("Impossible de créer un arc: les deux points sont identiques"); + } + + if (pointSelectionne.getEtape().equals(pointClique.getEtape())) { + pointSelectionne = null; + pointsCliques.clear(); + throw new ArcException("Impossible de créer un arc: les deux points appartiennent à la même étape"); + } + + // Vérifier les autres contraintes comme dans la méthode ajouter + for (ArcIG arc : arcs) { + if ((arc.getP1().equals(pointSelectionne) && arc.getP2().equals(pointClique))) { + pointSelectionne = null; + pointsCliques.clear(); + throw new ArcException("Impossible de créer un arc: un arc existe déjà entre ces deux points"); + } + } + + for (ArcIG arc : arcs) { + if ((arc.getP1().equals(pointClique) && arc.getP2().equals(pointSelectionne)) || + (arc.getP1().getEtape().equals(pointClique.getEtape()) && arc.getP2().getEtape().equals(pointSelectionne.getEtape()))) { + pointSelectionne = null; + pointsCliques.clear(); + throw new ArcException("Impossible de créer un arc: un arc existe déjà dans le sens inverse"); + } + } + + for (ArcIG arc : arcs) { + if (arc.getP1().equals(pointSelectionne)) { + pointSelectionne = null; + pointsCliques.clear(); + throw new ArcException("Impossible de créer un arc: le point de départ est déjà utilisé comme source d'un autre arc"); + } + } + + for (ArcIG arc : arcs) { + if (arc.getP2().equals(pointClique)) { + pointSelectionne = null; + pointsCliques.clear(); + throw new ArcException("Impossible de créer un arc: le point d'arrivée est déjà utilisé comme destination d'un autre arc"); + } + } + + // Créer une courbe + ArcIG arc = new CourbeIG( + pointSelectionne, + pointClique, + (int) p2.getX(), (int) p2.getY(), + (int) p3.getX(), (int) p3.getY() + ); + arcs.add(arc); + + // Réinitialiser + pointSelectionne = null; + pointsCliques.clear(); + + // Notifier les observateurs + notifierObservateurs(); + } + } + // Sinon, si on est en train de créer une courbe (entre 1 et 3 points déjà cliqués), + // ajouter le point comme point de contrôle intermédiaire + else if (pointsCliques.size() >= 1 && pointsCliques.size() < NB_POINTS_COURBE - 1) { + pointsCliques.add(new Point2D(x, y)); + } + } + } diff --git a/src/src/twisk/modele/PointDeControleIG.java b/src/src/twisk/modele/PointDeControleIG.java new file mode 100644 index 0000000..30fc179 --- /dev/null +++ b/src/src/twisk/modele/PointDeControleIG.java @@ -0,0 +1,57 @@ +package twisk.modele; + +/** + * Classe représentant un point de contrôle sur une étape + */ +public class PointDeControleIG { + private final int posX; + private final int posY; + private final String id; + private final EtapeIG etape; + + /** + * Constructeur + * @param posX position X du centre du point + * @param posY position Y du centre du point + * @param id identifiant unique + * @param etape étape à laquelle le point est rattaché + */ + public PointDeControleIG(int posX, int posY, String id, EtapeIG etape) { + this.posX = posX; + this.posY = posY; + this.id = id; + this.etape = etape; + } + + /** + * Retourne la position X du point + * @return position X + */ + public int getPosX() { + return posX; + } + + /** + * Retourne la position Y du point + * @return position Y + */ + public int getPosY() { + return posY; + } + + /** + * Retourne l'identifiant du point + * @return identifiant + */ + public String getId() { + return id; + } + + /** + * Retourne l'étape à laquelle le point est rattaché + * @return étape + */ + public EtapeIG getEtape() { + return etape; + } +} diff --git a/src/src/twisk/outils/TailleComposants.java b/src/src/twisk/outils/TailleComposants.java new file mode 100644 index 0000000..e080df4 --- /dev/null +++ b/src/src/twisk/outils/TailleComposants.java @@ -0,0 +1,45 @@ +package twisk.outils; + +/** + * Classe singleton qui centralise les constantes de taille des composants graphiques + */ +public class TailleComposants { + private static TailleComposants instance = new TailleComposants(); + + // Tailles pour les activités + private final int largeurActivite; + private final int hauteurActivite; + + // Constructeur privé (singleton) + private TailleComposants() { + // Initialisation des tailles par défaut + largeurActivite = 100; + hauteurActivite = 60; + + // D'autres tailles pourront être ajoutées ici dans le futur + } + + /** + * Retourne l'instance unique de TailleComposants + * @return l'instance de TailleComposants + */ + public static TailleComposants getInstance() { + return instance; + } + + /** + * Retourne la largeur par défaut d'une activité + * @return la largeur en pixels + */ + public int getLargeurActivite() { + return largeurActivite; + } + + /** + * Retourne la hauteur par défaut d'une activité + * @return la hauteur en pixels + */ + public int getHauteurActivite() { + return hauteurActivite; + } +} diff --git a/src/src/twisk/ressources/css/style.css b/src/src/twisk/ressources/css/style.css new file mode 100644 index 0000000..9618368 --- /dev/null +++ b/src/src/twisk/ressources/css/style.css @@ -0,0 +1,99 @@ +/* Styles généraux */ +.root { + -fx-font-family: 'Segoe UI', Arial, sans-serif; + -fx-background-color: #f5f5f5; +} + +/* Style pour VueMondeIG */ +.monde { + -fx-background-color: white; + -fx-padding: 10px; +} + +/* Style pour VueActiviteIG */ +.activite { + -fx-background-color: #e6f2ff; + -fx-border-color: #0066cc; + -fx-border-width: 2px; + -fx-border-radius: 5px; + -fx-background-radius: 5px; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 5, 0, 0, 2); +} + +/* Style pour le titre de l'activité */ +.titre-activite { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #0066cc; + -fx-padding: 5px; + -fx-background-color: #cce0ff; + -fx-background-radius: 5px 5px 0 0; +} + +/* Style pour la zone des clients */ +.zone-clients { + -fx-background-color: white; + -fx-border-color: #99ccff; + -fx-border-width: 1px; + -fx-border-radius: 0 0 5px 5px; + -fx-background-radius: 0 0 5px 5px; + -fx-padding: 5px; + -fx-min-height: 30px; +} + +/* Style pour la barre d'outils */ +.outils { + -fx-background-color: #333333; + -fx-padding: 10px; + -fx-spacing: 10px; +} + +/* Style pour les boutons */ +.bouton { + -fx-background-color: #0066cc; + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-padding: 8px 15px; + -fx-background-radius: 5px; + -fx-cursor: hand; +} + +.bouton:hover { + -fx-background-color: #0052a3; +} + +.bouton:pressed { + -fx-background-color: #003d7a; +} + +/* Style pour les points de contrôle */ +.point-controle { + -fx-cursor: hand; +} + +.point-controle:hover { + -fx-fill: #66b3ff; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 3, 0, 0, 0); +} + +/* Style pour les arcs */ +.arc-ligne { + -fx-stroke: #0066cc; + -fx-stroke-width: 2; +} + +.arc-fleche { + -fx-fill: #0066cc; + -fx-stroke: #0066cc; + -fx-stroke-width: 1; +} + +/* Style pour les points de contrôle */ +.point-controle { + -fx-cursor: hand; +} + +.point-controle:hover { + -fx-fill: #66b3ff; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 3, 0, 0, 0); +} diff --git a/src/src/twisk/test/modele/MondeIGTest.java b/src/src/twisk/test/modele/MondeIGTest.java index 09129b8..5cd742c 100644 --- a/src/src/twisk/test/modele/MondeIGTest.java +++ b/src/src/twisk/test/modele/MondeIGTest.java @@ -2,10 +2,8 @@ package twisk.test.modele; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import twisk.modele.ActiviteIG; -import twisk.modele.EtapeIG; -import twisk.modele.FabriqueIdentifiant; -import twisk.modele.MondeIG; +import twisk.exceptions.ArcException; +import twisk.modele.*; import java.util.Iterator; @@ -69,4 +67,77 @@ class MondeIGTest { assertEquals(3, count, "Le monde devrait contenir 3 étapes"); } + @Test + void testAjouterArcAvecExceptions() { + MondeIG monde = new MondeIG(); + + // Ajouter deux activités + monde.ajouter("Activite"); // Activite 2 + + // Récupérer les activités + Iterator<EtapeIG> it = monde.iterator(); + EtapeIG etape1 = it.next(); // Activite 1 (par défaut) + EtapeIG etape2 = it.next(); // Activite 2 + + // Récupérer les points de contrôle + Iterator<PointDeControleIG> itPt1 = etape1.iterator(); + PointDeControleIG pt1 = itPt1.next(); // Premier point de l'activité 1 + + Iterator<PointDeControleIG> itPt2 = etape2.iterator(); + PointDeControleIG pt2 = itPt2.next(); // Premier point de l'activité 2 + + // Test d'ajout d'un arc valide + try { + monde.ajouter(pt1, pt2); + // Vérifier que l'arc a été ajouté + Iterator<ArcIG> itArc = monde.iteratorArcs(); + assertTrue(itArc.hasNext(), "Un arc devrait avoir été ajouté"); + } catch (ArcException e) { + fail("L'ajout d'un arc valide ne devrait pas lever d'exception"); + } + + // Test de la contrainte 2: pas de duplication d'arc + try { + monde.ajouter(pt1, pt2); + fail("Une exception aurait dû être levée pour un arc dupliqué"); + } catch (ArcException e) { + // C'est le comportement attendu + } + + // Test de la contrainte 3: pas d'arc dans le sens inverse + try { + monde.ajouter(pt2, pt1); + fail("Une exception aurait dû être levée pour un arc dans le sens inverse"); + } catch (ArcException e) { + // C'est le comportement attendu + } + + // Test de la contrainte 5: un point ne peut être source que d'un seul arc + PointDeControleIG pt1bis = itPt1.next(); // Deuxième point de l'activité 1 + PointDeControleIG pt2bis = itPt2.next(); // Deuxième point de l'activité 2 + try { + monde.ajouter(pt1, pt2bis); + fail("Une exception aurait dû être levée pour un point source déjà utilisé"); + } catch (ArcException e) { + // C'est le comportement attendu + } + + // Test de la contrainte 6: un point ne peut être destination que d'un seul arc + try { + monde.ajouter(pt1bis, pt2); + fail("Une exception aurait dû être levée pour un point destination déjà utilisé"); + } catch (ArcException e) { + // C'est le comportement attendu + } + + // Test de la contrainte 1: pas d'arc entre points de la même étape + PointDeControleIG pt1ter = itPt1.next(); // Troisième point de l'activité 1 + try { + monde.ajouter(pt1, pt1ter); + fail("Une exception aurait dû être levée pour un arc entre points de la même étape"); + } catch (ArcException e) { + // C'est le comportement attendu + } + } + } diff --git a/src/src/twisk/vues/VueActiviteIG.java b/src/src/twisk/vues/VueActiviteIG.java index 7d4a70a..787d348 100644 --- a/src/src/twisk/vues/VueActiviteIG.java +++ b/src/src/twisk/vues/VueActiviteIG.java @@ -2,7 +2,6 @@ package twisk.vues; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import twisk.modele.ActiviteIG; import twisk.modele.EtapeIG; public class VueActiviteIG extends VueEtapeIG { @@ -11,23 +10,27 @@ public class VueActiviteIG extends VueEtapeIG { public VueActiviteIG(EtapeIG etape) { super(etape); + // Appliquer la classe CSS à l'activité + this.getStyleClass().add("activite"); + + // Appliquer la classe CSS au titre + titre.getStyleClass().add("titre-activite"); + // Création de la zone pour les clients zoneClients = new HBox(); - zoneClients.setPrefSize(etape.getLargeur(), etape.getHauteur() - 20); // Réserver de l'espace pour le titre - zoneClients.setStyle("-fx-border-color: #0059FF; -fx-background-insets: 0 0 -1 0, 0, 1, 2; -fx-background-radius: 3px, 3px, 2px, 1px;"); + zoneClients.setPrefSize(etape.getLargeur(), etape.getHauteur() - 30); // Réserver de l'espace pour le titre + zoneClients.getStyleClass().add("zone-clients"); // Organisation verticale: titre en haut, zone clients en dessous VBox contenu = new VBox(); contenu.getChildren().addAll(titre, zoneClients); - - // Définir la taille du composant - this.setPrefSize(etape.getLargeur(), etape.getHauteur()); + contenu.setPrefSize(etape.getLargeur(), etape.getHauteur()); // Ajout du contenu au composant this.getChildren().clear(); this.getChildren().add(contenu); - // Ajout d'un style pour la boîte - this.setStyle("-fx-border-color: black; -fx-border-width: 1px;"); + // Positionnement du composant + relocate(etape.getPosX(), etape.getPosY()); } } diff --git a/src/src/twisk/vues/VueArcIG.java b/src/src/twisk/vues/VueArcIG.java new file mode 100644 index 0000000..94adb20 --- /dev/null +++ b/src/src/twisk/vues/VueArcIG.java @@ -0,0 +1,23 @@ +package twisk.vues; + +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import twisk.modele.ArcIG; + +/** + * Vue abstraite d'un arc + */ +public abstract class VueArcIG extends Pane { + protected final ArcIG arc; + + /** + * Constructeur + * @param arc l'arc à représenter + */ + public VueArcIG(ArcIG arc) { + this.arc = arc; + + // Ajouter une classe CSS pour le styling + this.getStyleClass().add("arc"); + } +} diff --git a/src/src/twisk/vues/VueCourbeIG.java b/src/src/twisk/vues/VueCourbeIG.java new file mode 100644 index 0000000..a1c973d --- /dev/null +++ b/src/src/twisk/vues/VueCourbeIG.java @@ -0,0 +1,93 @@ +package twisk.vues; + +import javafx.scene.paint.Color; +import javafx.scene.shape.CubicCurve; +import javafx.scene.shape.Polyline; +import twisk.modele.ArcIG; +import twisk.modele.CourbeIG; +import twisk.modele.PointDeControleIG; + +/** + * Vue d'une courbe + */ +public class VueCourbeIG extends VueArcIG { + private final CubicCurve courbe; + private final Polyline fleche; + + /** + * Constructeur + * @param arc l'arc à représenter + */ + public VueCourbeIG(ArcIG arc) { + super(arc); + + CourbeIG courbeIG = (CourbeIG) arc; + + // Récupérer les coordonnées des points de contrôle + PointDeControleIG p1 = arc.getP1(); + PointDeControleIG p2 = arc.getP2(); + + // Créer la courbe + courbe = new CubicCurve( + p1.getPosX(), p1.getPosY(), // Point de départ + courbeIG.getControlX1(), courbeIG.getControlY1(), // Premier point de contrôle + courbeIG.getControlX2(), courbeIG.getControlY2(), // Deuxième point de contrôle + p2.getPosX(), p2.getPosY() // Point d'arrivée + ); + courbe.setFill(null); // Pas de remplissage + courbe.setStroke(Color.valueOf("#0066cc")); + courbe.setStrokeWidth(2); + courbe.getStyleClass().add("arc-courbe"); + + // Calculer les coordonnées de la flèche + // Pour une courbe, on utilise la tangente au point d'arrivée + double[] coordsFleche = calculerCoordonneesTrianglePourCourbe( + courbeIG.getControlX2(), courbeIG.getControlY2(), + p2.getPosX(), p2.getPosY() + ); + + // Créer la flèche (triangle) + fleche = new Polyline(coordsFleche); + fleche.setFill(Color.valueOf("#0066cc")); + fleche.setStroke(Color.valueOf("#0066cc")); + fleche.getStyleClass().add("arc-fleche"); + + // Ajouter les composants au Pane + this.getChildren().addAll(courbe, fleche); + } + + /** + * Calcule les coordonnées des trois points du triangle formant la flèche + * en utilisant la tangente à la courbe au point d'arrivée + * @param controlX coordonnée x du point de contrôle précédant le point d'arrivée + * @param controlY coordonnée y du point de contrôle précédant le point d'arrivée + * @param x2 coordonnée x du point d'arrivée + * @param y2 coordonnée y du point d'arrivée + * @return tableau des coordonnées des trois points du triangle + */ + private double[] calculerCoordonneesTrianglePourCourbe(double controlX, double controlY, double x2, double y2) { + // Longueur de la flèche + double longueurFleche = 15; + + // Angle de la flèche (en radians) + double angleFleche = Math.PI / 6; // 30 degrés + + // Calculer l'angle de la tangente à la courbe au point d'arrivée + double angle = Math.atan2(y2 - controlY, x2 - controlX); + + // Calculer les coordonnées des trois points du triangle + double x3 = x2 - longueurFleche * Math.cos(angle - angleFleche); + double y3 = y2 - longueurFleche * Math.sin(angle - angleFleche); + + double x4 = x2 - longueurFleche * Math.cos(angle + angleFleche); + double y4 = y2 - longueurFleche * Math.sin(angle + angleFleche); + + // Retourner les coordonnées sous forme de tableau pour la Polyline + return new double[] { + x2, y2, // Sommet de la flèche + x3, y3, // Premier point de la base + x4, y4, // Deuxième point de la base + x2, y2 // Retour au sommet pour fermer le triangle + }; + } +} diff --git a/src/src/twisk/vues/VueLigneDroiteIG.java b/src/src/twisk/vues/VueLigneDroiteIG.java new file mode 100644 index 0000000..0fe42a1 --- /dev/null +++ b/src/src/twisk/vues/VueLigneDroiteIG.java @@ -0,0 +1,80 @@ +package twisk.vues; + +import javafx.scene.paint.Color; +import javafx.scene.shape.Line; +import javafx.scene.shape.Polyline; +import twisk.modele.ArcIG; +import twisk.modele.LigneDroiteIG; +import twisk.modele.PointDeControleIG; + +/** + * Vue d'une ligne droite + */ +public class VueLigneDroiteIG extends VueArcIG { + private final Line ligne; + private final Polyline fleche; + + /** + * Constructeur + * @param arc l'arc à représenter + */ + public VueLigneDroiteIG(ArcIG arc) { + super(arc); + + // Récupérer les coordonnées des points de contrôle + PointDeControleIG p1 = arc.getP1(); + PointDeControleIG p2 = arc.getP2(); + + // Créer la ligne + ligne = new Line(p1.getPosX(), p1.getPosY(), p2.getPosX(), p2.getPosY()); + ligne.setStroke(Color.valueOf("#0066cc")); + ligne.setStrokeWidth(2); + ligne.getStyleClass().add("arc-ligne"); + + // Calculer les coordonnées de la flèche + double[] coordsFleche = calculerCoordonneesTriangle(p1.getPosX(), p1.getPosY(), p2.getPosX(), p2.getPosY()); + + // Créer la flèche (triangle) + fleche = new Polyline(coordsFleche); + fleche.setFill(Color.valueOf("#0066cc")); + fleche.setStroke(Color.valueOf("#0066cc")); + fleche.getStyleClass().add("arc-fleche"); + + // Ajouter les composants au Pane + this.getChildren().addAll(ligne, fleche); + } + + /** + * Calcule les coordonnées des trois points du triangle formant la flèche + * @param x1 coordonnée x du premier point + * @param y1 coordonnée y du premier point + * @param x2 coordonnée x du deuxième point + * @param y2 coordonnée y du deuxième point + * @return tableau des coordonnées des trois points du triangle + */ + private double[] calculerCoordonneesTriangle(double x1, double y1, double x2, double y2) { + // Longueur de la flèche + double longueurFleche = 15; + + // Angle de la flèche (en radians) + double angleFleche = Math.PI / 6; // 30 degrés + + // Calculer l'angle de la ligne + double angle = Math.atan2(y2 - y1, x2 - x1); + + // Calculer les coordonnées des trois points du triangle + double x3 = x2 - longueurFleche * Math.cos(angle - angleFleche); + double y3 = y2 - longueurFleche * Math.sin(angle - angleFleche); + + double x4 = x2 - longueurFleche * Math.cos(angle + angleFleche); + double y4 = y2 - longueurFleche * Math.sin(angle + angleFleche); + + // Retourner les coordonnées sous forme de tableau pour la Polyline + return new double[] { + x2, y2, // Sommet de la flèche + x3, y3, // Premier point de la base + x4, y4, // Deuxième point de la base + x2, y2 // Retour au sommet pour fermer le triangle + }; + } +} diff --git a/src/src/twisk/vues/VueMondeIG.java b/src/src/twisk/vues/VueMondeIG.java index 8b1b5fc..0db916a 100644 --- a/src/src/twisk/vues/VueMondeIG.java +++ b/src/src/twisk/vues/VueMondeIG.java @@ -1,19 +1,36 @@ package twisk.vues; +import javafx.animation.PauseTransition; +import javafx.scene.control.Alert; import javafx.scene.layout.Pane; -import twisk.modele.EtapeIG; -import twisk.modele.MondeIG; +import javafx.util.Duration; +import twisk.exceptions.ArcException; +import twisk.modele.*; import twisk.outils.Observateur; +import java.util.Iterator; + public class VueMondeIG extends Pane implements Observateur { private MondeIG monde; public VueMondeIG(MondeIG monde) { this.monde = monde; + // Appliquer la classe CSS + this.getStyleClass().add("monde"); + // S'enregistrer comme observateur du monde monde.ajouterObservateur(this); + // Ajouter un écouteur de clic sur le monde + this.setOnMouseClicked(event -> { + try { + monde.clic(event.getX(), event.getY()); + } catch (ArcException e) { + afficherMessageErreur(e.getMessage()); + } + }); + // Initialiser la vue reagir(); } @@ -23,10 +40,56 @@ public class VueMondeIG extends Pane implements Observateur { // Effacer tous les composants existants this.getChildren().clear(); - // Ajouter une vue pour chaque étape du monde + // IMPORTANT: Afficher d'abord les arcs + Iterator<ArcIG> itArcs = monde.iteratorArcs(); + while (itArcs.hasNext()) { + ArcIG arc = itArcs.next(); + VueArcIG vueArc; + + // Créer la vue appropriée selon le type d'arc + if (arc instanceof LigneDroiteIG) { + vueArc = new VueLigneDroiteIG(arc); + } else if (arc instanceof CourbeIG) { + vueArc = new VueCourbeIG(arc); + } else { + // Ne devrait jamais arriver, mais au cas où + continue; + } + + this.getChildren().add(vueArc); + } + + // Ensuite, afficher les étapes et leurs points de contrôle for (EtapeIG etape : monde) { + // Créer et ajouter la vue de l'étape VueEtapeIG vueEtape = new VueActiviteIG(etape); this.getChildren().add(vueEtape); + + // Ajouter les vues des points de contrôle de l'étape + for (PointDeControleIG pt : etape) { + VuePointDeControleIG vuePt = new VuePointDeControleIG(pt, monde); + this.getChildren().add(vuePt); + } } } + /** + * Affiche un message d'erreur dans une boîte de dialogue + * @param message le message à afficher + */ + private void afficherMessageErreur(String message) { + // Créer une boîte de dialogue d'alerte + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Erreur"); + alert.setHeaderText("Impossible de créer l'arc"); + alert.setContentText(message); + + // Configurer la fermeture automatique après 3 secondes + PauseTransition delay = new PauseTransition(Duration.seconds(3)); + delay.setOnFinished(event -> alert.close()); + delay.play(); + + // Afficher la boîte de dialogue + alert.show(); + } + } diff --git a/src/src/twisk/vues/VueOutils.java b/src/src/twisk/vues/VueOutils.java index 50e50d5..013711d 100644 --- a/src/src/twisk/vues/VueOutils.java +++ b/src/src/twisk/vues/VueOutils.java @@ -11,8 +11,12 @@ public class VueOutils extends HBox { public VueOutils(MondeIG monde) { this.monde = monde; + // Appliquer la classe CSS + this.getStyleClass().add("outils"); + // Création du bouton pour ajouter une activité Button btnAjouterActivite = new Button("+ Activité"); + btnAjouterActivite.getStyleClass().add("bouton"); // Ajout d'un tooltip Tooltip tooltip = new Tooltip("Ajouter une nouvelle activité"); diff --git a/src/src/twisk/vues/VuePointDeControleIG.java b/src/src/twisk/vues/VuePointDeControleIG.java new file mode 100644 index 0000000..6389576 --- /dev/null +++ b/src/src/twisk/vues/VuePointDeControleIG.java @@ -0,0 +1,68 @@ +package twisk.vues; + +import javafx.animation.PauseTransition; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.util.Duration; +import twisk.exceptions.ArcException; +import twisk.modele.MondeIG; +import twisk.modele.PointDeControleIG; + +/** + * Vue d'un point de contrôle + */ +public class VuePointDeControleIG extends Circle { + private final PointDeControleIG pointDeControle; + private final MondeIG monde; + + /** + * Constructeur + * @param pointDeControle le point de contrôle à représenter + * @param monde le monde + */ + public VuePointDeControleIG(PointDeControleIG pointDeControle, MondeIG monde) { + super(pointDeControle.getPosX(), pointDeControle.getPosY(), 5); // Rayon de 5 pixels + this.pointDeControle = pointDeControle; + this.monde = monde; + + // Style du point de contrôle + this.setFill(Color.LIGHTBLUE); + this.setStroke(Color.BLUE); + this.setStrokeWidth(1); + + // Ajouter une classe CSS pour le styling + this.getStyleClass().add("point-controle"); + + } + + + /** + * Affiche un message d'erreur dans une boîte de dialogue + * @param message le message à afficher + */ + private void afficherMessageErreur(String message) { + // Créer une boîte de dialogue d'alerte + Alert alert = new Alert(AlertType.ERROR); + alert.setTitle("Erreur"); + alert.setHeaderText("Impossible de créer l'arc"); + alert.setContentText(message); + + // Configurer la fermeture automatique après 3 secondes + PauseTransition delay = new PauseTransition(Duration.seconds(3)); + delay.setOnFinished(event -> alert.close()); + delay.play(); + + // Afficher la boîte de dialogue + alert.show(); + } + + /** + * Retourne le point de contrôle associé à cette vue + * @return le point de contrôle + */ + public PointDeControleIG getPointDeControle() { + return pointDeControle; + } +} -- GitLab