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 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
685 ssm.setAddTempFacAnnot(
686 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
687 ssm.setProcessSecondaryStructure(
688 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
689 // JAL-3915 - RNAView is no longer an option so this has no effect
690 ssm.setSecStructServices(
691 Cache.getDefault(Preferences.USE_RNAVIEW, false));
695 ssm.setAddTempFacAnnot(false);
696 ssm.setProcessSecondaryStructure(false);
697 ssm.setSecStructServices(false);
701 public void checkForNews()
703 final Desktop me = this;
704 // Thread off the news reader, in case there are connection problems.
705 new Thread(new Runnable()
710 jalview.bin.Console.debug("Starting news thread.");
711 jvnews = new BlogReader(me);
712 showNews.setVisible(true);
713 jalview.bin.Console.debug("Completed news thread.");
718 public void getIdentifiersOrgData()
720 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
721 {// Thread off the identifiers fetcher
722 new Thread(new Runnable()
728 .debug("Downloading data from identifiers.org");
731 UrlDownloadClient.download(IdOrgSettings.getUrl(),
732 IdOrgSettings.getDownloadLocation());
733 } catch (IOException e)
736 .debug("Exception downloading identifiers.org data"
746 protected void showNews_actionPerformed(ActionEvent e)
748 showNews(showNews.isSelected());
751 void showNews(boolean visible)
753 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
754 showNews.setSelected(visible);
755 if (visible && !jvnews.isVisible())
757 new Thread(new Runnable()
762 long now = System.currentTimeMillis();
763 Desktop.instance.setProgressBar(
764 MessageManager.getString("status.refreshing_news"), now);
765 jvnews.refreshNews();
766 Desktop.instance.setProgressBar(null, now);
774 * recover the last known dimensions for a jalview window
777 * - empty string is desktop, all other windows have unique prefix
778 * @return null or last known dimensions scaled to current geometry (if last
779 * window geom was known)
781 Rectangle getLastKnownDimensions(String windowName)
783 // TODO: lock aspect ratio for scaling desktop Bug #0058199
784 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
785 String x = Cache.getProperty(windowName + "SCREEN_X");
786 String y = Cache.getProperty(windowName + "SCREEN_Y");
787 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
788 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
789 if ((x != null) && (y != null) && (width != null) && (height != null))
791 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
792 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
793 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
795 // attempt #1 - try to cope with change in screen geometry - this
796 // version doesn't preserve original jv aspect ratio.
797 // take ratio of current screen size vs original screen size.
798 double sw = ((1f * screenSize.width) / (1f * Integer
799 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
800 double sh = ((1f * screenSize.height) / (1f * Integer
801 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
802 // rescale the bounds depending upon the current screen geometry.
803 ix = (int) (ix * sw);
804 iw = (int) (iw * sw);
805 iy = (int) (iy * sh);
806 ih = (int) (ih * sh);
807 if (ix >= screenSize.width)
809 jalview.bin.Console.debug(
810 "Window geometry location recall error: shifting horizontal to within screenbounds.");
811 ix = ix % screenSize.width;
813 if (iy >= screenSize.height)
815 jalview.bin.Console.debug(
816 "Window geometry location recall error: shifting vertical to within screenbounds.");
817 iy = iy % screenSize.height;
819 jalview.bin.Console.debug(
820 "Got last known dimensions for " + windowName + ": x:" + ix
821 + " y:" + iy + " width:" + iw + " height:" + ih);
823 // return dimensions for new instance
824 return new Rectangle(ix, iy, iw, ih);
829 void showPasteMenu(int x, int y)
831 JPopupMenu popup = new JPopupMenu();
832 JMenuItem item = new JMenuItem(
833 MessageManager.getString("label.paste_new_window"));
834 item.addActionListener(new ActionListener()
837 public void actionPerformed(ActionEvent evt)
844 popup.show(this, x, y);
849 // quick patch for JAL-4150 - needs some more work and test coverage
850 // TODO - unify below and AlignFrame.paste()
851 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
852 // clipboard has come from a different alignment window than the one where
853 // paste has been called! JAL-4151
855 if (Desktop.jalviewClipboard != null)
857 // The clipboard was filled from within Jalview, we must use the
859 // And dataset from the copied alignment
860 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
861 // be doubly sure that we create *new* sequence objects.
862 SequenceI[] sequences = new SequenceI[newseq.length];
863 for (int i = 0; i < newseq.length; i++)
865 sequences[i] = new Sequence(newseq[i]);
867 Alignment alignment = new Alignment(sequences);
868 // dataset is inherited
869 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
870 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
871 AlignFrame.DEFAULT_HEIGHT);
872 String newtitle = new String("Copied sequences");
874 if (Desktop.jalviewClipboard[2] != null)
876 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
877 af.viewport.setHiddenColumns(hc);
880 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
881 AlignFrame.DEFAULT_HEIGHT);
888 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
889 Transferable contents = c.getContents(this);
891 if (contents != null)
893 String file = (String) contents
894 .getTransferData(DataFlavor.stringFlavor);
896 FileFormatI format = new IdentifyFile().identify(file,
897 DataSourceType.PASTE);
899 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
902 } catch (Exception ex)
904 jalview.bin.Console.outPrintln(
905 "Unable to paste alignment from system clipboard:\n" + ex);
911 * Adds and opens the given frame to the desktop
922 public static synchronized void addInternalFrame(
923 final JInternalFrame frame, String title, int w, int h)
925 addInternalFrame(frame, title, true, w, h, true, false);
929 * Add an internal frame to the Jalview desktop
936 * When true, display frame immediately, otherwise, caller must call
937 * setVisible themselves.
943 public static synchronized void addInternalFrame(
944 final JInternalFrame frame, String title, boolean makeVisible,
947 addInternalFrame(frame, title, makeVisible, w, h, true, false);
951 * Add an internal frame to the Jalview desktop and make it visible
964 public static synchronized void addInternalFrame(
965 final JInternalFrame frame, String title, int w, int h,
968 addInternalFrame(frame, title, true, w, h, resizable, false);
972 * Add an internal frame to the Jalview desktop
979 * When true, display frame immediately, otherwise, caller must call
980 * setVisible themselves.
987 * @param ignoreMinSize
988 * Do not set the default minimum size for frame
990 public static synchronized void addInternalFrame(
991 final JInternalFrame frame, String title, boolean makeVisible,
992 int w, int h, boolean resizable, boolean ignoreMinSize)
995 // TODO: allow callers to determine X and Y position of frame (eg. via
997 // TODO: consider fixing method to update entries in the window submenu with
998 // the current window title
1000 frame.setTitle(title);
1001 if (frame.getWidth() < 1 || frame.getHeight() < 1)
1003 frame.setSize(w, h);
1005 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
1006 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
1007 // IF JALVIEW IS RUNNING HEADLESS
1008 // ///////////////////////////////////////////////
1009 if (instance == null || (System.getProperty("java.awt.headless") != null
1010 && System.getProperty("java.awt.headless").equals("true")))
1019 frame.setMinimumSize(
1020 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1022 // Set default dimension for Alignment Frame window.
1023 // The Alignment Frame window could be added from a number of places,
1025 // I did this here in order not to miss out on any Alignment frame.
1026 if (frame instanceof AlignFrame)
1028 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1029 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1033 frame.setVisible(makeVisible);
1034 frame.setClosable(true);
1035 frame.setResizable(resizable);
1036 frame.setMaximizable(resizable);
1037 frame.setIconifiable(resizable);
1038 frame.setOpaque(Platform.isJS());
1040 if (frame.getX() < 1 && frame.getY() < 1)
1042 frame.setLocation(xOffset * openFrameCount,
1043 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1047 * add an entry for the new frame in the Window menu (and remove it when the
1050 final JMenuItem menuItem = new JMenuItem(title);
1051 frame.addInternalFrameListener(new InternalFrameAdapter()
1054 public void internalFrameActivated(InternalFrameEvent evt)
1056 JInternalFrame itf = desktop.getSelectedFrame();
1059 if (itf instanceof AlignFrame)
1061 Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf);
1068 public void internalFrameClosed(InternalFrameEvent evt)
1070 PaintRefresher.RemoveComponent(frame);
1073 * defensive check to prevent frames being added half off the window
1075 if (openFrameCount > 0)
1081 * ensure no reference to alignFrame retained by menu item listener
1083 if (menuItem.getActionListeners().length > 0)
1085 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1087 windowMenu.remove(menuItem);
1091 menuItem.addActionListener(new ActionListener()
1094 public void actionPerformed(ActionEvent e)
1098 frame.setSelected(true);
1099 frame.setIcon(false);
1100 } catch (java.beans.PropertyVetoException ex)
1107 setKeyBindings(frame);
1109 // Since the latest FlatLaf patch, we occasionally have problems showing
1110 // structureViewer frames...
1112 boolean shown = false;
1113 Exception last = null;
1120 } catch (IllegalArgumentException iaex)
1124 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1125 + tries + " left) for " + frame.getTitle(), iaex);
1129 } catch (InterruptedException iex)
1134 } while (!shown && tries > 0);
1137 jalview.bin.Console.error(
1138 "Serious Problem whilst showing window " + frame.getTitle(),
1142 windowMenu.add(menuItem);
1147 frame.setSelected(true);
1148 frame.requestFocus();
1149 } catch (java.beans.PropertyVetoException ve)
1151 } catch (java.lang.ClassCastException cex)
1153 jalview.bin.Console.warn(
1154 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1160 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1165 private static void setKeyBindings(JInternalFrame frame)
1167 @SuppressWarnings("serial")
1168 final Action closeAction = new AbstractAction()
1171 public void actionPerformed(ActionEvent e)
1178 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1180 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1181 InputEvent.CTRL_DOWN_MASK);
1182 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1183 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1185 InputMap inputMap = frame
1186 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1187 String ctrlW = ctrlWKey.toString();
1188 inputMap.put(ctrlWKey, ctrlW);
1189 inputMap.put(cmdWKey, ctrlW);
1191 ActionMap actionMap = frame.getActionMap();
1192 actionMap.put(ctrlW, closeAction);
1196 public void lostOwnership(Clipboard clipboard, Transferable contents)
1200 Desktop.jalviewClipboard = null;
1203 internalCopy = false;
1207 public void dragEnter(DropTargetDragEvent evt)
1212 public void dragExit(DropTargetEvent evt)
1217 public void dragOver(DropTargetDragEvent evt)
1222 public void dropActionChanged(DropTargetDragEvent evt)
1233 public void drop(DropTargetDropEvent evt)
1235 boolean success = true;
1236 // JAL-1552 - acceptDrop required before getTransferable call for
1237 // Java's Transferable for native dnd
1238 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1239 Transferable t = evt.getTransferable();
1240 List<Object> files = new ArrayList<>();
1241 List<DataSourceType> protocols = new ArrayList<>();
1245 Desktop.transferFromDropTarget(files, protocols, evt, t);
1246 } catch (Exception e)
1248 e.printStackTrace();
1256 for (int i = 0; i < files.size(); i++)
1258 // BH 2018 File or String
1259 Object file = files.get(i);
1260 String fileName = file.toString();
1261 DataSourceType protocol = (protocols == null)
1262 ? DataSourceType.FILE
1264 FileFormatI format = null;
1266 if (fileName.endsWith(".jar"))
1268 format = FileFormat.Jalview;
1273 format = new IdentifyFile().identify(file, protocol);
1275 if (file instanceof File)
1277 Platform.cacheFileData((File) file);
1279 new FileLoader().LoadFile(null, file, protocol, format);
1282 } catch (Exception ex)
1287 evt.dropComplete(success); // need this to ensure input focus is properly
1288 // transfered to any new windows created
1298 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1300 String fileFormat = FileLoader.getUseDefaultFileFormat()
1301 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1303 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1304 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1305 BackupFiles.getEnabled());
1307 chooser.setFileView(new JalviewFileView());
1308 chooser.setDialogTitle(
1309 MessageManager.getString("label.open_local_file"));
1310 chooser.setToolTipText(MessageManager.getString("action.open"));
1312 chooser.setResponseHandler(0, () -> {
1313 File selectedFile = chooser.getSelectedFile();
1314 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1316 FileFormatI format = chooser.getSelectedFormat();
1319 * Call IdentifyFile to verify the file contains what its extension implies.
1320 * Skip this step for dynamically added file formats, because IdentifyFile does
1321 * not know how to recognise them.
1323 if (FileFormats.getInstance().isIdentifiable(format))
1327 format = new IdentifyFile().identify(selectedFile,
1328 DataSourceType.FILE);
1329 } catch (FileFormatException e)
1331 // format = null; //??
1335 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1338 chooser.showOpenDialog(this);
1342 * Shows a dialog for input of a URL at which to retrieve alignment data
1347 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1349 // This construct allows us to have a wider textfield
1351 JLabel label = new JLabel(
1352 MessageManager.getString("label.input_file_url"));
1354 JPanel panel = new JPanel(new GridLayout(2, 1));
1358 * the URL to fetch is input in Java: an editable combobox with history JS:
1359 * (pending JAL-3038) a plain text field
1362 String urlBase = "https://www.";
1363 if (Platform.isJS())
1365 history = new JTextField(urlBase, 35);
1374 JComboBox<String> asCombo = new JComboBox<>();
1375 asCombo.setPreferredSize(new Dimension(400, 20));
1376 asCombo.setEditable(true);
1377 asCombo.addItem(urlBase);
1378 String historyItems = Cache.getProperty("RECENT_URL");
1379 if (historyItems != null)
1381 for (String token : historyItems.split("\\t"))
1383 asCombo.addItem(token);
1390 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1391 MessageManager.getString("action.cancel") };
1392 Runnable action = () -> {
1393 @SuppressWarnings("unchecked")
1394 String url = (history instanceof JTextField
1395 ? ((JTextField) history).getText()
1396 : ((JComboBox<String>) history).getEditor().getItem()
1397 .toString().trim());
1399 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1401 if (viewport != null)
1403 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1404 FileFormat.Jalview);
1408 new FileLoader().LoadFile(url, DataSourceType.URL,
1409 FileFormat.Jalview);
1414 FileFormatI format = null;
1417 format = new IdentifyFile().identify(url, DataSourceType.URL);
1418 } catch (FileFormatException e)
1420 // TODO revise error handling, distinguish between
1421 // URL not found and response not valid
1426 String msg = MessageManager.formatMessage("label.couldnt_locate",
1428 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1429 MessageManager.getString("label.url_not_found"),
1430 JvOptionPane.WARNING_MESSAGE);
1434 if (viewport != null)
1436 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1441 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1445 String dialogOption = MessageManager
1446 .getString("label.input_alignment_from_url");
1447 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1448 .showInternalDialog(panel, dialogOption,
1449 JvOptionPane.YES_NO_CANCEL_OPTION,
1450 JvOptionPane.PLAIN_MESSAGE, null, options,
1451 MessageManager.getString("action.ok"));
1455 * Opens the CutAndPaste window for the user to paste an alignment in to
1458 * - if not null, the pasted alignment is added to the current
1459 * alignment; if null, to a new alignment window
1462 public void inputTextboxMenuItem_actionPerformed(
1463 AlignmentViewPanel viewPanel)
1465 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1466 cap.setForInput(viewPanel);
1467 Desktop.addInternalFrame(cap,
1468 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1473 * Check with user and saving files before actually quitting
1475 public void desktopQuit()
1477 desktopQuit(true, false);
1481 * close everything, stash window geometries, and shut down all associated
1485 * - sets the dispose on close flag - JVM may terminate when set
1486 * @param terminateJvm
1487 * - quit with prejudice - stops the JVM.
1489 public void quitTheDesktop(boolean dispose, boolean terminateJvm)
1491 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1492 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1493 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1494 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1495 getWidth(), getHeight()));
1497 if (jconsole != null)
1499 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1500 jconsole.stopConsole();
1505 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1508 // Frames should all close automatically. Keeping external
1509 // viewers open should already be decided by user.
1510 closeAll_actionPerformed(null);
1512 if (dialogExecutor != null)
1514 dialogExecutor.shutdownNow();
1517 if (groovyConsole != null)
1519 // suppress a possible repeat prompt to save script
1520 groovyConsole.setDirty(false);
1523 if (((Window) groovyConsole.getFrame()) != null
1524 && ((Window) groovyConsole.getFrame()).isVisible())
1526 // console is visible -- FIXME JAL-4327
1527 groovyConsole.exit();
1531 // console is not, so just let it dispose itself when we shutdown
1532 // we don't call groovyConsole.exit() because it calls the shutdown
1533 // handler with invokeAndWait() causing deadlock
1534 groovyConsole = null;
1540 // note that shutdown hook will not be run
1541 jalview.bin.Console.debug("Force Quit selected by user");
1542 Runtime.getRuntime().halt(0);
1545 jalview.bin.Console.debug("Quit selected by user");
1548 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1549 // instance.dispose();
1553 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1555 final Runnable doDesktopQuit = () -> {
1557 // FIRST !! check for aborted quit
1558 if (QuitHandler.quitCancelled())
1561 .debug("Quit was cancelled - Desktop aborting quit");
1565 // Proceed with quitting
1566 quitTheDesktop(disposeFlag,
1567 QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT);
1572 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1573 QuitHandler.defaultCancelQuit);
1577 * Exits the program and the JVM.
1579 * Don't call this directly
1581 * - use desktopQuit() above to tidy up first.
1583 * - use closeDesktop() to shutdown Jalview without shutting down the JVM
1589 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1590 // not run a second time if gotQuitResponse flag has been set (i.e. user
1591 // confirmed quit of some kind).
1592 Jalview.exit("Desktop exiting.", ExitCode.OK);
1595 private void storeLastKnownDimensions(String string, Rectangle jc)
1597 jalview.bin.Console.debug("Storing last known dimensions for " + string
1598 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1599 + " height:" + jc.height);
1601 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1602 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1603 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1604 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1614 public void aboutMenuItem_actionPerformed(ActionEvent e)
1616 new Thread(new Runnable()
1621 new SplashScreen(false);
1627 * Returns the html text for the About screen, including any available version
1628 * number, build details, author details and citation reference, but without
1629 * the enclosing {@code html} tags
1633 public String getAboutMessage()
1635 StringBuilder message = new StringBuilder(1024);
1636 message.append("<div style=\"font-family: sans-serif;\">")
1637 .append("<h1><strong>Version: ")
1638 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1639 .append("<strong>Built: <em>")
1640 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1641 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1642 .append("</strong>");
1644 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1645 if (latestVersion.equals("Checking"))
1647 // JBP removed this message for 2.11: May be reinstated in future version
1648 // message.append("<br>...Checking latest version...</br>");
1650 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1652 boolean red = false;
1653 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1654 .indexOf("automated build") == -1)
1657 // Displayed when code version and jnlp version do not match and code
1658 // version is not a development build
1659 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1662 message.append("<br>!! Version ")
1663 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1664 .append(" is available for download from ")
1665 .append(Cache.getDefault("www.jalview.org",
1666 "https://www.jalview.org"))
1670 message.append("</div>");
1673 message.append("<br>Authors: ");
1674 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1675 message.append(CITATION);
1677 message.append("</div>");
1679 return message.toString();
1683 * Action on requesting Help documentation
1686 public void documentationMenuItem_actionPerformed()
1690 if (Platform.isJS())
1692 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1701 Help.showHelpWindow();
1703 } catch (Exception ex)
1706 .errPrintln("Error opening help: " + ex.getMessage());
1711 public void closeAll_actionPerformed(ActionEvent e)
1713 // TODO show a progress bar while closing?
1714 JInternalFrame[] frames = desktop.getAllFrames();
1715 for (int i = 0; i < frames.length; i++)
1719 frames[i].setClosed(true);
1720 } catch (java.beans.PropertyVetoException ex)
1724 Jalview.getInstance().setCurrentAlignFrame(null);
1725 jalview.bin.Console.info("ALL CLOSED");
1728 * reset state of singleton objects as appropriate (clear down session state
1729 * when all windows are closed)
1731 StructureSelectionManager ssm = StructureSelectionManager
1732 .getStructureSelectionManager(this);
1739 public int structureViewersStillRunningCount()
1742 JInternalFrame[] frames = desktop.getAllFrames();
1743 for (int i = 0; i < frames.length; i++)
1745 if (frames[i] != null
1746 && frames[i] instanceof JalviewStructureDisplayI)
1748 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1756 public void raiseRelated_actionPerformed(ActionEvent e)
1758 reorderAssociatedWindows(false, false);
1762 public void minimizeAssociated_actionPerformed(ActionEvent e)
1764 reorderAssociatedWindows(true, false);
1767 void closeAssociatedWindows()
1769 reorderAssociatedWindows(false, true);
1775 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1779 protected void garbageCollect_actionPerformed(ActionEvent e)
1781 // We simply collect the garbage
1782 jalview.bin.Console.debug("Collecting garbage...");
1784 jalview.bin.Console.debug("Finished garbage collection.");
1790 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1794 protected void showMemusage_actionPerformed(ActionEvent e)
1796 desktop.showMemoryUsage(showMemusage.isSelected());
1803 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1807 protected void showConsole_actionPerformed(ActionEvent e)
1809 showConsole(showConsole.isSelected());
1812 Console jconsole = null;
1815 * control whether the java console is visible or not
1819 void showConsole(boolean selected)
1821 // TODO: decide if we should update properties file
1822 if (jconsole != null) // BH 2018
1824 showConsole.setSelected(selected);
1825 Cache.setProperty("SHOW_JAVA_CONSOLE",
1826 Boolean.valueOf(selected).toString());
1827 jconsole.setVisible(selected);
1831 void reorderAssociatedWindows(boolean minimize, boolean close)
1833 JInternalFrame[] frames = desktop.getAllFrames();
1834 if (frames == null || frames.length < 1)
1839 AlignmentViewport source = null, target = null;
1840 if (frames[0] instanceof AlignFrame)
1842 source = ((AlignFrame) frames[0]).getCurrentView();
1844 else if (frames[0] instanceof TreePanel)
1846 source = ((TreePanel) frames[0]).getViewPort();
1848 else if (frames[0] instanceof PCAPanel)
1850 source = ((PCAPanel) frames[0]).av;
1852 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1854 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1859 for (int i = 0; i < frames.length; i++)
1862 if (frames[i] == null)
1866 if (frames[i] instanceof AlignFrame)
1868 target = ((AlignFrame) frames[i]).getCurrentView();
1870 else if (frames[i] instanceof TreePanel)
1872 target = ((TreePanel) frames[i]).getViewPort();
1874 else if (frames[i] instanceof PCAPanel)
1876 target = ((PCAPanel) frames[i]).av;
1878 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1880 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1883 if (source == target)
1889 frames[i].setClosed(true);
1893 frames[i].setIcon(minimize);
1896 frames[i].toFront();
1900 } catch (java.beans.PropertyVetoException ex)
1915 protected void preferences_actionPerformed(ActionEvent e)
1917 Preferences.openPreferences();
1921 * Prompts the user to choose a file and then saves the Jalview state as a
1922 * Jalview project file
1925 public void saveState_actionPerformed()
1927 saveState_actionPerformed(false);
1930 public void saveState_actionPerformed(boolean saveAs)
1932 java.io.File projectFile = getProjectFile();
1933 // autoSave indicates we already have a file and don't need to ask
1934 boolean autoSave = projectFile != null && !saveAs
1935 && BackupFiles.getEnabled();
1937 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1938 // projectFile='"+projectFile+"',
1939 // saveAs="+saveAs+", Backups
1940 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1942 boolean approveSave = false;
1945 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1948 chooser.setFileView(new JalviewFileView());
1949 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1951 int value = chooser.showSaveDialog(this);
1953 if (value == JalviewFileChooser.APPROVE_OPTION)
1955 projectFile = chooser.getSelectedFile();
1956 setProjectFile(projectFile);
1961 if (approveSave || autoSave)
1963 final Desktop me = this;
1964 final java.io.File chosenFile = projectFile;
1965 new Thread(new Runnable()
1970 // TODO: refactor to Jalview desktop session controller action.
1971 setProgressBar(MessageManager.formatMessage(
1972 "label.saving_jalview_project", new Object[]
1973 { chosenFile.getName() }), chosenFile.hashCode());
1974 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1975 // TODO catch and handle errors for savestate
1976 // TODO prevent user from messing with the Desktop whilst we're saving
1979 boolean doBackup = BackupFiles.getEnabled();
1980 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1983 new Jalview2XML().saveState(
1984 doBackup ? backupfiles.getTempFile() : chosenFile);
1988 backupfiles.setWriteSuccess(true);
1989 backupfiles.rollBackupsAndRenameTempFile();
1991 } catch (OutOfMemoryError oom)
1993 new OOMWarning("Whilst saving current state to "
1994 + chosenFile.getName(), oom);
1995 } catch (Exception ex)
1997 jalview.bin.Console.error("Problems whilst trying to save to "
1998 + chosenFile.getName(), ex);
1999 JvOptionPane.showMessageDialog(me,
2000 MessageManager.formatMessage(
2001 "label.error_whilst_saving_current_state_to",
2003 { chosenFile.getName() }),
2004 MessageManager.getString("label.couldnt_save_project"),
2005 JvOptionPane.WARNING_MESSAGE);
2007 setProgressBar(null, chosenFile.hashCode());
2014 public void saveAsState_actionPerformed(ActionEvent e)
2016 saveState_actionPerformed(true);
2019 protected void setProjectFile(File choice)
2021 this.projectFile = choice;
2024 public File getProjectFile()
2026 return this.projectFile;
2030 * Shows a file chooser dialog and tries to read in the selected file as a
2034 public void loadState_actionPerformed()
2036 final String[] suffix = new String[] { "jvp", "jar" };
2037 final String[] desc = new String[] { "Jalview Project",
2038 "Jalview Project (old)" };
2039 JalviewFileChooser chooser = new JalviewFileChooser(
2040 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2041 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2045 chooser.setFileView(new JalviewFileView());
2046 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2047 chooser.setResponseHandler(0, () -> {
2048 File selectedFile = chooser.getSelectedFile();
2049 setProjectFile(selectedFile);
2050 String choice = selectedFile.getAbsolutePath();
2051 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2052 new Thread(new Runnable()
2059 new Jalview2XML().loadJalviewAlign(selectedFile);
2060 } catch (OutOfMemoryError oom)
2062 new OOMWarning("Whilst loading project from " + choice, oom);
2063 } catch (Exception ex)
2065 jalview.bin.Console.error(
2066 "Problems whilst loading project from " + choice, ex);
2067 JvOptionPane.showMessageDialog(Desktop.desktop,
2068 MessageManager.formatMessage(
2069 "label.error_whilst_loading_project_from",
2072 MessageManager.getString("label.couldnt_load_project"),
2073 JvOptionPane.WARNING_MESSAGE);
2076 }, "Project Loader").start();
2079 chooser.showOpenDialog(this);
2083 public void inputSequence_actionPerformed(ActionEvent e)
2085 new SequenceFetcher(this);
2088 JPanel progressPanel;
2090 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2092 public void startLoading(final Object fileName)
2094 if (fileLoadingCount == 0)
2096 fileLoadingPanels.add(addProgressPanel(MessageManager
2097 .formatMessage("label.loading_file", new Object[]
2103 private JPanel addProgressPanel(String string)
2105 if (progressPanel == null)
2107 progressPanel = new JPanel(new GridLayout(1, 1));
2108 totalProgressCount = 0;
2109 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2111 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2112 JProgressBar progressBar = new JProgressBar();
2113 progressBar.setIndeterminate(true);
2115 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2117 thisprogress.add(progressBar, BorderLayout.CENTER);
2118 progressPanel.add(thisprogress);
2119 ((GridLayout) progressPanel.getLayout()).setRows(
2120 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2121 ++totalProgressCount;
2122 instance.validate();
2123 return thisprogress;
2126 int totalProgressCount = 0;
2128 private void removeProgressPanel(JPanel progbar)
2130 if (progressPanel != null)
2132 synchronized (progressPanel)
2134 progressPanel.remove(progbar);
2135 GridLayout gl = (GridLayout) progressPanel.getLayout();
2136 gl.setRows(gl.getRows() - 1);
2137 if (--totalProgressCount < 1)
2139 this.getContentPane().remove(progressPanel);
2140 progressPanel = null;
2147 public void stopLoading()
2150 if (fileLoadingCount < 1)
2152 while (fileLoadingPanels.size() > 0)
2154 removeProgressPanel(fileLoadingPanels.remove(0));
2156 fileLoadingPanels.clear();
2157 fileLoadingCount = 0;
2162 public static int getViewCount(String alignmentId)
2164 AlignmentViewport[] aps = getViewports(alignmentId);
2165 return (aps == null) ? 0 : aps.length;
2170 * @param alignmentId
2171 * - if null, all sets are returned
2172 * @return all AlignmentPanels concerning the alignmentId sequence set
2174 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2176 if (Desktop.desktop == null)
2178 // no frames created and in headless mode
2179 // TODO: verify that frames are recoverable when in headless mode
2182 List<AlignmentPanel> aps = new ArrayList<>();
2183 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2188 for (AlignFrame af : frames)
2190 for (AlignmentPanel ap : af.alignPanels)
2192 if (alignmentId == null
2193 || alignmentId.equals(ap.av.getSequenceSetId()))
2199 if (aps.size() == 0)
2203 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2208 * get all the viewports on an alignment.
2210 * @param sequenceSetId
2211 * unique alignment id (may be null - all viewports returned in that
2213 * @return all viewports on the alignment bound to sequenceSetId
2215 public static AlignmentViewport[] getViewports(String sequenceSetId)
2217 List<AlignmentViewport> viewp = new ArrayList<>();
2218 if (desktop != null)
2220 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2222 for (AlignFrame afr : frames)
2224 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2225 .equals(sequenceSetId))
2227 if (afr.alignPanels != null)
2229 for (AlignmentPanel ap : afr.alignPanels)
2231 if (sequenceSetId == null
2232 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2240 viewp.add(afr.getViewport());
2244 if (viewp.size() > 0)
2246 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2253 * Explode the views in the given frame into separate AlignFrame
2257 public static void explodeViews(AlignFrame af)
2259 int size = af.alignPanels.size();
2265 // FIXME: ideally should use UI interface API
2266 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2267 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2268 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2269 for (int i = 0; i < size; i++)
2271 AlignmentPanel ap = af.alignPanels.get(i);
2273 AlignFrame newaf = new AlignFrame(ap);
2275 // transfer reference for existing feature settings to new alignFrame
2276 if (ap == af.alignPanel)
2278 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2280 newaf.featureSettings = viewFeatureSettings;
2282 newaf.setFeatureSettingsGeometry(fsBounds);
2286 * Restore the view's last exploded frame geometry if known. Multiple views from
2287 * one exploded frame share and restore the same (frame) position and size.
2289 Rectangle geometry = ap.av.getExplodedGeometry();
2290 if (geometry != null)
2292 newaf.setBounds(geometry);
2295 ap.av.setGatherViewsHere(false);
2297 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2298 AlignFrame.DEFAULT_HEIGHT);
2299 // and materialise a new feature settings dialog instance for the new
2301 // (closes the old as if 'OK' was pressed)
2302 if (ap == af.alignPanel && newaf.featureSettings != null
2303 && newaf.featureSettings.isOpen()
2304 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2306 newaf.showFeatureSettingsUI();
2310 af.featureSettings = null;
2311 af.alignPanels.clear();
2312 af.closeMenuItem_actionPerformed(true);
2317 * Gather expanded views (separate AlignFrame's) with the same sequence set
2318 * identifier back in to this frame as additional views, and close the
2319 * expanded views. Note the expanded frames may themselves have multiple
2320 * views. We take the lot.
2324 public void gatherViews(AlignFrame source)
2326 source.viewport.setGatherViewsHere(true);
2327 source.viewport.setExplodedGeometry(source.getBounds());
2328 JInternalFrame[] frames = desktop.getAllFrames();
2329 String viewId = source.viewport.getSequenceSetId();
2330 for (int t = 0; t < frames.length; t++)
2332 if (frames[t] instanceof AlignFrame && frames[t] != source)
2334 AlignFrame af = (AlignFrame) frames[t];
2335 boolean gatherThis = false;
2336 for (int a = 0; a < af.alignPanels.size(); a++)
2338 AlignmentPanel ap = af.alignPanels.get(a);
2339 if (viewId.equals(ap.av.getSequenceSetId()))
2342 ap.av.setGatherViewsHere(false);
2343 ap.av.setExplodedGeometry(af.getBounds());
2344 source.addAlignmentPanel(ap, false);
2350 if (af.featureSettings != null && af.featureSettings.isOpen())
2352 if (source.featureSettings == null)
2354 // preserve the feature settings geometry for this frame
2355 source.featureSettings = af.featureSettings;
2356 source.setFeatureSettingsGeometry(
2357 af.getFeatureSettingsGeometry());
2361 // close it and forget
2362 af.featureSettings.close();
2365 af.alignPanels.clear();
2366 af.closeMenuItem_actionPerformed(true);
2371 // refresh the feature setting UI for the source frame if it exists
2372 if (source.featureSettings != null && source.featureSettings.isOpen())
2374 source.showFeatureSettingsUI();
2379 public JInternalFrame[] getAllFrames()
2381 return desktop.getAllFrames();
2385 * Checks the given url to see if it gives a response indicating that the user
2386 * should be informed of a new questionnaire.
2390 public void checkForQuestionnaire(String url)
2392 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2393 // javax.swing.SwingUtilities.invokeLater(jvq);
2394 new Thread(jvq).start();
2397 public void checkURLLinks()
2399 // Thread off the URL link checker
2400 addDialogThread(new Runnable()
2405 if (Cache.getDefault("CHECKURLLINKS", true))
2407 // check what the actual links are - if it's just the default don't
2408 // bother with the warning
2409 List<String> links = Preferences.sequenceUrlLinks
2412 // only need to check links if there is one with a
2413 // SEQUENCE_ID which is not the default EMBL_EBI link
2414 ListIterator<String> li = links.listIterator();
2415 boolean check = false;
2416 List<JLabel> urls = new ArrayList<>();
2417 while (li.hasNext())
2419 String link = li.next();
2420 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2421 && !UrlConstants.isDefaultString(link))
2424 int barPos = link.indexOf("|");
2425 String urlMsg = barPos == -1 ? link
2426 : link.substring(0, barPos) + ": "
2427 + link.substring(barPos + 1);
2428 urls.add(new JLabel(urlMsg));
2436 // ask user to check in case URL links use old style tokens
2437 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2438 JPanel msgPanel = new JPanel();
2439 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2440 msgPanel.add(Box.createVerticalGlue());
2441 JLabel msg = new JLabel(MessageManager
2442 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2443 JLabel msg2 = new JLabel(MessageManager
2444 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2446 for (JLabel url : urls)
2452 final JCheckBox jcb = new JCheckBox(
2453 MessageManager.getString("label.do_not_display_again"));
2454 jcb.addActionListener(new ActionListener()
2457 public void actionPerformed(ActionEvent e)
2459 // update Cache settings for "don't show this again"
2460 boolean showWarningAgain = !jcb.isSelected();
2461 Cache.setProperty("CHECKURLLINKS",
2462 Boolean.valueOf(showWarningAgain).toString());
2467 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2469 .getString("label.SEQUENCE_ID_no_longer_used"),
2470 JvOptionPane.WARNING_MESSAGE);
2477 * Proxy class for JDesktopPane which optionally displays the current memory
2478 * usage and highlights the desktop area with a red bar if free memory runs
2483 public class MyDesktopPane extends JDesktopPane implements Runnable
2485 private static final float ONE_MB = 1048576f;
2487 boolean showMemoryUsage = false;
2491 java.text.NumberFormat df;
2493 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2496 public MyDesktopPane(boolean showMemoryUsage)
2498 showMemoryUsage(showMemoryUsage);
2501 public void showMemoryUsage(boolean showMemory)
2503 this.showMemoryUsage = showMemory;
2506 Thread worker = new Thread(this);
2512 public boolean isShowMemoryUsage()
2514 return showMemoryUsage;
2520 df = java.text.NumberFormat.getNumberInstance();
2521 df.setMaximumFractionDigits(2);
2522 runtime = Runtime.getRuntime();
2524 while (showMemoryUsage)
2528 maxMemory = runtime.maxMemory() / ONE_MB;
2529 allocatedMemory = runtime.totalMemory() / ONE_MB;
2530 freeMemory = runtime.freeMemory() / ONE_MB;
2531 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2533 percentUsage = (totalFreeMemory / maxMemory) * 100;
2535 // if (percentUsage < 20)
2537 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2539 // instance.set.setBorder(border1);
2542 // sleep after showing usage
2544 } catch (Exception ex)
2546 ex.printStackTrace();
2552 public void paintComponent(Graphics g)
2554 if (showMemoryUsage && g != null && df != null)
2556 if (percentUsage < 20)
2558 g.setColor(Color.red);
2560 FontMetrics fm = g.getFontMetrics();
2563 g.drawString(MessageManager.formatMessage("label.memory_stats",
2565 { df.format(totalFreeMemory), df.format(maxMemory),
2566 df.format(percentUsage) }),
2567 10, getHeight() - fm.getHeight());
2571 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2572 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2577 * Accessor method to quickly get all the AlignmentFrames loaded.
2579 * @return an array of AlignFrame, or null if none found
2582 public AlignFrame[] getAlignFrames()
2584 if (desktop == null)
2589 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2595 List<AlignFrame> avp = new ArrayList<>();
2597 for (int i = frames.length - 1; i > -1; i--)
2599 if (frames[i] instanceof AlignFrame)
2601 avp.add((AlignFrame) frames[i]);
2603 else if (frames[i] instanceof SplitFrame)
2606 * Also check for a split frame containing an AlignFrame
2608 GSplitFrame sf = (GSplitFrame) frames[i];
2609 if (sf.getTopFrame() instanceof AlignFrame)
2611 avp.add((AlignFrame) sf.getTopFrame());
2613 if (sf.getBottomFrame() instanceof AlignFrame)
2615 avp.add((AlignFrame) sf.getBottomFrame());
2619 if (avp.size() == 0)
2623 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2630 public static AlignFrame[] getDesktopAlignFrames()
2632 if (Jalview.isHeadlessMode())
2634 // Desktop.desktop is null in headless mode
2635 return Jalview.getInstance().getAlignFrames();
2638 if (instance != null && desktop != null)
2640 return instance.getAlignFrames();
2647 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2651 public GStructureViewer[] getJmols()
2653 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2659 List<GStructureViewer> avp = new ArrayList<>();
2661 for (int i = frames.length - 1; i > -1; i--)
2663 if (frames[i] instanceof AppJmol)
2665 GStructureViewer af = (GStructureViewer) frames[i];
2669 if (avp.size() == 0)
2673 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2678 * Add Groovy Support to Jalview
2681 public void groovyShell_actionPerformed()
2685 openGroovyConsole();
2686 } catch (Exception ex)
2688 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2689 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2691 MessageManager.getString("label.couldnt_create_groovy_shell"),
2692 MessageManager.getString("label.groovy_support_failed"),
2693 JvOptionPane.ERROR_MESSAGE);
2698 * Open the Groovy console
2700 void openGroovyConsole()
2702 if (groovyConsole == null)
2704 JalviewObjectI j = new JalviewObject(this);
2705 groovyConsole = new groovy.console.ui.Console();
2706 groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j);
2707 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2708 getCurrentAlignFrame());
2709 groovyConsole.run();
2712 * We allow only one console at a time, so that AlignFrame menu option
2713 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2714 * enable 'Run script', when the console is opened, and the reverse when it is
2717 Window window = (Window) groovyConsole.getFrame();
2718 window.addWindowListener(new WindowAdapter()
2721 public void windowClosed(WindowEvent e)
2724 * rebind CMD-Q from Groovy Console to Jalview Quit
2727 enableExecuteGroovy(false);
2733 * show Groovy console window (after close and reopen)
2735 ((Window) groovyConsole.getFrame()).setVisible(true);
2738 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2739 * opening a second console
2741 enableExecuteGroovy(true);
2745 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2746 * binding when opened
2748 protected void addQuitHandler()
2751 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2753 .getKeyStroke(KeyEvent.VK_Q,
2754 jalview.util.ShortcutKeyMaskExWrapper
2755 .getMenuShortcutKeyMaskEx()),
2757 getRootPane().getActionMap().put("Quit", new AbstractAction()
2760 public void actionPerformed(ActionEvent e)
2768 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2771 * true if Groovy console is open
2773 public void enableExecuteGroovy(boolean enabled)
2776 * disable opening a second Groovy console (or re-enable when the console is
2779 groovyShell.setEnabled(!enabled);
2781 AlignFrame[] alignFrames = getDesktopAlignFrames();
2782 if (alignFrames != null)
2784 for (AlignFrame af : alignFrames)
2786 af.setGroovyEnabled(enabled);
2792 * Progress bars managed by the IProgressIndicator method.
2794 private Hashtable<Long, JPanel> progressBars;
2796 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2801 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2804 public void setProgressBar(String message, long id)
2806 if (progressBars == null)
2808 progressBars = new Hashtable<>();
2809 progressBarHandlers = new Hashtable<>();
2812 if (progressBars.get(Long.valueOf(id)) != null)
2814 JPanel panel = progressBars.remove(Long.valueOf(id));
2815 if (progressBarHandlers.contains(Long.valueOf(id)))
2817 progressBarHandlers.remove(Long.valueOf(id));
2819 removeProgressPanel(panel);
2823 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2830 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2831 * jalview.gui.IProgressIndicatorHandler)
2834 public void registerHandler(final long id,
2835 final IProgressIndicatorHandler handler)
2837 if (progressBarHandlers == null
2838 || !progressBars.containsKey(Long.valueOf(id)))
2840 throw new Error(MessageManager.getString(
2841 "error.call_setprogressbar_before_registering_handler"));
2843 progressBarHandlers.put(Long.valueOf(id), handler);
2844 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2845 if (handler.canCancel())
2847 JButton cancel = new JButton(
2848 MessageManager.getString("action.cancel"));
2849 final IProgressIndicator us = this;
2850 cancel.addActionListener(new ActionListener()
2854 public void actionPerformed(ActionEvent e)
2856 handler.cancelActivity(id);
2857 us.setProgressBar(MessageManager
2858 .formatMessage("label.cancelled_params", new Object[]
2859 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2863 progressPanel.add(cancel, BorderLayout.EAST);
2869 * @return true if any progress bars are still active
2872 public boolean operationInProgress()
2874 if (progressBars != null && progressBars.size() > 0)
2882 * This will return the first AlignFrame holding the given viewport instance.
2883 * It will break if there are more than one AlignFrames viewing a particular
2887 * @return alignFrame for viewport
2889 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2891 if (desktop != null)
2893 AlignmentPanel[] aps = getAlignmentPanels(
2894 viewport.getSequenceSetId());
2895 for (int panel = 0; aps != null && panel < aps.length; panel++)
2897 if (aps[panel] != null && aps[panel].av == viewport)
2899 return aps[panel].alignFrame;
2906 public VamsasApplication getVamsasApplication()
2908 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2914 * flag set if jalview GUI is being operated programmatically
2916 private boolean inBatchMode = false;
2919 * check if jalview GUI is being operated programmatically
2921 * @return inBatchMode
2923 public boolean isInBatchMode()
2929 * set flag if jalview GUI is being operated programmatically
2931 * @param inBatchMode
2933 public void setInBatchMode(boolean inBatchMode)
2935 this.inBatchMode = inBatchMode;
2939 * start service discovery and wait till it is done
2941 public void startServiceDiscovery()
2943 startServiceDiscovery(false);
2947 * start service discovery threads - blocking or non-blocking
2951 public void startServiceDiscovery(boolean blocking)
2953 startServiceDiscovery(blocking, false);
2957 * start service discovery threads
2960 * - false means call returns immediately
2961 * @param ignore_SHOW_JWS2_SERVICES_preference
2962 * - when true JABA services are discovered regardless of user's JWS2
2963 * discovery preference setting
2965 public void startServiceDiscovery(boolean blocking,
2966 boolean ignore_SHOW_JWS2_SERVICES_preference)
2968 boolean alive = true;
2969 Thread t0 = null, t1 = null, t2 = null;
2970 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2973 // todo: changesupport handlers need to be transferred
2974 if (discoverer == null)
2976 discoverer = new jalview.ws.jws1.Discoverer();
2977 // register PCS handler for desktop.
2978 discoverer.addPropertyChangeListener(changeSupport);
2980 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2981 // until we phase out completely
2982 (t0 = new Thread(discoverer)).start();
2985 if (ignore_SHOW_JWS2_SERVICES_preference
2986 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2988 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2989 .startDiscoverer(changeSupport);
2993 // TODO: do rest service discovery
3002 } catch (Exception e)
3005 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
3006 || (t3 != null && t3.isAlive())
3007 || (t0 != null && t0.isAlive());
3013 * called to check if the service discovery process completed successfully.
3017 protected void JalviewServicesChanged(PropertyChangeEvent evt)
3019 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
3021 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3022 .getErrorMessages();
3025 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3027 if (serviceChangedDialog == null)
3029 // only run if we aren't already displaying one of these.
3030 addDialogThread(serviceChangedDialog = new Runnable()
3037 * JalviewDialog jd =new JalviewDialog() {
3039 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3041 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3043 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3045 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3047 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3048 * + " or mis-configured HTTP proxy settings.<br/>" +
3049 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3050 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3051 * true, true, "Web Service Configuration Problem", 450, 400);
3053 * jd.waitForInput();
3055 JvOptionPane.showConfirmDialog(Desktop.desktop,
3056 new JLabel("<html><table width=\"450\"><tr><td>"
3057 + ermsg + "</td></tr></table>"
3058 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3059 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3060 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3061 + " Tools->Preferences dialog box to change them.</p></html>"),
3062 "Web Service Configuration Problem",
3063 JvOptionPane.DEFAULT_OPTION,
3064 JvOptionPane.ERROR_MESSAGE);
3065 serviceChangedDialog = null;
3073 jalview.bin.Console.error(
3074 "Errors reported by JABA discovery service. Check web services preferences.\n"
3081 private Runnable serviceChangedDialog = null;
3084 * start a thread to open a URL in the configured browser. Pops up a warning
3085 * dialog to the user if there is an exception when calling out to the browser
3090 public static void showUrl(final String url)
3092 if (url != null && !url.trim().equals(""))
3094 jalview.bin.Console.info("Opening URL: " + url);
3095 showUrl(url, Desktop.instance);
3099 jalview.bin.Console.warn("Ignoring attempt to show an empty URL.");
3105 * Like showUrl but allows progress handler to be specified
3109 * (null) or object implementing IProgressIndicator
3111 public static void showUrl(final String url,
3112 final IProgressIndicator progress)
3114 new Thread(new Runnable()
3121 if (progress != null)
3123 progress.setProgressBar(MessageManager
3124 .formatMessage("status.opening_params", new Object[]
3125 { url }), this.hashCode());
3127 jalview.util.BrowserLauncher.openURL(url);
3128 } catch (Exception ex)
3130 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3132 .getString("label.web_browser_not_found_unix"),
3133 MessageManager.getString("label.web_browser_not_found"),
3134 JvOptionPane.WARNING_MESSAGE);
3136 ex.printStackTrace();
3138 if (progress != null)
3140 progress.setProgressBar(null, this.hashCode());
3146 public static WsParamSetManager wsparamManager = null;
3148 public static ParamManager getUserParameterStore()
3150 if (wsparamManager == null)
3152 wsparamManager = new WsParamSetManager();
3154 return wsparamManager;
3158 * static hyperlink handler proxy method for use by Jalview's internal windows
3162 public static void hyperlinkUpdate(HyperlinkEvent e)
3164 if (e.getEventType() == EventType.ACTIVATED)
3169 url = e.getURL().toString();
3170 Desktop.showUrl(url);
3171 } catch (Exception x)
3176 .error("Couldn't handle string " + url + " as a URL.");
3178 // ignore any exceptions due to dud links.
3185 * single thread that handles display of dialogs to user.
3187 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3190 * flag indicating if dialogExecutor should try to acquire a permit
3192 private volatile boolean dialogPause = true;
3197 private Semaphore block = new Semaphore(0);
3199 private static groovy.console.ui.Console groovyConsole;
3202 * add another dialog thread to the queue
3206 public void addDialogThread(final Runnable prompter)
3208 dialogExecutor.submit(new Runnable()
3215 acquireDialogQueue();
3217 if (instance == null)
3223 SwingUtilities.invokeAndWait(prompter);
3224 } catch (Exception q)
3226 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3233 private boolean dialogQueueStarted = false;
3235 public void startDialogQueue()
3237 if (dialogQueueStarted)
3241 // set the flag so we don't pause waiting for another permit and semaphore
3242 // the current task to begin
3243 releaseDialogQueue();
3244 dialogQueueStarted = true;
3247 public void acquireDialogQueue()
3253 } catch (InterruptedException e)
3255 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3260 public void releaseDialogQueue()
3267 dialogPause = false;
3271 * Outputs an image of the desktop to file in EPS format, after prompting the
3272 * user for choice of Text or Lineart character rendering (unless a preference
3273 * has been set). The file name is generated as
3276 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3280 protected void snapShotWindow_actionPerformed(ActionEvent e)
3282 // currently the menu option to do this is not shown
3285 int width = getWidth();
3286 int height = getHeight();
3288 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3289 ImageWriterI writer = new ImageWriterI()
3292 public void exportImage(Graphics g) throws Exception
3295 jalview.bin.Console.info("Successfully written snapshot to file "
3296 + of.getAbsolutePath());
3299 String title = "View of desktop";
3300 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3304 exporter.doExport(of, this, width, height, title);
3305 } catch (ImageOutputException ioex)
3307 jalview.bin.Console.error(
3308 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3314 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3315 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3316 * and location last time the view was expanded (if any). However it does not
3317 * remember the split pane divider location - this is set to match the
3318 * 'exploding' frame.
3322 public void explodeViews(SplitFrame sf)
3324 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3325 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3326 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3328 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3330 int viewCount = topPanels.size();
3337 * Processing in reverse order works, forwards order leaves the first panels not
3338 * visible. I don't know why!
3340 for (int i = viewCount - 1; i >= 0; i--)
3343 * Make new top and bottom frames. These take over the respective AlignmentPanel
3344 * objects, including their AlignmentViewports, so the cdna/protein
3345 * relationships between the viewports is carried over to the new split frames.
3347 * explodedGeometry holds the (x, y) position of the previously exploded
3348 * SplitFrame, and the (width, height) of the AlignFrame component
3350 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3351 AlignFrame newTopFrame = new AlignFrame(topPanel);
3352 newTopFrame.setSize(oldTopFrame.getSize());
3353 newTopFrame.setVisible(true);
3354 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3355 .getExplodedGeometry();
3356 if (geometry != null)
3358 newTopFrame.setSize(geometry.getSize());
3361 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3362 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3363 newBottomFrame.setSize(oldBottomFrame.getSize());
3364 newBottomFrame.setVisible(true);
3365 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3366 .getExplodedGeometry();
3367 if (geometry != null)
3369 newBottomFrame.setSize(geometry.getSize());
3372 topPanel.av.setGatherViewsHere(false);
3373 bottomPanel.av.setGatherViewsHere(false);
3374 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3376 if (geometry != null)
3378 splitFrame.setLocation(geometry.getLocation());
3380 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3384 * Clear references to the panels (now relocated in the new SplitFrames) before
3385 * closing the old SplitFrame.
3388 bottomPanels.clear();
3393 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3394 * back into the given SplitFrame as additional views. Note that the gathered
3395 * frames may themselves have multiple views.
3399 public void gatherViews(GSplitFrame source)
3402 * special handling of explodedGeometry for a view within a SplitFrame: - it
3403 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3404 * height) of the AlignFrame component
3406 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3407 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3408 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3409 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3410 myBottomFrame.viewport
3411 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3412 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3413 myTopFrame.viewport.setGatherViewsHere(true);
3414 myBottomFrame.viewport.setGatherViewsHere(true);
3415 String topViewId = myTopFrame.viewport.getSequenceSetId();
3416 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3418 JInternalFrame[] frames = desktop.getAllFrames();
3419 for (JInternalFrame frame : frames)
3421 if (frame instanceof SplitFrame && frame != source)
3423 SplitFrame sf = (SplitFrame) frame;
3424 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3425 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3426 boolean gatherThis = false;
3427 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3429 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3430 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3431 if (topViewId.equals(topPanel.av.getSequenceSetId())
3432 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3435 topPanel.av.setGatherViewsHere(false);
3436 bottomPanel.av.setGatherViewsHere(false);
3437 topPanel.av.setExplodedGeometry(
3438 new Rectangle(sf.getLocation(), topFrame.getSize()));
3439 bottomPanel.av.setExplodedGeometry(
3440 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3441 myTopFrame.addAlignmentPanel(topPanel, false);
3442 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3448 topFrame.getAlignPanels().clear();
3449 bottomFrame.getAlignPanels().clear();
3456 * The dust settles...give focus to the tab we did this from.
3458 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3461 public static groovy.console.ui.Console getGroovyConsole()
3463 return groovyConsole;
3467 * handles the payload of a drag and drop event.
3469 * TODO refactor to desktop utilities class
3472 * - Data source strings extracted from the drop event
3474 * - protocol for each data source extracted from the drop event
3478 * - the payload from the drop event
3481 public static void transferFromDropTarget(List<Object> files,
3482 List<DataSourceType> protocols, DropTargetDropEvent evt,
3483 Transferable t) throws Exception
3486 DataFlavor uriListFlavor = new DataFlavor(
3487 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3490 urlFlavour = new DataFlavor(
3491 "application/x-java-url; class=java.net.URL");
3492 } catch (ClassNotFoundException cfe)
3494 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3498 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3503 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3504 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3505 // means url may be null.
3508 protocols.add(DataSourceType.URL);
3509 files.add(url.toString());
3510 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3511 + files.get(files.size() - 1));
3516 if (Platform.isAMacAndNotJS())
3518 jalview.bin.Console.errPrintln(
3519 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3522 } catch (Throwable ex)
3524 jalview.bin.Console.debug("URL drop handler failed.", ex);
3527 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3529 // Works on Windows and MacOSX
3530 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3531 for (Object file : (List) t
3532 .getTransferData(DataFlavor.javaFileListFlavor))
3535 protocols.add(DataSourceType.FILE);
3540 // Unix like behaviour
3541 boolean added = false;
3543 if (t.isDataFlavorSupported(uriListFlavor))
3545 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3546 // This is used by Unix drag system
3547 data = (String) t.getTransferData(uriListFlavor);
3551 // fallback to text: workaround - on OSX where there's a JVM bug
3553 .debug("standard URIListFlavor failed. Trying text");
3554 // try text fallback
3555 DataFlavor textDf = new DataFlavor(
3556 "text/plain;class=java.lang.String");
3557 if (t.isDataFlavorSupported(textDf))
3559 data = (String) t.getTransferData(textDf);
3562 jalview.bin.Console.debug("Plain text drop content returned "
3563 + (data == null ? "Null - failed" : data));
3568 while (protocols.size() < files.size())
3570 jalview.bin.Console.debug("Adding missing FILE protocol for "
3571 + files.get(protocols.size()));
3572 protocols.add(DataSourceType.FILE);
3574 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3575 data, "\r\n"); st.hasMoreTokens();)
3578 String s = st.nextToken();
3579 if (s.startsWith("#"))
3581 // the line is a comment (as per the RFC 2483)
3584 java.net.URI uri = new java.net.URI(s);
3585 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3587 protocols.add(DataSourceType.URL);
3588 files.add(uri.toString());
3592 // otherwise preserve old behaviour: catch all for file objects
3593 java.io.File file = new java.io.File(uri);
3594 protocols.add(DataSourceType.FILE);
3595 files.add(file.toString());
3600 if (jalview.bin.Console.isDebugEnabled())
3602 if (data == null || !added)
3605 if (t.getTransferDataFlavors() != null
3606 && t.getTransferDataFlavors().length > 0)
3608 jalview.bin.Console.debug(
3609 "Couldn't resolve drop data. Here are the supported flavors:");
3610 for (DataFlavor fl : t.getTransferDataFlavors())
3612 jalview.bin.Console.debug(
3613 "Supported transfer dataflavor: " + fl.toString());
3614 Object df = t.getTransferData(fl);
3617 jalview.bin.Console.debug("Retrieves: " + df);
3621 jalview.bin.Console.debug("Retrieved nothing");
3628 .debug("Couldn't resolve dataflavor for drop: "
3634 if (Platform.isWindowsAndNotJS())
3637 .debug("Scanning dropped content for Windows Link Files");
3639 // resolve any .lnk files in the file drop
3640 for (int f = 0; f < files.size(); f++)
3642 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3643 if (protocols.get(f).equals(DataSourceType.FILE)
3644 && (source.endsWith(".lnk") || source.endsWith(".url")
3645 || source.endsWith(".site")))
3649 Object obj = files.get(f);
3650 File lf = (obj instanceof File ? (File) obj
3651 : new File((String) obj));
3652 // process link file to get a URL
3653 jalview.bin.Console.debug("Found potential link file: " + lf);
3654 WindowsShortcut wscfile = new WindowsShortcut(lf);
3655 String fullname = wscfile.getRealFilename();
3656 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3657 files.set(f, fullname);
3658 jalview.bin.Console.debug("Parsed real filename " + fullname
3659 + " to extract protocol: " + protocols.get(f));
3660 } catch (Exception ex)
3662 jalview.bin.Console.error(
3663 "Couldn't parse " + files.get(f) + " as a link file.",
3672 * Sets the Preferences property for experimental features to True or False
3673 * depending on the state of the controlling menu item
3676 protected void showExperimental_actionPerformed(boolean selected)
3678 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3682 * Answers a (possibly empty) list of any structure viewer frames (currently
3683 * for either Jmol or Chimera) which are currently open. This may optionally
3684 * be restricted to viewers of a specified class, or viewers linked to a
3685 * specified alignment panel.
3688 * if not null, only return viewers linked to this panel
3689 * @param structureViewerClass
3690 * if not null, only return viewers of this class
3693 public List<StructureViewerBase> getStructureViewers(
3694 AlignmentPanel apanel,
3695 Class<? extends StructureViewerBase> structureViewerClass)
3697 List<StructureViewerBase> result = new ArrayList<>();
3698 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3700 for (JInternalFrame frame : frames)
3702 if (frame instanceof StructureViewerBase)
3704 if (structureViewerClass == null
3705 || structureViewerClass.isInstance(frame))
3708 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3710 result.add((StructureViewerBase) frame);
3718 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3720 private static boolean debugScaleMessageDone = false;
3722 public static void debugScaleMessage(Graphics g)
3724 if (debugScaleMessageDone)
3728 // output used by tests to check HiDPI scaling settings in action
3731 Graphics2D gg = (Graphics2D) g;
3734 AffineTransform t = gg.getTransform();
3735 double scaleX = t.getScaleX();
3736 double scaleY = t.getScaleY();
3737 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3738 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3739 debugScaleMessageDone = true;
3743 jalview.bin.Console.debug("Desktop graphics null");
3745 } catch (Exception e)
3747 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3752 * closes the current instance window, but leaves the JVM running. Bypasses
3753 * any shutdown prompts, but does not set window dispose on close in case JVM
3756 public static void closeDesktop()
3758 if (Desktop.instance != null)
3760 Desktop us = Desktop.instance;
3761 Desktop.instance.quitTheDesktop(false, false);
3762 // call dispose in a separate thread - try to avoid indirect deadlocks
3765 new Thread(new Runnable()
3770 ExecutorService dex = us.dialogExecutor;
3774 us.dialogExecutor = null;
3775 us.block.drainPermits();
3785 * checks if any progress bars are being displayed in any of the windows
3786 * managed by the desktop
3790 public boolean operationsAreInProgress()
3792 JInternalFrame[] frames = getAllFrames();
3793 for (JInternalFrame frame : frames)
3795 if (frame instanceof IProgressIndicator)
3797 if (((IProgressIndicator) frame).operationInProgress())
3803 return operationInProgress();
3807 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3808 * The way the modal JInternalFrame is made means it cannot be a child of an
3809 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3811 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3813 protected static void addModal(AlignFrame af, JInternalFrame jif)
3815 alignFrameModalMap.put(af, jif);
3818 protected static void closeModal(AlignFrame af)
3820 if (!alignFrameModalMap.containsKey(af))
3824 JInternalFrame jif = alignFrameModalMap.get(af);
3829 jif.setClosed(true);
3830 } catch (PropertyVetoException e)
3832 e.printStackTrace();
3835 alignFrameModalMap.remove(af);
3838 public void nonBlockingDialog(String title, String message, String button,
3839 int type, boolean scrollable, boolean modal)
3841 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3845 public void nonBlockingDialog(String title, String message,
3846 String boxtext, String button, int type, boolean scrollable,
3847 boolean html, boolean modal, int timeout)
3849 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3850 scrollable, html, modal, timeout);
3853 public void nonBlockingDialog(int width, int height, String title,
3854 String message, String boxtext, String button, int type,
3855 boolean scrollable, boolean html, boolean modal, int timeout)
3859 type = JvOptionPane.WARNING_MESSAGE;
3861 JLabel jl = new JLabel(message);
3863 JTextComponent jtc = null;
3866 JTextPane jtp = new JTextPane();
3867 jtp.setContentType("text/html");
3868 jtp.setEditable(false);
3869 jtp.setAutoscrolls(true);
3870 jtp.setText(boxtext);
3876 JTextArea jta = new JTextArea(height, width);
3877 // jta.setLineWrap(true);
3878 jta.setEditable(false);
3879 jta.setWrapStyleWord(true);
3880 jta.setAutoscrolls(true);
3881 jta.setText(boxtext);
3886 JScrollPane jsp = scrollable
3887 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3888 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3891 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3893 JPanel jp = new JPanel();
3894 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3896 if (message != null)
3898 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3901 if (boxtext != null)
3905 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3910 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3915 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3917 jvp.setTimeout(timeout);
3918 JButton jb = new JButton(button);
3919 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3921 { button }, button, modal, new JButton[] { jb }, false);
3925 public AlignFrame getCurrentAlignFrame()
3927 return Jalview.getInstance().getCurrentAlignFrame();