source: josm/trunk/src/org/openstreetmap/josm/actions/OpenFileAction.java@ 12867

Last change on this file since 12867 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 15.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.io.BufferedReader;
11import java.io.File;
12import java.io.IOException;
13import java.nio.charset.StandardCharsets;
14import java.nio.file.Files;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.HashSet;
20import java.util.LinkedHashSet;
21import java.util.LinkedList;
22import java.util.List;
23import java.util.Set;
24import java.util.concurrent.Future;
25import java.util.regex.Matcher;
26import java.util.regex.Pattern;
27import java.util.regex.PatternSyntaxException;
28
29import javax.swing.JOptionPane;
30import javax.swing.SwingUtilities;
31import javax.swing.filechooser.FileFilter;
32
33import org.openstreetmap.josm.Main;
34import org.openstreetmap.josm.gui.HelpAwareOptionPane;
35import org.openstreetmap.josm.gui.MainApplication;
36import org.openstreetmap.josm.gui.MapFrame;
37import org.openstreetmap.josm.gui.PleaseWaitRunnable;
38import org.openstreetmap.josm.gui.io.importexport.AllFormatsImporter;
39import org.openstreetmap.josm.gui.io.importexport.FileImporter;
40import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
41import org.openstreetmap.josm.io.OsmTransferException;
42import org.openstreetmap.josm.spi.preferences.Config;
43import org.openstreetmap.josm.tools.Logging;
44import org.openstreetmap.josm.tools.MultiMap;
45import org.openstreetmap.josm.tools.Shortcut;
46import org.openstreetmap.josm.tools.Utils;
47import org.xml.sax.SAXException;
48
49/**
50 * Open a file chooser dialog and select a file to import.
51 *
52 * @author imi
53 * @since 1146
54 */
55public class OpenFileAction extends DiskAccessAction {
56
57 /**
58 * The {@link ExtensionFileFilter} matching .url files
59 */
60 public static final ExtensionFileFilter URL_FILE_FILTER = new ExtensionFileFilter("url", "url", tr("URL Files") + " (*.url)");
61
62 /**
63 * Create an open action. The name is "Open a file".
64 */
65 public OpenFileAction() {
66 super(tr("Open..."), "open", tr("Open a file."),
67 Shortcut.registerShortcut("system:open", tr("File: {0}", tr("Open...")), KeyEvent.VK_O, Shortcut.CTRL));
68 putValue("help", ht("/Action/Open"));
69 }
70
71 @Override
72 public void actionPerformed(ActionEvent e) {
73 AbstractFileChooser fc = createAndOpenFileChooser(true, true, null);
74 if (fc == null)
75 return;
76 File[] files = fc.getSelectedFiles();
77 OpenFileTask task = new OpenFileTask(Arrays.asList(files), fc.getFileFilter());
78 task.setRecordHistory(true);
79 MainApplication.worker.submit(task);
80 }
81
82 @Override
83 protected void updateEnabledState() {
84 setEnabled(true);
85 }
86
87 /**
88 * Open a list of files. The complete list will be passed to batch importers.
89 * Filenames will not be saved in history.
90 * @param fileList A list of files
91 * @return the future task
92 * @since 11986 (return task)
93 */
94 public static Future<?> openFiles(List<File> fileList) {
95 return openFiles(fileList, false);
96 }
97
98 /**
99 * Open a list of files. The complete list will be passed to batch importers.
100 * @param fileList A list of files
101 * @param recordHistory {@code true} to save filename in history (default: false)
102 * @return the future task
103 * @since 11986 (return task)
104 */
105 public static Future<?> openFiles(List<File> fileList, boolean recordHistory) {
106 OpenFileTask task = new OpenFileTask(fileList, null);
107 task.setRecordHistory(recordHistory);
108 return MainApplication.worker.submit(task);
109 }
110
111 /**
112 * Task to open files.
113 */
114 public static class OpenFileTask extends PleaseWaitRunnable {
115 private final List<File> files;
116 private final List<File> successfullyOpenedFiles = new ArrayList<>();
117 private final Set<String> fileHistory = new LinkedHashSet<>();
118 private final Set<String> failedAll = new HashSet<>();
119 private final FileFilter fileFilter;
120 private boolean canceled;
121 private boolean recordHistory;
122
123 /**
124 * Constructs a new {@code OpenFileTask}.
125 * @param files files to open
126 * @param fileFilter file filter
127 * @param title message for the user
128 */
129 public OpenFileTask(final List<File> files, final FileFilter fileFilter, final String title) {
130 super(title, false /* don't ignore exception */);
131 this.fileFilter = fileFilter;
132 this.files = new ArrayList<>(files.size());
133 for (final File file : files) {
134 if (file.exists()) {
135 this.files.add(file);
136 } else if (file.getParentFile() != null) {
137 // try to guess an extension using the specified fileFilter
138 final File[] matchingFiles = file.getParentFile().listFiles((dir, name) ->
139 name.startsWith(file.getName()) && fileFilter != null && fileFilter.accept(new File(dir, name)));
140 if (matchingFiles != null && matchingFiles.length == 1) {
141 // use the unique match as filename
142 this.files.add(matchingFiles[0]);
143 } else {
144 // add original filename for error reporting later on
145 this.files.add(file);
146 }
147 }
148 }
149 }
150
151 /**
152 * Constructs a new {@code OpenFileTask}.
153 * @param files files to open
154 * @param fileFilter file filter
155 */
156 public OpenFileTask(List<File> files, FileFilter fileFilter) {
157 this(files, fileFilter, tr("Opening files"));
158 }
159
160 /**
161 * Sets whether to save filename in history (for list of recently opened files).
162 * @param recordHistory {@code true} to save filename in history (default: false)
163 */
164 public void setRecordHistory(boolean recordHistory) {
165 this.recordHistory = recordHistory;
166 }
167
168 /**
169 * Determines if filename must be saved in history (for list of recently opened files).
170 * @return {@code true} if filename must be saved in history
171 */
172 public boolean isRecordHistory() {
173 return recordHistory;
174 }
175
176 @Override
177 protected void cancel() {
178 this.canceled = true;
179 }
180
181 @Override
182 protected void finish() {
183 MapFrame map = MainApplication.getMap();
184 if (map != null) {
185 map.repaint();
186 }
187 }
188
189 protected void alertFilesNotMatchingWithImporter(Collection<File> files, FileImporter importer) {
190 final StringBuilder msg = new StringBuilder(128).append("<html>").append(
191 trn("Cannot open {0} file with the file importer ''{1}''.",
192 "Cannot open {0} files with the file importer ''{1}''.",
193 files.size(),
194 files.size(),
195 Utils.escapeReservedCharactersHTML(importer.filter.getDescription())
196 )
197 ).append("<br><ul>");
198 for (File f: files) {
199 msg.append("<li>").append(f.getAbsolutePath()).append("</li>");
200 }
201 msg.append("</ul></html>");
202
203 HelpAwareOptionPane.showMessageDialogInEDT(
204 Main.parent,
205 msg.toString(),
206 tr("Warning"),
207 JOptionPane.WARNING_MESSAGE,
208 ht("/Action/Open#ImporterCantImportFiles")
209 );
210 }
211
212 protected void alertFilesWithUnknownImporter(Collection<File> files) {
213 final StringBuilder msg = new StringBuilder(128).append("<html>").append(
214 trn("Cannot open {0} file because file does not exist or no suitable file importer is available.",
215 "Cannot open {0} files because files do not exist or no suitable file importer is available.",
216 files.size(),
217 files.size()
218 )
219 ).append("<br><ul>");
220 for (File f: files) {
221 msg.append("<li>").append(f.getAbsolutePath()).append(" (<i>")
222 .append(f.exists() ? tr("no importer") : tr("does not exist"))
223 .append("</i>)</li>");
224 }
225 msg.append("</ul></html>");
226
227 HelpAwareOptionPane.showMessageDialogInEDT(
228 Main.parent,
229 msg.toString(),
230 tr("Warning"),
231 JOptionPane.WARNING_MESSAGE,
232 ht("/Action/Open#MissingImporterForFiles")
233 );
234 }
235
236 @Override
237 protected void realRun() throws SAXException, IOException, OsmTransferException {
238 if (files == null || files.isEmpty()) return;
239
240 /**
241 * Find the importer with the chosen file filter
242 */
243 FileImporter chosenImporter = null;
244 if (fileFilter != null) {
245 for (FileImporter importer : ExtensionFileFilter.getImporters()) {
246 if (fileFilter.equals(importer.filter)) {
247 chosenImporter = importer;
248 }
249 }
250 }
251 /**
252 * If the filter hasn't been changed in the dialog, chosenImporter is null now.
253 * When the filter has been set explicitly to AllFormatsImporter, treat this the same.
254 */
255 if (chosenImporter instanceof AllFormatsImporter) {
256 chosenImporter = null;
257 }
258 getProgressMonitor().setTicksCount(files.size());
259
260 if (chosenImporter != null) {
261 // The importer was explicitly chosen, so use it.
262 List<File> filesNotMatchingWithImporter = new LinkedList<>();
263 List<File> filesMatchingWithImporter = new LinkedList<>();
264 for (final File f : files) {
265 if (!chosenImporter.acceptFile(f)) {
266 if (f.isDirectory()) {
267 SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(Main.parent, tr(
268 "<html>Cannot open directory ''{0}''.<br>Please select a file.</html>",
269 f.getAbsolutePath()), tr("Open file"), JOptionPane.ERROR_MESSAGE));
270 // TODO when changing to Java 6: Don't cancel the task here but use different modality. (Currently 2 dialogs
271 // would block each other.)
272 return;
273 } else {
274 filesNotMatchingWithImporter.add(f);
275 }
276 } else {
277 filesMatchingWithImporter.add(f);
278 }
279 }
280
281 if (!filesNotMatchingWithImporter.isEmpty()) {
282 alertFilesNotMatchingWithImporter(filesNotMatchingWithImporter, chosenImporter);
283 }
284 if (!filesMatchingWithImporter.isEmpty()) {
285 importData(chosenImporter, filesMatchingWithImporter);
286 }
287 } else {
288 // find appropriate importer
289 MultiMap<FileImporter, File> importerMap = new MultiMap<>();
290 List<File> filesWithUnknownImporter = new LinkedList<>();
291 List<File> urlFiles = new LinkedList<>();
292 FILES: for (File f : files) {
293 for (FileImporter importer : ExtensionFileFilter.getImporters()) {
294 if (importer.acceptFile(f)) {
295 importerMap.put(importer, f);
296 continue FILES;
297 }
298 }
299 if (URL_FILE_FILTER.accept(f)) {
300 urlFiles.add(f);
301 } else {
302 filesWithUnknownImporter.add(f);
303 }
304 }
305 if (!filesWithUnknownImporter.isEmpty()) {
306 alertFilesWithUnknownImporter(filesWithUnknownImporter);
307 }
308 List<FileImporter> importers = new ArrayList<>(importerMap.keySet());
309 Collections.sort(importers);
310 Collections.reverse(importers);
311
312 for (FileImporter importer : importers) {
313 importData(importer, new ArrayList<>(importerMap.get(importer)));
314 }
315
316 for (File urlFile: urlFiles) {
317 try (BufferedReader reader = Files.newBufferedReader(urlFile.toPath(), StandardCharsets.UTF_8)) {
318 String line;
319 while ((line = reader.readLine()) != null) {
320 Matcher m = Pattern.compile(".*(https?://.*)").matcher(line);
321 if (m.matches()) {
322 String url = m.group(1);
323 MainApplication.getMenu().openLocation.openUrl(false, url);
324 }
325 }
326 } catch (IOException | PatternSyntaxException | IllegalStateException | IndexOutOfBoundsException e) {
327 Logging.error(e);
328 }
329 }
330 }
331
332 if (recordHistory) {
333 Collection<String> oldFileHistory = Config.getPref().getList("file-open.history");
334 fileHistory.addAll(oldFileHistory);
335 // remove the files which failed to load from the list
336 fileHistory.removeAll(failedAll);
337 int maxsize = Math.max(0, Config.getPref().getInt("file-open.history.max-size", 15));
338 Main.pref.putCollectionBounded("file-open.history", maxsize, fileHistory);
339 }
340 }
341
342 /**
343 * Import data files with the given importer.
344 * @param importer file importer
345 * @param files data files to import
346 */
347 public void importData(FileImporter importer, List<File> files) {
348 if (importer.isBatchImporter()) {
349 if (canceled) return;
350 String msg = trn("Opening {0} file...", "Opening {0} files...", files.size(), files.size());
351 getProgressMonitor().setCustomText(msg);
352 getProgressMonitor().indeterminateSubTask(msg);
353 if (importer.importDataHandleExceptions(files, getProgressMonitor().createSubTaskMonitor(files.size(), false))) {
354 successfullyOpenedFiles.addAll(files);
355 }
356 } else {
357 for (File f : files) {
358 if (canceled) return;
359 getProgressMonitor().indeterminateSubTask(tr("Opening file ''{0}'' ...", f.getAbsolutePath()));
360 if (importer.importDataHandleExceptions(f, getProgressMonitor().createSubTaskMonitor(1, false))) {
361 successfullyOpenedFiles.add(f);
362 }
363 }
364 }
365 if (recordHistory && !importer.isBatchImporter()) {
366 for (File f : files) {
367 try {
368 if (successfullyOpenedFiles.contains(f)) {
369 fileHistory.add(f.getCanonicalPath());
370 } else {
371 failedAll.add(f.getCanonicalPath());
372 }
373 } catch (IOException e) {
374 Logging.warn(e);
375 }
376 }
377 }
378 }
379
380 /**
381 * Replies the list of files that have been successfully opened.
382 * @return The list of files that have been successfully opened.
383 */
384 public List<File> getSuccessfullyOpenedFiles() {
385 return successfullyOpenedFiles;
386 }
387 }
388}
Note: See TracBrowser for help on using the repository browser.