Clover icon

Coverage Report

  1. Project Clover database Mon Nov 11 2024 20:42:03 GMT
  2. Package jalview.gui

File Desktop.java

 

Coverage histogram

../../img/srcFileCovDistChart5.png
42% of files have more coverage

Code metrics

506
1,180
163
3
4,175
3,017
504
0.43
7.24
54.33
3.09

Classes

Class Line # Actions
Desktop 168 1,128 472
0.4404086244%
Desktop.MyDesktopManager 300 25 19
0.220%
Desktop.MyDesktopPane 2673 27 13
0.3095238231%
 

Contributing tests

This file is covered by 240 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    package jalview.gui;
22   
23    import java.awt.BorderLayout;
24    import java.awt.Color;
25    import java.awt.Component;
26    import java.awt.Container;
27    import java.awt.Dimension;
28    import java.awt.FontMetrics;
29    import java.awt.Graphics;
30    import java.awt.Graphics2D;
31    import java.awt.GridLayout;
32    import java.awt.Point;
33    import java.awt.Rectangle;
34    import java.awt.Toolkit;
35    import java.awt.Window;
36    import java.awt.datatransfer.Clipboard;
37    import java.awt.datatransfer.ClipboardOwner;
38    import java.awt.datatransfer.DataFlavor;
39    import java.awt.datatransfer.Transferable;
40    import java.awt.dnd.DnDConstants;
41    import java.awt.dnd.DropTargetDragEvent;
42    import java.awt.dnd.DropTargetDropEvent;
43    import java.awt.dnd.DropTargetEvent;
44    import java.awt.dnd.DropTargetListener;
45    import java.awt.event.ActionEvent;
46    import java.awt.event.ActionListener;
47    import java.awt.event.InputEvent;
48    import java.awt.event.KeyEvent;
49    import java.awt.event.MouseAdapter;
50    import java.awt.event.MouseEvent;
51    import java.awt.event.WindowAdapter;
52    import java.awt.event.WindowEvent;
53    import java.awt.geom.AffineTransform;
54    import java.beans.PropertyChangeEvent;
55    import java.beans.PropertyChangeListener;
56    import java.beans.PropertyVetoException;
57    import java.io.File;
58    import java.io.FileNotFoundException;
59    import java.io.FileWriter;
60    import java.io.IOException;
61    import java.lang.reflect.Field;
62    import java.net.URL;
63    import java.util.ArrayList;
64    import java.util.Arrays;
65    import java.util.HashMap;
66    import java.util.Hashtable;
67    import java.util.List;
68    import java.util.ListIterator;
69    import java.util.Locale;
70    import java.util.Map;
71    import java.util.Vector;
72    import java.util.concurrent.ExecutorService;
73    import java.util.concurrent.Executors;
74    import java.util.concurrent.Semaphore;
75   
76    import javax.swing.AbstractAction;
77    import javax.swing.Action;
78    import javax.swing.ActionMap;
79    import javax.swing.Box;
80    import javax.swing.BoxLayout;
81    import javax.swing.DefaultDesktopManager;
82    import javax.swing.DesktopManager;
83    import javax.swing.InputMap;
84    import javax.swing.JButton;
85    import javax.swing.JCheckBox;
86    import javax.swing.JComboBox;
87    import javax.swing.JComponent;
88    import javax.swing.JDesktopPane;
89    import javax.swing.JFrame;
90    import javax.swing.JInternalFrame;
91    import javax.swing.JLabel;
92    import javax.swing.JMenuItem;
93    import javax.swing.JOptionPane;
94    import javax.swing.JPanel;
95    import javax.swing.JPopupMenu;
96    import javax.swing.JProgressBar;
97    import javax.swing.JScrollPane;
98    import javax.swing.JTextArea;
99    import javax.swing.JTextField;
100    import javax.swing.JTextPane;
101    import javax.swing.KeyStroke;
102    import javax.swing.SwingUtilities;
103    import javax.swing.WindowConstants;
104    import javax.swing.event.HyperlinkEvent;
105    import javax.swing.event.HyperlinkEvent.EventType;
106    import javax.swing.event.InternalFrameAdapter;
107    import javax.swing.event.InternalFrameEvent;
108    import javax.swing.text.JTextComponent;
109   
110    import org.stackoverflowusers.file.WindowsShortcut;
111   
112    import jalview.api.AlignViewportI;
113    import jalview.api.AlignmentViewPanel;
114    import jalview.api.structures.JalviewStructureDisplayI;
115    import jalview.bin.Cache;
116    import jalview.bin.Jalview;
117    import jalview.bin.Jalview.ExitCode;
118    import jalview.bin.argparser.Arg;
119    import jalview.bin.groovy.JalviewObject;
120    import jalview.bin.groovy.JalviewObjectI;
121    import jalview.datamodel.Alignment;
122    import jalview.datamodel.HiddenColumns;
123    import jalview.datamodel.Sequence;
124    import jalview.datamodel.SequenceI;
125    import jalview.gui.ImageExporter.ImageWriterI;
126    import jalview.gui.QuitHandler.QResponse;
127    import jalview.io.AppletFormatAdapter;
128    import jalview.io.BackupFiles;
129    import jalview.io.DataSourceType;
130    import jalview.io.FileFormat;
131    import jalview.io.FileFormatException;
132    import jalview.io.FileFormatI;
133    import jalview.io.FileFormats;
134    import jalview.io.FileLoader;
135    import jalview.io.FormatAdapter;
136    import jalview.io.IdentifyFile;
137    import jalview.io.JalviewFileChooser;
138    import jalview.io.JalviewFileView;
139    import jalview.io.NewickFile;
140    import jalview.io.exceptions.ImageOutputException;
141    import jalview.jbgui.GSplitFrame;
142    import jalview.jbgui.GStructureViewer;
143    import jalview.project.Jalview2XML;
144    import jalview.structure.StructureSelectionManager;
145    import jalview.urls.IdOrgSettings;
146    import jalview.util.ArgParserUtils;
147    import jalview.util.ArgParserUtils.BaseInfo;
148    import jalview.util.BrowserLauncher;
149    import jalview.util.ChannelProperties;
150    import jalview.util.FileUtils;
151    import jalview.util.ImageMaker.TYPE;
152    import jalview.util.LaunchUtils;
153    import jalview.util.MessageManager;
154    import jalview.util.Platform;
155    import jalview.util.ShortcutKeyMaskExWrapper;
156    import jalview.util.UrlConstants;
157    import jalview.viewmodel.AlignmentViewport;
158    import jalview.ws.params.ParamManager;
159    import jalview.ws.utils.UrlDownloadClient;
160   
161    /**
162    * Jalview Desktop
163    *
164    *
165    * @author $author$
166    * @version $Revision: 1.155 $
167    */
 
168    public class Desktop extends jalview.jbgui.GDesktop
169    implements DropTargetListener, ClipboardOwner, IProgressIndicator,
170    jalview.api.StructureSelectionManagerProvider, JalviewObjectI
171    {
172    private static final String CITATION;
 
173  54 toggle static
174    {
175  54 URL bg_logo_url = ChannelProperties.getImageURL(
176    "bg_logo." + String.valueOf(SplashScreen.logoSize));
177  54 URL uod_logo_url = ChannelProperties.getImageURL(
178    "uod_banner." + String.valueOf(SplashScreen.logoSize));
179  54 boolean logo = (bg_logo_url != null || uod_logo_url != null);
180  54 StringBuilder sb = new StringBuilder();
181  54 sb.append(
182    "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
183  54 if (logo)
184    {
185  54 sb.append("<br>");
186    }
187  54 sb.append(bg_logo_url == null ? ""
188    : "<img alt=\"Barton Group logo\" src=\""
189    + bg_logo_url.toString() + "\">");
190  54 sb.append(uod_logo_url == null ? ""
191    : "&nbsp;<img alt=\"University of Dundee shield\" src=\""
192    + uod_logo_url.toString() + "\">");
193  54 sb.append(
194    "<br><br>For help, see <a href=\"https://www.jalview.org/help/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
195  54 sb.append("<br><br>If you use Jalview, please cite:"
196    + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
197    + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
198    + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
199  54 CITATION = sb.toString();
200    }
201   
202    private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
203   
204    private static int DEFAULT_MIN_WIDTH = 300;
205   
206    private static int DEFAULT_MIN_HEIGHT = 250;
207   
208    private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
209   
210    private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
211   
212    private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
213   
214    public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
215   
216    public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
217   
218    private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
219   
 
220  150 toggle public static void setLiveDragMode(boolean b)
221    {
222  150 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
223    : JDesktopPane.OUTLINE_DRAG_MODE;
224  150 if (desktop != null)
225  130 desktop.setDragMode(DRAG_MODE);
226    }
227   
228    private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
229   
230    public static boolean nosplash = false;
231   
232    /**
233    * news reader - null if it was never started.
234    */
235    private BlogReader jvnews = null;
236   
237    private File projectFile;
238   
239    /**
240    * @param listener
241    * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
242    */
 
243  0 toggle public void addJalviewPropertyChangeListener(
244    PropertyChangeListener listener)
245    {
246  0 changeSupport.addJalviewPropertyChangeListener(listener);
247    }
248   
249    /**
250    * @param propertyName
251    * @param listener
252    * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
253    * java.beans.PropertyChangeListener)
254    */
 
255  397 toggle public void addJalviewPropertyChangeListener(String propertyName,
256    PropertyChangeListener listener)
257    {
258  397 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
259    }
260   
261    /**
262    * @param propertyName
263    * @param listener
264    * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
265    * java.beans.PropertyChangeListener)
266    */
 
267  260 toggle public void removeJalviewPropertyChangeListener(String propertyName,
268    PropertyChangeListener listener)
269    {
270  260 changeSupport.removeJalviewPropertyChangeListener(propertyName,
271    listener);
272    }
273   
274    /** Singleton Desktop instance */
275    public static Desktop instance;
276   
277    public static MyDesktopPane desktop;
278   
 
279  2778 toggle public static MyDesktopPane getDesktop()
280    {
281    // BH 2018 could use currentThread() here as a reference to a
282    // Hashtable<Thread, MyDesktopPane> in JavaScript
283  2778 return desktop;
284    }
285   
286    static int openFrameCount = 0;
287   
288    static final int xOffset = 30;
289   
290    static final int yOffset = 30;
291   
292    public static jalview.ws.jws1.Discoverer discoverer;
293   
294    public static Object[] jalviewClipboard;
295   
296    public static boolean internalCopy = false;
297   
298    static int fileLoadingCount = 0;
299   
 
300    class MyDesktopManager implements DesktopManager
301    {
302   
303    private DesktopManager delegate;
304   
 
305  75 toggle public MyDesktopManager(DesktopManager delegate)
306    {
307  75 this.delegate = delegate;
308    }
309   
 
310  740 toggle @Override
311    public void activateFrame(JInternalFrame f)
312    {
313  740 try
314    {
315  740 delegate.activateFrame(f);
316    } catch (NullPointerException npe)
317    {
318  0 Point p = getMousePosition();
319  0 instance.showPasteMenu(p.x, p.y);
320    }
321    }
322   
 
323  0 toggle @Override
324    public void beginDraggingFrame(JComponent f)
325    {
326  0 delegate.beginDraggingFrame(f);
327    }
328   
 
329  0 toggle @Override
330    public void beginResizingFrame(JComponent f, int direction)
331    {
332  0 delegate.beginResizingFrame(f, direction);
333    }
334   
 
335  431 toggle @Override
336    public void closeFrame(JInternalFrame f)
337    {
338  431 delegate.closeFrame(f);
339    }
340   
 
341  719 toggle @Override
342    public void deactivateFrame(JInternalFrame f)
343    {
344  719 delegate.deactivateFrame(f);
345    }
346   
 
347  0 toggle @Override
348    public void deiconifyFrame(JInternalFrame f)
349    {
350  0 delegate.deiconifyFrame(f);
351    }
352   
 
353  0 toggle @Override
354    public void dragFrame(JComponent f, int newX, int newY)
355    {
356  0 if (newY < 0)
357    {
358  0 newY = 0;
359    }
360  0 delegate.dragFrame(f, newX, newY);
361    }
362   
 
363  0 toggle @Override
364    public void endDraggingFrame(JComponent f)
365    {
366  0 delegate.endDraggingFrame(f);
367  0 desktop.repaint();
368    }
369   
 
370  0 toggle @Override
371    public void endResizingFrame(JComponent f)
372    {
373  0 delegate.endResizingFrame(f);
374  0 desktop.repaint();
375    }
376   
 
377  0 toggle @Override
378    public void iconifyFrame(JInternalFrame f)
379    {
380  0 delegate.iconifyFrame(f);
381    }
382   
 
383  0 toggle @Override
384    public void maximizeFrame(JInternalFrame f)
385    {
386  0 delegate.maximizeFrame(f);
387    }
388   
 
389  0 toggle @Override
390    public void minimizeFrame(JInternalFrame f)
391    {
392  0 delegate.minimizeFrame(f);
393    }
394   
 
395  0 toggle @Override
396    public void openFrame(JInternalFrame f)
397    {
398  0 delegate.openFrame(f);
399    }
400   
 
401  0 toggle @Override
402    public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
403    int newHeight)
404    {
405  0 if (newY < 0)
406    {
407  0 newY = 0;
408    }
409  0 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
410    }
411   
 
412  0 toggle @Override
413    public void setBoundsForFrame(JComponent f, int newX, int newY,
414    int newWidth, int newHeight)
415    {
416  0 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
417    }
418   
419    // All other methods, simply delegate
420   
421    }
422   
423    /**
424    * Creates a new Desktop object.
425    */
 
426  75 toggle public Desktop()
427    {
428  75 super();
429    /**
430    * A note to implementors. It is ESSENTIAL that any activities that might
431    * block are spawned off as threads rather than waited for during this
432    * constructor.
433    */
434  75 instance = this;
435   
436  75 doConfigureStructurePrefs();
437  75 setTitle(ChannelProperties.getProperty("app_name") + " "
438    + Cache.getProperty("VERSION"));
439   
440    /**
441    * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
442    * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
443    * officially documented or guaranteed to exist, so we access it via
444    * reflection. There appear to be unfathomable criteria about what this
445    * string can contain, and it if doesn't meet those criteria then "java"
446    * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
447    * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
448    * not. The reflection access may generate a warning: WARNING: An illegal
449    * reflective access operation has occurred WARNING: Illegal reflective
450    * access by jalview.gui.Desktop () to field
451    * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
452    */
453  75 if (Platform.isLinux())
454    {
455  75 if (LaunchUtils.getJavaVersion() >= 11)
456    {
457    /*
458    * Send this message to stderr as the warning that follows (due to
459    * reflection) also goes to stderr.
460    */
461  75 jalview.bin.Console.errPrintln(
462    "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
463    }
464  75 final String awtAppClassName = "awtAppClassName";
465  75 try
466    {
467  75 Toolkit xToolkit = Toolkit.getDefaultToolkit();
468  75 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
469  75 Field awtAppClassNameField = null;
470   
471  75 if (Arrays.stream(declaredFields)
472    .anyMatch(f -> f.getName().equals(awtAppClassName)))
473    {
474  75 awtAppClassNameField = xToolkit.getClass()
475    .getDeclaredField(awtAppClassName);
476    }
477   
478  75 String title = ChannelProperties.getProperty("app_name");
479  75 if (awtAppClassNameField != null)
480    {
481  75 awtAppClassNameField.setAccessible(true);
482  75 awtAppClassNameField.set(xToolkit, title);
483    }
484    else
485    {
486  0 jalview.bin.Console
487    .debug("XToolkit: " + awtAppClassName + " not found");
488    }
489    } catch (Exception e)
490    {
491  0 jalview.bin.Console.debug("Error setting " + awtAppClassName);
492  0 jalview.bin.Console.trace(Cache.getStackTraceString(e));
493    }
494    }
495   
496  75 setIconImages(ChannelProperties.getIconList());
497   
498    // override quit handling when GUI OS close [X] button pressed
499  75 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
500  75 addWindowListener(new WindowAdapter()
501    {
 
502  0 toggle @Override
503    public void windowClosing(WindowEvent ev)
504    {
505  0 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
506    }
507    });
508   
509  75 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
510   
511  75 boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE,
512    "SHOW_JAVA_CONSOLE", false);
513   
514    // start dialogue queue for single dialogues
515  75 startDialogQueue();
516   
517  75 if (!Platform.isJS())
518    /**
519    * Java only
520    *
521    * @j2sIgnore
522    */
523    {
524  75 Desktop.instance.acquireDialogQueue();
525   
526  75 jconsole = new Console(this);
527  75 jconsole.setHeader(Cache.getVersionDetailsForConsole());
528  75 showConsole(showjconsole);
529   
530  75 Desktop.instance.releaseDialogQueue();
531    }
532   
533  75 desktop = new MyDesktopPane(selmemusage);
534   
535  75 showMemusage.setSelected(selmemusage);
536  75 desktop.setBackground(Color.white);
537   
538  75 getContentPane().setLayout(new BorderLayout());
539    // alternate config - have scrollbars - see notes in JAL-153
540    // JScrollPane sp = new JScrollPane();
541    // sp.getViewport().setView(desktop);
542    // getContentPane().add(sp, BorderLayout.CENTER);
543   
544    // BH 2018 - just an experiment to try unclipped JInternalFrames.
545  75 if (Platform.isJS())
546    {
547  0 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
548    }
549   
550  75 getContentPane().add(desktop, BorderLayout.CENTER);
551  75 desktop.setDragMode(DRAG_MODE);
552   
553    // This line prevents Windows Look&Feel resizing all new windows to maximum
554    // if previous window was maximised
555  75 desktop.setDesktopManager(new MyDesktopManager(
556  75 Platform.isJS() ? desktop.getDesktopManager()
557    : new DefaultDesktopManager()));
558    /*
559    (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
560    : Platform.isAMacAndNotJS()
561    ? new AquaInternalFrameManager(
562    desktop.getDesktopManager())
563    : desktop.getDesktopManager())));
564    */
565   
566  75 Rectangle dims = getLastKnownDimensions("");
567  75 if (dims != null)
568    {
569  59 setBounds(dims);
570    }
571    else
572    {
573  16 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
574  16 int xPos = Math.max(5, (screenSize.width - 900) / 2);
575  16 int yPos = Math.max(5, (screenSize.height - 650) / 2);
576  16 setBounds(xPos, yPos, 900, 650);
577    }
578   
579  75 if (!Platform.isJS())
580    /**
581    * Java only
582    *
583    * @j2sIgnore
584    */
585    {
586  75 showNews.setVisible(false);
587   
588  75 experimentalFeatures.setSelected(showExperimental());
589   
590  75 getIdentifiersOrgData();
591   
592  75 checkURLLinks();
593   
594    // Spawn a thread that shows the splashscreen
595  75 if (!nosplash)
596    {
597  50 SwingUtilities.invokeLater(new Runnable()
598    {
 
599  50 toggle @Override
600    public void run()
601    {
602  50 new SplashScreen(true);
603    }
604    });
605    }
606   
607    // Thread off a new instance of the file chooser - this reduces the time
608    // it takes to open it later on.
609  75 new Thread(new Runnable()
610    {
 
611  75 toggle @Override
612    public void run()
613    {
614  75 jalview.bin.Console.debug("Filechooser init thread started.");
615  75 String fileFormat = FileLoader.getUseDefaultFileFormat()
616    ? Cache.getProperty("DEFAULT_FILE_FORMAT")
617    : null;
618  75 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
619    fileFormat);
620  72 jalview.bin.Console.debug("Filechooser init thread finished.");
621    }
622    }).start();
623    // Add the service change listener
624  75 changeSupport.addJalviewPropertyChangeListener("services",
625    new PropertyChangeListener()
626    {
627   
 
628  1325 toggle @Override
629    public void propertyChange(PropertyChangeEvent evt)
630    {
631  1325 jalview.bin.Console
632    .debug("Firing service changed event for "
633    + evt.getNewValue());
634  1325 JalviewServicesChanged(evt);
635    }
636    });
637    }
638   
639  75 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
640   
641  75 MouseAdapter ma;
642  75 this.addMouseListener(ma = new MouseAdapter()
643    {
 
644  0 toggle @Override
645    public void mousePressed(MouseEvent evt)
646    {
647  0 if (evt.isPopupTrigger()) // Mac
648    {
649  0 showPasteMenu(evt.getX(), evt.getY());
650    }
651    }
652   
 
653  0 toggle @Override
654    public void mouseReleased(MouseEvent evt)
655    {
656  0 if (evt.isPopupTrigger()) // Windows
657    {
658  0 showPasteMenu(evt.getX(), evt.getY());
659    }
660    }
661    });
662  75 desktop.addMouseListener(ma);
663   
664    // This is important for jalviewjsTest task and also early Getdown
665    // splashscreen removal
666  75 jalview.bin.Console.info((Platform.isJS() ? "JALVIEWJS" : "JALVIEW")
667    + ": CREATED DESKTOP");
668   
669    }
670   
671    /**
672    * Answers true if user preferences to enable experimental features is True
673    * (on), else false
674    *
675    * @return
676    */
 
677  75 toggle public boolean showExperimental()
678    {
679  75 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
680    Boolean.FALSE.toString());
681  75 return Boolean.valueOf(experimental).booleanValue();
682    }
683   
 
684  75 toggle public void doConfigureStructurePrefs()
685    {
686    // configure services
687  75 StructureSelectionManager ssm = StructureSelectionManager
688    .getStructureSelectionManager(this);
689  75 StructureSelectionManager.doConfigureStructurePrefs(ssm);
690    }
691   
 
692  42 toggle public void checkForNews()
693    {
694  42 final Desktop me = this;
695    // Thread off the news reader, in case there are connection problems.
696  42 new Thread(new Runnable()
697    {
 
698  42 toggle @Override
699    public void run()
700    {
701  42 jalview.bin.Console.debug("Starting news thread.");
702  42 jvnews = new BlogReader(me);
703  42 showNews.setVisible(true);
704  42 jalview.bin.Console.debug("Completed news thread.");
705    }
706    }).start();
707    }
708   
 
709  75 toggle public void getIdentifiersOrgData()
710    {
711  75 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
712    {// Thread off the identifiers fetcher
713  75 new Thread(new Runnable()
714    {
 
715  75 toggle @Override
716    public void run()
717    {
718  75 jalview.bin.Console
719    .debug("Downloading data from identifiers.org");
720  75 try
721    {
722  75 UrlDownloadClient.download(IdOrgSettings.getUrl(),
723    IdOrgSettings.getDownloadLocation());
724    } catch (IOException e)
725    {
726  0 jalview.bin.Console
727    .debug("Exception downloading identifiers.org data"
728    + e.getMessage());
729    }
730    }
731    }).start();
732  75 ;
733    }
734    }
735   
 
736  0 toggle @Override
737    protected void showNews_actionPerformed(ActionEvent e)
738    {
739  0 showNews(showNews.isSelected());
740    }
741   
 
742  0 toggle void showNews(boolean visible)
743    {
744  0 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
745  0 showNews.setSelected(visible);
746  0 if (visible && !jvnews.isVisible())
747    {
748  0 new Thread(new Runnable()
749    {
 
750  0 toggle @Override
751    public void run()
752    {
753  0 long now = System.currentTimeMillis();
754  0 Desktop.instance.setProgressBar(
755    MessageManager.getString("status.refreshing_news"), now);
756  0 jvnews.refreshNews();
757  0 Desktop.instance.setProgressBar(null, now);
758  0 jvnews.showNews();
759    }
760    }).start();
761    }
762    }
763   
764    /**
765    * recover the last known dimensions for a jalview window
766    *
767    * @param windowName
768    * - empty string is desktop, all other windows have unique prefix
769    * @return null or last known dimensions scaled to current geometry (if last
770    * window geom was known)
771    */
 
772  150 toggle Rectangle getLastKnownDimensions(String windowName)
773    {
774    // TODO: lock aspect ratio for scaling desktop Bug #0058199
775  150 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
776  150 String x = Cache.getProperty(windowName + "SCREEN_X");
777  150 String y = Cache.getProperty(windowName + "SCREEN_Y");
778  150 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
779  150 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
780  150 if ((x != null) && (y != null) && (width != null) && (height != null))
781    {
782  118 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
783    iw = Integer.parseInt(width), ih = Integer.parseInt(height);
784  118 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
785    {
786    // attempt #1 - try to cope with change in screen geometry - this
787    // version doesn't preserve original jv aspect ratio.
788    // take ratio of current screen size vs original screen size.
789  118 double sw = ((1f * screenSize.width) / (1f * Integer
790    .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
791  118 double sh = ((1f * screenSize.height) / (1f * Integer
792    .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
793    // rescale the bounds depending upon the current screen geometry.
794  118 ix = (int) (ix * sw);
795  118 iw = (int) (iw * sw);
796  118 iy = (int) (iy * sh);
797  118 ih = (int) (ih * sh);
798  118 if (ix >= screenSize.width)
799    {
800  0 jalview.bin.Console.debug(
801    "Window geometry location recall error: shifting horizontal to within screenbounds.");
802  0 ix = ix % screenSize.width;
803    }
804  118 if (iy >= screenSize.height)
805    {
806  0 jalview.bin.Console.debug(
807    "Window geometry location recall error: shifting vertical to within screenbounds.");
808  0 iy = iy % screenSize.height;
809    }
810  118 jalview.bin.Console.debug(
811    "Got last known dimensions for " + windowName + ": x:" + ix
812    + " y:" + iy + " width:" + iw + " height:" + ih);
813    }
814    // return dimensions for new instance
815  118 return new Rectangle(ix, iy, iw, ih);
816    }
817  32 return null;
818    }
819   
 
820  0 toggle void showPasteMenu(int x, int y)
821    {
822  0 JPopupMenu popup = new JPopupMenu();
823  0 JMenuItem item = new JMenuItem(
824    MessageManager.getString("label.paste_new_window"));
825  0 item.addActionListener(new ActionListener()
826    {
 
827  0 toggle @Override
828    public void actionPerformed(ActionEvent evt)
829    {
830  0 paste();
831    }
832    });
833   
834  0 popup.add(item);
835  0 popup.show(this, x, y);
836    }
837   
 
838  1 toggle public void paste()
839    {
840    // quick patch for JAL-4150 - needs some more work and test coverage
841    // TODO - unify below and AlignFrame.paste()
842    // TODO - write tests and fix AlignFrame.paste() which doesn't track if
843    // clipboard has come from a different alignment window than the one where
844    // paste has been called! JAL-4151
845   
846  1 if (Desktop.jalviewClipboard != null)
847    {
848    // The clipboard was filled from within Jalview, we must use the
849    // sequences
850    // And dataset from the copied alignment
851  1 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
852    // be doubly sure that we create *new* sequence objects.
853  1 SequenceI[] sequences = new SequenceI[newseq.length];
854  16 for (int i = 0; i < newseq.length; i++)
855    {
856  15 sequences[i] = new Sequence(newseq[i]);
857    }
858  1 Alignment alignment = new Alignment(sequences);
859    // dataset is inherited
860  1 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
861  1 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
862    AlignFrame.DEFAULT_HEIGHT);
863  1 String newtitle = new String("Copied sequences");
864   
865  1 if (Desktop.jalviewClipboard[2] != null)
866    {
867  0 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
868  0 af.viewport.setHiddenColumns(hc);
869    }
870   
871  1 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
872    AlignFrame.DEFAULT_HEIGHT);
873   
874    }
875    else
876    {
877  0 try
878    {
879  0 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
880  0 Transferable contents = c.getContents(this);
881   
882  0 if (contents != null)
883    {
884  0 String file = (String) contents
885    .getTransferData(DataFlavor.stringFlavor);
886   
887  0 FileFormatI format = new IdentifyFile().identify(file,
888    DataSourceType.PASTE);
889   
890  0 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
891   
892    }
893    } catch (Exception ex)
894    {
895  0 jalview.bin.Console.outPrintln(
896    "Unable to paste alignment from system clipboard:\n" + ex);
897    }
898    }
899    }
900   
901    /**
902    * Adds and opens the given frame to the desktop
903    *
904    * @param frame
905    * Frame to show
906    * @param title
907    * Visible Title
908    * @param w
909    * width
910    * @param h
911    * height
912    */
 
913  430 toggle public static synchronized void addInternalFrame(
914    final JInternalFrame frame, String title, int w, int h)
915    {
916  430 addInternalFrame(frame, title, true, w, h, true, false);
917    }
918   
919    /**
920    * Add an internal frame to the Jalview desktop
921    *
922    * @param frame
923    * Frame to show
924    * @param title
925    * Visible Title
926    * @param makeVisible
927    * When true, display frame immediately, otherwise, caller must call
928    * setVisible themselves.
929    * @param w
930    * width
931    * @param h
932    * height
933    */
 
934  0 toggle public static synchronized void addInternalFrame(
935    final JInternalFrame frame, String title, boolean makeVisible,
936    int w, int h)
937    {
938  0 addInternalFrame(frame, title, makeVisible, w, h, true, false);
939    }
940   
941    /**
942    * Add an internal frame to the Jalview desktop and make it visible
943    *
944    * @param frame
945    * Frame to show
946    * @param title
947    * Visible Title
948    * @param w
949    * width
950    * @param h
951    * height
952    * @param resizable
953    * Allow resize
954    */
 
955  17 toggle public static synchronized void addInternalFrame(
956    final JInternalFrame frame, String title, int w, int h,
957    boolean resizable)
958    {
959  17 addInternalFrame(frame, title, true, w, h, resizable, false);
960    }
961   
962    /**
963    * Add an internal frame to the Jalview desktop
964    *
965    * @param frame
966    * Frame to show
967    * @param title
968    * Visible Title
969    * @param makeVisible
970    * When true, display frame immediately, otherwise, caller must call
971    * setVisible themselves.
972    * @param w
973    * width
974    * @param h
975    * height
976    * @param resizable
977    * Allow resize
978    * @param ignoreMinSize
979    * Do not set the default minimum size for frame
980    */
 
981  507 toggle public static synchronized void addInternalFrame(
982    final JInternalFrame frame, String title, boolean makeVisible,
983    int w, int h, boolean resizable, boolean ignoreMinSize)
984    {
985   
986    // TODO: allow callers to determine X and Y position of frame (eg. via
987    // bounds object).
988    // TODO: consider fixing method to update entries in the window submenu with
989    // the current window title
990   
991  507 frame.setTitle(title);
992  507 if (frame.getWidth() < 1 || frame.getHeight() < 1)
993    {
994  137 frame.setSize(w, h);
995    }
996    // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
997    // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
998    // IF JALVIEW IS RUNNING HEADLESS
999    // ///////////////////////////////////////////////
1000  507 if (instance == null || (System.getProperty("java.awt.headless") != null
1001    && System.getProperty("java.awt.headless").equals("true")))
1002    {
1003  52 return;
1004    }
1005   
1006  455 openFrameCount++;
1007   
1008  455 if (!ignoreMinSize)
1009    {
1010  395 frame.setMinimumSize(
1011    new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1012   
1013    // Set default dimension for Alignment Frame window.
1014    // The Alignment Frame window could be added from a number of places,
1015    // hence,
1016    // I did this here in order not to miss out on any Alignment frame.
1017  395 if (frame instanceof AlignFrame)
1018    {
1019  295 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1020    ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1021    }
1022    }
1023   
1024  455 frame.setVisible(makeVisible);
1025  455 frame.setClosable(true);
1026  455 frame.setResizable(resizable);
1027  455 frame.setMaximizable(resizable);
1028  455 frame.setIconifiable(resizable);
1029  455 frame.setOpaque(Platform.isJS());
1030   
1031  455 if (frame.getX() < 1 && frame.getY() < 1)
1032    {
1033  345 frame.setLocation(xOffset * openFrameCount,
1034    yOffset * ((openFrameCount - 1) % 10) + yOffset);
1035    }
1036   
1037    /*
1038    * add an entry for the new frame in the Window menu (and remove it when the
1039    * frame is closed)
1040    */
1041  455 final JMenuItem menuItem = new JMenuItem(title);
1042  455 frame.addInternalFrameListener(new InternalFrameAdapter()
1043    {
 
1044  637 toggle @Override
1045    public void internalFrameActivated(InternalFrameEvent evt)
1046    {
1047  637 JInternalFrame itf = desktop.getSelectedFrame();
1048  637 if (itf != null)
1049    {
1050  637 if (itf instanceof AlignFrame)
1051    {
1052  431 Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf);
1053    }
1054  637 itf.requestFocus();
1055    }
1056    }
1057   
 
1058  403 toggle @Override
1059    public void internalFrameClosed(InternalFrameEvent evt)
1060    {
1061  403 PaintRefresher.RemoveComponent(frame);
1062   
1063    /*
1064    * defensive check to prevent frames being added half off the window
1065    */
1066  403 if (openFrameCount > 0)
1067    {
1068  390 openFrameCount--;
1069    }
1070   
1071    /*
1072    * ensure no reference to alignFrame retained by menu item listener
1073    */
1074  403 if (menuItem.getActionListeners().length > 0)
1075    {
1076  382 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1077    }
1078  403 windowMenu.remove(menuItem);
1079    }
1080    });
1081   
1082  455 menuItem.addActionListener(new ActionListener()
1083    {
 
1084  0 toggle @Override
1085    public void actionPerformed(ActionEvent e)
1086    {
1087  0 try
1088    {
1089  0 frame.setSelected(true);
1090  0 frame.setIcon(false);
1091    } catch (java.beans.PropertyVetoException ex)
1092    {
1093   
1094    }
1095    }
1096    });
1097   
1098  455 setKeyBindings(frame);
1099   
1100    // Since the latest FlatLaf patch, we occasionally have problems showing
1101    // structureViewer frames...
1102  455 int tries = 3;
1103  455 boolean shown = false;
1104  455 Exception last = null;
1105  455 do
1106    {
1107  455 try
1108    {
1109  455 desktop.add(frame);
1110  455 shown = true;
1111    } catch (IllegalArgumentException iaex)
1112    {
1113  0 last = iaex;
1114  0 tries--;
1115  0 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1116    + tries + " left) for " + frame.getTitle(), iaex);
1117  0 try
1118    {
1119  0 Thread.sleep(5);
1120    } catch (InterruptedException iex)
1121    {
1122    }
1123  0 ;
1124    }
1125  455 } while (!shown && tries > 0);
1126  455 if (!shown)
1127    {
1128  0 jalview.bin.Console.error(
1129    "Serious Problem whilst showing window " + frame.getTitle(),
1130    last);
1131    }
1132   
1133  455 windowMenu.add(menuItem);
1134   
1135  455 frame.toFront();
1136  455 try
1137    {
1138  455 frame.setSelected(true);
1139  455 frame.requestFocus();
1140    } catch (java.beans.PropertyVetoException ve)
1141    {
1142    } catch (java.lang.ClassCastException cex)
1143    {
1144  0 jalview.bin.Console.warn(
1145    "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1146    cex);
1147    }
1148    }
1149   
1150    /**
1151    * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1152    * the window
1153    *
1154    * @param frame
1155    */
 
1156  455 toggle private static void setKeyBindings(JInternalFrame frame)
1157    {
1158  455 @SuppressWarnings("serial")
1159    final Action closeAction = new AbstractAction()
1160    {
 
1161  0 toggle @Override
1162    public void actionPerformed(ActionEvent e)
1163    {
1164  0 frame.dispose();
1165    }
1166    };
1167   
1168    /*
1169    * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1170    */
1171  455 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1172    InputEvent.CTRL_DOWN_MASK);
1173  455 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1174    ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1175   
1176  455 InputMap inputMap = frame
1177    .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1178  455 String ctrlW = ctrlWKey.toString();
1179  455 inputMap.put(ctrlWKey, ctrlW);
1180  455 inputMap.put(cmdWKey, ctrlW);
1181   
1182  455 ActionMap actionMap = frame.getActionMap();
1183  455 actionMap.put(ctrlW, closeAction);
1184    }
1185   
 
1186  0 toggle @Override
1187    public void lostOwnership(Clipboard clipboard, Transferable contents)
1188    {
1189  0 if (!internalCopy)
1190    {
1191  0 Desktop.jalviewClipboard = null;
1192    }
1193   
1194  0 internalCopy = false;
1195    }
1196   
 
1197  0 toggle @Override
1198    public void dragEnter(DropTargetDragEvent evt)
1199    {
1200    }
1201   
 
1202  0 toggle @Override
1203    public void dragExit(DropTargetEvent evt)
1204    {
1205    }
1206   
 
1207  0 toggle @Override
1208    public void dragOver(DropTargetDragEvent evt)
1209    {
1210    }
1211   
 
1212  0 toggle @Override
1213    public void dropActionChanged(DropTargetDragEvent evt)
1214    {
1215    }
1216   
1217    /**
1218    * load all files dropped (2.11.4.0 behaviour)
1219    * @return false if any file resulted in an error
1220    */
 
1221  0 toggle public boolean loadDroppedFiles(List<Object> files,
1222    List<DataSourceType> protocols, List<Object> failed,
1223    List<Exception> failed_exceptions)
1224    {
1225  0 for (int i = 0; i < files.size(); i++)
1226    {
1227  0 Object file = files.get(i);
1228  0 try
1229    {
1230    // BH 2018 File or String
1231  0 String fileName = file.toString();
1232  0 DataSourceType protocol = (protocols == null) ? DataSourceType.FILE
1233    : protocols.get(i);
1234  0 FileFormatI format = null;
1235   
1236  0 if (fileName.endsWith(".jar"))
1237    {
1238  0 format = FileFormat.Jalview;
1239    }
1240    else
1241    {
1242  0 format = new IdentifyFile().identify(file, protocol);
1243    }
1244  0 if (file instanceof File)
1245    {
1246  0 Platform.cacheFileData((File) file);
1247    }
1248  0 new FileLoader().LoadFile(null, file, protocol, format);
1249    } catch (Exception x)
1250    {
1251  0 failed.add(file);
1252  0 failed_exceptions.add(x);
1253  0 jalview.bin.Console.warn(
1254    "Unexpected Exception when handling " + file.toString(), x);
1255    }
1256    }
1257  0 return failed.size()==0; // at least one was loaded.
1258    }
1259   
1260    /**
1261    * analyse dropped files and process them according to type: load alignments,
1262    * add annotation, trees or features to alignments with the same basename
1263    *
1264    * @return false if any exception was raised
1265    */
 
1266  0 toggle public boolean processAndLoadDroppedFiles(List<Object> files,
1267    List<DataSourceType> protocols, List<Object> failed,
1268    List<Exception> failed_exceptions)
1269    {
1270    // map list to list of strings
1271  0 List<String> filenames = new ArrayList<>();
1272  0 for (int i = 0; i < files.size(); i++)
1273    {
1274  0 filenames.add(files.get(i).toString());
1275    }
1276   
1277    // processFilenames will take likely associated files OUT of the list of
1278    // filenames and return them in a BaseInfo object in the Map
1279  0 Map<String, BaseInfo> baseInfoMap = ArgParserUtils
1280    .processFilenames(filenames, false, files);
1281    // so we can catch exceptions for this file
1282  0 Object file = null;
1283  0 try
1284    {
1285   
1286  0 for (int i = 0; i < files.size(); i++)
1287    {
1288    // BH 2018 File or String
1289  0 file = files.get(i);
1290  0 String fileName = file.toString();
1291  0 DataSourceType protocol = (protocols == null) ? DataSourceType.FILE
1292    : protocols.get(i);
1293  0 FileFormatI format = null;
1294   
1295  0 if (fileName.toLowerCase(Locale.ROOT).endsWith(".jar"))
1296    {
1297  0 format = FileFormat.Jalview;
1298    }
1299    else
1300    {
1301  0 format = new IdentifyFile().identify(file, protocol);
1302    }
1303    // If features/annotations/tree file that hasn't been put into the
1304    // baseInfoMap, look through titles of opened AlignFrames to add.
1305  0 String ext = FileUtils.getExtension(fileName);
1306  0 boolean isFeatures = ArgParserUtils.featuresExtensions
1307    .contains(ext);
1308  0 boolean isAnnotations = ArgParserUtils.annotationsExtensions
1309    .contains(ext);
1310  0 boolean isTree = ArgParserUtils.treeExtensions.contains(ext);
1311  0 if (isFeatures || isAnnotations || isTree)
1312    {
1313  0 String base = FileUtils.getBase(fileName);
1314  0 AlignFrame matchingAf = null;
1315  0 AlignFrame[] afs = Desktop.instance.getAlignFrames();
1316  0 boolean dontSkip = false;
1317  0 for (AlignFrame af : afs)
1318    {
1319  0 String afFilename = af.fileName;
1320  0 String afTitle = af.getTitle();
1321    /**
1322    * don't need to check the matching afFilename or afTitle has an
1323    * alignment extenstion. It's obviously an alignment!
1324    */
1325    /*
1326    if ((base.equals(FileUtils.getBase(afFilename))
1327    && ArgParserUtils.alignmentExtensions
1328    .contains(FileUtils.getExtension(afFilename)))
1329    || (base.equals(FileUtils.getBase(afTitle))
1330    && ArgParserUtils.alignmentExtensions
1331    .contains(FileUtils
1332    .getExtension(afTitle))))
1333    */
1334  0 if (base.equals(FileUtils.getBase(afFilename))
1335    || base.equals(FileUtils.getBase(afTitle)))
1336    {
1337  0 matchingAf = af;
1338  0 break;
1339    }
1340    }
1341  0 if (matchingAf != null)
1342    {
1343  0 if (isFeatures)
1344    {
1345  0 matchingAf.parseFeaturesFile(fileName,
1346    AppletFormatAdapter.checkProtocol(fileName));
1347    }
1348  0 else if (isAnnotations)
1349    {
1350  0 matchingAf.loadJalviewDataFile(fileName, null, null, null);
1351    }
1352  0 else if (isTree)
1353    {
1354  0 try
1355    {
1356  0 NewickFile nf = new NewickFile(fileName,
1357    AppletFormatAdapter.checkProtocol(fileName));
1358  0 matchingAf.getViewport().setCurrentTree(
1359    matchingAf.showNewickTree(nf, fileName).getTree());
1360    } catch (IOException e)
1361    {
1362  0 jalview.bin.Console.warn(
1363    "Couldn't add potential tree '" + fileName + "'");
1364  0 dontSkip = true;
1365    }
1366    }
1367  0 if (!dontSkip)
1368    {
1369    // skip to next file
1370  0 continue;
1371    }
1372    }
1373    }
1374   
1375  0 if (file instanceof File)
1376    {
1377  0 Platform.cacheFileData((File) file);
1378    }
1379    // new FileLoader().LoadFile(null, file, protocol, format);
1380  0 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(fileName,
1381    protocol, format);
1382    // now we add any associated files to the alignframe
1383  0 if (baseInfoMap.containsKey(fileName) && baseInfoMap.get(fileName)
1384    .getAssociatedFilesMap() != null)
1385    {
1386  0 BaseInfo bi = baseInfoMap.get(fileName);
1387  0 if (bi.getAssociatedFilesMap().containsKey(Arg.FEATURES))
1388    {
1389  0 String featuresfile = bi.getAssociatedFilesMap()
1390    .get(Arg.FEATURES);
1391  0 af.parseFeaturesFile(featuresfile,
1392    AppletFormatAdapter.checkProtocol(featuresfile));
1393    }
1394  0 if (bi.getAssociatedFilesMap().containsKey(Arg.ANNOTATIONS))
1395    {
1396  0 String annotationsfile = bi.getAssociatedFilesMap()
1397    .get(Arg.ANNOTATIONS);
1398  0 af.loadJalviewDataFile(annotationsfile, null, null, null);
1399    }
1400  0 if (bi.getAssociatedFilesMap().containsKey(Arg.TREE))
1401    {
1402  0 String treefile = bi.getAssociatedFilesMap().get(Arg.TREE);
1403  0 try
1404    {
1405  0 NewickFile nf = new NewickFile(treefile,
1406    AppletFormatAdapter.checkProtocol(treefile));
1407  0 af.getViewport().setCurrentTree(
1408    af.showNewickTree(nf, treefile).getTree());
1409    } catch (IOException e)
1410    {
1411  0 jalview.bin.Console.warn(
1412    "Couldn't add potential tree '" + treefile + "'");
1413    }
1414   
1415    }
1416   
1417    }
1418   
1419    }
1420    } catch (Exception ex)
1421    {
1422   
1423  0 jalview.bin.Console.warn(
1424    "Unexpected exception whilst handling drop to Desktop.", ex);
1425  0 failed.add(file);
1426  0 failed_exceptions.add(ex);
1427  0 return false;
1428    }
1429  0 return failed.size() == 0; // redundant for 2.11.4.0 implementation
1430    }
1431   
 
1432  0 toggle @Override
1433    public void drop(DropTargetDropEvent evt)
1434    {
1435  0 boolean success = true;
1436    // JAL-1552 - acceptDrop required before getTransferable call for
1437    // Java's Transferable for native dnd
1438  0 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1439  0 Transferable t = evt.getTransferable();
1440  0 List<Object> files = new ArrayList<>();
1441  0 List<DataSourceType> protocols = new ArrayList<>();
1442   
1443  0 try
1444    {
1445  0 Desktop.transferFromDropTarget(files, protocols, evt, t);
1446    } catch (Exception e)
1447    {
1448  0 e.printStackTrace();
1449  0 success = false;
1450    }
1451  0 try
1452    {
1453  0 if (files != null)
1454    {
1455  0 List<Object> failed = new ArrayList<Object>();
1456  0 List<Exception> failed_exceptions = new ArrayList<Exception>();
1457  0 if (!showExperimental())
1458    {
1459  0 success = loadDroppedFiles(files, protocols, failed,
1460    failed_exceptions);
1461    }
1462    else
1463    {
1464  0 success = processAndLoadDroppedFiles(files, protocols, failed,
1465    failed_exceptions);
1466    }
1467    }
1468    } catch (Throwable ex)
1469    {
1470  0 jalview.bin.Console.warn(
1471    "Unexpected exception whilst handling drop to Desktop.", ex);
1472  0 success = false;
1473    }
1474  0 evt.dropComplete(success); // need this to ensure input focus is properly
1475    // transfered to any new windows created
1476    }
1477   
1478    /**
1479    * DOCUMENT ME!
1480    *
1481    * @param e
1482    * DOCUMENT ME!
1483    */
 
1484  0 toggle @Override
1485    public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1486    {
1487  0 String fileFormat = FileLoader.getUseDefaultFileFormat()
1488    ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1489    : null;
1490  0 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1491    Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1492    BackupFiles.getEnabled());
1493   
1494  0 chooser.setFileView(new JalviewFileView());
1495  0 chooser.setDialogTitle(
1496    MessageManager.getString("label.open_local_file"));
1497  0 chooser.setToolTipText(MessageManager.getString("action.open"));
1498   
1499  0 chooser.setResponseHandler(0, () -> {
1500  0 File selectedFile = chooser.getSelectedFile();
1501  0 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1502   
1503  0 FileFormatI format = chooser.getSelectedFormat();
1504   
1505    /*
1506    * Call IdentifyFile to verify the file contains what its extension implies.
1507    * Skip this step for dynamically added file formats, because IdentifyFile does
1508    * not know how to recognise them.
1509    */
1510  0 if (FileFormats.getInstance().isIdentifiable(format))
1511    {
1512  0 try
1513    {
1514  0 format = new IdentifyFile().identify(selectedFile,
1515    DataSourceType.FILE);
1516    } catch (FileFormatException e)
1517    {
1518    // format = null; //??
1519    }
1520    }
1521   
1522  0 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1523    format);
1524    });
1525  0 chooser.showOpenDialog(this);
1526    }
1527   
1528    /**
1529    * Shows a dialog for input of a URL at which to retrieve alignment data
1530    *
1531    * @param viewport
1532    */
 
1533  0 toggle @Override
1534    public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1535    {
1536    // This construct allows us to have a wider textfield
1537    // for viewing
1538  0 JLabel label = new JLabel(
1539    MessageManager.getString("label.input_file_url"));
1540   
1541  0 JPanel panel = new JPanel(new GridLayout(2, 1));
1542  0 panel.add(label);
1543   
1544    /*
1545    * the URL to fetch is input in Java: an editable combobox with history JS:
1546    * (pending JAL-3038) a plain text field
1547    */
1548  0 JComponent history;
1549  0 String urlBase = "https://www.";
1550  0 if (Platform.isJS())
1551    {
1552  0 history = new JTextField(urlBase, 35);
1553    }
1554    else
1555    /**
1556    * Java only
1557    *
1558    * @j2sIgnore
1559    */
1560    {
1561  0 JComboBox<String> asCombo = new JComboBox<>();
1562  0 asCombo.setPreferredSize(new Dimension(400, 20));
1563  0 asCombo.setEditable(true);
1564  0 asCombo.addItem(urlBase);
1565  0 String historyItems = Cache.getProperty("RECENT_URL");
1566  0 if (historyItems != null)
1567    {
1568  0 for (String token : historyItems.split("\\t"))
1569    {
1570  0 asCombo.addItem(token);
1571    }
1572    }
1573  0 history = asCombo;
1574    }
1575  0 panel.add(history);
1576   
1577  0 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1578    MessageManager.getString("action.cancel") };
1579  0 Runnable action = () -> {
1580  0 @SuppressWarnings("unchecked")
1581  0 String url = (history instanceof JTextField
1582    ? ((JTextField) history).getText()
1583    : ((JComboBox<String>) history).getEditor().getItem()
1584    .toString().trim());
1585   
1586  0 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1587    {
1588  0 if (viewport != null)
1589    {
1590  0 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1591    FileFormat.Jalview);
1592    }
1593    else
1594    {
1595  0 new FileLoader().LoadFile(url, DataSourceType.URL,
1596    FileFormat.Jalview);
1597    }
1598    }
1599    else
1600    {
1601  0 FileFormatI format = null;
1602  0 try
1603    {
1604  0 format = new IdentifyFile().identify(url, DataSourceType.URL);
1605    } catch (FileNotFoundException e)
1606    {
1607  0 jalview.bin.Console.error("URL '" + url + "' not found", e);
1608    } catch (FileFormatException e)
1609    {
1610  0 jalview.bin.Console.error(
1611    "File at URL '" + url + "' format not recognised", e);
1612    }
1613   
1614  0 if (format == null)
1615    {
1616  0 String msg = MessageManager.formatMessage("label.couldnt_locate",
1617    url);
1618  0 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1619    MessageManager.getString("label.url_not_found"),
1620    JvOptionPane.WARNING_MESSAGE);
1621  0 return;
1622    }
1623   
1624  0 if (viewport != null)
1625    {
1626  0 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1627    format);
1628    }
1629    else
1630    {
1631  0 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1632    }
1633    }
1634    };
1635  0 String dialogOption = MessageManager
1636    .getString("label.input_alignment_from_url");
1637  0 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1638    .showInternalDialog(panel, dialogOption,
1639    JvOptionPane.YES_NO_CANCEL_OPTION,
1640    JvOptionPane.PLAIN_MESSAGE, null, options,
1641    MessageManager.getString("action.ok"));
1642    }
1643   
1644    /**
1645    * Opens the CutAndPaste window for the user to paste an alignment in to
1646    *
1647    * @param viewPanel
1648    * - if not null, the pasted alignment is added to the current
1649    * alignment; if null, to a new alignment window
1650    */
 
1651  0 toggle @Override
1652    public void inputTextboxMenuItem_actionPerformed(
1653    AlignmentViewPanel viewPanel)
1654    {
1655  0 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1656  0 cap.setForInput(viewPanel);
1657  0 Desktop.addInternalFrame(cap,
1658    MessageManager.getString("label.cut_paste_alignmen_file"), true,
1659    600, 500);
1660    }
1661   
1662    /*
1663    * Check with user and saving files before actually quitting
1664    */
 
1665  0 toggle public void desktopQuit()
1666    {
1667  0 desktopQuit(true, false);
1668    }
1669   
1670    /**
1671    * close everything, stash window geometries, and shut down all associated
1672    * threads/workers
1673    *
1674    * @param dispose
1675    * - sets the dispose on close flag - JVM may terminate when set
1676    * @param terminateJvm
1677    * - quit with prejudice - stops the JVM.
1678    */
 
1679  81 toggle public void quitTheDesktop(boolean dispose, boolean terminateJvm)
1680    {
1681  81 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1682  81 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1683  81 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1684  81 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1685    getWidth(), getHeight()));
1686   
1687  81 if (jconsole != null)
1688    {
1689  81 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1690  81 jconsole.stopConsole();
1691    }
1692   
1693  81 if (jvnews != null)
1694    {
1695  71 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1696    }
1697   
1698    // Frames should all close automatically. Keeping external
1699    // viewers open should already be decided by user.
1700  81 closeAll_actionPerformed(null);
1701   
1702  81 if (dialogExecutor != null)
1703    {
1704  42 dialogExecutor.shutdownNow();
1705    }
1706   
1707  81 if (groovyConsole != null)
1708    {
1709    // suppress a possible repeat prompt to save script
1710  0 groovyConsole.setDirty(false);
1711   
1712    // and tidy up
1713  0 if (((Window) groovyConsole.getFrame()) != null
1714    && ((Window) groovyConsole.getFrame()).isVisible())
1715    {
1716    // console is visible -- FIXME JAL-4327
1717  0 groovyConsole.exit();
1718    }
1719    else
1720    {
1721    // console is not, so just let it dispose itself when we shutdown
1722    // we don't call groovyConsole.exit() because it calls the shutdown
1723    // handler with invokeAndWait() causing deadlock
1724  0 groovyConsole = null;
1725    }
1726    }
1727   
1728  81 if (terminateJvm)
1729    {
1730    // note that shutdown hook will not be run
1731  0 jalview.bin.Console.debug("Force Quit selected by user");
1732  0 Runtime.getRuntime().halt(0);
1733    }
1734   
1735  81 jalview.bin.Console.debug("Quit selected by user");
1736  81 if (dispose)
1737    {
1738  0 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1739    // instance.dispose();
1740    }
1741    }
1742   
 
1743  0 toggle public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1744    {
1745  0 final Runnable doDesktopQuit = () -> {
1746   
1747    // FIRST !! check for aborted quit
1748  0 if (QuitHandler.quitCancelled())
1749    {
1750  0 jalview.bin.Console
1751    .debug("Quit was cancelled - Desktop aborting quit");
1752  0 return;
1753    }
1754   
1755    // Proceed with quitting
1756  0 quitTheDesktop(disposeFlag,
1757    QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT);
1758    // and exit the JVM
1759  0 instance.quit();
1760    };
1761   
1762  0 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1763    QuitHandler.defaultCancelQuit);
1764    }
1765   
1766    /**
1767    * Exits the program and the JVM.
1768    *
1769    * Don't call this directly
1770    *
1771    * - use desktopQuit() above to tidy up first.
1772    *
1773    * - use closeDesktop() to shutdown Jalview without shutting down the JVM
1774    *
1775    */
 
1776  0 toggle @Override
1777    public void quit()
1778    {
1779    // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1780    // not run a second time if gotQuitResponse flag has been set (i.e. user
1781    // confirmed quit of some kind).
1782  0 Jalview.exit("Desktop exiting.", ExitCode.OK);
1783    }
1784   
 
1785  233 toggle private void storeLastKnownDimensions(String string, Rectangle jc)
1786    {
1787  233 jalview.bin.Console.debug("Storing last known dimensions for " + string
1788    + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1789    + " height:" + jc.height);
1790   
1791  233 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1792  233 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1793  233 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1794  233 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1795    }
1796   
1797    /**
1798    * DOCUMENT ME!
1799    *
1800    * @param e
1801    * DOCUMENT ME!
1802    */
 
1803  0 toggle @Override
1804    public void aboutMenuItem_actionPerformed(ActionEvent e)
1805    {
1806  0 new Thread(new Runnable()
1807    {
 
1808  0 toggle @Override
1809    public void run()
1810    {
1811  0 new SplashScreen(false);
1812    }
1813    }).start();
1814    }
1815   
1816    /**
1817    * Returns the html text for the About screen, including any available version
1818    * number, build details, author details and citation reference, but without
1819    * the enclosing {@code html} tags
1820    *
1821    * @return
1822    */
 
1823  560 toggle public String getAboutMessage()
1824    {
1825  560 StringBuilder message = new StringBuilder(1024);
1826  560 message.append("<div style=\"font-family: sans-serif;\">")
1827    .append("<h1><strong>Version: ")
1828    .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1829    .append("<strong>Built: <em>")
1830    .append(Cache.getDefault("BUILD_DATE", "unknown"))
1831    .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1832    .append("</strong>");
1833   
1834  559 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1835  559 if (latestVersion.equals("Checking"))
1836    {
1837    // JBP removed this message for 2.11: May be reinstated in future version
1838    // message.append("<br>...Checking latest version...</br>");
1839    }
1840  316 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1841    {
1842  316 boolean red = false;
1843  316 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1844    .indexOf("automated build") == -1)
1845    {
1846  316 red = true;
1847    // Displayed when code version and jnlp version do not match and code
1848    // version is not a development build
1849  316 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1850    }
1851   
1852  316 message.append("<br>!! Version ")
1853    .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1854    .append(" is available for download from ")
1855    .append(Cache.getDefault("www.jalview.org",
1856    "https://www.jalview.org"))
1857    .append(" !!");
1858  316 if (red)
1859    {
1860  316 message.append("</div>");
1861    }
1862    }
1863  560 message.append("<br>Authors: ");
1864  559 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1865  560 message.append(CITATION);
1866   
1867  560 message.append("</div>");
1868   
1869  560 return message.toString();
1870    }
1871   
1872    /**
1873    * Action on requesting Help documentation
1874    */
 
1875  0 toggle @Override
1876    public void documentationMenuItem_actionPerformed()
1877    {
1878  0 try
1879    {
1880  0 if (Platform.isJS())
1881    {
1882  0 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1883    }
1884    else
1885    /**
1886    * Java only
1887    *
1888    * @j2sIgnore
1889    */
1890    {
1891  0 Help.showHelpWindow();
1892    }
1893    } catch (Exception ex)
1894    {
1895  0 jalview.bin.Console
1896    .errPrintln("Error opening help: " + ex.getMessage());
1897    }
1898    }
1899   
 
1900  233 toggle @Override
1901    public void closeAll_actionPerformed(ActionEvent e)
1902    {
1903    // TODO show a progress bar while closing?
1904  233 JInternalFrame[] frames = desktop.getAllFrames();
1905  467 for (int i = 0; i < frames.length; i++)
1906    {
1907  234 try
1908    {
1909  234 frames[i].setClosed(true);
1910    } catch (java.beans.PropertyVetoException ex)
1911    {
1912    }
1913    }
1914  233 Jalview.getInstance().setCurrentAlignFrame(null);
1915  233 jalview.bin.Console.info("ALL CLOSED");
1916   
1917    /*
1918    * reset state of singleton objects as appropriate (clear down session state
1919    * when all windows are closed)
1920    */
1921  233 StructureSelectionManager ssm = StructureSelectionManager
1922    .getStructureSelectionManager(this);
1923  233 if (ssm != null)
1924    {
1925  233 ssm.resetAll();
1926    }
1927    }
1928   
 
1929  7 toggle public int structureViewersStillRunningCount()
1930    {
1931  7 int count = 0;
1932  7 JInternalFrame[] frames = desktop.getAllFrames();
1933  14 for (int i = 0; i < frames.length; i++)
1934    {
1935  7 if (frames[i] != null
1936    && frames[i] instanceof JalviewStructureDisplayI)
1937    {
1938  0 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1939  0 count++;
1940    }
1941    }
1942  7 return count;
1943    }
1944   
 
1945  0 toggle @Override
1946    public void raiseRelated_actionPerformed(ActionEvent e)
1947    {
1948  0 reorderAssociatedWindows(false, false);
1949    }
1950   
 
1951  0 toggle @Override
1952    public void minimizeAssociated_actionPerformed(ActionEvent e)
1953    {
1954  0 reorderAssociatedWindows(true, false);
1955    }
1956   
 
1957  0 toggle void closeAssociatedWindows()
1958    {
1959  0 reorderAssociatedWindows(false, true);
1960    }
1961   
1962    /*
1963    * (non-Javadoc)
1964    *
1965    * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1966    * ActionEvent)
1967    */
 
1968  0 toggle @Override
1969    protected void garbageCollect_actionPerformed(ActionEvent e)
1970    {
1971    // We simply collect the garbage
1972  0 jalview.bin.Console.debug("Collecting garbage...");
1973  0 System.gc();
1974  0 jalview.bin.Console.debug("Finished garbage collection.");
1975    }
1976   
1977    /*
1978    * (non-Javadoc)
1979    *
1980    * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1981    * ActionEvent )
1982    */
 
1983  0 toggle @Override
1984    protected void showMemusage_actionPerformed(ActionEvent e)
1985    {
1986  0 desktop.showMemoryUsage(showMemusage.isSelected());
1987    }
1988   
1989    /*
1990    * (non-Javadoc)
1991    *
1992    * @see
1993    * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1994    * )
1995    */
 
1996  0 toggle @Override
1997    protected void showConsole_actionPerformed(ActionEvent e)
1998    {
1999  0 showConsole(showConsole.isSelected());
2000    }
2001   
2002    Console jconsole = null;
2003   
2004    /**
2005    * control whether the java console is visible or not
2006    *
2007    * @param selected
2008    */
 
2009  75 toggle void showConsole(boolean selected)
2010    {
2011    // TODO: decide if we should update properties file
2012  75 if (jconsole != null) // BH 2018
2013    {
2014  75 showConsole.setSelected(selected);
2015  75 Cache.setProperty("SHOW_JAVA_CONSOLE",
2016    Boolean.valueOf(selected).toString());
2017  75 jconsole.setVisible(selected);
2018    }
2019    }
2020   
 
2021  0 toggle void reorderAssociatedWindows(boolean minimize, boolean close)
2022    {
2023  0 JInternalFrame[] frames = desktop.getAllFrames();
2024  0 if (frames == null || frames.length < 1)
2025    {
2026  0 return;
2027    }
2028   
2029  0 AlignmentViewport source = null, target = null;
2030  0 if (frames[0] instanceof AlignFrame)
2031    {
2032  0 source = ((AlignFrame) frames[0]).getCurrentView();
2033    }
2034  0 else if (frames[0] instanceof TreePanel)
2035    {
2036  0 source = ((TreePanel) frames[0]).getViewPort();
2037    }
2038  0 else if (frames[0] instanceof PCAPanel)
2039    {
2040  0 source = ((PCAPanel) frames[0]).av;
2041    }
2042  0 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
2043    {
2044  0 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
2045    }
2046   
2047  0 if (source != null)
2048    {
2049  0 for (int i = 0; i < frames.length; i++)
2050    {
2051  0 target = null;
2052  0 if (frames[i] == null)
2053    {
2054  0 continue;
2055    }
2056  0 if (frames[i] instanceof AlignFrame)
2057    {
2058  0 target = ((AlignFrame) frames[i]).getCurrentView();
2059    }
2060  0 else if (frames[i] instanceof TreePanel)
2061    {
2062  0 target = ((TreePanel) frames[i]).getViewPort();
2063    }
2064  0 else if (frames[i] instanceof PCAPanel)
2065    {
2066  0 target = ((PCAPanel) frames[i]).av;
2067    }
2068  0 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
2069    {
2070  0 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
2071    }
2072   
2073  0 if (source == target)
2074    {
2075  0 try
2076    {
2077  0 if (close)
2078    {
2079  0 frames[i].setClosed(true);
2080    }
2081    else
2082    {
2083  0 frames[i].setIcon(minimize);
2084  0 if (!minimize)
2085    {
2086  0 frames[i].toFront();
2087    }
2088    }
2089   
2090    } catch (java.beans.PropertyVetoException ex)
2091    {
2092    }
2093    }
2094    }
2095    }
2096    }
2097   
2098    /**
2099    * DOCUMENT ME!
2100    *
2101    * @param e
2102    * DOCUMENT ME!
2103    */
 
2104  0 toggle @Override
2105    protected void preferences_actionPerformed(ActionEvent e)
2106    {
2107  0 Preferences.openPreferences();
2108    }
2109   
2110    /**
2111    * Prompts the user to choose a file and then saves the Jalview state as a
2112    * Jalview project file
2113    */
 
2114  0 toggle @Override
2115    public void saveState_actionPerformed()
2116    {
2117  0 saveState_actionPerformed(false);
2118    }
2119   
 
2120  2 toggle public void saveState_actionPerformed(boolean saveAs)
2121    {
2122  2 java.io.File projectFile = getProjectFile();
2123    // autoSave indicates we already have a file and don't need to ask
2124  2 boolean autoSave = projectFile != null && !saveAs
2125    && BackupFiles.getEnabled();
2126   
2127    // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
2128    // projectFile='"+projectFile+"',
2129    // saveAs="+saveAs+", Backups
2130    // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
2131   
2132  2 boolean approveSave = false;
2133  2 if (!autoSave)
2134    {
2135  0 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
2136    "Jalview Project");
2137   
2138  0 chooser.setFileView(new JalviewFileView());
2139  0 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
2140   
2141  0 int value = chooser.showSaveDialog(this);
2142   
2143  0 if (value == JalviewFileChooser.APPROVE_OPTION)
2144    {
2145  0 projectFile = chooser.getSelectedFile();
2146  0 setProjectFile(projectFile);
2147  0 approveSave = true;
2148    }
2149    }
2150   
2151  2 if (approveSave || autoSave)
2152    {
2153  2 final Desktop me = this;
2154  2 final java.io.File chosenFile = projectFile;
2155  2 new Thread(new Runnable()
2156    {
 
2157  2 toggle @Override
2158    public void run()
2159    {
2160    // TODO: refactor to Jalview desktop session controller action.
2161  2 setProgressBar(MessageManager.formatMessage(
2162    "label.saving_jalview_project", new Object[]
2163    { chosenFile.getName() }), chosenFile.hashCode());
2164  2 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
2165    // TODO catch and handle errors for savestate
2166    // TODO prevent user from messing with the Desktop whilst we're saving
2167  2 try
2168    {
2169  2 boolean doBackup = BackupFiles.getEnabled();
2170  2 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
2171    : null;
2172   
2173  2 new Jalview2XML().saveState(
2174  2 doBackup ? backupfiles.getTempFile() : chosenFile);
2175   
2176  2 if (doBackup)
2177    {
2178  2 backupfiles.setWriteSuccess(true);
2179  2 backupfiles.rollBackupsAndRenameTempFile();
2180    }
2181    } catch (OutOfMemoryError oom)
2182    {
2183  0 new OOMWarning("Whilst saving current state to "
2184    + chosenFile.getName(), oom);
2185    } catch (Exception ex)
2186    {
2187  0 jalview.bin.Console.error("Problems whilst trying to save to "
2188    + chosenFile.getName(), ex);
2189  0 JvOptionPane.showMessageDialog(me,
2190    MessageManager.formatMessage(
2191    "label.error_whilst_saving_current_state_to",
2192    new Object[]
2193    { chosenFile.getName() }),
2194    MessageManager.getString("label.couldnt_save_project"),
2195    JvOptionPane.WARNING_MESSAGE);
2196    }
2197  2 setProgressBar(null, chosenFile.hashCode());
2198    }
2199    }).start();
2200    }
2201    }
2202   
 
2203  0 toggle @Override
2204    public void saveAsState_actionPerformed(ActionEvent e)
2205    {
2206  0 saveState_actionPerformed(true);
2207    }
2208   
 
2209  7 toggle protected void setProjectFile(File choice)
2210    {
2211  7 this.projectFile = choice;
2212    }
2213   
 
2214  2 toggle public File getProjectFile()
2215    {
2216  2 return this.projectFile;
2217    }
2218   
2219    /**
2220    * Shows a file chooser dialog and tries to read in the selected file as a
2221    * Jalview project
2222    */
 
2223  0 toggle @Override
2224    public void loadState_actionPerformed()
2225    {
2226  0 final String[] suffix = new String[] { "jvp", "jar" };
2227  0 final String[] desc = new String[] { "Jalview Project",
2228    "Jalview Project (old)" };
2229  0 JalviewFileChooser chooser = new JalviewFileChooser(
2230    Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2231    "Jalview Project", true, BackupFiles.getEnabled()); // last two
2232    // booleans:
2233    // allFiles,
2234    // allowBackupFiles
2235  0 chooser.setFileView(new JalviewFileView());
2236  0 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2237  0 chooser.setResponseHandler(0, () -> {
2238  0 File selectedFile = chooser.getSelectedFile();
2239  0 setProjectFile(selectedFile);
2240  0 String choice = selectedFile.getAbsolutePath();
2241  0 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2242  0 new Thread(new Runnable()
2243    {
 
2244  0 toggle @Override
2245    public void run()
2246    {
2247  0 try
2248    {
2249  0 new Jalview2XML().loadJalviewAlign(selectedFile);
2250    } catch (OutOfMemoryError oom)
2251    {
2252  0 new OOMWarning("Whilst loading project from " + choice, oom);
2253    } catch (Exception ex)
2254    {
2255  0 jalview.bin.Console.error(
2256    "Problems whilst loading project from " + choice, ex);
2257  0 JvOptionPane.showMessageDialog(Desktop.desktop,
2258    MessageManager.formatMessage(
2259    "label.error_whilst_loading_project_from",
2260    new Object[]
2261    { choice }),
2262    MessageManager.getString("label.couldnt_load_project"),
2263    JvOptionPane.WARNING_MESSAGE);
2264    }
2265    }
2266    }, "Project Loader").start();
2267    });
2268   
2269  0 chooser.showOpenDialog(this);
2270    }
2271   
 
2272  0 toggle @Override
2273    public void inputSequence_actionPerformed(ActionEvent e)
2274    {
2275  0 new SequenceFetcher(this);
2276    }
2277   
2278    JPanel progressPanel;
2279   
2280    ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2281   
 
2282  293 toggle public void startLoading(final Object fileName)
2283    {
2284  293 if (fileLoadingCount == 0)
2285    {
2286  293 fileLoadingPanels.add(addProgressPanel(MessageManager
2287    .formatMessage("label.loading_file", new Object[]
2288    { fileName })));
2289    }
2290  293 fileLoadingCount++;
2291    }
2292   
 
2293  413 toggle private JPanel addProgressPanel(String string)
2294    {
2295  413 if (progressPanel == null)
2296    {
2297  292 progressPanel = new JPanel(new GridLayout(1, 1));
2298  292 totalProgressCount = 0;
2299  292 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2300    }
2301  413 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2302  413 JProgressBar progressBar = new JProgressBar();
2303  413 progressBar.setIndeterminate(true);
2304   
2305  413 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2306   
2307  413 thisprogress.add(progressBar, BorderLayout.CENTER);
2308  413 progressPanel.add(thisprogress);
2309  413 ((GridLayout) progressPanel.getLayout()).setRows(
2310    ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2311  413 ++totalProgressCount;
2312  413 instance.validate();
2313  413 return thisprogress;
2314    }
2315   
2316    int totalProgressCount = 0;
2317   
 
2318  405 toggle private void removeProgressPanel(JPanel progbar)
2319    {
2320  405 if (progressPanel != null)
2321    {
2322  396 synchronized (progressPanel)
2323    {
2324  396 progressPanel.remove(progbar);
2325  396 GridLayout gl = (GridLayout) progressPanel.getLayout();
2326  396 gl.setRows(gl.getRows() - 1);
2327  396 if (--totalProgressCount < 1)
2328    {
2329  273 this.getContentPane().remove(progressPanel);
2330  273 progressPanel = null;
2331    }
2332    }
2333    }
2334  405 validate();
2335    }
2336   
 
2337  319 toggle public void stopLoading()
2338    {
2339  319 fileLoadingCount--;
2340  319 if (fileLoadingCount < 1)
2341    {
2342  608 while (fileLoadingPanels.size() > 0)
2343    {
2344  289 removeProgressPanel(fileLoadingPanels.remove(0));
2345    }
2346  319 fileLoadingPanels.clear();
2347  319 fileLoadingCount = 0;
2348    }
2349  319 validate();
2350    }
2351   
 
2352  10 toggle public static int getViewCount(String alignmentId)
2353    {
2354  10 AlignmentViewport[] aps = getViewports(alignmentId);
2355  10 return (aps == null) ? 0 : aps.length;
2356    }
2357   
2358    /**
2359    *
2360    * @param alignmentId
2361    * - if null, all sets are returned
2362    * @return all AlignmentPanels concerning the alignmentId sequence set
2363    */
 
2364  89 toggle public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2365    {
2366  89 if (Desktop.desktop == null)
2367    {
2368    // no frames created and in headless mode
2369    // TODO: verify that frames are recoverable when in headless mode
2370  0 return null;
2371    }
2372  89 List<AlignmentPanel> aps = new ArrayList<>();
2373  89 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2374  89 if (frames == null)
2375    {
2376  21 return null;
2377    }
2378  68 for (AlignFrame af : frames)
2379    {
2380  178 for (AlignmentPanel ap : af.alignPanels)
2381    {
2382  228 if (alignmentId == null
2383    || alignmentId.equals(ap.av.getSequenceSetId()))
2384    {
2385  138 aps.add(ap);
2386    }
2387    }
2388    }
2389  68 if (aps.size() == 0)
2390    {
2391  11 return null;
2392    }
2393  57 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2394  57 return vap;
2395    }
2396   
2397    /**
2398    * get all the viewports on an alignment.
2399    *
2400    * @param sequenceSetId
2401    * unique alignment id (may be null - all viewports returned in that
2402    * case)
2403    * @return all viewports on the alignment bound to sequenceSetId
2404    */
 
2405  10 toggle public static AlignmentViewport[] getViewports(String sequenceSetId)
2406    {
2407  10 List<AlignmentViewport> viewp = new ArrayList<>();
2408  10 if (desktop != null)
2409    {
2410  10 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2411   
2412  10 for (AlignFrame afr : frames)
2413    {
2414  11 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2415    .equals(sequenceSetId))
2416    {
2417  10 if (afr.alignPanels != null)
2418    {
2419  10 for (AlignmentPanel ap : afr.alignPanels)
2420    {
2421  11 if (sequenceSetId == null
2422    || sequenceSetId.equals(ap.av.getSequenceSetId()))
2423    {
2424  11 viewp.add(ap.av);
2425    }
2426    }
2427    }
2428    else
2429    {
2430  0 viewp.add(afr.getViewport());
2431    }
2432    }
2433    }
2434  10 if (viewp.size() > 0)
2435    {
2436  10 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2437    }
2438    }
2439  0 return null;
2440    }
2441   
2442    /**
2443    * Explode the views in the given frame into separate AlignFrame
2444    *
2445    * @param af
2446    */
 
2447  1 toggle public static void explodeViews(AlignFrame af)
2448    {
2449  1 int size = af.alignPanels.size();
2450  1 if (size < 2)
2451    {
2452  0 return;
2453    }
2454   
2455    // FIXME: ideally should use UI interface API
2456  1 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2457    && af.featureSettings.isOpen()) ? af.featureSettings : null;
2458  1 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2459  6 for (int i = 0; i < size; i++)
2460    {
2461  5 AlignmentPanel ap = af.alignPanels.get(i);
2462   
2463  5 AlignFrame newaf = new AlignFrame(ap);
2464   
2465    // transfer reference for existing feature settings to new alignFrame
2466  5 if (ap == af.alignPanel)
2467    {
2468  1 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2469    {
2470  0 newaf.featureSettings = viewFeatureSettings;
2471    }
2472  1 newaf.setFeatureSettingsGeometry(fsBounds);
2473    }
2474   
2475    /*
2476    * Restore the view's last exploded frame geometry if known. Multiple views from
2477    * one exploded frame share and restore the same (frame) position and size.
2478    */
2479  5 Rectangle geometry = ap.av.getExplodedGeometry();
2480  5 if (geometry != null)
2481    {
2482  5 newaf.setBounds(geometry);
2483    }
2484   
2485  5 ap.av.setGatherViewsHere(false);
2486   
2487  5 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2488    AlignFrame.DEFAULT_HEIGHT);
2489    // and materialise a new feature settings dialog instance for the new
2490    // alignframe
2491    // (closes the old as if 'OK' was pressed)
2492  5 if (ap == af.alignPanel && newaf.featureSettings != null
2493    && newaf.featureSettings.isOpen()
2494    && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2495    {
2496  0 newaf.showFeatureSettingsUI();
2497    }
2498    }
2499   
2500  1 af.featureSettings = null;
2501  1 af.alignPanels.clear();
2502  1 af.closeMenuItem_actionPerformed(true);
2503   
2504    }
2505   
2506    /**
2507    * Gather expanded views (separate AlignFrame's) with the same sequence set
2508    * identifier back in to this frame as additional views, and close the
2509    * expanded views. Note the expanded frames may themselves have multiple
2510    * views. We take the lot.
2511    *
2512    * @param source
2513    */
 
2514  14 toggle public void gatherViews(AlignFrame source)
2515    {
2516  14 source.viewport.setGatherViewsHere(true);
2517  14 source.viewport.setExplodedGeometry(source.getBounds());
2518  14 JInternalFrame[] frames = desktop.getAllFrames();
2519  14 String viewId = source.viewport.getSequenceSetId();
2520  121 for (int t = 0; t < frames.length; t++)
2521    {
2522  107 if (frames[t] instanceof AlignFrame && frames[t] != source)
2523    {
2524  54 AlignFrame af = (AlignFrame) frames[t];
2525  54 boolean gatherThis = false;
2526  119 for (int a = 0; a < af.alignPanels.size(); a++)
2527    {
2528  65 AlignmentPanel ap = af.alignPanels.get(a);
2529  65 if (viewId.equals(ap.av.getSequenceSetId()))
2530    {
2531  39 gatherThis = true;
2532  39 ap.av.setGatherViewsHere(false);
2533  39 ap.av.setExplodedGeometry(af.getBounds());
2534  39 source.addAlignmentPanel(ap, false);
2535    }
2536    }
2537   
2538  54 if (gatherThis)
2539    {
2540  39 if (af.featureSettings != null && af.featureSettings.isOpen())
2541    {
2542  0 if (source.featureSettings == null)
2543    {
2544    // preserve the feature settings geometry for this frame
2545  0 source.featureSettings = af.featureSettings;
2546  0 source.setFeatureSettingsGeometry(
2547    af.getFeatureSettingsGeometry());
2548    }
2549    else
2550    {
2551    // close it and forget
2552  0 af.featureSettings.close();
2553    }
2554    }
2555  39 af.alignPanels.clear();
2556  39 af.closeMenuItem_actionPerformed(true);
2557    }
2558    }
2559    }
2560   
2561    // refresh the feature setting UI for the source frame if it exists
2562  14 if (source.featureSettings != null && source.featureSettings.isOpen())
2563    {
2564  0 source.showFeatureSettingsUI();
2565    }
2566   
2567    }
2568   
 
2569  37 toggle public JInternalFrame[] getAllFrames()
2570    {
2571  37 return desktop.getAllFrames();
2572    }
2573   
2574    /**
2575    * Checks the given url to see if it gives a response indicating that the user
2576    * should be informed of a new questionnaire.
2577    *
2578    * @param url
2579    */
 
2580  36 toggle public void checkForQuestionnaire(String url)
2581    {
2582  36 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2583    // javax.swing.SwingUtilities.invokeLater(jvq);
2584  36 new Thread(jvq).start();
2585    }
2586   
 
2587  75 toggle public void checkURLLinks()
2588    {
2589    // Thread off the URL link checker
2590  75 addDialogThread(new Runnable()
2591    {
 
2592  73 toggle @Override
2593    public void run()
2594    {
2595  73 if (Cache.getDefault("CHECKURLLINKS", true))
2596    {
2597    // check what the actual links are - if it's just the default don't
2598    // bother with the warning
2599  73 List<String> links = Preferences.sequenceUrlLinks
2600    .getLinksForMenu();
2601   
2602    // only need to check links if there is one with a
2603    // SEQUENCE_ID which is not the default EMBL_EBI link
2604  73 ListIterator<String> li = links.listIterator();
2605  73 boolean check = false;
2606  73 List<JLabel> urls = new ArrayList<>();
2607  215 while (li.hasNext())
2608    {
2609  142 String link = li.next();
2610  142 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2611    && !UrlConstants.isDefaultString(link))
2612    {
2613  0 check = true;
2614  0 int barPos = link.indexOf("|");
2615  0 String urlMsg = barPos == -1 ? link
2616    : link.substring(0, barPos) + ": "
2617    + link.substring(barPos + 1);
2618  0 urls.add(new JLabel(urlMsg));
2619    }
2620    }
2621  73 if (!check)
2622    {
2623  73 return;
2624    }
2625   
2626    // ask user to check in case URL links use old style tokens
2627    // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2628  0 JPanel msgPanel = new JPanel();
2629  0 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2630  0 msgPanel.add(Box.createVerticalGlue());
2631  0 JLabel msg = new JLabel(MessageManager
2632    .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2633  0 JLabel msg2 = new JLabel(MessageManager
2634    .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2635  0 msgPanel.add(msg);
2636  0 for (JLabel url : urls)
2637    {
2638  0 msgPanel.add(url);
2639    }
2640  0 msgPanel.add(msg2);
2641   
2642  0 final JCheckBox jcb = new JCheckBox(
2643    MessageManager.getString("label.do_not_display_again"));
2644  0 jcb.addActionListener(new ActionListener()
2645    {
 
2646  0 toggle @Override
2647    public void actionPerformed(ActionEvent e)
2648    {
2649    // update Cache settings for "don't show this again"
2650  0 boolean showWarningAgain = !jcb.isSelected();
2651  0 Cache.setProperty("CHECKURLLINKS",
2652    Boolean.valueOf(showWarningAgain).toString());
2653    }
2654    });
2655  0 msgPanel.add(jcb);
2656   
2657  0 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2658    MessageManager
2659    .getString("label.SEQUENCE_ID_no_longer_used"),
2660    JvOptionPane.WARNING_MESSAGE);
2661    }
2662    }
2663    });
2664    }
2665   
2666    /**
2667    * Proxy class for JDesktopPane which optionally displays the current memory
2668    * usage and highlights the desktop area with a red bar if free memory runs
2669    * low.
2670    *
2671    * @author AMW
2672    */
 
2673    public class MyDesktopPane extends JDesktopPane implements Runnable
2674    {
2675    private static final float ONE_MB = 1048576f;
2676   
2677    boolean showMemoryUsage = false;
2678   
2679    Runtime runtime;
2680   
2681    java.text.NumberFormat df;
2682   
2683    float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2684    percentUsage;
2685   
 
2686  75 toggle public MyDesktopPane(boolean showMemoryUsage)
2687    {
2688  75 showMemoryUsage(showMemoryUsage);
2689    }
2690   
 
2691  75 toggle public void showMemoryUsage(boolean showMemory)
2692    {
2693  75 this.showMemoryUsage = showMemory;
2694  75 if (showMemory)
2695    {
2696  0 Thread worker = new Thread(this);
2697  0 worker.start();
2698    }
2699  75 repaint();
2700    }
2701   
 
2702  582 toggle public boolean isShowMemoryUsage()
2703    {
2704  582 return showMemoryUsage;
2705    }
2706   
 
2707  0 toggle @Override
2708    public void run()
2709    {
2710  0 df = java.text.NumberFormat.getNumberInstance();
2711  0 df.setMaximumFractionDigits(2);
2712  0 runtime = Runtime.getRuntime();
2713   
2714  0 while (showMemoryUsage)
2715    {
2716  0 try
2717    {
2718  0 maxMemory = runtime.maxMemory() / ONE_MB;
2719  0 allocatedMemory = runtime.totalMemory() / ONE_MB;
2720  0 freeMemory = runtime.freeMemory() / ONE_MB;
2721  0 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2722   
2723  0 percentUsage = (totalFreeMemory / maxMemory) * 100;
2724   
2725    // if (percentUsage < 20)
2726    {
2727    // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2728    // Color.red);
2729    // instance.set.setBorder(border1);
2730    }
2731  0 repaint();
2732    // sleep after showing usage
2733  0 Thread.sleep(3000);
2734    } catch (Exception ex)
2735    {
2736  0 ex.printStackTrace();
2737    }
2738    }
2739    }
2740   
 
2741  2778 toggle @Override
2742    public void paintComponent(Graphics g)
2743    {
2744  2778 if (showMemoryUsage && g != null && df != null)
2745    {
2746  0 if (percentUsage < 20)
2747    {
2748  0 g.setColor(Color.red);
2749    }
2750  0 FontMetrics fm = g.getFontMetrics();
2751  0 if (fm != null)
2752    {
2753  0 g.drawString(MessageManager.formatMessage("label.memory_stats",
2754    new Object[]
2755    { df.format(totalFreeMemory), df.format(maxMemory),
2756    df.format(percentUsage) }),
2757    10, getHeight() - fm.getHeight());
2758    }
2759    }
2760   
2761    // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2762  2778 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2763    }
2764    }
2765   
2766    /**
2767    * Accessor method to quickly get all the AlignmentFrames loaded.
2768    *
2769    * @return an array of AlignFrame, or null if none found
2770    */
 
2771  305 toggle @Override
2772    public AlignFrame[] getAlignFrames()
2773    {
2774  305 if (desktop == null)
2775    {
2776  0 return null;
2777    }
2778   
2779  305 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2780   
2781  305 if (frames == null)
2782    {
2783  0 return null;
2784    }
2785  305 List<AlignFrame> avp = new ArrayList<>();
2786    // REVERSE ORDER
2787  1007 for (int i = frames.length - 1; i > -1; i--)
2788    {
2789  702 if (frames[i] instanceof AlignFrame)
2790    {
2791  419 avp.add((AlignFrame) frames[i]);
2792    }
2793  283 else if (frames[i] instanceof SplitFrame)
2794    {
2795    /*
2796    * Also check for a split frame containing an AlignFrame
2797    */
2798  48 GSplitFrame sf = (GSplitFrame) frames[i];
2799  48 if (sf.getTopFrame() instanceof AlignFrame)
2800    {
2801  48 avp.add((AlignFrame) sf.getTopFrame());
2802    }
2803  48 if (sf.getBottomFrame() instanceof AlignFrame)
2804    {
2805  48 avp.add((AlignFrame) sf.getBottomFrame());
2806    }
2807    }
2808    }
2809  305 if (avp.size() == 0)
2810    {
2811  50 return null;
2812    }
2813  255 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2814  255 return afs;
2815    }
2816   
2817    /**
2818    * static version
2819    */
 
2820  305 toggle public static AlignFrame[] getDesktopAlignFrames()
2821    {
2822  305 if (Jalview.isHeadlessMode())
2823    {
2824    // Desktop.desktop is null in headless mode
2825  0 return Jalview.getInstance().getAlignFrames();
2826    }
2827   
2828  305 if (instance != null && desktop != null)
2829    {
2830  305 return instance.getAlignFrames();
2831    }
2832   
2833  0 return null;
2834    }
2835   
2836    /**
2837    * Returns an array of any AppJmol frames in the Desktop (or null if none).
2838    *
2839    * @return
2840    */
 
2841  0 toggle public GStructureViewer[] getJmols()
2842    {
2843  0 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2844   
2845  0 if (frames == null)
2846    {
2847  0 return null;
2848    }
2849  0 List<GStructureViewer> avp = new ArrayList<>();
2850    // REVERSE ORDER
2851  0 for (int i = frames.length - 1; i > -1; i--)
2852    {
2853  0 if (frames[i] instanceof AppJmol)
2854    {
2855  0 GStructureViewer af = (GStructureViewer) frames[i];
2856  0 avp.add(af);
2857    }
2858    }
2859  0 if (avp.size() == 0)
2860    {
2861  0 return null;
2862    }
2863  0 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2864  0 return afs;
2865    }
2866   
2867    /**
2868    * Add Groovy Support to Jalview
2869    */
 
2870  0 toggle @Override
2871    public void groovyShell_actionPerformed()
2872    {
2873  0 try
2874    {
2875  0 openGroovyConsole();
2876    } catch (Exception ex)
2877    {
2878  0 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2879  0 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2880   
2881    MessageManager.getString("label.couldnt_create_groovy_shell"),
2882    MessageManager.getString("label.groovy_support_failed"),
2883    JvOptionPane.ERROR_MESSAGE);
2884    }
2885    }
2886   
2887    /**
2888    * Open the Groovy console
2889    */
 
2890  0 toggle void openGroovyConsole()
2891    {
2892  0 if (groovyConsole == null)
2893    {
2894  0 JalviewObjectI j = new JalviewObject(this);
2895  0 groovyConsole = new groovy.console.ui.Console();
2896  0 groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j);
2897  0 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2898    getCurrentAlignFrame());
2899  0 groovyConsole.run();
2900   
2901    /*
2902    * We allow only one console at a time, so that AlignFrame menu option
2903    * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2904    * enable 'Run script', when the console is opened, and the reverse when it is
2905    * closed
2906    */
2907  0 Window window = (Window) groovyConsole.getFrame();
2908  0 window.addWindowListener(new WindowAdapter()
2909    {
 
2910  0 toggle @Override
2911    public void windowClosed(WindowEvent e)
2912    {
2913    /*
2914    * rebind CMD-Q from Groovy Console to Jalview Quit
2915    */
2916  0 addQuitHandler();
2917  0 enableExecuteGroovy(false);
2918    }
2919    });
2920    }
2921   
2922    /*
2923    * show Groovy console window (after close and reopen)
2924    */
2925  0 ((Window) groovyConsole.getFrame()).setVisible(true);
2926   
2927    /*
2928    * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2929    * opening a second console
2930    */
2931  0 enableExecuteGroovy(true);
2932    }
2933   
2934    /**
2935    * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2936    * binding when opened
2937    */
 
2938  0 toggle protected void addQuitHandler()
2939    {
2940  0 getRootPane()
2941    .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2942    KeyStroke
2943    .getKeyStroke(KeyEvent.VK_Q,
2944    jalview.util.ShortcutKeyMaskExWrapper
2945    .getMenuShortcutKeyMaskEx()),
2946    "Quit");
2947  0 getRootPane().getActionMap().put("Quit", new AbstractAction()
2948    {
 
2949  0 toggle @Override
2950    public void actionPerformed(ActionEvent e)
2951    {
2952  0 desktopQuit();
2953    }
2954    });
2955    }
2956   
2957    /**
2958    * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2959    *
2960    * @param enabled
2961    * true if Groovy console is open
2962    */
 
2963  0 toggle public void enableExecuteGroovy(boolean enabled)
2964    {
2965    /*
2966    * disable opening a second Groovy console (or re-enable when the console is
2967    * closed)
2968    */
2969  0 groovyShell.setEnabled(!enabled);
2970   
2971  0 AlignFrame[] alignFrames = getDesktopAlignFrames();
2972  0 if (alignFrames != null)
2973    {
2974  0 for (AlignFrame af : alignFrames)
2975    {
2976  0 af.setGroovyEnabled(enabled);
2977    }
2978    }
2979    }
2980   
2981    /**
2982    * Progress bars managed by the IProgressIndicator method.
2983    * TODO - delegate to jalview.gui.ProgressBar
2984    */
2985    private Hashtable<Long, JPanel> progressBars;
2986   
2987    private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2988   
2989    private Hashtable<Long, String> progressBarMessages;
2990   
2991    /*
2992    * (non-Javadoc)
2993    *
2994    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2995    */
 
2996  236 toggle @Override
2997    public void setProgressBar(String message, long id)
2998    {
2999  236 if (progressBars == null)
3000    {
3001  46 progressBars = new Hashtable<>();
3002  46 progressBarHandlers = new Hashtable<>();
3003  46 progressBarMessages = new Hashtable<>();
3004    }
3005   
3006  236 if (progressBars.get(Long.valueOf(id)) != null)
3007    {
3008  116 JPanel panel = progressBars.remove(Long.valueOf(id));
3009  116 if (progressBarHandlers.contains(Long.valueOf(id)))
3010    {
3011  0 progressBarHandlers.remove(Long.valueOf(id));
3012    }
3013  116 removeProgressPanel(panel);
3014    }
3015    else
3016    {
3017  120 progressBars.put(Long.valueOf(id), addProgressPanel(message));
3018  120 if (message!=null) {
3019  118 progressBarMessages.put(id, message);
3020    } else {
3021  2 progressBarMessages.remove(id);
3022    }
3023    }
3024    }
3025   
 
3026  0 toggle @Override
3027    public JProgressBar getProgressBar(long id)
3028    {
3029  0 if (progressBars == null)
3030  0 return null;
3031   
3032  0 if (progressBars.get(Long.valueOf(id)) == null)
3033  0 return null;
3034   
3035  0 for (Component c : progressBars.get(Long.valueOf(id)).getComponents())
3036    {
3037  0 if (c.getClass() == JProgressBar.class)
3038  0 return (JProgressBar) c;
3039    }
3040  0 return null;
3041    }
3042   
3043    /*
3044    * (non-Javadoc)
3045    *
3046    * @see jalview.gui.IProgressIndicator#registerHandler(long,
3047    * jalview.gui.IProgressIndicatorHandler)
3048    */
 
3049  0 toggle @Override
3050    public void registerHandler(final long id,
3051    final IProgressIndicatorHandler handler)
3052    {
3053  0 if (progressBarHandlers == null
3054    || !progressBars.containsKey(Long.valueOf(id)))
3055    {
3056  0 throw new Error(MessageManager.getString(
3057    "error.call_setprogressbar_before_registering_handler"));
3058    }
3059  0 progressBarHandlers.put(Long.valueOf(id), handler);
3060  0 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
3061  0 if (handler.canCancel())
3062    {
3063  0 JButton cancel = new JButton(
3064    MessageManager.getString("action.cancel"));
3065  0 final IProgressIndicator us = this;
3066  0 cancel.addActionListener(new ActionListener()
3067    {
3068   
 
3069  0 toggle @Override
3070    public void actionPerformed(ActionEvent e)
3071    {
3072  0 handler.cancelActivity(id);
3073  0 us.setProgressBar(MessageManager
3074    .formatMessage("label.cancelled_params", new Object[]
3075    { ((JLabel) progressPanel.getComponent(0)).getText() }),
3076    id);
3077    }
3078    });
3079  0 progressPanel.add(cancel, BorderLayout.EAST);
3080    }
3081    }
3082   
3083   
 
3084  0 toggle @Override
3085    public String getMessage(long id)
3086    {
3087  0 return progressBarMessages.get(id);
3088    }
3089   
3090    /**
3091    * change the text shown alongside a progress bar
3092    * @param id
3093    * @param message
3094    */
 
3095  0 toggle @Override
3096    public void setProgressBarMessage(long id,String message)
3097    {
3098  0 Container progBar = progressBars.get(id);
3099  0 if (progBar == null || progBar.getComponentCount() == 0)
3100    {
3101  0 return;
3102    }
3103  0 for (Component component : progBar.getComponents())
3104    {
3105  0 if (component.getClass().equals(JLabel.class))
3106    {
3107  0 ((JLabel) component).setText(message);;
3108  0 progBar.revalidate();
3109    }
3110    }
3111    }
3112   
3113    /**
3114    *
3115    * @return true if any progress bars are still active
3116    */
 
3117  10 toggle @Override
3118    public boolean operationInProgress()
3119    {
3120  10 if (progressBars != null && progressBars.size() > 0)
3121    {
3122  0 return true;
3123    }
3124  10 return false;
3125    }
3126   
3127    /**
3128    * This will return the first AlignFrame holding the given viewport instance.
3129    * It will break if there are more than one AlignFrames viewing a particular
3130    * av.
3131    *
3132    * @param viewport
3133    * @return alignFrame for viewport
3134    */
 
3135  1 toggle public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
3136    {
3137  1 if (desktop != null)
3138    {
3139  1 AlignmentPanel[] aps = getAlignmentPanels(
3140    viewport.getSequenceSetId());
3141  1 for (int panel = 0; aps != null && panel < aps.length; panel++)
3142    {
3143  1 if (aps[panel] != null && aps[panel].av == viewport)
3144    {
3145  1 return aps[panel].alignFrame;
3146    }
3147    }
3148    }
3149  0 return null;
3150    }
3151   
 
3152  0 toggle public VamsasApplication getVamsasApplication()
3153    {
3154    // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
3155  0 return null;
3156   
3157    }
3158   
3159    /**
3160    * flag set if jalview GUI is being operated programmatically
3161    */
3162    private boolean inBatchMode = false;
3163   
3164    /**
3165    * check if jalview GUI is being operated programmatically
3166    *
3167    * @return inBatchMode
3168    */
 
3169  27 toggle public boolean isInBatchMode()
3170    {
3171  27 return inBatchMode;
3172    }
3173   
3174    /**
3175    * set flag if jalview GUI is being operated programmatically
3176    *
3177    * @param inBatchMode
3178    */
 
3179  145 toggle public void setInBatchMode(boolean inBatchMode)
3180    {
3181  145 this.inBatchMode = inBatchMode;
3182    }
3183   
3184    /**
3185    * start service discovery and wait till it is done
3186    */
 
3187  65 toggle public void startServiceDiscovery()
3188    {
3189  65 startServiceDiscovery(false);
3190    }
3191   
3192    /**
3193    * start service discovery threads - blocking or non-blocking
3194    *
3195    * @param blocking
3196    */
 
3197  65 toggle public void startServiceDiscovery(boolean blocking)
3198    {
3199  65 startServiceDiscovery(blocking, false);
3200    }
3201   
3202    /**
3203    * start service discovery threads
3204    *
3205    * @param blocking
3206    * - false means call returns immediately
3207    * @param ignore_SHOW_JWS2_SERVICES_preference
3208    * - when true JABA services are discovered regardless of user's JWS2
3209    * discovery preference setting
3210    */
 
3211  65 toggle public void startServiceDiscovery(boolean blocking,
3212    boolean ignore_SHOW_JWS2_SERVICES_preference)
3213    {
3214  65 boolean alive = true;
3215  65 Thread t0 = null, t1 = null, t2 = null;
3216    // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
3217  65 if (true)
3218    {
3219    // todo: changesupport handlers need to be transferred
3220  65 if (discoverer == null)
3221    {
3222  6 discoverer = new jalview.ws.jws1.Discoverer();
3223    // register PCS handler for desktop.
3224  6 discoverer.addPropertyChangeListener(changeSupport);
3225    }
3226    // JAL-940 - disabled JWS1 service configuration - always start discoverer
3227    // until we phase out completely
3228  65 (t0 = new Thread(discoverer)).start();
3229    }
3230   
3231  65 if (ignore_SHOW_JWS2_SERVICES_preference
3232    || Cache.getDefault("SHOW_JWS2_SERVICES", true))
3233    {
3234  65 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3235    .startDiscoverer(changeSupport);
3236    }
3237  65 Thread t3 = null;
3238    {
3239    // TODO: do rest service discovery
3240    }
3241  65 if (blocking)
3242    {
3243  0 while (alive)
3244    {
3245  0 try
3246    {
3247  0 Thread.sleep(15);
3248    } catch (Exception e)
3249    {
3250    }
3251  0 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
3252    || (t3 != null && t3.isAlive())
3253    || (t0 != null && t0.isAlive());
3254    }
3255    }
3256    }
3257   
3258    /**
3259    * called to check if the service discovery process completed successfully.
3260    *
3261    * @param evt
3262    */
 
3263  1325 toggle protected void JalviewServicesChanged(PropertyChangeEvent evt)
3264    {
3265  1325 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
3266    {
3267  1325 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3268    .getErrorMessages();
3269  1325 if (ermsg != null)
3270    {
3271  0 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3272    {
3273  0 if (serviceChangedDialog == null)
3274    {
3275    // only run if we aren't already displaying one of these.
3276  0 addDialogThread(serviceChangedDialog = new Runnable()
3277    {
 
3278  0 toggle @Override
3279    public void run()
3280    {
3281   
3282    /*
3283    * JalviewDialog jd =new JalviewDialog() {
3284    *
3285    * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3286    *
3287    * }@Override protected void okPressed() { // TODO Auto-generated method stub
3288    *
3289    * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3290    *
3291    * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3292    * ermsg +
3293    * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3294    * + " or mis-configured HTTP proxy settings.<br/>" +
3295    * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3296    * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3297    * true, true, "Web Service Configuration Problem", 450, 400);
3298    *
3299    * jd.waitForInput();
3300    */
3301  0 JvOptionPane.showConfirmDialog(Desktop.desktop,
3302    new JLabel("<html><table width=\"450\"><tr><td>"
3303    + ermsg + "</td></tr></table>"
3304    + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3305    + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3306    + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3307    + " Tools->Preferences dialog box to change them.</p></html>"),
3308    "Web Service Configuration Problem",
3309    JvOptionPane.DEFAULT_OPTION,
3310    JvOptionPane.ERROR_MESSAGE);
3311  0 serviceChangedDialog = null;
3312   
3313    }
3314    });
3315    }
3316    }
3317    else
3318    {
3319  0 jalview.bin.Console.error(
3320    "Errors reported by JABA discovery service. Check web services preferences.\n"
3321    + ermsg);
3322    }
3323    }
3324    }
3325    }
3326   
3327    private Runnable serviceChangedDialog = null;
3328   
3329    /**
3330    * start a thread to open a URL in the configured browser. Pops up a warning
3331    * dialog to the user if there is an exception when calling out to the browser
3332    * to open the URL.
3333    *
3334    * @param url
3335    */
 
3336  0 toggle public static void showUrl(final String url)
3337    {
3338  0 if (url != null && !url.trim().equals(""))
3339    {
3340  0 jalview.bin.Console.info("Opening URL: " + url);
3341  0 showUrl(url, Desktop.instance);
3342    }
3343    else
3344    {
3345  0 jalview.bin.Console.warn("Ignoring attempt to show an empty URL.");
3346    }
3347   
3348    }
3349   
3350    /**
3351    * Like showUrl but allows progress handler to be specified
3352    *
3353    * @param url
3354    * @param progress
3355    * (null) or object implementing IProgressIndicator
3356    */
 
3357  0 toggle public static void showUrl(final String url,
3358    final IProgressIndicator progress)
3359    {
3360  0 new Thread(new Runnable()
3361    {
 
3362  0 toggle @Override
3363    public void run()
3364    {
3365  0 try
3366    {
3367  0 if (progress != null)
3368    {
3369  0 progress.setProgressBar(MessageManager
3370    .formatMessage("status.opening_params", new Object[]
3371    { url }), this.hashCode());
3372    }
3373  0 jalview.util.BrowserLauncher.openURL(url);
3374    } catch (Exception ex)
3375    {
3376  0 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3377    MessageManager
3378    .getString("label.web_browser_not_found_unix"),
3379    MessageManager.getString("label.web_browser_not_found"),
3380    JvOptionPane.WARNING_MESSAGE);
3381   
3382  0 ex.printStackTrace();
3383    }
3384  0 if (progress != null)
3385    {
3386  0 progress.setProgressBar(null, this.hashCode());
3387    }
3388    }
3389    }).start();
3390    }
3391   
3392    public static WsParamSetManager wsparamManager = null;
3393   
 
3394  318 toggle public static ParamManager getUserParameterStore()
3395    {
3396  318 if (wsparamManager == null)
3397    {
3398  6 wsparamManager = new WsParamSetManager();
3399    }
3400  318 return wsparamManager;
3401    }
3402   
3403    /**
3404    * static hyperlink handler proxy method for use by Jalview's internal windows
3405    *
3406    * @param e
3407    */
 
3408  0 toggle public static void hyperlinkUpdate(HyperlinkEvent e)
3409    {
3410  0 if (e.getEventType() == EventType.ACTIVATED)
3411    {
3412  0 String url = null;
3413  0 try
3414    {
3415  0 url = e.getURL().toString();
3416  0 Desktop.showUrl(url);
3417    } catch (Exception x)
3418    {
3419  0 if (url != null)
3420    {
3421  0 jalview.bin.Console
3422    .error("Couldn't handle string " + url + " as a URL.");
3423    }
3424    // ignore any exceptions due to dud links.
3425    }
3426   
3427    }
3428    }
3429   
3430    /**
3431    * single thread that handles display of dialogs to user.
3432    */
3433    ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3434   
3435    /**
3436    * flag indicating if dialogExecutor should try to acquire a permit
3437    */
3438    private volatile boolean dialogPause = true;
3439   
3440    /**
3441    * pause the queue
3442    */
3443    private Semaphore block = new Semaphore(0);
3444   
3445    private static groovy.console.ui.Console groovyConsole;
3446   
3447    /**
3448    * add another dialog thread to the queue
3449    *
3450    * @param prompter
3451    */
 
3452  144 toggle public void addDialogThread(final Runnable prompter)
3453    {
3454  144 dialogExecutor.submit(new Runnable()
3455    {
 
3456  144 toggle @Override
3457    public void run()
3458    {
3459  144 if (dialogPause)
3460    {
3461  59 acquireDialogQueue();
3462    }
3463  133 if (instance == null)
3464    {
3465  0 return;
3466    }
3467  133 try
3468    {
3469  133 SwingUtilities.invokeAndWait(prompter);
3470    } catch (Exception q)
3471    {
3472  18 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3473    q);
3474    }
3475    }
3476    });
3477    }
3478   
3479    private boolean dialogQueueStarted = false;
3480   
 
3481  75 toggle public void startDialogQueue()
3482    {
3483  75 if (dialogQueueStarted)
3484    {
3485  0 return;
3486    }
3487    // set the flag so we don't pause waiting for another permit and semaphore
3488    // the current task to begin
3489  75 releaseDialogQueue();
3490  75 dialogQueueStarted = true;
3491    }
3492   
 
3493  184 toggle public void acquireDialogQueue()
3494    {
3495  184 try
3496    {
3497  184 block.acquire();
3498  137 dialogPause = true;
3499    } catch (InterruptedException e)
3500    {
3501  36 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3502    e);
3503    }
3504    }
3505   
 
3506  199 toggle public void releaseDialogQueue()
3507    {
3508  199 if (!dialogPause)
3509    {
3510  29 return;
3511    }
3512  170 block.release();
3513  170 dialogPause = false;
3514    }
3515   
3516    /**
3517    * Outputs an image of the desktop to file in EPS format, after prompting the
3518    * user for choice of Text or Lineart character rendering (unless a preference
3519    * has been set). The file name is generated as
3520    *
3521    * <pre>
3522    * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3523    * </pre>
3524    */
 
3525  0 toggle @Override
3526    protected void snapShotWindow_actionPerformed(ActionEvent e)
3527    {
3528    // currently the menu option to do this is not shown
3529  0 invalidate();
3530   
3531  0 int width = getWidth();
3532  0 int height = getHeight();
3533  0 File of = new File(
3534    "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3535  0 ImageWriterI writer = new ImageWriterI()
3536    {
 
3537  0 toggle @Override
3538    public void exportImage(Graphics g) throws Exception
3539    {
3540  0 paintAll(g);
3541  0 jalview.bin.Console.info("Successfully written snapshot to file "
3542    + of.getAbsolutePath());
3543    }
3544    };
3545  0 String title = "View of desktop";
3546  0 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3547    title);
3548  0 try
3549    {
3550  0 exporter.doExport(of, this, width, height, title);
3551    } catch (ImageOutputException ioex)
3552    {
3553  0 jalview.bin.Console.error(
3554    "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3555    ioex);
3556    }
3557    }
3558   
3559    /**
3560    * Explode the views in the given SplitFrame into separate SplitFrame windows.
3561    * This respects (remembers) any previous 'exploded geometry' i.e. the size
3562    * and location last time the view was expanded (if any). However it does not
3563    * remember the split pane divider location - this is set to match the
3564    * 'exploding' frame.
3565    *
3566    * @param sf
3567    */
 
3568  0 toggle public void explodeViews(SplitFrame sf)
3569    {
3570  0 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3571  0 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3572  0 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3573    .getAlignPanels();
3574  0 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3575    .getAlignPanels();
3576  0 int viewCount = topPanels.size();
3577  0 if (viewCount < 2)
3578    {
3579  0 return;
3580    }
3581   
3582    /*
3583    * Processing in reverse order works, forwards order leaves the first panels not
3584    * visible. I don't know why!
3585    */
3586  0 for (int i = viewCount - 1; i >= 0; i--)
3587    {
3588    /*
3589    * Make new top and bottom frames. These take over the respective AlignmentPanel
3590    * objects, including their AlignmentViewports, so the cdna/protein
3591    * relationships between the viewports is carried over to the new split frames.
3592    *
3593    * explodedGeometry holds the (x, y) position of the previously exploded
3594    * SplitFrame, and the (width, height) of the AlignFrame component
3595    */
3596  0 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3597  0 AlignFrame newTopFrame = new AlignFrame(topPanel);
3598  0 newTopFrame.setSize(oldTopFrame.getSize());
3599  0 newTopFrame.setVisible(true);
3600  0 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3601    .getExplodedGeometry();
3602  0 if (geometry != null)
3603    {
3604  0 newTopFrame.setSize(geometry.getSize());
3605    }
3606   
3607  0 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3608  0 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3609  0 newBottomFrame.setSize(oldBottomFrame.getSize());
3610  0 newBottomFrame.setVisible(true);
3611  0 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3612    .getExplodedGeometry();
3613  0 if (geometry != null)
3614    {
3615  0 newBottomFrame.setSize(geometry.getSize());
3616    }
3617   
3618  0 topPanel.av.setGatherViewsHere(false);
3619  0 bottomPanel.av.setGatherViewsHere(false);
3620  0 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3621    newBottomFrame);
3622  0 if (geometry != null)
3623    {
3624  0 splitFrame.setLocation(geometry.getLocation());
3625    }
3626  0 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3627    }
3628   
3629    /*
3630    * Clear references to the panels (now relocated in the new SplitFrames) before
3631    * closing the old SplitFrame.
3632    */
3633  0 topPanels.clear();
3634  0 bottomPanels.clear();
3635  0 sf.close();
3636    }
3637   
3638    /**
3639    * Gather expanded split frames, sharing the same pairs of sequence set ids,
3640    * back into the given SplitFrame as additional views. Note that the gathered
3641    * frames may themselves have multiple views.
3642    *
3643    * @param source
3644    */
 
3645  0 toggle public void gatherViews(GSplitFrame source)
3646    {
3647    /*
3648    * special handling of explodedGeometry for a view within a SplitFrame: - it
3649    * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3650    * height) of the AlignFrame component
3651    */
3652  0 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3653  0 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3654  0 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3655    source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3656  0 myBottomFrame.viewport
3657    .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3658    myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3659  0 myTopFrame.viewport.setGatherViewsHere(true);
3660  0 myBottomFrame.viewport.setGatherViewsHere(true);
3661  0 String topViewId = myTopFrame.viewport.getSequenceSetId();
3662  0 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3663   
3664  0 JInternalFrame[] frames = desktop.getAllFrames();
3665  0 for (JInternalFrame frame : frames)
3666    {
3667  0 if (frame instanceof SplitFrame && frame != source)
3668    {
3669  0 SplitFrame sf = (SplitFrame) frame;
3670  0 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3671  0 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3672  0 boolean gatherThis = false;
3673  0 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3674    {
3675  0 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3676  0 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3677  0 if (topViewId.equals(topPanel.av.getSequenceSetId())
3678    && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3679    {
3680  0 gatherThis = true;
3681  0 topPanel.av.setGatherViewsHere(false);
3682  0 bottomPanel.av.setGatherViewsHere(false);
3683  0 topPanel.av.setExplodedGeometry(
3684    new Rectangle(sf.getLocation(), topFrame.getSize()));
3685  0 bottomPanel.av.setExplodedGeometry(
3686    new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3687  0 myTopFrame.addAlignmentPanel(topPanel, false);
3688  0 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3689    }
3690    }
3691   
3692  0 if (gatherThis)
3693    {
3694  0 topFrame.getAlignPanels().clear();
3695  0 bottomFrame.getAlignPanels().clear();
3696  0 sf.close();
3697    }
3698    }
3699    }
3700   
3701    /*
3702    * The dust settles...give focus to the tab we did this from.
3703    */
3704  0 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3705    }
3706   
 
3707  672 toggle public static groovy.console.ui.Console getGroovyConsole()
3708    {
3709  672 return groovyConsole;
3710    }
3711   
3712    /**
3713    * handles the payload of a drag and drop event.
3714    *
3715    * TODO refactor to desktop utilities class
3716    *
3717    * @param files
3718    * - Data source strings extracted from the drop event
3719    * @param protocols
3720    * - protocol for each data source extracted from the drop event
3721    * @param evt
3722    * - the drop event
3723    * @param t
3724    * - the payload from the drop event
3725    * @throws Exception
3726    */
 
3727  0 toggle public static void transferFromDropTarget(List<Object> files,
3728    List<DataSourceType> protocols, DropTargetDropEvent evt,
3729    Transferable t) throws Exception
3730    {
3731   
3732  0 DataFlavor uriListFlavor = new DataFlavor(
3733    "text/uri-list;class=java.lang.String"), urlFlavour = null;
3734  0 try
3735    {
3736  0 urlFlavour = new DataFlavor(
3737    "application/x-java-url; class=java.net.URL");
3738    } catch (ClassNotFoundException cfe)
3739    {
3740  0 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3741    cfe);
3742    }
3743   
3744  0 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3745    {
3746   
3747  0 try
3748    {
3749  0 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3750    // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3751    // means url may be null.
3752  0 if (url != null)
3753    {
3754  0 protocols.add(DataSourceType.URL);
3755  0 files.add(url.toString());
3756  0 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3757    + files.get(files.size() - 1));
3758  0 return;
3759    }
3760    else
3761    {
3762  0 if (Platform.isAMacAndNotJS())
3763    {
3764  0 jalview.bin.Console.errPrintln(
3765    "Please ignore plist error - occurs due to problem with java 8 on OSX");
3766    }
3767    }
3768    } catch (Throwable ex)
3769    {
3770  0 jalview.bin.Console.debug("URL drop handler failed.", ex);
3771    }
3772    }
3773  0 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3774    {
3775    // Works on Windows and MacOSX
3776  0 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3777  0 for (Object file : (List) t
3778    .getTransferData(DataFlavor.javaFileListFlavor))
3779    {
3780  0 files.add(file);
3781  0 protocols.add(DataSourceType.FILE);
3782    }
3783    }
3784    else
3785    {
3786    // Unix like behaviour
3787  0 boolean added = false;
3788  0 String data = null;
3789  0 if (t.isDataFlavorSupported(uriListFlavor))
3790    {
3791  0 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3792    // This is used by Unix drag system
3793  0 data = (String) t.getTransferData(uriListFlavor);
3794    }
3795  0 if (data == null)
3796    {
3797    // fallback to text: workaround - on OSX where there's a JVM bug
3798  0 jalview.bin.Console
3799    .debug("standard URIListFlavor failed. Trying text");
3800    // try text fallback
3801  0 DataFlavor textDf = new DataFlavor(
3802    "text/plain;class=java.lang.String");
3803  0 if (t.isDataFlavorSupported(textDf))
3804    {
3805  0 data = (String) t.getTransferData(textDf);
3806    }
3807   
3808  0 jalview.bin.Console.debug("Plain text drop content returned "
3809  0 + (data == null ? "Null - failed" : data));
3810   
3811    }
3812  0 if (data != null)
3813    {
3814  0 while (protocols.size() < files.size())
3815    {
3816  0 jalview.bin.Console.debug("Adding missing FILE protocol for "
3817    + files.get(protocols.size()));
3818  0 protocols.add(DataSourceType.FILE);
3819    }
3820  0 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3821  0 data, "\r\n"); st.hasMoreTokens();)
3822    {
3823  0 added = true;
3824  0 String s = st.nextToken();
3825  0 if (s.startsWith("#"))
3826    {
3827    // the line is a comment (as per the RFC 2483)
3828  0 continue;
3829    }
3830  0 java.net.URI uri = new java.net.URI(s);
3831  0 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3832    {
3833  0 protocols.add(DataSourceType.URL);
3834  0 files.add(uri.toString());
3835    }
3836    else
3837    {
3838    // otherwise preserve old behaviour: catch all for file objects
3839  0 java.io.File file = new java.io.File(uri);
3840  0 protocols.add(DataSourceType.FILE);
3841  0 files.add(file.toString());
3842    }
3843    }
3844    }
3845   
3846  0 if (jalview.bin.Console.isDebugEnabled())
3847    {
3848  0 if (data == null || !added)
3849    {
3850   
3851  0 if (t.getTransferDataFlavors() != null
3852    && t.getTransferDataFlavors().length > 0)
3853    {
3854  0 jalview.bin.Console.debug(
3855    "Couldn't resolve drop data. Here are the supported flavors:");
3856  0 for (DataFlavor fl : t.getTransferDataFlavors())
3857    {
3858  0 jalview.bin.Console.debug(
3859    "Supported transfer dataflavor: " + fl.toString());
3860  0 Object df = t.getTransferData(fl);
3861  0 if (df != null)
3862    {
3863  0 jalview.bin.Console.debug("Retrieves: " + df);
3864    }
3865    else
3866    {
3867  0 jalview.bin.Console.debug("Retrieved nothing");
3868    }
3869    }
3870    }
3871    else
3872    {
3873  0 jalview.bin.Console
3874    .debug("Couldn't resolve dataflavor for drop: "
3875    + t.toString());
3876    }
3877    }
3878    }
3879    }
3880  0 if (Platform.isWindowsAndNotJS())
3881    {
3882  0 jalview.bin.Console
3883    .debug("Scanning dropped content for Windows Link Files");
3884   
3885    // resolve any .lnk files in the file drop
3886  0 for (int f = 0; f < files.size(); f++)
3887    {
3888  0 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3889  0 if (protocols.get(f).equals(DataSourceType.FILE)
3890    && (source.endsWith(".lnk") || source.endsWith(".url")
3891    || source.endsWith(".site")))
3892    {
3893  0 try
3894    {
3895  0 Object obj = files.get(f);
3896  0 File lf = (obj instanceof File ? (File) obj
3897    : new File((String) obj));
3898    // process link file to get a URL
3899  0 jalview.bin.Console.debug("Found potential link file: " + lf);
3900  0 WindowsShortcut wscfile = new WindowsShortcut(lf);
3901  0 String fullname = wscfile.getRealFilename();
3902  0 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3903  0 files.set(f, fullname);
3904  0 jalview.bin.Console.debug("Parsed real filename " + fullname
3905    + " to extract protocol: " + protocols.get(f));
3906    } catch (Exception ex)
3907    {
3908  0 jalview.bin.Console.error(
3909    "Couldn't parse " + files.get(f) + " as a link file.",
3910    ex);
3911    }
3912    }
3913    }
3914    }
3915    }
3916   
3917    /**
3918    * Sets the Preferences property for experimental features to True or False
3919    * depending on the state of the controlling menu item
3920    */
 
3921  0 toggle @Override
3922    protected void showExperimental_actionPerformed(boolean selected)
3923    {
3924  0 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3925    }
3926   
3927    /**
3928    * Answers a (possibly empty) list of any structure viewer frames (currently
3929    * for either Jmol or Chimera) which are currently open. This may optionally
3930    * be restricted to viewers of a specified class, or viewers linked to a
3931    * specified alignment panel.
3932    *
3933    * @param apanel
3934    * if not null, only return viewers linked to this panel
3935    * @param structureViewerClass
3936    * if not null, only return viewers of this class
3937    * @return
3938    */
 
3939  19 toggle public List<StructureViewerBase> getStructureViewers(
3940    AlignmentPanel apanel,
3941    Class<? extends StructureViewerBase> structureViewerClass)
3942    {
3943  19 List<StructureViewerBase> result = new ArrayList<>();
3944  19 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3945   
3946  19 for (JInternalFrame frame : frames)
3947    {
3948  48 if (frame instanceof StructureViewerBase)
3949    {
3950  17 if (structureViewerClass == null
3951    || structureViewerClass.isInstance(frame))
3952    {
3953  17 if (apanel == null
3954    || ((StructureViewerBase) frame).isLinkedWith(apanel))
3955    {
3956  17 result.add((StructureViewerBase) frame);
3957    }
3958    }
3959    }
3960    }
3961  19 return result;
3962    }
3963   
3964    public static final String debugScaleMessage = "Desktop graphics transform scale=";
3965   
3966    private static boolean debugScaleMessageDone = false;
3967   
 
3968  2778 toggle public static void debugScaleMessage(Graphics g)
3969    {
3970  2778 if (debugScaleMessageDone)
3971    {
3972  2768 return;
3973    }
3974    // output used by tests to check HiDPI scaling settings in action
3975  10 try
3976    {
3977  10 Graphics2D gg = (Graphics2D) g;
3978  10 if (gg != null)
3979    {
3980  10 AffineTransform t = gg.getTransform();
3981  10 double scaleX = t.getScaleX();
3982  10 double scaleY = t.getScaleY();
3983  10 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3984  10 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3985  10 debugScaleMessageDone = true;
3986    }
3987    else
3988    {
3989  0 jalview.bin.Console.debug("Desktop graphics null");
3990    }
3991    } catch (Exception e)
3992    {
3993  0 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3994    }
3995    }
3996   
3997    /**
3998    * closes the current instance window, but leaves the JVM running. Bypasses
3999    * any shutdown prompts, but does not set window dispose on close in case JVM
4000    * terminates.
4001    */
 
4002  81 toggle public static void closeDesktop()
4003    {
4004  81 if (Desktop.instance != null)
4005    {
4006  81 Desktop us = Desktop.instance;
4007  81 Desktop.instance.quitTheDesktop(false, false);
4008    // call dispose in a separate thread - try to avoid indirect deadlocks
4009  81 if (us != null)
4010    {
4011  81 new Thread(new Runnable()
4012    {
 
4013  81 toggle @Override
4014    public void run()
4015    {
4016  81 ExecutorService dex = us.dialogExecutor;
4017  81 if (dex != null)
4018    {
4019  38 dex.shutdownNow();
4020  38 us.dialogExecutor = null;
4021  38 us.block.drainPermits();
4022    }
4023  81 us.dispose();
4024    }
4025    }).start();
4026    }
4027    }
4028    }
4029   
4030    /**
4031    * checks if any progress bars are being displayed in any of the windows
4032    * managed by the desktop
4033    *
4034    * @return
4035    */
 
4036  17 toggle public boolean operationsAreInProgress()
4037    {
4038  17 JInternalFrame[] frames = getAllFrames();
4039  17 for (JInternalFrame frame : frames)
4040    {
4041  38 if (frame instanceof IProgressIndicator)
4042    {
4043  17 if (((IProgressIndicator) frame).operationInProgress())
4044    {
4045  7 return true;
4046    }
4047    }
4048    }
4049  10 return operationInProgress();
4050    }
4051   
4052    /**
4053    * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
4054    * The way the modal JInternalFrame is made means it cannot be a child of an
4055    * AlignFrame, so closing the AlignFrame might leave the modal open :(
4056    */
4057    private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
4058   
 
4059  0 toggle protected static void addModal(AlignFrame af, JInternalFrame jif)
4060    {
4061  0 alignFrameModalMap.put(af, jif);
4062    }
4063   
 
4064  386 toggle protected static void closeModal(AlignFrame af)
4065    {
4066  386 if (!alignFrameModalMap.containsKey(af))
4067    {
4068  386 return;
4069    }
4070  0 JInternalFrame jif = alignFrameModalMap.get(af);
4071  0 if (jif != null)
4072    {
4073  0 try
4074    {
4075  0 jif.setClosed(true);
4076    } catch (PropertyVetoException e)
4077    {
4078  0 e.printStackTrace();
4079    }
4080    }
4081  0 alignFrameModalMap.remove(af);
4082    }
4083   
 
4084  0 toggle public void nonBlockingDialog(String title, String message, String button,
4085    int type, boolean scrollable, boolean modal)
4086    {
4087  0 nonBlockingDialog(title, message, null, button, type, scrollable, false,
4088    modal, -1);
4089    }
4090   
 
4091  1 toggle public void nonBlockingDialog(String title, String message,
4092    String boxtext, String button, int type, boolean scrollable,
4093    boolean html, boolean modal, int timeout)
4094    {
4095  1 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
4096    scrollable, html, modal, timeout);
4097    }
4098   
 
4099  1 toggle public void nonBlockingDialog(int width, int height, String title,
4100    String message, String boxtext, String button, int type,
4101    boolean scrollable, boolean html, boolean modal, int timeout)
4102    {
4103  1 if (type < 0)
4104    {
4105  0 type = JvOptionPane.WARNING_MESSAGE;
4106    }
4107  1 JLabel jl = new JLabel(message);
4108   
4109  1 JTextComponent jtc = null;
4110  1 if (html)
4111    {
4112  1 JTextPane jtp = new JTextPane();
4113  1 jtp.setContentType("text/html");
4114  1 jtp.setEditable(false);
4115  1 jtp.setAutoscrolls(true);
4116  1 jtp.setText(boxtext);
4117   
4118  1 jtc = jtp;
4119    }
4120    else
4121    {
4122  0 JTextArea jta = new JTextArea(height, width);
4123    // jta.setLineWrap(true);
4124  0 jta.setEditable(false);
4125  0 jta.setWrapStyleWord(true);
4126  0 jta.setAutoscrolls(true);
4127  0 jta.setText(boxtext);
4128   
4129  0 jtc = jta;
4130    }
4131   
4132  1 JScrollPane jsp = scrollable
4133    ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
4134    JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
4135    : null;
4136   
4137  1 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
4138   
4139  1 JPanel jp = new JPanel();
4140  1 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
4141   
4142  1 if (message != null)
4143    {
4144  1 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
4145  1 jp.add(jl);
4146    }
4147  1 if (boxtext != null)
4148    {
4149  1 if (scrollable)
4150    {
4151  0 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
4152  0 jp.add(jsp);
4153    }
4154    else
4155    {
4156  1 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
4157  1 jp.add(jtc);
4158    }
4159    }
4160   
4161  1 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
4162    });
4163  1 jvp.setTimeout(timeout);
4164  1 JButton jb = new JButton(button);
4165  1 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
4166    null, new Object[]
4167    { button }, button, modal, new JButton[] { jb }, false);
4168    }
4169   
 
4170  0 toggle @Override
4171    public AlignFrame getCurrentAlignFrame()
4172    {
4173  0 return Jalview.getInstance().getCurrentAlignFrame();
4174    }
4175    }