I am creating a swing clone of Notepad and I'm trying to figure out a way to improve the "do you want to save before exiting tab part.
I have a method that is called when the user wants to exit from the application:
private void onQuit() {
if (textArea.getText().equals("") && (path == "" || path == null)) {
System.exit(0);
} else {
int result = JOptionPane.showConfirmDialog(frame, "Do you want to save before exiting?",
"Notepad", JOptionPane.YES_NO_CANCEL_OPTION);
switch (result) {
case JOptionPane.YES_OPTION:
save(); //saves the edits made by the users
Thread thread = new Thread(() -> {
//fix this loop that is being called every second
//I don't want to waste resources but can't figure out another way
waiting = true;
while (waiting) {
if ((saveWorker != null && saveWorker.isDone()) || (saveAsWorker != null && saveAsWorker.isDone())) {
System.exit(0);
}
}
});
thread.start();
break;
case JOptionPane.NO_OPTION:
System.exit(0);
break;
}
}
Going over the code I made I first check if the application actually needs to save what has been edited through the first if. The problem is in the else though, where I create a JOptionPane and try to implement what to do when the YES option is selected. In this option the first thing I do is call save():
private void save() {
if (path == "" || path == null) {
saveAs();
} else {
saveWorker = new SwingWorker<>() {
@Override
protected NullType doInBackground() {
try {
if (!path.endsWith(".txt")) {
FileWriter writer = new FileWriter(path + ".txt");
writer.write(textArea.getText());
writer.close();
} else {
FileWriter writer = new FileWriter(path);
writer.write(textArea.getText());
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
};
saveWorker.execute();
}
}
which also calls saveAs():
private void saveAs() {
saveAsWorker = new SwingWorker<>() {
@Override
protected String doInBackground() {
JFileChooser chooser = new JFileChooser();
int choice = chooser.showSaveDialog(frame);
String fileName = "Untitled";
if (choice == JFileChooser.APPROVE_OPTION) {
try {
File file = chooser.getSelectedFile();
path = file.toString();
if (!path.endsWith(".txt")) {
FileWriter writer = new FileWriter(path + ".txt");
writer.write(textArea.getText());
writer.close();
} else {
FileWriter writer = new FileWriter(path);
writer.write(textArea.getText());
writer.close();
}
StringBuilder builder = new StringBuilder(path);
fileName = builder.substring(builder.lastIndexOf("\\") + 1);
} catch (IOException e) {
e.printStackTrace();
}
}
return fileName;
}
@Override
protected void done() {
try {
frame.setTitle(get() + ".txt");
} catch (InterruptedException | ExecutionException exception) {
exception.printStackTrace();
}
}
};
saveAsWorker.execute();
}
Both of these methods use SwingWorker threads. Finally the point is, going back to what happens when the YES option is selected, I don't know how to let the Thread I created "wait" that the SwingWorkers are done. What I'm doing at the moment (and it's working) is using a while (true) loop from that thread that keeps checking if those threads are done, but this of course wastes a lots of resources. I'm sure there must be a way to do something like this: "make the closing Thread wait until one worker says I'm done you can close the application". I tried using guarded blocks from official java tutorials but they are hard to implement with SwingWorker and I don't know how to really work with the intrinsic lock either which I think might be a solution too, but in the end I'm always stuck.
Application is actually just a class so I might as well send it so you can test things out
public class TextEditor implements ActionListener {
private JFrame frame;
private JTextArea textArea;
private JScrollPane scrollPane;
private JMenuBar menuBar;
private JMenu fileMenu, editMenu, fontMenu, sizeMenu;
private JMenuItem newItem, openItem, saveItem, saveAsItem, exitItem; //fileMenu
private JMenuItem undoItem, redoItem, copyItem, pasteItem, cutItem; //editMenu
private JMenuItem smallItem, mediumItem, largeItem; //sizeMenu
private UndoManager undoManager = new UndoManager();
private String path;
private SwingWorker<NullType, NullType> saveWorker;
private SwingWorker<String, NullType> saveAsWorker;
private boolean waiting;
//todo do you want to save before exiting?
//todo create frame of dimension depending size when it was closed
//todo implement fonts?
//todo add more customisable sizes?
//todo package the application and create an installer
public static void main(String[] args) throws Exception {
//custom look and feel
UIManager.setLookAndFeel(new FlatDarculaLaf() {
@Override
public void provideErrorFeedback(Component component) {
Toolkit toolkit = null;
if (component != null) {
toolkit = component.getToolkit();
} else {
toolkit = Toolkit.getDefaultToolkit();
}
//this line produces an annoying beep so just comment it out
//toolkit.beep();
}
});
SwingUtilities.invokeLater(() -> new TextEditor("Untitled" , 809, 500));
}
private TextEditor(String title, int width, int height) {
Image icon = new ImageIcon("src/main/resources/killua.png").getImage();
frame = new JFrame(title);
frame.setIconImage(icon);
frame.setSize(width, height);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
onQuit();
}
});
createMenuBar();
textArea = new JTextArea();
textArea.setFont(new Font("", Font.PLAIN, 35));
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.getDocument().addUndoableEditListener(e -> undoManager.addEdit(e.getEdit()));
scrollPane = new JScrollPane(textArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
frame.add(scrollPane);
frame.setVisible(true);
}
private void onQuit() {
if (textArea.getText().equals("") && (path == "" || path == null)) {
System.exit(0);
} else {
int result = JOptionPane.showConfirmDialog(frame, "Do you want to save before exiting?",
"Notepad", JOptionPane.YES_NO_CANCEL_OPTION);
switch (result) {
case JOptionPane.YES_OPTION:
save();
Thread thread = new Thread(() -> {
//fix this loop that is being called every second
//I don't want to waste resources but can't figure out another way
waiting = true;
while (waiting) {
if ((saveWorker != null && saveWorker.isDone()) || (saveAsWorker != null && saveAsWorker.isDone())) {
System.exit(0);
}
}
});
thread.start();
break;
case JOptionPane.NO_OPTION:
System.exit(0);
break;
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
switch (command) {
case "New" -> {
frame.setTitle("Untitled");
textArea.setText("");
path = "";
}
case "Open" -> open();
case "SaveAs" -> saveAs();
case "Save" -> save();
case "Exit" -> onQuit();
case "Undo" -> undoManager.undo();
case "Redo" -> undoManager.redo();
case "Copy" -> textArea.copy();
case "Paste" -> textArea.paste();
case "Cut" -> textArea.cut();
case "Small" -> textArea.setFont(new Font("", Font.PLAIN, 30));
case "Medium" -> textArea.setFont(new Font("", Font.PLAIN, 35));
case "Large" -> textArea.setFont(new Font("", Font.PLAIN, 40));
}
}
private void save() {
if (path == "" || path == null) {
saveAs();
} else {
saveWorker = new SwingWorker<>() {
@Override
protected NullType doInBackground() {
try {
if (!path.endsWith(".txt")) {
FileWriter writer = new FileWriter(path + ".txt");
writer.write(textArea.getText());
writer.close();
} else {
FileWriter writer = new FileWriter(path);
writer.write(textArea.getText());
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
};
saveWorker.execute();
}
}
private void saveAs() {
saveAsWorker = new SwingWorker<>() {
@Override
protected String doInBackground() {
JFileChooser chooser = new JFileChooser();
int choice = chooser.showSaveDialog(frame);
String fileName = "Untitled";
if (choice == JFileChooser.APPROVE_OPTION) {
try {
File file = chooser.getSelectedFile();
path = file.toString();
if (!path.endsWith(".txt")) {
FileWriter writer = new FileWriter(path + ".txt");
writer.write(textArea.getText());
writer.close();
} else {
FileWriter writer = new FileWriter(path);
writer.write(textArea.getText());
writer.close();
}
StringBuilder builder = new StringBuilder(path);
fileName = builder.substring(builder.lastIndexOf("\\") + 1);
} catch (IOException e) {
e.printStackTrace();
}
}
return fileName;
}
@Override
protected void done() {
try {
frame.setTitle(get() + ".txt");
} catch (InterruptedException | ExecutionException exception) {
exception.printStackTrace();
}
}
};
saveAsWorker.execute();
}
private void open() {
SwingWorker<List<String>, NullType> worker = new SwingWorker<>() {
@Override
protected List<String> doInBackground() {
JFileChooser chooser = new JFileChooser();
int choice = chooser.showOpenDialog(frame);
String fileName = "";
path = "";
if (choice == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
StringBuilder stringBuilder = new StringBuilder(file.toString());
fileName = stringBuilder.substring(stringBuilder.lastIndexOf("\\") + 1);
path = file.getPath();
}
ArrayList<String> lines = new ArrayList<>();
try {
FileReader fileReader = new FileReader(path);
BufferedReader reader = new BufferedReader(fileReader);
String line;
while ((line = reader.readLine()) != null)
lines.add(line);
fileReader.close();
reader.close();
lines.add(fileName);
} catch (IOException e) {
e.printStackTrace();
}
return lines;
}
@Override
protected void done() {
try {
textArea.setText("");
List<String> lines = get();
for (int i = 0; i <= lines.size() - 2; i++)
textArea.append(lines.get(i) + "\n");
frame.setTitle(lines.get(lines.size() - 1));
} catch (InterruptedException | ExecutionException | IndexOutOfBoundsException exception) {
exception.printStackTrace();
}
}
};
worker.execute();
}
private void createMenuBar() {
menuBar = new JMenuBar();
fileMenu = new JMenu("File");
editMenu = new JMenu("Edit");
fontMenu = new JMenu("Font");
sizeMenu = new JMenu("Size");
List<JMenu> menus = Arrays.asList(fileMenu, editMenu, fontMenu, sizeMenu);
menus.forEach(menu -> {
menu.getPopupMenu().setBorder(null);
menuBar.add(menu);
});
//file
newItem = new JMenuItem("New");
newItem.addActionListener(this);
newItem.setActionCommand("New");
fileMenu.add(newItem);
openItem = new JMenuItem("Open");
openItem.addActionListener(this);
openItem.setActionCommand("Open");
fileMenu.add(openItem);
saveItem = new JMenuItem("Save");
saveItem.addActionListener(this);
saveItem.setActionCommand("Save");
fileMenu.add(saveItem);
saveAsItem = new JMenuItem("SaveAs");
saveAsItem.addActionListener(this);
saveAsItem.setActionCommand("SaveAs");
fileMenu.add(saveAsItem);
exitItem = new JMenuItem("Exit");
exitItem.addActionListener(this);
exitItem.setActionCommand("Exit");
fileMenu.add(exitItem);
//edit
undoItem = new JMenuItem("Undo");
undoItem.addActionListener(this);
undoItem.setActionCommand("Undo");
editMenu.add(undoItem);
redoItem = new JMenuItem("Redo");
redoItem.addActionListener(this);
redoItem.setActionCommand("Redo");
editMenu.add(redoItem);
copyItem = new JMenuItem("Copy");
copyItem.addActionListener(this);
copyItem.setActionCommand("Copy");
editMenu.add(copyItem);
pasteItem = new JMenuItem("Paste");
pasteItem.addActionListener(this);
pasteItem.setActionCommand("Paste");
editMenu.add(pasteItem);
cutItem = new JMenuItem("Cut");
cutItem.addActionListener(this);
cutItem.setActionCommand("Cut");
editMenu.add(cutItem);
//font
//size
smallItem = new JMenuItem("Small");
smallItem.addActionListener(this);
smallItem.setActionCommand("Small");
sizeMenu.add(smallItem);
mediumItem = new JMenuItem("Medium");
mediumItem.addActionListener(this);
mediumItem.setActionCommand("Medium");
sizeMenu.add(mediumItem);
largeItem = new JMenuItem("Large");
largeItem.addActionListener(this);
largeItem.setActionCommand("Large");
sizeMenu.add(largeItem);
frame.setJMenuBar(menuBar);
}
}