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