Tue, 28 Jul 2020 14:27:14 +0200
bugfix: modified state is reset even when saving fails + more tests
1 /*
2 * Copyright 2013 Mike Becker. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
27 package de.uapcore.sudoku;
29 import javax.swing.*;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.ActionListener;
32 import java.io.File;
33 import java.io.IOException;
35 /**
36 * Handles all user issued actions in the application.
37 */
38 public final class ActionHandler implements ActionListener {
40 public static final String SAVE = "save";
41 public static final String CHECK = "check";
42 public static final String SOLVE = "solve";
44 public static final String NEW = "new";
45 public static final String OPEN = "open";
46 public static final String SAVE_AS = "save as";
47 public static final String QUIT = "quit";
48 public static final String ABOUT = "about";
50 private final Field field;
51 private final Solver solver;
52 private final DocumentHandler doc;
54 /**
55 * Constructs a new action handler instance.
56 *
57 * @param f a reference to the playing field
58 */
59 public ActionHandler(Field f) {
60 field = f;
61 solver = new Solver();
62 doc = new DocumentHandler();
63 }
65 /**
66 * Prompts the user for a file name.
67 * <p>
68 * If the user chooses an existing file, they are asked whether the file should be overwritten.
69 *
70 * @return true if the user approves a chosen file name
71 */
72 private boolean chooseSaveFilename() {
73 JFileChooser fc = new JFileChooser(".");
74 fc.setMultiSelectionEnabled(false);
75 if (fc.showSaveDialog(field) == JFileChooser.APPROVE_OPTION) {
76 File f = fc.getSelectedFile();
77 if (f.exists()) {
78 int result = JOptionPane.showConfirmDialog(field,
79 "Bereits existierende Datei überschreiben?", "Sudoku",
80 JOptionPane.YES_NO_OPTION);
81 if (result == JOptionPane.YES_OPTION) {
82 doc.setFilename(f.getAbsolutePath());
83 return true;
84 } else {
85 return false;
86 }
87 } else {
88 doc.setFilename(f.getAbsolutePath());
89 return true;
90 }
91 } else {
92 return false;
93 }
94 }
96 /**
97 * Prompts the user for a file to open and, if approved, loads that file.
98 */
99 private void open() {
100 JFileChooser fc = new JFileChooser(".");
101 fc.setMultiSelectionEnabled(false);
102 if (fc.showOpenDialog(field) == JFileChooser.APPROVE_OPTION) {
103 File f = fc.getSelectedFile();
104 doc.setFilename(f.getAbsolutePath());
105 try {
106 doc.load(field);
107 } catch (IOException e) {
108 JOptionPane.showMessageDialog(field,
109 "Datei konnte nicht geladen werden: " + e.getMessage(),
110 "Sudoku", JOptionPane.ERROR_MESSAGE);
111 }
112 }
113 }
115 /**
116 * Attempts to save the Sudoku field to a file.
117 * <p>
118 * If necessary, the user is prompted for a file name.
119 * <p>
120 * The field must be solvable, otherwise it cannot be saved.
121 *
122 * @param rename true if the user shall always be prompted, even if a file name is already known
123 * @return true if the user approves the chosen file name
124 */
125 private boolean save(boolean rename) {
126 if (!doc.isFilenameSet() || rename) {
127 if (!chooseSaveFilename()) {
128 return false;
129 }
130 }
131 if (solver.check(field)) {
132 try {
133 doc.save(field);
134 } catch (IOException e) {
135 JOptionPane.showMessageDialog(field,
136 "Datei konnte nicht gespeichert werden: " + e.getMessage(),
137 "Sudoku", JOptionPane.ERROR_MESSAGE);
138 }
139 return true;
140 } else {
141 JOptionPane.showMessageDialog(field,
142 "Das Feld kann mit Fehlern nicht gespeichert werden!",
143 "Sudoku", JOptionPane.ERROR_MESSAGE);
144 return false;
145 }
146 }
148 /**
149 * Checks the Sudoku field and displays the result as a dialog box.
150 */
151 private void check() {
152 if (solver.check(field)) {
153 JOptionPane.showMessageDialog(field, "Überprüfung erfolgreich!",
154 "Sudoku", JOptionPane.INFORMATION_MESSAGE);
155 } else {
156 JOptionPane.showMessageDialog(field, "Das Feld enthält Fehler!",
157 "Sudoku", JOptionPane.WARNING_MESSAGE);
158 }
159 }
161 /**
162 * Solves the field or displays an error dialog if the field is not solvable.
163 */
164 private void solve() {
165 if (!solver.check(field) || !solver.solve(field)) {
166 JOptionPane.showMessageDialog(field, "Das Feld ist nicht lösbar!",
167 "Sudoku", JOptionPane.WARNING_MESSAGE);
168 }
169 }
171 /**
172 * Checks whether there are unsaved changes and asks the user to save the field.
173 *
174 * @return true if there are no unsaved changes or the user actively decides to continue - false, otherwise
175 */
176 private boolean saveUnsaved() {
177 boolean proceed = false;
178 if (field.isAnyCellModified()) {
179 int result = JOptionPane.showConfirmDialog(field,
180 "Das Feld ist ungespeichert - jetzt speichern?",
181 "Sudoku", JOptionPane.YES_NO_CANCEL_OPTION);
182 if (result == JOptionPane.YES_OPTION) {
183 if (save(false)) {
184 proceed = true;
185 }
186 } else if (result == JOptionPane.NO_OPTION) {
187 proceed = true;
188 }
189 } else {
190 proceed = true;
191 }
193 return proceed;
194 }
196 @Override
197 public void actionPerformed(ActionEvent e) {
198 switch (e.getActionCommand()) {
199 case NEW:
200 if (saveUnsaved()) {
201 doc.clearFilename();
202 field.clear();
203 }
204 break;
205 case OPEN:
206 open();
207 break;
208 case SAVE:
209 save(false);
210 break;
211 case SAVE_AS:
212 save(true);
213 break;
214 case CHECK:
215 check();
216 break;
217 case SOLVE:
218 solve();
219 break;
220 case QUIT:
221 if (saveUnsaved()) {
222 System.exit(0);
223 }
224 break;
225 case ABOUT:
226 JOptionPane.showMessageDialog(field,
227 "Sudoku - Copyright (c) 2013 Mike Becker\nwww.uap-core.de" +
228 "\nPublished under the BSD License",
229 "Sudoku", JOptionPane.INFORMATION_MESSAGE);
230 break;
231 default:
232 throw new UnsupportedOperationException(
233 "unknown action: " + e.getActionCommand());
234 }
235 }
237 }