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.FileNotFoundException;
58 import java.io.FileWriter;
59 import java.io.IOException;
60 import java.lang.reflect.Field;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.HashMap;
65 import java.util.Hashtable;
66 import java.util.List;
67 import java.util.ListIterator;
68 import java.util.Locale;
70 import java.util.Vector;
71 import java.util.concurrent.ExecutorService;
72 import java.util.concurrent.Executors;
73 import java.util.concurrent.Semaphore;
75 import javax.swing.AbstractAction;
76 import javax.swing.Action;
77 import javax.swing.ActionMap;
78 import javax.swing.Box;
79 import javax.swing.BoxLayout;
80 import javax.swing.DefaultDesktopManager;
81 import javax.swing.DesktopManager;
82 import javax.swing.InputMap;
83 import javax.swing.JButton;
84 import javax.swing.JCheckBox;
85 import javax.swing.JComboBox;
86 import javax.swing.JComponent;
87 import javax.swing.JDesktopPane;
88 import javax.swing.JFrame;
89 import javax.swing.JInternalFrame;
90 import javax.swing.JLabel;
91 import javax.swing.JMenuItem;
92 import javax.swing.JOptionPane;
93 import javax.swing.JPanel;
94 import javax.swing.JPopupMenu;
95 import javax.swing.JProgressBar;
96 import javax.swing.JScrollPane;
97 import javax.swing.JTextArea;
98 import javax.swing.JTextField;
99 import javax.swing.JTextPane;
100 import javax.swing.KeyStroke;
101 import javax.swing.SwingUtilities;
102 import javax.swing.WindowConstants;
103 import javax.swing.event.HyperlinkEvent;
104 import javax.swing.event.HyperlinkEvent.EventType;
105 import javax.swing.event.InternalFrameAdapter;
106 import javax.swing.event.InternalFrameEvent;
107 import javax.swing.text.JTextComponent;
109 import org.stackoverflowusers.file.WindowsShortcut;
111 import jalview.api.AlignViewportI;
112 import jalview.api.AlignmentViewPanel;
113 import jalview.api.structures.JalviewStructureDisplayI;
114 import jalview.bin.Cache;
115 import jalview.bin.Jalview;
116 import jalview.bin.Jalview.ExitCode;
117 import jalview.bin.argparser.Arg;
118 import jalview.bin.groovy.JalviewObject;
119 import jalview.bin.groovy.JalviewObjectI;
120 import jalview.datamodel.Alignment;
121 import jalview.datamodel.HiddenColumns;
122 import jalview.datamodel.Sequence;
123 import jalview.datamodel.SequenceI;
124 import jalview.gui.ImageExporter.ImageWriterI;
125 import jalview.gui.QuitHandler.QResponse;
126 import jalview.io.BackupFiles;
127 import jalview.io.DataSourceType;
128 import jalview.io.FileFormat;
129 import jalview.io.FileFormatException;
130 import jalview.io.FileFormatI;
131 import jalview.io.FileFormats;
132 import jalview.io.FileLoader;
133 import jalview.io.FormatAdapter;
134 import jalview.io.IdentifyFile;
135 import jalview.io.JalviewFileChooser;
136 import jalview.io.JalviewFileView;
137 import jalview.io.exceptions.ImageOutputException;
138 import jalview.jbgui.GSplitFrame;
139 import jalview.jbgui.GStructureViewer;
140 import jalview.project.Jalview2XML;
141 import jalview.structure.StructureSelectionManager;
142 import jalview.urls.IdOrgSettings;
143 import jalview.util.BrowserLauncher;
144 import jalview.util.ChannelProperties;
145 import jalview.util.ImageMaker.TYPE;
146 import jalview.util.LaunchUtils;
147 import jalview.util.MessageManager;
148 import jalview.util.Platform;
149 import jalview.util.ShortcutKeyMaskExWrapper;
150 import jalview.util.UrlConstants;
151 import jalview.viewmodel.AlignmentViewport;
152 import jalview.ws.params.ParamManager;
153 import jalview.ws.utils.UrlDownloadClient;
160 * @version $Revision: 1.155 $
162 public class Desktop extends jalview.jbgui.GDesktop
163 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
164 jalview.api.StructureSelectionManagerProvider, JalviewObjectI
166 private static final String CITATION;
169 URL bg_logo_url = ChannelProperties.getImageURL(
170 "bg_logo." + String.valueOf(SplashScreen.logoSize));
171 URL uod_logo_url = ChannelProperties.getImageURL(
172 "uod_banner." + String.valueOf(SplashScreen.logoSize));
173 boolean logo = (bg_logo_url != null || uod_logo_url != null);
174 StringBuilder sb = new StringBuilder();
176 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
181 sb.append(bg_logo_url == null ? ""
182 : "<img alt=\"Barton Group logo\" src=\""
183 + bg_logo_url.toString() + "\">");
184 sb.append(uod_logo_url == null ? ""
185 : " <img alt=\"University of Dundee shield\" src=\""
186 + uod_logo_url.toString() + "\">");
188 "<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>");
189 sb.append("<br><br>If you use Jalview, please cite:"
190 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
191 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
192 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
193 CITATION = sb.toString();
196 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
198 private static int DEFAULT_MIN_WIDTH = 300;
200 private static int DEFAULT_MIN_HEIGHT = 250;
202 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
204 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
206 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
208 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
210 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
212 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
214 public static void setLiveDragMode(boolean b)
216 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
217 : JDesktopPane.OUTLINE_DRAG_MODE;
219 desktop.setDragMode(DRAG_MODE);
222 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
224 public static boolean nosplash = false;
227 * news reader - null if it was never started.
229 private BlogReader jvnews = null;
231 private File projectFile;
235 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
237 public void addJalviewPropertyChangeListener(
238 PropertyChangeListener listener)
240 changeSupport.addJalviewPropertyChangeListener(listener);
244 * @param propertyName
246 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
247 * java.beans.PropertyChangeListener)
249 public void addJalviewPropertyChangeListener(String propertyName,
250 PropertyChangeListener listener)
252 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
256 * @param propertyName
258 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
259 * java.beans.PropertyChangeListener)
261 public void removeJalviewPropertyChangeListener(String propertyName,
262 PropertyChangeListener listener)
264 changeSupport.removeJalviewPropertyChangeListener(propertyName,
268 /** Singleton Desktop instance */
269 public static Desktop instance;
271 public static MyDesktopPane desktop;
273 public static MyDesktopPane getDesktop()
275 // BH 2018 could use currentThread() here as a reference to a
276 // Hashtable<Thread, MyDesktopPane> in JavaScript
280 static int openFrameCount = 0;
282 static final int xOffset = 30;
284 static final int yOffset = 30;
286 public static jalview.ws.jws1.Discoverer discoverer;
288 public static Object[] jalviewClipboard;
290 public static boolean internalCopy = false;
292 static int fileLoadingCount = 0;
294 class MyDesktopManager implements DesktopManager
297 private DesktopManager delegate;
299 public MyDesktopManager(DesktopManager delegate)
301 this.delegate = delegate;
305 public void activateFrame(JInternalFrame f)
309 delegate.activateFrame(f);
310 } catch (NullPointerException npe)
312 Point p = getMousePosition();
313 instance.showPasteMenu(p.x, p.y);
318 public void beginDraggingFrame(JComponent f)
320 delegate.beginDraggingFrame(f);
324 public void beginResizingFrame(JComponent f, int direction)
326 delegate.beginResizingFrame(f, direction);
330 public void closeFrame(JInternalFrame f)
332 delegate.closeFrame(f);
336 public void deactivateFrame(JInternalFrame f)
338 delegate.deactivateFrame(f);
342 public void deiconifyFrame(JInternalFrame f)
344 delegate.deiconifyFrame(f);
348 public void dragFrame(JComponent f, int newX, int newY)
354 delegate.dragFrame(f, newX, newY);
358 public void endDraggingFrame(JComponent f)
360 delegate.endDraggingFrame(f);
365 public void endResizingFrame(JComponent f)
367 delegate.endResizingFrame(f);
372 public void iconifyFrame(JInternalFrame f)
374 delegate.iconifyFrame(f);
378 public void maximizeFrame(JInternalFrame f)
380 delegate.maximizeFrame(f);
384 public void minimizeFrame(JInternalFrame f)
386 delegate.minimizeFrame(f);
390 public void openFrame(JInternalFrame f)
392 delegate.openFrame(f);
396 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
403 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
407 public void setBoundsForFrame(JComponent f, int newX, int newY,
408 int newWidth, int newHeight)
410 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
413 // All other methods, simply delegate
418 * Creates a new Desktop object.
424 * A note to implementors. It is ESSENTIAL that any activities that might
425 * block are spawned off as threads rather than waited for during this
430 doConfigureStructurePrefs();
431 setTitle(ChannelProperties.getProperty("app_name") + " "
432 + Cache.getProperty("VERSION"));
435 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
436 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
437 * officially documented or guaranteed to exist, so we access it via
438 * reflection. There appear to be unfathomable criteria about what this
439 * string can contain, and it if doesn't meet those criteria then "java"
440 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
441 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
442 * not. The reflection access may generate a warning: WARNING: An illegal
443 * reflective access operation has occurred WARNING: Illegal reflective
444 * access by jalview.gui.Desktop () to field
445 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
447 if (Platform.isLinux())
449 if (LaunchUtils.getJavaVersion() >= 11)
452 * Send this message to stderr as the warning that follows (due to
453 * reflection) also goes to stderr.
455 jalview.bin.Console.errPrintln(
456 "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.");
458 final String awtAppClassName = "awtAppClassName";
461 Toolkit xToolkit = Toolkit.getDefaultToolkit();
462 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
463 Field awtAppClassNameField = null;
465 if (Arrays.stream(declaredFields)
466 .anyMatch(f -> f.getName().equals(awtAppClassName)))
468 awtAppClassNameField = xToolkit.getClass()
469 .getDeclaredField(awtAppClassName);
472 String title = ChannelProperties.getProperty("app_name");
473 if (awtAppClassNameField != null)
475 awtAppClassNameField.setAccessible(true);
476 awtAppClassNameField.set(xToolkit, title);
481 .debug("XToolkit: " + awtAppClassName + " not found");
483 } catch (Exception e)
485 jalview.bin.Console.debug("Error setting " + awtAppClassName);
486 jalview.bin.Console.trace(Cache.getStackTraceString(e));
490 setIconImages(ChannelProperties.getIconList());
492 // override quit handling when GUI OS close [X] button pressed
493 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
494 addWindowListener(new WindowAdapter()
497 public void windowClosing(WindowEvent ev)
499 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
503 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
505 boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE,
506 "SHOW_JAVA_CONSOLE", false);
508 // start dialogue queue for single dialogues
511 if (!Platform.isJS())
518 Desktop.instance.acquireDialogQueue();
520 jconsole = new Console(this);
521 jconsole.setHeader(Cache.getVersionDetailsForConsole());
522 showConsole(showjconsole);
524 Desktop.instance.releaseDialogQueue();
527 desktop = new MyDesktopPane(selmemusage);
529 showMemusage.setSelected(selmemusage);
530 desktop.setBackground(Color.white);
532 getContentPane().setLayout(new BorderLayout());
533 // alternate config - have scrollbars - see notes in JAL-153
534 // JScrollPane sp = new JScrollPane();
535 // sp.getViewport().setView(desktop);
536 // getContentPane().add(sp, BorderLayout.CENTER);
538 // BH 2018 - just an experiment to try unclipped JInternalFrames.
541 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
544 getContentPane().add(desktop, BorderLayout.CENTER);
545 desktop.setDragMode(DRAG_MODE);
547 // This line prevents Windows Look&Feel resizing all new windows to maximum
548 // if previous window was maximised
549 desktop.setDesktopManager(new MyDesktopManager(
550 Platform.isJS() ? desktop.getDesktopManager()
551 : new DefaultDesktopManager()));
553 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
554 : Platform.isAMacAndNotJS()
555 ? new AquaInternalFrameManager(
556 desktop.getDesktopManager())
557 : desktop.getDesktopManager())));
560 Rectangle dims = getLastKnownDimensions("");
567 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
568 int xPos = Math.max(5, (screenSize.width - 900) / 2);
569 int yPos = Math.max(5, (screenSize.height - 650) / 2);
570 setBounds(xPos, yPos, 900, 650);
573 if (!Platform.isJS())
580 showNews.setVisible(false);
582 experimentalFeatures.setSelected(showExperimental());
584 getIdentifiersOrgData();
588 // Spawn a thread that shows the splashscreen
591 SwingUtilities.invokeLater(new Runnable()
596 new SplashScreen(true);
601 // Thread off a new instance of the file chooser - this reduces the time
602 // it takes to open it later on.
603 new Thread(new Runnable()
608 jalview.bin.Console.debug("Filechooser init thread started.");
609 String fileFormat = FileLoader.getUseDefaultFileFormat()
610 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
612 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
614 jalview.bin.Console.debug("Filechooser init thread finished.");
617 // Add the service change listener
618 changeSupport.addJalviewPropertyChangeListener("services",
619 new PropertyChangeListener()
623 public void propertyChange(PropertyChangeEvent evt)
626 .debug("Firing service changed event for "
627 + evt.getNewValue());
628 JalviewServicesChanged(evt);
633 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
636 this.addMouseListener(ma = new MouseAdapter()
639 public void mousePressed(MouseEvent evt)
641 if (evt.isPopupTrigger()) // Mac
643 showPasteMenu(evt.getX(), evt.getY());
648 public void mouseReleased(MouseEvent evt)
650 if (evt.isPopupTrigger()) // Windows
652 showPasteMenu(evt.getX(), evt.getY());
656 desktop.addMouseListener(ma);
660 // used for jalviewjsTest
661 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
667 * Answers true if user preferences to enable experimental features is True
672 public boolean showExperimental()
674 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
675 Boolean.FALSE.toString());
676 return Boolean.valueOf(experimental).booleanValue();
679 public void doConfigureStructurePrefs()
681 // configure services
682 StructureSelectionManager ssm = StructureSelectionManager
683 .getStructureSelectionManager(this);
684 StructureSelectionManager.doConfigureStructurePrefs(ssm);
687 public void checkForNews()
689 final Desktop me = this;
690 // Thread off the news reader, in case there are connection problems.
691 new Thread(new Runnable()
696 jalview.bin.Console.debug("Starting news thread.");
697 jvnews = new BlogReader(me);
698 showNews.setVisible(true);
699 jalview.bin.Console.debug("Completed news thread.");
704 public void getIdentifiersOrgData()
706 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
707 {// Thread off the identifiers fetcher
708 new Thread(new Runnable()
714 .debug("Downloading data from identifiers.org");
717 UrlDownloadClient.download(IdOrgSettings.getUrl(),
718 IdOrgSettings.getDownloadLocation());
719 } catch (IOException e)
722 .debug("Exception downloading identifiers.org data"
732 protected void showNews_actionPerformed(ActionEvent e)
734 showNews(showNews.isSelected());
737 void showNews(boolean visible)
739 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
740 showNews.setSelected(visible);
741 if (visible && !jvnews.isVisible())
743 new Thread(new Runnable()
748 long now = System.currentTimeMillis();
749 Desktop.instance.setProgressBar(
750 MessageManager.getString("status.refreshing_news"), now);
751 jvnews.refreshNews();
752 Desktop.instance.setProgressBar(null, now);
760 * recover the last known dimensions for a jalview window
763 * - empty string is desktop, all other windows have unique prefix
764 * @return null or last known dimensions scaled to current geometry (if last
765 * window geom was known)
767 Rectangle getLastKnownDimensions(String windowName)
769 // TODO: lock aspect ratio for scaling desktop Bug #0058199
770 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
771 String x = Cache.getProperty(windowName + "SCREEN_X");
772 String y = Cache.getProperty(windowName + "SCREEN_Y");
773 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
774 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
775 if ((x != null) && (y != null) && (width != null) && (height != null))
777 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
778 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
779 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
781 // attempt #1 - try to cope with change in screen geometry - this
782 // version doesn't preserve original jv aspect ratio.
783 // take ratio of current screen size vs original screen size.
784 double sw = ((1f * screenSize.width) / (1f * Integer
785 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
786 double sh = ((1f * screenSize.height) / (1f * Integer
787 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
788 // rescale the bounds depending upon the current screen geometry.
789 ix = (int) (ix * sw);
790 iw = (int) (iw * sw);
791 iy = (int) (iy * sh);
792 ih = (int) (ih * sh);
793 if (ix >= screenSize.width)
795 jalview.bin.Console.debug(
796 "Window geometry location recall error: shifting horizontal to within screenbounds.");
797 ix = ix % screenSize.width;
799 if (iy >= screenSize.height)
801 jalview.bin.Console.debug(
802 "Window geometry location recall error: shifting vertical to within screenbounds.");
803 iy = iy % screenSize.height;
805 jalview.bin.Console.debug(
806 "Got last known dimensions for " + windowName + ": x:" + ix
807 + " y:" + iy + " width:" + iw + " height:" + ih);
809 // return dimensions for new instance
810 return new Rectangle(ix, iy, iw, ih);
815 void showPasteMenu(int x, int y)
817 JPopupMenu popup = new JPopupMenu();
818 JMenuItem item = new JMenuItem(
819 MessageManager.getString("label.paste_new_window"));
820 item.addActionListener(new ActionListener()
823 public void actionPerformed(ActionEvent evt)
830 popup.show(this, x, y);
835 // quick patch for JAL-4150 - needs some more work and test coverage
836 // TODO - unify below and AlignFrame.paste()
837 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
838 // clipboard has come from a different alignment window than the one where
839 // paste has been called! JAL-4151
841 if (Desktop.jalviewClipboard != null)
843 // The clipboard was filled from within Jalview, we must use the
845 // And dataset from the copied alignment
846 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
847 // be doubly sure that we create *new* sequence objects.
848 SequenceI[] sequences = new SequenceI[newseq.length];
849 for (int i = 0; i < newseq.length; i++)
851 sequences[i] = new Sequence(newseq[i]);
853 Alignment alignment = new Alignment(sequences);
854 // dataset is inherited
855 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
856 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
857 AlignFrame.DEFAULT_HEIGHT);
858 String newtitle = new String("Copied sequences");
860 if (Desktop.jalviewClipboard[2] != null)
862 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
863 af.viewport.setHiddenColumns(hc);
866 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
867 AlignFrame.DEFAULT_HEIGHT);
874 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
875 Transferable contents = c.getContents(this);
877 if (contents != null)
879 String file = (String) contents
880 .getTransferData(DataFlavor.stringFlavor);
882 FileFormatI format = new IdentifyFile().identify(file,
883 DataSourceType.PASTE);
885 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
888 } catch (Exception ex)
890 jalview.bin.Console.outPrintln(
891 "Unable to paste alignment from system clipboard:\n" + ex);
897 * Adds and opens the given frame to the desktop
908 public static synchronized void addInternalFrame(
909 final JInternalFrame frame, String title, int w, int h)
911 addInternalFrame(frame, title, true, w, h, true, false);
915 * Add an internal frame to the Jalview desktop
922 * When true, display frame immediately, otherwise, caller must call
923 * setVisible themselves.
929 public static synchronized void addInternalFrame(
930 final JInternalFrame frame, String title, boolean makeVisible,
933 addInternalFrame(frame, title, makeVisible, w, h, true, false);
937 * Add an internal frame to the Jalview desktop and make it visible
950 public static synchronized void addInternalFrame(
951 final JInternalFrame frame, String title, int w, int h,
954 addInternalFrame(frame, title, true, w, h, resizable, false);
958 * Add an internal frame to the Jalview desktop
965 * When true, display frame immediately, otherwise, caller must call
966 * setVisible themselves.
973 * @param ignoreMinSize
974 * Do not set the default minimum size for frame
976 public static synchronized void addInternalFrame(
977 final JInternalFrame frame, String title, boolean makeVisible,
978 int w, int h, boolean resizable, boolean ignoreMinSize)
981 // TODO: allow callers to determine X and Y position of frame (eg. via
983 // TODO: consider fixing method to update entries in the window submenu with
984 // the current window title
986 frame.setTitle(title);
987 if (frame.getWidth() < 1 || frame.getHeight() < 1)
991 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
992 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
993 // IF JALVIEW IS RUNNING HEADLESS
994 // ///////////////////////////////////////////////
995 if (instance == null || (System.getProperty("java.awt.headless") != null
996 && System.getProperty("java.awt.headless").equals("true")))
1005 frame.setMinimumSize(
1006 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1008 // Set default dimension for Alignment Frame window.
1009 // The Alignment Frame window could be added from a number of places,
1011 // I did this here in order not to miss out on any Alignment frame.
1012 if (frame instanceof AlignFrame)
1014 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1015 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1019 frame.setVisible(makeVisible);
1020 frame.setClosable(true);
1021 frame.setResizable(resizable);
1022 frame.setMaximizable(resizable);
1023 frame.setIconifiable(resizable);
1024 frame.setOpaque(Platform.isJS());
1026 if (frame.getX() < 1 && frame.getY() < 1)
1028 frame.setLocation(xOffset * openFrameCount,
1029 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1033 * add an entry for the new frame in the Window menu (and remove it when the
1036 final JMenuItem menuItem = new JMenuItem(title);
1037 frame.addInternalFrameListener(new InternalFrameAdapter()
1040 public void internalFrameActivated(InternalFrameEvent evt)
1042 JInternalFrame itf = desktop.getSelectedFrame();
1045 if (itf instanceof AlignFrame)
1047 Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf);
1054 public void internalFrameClosed(InternalFrameEvent evt)
1056 PaintRefresher.RemoveComponent(frame);
1059 * defensive check to prevent frames being added half off the window
1061 if (openFrameCount > 0)
1067 * ensure no reference to alignFrame retained by menu item listener
1069 if (menuItem.getActionListeners().length > 0)
1071 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1073 windowMenu.remove(menuItem);
1077 menuItem.addActionListener(new ActionListener()
1080 public void actionPerformed(ActionEvent e)
1084 frame.setSelected(true);
1085 frame.setIcon(false);
1086 } catch (java.beans.PropertyVetoException ex)
1093 setKeyBindings(frame);
1095 // Since the latest FlatLaf patch, we occasionally have problems showing
1096 // structureViewer frames...
1098 boolean shown = false;
1099 Exception last = null;
1106 } catch (IllegalArgumentException iaex)
1110 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1111 + tries + " left) for " + frame.getTitle(), iaex);
1115 } catch (InterruptedException iex)
1120 } while (!shown && tries > 0);
1123 jalview.bin.Console.error(
1124 "Serious Problem whilst showing window " + frame.getTitle(),
1128 windowMenu.add(menuItem);
1133 frame.setSelected(true);
1134 frame.requestFocus();
1135 } catch (java.beans.PropertyVetoException ve)
1137 } catch (java.lang.ClassCastException cex)
1139 jalview.bin.Console.warn(
1140 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1146 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1151 private static void setKeyBindings(JInternalFrame frame)
1153 @SuppressWarnings("serial")
1154 final Action closeAction = new AbstractAction()
1157 public void actionPerformed(ActionEvent e)
1164 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1166 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1167 InputEvent.CTRL_DOWN_MASK);
1168 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1169 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1171 InputMap inputMap = frame
1172 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1173 String ctrlW = ctrlWKey.toString();
1174 inputMap.put(ctrlWKey, ctrlW);
1175 inputMap.put(cmdWKey, ctrlW);
1177 ActionMap actionMap = frame.getActionMap();
1178 actionMap.put(ctrlW, closeAction);
1182 public void lostOwnership(Clipboard clipboard, Transferable contents)
1186 Desktop.jalviewClipboard = null;
1189 internalCopy = false;
1193 public void dragEnter(DropTargetDragEvent evt)
1198 public void dragExit(DropTargetEvent evt)
1203 public void dragOver(DropTargetDragEvent evt)
1208 public void dropActionChanged(DropTargetDragEvent evt)
1219 public void drop(DropTargetDropEvent evt)
1221 boolean success = true;
1222 // JAL-1552 - acceptDrop required before getTransferable call for
1223 // Java's Transferable for native dnd
1224 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1225 Transferable t = evt.getTransferable();
1226 List<Object> files = new ArrayList<>();
1227 List<DataSourceType> protocols = new ArrayList<>();
1231 Desktop.transferFromDropTarget(files, protocols, evt, t);
1232 } catch (Exception e)
1234 e.printStackTrace();
1242 for (int i = 0; i < files.size(); i++)
1244 // BH 2018 File or String
1245 Object file = files.get(i);
1246 String fileName = file.toString();
1247 DataSourceType protocol = (protocols == null)
1248 ? DataSourceType.FILE
1250 FileFormatI format = null;
1252 if (fileName.endsWith(".jar"))
1254 format = FileFormat.Jalview;
1259 format = new IdentifyFile().identify(file, protocol);
1261 if (file instanceof File)
1263 Platform.cacheFileData((File) file);
1265 new FileLoader().LoadFile(null, file, protocol, format);
1268 } catch (Exception ex)
1273 evt.dropComplete(success); // need this to ensure input focus is properly
1274 // transfered to any new windows created
1284 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1286 String fileFormat = FileLoader.getUseDefaultFileFormat()
1287 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1289 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1290 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1291 BackupFiles.getEnabled());
1293 chooser.setFileView(new JalviewFileView());
1294 chooser.setDialogTitle(
1295 MessageManager.getString("label.open_local_file"));
1296 chooser.setToolTipText(MessageManager.getString("action.open"));
1298 chooser.setResponseHandler(0, () -> {
1299 File selectedFile = chooser.getSelectedFile();
1300 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1302 FileFormatI format = chooser.getSelectedFormat();
1305 * Call IdentifyFile to verify the file contains what its extension implies.
1306 * Skip this step for dynamically added file formats, because IdentifyFile does
1307 * not know how to recognise them.
1309 if (FileFormats.getInstance().isIdentifiable(format))
1313 format = new IdentifyFile().identify(selectedFile,
1314 DataSourceType.FILE);
1315 } catch (FileFormatException e)
1317 // format = null; //??
1321 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1324 chooser.showOpenDialog(this);
1328 * Shows a dialog for input of a URL at which to retrieve alignment data
1333 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1335 // This construct allows us to have a wider textfield
1337 JLabel label = new JLabel(
1338 MessageManager.getString("label.input_file_url"));
1340 JPanel panel = new JPanel(new GridLayout(2, 1));
1344 * the URL to fetch is input in Java: an editable combobox with history JS:
1345 * (pending JAL-3038) a plain text field
1348 String urlBase = "https://www.";
1349 if (Platform.isJS())
1351 history = new JTextField(urlBase, 35);
1360 JComboBox<String> asCombo = new JComboBox<>();
1361 asCombo.setPreferredSize(new Dimension(400, 20));
1362 asCombo.setEditable(true);
1363 asCombo.addItem(urlBase);
1364 String historyItems = Cache.getProperty("RECENT_URL");
1365 if (historyItems != null)
1367 for (String token : historyItems.split("\\t"))
1369 asCombo.addItem(token);
1376 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1377 MessageManager.getString("action.cancel") };
1378 Runnable action = () -> {
1379 @SuppressWarnings("unchecked")
1380 String url = (history instanceof JTextField
1381 ? ((JTextField) history).getText()
1382 : ((JComboBox<String>) history).getEditor().getItem()
1383 .toString().trim());
1385 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1387 if (viewport != null)
1389 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1390 FileFormat.Jalview);
1394 new FileLoader().LoadFile(url, DataSourceType.URL,
1395 FileFormat.Jalview);
1400 FileFormatI format = null;
1403 format = new IdentifyFile().identify(url, DataSourceType.URL);
1404 } catch (FileNotFoundException e)
1406 jalview.bin.Console.error("URL '" + url + "' not found", e);
1407 } catch (FileFormatException e)
1409 jalview.bin.Console.error(
1410 "File at URL '" + url + "' format not recognised", e);
1415 String msg = MessageManager.formatMessage("label.couldnt_locate",
1417 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1418 MessageManager.getString("label.url_not_found"),
1419 JvOptionPane.WARNING_MESSAGE);
1423 if (viewport != null)
1425 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1430 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1434 String dialogOption = MessageManager
1435 .getString("label.input_alignment_from_url");
1436 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1437 .showInternalDialog(panel, dialogOption,
1438 JvOptionPane.YES_NO_CANCEL_OPTION,
1439 JvOptionPane.PLAIN_MESSAGE, null, options,
1440 MessageManager.getString("action.ok"));
1444 * Opens the CutAndPaste window for the user to paste an alignment in to
1447 * - if not null, the pasted alignment is added to the current
1448 * alignment; if null, to a new alignment window
1451 public void inputTextboxMenuItem_actionPerformed(
1452 AlignmentViewPanel viewPanel)
1454 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1455 cap.setForInput(viewPanel);
1456 Desktop.addInternalFrame(cap,
1457 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1462 * Check with user and saving files before actually quitting
1464 public void desktopQuit()
1466 desktopQuit(true, false);
1470 * close everything, stash window geometries, and shut down all associated
1474 * - sets the dispose on close flag - JVM may terminate when set
1475 * @param terminateJvm
1476 * - quit with prejudice - stops the JVM.
1478 public void quitTheDesktop(boolean dispose, boolean terminateJvm)
1480 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1481 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1482 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1483 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1484 getWidth(), getHeight()));
1486 if (jconsole != null)
1488 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1489 jconsole.stopConsole();
1494 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1497 // Frames should all close automatically. Keeping external
1498 // viewers open should already be decided by user.
1499 closeAll_actionPerformed(null);
1501 if (dialogExecutor != null)
1503 dialogExecutor.shutdownNow();
1506 if (groovyConsole != null)
1508 // suppress a possible repeat prompt to save script
1509 groovyConsole.setDirty(false);
1512 if (((Window) groovyConsole.getFrame()) != null
1513 && ((Window) groovyConsole.getFrame()).isVisible())
1515 // console is visible -- FIXME JAL-4327
1516 groovyConsole.exit();
1520 // console is not, so just let it dispose itself when we shutdown
1521 // we don't call groovyConsole.exit() because it calls the shutdown
1522 // handler with invokeAndWait() causing deadlock
1523 groovyConsole = null;
1529 // note that shutdown hook will not be run
1530 jalview.bin.Console.debug("Force Quit selected by user");
1531 Runtime.getRuntime().halt(0);
1534 jalview.bin.Console.debug("Quit selected by user");
1537 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1538 // instance.dispose();
1542 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1544 final Runnable doDesktopQuit = () -> {
1546 // FIRST !! check for aborted quit
1547 if (QuitHandler.quitCancelled())
1550 .debug("Quit was cancelled - Desktop aborting quit");
1554 // Proceed with quitting
1555 quitTheDesktop(disposeFlag,
1556 QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT);
1561 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1562 QuitHandler.defaultCancelQuit);
1566 * Exits the program and the JVM.
1568 * Don't call this directly
1570 * - use desktopQuit() above to tidy up first.
1572 * - use closeDesktop() to shutdown Jalview without shutting down the JVM
1578 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1579 // not run a second time if gotQuitResponse flag has been set (i.e. user
1580 // confirmed quit of some kind).
1581 Jalview.exit("Desktop exiting.", ExitCode.OK);
1584 private void storeLastKnownDimensions(String string, Rectangle jc)
1586 jalview.bin.Console.debug("Storing last known dimensions for " + string
1587 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1588 + " height:" + jc.height);
1590 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1591 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1592 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1593 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1603 public void aboutMenuItem_actionPerformed(ActionEvent e)
1605 new Thread(new Runnable()
1610 new SplashScreen(false);
1616 * Returns the html text for the About screen, including any available version
1617 * number, build details, author details and citation reference, but without
1618 * the enclosing {@code html} tags
1622 public String getAboutMessage()
1624 StringBuilder message = new StringBuilder(1024);
1625 message.append("<div style=\"font-family: sans-serif;\">")
1626 .append("<h1><strong>Version: ")
1627 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1628 .append("<strong>Built: <em>")
1629 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1630 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1631 .append("</strong>");
1633 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1634 if (latestVersion.equals("Checking"))
1636 // JBP removed this message for 2.11: May be reinstated in future version
1637 // message.append("<br>...Checking latest version...</br>");
1639 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1641 boolean red = false;
1642 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1643 .indexOf("automated build") == -1)
1646 // Displayed when code version and jnlp version do not match and code
1647 // version is not a development build
1648 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1651 message.append("<br>!! Version ")
1652 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1653 .append(" is available for download from ")
1654 .append(Cache.getDefault("www.jalview.org",
1655 "https://www.jalview.org"))
1659 message.append("</div>");
1662 message.append("<br>Authors: ");
1663 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1664 message.append(CITATION);
1666 message.append("</div>");
1668 return message.toString();
1672 * Action on requesting Help documentation
1675 public void documentationMenuItem_actionPerformed()
1679 if (Platform.isJS())
1681 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1690 Help.showHelpWindow();
1692 } catch (Exception ex)
1695 .errPrintln("Error opening help: " + ex.getMessage());
1700 public void closeAll_actionPerformed(ActionEvent e)
1702 // TODO show a progress bar while closing?
1703 JInternalFrame[] frames = desktop.getAllFrames();
1704 for (int i = 0; i < frames.length; i++)
1708 frames[i].setClosed(true);
1709 } catch (java.beans.PropertyVetoException ex)
1713 Jalview.getInstance().setCurrentAlignFrame(null);
1714 jalview.bin.Console.info("ALL CLOSED");
1717 * reset state of singleton objects as appropriate (clear down session state
1718 * when all windows are closed)
1720 StructureSelectionManager ssm = StructureSelectionManager
1721 .getStructureSelectionManager(this);
1728 public int structureViewersStillRunningCount()
1731 JInternalFrame[] frames = desktop.getAllFrames();
1732 for (int i = 0; i < frames.length; i++)
1734 if (frames[i] != null
1735 && frames[i] instanceof JalviewStructureDisplayI)
1737 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1745 public void raiseRelated_actionPerformed(ActionEvent e)
1747 reorderAssociatedWindows(false, false);
1751 public void minimizeAssociated_actionPerformed(ActionEvent e)
1753 reorderAssociatedWindows(true, false);
1756 void closeAssociatedWindows()
1758 reorderAssociatedWindows(false, true);
1764 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1768 protected void garbageCollect_actionPerformed(ActionEvent e)
1770 // We simply collect the garbage
1771 jalview.bin.Console.debug("Collecting garbage...");
1773 jalview.bin.Console.debug("Finished garbage collection.");
1779 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1783 protected void showMemusage_actionPerformed(ActionEvent e)
1785 desktop.showMemoryUsage(showMemusage.isSelected());
1792 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1796 protected void showConsole_actionPerformed(ActionEvent e)
1798 showConsole(showConsole.isSelected());
1801 Console jconsole = null;
1804 * control whether the java console is visible or not
1808 void showConsole(boolean selected)
1810 // TODO: decide if we should update properties file
1811 if (jconsole != null) // BH 2018
1813 showConsole.setSelected(selected);
1814 Cache.setProperty("SHOW_JAVA_CONSOLE",
1815 Boolean.valueOf(selected).toString());
1816 jconsole.setVisible(selected);
1820 void reorderAssociatedWindows(boolean minimize, boolean close)
1822 JInternalFrame[] frames = desktop.getAllFrames();
1823 if (frames == null || frames.length < 1)
1828 AlignmentViewport source = null, target = null;
1829 if (frames[0] instanceof AlignFrame)
1831 source = ((AlignFrame) frames[0]).getCurrentView();
1833 else if (frames[0] instanceof TreePanel)
1835 source = ((TreePanel) frames[0]).getViewPort();
1837 else if (frames[0] instanceof PCAPanel)
1839 source = ((PCAPanel) frames[0]).av;
1841 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1843 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1848 for (int i = 0; i < frames.length; i++)
1851 if (frames[i] == null)
1855 if (frames[i] instanceof AlignFrame)
1857 target = ((AlignFrame) frames[i]).getCurrentView();
1859 else if (frames[i] instanceof TreePanel)
1861 target = ((TreePanel) frames[i]).getViewPort();
1863 else if (frames[i] instanceof PCAPanel)
1865 target = ((PCAPanel) frames[i]).av;
1867 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1869 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1872 if (source == target)
1878 frames[i].setClosed(true);
1882 frames[i].setIcon(minimize);
1885 frames[i].toFront();
1889 } catch (java.beans.PropertyVetoException ex)
1904 protected void preferences_actionPerformed(ActionEvent e)
1906 Preferences.openPreferences();
1910 * Prompts the user to choose a file and then saves the Jalview state as a
1911 * Jalview project file
1914 public void saveState_actionPerformed()
1916 saveState_actionPerformed(false);
1919 public void saveState_actionPerformed(boolean saveAs)
1921 java.io.File projectFile = getProjectFile();
1922 // autoSave indicates we already have a file and don't need to ask
1923 boolean autoSave = projectFile != null && !saveAs
1924 && BackupFiles.getEnabled();
1926 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1927 // projectFile='"+projectFile+"',
1928 // saveAs="+saveAs+", Backups
1929 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1931 boolean approveSave = false;
1934 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1937 chooser.setFileView(new JalviewFileView());
1938 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1940 int value = chooser.showSaveDialog(this);
1942 if (value == JalviewFileChooser.APPROVE_OPTION)
1944 projectFile = chooser.getSelectedFile();
1945 setProjectFile(projectFile);
1950 if (approveSave || autoSave)
1952 final Desktop me = this;
1953 final java.io.File chosenFile = projectFile;
1954 new Thread(new Runnable()
1959 // TODO: refactor to Jalview desktop session controller action.
1960 setProgressBar(MessageManager.formatMessage(
1961 "label.saving_jalview_project", new Object[]
1962 { chosenFile.getName() }), chosenFile.hashCode());
1963 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1964 // TODO catch and handle errors for savestate
1965 // TODO prevent user from messing with the Desktop whilst we're saving
1968 boolean doBackup = BackupFiles.getEnabled();
1969 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1972 new Jalview2XML().saveState(
1973 doBackup ? backupfiles.getTempFile() : chosenFile);
1977 backupfiles.setWriteSuccess(true);
1978 backupfiles.rollBackupsAndRenameTempFile();
1980 } catch (OutOfMemoryError oom)
1982 new OOMWarning("Whilst saving current state to "
1983 + chosenFile.getName(), oom);
1984 } catch (Exception ex)
1986 jalview.bin.Console.error("Problems whilst trying to save to "
1987 + chosenFile.getName(), ex);
1988 JvOptionPane.showMessageDialog(me,
1989 MessageManager.formatMessage(
1990 "label.error_whilst_saving_current_state_to",
1992 { chosenFile.getName() }),
1993 MessageManager.getString("label.couldnt_save_project"),
1994 JvOptionPane.WARNING_MESSAGE);
1996 setProgressBar(null, chosenFile.hashCode());
2003 public void saveAsState_actionPerformed(ActionEvent e)
2005 saveState_actionPerformed(true);
2008 protected void setProjectFile(File choice)
2010 this.projectFile = choice;
2013 public File getProjectFile()
2015 return this.projectFile;
2019 * Shows a file chooser dialog and tries to read in the selected file as a
2023 public void loadState_actionPerformed()
2025 final String[] suffix = new String[] { "jvp", "jar" };
2026 final String[] desc = new String[] { "Jalview Project",
2027 "Jalview Project (old)" };
2028 JalviewFileChooser chooser = new JalviewFileChooser(
2029 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2030 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2034 chooser.setFileView(new JalviewFileView());
2035 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2036 chooser.setResponseHandler(0, () -> {
2037 File selectedFile = chooser.getSelectedFile();
2038 setProjectFile(selectedFile);
2039 String choice = selectedFile.getAbsolutePath();
2040 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2041 new Thread(new Runnable()
2048 new Jalview2XML().loadJalviewAlign(selectedFile);
2049 } catch (OutOfMemoryError oom)
2051 new OOMWarning("Whilst loading project from " + choice, oom);
2052 } catch (Exception ex)
2054 jalview.bin.Console.error(
2055 "Problems whilst loading project from " + choice, ex);
2056 JvOptionPane.showMessageDialog(Desktop.desktop,
2057 MessageManager.formatMessage(
2058 "label.error_whilst_loading_project_from",
2061 MessageManager.getString("label.couldnt_load_project"),
2062 JvOptionPane.WARNING_MESSAGE);
2065 }, "Project Loader").start();
2068 chooser.showOpenDialog(this);
2072 public void inputSequence_actionPerformed(ActionEvent e)
2074 new SequenceFetcher(this);
2077 JPanel progressPanel;
2079 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2081 public void startLoading(final Object fileName)
2083 if (fileLoadingCount == 0)
2085 fileLoadingPanels.add(addProgressPanel(MessageManager
2086 .formatMessage("label.loading_file", new Object[]
2092 private JPanel addProgressPanel(String string)
2094 if (progressPanel == null)
2096 progressPanel = new JPanel(new GridLayout(1, 1));
2097 totalProgressCount = 0;
2098 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2100 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2101 JProgressBar progressBar = new JProgressBar();
2102 progressBar.setIndeterminate(true);
2104 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2106 thisprogress.add(progressBar, BorderLayout.CENTER);
2107 progressPanel.add(thisprogress);
2108 ((GridLayout) progressPanel.getLayout()).setRows(
2109 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2110 ++totalProgressCount;
2111 instance.validate();
2112 return thisprogress;
2115 int totalProgressCount = 0;
2117 private void removeProgressPanel(JPanel progbar)
2119 if (progressPanel != null)
2121 synchronized (progressPanel)
2123 progressPanel.remove(progbar);
2124 GridLayout gl = (GridLayout) progressPanel.getLayout();
2125 gl.setRows(gl.getRows() - 1);
2126 if (--totalProgressCount < 1)
2128 this.getContentPane().remove(progressPanel);
2129 progressPanel = null;
2136 public void stopLoading()
2139 if (fileLoadingCount < 1)
2141 while (fileLoadingPanels.size() > 0)
2143 removeProgressPanel(fileLoadingPanels.remove(0));
2145 fileLoadingPanels.clear();
2146 fileLoadingCount = 0;
2151 public static int getViewCount(String alignmentId)
2153 AlignmentViewport[] aps = getViewports(alignmentId);
2154 return (aps == null) ? 0 : aps.length;
2159 * @param alignmentId
2160 * - if null, all sets are returned
2161 * @return all AlignmentPanels concerning the alignmentId sequence set
2163 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2165 if (Desktop.desktop == null)
2167 // no frames created and in headless mode
2168 // TODO: verify that frames are recoverable when in headless mode
2171 List<AlignmentPanel> aps = new ArrayList<>();
2172 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2177 for (AlignFrame af : frames)
2179 for (AlignmentPanel ap : af.alignPanels)
2181 if (alignmentId == null
2182 || alignmentId.equals(ap.av.getSequenceSetId()))
2188 if (aps.size() == 0)
2192 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2197 * get all the viewports on an alignment.
2199 * @param sequenceSetId
2200 * unique alignment id (may be null - all viewports returned in that
2202 * @return all viewports on the alignment bound to sequenceSetId
2204 public static AlignmentViewport[] getViewports(String sequenceSetId)
2206 List<AlignmentViewport> viewp = new ArrayList<>();
2207 if (desktop != null)
2209 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2211 for (AlignFrame afr : frames)
2213 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2214 .equals(sequenceSetId))
2216 if (afr.alignPanels != null)
2218 for (AlignmentPanel ap : afr.alignPanels)
2220 if (sequenceSetId == null
2221 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2229 viewp.add(afr.getViewport());
2233 if (viewp.size() > 0)
2235 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2242 * Explode the views in the given frame into separate AlignFrame
2246 public static void explodeViews(AlignFrame af)
2248 int size = af.alignPanels.size();
2254 // FIXME: ideally should use UI interface API
2255 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2256 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2257 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2258 for (int i = 0; i < size; i++)
2260 AlignmentPanel ap = af.alignPanels.get(i);
2262 AlignFrame newaf = new AlignFrame(ap);
2264 // transfer reference for existing feature settings to new alignFrame
2265 if (ap == af.alignPanel)
2267 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2269 newaf.featureSettings = viewFeatureSettings;
2271 newaf.setFeatureSettingsGeometry(fsBounds);
2275 * Restore the view's last exploded frame geometry if known. Multiple views from
2276 * one exploded frame share and restore the same (frame) position and size.
2278 Rectangle geometry = ap.av.getExplodedGeometry();
2279 if (geometry != null)
2281 newaf.setBounds(geometry);
2284 ap.av.setGatherViewsHere(false);
2286 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2287 AlignFrame.DEFAULT_HEIGHT);
2288 // and materialise a new feature settings dialog instance for the new
2290 // (closes the old as if 'OK' was pressed)
2291 if (ap == af.alignPanel && newaf.featureSettings != null
2292 && newaf.featureSettings.isOpen()
2293 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2295 newaf.showFeatureSettingsUI();
2299 af.featureSettings = null;
2300 af.alignPanels.clear();
2301 af.closeMenuItem_actionPerformed(true);
2306 * Gather expanded views (separate AlignFrame's) with the same sequence set
2307 * identifier back in to this frame as additional views, and close the
2308 * expanded views. Note the expanded frames may themselves have multiple
2309 * views. We take the lot.
2313 public void gatherViews(AlignFrame source)
2315 source.viewport.setGatherViewsHere(true);
2316 source.viewport.setExplodedGeometry(source.getBounds());
2317 JInternalFrame[] frames = desktop.getAllFrames();
2318 String viewId = source.viewport.getSequenceSetId();
2319 for (int t = 0; t < frames.length; t++)
2321 if (frames[t] instanceof AlignFrame && frames[t] != source)
2323 AlignFrame af = (AlignFrame) frames[t];
2324 boolean gatherThis = false;
2325 for (int a = 0; a < af.alignPanels.size(); a++)
2327 AlignmentPanel ap = af.alignPanels.get(a);
2328 if (viewId.equals(ap.av.getSequenceSetId()))
2331 ap.av.setGatherViewsHere(false);
2332 ap.av.setExplodedGeometry(af.getBounds());
2333 source.addAlignmentPanel(ap, false);
2339 if (af.featureSettings != null && af.featureSettings.isOpen())
2341 if (source.featureSettings == null)
2343 // preserve the feature settings geometry for this frame
2344 source.featureSettings = af.featureSettings;
2345 source.setFeatureSettingsGeometry(
2346 af.getFeatureSettingsGeometry());
2350 // close it and forget
2351 af.featureSettings.close();
2354 af.alignPanels.clear();
2355 af.closeMenuItem_actionPerformed(true);
2360 // refresh the feature setting UI for the source frame if it exists
2361 if (source.featureSettings != null && source.featureSettings.isOpen())
2363 source.showFeatureSettingsUI();
2368 public JInternalFrame[] getAllFrames()
2370 return desktop.getAllFrames();
2374 * Checks the given url to see if it gives a response indicating that the user
2375 * should be informed of a new questionnaire.
2379 public void checkForQuestionnaire(String url)
2381 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2382 // javax.swing.SwingUtilities.invokeLater(jvq);
2383 new Thread(jvq).start();
2386 public void checkURLLinks()
2388 // Thread off the URL link checker
2389 addDialogThread(new Runnable()
2394 if (Cache.getDefault("CHECKURLLINKS", true))
2396 // check what the actual links are - if it's just the default don't
2397 // bother with the warning
2398 List<String> links = Preferences.sequenceUrlLinks
2401 // only need to check links if there is one with a
2402 // SEQUENCE_ID which is not the default EMBL_EBI link
2403 ListIterator<String> li = links.listIterator();
2404 boolean check = false;
2405 List<JLabel> urls = new ArrayList<>();
2406 while (li.hasNext())
2408 String link = li.next();
2409 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2410 && !UrlConstants.isDefaultString(link))
2413 int barPos = link.indexOf("|");
2414 String urlMsg = barPos == -1 ? link
2415 : link.substring(0, barPos) + ": "
2416 + link.substring(barPos + 1);
2417 urls.add(new JLabel(urlMsg));
2425 // ask user to check in case URL links use old style tokens
2426 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2427 JPanel msgPanel = new JPanel();
2428 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2429 msgPanel.add(Box.createVerticalGlue());
2430 JLabel msg = new JLabel(MessageManager
2431 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2432 JLabel msg2 = new JLabel(MessageManager
2433 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2435 for (JLabel url : urls)
2441 final JCheckBox jcb = new JCheckBox(
2442 MessageManager.getString("label.do_not_display_again"));
2443 jcb.addActionListener(new ActionListener()
2446 public void actionPerformed(ActionEvent e)
2448 // update Cache settings for "don't show this again"
2449 boolean showWarningAgain = !jcb.isSelected();
2450 Cache.setProperty("CHECKURLLINKS",
2451 Boolean.valueOf(showWarningAgain).toString());
2456 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2458 .getString("label.SEQUENCE_ID_no_longer_used"),
2459 JvOptionPane.WARNING_MESSAGE);
2466 * Proxy class for JDesktopPane which optionally displays the current memory
2467 * usage and highlights the desktop area with a red bar if free memory runs
2472 public class MyDesktopPane extends JDesktopPane implements Runnable
2474 private static final float ONE_MB = 1048576f;
2476 boolean showMemoryUsage = false;
2480 java.text.NumberFormat df;
2482 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2485 public MyDesktopPane(boolean showMemoryUsage)
2487 showMemoryUsage(showMemoryUsage);
2490 public void showMemoryUsage(boolean showMemory)
2492 this.showMemoryUsage = showMemory;
2495 Thread worker = new Thread(this);
2501 public boolean isShowMemoryUsage()
2503 return showMemoryUsage;
2509 df = java.text.NumberFormat.getNumberInstance();
2510 df.setMaximumFractionDigits(2);
2511 runtime = Runtime.getRuntime();
2513 while (showMemoryUsage)
2517 maxMemory = runtime.maxMemory() / ONE_MB;
2518 allocatedMemory = runtime.totalMemory() / ONE_MB;
2519 freeMemory = runtime.freeMemory() / ONE_MB;
2520 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2522 percentUsage = (totalFreeMemory / maxMemory) * 100;
2524 // if (percentUsage < 20)
2526 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2528 // instance.set.setBorder(border1);
2531 // sleep after showing usage
2533 } catch (Exception ex)
2535 ex.printStackTrace();
2541 public void paintComponent(Graphics g)
2543 if (showMemoryUsage && g != null && df != null)
2545 if (percentUsage < 20)
2547 g.setColor(Color.red);
2549 FontMetrics fm = g.getFontMetrics();
2552 g.drawString(MessageManager.formatMessage("label.memory_stats",
2554 { df.format(totalFreeMemory), df.format(maxMemory),
2555 df.format(percentUsage) }),
2556 10, getHeight() - fm.getHeight());
2560 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2561 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2566 * Accessor method to quickly get all the AlignmentFrames loaded.
2568 * @return an array of AlignFrame, or null if none found
2571 public AlignFrame[] getAlignFrames()
2573 if (desktop == null)
2578 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2584 List<AlignFrame> avp = new ArrayList<>();
2586 for (int i = frames.length - 1; i > -1; i--)
2588 if (frames[i] instanceof AlignFrame)
2590 avp.add((AlignFrame) frames[i]);
2592 else if (frames[i] instanceof SplitFrame)
2595 * Also check for a split frame containing an AlignFrame
2597 GSplitFrame sf = (GSplitFrame) frames[i];
2598 if (sf.getTopFrame() instanceof AlignFrame)
2600 avp.add((AlignFrame) sf.getTopFrame());
2602 if (sf.getBottomFrame() instanceof AlignFrame)
2604 avp.add((AlignFrame) sf.getBottomFrame());
2608 if (avp.size() == 0)
2612 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2619 public static AlignFrame[] getDesktopAlignFrames()
2621 if (Jalview.isHeadlessMode())
2623 // Desktop.desktop is null in headless mode
2624 return Jalview.getInstance().getAlignFrames();
2627 if (instance != null && desktop != null)
2629 return instance.getAlignFrames();
2636 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2640 public GStructureViewer[] getJmols()
2642 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2648 List<GStructureViewer> avp = new ArrayList<>();
2650 for (int i = frames.length - 1; i > -1; i--)
2652 if (frames[i] instanceof AppJmol)
2654 GStructureViewer af = (GStructureViewer) frames[i];
2658 if (avp.size() == 0)
2662 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2667 * Add Groovy Support to Jalview
2670 public void groovyShell_actionPerformed()
2674 openGroovyConsole();
2675 } catch (Exception ex)
2677 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2678 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2680 MessageManager.getString("label.couldnt_create_groovy_shell"),
2681 MessageManager.getString("label.groovy_support_failed"),
2682 JvOptionPane.ERROR_MESSAGE);
2687 * Open the Groovy console
2689 void openGroovyConsole()
2691 if (groovyConsole == null)
2693 JalviewObjectI j = new JalviewObject(this);
2694 groovyConsole = new groovy.console.ui.Console();
2695 groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j);
2696 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2697 getCurrentAlignFrame());
2698 groovyConsole.run();
2701 * We allow only one console at a time, so that AlignFrame menu option
2702 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2703 * enable 'Run script', when the console is opened, and the reverse when it is
2706 Window window = (Window) groovyConsole.getFrame();
2707 window.addWindowListener(new WindowAdapter()
2710 public void windowClosed(WindowEvent e)
2713 * rebind CMD-Q from Groovy Console to Jalview Quit
2716 enableExecuteGroovy(false);
2722 * show Groovy console window (after close and reopen)
2724 ((Window) groovyConsole.getFrame()).setVisible(true);
2727 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2728 * opening a second console
2730 enableExecuteGroovy(true);
2734 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2735 * binding when opened
2737 protected void addQuitHandler()
2740 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2742 .getKeyStroke(KeyEvent.VK_Q,
2743 jalview.util.ShortcutKeyMaskExWrapper
2744 .getMenuShortcutKeyMaskEx()),
2746 getRootPane().getActionMap().put("Quit", new AbstractAction()
2749 public void actionPerformed(ActionEvent e)
2757 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2760 * true if Groovy console is open
2762 public void enableExecuteGroovy(boolean enabled)
2765 * disable opening a second Groovy console (or re-enable when the console is
2768 groovyShell.setEnabled(!enabled);
2770 AlignFrame[] alignFrames = getDesktopAlignFrames();
2771 if (alignFrames != null)
2773 for (AlignFrame af : alignFrames)
2775 af.setGroovyEnabled(enabled);
2781 * Progress bars managed by the IProgressIndicator method.
2783 private Hashtable<Long, JPanel> progressBars;
2785 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2790 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2793 public void setProgressBar(String message, long id)
2795 if (progressBars == null)
2797 progressBars = new Hashtable<>();
2798 progressBarHandlers = new Hashtable<>();
2801 if (progressBars.get(Long.valueOf(id)) != null)
2803 JPanel panel = progressBars.remove(Long.valueOf(id));
2804 if (progressBarHandlers.contains(Long.valueOf(id)))
2806 progressBarHandlers.remove(Long.valueOf(id));
2808 removeProgressPanel(panel);
2812 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2819 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2820 * jalview.gui.IProgressIndicatorHandler)
2823 public void registerHandler(final long id,
2824 final IProgressIndicatorHandler handler)
2826 if (progressBarHandlers == null
2827 || !progressBars.containsKey(Long.valueOf(id)))
2829 throw new Error(MessageManager.getString(
2830 "error.call_setprogressbar_before_registering_handler"));
2832 progressBarHandlers.put(Long.valueOf(id), handler);
2833 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2834 if (handler.canCancel())
2836 JButton cancel = new JButton(
2837 MessageManager.getString("action.cancel"));
2838 final IProgressIndicator us = this;
2839 cancel.addActionListener(new ActionListener()
2843 public void actionPerformed(ActionEvent e)
2845 handler.cancelActivity(id);
2846 us.setProgressBar(MessageManager
2847 .formatMessage("label.cancelled_params", new Object[]
2848 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2852 progressPanel.add(cancel, BorderLayout.EAST);
2858 * @return true if any progress bars are still active
2861 public boolean operationInProgress()
2863 if (progressBars != null && progressBars.size() > 0)
2871 * This will return the first AlignFrame holding the given viewport instance.
2872 * It will break if there are more than one AlignFrames viewing a particular
2876 * @return alignFrame for viewport
2878 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2880 if (desktop != null)
2882 AlignmentPanel[] aps = getAlignmentPanels(
2883 viewport.getSequenceSetId());
2884 for (int panel = 0; aps != null && panel < aps.length; panel++)
2886 if (aps[panel] != null && aps[panel].av == viewport)
2888 return aps[panel].alignFrame;
2895 public VamsasApplication getVamsasApplication()
2897 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2903 * flag set if jalview GUI is being operated programmatically
2905 private boolean inBatchMode = false;
2908 * check if jalview GUI is being operated programmatically
2910 * @return inBatchMode
2912 public boolean isInBatchMode()
2918 * set flag if jalview GUI is being operated programmatically
2920 * @param inBatchMode
2922 public void setInBatchMode(boolean inBatchMode)
2924 this.inBatchMode = inBatchMode;
2928 * start service discovery and wait till it is done
2930 public void startServiceDiscovery()
2932 startServiceDiscovery(false);
2936 * start service discovery threads - blocking or non-blocking
2940 public void startServiceDiscovery(boolean blocking)
2942 startServiceDiscovery(blocking, false);
2946 * start service discovery threads
2949 * - false means call returns immediately
2950 * @param ignore_SHOW_JWS2_SERVICES_preference
2951 * - when true JABA services are discovered regardless of user's JWS2
2952 * discovery preference setting
2954 public void startServiceDiscovery(boolean blocking,
2955 boolean ignore_SHOW_JWS2_SERVICES_preference)
2957 boolean alive = true;
2958 Thread t0 = null, t1 = null, t2 = null;
2959 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2962 // todo: changesupport handlers need to be transferred
2963 if (discoverer == null)
2965 discoverer = new jalview.ws.jws1.Discoverer();
2966 // register PCS handler for desktop.
2967 discoverer.addPropertyChangeListener(changeSupport);
2969 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2970 // until we phase out completely
2971 (t0 = new Thread(discoverer)).start();
2974 if (ignore_SHOW_JWS2_SERVICES_preference
2975 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2977 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2978 .startDiscoverer(changeSupport);
2982 // TODO: do rest service discovery
2991 } catch (Exception e)
2994 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2995 || (t3 != null && t3.isAlive())
2996 || (t0 != null && t0.isAlive());
3002 * called to check if the service discovery process completed successfully.
3006 protected void JalviewServicesChanged(PropertyChangeEvent evt)
3008 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
3010 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3011 .getErrorMessages();
3014 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3016 if (serviceChangedDialog == null)
3018 // only run if we aren't already displaying one of these.
3019 addDialogThread(serviceChangedDialog = new Runnable()
3026 * JalviewDialog jd =new JalviewDialog() {
3028 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3030 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3032 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3034 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3036 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3037 * + " or mis-configured HTTP proxy settings.<br/>" +
3038 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3039 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3040 * true, true, "Web Service Configuration Problem", 450, 400);
3042 * jd.waitForInput();
3044 JvOptionPane.showConfirmDialog(Desktop.desktop,
3045 new JLabel("<html><table width=\"450\"><tr><td>"
3046 + ermsg + "</td></tr></table>"
3047 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3048 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3049 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3050 + " Tools->Preferences dialog box to change them.</p></html>"),
3051 "Web Service Configuration Problem",
3052 JvOptionPane.DEFAULT_OPTION,
3053 JvOptionPane.ERROR_MESSAGE);
3054 serviceChangedDialog = null;
3062 jalview.bin.Console.error(
3063 "Errors reported by JABA discovery service. Check web services preferences.\n"
3070 private Runnable serviceChangedDialog = null;
3073 * start a thread to open a URL in the configured browser. Pops up a warning
3074 * dialog to the user if there is an exception when calling out to the browser
3079 public static void showUrl(final String url)
3081 if (url != null && !url.trim().equals(""))
3083 jalview.bin.Console.info("Opening URL: " + url);
3084 showUrl(url, Desktop.instance);
3088 jalview.bin.Console.warn("Ignoring attempt to show an empty URL.");
3094 * Like showUrl but allows progress handler to be specified
3098 * (null) or object implementing IProgressIndicator
3100 public static void showUrl(final String url,
3101 final IProgressIndicator progress)
3103 new Thread(new Runnable()
3110 if (progress != null)
3112 progress.setProgressBar(MessageManager
3113 .formatMessage("status.opening_params", new Object[]
3114 { url }), this.hashCode());
3116 jalview.util.BrowserLauncher.openURL(url);
3117 } catch (Exception ex)
3119 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3121 .getString("label.web_browser_not_found_unix"),
3122 MessageManager.getString("label.web_browser_not_found"),
3123 JvOptionPane.WARNING_MESSAGE);
3125 ex.printStackTrace();
3127 if (progress != null)
3129 progress.setProgressBar(null, this.hashCode());
3135 public static WsParamSetManager wsparamManager = null;
3137 public static ParamManager getUserParameterStore()
3139 if (wsparamManager == null)
3141 wsparamManager = new WsParamSetManager();
3143 return wsparamManager;
3147 * static hyperlink handler proxy method for use by Jalview's internal windows
3151 public static void hyperlinkUpdate(HyperlinkEvent e)
3153 if (e.getEventType() == EventType.ACTIVATED)
3158 url = e.getURL().toString();
3159 Desktop.showUrl(url);
3160 } catch (Exception x)
3165 .error("Couldn't handle string " + url + " as a URL.");
3167 // ignore any exceptions due to dud links.
3174 * single thread that handles display of dialogs to user.
3176 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3179 * flag indicating if dialogExecutor should try to acquire a permit
3181 private volatile boolean dialogPause = true;
3186 private Semaphore block = new Semaphore(0);
3188 private static groovy.console.ui.Console groovyConsole;
3191 * add another dialog thread to the queue
3195 public void addDialogThread(final Runnable prompter)
3197 dialogExecutor.submit(new Runnable()
3204 acquireDialogQueue();
3206 if (instance == null)
3212 SwingUtilities.invokeAndWait(prompter);
3213 } catch (Exception q)
3215 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3222 private boolean dialogQueueStarted = false;
3224 public void startDialogQueue()
3226 if (dialogQueueStarted)
3230 // set the flag so we don't pause waiting for another permit and semaphore
3231 // the current task to begin
3232 releaseDialogQueue();
3233 dialogQueueStarted = true;
3236 public void acquireDialogQueue()
3242 } catch (InterruptedException e)
3244 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3249 public void releaseDialogQueue()
3256 dialogPause = false;
3260 * Outputs an image of the desktop to file in EPS format, after prompting the
3261 * user for choice of Text or Lineart character rendering (unless a preference
3262 * has been set). The file name is generated as
3265 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3269 protected void snapShotWindow_actionPerformed(ActionEvent e)
3271 // currently the menu option to do this is not shown
3274 int width = getWidth();
3275 int height = getHeight();
3277 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3278 ImageWriterI writer = new ImageWriterI()
3281 public void exportImage(Graphics g) throws Exception
3284 jalview.bin.Console.info("Successfully written snapshot to file "
3285 + of.getAbsolutePath());
3288 String title = "View of desktop";
3289 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3293 exporter.doExport(of, this, width, height, title);
3294 } catch (ImageOutputException ioex)
3296 jalview.bin.Console.error(
3297 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3303 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3304 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3305 * and location last time the view was expanded (if any). However it does not
3306 * remember the split pane divider location - this is set to match the
3307 * 'exploding' frame.
3311 public void explodeViews(SplitFrame sf)
3313 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3314 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3315 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3317 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3319 int viewCount = topPanels.size();
3326 * Processing in reverse order works, forwards order leaves the first panels not
3327 * visible. I don't know why!
3329 for (int i = viewCount - 1; i >= 0; i--)
3332 * Make new top and bottom frames. These take over the respective AlignmentPanel
3333 * objects, including their AlignmentViewports, so the cdna/protein
3334 * relationships between the viewports is carried over to the new split frames.
3336 * explodedGeometry holds the (x, y) position of the previously exploded
3337 * SplitFrame, and the (width, height) of the AlignFrame component
3339 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3340 AlignFrame newTopFrame = new AlignFrame(topPanel);
3341 newTopFrame.setSize(oldTopFrame.getSize());
3342 newTopFrame.setVisible(true);
3343 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3344 .getExplodedGeometry();
3345 if (geometry != null)
3347 newTopFrame.setSize(geometry.getSize());
3350 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3351 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3352 newBottomFrame.setSize(oldBottomFrame.getSize());
3353 newBottomFrame.setVisible(true);
3354 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3355 .getExplodedGeometry();
3356 if (geometry != null)
3358 newBottomFrame.setSize(geometry.getSize());
3361 topPanel.av.setGatherViewsHere(false);
3362 bottomPanel.av.setGatherViewsHere(false);
3363 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3365 if (geometry != null)
3367 splitFrame.setLocation(geometry.getLocation());
3369 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3373 * Clear references to the panels (now relocated in the new SplitFrames) before
3374 * closing the old SplitFrame.
3377 bottomPanels.clear();
3382 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3383 * back into the given SplitFrame as additional views. Note that the gathered
3384 * frames may themselves have multiple views.
3388 public void gatherViews(GSplitFrame source)
3391 * special handling of explodedGeometry for a view within a SplitFrame: - it
3392 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3393 * height) of the AlignFrame component
3395 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3396 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3397 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3398 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3399 myBottomFrame.viewport
3400 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3401 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3402 myTopFrame.viewport.setGatherViewsHere(true);
3403 myBottomFrame.viewport.setGatherViewsHere(true);
3404 String topViewId = myTopFrame.viewport.getSequenceSetId();
3405 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3407 JInternalFrame[] frames = desktop.getAllFrames();
3408 for (JInternalFrame frame : frames)
3410 if (frame instanceof SplitFrame && frame != source)
3412 SplitFrame sf = (SplitFrame) frame;
3413 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3414 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3415 boolean gatherThis = false;
3416 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3418 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3419 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3420 if (topViewId.equals(topPanel.av.getSequenceSetId())
3421 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3424 topPanel.av.setGatherViewsHere(false);
3425 bottomPanel.av.setGatherViewsHere(false);
3426 topPanel.av.setExplodedGeometry(
3427 new Rectangle(sf.getLocation(), topFrame.getSize()));
3428 bottomPanel.av.setExplodedGeometry(
3429 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3430 myTopFrame.addAlignmentPanel(topPanel, false);
3431 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3437 topFrame.getAlignPanels().clear();
3438 bottomFrame.getAlignPanels().clear();
3445 * The dust settles...give focus to the tab we did this from.
3447 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3450 public static groovy.console.ui.Console getGroovyConsole()
3452 return groovyConsole;
3456 * handles the payload of a drag and drop event.
3458 * TODO refactor to desktop utilities class
3461 * - Data source strings extracted from the drop event
3463 * - protocol for each data source extracted from the drop event
3467 * - the payload from the drop event
3470 public static void transferFromDropTarget(List<Object> files,
3471 List<DataSourceType> protocols, DropTargetDropEvent evt,
3472 Transferable t) throws Exception
3475 DataFlavor uriListFlavor = new DataFlavor(
3476 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3479 urlFlavour = new DataFlavor(
3480 "application/x-java-url; class=java.net.URL");
3481 } catch (ClassNotFoundException cfe)
3483 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3487 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3492 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3493 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3494 // means url may be null.
3497 protocols.add(DataSourceType.URL);
3498 files.add(url.toString());
3499 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3500 + files.get(files.size() - 1));
3505 if (Platform.isAMacAndNotJS())
3507 jalview.bin.Console.errPrintln(
3508 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3511 } catch (Throwable ex)
3513 jalview.bin.Console.debug("URL drop handler failed.", ex);
3516 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3518 // Works on Windows and MacOSX
3519 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3520 for (Object file : (List) t
3521 .getTransferData(DataFlavor.javaFileListFlavor))
3524 protocols.add(DataSourceType.FILE);
3529 // Unix like behaviour
3530 boolean added = false;
3532 if (t.isDataFlavorSupported(uriListFlavor))
3534 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3535 // This is used by Unix drag system
3536 data = (String) t.getTransferData(uriListFlavor);
3540 // fallback to text: workaround - on OSX where there's a JVM bug
3542 .debug("standard URIListFlavor failed. Trying text");
3543 // try text fallback
3544 DataFlavor textDf = new DataFlavor(
3545 "text/plain;class=java.lang.String");
3546 if (t.isDataFlavorSupported(textDf))
3548 data = (String) t.getTransferData(textDf);
3551 jalview.bin.Console.debug("Plain text drop content returned "
3552 + (data == null ? "Null - failed" : data));
3557 while (protocols.size() < files.size())
3559 jalview.bin.Console.debug("Adding missing FILE protocol for "
3560 + files.get(protocols.size()));
3561 protocols.add(DataSourceType.FILE);
3563 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3564 data, "\r\n"); st.hasMoreTokens();)
3567 String s = st.nextToken();
3568 if (s.startsWith("#"))
3570 // the line is a comment (as per the RFC 2483)
3573 java.net.URI uri = new java.net.URI(s);
3574 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3576 protocols.add(DataSourceType.URL);
3577 files.add(uri.toString());
3581 // otherwise preserve old behaviour: catch all for file objects
3582 java.io.File file = new java.io.File(uri);
3583 protocols.add(DataSourceType.FILE);
3584 files.add(file.toString());
3589 if (jalview.bin.Console.isDebugEnabled())
3591 if (data == null || !added)
3594 if (t.getTransferDataFlavors() != null
3595 && t.getTransferDataFlavors().length > 0)
3597 jalview.bin.Console.debug(
3598 "Couldn't resolve drop data. Here are the supported flavors:");
3599 for (DataFlavor fl : t.getTransferDataFlavors())
3601 jalview.bin.Console.debug(
3602 "Supported transfer dataflavor: " + fl.toString());
3603 Object df = t.getTransferData(fl);
3606 jalview.bin.Console.debug("Retrieves: " + df);
3610 jalview.bin.Console.debug("Retrieved nothing");
3617 .debug("Couldn't resolve dataflavor for drop: "
3623 if (Platform.isWindowsAndNotJS())
3626 .debug("Scanning dropped content for Windows Link Files");
3628 // resolve any .lnk files in the file drop
3629 for (int f = 0; f < files.size(); f++)
3631 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3632 if (protocols.get(f).equals(DataSourceType.FILE)
3633 && (source.endsWith(".lnk") || source.endsWith(".url")
3634 || source.endsWith(".site")))
3638 Object obj = files.get(f);
3639 File lf = (obj instanceof File ? (File) obj
3640 : new File((String) obj));
3641 // process link file to get a URL
3642 jalview.bin.Console.debug("Found potential link file: " + lf);
3643 WindowsShortcut wscfile = new WindowsShortcut(lf);
3644 String fullname = wscfile.getRealFilename();
3645 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3646 files.set(f, fullname);
3647 jalview.bin.Console.debug("Parsed real filename " + fullname
3648 + " to extract protocol: " + protocols.get(f));
3649 } catch (Exception ex)
3651 jalview.bin.Console.error(
3652 "Couldn't parse " + files.get(f) + " as a link file.",
3661 * Sets the Preferences property for experimental features to True or False
3662 * depending on the state of the controlling menu item
3665 protected void showExperimental_actionPerformed(boolean selected)
3667 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3671 * Answers a (possibly empty) list of any structure viewer frames (currently
3672 * for either Jmol or Chimera) which are currently open. This may optionally
3673 * be restricted to viewers of a specified class, or viewers linked to a
3674 * specified alignment panel.
3677 * if not null, only return viewers linked to this panel
3678 * @param structureViewerClass
3679 * if not null, only return viewers of this class
3682 public List<StructureViewerBase> getStructureViewers(
3683 AlignmentPanel apanel,
3684 Class<? extends StructureViewerBase> structureViewerClass)
3686 List<StructureViewerBase> result = new ArrayList<>();
3687 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3689 for (JInternalFrame frame : frames)
3691 if (frame instanceof StructureViewerBase)
3693 if (structureViewerClass == null
3694 || structureViewerClass.isInstance(frame))
3697 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3699 result.add((StructureViewerBase) frame);
3707 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3709 private static boolean debugScaleMessageDone = false;
3711 public static void debugScaleMessage(Graphics g)
3713 if (debugScaleMessageDone)
3717 // output used by tests to check HiDPI scaling settings in action
3720 Graphics2D gg = (Graphics2D) g;
3723 AffineTransform t = gg.getTransform();
3724 double scaleX = t.getScaleX();
3725 double scaleY = t.getScaleY();
3726 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3727 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3728 debugScaleMessageDone = true;
3732 jalview.bin.Console.debug("Desktop graphics null");
3734 } catch (Exception e)
3736 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3741 * closes the current instance window, but leaves the JVM running. Bypasses
3742 * any shutdown prompts, but does not set window dispose on close in case JVM
3745 public static void closeDesktop()
3747 if (Desktop.instance != null)
3749 Desktop us = Desktop.instance;
3750 Desktop.instance.quitTheDesktop(false, false);
3751 // call dispose in a separate thread - try to avoid indirect deadlocks
3754 new Thread(new Runnable()
3759 ExecutorService dex = us.dialogExecutor;
3763 us.dialogExecutor = null;
3764 us.block.drainPermits();
3774 * checks if any progress bars are being displayed in any of the windows
3775 * managed by the desktop
3779 public boolean operationsAreInProgress()
3781 JInternalFrame[] frames = getAllFrames();
3782 for (JInternalFrame frame : frames)
3784 if (frame instanceof IProgressIndicator)
3786 if (((IProgressIndicator) frame).operationInProgress())
3792 return operationInProgress();
3796 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3797 * The way the modal JInternalFrame is made means it cannot be a child of an
3798 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3800 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3802 protected static void addModal(AlignFrame af, JInternalFrame jif)
3804 alignFrameModalMap.put(af, jif);
3807 protected static void closeModal(AlignFrame af)
3809 if (!alignFrameModalMap.containsKey(af))
3813 JInternalFrame jif = alignFrameModalMap.get(af);
3818 jif.setClosed(true);
3819 } catch (PropertyVetoException e)
3821 e.printStackTrace();
3824 alignFrameModalMap.remove(af);
3827 public void nonBlockingDialog(String title, String message, String button,
3828 int type, boolean scrollable, boolean modal)
3830 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3834 public void nonBlockingDialog(String title, String message,
3835 String boxtext, String button, int type, boolean scrollable,
3836 boolean html, boolean modal, int timeout)
3838 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3839 scrollable, html, modal, timeout);
3842 public void nonBlockingDialog(int width, int height, String title,
3843 String message, String boxtext, String button, int type,
3844 boolean scrollable, boolean html, boolean modal, int timeout)
3848 type = JvOptionPane.WARNING_MESSAGE;
3850 JLabel jl = new JLabel(message);
3852 JTextComponent jtc = null;
3855 JTextPane jtp = new JTextPane();
3856 jtp.setContentType("text/html");
3857 jtp.setEditable(false);
3858 jtp.setAutoscrolls(true);
3859 jtp.setText(boxtext);
3865 JTextArea jta = new JTextArea(height, width);
3866 // jta.setLineWrap(true);
3867 jta.setEditable(false);
3868 jta.setWrapStyleWord(true);
3869 jta.setAutoscrolls(true);
3870 jta.setText(boxtext);
3875 JScrollPane jsp = scrollable
3876 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3877 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3880 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3882 JPanel jp = new JPanel();
3883 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3885 if (message != null)
3887 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3890 if (boxtext != null)
3894 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3899 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3904 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3906 jvp.setTimeout(timeout);
3907 JButton jb = new JButton(button);
3908 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3910 { button }, button, modal, new JButton[] { jb }, false);
3914 public AlignFrame getCurrentAlignFrame()
3916 return Jalview.getInstance().getCurrentAlignFrame();