Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.io

File JalviewFileChooser.java

 

Coverage histogram

../../img/srcFileCovDistChart4.png
44% of files have more coverage

Code metrics

70
172
26
2
642
446
72
0.42
6.62
13
2.77

Classes

Class Line # Actions
JalviewFileChooser 68 147 66
0.273504327.4%
JalviewFileChooser.RecentlyOpened 524 25 6
0.8529411685.3%
 

Contributing tests

This file is covered by 11 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    //////////////////////////////////////////////////////////////////
22    package jalview.io;
23   
24    import jalview.bin.Cache;
25    import jalview.gui.JvOptionPane;
26    import jalview.util.MessageManager;
27    import jalview.util.Platform;
28    import jalview.util.dialogrunner.DialogRunnerI;
29   
30    import java.awt.Component;
31    import java.awt.Dimension;
32    import java.awt.EventQueue;
33    import java.awt.HeadlessException;
34    import java.awt.event.ActionEvent;
35    import java.awt.event.ActionListener;
36    import java.awt.event.MouseAdapter;
37    import java.awt.event.MouseEvent;
38    import java.beans.PropertyChangeEvent;
39    import java.beans.PropertyChangeListener;
40    import java.io.File;
41    import java.util.ArrayList;
42    import java.util.HashMap;
43    import java.util.List;
44    import java.util.Map;
45    import java.util.StringTokenizer;
46    import java.util.Vector;
47   
48    import javax.swing.BoxLayout;
49    import javax.swing.DefaultListCellRenderer;
50    import javax.swing.JCheckBox;
51    import javax.swing.JFileChooser;
52    import javax.swing.JList;
53    import javax.swing.JPanel;
54    import javax.swing.JScrollPane;
55    import javax.swing.SpringLayout;
56    import javax.swing.filechooser.FileFilter;
57    import javax.swing.plaf.basic.BasicFileChooserUI;
58   
59    /**
60    * Enhanced file chooser dialog box.
61    *
62    * NOTE: bug on Windows systems when filechooser opened on directory to view
63    * files with colons in title.
64    *
65    * @author AMW
66    *
67    */
 
68    public class JalviewFileChooser extends JFileChooser implements DialogRunnerI,
69    PropertyChangeListener
70    {
71    private static final long serialVersionUID = 1L;
72   
73    private Map<Object, Runnable> callbacks = new HashMap<>();
74   
75    File selectedFile = null;
76   
77    /**
78    * backupfilesCheckBox = "Include backup files" checkbox includeBackupfiles =
79    * flag set by checkbox
80    */
81    private JCheckBox backupfilesCheckBox = null;
82   
83    protected boolean includeBackupFiles = false;
84   
85    /**
86    * Factory method to return a file chooser that offers readable alignment file
87    * formats
88    *
89    * @param directory
90    * @param selected
91    * @return
92    */
 
93  18 toggle public static JalviewFileChooser forRead(String directory,
94    String selected)
95    {
96  18 return JalviewFileChooser.forRead(directory, selected, false);
97    }
98   
 
99  18 toggle public static JalviewFileChooser forRead(String directory,
100    String selected, boolean allowBackupFiles)
101    {
102  18 List<String> extensions = new ArrayList<>();
103  18 List<String> descs = new ArrayList<>();
104  18 for (FileFormatI format : FileFormats.getInstance().getFormats())
105    {
106  342 if (format.isReadable())
107    {
108  306 extensions.add(format.getExtensions());
109  306 descs.add(format.getName());
110    }
111    }
112   
113  18 return new JalviewFileChooser(directory,
114    extensions.toArray(new String[extensions.size()]),
115    descs.toArray(new String[descs.size()]), selected, true,
116    allowBackupFiles);
117    }
118   
119    /**
120    * Factory method to return a file chooser that offers writable alignment file
121    * formats
122    *
123    * @param directory
124    * @param selected
125    * @return
126    */
 
127  0 toggle public static JalviewFileChooser forWrite(String directory,
128    String selected)
129    {
130    // TODO in Java 8, forRead and forWrite can be a single method
131    // with a lambda expression parameter for isReadable/isWritable
132  0 List<String> extensions = new ArrayList<>();
133  0 List<String> descs = new ArrayList<>();
134  0 for (FileFormatI format : FileFormats.getInstance().getFormats())
135    {
136  0 if (format.isWritable())
137    {
138  0 extensions.add(format.getExtensions());
139  0 descs.add(format.getName());
140    }
141    }
142  0 return new JalviewFileChooser(directory,
143    extensions.toArray(new String[extensions.size()]),
144    descs.toArray(new String[descs.size()]), selected, false);
145    }
146   
 
147  0 toggle public JalviewFileChooser(String dir)
148    {
149  0 super(safePath(dir));
150  0 setAccessory(new RecentlyOpened());
151    }
152   
 
153  0 toggle public JalviewFileChooser(String dir, String[] suffix, String[] desc,
154    String selected)
155    {
156  0 this(dir, suffix, desc, selected, true);
157    }
158   
159    /**
160    * Constructor for a single choice of file extension and description
161    *
162    * @param extension
163    * @param desc
164    */
 
165  0 toggle public JalviewFileChooser(String extension, String desc)
166    {
167  0 this(Cache.getProperty("LAST_DIRECTORY"), new String[] { extension },
168    new String[]
169    { desc }, desc, true);
170    }
171   
 
172  0 toggle JalviewFileChooser(String dir, String[] extensions, String[] descs,
173    String selected, boolean acceptAny)
174    {
175  0 this(dir, extensions, descs, selected, acceptAny, false);
176    }
177   
 
178  18 toggle public JalviewFileChooser(String dir, String[] extensions, String[] descs,
179    String selected, boolean acceptAny, boolean allowBackupFiles)
180    {
181  18 super(safePath(dir));
182  18 if (extensions.length == descs.length)
183    {
184  18 List<String[]> formats = new ArrayList<>();
185  324 for (int i = 0; i < extensions.length; i++)
186    {
187  306 formats.add(new String[] { extensions[i], descs[i] });
188    }
189  18 init(formats, selected, acceptAny, allowBackupFiles);
190    }
191    else
192    {
193  0 System.err.println("JalviewFileChooser arguments mismatch: "
194    + extensions + ", " + descs);
195    }
196    }
197   
 
198  18 toggle private static File safePath(String dir)
199    {
200  18 if (dir == null)
201    {
202  1 return null;
203    }
204   
205  17 File f = new File(dir);
206  17 if (f.getName().indexOf(':') > -1)
207    {
208  0 return null;
209    }
210  17 return f;
211    }
212   
213    /**
214    * Overridden for JalviewJS compatibility: only one thread in Javascript,
215    * so we can't wait for user choice in another thread and then perform the
216    * desired action
217    */
 
218  0 toggle @Override
219    public int showOpenDialog(Component parent)
220    {
221  0 int value = super.showOpenDialog(this);
222   
223  0 if (!Platform.isJS())
224    /**
225    * Java only
226    *
227    * @j2sIgnore
228    */
229    {
230    /*
231    * code here is not run in JalviewJS, instead
232    * propertyChange() is called for dialog action
233    */
234  0 handleResponse(value);
235    }
236  0 return value;
237    }
238   
239    /**
240    *
241    * @param formats
242    * a list of {extensions, description} for each file format
243    * @param selected
244    * @param acceptAny
245    * if true, 'any format' option is included
246    */
 
247  0 toggle void init(List<String[]> formats, String selected, boolean acceptAny)
248    {
249  0 init(formats, selected, acceptAny, false);
250    }
251   
 
252  18 toggle void init(List<String[]> formats, String selected, boolean acceptAny,
253    boolean allowBackupFiles)
254    {
255   
256  18 JalviewFileFilter chosen = null;
257   
258    // SelectAllFilter needs to be set first before adding further
259    // file filters to fix bug on Mac OSX
260  18 setAcceptAllFileFilterUsed(acceptAny);
261   
262  18 for (String[] format : formats)
263    {
264  306 JalviewFileFilter jvf = new JalviewFileFilter(format[0], format[1]);
265  306 if (allowBackupFiles)
266    {
267  0 jvf.setParentJFC(this);
268    }
269  306 addChoosableFileFilter(jvf);
270  306 if ((selected != null) && selected.equalsIgnoreCase(format[1]))
271    {
272  18 chosen = jvf;
273    }
274    }
275   
276  18 if (chosen != null)
277    {
278  18 setFileFilter(chosen);
279    }
280   
281  18 if (allowBackupFiles)
282    {
283  0 JPanel multi = new JPanel();
284  0 multi.setLayout(new BoxLayout(multi, BoxLayout.PAGE_AXIS));
285  0 if (backupfilesCheckBox == null)
286    {
287  0 try {
288  0 includeBackupFiles = Boolean.parseBoolean(
289    Cache.getProperty(BackupFiles.NS + "_FC_INCLUDE"));
290    } catch (Exception e)
291    {
292  0 includeBackupFiles = false;
293    }
294  0 backupfilesCheckBox = new JCheckBox(
295    MessageManager.getString("label.include_backup_files"),
296    includeBackupFiles);
297  0 backupfilesCheckBox.setAlignmentX(Component.CENTER_ALIGNMENT);
298  0 JalviewFileChooser jfc = this;
299  0 backupfilesCheckBox.addActionListener(new ActionListener()
300    {
 
301  0 toggle @Override
302    public void actionPerformed(ActionEvent e)
303    {
304  0 includeBackupFiles = backupfilesCheckBox.isSelected();
305  0 Cache.setProperty(BackupFiles.NS + "_FC_INCLUDE",
306    String.valueOf(includeBackupFiles));
307   
308  0 FileFilter f = jfc.getFileFilter();
309    // deselect the selected file if it's no longer choosable
310  0 File selectedFile = jfc.getSelectedFile();
311  0 if (selectedFile != null && !f.accept(selectedFile))
312    {
313  0 jfc.setSelectedFile(null);
314    }
315    // fake the OK button changing (to force it to upate)
316  0 String s = jfc.getApproveButtonText();
317  0 jfc.firePropertyChange(
318    APPROVE_BUTTON_TEXT_CHANGED_PROPERTY, null, s);
319    // fake the file filter changing (its behaviour actually has)
320  0 jfc.firePropertyChange(FILE_FILTER_CHANGED_PROPERTY, null, f);
321   
322  0 jfc.rescanCurrentDirectory();
323  0 jfc.revalidate();
324  0 jfc.repaint();
325    }
326    });
327    }
328  0 multi.add(new RecentlyOpened());
329  0 multi.add(backupfilesCheckBox);
330  0 setAccessory(multi);
331    }
332    else
333    {
334    // set includeBackupFiles=false to avoid other file choosers from picking
335    // up backup files (Just In Case)
336  18 includeBackupFiles = false;
337  18 setAccessory(new RecentlyOpened());
338    }
339    }
340   
 
341  90 toggle @Override
342    public void setFileFilter(javax.swing.filechooser.FileFilter filter)
343    {
344  90 super.setFileFilter(filter);
345   
346  90 try
347    {
348  90 if (getUI() instanceof BasicFileChooserUI)
349    {
350  90 final BasicFileChooserUI fcui = (BasicFileChooserUI) getUI();
351  90 final String name = fcui.getFileName().trim();
352   
353  90 if ((name == null) || (name.length() == 0))
354    {
355  90 return;
356    }
357   
358  0 EventQueue.invokeLater(new Thread()
359    {
 
360  0 toggle @Override
361    public void run()
362    {
363  0 String currentName = fcui.getFileName();
364  0 if ((currentName == null) || (currentName.length() == 0))
365    {
366  0 fcui.setFileName(name);
367    }
368    }
369    });
370    }
371    } catch (Exception ex)
372    {
373  0 ex.printStackTrace();
374    // Some platforms do not have BasicFileChooserUI
375    }
376    }
377   
378    /**
379    * Returns the selected file format, or null if none selected
380    *
381    * @return
382    */
 
383  0 toggle public FileFormatI getSelectedFormat()
384    {
385  0 if (getFileFilter() == null)
386    {
387  0 return null;
388    }
389   
390    /*
391    * logic here depends on option description being formatted as
392    * formatName (extension, extension...)
393    * or the 'no option selected' value
394    * All Files
395    * @see JalviewFileFilter.getDescription
396    */
397  0 String format = getFileFilter().getDescription();
398  0 int parenPos = format.indexOf("(");
399  0 if (parenPos > 0)
400    {
401  0 format = format.substring(0, parenPos).trim();
402  0 try
403    {
404  0 return FileFormats.getInstance().forName(format);
405    } catch (IllegalArgumentException e)
406    {
407  0 System.err.println("Unexpected format: " + format);
408    }
409    }
410  0 return null;
411    }
412   
 
413  18 toggle @Override
414    public File getSelectedFile()
415    {
416  18 File f = super.getSelectedFile();
417  18 return f == null ? selectedFile : f;
418    }
419   
 
420  0 toggle @Override
421    public int showSaveDialog(Component parent) throws HeadlessException
422    {
423  0 this.setAccessory(null);
424    // Java 9,10,11 on OSX - clear selected file so name isn't auto populated
425  0 this.setSelectedFile(null);
426   
427  0 return super.showSaveDialog(parent);
428    }
429   
430    /**
431    * If doing a Save, and an existing file is chosen or entered, prompt for
432    * confirmation of overwrite. Proceed if Yes, else leave the file chooser
433    * open.
434    *
435    * @see https://stackoverflow.com/questions/8581215/jfilechooser-and-checking-for-overwrite
436    */
 
437  0 toggle @Override
438    public void approveSelection()
439    {
440  0 if (getDialogType() != SAVE_DIALOG)
441    {
442  0 super.approveSelection();
443  0 return;
444    }
445   
446  0 selectedFile = getSelectedFile();
447   
448  0 if (selectedFile == null)
449    {
450    // Workaround for Java 9,10 on OSX - no selected file, but there is a
451    // filename typed in
452  0 try
453    {
454  0 String filename = ((BasicFileChooserUI) getUI()).getFileName();
455  0 if (filename != null && filename.length() > 0)
456    {
457  0 selectedFile = new File(getCurrentDirectory(), filename);
458    }
459    } catch (Throwable x)
460    {
461  0 System.err.println(
462    "Unexpected exception when trying to get filename.");
463  0 x.printStackTrace();
464    }
465    // TODO: ENSURE THAT FILES SAVED WITH A ':' IN THE NAME ARE REFUSED AND
466    // THE
467    // USER PROMPTED FOR A NEW FILENAME
468    }
469   
470  0 if (selectedFile == null)
471    {
472  0 return;
473    }
474   
475  0 if (getFileFilter() instanceof JalviewFileFilter)
476    {
477  0 JalviewFileFilter jvf = (JalviewFileFilter) getFileFilter();
478   
479  0 if (!jvf.accept(selectedFile))
480    {
481  0 String withExtension = getSelectedFile().getName() + "."
482    + jvf.getAcceptableExtension();
483  0 selectedFile = (new File(getCurrentDirectory(), withExtension));
484  0 setSelectedFile(selectedFile);
485    }
486    }
487   
488  0 if (selectedFile.exists())
489    {
490  0 int confirm = JvOptionPane.showConfirmDialog(this,
491    MessageManager.getString("label.overwrite_existing_file"),
492    MessageManager.getString("label.file_already_exists"),
493    JvOptionPane.YES_NO_OPTION);
494   
495  0 if (confirm != JvOptionPane.YES_OPTION)
496    {
497  0 return;
498    }
499    }
500   
501  0 super.approveSelection();
502    }
503   
 
504  0 toggle void recentListSelectionChanged(Object selection)
505    {
506  0 setSelectedFile(null);
507  0 if (selection != null)
508    {
509  0 File file = new File((String) selection);
510  0 if (getFileFilter() instanceof JalviewFileFilter)
511    {
512  0 JalviewFileFilter jvf = (JalviewFileFilter) this.getFileFilter();
513   
514  0 if (!jvf.accept(file))
515    {
516  0 setFileFilter(getChoosableFileFilters()[0]);
517    }
518    }
519   
520  0 setSelectedFile(file);
521    }
522    }
523   
 
524    class RecentlyOpened extends JPanel
525    {
526    private static final long serialVersionUID = 1L;
527    JList<String> list;
528   
 
529  18 toggle RecentlyOpened()
530    {
531  18 setPreferredSize(new Dimension(300,100));
532  18 String historyItems = Cache.getProperty("RECENT_FILE");
533  18 StringTokenizer st;
534  18 Vector<String> recent = new Vector<>();
535   
536  18 if (historyItems != null)
537    {
538  18 st = new StringTokenizer(historyItems, "\t");
539   
540  70 while (st.hasMoreTokens())
541    {
542  52 recent.addElement(st.nextToken());
543    }
544    }
545   
546  18 list = new JList<>(recent);
547   
548  18 DefaultListCellRenderer dlcr = new DefaultListCellRenderer();
549  18 dlcr.setHorizontalAlignment(DefaultListCellRenderer.RIGHT);
550  18 list.setCellRenderer(dlcr);
551   
552  18 list.addMouseListener(new MouseAdapter()
553    {
 
554  0 toggle @Override
555    public void mousePressed(MouseEvent evt)
556    {
557  0 recentListSelectionChanged(list.getSelectedValue());
558    }
559    });
560   
561  18 this.setBorder(new javax.swing.border.TitledBorder(
562    MessageManager.getString("label.recently_opened")));
563   
564  18 final JScrollPane scroller = new JScrollPane(list);
565   
566  18 SpringLayout layout = new SpringLayout();
567  18 layout.putConstraint(SpringLayout.WEST, scroller, 5,
568    SpringLayout.WEST, this);
569  18 layout.putConstraint(SpringLayout.NORTH, scroller, 5,
570    SpringLayout.NORTH, this);
571   
572  18 if (Platform.isAMacAndNotJS())
573    {
574  0 scroller.setPreferredSize(new Dimension(500, 100));
575    }
576    else
577    {
578  18 scroller.setPreferredSize(new Dimension(530, 200));
579    }
580   
581  18 this.add(scroller);
582   
583  18 javax.swing.SwingUtilities.invokeLater(new Runnable()
584    {
 
585  18 toggle @Override
586    public void run()
587    {
588  18 scroller.getHorizontalScrollBar()
589    .setValue(scroller.getHorizontalScrollBar().getMaximum());
590    }
591    });
592   
593    }
594   
595    }
596   
 
597  0 toggle @Override
598    public DialogRunnerI setResponseHandler(Object response, Runnable action)
599    {
600  0 callbacks.put(response, action);
601  0 return this;
602    }
603   
 
604  0 toggle @Override
605    public void handleResponse(Object response)
606    {
607    /*
608    * this test is for NaN in Chrome
609    */
610  0 if (response != null && !response.equals(response))
611    {
612  0 return;
613    }
614  0 Runnable action = callbacks.get(response);
615  0 if (action != null)
616    {
617  0 action.run();
618    }
619    }
620   
621    /**
622    * JalviewJS signals file selection by a property change event
623    * for property "SelectedFile". This methods responds to that by
624    * running the response action for 'OK' in the dialog.
625    *
626    * @param evt
627    */
 
628  0 toggle @Override
629    public void propertyChange(PropertyChangeEvent evt)
630    {
631    // TODO other properties need runners...
632  0 switch (evt.getPropertyName())
633    {
634    /*
635    * property name here matches that used in JFileChooser.js
636    */
637  0 case "SelectedFile":
638  0 handleResponse(APPROVE_OPTION);
639  0 break;
640    }
641    }
642    }