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;
}
}
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).