/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Copyright (C) 2011-2013 Marchand Eric <ricoh51@free.fr>
This file is part of Freegressi.
Freegressi is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Freegressi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Freegressi. If not, see <http://www.gnu.org/licenses/>.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package freegressi.graphics;
import freegressi.tableur.Colonne;
import freegressi.tableur.SpreadSheets;
import freegressi.tableur.Tableur;
import freegressi.utils.Utils;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.border.TitledBorder;
/**
* La boite de dialogue qui permet d'éditer les courbes
* @author marchand
*/
public class JDialogEditCurves extends JDialog implements ActionListener {
/** Action d'ajouter une courbe */
private AddCurveAction addCurveAction = new AddCurveAction(null,
new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/Crystal_Clear_action_edit_add-48.png")),
"Ajouter une courbe", 0);
/** Action de supprimer une courbe */
private DeleteCurveAction deleteCurveAction = new DeleteCurveAction(null,
new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/Gnome-edit-delete-24.png")),
"Supprimer cette courbe", 0);
/** le bouton Ok de sortie du dialogue */
private JButton jbOk;
/** le bouton Annuler de sortie du dialogue */
private JButton jbAnnuler;
/** Flag qui permet de savoir si l'utilisateur est sorti par le bouton Ok */
private boolean ok = false;
/** Le JTabbedPane dans lequel on place différents les panneaux */
private JTabbedPane jtp;
/** Le tableau des images des styles de points */
private ImageIcon[] iconPoints;
/** Le tableau des textes associés aux images des styles de points */
private String[] textPoints;
/** Le tableau des images des styles de lignes */
private ImageIcon[] iconLines;
/** Le tableau des textes associés aux images des styles de lignes */
private String[] textLines;
/** Le panneau des réglages généraux */
private JPanelGeneral jpgene;
/** Le panneau de réglage de l'abscisse (unique) des courbes */
private JPanelAbscissa abscissaPanel;
/** Le panneau de la liste des courbes */
private JPanel curvesPanel;
/** Le panneau de la liste des axes */
private JPanel axisPanel;
/** Le panneau qui contient abscissaPanel curvesPanel et axisPanel */
private JPanel editCurvesPanel;
/** La liste des panneaux de courbes */
private List<JPanelCurve> panelCurves = new ArrayList<>();
/** Les 4 panneaux d'axes */
private JPanelAxis bottom, top, left, right;
/**
* Constructeur
* @param parent la frame appelante
* @param graphicStyle le GraphicStyle qui permet d'initialiser les
* champs du dialogue
*/
public JDialogEditCurves(JFrame parent, GraphicStyle graphicStyle) {
super(parent, true);
setTitle("Éditer les courbes");
createUI(graphicStyle);
setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
pack();
setLocationRelativeTo(null);
jtp.setSelectedIndex(1);
}
/**
* Crée le panneau réglant les différents courbes
* @param graphicStyle
*/
private void createCurvesPanel(GraphicStyle graphicStyle){
curvesPanel = new JPanel();
curvesPanel.setLayout(new BoxLayout(curvesPanel, BoxLayout.Y_AXIS));
for( Curve curve : graphicStyle.getCurves()){
addCurvePanel(curve);
}
curvesPanel.setBorder(new TitledBorder("Courbes"));
curvesPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
}
/**
* Crée le panneau réglant l'abscisse du graphique
* @param graphicStyle
*/
private void createAbscissaPanel(GraphicStyle graphicStyle){
abscissaPanel = new JPanelAbscissa(this, graphicStyle);
abscissaPanel.setBorder(new TitledBorder("Abscisse"));
abscissaPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
}
/**
* Crée une liste de noms de variables dont l'axe est à la position
* "position"
* @param position la position de l'axe
* @return la liste des noms de variable
*/
private List<String> getNamesAtPosition(Position position){
List<String> names = new ArrayList<>();
if (abscissaPanel.getAbscissaPosition() == position){
names.add(abscissaPanel.getAbscissaName());
}
for (JPanelCurve jpc : panelCurves){
if (jpc.getPosition() == position){
names.add(jpc.getCurveName());
}
}
return names;
}
/**
* Affecte de nouvelles valeurs au min et max d'un axe. Les valeurs sont
* formattées en fonction du nombre de chiffres significatifs
* @param jpa l'axe à modifier
* @param min la nouvelle valeur du min
* @param max la nouvelle valeur du max
*/
private void changeMinMax(JPanelAxis jpa, double min, double max){
// Pas moyen de formatter le min lorsqu'il n'y a aucun point, le min
// vaut Double.MAX_VALUE et est arrondi à ∞ qui provoque une exception!
// idem pour le max
int CS = SpreadSheets.getInstance().getCS();
String minStr = Utils.formatteNombre(min, CS);
minStr = Utils.replaceVirguleToPoint(minStr);
try{
Double.parseDouble(minStr);
jpa.setMin(minStr);
} catch (NumberFormatException e){
jpa.setMin(Double.toString(min));
}
String maxStr = Utils.formatteNombre(max, CS);
maxStr = Utils.replaceVirguleToPoint(maxStr);
try{
Double.parseDouble(maxStr);
jpa.setMax(maxStr);
} catch (NumberFormatException e){
jpa.setMax(Double.toString(max));
}
}
/**
* Un nom (d'abscisse ou de courbe) vient de changer
* @param newName le nouveau nom
* @param position la position de l'axe correspondant
*/
public void notifyNameChanged(String newName, Position position){
Tableur sheet = SpreadSheets.getInstance().getActiveSheet();
Colonne col = sheet.donneColonne(newName);
double min = col.getMin();
double max = col.getMax();
JPanelAxis jpa = bottom;
switch (position){
case BOTTOM : jpa = bottom; break;
case TOP : jpa = top; break;
case LEFT : jpa = left; break;
case RIGHT : jpa = right; break;
}
changeMinMax(jpa, min, max);
jpa.setScaleAuto(true);
}
/**
*
* @param pos
* @return
*/
private String createAxisText(Position pos){
String str = "";
for (JPanelCurve jpc : panelCurves){
if (jpc.getPosition() == pos){
if (!str.equals("")){
str += ", ";
}
str += jpc.getCurveName();
}
}
return str;
}
/**
* Trouve le JPanelAxis correspondant à la position
* @param pos la position
* @return le JPanelAxis
*/
private JPanelAxis getJPanelAxis(Position pos){
switch (pos){
case BOTTOM : return bottom;
case TOP : return top;
case LEFT : return left;
case RIGHT : return right;
}
return bottom;
}
/**
* Donne la position d'un JPanelAxis
* @param jpa
* @return
*/
private Position getPosition(JPanelAxis jpa){
Position position = Position.BOTTOM;
if (jpa == bottom){
position = Position.BOTTOM;
} else if (jpa == top){
position = Position.TOP;
} else if (jpa == left){
position = Position.LEFT;
} else if (jpa == right){
position = Position.RIGHT;
}
return position;
}
/**
* La position de l'abscisse a changé, il faut rendre visible le
* nouvel axe et cacher l'ancien.
* @param pos
*/
public void notifyAbscissaPositionChanged(Position pos){
bottom.setVisible(pos == Position.BOTTOM);
top.setVisible(pos == Position.TOP);
double min, max;
boolean auto;
String title;
if (pos == Position.BOTTOM){
min = top.getMin();
max = top.getMax();
auto = top.isScaleAuto();
title = top.getText();
bottom.setAll(min, max, auto, title);
} else {
min = bottom.getMin();
max = bottom.getMax();
auto = bottom.isScaleAuto();
title = bottom.getText();
top.setAll(min, max, auto, title);
}
pack();
}
public void update(){
reCalculateAbscissaAxis();
reCalculateCurvesAxis();
notifyMinMaxChanged();
}
private void reCalculateAbscissaAxis(){
String name = abscissaPanel.getAbscissaName();
Tableur sheet = SpreadSheets.getInstance().getActiveSheet();
Colonne col = sheet.donneColonne(name);
if (bottom.isVisible()){
if (bottom.isScaleAuto()){
changeMinMax(bottom, col.getMin(), col.getMax());
}
}
if (top.isVisible()){
if (top.isScaleAuto()){
changeMinMax(top, col.getMin(), col.getMax());
}
}
}
/**
* Quelquechose a changé, il faut mettre à jour les axes left et right.
*
*/
private void reCalculateCurvesAxis(){
boolean l = false, r = false;
double minL = Double.MAX_VALUE, maxL = Double.MIN_VALUE, min;
double minR = Double.MAX_VALUE, maxR = Double.MIN_VALUE, max;
String name;
Colonne col;
Tableur sheet = SpreadSheets.getInstance().getActiveSheet();
boolean leftWasVisible = left.isVisible(), rightWasVisible = right.isVisible();
boolean changeLeftMinMax = true, changeRightMinMax = true;
for (JPanelCurve jpc : panelCurves){
name = jpc.getCurveName();
col = sheet.donneColonne(name);
min = col.getMin();
max = col.getMax();
if (jpc.getPosition() == Position.LEFT){ // Axe de gauche
l = true;
if (!leftWasVisible){
left.setScaleAuto(true);
leftWasVisible = true;
}
if (left.isScaleAuto()){
if (min < minL){ minL = min; }
if (max > maxL){ maxL = max; }
} else {
if (leftWasVisible){ // laisse les min et max sans les toucher
changeLeftMinMax = false;
}
}
} else if (jpc.getPosition() == Position.RIGHT){ // Axe de droite
r = true;
if (!rightWasVisible){
right.setScaleAuto(true);
rightWasVisible = true;
}
if (right.isScaleAuto()){
if (min < minR){ minR = min; }
if (max > maxR){ maxR = max; }
} else {
if (rightWasVisible){ // laisse les min et max sans les toucher
changeRightMinMax = false;
}
}
}
}
left.setVisible(l);
right.setVisible(r);
if (l && changeLeftMinMax){
changeMinMax(left, minL, maxL);
left.setText(createAxisText(Position.LEFT));
}
if (r && changeRightMinMax){
changeMinMax(right, minR, maxR);
right.setText(createAxisText(Position.RIGHT));
}
}
private void createAxisPanel(GraphicStyle gs){
axisPanel = new JPanel();
axisPanel.setBorder(new TitledBorder("Axes"));
axisPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
axisPanel.setLayout(new BoxLayout(axisPanel, BoxLayout.Y_AXIS));
JPanelAxis jpa;
bottom = new JPanelAxis(this, "Bas", 0.0, 0.0, true, "");
axisPanel.add(bottom);
top = new JPanelAxis(this, "Haut", 0.0, 0.0, true, "");
axisPanel.add(top);
left = new JPanelAxis(this, "Gauche", 0.0, 0.0, true, "");
axisPanel.add(left);
right = new JPanelAxis(this, "Droite", 0.0, 0.0, true, "");
axisPanel.add(right);
if (gs.getAbscissaPosition() == Position.BOTTOM){
top.setVisible(false);
bottom.setAll(gs.getaX1().getMin(), gs.getaX1().getMax(),
gs.getaX1().isAutoScale(), gs.getaX1().getTitle());
} else {
bottom.setVisible(false);
top.setAll(gs.getaX2().getMin(), gs.getaX2().getMax(),
gs.getaX2().isAutoScale(), gs.getaX2().getTitle());
}
}
/**
* Crée toute l'interface graphique du dialogue
* @param graphicStyle
*/
private void createUI(GraphicStyle graphicStyle){
loadImages();
JToolBar toolbar = new JToolBar();
toolbar.setFloatable(false); // toolbar fixe
toolbar.add(addCurveAction);
add(toolbar, BorderLayout.PAGE_START);
jtp = new JTabbedPane();
add(jtp);
jpgene = new JPanelGeneral();
jpgene.setGrid(graphicStyle.isDrawGrid());
jtp.addTab("Général", jpgene);
editCurvesPanel = new JPanel();
editCurvesPanel.setLayout(new BoxLayout(editCurvesPanel, BoxLayout.Y_AXIS));
jtp.addTab("Courbes", editCurvesPanel);
createAbscissaPanel(graphicStyle);
editCurvesPanel.add(abscissaPanel);
createCurvesPanel(graphicStyle);
editCurvesPanel.add(curvesPanel);
createAxisPanel(graphicStyle);
editCurvesPanel.add(axisPanel);
reCalculateCurvesAxis();
jbOk = new JButton("Ok", new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/Crystal_Clear_app_clean-24.png")));
jbOk.addActionListener(this);
jbAnnuler = new JButton("Annuler", new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/Crystal_Clear_action_button_cancel-24.png")));
jbAnnuler.addActionListener(this);
JPanel jpBoutons = new JPanel();
FlowLayout layout2 = new FlowLayout();
jpBoutons.setLayout(layout2);
layout2.setAlignment(FlowLayout.RIGHT);
jpBoutons.add(jbAnnuler);
jpBoutons.add(jbOk);
add(jpBoutons, BorderLayout.PAGE_END);
}
/**
* Charge les images et les textes qui iront dans les JComboBox
*/
private void loadImages() {
// Les images de type de points
int numberIcons = 5;
iconPoints = new ImageIcon[numberIcons];
textPoints = new String[numberIcons];
iconPoints[0] = null;
iconPoints[1] = new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/point_rond-30.png"));
iconPoints[2] = new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/point_carre-30.png"));
iconPoints[3] = new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/point_croix-30.png"));
iconPoints[4] = new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/point_croix_oblique-30.png"));
textPoints[0] = PointStyle.NONE.getLabel();
textPoints[1] = PointStyle.CIRCLE.getLabel();
textPoints[2] = PointStyle.SQUARE.getLabel();
textPoints[3] = PointStyle.CROSS.getLabel();
textPoints[4] = PointStyle.OBLIQUE_CROSS.getLabel();
// Les images de type de lignes
numberIcons = 4;
iconLines = new ImageIcon[numberIcons];
textLines = new String[numberIcons];
iconLines[0] = null;
iconLines[1] = new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/ligne_continue_30x24.png"));
iconLines[2] = new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/ligne_brisee_30x24.png"));
iconLines[3] = new javax.swing.ImageIcon(getClass().getResource("/freegressi/img/ligne_points_30x24.png"));
textLines[0] = LineStyle.NONE.getLabel();
textLines[1] = LineStyle.CONTINUE.getLabel();
textLines[2] = LineStyle.BROKEN.getLabel();
textLines[3] = LineStyle.DOTTED.getLabel();
}
/**
* Répond au clic sur les boutons Ok et Annuler
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource().equals(jbOk)) {
ok = true;
}
setVisible(false);
}
/**
*
* @return true si le dialog s'est terminé avec Ok false sinon
*/
public boolean isOk() {
return ok;
}
/**
* Un min ou un max d'échelle d'axe vient d'être modifié par l'utilisateur.
* On vérifie que ce sont bien des doubles et on désactive le bouton Ok
* si ce n'est pas le cas.
*/
public void notifyMinMaxChanged(){
jbOk.setEnabled(verifyMinMax(bottom) && verifyMinMax(top)
&& verifyMinMax(left) && verifyMinMax(right));
System.out.println("notifyMinMaxChanged");
}
/**
* Vérifie qu'il n'y a pas d'erreur dans les min et max d'un axe
* @param jpa le JPanelAxis à vérifier
* @return true si les deux min et max de l'axe sont bien des doubles
*/
private boolean verifyMinMax(JPanelAxis jpa){
return jpa == null? true: jpa.goodMinMax();
}
/**
* Remplit les champs min max auto et text d'un GraphicStyle. Utilisé
* dans getNewGraphicStyle
* @param jpa
* @param as
*/
private void fillAxis(JPanelAxis jpa, AxisStyle as){
if (/*jpa == null*/!jpa.isVisible()){
as.setMin(0);
as.setMax(0);
as.setAutoScale(true);
as.setText("");
} else {
as.setMin(jpa.getMin());
as.setMax(jpa.getMax());
as.setAutoScale(jpa.isScaleAuto());
as.setText(jpa.getText());
}
}
private String getAbscissaText(){
String text;
if (bottom.isVisible()){
text = bottom.getText();
} else{
text = top.getText();
}
return text;
}
/**
* Permet de récupérer le GraphicStyle définit par l'utilisateur une
* fois que celui-ci a cliqué sur le bouton Ok
* @return le GraphicStyle réglé par l'utilisateur
*/
public GraphicStyle getNewGraphicStyle(){
GraphicStyle gs = new GraphicStyle();
Axis abscissa;
Position abscissaPosition = abscissaPanel.getAbscissaPosition();
String abscissaName = abscissaPanel.getAbscissaName();
switch(abscissaPosition){
case BOTTOM:
abscissa = gs.getaX1();
break;
case TOP:
abscissa = gs.getaX2();
break;
default:
throw new RuntimeException("Bad Location : Abscissa");
}
gs.setDrawGrid(jpgene.getGrid());
gs.setAbscissa(abscissaName);
gs.setAbscissaPosition(abscissaPosition);
//abscissa.getAxisStyle().setName(abscissaName);
abscissa.getAxisStyle().setText(getAbscissaText());
fillAxis(bottom, gs.getaX1().getAxisStyle());
fillAxis(top, gs.getaX2().getAxisStyle());
fillAxis(left, gs.getaY1().getAxisStyle());
fillAxis(right, gs.getaY2().getAxisStyle());
List<Curve> curves = new ArrayList<>();
for (JPanelCurve jpc : panelCurves){
PointLineStyle pls = jpc.getPointLineStyle();
Position pos = jpc.getPosition();
Axis axis;
switch(pos){
case LEFT:
axis = gs.getaY1();
break;
case RIGHT:
axis = gs.getaY2();
break;
default:
throw new RuntimeException("Bad Location : Curve");
}
// axis.getAxisStyle().setName(jpc.getCurveName());
// axis.getAxisStyle().setText(jpc.getCurveName());
Curve curve = new Curve(abscissaName, jpc.getCurveName(), abscissa, axis, pls);
curves.add(curve);
}
gs.setCurves(curves);
return gs;
}
/**
* Ajoute un panneau correspondant à une nouvelle courbe.
* @param curve
*/
private void addCurvePanel(Curve curve){
JPanelCurve jpc = new JPanelCurve(this, curve, iconPoints, textPoints, iconLines, textLines);
curvesPanel.add(jpc);
panelCurves.add(jpc);
jpc.getRemoveButton().setAction(deleteCurveAction);
//setMinMaxAtPosition(jpc.getPosition());
}
/**
* La classe qui permet d'ajouter une courbe
*/
class AddCurveAction extends AbstractAction {
public AddCurveAction(String text, ImageIcon icon, String desc, Integer mnemonic) {
super(text, icon);
putValue(SHORT_DESCRIPTION, desc);
putValue(MNEMONIC_KEY, mnemonic);
}
@Override
public void actionPerformed(ActionEvent e) {
addCurvePanel(null);
//notifyPositionChanged();
pack();
}
}
/**
* La classe qui permet de supprimer une courbe
*/
class DeleteCurveAction extends AbstractAction {
public DeleteCurveAction(String text, ImageIcon icon, String desc, Integer mnemonic) {
super(text, icon);
putValue(SHORT_DESCRIPTION, desc);
putValue(MNEMONIC_KEY, mnemonic);
}
@Override
public void actionPerformed(ActionEvent e) {
JButton jb = (JButton) e.getSource();
JPanelCurve jpc = (JPanelCurve) jb.getParent();
curvesPanel.remove(jpc);
panelCurves.remove(jpc);
//notifyPositionChanged();
pack();
}
}
}