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) { 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.getCell(this.getColumnName(col), row + 1).containFormula() ? grid.getFormulaAsString(this.getColumnName(col), row + 1) + "=" + grid.getValue(this.getColumnName(col), row + 1) : grid.getValue(this.getColumnName(col), row + 1); return grid.getFormulaAsString(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 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); } }); } }