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.IdUtils;
145 import jalview.util.IdUtils.IdType;
146 import jalview.util.ImageMaker.TYPE;
147 import jalview.util.LaunchUtils;
148 import jalview.util.MessageManager;
149 import jalview.util.Platform;
150 import jalview.util.ShortcutKeyMaskExWrapper;
151 import jalview.util.UrlConstants;
152 import jalview.viewmodel.AlignmentViewport;
153 import jalview.ws.params.ParamManager;
154 import jalview.ws.utils.UrlDownloadClient;
161 * @version $Revision: 1.155 $
163 public class Desktop extends jalview.jbgui.GDesktop
164 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
165 jalview.api.StructureSelectionManagerProvider, JalviewObjectI
167 private static final String CITATION;
170 URL bg_logo_url = ChannelProperties.getImageURL(
171 "bg_logo." + String.valueOf(SplashScreen.logoSize));
172 URL uod_logo_url = ChannelProperties.getImageURL(
173 "uod_banner." + String.valueOf(SplashScreen.logoSize));
174 boolean logo = (bg_logo_url != null || uod_logo_url != null);
175 StringBuilder sb = new StringBuilder();
177 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
182 sb.append(bg_logo_url == null ? ""
183 : "<img alt=\"Barton Group logo\" src=\""
184 + bg_logo_url.toString() + "\">");
185 sb.append(uod_logo_url == null ? ""
186 : " <img alt=\"University of Dundee shield\" src=\""
187 + uod_logo_url.toString() + "\">");
189 "<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>");
190 sb.append("<br><br>If you use Jalview, please cite:"
191 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
192 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
193 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
194 CITATION = sb.toString();
197 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
199 private static int DEFAULT_MIN_WIDTH = 300;
201 private static int DEFAULT_MIN_HEIGHT = 250;
203 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
205 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
207 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
209 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
211 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
213 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
215 public static void setLiveDragMode(boolean b)
217 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
218 : JDesktopPane.OUTLINE_DRAG_MODE;
220 desktop.setDragMode(DRAG_MODE);
223 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
225 public static boolean nosplash = false;
228 * news reader - null if it was never started.
230 private BlogReader jvnews = null;
232 private File projectFile;
236 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
238 public void addJalviewPropertyChangeListener(
239 PropertyChangeListener listener)
241 changeSupport.addJalviewPropertyChangeListener(listener);
245 * @param propertyName
247 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
248 * java.beans.PropertyChangeListener)
250 public void addJalviewPropertyChangeListener(String propertyName,
251 PropertyChangeListener listener)
253 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
257 * @param propertyName
259 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
260 * java.beans.PropertyChangeListener)
262 public void removeJalviewPropertyChangeListener(String propertyName,
263 PropertyChangeListener listener)
265 changeSupport.removeJalviewPropertyChangeListener(propertyName,
269 /** Singleton Desktop instance */
270 public static Desktop instance;
272 public static MyDesktopPane desktop;
274 public static MyDesktopPane getDesktop()
276 // BH 2018 could use currentThread() here as a reference to a
277 // Hashtable<Thread, MyDesktopPane> in JavaScript
281 static int openFrameCount = 0;
283 static final int xOffset = 30;
285 static final int yOffset = 30;
287 public static jalview.ws.jws1.Discoverer discoverer;
289 public static Object[] jalviewClipboard;
291 public static boolean internalCopy = false;
293 static int fileLoadingCount = 0;
295 class MyDesktopManager implements DesktopManager
298 private DesktopManager delegate;
300 public MyDesktopManager(DesktopManager delegate)
302 this.delegate = delegate;
306 public void activateFrame(JInternalFrame f)
310 delegate.activateFrame(f);
311 } catch (NullPointerException npe)
313 Point p = getMousePosition();
314 instance.showPasteMenu(p.x, p.y);
319 public void beginDraggingFrame(JComponent f)
321 delegate.beginDraggingFrame(f);
325 public void beginResizingFrame(JComponent f, int direction)
327 delegate.beginResizingFrame(f, direction);
331 public void closeFrame(JInternalFrame f)
333 delegate.closeFrame(f);
337 public void deactivateFrame(JInternalFrame f)
339 delegate.deactivateFrame(f);
343 public void deiconifyFrame(JInternalFrame f)
345 delegate.deiconifyFrame(f);
349 public void dragFrame(JComponent f, int newX, int newY)
355 delegate.dragFrame(f, newX, newY);
359 public void endDraggingFrame(JComponent f)
361 delegate.endDraggingFrame(f);
366 public void endResizingFrame(JComponent f)
368 delegate.endResizingFrame(f);
373 public void iconifyFrame(JInternalFrame f)
375 delegate.iconifyFrame(f);
379 public void maximizeFrame(JInternalFrame f)
381 delegate.maximizeFrame(f);
385 public void minimizeFrame(JInternalFrame f)
387 delegate.minimizeFrame(f);
391 public void openFrame(JInternalFrame f)
393 delegate.openFrame(f);
397 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
404 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
408 public void setBoundsForFrame(JComponent f, int newX, int newY,
409 int newWidth, int newHeight)
411 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
414 // All other methods, simply delegate
419 * Creates a new Desktop object.
425 * A note to implementors. It is ESSENTIAL that any activities that might
426 * block are spawned off as threads rather than waited for during this
431 doConfigureStructurePrefs();
432 setTitle(ChannelProperties.getProperty("app_name") + " "
433 + Cache.getProperty("VERSION"));
436 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
437 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
438 * officially documented or guaranteed to exist, so we access it via
439 * reflection. There appear to be unfathomable criteria about what this
440 * string can contain, and it if doesn't meet those criteria then "java"
441 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
442 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
443 * not. The reflection access may generate a warning: WARNING: An illegal
444 * reflective access operation has occurred WARNING: Illegal reflective
445 * access by jalview.gui.Desktop () to field
446 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
448 if (Platform.isLinux())
450 if (LaunchUtils.getJavaVersion() >= 11)
453 * Send this message to stderr as the warning that follows (due to reflection)
454 * also goes to stderr.
456 jalview.bin.Console.errPrintln(
457 "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.");
459 final String awtAppClassName = "awtAppClassName";
462 Toolkit xToolkit = Toolkit.getDefaultToolkit();
463 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
464 Field awtAppClassNameField = null;
466 if (Arrays.stream(declaredFields)
467 .anyMatch(f -> f.getName().equals(awtAppClassName)))
469 awtAppClassNameField = xToolkit.getClass()
470 .getDeclaredField(awtAppClassName);
473 String title = ChannelProperties.getProperty("app_name");
474 if (awtAppClassNameField != null)
476 awtAppClassNameField.setAccessible(true);
477 awtAppClassNameField.set(xToolkit, title);
482 .debug("XToolkit: " + awtAppClassName + " not found");
484 } catch (Exception e)
486 jalview.bin.Console.debug("Error setting " + awtAppClassName);
487 jalview.bin.Console.trace(Cache.getStackTraceString(e));
491 setIconImages(ChannelProperties.getIconList());
493 // override quit handling when GUI OS close [X] button pressed
494 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
495 addWindowListener(new WindowAdapter()
498 public void windowClosing(WindowEvent ev)
500 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
504 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
506 boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE,
507 "SHOW_JAVA_CONSOLE", false);
509 // start dialogue queue for single dialogues
512 if (!Platform.isJS())
519 Desktop.instance.acquireDialogQueue();
521 jconsole = new Console(this);
522 jconsole.setHeader(Cache.getVersionDetailsForConsole());
523 showConsole(showjconsole);
525 Desktop.instance.releaseDialogQueue();
528 desktop = new MyDesktopPane(selmemusage);
530 showMemusage.setSelected(selmemusage);
531 desktop.setBackground(Color.white);
533 getContentPane().setLayout(new BorderLayout());
534 // alternate config - have scrollbars - see notes in JAL-153
535 // JScrollPane sp = new JScrollPane();
536 // sp.getViewport().setView(desktop);
537 // getContentPane().add(sp, BorderLayout.CENTER);
539 // BH 2018 - just an experiment to try unclipped JInternalFrames.
542 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
545 getContentPane().add(desktop, BorderLayout.CENTER);
546 desktop.setDragMode(DRAG_MODE);
548 // This line prevents Windows Look&Feel resizing all new windows to maximum
549 // if previous window was maximised
550 desktop.setDesktopManager(new MyDesktopManager(
551 Platform.isJS() ? desktop.getDesktopManager()
552 : new DefaultDesktopManager()));
554 * (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager() :
555 * Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(
556 * desktop.getDesktopManager()) : 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 String ns = Jalview.getInstance().getJSNamespace();
663 String nsc = ns + (ns.length() > 0 ? ":" : "");
664 String splashId = nsc + "jalviewSplash";
666 * @j2sNative let splash = document.getElementById(splashId);
668 * if (splash != null) {
670 * splash.style.display = "none";
675 // used for jalviewjsTest
676 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
682 * Answers true if user preferences to enable experimental features is True
687 public boolean showExperimental()
689 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
690 Boolean.FALSE.toString());
691 return Boolean.valueOf(experimental).booleanValue();
694 public void doConfigureStructurePrefs()
696 // configure services
697 StructureSelectionManager ssm = StructureSelectionManager
698 .getStructureSelectionManager(this);
699 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
701 ssm.setAddTempFacAnnot(
702 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
703 ssm.setProcessSecondaryStructure(
704 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
705 // JAL-3915 - RNAView is no longer an option so this has no effect
706 ssm.setSecStructServices(
707 Cache.getDefault(Preferences.USE_RNAVIEW, false));
711 ssm.setAddTempFacAnnot(false);
712 ssm.setProcessSecondaryStructure(false);
713 ssm.setSecStructServices(false);
717 public void checkForNews()
719 final Desktop me = this;
720 // Thread off the news reader, in case there are connection problems.
721 new Thread(new Runnable()
726 jalview.bin.Console.debug("Starting news thread.");
727 jvnews = new BlogReader(me);
728 showNews.setVisible(true);
729 jalview.bin.Console.debug("Completed news thread.");
734 public void getIdentifiersOrgData()
736 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
737 {// Thread off the identifiers fetcher
738 new Thread(new Runnable()
744 .debug("Downloading data from identifiers.org");
747 UrlDownloadClient.download(IdOrgSettings.getUrl(),
748 IdOrgSettings.getDownloadLocation());
749 } catch (IOException e)
752 .debug("Exception downloading identifiers.org data"
762 protected void showNews_actionPerformed(ActionEvent e)
764 showNews(showNews.isSelected());
767 void showNews(boolean visible)
769 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
770 showNews.setSelected(visible);
771 if (visible && !jvnews.isVisible())
773 new Thread(new Runnable()
778 long progressId = IdUtils.newId(IdType.PROGRESS);
779 Desktop.instance.setProgressBar(
780 MessageManager.getString("status.refreshing_news"),
782 jvnews.refreshNews();
783 Desktop.instance.setProgressBar(null, progressId);
791 * recover the last known dimensions for a jalview window
794 * - empty string is desktop, all other windows have unique prefix
795 * @return null or last known dimensions scaled to current geometry (if last
796 * window geom was known)
798 Rectangle getLastKnownDimensions(String windowName)
800 // TODO: lock aspect ratio for scaling desktop Bug #0058199
801 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
802 String x = Cache.getProperty(windowName + "SCREEN_X");
803 String y = Cache.getProperty(windowName + "SCREEN_Y");
804 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
805 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
806 if ((x != null) && (y != null) && (width != null) && (height != null))
808 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
809 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
810 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
812 // attempt #1 - try to cope with change in screen geometry - this
813 // version doesn't preserve original jv aspect ratio.
814 // take ratio of current screen size vs original screen size.
815 double sw = ((1f * screenSize.width) / (1f * Integer
816 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
817 double sh = ((1f * screenSize.height) / (1f * Integer
818 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
819 // rescale the bounds depending upon the current screen geometry.
820 ix = (int) (ix * sw);
821 iw = (int) (iw * sw);
822 iy = (int) (iy * sh);
823 ih = (int) (ih * sh);
824 if (ix >= screenSize.width)
826 jalview.bin.Console.debug(
827 "Window geometry location recall error: shifting horizontal to within screenbounds.");
828 ix = ix % screenSize.width;
830 if (iy >= screenSize.height)
832 jalview.bin.Console.debug(
833 "Window geometry location recall error: shifting vertical to within screenbounds.");
834 iy = iy % screenSize.height;
836 jalview.bin.Console.debug(
837 "Got last known dimensions for " + windowName + ": x:" + ix
838 + " y:" + iy + " width:" + iw + " height:" + ih);
840 // return dimensions for new instance
841 return new Rectangle(ix, iy, iw, ih);
846 void showPasteMenu(int x, int y)
848 JPopupMenu popup = new JPopupMenu();
849 JMenuItem item = new JMenuItem(
850 MessageManager.getString("label.paste_new_window"));
851 item.addActionListener(new ActionListener()
854 public void actionPerformed(ActionEvent evt)
861 popup.show(this, x, y);
866 // quick patch for JAL-4150 - needs some more work and test coverage
867 // TODO - unify below and AlignFrame.paste()
868 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
869 // clipboard has come from a different alignment window than the one where
870 // paste has been called! JAL-4151
872 if (Desktop.jalviewClipboard != null)
874 // The clipboard was filled from within Jalview, we must use the
876 // And dataset from the copied alignment
877 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
878 // be doubly sure that we create *new* sequence objects.
879 SequenceI[] sequences = new SequenceI[newseq.length];
880 for (int i = 0; i < newseq.length; i++)
882 sequences[i] = new Sequence(newseq[i]);
884 Alignment alignment = new Alignment(sequences);
885 // dataset is inherited
886 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
887 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
888 AlignFrame.DEFAULT_HEIGHT);
889 String newtitle = new String("Copied sequences");
891 if (Desktop.jalviewClipboard[2] != null)
893 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
894 af.viewport.setHiddenColumns(hc);
897 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
898 AlignFrame.DEFAULT_HEIGHT);
905 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
906 Transferable contents = c.getContents(this);
908 if (contents != null)
910 String file = (String) contents
911 .getTransferData(DataFlavor.stringFlavor);
913 FileFormatI format = new IdentifyFile().identify(file,
914 DataSourceType.PASTE);
916 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
919 } catch (Exception ex)
921 jalview.bin.Console.outPrintln(
922 "Unable to paste alignment from system clipboard:\n" + ex);
928 * Adds and opens the given frame to the desktop
939 public static synchronized void addInternalFrame(
940 final JInternalFrame frame, String title, int w, int h)
942 addInternalFrame(frame, title, true, w, h, true, false);
946 * Add an internal frame to the Jalview desktop
953 * When true, display frame immediately, otherwise, caller must call
954 * setVisible themselves.
960 public static synchronized void addInternalFrame(
961 final JInternalFrame frame, String title, boolean makeVisible,
964 addInternalFrame(frame, title, makeVisible, w, h, true, false);
968 * Add an internal frame to the Jalview desktop and make it visible
981 public static synchronized void addInternalFrame(
982 final JInternalFrame frame, String title, int w, int h,
985 addInternalFrame(frame, title, true, w, h, resizable, false);
989 * Add an internal frame to the Jalview desktop
996 * When true, display frame immediately, otherwise, caller must call
997 * setVisible themselves.
1004 * @param ignoreMinSize
1005 * Do not set the default minimum size for frame
1007 public static synchronized void addInternalFrame(
1008 final JInternalFrame frame, String title, boolean makeVisible,
1009 int w, int h, boolean resizable, boolean ignoreMinSize)
1012 // TODO: allow callers to determine X and Y position of frame (eg. via
1014 // TODO: consider fixing method to update entries in the window submenu with
1015 // the current window title
1017 frame.setTitle(title);
1018 if (frame.getWidth() < 1 || frame.getHeight() < 1)
1020 frame.setSize(w, h);
1022 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
1023 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
1024 // IF JALVIEW IS RUNNING HEADLESS
1025 // ///////////////////////////////////////////////
1026 if (instance == null || (System.getProperty("java.awt.headless") != null
1027 && System.getProperty("java.awt.headless").equals("true")))
1036 frame.setMinimumSize(
1037 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1039 // Set default dimension for Alignment Frame window.
1040 // The Alignment Frame window could be added from a number of places,
1042 // I did this here in order not to miss out on any Alignment frame.
1043 if (frame instanceof AlignFrame)
1045 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1046 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1050 frame.setVisible(makeVisible);
1051 frame.setClosable(true);
1052 frame.setResizable(resizable);
1053 frame.setMaximizable(resizable);
1054 frame.setIconifiable(resizable);
1055 frame.setOpaque(Platform.isJS());
1057 if (frame.getX() < 1 && frame.getY() < 1)
1059 frame.setLocation(xOffset * openFrameCount,
1060 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1064 * add an entry for the new frame in the Window menu (and remove it when the
1067 final JMenuItem menuItem = new JMenuItem(title);
1068 frame.addInternalFrameListener(new InternalFrameAdapter()
1071 public void internalFrameActivated(InternalFrameEvent evt)
1073 JInternalFrame itf = desktop.getSelectedFrame();
1076 if (itf instanceof AlignFrame)
1078 Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf);
1085 public void internalFrameClosed(InternalFrameEvent evt)
1087 PaintRefresher.RemoveComponent(frame);
1090 * defensive check to prevent frames being added half off the window
1092 if (openFrameCount > 0)
1098 * ensure no reference to alignFrame retained by menu item listener
1100 if (menuItem.getActionListeners().length > 0)
1102 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1104 windowMenu.remove(menuItem);
1108 menuItem.addActionListener(new ActionListener()
1111 public void actionPerformed(ActionEvent e)
1115 frame.setSelected(true);
1116 frame.setIcon(false);
1117 } catch (java.beans.PropertyVetoException ex)
1124 setKeyBindings(frame);
1126 // Since the latest FlatLaf patch, we occasionally have problems showing
1127 // structureViewer frames...
1129 boolean shown = false;
1130 Exception last = null;
1137 } catch (IllegalArgumentException iaex)
1141 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1142 + tries + " left) for " + frame.getTitle(), iaex);
1146 } catch (InterruptedException iex)
1151 } while (!shown && tries > 0);
1154 jalview.bin.Console.error(
1155 "Serious Problem whilst showing window " + frame.getTitle(),
1159 windowMenu.add(menuItem);
1164 frame.setSelected(true);
1165 frame.requestFocus();
1166 } catch (java.beans.PropertyVetoException ve)
1168 } catch (java.lang.ClassCastException cex)
1170 jalview.bin.Console.warn(
1171 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1177 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1182 private static void setKeyBindings(JInternalFrame frame)
1184 @SuppressWarnings("serial")
1185 final Action closeAction = new AbstractAction()
1188 public void actionPerformed(ActionEvent e)
1195 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1197 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1198 InputEvent.CTRL_DOWN_MASK);
1199 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1200 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1202 InputMap inputMap = frame
1203 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1204 String ctrlW = ctrlWKey.toString();
1205 inputMap.put(ctrlWKey, ctrlW);
1206 inputMap.put(cmdWKey, ctrlW);
1208 ActionMap actionMap = frame.getActionMap();
1209 actionMap.put(ctrlW, closeAction);
1213 public void lostOwnership(Clipboard clipboard, Transferable contents)
1217 Desktop.jalviewClipboard = null;
1220 internalCopy = false;
1224 public void dragEnter(DropTargetDragEvent evt)
1229 public void dragExit(DropTargetEvent evt)
1234 public void dragOver(DropTargetDragEvent evt)
1239 public void dropActionChanged(DropTargetDragEvent evt)
1250 public void drop(DropTargetDropEvent evt)
1252 boolean success = true;
1253 // JAL-1552 - acceptDrop required before getTransferable call for
1254 // Java's Transferable for native dnd
1255 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1256 Transferable t = evt.getTransferable();
1257 List<Object> files = new ArrayList<>();
1258 List<DataSourceType> protocols = new ArrayList<>();
1262 Desktop.transferFromDropTarget(files, protocols, evt, t);
1263 } catch (Exception e)
1265 e.printStackTrace();
1273 for (int i = 0; i < files.size(); i++)
1275 // BH 2018 File or String
1276 Object file = files.get(i);
1277 String fileName = file.toString();
1278 DataSourceType protocol = (protocols == null)
1279 ? DataSourceType.FILE
1281 FileFormatI format = null;
1283 if (fileName.endsWith(".jar"))
1285 format = FileFormat.Jalview;
1290 format = new IdentifyFile().identify(file, protocol);
1292 if (file instanceof File)
1294 Platform.cacheFileData((File) file);
1296 new FileLoader().LoadFile(null, file, protocol, format);
1299 } catch (Exception ex)
1304 evt.dropComplete(success); // need this to ensure input focus is properly
1305 // transfered to any new windows created
1315 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1317 String fileFormat = FileLoader.getUseDefaultFileFormat()
1318 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1320 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1321 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1322 BackupFiles.getEnabled());
1324 chooser.setFileView(new JalviewFileView());
1325 chooser.setDialogTitle(
1326 MessageManager.getString("label.open_local_file"));
1327 chooser.setToolTipText(MessageManager.getString("action.open"));
1329 chooser.setResponseHandler(0, () -> {
1330 File selectedFile = chooser.getSelectedFile();
1331 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1333 FileFormatI format = chooser.getSelectedFormat();
1336 * Call IdentifyFile to verify the file contains what its extension implies.
1337 * Skip this step for dynamically added file formats, because IdentifyFile does
1338 * not know how to recognise them.
1340 if (FileFormats.getInstance().isIdentifiable(format))
1344 format = new IdentifyFile().identify(selectedFile,
1345 DataSourceType.FILE);
1346 } catch (FileFormatException e)
1348 // format = null; //??
1352 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1355 chooser.showOpenDialog(this);
1359 * Shows a dialog for input of a URL at which to retrieve alignment data
1364 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1366 // This construct allows us to have a wider textfield
1368 JLabel label = new JLabel(
1369 MessageManager.getString("label.input_file_url"));
1371 JPanel panel = new JPanel(new GridLayout(2, 1));
1375 * the URL to fetch is input in Java: an editable combobox with history JS:
1376 * (pending JAL-3038) a plain text field
1379 String urlBase = "https://www.";
1380 if (Platform.isJS())
1382 history = new JTextField(urlBase, 35);
1391 JComboBox<String> asCombo = new JComboBox<>();
1392 asCombo.setPreferredSize(new Dimension(400, 20));
1393 asCombo.setEditable(true);
1394 asCombo.addItem(urlBase);
1395 String historyItems = Cache.getProperty("RECENT_URL");
1396 if (historyItems != null)
1398 for (String token : historyItems.split("\\t"))
1400 asCombo.addItem(token);
1407 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1408 MessageManager.getString("action.cancel") };
1409 Runnable action = () -> {
1410 @SuppressWarnings("unchecked")
1411 String url = (history instanceof JTextField
1412 ? ((JTextField) history).getText()
1413 : ((JComboBox<String>) history).getEditor().getItem()
1414 .toString().trim());
1416 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1418 if (viewport != null)
1420 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1421 FileFormat.Jalview);
1425 new FileLoader().LoadFile(url, DataSourceType.URL,
1426 FileFormat.Jalview);
1431 FileFormatI format = null;
1434 format = new IdentifyFile().identify(url, DataSourceType.URL);
1435 } catch (FileFormatException e)
1437 // TODO revise error handling, distinguish between
1438 // URL not found and response not valid
1443 String msg = MessageManager.formatMessage("label.couldnt_locate",
1445 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1446 MessageManager.getString("label.url_not_found"),
1447 JvOptionPane.WARNING_MESSAGE);
1451 if (viewport != null)
1453 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1458 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1462 String dialogOption = MessageManager
1463 .getString("label.input_alignment_from_url");
1464 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1465 .showInternalDialog(panel, dialogOption,
1466 JvOptionPane.YES_NO_CANCEL_OPTION,
1467 JvOptionPane.PLAIN_MESSAGE, null, options,
1468 MessageManager.getString("action.ok"));
1472 * Opens the CutAndPaste window for the user to paste an alignment in to
1475 * - if not null, the pasted alignment is added to the current
1476 * alignment; if null, to a new alignment window
1479 public void inputTextboxMenuItem_actionPerformed(
1480 AlignmentViewPanel viewPanel)
1482 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1483 cap.setForInput(viewPanel);
1484 Desktop.addInternalFrame(cap,
1485 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1490 * Check with user and saving files before actually quitting
1492 public void desktopQuit()
1494 desktopQuit(true, false);
1498 * close everything, stash window geometries, and shut down all associated
1502 * - sets the dispose on close flag - JVM may terminate when set
1503 * @param terminateJvm
1504 * - quit with prejudice - stops the JVM.
1506 public void quitTheDesktop(boolean dispose, boolean terminateJvm)
1508 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1509 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1510 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1511 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1512 getWidth(), getHeight()));
1514 if (jconsole != null)
1516 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1517 jconsole.stopConsole();
1522 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1525 // Frames should all close automatically. Keeping external
1526 // viewers open should already be decided by user.
1527 closeAll_actionPerformed(null);
1529 if (dialogExecutor != null)
1531 dialogExecutor.shutdownNow();
1534 if (groovyConsole != null)
1536 // suppress a possible repeat prompt to save script
1537 groovyConsole.setDirty(false);
1538 groovyConsole.exit();
1543 // note that shutdown hook will not be run
1544 jalview.bin.Console.debug("Force Quit selected by user");
1545 Runtime.getRuntime().halt(0);
1548 jalview.bin.Console.debug("Quit selected by user");
1551 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1552 // instance.dispose();
1556 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1558 final Runnable doDesktopQuit = () -> {
1560 // FIRST !! check for aborted quit
1561 if (QuitHandler.quitCancelled())
1564 .debug("Quit was cancelled - Desktop aborting quit");
1568 // Proceed with quitting
1569 quitTheDesktop(disposeFlag,
1570 QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT);
1575 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1576 QuitHandler.defaultCancelQuit);
1580 * Exits the program and the JVM.
1582 * Don't call this directly
1584 * - use desktopQuit() above to tidy up first.
1586 * - use closeDesktop() to shutdown Jalview without shutting down the JVM
1592 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1593 // not run a second time if gotQuitResponse flag has been set (i.e. user
1594 // confirmed quit of some kind).
1595 Jalview.exit("Desktop exiting.", ExitCode.OK);
1598 private void storeLastKnownDimensions(String string, Rectangle jc)
1600 jalview.bin.Console.debug("Storing last known dimensions for " + string
1601 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1602 + " height:" + jc.height);
1604 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1605 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1606 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1607 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1617 public void aboutMenuItem_actionPerformed(ActionEvent e)
1619 new Thread(new Runnable()
1624 new SplashScreen(false);
1630 * Returns the html text for the About screen, including any available version
1631 * number, build details, author details and citation reference, but without
1632 * the enclosing {@code html} tags
1636 public String getAboutMessage()
1638 StringBuilder message = new StringBuilder(1024);
1639 message.append("<div style=\"font-family: sans-serif;\">")
1640 .append("<h1><strong>Version: ")
1641 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1642 .append("<strong>Built: <em>")
1643 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1644 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1645 .append("</strong>");
1647 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1648 if (latestVersion.equals("Checking"))
1650 // JBP removed this message for 2.11: May be reinstated in future version
1651 // message.append("<br>...Checking latest version...</br>");
1653 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1655 boolean red = false;
1656 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1657 .indexOf("automated build") == -1)
1660 // Displayed when code version and jnlp version do not match and code
1661 // version is not a development build
1662 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1665 message.append("<br>!! Version ")
1666 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1667 .append(" is available for download from ")
1668 .append(Cache.getDefault("www.jalview.org",
1669 "https://www.jalview.org"))
1673 message.append("</div>");
1676 message.append("<br>Authors: ");
1677 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1678 message.append(CITATION);
1680 message.append("</div>");
1682 return message.toString();
1686 * Action on requesting Help documentation
1689 public void documentationMenuItem_actionPerformed()
1693 if (Platform.isJS())
1695 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1704 Help.showHelpWindow();
1706 } catch (Exception ex)
1709 .errPrintln("Error opening help: " + ex.getMessage());
1714 public void closeAll_actionPerformed(ActionEvent e)
1716 // TODO show a progress bar while closing?
1717 JInternalFrame[] frames = desktop.getAllFrames();
1718 for (int i = 0; i < frames.length; i++)
1722 frames[i].setClosed(true);
1723 } catch (java.beans.PropertyVetoException ex)
1727 Jalview.getInstance().setCurrentAlignFrame(null);
1728 jalview.bin.Console.info("ALL CLOSED");
1731 * reset state of singleton objects as appropriate (clear down session state
1732 * when all windows are closed)
1734 StructureSelectionManager ssm = StructureSelectionManager
1735 .getStructureSelectionManager(this);
1742 public int structureViewersStillRunningCount()
1745 JInternalFrame[] frames = desktop.getAllFrames();
1746 for (int i = 0; i < frames.length; i++)
1748 if (frames[i] != null
1749 && frames[i] instanceof JalviewStructureDisplayI)
1751 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1759 public void raiseRelated_actionPerformed(ActionEvent e)
1761 reorderAssociatedWindows(false, false);
1765 public void minimizeAssociated_actionPerformed(ActionEvent e)
1767 reorderAssociatedWindows(true, false);
1770 void closeAssociatedWindows()
1772 reorderAssociatedWindows(false, true);
1778 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1782 protected void garbageCollect_actionPerformed(ActionEvent e)
1784 // We simply collect the garbage
1785 jalview.bin.Console.debug("Collecting garbage...");
1787 jalview.bin.Console.debug("Finished garbage collection.");
1793 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1797 protected void showMemusage_actionPerformed(ActionEvent e)
1799 desktop.showMemoryUsage(showMemusage.isSelected());
1806 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1810 protected void showConsole_actionPerformed(ActionEvent e)
1812 showConsole(showConsole.isSelected());
1815 Console jconsole = null;
1818 * control whether the java console is visible or not
1822 void showConsole(boolean selected)
1824 // TODO: decide if we should update properties file
1825 if (jconsole != null) // BH 2018
1827 showConsole.setSelected(selected);
1828 Cache.setProperty("SHOW_JAVA_CONSOLE",
1829 Boolean.valueOf(selected).toString());
1830 jconsole.setVisible(selected);
1834 void reorderAssociatedWindows(boolean minimize, boolean close)
1836 JInternalFrame[] frames = desktop.getAllFrames();
1837 if (frames == null || frames.length < 1)
1842 AlignmentViewport source = null, target = null;
1843 if (frames[0] instanceof AlignFrame)
1845 source = ((AlignFrame) frames[0]).getCurrentView();
1847 else if (frames[0] instanceof TreePanel)
1849 source = ((TreePanel) frames[0]).getViewPort();
1851 else if (frames[0] instanceof PCAPanel)
1853 source = ((PCAPanel) frames[0]).av;
1855 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1857 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1862 for (int i = 0; i < frames.length; i++)
1865 if (frames[i] == null)
1869 if (frames[i] instanceof AlignFrame)
1871 target = ((AlignFrame) frames[i]).getCurrentView();
1873 else if (frames[i] instanceof TreePanel)
1875 target = ((TreePanel) frames[i]).getViewPort();
1877 else if (frames[i] instanceof PCAPanel)
1879 target = ((PCAPanel) frames[i]).av;
1881 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1883 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1886 if (source == target)
1892 frames[i].setClosed(true);
1896 frames[i].setIcon(minimize);
1899 frames[i].toFront();
1903 } catch (java.beans.PropertyVetoException ex)
1918 protected void preferences_actionPerformed(ActionEvent e)
1920 Preferences.openPreferences();
1924 * Prompts the user to choose a file and then saves the Jalview state as a
1925 * Jalview project file
1928 public void saveState_actionPerformed()
1930 saveState_actionPerformed(false);
1933 public void saveState_actionPerformed(boolean saveAs)
1935 java.io.File projectFile = getProjectFile();
1936 // autoSave indicates we already have a file and don't need to ask
1937 boolean autoSave = projectFile != null && !saveAs
1938 && BackupFiles.getEnabled();
1940 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1941 // projectFile='"+projectFile+"',
1942 // saveAs="+saveAs+", Backups
1943 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1945 boolean approveSave = false;
1948 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1951 chooser.setFileView(new JalviewFileView());
1952 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1954 int value = chooser.showSaveDialog(this);
1956 if (value == JalviewFileChooser.APPROVE_OPTION)
1958 projectFile = chooser.getSelectedFile();
1959 setProjectFile(projectFile);
1964 if (approveSave || autoSave)
1966 final Desktop me = this;
1967 final java.io.File chosenFile = projectFile;
1968 new Thread(new Runnable()
1973 // TODO: refactor to Jalview desktop session controller action.
1974 setProgressBar(MessageManager.formatMessage(
1975 "label.saving_jalview_project", new Object[]
1976 { chosenFile.getName() }),
1977 IdUtils.newId(IdType.PROGRESS, chosenFile));
1978 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1979 // TODO catch and handle errors for savestate
1980 // TODO prevent user from messing with the Desktop whilst we're saving
1983 boolean doBackup = BackupFiles.getEnabled();
1984 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1987 new Jalview2XML().saveState(
1988 doBackup ? backupfiles.getTempFile() : chosenFile);
1992 backupfiles.setWriteSuccess(true);
1993 backupfiles.rollBackupsAndRenameTempFile();
1995 } catch (OutOfMemoryError oom)
1997 new OOMWarning("Whilst saving current state to "
1998 + chosenFile.getName(), oom);
1999 } catch (Exception ex)
2001 jalview.bin.Console.error("Problems whilst trying to save to "
2002 + chosenFile.getName(), ex);
2003 JvOptionPane.showMessageDialog(me,
2004 MessageManager.formatMessage(
2005 "label.error_whilst_saving_current_state_to",
2007 { chosenFile.getName() }),
2008 MessageManager.getString("label.couldnt_save_project"),
2009 JvOptionPane.WARNING_MESSAGE);
2011 setProgressBar(null, IdUtils.newId(IdType.PROGRESS, chosenFile));
2018 public void saveAsState_actionPerformed(ActionEvent e)
2020 saveState_actionPerformed(true);
2023 protected void setProjectFile(File choice)
2025 this.projectFile = choice;
2028 public File getProjectFile()
2030 return this.projectFile;
2034 * Shows a file chooser dialog and tries to read in the selected file as a
2038 public void loadState_actionPerformed()
2040 final String[] suffix = new String[] { "jvp", "jar" };
2041 final String[] desc = new String[] { "Jalview Project",
2042 "Jalview Project (old)" };
2043 JalviewFileChooser chooser = new JalviewFileChooser(
2044 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2045 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2049 chooser.setFileView(new JalviewFileView());
2050 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2051 chooser.setResponseHandler(0, () -> {
2052 File selectedFile = chooser.getSelectedFile();
2053 setProjectFile(selectedFile);
2054 String choice = selectedFile.getAbsolutePath();
2055 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2056 new Thread(new Runnable()
2063 new Jalview2XML().loadJalviewAlign(selectedFile);
2064 } catch (OutOfMemoryError oom)
2066 new OOMWarning("Whilst loading project from " + choice, oom);
2067 } catch (Exception ex)
2069 jalview.bin.Console.error(
2070 "Problems whilst loading project from " + choice, ex);
2071 JvOptionPane.showMessageDialog(Desktop.desktop,
2072 MessageManager.formatMessage(
2073 "label.error_whilst_loading_project_from",
2076 MessageManager.getString("label.couldnt_load_project"),
2077 JvOptionPane.WARNING_MESSAGE);
2080 }, "Project Loader").start();
2083 chooser.showOpenDialog(this);
2087 public void inputSequence_actionPerformed(ActionEvent e)
2089 new SequenceFetcher(this);
2092 JPanel progressPanel;
2094 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2096 public void startLoading(final Object fileName)
2098 if (fileLoadingCount == 0)
2100 fileLoadingPanels.add(addProgressPanel(MessageManager
2101 .formatMessage("label.loading_file", new Object[]
2107 private JPanel addProgressPanel(String string)
2109 if (progressPanel == null)
2111 progressPanel = new JPanel(new GridLayout(1, 1));
2112 totalProgressCount = 0;
2113 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2115 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2116 JProgressBar progressBar = new JProgressBar();
2117 progressBar.setIndeterminate(true);
2119 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2121 thisprogress.add(progressBar, BorderLayout.CENTER);
2122 progressPanel.add(thisprogress);
2123 ((GridLayout) progressPanel.getLayout()).setRows(
2124 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2125 ++totalProgressCount;
2126 instance.validate();
2127 return thisprogress;
2130 int totalProgressCount = 0;
2132 private void removeProgressPanel(JPanel progbar)
2134 if (progressPanel != null)
2136 synchronized (progressPanel)
2138 progressPanel.remove(progbar);
2139 GridLayout gl = (GridLayout) progressPanel.getLayout();
2140 gl.setRows(gl.getRows() - 1);
2141 if (--totalProgressCount < 1)
2143 this.getContentPane().remove(progressPanel);
2144 progressPanel = null;
2151 public void stopLoading()
2154 if (fileLoadingCount < 1)
2156 while (fileLoadingPanels.size() > 0)
2158 removeProgressPanel(fileLoadingPanels.remove(0));
2160 fileLoadingPanels.clear();
2161 fileLoadingCount = 0;
2166 public static int getViewCount(String alignmentId)
2168 AlignmentViewport[] aps = getViewports(alignmentId);
2169 return (aps == null) ? 0 : aps.length;
2174 * @param alignmentId
2175 * - if null, all sets are returned
2176 * @return all AlignmentPanels concerning the alignmentId sequence set
2178 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2180 if (Desktop.desktop == null)
2182 // no frames created and in headless mode
2183 // TODO: verify that frames are recoverable when in headless mode
2186 List<AlignmentPanel> aps = new ArrayList<>();
2187 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2192 for (AlignFrame af : frames)
2194 for (AlignmentPanel ap : af.alignPanels)
2196 if (alignmentId == null
2197 || alignmentId.equals(ap.av.getSequenceSetId()))
2203 if (aps.size() == 0)
2207 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2212 * get all the viewports on an alignment.
2214 * @param sequenceSetId
2215 * unique alignment id (may be null - all viewports returned in that
2217 * @return all viewports on the alignment bound to sequenceSetId
2219 public static AlignmentViewport[] getViewports(String sequenceSetId)
2221 List<AlignmentViewport> viewp = new ArrayList<>();
2222 if (desktop != null)
2224 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2226 for (AlignFrame afr : frames)
2228 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2229 .equals(sequenceSetId))
2231 if (afr.alignPanels != null)
2233 for (AlignmentPanel ap : afr.alignPanels)
2235 if (sequenceSetId == null
2236 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2244 viewp.add(afr.getViewport());
2248 if (viewp.size() > 0)
2250 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2257 * Explode the views in the given frame into separate AlignFrame
2261 public static void explodeViews(AlignFrame af)
2263 int size = af.alignPanels.size();
2269 // FIXME: ideally should use UI interface API
2270 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2271 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2272 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2273 for (int i = 0; i < size; i++)
2275 AlignmentPanel ap = af.alignPanels.get(i);
2277 AlignFrame newaf = new AlignFrame(ap);
2279 // transfer reference for existing feature settings to new alignFrame
2280 if (ap == af.alignPanel)
2282 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2284 newaf.featureSettings = viewFeatureSettings;
2286 newaf.setFeatureSettingsGeometry(fsBounds);
2290 * Restore the view's last exploded frame geometry if known. Multiple views from
2291 * one exploded frame share and restore the same (frame) position and size.
2293 Rectangle geometry = ap.av.getExplodedGeometry();
2294 if (geometry != null)
2296 newaf.setBounds(geometry);
2299 ap.av.setGatherViewsHere(false);
2301 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2302 AlignFrame.DEFAULT_HEIGHT);
2303 // and materialise a new feature settings dialog instance for the new
2305 // (closes the old as if 'OK' was pressed)
2306 if (ap == af.alignPanel && newaf.featureSettings != null
2307 && newaf.featureSettings.isOpen()
2308 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2310 newaf.showFeatureSettingsUI();
2314 af.featureSettings = null;
2315 af.alignPanels.clear();
2316 af.closeMenuItem_actionPerformed(true);
2321 * Gather expanded views (separate AlignFrame's) with the same sequence set
2322 * identifier back in to this frame as additional views, and close the
2323 * expanded views. Note the expanded frames may themselves have multiple
2324 * views. We take the lot.
2328 public void gatherViews(AlignFrame source)
2330 source.viewport.setGatherViewsHere(true);
2331 source.viewport.setExplodedGeometry(source.getBounds());
2332 JInternalFrame[] frames = desktop.getAllFrames();
2333 String viewId = source.viewport.getSequenceSetId();
2334 for (int t = 0; t < frames.length; t++)
2336 if (frames[t] instanceof AlignFrame && frames[t] != source)
2338 AlignFrame af = (AlignFrame) frames[t];
2339 boolean gatherThis = false;
2340 for (int a = 0; a < af.alignPanels.size(); a++)
2342 AlignmentPanel ap = af.alignPanels.get(a);
2343 if (viewId.equals(ap.av.getSequenceSetId()))
2346 ap.av.setGatherViewsHere(false);
2347 ap.av.setExplodedGeometry(af.getBounds());
2348 source.addAlignmentPanel(ap, false);
2354 if (af.featureSettings != null && af.featureSettings.isOpen())
2356 if (source.featureSettings == null)
2358 // preserve the feature settings geometry for this frame
2359 source.featureSettings = af.featureSettings;
2360 source.setFeatureSettingsGeometry(
2361 af.getFeatureSettingsGeometry());
2365 // close it and forget
2366 af.featureSettings.close();
2369 af.alignPanels.clear();
2370 af.closeMenuItem_actionPerformed(true);
2375 // refresh the feature setting UI for the source frame if it exists
2376 if (source.featureSettings != null && source.featureSettings.isOpen())
2378 source.showFeatureSettingsUI();
2383 public JInternalFrame[] getAllFrames()
2385 return desktop.getAllFrames();
2389 * Checks the given url to see if it gives a response indicating that the user
2390 * should be informed of a new questionnaire.
2394 public void checkForQuestionnaire(String url)
2396 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2397 // javax.swing.SwingUtilities.invokeLater(jvq);
2398 new Thread(jvq).start();
2401 public void checkURLLinks()
2403 // Thread off the URL link checker
2404 addDialogThread(new Runnable()
2409 if (Cache.getDefault("CHECKURLLINKS", true))
2411 // check what the actual links are - if it's just the default don't
2412 // bother with the warning
2413 List<String> links = Preferences.sequenceUrlLinks
2416 // only need to check links if there is one with a
2417 // SEQUENCE_ID which is not the default EMBL_EBI link
2418 ListIterator<String> li = links.listIterator();
2419 boolean check = false;
2420 List<JLabel> urls = new ArrayList<>();
2421 while (li.hasNext())
2423 String link = li.next();
2424 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2425 && !UrlConstants.isDefaultString(link))
2428 int barPos = link.indexOf("|");
2429 String urlMsg = barPos == -1 ? link
2430 : link.substring(0, barPos) + ": "
2431 + link.substring(barPos + 1);
2432 urls.add(new JLabel(urlMsg));
2440 // ask user to check in case URL links use old style tokens
2441 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2442 JPanel msgPanel = new JPanel();
2443 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2444 msgPanel.add(Box.createVerticalGlue());
2445 JLabel msg = new JLabel(MessageManager
2446 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2447 JLabel msg2 = new JLabel(MessageManager
2448 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2450 for (JLabel url : urls)
2456 final JCheckBox jcb = new JCheckBox(
2457 MessageManager.getString("label.do_not_display_again"));
2458 jcb.addActionListener(new ActionListener()
2461 public void actionPerformed(ActionEvent e)
2463 // update Cache settings for "don't show this again"
2464 boolean showWarningAgain = !jcb.isSelected();
2465 Cache.setProperty("CHECKURLLINKS",
2466 Boolean.valueOf(showWarningAgain).toString());
2471 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2473 .getString("label.SEQUENCE_ID_no_longer_used"),
2474 JvOptionPane.WARNING_MESSAGE);
2481 * Proxy class for JDesktopPane which optionally displays the current memory
2482 * usage and highlights the desktop area with a red bar if free memory runs
2487 public class MyDesktopPane extends JDesktopPane implements Runnable
2489 private static final float ONE_MB = 1048576f;
2491 boolean showMemoryUsage = false;
2495 java.text.NumberFormat df;
2497 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2500 public MyDesktopPane(boolean showMemoryUsage)
2502 showMemoryUsage(showMemoryUsage);
2505 public void showMemoryUsage(boolean showMemory)
2507 this.showMemoryUsage = showMemory;
2510 Thread worker = new Thread(this);
2516 public boolean isShowMemoryUsage()
2518 return showMemoryUsage;
2524 df = java.text.NumberFormat.getNumberInstance();
2525 df.setMaximumFractionDigits(2);
2526 runtime = Runtime.getRuntime();
2528 while (showMemoryUsage)
2532 maxMemory = runtime.maxMemory() / ONE_MB;
2533 allocatedMemory = runtime.totalMemory() / ONE_MB;
2534 freeMemory = runtime.freeMemory() / ONE_MB;
2535 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2537 percentUsage = (totalFreeMemory / maxMemory) * 100;
2539 // if (percentUsage < 20)
2541 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2543 // instance.set.setBorder(border1);
2546 // sleep after showing usage
2548 } catch (Exception ex)
2550 ex.printStackTrace();
2556 public void paintComponent(Graphics g)
2558 if (showMemoryUsage && g != null && df != null)
2560 if (percentUsage < 20)
2562 g.setColor(Color.red);
2564 FontMetrics fm = g.getFontMetrics();
2567 g.drawString(MessageManager.formatMessage("label.memory_stats",
2569 { df.format(totalFreeMemory), df.format(maxMemory),
2570 df.format(percentUsage) }),
2571 10, getHeight() - fm.getHeight());
2575 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2576 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2581 * Accessor method to quickly get all the AlignmentFrames loaded.
2583 * @return an array of AlignFrame, or null if none found
2586 public AlignFrame[] getAlignFrames()
2588 if (desktop == null)
2593 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2599 List<AlignFrame> avp = new ArrayList<>();
2601 for (int i = frames.length - 1; i > -1; i--)
2603 if (frames[i] instanceof AlignFrame)
2605 avp.add((AlignFrame) frames[i]);
2607 else if (frames[i] instanceof SplitFrame)
2610 * Also check for a split frame containing an AlignFrame
2612 GSplitFrame sf = (GSplitFrame) frames[i];
2613 if (sf.getTopFrame() instanceof AlignFrame)
2615 avp.add((AlignFrame) sf.getTopFrame());
2617 if (sf.getBottomFrame() instanceof AlignFrame)
2619 avp.add((AlignFrame) sf.getBottomFrame());
2623 if (avp.size() == 0)
2627 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2634 public static AlignFrame[] getDesktopAlignFrames()
2636 if (Jalview.isHeadlessMode())
2638 // Desktop.desktop is null in headless mode
2639 return Jalview.getInstance().getAlignFrames();
2642 if (instance != null && desktop != null)
2644 return instance.getAlignFrames();
2651 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2655 public GStructureViewer[] getJmols()
2657 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2663 List<GStructureViewer> avp = new ArrayList<>();
2665 for (int i = frames.length - 1; i > -1; i--)
2667 if (frames[i] instanceof AppJmol)
2669 GStructureViewer af = (GStructureViewer) frames[i];
2673 if (avp.size() == 0)
2677 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2682 * Add Groovy Support to Jalview
2685 public void groovyShell_actionPerformed()
2689 openGroovyConsole();
2690 } catch (Exception ex)
2692 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2693 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2695 MessageManager.getString("label.couldnt_create_groovy_shell"),
2696 MessageManager.getString("label.groovy_support_failed"),
2697 JvOptionPane.ERROR_MESSAGE);
2702 * Open the Groovy console
2704 void openGroovyConsole()
2706 if (groovyConsole == null)
2708 JalviewObjectI j = new JalviewObject(this);
2709 groovyConsole = new groovy.console.ui.Console();
2710 groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j);
2711 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2712 getCurrentAlignFrame());
2713 groovyConsole.run();
2716 * We allow only one console at a time, so that AlignFrame menu option
2717 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2718 * enable 'Run script', when the console is opened, and the reverse when it is
2721 Window window = (Window) groovyConsole.getFrame();
2722 window.addWindowListener(new WindowAdapter()
2725 public void windowClosed(WindowEvent e)
2728 * rebind CMD-Q from Groovy Console to Jalview Quit
2731 enableExecuteGroovy(false);
2737 * show Groovy console window (after close and reopen)
2739 ((Window) groovyConsole.getFrame()).setVisible(true);
2742 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2743 * opening a second console
2745 enableExecuteGroovy(true);
2749 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2750 * binding when opened
2752 protected void addQuitHandler()
2755 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2757 .getKeyStroke(KeyEvent.VK_Q,
2758 jalview.util.ShortcutKeyMaskExWrapper
2759 .getMenuShortcutKeyMaskEx()),
2761 getRootPane().getActionMap().put("Quit", new AbstractAction()
2764 public void actionPerformed(ActionEvent e)
2772 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2775 * true if Groovy console is open
2777 public void enableExecuteGroovy(boolean enabled)
2780 * disable opening a second Groovy console (or re-enable when the console is
2783 groovyShell.setEnabled(!enabled);
2785 AlignFrame[] alignFrames = getDesktopAlignFrames();
2786 if (alignFrames != null)
2788 for (AlignFrame af : alignFrames)
2790 af.setGroovyEnabled(enabled);
2796 * Progress bars managed by the IProgressIndicator method.
2798 private Hashtable<Long, JPanel> progressBars;
2800 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2805 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2808 public void setProgressBar(String message, long id)
2810 if (progressBars == null)
2812 progressBars = new Hashtable<>();
2813 progressBarHandlers = new Hashtable<>();
2816 if (progressBars.get(Long.valueOf(id)) != null)
2818 JPanel panel = progressBars.remove(Long.valueOf(id));
2819 if (progressBarHandlers.contains(Long.valueOf(id)))
2821 progressBarHandlers.remove(Long.valueOf(id));
2823 removeProgressPanel(panel);
2827 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2834 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2835 * jalview.gui.IProgressIndicatorHandler)
2838 public void registerHandler(final long id,
2839 final IProgressIndicatorHandler handler)
2841 if (progressBarHandlers == null
2842 || !progressBars.containsKey(Long.valueOf(id)))
2844 throw new Error(MessageManager.getString(
2845 "error.call_setprogressbar_before_registering_handler"));
2847 progressBarHandlers.put(Long.valueOf(id), handler);
2848 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2849 if (handler.canCancel())
2851 JButton cancel = new JButton(
2852 MessageManager.getString("action.cancel"));
2853 final IProgressIndicator us = this;
2854 cancel.addActionListener(new ActionListener()
2858 public void actionPerformed(ActionEvent e)
2860 handler.cancelActivity(id);
2861 us.setProgressBar(MessageManager
2862 .formatMessage("label.cancelled_params", new Object[]
2863 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2867 progressPanel.add(cancel, BorderLayout.EAST);
2873 * @return true if any progress bars are still active
2876 public boolean operationInProgress()
2878 if (progressBars != null && progressBars.size() > 0)
2886 * This will return the first AlignFrame holding the given viewport instance.
2887 * It will break if there are more than one AlignFrames viewing a particular
2891 * @return alignFrame for viewport
2893 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2895 if (desktop != null)
2897 AlignmentPanel[] aps = getAlignmentPanels(
2898 viewport.getSequenceSetId());
2899 for (int panel = 0; aps != null && panel < aps.length; panel++)
2901 if (aps[panel] != null && aps[panel].av == viewport)
2903 return aps[panel].alignFrame;
2910 public VamsasApplication getVamsasApplication()
2912 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2918 * flag set if jalview GUI is being operated programmatically
2920 private boolean inBatchMode = false;
2923 * check if jalview GUI is being operated programmatically
2925 * @return inBatchMode
2927 public boolean isInBatchMode()
2933 * set flag if jalview GUI is being operated programmatically
2935 * @param inBatchMode
2937 public void setInBatchMode(boolean inBatchMode)
2939 this.inBatchMode = inBatchMode;
2943 * start service discovery and wait till it is done
2945 public void startServiceDiscovery()
2947 startServiceDiscovery(false);
2951 * start service discovery threads - blocking or non-blocking
2955 public void startServiceDiscovery(boolean blocking)
2957 startServiceDiscovery(blocking, false);
2961 * start service discovery threads
2964 * - false means call returns immediately
2965 * @param ignore_SHOW_JWS2_SERVICES_preference
2966 * - when true JABA services are discovered regardless of user's JWS2
2967 * discovery preference setting
2969 public void startServiceDiscovery(boolean blocking,
2970 boolean ignore_SHOW_JWS2_SERVICES_preference)
2972 boolean alive = true;
2973 Thread t0 = null, t1 = null, t2 = null;
2974 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2977 // todo: changesupport handlers need to be transferred
2978 if (discoverer == null)
2980 discoverer = new jalview.ws.jws1.Discoverer();
2981 // register PCS handler for desktop.
2982 discoverer.addPropertyChangeListener(changeSupport);
2984 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2985 // until we phase out completely
2986 (t0 = new Thread(discoverer)).start();
2989 if (ignore_SHOW_JWS2_SERVICES_preference
2990 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2992 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2993 .startDiscoverer(changeSupport);
2997 // TODO: do rest service discovery
3006 } catch (Exception e)
3009 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
3010 || (t3 != null && t3.isAlive())
3011 || (t0 != null && t0.isAlive());
3017 * called to check if the service discovery process completed successfully.
3021 protected void JalviewServicesChanged(PropertyChangeEvent evt)
3023 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
3025 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3026 .getErrorMessages();
3029 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3031 if (serviceChangedDialog == null)
3033 // only run if we aren't already displaying one of these.
3034 addDialogThread(serviceChangedDialog = new Runnable()
3041 * JalviewDialog jd =new JalviewDialog() {
3043 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3045 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3047 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3049 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3051 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3052 * + " or mis-configured HTTP proxy settings.<br/>" +
3053 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3054 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3055 * true, true, "Web Service Configuration Problem", 450, 400);
3057 * jd.waitForInput();
3059 JvOptionPane.showConfirmDialog(Desktop.desktop,
3060 new JLabel("<html><table width=\"450\"><tr><td>"
3061 + ermsg + "</td></tr></table>"
3062 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3063 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3064 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3065 + " Tools->Preferences dialog box to change them.</p></html>"),
3066 "Web Service Configuration Problem",
3067 JvOptionPane.DEFAULT_OPTION,
3068 JvOptionPane.ERROR_MESSAGE);
3069 serviceChangedDialog = null;
3077 jalview.bin.Console.error(
3078 "Errors reported by JABA discovery service. Check web services preferences.\n"
3085 private Runnable serviceChangedDialog = null;
3088 * start a thread to open a URL in the configured browser. Pops up a warning
3089 * dialog to the user if there is an exception when calling out to the browser
3094 public static void showUrl(final String url)
3096 if (url != null && !url.trim().equals(""))
3098 jalview.bin.Console.info("Opening URL: " + url);
3099 showUrl(url, Desktop.instance);
3103 jalview.bin.Console.warn("Ignoring attempt to show an empty URL.");
3109 * Like showUrl but allows progress handler to be specified
3113 * (null) or object implementing IProgressIndicator
3115 public static void showUrl(final String url,
3116 final IProgressIndicator progress)
3118 new Thread(new Runnable()
3125 if (progress != null)
3127 progress.setProgressBar(MessageManager
3128 .formatMessage("status.opening_params", new Object[]
3129 { url }), IdUtils.newId(IdType.PROGRESS, this));
3131 jalview.util.BrowserLauncher.openURL(url);
3132 } catch (Exception ex)
3134 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3136 .getString("label.web_browser_not_found_unix"),
3137 MessageManager.getString("label.web_browser_not_found"),
3138 JvOptionPane.WARNING_MESSAGE);
3140 ex.printStackTrace();
3142 if (progress != null)
3144 progress.setProgressBar(null,
3145 IdUtils.newId(IdType.PROGRESS, this));
3151 public static WsParamSetManager wsparamManager = null;
3153 public static ParamManager getUserParameterStore()
3155 if (wsparamManager == null)
3157 wsparamManager = new WsParamSetManager();
3159 return wsparamManager;
3163 * static hyperlink handler proxy method for use by Jalview's internal windows
3167 public static void hyperlinkUpdate(HyperlinkEvent e)
3169 if (e.getEventType() == EventType.ACTIVATED)
3174 url = e.getURL().toString();
3175 Desktop.showUrl(url);
3176 } catch (Exception x)
3181 .error("Couldn't handle string " + url + " as a URL.");
3183 // ignore any exceptions due to dud links.
3190 * single thread that handles display of dialogs to user.
3192 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3195 * flag indicating if dialogExecutor should try to acquire a permit
3197 private volatile boolean dialogPause = true;
3202 private Semaphore block = new Semaphore(0);
3204 private static groovy.console.ui.Console groovyConsole;
3207 * add another dialog thread to the queue
3211 public void addDialogThread(final Runnable prompter)
3213 dialogExecutor.submit(new Runnable()
3220 acquireDialogQueue();
3222 if (instance == null)
3228 SwingUtilities.invokeAndWait(prompter);
3229 } catch (Exception q)
3231 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3238 private boolean dialogQueueStarted = false;
3240 public void startDialogQueue()
3242 if (dialogQueueStarted)
3246 // set the flag so we don't pause waiting for another permit and semaphore
3247 // the current task to begin
3248 releaseDialogQueue();
3249 dialogQueueStarted = true;
3252 public void acquireDialogQueue()
3258 } catch (InterruptedException e)
3260 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3265 public void releaseDialogQueue()
3272 dialogPause = false;
3276 * Outputs an image of the desktop to file in EPS format, after prompting the
3277 * user for choice of Text or Lineart character rendering (unless a preference
3278 * has been set). The file name is generated as
3281 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3285 protected void snapShotWindow_actionPerformed(ActionEvent e)
3287 // currently the menu option to do this is not shown
3290 int width = getWidth();
3291 int height = getHeight();
3293 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3294 ImageWriterI writer = new ImageWriterI()
3297 public void exportImage(Graphics g) throws Exception
3300 jalview.bin.Console.info("Successfully written snapshot to file "
3301 + of.getAbsolutePath());
3304 String title = "View of desktop";
3305 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3309 exporter.doExport(of, this, width, height, title);
3310 } catch (ImageOutputException ioex)
3312 jalview.bin.Console.error(
3313 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3319 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3320 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3321 * and location last time the view was expanded (if any). However it does not
3322 * remember the split pane divider location - this is set to match the
3323 * 'exploding' frame.
3327 public void explodeViews(SplitFrame sf)
3329 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3330 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3331 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3333 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3335 int viewCount = topPanels.size();
3342 * Processing in reverse order works, forwards order leaves the first panels not
3343 * visible. I don't know why!
3345 for (int i = viewCount - 1; i >= 0; i--)
3348 * Make new top and bottom frames. These take over the respective AlignmentPanel
3349 * objects, including their AlignmentViewports, so the cdna/protein
3350 * relationships between the viewports is carried over to the new split frames.
3352 * explodedGeometry holds the (x, y) position of the previously exploded
3353 * SplitFrame, and the (width, height) of the AlignFrame component
3355 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3356 AlignFrame newTopFrame = new AlignFrame(topPanel);
3357 newTopFrame.setSize(oldTopFrame.getSize());
3358 newTopFrame.setVisible(true);
3359 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3360 .getExplodedGeometry();
3361 if (geometry != null)
3363 newTopFrame.setSize(geometry.getSize());
3366 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3367 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3368 newBottomFrame.setSize(oldBottomFrame.getSize());
3369 newBottomFrame.setVisible(true);
3370 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3371 .getExplodedGeometry();
3372 if (geometry != null)
3374 newBottomFrame.setSize(geometry.getSize());
3377 topPanel.av.setGatherViewsHere(false);
3378 bottomPanel.av.setGatherViewsHere(false);
3379 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3381 if (geometry != null)
3383 splitFrame.setLocation(geometry.getLocation());
3385 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3389 * Clear references to the panels (now relocated in the new SplitFrames) before
3390 * closing the old SplitFrame.
3393 bottomPanels.clear();
3398 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3399 * back into the given SplitFrame as additional views. Note that the gathered
3400 * frames may themselves have multiple views.
3404 public void gatherViews(GSplitFrame source)
3407 * special handling of explodedGeometry for a view within a SplitFrame: - it
3408 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3409 * height) of the AlignFrame component
3411 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3412 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3413 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3414 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3415 myBottomFrame.viewport
3416 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3417 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3418 myTopFrame.viewport.setGatherViewsHere(true);
3419 myBottomFrame.viewport.setGatherViewsHere(true);
3420 String topViewId = myTopFrame.viewport.getSequenceSetId();
3421 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3423 JInternalFrame[] frames = desktop.getAllFrames();
3424 for (JInternalFrame frame : frames)
3426 if (frame instanceof SplitFrame && frame != source)
3428 SplitFrame sf = (SplitFrame) frame;
3429 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3430 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3431 boolean gatherThis = false;
3432 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3434 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3435 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3436 if (topViewId.equals(topPanel.av.getSequenceSetId())
3437 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3440 topPanel.av.setGatherViewsHere(false);
3441 bottomPanel.av.setGatherViewsHere(false);
3442 topPanel.av.setExplodedGeometry(
3443 new Rectangle(sf.getLocation(), topFrame.getSize()));
3444 bottomPanel.av.setExplodedGeometry(
3445 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3446 myTopFrame.addAlignmentPanel(topPanel, false);
3447 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3453 topFrame.getAlignPanels().clear();
3454 bottomFrame.getAlignPanels().clear();
3461 * The dust settles...give focus to the tab we did this from.
3463 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3466 public static groovy.console.ui.Console getGroovyConsole()
3468 return groovyConsole;
3472 * handles the payload of a drag and drop event.
3474 * TODO refactor to desktop utilities class
3477 * - Data source strings extracted from the drop event
3479 * - protocol for each data source extracted from the drop event
3483 * - the payload from the drop event
3486 public static void transferFromDropTarget(List<Object> files,
3487 List<DataSourceType> protocols, DropTargetDropEvent evt,
3488 Transferable t) throws Exception
3491 DataFlavor uriListFlavor = new DataFlavor(
3492 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3495 urlFlavour = new DataFlavor(
3496 "application/x-java-url; class=java.net.URL");
3497 } catch (ClassNotFoundException cfe)
3499 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3503 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3508 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3509 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3510 // means url may be null.
3513 protocols.add(DataSourceType.URL);
3514 files.add(url.toString());
3515 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3516 + files.get(files.size() - 1));
3521 if (Platform.isAMacAndNotJS())
3523 jalview.bin.Console.errPrintln(
3524 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3527 } catch (Throwable ex)
3529 jalview.bin.Console.debug("URL drop handler failed.", ex);
3532 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3534 // Works on Windows and MacOSX
3535 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3536 for (Object file : (List) t
3537 .getTransferData(DataFlavor.javaFileListFlavor))
3540 protocols.add(DataSourceType.FILE);
3545 // Unix like behaviour
3546 boolean added = false;
3548 if (t.isDataFlavorSupported(uriListFlavor))
3550 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3551 // This is used by Unix drag system
3552 data = (String) t.getTransferData(uriListFlavor);
3556 // fallback to text: workaround - on OSX where there's a JVM bug
3558 .debug("standard URIListFlavor failed. Trying text");
3559 // try text fallback
3560 DataFlavor textDf = new DataFlavor(
3561 "text/plain;class=java.lang.String");
3562 if (t.isDataFlavorSupported(textDf))
3564 data = (String) t.getTransferData(textDf);
3567 jalview.bin.Console.debug("Plain text drop content returned "
3568 + (data == null ? "Null - failed" : data));
3573 while (protocols.size() < files.size())
3575 jalview.bin.Console.debug("Adding missing FILE protocol for "
3576 + files.get(protocols.size()));
3577 protocols.add(DataSourceType.FILE);
3579 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3580 data, "\r\n"); st.hasMoreTokens();)
3583 String s = st.nextToken();
3584 if (s.startsWith("#"))
3586 // the line is a comment (as per the RFC 2483)
3589 java.net.URI uri = new java.net.URI(s);
3590 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3592 protocols.add(DataSourceType.URL);
3593 files.add(uri.toString());
3597 // otherwise preserve old behaviour: catch all for file objects
3598 java.io.File file = new java.io.File(uri);
3599 protocols.add(DataSourceType.FILE);
3600 files.add(file.toString());
3605 if (jalview.bin.Console.isDebugEnabled())
3607 if (data == null || !added)
3610 if (t.getTransferDataFlavors() != null
3611 && t.getTransferDataFlavors().length > 0)
3613 jalview.bin.Console.debug(
3614 "Couldn't resolve drop data. Here are the supported flavors:");
3615 for (DataFlavor fl : t.getTransferDataFlavors())
3617 jalview.bin.Console.debug(
3618 "Supported transfer dataflavor: " + fl.toString());
3619 Object df = t.getTransferData(fl);
3622 jalview.bin.Console.debug("Retrieves: " + df);
3626 jalview.bin.Console.debug("Retrieved nothing");
3633 .debug("Couldn't resolve dataflavor for drop: "
3639 if (Platform.isWindowsAndNotJS())
3642 .debug("Scanning dropped content for Windows Link Files");
3644 // resolve any .lnk files in the file drop
3645 for (int f = 0; f < files.size(); f++)
3647 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3648 if (protocols.get(f).equals(DataSourceType.FILE)
3649 && (source.endsWith(".lnk") || source.endsWith(".url")
3650 || source.endsWith(".site")))
3654 Object obj = files.get(f);
3655 File lf = (obj instanceof File ? (File) obj
3656 : new File((String) obj));
3657 // process link file to get a URL
3658 jalview.bin.Console.debug("Found potential link file: " + lf);
3659 WindowsShortcut wscfile = new WindowsShortcut(lf);
3660 String fullname = wscfile.getRealFilename();
3661 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3662 files.set(f, fullname);
3663 jalview.bin.Console.debug("Parsed real filename " + fullname
3664 + " to extract protocol: " + protocols.get(f));
3665 } catch (Exception ex)
3667 jalview.bin.Console.error(
3668 "Couldn't parse " + files.get(f) + " as a link file.",
3677 * Sets the Preferences property for experimental features to True or False
3678 * depending on the state of the controlling menu item
3681 protected void showExperimental_actionPerformed(boolean selected)
3683 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3687 * Answers a (possibly empty) list of any structure viewer frames (currently
3688 * for either Jmol or Chimera) which are currently open. This may optionally
3689 * be restricted to viewers of a specified class, or viewers linked to a
3690 * specified alignment panel.
3693 * if not null, only return viewers linked to this panel
3694 * @param structureViewerClass
3695 * if not null, only return viewers of this class
3698 public List<StructureViewerBase> getStructureViewers(
3699 AlignmentPanel apanel,
3700 Class<? extends StructureViewerBase> structureViewerClass)
3702 List<StructureViewerBase> result = new ArrayList<>();
3703 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3705 for (JInternalFrame frame : frames)
3707 if (frame instanceof StructureViewerBase)
3709 if (structureViewerClass == null
3710 || structureViewerClass.isInstance(frame))
3713 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3715 result.add((StructureViewerBase) frame);
3723 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3725 private static boolean debugScaleMessageDone = false;
3727 public static void debugScaleMessage(Graphics g)
3729 if (debugScaleMessageDone)
3733 // output used by tests to check HiDPI scaling settings in action
3736 Graphics2D gg = (Graphics2D) g;
3739 AffineTransform t = gg.getTransform();
3740 double scaleX = t.getScaleX();
3741 double scaleY = t.getScaleY();
3742 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3743 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3744 debugScaleMessageDone = true;
3748 jalview.bin.Console.debug("Desktop graphics null");
3750 } catch (Exception e)
3752 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3757 * closes the current instance window, but leaves the JVM running. Bypasses
3758 * any shutdown prompts, but does not set window dispose on close in case JVM
3761 public static void closeDesktop()
3763 if (Desktop.instance != null)
3765 Desktop us = Desktop.instance;
3766 Desktop.instance.quitTheDesktop(false, false);
3767 // call dispose in a separate thread - try to avoid indirect deadlocks
3770 new Thread(new Runnable()
3775 ExecutorService dex = us.dialogExecutor;
3779 us.dialogExecutor = null;
3780 us.block.drainPermits();
3790 * checks if any progress bars are being displayed in any of the windows
3791 * managed by the desktop
3795 public boolean operationsAreInProgress()
3797 JInternalFrame[] frames = getAllFrames();
3798 for (JInternalFrame frame : frames)
3800 if (frame instanceof IProgressIndicator)
3802 if (((IProgressIndicator) frame).operationInProgress())
3808 return operationInProgress();
3812 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3813 * The way the modal JInternalFrame is made means it cannot be a child of an
3814 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3816 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3818 protected static void addModal(AlignFrame af, JInternalFrame jif)
3820 alignFrameModalMap.put(af, jif);
3823 protected static void closeModal(AlignFrame af)
3825 if (!alignFrameModalMap.containsKey(af))
3829 JInternalFrame jif = alignFrameModalMap.get(af);
3834 jif.setClosed(true);
3835 } catch (PropertyVetoException e)
3837 e.printStackTrace();
3840 alignFrameModalMap.remove(af);
3843 public void nonBlockingDialog(String title, String message, String button,
3844 int type, boolean scrollable, boolean modal)
3846 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3850 public void nonBlockingDialog(String title, String message,
3851 String boxtext, String button, int type, boolean scrollable,
3852 boolean html, boolean modal, int timeout)
3854 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3855 scrollable, html, modal, timeout);
3858 public void nonBlockingDialog(int width, int height, String title,
3859 String message, String boxtext, String button, int type,
3860 boolean scrollable, boolean html, boolean modal, int timeout)
3864 type = JvOptionPane.WARNING_MESSAGE;
3866 JLabel jl = new JLabel(message);
3868 JTextComponent jtc = null;
3871 JTextPane jtp = new JTextPane();
3872 jtp.setContentType("text/html");
3873 jtp.setEditable(false);
3874 jtp.setAutoscrolls(true);
3875 jtp.setText(boxtext);
3881 JTextArea jta = new JTextArea(height, width);
3882 // jta.setLineWrap(true);
3883 jta.setEditable(false);
3884 jta.setWrapStyleWord(true);
3885 jta.setAutoscrolls(true);
3886 jta.setText(boxtext);
3891 JScrollPane jsp = scrollable
3892 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3893 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3896 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3898 JPanel jp = new JPanel();
3899 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3901 if (message != null)
3903 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3906 if (boxtext != null)
3910 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3915 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3920 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3922 jvp.setTimeout(timeout);
3923 JButton jb = new JButton(button);
3924 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3926 { button }, button, modal, new JButton[] { jb }, false);
3930 public AlignFrame getCurrentAlignFrame()
3932 return Jalview.getInstance().getCurrentAlignFrame();