Tutoriel sur Java2D pour le développement d'une interface de gestion de maillage 2D

Jean-Michel BORLOT

Cet article présente la mise en place en Java d'un panneau graphique personnalisé hérité du composant Swing JPanel.

Commentez

- Titre : Tutoriel sur Java2D pour le développement d'une interface de gestion de maillage 2D

- Auteur : Jean-Michel BORLOT

- Parution : 8 juin 2013

- Mise à jour : 5 septembre 2013

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 JM BORLOT. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Définition du panneau graphique

- II-A. Surcharge de JPanel : la classe PFEM2DGuiPanel

- II-A-1. Conversion coordonnées réelles - coordonnées locales

- II-A-2. Surcharge de paintComponents

- II-A-3. Interaction avec la souris

- II-B. Capture d'écran

- II-C. Recadrage de l'affichage

- III. Le gestionnaire d'éléments graphiques

- IV. Les éléments graphiques

- IV-A. L'interface IPFEM2DDrawableObject

- IV-B. L'interface IPFEM2DCurve

- IV-C. La classe PFEM2DModel

- IV-D. La classe PFEM2DPoint

- IV-E. La classe PFEM2DLine

- V. Exemple d'application

- VI. Conclusion

- Remerciements

I - Introduction

Nombre de questions reviennent sur les forums afin de savoir comment dessiner un graphe en Java, quelle API utiliser… Ce tutoriel permet de poser les bases de conception d'un composant graphique de type JPanel personnalisé. Personnalisé au sens où tout l'affichage est à définir par le programmeur afin d'avoir le rendu désiré par l'utilisateur : couleurs, éléments à afficher, interaction utilisateur…

Le composant graphique sera tout d'abord créé par héritage de la classe Swing JPanel. Un gestionnaire d'éléments graphiques sera ensuite défini afin de gérer l'affichage des différents éléments. Les éléments graphiques seront alors définis dans différentes classes (modèle, point, ligne…). Un exemple d'application final permettra d'observer le comportement de notre composant graphique en situation d'utilisation.

Ce tutoriel s'intègre dans une série d'articles basés sur le développement d'un logiciel de modélisation éléments finis 2D nommé PFEM2D. Les différentes classes porteront donc ce préfixe dans la suite de l'article.

Dans le cadre de PFEM2D, l'interface visuelle nécessite l'affichage du modèle en 2D, avec un quadrillage vertical et horizontal afin de positionner les différents éléments. Une interaction avec la souris est également nécessaire afin de déplacer et zoomer l'affichage.

L'image suivante montre un rendu du composant graphique personnalisé, intégré dans l'application PFEM2D :

Dans la suite de cet article, le code source est présenté au plus proche de la description technique des diverses fonctionnalités du code final. Le code affiché peut donc n'être qu'un extrait partiel d'une classe particulière.
Le code source complet de l'article est disponible en téléchargement au chapitre VI.

II - Définition du panneau graphique

II-A - Surcharge de JPanel : la classe PFEM2DGuiPanel

La méthode de base pour créer un composant graphique est de s'appuyer sur celui qui s'en rapproche le plus dans la bibliothèque de composants Swing, et de surcharger sa méthode paintComponents.

Dans notre cas, il s'agit du composant JPanel. Le composant graphique créé portera le nom de PFEM2DGuiPanel.

La méthode paintComponents est surchargée afin de dessiner :

- le fond en noir ;

- les axes X et Y du repère 2D en trait plein vert ;

- les quadrillages vertical et horizontal en trait pointillé vert ;

- les éléments graphiques, avec leur affichage personnalisé.

L'interaction avec la souris est ajoutée au composant PFEM2DGuiPanel en ajoutant des listeners MouseListener et MouseMotionListener.

Afin d'améliorer l'expérience utilisateur, deux méthodes seront ajoutées :

- une méthode permettant d'effectuer une capture d'écran de l'affichage du composant PFEM2DGuiPanel (pratique pour créer les images de cet article !) ;

- une méthode permettant de centrer l'affichage sur les éléments à afficher.

La trame de base de la classe PFEM2DGuiPanel est présentée ci-après :

Classe PFEM2DGuiPanel

1. package plegatfem2d;

3. import java.awt.BasicStroke;

4. import java.awt.Color;

5. import java.awt.Graphics;

6. import java.awt.Graphics2D;

7. import java.awt.event.MouseEvent;

8. import java.awt.event.MouseListener;

9. import java.awt.event.MouseMotionListener;

10. import java.awt.image.BufferedImage;

11. import java.io.File;

12. import java.io.IOException;

13. import javax.imageio.ImageIO;

14. import javax.swing.JFileChooser;

15. import javax.swing.JOptionPane;

16. import javax.swing.JPanel;

18. /**

19. * Classe définissant un JPanel personnalisé.

20. * Ce JPanel permet l'affichage d'une grille 2D (axes X et Y, quadrillages horizontal et vertical)

21. * ainsi que d'objets graphiques 2D définis par le programmeur.

22. * L'affichage est interactif via la souris, l'utilisateur peut ainsi déplacer la vue, ainsi que zoomer

23. * sur une zone particulière.

24. *

25. * @author Jean-Michel BORLOT

26. */

27. public class PFEM2DGuiPanel extends JPanel implements MouseListener, MouseMotionListener {

29. /**

30. * Constructeur.

31. */

32. public PFEM2DGuiPanel() {

33. super();

35. this.mode = VIEW;

37. this.offsetX = 0;

38. this.offsetY = 0;

40. this.echelle = ECHELLE_BASE;

42. this.addMouseListener(this);

43. this.addMouseMotionListener(this);

44. }

46. private PFEM2DObjectManager pom;            // gestionnaire d'objets graphiques

47. private int xold, yold;                     // coordonnées du point local précédent

48. private int xstart, ystart;                 // coordonnées du point local initial

49. private int mode;                           // mode d'interaction : VIEW, DRAG ou SCALE

50. private double offsetX, offsetY;            // décalage de la vue

51. private double echelle;                     // échelle de la vue

52. private static int VIEW = 0;

53. private static int DRAG = 1;

54. private static int SCALE = 2;

55. private static double ECHELLE_BASE = 100.;  // échelle de base

56. private static double stepX=100, stepY=100; // pas des quadrillages sens X et sens Y

58. /**

59. * Renvoie le gestionnaire d'objets graphiques

60. * @return le gestionnaire d'objets graphiques

61. */

62. public PFEM2DObjectManager getPom() {

63. return pom;

64. }

66. /**

67. * Définit le gestionnaire d'objets graphiques

68. * @param pom le gestionnaire d'objets graphiques

69. */

70. public void setPom(PFEM2DObjectManager pom) {

71. this.pom = pom;

72. }

73. }

Dans ce code, les propriétés ECHELLE_BASE, stepX et stepY sont définies « en dur », et ne font pas l'objet de modifications ultérieures. Dans le cas où l'utilisateur doit avoir la possibilité de modifier ces valeurs, il conviendra d'intégrer les getter et setter nécessaires.

II-A-1 - Conversion coordonnées réelles - coordonnées locales

La fonction primordiale de la classe PFEM2DGuiPanel est d'afficher à l'écran les objets que l'on souhaite dessiner. Pour cela, il est nécessaire de faire une conversion entre les coordonnées réelles des objets, vers les coordonnées de la zone d'affichage sur l'écran (ou « locales »), et réciproquement. Quatre méthodes permettent de faire ces conversions :

- getLocalCoordX, pour la conversion d'une abscisse réelle vers l'abscisse locale ;

- getLocalCoordY, pour la conversion d'une ordonnée réelle vers l'ordonnée locale ;

- getRealCoordX, pour la conversion d'une abscisse locale vers l'abscisse réelle ;

- getRealCoordY, pour la conversion d'une ordonnée locale vers l'ordonnée réelle.

Afin de mieux comprendre le processus de conversion, observons le schéma suivant :

Le rectangle bleu représente la zone qui est affichée à l'écran. Son origine est localisée au coin haut/gauche, l'axe X local positif est orienté vers la droite, l'axe Y local positif vers le bas.

Son centre est décalé par rapport à l'origine du repère réel de offsetX suivant l'axe X, et de offsetY suivant l'axe Y.

Le signe des valeurs offsetX et offsetY est défini relativement au repère local, c'est-à-dire qu'une valeur positive décale la zone bleue respectivement vers la gauche et vers le bas.

La procédure de conversion « repère réel vers repère local » est très simple :

- on part de la valeur de coordonnée dans le repère réel ;

- on multiplie par un facteur échelle/ECHELLE_BASE ;

- on multiplie par un facteur -1 si on calcule une valeur Y, afin de réorienter l'axe vers le bas ;

- on ajoute respectivement W/2 ou H/2 suivant que l'on calcule une valeur X ou Y, afin de positionner l'origine au centre de la zone d'affichage ;

- on ajoute respectivement le décalage offsetX ou offsetY suivant que l'on calcule une valeur X ou Y, afin de décaler l'origine à sa position réelle dans le repère local.

La procédure de conversion inverse, « repère local vers repère réel », reprend la procédure ci-dessus, mais en sens inverse.

Les quatre méthodes permettant de faire ces conversions sont présentées ci-après :

Conversion de coordonnées

1. /**

2. * Convertit une abscisse réelle en abscisse locale (repère JPanel)

3. * @param x l'abscisse réelle

4. * @return l'abscisse locale

5. */

6. public int getLocalCoordX(double x) {

8. return (int) Math.round(this.getWidth() / 2 + this.offsetX + x * this.echelle / ECHELLE_BASE);

10. }

12. /**

13. * Convertit une ordonnée réelle en ordonnée locale (repère JPanel)

14. * @param y l'ordonnée réelle

15. * @return l'ordonnée locale

16. */

17. public int getLocalCoordY(double y) {

19. return (int) Math.round(this.getHeight() / 2 + this.offsetY - y * this.echelle / ECHELLE_BASE);

21. }

23. /**

24. * Convertit une abscisse locale (repère JPanel) en abscisse réelle

25. * @param x l'abscisse locale

26. * @return l'abscisse réelle

27. */

28. public double getRealCoordX(double x) {

30. return (x - (this.getWidth() / 2 + this.offsetX)) * ECHELLE_BASE / this.echelle;

32. }

34. /**

35. * Convertit une ordonnée locale (repère JPanel) en ordonnée réelle

36. * @param y l'ordonnée locale

37. * @return l'ordonnée réelle

38. */

39. public double getRealCoordY(double y) {

41. return (y - (this.getHeight() / 2 + this.offsetY)) * -ECHELLE_BASE / this.echelle;

43. }

II-A-2 - Surcharge de paintComponents

La méthode à modifier pour personnaliser l'affichage de notre JPanel est la méthode paintComponents. Mais avant de la modifier, il faut savoir ce que nous voulons lui faire dessiner !

Dans notre cas, l'affichage se compose :

- d'un fond noir ;

- des axes X et Y en trait plein vert ;

- des axes de quadrillage sens X et sens Y, en trait pointillé vert foncé (codage RGB=60/85/60), avec un pas stepX en sens X et stepY en sens Y ;

- des éléments graphiques qui seront dessinés par le gestionnaire d'éléments graphiques.

Ceci se traduit dans le code Java par les blocs suivants :

- dessin avec fillRect d'un rectangle plein noir occupant toute la zone d'affichage ;

- changement de couleur de dessin avec setColor, et dessin avec drawLine d'une ligne horizontale (à l'ordonnée y=0 locale) et d'une ligne verticale (à l'abscisse x=0 locale) ;

- définition du motif des pointillés avec setStroke et BasicStroke, puis dessin des quadrillages horizontal et vertical ;

- appel de la méthode de dessin du gestionnaire d'éléments graphiques.

Le code complet de la méthode paintComponents est présenté ci-après :

Méthode paintComponents

1. // surcharge de la méthode paintComponent afin de dessiner notre vue personnalisée

2. @Override

3. public void paintComponent(Graphics g) {

5. Graphics2D g2 = (Graphics2D) g;

7. int h = this.getHeight();

8. int w = this.getWidth();

10. g.setColor(Color.BLACK);

11. g.fillRect(0, 0, w, h);

13. //tracé des axes et du quadrillage

15. int roundOffsetX = (int) Math.round(this.offsetX);

16. int roundOffsetY = (int) Math.round(this.offsetY);

17. int localStepX = (int) Math.round(this.echelle / ECHELLE_BASE * stepX);

18. int localStepY = (int) Math.round(this.echelle / ECHELLE_BASE * stepY);

20. g.setColor(Color.GREEN);

21. g.drawLine(w / 2 + roundOffsetX, 0, w / 2 + roundOffsetX, h);

22. g.drawLine(0, h / 2 + roundOffsetY, w, h / 2 + roundOffsetY);

24. int offsetStrokeX = 7 - roundOffsetX % 7;

25. int offsetStrokeY = 7 - roundOffsetY % 7;

27. float[] dash = {2, 5};

28. BasicStroke bsX = new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, 10, dash, offsetStrokeX);

29. BasicStroke bsY = new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, 10, dash, offsetStrokeY);

31. g.setColor(new Color(60, 85, 60));

33. g2.setStroke(bsY);

35. int nbxp = (w - (w / 2 + roundOffsetX)) / localStepX + 1;

36. int nbxm = (w / 2 + roundOffsetX) / localStepX + 1;

38. for (int i = 1; i < nbxp; i++) {

39. int xline = w / 2 + roundOffsetX + (int) Math.round(i * stepX * this.echelle / ECHELLE_BASE);

40. g.drawLine(xline, 0, xline, h);

42. }

43. for (int i = 1; i < nbxm; i++) {

44. int xline = w / 2 + roundOffsetX - (int) Math.round(i * stepX * this.echelle / ECHELLE_BASE);

45. g.drawLine(xline, 0, xline, h);

46. }

48. g2.setStroke(bsX);

50. int nbym = (h - (h / 2 + roundOffsetY)) / localStepY + 1;

51. int nbyp = (h / 2 + roundOffsetY) / localStepY + 1;

53. for (int i = 1; i < nbyp; i++) {

54. int yline = h / 2 + roundOffsetY - (int) Math.round(i * stepY * this.echelle / ECHELLE_BASE);

55. g.drawLine(0, yline, w, yline);

56. }

57. for (int i = 1; i < nbym; i++) {

58. int yline = h / 2 + roundOffsetY + (int) Math.round(i * stepY * this.echelle / ECHELLE_BASE);

59. g.drawLine(0, yline, w, yline);

60. }

62. g2.setStroke(new BasicStroke());

64. if (this.pom != null) {

65. this.pom.draw(g, this);

66. }

67. }

II-A-3 - Interaction avec la souris

La classe PFEM2DGuiPanel étendant les interfaces MouseListener et MouseMotionListener, les événements suivants sont gérés par la classe, avec les actions associées ci-dessous :

- mouseDragged : suivant le mode courant, et en fonction du déplacement de la souris :

- en mode « view » : pas d'effet,

- en mode « drag » : déplace la zone affichée,

- en mode « scale » : modifie le facteur de zoom ;

- mouseMoved : pas d'action associée ;

- mouseClicked : pas d'action associée ;

- mousePressed : changement de mode suivant le bouton de la souris pressé :

- bouton gauche : bascule en mode « drag » (déplacement),

- bouton milieu : bascule en mode « scale » (zoom) ;

- mouseReleased : passage en mode « view » (visualisation) ;

- mouseEntered : pas d'action associée ;

- mouseExited : pas d'action associée.

La méthode mousePressed est relativement simple, elle modifie l'état de la propriété mode suivant le bouton souris pressé et initialise les propriétés xOld et yOld (ainsi que les propriétés xstart et ystart en cas de mode « scale » activé) avec les coordonnées de la souris.

La méthode mouseDragged est un peu plus complexe, étant donné qu'elle gère l'influence du déplacement de la souris pour les deux modes principaux d'interaction :

- en mode « drag » (déplacement de la zone d'affichage), le déplacement de la souris résulte en un déplacement similaire de la zone d'affichage. C'est-à-dire que si la souris se déplace de 10 pixels vers la droite, la zone d'affichage se déplace également de 10 pixels vers la droite.
Pour cela, on ajoute aux propriétés offsetX et offsetY (qui gèrent le décalage de la zone d'affichage) l'écart de position entre la position actuelle de la souris et la position précédente, gardée en mémoire dans les propriétés xOld et yOld. Les valeurs de xOld et yOld sont finalement modifiées pour prendre celles de la position actuelle de la souris ;

- en mode « scale » (zoom de la zone d'affichage), le déplacement de la souris résulte en une modification du facteur d'échelle de la zone d'affichage. Afin d'avoir une interaction « intuitive », le centre du zoom est positionné à l'endroit où l'utilisateur presse le bouton milieu de la souris (événement géré par la méthode mousePressed, qui place les coordonnées de ce point dans les propriétés xstart et ystart). Cette fonctionnalité complique un peu le calcul, dans la mesure où une modification de l'échelle uniquement va zoomer la zone d'affichage en prenant l'origine du repère réel comme centre de zoom.
Afin de positionner le centre de zoom là où l'utilisateur a initialement cliqué, il nous faut modifier simultanément les valeurs de l'échelle et des décalages offsetX et offsetY. Ces modifications sont présentées dans le code complet des méthodes de gestion des événements en lignes 171 à 173.
De la même manière qu'en mode « drag », les valeurs de xOld et yOld sont finalement modifiées pour prendre celles de la position actuelle de la souris.

Les formules de calcul des décalages pour le zoom s'appuient sur le fait que le centre de zoom doit garder les mêmes coordonnées en repère local, quelle que soit la valeur de l'échelle.

Le code complet des méthodes de gestion des événements souris est présenté ci-après :

Interaction avec la souris

1. // surcharge de la méthode mouseDragged. Gestion du déplacement souris avec bouton appuyé

2. @Override

3. public void mouseDragged(MouseEvent e) {

4. if (this.mode == DRAG) {

5. int x = e.getX();

6. int y = e.getY();

8. this.offsetX = this.offsetX + (x - this.xold);

9. this.offsetY = this.offsetY + (y - this.yold);

11. this.xold = x;

12. this.yold = y;

14. this.repaint();

15. } else if (this.mode == SCALE) {

16. int x = e.getX();

17. int y = e.getY();

19. int dx = x - this.xold;

20. int dy = y - this.yold;

22. int delta;

23. if (Math.abs(dx) > Math.abs(dy)) {

24. delta = dx;

25. } else {

26. delta = dy;

27. }

29. double newEchelle = Math.max(1, this.echelle * Math.pow(1.01, delta));

30. double newOffsetX = this.offsetX + this.getRealCoordX(this.xstart) / ECHELLE_BASE * (this.echelle - newEchelle);

31. double newOffsetY = this.offsetY - this.getRealCoordY(this.ystart) / ECHELLE_BASE * (this.echelle - newEchelle);

33. this.offsetX = newOffsetX;

34. this.offsetY = newOffsetY;

35. this.echelle = newEchelle;

37. this.xold = x;

38. this.yold = y;

40. this.repaint();

41. }

42. }

44. @Override

45. public void mouseMoved(MouseEvent e) {

46. }

48. @Override

49. public void mouseClicked(MouseEvent e) {

50. }

52. // surcharge de la méthode mousePressed. Gestion du clic souris.

53. @Override

54. public void mousePressed(MouseEvent e) {

55. this.xold = e.getX();

56. this.yold = e.getY();

58. if (e.getButton() == MouseEvent.BUTTON1) {

59. this.mode = DRAG;

60. } else if (e.getButton() == MouseEvent.BUTTON2) {

61. this.mode = SCALE;

62. this.xstart = e.getX();

63. this.ystart = e.getY();

64. } else {

65. this.mode = VIEW;

66. }

67. }

69. // surcharge de la méthode mouseReleased. Gestion du lâcher de clic souris.

70. @Override

71. public void mouseReleased(MouseEvent e) {

72. this.mode = VIEW;

73. }

75. @Override

76. public void mouseEntered(MouseEvent e) {

77. }

79. @Override

80. public void mouseExited(MouseEvent e) {

81. }

II-B - Capture d'écran

La première méthode additionnelle effectue une capture d'écran de la zone d'affichage du composant PFEM2DGuiPanel.

Un JfFileChooser permet de sélectionner le fichier de sortie dans lequel sera enregistrée l'image. Seuls deux formats sont autorisés dans cette méthode, le png et le jpg. Après vérification de l'extension du fichier, l'image est dessinée dans une BufferedImage, puis enregistrée dans le fichier choisi.

Méthode takeScreenshot

1. /**

2. * Crée une capture d'écran de l'affichage courant.

3. * Le fichier de sortie est défini par sélection du fichier via un JFileChooser.

4. * Deux formats possibles en sortie: png ou jpg

5. */

6. public void takeScreenshot() {

8. JFileChooser fc = new JFileChooser();

10. int returnVal = fc.showOpenDialog(this);

12. if (returnVal == JFileChooser.APPROVE_OPTION) {

14. File fichier = fc.getSelectedFile();

15. String nomFichier = fichier.getName().toLowerCase();

17. if ((nomFichier.endsWith("png")) || (nomFichier.endsWith("jpg"))) {

18. try {

19. BufferedImage bufImage = new BufferedImage(this.getSize().width, this.getSize().height, BufferedImage.TYPE_INT_RGB);

20. this.paint(bufImage.createGraphics());

22. String extension = "png";

23. if (nomFichier.endsWith("jpg")) {

24. extension = "jpg";

25. }

27. ImageIO.write(bufImage, extension, fichier);

28. } catch (IOException ex) {

29. ex.printStackTrace();

30. }

31. } else {

32. JOptionPane.showMessageDialog(this, "Veuillez préciser l'extension du fichier image (png ou jpg)",

33. "Extension fichier manquante", JOptionPane.ERROR_MESSAGE);

34. }

35. }

36. }

Les formats choisis ici (png et jpg) ne sont pas une limitation de Java mais uniquement un choix personnel. Les formats bmp et gif peuvent également être utilisés.

II-C - Recadrage de l'affichage

La seconde méthode additionnelle recentre l'affichage sur les éléments graphiques à afficher :

Méthode centreView

1. /**

2. * Centre la vue sur les éléments à afficher.

3. * @param boundingBox tableau de 4 double: Xmin, Xmax, Ymin,Ymax, en coordonnées réelles

4. */

5. public void centreView(double[] boundingBox) {

7. if ((boundingBox[1] - boundingBox[0] != Double.NEGATIVE_INFINITY) && (boundingBox[3] - boundingBox[2] != Double.NEGATIVE_INFINITY)) {

9. double echelleX = (this.getWidth() - 50) * ECHELLE_BASE / (boundingBox[1] - boundingBox[0]);

10. double echelleY = (this.getHeight() - 50) * ECHELLE_BASE / (boundingBox[3] - boundingBox[2]);

12. this.echelle = Math.min(echelleX, echelleY);

14. this.offsetX = -(this.getLocalCoordX((boundingBox[1] + boundingBox[0]) / 2) - this.getLocalCoordX(0));

15. this.offsetY = -(this.getLocalCoordY((boundingBox[3] + boundingBox[2]) / 2) - this.getLocalCoordY(0));

17. this.repaint();

18. }

19. }

Cette méthode nécessite le passage en paramètre des coordonnées de la « bounding box », ou boîte englobante en français, afin de pouvoir recadrer au mieux la vue sur les éléments à afficher.

Une vérification sur la taille de la « bounding box » est effectuée, de manière à ne pas recentrer la vue s'il n'y a rien à afficher !

Une marge de 50 pixels est ajoutée autour de la zone d'affichage afin d'améliorer le rendu.

Les échelles sens X et Y sont calculées, l'échelle finale étant le minimum de ces deux échelles.

Les décalages sens X et Y sont calculés comme étant les distances du centre de la « bounding box » au centre de l'affichage (point (0,0) local).

Finalement, un rafraîchissement de l'affichage est appelé.

III - Le gestionnaire d'éléments graphiques

Nous allons utiliser un gestionnaire d'éléments graphiques pour gérer l'affichage dans notre PFEM2DGuiPanel. Il sera chargé de gérer l'affichage de tous les éléments graphiques que nous allons créer dans notre espace d'affichage. Les éléments graphiques seront simplement ajoutés ou supprimés du gestionnaire, charge à lui de gérer l'affichage correctement.

Le gestionnaire d'éléments graphiques est décrit par la classe PFEM2DObjectManager. Les propriétés de l'objet PFEM2DObjectManager sont :

- la liste objects des éléments graphiques gérés (de type IPFEM2DDrawableObject, voir chapitre IV.A) ;

- et l'identité idCurrentPoint du point courant. Cette propriété est utilisée à titre d'exemple dans cet article pour illustrer la méthode de gestion automatique des identités des éléments graphiques.

Les méthodes de la classe PFEM2DObjectManager sont les suivantes :

- le constructeur, qui initialise la liste des éléments graphiques ;

- la méthode init, qui permet d'initialiser la liste des éléments graphiques ;

- la méthode addObject, qui permet d'ajouter un élément graphique à la liste ;

- deux méthodes removeObject, permettant de supprimer un élément de la liste soit par son rang dans la liste, soit en passant directement l'objet à supprimer en paramètre ;

- la méthode draw, qui parcourt la liste des éléments graphiques et provoque leur affichage par appel à leur méthode draw ;

- la méthode updateId, qui met à jour la propriété idCurrentPoint. Le point ayant l'identité la plus élevée est recherché, cette valeur est incrémentée de un pour être affectée à idCurrentPoint. La propriété idCurrentPoint peut ainsi être utilisée directement afin d'affecter automatiquement la valeur d'identité du prochain point créé ;

- les méthodes getIdCurrentPoint, incrementIdCurrentPoint et setIdCurrentPoint permettent respectivement d'obtenir, incrémenter et définir la valeur de la propriété idCurrentPoint ;

- la méthode getBoundingBox est similaire à celle de l'interface IPFEM2DDrawableObject. Elle parcourt la liste des éléments graphiques, récupère leur « bounding box », et calcule à partir de ces informations la « bounding box » globale englobant tous les éléments gérés par le gestionnaire d'éléments graphiques.

Le code de la classe est présenté ci-après :

Classe PFEM2DObjectManager

1. package plegatfem2d;

3. import java.awt.Graphics;

4. import java.io.BufferedReader;

5. import java.io.File;

6. import java.io.FileNotFoundException;

7. import java.io.FileReader;

8. import java.util.ArrayList;

9. import java.util.List;

10. import javax.script.Bindings;

11. import javax.script.ScriptContext;

12. import javax.script.ScriptEngine;

13. import javax.script.ScriptEngineManager;

14. import javax.script.ScriptException;

15. import plegatfem2d.objects.IPFEM2DDrawableObject;

16. import plegatfem2d.objects.PFEM2DPoint;

18. /**

19. * Classe définissant le gestionnaire d'objets graphiques.

20. *

21. * @author Jean-Michel BORLOT

22. */

23. public class PFEM2DObjectManager {

25. /**

26. * Constructeur

27. */

28. public PFEM2DObjectManager() {

29. this.objects = new ArrayList<>();

30. }

32. /**

33. * Méthode d'initialisation de la liste des objets gérés

34. */

35. public void init() {

36. this.objects.clear();

37. }

39. /**

40. * Ajoute un objet graphique au gestionnaire

41. * @param obj l'objet graphique

42. */

43. public void addObject(IPFEM2DDrawableObject obj) {

44. this.objects.add(obj);

45. }

47. /**

48. * Supprime un objet graphique du gestionnaire

49. * @param rank le rang de l'objet graphique

50. */

51. public void removeObject(int rank) {

52. this.objects.remove(rank);

53. }

55. /**

56. * Supprime un objet graphique du gestionnaire

57. * @param obj l'objet graphique

58. */

59. public void removeObject(IPFEM2DDrawableObject obj) {

60. this.objects.remove(obj);

61. }

63. /**

64. * Affiche les objets graphiques gérés par le gestionnaire

65. * @param g l'objet Graphics sur lequel dessiner

66. * @param panel l'objet PFEM2DGuiPanel dans lequel dessiner

67. */

68. public void draw(Graphics g, PFEM2DGuiPanel panel) {

70. for (IPFEM2DDrawableObject current : this.objects) {

71. current.draw(g,panel);

72. }

73. }

75. /**

76. * Méthode de mise à jour des identités des objets graphiques de type PFEM2DPoint

77. */

78. public void updateId() {

80. this.idCurrentPoint = 1;

82. for (int i = 0; i < this.objects.size(); i++) {

84. IPFEM2DDrawableObject obj = this.objects.get(i);

86. if (obj instanceof IPFEM2DDrawableObject) {

88. PFEM2DPoint[] pts = obj.getPoints();

90. if (pts != null) {

92. for (int j = 0; j < pts.length; j++) {

93. long id = pts[j].getNumId();

95. if (id > this.idCurrentPoint) {

96. idCurrentPoint = id;

97. }

98. }

99. }

100. }

101. }

102. }

104. private List<IPFEM2DDrawableObject> objects;    // liste des objets graphiques

105. private long idCurrentPoint;                    // identité courante pour les objets de type PFEM2DPoint

107. /**

108. * Renvoie l'identité courante des objets de type PFEM2DPoint

109. * @return l'identité courante

110. */

111. public long getIdCurrentPoint() {

112. idCurrentPoint++;

113. return idCurrentPoint;

114. }

116. /**

117. * Incrémente l'identité courante des objets de type PFEM2DPoint

118. */

119. public void incrementIdCurrentPoint() {

120. idCurrentPoint++;

121. }

123. /**

124. * Définit l'identité courante des objets de type PFEM2DPoint

125. * @param idCurrentPoint

126. */

127. public void setIdCurrentPoint(long idCurrentPoint) {

128. this.idCurrentPoint = idCurrentPoint;

129. }

131. /**

132. * Renvoie les valeurs Xmin/max et Ymin/max de la boite englobante des objets graphiques

133. * @return un tableau de 4 double: Xmin, Xmax, Ymin, Ymax

134. */

135. public double[] getBoundingBox() {

137. double data[] = new double[4];

139. data[0] = Double.POSITIVE_INFINITY;     // x min

140. data[1] = Double.NEGATIVE_INFINITY;     // x max

141. data[2] = Double.POSITIVE_INFINITY;     // y min

142. data[3] = Double.NEGATIVE_INFINITY;     // y max

144. for (IPFEM2DDrawableObject current : this.objects) {

146. double[] bb = current.getBoundingBox();

148. if (bb[0] < data[0]) {

149. data[0] = bb[0];

150. }

151. if (bb[1] > data[1]) {

152. data[1] = bb[1];

153. }

154. if (bb[2] < data[2]) {

155. data[2] = bb[2];

156. }

157. if (bb[3] > data[3]) {

158. data[3] = bb[3];

159. }

160. }

162. return data;

163. }

164. }

En complément à la définition de la classe PFEM2DObjectManager, une méthode d'initialisation de la liste des éléments est définie. Celle-ci nous va nous permettre d'importer des éléments graphiques à partir d'un fichier de script Jython. L'avantage de cette méthode est qu'elle va ainsi nous éviter de coder la description des éléments graphiques à afficher dans le code de notre application.

Afin de pouvoir utiliser les scripts Jython, nous supposerons que celui-ci est installé sur votre machine. Si ce n'est pas le cas, je vous renvoie vers le site officiel Jython.

Pourquoi Jython ?
En fait, vous pouvez utiliser n'importe quel moteur de script compatible avec la machine virtuelle Java (Jython, Groovy, Rhino…) selon vos souhaits, besoins et habitudes de programmation.
Dans ce tutoriel, nous avons choisi d'utiliser Jython.

La méthode openFile initialise la liste des éléments graphiques. Elle cherche ensuite à créer le moteur de script Jython, et définit une variable « pom », qui n'est autre que notre gestionnaire d'éléments graphiques. Cette variable « pom » sera utilisée dans le script Jython pour récupérer les éléments graphiques, et faire le lien entre le script Jython et notre application. Le moteur de script Jython évalue finalement le fichier passé en paramètre.

Le code de la méthode openFile est présenté ci-après :

Méthode openFile

1. /**

2. * Ouvre un fichier de script Jython permettant de définir le contenu du gestionnaire d'objets

3. * graphiques.

4. * @param fichier le fichier script Jython

5. * @return true si l'importation s'est déroulée correctement, false sinon

6. */

7. public boolean openFile(File fichier) {

9. this.init();

11. ScriptEngineManager manager = new ScriptEngineManager();

12. ScriptEngine moteur = manager.getEngineByName("jython");

14. if (moteur == null) {

15. System.out.println("Impossible de trouver le moteur de scripting recherché.");

16. return false;

17. } else {

18. try {

19. Bindings bindings = moteur.getBindings(ScriptContext.ENGINE_SCOPE);

20. bindings.clear();

21. bindings.put("pom", this);

23. moteur.eval(new BufferedReader(new FileReader(fichier)));

25. System.out.println("end of file importation");

26. } catch (FileNotFoundException | ScriptException ex) {

27. ex.printStackTrace();

28. return false;

29. }

30. }

32. return true;

33. }

IV - Les éléments graphiques

IV-A - L'interface IPFEM2DDrawableObject

Cette interface définit les méthodes des objets graphiques qui seront affichables à l'écran :

- draw, qui dessine l'objet graphique ;

- setVisible, qui définit la visibilité de l'objet graphique ;

- isVisible, qui renvoie l'état de visibilité de l'objet graphique ;

- getPoints, qui renvoie la liste des points définissant l'objet graphique ;

- getId, qui renvoie l'identité de l'objet graphique ;

- getBoundingBox, qui renvoie la boîte englobante de l'objet graphique.

Le code de l'interface est présenté ci-après :

Classe IPFEM2DDrawableObject

1. package plegatfem2d.objects;

3. import java.awt.Graphics;

4. import plegatfem2d.PFEM2DGuiPanel;

6. /**

7. * Interface définissant les objets graphiques

8. * @author Jean-Michel BORLOT

9. */

10. public interface IPFEM2DDrawableObject {

12. /**

13. * Dessine l'objet graphique

14. * @param g l'objet Graphics sur lequel dessiner

15. * @param panel l'objet PFEM2DGuiPanel sur lequel dessiner

16. */

17. public void draw(Graphics g, PFEM2DGuiPanel panel);

19. /**

20. * Définit la visibilité de l'objet graphique

21. * @param flag true si l'objet est visible, false s'il ne l'est pas

22. */

23. public void setVisible(boolean flag);

25. /**

26. * Renvoie l'état de visibilité de l'objet graphique

27. * @return true si l'objet est visible, false s'il ne l'est pas

28. */

29. public boolean isVisible();

31. /**

32. * Renvoie la liste des points définissant l'objet graphique

33. * @return la liste des points

34. */

35. public PFEM2DPoint[] getPoints();

37. /**

38. * Renvoie l'identité de l'objet graphique

39. * @return l'identité de l'objet graphique

40. */

41. public String getId();

43. /**

44. * Renvoie la boite englobante de l'objet graphique

45. * @return un tableau de 4 double: Xmin, Xmax, Ymin, Ymax

46. */

47. public double[] getBoundingBox();

49. }

IV-B - L'interface IPFEM2DCurve

Cette interface définit les méthodes des objets 2D similaires à des courbes : segment, cercle, arc de cercle… :

- getStartPoint, qui renvoie le point de départ de la courbe ;

- getEndPoint, qui renvoie le point d'arrivée de la courbe.

Le code de l'interface est présenté ci-après :

Interface IPFEM2DCurve

1. package plegatfem2d.objects;

3. /**

4. * Interface de définition des objets de type "courbe"

5. * @author Jean-Michel BORLOT

6. */

7. public interface IPFEM2DCurve {

9. /**

10. * Renvoie le point de départ de la courbe

11. * @return le point de départ de la courbe

12. */

13. public PFEM2DPoint getStartPoint();

15. /**

16. * Renvoie le point d'arrivée de la courbe

17. * @return le point d'arrivée de la courbe

18. */

19. public PFEM2DPoint getEndPoint();

20. }

IV-C - La classe PFEM2DModel

La forme à dessiner dans la zone graphique va être encapsulée dans un objet PFEM2DModel, qui va contenir tous les éléments individuels à afficher. Cela permet d'avoir un accès unique à tous les composants d'un objet graphique complexe, la gestion de l'affichage étant alors prise en charge par l'objet PFEM2DModel. L'ensemble des éléments individuels forme ainsi un objet complexe que nous appellerons un « modèle » (ou « model » en anglais).

Cette classe implémente l'interface IPFEM2DDrawableObject. On retrouve donc les méthodes qui ont été décrites au chapitre IV.A.

À ces méthodes se rajoutent les méthodes spécifiques à l'objet PFEM2DModel :

- son constructeur, qui initialise la liste des objets composant le modèle ;

- addObject, permettant d'ajouter un objet individuel ;

- removeObject, permettant de supprimer un objet individuel.

Le code de la classe PFEM2DModel est présenté ci-après :

Classe PFEM2DModel

1. package plegatfem2d.objects;

3. import java.awt.Graphics;

4. import java.util.ArrayList;

5. import java.util.Arrays;

6. import java.util.List;

7. import plegatfem2d.PFEM2DGuiPanel;

9. /**

10. * Classe définissant l'objet "racine", composé d'un assemblage d'objets

11. * graphiques

12. *

13. * @author Jean-Michel BORLOT

14. */

15. public class PFEM2DModel implements IPFEM2DDrawableObject {

17. private List<IPFEM2DDrawableObject> objects = new ArrayList<>();   // liste des objets constituant l'objet "racine"

18. private boolean visible = true;                                         // état de visibilité de l'objet

20. /**

21. * Constructeur

22. */

23. public PFEM2DModel() {

24. this.objects.clear();

25. }

27. /**

28. * Ajoute un objet graphique à l'objet racine

29. *

30. * @param obj l'objet graphique

31. */

32. public void addObject(IPFEM2DDrawableObject obj) {

33. this.objects.add(obj);

34. }

36. /**

37. * Supprime un objet graphique de l'objet racine

38. *

39. * @param obj l'objet graphique

40. */

41. public void removeObject(IPFEM2DDrawableObject obj) {

42. this.objects.remove(obj);

43. }

45. /**

46. * Dessine l'objet graphique

47. *

48. * @param g l'objet Graphics sur lequel dessiner

49. * @param panel l'objet PFEM2DGuiPanel sur lequel dessiner

50. */

51. @Override

52. public void draw(Graphics g, PFEM2DGuiPanel panel) {

54. for (IPFEM2DDrawableObject current : this.objects) {

55. if (current.isVisible()) {

56. current.draw(g, panel);

57. }

58. }

59. }

61. /**

62. * Définit la visibilité de l'objet graphique

63. *

64. * @param flag true si l'objet est visible, false s'il ne l'est pas

65. */

66. @Override

67. public void setVisible(boolean flag) {

68. this.visible = flag;

69. }

71. /**

72. * Renvoie l'état de visibilité de l'objet graphique

73. *

74. * @return true si l'objet est visible, false s'il ne l'est pas

75. */

76. @Override

77. public boolean isVisible() {

78. return this.visible;

79. }

81. /**

82. * Renvoie l'identité de l'objet graphique

83. *

84. * @return l'identité de l'objet graphique

85. */

86. @Override

87. public String getId() {

88. return "Root";

89. }

91. /**

92. * Renvoie la boite englobante de l'objet graphique

93. *

94. * @return un tableau de 4 double: Xmin, Xmax, Ymin, Ymax

95. */

96. @Override

97. public double[] getBoundingBox() {

99. double data[] = new double[4];

101. data[0] = Double.POSITIVE_INFINITY;

102. data[1] = Double.NEGATIVE_INFINITY;

103. data[2] = Double.POSITIVE_INFINITY;

104. data[3] = Double.NEGATIVE_INFINITY;

106. for (IPFEM2DDrawableObject current : this.objects) {

108. double bb[] = current.getBoundingBox();

110. if (bb[0] < data[0]) {

111. data[0] = bb[0];

112. }

113. if (bb[1] > data[1]) {

114. data[1] = bb[1];

115. }

116. if (bb[2] < data[2]) {

117. data[2] = bb[2];

118. }

119. if (bb[3] > data[3]) {

120. data[3] = bb[3];

121. }

122. }

124. return data;

125. }

127. /**

128. * Renvoie la liste des points définissant l'objet graphique

129. *

130. * @return la liste des points

131. */

132. @Override

133. public PFEM2DPoint[] getPoints() {

134. ArrayList<PFEM2DPoint> listPts = new ArrayList<>();

136. for (IPFEM2DDrawableObject current : this.objects) {

137. PFEM2DPoint[] pts = current.getPoints();

138. listPts.addAll(Arrays.asList(pts));

139. }

141. return listPts.toArray(new PFEM2DPoint[listPts.size()]);

142. }

143. }

IV-D - La classe PFEM2DPoint

Maintenant que nous avons un « conteneur » pour décrire les objets que nous voulons afficher, il nous faut définir les éléments individuels qui vont nous permettre de construire nos modèles par assemblage.

Le premier de ces éléments individuels est un élément de base : le point. Il est décrit par la classe PFEM2DPoint. Comme nous travaillons en 2D, un point est décrit par un couple de valeurs, x et y, qui définissent sa position dans l'espace 2D (attention de ne pas confondre la position du point dans l'espace 2D, et sa position dans le panneau graphique !).

Cette classe implémente l'interface IPFEM2DDrawableObject. On retrouve donc les méthodes qui ont été décrites au chapitre IV.A.

À ces méthodes se rajoutent les méthodes spécifiques à l'objet PFEM2DPoint :

- deux constructeurs :

- un constructeur par défaut, initialisant le point à l'origine du repère 2D,

- un constructeur prenant en paramètres les coordonnées du point, ainsi que son identité ;

- les getters et setters pour l'identité et les coordonnées du point (le getter pour l'identité du point fait partie des méthodes de l'interface IPFEM2DDrawableObject) ;

- la méthode getNumId, qui retourne l'identité du point sous forme numérique et non pas littérale comme le fait la méthode getId de l'interface IPFEM2DDrawableObject ;

- la méthode getDistanceTo, qui permet d'obtenir la distance du point courant à un autre point passé en paramètre ;

- la méthode getRotated, qui permet de créer un point par rotation du point courant ;

- et la surcharge de la méthode toString, donnant les informations relatives au point sous forme littérale.

La méthode draw affiche un carré de 10 pixels centré sur la position du point.

Le code de la classe est présenté ci-après :

Classe PFEM2DPoint

1. package plegatfem2d.objects;

3. import java.awt.Color;

4. import java.awt.Graphics;

5. import plegatfem2d.PFEM2DGuiPanel;

7. /**

8. * Classe définissant un objet graphique de type "point"

9. * @author Jean-Michel BORLOT

10. */

11. public class PFEM2DPoint implements IPFEM2DDrawableObject {

13. private double x, y;

14. private long id;

15. private boolean visible;

17. /**

18. * Constructeur

19. * @param id l'identité du point

20. * @param x l'abscisse du point

21. * @param y l'ordonnée du point

22. */

23. public PFEM2DPoint(long id, double x, double y) {

24. this.id = id;

25. this.x = x;

26. this.y = y;

27. this.visible = true;

28. }

30. /**

31. * Constructeur par défaut

32. */

33. public PFEM2DPoint() {

34. this.id = 0;

35. this.x = 0;

36. this.y = 0;

37. this.visible = false;

38. }

40. /**

41. * Définit l'identité du point

42. * @param id l'identité du point

43. */

44. public void setId(long id) {

45. this.id = id;

46. }

48. /**

49. * Renvoie l'abscisse du point

50. * @return l'abscisse du point

51. */

52. public double getX() {

53. return x;

54. }

56. /**

57. * Définit l'abscisse du point

58. * @param x l'abscisse du point

59. */

60. public void setX(double x) {

61. this.x = x;

62. }

64. /**

65. * Renvoie l'ordonnée du point

66. * @return l'ordonnée du point

67. */

68. public double getY() {

69. return y;

70. }

72. /**

73. * Définit l'ordonnée du point

74. * @param y l'ordonnée du point

75. */

76. public void setY(double y) {

77. this.y = y;

78. }

80. /**

81. * Dessine l'objet graphique

82. * @param g l'objet Graphics sur lequel dessiner

83. * @param panel l'objet PFEM2DGuiPanel sur lequel dessiner

84. */

85. @Override

86. public void draw(Graphics g, PFEM2DGuiPanel panel) {

88. if (this.isVisible()) {

89. int xloc = panel.getLocalCoordX(this.x);

90. int yloc = panel.getLocalCoordY(this.y);

92. int[] xPoints = {xloc + 5, xloc - 5, xloc - 5, xloc + 5, xloc + 5};

93. int[] yPoints = {yloc + 5, yloc + 5, yloc - 5, yloc - 5, yloc + 5};

95. g.setColor(Color.cyan);

96. g.drawPolyline(xPoints, yPoints, 5);

98. // dessin id

100. g.drawString("P" + this.id, xloc + 10, yloc + 10);

101. }

103. }

105. /**

106. * Définit la visibilité de l'objet graphique

107. * @param flag true si l'objet est visible, false s'il ne l'est pas

108. */

109. @Override

110. public void setVisible(boolean flag) {

111. this.visible = flag;

112. }

114. /**

115. * Renvoie l'état de visibilité de l'objet graphique

116. * @return true si l'objet est visible, false s'il ne l'est pas

117. */

118. @Override

119. public boolean isVisible() {

120. return this.visible;

121. }

123. /**

124. * Renvoie l'identité de l'objet graphique

125. * @return l'identité de l'objet graphique

126. */

127. @Override

128. public String getId() {

129. return "Point " + this.id;

130. }

132. /**

133. * Renvoie l'identité sous format numérique de l'objet graphique

134. * @return l'identité sous format numérique de l'objet graphique

135. */

136. public long getNumId() {

137. return this.id;

138. }

140. /**

141. * Renvoie la distance du point à un autre point

142. * @param pt l'autre point

143. * @return la distance

144. */

145. public double getDistanceTo(PFEM2DPoint pt) {

146. return Math.sqrt(Math.pow(pt.getX() - this.getX(), 2) + Math.pow(pt.getY() - this.getY(), 2));

147. }

149. /**

150. * Constructeur par rotation du point courant

151. * @param centre le centre de la rotation

152. * @param angle l'angle de la rotation

153. * @return le nouveau point créé

154. */

155. public PFEM2DPoint getRotated(PFEM2DPoint centre, double angle) {

157. double dx = this.getX() - centre.getX();

158. double dy = this.getY() - centre.getY();

160. double angleBase = Math.atan2(dy, dx);

161. double radius = centre.getDistanceTo(this);

163. double xRot = centre.getX() + radius * Math.cos(angleBase + angle / 180. * Math.PI);

164. double yRot = centre.getY() + radius * Math.sin(angleBase + angle / 180. * Math.PI);

166. return new PFEM2DPoint(0, xRot, yRot);

167. }

169. // méthode toString

170. @Override

171. public String toString() {

173. return "Point #"+this.id+", x/y="+this.x+"/"+this.y;

175. }

177. /**

178. * Renvoie la boite englobante de l'objet graphique

179. * @return un tableau de 4 double: Xmin, Xmax, Ymin, Ymax

180. */

181. @Override

182. public double[] getBoundingBox() {

183. return new double[]{this.x,this.x,this.y,this.y};

184. }

186. /**

187. * Renvoie la liste des points définissant l'objet graphique

188. * @return la liste des points

189. */

190. @Override

191. public PFEM2DPoint[] getPoints() {

192. return new PFEM2DPoint[]{this};

193. }

195. }

IV-E - La classe PFEM2DLine

Le second élément individuel que nous utiliserons est le segment, qui est défini par deux points. Il est décrit par la classe PFEM2DLine.

Cette classe implémente les interfaces IPFEM2DDrawableObject et IPFEM2DCurve. On retrouve donc les méthodes qui ont été décrites aux chapitres IV.A et IV.B.

À ces méthodes se rajoutent les méthodes spécifiques à l'objet PFEM2DPoint :

- un constructeur, prenant en paramètres les deux points définissant le segment et l'identité du segment ;

- les getters et setters pour l'identité du segment et les deux points le définissant (le getter pour l'identité du segment fait partie des méthodes de l'interface IPFEM2DDrawableObject).

La méthode draw affiche simplement un trait entre les deux points de définition du segment.

Le code de la classe est présenté ci-après :

Classe PFEM2DLine

1. package plegatfem2d.objects;

3. import java.awt.Color;

4. import java.awt.Graphics;

5. import plegatfem2d.PFEM2DGuiPanel;

7. /**

8. * Classe définissant un objet graphique de type "ligne", reliant deux points.

9. * @author Jean-Michel BORLOT

10. */

11. public class PFEM2DLine implements IPFEM2DDrawableObject, IPFEM2DCurve {

13. private PFEM2DPoint pt1, pt2;   // les deux points définissant la ligne

14. private int id;                 // l'identité de la ligne

15. private boolean visible;        // l'état de visibilité de la ligne

17. /**

18. * Constructeur

19. * @param id identité

20. * @param pt1 point n°1

21. * @param pt2 point n°2

22. */

23. public PFEM2DLine(int id, PFEM2DPoint pt1, PFEM2DPoint pt2) {

24. this.pt1 = pt1;

25. this.pt2 = pt2;

26. this.id = id;

27. this.visible = true;

28. }

30. /**

31. * Définit l'identité de la ligne

32. * @param id l'identité de la ligne

33. */

34. public void setId(int id) {

35. this.id = id;

36. }

38. /**

39. * Renvoie le premier point

40. * @return le premier point

41. */

42. public PFEM2DPoint getPt1() {

43. return pt1;

44. }

46. /**

47. * Définit le premier point

48. * @param pt1 le premier point

49. */

50. public void setPt1(PFEM2DPoint pt1) {

51. this.pt1 = pt1;

52. }

54. /**

55. * Renvoie le second point

56. * @return le second point

57. */

58. public PFEM2DPoint getPt2() {

59. return pt2;

60. }

62. /**

63. * Définit le second point

64. * @param pt2 le second point

65. */

66. public void setPt2(PFEM2DPoint pt2) {

67. this.pt2 = pt2;

68. }

70. /**

71. * Dessine l'objet graphique

72. * @param g l'objet Graphics sur lequel dessiner

73. * @param panel l'objet PFEM2DGuiPanel sur lequel dessiner

74. */

75. @Override

76. public void draw(Graphics g, PFEM2DGuiPanel panel) {

78. if (this.isVisible()) {

79. int xloc1 = panel.getLocalCoordX(this.pt1.getX());

80. int yloc1 = panel.getLocalCoordY(this.pt1.getY());

82. int xloc2 = panel.getLocalCoordX(this.pt2.getX());

83. int yloc2 = panel.getLocalCoordY(this.pt2.getY());

85. g.setColor(Color.yellow);

86. g.drawLine(xloc1, yloc1, xloc2, yloc2);

87. }

88. }

90. /**

91. * Définit la visibilité de l'objet graphique

92. * @param flag true si l'objet est visible, false s'il ne l'est pas

93. */

94. @Override

95. public void setVisible(boolean flag) {

96. this.visible = flag;

97. }

99. /**

100. * Renvoie l'état de visibilité de l'objet graphique

101. * @return true si l'objet est visible, false s'il ne l'est pas

102. */

103. @Override

104. public boolean isVisible() {

105. return this.visible;

106. }

108. /**

109. * Renvoie l'identité de l'objet graphique

110. * @return l'identité de l'objet graphique

111. */

112. @Override

113. public String getId() {

114. return "Line " + this.id;

115. }

117. /**

118. * Renvoie le point de départ de la courbe

119. * @return le point de départ de la courbe

120. */

121. @Override

122. public PFEM2DPoint getStartPoint() {

123. return this.pt1;

124. }

126. /**

127. * Renvoie le point d'arrivée de la courbe

128. * @return le point d'arrivée de la courbe

129. */

130. @Override

131. public PFEM2DPoint getEndPoint() {

132. return this.pt2;

133. }

135. /**

136. * Renvoie la boite englobante de l'objet graphique

137. * @return un tableau de 4 double: Xmin, Xmax, Ymin, Ymax

138. */

139. @Override

140. public double[] getBoundingBox() {

142. double data[]=new double[4];

144. data[0]=Math.min(this.pt1.getX(),this.pt2.getX());

145. data[1]=Math.max(this.pt1.getX(),this.pt2.getX());

146. data[2]=Math.min(this.pt1.getY(),this.pt2.getY());

147. data[3]=Math.max(this.pt1.getY(),this.pt2.getY());

149. return data;

150. }

152. /**

153. * Renvoie la liste des points définissant l'objet graphique

154. * @return la liste des points

155. */

156. @Override

157. public PFEM2DPoint[] getPoints() {

158. return new PFEM2DPoint[]{this.pt1,this.pt2};

159. }

160. }

V - Exemple d'application

Nous allons utiliser toutes les classes et interfaces que nous venons de décrire dans un exemple d'utilisation. Nous allons créer une fenêtre, incluant un panneau PFEM2DGuiPanel, ainsi qu'une barre d'outils de quatre boutons qui nous permettront :

- d'ouvrir un fichier script Jython ;

- de faire une impression d'écran de notre zone d'affichage ;

- de recadrer l'affichage sur les éléments à afficher ;

- de fermer notre application de démonstration.

Le code de cette classe est relativement simple et ne présente pas de difficultés particulières. Il est présenté ci-après :

Classe PlegatFem2D_GuiPanel

1. package plegatfem2d;

3. import java.awt.BorderLayout;

4. import java.awt.Dimension;

5. import java.awt.event.ActionEvent;

6. import java.awt.event.ActionListener;

7. import java.io.File;

8. import javax.swing.JButton;

9. import javax.swing.JFileChooser;

10. import javax.swing.JFrame;

11. import javax.swing.JSeparator;

12. import javax.swing.JToolBar;

14. /**

15. * Classe de démonstration du panneau personnalisé PFEM2DGuiPanel. Crée une

16. * fenêtre incorporant une barre d'outil simple (ouvrir un fichier, capture

17. * d'écran, recentrer la vue, et fermer la fenêtre) et un élément

18. * PFEM2DGuiPanel.

19. *

20. * @author Jean-Michel BORLOT

21. */

22. public class PlegatFem2D_GuiPanel extends JFrame {

24. /**

25. * Constructeur.

26. *

27. * @param frameTitle le titre apparaissant dans la barre de la fenêtre

28. */

29. public PlegatFem2D_GuiPanel(String frameTitle) {

30. super(frameTitle);

31. this.initComponents();

32. }

34. private void initComponents() {

36. final PFEM2DObjectManager pom = new PFEM2DObjectManager();

38. this.pfguipanel = new PFEM2DGuiPanel();

39. this.pfguipanel.setPreferredSize(new Dimension(800, 600));

40. this.add(this.pfguipanel, BorderLayout.CENTER);

42. JToolBar toolbar = new JToolBar();

43. toolbar.setPreferredSize(new Dimension(100, 40));

44. toolbar.setFloatable(false);

46. // bouton open

48. JButton jbOpen = new JButton("open", new javax.swing.ImageIcon(getClass().getResource("/ressources/icons/add.png")));

49. jbOpen.addActionListener(new ActionListener() {

50. @Override

51. public void actionPerformed(ActionEvent e) {

53. JFileChooser fc = new JFileChooser();

54. int returnVal = fc.showOpenDialog(pfguipanel.getParent());

56. if (returnVal == JFileChooser.APPROVE_OPTION) {

58. File fichier = fc.getSelectedFile();

59. boolean flag = pom.openFile(fichier);

61. if (flag) {

62. pfguipanel.repaint();

63. }

64. }

65. }

66. });

67. toolbar.add(jbOpen);

69. // bouton screenshot

71. JButton jbScreen = new JButton("screenshot", new javax.swing.ImageIcon(getClass().getResource("/ressources/icons/snapshot.png")));

72. jbScreen.addActionListener(new ActionListener() {

73. @Override

74. public void actionPerformed(ActionEvent e) {

75. pfguipanel.takeScreenshot();

76. }

77. });

78. toolbar.add(jbScreen);

80. // bouton resize

82. JButton jbResize = new JButton("resize", new javax.swing.ImageIcon(getClass().getResource("/ressources/icons/resize_picture.png")));

83. jbResize.addActionListener(new ActionListener() {

84. @Override

85. public void actionPerformed(ActionEvent e) {

86. pfguipanel.centreView(pom.getBoundingBox());

87. }

88. });

89. toolbar.add(jbResize);

92. toolbar.add(new JSeparator(JSeparator.VERTICAL));

94. // bouton close

96. JButton jbClose = new JButton("close", new javax.swing.ImageIcon(getClass().getResource("/ressources/icons/cross.png")));

97. jbClose.addActionListener(new ActionListener() {

98. @Override

99. public void actionPerformed(ActionEvent e) {

100. dispose();

101. }

102. });

103. toolbar.add(jbClose);

105. this.add(toolbar, BorderLayout.NORTH);

106. this.pack();

108. this.setDefaultCloseOperation(EXIT_ON_CLOSE);

110. this.pfguipanel.setPom(pom);

112. }

113. private PFEM2DGuiPanel pfguipanel;

115. /**

116. * Main class.

117. *

118. * @param args the command line arguments

119. */

120. public static void main(String[] args) {

122. java.awt.EventQueue.invokeLater(new Runnable() {

123. @Override

124. public void run() {

125. new PlegatFem2D_GuiPanel("Demo PFEM - GUI Panel").setVisible(true);

126. }

127. });

128. }

129. }

Les fichiers image pour les icônes de la barre d'outils sont disponibles dans les sources de cet article. Ils sont issus du site http://www.fatcow.com/free-icons.

Il ne reste plus qu'à définir le fichier script Jython pour décrire les éléments graphiques que nous allons afficher.

Celui-ci crée quatre points, et les relie par quatre segments. Ces éléments sont ensuite ajoutés à un objet PFEM2DModel, qui est lui-même ajouté à notre gestionnaire d'objets graphiques représenté par la variable « pom » (voir chapitre III).

Le code du script Jython est présenté ci-après :

Fichier script Jython

1. from plegatfem2d import *

2. from plegatfem2d.objects import *

4. # définition du modèle

6. pt1 = PFEM2DPoint(1, -60, -60);

7. pt2 = PFEM2DPoint(2, 60, -60);

8. pt3 = PFEM2DPoint(3, 60, 60);

9. pt4 = PFEM2DPoint(4, -60, 60);

11. line1=PFEM2DLine(1,pt1,pt2);

12. line2=PFEM2DLine(2,pt2,pt3);

13. line3=PFEM2DLine(3,pt3,pt4);

14. line4=PFEM2DLine(4,pt4,pt1);

16. model = PFEM2DModel();

17. model.addObject(line1);

18. model.addObject(line2);

19. model.addObject(line3);

20. model.addObject(line4);

22. pom.addObject(model);

Après avoir lancé l'application, le script Jython peut être importé en cliquant sur le bouton « open » et en sélectionnant le fichier script. L'affichage doit ressembler à la figure suivante :

Une copie écran peut ensuite être sauvegardée grâce au bouton « screenshot ».

Si la forme géométrique sort de l'écran suite à une manipulation de souris trop rapide, et n'est plus visible, l'affichage peut être recadré sur celle-ci en appuyant sur le bouton « resize ».

Enfin, l'application peut être fermée avec le bouton « close ».

VI - Conclusion

Ce tutoriel nous a expliqué comment créer un JPanel personnalisé, nous permettant d'afficher des formes simples telles que des points et des lignes. Plus qu'une simple adaptation de la classe JPanel, c'est une miniapplication graphique qui est définie dans cet article, que vous pouvez compléter et adapter afin de répondre à vos besoins d'affichage !

Les sources de cet article sont disponibles en suivant ce lien : src_PlegatFem2D_GUIPanel.zip (miroir).

Les icônes utilisées dans l'application exemple sont incluses dans les sources. Elles peuvent également être téléchargées sur le site www.fatcow.com (sous licence CC).

Remerciements

Merci à keulkeul pour ses commentaires, à ClaudeLELOUP et f-leb pour leur relecture orthographique, ainsi qu'à djibril pour son aide sur le KitOOoDVP.