package main.java.view; import java.util.Optional; import org.controlsfx.dialog.Wizard; import org.controlsfx.dialog.Wizard.WizardPane; import main.java.Global; import main.java.MainApp; import main.java.model.*; import javafx.fxml.FXML; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.TextInputDialog; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; /** * Main handler class for GUI elements in Matrixonator. * * @author Isaac Jordan */ public class MatrixOverviewController { @FXML private TableView<Matrix> matrixTable; @FXML private TableColumn<Matrix, String> nameColumn; @FXML private TableColumn<Matrix, Integer> numRowsColumn; @FXML private TableColumn<Matrix, Integer> numColsColumn; @FXML private Label nameLabel; @FXML private Label numRowsLabel; @FXML private Label numColsLabel; @FXML private Label createdDateLabel; // Reference to the main application. @SuppressWarnings("unused") private MainApp mainApp; /** * The constructor. The constructor is called before the initialise() method. */ public MatrixOverviewController() {} /** * Initialises the controller class. This method is automatically called after the fxml file has * been loaded. */ @FXML private void initialize() { // Initialise the person table with the two columns. nameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); // Not typesafe numRowsColumn.setCellValueFactory(new PropertyValueFactory<Matrix, Integer>("numRows")); numColsColumn.setCellValueFactory(new PropertyValueFactory<Matrix, Integer>("numCols")); // Clear matrix details. showMatrixDetails(null); } /** * Handler when MatrixOverview has been brought back into focus */ private void updateMatrixList() { matrixTable.setItems(Global.getMatrices()); } /** * Is called by the main application to give a reference back to itself. * * @param mainApp */ public void setMainApp(MainApp mainApp) { this.mainApp = mainApp; // Add observable list data to the table matrixTable.setItems(Global.getMatrices()); // Listen for selection changes and show the person details when // changed. matrixTable.getSelectionModel().selectedItemProperty() .addListener((observable, oldValue, newValue) -> showMatrixDetails(newValue)); } /** * This method is called when the listener detects that a matrix was selected on the left hand * table. It updates the labels on the right-hand side of the GUI. * * @param matrix */ private void showMatrixDetails(Matrix matrix) { if (matrix != null) { nameLabel.setText(matrix.getName()); numRowsLabel.setText(Integer.toString(matrix.getNumRows())); numColsLabel.setText(Integer.toString(matrix.getNumCols())); createdDateLabel.setText(matrix.getCreatedDate().toString()); } else { nameLabel.setText(""); numRowsLabel.setText(""); numColsLabel.setText(""); createdDateLabel.setText(""); } } /** * Called when the user clicks on the new button. This method will guide the user through the * wizard that asks them to enter the matrix details. */ @FXML private void handleNewMatrix() { // define pages to show Wizard wizard = new Wizard(); wizard.setTitle("Create New Matrix"); // --- page 1 int row = 0; GridPane page1Grid = new GridPane(); page1Grid.setVgap(10); page1Grid.setHgap(10); page1Grid.add(new Label("Name:"), 0, row); TextField txFirstName = createTextField("name", 80); page1Grid.add(txFirstName, 1, row++); page1Grid.add(new Label("Number of rows:"), 0, row); TextField txNumRows = createTextField("numRows", 80); page1Grid.add(txNumRows, 1, row++); page1Grid.add(new Label("Number of columns:"), 0, row); TextField txNumCols = createTextField("numCols", 80); page1Grid.add(txNumCols, 1, row); WizardPane page1 = new WizardPane(); page1.setHeaderText("Please Enter Matrix Details"); page1.setContent(page1Grid); TextField[] userData = new TextField[3]; userData[0] = txFirstName; userData[1] = txNumRows; userData[2] = txNumCols; page1.setUserData(userData); // --- page 2 final WizardPane page2 = new WizardPane() { @Override public void onEnteringPage(Wizard wizard) { int numRows = 0; int numCols = 0; try { numRows = Integer.parseInt((String) wizard.getSettings().get("numRows")); numCols = Integer.parseInt((String) wizard.getSettings().get("numCols")); } catch (NumberFormatException e) { MatrixAlerts.invalidRowColAlert(); return; } GridPane page2Grid = new GridPane(); for (int i = 0; i < numRows; i++) { for (int j = 0; j < numCols; j++) { // Naming of text fields needs to be improved TextField tx = createTextField("" + i + " " + j, 20); tx.setPromptText("Enter value"); page2Grid.add(tx, j, i); } } page2Grid.setHgap(5); page2Grid.setVgap(10); setContent(page2Grid); } }; page2.setHeaderText("Creating Matrix"); // --- page 3 WizardPane page3 = new WizardPane() { @Override public void onEnteringPage(Wizard wizard) { String name = (String) wizard.getSettings().get("name"); int numRows = 0; int numCols = 0; try { numRows = Integer.parseInt((String) wizard.getSettings().get("numRows")); numCols = Integer.parseInt((String) wizard.getSettings().get("numCols")); } catch (NumberFormatException e) { MatrixAlerts.invalidRowColAlert(); return; } double[][] data = new double[numRows][numCols]; double currentData; for (int i = 0; i < numRows; i++) { for (int j = 0; j < numCols; j++) { String raw = (String) wizard.getSettings().get("" + i + " " + j); try { currentData = Double.valueOf(raw); } catch (NumberFormatException e) { currentData = 0; } data[i][j] = currentData; } } Global.addMatrix(new Matrix(name, data, null)); updateMatrixList(); } }; page3.setHeaderText("Goodbye!"); page3.setContentText("Matrix created."); Wizard.Flow branchingFlow = new Wizard.Flow() { public Optional<WizardPane> advance(WizardPane currentPage) { return Optional.of(getNext(currentPage)); } public boolean canAdvance(WizardPane currentPage) { if (currentPage != page3) { return true; } return false; } private WizardPane getNext(WizardPane currentPage) { if ( currentPage == null ) { return page1; } else if ( currentPage == page1) { // Input validation for page 1 if (page1.getUserData() != null) { System.out.println(((TextField[]) page1.getUserData())[0].getText()); try { Integer.parseInt(((TextField[]) page1.getUserData())[1].getText()); Integer.parseInt(((TextField[]) page1.getUserData())[2].getText()); } catch (NumberFormatException e) { MatrixAlerts.invalidRowColAlert(); return page1; } return page2; } return page2; } else { return page3; } } }; wizard.setFlow(branchingFlow); // show wizard and wait for response wizard.showAndWait(); } /** * Method is called when the "Edit" button is pressed. If a valid matrix is selected in the table * on the left, then it is deleted from the matrixTable. */ @FXML private void handleEditMatrix() { int selectedIndex = matrixTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { // User has selected a valid matrix on the left. TextInputDialog dialog = new TextInputDialog(matrixTable.getSelectionModel().getSelectedItem().getName()); dialog.setTitle("Editing Matrix"); dialog.setHeaderText("Leave blank, or click cancel for no changes."); dialog.setContentText("Please enter new name:"); Optional<String> result = dialog.showAndWait(); result.ifPresent(name -> matrixTable.getSelectionModel().getSelectedItem().setName(name)); updateMatrixList(); } else { // Nothing is selected MatrixAlerts.noSelectionAlert(); } } /** * Method is called when the "Delete" button is pressed. If a valid matrix is selected in the * table on the left, then it is deleted from the matrixTable. */ @FXML public void handleDeleteMatrix() { int selectedIndex = matrixTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { Matrix m = matrixTable.getSelectionModel().getSelectedItem(); // Prompt user if they want it removed completely boolean shallDelete = MatrixAlerts.handleDeleteRequest(m.getName()); if (shallDelete) { MatrixIO.deleteFile(m.getName() + ".matrix"); MatrixAlerts.showDelComplete(m.getName()); } else { MatrixAlerts.showRemComplete(m.getName()); } // TODO Remove misleading info if there is no such file existing matrixTable.getItems().remove(selectedIndex); // TODO Remove from Global as well (TEST) Global.removeMatrix(m); updateMatrixList(); } else { // Nothing is selected MatrixAlerts.noSelectionAlert(); } } @FXML private void handleShowData() { int selectedIndex = matrixTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { MatrixAlerts.dataAlert(matrixTable.getSelectionModel().getSelectedItem().normalise(), null); } else { // Nothing is selected MatrixAlerts.noSelectionAlert(); } } @FXML private void handleCalculateRREF() { int selectedIndex = matrixTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { MatrixAlerts.dataAlert( matrixTable.getSelectionModel().getSelectedItem().reducedEchelonForm(), null); } else { // Nothing is selected MatrixAlerts.noSelectionAlert(); } } @FXML private void handleCalculateDeterminant() { int selectedIndex = matrixTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { Matrix m = matrixTable.getSelectionModel().getSelectedItem(); if (m.getNumCols() != m.getNumRows()) { MatrixAlerts.showSquareWarning(); } else { Alert alert = new Alert(AlertType.INFORMATION); alert.setTitle("Determinant of " + m.getName()); alert.setHeaderText("Value displayed below."); alert.setContentText(String.valueOf(m.determinant())); alert.showAndWait(); } } else { // Nothing is selected MatrixAlerts.noSelectionAlert(); } } @FXML private void handleCalculateTrace() { int selectedIndex = matrixTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { Matrix m = matrixTable.getSelectionModel().getSelectedItem(); if (m.getNumCols() != m.getNumRows()) { MatrixAlerts.showSquareWarning(); } else { Alert alert = new Alert(AlertType.INFORMATION); alert.setTitle("Trace of " + m.getName()); alert.setHeaderText("Value displayed below."); alert.setContentText(String.valueOf(m.trace())); alert.showAndWait(); } } else { MatrixAlerts.noSelectionAlert(); } } @FXML private void handleCalculateInverse() { int selectedIndex = matrixTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { Matrix m = matrixTable.getSelectionModel().getSelectedItem(); if (m.getNumCols() != m.getNumRows()) { MatrixAlerts.showSquareWarning(); } else { MatrixAlerts.dataAlert(m.inverse(), m.getName()); } } else { // Nothing is selected MatrixAlerts.noSelectionAlert(); } } @FXML private void handleCalculateCofactor() { int selectedIndex = matrixTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { Matrix m = matrixTable.getSelectionModel().getSelectedItem(); if (m.getNumCols() != m.getNumRows()) { MatrixAlerts.showSquareWarning(); } else { MatrixAlerts.dataAlert(m.cofactorMatrix(), m.getName()); } } else { // Nothing is selected MatrixAlerts.noSelectionAlert(); } } @FXML // Handles when a save operation is requested. DOES NOT SAVE THE DEFAULT MATRICES private void handleSaveMatrix() { int selectedIndex = matrixTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { // Do the save command Matrix data = matrixTable.getSelectionModel().getSelectedItem(); // TODO Add proper message if save fails. (Which it should not) boolean result = MatrixIO.save(data); if (result) { MatrixAlerts.onSave(); } else { System.out.println("Matrix file was not saved correctly"); } } else { // Nothing is selected MatrixAlerts.noSelectionAlert(); } } /** * A utility method for creating TextFields with specified id and width. * * @param id * @param width * @return */ private TextField createTextField(String id, int width) { TextField textField = new TextField(); textField.setId(id); textField.setPrefWidth(width); GridPane.setHgrow(textField, Priority.ALWAYS); return textField; } }