2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.datatransfer.Clipboard;
36 import java.awt.datatransfer.ClipboardOwner;
37 import java.awt.datatransfer.DataFlavor;
38 import java.awt.datatransfer.Transferable;
39 import java.awt.dnd.DnDConstants;
40 import java.awt.dnd.DropTargetDragEvent;
41 import java.awt.dnd.DropTargetDropEvent;
42 import java.awt.dnd.DropTargetEvent;
43 import java.awt.dnd.DropTargetListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.WindowAdapter;
51 import java.awt.event.WindowEvent;
52 import java.awt.geom.AffineTransform;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
55 import java.beans.PropertyVetoException;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Locale;
69 import java.util.Vector;
70 import java.util.concurrent.ExecutorService;
71 import java.util.concurrent.Executors;
72 import java.util.concurrent.Semaphore;
74 import javax.swing.AbstractAction;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.Box;
78 import javax.swing.BoxLayout;
79 import javax.swing.DefaultDesktopManager;
80 import javax.swing.DesktopManager;
81 import javax.swing.InputMap;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JComboBox;
85 import javax.swing.JComponent;
86 import javax.swing.JDesktopPane;
87 import javax.swing.JFrame;
88 import javax.swing.JInternalFrame;
89 import javax.swing.JLabel;
90 import javax.swing.JMenuItem;
91 import javax.swing.JOptionPane;
92 import javax.swing.JPanel;
93 import javax.swing.JPopupMenu;
94 import javax.swing.JProgressBar;
95 import javax.swing.JScrollPane;
96 import javax.swing.JTextArea;
97 import javax.swing.JTextField;
98 import javax.swing.KeyStroke;
99 import javax.swing.SwingUtilities;
100 import javax.swing.WindowConstants;
101 import javax.swing.event.HyperlinkEvent;
102 import javax.swing.event.HyperlinkEvent.EventType;
103 import javax.swing.event.InternalFrameAdapter;
104 import javax.swing.event.InternalFrameEvent;
106 import org.stackoverflowusers.file.WindowsShortcut;
108 import jalview.api.AlignViewportI;
109 import jalview.api.AlignmentViewPanel;
110 import jalview.api.structures.JalviewStructureDisplayI;
111 import jalview.bin.Cache;
112 import jalview.bin.Jalview;
113 import jalview.bin.Jalview.ExitCode;
114 import jalview.datamodel.Alignment;
115 import jalview.datamodel.HiddenColumns;
116 import jalview.datamodel.Sequence;
117 import jalview.datamodel.SequenceI;
118 import jalview.gui.ImageExporter.ImageWriterI;
119 import jalview.gui.QuitHandler.QResponse;
120 import jalview.io.BackupFiles;
121 import jalview.io.DataSourceType;
122 import jalview.io.FileFormat;
123 import jalview.io.FileFormatException;
124 import jalview.io.FileFormatI;
125 import jalview.io.FileFormats;
126 import jalview.io.FileLoader;
127 import jalview.io.FormatAdapter;
128 import jalview.io.IdentifyFile;
129 import jalview.io.JalviewFileChooser;
130 import jalview.io.JalviewFileView;
131 import jalview.io.exceptions.ImageOutputException;
132 import jalview.jbgui.GSplitFrame;
133 import jalview.jbgui.GStructureViewer;
134 import jalview.project.Jalview2XML;
135 import jalview.structure.StructureSelectionManager;
136 import jalview.urls.IdOrgSettings;
137 import jalview.util.BrowserLauncher;
138 import jalview.util.ChannelProperties;
139 import jalview.util.ImageMaker.TYPE;
140 import jalview.util.LaunchUtils;
141 import jalview.util.MessageManager;
142 import jalview.util.Platform;
143 import jalview.util.ShortcutKeyMaskExWrapper;
144 import jalview.util.UrlConstants;
145 import jalview.viewmodel.AlignmentViewport;
146 import jalview.ws.params.ParamManager;
147 import jalview.ws.utils.UrlDownloadClient;
154 * @version $Revision: 1.155 $
156 public class Desktop extends jalview.jbgui.GDesktop
157 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
158 jalview.api.StructureSelectionManagerProvider
160 private static final String CITATION;
163 URL bg_logo_url = ChannelProperties.getImageURL(
164 "bg_logo." + String.valueOf(SplashScreen.logoSize));
165 URL uod_logo_url = ChannelProperties.getImageURL(
166 "uod_banner." + String.valueOf(SplashScreen.logoSize));
167 boolean logo = (bg_logo_url != null || uod_logo_url != null);
168 StringBuilder sb = new StringBuilder();
170 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
175 sb.append(bg_logo_url == null ? ""
176 : "<img alt=\"Barton Group logo\" src=\""
177 + bg_logo_url.toString() + "\">");
178 sb.append(uod_logo_url == null ? ""
179 : " <img alt=\"University of Dundee shield\" src=\""
180 + uod_logo_url.toString() + "\">");
182 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
183 sb.append("<br><br>If you use Jalview, please cite:"
184 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
185 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
186 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
187 CITATION = sb.toString();
190 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
192 private static int DEFAULT_MIN_WIDTH = 300;
194 private static int DEFAULT_MIN_HEIGHT = 250;
196 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
198 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
200 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
202 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
204 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
206 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
208 public static void setLiveDragMode(boolean b)
210 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
211 : JDesktopPane.OUTLINE_DRAG_MODE;
213 desktop.setDragMode(DRAG_MODE);
216 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
218 public static boolean nosplash = false;
221 * news reader - null if it was never started.
223 private BlogReader jvnews = null;
225 private File projectFile;
229 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
231 public void addJalviewPropertyChangeListener(
232 PropertyChangeListener listener)
234 changeSupport.addJalviewPropertyChangeListener(listener);
238 * @param propertyName
240 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
241 * java.beans.PropertyChangeListener)
243 public void addJalviewPropertyChangeListener(String propertyName,
244 PropertyChangeListener listener)
246 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
250 * @param propertyName
252 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
253 * java.beans.PropertyChangeListener)
255 public void removeJalviewPropertyChangeListener(String propertyName,
256 PropertyChangeListener listener)
258 changeSupport.removeJalviewPropertyChangeListener(propertyName,
262 /** Singleton Desktop instance */
263 public static Desktop instance;
265 public static MyDesktopPane desktop;
267 public static MyDesktopPane getDesktop()
269 // BH 2018 could use currentThread() here as a reference to a
270 // Hashtable<Thread, MyDesktopPane> in JavaScript
274 static int openFrameCount = 0;
276 static final int xOffset = 30;
278 static final int yOffset = 30;
280 public static jalview.ws.jws1.Discoverer discoverer;
282 public static Object[] jalviewClipboard;
284 public static boolean internalCopy = false;
286 static int fileLoadingCount = 0;
288 class MyDesktopManager implements DesktopManager
291 private DesktopManager delegate;
293 public MyDesktopManager(DesktopManager delegate)
295 this.delegate = delegate;
299 public void activateFrame(JInternalFrame f)
303 delegate.activateFrame(f);
304 } catch (NullPointerException npe)
306 Point p = getMousePosition();
307 instance.showPasteMenu(p.x, p.y);
312 public void beginDraggingFrame(JComponent f)
314 delegate.beginDraggingFrame(f);
318 public void beginResizingFrame(JComponent f, int direction)
320 delegate.beginResizingFrame(f, direction);
324 public void closeFrame(JInternalFrame f)
326 delegate.closeFrame(f);
330 public void deactivateFrame(JInternalFrame f)
332 delegate.deactivateFrame(f);
336 public void deiconifyFrame(JInternalFrame f)
338 delegate.deiconifyFrame(f);
342 public void dragFrame(JComponent f, int newX, int newY)
348 delegate.dragFrame(f, newX, newY);
352 public void endDraggingFrame(JComponent f)
354 delegate.endDraggingFrame(f);
359 public void endResizingFrame(JComponent f)
361 delegate.endResizingFrame(f);
366 public void iconifyFrame(JInternalFrame f)
368 delegate.iconifyFrame(f);
372 public void maximizeFrame(JInternalFrame f)
374 delegate.maximizeFrame(f);
378 public void minimizeFrame(JInternalFrame f)
380 delegate.minimizeFrame(f);
384 public void openFrame(JInternalFrame f)
386 delegate.openFrame(f);
390 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
397 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
401 public void setBoundsForFrame(JComponent f, int newX, int newY,
402 int newWidth, int newHeight)
404 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
407 // All other methods, simply delegate
412 * Creates a new Desktop object.
418 * A note to implementors. It is ESSENTIAL that any activities that might
419 * block are spawned off as threads rather than waited for during this
424 doConfigureStructurePrefs();
425 setTitle(ChannelProperties.getProperty("app_name") + " "
426 + Cache.getProperty("VERSION"));
429 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
430 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
431 * officially documented or guaranteed to exist, so we access it via
432 * reflection. There appear to be unfathomable criteria about what this
433 * string can contain, and it if doesn't meet those criteria then "java"
434 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
435 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
436 * not. The reflection access may generate a warning: WARNING: An illegal
437 * reflective access operation has occurred WARNING: Illegal reflective
438 * access by jalview.gui.Desktop () to field
439 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
441 if (Platform.isLinux())
443 if (LaunchUtils.getJavaVersion() >= 11)
446 * Send this message to stderr as the warning that follows (due to
447 * reflection) also goes to stderr.
449 jalview.bin.Console.errPrintln(
450 "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.");
452 final String awtAppClassName = "awtAppClassName";
455 Toolkit xToolkit = Toolkit.getDefaultToolkit();
456 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
457 Field awtAppClassNameField = null;
459 if (Arrays.stream(declaredFields)
460 .anyMatch(f -> f.getName().equals(awtAppClassName)))
462 awtAppClassNameField = xToolkit.getClass()
463 .getDeclaredField(awtAppClassName);
466 String title = ChannelProperties.getProperty("app_name");
467 if (awtAppClassNameField != null)
469 awtAppClassNameField.setAccessible(true);
470 awtAppClassNameField.set(xToolkit, title);
475 .debug("XToolkit: " + awtAppClassName + " not found");
477 } catch (Exception e)
479 jalview.bin.Console.debug("Error setting " + awtAppClassName);
480 jalview.bin.Console.trace(Cache.getStackTraceString(e));
484 setIconImages(ChannelProperties.getIconList());
486 // override quit handling when GUI OS close [X] button pressed
487 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
488 addWindowListener(new WindowAdapter()
491 public void windowClosing(WindowEvent ev)
493 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
497 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
499 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
500 desktop = new MyDesktopPane(selmemusage);
502 showMemusage.setSelected(selmemusage);
503 desktop.setBackground(Color.white);
505 getContentPane().setLayout(new BorderLayout());
506 // alternate config - have scrollbars - see notes in JAL-153
507 // JScrollPane sp = new JScrollPane();
508 // sp.getViewport().setView(desktop);
509 // getContentPane().add(sp, BorderLayout.CENTER);
511 // BH 2018 - just an experiment to try unclipped JInternalFrames.
514 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
517 getContentPane().add(desktop, BorderLayout.CENTER);
518 desktop.setDragMode(DRAG_MODE);
520 // This line prevents Windows Look&Feel resizing all new windows to maximum
521 // if previous window was maximised
522 desktop.setDesktopManager(new MyDesktopManager(
523 Platform.isJS() ? desktop.getDesktopManager()
524 : new DefaultDesktopManager()));
526 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
527 : Platform.isAMacAndNotJS()
528 ? new AquaInternalFrameManager(
529 desktop.getDesktopManager())
530 : desktop.getDesktopManager())));
533 Rectangle dims = getLastKnownDimensions("");
540 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
541 int xPos = Math.max(5, (screenSize.width - 900) / 2);
542 int yPos = Math.max(5, (screenSize.height - 650) / 2);
543 setBounds(xPos, yPos, 900, 650);
546 // start dialogue queue for single dialogues
549 if (!Platform.isJS())
556 jconsole = new Console(this, showjconsole);
557 jconsole.setHeader(Cache.getVersionDetailsForConsole());
558 showConsole(showjconsole);
560 showNews.setVisible(false);
562 experimentalFeatures.setSelected(showExperimental());
564 getIdentifiersOrgData();
568 // Spawn a thread that shows the splashscreen
571 SwingUtilities.invokeLater(new Runnable()
576 new SplashScreen(true);
581 // Thread off a new instance of the file chooser - this reduces the time
582 // it takes to open it later on.
583 new Thread(new Runnable()
588 jalview.bin.Console.debug("Filechooser init thread started.");
589 String fileFormat = FileLoader.getUseDefaultFileFormat()
590 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
592 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
594 jalview.bin.Console.debug("Filechooser init thread finished.");
597 // Add the service change listener
598 changeSupport.addJalviewPropertyChangeListener("services",
599 new PropertyChangeListener()
603 public void propertyChange(PropertyChangeEvent evt)
606 .debug("Firing service changed event for "
607 + evt.getNewValue());
608 JalviewServicesChanged(evt);
613 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
616 this.addMouseListener(ma = new MouseAdapter()
619 public void mousePressed(MouseEvent evt)
621 if (evt.isPopupTrigger()) // Mac
623 showPasteMenu(evt.getX(), evt.getY());
628 public void mouseReleased(MouseEvent evt)
630 if (evt.isPopupTrigger()) // Windows
632 showPasteMenu(evt.getX(), evt.getY());
636 desktop.addMouseListener(ma);
640 // used for jalviewjsTest
641 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
647 * Answers true if user preferences to enable experimental features is True
652 public boolean showExperimental()
654 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
655 Boolean.FALSE.toString());
656 return Boolean.valueOf(experimental).booleanValue();
659 public void doConfigureStructurePrefs()
661 // configure services
662 StructureSelectionManager ssm = StructureSelectionManager
663 .getStructureSelectionManager(this);
664 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
666 ssm.setAddTempFacAnnot(
667 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
668 ssm.setProcessSecondaryStructure(
669 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
670 // JAL-3915 - RNAView is no longer an option so this has no effect
671 ssm.setSecStructServices(
672 Cache.getDefault(Preferences.USE_RNAVIEW, false));
676 ssm.setAddTempFacAnnot(false);
677 ssm.setProcessSecondaryStructure(false);
678 ssm.setSecStructServices(false);
682 public void checkForNews()
684 final Desktop me = this;
685 // Thread off the news reader, in case there are connection problems.
686 new Thread(new Runnable()
691 jalview.bin.Console.debug("Starting news thread.");
692 jvnews = new BlogReader(me);
693 showNews.setVisible(true);
694 jalview.bin.Console.debug("Completed news thread.");
699 public void getIdentifiersOrgData()
701 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
702 {// Thread off the identifiers fetcher
703 new Thread(new Runnable()
709 .debug("Downloading data from identifiers.org");
712 UrlDownloadClient.download(IdOrgSettings.getUrl(),
713 IdOrgSettings.getDownloadLocation());
714 } catch (IOException e)
717 .debug("Exception downloading identifiers.org data"
727 protected void showNews_actionPerformed(ActionEvent e)
729 showNews(showNews.isSelected());
732 void showNews(boolean visible)
734 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
735 showNews.setSelected(visible);
736 if (visible && !jvnews.isVisible())
738 new Thread(new Runnable()
743 long now = System.currentTimeMillis();
744 Desktop.instance.setProgressBar(
745 MessageManager.getString("status.refreshing_news"), now);
746 jvnews.refreshNews();
747 Desktop.instance.setProgressBar(null, now);
755 * recover the last known dimensions for a jalview window
758 * - empty string is desktop, all other windows have unique prefix
759 * @return null or last known dimensions scaled to current geometry (if last
760 * window geom was known)
762 Rectangle getLastKnownDimensions(String windowName)
764 // TODO: lock aspect ratio for scaling desktop Bug #0058199
765 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
766 String x = Cache.getProperty(windowName + "SCREEN_X");
767 String y = Cache.getProperty(windowName + "SCREEN_Y");
768 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
769 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
770 if ((x != null) && (y != null) && (width != null) && (height != null))
772 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
773 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
774 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
776 // attempt #1 - try to cope with change in screen geometry - this
777 // version doesn't preserve original jv aspect ratio.
778 // take ratio of current screen size vs original screen size.
779 double sw = ((1f * screenSize.width) / (1f * Integer
780 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
781 double sh = ((1f * screenSize.height) / (1f * Integer
782 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
783 // rescale the bounds depending upon the current screen geometry.
784 ix = (int) (ix * sw);
785 iw = (int) (iw * sw);
786 iy = (int) (iy * sh);
787 ih = (int) (ih * sh);
788 while (ix >= screenSize.width)
790 jalview.bin.Console.debug(
791 "Window geometry location recall error: shifting horizontal to within screenbounds.");
792 ix -= screenSize.width;
794 while (iy >= screenSize.height)
796 jalview.bin.Console.debug(
797 "Window geometry location recall error: shifting vertical to within screenbounds.");
798 iy -= screenSize.height;
800 jalview.bin.Console.debug(
801 "Got last known dimensions for " + windowName + ": x:" + ix
802 + " y:" + iy + " width:" + iw + " height:" + ih);
804 // return dimensions for new instance
805 return new Rectangle(ix, iy, iw, ih);
810 void showPasteMenu(int x, int y)
812 JPopupMenu popup = new JPopupMenu();
813 JMenuItem item = new JMenuItem(
814 MessageManager.getString("label.paste_new_window"));
815 item.addActionListener(new ActionListener()
818 public void actionPerformed(ActionEvent evt)
825 popup.show(this, x, y);
830 // quick patch for JAL-4150 - needs some more work and test coverage
831 // TODO - unify below and AlignFrame.paste()
832 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
833 // clipboard has come from a different alignment window than the one where
834 // paste has been called! JAL-4151
836 if (Desktop.jalviewClipboard != null)
838 // The clipboard was filled from within Jalview, we must use the
840 // And dataset from the copied alignment
841 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
842 // be doubly sure that we create *new* sequence objects.
843 SequenceI[] sequences = new SequenceI[newseq.length];
844 for (int i = 0; i < newseq.length; i++)
846 sequences[i] = new Sequence(newseq[i]);
848 Alignment alignment = new Alignment(sequences);
849 // dataset is inherited
850 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
851 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
852 AlignFrame.DEFAULT_HEIGHT);
853 String newtitle = new String("Copied sequences");
855 if (Desktop.jalviewClipboard[2] != null)
857 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
858 af.viewport.setHiddenColumns(hc);
861 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
862 AlignFrame.DEFAULT_HEIGHT);
869 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
870 Transferable contents = c.getContents(this);
872 if (contents != null)
874 String file = (String) contents
875 .getTransferData(DataFlavor.stringFlavor);
877 FileFormatI format = new IdentifyFile().identify(file,
878 DataSourceType.PASTE);
880 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
883 } catch (Exception ex)
885 jalview.bin.Console.outPrintln(
886 "Unable to paste alignment from system clipboard:\n" + ex);
892 * Adds and opens the given frame to the desktop
903 public static synchronized void addInternalFrame(
904 final JInternalFrame frame, String title, int w, int h)
906 addInternalFrame(frame, title, true, w, h, true, false);
910 * Add an internal frame to the Jalview desktop
917 * When true, display frame immediately, otherwise, caller must call
918 * setVisible themselves.
924 public static synchronized void addInternalFrame(
925 final JInternalFrame frame, String title, boolean makeVisible,
928 addInternalFrame(frame, title, makeVisible, w, h, true, false);
932 * Add an internal frame to the Jalview desktop and make it visible
945 public static synchronized void addInternalFrame(
946 final JInternalFrame frame, String title, int w, int h,
949 addInternalFrame(frame, title, true, w, h, resizable, false);
953 * Add an internal frame to the Jalview desktop
960 * When true, display frame immediately, otherwise, caller must call
961 * setVisible themselves.
968 * @param ignoreMinSize
969 * Do not set the default minimum size for frame
971 public static synchronized void addInternalFrame(
972 final JInternalFrame frame, String title, boolean makeVisible,
973 int w, int h, boolean resizable, boolean ignoreMinSize)
976 // TODO: allow callers to determine X and Y position of frame (eg. via
978 // TODO: consider fixing method to update entries in the window submenu with
979 // the current window title
981 frame.setTitle(title);
982 if (frame.getWidth() < 1 || frame.getHeight() < 1)
986 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
987 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
988 // IF JALVIEW IS RUNNING HEADLESS
989 // ///////////////////////////////////////////////
990 if (instance == null || (System.getProperty("java.awt.headless") != null
991 && System.getProperty("java.awt.headless").equals("true")))
1000 frame.setMinimumSize(
1001 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1003 // Set default dimension for Alignment Frame window.
1004 // The Alignment Frame window could be added from a number of places,
1006 // I did this here in order not to miss out on any Alignment frame.
1007 if (frame instanceof AlignFrame)
1009 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1010 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1014 frame.setVisible(makeVisible);
1015 frame.setClosable(true);
1016 frame.setResizable(resizable);
1017 frame.setMaximizable(resizable);
1018 frame.setIconifiable(resizable);
1019 frame.setOpaque(Platform.isJS());
1021 if (frame.getX() < 1 && frame.getY() < 1)
1023 frame.setLocation(xOffset * openFrameCount,
1024 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1028 * add an entry for the new frame in the Window menu (and remove it when the
1031 final JMenuItem menuItem = new JMenuItem(title);
1032 frame.addInternalFrameListener(new InternalFrameAdapter()
1035 public void internalFrameActivated(InternalFrameEvent evt)
1037 JInternalFrame itf = desktop.getSelectedFrame();
1040 if (itf instanceof AlignFrame)
1042 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1049 public void internalFrameClosed(InternalFrameEvent evt)
1051 PaintRefresher.RemoveComponent(frame);
1054 * defensive check to prevent frames being added half off the window
1056 if (openFrameCount > 0)
1062 * ensure no reference to alignFrame retained by menu item listener
1064 if (menuItem.getActionListeners().length > 0)
1066 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1068 windowMenu.remove(menuItem);
1072 menuItem.addActionListener(new ActionListener()
1075 public void actionPerformed(ActionEvent e)
1079 frame.setSelected(true);
1080 frame.setIcon(false);
1081 } catch (java.beans.PropertyVetoException ex)
1088 setKeyBindings(frame);
1090 // Since the latest FlatLaf patch, we occasionally have problems showing
1091 // structureViewer frames...
1093 boolean shown = false;
1094 Exception last = null;
1101 } catch (IllegalArgumentException iaex)
1105 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1106 + tries + " left) for " + frame.getTitle(), iaex);
1110 } catch (InterruptedException iex)
1115 } while (!shown && tries > 0);
1118 jalview.bin.Console.error(
1119 "Serious Problem whilst showing window " + frame.getTitle(),
1123 windowMenu.add(menuItem);
1128 frame.setSelected(true);
1129 frame.requestFocus();
1130 } catch (java.beans.PropertyVetoException ve)
1132 } catch (java.lang.ClassCastException cex)
1134 jalview.bin.Console.warn(
1135 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1141 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1146 private static void setKeyBindings(JInternalFrame frame)
1148 @SuppressWarnings("serial")
1149 final Action closeAction = new AbstractAction()
1152 public void actionPerformed(ActionEvent e)
1159 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1161 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1162 InputEvent.CTRL_DOWN_MASK);
1163 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1164 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1166 InputMap inputMap = frame
1167 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1168 String ctrlW = ctrlWKey.toString();
1169 inputMap.put(ctrlWKey, ctrlW);
1170 inputMap.put(cmdWKey, ctrlW);
1172 ActionMap actionMap = frame.getActionMap();
1173 actionMap.put(ctrlW, closeAction);
1177 public void lostOwnership(Clipboard clipboard, Transferable contents)
1181 Desktop.jalviewClipboard = null;
1184 internalCopy = false;
1188 public void dragEnter(DropTargetDragEvent evt)
1193 public void dragExit(DropTargetEvent evt)
1198 public void dragOver(DropTargetDragEvent evt)
1203 public void dropActionChanged(DropTargetDragEvent evt)
1214 public void drop(DropTargetDropEvent evt)
1216 boolean success = true;
1217 // JAL-1552 - acceptDrop required before getTransferable call for
1218 // Java's Transferable for native dnd
1219 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1220 Transferable t = evt.getTransferable();
1221 List<Object> files = new ArrayList<>();
1222 List<DataSourceType> protocols = new ArrayList<>();
1226 Desktop.transferFromDropTarget(files, protocols, evt, t);
1227 } catch (Exception e)
1229 e.printStackTrace();
1237 for (int i = 0; i < files.size(); i++)
1239 // BH 2018 File or String
1240 Object file = files.get(i);
1241 String fileName = file.toString();
1242 DataSourceType protocol = (protocols == null)
1243 ? DataSourceType.FILE
1245 FileFormatI format = null;
1247 if (fileName.endsWith(".jar"))
1249 format = FileFormat.Jalview;
1254 format = new IdentifyFile().identify(file, protocol);
1256 if (file instanceof File)
1258 Platform.cacheFileData((File) file);
1260 new FileLoader().LoadFile(null, file, protocol, format);
1263 } catch (Exception ex)
1268 evt.dropComplete(success); // need this to ensure input focus is properly
1269 // transfered to any new windows created
1279 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1281 String fileFormat = FileLoader.getUseDefaultFileFormat()
1282 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1284 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1285 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1286 BackupFiles.getEnabled());
1288 chooser.setFileView(new JalviewFileView());
1289 chooser.setDialogTitle(
1290 MessageManager.getString("label.open_local_file"));
1291 chooser.setToolTipText(MessageManager.getString("action.open"));
1293 chooser.setResponseHandler(0, () -> {
1294 File selectedFile = chooser.getSelectedFile();
1295 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1297 FileFormatI format = chooser.getSelectedFormat();
1300 * Call IdentifyFile to verify the file contains what its extension implies.
1301 * Skip this step for dynamically added file formats, because IdentifyFile does
1302 * not know how to recognise them.
1304 if (FileFormats.getInstance().isIdentifiable(format))
1308 format = new IdentifyFile().identify(selectedFile,
1309 DataSourceType.FILE);
1310 } catch (FileFormatException e)
1312 // format = null; //??
1316 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1319 chooser.showOpenDialog(this);
1323 * Shows a dialog for input of a URL at which to retrieve alignment data
1328 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1330 // This construct allows us to have a wider textfield
1332 JLabel label = new JLabel(
1333 MessageManager.getString("label.input_file_url"));
1335 JPanel panel = new JPanel(new GridLayout(2, 1));
1339 * the URL to fetch is input in Java: an editable combobox with history JS:
1340 * (pending JAL-3038) a plain text field
1343 String urlBase = "https://www.";
1344 if (Platform.isJS())
1346 history = new JTextField(urlBase, 35);
1355 JComboBox<String> asCombo = new JComboBox<>();
1356 asCombo.setPreferredSize(new Dimension(400, 20));
1357 asCombo.setEditable(true);
1358 asCombo.addItem(urlBase);
1359 String historyItems = Cache.getProperty("RECENT_URL");
1360 if (historyItems != null)
1362 for (String token : historyItems.split("\\t"))
1364 asCombo.addItem(token);
1371 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1372 MessageManager.getString("action.cancel") };
1373 Runnable action = () -> {
1374 @SuppressWarnings("unchecked")
1375 String url = (history instanceof JTextField
1376 ? ((JTextField) history).getText()
1377 : ((JComboBox<String>) history).getEditor().getItem()
1378 .toString().trim());
1380 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1382 if (viewport != null)
1384 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1385 FileFormat.Jalview);
1389 new FileLoader().LoadFile(url, DataSourceType.URL,
1390 FileFormat.Jalview);
1395 FileFormatI format = null;
1398 format = new IdentifyFile().identify(url, DataSourceType.URL);
1399 } catch (FileFormatException e)
1401 // TODO revise error handling, distinguish between
1402 // URL not found and response not valid
1407 String msg = MessageManager.formatMessage("label.couldnt_locate",
1409 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1410 MessageManager.getString("label.url_not_found"),
1411 JvOptionPane.WARNING_MESSAGE);
1415 if (viewport != null)
1417 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1422 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1426 String dialogOption = MessageManager
1427 .getString("label.input_alignment_from_url");
1428 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1429 .showInternalDialog(panel, dialogOption,
1430 JvOptionPane.YES_NO_CANCEL_OPTION,
1431 JvOptionPane.PLAIN_MESSAGE, null, options,
1432 MessageManager.getString("action.ok"));
1436 * Opens the CutAndPaste window for the user to paste an alignment in to
1439 * - if not null, the pasted alignment is added to the current
1440 * alignment; if null, to a new alignment window
1443 public void inputTextboxMenuItem_actionPerformed(
1444 AlignmentViewPanel viewPanel)
1446 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1447 cap.setForInput(viewPanel);
1448 Desktop.addInternalFrame(cap,
1449 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1454 * Check with user and saving files before actually quitting
1456 public void desktopQuit()
1458 desktopQuit(true, false);
1461 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1463 final Runnable doDesktopQuit = () -> {
1464 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1465 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1466 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1467 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1468 getBounds().y, getWidth(), getHeight()));
1470 if (jconsole != null)
1472 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1473 jconsole.stopConsole();
1478 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1481 // Frames should all close automatically. Keeping external
1482 // viewers open should already be decided by user.
1483 closeAll_actionPerformed(null);
1485 // check for aborted quit
1486 if (QuitHandler.quitCancelled())
1488 jalview.bin.Console.debug("Desktop aborting quit");
1492 if (dialogExecutor != null)
1494 dialogExecutor.shutdownNow();
1497 if (groovyConsole != null)
1499 // suppress a possible repeat prompt to save script
1500 groovyConsole.setDirty(false);
1501 groovyConsole.exit();
1504 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1506 // note that shutdown hook will not be run
1507 jalview.bin.Console.debug("Force Quit selected by user");
1508 Runtime.getRuntime().halt(0);
1511 jalview.bin.Console.debug("Quit selected by user");
1514 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1515 // instance.dispose();
1520 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1521 QuitHandler.defaultCancelQuit);
1525 * Don't call this directly, use desktopQuit() above. Exits the program.
1530 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1531 // not run a second time if gotQuitResponse flag has been set (i.e. user
1532 // confirmed quit of some kind).
1533 Jalview.exit("Desktop exiting.", ExitCode.OK);
1536 private void storeLastKnownDimensions(String string, Rectangle jc)
1538 jalview.bin.Console.debug("Storing last known dimensions for " + string
1539 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1540 + " height:" + jc.height);
1542 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1543 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1544 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1545 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1555 public void aboutMenuItem_actionPerformed(ActionEvent e)
1557 new Thread(new Runnable()
1562 new SplashScreen(false);
1568 * Returns the html text for the About screen, including any available version
1569 * number, build details, author details and citation reference, but without
1570 * the enclosing {@code html} tags
1574 public String getAboutMessage()
1576 StringBuilder message = new StringBuilder(1024);
1577 message.append("<div style=\"font-family: sans-serif;\">")
1578 .append("<h1><strong>Version: ")
1579 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1580 .append("<strong>Built: <em>")
1581 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1582 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1583 .append("</strong>");
1585 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1586 if (latestVersion.equals("Checking"))
1588 // JBP removed this message for 2.11: May be reinstated in future version
1589 // message.append("<br>...Checking latest version...</br>");
1591 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1593 boolean red = false;
1594 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1595 .indexOf("automated build") == -1)
1598 // Displayed when code version and jnlp version do not match and code
1599 // version is not a development build
1600 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1603 message.append("<br>!! Version ")
1604 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1605 .append(" is available for download from ")
1606 .append(Cache.getDefault("www.jalview.org",
1607 "https://www.jalview.org"))
1611 message.append("</div>");
1614 message.append("<br>Authors: ");
1615 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1616 message.append(CITATION);
1618 message.append("</div>");
1620 return message.toString();
1624 * Action on requesting Help documentation
1627 public void documentationMenuItem_actionPerformed()
1631 if (Platform.isJS())
1633 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1642 Help.showHelpWindow();
1644 } catch (Exception ex)
1647 .errPrintln("Error opening help: " + ex.getMessage());
1652 public void closeAll_actionPerformed(ActionEvent e)
1654 // TODO show a progress bar while closing?
1655 JInternalFrame[] frames = desktop.getAllFrames();
1656 for (int i = 0; i < frames.length; i++)
1660 frames[i].setClosed(true);
1661 } catch (java.beans.PropertyVetoException ex)
1665 Jalview.setCurrentAlignFrame(null);
1666 jalview.bin.Console.info("ALL CLOSED");
1669 * reset state of singleton objects as appropriate (clear down session state
1670 * when all windows are closed)
1672 StructureSelectionManager ssm = StructureSelectionManager
1673 .getStructureSelectionManager(this);
1680 public int structureViewersStillRunningCount()
1683 JInternalFrame[] frames = desktop.getAllFrames();
1684 for (int i = 0; i < frames.length; i++)
1686 if (frames[i] != null
1687 && frames[i] instanceof JalviewStructureDisplayI)
1689 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1697 public void raiseRelated_actionPerformed(ActionEvent e)
1699 reorderAssociatedWindows(false, false);
1703 public void minimizeAssociated_actionPerformed(ActionEvent e)
1705 reorderAssociatedWindows(true, false);
1708 void closeAssociatedWindows()
1710 reorderAssociatedWindows(false, true);
1716 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1720 protected void garbageCollect_actionPerformed(ActionEvent e)
1722 // We simply collect the garbage
1723 jalview.bin.Console.debug("Collecting garbage...");
1725 jalview.bin.Console.debug("Finished garbage collection.");
1731 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1735 protected void showMemusage_actionPerformed(ActionEvent e)
1737 desktop.showMemoryUsage(showMemusage.isSelected());
1744 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1748 protected void showConsole_actionPerformed(ActionEvent e)
1750 showConsole(showConsole.isSelected());
1753 Console jconsole = null;
1756 * control whether the java console is visible or not
1760 void showConsole(boolean selected)
1762 // TODO: decide if we should update properties file
1763 if (jconsole != null) // BH 2018
1765 showConsole.setSelected(selected);
1766 Cache.setProperty("SHOW_JAVA_CONSOLE",
1767 Boolean.valueOf(selected).toString());
1768 jconsole.setVisible(selected);
1772 void reorderAssociatedWindows(boolean minimize, boolean close)
1774 JInternalFrame[] frames = desktop.getAllFrames();
1775 if (frames == null || frames.length < 1)
1780 AlignmentViewport source = null, target = null;
1781 if (frames[0] instanceof AlignFrame)
1783 source = ((AlignFrame) frames[0]).getCurrentView();
1785 else if (frames[0] instanceof TreePanel)
1787 source = ((TreePanel) frames[0]).getViewPort();
1789 else if (frames[0] instanceof PCAPanel)
1791 source = ((PCAPanel) frames[0]).av;
1793 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1795 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1800 for (int i = 0; i < frames.length; i++)
1803 if (frames[i] == null)
1807 if (frames[i] instanceof AlignFrame)
1809 target = ((AlignFrame) frames[i]).getCurrentView();
1811 else if (frames[i] instanceof TreePanel)
1813 target = ((TreePanel) frames[i]).getViewPort();
1815 else if (frames[i] instanceof PCAPanel)
1817 target = ((PCAPanel) frames[i]).av;
1819 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1821 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1824 if (source == target)
1830 frames[i].setClosed(true);
1834 frames[i].setIcon(minimize);
1837 frames[i].toFront();
1841 } catch (java.beans.PropertyVetoException ex)
1856 protected void preferences_actionPerformed(ActionEvent e)
1858 Preferences.openPreferences();
1862 * Prompts the user to choose a file and then saves the Jalview state as a
1863 * Jalview project file
1866 public void saveState_actionPerformed()
1868 saveState_actionPerformed(false);
1871 public void saveState_actionPerformed(boolean saveAs)
1873 java.io.File projectFile = getProjectFile();
1874 // autoSave indicates we already have a file and don't need to ask
1875 boolean autoSave = projectFile != null && !saveAs
1876 && BackupFiles.getEnabled();
1878 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1879 // projectFile='"+projectFile+"',
1880 // saveAs="+saveAs+", Backups
1881 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1883 boolean approveSave = false;
1886 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1889 chooser.setFileView(new JalviewFileView());
1890 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1892 int value = chooser.showSaveDialog(this);
1894 if (value == JalviewFileChooser.APPROVE_OPTION)
1896 projectFile = chooser.getSelectedFile();
1897 setProjectFile(projectFile);
1902 if (approveSave || autoSave)
1904 final Desktop me = this;
1905 final java.io.File chosenFile = projectFile;
1906 new Thread(new Runnable()
1911 // TODO: refactor to Jalview desktop session controller action.
1912 setProgressBar(MessageManager.formatMessage(
1913 "label.saving_jalview_project", new Object[]
1914 { chosenFile.getName() }), chosenFile.hashCode());
1915 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1916 // TODO catch and handle errors for savestate
1917 // TODO prevent user from messing with the Desktop whilst we're saving
1920 boolean doBackup = BackupFiles.getEnabled();
1921 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1924 new Jalview2XML().saveState(
1925 doBackup ? backupfiles.getTempFile() : chosenFile);
1929 backupfiles.setWriteSuccess(true);
1930 backupfiles.rollBackupsAndRenameTempFile();
1932 } catch (OutOfMemoryError oom)
1934 new OOMWarning("Whilst saving current state to "
1935 + chosenFile.getName(), oom);
1936 } catch (Exception ex)
1938 jalview.bin.Console.error("Problems whilst trying to save to "
1939 + chosenFile.getName(), ex);
1940 JvOptionPane.showMessageDialog(me,
1941 MessageManager.formatMessage(
1942 "label.error_whilst_saving_current_state_to",
1944 { chosenFile.getName() }),
1945 MessageManager.getString("label.couldnt_save_project"),
1946 JvOptionPane.WARNING_MESSAGE);
1948 setProgressBar(null, chosenFile.hashCode());
1955 public void saveAsState_actionPerformed(ActionEvent e)
1957 saveState_actionPerformed(true);
1960 protected void setProjectFile(File choice)
1962 this.projectFile = choice;
1965 public File getProjectFile()
1967 return this.projectFile;
1971 * Shows a file chooser dialog and tries to read in the selected file as a
1975 public void loadState_actionPerformed()
1977 final String[] suffix = new String[] { "jvp", "jar" };
1978 final String[] desc = new String[] { "Jalview Project",
1979 "Jalview Project (old)" };
1980 JalviewFileChooser chooser = new JalviewFileChooser(
1981 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1982 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1986 chooser.setFileView(new JalviewFileView());
1987 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1988 chooser.setResponseHandler(0, () -> {
1989 File selectedFile = chooser.getSelectedFile();
1990 setProjectFile(selectedFile);
1991 String choice = selectedFile.getAbsolutePath();
1992 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1993 new Thread(new Runnable()
2000 new Jalview2XML().loadJalviewAlign(selectedFile);
2001 } catch (OutOfMemoryError oom)
2003 new OOMWarning("Whilst loading project from " + choice, oom);
2004 } catch (Exception ex)
2006 jalview.bin.Console.error(
2007 "Problems whilst loading project from " + choice, ex);
2008 JvOptionPane.showMessageDialog(Desktop.desktop,
2009 MessageManager.formatMessage(
2010 "label.error_whilst_loading_project_from",
2013 MessageManager.getString("label.couldnt_load_project"),
2014 JvOptionPane.WARNING_MESSAGE);
2017 }, "Project Loader").start();
2020 chooser.showOpenDialog(this);
2024 public void inputSequence_actionPerformed(ActionEvent e)
2026 new SequenceFetcher(this);
2029 JPanel progressPanel;
2031 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2033 public void startLoading(final Object fileName)
2035 if (fileLoadingCount == 0)
2037 fileLoadingPanels.add(addProgressPanel(MessageManager
2038 .formatMessage("label.loading_file", new Object[]
2044 private JPanel addProgressPanel(String string)
2046 if (progressPanel == null)
2048 progressPanel = new JPanel(new GridLayout(1, 1));
2049 totalProgressCount = 0;
2050 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2052 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2053 JProgressBar progressBar = new JProgressBar();
2054 progressBar.setIndeterminate(true);
2056 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2058 thisprogress.add(progressBar, BorderLayout.CENTER);
2059 progressPanel.add(thisprogress);
2060 ((GridLayout) progressPanel.getLayout()).setRows(
2061 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2062 ++totalProgressCount;
2063 instance.validate();
2064 return thisprogress;
2067 int totalProgressCount = 0;
2069 private void removeProgressPanel(JPanel progbar)
2071 if (progressPanel != null)
2073 synchronized (progressPanel)
2075 progressPanel.remove(progbar);
2076 GridLayout gl = (GridLayout) progressPanel.getLayout();
2077 gl.setRows(gl.getRows() - 1);
2078 if (--totalProgressCount < 1)
2080 this.getContentPane().remove(progressPanel);
2081 progressPanel = null;
2088 public void stopLoading()
2091 if (fileLoadingCount < 1)
2093 while (fileLoadingPanels.size() > 0)
2095 removeProgressPanel(fileLoadingPanels.remove(0));
2097 fileLoadingPanels.clear();
2098 fileLoadingCount = 0;
2103 public static int getViewCount(String alignmentId)
2105 AlignmentViewport[] aps = getViewports(alignmentId);
2106 return (aps == null) ? 0 : aps.length;
2111 * @param alignmentId
2112 * - if null, all sets are returned
2113 * @return all AlignmentPanels concerning the alignmentId sequence set
2115 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2117 if (Desktop.desktop == null)
2119 // no frames created and in headless mode
2120 // TODO: verify that frames are recoverable when in headless mode
2123 List<AlignmentPanel> aps = new ArrayList<>();
2124 AlignFrame[] frames = getAlignFrames();
2129 for (AlignFrame af : frames)
2131 for (AlignmentPanel ap : af.alignPanels)
2133 if (alignmentId == null
2134 || alignmentId.equals(ap.av.getSequenceSetId()))
2140 if (aps.size() == 0)
2144 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2149 * get all the viewports on an alignment.
2151 * @param sequenceSetId
2152 * unique alignment id (may be null - all viewports returned in that
2154 * @return all viewports on the alignment bound to sequenceSetId
2156 public static AlignmentViewport[] getViewports(String sequenceSetId)
2158 List<AlignmentViewport> viewp = new ArrayList<>();
2159 if (desktop != null)
2161 AlignFrame[] frames = Desktop.getAlignFrames();
2163 for (AlignFrame afr : frames)
2165 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2166 .equals(sequenceSetId))
2168 if (afr.alignPanels != null)
2170 for (AlignmentPanel ap : afr.alignPanels)
2172 if (sequenceSetId == null
2173 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2181 viewp.add(afr.getViewport());
2185 if (viewp.size() > 0)
2187 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2194 * Explode the views in the given frame into separate AlignFrame
2198 public static void explodeViews(AlignFrame af)
2200 int size = af.alignPanels.size();
2206 // FIXME: ideally should use UI interface API
2207 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2208 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2209 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2210 for (int i = 0; i < size; i++)
2212 AlignmentPanel ap = af.alignPanels.get(i);
2214 AlignFrame newaf = new AlignFrame(ap);
2216 // transfer reference for existing feature settings to new alignFrame
2217 if (ap == af.alignPanel)
2219 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2221 newaf.featureSettings = viewFeatureSettings;
2223 newaf.setFeatureSettingsGeometry(fsBounds);
2227 * Restore the view's last exploded frame geometry if known. Multiple views from
2228 * one exploded frame share and restore the same (frame) position and size.
2230 Rectangle geometry = ap.av.getExplodedGeometry();
2231 if (geometry != null)
2233 newaf.setBounds(geometry);
2236 ap.av.setGatherViewsHere(false);
2238 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2239 AlignFrame.DEFAULT_HEIGHT);
2240 // and materialise a new feature settings dialog instance for the new
2242 // (closes the old as if 'OK' was pressed)
2243 if (ap == af.alignPanel && newaf.featureSettings != null
2244 && newaf.featureSettings.isOpen()
2245 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2247 newaf.showFeatureSettingsUI();
2251 af.featureSettings = null;
2252 af.alignPanels.clear();
2253 af.closeMenuItem_actionPerformed(true);
2258 * Gather expanded views (separate AlignFrame's) with the same sequence set
2259 * identifier back in to this frame as additional views, and close the
2260 * expanded views. Note the expanded frames may themselves have multiple
2261 * views. We take the lot.
2265 public void gatherViews(AlignFrame source)
2267 source.viewport.setGatherViewsHere(true);
2268 source.viewport.setExplodedGeometry(source.getBounds());
2269 JInternalFrame[] frames = desktop.getAllFrames();
2270 String viewId = source.viewport.getSequenceSetId();
2271 for (int t = 0; t < frames.length; t++)
2273 if (frames[t] instanceof AlignFrame && frames[t] != source)
2275 AlignFrame af = (AlignFrame) frames[t];
2276 boolean gatherThis = false;
2277 for (int a = 0; a < af.alignPanels.size(); a++)
2279 AlignmentPanel ap = af.alignPanels.get(a);
2280 if (viewId.equals(ap.av.getSequenceSetId()))
2283 ap.av.setGatherViewsHere(false);
2284 ap.av.setExplodedGeometry(af.getBounds());
2285 source.addAlignmentPanel(ap, false);
2291 if (af.featureSettings != null && af.featureSettings.isOpen())
2293 if (source.featureSettings == null)
2295 // preserve the feature settings geometry for this frame
2296 source.featureSettings = af.featureSettings;
2297 source.setFeatureSettingsGeometry(
2298 af.getFeatureSettingsGeometry());
2302 // close it and forget
2303 af.featureSettings.close();
2306 af.alignPanels.clear();
2307 af.closeMenuItem_actionPerformed(true);
2312 // refresh the feature setting UI for the source frame if it exists
2313 if (source.featureSettings != null && source.featureSettings.isOpen())
2315 source.showFeatureSettingsUI();
2320 public JInternalFrame[] getAllFrames()
2322 return desktop.getAllFrames();
2326 * Checks the given url to see if it gives a response indicating that the user
2327 * should be informed of a new questionnaire.
2331 public void checkForQuestionnaire(String url)
2333 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2334 // javax.swing.SwingUtilities.invokeLater(jvq);
2335 new Thread(jvq).start();
2338 public void checkURLLinks()
2340 // Thread off the URL link checker
2341 addDialogThread(new Runnable()
2346 if (Cache.getDefault("CHECKURLLINKS", true))
2348 // check what the actual links are - if it's just the default don't
2349 // bother with the warning
2350 List<String> links = Preferences.sequenceUrlLinks
2353 // only need to check links if there is one with a
2354 // SEQUENCE_ID which is not the default EMBL_EBI link
2355 ListIterator<String> li = links.listIterator();
2356 boolean check = false;
2357 List<JLabel> urls = new ArrayList<>();
2358 while (li.hasNext())
2360 String link = li.next();
2361 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2362 && !UrlConstants.isDefaultString(link))
2365 int barPos = link.indexOf("|");
2366 String urlMsg = barPos == -1 ? link
2367 : link.substring(0, barPos) + ": "
2368 + link.substring(barPos + 1);
2369 urls.add(new JLabel(urlMsg));
2377 // ask user to check in case URL links use old style tokens
2378 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2379 JPanel msgPanel = new JPanel();
2380 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2381 msgPanel.add(Box.createVerticalGlue());
2382 JLabel msg = new JLabel(MessageManager
2383 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2384 JLabel msg2 = new JLabel(MessageManager
2385 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2387 for (JLabel url : urls)
2393 final JCheckBox jcb = new JCheckBox(
2394 MessageManager.getString("label.do_not_display_again"));
2395 jcb.addActionListener(new ActionListener()
2398 public void actionPerformed(ActionEvent e)
2400 // update Cache settings for "don't show this again"
2401 boolean showWarningAgain = !jcb.isSelected();
2402 Cache.setProperty("CHECKURLLINKS",
2403 Boolean.valueOf(showWarningAgain).toString());
2408 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2410 .getString("label.SEQUENCE_ID_no_longer_used"),
2411 JvOptionPane.WARNING_MESSAGE);
2418 * Proxy class for JDesktopPane which optionally displays the current memory
2419 * usage and highlights the desktop area with a red bar if free memory runs
2424 public class MyDesktopPane extends JDesktopPane implements Runnable
2426 private static final float ONE_MB = 1048576f;
2428 boolean showMemoryUsage = false;
2432 java.text.NumberFormat df;
2434 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2437 public MyDesktopPane(boolean showMemoryUsage)
2439 showMemoryUsage(showMemoryUsage);
2442 public void showMemoryUsage(boolean showMemory)
2444 this.showMemoryUsage = showMemory;
2447 Thread worker = new Thread(this);
2453 public boolean isShowMemoryUsage()
2455 return showMemoryUsage;
2461 df = java.text.NumberFormat.getNumberInstance();
2462 df.setMaximumFractionDigits(2);
2463 runtime = Runtime.getRuntime();
2465 while (showMemoryUsage)
2469 maxMemory = runtime.maxMemory() / ONE_MB;
2470 allocatedMemory = runtime.totalMemory() / ONE_MB;
2471 freeMemory = runtime.freeMemory() / ONE_MB;
2472 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2474 percentUsage = (totalFreeMemory / maxMemory) * 100;
2476 // if (percentUsage < 20)
2478 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2480 // instance.set.setBorder(border1);
2483 // sleep after showing usage
2485 } catch (Exception ex)
2487 ex.printStackTrace();
2493 public void paintComponent(Graphics g)
2495 if (showMemoryUsage && g != null && df != null)
2497 if (percentUsage < 20)
2499 g.setColor(Color.red);
2501 FontMetrics fm = g.getFontMetrics();
2504 g.drawString(MessageManager.formatMessage("label.memory_stats",
2506 { df.format(totalFreeMemory), df.format(maxMemory),
2507 df.format(percentUsage) }),
2508 10, getHeight() - fm.getHeight());
2512 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2513 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2518 * Accessor method to quickly get all the AlignmentFrames loaded.
2520 * @return an array of AlignFrame, or null if none found
2522 public static AlignFrame[] getAlignFrames()
2524 if (Jalview.isHeadlessMode())
2526 // Desktop.desktop is null in headless mode
2527 return new AlignFrame[] { Jalview.currentAlignFrame };
2530 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2536 List<AlignFrame> avp = new ArrayList<>();
2538 for (int i = frames.length - 1; i > -1; i--)
2540 if (frames[i] instanceof AlignFrame)
2542 avp.add((AlignFrame) frames[i]);
2544 else if (frames[i] instanceof SplitFrame)
2547 * Also check for a split frame containing an AlignFrame
2549 GSplitFrame sf = (GSplitFrame) frames[i];
2550 if (sf.getTopFrame() instanceof AlignFrame)
2552 avp.add((AlignFrame) sf.getTopFrame());
2554 if (sf.getBottomFrame() instanceof AlignFrame)
2556 avp.add((AlignFrame) sf.getBottomFrame());
2560 if (avp.size() == 0)
2564 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2569 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2573 public GStructureViewer[] getJmols()
2575 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2581 List<GStructureViewer> avp = new ArrayList<>();
2583 for (int i = frames.length - 1; i > -1; i--)
2585 if (frames[i] instanceof AppJmol)
2587 GStructureViewer af = (GStructureViewer) frames[i];
2591 if (avp.size() == 0)
2595 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2600 * Add Groovy Support to Jalview
2603 public void groovyShell_actionPerformed()
2607 openGroovyConsole();
2608 } catch (Exception ex)
2610 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2611 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2613 MessageManager.getString("label.couldnt_create_groovy_shell"),
2614 MessageManager.getString("label.groovy_support_failed"),
2615 JvOptionPane.ERROR_MESSAGE);
2620 * Open the Groovy console
2622 void openGroovyConsole()
2624 if (groovyConsole == null)
2626 groovyConsole = new groovy.ui.Console();
2627 groovyConsole.setVariable("Jalview", this);
2628 groovyConsole.run();
2631 * We allow only one console at a time, so that AlignFrame menu option
2632 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2633 * enable 'Run script', when the console is opened, and the reverse when it is
2636 Window window = (Window) groovyConsole.getFrame();
2637 window.addWindowListener(new WindowAdapter()
2640 public void windowClosed(WindowEvent e)
2643 * rebind CMD-Q from Groovy Console to Jalview Quit
2646 enableExecuteGroovy(false);
2652 * show Groovy console window (after close and reopen)
2654 ((Window) groovyConsole.getFrame()).setVisible(true);
2657 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2658 * opening a second console
2660 enableExecuteGroovy(true);
2664 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2665 * binding when opened
2667 protected void addQuitHandler()
2670 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2672 .getKeyStroke(KeyEvent.VK_Q,
2673 jalview.util.ShortcutKeyMaskExWrapper
2674 .getMenuShortcutKeyMaskEx()),
2676 getRootPane().getActionMap().put("Quit", new AbstractAction()
2679 public void actionPerformed(ActionEvent e)
2687 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2690 * true if Groovy console is open
2692 public void enableExecuteGroovy(boolean enabled)
2695 * disable opening a second Groovy console (or re-enable when the console is
2698 groovyShell.setEnabled(!enabled);
2700 AlignFrame[] alignFrames = getAlignFrames();
2701 if (alignFrames != null)
2703 for (AlignFrame af : alignFrames)
2705 af.setGroovyEnabled(enabled);
2711 * Progress bars managed by the IProgressIndicator method.
2713 private Hashtable<Long, JPanel> progressBars;
2715 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2720 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2723 public void setProgressBar(String message, long id)
2725 if (progressBars == null)
2727 progressBars = new Hashtable<>();
2728 progressBarHandlers = new Hashtable<>();
2731 if (progressBars.get(Long.valueOf(id)) != null)
2733 JPanel panel = progressBars.remove(Long.valueOf(id));
2734 if (progressBarHandlers.contains(Long.valueOf(id)))
2736 progressBarHandlers.remove(Long.valueOf(id));
2738 removeProgressPanel(panel);
2742 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2749 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2750 * jalview.gui.IProgressIndicatorHandler)
2753 public void registerHandler(final long id,
2754 final IProgressIndicatorHandler handler)
2756 if (progressBarHandlers == null
2757 || !progressBars.containsKey(Long.valueOf(id)))
2759 throw new Error(MessageManager.getString(
2760 "error.call_setprogressbar_before_registering_handler"));
2762 progressBarHandlers.put(Long.valueOf(id), handler);
2763 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2764 if (handler.canCancel())
2766 JButton cancel = new JButton(
2767 MessageManager.getString("action.cancel"));
2768 final IProgressIndicator us = this;
2769 cancel.addActionListener(new ActionListener()
2773 public void actionPerformed(ActionEvent e)
2775 handler.cancelActivity(id);
2776 us.setProgressBar(MessageManager
2777 .formatMessage("label.cancelled_params", new Object[]
2778 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2782 progressPanel.add(cancel, BorderLayout.EAST);
2788 * @return true if any progress bars are still active
2791 public boolean operationInProgress()
2793 if (progressBars != null && progressBars.size() > 0)
2801 * This will return the first AlignFrame holding the given viewport instance.
2802 * It will break if there are more than one AlignFrames viewing a particular
2806 * @return alignFrame for viewport
2808 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2810 if (desktop != null)
2812 AlignmentPanel[] aps = getAlignmentPanels(
2813 viewport.getSequenceSetId());
2814 for (int panel = 0; aps != null && panel < aps.length; panel++)
2816 if (aps[panel] != null && aps[panel].av == viewport)
2818 return aps[panel].alignFrame;
2825 public VamsasApplication getVamsasApplication()
2827 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2833 * flag set if jalview GUI is being operated programmatically
2835 private boolean inBatchMode = false;
2838 * check if jalview GUI is being operated programmatically
2840 * @return inBatchMode
2842 public boolean isInBatchMode()
2848 * set flag if jalview GUI is being operated programmatically
2850 * @param inBatchMode
2852 public void setInBatchMode(boolean inBatchMode)
2854 this.inBatchMode = inBatchMode;
2858 * start service discovery and wait till it is done
2860 public void startServiceDiscovery()
2862 startServiceDiscovery(false);
2866 * start service discovery threads - blocking or non-blocking
2870 public void startServiceDiscovery(boolean blocking)
2872 startServiceDiscovery(blocking, false);
2876 * start service discovery threads
2879 * - false means call returns immediately
2880 * @param ignore_SHOW_JWS2_SERVICES_preference
2881 * - when true JABA services are discovered regardless of user's JWS2
2882 * discovery preference setting
2884 public void startServiceDiscovery(boolean blocking,
2885 boolean ignore_SHOW_JWS2_SERVICES_preference)
2887 boolean alive = true;
2888 Thread t0 = null, t1 = null, t2 = null;
2889 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2892 // todo: changesupport handlers need to be transferred
2893 if (discoverer == null)
2895 discoverer = new jalview.ws.jws1.Discoverer();
2896 // register PCS handler for desktop.
2897 discoverer.addPropertyChangeListener(changeSupport);
2899 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2900 // until we phase out completely
2901 (t0 = new Thread(discoverer)).start();
2904 if (ignore_SHOW_JWS2_SERVICES_preference
2905 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2907 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2908 .startDiscoverer(changeSupport);
2912 // TODO: do rest service discovery
2921 } catch (Exception e)
2924 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2925 || (t3 != null && t3.isAlive())
2926 || (t0 != null && t0.isAlive());
2932 * called to check if the service discovery process completed successfully.
2936 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2938 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2940 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2941 .getErrorMessages();
2944 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2946 if (serviceChangedDialog == null)
2948 // only run if we aren't already displaying one of these.
2949 addDialogThread(serviceChangedDialog = new Runnable()
2956 * JalviewDialog jd =new JalviewDialog() {
2958 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2960 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2962 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2964 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2966 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2967 * + " or mis-configured HTTP proxy settings.<br/>" +
2968 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2969 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2970 * true, true, "Web Service Configuration Problem", 450, 400);
2972 * jd.waitForInput();
2974 JvOptionPane.showConfirmDialog(Desktop.desktop,
2975 new JLabel("<html><table width=\"450\"><tr><td>"
2976 + ermsg + "</td></tr></table>"
2977 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2978 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2979 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2980 + " Tools->Preferences dialog box to change them.</p></html>"),
2981 "Web Service Configuration Problem",
2982 JvOptionPane.DEFAULT_OPTION,
2983 JvOptionPane.ERROR_MESSAGE);
2984 serviceChangedDialog = null;
2992 jalview.bin.Console.error(
2993 "Errors reported by JABA discovery service. Check web services preferences.\n"
3000 private Runnable serviceChangedDialog = null;
3003 * start a thread to open a URL in the configured browser. Pops up a warning
3004 * dialog to the user if there is an exception when calling out to the browser
3009 public static void showUrl(final String url)
3011 showUrl(url, Desktop.instance);
3015 * Like showUrl but allows progress handler to be specified
3019 * (null) or object implementing IProgressIndicator
3021 public static void showUrl(final String url,
3022 final IProgressIndicator progress)
3024 new Thread(new Runnable()
3031 if (progress != null)
3033 progress.setProgressBar(MessageManager
3034 .formatMessage("status.opening_params", new Object[]
3035 { url }), this.hashCode());
3037 jalview.util.BrowserLauncher.openURL(url);
3038 } catch (Exception ex)
3040 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3042 .getString("label.web_browser_not_found_unix"),
3043 MessageManager.getString("label.web_browser_not_found"),
3044 JvOptionPane.WARNING_MESSAGE);
3046 ex.printStackTrace();
3048 if (progress != null)
3050 progress.setProgressBar(null, this.hashCode());
3056 public static WsParamSetManager wsparamManager = null;
3058 public static ParamManager getUserParameterStore()
3060 if (wsparamManager == null)
3062 wsparamManager = new WsParamSetManager();
3064 return wsparamManager;
3068 * static hyperlink handler proxy method for use by Jalview's internal windows
3072 public static void hyperlinkUpdate(HyperlinkEvent e)
3074 if (e.getEventType() == EventType.ACTIVATED)
3079 url = e.getURL().toString();
3080 Desktop.showUrl(url);
3081 } catch (Exception x)
3086 .error("Couldn't handle string " + url + " as a URL.");
3088 // ignore any exceptions due to dud links.
3095 * single thread that handles display of dialogs to user.
3097 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3100 * flag indicating if dialogExecutor should try to acquire a permit
3102 private volatile boolean dialogPause = true;
3107 private Semaphore block = new Semaphore(0);
3109 private static groovy.ui.Console groovyConsole;
3112 * add another dialog thread to the queue
3116 public void addDialogThread(final Runnable prompter)
3118 dialogExecutor.submit(new Runnable()
3125 acquireDialogQueue();
3127 if (instance == null)
3133 SwingUtilities.invokeAndWait(prompter);
3134 } catch (Exception q)
3136 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3143 private boolean dialogQueueStarted = false;
3145 public void startDialogQueue()
3147 if (dialogQueueStarted)
3151 // set the flag so we don't pause waiting for another permit and semaphore
3152 // the current task to begin
3153 releaseDialogQueue();
3154 dialogQueueStarted = true;
3157 public void acquireDialogQueue()
3163 } catch (InterruptedException e)
3165 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3170 public void releaseDialogQueue()
3177 dialogPause = false;
3181 * Outputs an image of the desktop to file in EPS format, after prompting the
3182 * user for choice of Text or Lineart character rendering (unless a preference
3183 * has been set). The file name is generated as
3186 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3190 protected void snapShotWindow_actionPerformed(ActionEvent e)
3192 // currently the menu option to do this is not shown
3195 int width = getWidth();
3196 int height = getHeight();
3198 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3199 ImageWriterI writer = new ImageWriterI()
3202 public void exportImage(Graphics g) throws Exception
3205 jalview.bin.Console.info("Successfully written snapshot to file "
3206 + of.getAbsolutePath());
3209 String title = "View of desktop";
3210 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3214 exporter.doExport(of, this, width, height, title);
3215 } catch (ImageOutputException ioex)
3217 jalview.bin.Console.error(
3218 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3224 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3225 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3226 * and location last time the view was expanded (if any). However it does not
3227 * remember the split pane divider location - this is set to match the
3228 * 'exploding' frame.
3232 public void explodeViews(SplitFrame sf)
3234 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3235 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3236 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3238 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3240 int viewCount = topPanels.size();
3247 * Processing in reverse order works, forwards order leaves the first panels not
3248 * visible. I don't know why!
3250 for (int i = viewCount - 1; i >= 0; i--)
3253 * Make new top and bottom frames. These take over the respective AlignmentPanel
3254 * objects, including their AlignmentViewports, so the cdna/protein
3255 * relationships between the viewports is carried over to the new split frames.
3257 * explodedGeometry holds the (x, y) position of the previously exploded
3258 * SplitFrame, and the (width, height) of the AlignFrame component
3260 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3261 AlignFrame newTopFrame = new AlignFrame(topPanel);
3262 newTopFrame.setSize(oldTopFrame.getSize());
3263 newTopFrame.setVisible(true);
3264 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3265 .getExplodedGeometry();
3266 if (geometry != null)
3268 newTopFrame.setSize(geometry.getSize());
3271 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3272 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3273 newBottomFrame.setSize(oldBottomFrame.getSize());
3274 newBottomFrame.setVisible(true);
3275 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3276 .getExplodedGeometry();
3277 if (geometry != null)
3279 newBottomFrame.setSize(geometry.getSize());
3282 topPanel.av.setGatherViewsHere(false);
3283 bottomPanel.av.setGatherViewsHere(false);
3284 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3286 if (geometry != null)
3288 splitFrame.setLocation(geometry.getLocation());
3290 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3294 * Clear references to the panels (now relocated in the new SplitFrames) before
3295 * closing the old SplitFrame.
3298 bottomPanels.clear();
3303 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3304 * back into the given SplitFrame as additional views. Note that the gathered
3305 * frames may themselves have multiple views.
3309 public void gatherViews(GSplitFrame source)
3312 * special handling of explodedGeometry for a view within a SplitFrame: - it
3313 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3314 * height) of the AlignFrame component
3316 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3317 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3318 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3319 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3320 myBottomFrame.viewport
3321 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3322 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3323 myTopFrame.viewport.setGatherViewsHere(true);
3324 myBottomFrame.viewport.setGatherViewsHere(true);
3325 String topViewId = myTopFrame.viewport.getSequenceSetId();
3326 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3328 JInternalFrame[] frames = desktop.getAllFrames();
3329 for (JInternalFrame frame : frames)
3331 if (frame instanceof SplitFrame && frame != source)
3333 SplitFrame sf = (SplitFrame) frame;
3334 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3335 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3336 boolean gatherThis = false;
3337 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3339 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3340 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3341 if (topViewId.equals(topPanel.av.getSequenceSetId())
3342 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3345 topPanel.av.setGatherViewsHere(false);
3346 bottomPanel.av.setGatherViewsHere(false);
3347 topPanel.av.setExplodedGeometry(
3348 new Rectangle(sf.getLocation(), topFrame.getSize()));
3349 bottomPanel.av.setExplodedGeometry(
3350 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3351 myTopFrame.addAlignmentPanel(topPanel, false);
3352 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3358 topFrame.getAlignPanels().clear();
3359 bottomFrame.getAlignPanels().clear();
3366 * The dust settles...give focus to the tab we did this from.
3368 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3371 public static groovy.ui.Console getGroovyConsole()
3373 return groovyConsole;
3377 * handles the payload of a drag and drop event.
3379 * TODO refactor to desktop utilities class
3382 * - Data source strings extracted from the drop event
3384 * - protocol for each data source extracted from the drop event
3388 * - the payload from the drop event
3391 public static void transferFromDropTarget(List<Object> files,
3392 List<DataSourceType> protocols, DropTargetDropEvent evt,
3393 Transferable t) throws Exception
3396 DataFlavor uriListFlavor = new DataFlavor(
3397 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3400 urlFlavour = new DataFlavor(
3401 "application/x-java-url; class=java.net.URL");
3402 } catch (ClassNotFoundException cfe)
3404 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3408 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3413 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3414 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3415 // means url may be null.
3418 protocols.add(DataSourceType.URL);
3419 files.add(url.toString());
3420 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3421 + files.get(files.size() - 1));
3426 if (Platform.isAMacAndNotJS())
3428 jalview.bin.Console.errPrintln(
3429 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3432 } catch (Throwable ex)
3434 jalview.bin.Console.debug("URL drop handler failed.", ex);
3437 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3439 // Works on Windows and MacOSX
3440 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3441 for (Object file : (List) t
3442 .getTransferData(DataFlavor.javaFileListFlavor))
3445 protocols.add(DataSourceType.FILE);
3450 // Unix like behaviour
3451 boolean added = false;
3453 if (t.isDataFlavorSupported(uriListFlavor))
3455 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3456 // This is used by Unix drag system
3457 data = (String) t.getTransferData(uriListFlavor);
3461 // fallback to text: workaround - on OSX where there's a JVM bug
3463 .debug("standard URIListFlavor failed. Trying text");
3464 // try text fallback
3465 DataFlavor textDf = new DataFlavor(
3466 "text/plain;class=java.lang.String");
3467 if (t.isDataFlavorSupported(textDf))
3469 data = (String) t.getTransferData(textDf);
3472 jalview.bin.Console.debug("Plain text drop content returned "
3473 + (data == null ? "Null - failed" : data));
3478 while (protocols.size() < files.size())
3480 jalview.bin.Console.debug("Adding missing FILE protocol for "
3481 + files.get(protocols.size()));
3482 protocols.add(DataSourceType.FILE);
3484 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3485 data, "\r\n"); st.hasMoreTokens();)
3488 String s = st.nextToken();
3489 if (s.startsWith("#"))
3491 // the line is a comment (as per the RFC 2483)
3494 java.net.URI uri = new java.net.URI(s);
3495 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3497 protocols.add(DataSourceType.URL);
3498 files.add(uri.toString());
3502 // otherwise preserve old behaviour: catch all for file objects
3503 java.io.File file = new java.io.File(uri);
3504 protocols.add(DataSourceType.FILE);
3505 files.add(file.toString());
3510 if (jalview.bin.Console.isDebugEnabled())
3512 if (data == null || !added)
3515 if (t.getTransferDataFlavors() != null
3516 && t.getTransferDataFlavors().length > 0)
3518 jalview.bin.Console.debug(
3519 "Couldn't resolve drop data. Here are the supported flavors:");
3520 for (DataFlavor fl : t.getTransferDataFlavors())
3522 jalview.bin.Console.debug(
3523 "Supported transfer dataflavor: " + fl.toString());
3524 Object df = t.getTransferData(fl);
3527 jalview.bin.Console.debug("Retrieves: " + df);
3531 jalview.bin.Console.debug("Retrieved nothing");
3538 .debug("Couldn't resolve dataflavor for drop: "
3544 if (Platform.isWindowsAndNotJS())
3547 .debug("Scanning dropped content for Windows Link Files");
3549 // resolve any .lnk files in the file drop
3550 for (int f = 0; f < files.size(); f++)
3552 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3553 if (protocols.get(f).equals(DataSourceType.FILE)
3554 && (source.endsWith(".lnk") || source.endsWith(".url")
3555 || source.endsWith(".site")))
3559 Object obj = files.get(f);
3560 File lf = (obj instanceof File ? (File) obj
3561 : new File((String) obj));
3562 // process link file to get a URL
3563 jalview.bin.Console.debug("Found potential link file: " + lf);
3564 WindowsShortcut wscfile = new WindowsShortcut(lf);
3565 String fullname = wscfile.getRealFilename();
3566 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3567 files.set(f, fullname);
3568 jalview.bin.Console.debug("Parsed real filename " + fullname
3569 + " to extract protocol: " + protocols.get(f));
3570 } catch (Exception ex)
3572 jalview.bin.Console.error(
3573 "Couldn't parse " + files.get(f) + " as a link file.",
3582 * Sets the Preferences property for experimental features to True or False
3583 * depending on the state of the controlling menu item
3586 protected void showExperimental_actionPerformed(boolean selected)
3588 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3592 * Answers a (possibly empty) list of any structure viewer frames (currently
3593 * for either Jmol or Chimera) which are currently open. This may optionally
3594 * be restricted to viewers of a specified class, or viewers linked to a
3595 * specified alignment panel.
3598 * if not null, only return viewers linked to this panel
3599 * @param structureViewerClass
3600 * if not null, only return viewers of this class
3603 public List<StructureViewerBase> getStructureViewers(
3604 AlignmentPanel apanel,
3605 Class<? extends StructureViewerBase> structureViewerClass)
3607 List<StructureViewerBase> result = new ArrayList<>();
3608 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3610 for (JInternalFrame frame : frames)
3612 if (frame instanceof StructureViewerBase)
3614 if (structureViewerClass == null
3615 || structureViewerClass.isInstance(frame))
3618 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3620 result.add((StructureViewerBase) frame);
3628 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3630 private static boolean debugScaleMessageDone = false;
3632 public static void debugScaleMessage(Graphics g)
3634 if (debugScaleMessageDone)
3638 // output used by tests to check HiDPI scaling settings in action
3641 Graphics2D gg = (Graphics2D) g;
3644 AffineTransform t = gg.getTransform();
3645 double scaleX = t.getScaleX();
3646 double scaleY = t.getScaleY();
3647 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3648 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3649 debugScaleMessageDone = true;
3653 jalview.bin.Console.debug("Desktop graphics null");
3655 } catch (Exception e)
3657 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3662 * closes the current instance window, disposes and forgets about it.
3664 public static void closeDesktop()
3666 if (Desktop.instance != null)
3668 Desktop.instance.closeAll_actionPerformed(null);
3669 Desktop.instance.setVisible(false);
3670 Desktop us = Desktop.instance;
3671 Desktop.instance = null;
3672 // call dispose in a separate thread - try to avoid indirect deadlocks
3673 new Thread(new Runnable()
3678 ExecutorService dex = us.dialogExecutor;
3682 us.dialogExecutor = null;
3683 us.block.drainPermits();
3692 * checks if any progress bars are being displayed in any of the windows
3693 * managed by the desktop
3697 public boolean operationsAreInProgress()
3699 JInternalFrame[] frames = getAllFrames();
3700 for (JInternalFrame frame : frames)
3702 if (frame instanceof IProgressIndicator)
3704 if (((IProgressIndicator) frame).operationInProgress())
3710 return operationInProgress();
3714 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3715 * The way the modal JInternalFrame is made means it cannot be a child of an
3716 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3718 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3720 protected static void addModal(AlignFrame af, JInternalFrame jif)
3722 alignFrameModalMap.put(af, jif);
3725 protected static void closeModal(AlignFrame af)
3727 if (!alignFrameModalMap.containsKey(af))
3731 JInternalFrame jif = alignFrameModalMap.get(af);
3736 jif.setClosed(true);
3737 } catch (PropertyVetoException e)
3739 e.printStackTrace();
3742 alignFrameModalMap.remove(af);
3745 public void nonBlockingDialog(String title, String message, String button,
3746 int type, boolean scrollable, boolean modal)
3748 nonBlockingDialog(32, 2, title, message, null, button, type, scrollable,
3752 public void nonBlockingDialog(int width, int height, String title,
3753 String message, String boxtext, String button, int type,
3754 boolean scrollable, boolean modal)
3758 type = JvOptionPane.WARNING_MESSAGE;
3760 JLabel jl = new JLabel(message);
3762 JTextArea jta = new JTextArea(height, width);
3763 // jta.setLineWrap(true);
3764 jta.setEditable(false);
3765 jta.setWrapStyleWord(true);
3766 jta.setAutoscrolls(true);
3767 jta.setText(boxtext);
3769 JScrollPane jsp = scrollable
3770 ? new JScrollPane(jta, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3771 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3774 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3776 JPanel jp = new JPanel();
3777 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3779 if (message != null)
3781 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3784 if (boxtext != null)
3788 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3793 jta.setAlignmentX(Component.LEFT_ALIGNMENT);
3798 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3800 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3802 { button }, button, modal, null, false);