fiore@3
|
1 /*
|
fiore@3
|
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
|
fiore@3
|
3
|
fiore@3
|
4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
|
fiore@3
|
5
|
fiore@3
|
6 This program is free software: you can redistribute it and/or modify
|
fiore@3
|
7 it under the terms of the GNU General Public License as published by
|
fiore@3
|
8 the Free Software Foundation, either version 3 of the License, or
|
fiore@3
|
9 (at your option) any later version.
|
fiore@3
|
10
|
fiore@3
|
11 This program is distributed in the hope that it will be useful,
|
fiore@3
|
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
fiore@3
|
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
fiore@3
|
14 GNU General Public License for more details.
|
fiore@3
|
15
|
fiore@3
|
16 You should have received a copy of the GNU General Public License
|
fiore@3
|
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
fiore@3
|
18 */
|
fiore@0
|
19 package uk.ac.qmul.eecs.ccmi.gui;
|
fiore@0
|
20
|
fiore@0
|
21 import java.awt.Frame;
|
fiore@0
|
22 import java.io.BufferedInputStream;
|
fiore@0
|
23 import java.io.BufferedOutputStream;
|
fiore@0
|
24 import java.io.File;
|
fiore@0
|
25 import java.io.FileInputStream;
|
fiore@0
|
26 import java.io.FileNotFoundException;
|
fiore@0
|
27 import java.io.FileOutputStream;
|
fiore@0
|
28 import java.io.IOException;
|
fiore@0
|
29 import java.io.InputStream;
|
fiore@0
|
30 import java.io.OutputStream;
|
fiore@0
|
31 import java.util.ResourceBundle;
|
fiore@0
|
32
|
fiore@0
|
33 import javax.swing.JFileChooser;
|
fiore@0
|
34 import javax.swing.JOptionPane;
|
fiore@0
|
35
|
fiore@0
|
36 import uk.ac.qmul.eecs.ccmi.gui.filechooser.FileChooser;
|
fiore@0
|
37 import uk.ac.qmul.eecs.ccmi.gui.filechooser.FileChooserFactory;
|
fiore@0
|
38 import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
|
fiore@0
|
39 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
|
fiore@0
|
40 import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
|
fiore@0
|
41
|
fiore@5
|
42 /**
|
fiore@5
|
43 * A Utility class providing inner classes and interfaces for handling
|
fiore@5
|
44 * files.
|
fiore@5
|
45 */
|
fiore@0
|
46 public abstract class FileService
|
fiore@0
|
47 {
|
fiore@0
|
48
|
fiore@3
|
49 /**
|
fiore@3
|
50 * An Open object encapsulates the stream, name and path of the file that the user selected for opening.
|
fiore@3
|
51 */
|
fiore@3
|
52 public interface Open
|
fiore@3
|
53 {
|
fiore@3
|
54 /**
|
fiore@3
|
55 * Gets the input stream corresponding to the user selection.
|
fiore@3
|
56 * @return the input stream, or null if the user cancels the file selection task
|
fiore@3
|
57 */
|
fiore@5
|
58 InputStream getInputStream();
|
fiore@3
|
59 /**
|
fiore@3
|
60 * Gets the name of the file that the user selected.
|
fiore@3
|
61 * @return the file name, or null if the user cancels the file selection task
|
fiore@3
|
62 */
|
fiore@5
|
63 String getName();
|
fiore@0
|
64
|
fiore@3
|
65 /**
|
fiore@3
|
66 * Gets the path of the file that the user selected.
|
fiore@3
|
67 * @return the file path , or null if the user cancels the file selection task
|
fiore@3
|
68 */
|
fiore@5
|
69 String getPath();
|
fiore@0
|
70
|
fiore@3
|
71 }
|
fiore@0
|
72
|
fiore@3
|
73 /**
|
fiore@3
|
74 * A Save object encapsulates the stream and name of the file that the user selected for saving.
|
fiore@3
|
75 */
|
fiore@3
|
76 public interface Save
|
fiore@3
|
77 {
|
fiore@3
|
78 /**
|
fiore@3
|
79 * Gets the output stream corresponding to the user selection.
|
fiore@3
|
80 * @return the output stream, or null if the user cancels the file selection task
|
fiore@3
|
81 */
|
fiore@3
|
82 OutputStream getOutputStream();
|
fiore@3
|
83 /**
|
fiore@3
|
84 * Gets the name of the file that the user selected.
|
fiore@3
|
85 * @return the file name, or null if the user cancels the file selection task
|
fiore@3
|
86 */
|
fiore@3
|
87 String getName();
|
fiore@3
|
88 /**
|
fiore@3
|
89 * Gets the path of the file that the user selected.
|
fiore@3
|
90 * @return the file path, or null if the user cancels the file selection task
|
fiore@3
|
91 */
|
fiore@3
|
92 String getPath();
|
fiore@3
|
93 }
|
fiore@0
|
94
|
fiore@3
|
95 /**
|
fiore@5
|
96 * This class returns a FileService for opening and saving files, with either a JFileChooser or a
|
fiore@5
|
97 * SpeechFileChooser
|
fiore@3
|
98 */
|
fiore@3
|
99 public static class ChooserService
|
fiore@3
|
100 {
|
fiore@5
|
101 /**
|
fiore@5
|
102 * Creates a new {@code ChooserService}.
|
fiore@5
|
103 *
|
fiore@5
|
104 * @param initialDirectory the directory displayed when the files service is displayed
|
fiore@5
|
105 */
|
fiore@3
|
106 public ChooserService(File initialDirectory){
|
fiore@3
|
107 useAccessible = Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true"));
|
fiore@3
|
108 fileChooser = FileChooserFactory.getFileChooser(useAccessible);
|
fiore@3
|
109 fileChooser.setCurrentDirectory(initialDirectory);
|
fiore@3
|
110 }
|
fiore@0
|
111
|
fiore@5
|
112 /**
|
fiore@5
|
113 * Returns a {@code FileService.Open} out of a file chosen by the user. The user is prompted
|
fiore@5
|
114 * with either a normal or an accessible file choser.
|
fiore@5
|
115 *
|
fiore@5
|
116 * @param defaultDirectory the directory to open the file chooser, prompted to the user
|
fiore@5
|
117 * @param defaultFile the file selected when the file chooser is prompted to the user
|
fiore@5
|
118 * @param filter an {@code ExtensionFilter} to filter the filed displayed on the file chooser
|
fiore@5
|
119 * @param frame the frame where the file chooser is displayed
|
fiore@5
|
120 * @return an {@code FileService.Open} to handle the file selected by the user
|
fiore@5
|
121 * @throws IOException if the file chosen by the uses doesn't exist.
|
fiore@5
|
122 */
|
fiore@3
|
123 public FileService.Open open(String defaultDirectory, String defaultFile,
|
fiore@5
|
124 ExtensionFilter filter, Frame frame) throws IOException {
|
fiore@3
|
125 checkChangedOption();
|
fiore@3
|
126 fileChooser.resetChoosableFileFilters();
|
fiore@3
|
127 fileChooser.setFileFilter(filter);
|
fiore@3
|
128 if (defaultDirectory != null)
|
fiore@3
|
129 fileChooser.setCurrentDirectory(new File(defaultDirectory));
|
fiore@3
|
130 if (defaultFile == null)
|
fiore@3
|
131 fileChooser.setSelectedFile(null);
|
fiore@3
|
132 else
|
fiore@3
|
133 fileChooser.setSelectedFile(new File(defaultFile));
|
fiore@3
|
134 int response = fileChooser.showOpenDialog(frame);
|
fiore@3
|
135 if (response == JFileChooser.APPROVE_OPTION)
|
fiore@3
|
136 return new OpenImpl(fileChooser.getSelectedFile());
|
fiore@3
|
137 else{
|
fiore@3
|
138 /* If the user cancels the task (presses cancel button or the X at the top left corner) *
|
fiore@3
|
139 * the CANCEl sound is played (together with the registered playerListeenr if any) */
|
fiore@3
|
140 if(useAccessible)
|
fiore@3
|
141 SoundFactory.getInstance().play(SoundEvent.CANCEL);
|
fiore@3
|
142 return new OpenImpl(null);
|
fiore@3
|
143 }
|
fiore@3
|
144 }
|
fiore@3
|
145
|
fiore@5
|
146 /**
|
fiore@5
|
147 * Returns a {@code FileService.Save} out of a file chosen by the user. The user is prompted
|
fiore@5
|
148 * with either a normal or an accessible file chooser.
|
fiore@5
|
149 *
|
fiore@5
|
150 * @param defaultDirectory the directory to open the file chooser, prompted to the user
|
fiore@5
|
151 * @param defaultFile the file selected when the file chooser is prompted to the user
|
fiore@5
|
152 * @param filter an {@code ExtensionFilter} to filter the filed displayed on the file chooser
|
fiore@5
|
153 * @param removeExtension the extension to be removed from the chosen file name. Use {@code null} for removing no extension
|
fiore@5
|
154 * @param addExtension the extension to be added to the chosen file name.
|
fiore@5
|
155 * @param currentTabs an array of already open files names. If the selected file matches any of these, then an
|
fiore@6
|
156 * Exception is thrown. If {@code null} is passed, then this parameter will be ignored and no check will be done.
|
fiore@5
|
157 * @return an {@code FileService.Save} to handle the file selected by the user
|
fiore@5
|
158 * @throws IOException if the file chosen by the uses doesn't exist or has a file name that's already
|
fiore@5
|
159 * in {@code currentTabs}.
|
fiore@5
|
160 */
|
fiore@3
|
161 public FileService.Save save(String defaultDirectory, String defaultFile,
|
fiore@3
|
162 ExtensionFilter filter, String removeExtension, String addExtension, String[] currentTabs) throws IOException {
|
fiore@3
|
163 checkChangedOption();
|
fiore@3
|
164 fileChooser.resetChoosableFileFilters();
|
fiore@3
|
165 fileChooser.setFileFilter(filter);
|
fiore@3
|
166 if (defaultDirectory == null)
|
fiore@3
|
167 fileChooser.setCurrentDirectory(new File("."));
|
fiore@3
|
168 else
|
fiore@3
|
169 fileChooser.setCurrentDirectory(new File(defaultDirectory));
|
fiore@3
|
170 if (defaultFile != null){
|
fiore@3
|
171 File f = new File(editExtension(defaultFile, removeExtension, addExtension));
|
fiore@3
|
172 if(f.exists())
|
fiore@3
|
173 fileChooser.setSelectedFile(f);
|
fiore@3
|
174 else
|
fiore@3
|
175 fileChooser.setSelectedFile(null);
|
fiore@3
|
176 }else
|
fiore@3
|
177 fileChooser.setSelectedFile(null);
|
fiore@3
|
178 int response = fileChooser.showSaveDialog(null);
|
fiore@3
|
179 if (response == JFileChooser.APPROVE_OPTION){
|
fiore@3
|
180 ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
|
fiore@3
|
181 File f = fileChooser.getSelectedFile();
|
fiore@3
|
182 if (addExtension != null && f.getName().indexOf(".") < 0) // no extension supplied
|
fiore@3
|
183 f = new File(f.getPath() + addExtension);
|
fiore@3
|
184
|
fiore@3
|
185 String fileName = getFileNameFromPath(f.getAbsolutePath(),false);
|
fiore@5
|
186 /* check the name against the names of already open tabs */
|
fiore@5
|
187 if(currentTabs != null){
|
fiore@5
|
188 for(String tab : currentTabs){
|
fiore@5
|
189 if(fileName.equals(tab))
|
fiore@5
|
190 throw new IOException(resources.getString("dialog.error.same_file_name"));
|
fiore@5
|
191 }
|
fiore@3
|
192 }
|
fiore@3
|
193
|
fiore@3
|
194 if (!f.exists()) // file doesn't exits return the new SaveImpl with no problems
|
fiore@3
|
195 return new SaveImpl(f);
|
fiore@3
|
196
|
fiore@3
|
197 /* file with this name already exists, we must ask the user to confirm */
|
fiore@3
|
198 if(useAccessible){
|
fiore@3
|
199 int result = SpeechOptionPane.showConfirmDialog(
|
fiore@3
|
200 null,
|
fiore@3
|
201 resources.getString("dialog.overwrite"),
|
fiore@3
|
202 SpeechOptionPane.YES_NO_OPTION);
|
fiore@3
|
203 if (result == SpeechOptionPane.YES_OPTION)
|
fiore@3
|
204 return new SaveImpl(f);
|
fiore@3
|
205 }else{
|
fiore@3
|
206 int result = JOptionPane.showConfirmDialog(
|
fiore@3
|
207 null,
|
fiore@3
|
208 resources.getString("dialog.overwrite"),
|
fiore@3
|
209 null,
|
fiore@3
|
210 JOptionPane.YES_NO_OPTION);
|
fiore@3
|
211 if (result == JOptionPane.YES_OPTION)
|
fiore@3
|
212 return new SaveImpl(f);
|
fiore@3
|
213 }
|
fiore@3
|
214 }
|
fiore@5
|
215 /* If the user cancels the task (presses cancel button or the X at the top left) *
|
fiore@5
|
216 * the CANCEl sound is played (together with the registered playerListeenr if any) */
|
fiore@3
|
217 if(useAccessible)
|
fiore@3
|
218 SoundFactory.getInstance().play(SoundEvent.CANCEL);
|
fiore@3
|
219 /* returned if the user doesn't want to overwrite the file */
|
fiore@3
|
220 return new SaveImpl(null);
|
fiore@3
|
221 }
|
fiore@3
|
222
|
fiore@3
|
223 /* check if the user has changed the configuration since the last time a the fileChooser was shown */
|
fiore@3
|
224 private void checkChangedOption(){
|
fiore@3
|
225 boolean useAccessible = Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true"));
|
fiore@3
|
226 if(this.useAccessible != useAccessible){
|
fiore@3
|
227 this.useAccessible = useAccessible;
|
fiore@3
|
228 File currentDir = fileChooser.getCurrentDirectory();
|
fiore@3
|
229 fileChooser = FileChooserFactory.getFileChooser(useAccessible);
|
fiore@3
|
230 fileChooser.setCurrentDirectory(currentDir);
|
fiore@3
|
231 }
|
fiore@3
|
232 }
|
fiore@3
|
233
|
fiore@3
|
234 private FileChooser fileChooser;
|
fiore@3
|
235 private boolean useAccessible;
|
fiore@3
|
236 }
|
fiore@3
|
237
|
fiore@5
|
238 /**
|
fiore@5
|
239 * A file service which doesn't show any dialog to let
|
fiore@5
|
240 * the user choose which file to open or save.
|
fiore@5
|
241 */
|
fiore@3
|
242 public static class DirectService {
|
fiore@5
|
243 /**
|
fiore@5
|
244 * Creates a {@code FileService.Open} out of the file passed as argument.
|
fiore@5
|
245 *
|
fiore@5
|
246 * @param file the file to open
|
fiore@5
|
247 * @return a new {@code FileService.Open} instance
|
fiore@5
|
248 * @throws IOException if {@code file} cannot be found
|
fiore@5
|
249 */
|
fiore@5
|
250 public FileService.Open open(File file) throws IOException{
|
fiore@3
|
251 return new OpenImpl(file);
|
fiore@3
|
252 }
|
fiore@3
|
253
|
fiore@5
|
254 /**
|
fiore@5
|
255 * Creates a {@code FileService.Save} out of the file passed as argument.
|
fiore@5
|
256 *
|
fiore@5
|
257 * @param file the file to save
|
fiore@5
|
258 * @return a new {@code FileService.Save} instance
|
fiore@5
|
259 * @throws IOException if {@code file} cannot be found
|
fiore@5
|
260 */
|
fiore@5
|
261 public FileService.Save save(File file) throws IOException{
|
fiore@3
|
262 return new SaveImpl(file);
|
fiore@3
|
263 }
|
fiore@3
|
264 }
|
fiore@3
|
265
|
fiore@3
|
266 private static class SaveImpl implements FileService.Save{
|
fiore@3
|
267 public SaveImpl(File f) throws FileNotFoundException{
|
fiore@3
|
268 if (f != null){
|
fiore@3
|
269 path = f.getPath();
|
fiore@3
|
270 name = getFileNameFromPath(path,false);
|
fiore@3
|
271 out = new BufferedOutputStream(new FileOutputStream(f));
|
fiore@3
|
272 }
|
fiore@3
|
273 }
|
fiore@3
|
274
|
fiore@3
|
275 @Override
|
fiore@3
|
276 public String getName() { return name; }
|
fiore@3
|
277 @Override
|
fiore@3
|
278 public String getPath() {return path; }
|
fiore@3
|
279 @Override
|
fiore@3
|
280 public OutputStream getOutputStream() { return out; }
|
fiore@3
|
281
|
fiore@3
|
282 private String name;
|
fiore@3
|
283 private String path;
|
fiore@3
|
284 private OutputStream out;
|
fiore@3
|
285 }
|
fiore@3
|
286
|
fiore@5
|
287 private static class OpenImpl implements FileService.Open{
|
fiore@3
|
288 public OpenImpl(File f) throws FileNotFoundException{
|
fiore@3
|
289 if (f != null){
|
fiore@3
|
290 path = f.getPath();
|
fiore@3
|
291 name = getFileNameFromPath(path,false);
|
fiore@3
|
292 in = new BufferedInputStream(new FileInputStream(f));
|
fiore@3
|
293 }
|
fiore@3
|
294 }
|
fiore@3
|
295
|
fiore@3
|
296 @Override
|
fiore@3
|
297 public String getName() { return name; }
|
fiore@3
|
298 @Override
|
fiore@3
|
299 public String getPath() { return path; }
|
fiore@3
|
300 @Override
|
fiore@3
|
301 public InputStream getInputStream() { return in; }
|
fiore@3
|
302
|
fiore@3
|
303 private String path;
|
fiore@3
|
304 private String name;
|
fiore@3
|
305 private InputStream in;
|
fiore@3
|
306 }
|
fiore@3
|
307
|
fiore@3
|
308 /**
|
fiore@5
|
309 * Edits the file path so that it ends in the desired
|
fiore@5
|
310 * extension.
|
fiore@5
|
311 * @param original the file to use as a starting point
|
fiore@5
|
312 * @param toBeRemoved the extension that is to be
|
fiore@5
|
313 * removed before adding the desired extension. Use
|
fiore@5
|
314 * null if nothing needs to be removed.
|
fiore@5
|
315 * @param desired the desired extension (e.g. ".png"),
|
fiore@5
|
316 * or a | separated list of extensions
|
fiore@5
|
317 * @return original if it already has the desired
|
fiore@5
|
318 * extension, or a new file with the edited file path
|
fiore@3
|
319 */
|
fiore@3
|
320 public static String editExtension(String original,
|
fiore@3
|
321 String toBeRemoved, String desired){
|
fiore@3
|
322 if (original == null) return null;
|
fiore@3
|
323 int n = desired.indexOf('|');
|
fiore@3
|
324 if (n >= 0) desired = desired.substring(0, n);
|
fiore@3
|
325 String path = original;
|
fiore@3
|
326 if (!path.toLowerCase().endsWith(desired.toLowerCase())){
|
fiore@3
|
327 if (toBeRemoved != null && path.toLowerCase().endsWith(
|
fiore@3
|
328 toBeRemoved.toLowerCase()))
|
fiore@3
|
329 path = path.substring(0, path.length() - toBeRemoved.length());
|
fiore@3
|
330 path = path + desired;
|
fiore@3
|
331 }
|
fiore@3
|
332 return path;
|
fiore@3
|
333 }
|
fiore@3
|
334
|
fiore@5
|
335 /**
|
fiore@5
|
336 * Returns the single file name from a file path
|
fiore@5
|
337 *
|
fiore@5
|
338 * @param path the path to extract the file name from
|
fiore@5
|
339 * @param keepExtension whether to keep the extension of the file
|
fiore@5
|
340 * in the returned string
|
fiore@5
|
341 * @return the name of the file identified by {@code path}
|
fiore@5
|
342 */
|
fiore@3
|
343 public static String getFileNameFromPath(String path,boolean keepExtension){
|
fiore@3
|
344 int index = path.lastIndexOf(System.getProperty("file.separator"));
|
fiore@3
|
345 String name;
|
fiore@3
|
346 if(index == -1)
|
fiore@3
|
347 name = path;
|
fiore@3
|
348 else
|
fiore@3
|
349 name = path.substring(index+1);
|
fiore@3
|
350 if(!keepExtension){
|
fiore@3
|
351 index = name.lastIndexOf('.');
|
fiore@3
|
352 if(index != -1)
|
fiore@3
|
353 name = name.substring(0, index);
|
fiore@3
|
354 }
|
fiore@3
|
355 return name;
|
fiore@3
|
356 }
|
fiore@0
|
357 }
|