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.JTextPane;
99 import javax.swing.KeyStroke;
100 import javax.swing.SwingUtilities;
101 import javax.swing.WindowConstants;
102 import javax.swing.event.HyperlinkEvent;
103 import javax.swing.event.HyperlinkEvent.EventType;
104 import javax.swing.event.InternalFrameAdapter;
105 import javax.swing.event.InternalFrameEvent;
106 import javax.swing.text.JTextComponent;
108 import org.stackoverflowusers.file.WindowsShortcut;
110 import jalview.api.AlignViewportI;
111 import jalview.api.AlignmentViewPanel;
112 import jalview.api.structures.JalviewStructureDisplayI;
113 import jalview.bin.Cache;
114 import jalview.bin.Jalview;
115 import jalview.bin.Jalview.ExitCode;
116 import jalview.bin.argparser.Arg;
117 import jalview.bin.groovy.JalviewObject;
118 import jalview.bin.groovy.JalviewObjectI;
119 import jalview.datamodel.Alignment;
120 import jalview.datamodel.HiddenColumns;
121 import jalview.datamodel.Sequence;
122 import jalview.datamodel.SequenceI;
123 import jalview.gui.ImageExporter.ImageWriterI;
124 import jalview.gui.QuitHandler.QResponse;
125 import jalview.io.BackupFiles;
126 import jalview.io.DataSourceType;
127 import jalview.io.FileFormat;
128 import jalview.io.FileFormatException;
129 import jalview.io.FileFormatI;
130 import jalview.io.FileFormats;
131 import jalview.io.FileLoader;
132 import jalview.io.FormatAdapter;
133 import jalview.io.IdentifyFile;
134 import jalview.io.JalviewFileChooser;
135 import jalview.io.JalviewFileView;
136 import jalview.io.exceptions.ImageOutputException;
137 import jalview.jbgui.GSplitFrame;
138 import jalview.jbgui.GStructureViewer;
139 import jalview.project.Jalview2XML;
140 import jalview.structure.StructureSelectionManager;
141 import jalview.urls.IdOrgSettings;
142 import jalview.util.BrowserLauncher;
143 import jalview.util.ChannelProperties;
144 import jalview.util.ImageMaker.TYPE;
145 import jalview.util.LaunchUtils;
146 import jalview.util.MessageManager;
147 import jalview.util.Platform;
148 import jalview.util.ShortcutKeyMaskExWrapper;
149 import jalview.util.UrlConstants;
150 import jalview.viewmodel.AlignmentViewport;
151 import jalview.ws.params.ParamManager;
152 import jalview.ws.utils.UrlDownloadClient;
159 * @version $Revision: 1.155 $
161 public class Desktop extends jalview.jbgui.GDesktop
162 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
163 jalview.api.StructureSelectionManagerProvider, JalviewObjectI
165 private static final String CITATION;
168 URL bg_logo_url = ChannelProperties.getImageURL(
169 "bg_logo." + String.valueOf(SplashScreen.logoSize));
170 URL uod_logo_url = ChannelProperties.getImageURL(
171 "uod_banner." + String.valueOf(SplashScreen.logoSize));
172 boolean logo = (bg_logo_url != null || uod_logo_url != null);
173 StringBuilder sb = new StringBuilder();
175 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
180 sb.append(bg_logo_url == null ? ""
181 : "<img alt=\"Barton Group logo\" src=\""
182 + bg_logo_url.toString() + "\">");
183 sb.append(uod_logo_url == null ? ""
184 : " <img alt=\"University of Dundee shield\" src=\""
185 + uod_logo_url.toString() + "\">");
187 "<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>");
188 sb.append("<br><br>If you use Jalview, please cite:"
189 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
190 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
191 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
192 CITATION = sb.toString();
195 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
197 private static int DEFAULT_MIN_WIDTH = 300;
199 private static int DEFAULT_MIN_HEIGHT = 250;
201 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
203 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
205 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
207 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
209 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
211 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
213 public static void setLiveDragMode(boolean b)
215 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
216 : JDesktopPane.OUTLINE_DRAG_MODE;
218 desktop.setDragMode(DRAG_MODE);
221 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
223 public static boolean nosplash = false;
226 * news reader - null if it was never started.
228 private BlogReader jvnews = null;
230 private File projectFile;
234 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void addJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
255 * @param propertyName
257 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
258 * java.beans.PropertyChangeListener)
260 public void removeJalviewPropertyChangeListener(String propertyName,
261 PropertyChangeListener listener)
263 changeSupport.removeJalviewPropertyChangeListener(propertyName,
267 /** Singleton Desktop instance */
268 public static Desktop instance;
270 public static MyDesktopPane desktop;
272 public static MyDesktopPane getDesktop()
274 // BH 2018 could use currentThread() here as a reference to a
275 // Hashtable<Thread, MyDesktopPane> in JavaScript
279 static int openFrameCount = 0;
281 static final int xOffset = 30;
283 static final int yOffset = 30;
285 public static jalview.ws.jws1.Discoverer discoverer;
287 public static Object[] jalviewClipboard;
289 public static boolean internalCopy = false;
291 static int fileLoadingCount = 0;
293 class MyDesktopManager implements DesktopManager
296 private DesktopManager delegate;
298 public MyDesktopManager(DesktopManager delegate)
300 this.delegate = delegate;
304 public void activateFrame(JInternalFrame f)
308 delegate.activateFrame(f);
309 } catch (NullPointerException npe)
311 Point p = getMousePosition();
312 instance.showPasteMenu(p.x, p.y);
317 public void beginDraggingFrame(JComponent f)
319 delegate.beginDraggingFrame(f);
323 public void beginResizingFrame(JComponent f, int direction)
325 delegate.beginResizingFrame(f, direction);
329 public void closeFrame(JInternalFrame f)
331 delegate.closeFrame(f);
335 public void deactivateFrame(JInternalFrame f)
337 delegate.deactivateFrame(f);
341 public void deiconifyFrame(JInternalFrame f)
343 delegate.deiconifyFrame(f);
347 public void dragFrame(JComponent f, int newX, int newY)
353 delegate.dragFrame(f, newX, newY);
357 public void endDraggingFrame(JComponent f)
359 delegate.endDraggingFrame(f);
364 public void endResizingFrame(JComponent f)
366 delegate.endResizingFrame(f);
371 public void iconifyFrame(JInternalFrame f)
373 delegate.iconifyFrame(f);
377 public void maximizeFrame(JInternalFrame f)
379 delegate.maximizeFrame(f);
383 public void minimizeFrame(JInternalFrame f)
385 delegate.minimizeFrame(f);
389 public void openFrame(JInternalFrame f)
391 delegate.openFrame(f);
395 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
402 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
406 public void setBoundsForFrame(JComponent f, int newX, int newY,
407 int newWidth, int newHeight)
409 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
412 // All other methods, simply delegate
417 * Creates a new Desktop object.
423 * A note to implementors. It is ESSENTIAL that any activities that might
424 * block are spawned off as threads rather than waited for during this
429 doConfigureStructurePrefs();
430 setTitle(ChannelProperties.getProperty("app_name") + " "
431 + Cache.getProperty("VERSION"));
434 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
435 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
436 * officially documented or guaranteed to exist, so we access it via
437 * reflection. There appear to be unfathomable criteria about what this
438 * string can contain, and it if doesn't meet those criteria then "java"
439 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
440 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
441 * not. The reflection access may generate a warning: WARNING: An illegal
442 * reflective access operation has occurred WARNING: Illegal reflective
443 * access by jalview.gui.Desktop () to field
444 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
446 if (Platform.isLinux())
448 if (LaunchUtils.getJavaVersion() >= 11)
451 * Send this message to stderr as the warning that follows (due to
452 * reflection) also goes to stderr.
454 jalview.bin.Console.errPrintln(
455 "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.");
457 final String awtAppClassName = "awtAppClassName";
460 Toolkit xToolkit = Toolkit.getDefaultToolkit();
461 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
462 Field awtAppClassNameField = null;
464 if (Arrays.stream(declaredFields)
465 .anyMatch(f -> f.getName().equals(awtAppClassName)))
467 awtAppClassNameField = xToolkit.getClass()
468 .getDeclaredField(awtAppClassName);
471 String title = ChannelProperties.getProperty("app_name");
472 if (awtAppClassNameField != null)
474 awtAppClassNameField.setAccessible(true);
475 awtAppClassNameField.set(xToolkit, title);
480 .debug("XToolkit: " + awtAppClassName + " not found");
482 } catch (Exception e)
484 jalview.bin.Console.debug("Error setting " + awtAppClassName);
485 jalview.bin.Console.trace(Cache.getStackTraceString(e));
489 setIconImages(ChannelProperties.getIconList());
491 // override quit handling when GUI OS close [X] button pressed
492 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
493 addWindowListener(new WindowAdapter()
496 public void windowClosing(WindowEvent ev)
498 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
502 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
504 boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE,
505 "SHOW_JAVA_CONSOLE", false);
507 // start dialogue queue for single dialogues
510 if (!Platform.isJS())
517 Desktop.instance.acquireDialogQueue();
519 jconsole = new Console(this);
520 jconsole.setHeader(Cache.getVersionDetailsForConsole());
521 showConsole(showjconsole);
523 Desktop.instance.releaseDialogQueue();
526 desktop = new MyDesktopPane(selmemusage);
528 showMemusage.setSelected(selmemusage);
529 desktop.setBackground(Color.white);
531 getContentPane().setLayout(new BorderLayout());
532 // alternate config - have scrollbars - see notes in JAL-153
533 // JScrollPane sp = new JScrollPane();
534 // sp.getViewport().setView(desktop);
535 // getContentPane().add(sp, BorderLayout.CENTER);
537 // BH 2018 - just an experiment to try unclipped JInternalFrames.
540 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
543 getContentPane().add(desktop, BorderLayout.CENTER);
544 desktop.setDragMode(DRAG_MODE);
546 // This line prevents Windows Look&Feel resizing all new windows to maximum
547 // if previous window was maximised
548 desktop.setDesktopManager(new MyDesktopManager(
549 Platform.isJS() ? desktop.getDesktopManager()
550 : new DefaultDesktopManager()));
552 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
553 : Platform.isAMacAndNotJS()
554 ? new AquaInternalFrameManager(
555 desktop.getDesktopManager())
556 : desktop.getDesktopManager())));
559 Rectangle dims = getLastKnownDimensions("");
566 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
567 int xPos = Math.max(5, (screenSize.width - 900) / 2);
568 int yPos = Math.max(5, (screenSize.height - 650) / 2);
569 setBounds(xPos, yPos, 900, 650);
572 if (!Platform.isJS())
579 showNews.setVisible(false);
581 experimentalFeatures.setSelected(showExperimental());
583 getIdentifiersOrgData();
587 // Spawn a thread that shows the splashscreen
590 SwingUtilities.invokeLater(new Runnable()
595 new SplashScreen(true);
600 // Thread off a new instance of the file chooser - this reduces the time
601 // it takes to open it later on.
602 new Thread(new Runnable()
607 jalview.bin.Console.debug("Filechooser init thread started.");
608 String fileFormat = FileLoader.getUseDefaultFileFormat()
609 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
611 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
613 jalview.bin.Console.debug("Filechooser init thread finished.");
616 // Add the service change listener
617 changeSupport.addJalviewPropertyChangeListener("services",
618 new PropertyChangeListener()
622 public void propertyChange(PropertyChangeEvent evt)
625 .debug("Firing service changed event for "
626 + evt.getNewValue());
627 JalviewServicesChanged(evt);
632 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
635 this.addMouseListener(ma = new MouseAdapter()
638 public void mousePressed(MouseEvent evt)
640 if (evt.isPopupTrigger()) // Mac
642 showPasteMenu(evt.getX(), evt.getY());
647 public void mouseReleased(MouseEvent evt)
649 if (evt.isPopupTrigger()) // Windows
651 showPasteMenu(evt.getX(), evt.getY());
655 desktop.addMouseListener(ma);
659 // used for jalviewjsTest
660 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
666 * Answers true if user preferences to enable experimental features is True
671 public boolean showExperimental()
673 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
674 Boolean.FALSE.toString());
675 return Boolean.valueOf(experimental).booleanValue();
678 public void doConfigureStructurePrefs()
680 // configure services
681 StructureSelectionManager ssm = StructureSelectionManager
682 .getStructureSelectionManager(this);
683 StructureSelectionManager.doConfigureStructurePrefs(ssm);
686 public void checkForNews()
688 final Desktop me = this;
689 // Thread off the news reader, in case there are connection problems.
690 new Thread(new Runnable()
695 jalview.bin.Console.debug("Starting news thread.");
696 jvnews = new BlogReader(me);
697 showNews.setVisible(true);
698 jalview.bin.Console.debug("Completed news thread.");
703 public void getIdentifiersOrgData()
705 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
706 {// Thread off the identifiers fetcher
707 new Thread(new Runnable()
713 .debug("Downloading data from identifiers.org");
716 UrlDownloadClient.download(IdOrgSettings.getUrl(),
717 IdOrgSettings.getDownloadLocation());
718 } catch (IOException e)
721 .debug("Exception downloading identifiers.org data"
731 protected void showNews_actionPerformed(ActionEvent e)
733 showNews(showNews.isSelected());
736 void showNews(boolean visible)
738 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
739 showNews.setSelected(visible);
740 if (visible && !jvnews.isVisible())
742 new Thread(new Runnable()
747 long now = System.currentTimeMillis();
748 Desktop.instance.setProgressBar(
749 MessageManager.getString("status.refreshing_news"), now);
750 jvnews.refreshNews();
751 Desktop.instance.setProgressBar(null, now);
759 * recover the last known dimensions for a jalview window
762 * - empty string is desktop, all other windows have unique prefix
763 * @return null or last known dimensions scaled to current geometry (if last
764 * window geom was known)
766 Rectangle getLastKnownDimensions(String windowName)
768 // TODO: lock aspect ratio for scaling desktop Bug #0058199
769 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
770 String x = Cache.getProperty(windowName + "SCREEN_X");
771 String y = Cache.getProperty(windowName + "SCREEN_Y");
772 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
773 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
774 if ((x != null) && (y != null) && (width != null) && (height != null))
776 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
777 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
778 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
780 // attempt #1 - try to cope with change in screen geometry - this
781 // version doesn't preserve original jv aspect ratio.
782 // take ratio of current screen size vs original screen size.
783 double sw = ((1f * screenSize.width) / (1f * Integer
784 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
785 double sh = ((1f * screenSize.height) / (1f * Integer
786 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
787 // rescale the bounds depending upon the current screen geometry.
788 ix = (int) (ix * sw);
789 iw = (int) (iw * sw);
790 iy = (int) (iy * sh);
791 ih = (int) (ih * sh);
792 if (ix >= screenSize.width)
794 jalview.bin.Console.debug(
795 "Window geometry location recall error: shifting horizontal to within screenbounds.");
796 ix = ix % screenSize.width;
798 if (iy >= screenSize.height)
800 jalview.bin.Console.debug(
801 "Window geometry location recall error: shifting vertical to within screenbounds.");
802 iy = iy % screenSize.height;
804 jalview.bin.Console.debug(
805 "Got last known dimensions for " + windowName + ": x:" + ix
806 + " y:" + iy + " width:" + iw + " height:" + ih);
808 // return dimensions for new instance
809 return new Rectangle(ix, iy, iw, ih);
814 void showPasteMenu(int x, int y)
816 JPopupMenu popup = new JPopupMenu();
817 JMenuItem item = new JMenuItem(
818 MessageManager.getString("label.paste_new_window"));
819 item.addActionListener(new ActionListener()
822 public void actionPerformed(ActionEvent evt)
829 popup.show(this, x, y);
834 // quick patch for JAL-4150 - needs some more work and test coverage
835 // TODO - unify below and AlignFrame.paste()
836 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
837 // clipboard has come from a different alignment window than the one where
838 // paste has been called! JAL-4151
840 if (Desktop.jalviewClipboard != null)
842 // The clipboard was filled from within Jalview, we must use the
844 // And dataset from the copied alignment
845 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
846 // be doubly sure that we create *new* sequence objects.
847 SequenceI[] sequences = new SequenceI[newseq.length];
848 for (int i = 0; i < newseq.length; i++)
850 sequences[i] = new Sequence(newseq[i]);
852 Alignment alignment = new Alignment(sequences);
853 // dataset is inherited
854 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
855 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
856 AlignFrame.DEFAULT_HEIGHT);
857 String newtitle = new String("Copied sequences");
859 if (Desktop.jalviewClipboard[2] != null)
861 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
862 af.viewport.setHiddenColumns(hc);
865 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
866 AlignFrame.DEFAULT_HEIGHT);
873 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
874 Transferable contents = c.getContents(this);
876 if (contents != null)
878 String file = (String) contents
879 .getTransferData(DataFlavor.stringFlavor);
881 FileFormatI format = new IdentifyFile().identify(file,
882 DataSourceType.PASTE);
884 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
887 } catch (Exception ex)
889 jalview.bin.Console.outPrintln(
890 "Unable to paste alignment from system clipboard:\n" + ex);
896 * Adds and opens the given frame to the desktop
907 public static synchronized void addInternalFrame(
908 final JInternalFrame frame, String title, int w, int h)
910 addInternalFrame(frame, title, true, w, h, true, false);
914 * Add an internal frame to the Jalview desktop
921 * When true, display frame immediately, otherwise, caller must call
922 * setVisible themselves.
928 public static synchronized void addInternalFrame(
929 final JInternalFrame frame, String title, boolean makeVisible,
932 addInternalFrame(frame, title, makeVisible, w, h, true, false);
936 * Add an internal frame to the Jalview desktop and make it visible
949 public static synchronized void addInternalFrame(
950 final JInternalFrame frame, String title, int w, int h,
953 addInternalFrame(frame, title, true, w, h, resizable, false);
957 * Add an internal frame to the Jalview desktop
964 * When true, display frame immediately, otherwise, caller must call
965 * setVisible themselves.
972 * @param ignoreMinSize
973 * Do not set the default minimum size for frame
975 public static synchronized void addInternalFrame(
976 final JInternalFrame frame, String title, boolean makeVisible,
977 int w, int h, boolean resizable, boolean ignoreMinSize)
980 // TODO: allow callers to determine X and Y position of frame (eg. via
982 // TODO: consider fixing method to update entries in the window submenu with
983 // the current window title
985 frame.setTitle(title);
986 if (frame.getWidth() < 1 || frame.getHeight() < 1)
990 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
991 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
992 // IF JALVIEW IS RUNNING HEADLESS
993 // ///////////////////////////////////////////////
994 if (instance == null || (System.getProperty("java.awt.headless") != null
995 && System.getProperty("java.awt.headless").equals("true")))
1004 frame.setMinimumSize(
1005 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1007 // Set default dimension for Alignment Frame window.
1008 // The Alignment Frame window could be added from a number of places,
1010 // I did this here in order not to miss out on any Alignment frame.
1011 if (frame instanceof AlignFrame)
1013 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1014 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1018 frame.setVisible(makeVisible);
1019 frame.setClosable(true);
1020 frame.setResizable(resizable);
1021 frame.setMaximizable(resizable);
1022 frame.setIconifiable(resizable);
1023 frame.setOpaque(Platform.isJS());
1025 if (frame.getX() < 1 && frame.getY() < 1)
1027 frame.setLocation(xOffset * openFrameCount,
1028 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1032 * add an entry for the new frame in the Window menu (and remove it when the
1035 final JMenuItem menuItem = new JMenuItem(title);
1036 frame.addInternalFrameListener(new InternalFrameAdapter()
1039 public void internalFrameActivated(InternalFrameEvent evt)
1041 JInternalFrame itf = desktop.getSelectedFrame();
1044 if (itf instanceof AlignFrame)
1046 Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf);
1053 public void internalFrameClosed(InternalFrameEvent evt)
1055 PaintRefresher.RemoveComponent(frame);
1058 * defensive check to prevent frames being added half off the window
1060 if (openFrameCount > 0)
1066 * ensure no reference to alignFrame retained by menu item listener
1068 if (menuItem.getActionListeners().length > 0)
1070 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1072 windowMenu.remove(menuItem);
1076 menuItem.addActionListener(new ActionListener()
1079 public void actionPerformed(ActionEvent e)
1083 frame.setSelected(true);
1084 frame.setIcon(false);
1085 } catch (java.beans.PropertyVetoException ex)
1092 setKeyBindings(frame);
1094 // Since the latest FlatLaf patch, we occasionally have problems showing
1095 // structureViewer frames...
1097 boolean shown = false;
1098 Exception last = null;
1105 } catch (IllegalArgumentException iaex)
1109 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1110 + tries + " left) for " + frame.getTitle(), iaex);
1114 } catch (InterruptedException iex)
1119 } while (!shown && tries > 0);
1122 jalview.bin.Console.error(
1123 "Serious Problem whilst showing window " + frame.getTitle(),
1127 windowMenu.add(menuItem);
1132 frame.setSelected(true);
1133 frame.requestFocus();
1134 } catch (java.beans.PropertyVetoException ve)
1136 } catch (java.lang.ClassCastException cex)
1138 jalview.bin.Console.warn(
1139 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1145 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1150 private static void setKeyBindings(JInternalFrame frame)
1152 @SuppressWarnings("serial")
1153 final Action closeAction = new AbstractAction()
1156 public void actionPerformed(ActionEvent e)
1163 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1165 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1166 InputEvent.CTRL_DOWN_MASK);
1167 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1168 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1170 InputMap inputMap = frame
1171 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1172 String ctrlW = ctrlWKey.toString();
1173 inputMap.put(ctrlWKey, ctrlW);
1174 inputMap.put(cmdWKey, ctrlW);
1176 ActionMap actionMap = frame.getActionMap();
1177 actionMap.put(ctrlW, closeAction);
1181 public void lostOwnership(Clipboard clipboard, Transferable contents)
1185 Desktop.jalviewClipboard = null;
1188 internalCopy = false;
1192 public void dragEnter(DropTargetDragEvent evt)
1197 public void dragExit(DropTargetEvent evt)
1202 public void dragOver(DropTargetDragEvent evt)
1207 public void dropActionChanged(DropTargetDragEvent evt)
1218 public void drop(DropTargetDropEvent evt)
1220 boolean success = true;
1221 // JAL-1552 - acceptDrop required before getTransferable call for
1222 // Java's Transferable for native dnd
1223 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1224 Transferable t = evt.getTransferable();
1225 List<Object> files = new ArrayList<>();
1226 List<DataSourceType> protocols = new ArrayList<>();
1230 Desktop.transferFromDropTarget(files, protocols, evt, t);
1231 } catch (Exception e)
1233 e.printStackTrace();
1241 for (int i = 0; i < files.size(); i++)
1243 // BH 2018 File or String
1244 Object file = files.get(i);
1245 String fileName = file.toString();
1246 DataSourceType protocol = (protocols == null)
1247 ? DataSourceType.FILE
1249 FileFormatI format = null;
1251 if (fileName.endsWith(".jar"))
1253 format = FileFormat.Jalview;
1258 format = new IdentifyFile().identify(file, protocol);
1260 if (file instanceof File)
1262 Platform.cacheFileData((File) file);
1264 new FileLoader().LoadFile(null, file, protocol, format);
1267 } catch (Exception ex)
1272 evt.dropComplete(success); // need this to ensure input focus is properly
1273 // transfered to any new windows created
1283 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1285 String fileFormat = FileLoader.getUseDefaultFileFormat()
1286 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1288 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1289 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1290 BackupFiles.getEnabled());
1292 chooser.setFileView(new JalviewFileView());
1293 chooser.setDialogTitle(
1294 MessageManager.getString("label.open_local_file"));
1295 chooser.setToolTipText(MessageManager.getString("action.open"));
1297 chooser.setResponseHandler(0, () -> {
1298 File selectedFile = chooser.getSelectedFile();
1299 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1301 FileFormatI format = chooser.getSelectedFormat();
1304 * Call IdentifyFile to verify the file contains what its extension implies.
1305 * Skip this step for dynamically added file formats, because IdentifyFile does
1306 * not know how to recognise them.
1308 if (FileFormats.getInstance().isIdentifiable(format))
1312 format = new IdentifyFile().identify(selectedFile,
1313 DataSourceType.FILE);
1314 } catch (FileFormatException e)
1316 // format = null; //??
1320 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1323 chooser.showOpenDialog(this);
1327 * Shows a dialog for input of a URL at which to retrieve alignment data
1332 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1334 // This construct allows us to have a wider textfield
1336 JLabel label = new JLabel(
1337 MessageManager.getString("label.input_file_url"));
1339 JPanel panel = new JPanel(new GridLayout(2, 1));
1343 * the URL to fetch is input in Java: an editable combobox with history JS:
1344 * (pending JAL-3038) a plain text field
1347 String urlBase = "https://www.";
1348 if (Platform.isJS())
1350 history = new JTextField(urlBase, 35);
1359 JComboBox<String> asCombo = new JComboBox<>();
1360 asCombo.setPreferredSize(new Dimension(400, 20));
1361 asCombo.setEditable(true);
1362 asCombo.addItem(urlBase);
1363 String historyItems = Cache.getProperty("RECENT_URL");
1364 if (historyItems != null)
1366 for (String token : historyItems.split("\\t"))
1368 asCombo.addItem(token);
1375 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1376 MessageManager.getString("action.cancel") };
1377 Runnable action = () -> {
1378 @SuppressWarnings("unchecked")
1379 String url = (history instanceof JTextField
1380 ? ((JTextField) history).getText()
1381 : ((JComboBox<String>) history).getEditor().getItem()
1382 .toString().trim());
1384 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1386 if (viewport != null)
1388 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1389 FileFormat.Jalview);
1393 new FileLoader().LoadFile(url, DataSourceType.URL,
1394 FileFormat.Jalview);
1399 FileFormatI format = null;
1402 format = new IdentifyFile().identify(url, DataSourceType.URL);
1403 } catch (FileFormatException e)
1405 // TODO revise error handling, distinguish between
1406 // URL not found and response not valid
1411 String msg = MessageManager.formatMessage("label.couldnt_locate",
1413 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1414 MessageManager.getString("label.url_not_found"),
1415 JvOptionPane.WARNING_MESSAGE);
1419 if (viewport != null)
1421 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1426 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1430 String dialogOption = MessageManager
1431 .getString("label.input_alignment_from_url");
1432 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1433 .showInternalDialog(panel, dialogOption,
1434 JvOptionPane.YES_NO_CANCEL_OPTION,
1435 JvOptionPane.PLAIN_MESSAGE, null, options,
1436 MessageManager.getString("action.ok"));
1440 * Opens the CutAndPaste window for the user to paste an alignment in to
1443 * - if not null, the pasted alignment is added to the current
1444 * alignment; if null, to a new alignment window
1447 public void inputTextboxMenuItem_actionPerformed(
1448 AlignmentViewPanel viewPanel)
1450 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1451 cap.setForInput(viewPanel);
1452 Desktop.addInternalFrame(cap,
1453 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1458 * Check with user and saving files before actually quitting
1460 public void desktopQuit()
1462 desktopQuit(true, false);
1466 * close everything, stash window geometries, and shut down all associated
1470 * - sets the dispose on close flag - JVM may terminate when set
1471 * @param terminateJvm
1472 * - quit with prejudice - stops the JVM.
1474 public void quitTheDesktop(boolean dispose, boolean terminateJvm)
1476 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1477 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1478 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1479 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1480 getWidth(), getHeight()));
1482 if (jconsole != null)
1484 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1485 jconsole.stopConsole();
1490 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1493 // Frames should all close automatically. Keeping external
1494 // viewers open should already be decided by user.
1495 closeAll_actionPerformed(null);
1497 if (dialogExecutor != null)
1499 dialogExecutor.shutdownNow();
1502 if (groovyConsole != null)
1504 // suppress a possible repeat prompt to save script
1505 groovyConsole.setDirty(false);
1508 if (((Window) groovyConsole.getFrame()) != null
1509 && ((Window) groovyConsole.getFrame()).isVisible())
1511 // console is visible -- FIXME JAL-4327
1512 groovyConsole.exit();
1516 // console is not, so just let it dispose itself when we shutdown
1517 // we don't call groovyConsole.exit() because it calls the shutdown
1518 // handler with invokeAndWait() causing deadlock
1519 groovyConsole = null;
1525 // note that shutdown hook will not be run
1526 jalview.bin.Console.debug("Force Quit selected by user");
1527 Runtime.getRuntime().halt(0);
1530 jalview.bin.Console.debug("Quit selected by user");
1533 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1534 // instance.dispose();
1538 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1540 final Runnable doDesktopQuit = () -> {
1542 // FIRST !! check for aborted quit
1543 if (QuitHandler.quitCancelled())
1546 .debug("Quit was cancelled - Desktop aborting quit");
1550 // Proceed with quitting
1551 quitTheDesktop(disposeFlag,
1552 QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT);
1557 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1558 QuitHandler.defaultCancelQuit);
1562 * Exits the program and the JVM.
1564 * Don't call this directly
1566 * - use desktopQuit() above to tidy up first.
1568 * - use closeDesktop() to shutdown Jalview without shutting down the JVM
1574 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1575 // not run a second time if gotQuitResponse flag has been set (i.e. user
1576 // confirmed quit of some kind).
1577 Jalview.exit("Desktop exiting.", ExitCode.OK);
1580 private void storeLastKnownDimensions(String string, Rectangle jc)
1582 jalview.bin.Console.debug("Storing last known dimensions for " + string
1583 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1584 + " height:" + jc.height);
1586 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1587 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1588 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1589 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1599 public void aboutMenuItem_actionPerformed(ActionEvent e)
1601 new Thread(new Runnable()
1606 new SplashScreen(false);
1612 * Returns the html text for the About screen, including any available version
1613 * number, build details, author details and citation reference, but without
1614 * the enclosing {@code html} tags
1618 public String getAboutMessage()
1620 StringBuilder message = new StringBuilder(1024);
1621 message.append("<div style=\"font-family: sans-serif;\">")
1622 .append("<h1><strong>Version: ")
1623 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1624 .append("<strong>Built: <em>")
1625 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1626 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1627 .append("</strong>");
1629 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1630 if (latestVersion.equals("Checking"))
1632 // JBP removed this message for 2.11: May be reinstated in future version
1633 // message.append("<br>...Checking latest version...</br>");
1635 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1637 boolean red = false;
1638 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1639 .indexOf("automated build") == -1)
1642 // Displayed when code version and jnlp version do not match and code
1643 // version is not a development build
1644 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1647 message.append("<br>!! Version ")
1648 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1649 .append(" is available for download from ")
1650 .append(Cache.getDefault("www.jalview.org",
1651 "https://www.jalview.org"))
1655 message.append("</div>");
1658 message.append("<br>Authors: ");
1659 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1660 message.append(CITATION);
1662 message.append("</div>");
1664 return message.toString();
1668 * Action on requesting Help documentation
1671 public void documentationMenuItem_actionPerformed()
1675 if (Platform.isJS())
1677 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1686 Help.showHelpWindow();
1688 } catch (Exception ex)
1691 .errPrintln("Error opening help: " + ex.getMessage());
1696 public void closeAll_actionPerformed(ActionEvent e)
1698 // TODO show a progress bar while closing?
1699 JInternalFrame[] frames = desktop.getAllFrames();
1700 for (int i = 0; i < frames.length; i++)
1704 frames[i].setClosed(true);
1705 } catch (java.beans.PropertyVetoException ex)
1709 Jalview.getInstance().setCurrentAlignFrame(null);
1710 jalview.bin.Console.info("ALL CLOSED");
1713 * reset state of singleton objects as appropriate (clear down session state
1714 * when all windows are closed)
1716 StructureSelectionManager ssm = StructureSelectionManager
1717 .getStructureSelectionManager(this);
1724 public int structureViewersStillRunningCount()
1727 JInternalFrame[] frames = desktop.getAllFrames();
1728 for (int i = 0; i < frames.length; i++)
1730 if (frames[i] != null
1731 && frames[i] instanceof JalviewStructureDisplayI)
1733 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1741 public void raiseRelated_actionPerformed(ActionEvent e)
1743 reorderAssociatedWindows(false, false);
1747 public void minimizeAssociated_actionPerformed(ActionEvent e)
1749 reorderAssociatedWindows(true, false);
1752 void closeAssociatedWindows()
1754 reorderAssociatedWindows(false, true);
1760 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1764 protected void garbageCollect_actionPerformed(ActionEvent e)
1766 // We simply collect the garbage
1767 jalview.bin.Console.debug("Collecting garbage...");
1769 jalview.bin.Console.debug("Finished garbage collection.");
1775 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1779 protected void showMemusage_actionPerformed(ActionEvent e)
1781 desktop.showMemoryUsage(showMemusage.isSelected());
1788 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1792 protected void showConsole_actionPerformed(ActionEvent e)
1794 showConsole(showConsole.isSelected());
1797 Console jconsole = null;
1800 * control whether the java console is visible or not
1804 void showConsole(boolean selected)
1806 // TODO: decide if we should update properties file
1807 if (jconsole != null) // BH 2018
1809 showConsole.setSelected(selected);
1810 Cache.setProperty("SHOW_JAVA_CONSOLE",
1811 Boolean.valueOf(selected).toString());
1812 jconsole.setVisible(selected);
1816 void reorderAssociatedWindows(boolean minimize, boolean close)
1818 JInternalFrame[] frames = desktop.getAllFrames();
1819 if (frames == null || frames.length < 1)
1824 AlignmentViewport source = null, target = null;
1825 if (frames[0] instanceof AlignFrame)
1827 source = ((AlignFrame) frames[0]).getCurrentView();
1829 else if (frames[0] instanceof TreePanel)
1831 source = ((TreePanel) frames[0]).getViewPort();
1833 else if (frames[0] instanceof PCAPanel)
1835 source = ((PCAPanel) frames[0]).av;
1837 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1839 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1844 for (int i = 0; i < frames.length; i++)
1847 if (frames[i] == null)
1851 if (frames[i] instanceof AlignFrame)
1853 target = ((AlignFrame) frames[i]).getCurrentView();
1855 else if (frames[i] instanceof TreePanel)
1857 target = ((TreePanel) frames[i]).getViewPort();
1859 else if (frames[i] instanceof PCAPanel)
1861 target = ((PCAPanel) frames[i]).av;
1863 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1865 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1868 if (source == target)
1874 frames[i].setClosed(true);
1878 frames[i].setIcon(minimize);
1881 frames[i].toFront();
1885 } catch (java.beans.PropertyVetoException ex)
1900 protected void preferences_actionPerformed(ActionEvent e)
1902 Preferences.openPreferences();
1906 * Prompts the user to choose a file and then saves the Jalview state as a
1907 * Jalview project file
1910 public void saveState_actionPerformed()
1912 saveState_actionPerformed(false);
1915 public void saveState_actionPerformed(boolean saveAs)
1917 java.io.File projectFile = getProjectFile();
1918 // autoSave indicates we already have a file and don't need to ask
1919 boolean autoSave = projectFile != null && !saveAs
1920 && BackupFiles.getEnabled();
1922 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1923 // projectFile='"+projectFile+"',
1924 // saveAs="+saveAs+", Backups
1925 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1927 boolean approveSave = false;
1930 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1933 chooser.setFileView(new JalviewFileView());
1934 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1936 int value = chooser.showSaveDialog(this);
1938 if (value == JalviewFileChooser.APPROVE_OPTION)
1940 projectFile = chooser.getSelectedFile();
1941 setProjectFile(projectFile);
1946 if (approveSave || autoSave)
1948 final Desktop me = this;
1949 final java.io.File chosenFile = projectFile;
1950 new Thread(new Runnable()
1955 // TODO: refactor to Jalview desktop session controller action.
1956 setProgressBar(MessageManager.formatMessage(
1957 "label.saving_jalview_project", new Object[]
1958 { chosenFile.getName() }), chosenFile.hashCode());
1959 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1960 // TODO catch and handle errors for savestate
1961 // TODO prevent user from messing with the Desktop whilst we're saving
1964 boolean doBackup = BackupFiles.getEnabled();
1965 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1968 new Jalview2XML().saveState(
1969 doBackup ? backupfiles.getTempFile() : chosenFile);
1973 backupfiles.setWriteSuccess(true);
1974 backupfiles.rollBackupsAndRenameTempFile();
1976 } catch (OutOfMemoryError oom)
1978 new OOMWarning("Whilst saving current state to "
1979 + chosenFile.getName(), oom);
1980 } catch (Exception ex)
1982 jalview.bin.Console.error("Problems whilst trying to save to "
1983 + chosenFile.getName(), ex);
1984 JvOptionPane.showMessageDialog(me,
1985 MessageManager.formatMessage(
1986 "label.error_whilst_saving_current_state_to",
1988 { chosenFile.getName() }),
1989 MessageManager.getString("label.couldnt_save_project"),
1990 JvOptionPane.WARNING_MESSAGE);
1992 setProgressBar(null, chosenFile.hashCode());
1999 public void saveAsState_actionPerformed(ActionEvent e)
2001 saveState_actionPerformed(true);
2004 protected void setProjectFile(File choice)
2006 this.projectFile = choice;
2009 public File getProjectFile()
2011 return this.projectFile;
2015 * Shows a file chooser dialog and tries to read in the selected file as a
2019 public void loadState_actionPerformed()
2021 final String[] suffix = new String[] { "jvp", "jar" };
2022 final String[] desc = new String[] { "Jalview Project",
2023 "Jalview Project (old)" };
2024 JalviewFileChooser chooser = new JalviewFileChooser(
2025 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2026 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2030 chooser.setFileView(new JalviewFileView());
2031 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2032 chooser.setResponseHandler(0, () -> {
2033 File selectedFile = chooser.getSelectedFile();
2034 setProjectFile(selectedFile);
2035 String choice = selectedFile.getAbsolutePath();
2036 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2037 new Thread(new Runnable()
2044 new Jalview2XML().loadJalviewAlign(selectedFile);
2045 } catch (OutOfMemoryError oom)
2047 new OOMWarning("Whilst loading project from " + choice, oom);
2048 } catch (Exception ex)
2050 jalview.bin.Console.error(
2051 "Problems whilst loading project from " + choice, ex);
2052 JvOptionPane.showMessageDialog(Desktop.desktop,
2053 MessageManager.formatMessage(
2054 "label.error_whilst_loading_project_from",
2057 MessageManager.getString("label.couldnt_load_project"),
2058 JvOptionPane.WARNING_MESSAGE);
2061 }, "Project Loader").start();
2064 chooser.showOpenDialog(this);
2068 public void inputSequence_actionPerformed(ActionEvent e)
2070 new SequenceFetcher(this);
2073 JPanel progressPanel;
2075 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2077 public void startLoading(final Object fileName)
2079 if (fileLoadingCount == 0)
2081 fileLoadingPanels.add(addProgressPanel(MessageManager
2082 .formatMessage("label.loading_file", new Object[]
2088 private JPanel addProgressPanel(String string)
2090 if (progressPanel == null)
2092 progressPanel = new JPanel(new GridLayout(1, 1));
2093 totalProgressCount = 0;
2094 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2096 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2097 JProgressBar progressBar = new JProgressBar();
2098 progressBar.setIndeterminate(true);
2100 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2102 thisprogress.add(progressBar, BorderLayout.CENTER);
2103 progressPanel.add(thisprogress);
2104 ((GridLayout) progressPanel.getLayout()).setRows(
2105 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2106 ++totalProgressCount;
2107 instance.validate();
2108 return thisprogress;
2111 int totalProgressCount = 0;
2113 private void removeProgressPanel(JPanel progbar)
2115 if (progressPanel != null)
2117 synchronized (progressPanel)
2119 progressPanel.remove(progbar);
2120 GridLayout gl = (GridLayout) progressPanel.getLayout();
2121 gl.setRows(gl.getRows() - 1);
2122 if (--totalProgressCount < 1)
2124 this.getContentPane().remove(progressPanel);
2125 progressPanel = null;
2132 public void stopLoading()
2135 if (fileLoadingCount < 1)
2137 while (fileLoadingPanels.size() > 0)
2139 removeProgressPanel(fileLoadingPanels.remove(0));
2141 fileLoadingPanels.clear();
2142 fileLoadingCount = 0;
2147 public static int getViewCount(String alignmentId)
2149 AlignmentViewport[] aps = getViewports(alignmentId);
2150 return (aps == null) ? 0 : aps.length;
2155 * @param alignmentId
2156 * - if null, all sets are returned
2157 * @return all AlignmentPanels concerning the alignmentId sequence set
2159 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2161 if (Desktop.desktop == null)
2163 // no frames created and in headless mode
2164 // TODO: verify that frames are recoverable when in headless mode
2167 List<AlignmentPanel> aps = new ArrayList<>();
2168 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2173 for (AlignFrame af : frames)
2175 for (AlignmentPanel ap : af.alignPanels)
2177 if (alignmentId == null
2178 || alignmentId.equals(ap.av.getSequenceSetId()))
2184 if (aps.size() == 0)
2188 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2193 * get all the viewports on an alignment.
2195 * @param sequenceSetId
2196 * unique alignment id (may be null - all viewports returned in that
2198 * @return all viewports on the alignment bound to sequenceSetId
2200 public static AlignmentViewport[] getViewports(String sequenceSetId)
2202 List<AlignmentViewport> viewp = new ArrayList<>();
2203 if (desktop != null)
2205 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2207 for (AlignFrame afr : frames)
2209 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2210 .equals(sequenceSetId))
2212 if (afr.alignPanels != null)
2214 for (AlignmentPanel ap : afr.alignPanels)
2216 if (sequenceSetId == null
2217 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2225 viewp.add(afr.getViewport());
2229 if (viewp.size() > 0)
2231 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2238 * Explode the views in the given frame into separate AlignFrame
2242 public static void explodeViews(AlignFrame af)
2244 int size = af.alignPanels.size();
2250 // FIXME: ideally should use UI interface API
2251 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2252 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2253 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2254 for (int i = 0; i < size; i++)
2256 AlignmentPanel ap = af.alignPanels.get(i);
2258 AlignFrame newaf = new AlignFrame(ap);
2260 // transfer reference for existing feature settings to new alignFrame
2261 if (ap == af.alignPanel)
2263 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2265 newaf.featureSettings = viewFeatureSettings;
2267 newaf.setFeatureSettingsGeometry(fsBounds);
2271 * Restore the view's last exploded frame geometry if known. Multiple views from
2272 * one exploded frame share and restore the same (frame) position and size.
2274 Rectangle geometry = ap.av.getExplodedGeometry();
2275 if (geometry != null)
2277 newaf.setBounds(geometry);
2280 ap.av.setGatherViewsHere(false);
2282 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2283 AlignFrame.DEFAULT_HEIGHT);
2284 // and materialise a new feature settings dialog instance for the new
2286 // (closes the old as if 'OK' was pressed)
2287 if (ap == af.alignPanel && newaf.featureSettings != null
2288 && newaf.featureSettings.isOpen()
2289 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2291 newaf.showFeatureSettingsUI();
2295 af.featureSettings = null;
2296 af.alignPanels.clear();
2297 af.closeMenuItem_actionPerformed(true);
2302 * Gather expanded views (separate AlignFrame's) with the same sequence set
2303 * identifier back in to this frame as additional views, and close the
2304 * expanded views. Note the expanded frames may themselves have multiple
2305 * views. We take the lot.
2309 public void gatherViews(AlignFrame source)
2311 source.viewport.setGatherViewsHere(true);
2312 source.viewport.setExplodedGeometry(source.getBounds());
2313 JInternalFrame[] frames = desktop.getAllFrames();
2314 String viewId = source.viewport.getSequenceSetId();
2315 for (int t = 0; t < frames.length; t++)
2317 if (frames[t] instanceof AlignFrame && frames[t] != source)
2319 AlignFrame af = (AlignFrame) frames[t];
2320 boolean gatherThis = false;
2321 for (int a = 0; a < af.alignPanels.size(); a++)
2323 AlignmentPanel ap = af.alignPanels.get(a);
2324 if (viewId.equals(ap.av.getSequenceSetId()))
2327 ap.av.setGatherViewsHere(false);
2328 ap.av.setExplodedGeometry(af.getBounds());
2329 source.addAlignmentPanel(ap, false);
2335 if (af.featureSettings != null && af.featureSettings.isOpen())
2337 if (source.featureSettings == null)
2339 // preserve the feature settings geometry for this frame
2340 source.featureSettings = af.featureSettings;
2341 source.setFeatureSettingsGeometry(
2342 af.getFeatureSettingsGeometry());
2346 // close it and forget
2347 af.featureSettings.close();
2350 af.alignPanels.clear();
2351 af.closeMenuItem_actionPerformed(true);
2356 // refresh the feature setting UI for the source frame if it exists
2357 if (source.featureSettings != null && source.featureSettings.isOpen())
2359 source.showFeatureSettingsUI();
2364 public JInternalFrame[] getAllFrames()
2366 return desktop.getAllFrames();
2370 * Checks the given url to see if it gives a response indicating that the user
2371 * should be informed of a new questionnaire.
2375 public void checkForQuestionnaire(String url)
2377 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2378 // javax.swing.SwingUtilities.invokeLater(jvq);
2379 new Thread(jvq).start();
2382 public void checkURLLinks()
2384 // Thread off the URL link checker
2385 addDialogThread(new Runnable()
2390 if (Cache.getDefault("CHECKURLLINKS", true))
2392 // check what the actual links are - if it's just the default don't
2393 // bother with the warning
2394 List<String> links = Preferences.sequenceUrlLinks
2397 // only need to check links if there is one with a
2398 // SEQUENCE_ID which is not the default EMBL_EBI link
2399 ListIterator<String> li = links.listIterator();
2400 boolean check = false;
2401 List<JLabel> urls = new ArrayList<>();
2402 while (li.hasNext())
2404 String link = li.next();
2405 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2406 && !UrlConstants.isDefaultString(link))
2409 int barPos = link.indexOf("|");
2410 String urlMsg = barPos == -1 ? link
2411 : link.substring(0, barPos) + ": "
2412 + link.substring(barPos + 1);
2413 urls.add(new JLabel(urlMsg));
2421 // ask user to check in case URL links use old style tokens
2422 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2423 JPanel msgPanel = new JPanel();
2424 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2425 msgPanel.add(Box.createVerticalGlue());
2426 JLabel msg = new JLabel(MessageManager
2427 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2428 JLabel msg2 = new JLabel(MessageManager
2429 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2431 for (JLabel url : urls)
2437 final JCheckBox jcb = new JCheckBox(
2438 MessageManager.getString("label.do_not_display_again"));
2439 jcb.addActionListener(new ActionListener()
2442 public void actionPerformed(ActionEvent e)
2444 // update Cache settings for "don't show this again"
2445 boolean showWarningAgain = !jcb.isSelected();
2446 Cache.setProperty("CHECKURLLINKS",
2447 Boolean.valueOf(showWarningAgain).toString());
2452 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2454 .getString("label.SEQUENCE_ID_no_longer_used"),
2455 JvOptionPane.WARNING_MESSAGE);
2462 * Proxy class for JDesktopPane which optionally displays the current memory
2463 * usage and highlights the desktop area with a red bar if free memory runs
2468 public class MyDesktopPane extends JDesktopPane implements Runnable
2470 private static final float ONE_MB = 1048576f;
2472 boolean showMemoryUsage = false;
2476 java.text.NumberFormat df;
2478 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2481 public MyDesktopPane(boolean showMemoryUsage)
2483 showMemoryUsage(showMemoryUsage);
2486 public void showMemoryUsage(boolean showMemory)
2488 this.showMemoryUsage = showMemory;
2491 Thread worker = new Thread(this);
2497 public boolean isShowMemoryUsage()
2499 return showMemoryUsage;
2505 df = java.text.NumberFormat.getNumberInstance();
2506 df.setMaximumFractionDigits(2);
2507 runtime = Runtime.getRuntime();
2509 while (showMemoryUsage)
2513 maxMemory = runtime.maxMemory() / ONE_MB;
2514 allocatedMemory = runtime.totalMemory() / ONE_MB;
2515 freeMemory = runtime.freeMemory() / ONE_MB;
2516 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2518 percentUsage = (totalFreeMemory / maxMemory) * 100;
2520 // if (percentUsage < 20)
2522 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2524 // instance.set.setBorder(border1);
2527 // sleep after showing usage
2529 } catch (Exception ex)
2531 ex.printStackTrace();
2537 public void paintComponent(Graphics g)
2539 if (showMemoryUsage && g != null && df != null)
2541 if (percentUsage < 20)
2543 g.setColor(Color.red);
2545 FontMetrics fm = g.getFontMetrics();
2548 g.drawString(MessageManager.formatMessage("label.memory_stats",
2550 { df.format(totalFreeMemory), df.format(maxMemory),
2551 df.format(percentUsage) }),
2552 10, getHeight() - fm.getHeight());
2556 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2557 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2562 * Accessor method to quickly get all the AlignmentFrames loaded.
2564 * @return an array of AlignFrame, or null if none found
2567 public AlignFrame[] getAlignFrames()
2569 if (desktop == null)
2574 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2580 List<AlignFrame> avp = new ArrayList<>();
2582 for (int i = frames.length - 1; i > -1; i--)
2584 if (frames[i] instanceof AlignFrame)
2586 avp.add((AlignFrame) frames[i]);
2588 else if (frames[i] instanceof SplitFrame)
2591 * Also check for a split frame containing an AlignFrame
2593 GSplitFrame sf = (GSplitFrame) frames[i];
2594 if (sf.getTopFrame() instanceof AlignFrame)
2596 avp.add((AlignFrame) sf.getTopFrame());
2598 if (sf.getBottomFrame() instanceof AlignFrame)
2600 avp.add((AlignFrame) sf.getBottomFrame());
2604 if (avp.size() == 0)
2608 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2615 public static AlignFrame[] getDesktopAlignFrames()
2617 if (Jalview.isHeadlessMode())
2619 // Desktop.desktop is null in headless mode
2620 return Jalview.getInstance().getAlignFrames();
2623 if (instance != null && desktop != null)
2625 return instance.getAlignFrames();
2632 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2636 public GStructureViewer[] getJmols()
2638 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2644 List<GStructureViewer> avp = new ArrayList<>();
2646 for (int i = frames.length - 1; i > -1; i--)
2648 if (frames[i] instanceof AppJmol)
2650 GStructureViewer af = (GStructureViewer) frames[i];
2654 if (avp.size() == 0)
2658 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2663 * Add Groovy Support to Jalview
2666 public void groovyShell_actionPerformed()
2670 openGroovyConsole();
2671 } catch (Exception ex)
2673 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2674 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2676 MessageManager.getString("label.couldnt_create_groovy_shell"),
2677 MessageManager.getString("label.groovy_support_failed"),
2678 JvOptionPane.ERROR_MESSAGE);
2683 * Open the Groovy console
2685 void openGroovyConsole()
2687 if (groovyConsole == null)
2689 JalviewObjectI j = new JalviewObject(this);
2690 groovyConsole = new groovy.console.ui.Console();
2691 groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j);
2692 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2693 getCurrentAlignFrame());
2694 groovyConsole.run();
2697 * We allow only one console at a time, so that AlignFrame menu option
2698 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2699 * enable 'Run script', when the console is opened, and the reverse when it is
2702 Window window = (Window) groovyConsole.getFrame();
2703 window.addWindowListener(new WindowAdapter()
2706 public void windowClosed(WindowEvent e)
2709 * rebind CMD-Q from Groovy Console to Jalview Quit
2712 enableExecuteGroovy(false);
2718 * show Groovy console window (after close and reopen)
2720 ((Window) groovyConsole.getFrame()).setVisible(true);
2723 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2724 * opening a second console
2726 enableExecuteGroovy(true);
2730 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2731 * binding when opened
2733 protected void addQuitHandler()
2736 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2738 .getKeyStroke(KeyEvent.VK_Q,
2739 jalview.util.ShortcutKeyMaskExWrapper
2740 .getMenuShortcutKeyMaskEx()),
2742 getRootPane().getActionMap().put("Quit", new AbstractAction()
2745 public void actionPerformed(ActionEvent e)
2753 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2756 * true if Groovy console is open
2758 public void enableExecuteGroovy(boolean enabled)
2761 * disable opening a second Groovy console (or re-enable when the console is
2764 groovyShell.setEnabled(!enabled);
2766 AlignFrame[] alignFrames = getDesktopAlignFrames();
2767 if (alignFrames != null)
2769 for (AlignFrame af : alignFrames)
2771 af.setGroovyEnabled(enabled);
2777 * Progress bars managed by the IProgressIndicator method.
2779 private Hashtable<Long, JPanel> progressBars;
2781 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2786 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2789 public void setProgressBar(String message, long id)
2791 if (progressBars == null)
2793 progressBars = new Hashtable<>();
2794 progressBarHandlers = new Hashtable<>();
2797 if (progressBars.get(Long.valueOf(id)) != null)
2799 JPanel panel = progressBars.remove(Long.valueOf(id));
2800 if (progressBarHandlers.contains(Long.valueOf(id)))
2802 progressBarHandlers.remove(Long.valueOf(id));
2804 removeProgressPanel(panel);
2808 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2815 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2816 * jalview.gui.IProgressIndicatorHandler)
2819 public void registerHandler(final long id,
2820 final IProgressIndicatorHandler handler)
2822 if (progressBarHandlers == null
2823 || !progressBars.containsKey(Long.valueOf(id)))
2825 throw new Error(MessageManager.getString(
2826 "error.call_setprogressbar_before_registering_handler"));
2828 progressBarHandlers.put(Long.valueOf(id), handler);
2829 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2830 if (handler.canCancel())
2832 JButton cancel = new JButton(
2833 MessageManager.getString("action.cancel"));
2834 final IProgressIndicator us = this;
2835 cancel.addActionListener(new ActionListener()
2839 public void actionPerformed(ActionEvent e)
2841 handler.cancelActivity(id);
2842 us.setProgressBar(MessageManager
2843 .formatMessage("label.cancelled_params", new Object[]
2844 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2848 progressPanel.add(cancel, BorderLayout.EAST);
2854 * @return true if any progress bars are still active
2857 public boolean operationInProgress()
2859 if (progressBars != null && progressBars.size() > 0)
2867 * This will return the first AlignFrame holding the given viewport instance.
2868 * It will break if there are more than one AlignFrames viewing a particular
2872 * @return alignFrame for viewport
2874 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2876 if (desktop != null)
2878 AlignmentPanel[] aps = getAlignmentPanels(
2879 viewport.getSequenceSetId());
2880 for (int panel = 0; aps != null && panel < aps.length; panel++)
2882 if (aps[panel] != null && aps[panel].av == viewport)
2884 return aps[panel].alignFrame;
2891 public VamsasApplication getVamsasApplication()
2893 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2899 * flag set if jalview GUI is being operated programmatically
2901 private boolean inBatchMode = false;
2904 * check if jalview GUI is being operated programmatically
2906 * @return inBatchMode
2908 public boolean isInBatchMode()
2914 * set flag if jalview GUI is being operated programmatically
2916 * @param inBatchMode
2918 public void setInBatchMode(boolean inBatchMode)
2920 this.inBatchMode = inBatchMode;
2924 * start service discovery and wait till it is done
2926 public void startServiceDiscovery()
2928 startServiceDiscovery(false);
2932 * start service discovery threads - blocking or non-blocking
2936 public void startServiceDiscovery(boolean blocking)
2938 startServiceDiscovery(blocking, false);
2942 * start service discovery threads
2945 * - false means call returns immediately
2946 * @param ignore_SHOW_JWS2_SERVICES_preference
2947 * - when true JABA services are discovered regardless of user's JWS2
2948 * discovery preference setting
2950 public void startServiceDiscovery(boolean blocking,
2951 boolean ignore_SHOW_JWS2_SERVICES_preference)
2953 boolean alive = true;
2954 Thread t0 = null, t1 = null, t2 = null;
2955 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2958 // todo: changesupport handlers need to be transferred
2959 if (discoverer == null)
2961 discoverer = new jalview.ws.jws1.Discoverer();
2962 // register PCS handler for desktop.
2963 discoverer.addPropertyChangeListener(changeSupport);
2965 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2966 // until we phase out completely
2967 (t0 = new Thread(discoverer)).start();
2970 if (ignore_SHOW_JWS2_SERVICES_preference
2971 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2973 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2974 .startDiscoverer(changeSupport);
2978 // TODO: do rest service discovery
2987 } catch (Exception e)
2990 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2991 || (t3 != null && t3.isAlive())
2992 || (t0 != null && t0.isAlive());
2998 * called to check if the service discovery process completed successfully.
3002 protected void JalviewServicesChanged(PropertyChangeEvent evt)
3004 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
3006 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3007 .getErrorMessages();
3010 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3012 if (serviceChangedDialog == null)
3014 // only run if we aren't already displaying one of these.
3015 addDialogThread(serviceChangedDialog = new Runnable()
3022 * JalviewDialog jd =new JalviewDialog() {
3024 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3026 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3028 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3030 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3032 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3033 * + " or mis-configured HTTP proxy settings.<br/>" +
3034 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3035 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3036 * true, true, "Web Service Configuration Problem", 450, 400);
3038 * jd.waitForInput();
3040 JvOptionPane.showConfirmDialog(Desktop.desktop,
3041 new JLabel("<html><table width=\"450\"><tr><td>"
3042 + ermsg + "</td></tr></table>"
3043 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3044 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3045 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3046 + " Tools->Preferences dialog box to change them.</p></html>"),
3047 "Web Service Configuration Problem",
3048 JvOptionPane.DEFAULT_OPTION,
3049 JvOptionPane.ERROR_MESSAGE);
3050 serviceChangedDialog = null;
3058 jalview.bin.Console.error(
3059 "Errors reported by JABA discovery service. Check web services preferences.\n"
3066 private Runnable serviceChangedDialog = null;
3069 * start a thread to open a URL in the configured browser. Pops up a warning
3070 * dialog to the user if there is an exception when calling out to the browser
3075 public static void showUrl(final String url)
3077 if (url != null && !url.trim().equals(""))
3079 jalview.bin.Console.info("Opening URL: " + url);
3080 showUrl(url, Desktop.instance);
3084 jalview.bin.Console.warn("Ignoring attempt to show an empty URL.");
3090 * Like showUrl but allows progress handler to be specified
3094 * (null) or object implementing IProgressIndicator
3096 public static void showUrl(final String url,
3097 final IProgressIndicator progress)
3099 new Thread(new Runnable()
3106 if (progress != null)
3108 progress.setProgressBar(MessageManager
3109 .formatMessage("status.opening_params", new Object[]
3110 { url }), this.hashCode());
3112 jalview.util.BrowserLauncher.openURL(url);
3113 } catch (Exception ex)
3115 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3117 .getString("label.web_browser_not_found_unix"),
3118 MessageManager.getString("label.web_browser_not_found"),
3119 JvOptionPane.WARNING_MESSAGE);
3121 ex.printStackTrace();
3123 if (progress != null)
3125 progress.setProgressBar(null, this.hashCode());
3131 public static WsParamSetManager wsparamManager = null;
3133 public static ParamManager getUserParameterStore()
3135 if (wsparamManager == null)
3137 wsparamManager = new WsParamSetManager();
3139 return wsparamManager;
3143 * static hyperlink handler proxy method for use by Jalview's internal windows
3147 public static void hyperlinkUpdate(HyperlinkEvent e)
3149 if (e.getEventType() == EventType.ACTIVATED)
3154 url = e.getURL().toString();
3155 Desktop.showUrl(url);
3156 } catch (Exception x)
3161 .error("Couldn't handle string " + url + " as a URL.");
3163 // ignore any exceptions due to dud links.
3170 * single thread that handles display of dialogs to user.
3172 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3175 * flag indicating if dialogExecutor should try to acquire a permit
3177 private volatile boolean dialogPause = true;
3182 private Semaphore block = new Semaphore(0);
3184 private static groovy.console.ui.Console groovyConsole;
3187 * add another dialog thread to the queue
3191 public void addDialogThread(final Runnable prompter)
3193 dialogExecutor.submit(new Runnable()
3200 acquireDialogQueue();
3202 if (instance == null)
3208 SwingUtilities.invokeAndWait(prompter);
3209 } catch (Exception q)
3211 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3218 private boolean dialogQueueStarted = false;
3220 public void startDialogQueue()
3222 if (dialogQueueStarted)
3226 // set the flag so we don't pause waiting for another permit and semaphore
3227 // the current task to begin
3228 releaseDialogQueue();
3229 dialogQueueStarted = true;
3232 public void acquireDialogQueue()
3238 } catch (InterruptedException e)
3240 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3245 public void releaseDialogQueue()
3252 dialogPause = false;
3256 * Outputs an image of the desktop to file in EPS format, after prompting the
3257 * user for choice of Text or Lineart character rendering (unless a preference
3258 * has been set). The file name is generated as
3261 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3265 protected void snapShotWindow_actionPerformed(ActionEvent e)
3267 // currently the menu option to do this is not shown
3270 int width = getWidth();
3271 int height = getHeight();
3273 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3274 ImageWriterI writer = new ImageWriterI()
3277 public void exportImage(Graphics g) throws Exception
3280 jalview.bin.Console.info("Successfully written snapshot to file "
3281 + of.getAbsolutePath());
3284 String title = "View of desktop";
3285 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3289 exporter.doExport(of, this, width, height, title);
3290 } catch (ImageOutputException ioex)
3292 jalview.bin.Console.error(
3293 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3299 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3300 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3301 * and location last time the view was expanded (if any). However it does not
3302 * remember the split pane divider location - this is set to match the
3303 * 'exploding' frame.
3307 public void explodeViews(SplitFrame sf)
3309 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3310 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3311 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3313 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3315 int viewCount = topPanels.size();
3322 * Processing in reverse order works, forwards order leaves the first panels not
3323 * visible. I don't know why!
3325 for (int i = viewCount - 1; i >= 0; i--)
3328 * Make new top and bottom frames. These take over the respective AlignmentPanel
3329 * objects, including their AlignmentViewports, so the cdna/protein
3330 * relationships between the viewports is carried over to the new split frames.
3332 * explodedGeometry holds the (x, y) position of the previously exploded
3333 * SplitFrame, and the (width, height) of the AlignFrame component
3335 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3336 AlignFrame newTopFrame = new AlignFrame(topPanel);
3337 newTopFrame.setSize(oldTopFrame.getSize());
3338 newTopFrame.setVisible(true);
3339 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3340 .getExplodedGeometry();
3341 if (geometry != null)
3343 newTopFrame.setSize(geometry.getSize());
3346 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3347 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3348 newBottomFrame.setSize(oldBottomFrame.getSize());
3349 newBottomFrame.setVisible(true);
3350 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3351 .getExplodedGeometry();
3352 if (geometry != null)
3354 newBottomFrame.setSize(geometry.getSize());
3357 topPanel.av.setGatherViewsHere(false);
3358 bottomPanel.av.setGatherViewsHere(false);
3359 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3361 if (geometry != null)
3363 splitFrame.setLocation(geometry.getLocation());
3365 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3369 * Clear references to the panels (now relocated in the new SplitFrames) before
3370 * closing the old SplitFrame.
3373 bottomPanels.clear();
3378 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3379 * back into the given SplitFrame as additional views. Note that the gathered
3380 * frames may themselves have multiple views.
3384 public void gatherViews(GSplitFrame source)
3387 * special handling of explodedGeometry for a view within a SplitFrame: - it
3388 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3389 * height) of the AlignFrame component
3391 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3392 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3393 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3394 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3395 myBottomFrame.viewport
3396 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3397 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3398 myTopFrame.viewport.setGatherViewsHere(true);
3399 myBottomFrame.viewport.setGatherViewsHere(true);
3400 String topViewId = myTopFrame.viewport.getSequenceSetId();
3401 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3403 JInternalFrame[] frames = desktop.getAllFrames();
3404 for (JInternalFrame frame : frames)
3406 if (frame instanceof SplitFrame && frame != source)
3408 SplitFrame sf = (SplitFrame) frame;
3409 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3410 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3411 boolean gatherThis = false;
3412 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3414 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3415 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3416 if (topViewId.equals(topPanel.av.getSequenceSetId())
3417 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3420 topPanel.av.setGatherViewsHere(false);
3421 bottomPanel.av.setGatherViewsHere(false);
3422 topPanel.av.setExplodedGeometry(
3423 new Rectangle(sf.getLocation(), topFrame.getSize()));
3424 bottomPanel.av.setExplodedGeometry(
3425 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3426 myTopFrame.addAlignmentPanel(topPanel, false);
3427 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3433 topFrame.getAlignPanels().clear();
3434 bottomFrame.getAlignPanels().clear();
3441 * The dust settles...give focus to the tab we did this from.
3443 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3446 public static groovy.console.ui.Console getGroovyConsole()
3448 return groovyConsole;
3452 * handles the payload of a drag and drop event.
3454 * TODO refactor to desktop utilities class
3457 * - Data source strings extracted from the drop event
3459 * - protocol for each data source extracted from the drop event
3463 * - the payload from the drop event
3466 public static void transferFromDropTarget(List<Object> files,
3467 List<DataSourceType> protocols, DropTargetDropEvent evt,
3468 Transferable t) throws Exception
3471 DataFlavor uriListFlavor = new DataFlavor(
3472 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3475 urlFlavour = new DataFlavor(
3476 "application/x-java-url; class=java.net.URL");
3477 } catch (ClassNotFoundException cfe)
3479 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3483 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3488 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3489 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3490 // means url may be null.
3493 protocols.add(DataSourceType.URL);
3494 files.add(url.toString());
3495 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3496 + files.get(files.size() - 1));
3501 if (Platform.isAMacAndNotJS())
3503 jalview.bin.Console.errPrintln(
3504 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3507 } catch (Throwable ex)
3509 jalview.bin.Console.debug("URL drop handler failed.", ex);
3512 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3514 // Works on Windows and MacOSX
3515 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3516 for (Object file : (List) t
3517 .getTransferData(DataFlavor.javaFileListFlavor))
3520 protocols.add(DataSourceType.FILE);
3525 // Unix like behaviour
3526 boolean added = false;
3528 if (t.isDataFlavorSupported(uriListFlavor))
3530 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3531 // This is used by Unix drag system
3532 data = (String) t.getTransferData(uriListFlavor);
3536 // fallback to text: workaround - on OSX where there's a JVM bug
3538 .debug("standard URIListFlavor failed. Trying text");
3539 // try text fallback
3540 DataFlavor textDf = new DataFlavor(
3541 "text/plain;class=java.lang.String");
3542 if (t.isDataFlavorSupported(textDf))
3544 data = (String) t.getTransferData(textDf);
3547 jalview.bin.Console.debug("Plain text drop content returned "
3548 + (data == null ? "Null - failed" : data));
3553 while (protocols.size() < files.size())
3555 jalview.bin.Console.debug("Adding missing FILE protocol for "
3556 + files.get(protocols.size()));
3557 protocols.add(DataSourceType.FILE);
3559 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3560 data, "\r\n"); st.hasMoreTokens();)
3563 String s = st.nextToken();
3564 if (s.startsWith("#"))
3566 // the line is a comment (as per the RFC 2483)
3569 java.net.URI uri = new java.net.URI(s);
3570 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3572 protocols.add(DataSourceType.URL);
3573 files.add(uri.toString());
3577 // otherwise preserve old behaviour: catch all for file objects
3578 java.io.File file = new java.io.File(uri);
3579 protocols.add(DataSourceType.FILE);
3580 files.add(file.toString());
3585 if (jalview.bin.Console.isDebugEnabled())
3587 if (data == null || !added)
3590 if (t.getTransferDataFlavors() != null
3591 && t.getTransferDataFlavors().length > 0)
3593 jalview.bin.Console.debug(
3594 "Couldn't resolve drop data. Here are the supported flavors:");
3595 for (DataFlavor fl : t.getTransferDataFlavors())
3597 jalview.bin.Console.debug(
3598 "Supported transfer dataflavor: " + fl.toString());
3599 Object df = t.getTransferData(fl);
3602 jalview.bin.Console.debug("Retrieves: " + df);
3606 jalview.bin.Console.debug("Retrieved nothing");
3613 .debug("Couldn't resolve dataflavor for drop: "
3619 if (Platform.isWindowsAndNotJS())
3622 .debug("Scanning dropped content for Windows Link Files");
3624 // resolve any .lnk files in the file drop
3625 for (int f = 0; f < files.size(); f++)
3627 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3628 if (protocols.get(f).equals(DataSourceType.FILE)
3629 && (source.endsWith(".lnk") || source.endsWith(".url")
3630 || source.endsWith(".site")))
3634 Object obj = files.get(f);
3635 File lf = (obj instanceof File ? (File) obj
3636 : new File((String) obj));
3637 // process link file to get a URL
3638 jalview.bin.Console.debug("Found potential link file: " + lf);
3639 WindowsShortcut wscfile = new WindowsShortcut(lf);
3640 String fullname = wscfile.getRealFilename();
3641 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3642 files.set(f, fullname);
3643 jalview.bin.Console.debug("Parsed real filename " + fullname
3644 + " to extract protocol: " + protocols.get(f));
3645 } catch (Exception ex)
3647 jalview.bin.Console.error(
3648 "Couldn't parse " + files.get(f) + " as a link file.",
3657 * Sets the Preferences property for experimental features to True or False
3658 * depending on the state of the controlling menu item
3661 protected void showExperimental_actionPerformed(boolean selected)
3663 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3667 * Answers a (possibly empty) list of any structure viewer frames (currently
3668 * for either Jmol or Chimera) which are currently open. This may optionally
3669 * be restricted to viewers of a specified class, or viewers linked to a
3670 * specified alignment panel.
3673 * if not null, only return viewers linked to this panel
3674 * @param structureViewerClass
3675 * if not null, only return viewers of this class
3678 public List<StructureViewerBase> getStructureViewers(
3679 AlignmentPanel apanel,
3680 Class<? extends StructureViewerBase> structureViewerClass)
3682 List<StructureViewerBase> result = new ArrayList<>();
3683 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3685 for (JInternalFrame frame : frames)
3687 if (frame instanceof StructureViewerBase)
3689 if (structureViewerClass == null
3690 || structureViewerClass.isInstance(frame))
3693 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3695 result.add((StructureViewerBase) frame);
3703 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3705 private static boolean debugScaleMessageDone = false;
3707 public static void debugScaleMessage(Graphics g)
3709 if (debugScaleMessageDone)
3713 // output used by tests to check HiDPI scaling settings in action
3716 Graphics2D gg = (Graphics2D) g;
3719 AffineTransform t = gg.getTransform();
3720 double scaleX = t.getScaleX();
3721 double scaleY = t.getScaleY();
3722 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3723 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3724 debugScaleMessageDone = true;
3728 jalview.bin.Console.debug("Desktop graphics null");
3730 } catch (Exception e)
3732 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3737 * closes the current instance window, but leaves the JVM running. Bypasses
3738 * any shutdown prompts, but does not set window dispose on close in case JVM
3741 public static void closeDesktop()
3743 if (Desktop.instance != null)
3745 Desktop us = Desktop.instance;
3746 Desktop.instance.quitTheDesktop(false, false);
3747 // call dispose in a separate thread - try to avoid indirect deadlocks
3750 new Thread(new Runnable()
3755 ExecutorService dex = us.dialogExecutor;
3759 us.dialogExecutor = null;
3760 us.block.drainPermits();
3770 * checks if any progress bars are being displayed in any of the windows
3771 * managed by the desktop
3775 public boolean operationsAreInProgress()
3777 JInternalFrame[] frames = getAllFrames();
3778 for (JInternalFrame frame : frames)
3780 if (frame instanceof IProgressIndicator)
3782 if (((IProgressIndicator) frame).operationInProgress())
3788 return operationInProgress();
3792 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3793 * The way the modal JInternalFrame is made means it cannot be a child of an
3794 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3796 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3798 protected static void addModal(AlignFrame af, JInternalFrame jif)
3800 alignFrameModalMap.put(af, jif);
3803 protected static void closeModal(AlignFrame af)
3805 if (!alignFrameModalMap.containsKey(af))
3809 JInternalFrame jif = alignFrameModalMap.get(af);
3814 jif.setClosed(true);
3815 } catch (PropertyVetoException e)
3817 e.printStackTrace();
3820 alignFrameModalMap.remove(af);
3823 public void nonBlockingDialog(String title, String message, String button,
3824 int type, boolean scrollable, boolean modal)
3826 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3830 public void nonBlockingDialog(String title, String message,
3831 String boxtext, String button, int type, boolean scrollable,
3832 boolean html, boolean modal, int timeout)
3834 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3835 scrollable, html, modal, timeout);
3838 public void nonBlockingDialog(int width, int height, String title,
3839 String message, String boxtext, String button, int type,
3840 boolean scrollable, boolean html, boolean modal, int timeout)
3844 type = JvOptionPane.WARNING_MESSAGE;
3846 JLabel jl = new JLabel(message);
3848 JTextComponent jtc = null;
3851 JTextPane jtp = new JTextPane();
3852 jtp.setContentType("text/html");
3853 jtp.setEditable(false);
3854 jtp.setAutoscrolls(true);
3855 jtp.setText(boxtext);
3861 JTextArea jta = new JTextArea(height, width);
3862 // jta.setLineWrap(true);
3863 jta.setEditable(false);
3864 jta.setWrapStyleWord(true);
3865 jta.setAutoscrolls(true);
3866 jta.setText(boxtext);
3871 JScrollPane jsp = scrollable
3872 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3873 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3876 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3878 JPanel jp = new JPanel();
3879 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3881 if (message != null)
3883 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3886 if (boxtext != null)
3890 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3895 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3900 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3902 jvp.setTimeout(timeout);
3903 JButton jb = new JButton(button);
3904 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3906 { button }, button, modal, new JButton[] { jb }, false);
3910 public AlignFrame getCurrentAlignFrame()
3912 return Jalview.getInstance().getCurrentAlignFrame();