Newer
Older
package no.ntnu.idatt1002.demo.controller;
import java.io.IOException;
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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.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;

Harry Linrui Xu
committed
/**
* 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.

Harry Linrui Xu
committed
* @author Harry Linrui Xu
* @since 30.03.2023
*/
public class IncomeExpenseController implements FinanceController {
private final static String sumText = "Sum: ";
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

Harry Linrui Xu
committed
private Label inSum;

Harry Linrui Xu
committed
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;

Harry Linrui Xu
committed
@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;

Harry Linrui Xu
committed

Harry Linrui Xu
committed
public void initialize() {
fileHandling = new FileHandling();
//Initialize columns
setColumns();

Harry Linrui Xu
committed
//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);
refreshPieCharts();

Harry Linrui Xu
committed

Harry Linrui Xu
committed
inSum.setText(sumText + String.valueOf(incomeRegister.getTotalSum()));
expSum.setText(sumText + String.valueOf(expenseRegister.getTotalSum()));

Harry Linrui Xu
committed
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"));
}
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;
if (isEditIncome) {
handleEditIncome(chosenIncome);
} else if (isDeleteIncome) {
handleDeleteIncome(chosenIncome);
} else if (isEditExpense) {
handleEditExpense(chosenExpense);
} else if (isDeleteExpense) {
handleDeleteExpense(chosenExpense);
//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());

Harry Linrui Xu
committed
this.inSum.setText("Sum: " + String.valueOf(incomeRegister.getTotalSum()));
this.expenses.setAll(expenseRegister.getItems());

Harry Linrui Xu
committed
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"));

Harry Linrui Xu
committed
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) {

Harry Linrui Xu
committed
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"));

Harry Linrui Xu
committed
Expense newExpense;
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) {

Harry Linrui Xu
committed
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) {

Harry Linrui Xu
committed
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();

Harry Linrui Xu
committed
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) {

Harry Linrui Xu
committed
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) {

Harry Linrui Xu
committed
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) {

Harry Linrui Xu
committed
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());
}
/**
* 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

Harry Linrui Xu
committed
public Optional<ButtonType> showConfirmationDialog(String title, String header, String content) {
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();
}
/**
* 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");
}

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.
*/

Harry Linrui Xu
committed
public IncomeRegister loadIncomeDataFromFile(String fileName) {

Harry Linrui Xu
committed
try {
if (fileHandling.isEmpty(fileName)) {
incomeRegister = new IncomeRegister();
} else { //Load previous income register
incomeRegister = fileHandling.readIncomeRegisterFromFile(fileName);

Harry Linrui Xu
committed
} catch (IOException ioe) {
showErrorDialogBox("File reading error", "Error in reading from file", "Could not"
+ "read the IncomeRegister from file");
}
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.
*/

Harry Linrui Xu
committed
public ExpenseRegister loadExpenseDataFromFile(String fileName) {

Harry Linrui Xu
committed
try {
if (fileHandling.isEmpty(fileName)) {
expenseRegister = new ExpenseRegister();
} else { //Load previous income register
expenseRegister = fileHandling.readExpenseRegisterFromFile(fileName);
}

Harry Linrui Xu
committed
} catch (IOException ioe) {
showErrorDialogBox("File reading error", "Error in reading from file", "Could not"
+ "read the ExpenseRegister from file");
}
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.
*/

Harry Linrui Xu
committed
public GeneralBudget loadBudgetDataFromFile(String fileName) {
FileHandlingBudget fileHandlingBudget = new FileHandlingBudget();
//Instantiate new budget

Harry Linrui Xu
committed
try {
if (fileHandling.isEmpty(fileName)) {
generalBudget = new GeneralBudget(31, 1000);
} else { //Load previous income register
generalBudget = fileHandlingBudget.readGeneralBudgetFromFile(fileName);
}

Harry Linrui Xu
committed
} catch (IOException ioe) {
showErrorDialogBox("File reading error", "Error in reading from file", "Could not"
+ "read the GeneralBudget from file");
* 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
public void returnToMainMenu(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");
}