Clover icon

Coverage Report

  1. Project Clover database Wed Nov 13 2024 18:27:33 GMT
  2. Package jalview.gui

File Desktop.java

 

Coverage histogram

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

Code metrics

502
1,178
163
3
4,176
3,015
502
0.43
7.23
54.33
3.08

Classes

Class Line # Actions
Desktop 168 1,126 470
0.44020544%
Desktop.MyDesktopManager 300 25 19
0.220%
Desktop.MyDesktopPane 2670 27 13
0.3095238231%
 

Contributing tests

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