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