001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.actions; 003 004 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005 import static org.openstreetmap.josm.tools.I18n.tr; 006 import static org.openstreetmap.josm.tools.I18n.trn; 007 008 import java.awt.event.ActionEvent; 009 import java.awt.event.KeyEvent; 010 import java.io.BufferedReader; 011 import java.io.File; 012 import java.io.FileReader; 013 import java.io.IOException; 014 import java.util.ArrayList; 015 import java.util.Arrays; 016 import java.util.Collection; 017 import java.util.Collections; 018 import java.util.HashSet; 019 import java.util.LinkedHashSet; 020 import java.util.LinkedList; 021 import java.util.List; 022 import java.util.Set; 023 import java.util.regex.Matcher; 024 import java.util.regex.Pattern; 025 026 import javax.swing.JFileChooser; 027 import javax.swing.JOptionPane; 028 import javax.swing.SwingUtilities; 029 import javax.swing.filechooser.FileFilter; 030 031 import org.openstreetmap.josm.Main; 032 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 033 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 034 import org.openstreetmap.josm.gui.help.HelpUtil; 035 import org.openstreetmap.josm.io.AllFormatsImporter; 036 import org.openstreetmap.josm.io.FileImporter; 037 import org.openstreetmap.josm.io.OsmTransferException; 038 import org.openstreetmap.josm.tools.MultiMap; 039 import org.openstreetmap.josm.tools.Shortcut; 040 import org.xml.sax.SAXException; 041 042 /** 043 * Open a file chooser dialog and select an file to import. Then call the gpx-import driver. Finally 044 * open an internal frame into the main window with the gpx data shown. 045 * 046 * @author imi 047 */ 048 public class OpenFileAction extends DiskAccessAction { 049 050 public static final ExtensionFileFilter urlFileFilter = new ExtensionFileFilter("url", "url", tr("URL Files") + " (*.url)"); 051 052 /** 053 * Create an open action. The name is "Open a file". 054 */ 055 public OpenFileAction() { 056 super(tr("Open..."), "open", tr("Open a file."), 057 Shortcut.registerShortcut("system:open", tr("File: {0}", tr("Open...")), KeyEvent.VK_O, Shortcut.CTRL)); 058 putValue("help", ht("/Action/Open")); 059 060 } 061 062 public void actionPerformed(ActionEvent e) { 063 JFileChooser fc = createAndOpenFileChooser(true, true, null); 064 if (fc == null) 065 return; 066 File[] files = fc.getSelectedFiles(); 067 OpenFileTask task = new OpenFileTask(Arrays.asList(files), fc.getFileFilter()); 068 task.setRecordHistory(true); 069 Main.worker.submit(task); 070 } 071 072 @Override 073 protected void updateEnabledState() { 074 setEnabled(! Main.applet); 075 } 076 077 /** 078 * Open a list of files. The complete list will be passed to batch importers. 079 * @param fileList A list of files 080 */ 081 static public void openFiles(List<File> fileList) { 082 openFiles(fileList, false); 083 } 084 085 static public void openFiles(List<File> fileList, boolean recordHistory) { 086 OpenFileTask task = new OpenFileTask(fileList, null); 087 task.setRecordHistory(recordHistory); 088 Main.worker.submit(task); 089 } 090 091 static public class OpenFileTask extends PleaseWaitRunnable { 092 private List<File> files; 093 private List<File> successfullyOpenedFiles = new ArrayList<File>(); 094 private FileFilter fileFilter; 095 private boolean canceled; 096 private boolean recordHistory = false; 097 098 public OpenFileTask(List<File> files, FileFilter fileFilter, String title) { 099 super(title, false /* don't ignore exception */); 100 this.files = new ArrayList<File>(files); 101 this.fileFilter = fileFilter; 102 } 103 104 public OpenFileTask(List<File> files, FileFilter fileFilter) { 105 this(files, fileFilter, tr("Opening files")); 106 } 107 108 /** 109 * save filename in history (for list of recently opened files) 110 * default: false 111 */ 112 public void setRecordHistory(boolean recordHistory) { 113 this.recordHistory = recordHistory; 114 } 115 116 public boolean isRecordHistory() { 117 return recordHistory; 118 } 119 120 @Override 121 protected void cancel() { 122 this.canceled = true; 123 } 124 125 @Override 126 protected void finish() { 127 // do nothing 128 } 129 130 protected void alertFilesNotMatchingWithImporter(Collection<File> files, FileImporter importer) { 131 final StringBuffer msg = new StringBuffer(); 132 msg.append("<html>"); 133 msg.append( 134 trn( 135 "Cannot open {0} file with the file importer ''{1}''.", 136 "Cannot open {0} files with the file importer ''{1}''.", 137 files.size(), 138 files.size(), 139 importer.filter.getDescription() 140 ) 141 ).append("<br>"); 142 msg.append("<ul>"); 143 for (File f: files) { 144 msg.append("<li>").append(f.getAbsolutePath()).append("</li>"); 145 } 146 msg.append("</ul>"); 147 148 HelpAwareOptionPane.showMessageDialogInEDT( 149 Main.parent, 150 msg.toString(), 151 tr("Warning"), 152 JOptionPane.WARNING_MESSAGE, 153 HelpUtil.ht("/Action/Open#ImporterCantImportFiles") 154 ); 155 } 156 157 protected void alertFilesWithUnknownImporter(Collection<File> files) { 158 final StringBuffer msg = new StringBuffer(); 159 msg.append("<html>"); 160 msg.append( 161 trn( 162 "Cannot open {0} file because file does not exist or no suitable file importer is available.", 163 "Cannot open {0} files because files do not exist or no suitable file importer is available.", 164 files.size(), 165 files.size() 166 ) 167 ).append("<br>"); 168 msg.append("<ul>"); 169 for (File f: files) { 170 msg.append("<li>"); 171 msg.append(f.getAbsolutePath()); 172 msg.append(" (<i>"); 173 msg.append(f.exists() ? tr("no importer") : tr("does not exist")); 174 msg.append("</i>)</li>"); 175 } 176 msg.append("</ul>"); 177 178 HelpAwareOptionPane.showMessageDialogInEDT( 179 Main.parent, 180 msg.toString(), 181 tr("Warning"), 182 JOptionPane.WARNING_MESSAGE, 183 HelpUtil.ht("/Action/Open#MissingImporterForFiles") 184 ); 185 } 186 187 @Override 188 protected void realRun() throws SAXException, IOException, OsmTransferException { 189 if (files == null || files.isEmpty()) return; 190 191 /** 192 * Find the importer with the chosen file filter 193 */ 194 FileImporter chosenImporter = null; 195 for (FileImporter importer : ExtensionFileFilter.importers) { 196 if (fileFilter == importer.filter) { 197 chosenImporter = importer; 198 } 199 } 200 /** 201 * If the filter hasn't been changed in the dialog, chosenImporter is null now. 202 * When the filter has been set explicitly to AllFormatsImporter, treat this the same. 203 */ 204 if (chosenImporter instanceof AllFormatsImporter) { 205 chosenImporter = null; 206 } 207 getProgressMonitor().setTicksCount(files.size()); 208 209 if (chosenImporter != null) { 210 // The importer was explicitly chosen, so use it. 211 List<File> filesNotMatchingWithImporter = new LinkedList<File>(); 212 List<File> filesMatchingWithImporter = new LinkedList<File>(); 213 for (final File f : files) { 214 if (!chosenImporter.acceptFile(f)) { 215 if (f.isDirectory()) { 216 SwingUtilities.invokeLater(new Runnable() { 217 public void run() { 218 JOptionPane.showMessageDialog(Main.parent, tr( 219 "<html>Cannot open directory ''{0}''.<br>Please select a file.</html>", 220 f.getAbsolutePath()), tr("Open file"), JOptionPane.ERROR_MESSAGE); 221 } 222 }); 223 // TODO when changing to Java 6: Don't cancel the 224 // task here but use different modality. (Currently 2 dialogs 225 // would block each other.) 226 return; 227 } else { 228 filesNotMatchingWithImporter.add(f); 229 } 230 } else { 231 filesMatchingWithImporter.add(f); 232 } 233 } 234 235 if (!filesNotMatchingWithImporter.isEmpty()) { 236 alertFilesNotMatchingWithImporter(filesNotMatchingWithImporter, chosenImporter); 237 } 238 if (!filesMatchingWithImporter.isEmpty()) { 239 importData(chosenImporter, filesMatchingWithImporter); 240 } 241 } else { 242 // find appropriate importer 243 MultiMap<FileImporter, File> importerMap = new MultiMap<FileImporter, File>(); 244 List<File> filesWithUnknownImporter = new LinkedList<File>(); 245 List<File> urlFiles = new LinkedList<File>(); 246 FILES: for (File f : files) { 247 for (FileImporter importer : ExtensionFileFilter.importers) { 248 if (importer.acceptFile(f)) { 249 importerMap.put(importer, f); 250 continue FILES; 251 } 252 } 253 if (urlFileFilter.accept(f)) { 254 urlFiles.add(f); 255 } else { 256 filesWithUnknownImporter.add(f); 257 } 258 } 259 if (!filesWithUnknownImporter.isEmpty()) { 260 alertFilesWithUnknownImporter(filesWithUnknownImporter); 261 } 262 List<FileImporter> importers = new ArrayList<FileImporter>(importerMap.keySet()); 263 Collections.sort(importers); 264 Collections.reverse(importers); 265 266 Set<String> fileHistory = new LinkedHashSet<String>(); 267 Set<String> failedAll = new HashSet<String>(); 268 269 for (FileImporter importer : importers) { 270 List<File> files = new ArrayList<File>(importerMap.get(importer)); 271 importData(importer, files); 272 // suppose all files will fail to load 273 List<File> failedFiles = new ArrayList<File>(files); 274 275 if (recordHistory && !importer.isBatchImporter()) { 276 // remove the files which didn't fail to load from the failed list 277 failedFiles.removeAll(successfullyOpenedFiles); 278 for (File f : successfullyOpenedFiles) { 279 fileHistory.add(f.getCanonicalPath()); 280 } 281 for (File f : failedFiles) { 282 failedAll.add(f.getCanonicalPath()); 283 } 284 } 285 } 286 287 for (File urlFile: urlFiles) { 288 try { 289 BufferedReader reader = new BufferedReader(new FileReader(urlFile)); 290 String line; 291 while ((line = reader.readLine()) != null) { 292 Matcher m = Pattern.compile(".*(http://.*)").matcher(line); 293 if (m.matches()) { 294 String url = m.group(1); 295 Main.main.menu.openLocation.openUrl(false, url); 296 } 297 } 298 reader.close(); 299 } catch (Exception e) { 300 System.err.println(e.getMessage()); 301 } 302 } 303 304 if (recordHistory) { 305 Collection<String> oldFileHistory = Main.pref.getCollection("file-open.history"); 306 fileHistory.addAll(oldFileHistory); 307 // remove the files which failed to load from the list 308 fileHistory.removeAll(failedAll); 309 int maxsize = Math.max(0, Main.pref.getInteger("file-open.history.max-size", 15)); 310 Main.pref.putCollectionBounded("file-open.history", maxsize, fileHistory); 311 } 312 } 313 } 314 315 public void importData(FileImporter importer, List<File> files) { 316 if (importer.isBatchImporter()) { 317 if (canceled) return; 318 String msg = trn("Opening {0} file...", "Opening {0} files...", files.size(), files.size()); 319 getProgressMonitor().setCustomText(msg); 320 getProgressMonitor().indeterminateSubTask(msg); 321 if (importer.importDataHandleExceptions(files, getProgressMonitor().createSubTaskMonitor(files.size(), false))) { 322 successfullyOpenedFiles.addAll(files); 323 } 324 } else { 325 for (File f : files) { 326 if (canceled) return; 327 getProgressMonitor().indeterminateSubTask(tr("Opening file ''{0}'' ...", f.getAbsolutePath())); 328 if (importer.importDataHandleExceptions(f, getProgressMonitor().createSubTaskMonitor(1, false))) { 329 successfullyOpenedFiles.add(f); 330 } 331 } 332 } 333 } 334 335 public List<File> getSuccessfullyOpenedFiles() { 336 return successfullyOpenedFiles; 337 } 338 } 339 }