TablooProto.java 9.48 KB
package ihm;

/*
 * TablooProto.java requires no other files.
 *
 */

import kernel.Cell;
import kernel.Formula;
import kernel.Grid;
import kernel.exception.*;
import kernel.function.Average;
import kernel.function.Sum;
import kernel.operation.Addition;
import kernel.operation.Division;
import kernel.operation.Multiplication;
import kernel.operation.Subtraction;

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class TablooProto extends JPanel {
	
	private static Grid grid;
	
	// Fourni: ne rien changer.
	public TablooProto(Grid grid) throws ClassNotFoundException, IOException {
		super(new GridLayout(1, 0));
		
		// modele de donnees
		// cf. plus loin la inner classe MyTableModel a modifier...
		MyTableModel tableModel = new MyTableModel(grid);
		
		// la JTable et ses parametres
		JTable table = new JTable(tableModel);
		table.setPreferredScrollableViewportSize(new Dimension(1000, 500));
		table.setGridColor(Color.BLACK);
		table.setShowGrid(true);
		
		// on ajoute un scroll
		JScrollPane scrollPane = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		add(scrollPane);
		table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		
		// parametrage de la 1ere ligne = noms des colonnes
		((DefaultTableCellRenderer) table.getTableHeader().getDefaultRenderer()).setHorizontalAlignment(JLabel.CENTER);
		
		// parametrage de la 1ere colonne consacree a la numerotation des lignes
		TableColumn tm = table.getColumnModel().getColumn(0);
		tm.setPreferredWidth(tm.getPreferredWidth() * 2 / 3);
		tm.setCellRenderer(new PremiereColonneSpecificRenderer(Color.LIGHT_GRAY));
		
	}
	
	// Inner class pour changer l'aspect de la premiere colonne consacree a la numerotation des lignes
	// Fourni: ne rien changer.
	class PremiereColonneSpecificRenderer extends DefaultTableCellRenderer {
		
		/**
		 *
		 */
		Color couleur;
		
		public PremiereColonneSpecificRenderer(Color couleur) {
			super();
			this.couleur = couleur;
			this.setHorizontalAlignment(JLabel.CENTER);
		}
		
		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
			Component cell = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
			cell.setBackground(couleur);
			return cell;
		}
	}
	
	// Inner class pour etablir la connexion entre la JTable graphique et un modele de donnees.
	// Pour nous le modele de donnees sera une grille du noyau de representation et de calcul
	// construite et sauvegardee par serialisation comme precedemmment.
	// Dans ce prototype exemple, le modele de donnees est une simple matrice de String "en dur".
	// Il faudra le remplacer par une connexion a une telle grille.
	class MyTableModel extends AbstractTableModel {
		
		/**
		 *
		 */
		private Grid grid;
		
		MyTableModel(Grid grid) {
			this.grid = grid;
		}
		
		@Override
		// Standard: doit retourner le nbre de colonnes de la JTable
		public int getColumnCount() {
			return grid.getTotalColumn() + 1;
		}
		
		@Override
		// Standard: doit retourner le nbre de lignes de la JTable
		public int getRowCount() {
			return grid.getTotalRow();
		}
		
		// Standard: doit renvoyer le nom de la colonne a afficher en tete
		// Fourni: ne rien changer.
		@Override
		public String getColumnName(int col) {
			if (col == 0) {
				return ""; // colonne consacrée aux numeros de ligne
			} else {
				return "" + (char) ((int) ('A') + col - 1);
			}
		}
		
		// Utilitaire interne fourni (ne rien changer)
		// Retourne le nom d'une case a partir de ses coordonnees dans la JTable.
		private String getNomCase(int row, int col) {
			return this.getColumnName(col) + (row + 1); // row commence a 0
		}
		
		@Override
		// Standard: doit renvoyer le contenu a afficher de la case correspondante
		public Object getValueAt(int row, int col) {
			if (col == 0) {
				// Fourni: ne rien changer.
				// en colonne 0 : numeros de lignes
				return "" + (row + 1);
			} else {
				try {
					return grid.getDevelopedFormula(this.getColumnName(col), row + 1) + "=" + grid.getValue(this.getColumnName(col), row + 1);
				} catch (CellNotFoundException e) {
					// TODO Auto-generated catch block
					
				}
			}
			return "";
		}
		
		// Standard.
		// Fourni: ne rien changer.
		@Override
		public Class getColumnClass(int c) {
			return getValueAt(0, c).getClass();
		}
		
		// Standard: determine si une case est editable ou non.
		// Fourni: ne rien changer.
		// Seules les cases de la 1er colonne ne le sont pas
		// (consacrees a la numerotation des lignes)
		@Override
		public boolean isCellEditable(int row, int col) {
			return col > 0;
		}
		
		// Standard: l'utilisateur a entré une valeur dans une case,
		// mettre a jour le modèle de donnees connecte.
		// L'utilisateur a modifie une case.
		// Si c'est une valeur numerique
		// - modifier la case correspondante dans la grille si cette case existe
		// - ajouter la case correspondante dans la grille
		@Override
		public void setValueAt(Object input, int row, int col) {
			Formula formula = new Addition(null, null);
			double value = 0;
			int cellType;
			
			if (input instanceof String && !((String) input).isEmpty()) {
				String text = (String) input;
				try {
					try {
						// Récupération de la valeur
						value = Double.parseDouble(text);
						cellType = 0;
					} catch (NumberFormatException exception) {
						// Récupération de la formule
						formula = this.generateFormulaWithString(text);
						cellType = 1;
					}
					
					String cellName = this.getNomCase(row, col);
					switch (cellType) {
						case 0:
							if (this.grid.cellExist(cellName))
								this.grid.setValue(this.getColumnName(col), row + 1, value);
							else
								this.grid.createCell(this.getColumnName(col), row + 1, value);
							break;
						case 1:
							if (this.grid.cellExist(cellName))
								this.grid.setFormula(this.getColumnName(col), row + 1, formula);
							else
								this.grid.createCell(this.getColumnName(col), row + 1, formula);
							break;
					}
				} catch (CellNotFoundException | BadSyntaxException | CreateCycleException | InvalidIntervalException e) {
					JOptionPane.showMessageDialog(null, e.getMessage(), "Attention", JOptionPane.INFORMATION_MESSAGE);
				}
			} else {
				try {
					this.grid.deleteCell(this.getColumnName(col), row + 1);
				} catch (CannotDeleteCellException e) {
					JOptionPane.showMessageDialog(null, e.getMessage(), "Attention", JOptionPane.INFORMATION_MESSAGE);
				}
			}
			
			// mise a jour automatique de l'affichage de toute la table suite a la modification
			fireTableDataChanged();
		}
		
		private Formula generateFormulaWithString(String input) throws CellNotFoundException, BadSyntaxException {
			Pattern functionPattern = Pattern.compile("=([A-Z]+)\\(([A-Z0-9,]+)\\)");
			Matcher functionMatcher = functionPattern.matcher(input);
			
			if (functionMatcher.matches()) {
				List<Cell> cells = Arrays.stream(functionMatcher.group(2).split(","))
						.map(c -> grid.getCell(c))
						.collect(Collectors.toList());
				
				if (cells.contains(null))
					throw new CellNotFoundException();
				
				cells = cells.stream().filter(Objects::nonNull).collect(Collectors.toList());
				
				switch (functionMatcher.group(1)) {
					case "SUM":
					case "SOMME":
						return new Sum(cells);
					case "AVERAGE":
					case "MOYENNE":
						return new Average(cells);
				}
			} else {
				Pattern binaryOperationPattern = Pattern.compile("=([A-Z]+[0-9]+)([+\\-*/])([A-Z]+[0-9]+)");
				Matcher binaryOperationMatcher = binaryOperationPattern.matcher(input);
				
				if (!binaryOperationMatcher.matches())
					throw new BadSyntaxException();
				
				Cell leftCell = grid.getCell(binaryOperationMatcher.group(1));
				Cell rightCell = grid.getCell(binaryOperationMatcher.group(3));
				
				if (leftCell == null || rightCell == null)
					throw new CellNotFoundException();
				
				switch (binaryOperationMatcher.group(2)) {
					case "+":
						return new Addition(leftCell, rightCell);
					case "-":
						return new Subtraction(leftCell, rightCell);
					case "*":
						return new Multiplication(leftCell, rightCell);
					case "/":
						return new Division(leftCell, rightCell);
				}
			}
			
			return null;
		}
	}
	// Fin de la inner class MyTableModel
	
	// Exécution de l'interface graphique a partir d'un terminal.
	public static void main(String[] args) throws ClassNotFoundException, IOException {
		try {
			grid = Grid.load();
		} catch (IOException | ClassNotFoundException e) {
			grid = new Grid();
		}
		// a charger comme modele de donnees.
		TablooProto tableur = new TablooProto(grid);
		
		// Creation de l'application et lancement
		// Fourni: ne rien changer.
		JFrame frame = new JFrame("TABLO");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		tableur.setOpaque(true);
		frame.setContentPane(tableur);
		frame.pack();
		frame.setVisible(true);
		
		// Sauvegarde de la grille lors de la fermeture de l'application
		frame.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent we) {
				try {
					grid.save();
				} catch (IOException e) {
					System.out.println(e.getMessage());
				}
				System.exit(0);
			}
		});
	}
}