You need to sign in or sign up before continuing.
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;

Harry Linrui Xu
committed
import javafx.event.ActionEvent;
Harry Linrui XU
committed
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.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.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.FileHandlingBudgetArchive;
import no.ntnu.idatt1002.demo.data.Budget.FileHandlingSelectedBudget;
Harry Linrui XU
committed
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 extends 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 DatePicker date;
@FXML
private Label daysLeftLbl;
@FXML
private ComboBox<?> filter;
@FXML
private Button returnToMainMenuBtn;
Harry Linrui XU
committed
@FXML
private Button returnBtn;
Harry Linrui XU
committed
@FXML
private Button continueBtn;
Harry Linrui XU
committed
@FXML

Harry Linrui Xu
committed
private Label maxAmount;
@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 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
/**
* Initializes the window that is controlled by the controller.
* Instantiates the income and expense registers and set them to
* table views.
*/
Harry Linrui XU
committed
@FXML
public void initialize() {
Harry Linrui XU
committed
//Initialize columns
Harry Linrui XU
committed
setColumns();

Harry Linrui Xu
committed
//Initialize registers
incomeRegister = loadIncomeDataFromFile(

Harry Linrui Xu
committed
"budgets/" + FileHandlingSelectedBudget
.readSelectedBudget("budgets/SelectedBudget") + "/Income");
expenseRegister = loadExpenseDataFromFile(

Harry Linrui Xu
committed
"budgets/" + FileHandlingSelectedBudget
.readSelectedBudget("budgets/SelectedBudget") + "/Expense");
} catch(IOException ioe) {
showErrorDialogBox("File reading error", "Could not read register", "");
}

Harry Linrui Xu
committed
//Set data for tableviews
Harry Linrui XU
committed
income = FXCollections.observableArrayList(incomeRegister.getItems());
incomeTableView.setItems(income);
Harry Linrui XU
committed
expenses = FXCollections.observableArrayList(expenseRegister.getItems());
expenseTableView.setItems(expenses);
Harry Linrui XU
committed

Harry Linrui Xu
committed
//Format pie charts
Harry Linrui XU
committed
incomePieChart.setLegendSide(Side.RIGHT);
incomePieChart.setLabelLineLength(10);
Harry Linrui XU
committed
Harry Linrui XU
committed
expensePieChart.setLegendSide(Side.RIGHT);
expensePieChart.setLabelLineLength(10);
Harry Linrui XU
committed

Harry Linrui Xu
committed
//Update pie charts and set date
Harry Linrui XU
committed
refreshPieChart();
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
//Add event filter to continue, such that budgets cannot have max amount at 1 or less
continueBtn.addEventFilter(
ActionEvent.ACTION, event -> {
if (!isValidMaxAmount(incomeRegister.getTotalSum(), expenseRegister.getTotalSum())) {
event.consume();
showErrorDialogBox("Invalid disposable income",
"Disposable income must be above 1",
"Please increase the income or decrease the expenses");
}
}
);
Harry Linrui XU
committed
}

Harry Linrui Xu
committed
/**
* Method for initiating all tableview columns.
*/
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
/**
* Method for creating a list of data used for graphing expenses in a pie chart. The categories from the
* expenses register become the pieces of data.
* @return An observable list of the sum of expenditure on each category
*/
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
/**
* Method for creating a list of data used for graphing income in a pie chart. The categories from the
* income register become the pieces of data.
* @return An observable list of the sum of earnings on each category
*/
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.
*/
Harry Linrui XU
committed
@Override
public void formatDatePicker() {
Harry Linrui XU
committed
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.

Harry Linrui Xu
committed
Harry Linrui XU
committed
* @param event A button click on the add button.
*/
Harry Linrui XU
committed
Harry Linrui XU
committed
@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();

Harry Linrui Xu
committed
refreshDisposableIncome();
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) {
Harry Linrui XU
committed
Income chosenIncome = incomeTableView.getSelectionModel().getSelectedItem();
Expense chosenExpense = expenseTableView.getSelectionModel().getSelectedItem();

Harry Linrui Xu
committed
//Determines which editing or deleting option has been chosen.
boolean isEditIncome = event.getSource() == editIncomeMenu;
boolean isDeleteIncome = event.getSource() == deleteIncomeMenu;
boolean isEditExpense = event.getSource() == editExpenseMenu;
boolean isDeleteExpense = event.getSource() == deleteExpenseMenu;

Harry Linrui Xu
committed
//Initiate the chosen editing/deleting option
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
refreshDisposableIncome();
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
/**
* Method for synching the pie charts to the registers.
*/
Harry Linrui XU
committed
@Override
public void refreshPieChart() {
Harry Linrui XU
committed
this.incomePieChart.setData(createIncomePieChart());
this.expensePieChart.setData(createExpensePieChart());
Harry Linrui XU
committed
}
Harry Linrui XU
committed

Harry Linrui Xu
committed
/**
* Method for adding income to the income register
*/
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);

Harry Linrui Xu
committed
//update just the income pie chart
incomePieChart.setData(createIncomePieChart());
Harry Linrui XU
committed
}
}

Harry Linrui Xu
committed
/**
* Method for adding expense to the expense register.
*/
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";
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);

Harry Linrui Xu
committed
//Update just the expense pie chart.
expensePieChart.setData(createExpensePieChart());
Harry Linrui XU
committed
}
}

Harry Linrui Xu
committed
/**
* Method for editing a chosen income in the income register.
* @param chosenIncome The chosen income.
*/
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();

Harry Linrui Xu
committed
//Refresh just the income pie chart
incomePieChart.setData(createIncomePieChart());
Harry Linrui XU
committed
}

Harry Linrui Xu
committed
/**
* Method for editing a chosen expense in the expense register.
* @param chosenExpense The chosen expense
*/
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();

Harry Linrui Xu
committed
//Update just the expense pie chart
this.expensePieChart.setData(createExpensePieChart());
Harry Linrui XU
committed
}

Harry Linrui Xu
committed
/**
* Method for deleting a chosen income from the income register.
* @param chosenIncome The chosen income.
*/
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);
}

Harry Linrui Xu
committed
//Update pie chart
this.incomePieChart.setData(createIncomePieChart());
Harry Linrui XU
committed
}

Harry Linrui Xu
committed
/**
* Method for deleting a chosen expense from the expenses register.
* @param chosenExpense The chosen expense.
*/
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);
}

Harry Linrui Xu
committed
//Update pie chart
this.expensePieChart.setData(createExpensePieChart());
Harry Linrui XU
committed
}
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,

Harry Linrui Xu
committed
"budgets/" + FileHandlingSelectedBudget
.readSelectedBudget("budgets/SelectedBudget") + "/Income");
FileHandling.writeItemRegisterToFile(expenseRegister,

Harry Linrui Xu
committed
"budgets/" + FileHandlingSelectedBudget
.readSelectedBudget("budgets/SelectedBudget") + "/Expense");
Harry Linrui XU
committed
}

Harry Linrui Xu
committed
/**
* Method for automatically updating the disposable income label.
*/
private void refreshDisposableIncome() {
maxAmount.setText(String.valueOf(incomeRegister.getTotalSum() - expenseRegister.getTotalSum()));
}
/**
* Writes the disposable income amount to the budget file as the max amount field.
* @throws IOException if there is an input or exception.
*/
private void saveDisposableIncomeToFile() throws IOException {
String disposableIncomeAsString = String.valueOf(incomeRegister.getTotalSum() - expenseRegister.getTotalSum());
FileHandlingBudget.writeMaxAmountToFile(

Harry Linrui Xu
committed
"budgets/" + FileHandlingSelectedBudget
.readSelectedBudget("budgets/SelectedBudget") + "/Budget", disposableIncomeAsString);
}
/**
* Method for validating if the max amount is above 1.
* @param sumIncome Sum of incomes.
* @param sumExpense Sum of expenses.
* @return True, if the amount is above 1. Else, returns false
*/
private boolean isValidMaxAmount(double sumIncome, double sumExpense) {
return sumIncome - sumExpense > 1;

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

Harry Linrui Xu
committed
//Determine which scene to switch to
FXMLLoader loader = new FXMLLoader();
if (event.getSource() == returnToMainMenuBtn) {
loader.setLocation(getClass().getResource("/view/MainMenuNew.fxml"));
Harry Linrui XU
committed
} else if (event.getSource() == continueBtn) {
Optional<ButtonType> isConfirmed = showConfirmationDialog("Continuation confirmation",
"Are you satisfied with your changes?",
"You can still make changes before continuing");
if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) {
loader.setLocation(getClass().getResource("/view/newBudgetBudgert.fxml"));
saveDisposableIncomeToFile();
} else return;
} else if (event.getSource() == returnBtn) {
Optional<ButtonType> isConfirmed = showConfirmationDialog("Return confirmation",
"Are you sure you want to go back?", "The budget you are creating will be deleted");
if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) {
loader.setLocation(getClass().getResource("/view/FirstMenu.fxml"));
FileHandlingSelectedBudget.deleteBudgetDirectory("budgets/" + FileHandlingSelectedBudget
.readSelectedBudget("budgets/SelectedBudget"));
FileHandlingSelectedBudget.clearSelectedBudget("budgets/SelectedBudget");
} else return;
Harry Linrui XU
committed
}

Harry Linrui Xu
committed
//Load the scene
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) {
Harry Linrui XU
committed
showErrorDialogBox("Loading error", "Error in loading", "Could not save"