001package jmri.jmrit.catalog; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.awt.Color; 006import java.awt.event.ActionEvent; 007import java.awt.event.ActionListener; 008import java.io.File; 009 010import javax.swing.BorderFactory; 011import javax.swing.BoxLayout; 012import javax.swing.JFileChooser; 013import javax.swing.JFrame; 014import javax.swing.JLabel; 015import javax.swing.JPanel; 016import javax.swing.filechooser.FileNameExtensionFilter; 017import javax.swing.filechooser.FileSystemView; 018 019import jmri.CatalogTreeManager; 020import jmri.InstanceManager; 021import jmri.InstanceManagerAutoDefault; 022import jmri.util.ThreadingUtil; 023import jmri.util.swing.JmriJOptionPane; 024 025import org.apache.commons.io.FilenameUtils; 026 027/** 028 * A file system directory searcher to locate Image files to include in an Image 029 * Catalog. 030 * 031 * @author Pete Cressman Copyright 2010 032 */ 033public class DirectorySearcher implements InstanceManagerAutoDefault { 034 035 // For choosing image directories 036 private JFileChooser _directoryChooser = null; 037 038 PreviewDialog _previewDialog = null; 039 Seacher _searcher; 040 JFrame _waitDialog; 041 JLabel _waitText; 042 043 public DirectorySearcher() { 044 } 045 046 public static DirectorySearcher instance() { 047 return InstanceManager.getDefault(DirectorySearcher.class); 048 } 049 050 /** 051 * Open file anywhere in the file system and let the user decide whether to 052 * add it to the Catalog. 053 * 054 * @param msg Bundle property key (string) for i18n title string 055 * @param recurse if directory choice has no images, set chooser to sub 056 * directory so user can continue looking 057 * @return chosen directory or null to cancel operation 058 */ 059 @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification="false postive, guarded by logic") 060 private File getDirectory(String msg, boolean recurse) { 061 if (_directoryChooser == null) { 062 _directoryChooser = new jmri.util.swing.JmriJFileChooser(FileSystemView.getFileSystemView()); 063 _directoryChooser.setFileFilter(new FileNameExtensionFilter("Graphics Files", CatalogTreeManager.IMAGE_FILTER)); // NOI18N 064 } 065 _directoryChooser.setDialogTitle(Bundle.getMessage(msg)); 066 _directoryChooser.rescanCurrentDirectory(); 067 _directoryChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 068 069 while (true) { 070 int retVal = _directoryChooser.showOpenDialog(null); 071 if (retVal != JFileChooser.APPROVE_OPTION) { 072 return null; // give up if no file selected 073 } 074 File dir = _directoryChooser.getSelectedFile(); 075 if (dir != null) { 076 if (!recurse) { 077 return dir; 078 } 079 int cnt = numImageFiles(dir); 080 if (cnt > 0) { 081 return dir; 082 } else { 083 int choice = JmriJOptionPane.showOptionDialog(null, 084 Bundle.getMessage("NoImagesInDir", dir), Bundle.getMessage("QuestionTitle"), 085 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 086 new String[]{Bundle.getMessage("ButtonStop"), Bundle.getMessage("ButtonKeepLooking")}, 1); 087 switch (choice) { 088 case 0: // stop 089 return null; 090 case 1: // keep looking 091 _directoryChooser.setCurrentDirectory(dir); 092 break; 093 default: 094 return dir; 095 } 096 } 097 } 098 } 099 } 100 101 protected static int numImageFiles(File dir) { 102 File[] files = dir.listFiles(); 103 if (files == null) { 104 return 0; 105 } 106 int count = 0; 107 for (int i = 0; i < files.length; i++) { 108 String ext = FilenameUtils.getExtension(files[i].getName()); 109 for (int k = 0; k < CatalogTreeManager.IMAGE_FILTER.length; k++) { 110 if (ext != null && ext.equalsIgnoreCase(CatalogTreeManager.IMAGE_FILTER[k])) { 111 count++; // OK directory has image files 112 } 113 } 114 } 115 return count; 116 } 117 118 private void showWaitFrame(String msgkey, File dir) { 119 if (_waitDialog == null) { 120 _waitDialog = new JFrame(); 121 _waitDialog.setUndecorated(true); 122 JPanel panel = new JPanel(); 123 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 124 panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2, true)); 125 panel.add(new JLabel(Bundle.getMessage("waitWarning"))); 126 127 _waitText = new JLabel(); 128 panel.add(_waitText); 129 panel.setBackground(_waitText.getBackground()); 130 131 _waitDialog.getContentPane().add(panel); 132 _waitDialog.setLocationRelativeTo(null); 133 _waitDialog.setVisible(false); 134 } 135 if (dir != null) { 136 _waitText.setText(Bundle.getMessage(msgkey, dir.getName())); 137 _waitDialog.setVisible(true); 138 _waitDialog.pack(); 139 _waitDialog.toFront(); 140 } 141 } 142 143 private void closeWaitFrame() { 144 if (_waitDialog != null) { 145 _waitDialog.dispose(); 146 _waitDialog = null; 147 } 148 } 149 150 private void clearSearch() { 151 if (_previewDialog != null) { 152 _previewDialog.dispose(); 153 } 154 if (_searcher != null) { 155 synchronized (_searcher) { 156 _searcher.notify(); 157 } 158 } 159 160 } 161 162 /** 163 * Open one directory. 164 * 165 */ 166 public void openDirectory() { 167 clearSearch(); 168 File dir = getDirectory("openDirMenu", true); // NOI18N 169 if (dir != null) { 170 doPreviewDialog(dir, new MActionListener(dir, true), 171 null, new CActionListener(), 0); 172 closeWaitFrame(); 173 } 174 } 175 176 public void searchFS() { 177 clearSearch(); 178 File dir = getDirectory("searchFSMenu", false); // NOI18N 179 showWaitFrame("searchWait", dir); 180 if (dir != null) { 181 _searcher = new Seacher(dir); 182 _searcher.start(); 183 } 184 } 185 186 void searcherDone(File dir, int count) { 187 if (_previewDialog != null) { 188 _previewDialog.dispose(); 189 } 190 closeWaitFrame(); 191 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("numFound", count, dir.getAbsolutePath()), 192 Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE); 193 } 194 195 class Seacher extends Thread { 196 197 File dir; 198 boolean quit = false; 199 int count; 200 201 Seacher(File d) { 202 dir = d; 203 } 204 205 void quit() { 206 quit = true; 207 } 208 209 @Override 210 public void run() { 211 getImageDirectory(dir, CatalogTreeManager.IMAGE_FILTER); 212 if (log.isDebugEnabled()) { 213 log.debug("Searcher done for directory {} quit={}", dir.getAbsolutePath(), quit); 214 } 215 ThreadingUtil.runOnGUI(() -> { 216 searcherDone(dir, count); 217 }); 218 } 219 220 /** 221 * Find a Directory with image files. 222 * <p> 223 * This waits on completion of the PrivateDialong (which is itself not modal) 224 * so must not be called on the Layout or GUI threads 225 * 226 * @param dir directory 227 * @param filter file filter for images 228 */ 229 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = {"WA_NOT_IN_LOOP", "UW_UNCOND_WAIT"}, justification="Waiting for single possible event") 230 private void getImageDirectory(File dir, String[] filter) { 231 if (jmri.util.ThreadingUtil.isGUIThread() || jmri.util.ThreadingUtil.isLayoutThread()) log.error("getImageDirectory called on wrong thread"); 232 233 File[] files = dir.listFiles(); 234 if (files == null || quit) { 235 // no sub directories 236 return; 237 } 238 int cnt = numImageFiles(dir); 239 if (log.isDebugEnabled()) { 240 log.debug("getImageDirectory dir= {} has {} files", dir.getAbsolutePath(), cnt); 241 } 242 count += cnt; 243 if (cnt > 0) { 244 ThreadingUtil.runOnGUI(() -> { 245 doPreviewDialog(dir, new MActionListener(dir, false), 246 new LActionListener(dir), new CActionListener(), 0); 247 }); 248 // Since PreviewDialog is not modal, wait until user clicks a button to continue 249 synchronized (this) { 250 try { 251 wait(); 252 } catch (InterruptedException ie) { 253 log.error("InterruptedException at _waitForSync", ie); 254 } catch (java.lang.IllegalArgumentException iae) { 255 log.error("Illegal argument getting Image Directory", iae); 256 } 257 } 258 } 259 for (int k = 0; k < files.length; k++) { 260 if (files[k].isDirectory()) { 261 if (quit) { 262 return; 263 } 264 File f = files[k]; 265 ThreadingUtil.runOnGUI(() -> { 266 showWaitFrame("searchWait", f); 267 }); 268// if (log.isDebugEnabled()) log.debug("getImageDirectory SubDir= {} of {} has {} files", 269// files[k].getName(), dir.getName(), numImageFiles(files[k])); 270 getImageDirectory(files[k], filter); 271 } 272 } 273 } 274 } 275 276 // More action. Directory dir has too many icons - display in separate windows 277 class MActionListener implements ActionListener { 278 279 File dir; 280 boolean oneDir; 281 282 public MActionListener(File d, boolean o) { 283 dir = d; 284 oneDir = o; 285 } 286 287 @Override 288 public void actionPerformed(ActionEvent a) { 289 displayMore(dir, oneDir); 290 } 291 } 292 293 // Continue looking for images 294 class LActionListener implements ActionListener { 295 296 File dir; 297 298 public LActionListener(File d) { 299 dir = d; 300 } 301 302 @Override 303 public void actionPerformed(ActionEvent a) { 304 keepLooking(dir); 305 } 306 } 307 308 // Cancel - Quit 309 class CActionListener implements ActionListener { 310 311 @Override 312 public void actionPerformed(ActionEvent a) { 313 cancelLooking(); 314 } 315 } 316 317 private void doPreviewDialog(File dir, ActionListener moreAction, 318 ActionListener lookAction, ActionListener cancelAction, int startNum) { 319 showWaitFrame("previewWait", dir); 320 if (log.isDebugEnabled()) { 321 log.debug("doPreviewDialog dir= {}", dir.getAbsolutePath()); 322 } 323 324 _previewDialog = new PreviewDialog(null, "previewDir", dir, CatalogTreeManager.IMAGE_FILTER); 325 _previewDialog.init(moreAction, lookAction, cancelAction, startNum); 326 _waitDialog.setVisible(false); 327 } 328 329 private void displayMore(File dir, boolean oneDir) { 330 if (log.isDebugEnabled()) { 331 log.debug("displayMore: dir= {} has {} files", dir.getName(), numImageFiles(dir)); 332 } 333 if (_previewDialog != null) { 334 int numFilesShown = _previewDialog.getNumFilesShown(); 335 ActionListener lookAction = _previewDialog.getLookActionListener(); 336 _previewDialog.dispose(); 337 if (numFilesShown > 0) { 338 doPreviewDialog(dir, new MActionListener(dir, oneDir), 339 lookAction, new CActionListener(), numFilesShown); 340 } 341 342 } else { 343 synchronized (_searcher) { 344 _searcher.notify(); 345 } 346 } 347 } 348 349 private void keepLooking(File dir) { 350 if (log.isDebugEnabled()) { 351 log.debug("keepLooking: dir= {} has {} files", dir.getName(), numImageFiles(dir)); 352 } 353 if (_previewDialog != null) { 354 _previewDialog.dispose(); 355 _previewDialog = null; 356 } 357 if (_searcher != null) { 358 synchronized (_searcher) { 359 _searcher.notify(); 360 } 361 } 362 } 363 364 private void cancelLooking() { 365 closeWaitFrame(); 366 if (_previewDialog != null) { 367 _previewDialog.dispose(); 368 _previewDialog = null; 369 } 370 if (_searcher != null) { 371 synchronized (_searcher) { 372 _searcher.quit(); 373 _searcher.notify(); 374 } 375 } 376 } 377 378 public void close() { 379 closeWaitFrame(); 380 cancelLooking(); 381 } 382 383 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DirectorySearcher.class); 384}