package no.ntnu.idatt1002.demo.controller; import java.time.LocalDate; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.chart.PieChart; import javafx.scene.chart.PieChart.Data; import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.text.Text; import javafx.stage.Modality; import javafx.stage.Stage; import no.ntnu.idatt1002.demo.data.Budget.BudgetItem; import no.ntnu.idatt1002.demo.data.Budget.FileHandlingBudget; import no.ntnu.idatt1002.demo.data.Budget.GeneralBudget; import no.ntnu.idatt1002.demo.data.Economics.ExpenseCategory; import java.io.IOException; import java.util.Optional; import no.ntnu.idatt1002.demo.data.Economics.IncomeCategory; /** * Controller for budget scene in the application. This controller manages all actions that relates to the budget tableview (add, edit and delete), the switching * of scenes from the budget scene to another scene, and the saving of the table, whenever the user switches to another scene. * * @author Anders Emil Bergan * @since 24.3.2023 */ public class BudgetController implements FinanceController { private GeneralBudget general; @FXML private Button addBtn; @FXML private Button editBtn; @FXML private Button returnBtn; @FXML private TableColumn<BudgetItem, Double> amountCol; @FXML private TableView<BudgetItem> budgetTableView = new TableView<>(); @FXML private TableColumn<BudgetItem, ExpenseCategory> categoryCol; @FXML private TableColumn<BudgetItem, String> descriptionCol; @FXML private Text sum; @FXML private DatePicker date; @FXML private TableColumn<BudgetItem, Double> percentageColumn; @FXML private ObservableList<BudgetItem> budgetList; /** * Initializes the budget register, the observable budget list and the tableview, along with the values of the dropbox used for filtering the tableview. * @throws IOException If there occurs any exception when loading the budget register from a file. */ @FXML public void initialize() throws IOException { //TODO if budget is not empty - disable //Initialize table columns categoryCol.setCellValueFactory(new PropertyValueFactory<BudgetItem, ExpenseCategory>("budgetCategory")); amountCol.setCellValueFactory(new PropertyValueFactory<BudgetItem, Double>("budgetAmount")); descriptionCol.setCellValueFactory(new PropertyValueFactory<BudgetItem, String>("budgetDescription")); //Initialize registers and tableview general = loadBudgetDataFromFile("Budget"); budgetList = FXCollections.observableArrayList(general.getBudgetItems()); budgetTableView.setItems(budgetList); formatDatePicker(); //createBudgetPieChart(); //Initialize sum field under the tableview //sum.setText(String.valueOf(general.totalSum())); } private ObservableList<PieChart.Data> createBudgetPieChart() { //TODO DOESNT WORK IF BUDGETITEM HAS NO BUDGET return FXCollections.observableArrayList( new Data("Food", general.getBudgetItem(ExpenseCategory.FOOD).getBudgetAmount()), new Data("Books", general.getBudgetItem(ExpenseCategory.BOOKS).getBudgetAmount()), new Data("Clothes", general.getBudgetItem(ExpenseCategory.CLOTHES).getBudgetAmount()), new Data("Other", general.getBudgetItem(ExpenseCategory.OTHER).getBudgetAmount()) ); } /** * Method for disabling the date picker, yet having its opacity at max. */ private void formatDatePicker() { date.setValue(LocalDate.now()); date.setDisable(true); date.setStyle("-fx-opacity: 1"); date.getEditor().setStyle("-fx-opacity: 1"); } @Override public void handleAddBtn(ActionEvent event) { handleEditBtn(event); } /** * Adds or edits a budget item, depending on what mode the DialogMode enum is at. The method brings up a dialog box popup in which the user can fill and choose * values that the budget item will have. Open exiting the popup, the changes the saved to the tableview. * @param event A button click on either the add or delete button. */ @Override public void handleEditBtn(ActionEvent event) { BudgetItem item = null; String dialogTitle = ""; DialogMode dialogMode; FXMLLoader loader = new FXMLLoader(SceneController.class.getResource("/view/AddBudget.fxml")); Dialog<BudgetItem> dialog = new Dialog<>(); dialog.initModality(Modality.APPLICATION_MODAL); //Try to load the FXML file onto another dialogPane try{ dialog.getDialogPane().setContent(loader.load()); } catch(IOException e) { e.printStackTrace(); } //Loads the controller for the dialog box that appears whenever one adds or edits a budget item AddBudgetController budgetController = loader.getController(); //Sets the title of the dialog box if(event.getSource().equals(addBtn)){ dialogMode = DialogMode.ADD; dialogTitle = "New Budget"; } else if (event.getSource().equals(editBtn) && budgetTableView.getSelectionModel().getSelectedItem() != null) { dialogMode = DialogMode.EDIT; dialogTitle = "Edit expense"; //Gets the selected item from the table item = budgetTableView.getSelectionModel().getSelectedItem(); //Binds the selected item to another item which is defined in the budgetcontroller budgetController.setBudget(item); } else { return; } dialog.setTitle(dialogTitle); // Show the Dialog and wait for the user to close it dialog.showAndWait(); //Adds the new item to the register item = budgetController.getNewBudgetItem(); if(item != null && dialogMode == DialogMode.ADD){ try { general.addToBudgetBudgetItem(item); } catch(IllegalArgumentException e) { showIllegalBudgetItemDialog(); } } //Updates the tableview using the register refreshTableView(); } /** * Deletes an entry from the tableview, if an entry has been selected. The method brings up a popup window, asking for confirmation for deleting the entry. * @param event A button click on the delete button */ @FXML public void handleDeleteBtn(ActionEvent event) { //Gets the selected item from the tableview BudgetItem item = budgetTableView.getSelectionModel().getSelectedItem(); //Exits the method if nothing is selected if (item == null) { return; } //Brings up a confirmation popup Optional<ButtonType> isConfirmed = showConfirmationDialog(); if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) { general.deleteItemFromBudget(item.getBudgetCategory()); refreshTableView(); } } /** * Method for synching the register with the tableview. The observable list to which the tableview is set, is being refilled with all the entries * in the register, keeping it updated with new changes. */ public void refreshTableView(){ this.budgetList.setAll(general.getBudgetItems()); //Refreshing the sum of the amounts of the budget //this.sum.setText(String.valueOf(general.totalSum())); } /** * Returns an optional, which is a popup alert box, asking for confirmation for deleting an entry. * @return An alert box, asking for confirmation for deleting the selected entry of the tableview. */ @Override public Optional<ButtonType> showConfirmationDialog() { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle("Confirm Delete"); alert.setHeaderText("Delete Confirmation"); alert.setContentText("Are you sure you would like to delete the selected entry?"); return alert.showAndWait(); } /** * Saves the changes made to the tableview by writing the information to a file. * * @throws IOException If an error occurs while writing to the file. */ @Override public void saveDataToFile() throws IOException { FileHandlingBudget fileHandlingBudget = new FileHandlingBudget(); fileHandlingBudget.writeGeneralBudgetToFile("Budget", general); } /** * Returns an optional, which is a popup alert box, informing that either the budget amount has * been exceeded or that the same category has been entered twice in the budget tableview. */ private void showIllegalBudgetItemDialog() { Alert alert = new Alert(AlertType.ERROR); alert.setTitle("Budget amount exceeded/Category already exists"); alert.setHeaderText("Your budget exceeds the max limit OR a budget item of the same category already exists in the table"); alert.setContentText("The total budget sum must be below " + general.getMaxAmount() + " OR Each category can only have one entry in the budget table"); alert.showAndWait(); } /** * Method that either reads data from a file with which it fills a budget register, if this is an old budget, or instantiates a budget register if this is a new budget. * @param fileName The name of the file that is being read from. * @return An object of type GeneralBudget. * @throws IOException If an error occurs while reading from the file. */ public GeneralBudget loadBudgetDataFromFile(String fileName) throws IOException { FileHandlingBudget fileHandlingBudget = new FileHandlingBudget(); //Instantiate new budget if (fileHandlingBudget.isEmpty(fileName)) { general = new GeneralBudget(31, 1000); } else { //Load previous budget try { general = fileHandlingBudget.readGeneralBudgetFromFile(fileName); } catch (IOException e) { e.printStackTrace(); } } return general; } /** * Switches scenes back to main menu, by loading a new FXML file and setting the scene to this location. * @param event A button click on the return to main menu button * @throws IOException If an error occurs with loading any of the FXML files. */ @FXML public void returnToMainMenu(ActionEvent event) throws IOException { //Always saving the data when switching scenes saveDataToFile(); FXMLLoader loader = new FXMLLoader(); loader.setLocation(getClass().getResource("/view/MainMenuNew.fxml")); Parent root = loader.load(); Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow(); Scene scene = new Scene(root); stage.setScene(scene); stage.show(); } }