C. Recanati InHM Java MI1, 2012 TP 10 1) Reprendre d’abord l’exercice 2 de la feuille 9 (applet sur une JList) si vous ne l’avez pas fait. 2) On va maintenant transformer le programme Sketcher pour en faire un logiciel de dessin. On va dessiner dans la fenêtre au lieu d’imprimer des infos sur les événements se produisant sur les menus. Le JTextArea va donc être remplacé par un JPanel qui captera les clics de souris, et permettra de dessiner interactivement. A. Pour réaliser le logiciel, on va implémenter l’architecture Modèle/Vue introduite en cours, en définissant les deux classes SketcherView et SketcherModel (cf. poly). Un modèle de feuille de dessin sera défini par la classe SketcherModel comme une liste d’éléments (des formes colorées). La vue SketcherView (un JPanel étendu) gèrera l’affichage du modèle mais aussi l’interaction de l’utilisateur avec le logiciel. On va implanter dans SketcherView la création d’un nouvel élément (temporaire du point de vue interactif), et son ajout au modèle à la fin de l’interaction de l’utilisateur avec la souris. Cette « vue » (un JPanel) sera insérée dans le panneau de contenu du cadre de l’application, comme indiqué dans le poly. B. Avant d’implanter la partie interactive de l’application permettant d’introduire un élément de dessin, on va définir la classe abstraite Element qui définit les différentes formes d’éléments d’une feuille de dessin. Pour définir un élément, on a besoin de deux champs : une forme (basée sur l’interface Shape de javax.swing) et une couleur. On va ici définir une classe abstraite Element, avec des sous-classes internes statiques qui implémentent cette classe abstraite (une sousclasse Ligne, une sous-classe Rectangle, etc.). Ces sous-classes d’éléments (Ligne, Rectangle, etc.) - seront donc définies dans la classe abstraite Element comme classes internes, et accessibles dans le code avec l’opérateur point « . ». On aura ainsi accès à la classe Ligne en écrivant Element.Ligne, à la classe Rectangle avec Element.Rectangle. Les constructeurs d’une classe d’éléments prendront 3 arguments : un début et une fin (de type Point) pour positionner la forme, et un argument couleur (de type Color). Ces constructeurs « mémoriseront » la forme concrète créée dans un membre privé avec un type spécifique (ex : Line2D.Double pour une Ligne). Un objet de type Shape pourra néanmoins ensuite être retourné par l’accesseur getShape(), en « castant » la valeur de ce champ privé. En vous inspirant du code de la classe interne Element.Ligne qui suit, écrire le code de la classe interne Element.Rectangle. // Element.java import java.awt.*; import java.awt.geom.*; public abstract class Element { protected Color color; // un élément a tjs une couleur public Element(Color color) { this.color = color; } public Color getColor() { return color; } // un élément a une forme récupérable par getShape() : public abstract Shape getShape(); // un élément a aussi un plus petit rectangle englobant // récupérable par getBounds() : public abstract java.awt.Rectangle getBounds(); // + une méthode modify pour positionner l’élément : public abstract void modify(Point debut, Point fin); // on définit une sous-classe « concrète » de Element // accessible par Element.Ligne public static class Ligne extends Element { private Line2D.Double ligne; // ça sera sa forme Shape public Ligne(Point debut, Point fin, Color couleur){ super(couleur); ligne = new Line2D.Double(debut, fin); } public Shape getShape() { return ligne; } public java.awt.Rectangle getBounds() { return ligne.getBounds(); } public void modify(Point debut, Point fin) { ligne.x1 = debut.x; ligne.y1 = debut.y; ligne.x2 = fin.x; ligne.y2 = fin.y; } } // vient ensuite le code implémentant Element.Rectangle // vient ensuite le code implémentant Element.Cercle // etc. pour tous les types d’élements } La classe abstraite Element inclut un membre de type Color (avec un accesseur getColor() et un constructeur qui initialise ce membre) et déclare des méthodes abstract getShape() et abstract getBounds() retournant respectivement la forme correspondant à l’élément (un rectangle, cercle, etc.) et son rectangle englobant. Ce rectangle retourné par getBounds servira, lors de l’affichage d’un élément, à limiter la zone impliquée par l’affichage. On définit également une méthode abstraite modify(Point debut, Point fin) permettant de positionner un élément en spécifiant ces deux nouvelles extrémités. C. Dans cette partie, on va implémenter la création interactive d’un élément temporaire dans la vue (SketcherView.java), et laisser en commentaire la partie concernant le modèle. On gèrera avec un MouseHandler qui étend MouseInputAdapter la réaction du logiciel à l’enchaînement des événements souris permettant de dessiner. Ces événements sont un premier clic de souris, suivi d’un déplacement de souris avec le bouton enfoncé (drag), suivi finalement du relâchement du bouton. Ces événements vont permettre à l’utilisateur de dessiner interactivement un nouvel élément (de type ligne, rectangle, etc. selon le mode activé et mémorisé dans la variable elementType). - Sur le clic : on enregistre le point de début du dessin (pour un élément temporaire tempElement). - Sur le mouvement : a. on efface l’élément temporaire précédemment dessiné; b. on enregistre le point du curseur courant comme extrémité du nouveau dessin; et c. on trace le nouvel élément temporaire. - Sur le relâcher : on réinitialise les variables pour une nouvelle utilisation (dessin d’un autre élément) et on stocke l’élément tracé précédemment dans le modèle. 1. Pour dessiner (ou effacer, c’est pareil !), on utilise la méthode générique draw d’un objet Shape de graphics2D en mode XOR (utiliser setXorMode) . On mémorisera l’élément temporaire dans un membre de la classe MouseHandler ainsi que les points de début et de fin pour pouvoir continuer le dessin d’événement en événement. 2. Pour faire référence aux constantes qui identifient le type d’élément (LIGNE, RECTANGLE, etc.), on modifiera la classe SketcherView pour qu’elle implémente l’interface Constants. Elements de correction // SketcherView.java (exercice 2) import javax.swing.*; import javax.swing.event.*; import java.util.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; class SketcherView extends JPanel implements Observer, Constants { private Sketcher theApp; // Objet Application public SketcherView(Sketcher theApp) { this.theApp = theApp; setBackground(Color.white); setOpaque(true); setBorder(BorderFactory.createLineBorder(Color.black)); MouseHandler handler = new MouseHandler(); addMouseListener(handler); addMouseMotionListener(handler); } public void update(Observable o, Object rectangle) { // code correspondant aux modifs du modèle if (rectangle == null) repaint(); else repaint( (Rectangle) rectangle); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2D = (Graphics2D) g; Iterator elements = theApp.getModel().getIterator(); Element element; while (elements.hasNext()) { element = (Element) elements.next(); g2D.setPaint(element.getColor()); g2D.draw(element.getShape()); } } class MouseHandler extends MouseInputAdapter { private Point debut; // position au clic private Point dernier; // position en drag private Element tempElement ; private Graphics2D g2D; public void mousePressed (MouseEvent e) { // clic du bouton de souris debut = e.getPoint(); System.out.println("premier clic"); g2D = (Graphics2D) getGraphics(); g2D.setPaint(theApp.getWindow().getElementColor()); g2D.setXORMode(Color.white); } public void mouseDragged (MouseEvent e) { // deplacement de souris dernier = e.getPoint(); if (tempElement == null) { tempElement = createElement(debut, dernier); } else { g2D.draw(tempElement.getShape()); // effacer le precedent tempElement.modify(debut, dernier); // modifier l'element } g2D.draw(tempElement.getShape()); // dessiner le nouveau } public void mouseReleased (MouseEvent e) { // relachement de la souris System.out.println("souris relachee"); if (tempElement != null) { theApp.getModel().add(tempElement); tempElement = null; } if (g2D != null) { g2D.dispose(); g2D = null; } debut = dernier = null; } private Element createElement(Point debut, Point fin) { switch(theApp.getWindow().getElementType()) { case LIGNE: return new Element.Ligne(debut, fin, theApp.getWindow().getElementColor()); case RECTANGLE: return new Element.Rectangle(debut, fin, theApp.getWindow().getElementColor()); case CERCLE: return new Element.Cercle(debut, fin, theApp.getWindow().getElementColor()); case COURBE: return new Element.Courbe(debut, fin, theApp.getWindow().getElementColor()); } return null; }}}