Code source de l'applet Apdiacide

Retour à l'exercice

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import chapman.graphics.JPlot2D;

class Fonctions
{static double log10 (double x)
{double y=Math.log(x)/Math.log(10);
return y;
}

static double arrondi(double x, int nombre)
{double multiple= Math.pow(10,(double) nombre);
double y=Math.round(x*multiple);
y/=multiple;
return y;
}
}

La classe Fonctions contient deux méthodes fonctions, l'une qui renvoie le logarithme décimal du nombre x reçu en argument , l'autre qui renvoie la valeur arrondie du nombre x reçu en argument . ces méthodes sont déclarées du type static ; ce sont donc des méthodes de classe dont l'appel ne nécessite que le nom de la classe exemple Fonctions.log10(120) . Il est inutile de créer une instance de la classe Fonctions pour les utiliser.

public class Apdiacide extends JApplet
{public void init ()
{Container contenu = this.getContentPane();
cb =new Graphe();
tabbord =new Commandes( cb);
contenu.add(tabbord,"North");
contenu.add(cb);
cb.start();
}

static String [] retourneNomacide() {return nomacide; }
static String retourneAcide(int numero){return nomacide[numero];}
static double retournePka1(int numero) {return pka1[numero];}
static double retournePka2(int numero) {return pka2[numero];}


private Commandes tabbord;
private Graphe cb;
static private String [] nomacide ={"acide méthanoique","dioxyde de carbone","ion ammonium","acide oxalique"};
static private double [] pka1 ={3.80f,6.40f,9.20f,1.20f};
static private double [] pka2 ={0.0f,10.30f,0.0f,4.30f};
}

La classe Apdiacide déclarée public est donc la classe principale de l'application; elle comporte la méthode init automatiquement exécutée lors du lancement de l'applet ; on y déclare la variable nomacide qui référence un tableau d'éléments du type chaine de caractères; ce tableau est fourni en argument au constructeur du controle JComboBox créé par l'instruction suivante dans la classe Comandes; listeacides=new JComboBox(Apdiacide.retourneNomacide()); ce contrôle affiche ainsi la liste de ces acides.
L'argument du constructeur n'est pas en fait la variable nomacide elle-même .En effet cette variable ou champ est privé (private) (ce qu'il est conseillé de faire) ; de ce fait ce champ n'est pas accessible à l'extérieur de la classe; on ne peut y accéder que par l'intermédiaire d'un méthode de la classe retourneNomacide qui par contre doit être publique. Cette méthode étant statique, son appel ne nécessite que le nom de la classe soit Apdiacide.retourneNomacide().
La méthode init crée une instance d'un objet du type Commandes qui est le panneau contenant les contrôles de l'application . Elle crée aussi une instance d'un objet de la classe Graphe (dérivant de la classe JPlot2D ) qui est le graphique.Ces deux objets sont ensuite ajoutés au contenu de la fenêtre graphique de l'applet par la méthode add.
L'instruction cb.start() lance un processus (thread) faisant varier la concentration (animation).

class Commandes extends JPanel implements ItemListener,AdjustmentListener,ActionListener
{public Commandes (Graphe graphique)
{super();//appel constructeur de base
this.graphique=graphique;
this.setBackground(new Color(243,244,145));
FlowLayout miseforme = new FlowLayout(FlowLayout.CENTER,10,5);
this.setLayout(miseforme);
listeacides=new JComboBox(Apdiacide.retourneNomacide());
listeacides.setBackground(new Color(186,220,244));
listeacides.setSelectedIndex(0);
this.add(listeacides);listeacides.addItemListener(this);
JLabel c =new JLabel(" Co");
this.add(c);
valco=new JTextField("0.10",5);valco.setHorizontalAlignment(0);
valco.addActionListener(this);
valco.setEditable(true);valco.setBackground(new Color(186,220,244));this.add(valco);
Dimension dim=new Dimension(90,25);
co = new JScrollBar(0);co.setMinimum(-70);co.setMaximum(0);
co.addAdjustmentListener(this);
co.setUnitIncrement(1);co.setBlockIncrement(10);co.setPreferredSize(dim);
co.setBackground(new Color(186,220,244));this.add(co);
JLabel label1=new JLabel("pKa = ",0);
this.add(label1);
ka1=new JTextField((String)(" "+Fonctions.arrondi(Apdiacide.retournePka1(0),2)),4);
ka1.setEditable(false);ka1.setBackground(new Color(186,220,244));
this.add(ka1);
}

public void adjustmentValueChanged( AdjustmentEvent ev)
{if (ev.getSource().equals(this.co)){
double valeur=ev.getValue();
valeur/=10;
valeur=Math.pow(10.0f,valeur);
this.valco.setText(" "+Fonctions.arrondi(valeur,5));
System.out.println("C = "+Fonctions.arrondi(valeur,2));
graphique.stop();
graphique.changec(valeur);
}
}
public void actionPerformed(ActionEvent ev)
{try
{ if (ev.getSource().equals(this.valco))
{String valeur = this.valco.getText();
double y =Double.parseDouble(valeur);
int x=(int) (10*Fonctions.log10(y));
co.setValue(x);
graphique.changec(y);
}
}
catch (NumberFormatException e)
{JOptionPane.showMessageDialog(this.getParent(),"Valeur de Co non valide");
this.valco.setText("0.1");
}
}

public void itemStateChanged(ItemEvent ev)
{Object objet= ev.getSource();
if (objet.equals(listeacides))
{for (int i = 0;i<listeacides.getItemCount();++i)
{if (listeacides.getSelectedItem()==Apdiacide.retourneAcide(i))
{ka1.setText((String)(" "+Fonctions.arrondi(Apdiacide.retournePka1(i),2)));
if (Apdiacide.retournePka2(i)!=0)
{

if (label2==null){
label2=new JLabel("pKa2 = ",0);this.add(label2);
ka2=new JTextField((String)(" "+Fonctions.arrondi(Apdiacide.retournePka2(i),2)),4);ka2.setEditable(false);this.add(ka2);
ka2.setBackground(new Color(186,220,244));this.validate();}
//this.label1.setText("pKa1 = ");label1.revalidate();
}
if (Apdiacide.retournePka2(i)==0)
{

if (label2!=null) {this.remove(label2);this.remove(ka2);this.repaint();ka2=null;label2=null;}
//this.label1.setText("pKa = ");label1.revalidate();
}
graphique.changepka(Apdiacide.retournePka1(i),Apdiacide.retournePka2(i));
break;}
}
}
}

private JComboBox listeacides;
private JTextField valco, ka1,ka2;
private JLabel label1,label2;
private JScrollBar co;
private Graphe graphique;
}

La classe Commandes qui dérive de la classe JPanel permet de créer un panneau dans lequel sont placés les différents contrôles. Le contrôle listeacides du type JComboBox génère des évènements du type Item quand la valeur sélectionnée est modifiée . Le contrôle co du type JScrollBar (qui permet de modifier la valeur de la concentration) génère des évènements du type Adjustment quand on déplace le curseur. En ce qui concerne le contrôle du type Champ de texte valco (qui affiche la concentration et qui permet aussi de modifier la valeur de cette concentration) , on a décidé d'exploiter l'évènement Action généré quand l'utilisateur appuie sur la touche Entrée pour valider.
L'exploitation de ces évènements nécessite de définir les méthodes itemStateChanged ,adjustmentValueChanged et actionPerformed déclarées respectivement dans les interfaces ItemListener ,AdjustmentListener, ActionListener (il s'agit dans chaque cas de l'unique méthode de l'inteface ). La classe Commandes doit donc implémenter ces trois interfaces afin de redéfinir ces trois méthodes.
Le constructeur de la classe Commandes instancie les différents contrôles ; exemple ( listeacides=new JComboBox(Apdiacide.retourneNomacide());).
Le contrôle est ensuite ajouté au panneau par la méthode add ; exemple ( this.add(listeacides)) : this désigne l'objet de la classe Commandes en cours de construction.
On associe au contrôle l'objet écouteur d'évènements approprié ; exemple (
listeacides.addItemListener(this)); on associe au contrôle listeacides un objet écouteur d'évènements du type Item (changement de sélection dans la liste); le mot clé this désigne ici l'objet appelant la méthode soit listeacides : c'est donc cette instruction qui rend notre contrôle réceptif à un changement de valeur sélectionnée. Un tel changement génère alors un évènement de type Item qui est géré par le gestionnaire d'évènements itemStateChanged .

Le gestionnaire d'évènements :la méthode ev.getSource(); renvoie une référence à l'objet ayant généré l'évènement ; cette référence est afffectée à la variable objet du type Object . L'instruction if (objet.equals(listeacides) renvoie vrai si les références de l'objet source de l'évènement et du contrôle listeacides sont identiques. La méthode listeacides.getSelectedItem renvoie la valeur sélectionnée; cette valeur est comparée à l'aide d'une boucle for à chacune des valeurs du tableau fourni en argument au contrôle listeacides. La méthode de type static , Apdiacide.retourneAcide(i)) déclarée dans la classe Apdiacide renvoie la valeur de rang i du tableau nomacide .
Quand la valeur sélectionnée par l'utilisateur est trouvée dans le tableau , on met à jour (ka1.setText ...)le contenu de la zone de texte ka1 (affichant pKa1) ; la valeur de pKa1 étant obtenue par la méthode Apdiacide.retournePka1(i).
On teste ensuite si la valeur sélectionnée correspond à un diacide. Dans ce dernier cas la valeur renvoyée par la méthode
Apdiacide.retournePka2(i) est différente de 0. Si tel est le cas on vérifie d'abord si le panneau ne contient pas déjà l'étiquette et la zone de texte affichant la valeur de pka2 par l'instruction if (label2==null) (label2 est l'étiquette affichant pka2). Si cette étiquette n'existe pas , on la crée ainsi que la zone de texte ka2 permettant d'afficher la valeur de pka2. Ces deux contrôles sont ajoutés au panneau par add. On appelle la méthode validate du panneau afin que le gestionnaire de mise en forme réaffiche correctement l'ensemble des contrôles.
Si la valeur sélectionnée correspond à un monoacide et si les contrôles label2 et ka2 existent , il faut les enlever du panneau ce qui est obtenu par la méthode remove .

Les deux autres gestionnaires d'évènements récupèrent la nouvelle valeur de la concentration . La concentration de tracé du graphique est alors modifiée par l'intermédiaire de la méthode changec de la classe Graphe.

class Graphe extends JPlot2D implements Runnable
{public Graphe()
{super();
this.setPlotType(JPlot2D.LINEAR);
this.setBackground(new Color(240,234,190));
this.setLineColor(Color.red);
this.setLineWidth(2.0f);
this.setXLabel(" pH ");
this.setYLabel(" log (C) ");
this.setGridState(true);
for (int i =0;i<x.length;++i)
{x[i]=(double) (i/10);
y[i]=this.gety(x[i]);z[i]=this.getz(x[i]);w[i]=this.getw(x[i]);
u[i]= - x[i];
v[i]=-14.0f+x[i];
}
this.addCurve(x,y);
this.addCurve(x,z);
this.addCurve(x,w);
this.addCurve(x,u);
this.addCurve(x,v);
coordonnees="pH = "+this.maxx()[0]+" log(C) = "+this.maxy()[1];
// this.addAnnotation(coordonnees,0.0f,-13.0f);
}

public void changepka(double pk1, double pk2)
{this.pk1=pk1;this.pk2=pk2;
for (int i =0;i<x.length;++i)
{x[i]=i/10;
y[i]=this.gety(x[i]);z[i]=this.getz(x[i]);w[i]=this.getw(x[i]);
}
this.setCurrentCurve(0);this.setValues(x,y);
this.setCurrentCurve(1);this.setValues(x,z);
this.setCurrentCurve(2);this.setValues(x,w);
this.repaint();
}

public void changec(double conc)
{this.c=conc;
//System.out.println("C = "+Fonctions.arrondi(c,2));
for (int i =0;i<x.length;++i)
{x[i]=i/10;
y[i]=this.gety(x[i]);z[i]=this.getz(x[i]);w[i]=this.getw(x[i]);
}
this.setCurrentCurve(0);this.setValues(x,y);
this.setCurrentCurve(1);this.setValues(x,z);
this.setCurrentCurve(2);this.setValues(x,w);
this.repaint();
}
double gety (double x)
{double yy=(pk2==0)?Fonctions.log10(c/(1+Math.pow(10,-this.pk1)/Math.pow(10,-x))):Fonctions.log10(c/(1+Math.pow(10,-this.pk1)/Math.pow(10,-x)+Math.pow(10,-this.pk1)*Math.pow(10,-this.pk2)/(Math.pow(10,-x)*Math.pow(10,-x))));
return yy;}
double getz (double x)
{double zz=(pk2==0)?Fonctions.log10(c/(1+Math.pow(10,-x)/Math.pow(10,-this.pk1))):Fonctions.log10(c/(1+Math.pow(10,-x)/Math.pow(10,-this.pk1)+Math.pow(10,-this.pk2)/Math.pow(10,-x)));
return zz;}
double getw (double x)
{double ww=(pk2==0)?0.0f:Fonctions.log10(c/(1+Math.pow(10,-x)/Math.pow(10,-this.pk2)+Math.pow(10,-x)*Math.pow(10,-x)/(Math.pow(10,-this.pk1)*Math.pow(10,-this.pk2))));
return ww;}
double [] maxx () {return this.getXScale();}
double [] maxy () {return this.getYScale();}

/*public void changecoordonnes(double [] coord )
{this.coordonnees="pH = "+coord[0]+" log(C)= "+coord[1];
this.addAnnotation(coordonnees,0.0f,-11.0f);
this.setCurrentAnnotation(0);
this.repaint();
}
*/
public void start ()
{processus=new Thread(this);
/*crée le thread*/
processus.setPriority(Thread.MIN_PRIORITY);
processus.start();
/*démarre le thread*/
}

public void run ()
{Thread moi = Thread.currentThread();
/*déclaration d'une variable moi du type Thread ; cette variable
référence le Thread en cours d'éxécution état runable en principe processus*/
while (processus == moi)
/*cette boucle est nécessaire si on veut modifier de façon interactive
les valeurs de l et h à l'intérieur de la méthode run*/
{this.c=(c>=1.0e-7)?c/2:0.1f;
this.changec(c);
//repaint ();
try
{processus.sleep(200);}
catch (Exception e) {}
}
processus =moi;
}

public synchronized void stop()
{this.processus=null;
}

private double [] x =new double[141];
private double [] y = new double[141];
private double [] z = new double[141];
private double [] u = new double[141];
private double [] v = new double[141];
private double [] w = new double[141];
private double pk1=3.8f,pk2 = 0.0f,c=0.1;
private String coordonnees = " ";
private Thread processus;
}

La classe Graphe qui dérive de la classe JPlot2D de la bibliothèque de Chapman fournit les méthodes permettant de tracer le graphique. Le constructeur, méthode public Graphe , définit le type de graphique (setPlotType ), les étiquettes des deux axes (setXLabel et setYLabel ) , définit les valeurs de x (pH variant de 0 à 14) et les valeurs des différentes courbes. Le calcul des valeurs des courbes est en fait effectué par trois méthodes fonction getx, gety, getw .
Les courbes sont ajoutées au graphique par la méthode addCurve.
Les méthodes changepka et changec permettent de modifier les valeurs des variables privées c , pk1 et pk2. Ce sont ces méthodes qui sont appelées lorsque l'utilisateur modifie les valeurs de ces paramètres en agissant sur les contrôles du panneau de commandes (voir les gestionnaires d'évènements de la classe Commandes).

Retour à l'exercice