From ab680c99e3c2905ef29b9a72ea91e949d0d6de73 Mon Sep 17 00:00:00 2001 From: test <grave54122@gmail.com> Date: Sun, 30 Mar 2025 15:31:42 +0000 Subject: [PATCH] TP3 --- src/src/twisk/MainTwisk.java | 14 +- .../twisk/exceptions/ParametresException.java | 15 + src/src/twisk/modele/ActiviteIG.java | 54 ++++ src/src/twisk/modele/MondeIG.java | 134 ++++++++- src/src/twisk/ressources/css/style-colore.css | 149 ++++++++++ src/src/twisk/ressources/css/style-sombre.css | 144 +++++++++ src/src/twisk/ressources/css/style.css | 47 +++ src/src/twisk/vues/VueActiviteIG.java | 27 +- src/src/twisk/vues/VueMenu.java | 274 ++++++++++++++---- src/src/twisk/vues/VueMondeIG.java | 17 +- src/src/twisk/vues/VueOutils.java | 2 + 11 files changed, 809 insertions(+), 68 deletions(-) create mode 100644 src/src/twisk/exceptions/ParametresException.java create mode 100644 src/src/twisk/ressources/css/style-colore.css create mode 100644 src/src/twisk/ressources/css/style-sombre.css diff --git a/src/src/twisk/MainTwisk.java b/src/src/twisk/MainTwisk.java index 4d1e3b3..f4695a3 100644 --- a/src/src/twisk/MainTwisk.java +++ b/src/src/twisk/MainTwisk.java @@ -20,22 +20,22 @@ public class MainTwisk extends Application { // Création du conteneur principal BorderPane root = new BorderPane(); - // Création et placement de la barre de menu au nord - VueMenu vueMenu = new VueMenu(monde); - root.setTop(vueMenu); + // Création de la scène + Scene scene = new Scene(root, 800, 600); // Création et placement de la vue du monde au centre VueMondeIG vueMonde = new VueMondeIG(monde); root.setCenter(vueMonde); + // Création et placement du menu en haut + VueMenu vueMenu = new VueMenu(monde, scene); + root.setTop(vueMenu); + // Création et placement de la vue des outils au sud VueOutils vueOutils = new VueOutils(monde); root.setBottom(vueOutils); - // Création de la scène - Scene scene = new Scene(root, 800, 600); - - // Chargement du fichier CSS + // Chargement du fichier CSS par défaut String cssPath = getClass().getResource("/twisk/ressources/css/style.css").toExternalForm(); scene.getStylesheets().add(cssPath); diff --git a/src/src/twisk/exceptions/ParametresException.java b/src/src/twisk/exceptions/ParametresException.java new file mode 100644 index 0000000..68812c8 --- /dev/null +++ b/src/src/twisk/exceptions/ParametresException.java @@ -0,0 +1,15 @@ +package twisk.exceptions; + +/** + * Exception levée lors d'un problème avec les paramètres d'une activité + */ +public class ParametresException extends TwiskException { + + /** + * Constructeur + * @param message message d'erreur + */ + public ParametresException(String message) { + super(message); + } +} diff --git a/src/src/twisk/modele/ActiviteIG.java b/src/src/twisk/modele/ActiviteIG.java index bc4d491..7940cf9 100644 --- a/src/src/twisk/modele/ActiviteIG.java +++ b/src/src/twisk/modele/ActiviteIG.java @@ -1,12 +1,66 @@ package twisk.modele; +import twisk.exceptions.ParametresException; import twisk.outils.TailleComposants; public class ActiviteIG extends EtapeIG { + private int delai; + private int ecart; + /** + * Constructeur + * @param nom le nom de l'activité + */ public ActiviteIG(String nom) { // Utilisation des tailles définies dans TailleComposants super(nom, TailleComposants.getInstance().getLargeurActivite(), TailleComposants.getInstance().getHauteurActivite()); + + // Initialiser les paramètres avec des valeurs par défaut + this.delai = 4; // Valeur par défaut pour le délai + this.ecart = 2; // Valeur par défaut pour l'écart + } + + /** + * Définit le délai de l'activité + * @param delai le délai en unités de temps + * @throws ParametresException si le délai est négatif ou nul + */ + public void setDelai(int delai) throws ParametresException { + if (delai <= 0) { + throw new ParametresException("Le délai doit être un nombre positif"); + } + this.delai = delai; + } + + /** + * Définit l'écart de l'activité + * @param ecart l'écart en unités de temps + * @throws ParametresException si l'écart est négatif ou supérieur au délai + */ + public void setEcart(int ecart) throws ParametresException { + if (ecart < 0) { + throw new ParametresException("L'écart doit être un nombre positif ou nul"); + } + if (ecart >= delai) { + throw new ParametresException("L'écart doit être inférieur au délai"); + } + this.ecart = ecart; + } + + /** + * Retourne le délai de l'activité + * @return le délai + */ + public int getDelai() { + return delai; + } + + /** + * Retourne l'écart de l'activité + * @return l'écart + */ + public int getEcart() { + return ecart; } } diff --git a/src/src/twisk/modele/MondeIG.java b/src/src/twisk/modele/MondeIG.java index a612c51..1ef1609 100644 --- a/src/src/twisk/modele/MondeIG.java +++ b/src/src/twisk/modele/MondeIG.java @@ -2,6 +2,7 @@ package twisk.modele; import javafx.util.Pair; import twisk.exceptions.ArcException; +import twisk.exceptions.ParametresException; import twisk.outils.Observable; import twisk.outils.Observateur; @@ -18,6 +19,9 @@ public class MondeIG implements Iterable<EtapeIG>, Observable { private static final int NB_POINTS_COURBE = 4; private Set<String> etapesSelectionnees; private Set<ArcIG> arcsSelectionnes; + private Set<String> entrees; + private Set<String> sorties; + public MondeIG() { @@ -28,7 +32,9 @@ public class MondeIG implements Iterable<EtapeIG>, Observable { compteurActivites = 1; observateurs = new ArrayList<>(); etapesSelectionnees = new HashSet<>(); - arcsSelectionnes = new HashSet<>(); // Initialiser l'ensemble des arcs sélectionnés + arcsSelectionnes = new HashSet<>(); + entrees = new HashSet<>(); + sorties = new HashSet<>(); // Ajouter une activité par défaut à la construction ajouter("Activite"); @@ -570,5 +576,131 @@ public class MondeIG implements Iterable<EtapeIG>, Observable { return new Pair<>(false, e.getMessage()); } } + /** + * Définit ou supprime les étapes sélectionnées comme entrées + */ + public void definirEntrees() { + // Pour chaque étape sélectionnée + for (String idEtape : etapesSelectionnees) { + // Si l'étape est déjà une entrée, la supprimer + if (entrees.contains(idEtape)) { + entrees.remove(idEtape); + } else { + // Sinon, l'ajouter comme entrée et la retirer des sorties si elle y est + entrees.add(idEtape); + // Une entrée ne peut pas être une sortie + sorties.remove(idEtape); + } + } + + // Notifier les observateurs pour mettre à jour l'affichage + notifierObservateurs(); + } + + /** + * Définit ou supprime les étapes sélectionnées comme sorties + */ + public void definirSorties() { + // Pour chaque étape sélectionnée + for (String idEtape : etapesSelectionnees) { + // Si l'étape est déjà une sortie, la supprimer + if (sorties.contains(idEtape)) { + sorties.remove(idEtape); + } else { + // Sinon, l'ajouter comme sortie et la retirer des entrées si elle y est + sorties.add(idEtape); + // Une sortie ne peut pas être une entrée + entrees.remove(idEtape); + } + } + + // Notifier les observateurs pour mettre à jour l'affichage + notifierObservateurs(); + } + + /** + * Vérifie si une étape est une entrée + * @param idEtape l'identifiant de l'étape + * @return true si l'étape est une entrée, false sinon + */ + public boolean estUneEntree(String idEtape) { + return entrees.contains(idEtape); + } + + /** + * Vérifie si une étape est une sortie + * @param idEtape l'identifiant de l'étape + * @return true si l'étape est une sortie, false sinon + */ + public boolean estUneSortie(String idEtape) { + return sorties.contains(idEtape); + } + + /** + * Définit les paramètres (délai et écart) de l'activité sélectionnée + * @param delai le délai en unités de temps + * @param ecart l'écart en unités de temps + * @throws ParametresException si les paramètres sont incorrects + * @throws IllegalStateException si aucune activité n'est sélectionnée ou si plusieurs étapes sont sélectionnées + */ + public void definirParametres(int delai, int ecart) throws ParametresException, IllegalStateException { + // Vérifier qu'une seule étape est sélectionnée + if (etapesSelectionnees.size() != 1) { + throw new IllegalStateException("Il faut sélectionner une seule activité pour définir ses paramètres"); + } + + // Récupérer l'étape sélectionnée + String idEtape = etapesSelectionnees.iterator().next(); + EtapeIG etape = etapes.get(idEtape); + + // Vérifier que l'étape est bien une activité + if (!(etape instanceof ActiviteIG)) { + throw new IllegalStateException("Seules les activités peuvent avoir des paramètres de délai et d'écart"); + } + + // Définir les paramètres + ActiviteIG activite = (ActiviteIG) etape; + activite.setDelai(delai); + activite.setEcart(ecart); + + // Notifier les observateurs + notifierObservateurs(); + } + + /** + * Vérifie si une seule activité est sélectionnée + * @return true si une seule activité est sélectionnée, false sinon + */ + public boolean uneSeuleActiviteSelectionnee() { + if (etapesSelectionnees.size() != 1) { + return false; + } + + String idEtape = etapesSelectionnees.iterator().next(); + EtapeIG etape = etapes.get(idEtape); + + return etape instanceof ActiviteIG; + } + + /** + * Retourne l'activité sélectionnée si une seule activité est sélectionnée + * @return l'activité sélectionnée + * @throws IllegalStateException si aucune activité n'est sélectionnée ou si plusieurs étapes sont sélectionnées + */ + public ActiviteIG getActiviteSelectionnee() throws IllegalStateException { + if (etapesSelectionnees.size() != 1) { + throw new IllegalStateException("Il faut sélectionner une seule activité"); + } + + String idEtape = etapesSelectionnees.iterator().next(); + EtapeIG etape = etapes.get(idEtape); + + if (!(etape instanceof ActiviteIG)) { + throw new IllegalStateException("L'étape sélectionnée n'est pas une activité"); + } + + return (ActiviteIG) etape; + } + } diff --git a/src/src/twisk/ressources/css/style-colore.css b/src/src/twisk/ressources/css/style-colore.css new file mode 100644 index 0000000..b5f5201 --- /dev/null +++ b/src/src/twisk/ressources/css/style-colore.css @@ -0,0 +1,149 @@ +/* Styles généraux */ +.root { + -fx-font-family: 'Comic Sans MS', cursive; + -fx-background-color: #f0f8ff; +} + +/* Style pour VueMondeIG */ +.monde { + -fx-background-color: linear-gradient(to bottom right, #e6f7ff, #f0e6ff); + -fx-padding: 10px; +} + +/* Style pour VueActiviteIG */ +.activite { + -fx-background-color: transparent; +} + +.activite-rectangle { + -fx-fill: linear-gradient(to bottom, #ffffff, #f0f0ff); + -fx-stroke: #9966cc; + -fx-stroke-width: 3; + -fx-arc-width: 20; + -fx-arc-height: 20; + -fx-effect: dropshadow(three-pass-box, rgba(153,102,204,0.5), 10, 0, 0, 5); +} + +/* Style pour le titre de l'activité */ +.titre-activite { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #6600cc; + -fx-padding: 8px; + -fx-background-color: linear-gradient(to right, #e6ccff, #ccccff); + -fx-background-radius: 15 15 0 0; +} + +/* Modifications pour la zone des clients */ +.zone-clients { + -fx-background-color: #ffffff; + -fx-border-color: #cc99ff; + -fx-border-width: 2px; + -fx-border-radius: 0 0 15 15; + -fx-background-radius: 0 0 15 15; + -fx-padding: 8px; + -fx-min-height: 30px; + -fx-font-size: 12px; +} + +/* Modifications pour la zone des paramètres */ +.zone-parametres { + -fx-background-color: #f5f0ff; + -fx-border-color: #cc99ff; + -fx-border-width: 2px; + -fx-border-radius: 0 0 15 15; + -fx-background-radius: 0 0 15 15; + -fx-padding: 8px; + -fx-font-size: 10px; + -fx-text-fill: #6600cc; +} + + +/* Style pour la barre d'outils */ +.outils { + -fx-background-color: linear-gradient(to right, #cc99ff, #9966cc); + -fx-padding: 12px; + -fx-spacing: 15px; +} + +/* Style pour les boutons */ +.bouton { + -fx-background-color: #ff66cc; + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-padding: 10px 20px; + -fx-background-radius: 20px; + -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(255,102,204,0.5), 5, 0, 0, 2); +} + +.bouton:hover { + -fx-background-color: #ff33cc; + -fx-effect: dropshadow(three-pass-box, rgba(255,102,204,0.8), 8, 0, 0, 3); +} + +.bouton:pressed { + -fx-background-color: #cc0099; + -fx-effect: dropshadow(three-pass-box, rgba(255,102,204,0.3), 3, 0, 0, 1); +} + +/* Style pour les points de contrôle */ +.point-controle { + -fx-fill: #ff99cc; + -fx-stroke: #cc0099; + -fx-stroke-width: 2; + -fx-cursor: hand; +} + +.point-controle:hover { + -fx-fill: #ff66cc; + -fx-effect: dropshadow(three-pass-box, rgba(255,102,204,0.8), 5, 0, 0, 0); + -fx-scale-x: 1.2; + -fx-scale-y: 1.2; +} + +/* Style pour les arcs */ +.arc-ligne { + -fx-stroke: #9966cc; + -fx-stroke-width: 3; + -fx-stroke-dash-array: 10 5; + -fx-effect: dropshadow(three-pass-box, rgba(153,102,204,0.5), 3, 0, 0, 0); +} + +.arc-fleche { + -fx-fill: #9966cc; + -fx-stroke: #6600cc; + -fx-stroke-width: 2; +} + +/* Style pour les étapes sélectionnées */ +.etape-selectionnee { + -fx-effect: dropshadow(three-pass-box, #ff33cc, 15, 0, 0, 0); +} + +.etape-selectionnee .activite-rectangle { + -fx-stroke: #ff33cc; + -fx-stroke-width: 4; +} + +/* Style pour les entrées */ +.etape-entree .activite-rectangle { + -fx-fill: linear-gradient(to bottom, #ffffff, #e6ffe6); + -fx-stroke: #66cc66; +} + +.etape-entree .titre-activite { + -fx-text-fill: #006600; + -fx-background-color: linear-gradient(to right, #ccffcc, #99ff99); +} + +/* Style pour les sorties */ +.etape-sortie .activite-rectangle { + -fx-fill: linear-gradient(to bottom, #ffffff, #ffe6e6); + -fx-stroke: #cc6666; +} + +.etape-sortie .titre-activite { + -fx-text-fill: #660000; + -fx-background-color: linear-gradient(to right, #ffcccc, #ff9999); +} diff --git a/src/src/twisk/ressources/css/style-sombre.css b/src/src/twisk/ressources/css/style-sombre.css new file mode 100644 index 0000000..d1e20af --- /dev/null +++ b/src/src/twisk/ressources/css/style-sombre.css @@ -0,0 +1,144 @@ +/* Styles généraux */ +.root { + -fx-font-family: 'Segoe UI', Arial, sans-serif; + -fx-background-color: #2d2d2d; +} + +/* Style pour VueMondeIG */ +.monde { + -fx-background-color: #1e1e1e; + -fx-padding: 10px; +} + +/* Style pour VueActiviteIG */ +.activite { + -fx-background-color: transparent; +} + +.activite-rectangle { + -fx-fill: #3c3c3c; + -fx-stroke: #6c6c6c; + -fx-stroke-width: 2; + -fx-arc-width: 10; + -fx-arc-height: 10; +} + +/* Style pour le titre de l'activité */ +.titre-activite { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #e0e0e0; + -fx-padding: 5px; + -fx-background-color: #4c4c4c; + -fx-background-radius: 5px 5px 0 0; +} + +/* Style pour la zone des clients */ +.zone-clients { + -fx-background-color: #2d2d2d; + -fx-border-color: #6c6c6c; + -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; + -fx-text-fill: #e0e0e0; +} + +/* Style pour la zone des paramètres */ +.zone-parametres { + -fx-background-color: #2d2d2d; + -fx-border-color: #6c6c6c; + -fx-border-width: 1px; + -fx-border-radius: 0 0 5px 5px; + -fx-background-radius: 0 0 5px 5px; + -fx-padding: 5px; + -fx-font-size: 10px; + -fx-text-fill: #b0b0b0; +} + +/* 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: #4c4c4c; + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-padding: 8px 15px; + -fx-background-radius: 5px; + -fx-cursor: hand; +} + +.bouton:hover { + -fx-background-color: #5c5c5c; +} + +.bouton:pressed { + -fx-background-color: #3c3c3c; +} + +/* Style pour les points de contrôle */ +.point-controle { + -fx-fill: #6c6c6c; + -fx-stroke: #b0b0b0; + -fx-cursor: hand; +} + +.point-controle:hover { + -fx-fill: #b0b0b0; + -fx-effect: dropshadow(three-pass-box, rgba(255,255,255,0.3), 3, 0, 0, 0); +} + +/* Style pour les arcs */ +.arc-ligne { + -fx-stroke: #b0b0b0; + -fx-stroke-width: 2; +} + +.arc-fleche { + -fx-fill: #b0b0b0; + -fx-stroke: #b0b0b0; + -fx-stroke-width: 1; +} + +/* Style pour les étapes sélectionnées */ +.etape-selectionnee { + -fx-effect: dropshadow(three-pass-box, #ff9900, 10, 0, 0, 0); + -fx-border-color: #ff9900; + -fx-border-width: 3; +} + +/* Style pour les entrées */ +.etape-entree .activite-rectangle { + -fx-fill: #2d4d2d; + -fx-stroke: #4c8c4c; +} + +.etape-entree .titre-activite { + -fx-text-fill: #a0d0a0; + -fx-background-color: #3c5c3c; +} + +.etape-entree .zone-clients { + -fx-border-color: #4c8c4c; +} + +/* Style pour les sorties */ +.etape-sortie .activite-rectangle { + -fx-fill: #4d2d2d; + -fx-stroke: #8c4c4c; +} + +.etape-sortie .titre-activite { + -fx-text-fill: #d0a0a0; + -fx-background-color: #5c3c3c; +} + +.etape-sortie .zone-clients { + -fx-border-color: #8c4c4c; +} diff --git a/src/src/twisk/ressources/css/style.css b/src/src/twisk/ressources/css/style.css index a15023e..5f93165 100644 --- a/src/src/twisk/ressources/css/style.css +++ b/src/src/twisk/ressources/css/style.css @@ -134,4 +134,51 @@ -fx-cursor: hand; } +/* Style pour les entrées */ +.etape-entree .activite-rectangle { + -fx-fill: #e6ffe6; /* Fond légèrement vert */ + -fx-stroke: #28a745; /* Bordure verte */ +} + +.etape-entree .titre-activite { + -fx-text-fill: #28a745; + -fx-background-color: #d4edda; +} + +.etape-entree .zone-clients { + -fx-border-color: #28a745; +} + +/* Style pour les sorties */ +.etape-sortie .activite-rectangle { + -fx-fill: #ffe6e6; /* Fond légèrement rouge */ + -fx-stroke: #dc3545; /* Bordure rouge */ +} + +.etape-sortie .titre-activite { + -fx-text-fill: #dc3545; + -fx-background-color: #f8d7da; +} + +.etape-sortie .zone-clients { + -fx-border-color: #dc3545; +} + +/* Style pour les étapes sélectionnées */ +.etape-selectionnee { + -fx-effect: dropshadow(three-pass-box, #ff6600, 10, 0, 0, 0); +} + +/* Style pour la zone des paramètres */ +.zone-parametres { + -fx-background-color: #f8f9fa; + -fx-border-color: #dee2e6; + -fx-border-width: 1px; + -fx-border-radius: 0 0 5px 5px; + -fx-background-radius: 0 0 5px 5px; + -fx-padding: 5px; + -fx-font-size: 10px; + -fx-text-fill: #6c757d; +} + diff --git a/src/src/twisk/vues/VueActiviteIG.java b/src/src/twisk/vues/VueActiviteIG.java index 0c97e21..a9b7b31 100644 --- a/src/src/twisk/vues/VueActiviteIG.java +++ b/src/src/twisk/vues/VueActiviteIG.java @@ -2,7 +2,12 @@ package twisk.vues; import javafx.scene.control.Label; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import twisk.modele.ActiviteIG; import twisk.modele.EtapeIG; import twisk.modele.MondeIG; @@ -32,9 +37,14 @@ public class VueActiviteIG extends VueEtapeIG { zoneClients.getStyleClass().add("zone-clients"); zoneClients.setPrefWidth(etape.getLargeur()); + ActiviteIG activite = (ActiviteIG) etape; + Label zoneParametres = new Label("Délai: " + activite.getDelai() + ", Écart: " + activite.getEcart()); + zoneParametres.getStyleClass().add("zone-parametres"); + zoneParametres.setPrefWidth(etape.getLargeur()); + // Créer un conteneur pour organiser les éléments conteneur = new VBox(); - conteneur.getChildren().addAll(titre, zoneClients); + conteneur.getChildren().addAll(titre, zoneClients, zoneParametres); conteneur.setPrefWidth(etape.getLargeur()); conteneur.setPrefHeight(etape.getHauteur()); @@ -65,5 +75,18 @@ public class VueActiviteIG extends VueEtapeIG { // Style pour une étape non sélectionnée this.getStyleClass().remove("etape-selectionnee"); } + + // Vérifier si l'étape est une entrée ou une sortie + if (monde.estUneEntree(etape.getIdentifiant())) { + this.getStyleClass().add("etape-entree"); + } else { + this.getStyleClass().remove("etape-entree"); + } + + if (monde.estUneSortie(etape.getIdentifiant())) { + this.getStyleClass().add("etape-sortie"); + } else { + this.getStyleClass().remove("etape-sortie"); + } } -} +} \ No newline at end of file diff --git a/src/src/twisk/vues/VueMenu.java b/src/src/twisk/vues/VueMenu.java index d96f196..df60605 100644 --- a/src/src/twisk/vues/VueMenu.java +++ b/src/src/twisk/vues/VueMenu.java @@ -1,33 +1,42 @@ package twisk.vues; import javafx.application.Platform; -import javafx.scene.control.Menu; -import javafx.scene.control.MenuBar; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TextInputDialog; +import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.*; import javafx.scene.input.KeyCombination; -import twisk.modele.EtapeIG; +import javafx.scene.layout.GridPane; +import javafx.util.Pair; +import twisk.exceptions.ParametresException; +import twisk.modele.ActiviteIG; import twisk.modele.MondeIG; -import twisk.outils.Observateur; - -import java.util.Optional; /** * Vue représentant la barre de menu de l'application */ -public class VueMenu extends MenuBar implements Observateur { +public class VueMenu extends MenuBar { private final MondeIG monde; - private MenuItem itemRenommer; + private final Scene scene; + private final BooleanProperty uneEtapeSelectionnee; + private final BooleanProperty plusieursEtapesSelectionnees; + private final BooleanProperty uneActiviteSelectionnee; /** * Constructeur * @param monde le monde à manipuler + * @param scene la scène principale de l'application */ - public VueMenu(MondeIG monde) { + public VueMenu(MondeIG monde, Scene scene) { this.monde = monde; + this.scene = scene; - // S'enregistrer comme observateur du monde - monde.ajouterObservateur(this); + // Propriétés pour le binding + uneEtapeSelectionnee = new SimpleBooleanProperty(false); + plusieursEtapesSelectionnees = new SimpleBooleanProperty(false); + uneActiviteSelectionnee = new SimpleBooleanProperty(false); // Création du menu Fichier Menu menuFichier = new Menu("Fichier"); @@ -44,68 +53,231 @@ public class VueMenu extends MenuBar implements Observateur { Menu menuEdition = new Menu("Édition"); // Création de l'item Supprimer - MenuItem itemSupprimer = new MenuItem("Supprimer la sélection"); + MenuItem itemSupprimer = new MenuItem("Supprimer"); itemSupprimer.setAccelerator(KeyCombination.keyCombination("Delete")); - itemSupprimer.setOnAction(event -> monde.supprimerSelection()); - - // Création de l'item Effacer la sélection - MenuItem itemEffacerSelection = new MenuItem("Effacer la sélection"); - itemEffacerSelection.setAccelerator(KeyCombination.keyCombination("F1")); - itemEffacerSelection.setOnAction(event -> monde.deselectionnerTout()); + itemSupprimer.setOnAction(event -> monde.supprimerEtapesSelectionnees()); + // Activer seulement si au moins une étape est sélectionnée + itemSupprimer.disableProperty().bind( + uneEtapeSelectionnee.not().and(plusieursEtapesSelectionnees.not()) + ); // Création de l'item Renommer - itemRenommer = new MenuItem("Renommer la sélection"); - itemRenommer.setAccelerator(KeyCombination.keyCombination("F2")); - itemRenommer.setOnAction(event -> renommerSelection()); + MenuItem itemRenommer = new MenuItem("Renommer"); + itemRenommer.setAccelerator(KeyCombination.keyCombination("Ctrl+R")); + itemRenommer.setOnAction(event -> afficherDialogueRenommer()); + // Activer seulement si une seule étape est sélectionnée + itemRenommer.disableProperty().bind(uneEtapeSelectionnee.not()); - // Désactiver l'item Renommer par défaut (sera activé si une seule étape est sélectionnée) - itemRenommer.setDisable(true); + // Création de l'item Désélectionner + MenuItem itemDeselectionner = new MenuItem("Désélectionner tout"); + itemDeselectionner.setAccelerator(KeyCombination.keyCombination("Escape")); + itemDeselectionner.setOnAction(event -> monde.deselectionnerTout()); + // Activer seulement si au moins une étape est sélectionnée + itemDeselectionner.disableProperty().bind( + uneEtapeSelectionnee.not().and(plusieursEtapesSelectionnees.not()) + ); // Ajout des items au menu Édition - menuEdition.getItems().addAll(itemEffacerSelection, itemRenommer,itemSupprimer); + menuEdition.getItems().addAll(itemSupprimer, itemRenommer, new SeparatorMenuItem(), itemDeselectionner); + + // Création du menu Paramètres + Menu menuParametres = new Menu("Paramètres"); + + // Création de l'item Paramètres + MenuItem itemParametres = new MenuItem("Définir délai et écart"); + itemParametres.setOnAction(event -> afficherDialogueParametres()); + // Activer seulement si une seule activité est sélectionnée + itemParametres.disableProperty().bind(uneActiviteSelectionnee.not()); + + // Ajout de l'item au menu Paramètres + menuParametres.getItems().add(itemParametres); + + // Création du menu Monde + Menu menuMonde = new Menu("Monde"); + + // Création de l'item Entrée + MenuItem itemEntree = new MenuItem("Entrée"); + itemEntree.setOnAction(event -> monde.definirEntrees()); + // Activer seulement si au moins une étape est sélectionnée + itemEntree.disableProperty().bind( + uneEtapeSelectionnee.not().and(plusieursEtapesSelectionnees.not()) + ); + + // Création de l'item Sortie + MenuItem itemSortie = new MenuItem("Sortie"); + itemSortie.setOnAction(event -> monde.definirSorties()); + // Activer seulement si au moins une étape est sélectionnée + itemSortie.disableProperty().bind( + uneEtapeSelectionnee.not().and(plusieursEtapesSelectionnees.not()) + ); + + // Ajout des items au menu Monde + menuMonde.getItems().addAll(itemEntree, itemSortie); + + // Création du menu Style + Menu menuStyle = new Menu("Style"); + + // Créer un groupe pour les styles (pour que seul un style soit actif à la fois) + ToggleGroup groupeStyles = new ToggleGroup(); + + // Créer les items de style + RadioMenuItem styleClassique = new RadioMenuItem("Classique"); + styleClassique.setToggleGroup(groupeStyles); + styleClassique.setSelected(true); // Style par défaut + styleClassique.setOnAction(event -> changerStyle("style")); + + RadioMenuItem styleSombre = new RadioMenuItem("Sombre"); + styleSombre.setToggleGroup(groupeStyles); + styleSombre.setOnAction(event -> changerStyle("style-sombre")); + + RadioMenuItem styleColoré = new RadioMenuItem("Coloré"); + styleColoré.setToggleGroup(groupeStyles); + styleColoré.setOnAction(event -> changerStyle("style-colore")); + + RadioMenuItem styleMinimaliste = new RadioMenuItem("Minimaliste"); + styleMinimaliste.setToggleGroup(groupeStyles); + styleMinimaliste.setOnAction(event -> changerStyle("style-minimaliste")); + + // Ajouter les items au menu Style + menuStyle.getItems().addAll(styleClassique, styleSombre, styleColoré, styleMinimaliste); // Ajout des menus à la barre de menu - this.getMenus().addAll(menuFichier, menuEdition); + this.getMenus().addAll(menuFichier, menuEdition, menuParametres, menuMonde, menuStyle); - // Mettre à jour l'état des items - reagir(); + // S'enregistrer comme observateur du monde pour mettre à jour l'état du menu + monde.ajouterObservateur(() -> mettreAJourMenu()); } /** - * Ouvre une boîte de dialogue pour renommer l'étape sélectionnée + * Met à jour l'état du menu en fonction de l'état du monde */ - private void renommerSelection() { - // Vérifier qu'une seule étape est sélectionnée - if (monde.getNbEtapesSelectionnees() != 1) { - return; - } + private void mettreAJourMenu() { + int nbEtapesSelectionnees = monde.getNbEtapesSelectionnees(); + uneEtapeSelectionnee.set(nbEtapesSelectionnees == 1); + plusieursEtapesSelectionnees.set(nbEtapesSelectionnees > 1); + uneActiviteSelectionnee.set(monde.uneSeuleActiviteSelectionnee()); + } + /** + * Affiche une boîte de dialogue pour renommer l'étape sélectionnée + */ + private void afficherDialogueRenommer() { // Récupérer l'étape sélectionnée - EtapeIG etape = monde.getEtapeSelectionnee(); + if (monde.getNbEtapesSelectionnees() != 1) return; - // Créer une boîte de dialogue pour saisir le nouveau nom - TextInputDialog dialog = new TextInputDialog(etape.getNom()); + // Créer une boîte de dialogue + TextInputDialog dialog = new TextInputDialog(monde.getEtapeSelectionnee().getNom()); dialog.setTitle("Renommer l'activité"); - dialog.setHeaderText("Renommer l'activité " + etape.getNom()); - dialog.setContentText("Nouveau nom:"); - - // Afficher la boîte de dialogue et attendre la saisie - Optional<String> result = dialog.showAndWait(); + dialog.setHeaderText("Entrez le nouveau nom de l'activité"); + dialog.setContentText("Nom:"); - // Si l'utilisateur a saisi un nouveau nom, renommer l'étape - result.ifPresent(nouveauNom -> { + // Récupérer le résultat + dialog.showAndWait().ifPresent(nouveauNom -> { if (!nouveauNom.isEmpty()) { - monde.renommerEtape(etape, nouveauNom); + monde.renommerEtape(monde.getEtapeSelectionnee(), nouveauNom); } }); } /** - * Met à jour l'état des items du menu en fonction de l'état du monde + * Affiche une boîte de dialogue pour saisir les paramètres de l'activité sélectionnée + */ + private void afficherDialogueParametres() { + // Vérifier qu'une seule activité est sélectionnée + if (!monde.uneSeuleActiviteSelectionnee()) return; + + try { + // Récupérer l'activité sélectionnée + ActiviteIG activite = monde.getActiviteSelectionnee(); + + // Créer une boîte de dialogue personnalisée + Dialog<Pair<Integer, Integer>> dialog = new Dialog<>(); + dialog.setTitle("Paramètres de l'activité"); + dialog.setHeaderText("Définir le délai et l'écart"); + + // Ajouter les boutons + ButtonType boutonValider = new ButtonType("Valider", ButtonBar.ButtonData.OK_DONE); + dialog.getDialogPane().getButtonTypes().addAll(boutonValider, ButtonType.CANCEL); + + // Créer la grille pour les champs + GridPane grid = new GridPane(); + grid.setHgap(10); + grid.setVgap(10); + grid.setPadding(new Insets(20, 150, 10, 10)); + + // Créer les champs + TextField champDelai = new TextField(); + champDelai.setPromptText("Délai"); + TextField champEcart = new TextField(); + champEcart.setPromptText("Écart"); + + // Récupérer les valeurs actuelles + champDelai.setText(String.valueOf(activite.getDelai())); + champEcart.setText(String.valueOf(activite.getEcart())); + + // Ajouter les champs à la grille + grid.add(new Label("Délai:"), 0, 0); + grid.add(champDelai, 1, 0); + grid.add(new Label("Écart:"), 0, 1); + grid.add(champEcart, 1, 1); + + // Ajouter la grille à la boîte de dialogue + dialog.getDialogPane().setContent(grid); + + // Convertir le résultat + dialog.setResultConverter(dialogButton -> { + if (dialogButton == boutonValider) { + try { + int delai = Integer.parseInt(champDelai.getText()); + int ecart = Integer.parseInt(champEcart.getText()); + return new Pair<>(delai, ecart); + } catch (NumberFormatException e) { + return null; + } + } + return null; + }); + + // Afficher la boîte de dialogue et traiter le résultat + dialog.showAndWait().ifPresent(parametres -> { + try { + monde.definirParametres(parametres.getKey(), parametres.getValue()); + } catch (ParametresException e) { + // Afficher un message d'erreur + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Erreur"); + alert.setHeaderText("Paramètres invalides"); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } catch (IllegalStateException e) { + // Afficher un message d'erreur + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Erreur"); + alert.setHeaderText("Sélection invalide"); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + }); + } catch (IllegalStateException e) { + // Afficher un message d'erreur + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Erreur"); + alert.setHeaderText("Sélection invalide"); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + } + + /** + * Change le style de l'application + * @param nomStyle le nom du fichier CSS de style (sans l'extension) */ - @Override - public void reagir() { - // Activer l'item Renommer uniquement si une seule étape est sélectionnée - itemRenommer.setDisable(monde.getNbEtapesSelectionnees() != 1); + private void changerStyle(String nomStyle) { + // Supprimer tous les styles existants + scene.getStylesheets().clear(); + + // Ajouter le nouveau style + String cssPath = getClass().getResource("/twisk/ressources/css/" + nomStyle + ".css").toExternalForm(); + scene.getStylesheets().add(cssPath); } } diff --git a/src/src/twisk/vues/VueMondeIG.java b/src/src/twisk/vues/VueMondeIG.java index 1373997..821ff27 100644 --- a/src/src/twisk/vues/VueMondeIG.java +++ b/src/src/twisk/vues/VueMondeIG.java @@ -100,7 +100,14 @@ public class VueMondeIG extends Pane implements Observateur { // Effacer tous les composants existants this.getChildren().clear(); - // IMPORTANT: Afficher d'abord les arcs + // D'abord, afficher les étapes + for (EtapeIG etape : monde) { + // Créer et ajouter la vue de l'étape + VueEtapeIG vueEtape = new VueActiviteIG(etape, monde); + this.getChildren().add(vueEtape); + } + + // Ensuite, afficher les arcs (pour qu'ils soient au-dessus des étapes) Iterator<ArcIG> itArcs = monde.iteratorArcs(); while (itArcs.hasNext()) { ArcIG arc = itArcs.next(); @@ -122,13 +129,8 @@ public class VueMondeIG extends Pane implements Observateur { this.getChildren().add(vueArc); } - // Ensuite, afficher les étapes et leurs points de contrôle + // Enfin, afficher les points de contrôle (pour qu'ils soient tout en haut) for (EtapeIG etape : monde) { - // Créer et ajouter la vue de l'étape - VueEtapeIG vueEtape = new VueActiviteIG(etape, monde); - 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); @@ -137,6 +139,7 @@ public class VueMondeIG extends Pane implements Observateur { } + /** * Affiche un message d'erreur dans une boîte de dialogue * @param message le message à afficher diff --git a/src/src/twisk/vues/VueOutils.java b/src/src/twisk/vues/VueOutils.java index 013711d..997baa8 100644 --- a/src/src/twisk/vues/VueOutils.java +++ b/src/src/twisk/vues/VueOutils.java @@ -18,6 +18,8 @@ public class VueOutils extends HBox { Button btnAjouterActivite = new Button("+ Activité"); btnAjouterActivite.getStyleClass().add("bouton"); + + // Ajout d'un tooltip Tooltip tooltip = new Tooltip("Ajouter une nouvelle activité"); btnAjouterActivite.setTooltip(tooltip); -- GitLab