package no.ntnu.idatt1002.demo.controller; import java.io.IOException; import java.time.LocalDate; import java.util.Optional; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.geometry.Side; 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.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.ComboBox; import javafx.scene.control.DatePicker; import javafx.scene.control.Dialog; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javafx.stage.Modality; import javafx.stage.Stage; import no.ntnu.idatt1002.demo.data.Budget.FileHandlingBudget; import no.ntnu.idatt1002.demo.data.Budget.GeneralBudget; import no.ntnu.idatt1002.demo.data.Economics.Expense; import no.ntnu.idatt1002.demo.data.Economics.ExpenseCategory; import no.ntnu.idatt1002.demo.data.Economics.ExpenseRegister; import no.ntnu.idatt1002.demo.data.Economics.FileHandling; import no.ntnu.idatt1002.demo.data.Economics.Income; import no.ntnu.idatt1002.demo.data.Economics.IncomeCategory; import no.ntnu.idatt1002.demo.data.Economics.IncomeRegister; /** * Class for representing an overview of the income and expenses of the users budget. * Displays information in tables and pie charts. It is possible to add, edit and delete * income and expenses. The difference of the expense and income sum contribute to * the monthly budget progress. * @author Harry Linrui Xu * @since 30.03.2023 */ public class IncomeExpenseController extends FinanceController { private final static String sumText = "Sum: "; @FXML private TableColumn<Expense, Double> expAmountCol; @FXML private TableColumn<Expense, ExpenseCategory> expCategoryCol; @FXML private TableColumn<Expense, String> expDateCol; @FXML private TableColumn<Expense, String> expDescriptionCol; @FXML private TableColumn<Expense, Boolean> expRecurringCol; @FXML private TableColumn<Income, Double> inAmountCol; @FXML private TableColumn<Income, IncomeCategory> inCategoryCol; @FXML private TableColumn<Income, String> inDateCol; @FXML private TableColumn<Income, String> inDescriptionCol; @FXML private TableColumn<Income, Boolean> inRecurringCol; @FXML private TableView<Expense> expenseTableView; @FXML private TableView<Income> incomeTableView; @FXML private Label inSum; @FXML private Label expSum; @FXML private MenuItem addExpense; @FXML private MenuItem addIncome; @FXML private DatePicker date; @FXML private Label daysLeftLbl; @FXML private ComboBox<?> filter; @FXML private Button returnBtn; @FXML private Button continueBtn; @FXML private Label title; @FXML private Label errorMsg; @FXML private MenuItem editIncomeMenu; @FXML private MenuItem deleteIncomeMenu; @FXML private MenuItem editExpenseMenu; @FXML private MenuItem deleteExpenseMenu; private IncomeRegister incomeRegister; private ExpenseRegister expenseRegister; private GeneralBudget generalBudget; private ObservableList<Income> income; private ObservableList<Expense> expenses; @FXML private PieChart expensePieChart; @FXML private PieChart incomePieChart; @FXML public void initialize() { System.out.println("Start of initialize"); //Initialize columns setColumns(); //Initialize registers and tableview incomeRegister = loadIncomeDataFromFile("Income"); income = FXCollections.observableArrayList(incomeRegister.getItems()); incomeTableView.setItems(income); System.out.println("After income rgister "); expenseRegister = loadExpenseDataFromFile("Expense"); expenses = FXCollections.observableArrayList(expenseRegister.getItems()); expenseTableView.setItems(expenses); System.out.println("Afte expense register"); //Setting pie chart values to correspond with the registers incomePieChart.setLegendSide(Side.RIGHT); incomePieChart.setLabelLineLength(10); expensePieChart.setLegendSide(Side.RIGHT); expensePieChart.setLabelLineLength(10); refreshPieChart(); formatDatePicker(); //Initialize sum field under the tableview inSum.setText(sumText + String.valueOf(incomeRegister.getTotalSum())); expSum.setText(sumText + String.valueOf(expenseRegister.getTotalSum())); } private void setColumns() { inDateCol.setCellValueFactory(new PropertyValueFactory<>("date")); inAmountCol.setCellValueFactory(new PropertyValueFactory<>("amount")); inCategoryCol.setCellValueFactory(new PropertyValueFactory<>("category")); inDescriptionCol.setCellValueFactory(new PropertyValueFactory<>("description")); inRecurringCol.setCellValueFactory(new PropertyValueFactory<>("recurring")); expDateCol.setCellValueFactory(new PropertyValueFactory<>("date")); expAmountCol.setCellValueFactory(new PropertyValueFactory<>("amount")); expCategoryCol.setCellValueFactory(new PropertyValueFactory<>("category")); expDescriptionCol.setCellValueFactory(new PropertyValueFactory<>("description")); expRecurringCol.setCellValueFactory(new PropertyValueFactory<>("recurring")); } public ObservableList<PieChart.Data> createExpensePieChart() { return FXCollections.observableArrayList( new Data("Food", expenseRegister.getExpenseByCategory(ExpenseCategory.FOOD).getTotalSum()), new Data("Books", expenseRegister.getExpenseByCategory(ExpenseCategory.BOOKS).getTotalSum()), new Data("Clothes", expenseRegister.getExpenseByCategory(ExpenseCategory.CLOTHES).getTotalSum()), new Data("Other", expenseRegister.getExpenseByCategory(ExpenseCategory.OTHER).getTotalSum()) ); } private ObservableList<PieChart.Data> createIncomePieChart() { return FXCollections.observableArrayList( new Data("Gift", incomeRegister.getIncomeByCategory(IncomeCategory.GIFT).getTotalSum()), new Data("Salary", incomeRegister.getIncomeByCategory(IncomeCategory.SALARY).getTotalSum()), new Data("Loans", incomeRegister.getIncomeByCategory(IncomeCategory.STUDENT_LOAN).getTotalSum()) ); } /** * Method for disabling the date picker, yet having its opacity at max. */ @Override public void formatDatePicker() { date.setValue(LocalDate.now()); date.setDisable(true); date.setStyle("-fx-opacity: 1"); date.getEditor().setStyle("-fx-opacity: 1"); } /** * Method for handling the adding of new entries in the tableview. * @param event A button click on the add button. */ @Override public void handleAddBtn(javafx.event.ActionEvent event) { int sizeBf = (expenseRegister.getItems().size() + incomeRegister.getItems().size()); if (event.getSource() == addIncome) { handleAddIncome(); } else if (event.getSource() == addExpense){ handleAddExpense(); } int sizeAf = (expenseRegister.getItems().size() + incomeRegister.getItems().size()); if (sizeAf != sizeBf) { refreshTableView(); } } /** * Method for handling the editing of a chosen entry in the tableview. * * @param event A button click on the edit button. */ @Override public void handleEditBtn(javafx.event.ActionEvent event) { Income chosenIncome = incomeTableView.getSelectionModel().getSelectedItem(); Expense chosenExpense = expenseTableView.getSelectionModel().getSelectedItem(); boolean isEditIncome = event.getSource() == editIncomeMenu; boolean isDeleteIncome = event.getSource() == deleteIncomeMenu; boolean isEditExpense = event.getSource() == editExpenseMenu; boolean isDeleteExpense = event.getSource() == deleteExpenseMenu; if (isEditIncome) { handleEditIncome(chosenIncome); } else if (isDeleteIncome) { handleDeleteIncome(chosenIncome); } else if (isEditExpense) { handleEditExpense(chosenExpense); } else if (isDeleteExpense) { handleDeleteExpense(chosenExpense); } else return; //Updates the tableview and pie chart 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 */ @Override public void handleDeleteBtn(javafx.event.ActionEvent event) { handleEditBtn(event); } /** * 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. */ @Override public void refreshTableView() { this.income.setAll(incomeRegister.getItems()); this.inSum.setText("Sum: " + String.valueOf(incomeRegister.getTotalSum())); this.expenses.setAll(expenseRegister.getItems()); this.expSum.setText("Sum: " + String.valueOf(expenseRegister.getTotalSum())); } @Override public void refreshPieChart() { this.incomePieChart.setData(createIncomePieChart()); this.expensePieChart.setData(createExpensePieChart()); } @FXML private void handleAddIncome() { //Instantiate FXML loader and loads the popup for adding income FXMLLoader loader = new FXMLLoader(); loader.setLocation(getClass().getResource("/view/AddIncome.fxml")); Income newIncome; String dialogTitle = "Add income"; // Load the FXML file for your dialog box Dialog<Income> dialog = new Dialog<>(); dialog.initModality(Modality.APPLICATION_MODAL); try { // Set the Dialog's content to the loaded FXML file dialog.getDialogPane().setContent(loader.load()); } catch (IOException e) { showErrorDialogBox("Loading", "Error in loading dialog box", "Could not load" + "the AddIncome window"); } // Get the controller for the loaded FXML file AddIncomeController dialogController = loader.getController(); //Sets the title of the dialog box dialog.setTitle(dialogTitle); // Show the Dialog and wait for the user to close it dialog.showAndWait(); //Get the newly created income from the dialog pane newIncome = dialogController.getNewIncome(); //Adds the new item to the register if (newIncome != null) { incomeRegister.addItem(newIncome); incomePieChart.setData(createIncomePieChart()); } } @FXML private void handleAddExpense() { //Instantiate FXML loader and loads the popup for adding expense FXMLLoader loader = new FXMLLoader(); loader.setLocation(getClass().getResource("/view/AddExpense.fxml")); Expense newExpense; String dialogTitle = "Add expense"; Dialog<Expense> dialog = new Dialog<>(); dialog.initModality(Modality.APPLICATION_MODAL); try { // Set the Dialog's content to the loaded FXML file dialog.getDialogPane().setContent(loader.load()); } catch (IOException e) { showErrorDialogBox("Loading", "Error in loading dialog box", "Could not load" + "the AddExpense window"); } // Get the controller for the loaded FXML file AddExpenseController dialogController = loader.getController(); dialog.setTitle(dialogTitle); // Show the Dialog and wait for the user to close it dialog.showAndWait(); //Get the newly created expense from the dialog pane newExpense = dialogController.getNewExpense(); //Adds the new item to the register if (newExpense != null) { expenseRegister.addItem(newExpense); expensePieChart.setData(createExpensePieChart()); } } @FXML private void handleEditIncome(Income chosenIncome) { //Create copy of chosenIncome before changes //Instantiate FXML loader and loads the popup for adding income FXMLLoader loader = new FXMLLoader(); loader.setLocation(getClass().getResource("/view/AddIncome.fxml")); String dialogTitle = "Edit income"; // Load the FXML file for your dialog box Dialog<Income> dialog = new Dialog<>(); dialog.initModality(Modality.APPLICATION_MODAL); try { // Set the Dialog's content to the loaded FXML file dialog.getDialogPane().setContent(loader.load()); } catch (IOException e) { showErrorDialogBox("Loading", "Error in loading dialog box", "Could not load" + "the EditIncome window"); } // Get the controller for the loaded FXML file AddIncomeController dialogController = loader.getController(); //Binds the selected item to another item which is defined in the ItemController dialogController.setIncome(chosenIncome); dialog.setTitle(dialogTitle); // Show the Dialog and wait for the user to close it dialog.showAndWait(); incomePieChart.setData(createIncomePieChart()); } @FXML private void handleEditExpense(Expense chosenExpense) { //Instantiate FXML loader and loads the popup for adding expense FXMLLoader loader = new FXMLLoader(); loader.setLocation(getClass().getResource("/view/AddExpense.fxml")); String dialogTitle = "Edit expense"; // Load the FXML file for your dialog box Dialog<Expense> dialog = new Dialog<>(); dialog.initModality(Modality.APPLICATION_MODAL); try { // Set the Dialog's content to the loaded FXML file dialog.getDialogPane().setContent(loader.load()); } catch (IOException e) { showErrorDialogBox("Loading", "Error in loading dialog box", "Could not" + "load the EditExpense window"); } // Get the controller for the loaded FXML file AddExpenseController dialogController = loader.getController(); //Binds the selected item to another item which is defined in the ItemController dialogController.setExpense(chosenExpense); dialog.setTitle(dialogTitle); // Show the Dialog and wait for the user to close it dialog.showAndWait(); this.expensePieChart.setData(createExpensePieChart()); } @FXML private void handleDeleteIncome(Income chosenIncome) { String title = "Confirm Delete" ; String header = "Delete Confirmation"; String content = "Are you sure you would like to delete the selected income?"; Optional<ButtonType> isConfirmed = showConfirmationDialog(title, header, content); if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) { incomeRegister.removeItem(chosenIncome); } this.incomePieChart.setData(createIncomePieChart()); } @FXML private void handleDeleteExpense(Expense chosenExpense) { String title = "Confirm Delete" ; String header = "Delete Confirmation"; String content = "Are you sure you would like to delete the selected expense?"; Optional<ButtonType> isConfirmed = showConfirmationDialog(title, header, content); if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) { expenseRegister.removeItem(chosenExpense); } this.expensePieChart.setData(createExpensePieChart()); } /** * 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 { FileHandling.writeItemRegisterToFile(incomeRegister, "Income"); FileHandling.writeItemRegisterToFile(expenseRegister, "Expense"); } public void saveDisposableIncomeToFile() throws IOException { String disposableIncomeAsString = String.valueOf(incomeRegister.getTotalSum() - expenseRegister.getTotalSum()); FileHandlingBudget.writeMaxAmountToFile(FileHandlingBudget.readSelectedBudget(), disposableIncomeAsString); } /** * 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 */ @FXML public void switchScene(javafx.event.ActionEvent event) { try { FXMLLoader loader = new FXMLLoader(); if (event.getSource() == returnBtn) { saveDataToFile(); loader.setLocation(getClass().getResource("/view/MainMenuNew.fxml")); } else if (event.getSource() == continueBtn) { loader.setLocation(getClass().getResource("/view/newBudgetBudgert.fxml")); saveDisposableIncomeToFile(); } else if (event.getSource() == null) { FileHandlingBudget.deleteBudgetDirectory(FileHandlingBudget.readSelectedBudget()); //removeBudgetNameFromArchive FileHandlingBudget.updateSelectedBudget(""); } Parent root = loader.load(); Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow(); Scene scene = new Scene(root); stage.setScene(scene); stage.show(); } catch(IOException ioe) { ioe.printStackTrace(); showErrorDialogBox("Loading error", "Error in loading", "Could not save" + "to file"); } } }