package no.ntnu.idatt1002.demo.controller; import java.awt.event.ActionEvent; 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.Alert; import javafx.scene.control.Alert.AlertType; 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.ProgressBar; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; 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.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; public class IncomeExpenseController implements 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 ProgressBar budgetProgress; @FXML private DatePicker date; @FXML private Label daysLeftLbl; @FXML private ComboBox<?> filter; @FXML private Button returnBtn; @FXML private Label title; @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; FileHandling fileHandling; @FXML public void initialize() throws IOException { fileHandling = new FileHandling(); //Initialize columns setColumns(); //Initialize registers and tableview incomeRegister = loadIncomeDataFromFile("Income"); income = FXCollections.observableArrayList(incomeRegister.getItems()); incomeTableView.setItems(income); expenseRegister = loadExpenseDataFromFile("Expense"); expenses = FXCollections.observableArrayList(expenseRegister.getItems()); expenseTableView.setItems(expenses); //Setting pie chart values to correspond with the registers incomePieChart.setLegendSide(Side.RIGHT); expensePieChart.setLegendSide(Side.RIGHT); expensePieChart.setLabelLineLength(10); refreshPieCharts(); refreshProgress(); 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<Income, String>("date")); inAmountCol.setCellValueFactory(new PropertyValueFactory<Income, Double>("amount")); inCategoryCol.setCellValueFactory(new PropertyValueFactory<Income, IncomeCategory>("category")); inDescriptionCol.setCellValueFactory(new PropertyValueFactory<Income, String>("description")); inRecurringCol.setCellValueFactory(new PropertyValueFactory<Income, Boolean>("recurring")); expDateCol.setCellValueFactory(new PropertyValueFactory<Expense, String>("date")); expAmountCol.setCellValueFactory(new PropertyValueFactory<Expense, Double>("amount")); expCategoryCol.setCellValueFactory(new PropertyValueFactory<Expense, ExpenseCategory>("category")); expDescriptionCol.setCellValueFactory(new PropertyValueFactory<Expense, String>("description")); expRecurringCol.setCellValueFactory(new PropertyValueFactory<Expense, Boolean>("recurring")); } private 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. */ private 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(); refreshProgress(); } } /** * 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) { System.out.println(event.getSource()); 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; System.out.println(chosenIncome); System.out.println(chosenExpense); 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(); refreshProgress(); } /** * 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())); } private void refreshPieCharts() { this.incomePieChart.setData(createIncomePieChart()); this.expensePieChart.setData(createExpensePieChart()); } private void refreshProgress() { budgetProgress.setProgress(expenseRegister.getTotalSum()/incomeRegister.getTotalSum()); } @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 = null; 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) { e.printStackTrace(); } // 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 = null; String dialogTitle = "Add 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) { e.printStackTrace(); } // 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) { e.printStackTrace(); } // 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) { e.printStackTrace(); } // 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) { Optional<ButtonType> isConfirmed = showConfirmationDialog(); if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) { incomeRegister.removeItem(chosenIncome); } this.incomePieChart.setData(createIncomePieChart()); } @FXML private void handleDeleteExpense(Expense chosenExpense) { Optional<ButtonType> isConfirmed = showConfirmationDialog(); if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) { expenseRegister.removeItem(chosenExpense); } this.expensePieChart.setData(createExpensePieChart()); } /** * 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(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 { fileHandling.writeItemRegisterToFile(incomeRegister, "Income"); fileHandling.writeItemRegisterToFile(expenseRegister, "Expense"); } /** * Method that either reads data from a file with which it fills an income register, if older changes exist, or instantiates an income register if the file is empty. * @param fileName The name of the file that is being read from. * @return An object of type IncomeRegister. * @throws IOException If an error occurs while reading from the file. */ public IncomeRegister loadIncomeDataFromFile(String fileName) throws IOException { //Instantiate new incomeRegister if (fileHandling.isEmpty(fileName)) { incomeRegister = new IncomeRegister(); } else { //Load previous income register try { incomeRegister = fileHandling.readIncomeRegisterFromFile(fileName); } catch (IOException e) { e.printStackTrace(); } } return incomeRegister; } /** * Method that either reads data from a file with which it fills an expense register, if older changes exist, or instantiates an expense register if the file is empty. * @param fileName The name of the file that is being read from. * @return An object of type IncomeRegister. * @throws IOException If an error occurs while reading from the file. */ public ExpenseRegister loadExpenseDataFromFile(String fileName) throws IOException { //ItemRegister<T extends Item> if (fileHandling.isEmpty(fileName)) { expenseRegister = new ExpenseRegister(); } else { try { expenseRegister = fileHandling.readExpenseRegisterFromFile(fileName); } catch (IOException e) { e.printStackTrace(); } } return expenseRegister; } /** * 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)) { generalBudget = new GeneralBudget(31, 1000); } else { //Load previous budget try { generalBudget = fileHandlingBudget.readGeneralBudgetFromFile(fileName); } catch (IOException e) { e.printStackTrace(); } } return generalBudget; } /** * 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(javafx.event.ActionEvent event) throws IOException { 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(); } }