Newer
Older
Harry Linrui XU
committed
package no.ntnu.idatt1002.demo.controller;
import java.io.IOException;
Harry Linrui XU
committed
import java.time.LocalDate;
Harry Linrui XU
committed
import java.util.Optional;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
Harry Linrui XU
committed
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;
Harry Linrui XU
committed
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
Harry Linrui XU
committed
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
Harry Linrui XU
committed
import javafx.scene.control.Dialog;
Harry Linrui XU
committed
import javafx.scene.control.Label;
Harry Linrui XU
committed
import javafx.scene.control.MenuItem;
Harry Linrui XU
committed
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
Harry Linrui XU
committed
import javafx.stage.Modality;
Harry Linrui XU
committed
import javafx.stage.Stage;
Harry Linrui XU
committed
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
*/
Harry Linrui XU
committed
public class IncomeExpenseController implements FinanceController {
private final static String sumText = "Sum: ";
Harry Linrui XU
committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@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;
Harry Linrui XU
committed
@FXML
private Label expSum;
Harry Linrui XU
committed
@FXML
Harry Linrui XU
committed
private MenuItem addExpense;
@FXML
private MenuItem addIncome;
Harry Linrui XU
committed
@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 Label errorMsg;
@FXML
private MenuItem editIncomeMenu;
@FXML
private MenuItem deleteIncomeMenu;
@FXML
private MenuItem editExpenseMenu;
@FXML
private MenuItem deleteExpenseMenu;
Harry Linrui XU
committed
private IncomeRegister incomeRegister;
private ExpenseRegister expenseRegister;
private GeneralBudget generalBudget;
private ObservableList<Income> income;
private ObservableList<Expense> expenses;
Harry Linrui XU
committed
@FXML
private PieChart expensePieChart;
@FXML
private PieChart incomePieChart;
Harry Linrui XU
committed
Harry Linrui XU
committed
FileHandling fileHandling;
@FXML
public void initialize() {
Harry Linrui XU
committed
fileHandling = new FileHandling();
Harry Linrui XU
committed
//Initialize columns
Harry Linrui XU
committed
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);
Harry Linrui XU
committed
//Setting pie chart values to correspond with the registers
incomePieChart.setLegendSide(Side.RIGHT);
Harry Linrui XU
committed
expensePieChart.setLegendSide(Side.RIGHT);
expensePieChart.setLabelLineLength(10);
Harry Linrui XU
committed
Harry Linrui XU
committed
Harry Linrui XU
committed
refreshProgress();
Harry Linrui XU
committed
formatDatePicker();
Harry Linrui XU
committed
//Initialize sum field under the tableview
inSum.setText(sumText + String.valueOf(incomeRegister.getTotalSum()));
expSum.setText(sumText + String.valueOf(expenseRegister.getTotalSum()));
Harry Linrui XU
committed
}
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"));
Harry Linrui XU
committed
}
Harry Linrui XU
committed
Harry Linrui XU
committed
private ObservableList<PieChart.Data> createExpensePieChart() {
Harry Linrui XU
committed
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())
);
Harry Linrui XU
committed
}
Harry Linrui XU
committed
Harry Linrui XU
committed
private ObservableList<PieChart.Data> createIncomePieChart() {
Harry Linrui XU
committed
return FXCollections.observableArrayList(
Harry Linrui XU
committed
new Data("Gift", incomeRegister.getIncomeByCategory(IncomeCategory.GIFT).getTotalSum()),
Harry Linrui XU
committed
new Data("Salary", incomeRegister.getIncomeByCategory(IncomeCategory.SALARY).getTotalSum()),
Harry Linrui XU
committed
new Data("Loans", incomeRegister.getIncomeByCategory(IncomeCategory.STUDENT_LOAN).getTotalSum())
Harry Linrui XU
committed
);
Harry Linrui XU
committed
}
Harry Linrui XU
committed
/**
* 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");
}
Harry Linrui XU
committed
/**
* Method for handling the adding of new entries in the tableview.
* @param event A button click on the add button.
*/
@Override
Harry Linrui XU
committed
public void handleAddBtn(javafx.event.ActionEvent event) {
int sizeBf = (expenseRegister.getItems().size() + incomeRegister.getItems().size());
Harry Linrui XU
committed
if (event.getSource() == addIncome) {
handleAddIncome();
} else if (event.getSource() == addExpense){
handleAddExpense();
}
int sizeAf = (expenseRegister.getItems().size() + incomeRegister.getItems().size());
if (sizeAf != sizeBf) {
refreshTableView();
refreshProgress();
}
Harry Linrui XU
committed
}
/**
* 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());
Harry Linrui XU
committed
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);
Harry Linrui XU
committed
//Updates the tableview and pie chart using the register
refreshTableView();
Harry Linrui XU
committed
refreshProgress();
Harry Linrui XU
committed
}
/**
* 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
Harry Linrui XU
committed
public void handleDeleteBtn(javafx.event.ActionEvent event) {
Harry Linrui XU
committed
handleEditBtn(event);
Harry Linrui XU
committed
}
/**
* 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() {
Harry Linrui XU
committed
this.income.setAll(incomeRegister.getItems());
this.inSum.setText("Sum: " + String.valueOf(incomeRegister.getTotalSum()));
Harry Linrui XU
committed
Harry Linrui XU
committed
this.expenses.setAll(expenseRegister.getItems());
this.expSum.setText("Sum: " + String.valueOf(expenseRegister.getTotalSum()));
Harry Linrui XU
committed
}
Harry Linrui XU
committed
private void refreshPieCharts() {
Harry Linrui XU
committed
this.incomePieChart.setData(createIncomePieChart());
this.expensePieChart.setData(createExpensePieChart());
Harry Linrui XU
committed
}
Harry Linrui XU
committed
Harry Linrui XU
committed
private void refreshProgress() {
budgetProgress.setProgress(expenseRegister.getTotalSum()/incomeRegister.getTotalSum());
}
Harry Linrui XU
committed
@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"));
Harry Linrui XU
committed
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");
Harry Linrui XU
committed
}
// 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());
Harry Linrui XU
committed
}
}
@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"));
Harry Linrui XU
committed
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) {
showErrorDialogBox("Loading", "Error in loading dialog box", "Could not load"
+ "the AddExpense window");
Harry Linrui XU
committed
}
// 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());
Harry Linrui XU
committed
}
}
@FXML
private void handleEditIncome(Income chosenIncome) {
//Create copy of chosenIncome before changes
Harry Linrui XU
committed
//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");
Harry Linrui XU
committed
}
// 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());
Harry Linrui XU
committed
}
@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");
Harry Linrui XU
committed
}
// 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());
Harry Linrui XU
committed
}
@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);
Harry Linrui XU
committed
if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) {
incomeRegister.removeItem(chosenIncome);
}
this.incomePieChart.setData(createIncomePieChart());
Harry Linrui XU
committed
}
@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);
Harry Linrui XU
committed
if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) {
expenseRegister.removeItem(chosenExpense);
}
this.expensePieChart.setData(createExpensePieChart());
Harry Linrui XU
committed
}
/**
Harry Linrui XU
committed
* 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(String title, String header, String content) {
Harry Linrui XU
committed
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();
Harry Linrui XU
committed
}
/**
* Displays an alert box of type error, informing of a custom error.
*/
private void showErrorDialogBox(String title, String header, String content) {
Alert alert = new Alert(AlertType.ERROR);
alert.setTitle(title);
alert.setHeaderText(header);
alert.setContentText(content);
alert.showAndWait();
}
Harry Linrui XU
committed
/**
* 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
Harry Linrui XU
committed
public void saveDataToFile() throws IOException {
fileHandling.writeItemRegisterToFile(incomeRegister, "Income");
fileHandling.writeItemRegisterToFile(expenseRegister, "Expense");
Harry Linrui XU
committed
}
Harry Linrui XU
committed
/**
* 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.
*/
public IncomeRegister loadIncomeDataFromFile(String fileName) {
Harry Linrui XU
committed
//Instantiate new incomeRegister
try {
if (fileHandling.isEmpty(fileName)) {
incomeRegister = new IncomeRegister();
} else { //Load previous income register
incomeRegister = fileHandling.readIncomeRegisterFromFile(fileName);
Harry Linrui XU
committed
}
showErrorDialogBox("File reading error", "Error in reading from file", "Could not"
+ "read the IncomeRegister from file");
Harry Linrui XU
committed
}
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.
*/
public ExpenseRegister loadExpenseDataFromFile(String fileName) {
Harry Linrui XU
committed
//ItemRegister<T extends Item>
try {
if (fileHandling.isEmpty(fileName)) {
expenseRegister = new ExpenseRegister();
} else { //Load previous income register
Harry Linrui XU
committed
expenseRegister = fileHandling.readExpenseRegisterFromFile(fileName);
}
showErrorDialogBox("File reading error", "Error in reading from file", "Could not"
+ "read the ExpenseRegister from file");
Harry Linrui XU
committed
}
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.
*/
public GeneralBudget loadBudgetDataFromFile(String fileName) {
Harry Linrui XU
committed
FileHandlingBudget fileHandlingBudget = new FileHandlingBudget();
//Instantiate new budget
try {
if (fileHandling.isEmpty(fileName)) {
generalBudget = new GeneralBudget(1000);
} else { //Load previous income register
Harry Linrui XU
committed
generalBudget = fileHandlingBudget.readGeneralBudgetFromFile(fileName);
}
showErrorDialogBox("File reading error", "Error in reading from file", "Could not"
+ "read the GeneralBudget from file");
Harry Linrui XU
committed
}
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
Harry Linrui XU
committed
*/
Harry Linrui XU
committed
@FXML
public void switchScene(javafx.event.ActionEvent event) {
try {
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();
} catch(IOException ioe) {
showErrorDialogBox("Loading error", "Error in loading", "Could not save"
+ "to file");
Harry Linrui XU
committed