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/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 threads/workers
1482 * @param dispose - sets the dispose on close flag - JVM may terminate when set
1483 * @param terminateJvm - quit with prejudice - stops the JVM.
1485 public void quitTheDesktop(boolean dispose, boolean terminateJvm) {
1486 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1487 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1488 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1489 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1490 getBounds().y, getWidth(), getHeight()));
1492 if (jconsole != null)
1494 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1495 jconsole.stopConsole();
1500 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1503 // Frames should all close automatically. Keeping external
1504 // viewers open should already be decided by user.
1505 closeAll_actionPerformed(null);
1507 if (dialogExecutor != null)
1509 dialogExecutor.shutdownNow();
1512 if (groovyConsole != null)
1514 // suppress a possible repeat prompt to save script
1515 groovyConsole.setDirty(false);
1516 groovyConsole.exit();
1521 // note that shutdown hook will not be run
1522 jalview.bin.Console.debug("Force Quit selected by user");
1523 Runtime.getRuntime().halt(0);
1526 jalview.bin.Console.debug("Quit selected by user");
1529 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1530 // instance.dispose();
1533 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1535 final Runnable doDesktopQuit = () -> {
1537 // FIRST !! check for aborted quit
1538 if (QuitHandler.quitCancelled())
1540 jalview.bin.Console.debug("Quit was cancelled - Desktop aborting quit");
1544 // Proceed with quitting
1545 quitTheDesktop(disposeFlag, QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT);
1550 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1551 QuitHandler.defaultCancelQuit);
1555 * Exits the program and the JVM.
1557 * Don't call this directly
1559 * - use desktopQuit() above to tidy up first.
1561 * - use closeDesktop() to shutdown Jalview without shutting down the JVM
1567 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1568 // not run a second time if gotQuitResponse flag has been set (i.e. user
1569 // confirmed quit of some kind).
1570 Jalview.exit("Desktop exiting.", ExitCode.OK);
1573 private void storeLastKnownDimensions(String string, Rectangle jc)
1575 jalview.bin.Console.debug("Storing last known dimensions for " + string
1576 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1577 + " height:" + jc.height);
1579 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1580 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1581 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1582 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1592 public void aboutMenuItem_actionPerformed(ActionEvent e)
1594 new Thread(new Runnable()
1599 new SplashScreen(false);
1605 * Returns the html text for the About screen, including any available version
1606 * number, build details, author details and citation reference, but without
1607 * the enclosing {@code html} tags
1611 public String getAboutMessage()
1613 StringBuilder message = new StringBuilder(1024);
1614 message.append("<div style=\"font-family: sans-serif;\">")
1615 .append("<h1><strong>Version: ")
1616 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1617 .append("<strong>Built: <em>")
1618 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1619 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1620 .append("</strong>");
1622 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1623 if (latestVersion.equals("Checking"))
1625 // JBP removed this message for 2.11: May be reinstated in future version
1626 // message.append("<br>...Checking latest version...</br>");
1628 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1630 boolean red = false;
1631 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1632 .indexOf("automated build") == -1)
1635 // Displayed when code version and jnlp version do not match and code
1636 // version is not a development build
1637 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1640 message.append("<br>!! Version ")
1641 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1642 .append(" is available for download from ")
1643 .append(Cache.getDefault("www.jalview.org",
1644 "https://www.jalview.org"))
1648 message.append("</div>");
1651 message.append("<br>Authors: ");
1652 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1653 message.append(CITATION);
1655 message.append("</div>");
1657 return message.toString();
1661 * Action on requesting Help documentation
1664 public void documentationMenuItem_actionPerformed()
1668 if (Platform.isJS())
1670 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1679 Help.showHelpWindow();
1681 } catch (Exception ex)
1684 .errPrintln("Error opening help: " + ex.getMessage());
1689 public void closeAll_actionPerformed(ActionEvent e)
1691 // TODO show a progress bar while closing?
1692 JInternalFrame[] frames = desktop.getAllFrames();
1693 for (int i = 0; i < frames.length; i++)
1697 frames[i].setClosed(true);
1698 } catch (java.beans.PropertyVetoException ex)
1702 Jalview.getInstance().setCurrentAlignFrame(null);
1703 jalview.bin.Console.info("ALL CLOSED");
1706 * reset state of singleton objects as appropriate (clear down session state
1707 * when all windows are closed)
1709 StructureSelectionManager ssm = StructureSelectionManager
1710 .getStructureSelectionManager(this);
1717 public int structureViewersStillRunningCount()
1720 JInternalFrame[] frames = desktop.getAllFrames();
1721 for (int i = 0; i < frames.length; i++)
1723 if (frames[i] != null
1724 && frames[i] instanceof JalviewStructureDisplayI)
1726 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1734 public void raiseRelated_actionPerformed(ActionEvent e)
1736 reorderAssociatedWindows(false, false);
1740 public void minimizeAssociated_actionPerformed(ActionEvent e)
1742 reorderAssociatedWindows(true, false);
1745 void closeAssociatedWindows()
1747 reorderAssociatedWindows(false, true);
1753 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1757 protected void garbageCollect_actionPerformed(ActionEvent e)
1759 // We simply collect the garbage
1760 jalview.bin.Console.debug("Collecting garbage...");
1762 jalview.bin.Console.debug("Finished garbage collection.");
1768 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1772 protected void showMemusage_actionPerformed(ActionEvent e)
1774 desktop.showMemoryUsage(showMemusage.isSelected());
1781 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1785 protected void showConsole_actionPerformed(ActionEvent e)
1787 showConsole(showConsole.isSelected());
1790 Console jconsole = null;
1793 * control whether the java console is visible or not
1797 void showConsole(boolean selected)
1799 // TODO: decide if we should update properties file
1800 if (jconsole != null) // BH 2018
1802 showConsole.setSelected(selected);
1803 Cache.setProperty("SHOW_JAVA_CONSOLE",
1804 Boolean.valueOf(selected).toString());
1805 jconsole.setVisible(selected);
1809 void reorderAssociatedWindows(boolean minimize, boolean close)
1811 JInternalFrame[] frames = desktop.getAllFrames();
1812 if (frames == null || frames.length < 1)
1817 AlignmentViewport source = null, target = null;
1818 if (frames[0] instanceof AlignFrame)
1820 source = ((AlignFrame) frames[0]).getCurrentView();
1822 else if (frames[0] instanceof TreePanel)
1824 source = ((TreePanel) frames[0]).getViewPort();
1826 else if (frames[0] instanceof PCAPanel)
1828 source = ((PCAPanel) frames[0]).av;
1830 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1832 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1837 for (int i = 0; i < frames.length; i++)
1840 if (frames[i] == null)
1844 if (frames[i] instanceof AlignFrame)
1846 target = ((AlignFrame) frames[i]).getCurrentView();
1848 else if (frames[i] instanceof TreePanel)
1850 target = ((TreePanel) frames[i]).getViewPort();
1852 else if (frames[i] instanceof PCAPanel)
1854 target = ((PCAPanel) frames[i]).av;
1856 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1858 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1861 if (source == target)
1867 frames[i].setClosed(true);
1871 frames[i].setIcon(minimize);
1874 frames[i].toFront();
1878 } catch (java.beans.PropertyVetoException ex)
1893 protected void preferences_actionPerformed(ActionEvent e)
1895 Preferences.openPreferences();
1899 * Prompts the user to choose a file and then saves the Jalview state as a
1900 * Jalview project file
1903 public void saveState_actionPerformed()
1905 saveState_actionPerformed(false);
1908 public void saveState_actionPerformed(boolean saveAs)
1910 java.io.File projectFile = getProjectFile();
1911 // autoSave indicates we already have a file and don't need to ask
1912 boolean autoSave = projectFile != null && !saveAs
1913 && BackupFiles.getEnabled();
1915 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1916 // projectFile='"+projectFile+"',
1917 // saveAs="+saveAs+", Backups
1918 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1920 boolean approveSave = false;
1923 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1926 chooser.setFileView(new JalviewFileView());
1927 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1929 int value = chooser.showSaveDialog(this);
1931 if (value == JalviewFileChooser.APPROVE_OPTION)
1933 projectFile = chooser.getSelectedFile();
1934 setProjectFile(projectFile);
1939 if (approveSave || autoSave)
1941 final Desktop me = this;
1942 final java.io.File chosenFile = projectFile;
1943 new Thread(new Runnable()
1948 // TODO: refactor to Jalview desktop session controller action.
1949 setProgressBar(MessageManager.formatMessage(
1950 "label.saving_jalview_project", new Object[]
1951 { chosenFile.getName() }), chosenFile.hashCode());
1952 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1953 // TODO catch and handle errors for savestate
1954 // TODO prevent user from messing with the Desktop whilst we're saving
1957 boolean doBackup = BackupFiles.getEnabled();
1958 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1961 new Jalview2XML().saveState(
1962 doBackup ? backupfiles.getTempFile() : chosenFile);
1966 backupfiles.setWriteSuccess(true);
1967 backupfiles.rollBackupsAndRenameTempFile();
1969 } catch (OutOfMemoryError oom)
1971 new OOMWarning("Whilst saving current state to "
1972 + chosenFile.getName(), oom);
1973 } catch (Exception ex)
1975 jalview.bin.Console.error("Problems whilst trying to save to "
1976 + chosenFile.getName(), ex);
1977 JvOptionPane.showMessageDialog(me,
1978 MessageManager.formatMessage(
1979 "label.error_whilst_saving_current_state_to",
1981 { chosenFile.getName() }),
1982 MessageManager.getString("label.couldnt_save_project"),
1983 JvOptionPane.WARNING_MESSAGE);
1985 setProgressBar(null, chosenFile.hashCode());
1992 public void saveAsState_actionPerformed(ActionEvent e)
1994 saveState_actionPerformed(true);
1997 protected void setProjectFile(File choice)
1999 this.projectFile = choice;
2002 public File getProjectFile()
2004 return this.projectFile;
2008 * Shows a file chooser dialog and tries to read in the selected file as a
2012 public void loadState_actionPerformed()
2014 final String[] suffix = new String[] { "jvp", "jar" };
2015 final String[] desc = new String[] { "Jalview Project",
2016 "Jalview Project (old)" };
2017 JalviewFileChooser chooser = new JalviewFileChooser(
2018 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2019 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2023 chooser.setFileView(new JalviewFileView());
2024 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2025 chooser.setResponseHandler(0, () -> {
2026 File selectedFile = chooser.getSelectedFile();
2027 setProjectFile(selectedFile);
2028 String choice = selectedFile.getAbsolutePath();
2029 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2030 new Thread(new Runnable()
2037 new Jalview2XML().loadJalviewAlign(selectedFile);
2038 } catch (OutOfMemoryError oom)
2040 new OOMWarning("Whilst loading project from " + choice, oom);
2041 } catch (Exception ex)
2043 jalview.bin.Console.error(
2044 "Problems whilst loading project from " + choice, ex);
2045 JvOptionPane.showMessageDialog(Desktop.desktop,
2046 MessageManager.formatMessage(
2047 "label.error_whilst_loading_project_from",
2050 MessageManager.getString("label.couldnt_load_project"),
2051 JvOptionPane.WARNING_MESSAGE);
2054 }, "Project Loader").start();
2057 chooser.showOpenDialog(this);
2061 public void inputSequence_actionPerformed(ActionEvent e)
2063 new SequenceFetcher(this);
2066 JPanel progressPanel;
2068 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2070 public void startLoading(final Object fileName)
2072 if (fileLoadingCount == 0)
2074 fileLoadingPanels.add(addProgressPanel(MessageManager
2075 .formatMessage("label.loading_file", new Object[]
2081 private JPanel addProgressPanel(String string)
2083 if (progressPanel == null)
2085 progressPanel = new JPanel(new GridLayout(1, 1));
2086 totalProgressCount = 0;
2087 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2089 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2090 JProgressBar progressBar = new JProgressBar();
2091 progressBar.setIndeterminate(true);
2093 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2095 thisprogress.add(progressBar, BorderLayout.CENTER);
2096 progressPanel.add(thisprogress);
2097 ((GridLayout) progressPanel.getLayout()).setRows(
2098 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2099 ++totalProgressCount;
2100 instance.validate();
2101 return thisprogress;
2104 int totalProgressCount = 0;
2106 private void removeProgressPanel(JPanel progbar)
2108 if (progressPanel != null)
2110 synchronized (progressPanel)
2112 progressPanel.remove(progbar);
2113 GridLayout gl = (GridLayout) progressPanel.getLayout();
2114 gl.setRows(gl.getRows() - 1);
2115 if (--totalProgressCount < 1)
2117 this.getContentPane().remove(progressPanel);
2118 progressPanel = null;
2125 public void stopLoading()
2128 if (fileLoadingCount < 1)
2130 while (fileLoadingPanels.size() > 0)
2132 removeProgressPanel(fileLoadingPanels.remove(0));
2134 fileLoadingPanels.clear();
2135 fileLoadingCount = 0;
2140 public static int getViewCount(String alignmentId)
2142 AlignmentViewport[] aps = getViewports(alignmentId);
2143 return (aps == null) ? 0 : aps.length;
2148 * @param alignmentId
2149 * - if null, all sets are returned
2150 * @return all AlignmentPanels concerning the alignmentId sequence set
2152 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2154 if (Desktop.desktop == null)
2156 // no frames created and in headless mode
2157 // TODO: verify that frames are recoverable when in headless mode
2160 List<AlignmentPanel> aps = new ArrayList<>();
2161 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2166 for (AlignFrame af : frames)
2168 for (AlignmentPanel ap : af.alignPanels)
2170 if (alignmentId == null
2171 || alignmentId.equals(ap.av.getSequenceSetId()))
2177 if (aps.size() == 0)
2181 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2186 * get all the viewports on an alignment.
2188 * @param sequenceSetId
2189 * unique alignment id (may be null - all viewports returned in that
2191 * @return all viewports on the alignment bound to sequenceSetId
2193 public static AlignmentViewport[] getViewports(String sequenceSetId)
2195 List<AlignmentViewport> viewp = new ArrayList<>();
2196 if (desktop != null)
2198 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2200 for (AlignFrame afr : frames)
2202 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2203 .equals(sequenceSetId))
2205 if (afr.alignPanels != null)
2207 for (AlignmentPanel ap : afr.alignPanels)
2209 if (sequenceSetId == null
2210 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2218 viewp.add(afr.getViewport());
2222 if (viewp.size() > 0)
2224 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2231 * Explode the views in the given frame into separate AlignFrame
2235 public static void explodeViews(AlignFrame af)
2237 int size = af.alignPanels.size();
2243 // FIXME: ideally should use UI interface API
2244 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2245 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2246 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2247 for (int i = 0; i < size; i++)
2249 AlignmentPanel ap = af.alignPanels.get(i);
2251 AlignFrame newaf = new AlignFrame(ap);
2253 // transfer reference for existing feature settings to new alignFrame
2254 if (ap == af.alignPanel)
2256 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2258 newaf.featureSettings = viewFeatureSettings;
2260 newaf.setFeatureSettingsGeometry(fsBounds);
2264 * Restore the view's last exploded frame geometry if known. Multiple views from
2265 * one exploded frame share and restore the same (frame) position and size.
2267 Rectangle geometry = ap.av.getExplodedGeometry();
2268 if (geometry != null)
2270 newaf.setBounds(geometry);
2273 ap.av.setGatherViewsHere(false);
2275 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2276 AlignFrame.DEFAULT_HEIGHT);
2277 // and materialise a new feature settings dialog instance for the new
2279 // (closes the old as if 'OK' was pressed)
2280 if (ap == af.alignPanel && newaf.featureSettings != null
2281 && newaf.featureSettings.isOpen()
2282 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2284 newaf.showFeatureSettingsUI();
2288 af.featureSettings = null;
2289 af.alignPanels.clear();
2290 af.closeMenuItem_actionPerformed(true);
2295 * Gather expanded views (separate AlignFrame's) with the same sequence set
2296 * identifier back in to this frame as additional views, and close the
2297 * expanded views. Note the expanded frames may themselves have multiple
2298 * views. We take the lot.
2302 public void gatherViews(AlignFrame source)
2304 source.viewport.setGatherViewsHere(true);
2305 source.viewport.setExplodedGeometry(source.getBounds());
2306 JInternalFrame[] frames = desktop.getAllFrames();
2307 String viewId = source.viewport.getSequenceSetId();
2308 for (int t = 0; t < frames.length; t++)
2310 if (frames[t] instanceof AlignFrame && frames[t] != source)
2312 AlignFrame af = (AlignFrame) frames[t];
2313 boolean gatherThis = false;
2314 for (int a = 0; a < af.alignPanels.size(); a++)
2316 AlignmentPanel ap = af.alignPanels.get(a);
2317 if (viewId.equals(ap.av.getSequenceSetId()))
2320 ap.av.setGatherViewsHere(false);
2321 ap.av.setExplodedGeometry(af.getBounds());
2322 source.addAlignmentPanel(ap, false);
2328 if (af.featureSettings != null && af.featureSettings.isOpen())
2330 if (source.featureSettings == null)
2332 // preserve the feature settings geometry for this frame
2333 source.featureSettings = af.featureSettings;
2334 source.setFeatureSettingsGeometry(
2335 af.getFeatureSettingsGeometry());
2339 // close it and forget
2340 af.featureSettings.close();
2343 af.alignPanels.clear();
2344 af.closeMenuItem_actionPerformed(true);
2349 // refresh the feature setting UI for the source frame if it exists
2350 if (source.featureSettings != null && source.featureSettings.isOpen())
2352 source.showFeatureSettingsUI();
2357 public JInternalFrame[] getAllFrames()
2359 return desktop.getAllFrames();
2363 * Checks the given url to see if it gives a response indicating that the user
2364 * should be informed of a new questionnaire.
2368 public void checkForQuestionnaire(String url)
2370 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2371 // javax.swing.SwingUtilities.invokeLater(jvq);
2372 new Thread(jvq).start();
2375 public void checkURLLinks()
2377 // Thread off the URL link checker
2378 addDialogThread(new Runnable()
2383 if (Cache.getDefault("CHECKURLLINKS", true))
2385 // check what the actual links are - if it's just the default don't
2386 // bother with the warning
2387 List<String> links = Preferences.sequenceUrlLinks
2390 // only need to check links if there is one with a
2391 // SEQUENCE_ID which is not the default EMBL_EBI link
2392 ListIterator<String> li = links.listIterator();
2393 boolean check = false;
2394 List<JLabel> urls = new ArrayList<>();
2395 while (li.hasNext())
2397 String link = li.next();
2398 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2399 && !UrlConstants.isDefaultString(link))
2402 int barPos = link.indexOf("|");
2403 String urlMsg = barPos == -1 ? link
2404 : link.substring(0, barPos) + ": "
2405 + link.substring(barPos + 1);
2406 urls.add(new JLabel(urlMsg));
2414 // ask user to check in case URL links use old style tokens
2415 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2416 JPanel msgPanel = new JPanel();
2417 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2418 msgPanel.add(Box.createVerticalGlue());
2419 JLabel msg = new JLabel(MessageManager
2420 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2421 JLabel msg2 = new JLabel(MessageManager
2422 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2424 for (JLabel url : urls)
2430 final JCheckBox jcb = new JCheckBox(
2431 MessageManager.getString("label.do_not_display_again"));
2432 jcb.addActionListener(new ActionListener()
2435 public void actionPerformed(ActionEvent e)
2437 // update Cache settings for "don't show this again"
2438 boolean showWarningAgain = !jcb.isSelected();
2439 Cache.setProperty("CHECKURLLINKS",
2440 Boolean.valueOf(showWarningAgain).toString());
2445 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2447 .getString("label.SEQUENCE_ID_no_longer_used"),
2448 JvOptionPane.WARNING_MESSAGE);
2455 * Proxy class for JDesktopPane which optionally displays the current memory
2456 * usage and highlights the desktop area with a red bar if free memory runs
2461 public class MyDesktopPane extends JDesktopPane implements Runnable
2463 private static final float ONE_MB = 1048576f;
2465 boolean showMemoryUsage = false;
2469 java.text.NumberFormat df;
2471 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2474 public MyDesktopPane(boolean showMemoryUsage)
2476 showMemoryUsage(showMemoryUsage);
2479 public void showMemoryUsage(boolean showMemory)
2481 this.showMemoryUsage = showMemory;
2484 Thread worker = new Thread(this);
2490 public boolean isShowMemoryUsage()
2492 return showMemoryUsage;
2498 df = java.text.NumberFormat.getNumberInstance();
2499 df.setMaximumFractionDigits(2);
2500 runtime = Runtime.getRuntime();
2502 while (showMemoryUsage)
2506 maxMemory = runtime.maxMemory() / ONE_MB;
2507 allocatedMemory = runtime.totalMemory() / ONE_MB;
2508 freeMemory = runtime.freeMemory() / ONE_MB;
2509 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2511 percentUsage = (totalFreeMemory / maxMemory) * 100;
2513 // if (percentUsage < 20)
2515 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2517 // instance.set.setBorder(border1);
2520 // sleep after showing usage
2522 } catch (Exception ex)
2524 ex.printStackTrace();
2530 public void paintComponent(Graphics g)
2532 if (showMemoryUsage && g != null && df != null)
2534 if (percentUsage < 20)
2536 g.setColor(Color.red);
2538 FontMetrics fm = g.getFontMetrics();
2541 g.drawString(MessageManager.formatMessage("label.memory_stats",
2543 { df.format(totalFreeMemory), df.format(maxMemory),
2544 df.format(percentUsage) }),
2545 10, getHeight() - fm.getHeight());
2549 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2550 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2555 * Accessor method to quickly get all the AlignmentFrames loaded.
2557 * @return an array of AlignFrame, or null if none found
2560 public AlignFrame[] getAlignFrames()
2562 if (desktop == null)
2567 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2573 List<AlignFrame> avp = new ArrayList<>();
2575 for (int i = frames.length - 1; i > -1; i--)
2577 if (frames[i] instanceof AlignFrame)
2579 avp.add((AlignFrame) frames[i]);
2581 else if (frames[i] instanceof SplitFrame)
2584 * Also check for a split frame containing an AlignFrame
2586 GSplitFrame sf = (GSplitFrame) frames[i];
2587 if (sf.getTopFrame() instanceof AlignFrame)
2589 avp.add((AlignFrame) sf.getTopFrame());
2591 if (sf.getBottomFrame() instanceof AlignFrame)
2593 avp.add((AlignFrame) sf.getBottomFrame());
2597 if (avp.size() == 0)
2601 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2608 public static AlignFrame[] getDesktopAlignFrames()
2610 if (Jalview.isHeadlessMode())
2612 // Desktop.desktop is null in headless mode
2613 return Jalview.getInstance().getAlignFrames();
2616 if (instance != null && desktop != null)
2618 return instance.getAlignFrames();
2625 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2629 public GStructureViewer[] getJmols()
2631 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2637 List<GStructureViewer> avp = new ArrayList<>();
2639 for (int i = frames.length - 1; i > -1; i--)
2641 if (frames[i] instanceof AppJmol)
2643 GStructureViewer af = (GStructureViewer) frames[i];
2647 if (avp.size() == 0)
2651 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2656 * Add Groovy Support to Jalview
2659 public void groovyShell_actionPerformed()
2663 openGroovyConsole();
2664 } catch (Exception ex)
2666 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2667 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2669 MessageManager.getString("label.couldnt_create_groovy_shell"),
2670 MessageManager.getString("label.groovy_support_failed"),
2671 JvOptionPane.ERROR_MESSAGE);
2676 * Open the Groovy console
2678 void openGroovyConsole()
2680 if (groovyConsole == null)
2682 JalviewObjectI j = new JalviewObject(this);
2683 groovyConsole = new groovy.console.ui.Console();
2684 groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j);
2685 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2686 getCurrentAlignFrame());
2687 groovyConsole.run();
2690 * We allow only one console at a time, so that AlignFrame menu option
2691 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2692 * enable 'Run script', when the console is opened, and the reverse when it is
2695 Window window = (Window) groovyConsole.getFrame();
2696 window.addWindowListener(new WindowAdapter()
2699 public void windowClosed(WindowEvent e)
2702 * rebind CMD-Q from Groovy Console to Jalview Quit
2705 enableExecuteGroovy(false);
2711 * show Groovy console window (after close and reopen)
2713 ((Window) groovyConsole.getFrame()).setVisible(true);
2716 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2717 * opening a second console
2719 enableExecuteGroovy(true);
2723 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2724 * binding when opened
2726 protected void addQuitHandler()
2729 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2731 .getKeyStroke(KeyEvent.VK_Q,
2732 jalview.util.ShortcutKeyMaskExWrapper
2733 .getMenuShortcutKeyMaskEx()),
2735 getRootPane().getActionMap().put("Quit", new AbstractAction()
2738 public void actionPerformed(ActionEvent e)
2746 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2749 * true if Groovy console is open
2751 public void enableExecuteGroovy(boolean enabled)
2754 * disable opening a second Groovy console (or re-enable when the console is
2757 groovyShell.setEnabled(!enabled);
2759 AlignFrame[] alignFrames = getDesktopAlignFrames();
2760 if (alignFrames != null)
2762 for (AlignFrame af : alignFrames)
2764 af.setGroovyEnabled(enabled);
2770 * Progress bars managed by the IProgressIndicator method.
2772 private Hashtable<Long, JPanel> progressBars;
2774 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2779 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2782 public void setProgressBar(String message, long id)
2784 if (progressBars == null)
2786 progressBars = new Hashtable<>();
2787 progressBarHandlers = new Hashtable<>();
2790 if (progressBars.get(Long.valueOf(id)) != null)
2792 JPanel panel = progressBars.remove(Long.valueOf(id));
2793 if (progressBarHandlers.contains(Long.valueOf(id)))
2795 progressBarHandlers.remove(Long.valueOf(id));
2797 removeProgressPanel(panel);
2801 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2808 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2809 * jalview.gui.IProgressIndicatorHandler)
2812 public void registerHandler(final long id,
2813 final IProgressIndicatorHandler handler)
2815 if (progressBarHandlers == null
2816 || !progressBars.containsKey(Long.valueOf(id)))
2818 throw new Error(MessageManager.getString(
2819 "error.call_setprogressbar_before_registering_handler"));
2821 progressBarHandlers.put(Long.valueOf(id), handler);
2822 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2823 if (handler.canCancel())
2825 JButton cancel = new JButton(
2826 MessageManager.getString("action.cancel"));
2827 final IProgressIndicator us = this;
2828 cancel.addActionListener(new ActionListener()
2832 public void actionPerformed(ActionEvent e)
2834 handler.cancelActivity(id);
2835 us.setProgressBar(MessageManager
2836 .formatMessage("label.cancelled_params", new Object[]
2837 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2841 progressPanel.add(cancel, BorderLayout.EAST);
2847 * @return true if any progress bars are still active
2850 public boolean operationInProgress()
2852 if (progressBars != null && progressBars.size() > 0)
2860 * This will return the first AlignFrame holding the given viewport instance.
2861 * It will break if there are more than one AlignFrames viewing a particular
2865 * @return alignFrame for viewport
2867 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2869 if (desktop != null)
2871 AlignmentPanel[] aps = getAlignmentPanels(
2872 viewport.getSequenceSetId());
2873 for (int panel = 0; aps != null && panel < aps.length; panel++)
2875 if (aps[panel] != null && aps[panel].av == viewport)
2877 return aps[panel].alignFrame;
2884 public VamsasApplication getVamsasApplication()
2886 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2892 * flag set if jalview GUI is being operated programmatically
2894 private boolean inBatchMode = false;
2897 * check if jalview GUI is being operated programmatically
2899 * @return inBatchMode
2901 public boolean isInBatchMode()
2907 * set flag if jalview GUI is being operated programmatically
2909 * @param inBatchMode
2911 public void setInBatchMode(boolean inBatchMode)
2913 this.inBatchMode = inBatchMode;
2917 * start service discovery and wait till it is done
2919 public void startServiceDiscovery()
2921 startServiceDiscovery(false);
2925 * start service discovery threads - blocking or non-blocking
2929 public void startServiceDiscovery(boolean blocking)
2931 startServiceDiscovery(blocking, false);
2935 * start service discovery threads
2938 * - false means call returns immediately
2939 * @param ignore_SHOW_JWS2_SERVICES_preference
2940 * - when true JABA services are discovered regardless of user's JWS2
2941 * discovery preference setting
2943 public void startServiceDiscovery(boolean blocking,
2944 boolean ignore_SHOW_JWS2_SERVICES_preference)
2946 boolean alive = true;
2947 Thread t0 = null, t1 = null, t2 = null;
2948 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2951 // todo: changesupport handlers need to be transferred
2952 if (discoverer == null)
2954 discoverer = new jalview.ws.jws1.Discoverer();
2955 // register PCS handler for desktop.
2956 discoverer.addPropertyChangeListener(changeSupport);
2958 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2959 // until we phase out completely
2960 (t0 = new Thread(discoverer)).start();
2963 if (ignore_SHOW_JWS2_SERVICES_preference
2964 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2966 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2967 .startDiscoverer(changeSupport);
2971 // TODO: do rest service discovery
2980 } catch (Exception e)
2983 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2984 || (t3 != null && t3.isAlive())
2985 || (t0 != null && t0.isAlive());
2991 * called to check if the service discovery process completed successfully.
2995 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2997 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2999 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3000 .getErrorMessages();
3003 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3005 if (serviceChangedDialog == null)
3007 // only run if we aren't already displaying one of these.
3008 addDialogThread(serviceChangedDialog = new Runnable()
3015 * JalviewDialog jd =new JalviewDialog() {
3017 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3019 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3021 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3023 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3025 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3026 * + " or mis-configured HTTP proxy settings.<br/>" +
3027 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3028 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3029 * true, true, "Web Service Configuration Problem", 450, 400);
3031 * jd.waitForInput();
3033 JvOptionPane.showConfirmDialog(Desktop.desktop,
3034 new JLabel("<html><table width=\"450\"><tr><td>"
3035 + ermsg + "</td></tr></table>"
3036 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3037 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3038 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3039 + " Tools->Preferences dialog box to change them.</p></html>"),
3040 "Web Service Configuration Problem",
3041 JvOptionPane.DEFAULT_OPTION,
3042 JvOptionPane.ERROR_MESSAGE);
3043 serviceChangedDialog = null;
3051 jalview.bin.Console.error(
3052 "Errors reported by JABA discovery service. Check web services preferences.\n"
3059 private Runnable serviceChangedDialog = null;
3062 * start a thread to open a URL in the configured browser. Pops up a warning
3063 * dialog to the user if there is an exception when calling out to the browser
3068 public static void showUrl(final String url)
3070 if (url!=null && !url.trim().equals("")) {
3071 jalview.bin.Console.info("Opening URL: "+url);
3072 showUrl(url, Desktop.instance);
3074 jalview.bin.Console.warn("Ignoring attempt to show an empty URL.");
3080 * Like showUrl but allows progress handler to be specified
3084 * (null) or object implementing IProgressIndicator
3086 public static void showUrl(final String url,
3087 final IProgressIndicator progress)
3089 new Thread(new Runnable()
3096 if (progress != null)
3098 progress.setProgressBar(MessageManager
3099 .formatMessage("status.opening_params", new Object[]
3100 { url }), this.hashCode());
3102 jalview.util.BrowserLauncher.openURL(url);
3103 } catch (Exception ex)
3105 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3107 .getString("label.web_browser_not_found_unix"),
3108 MessageManager.getString("label.web_browser_not_found"),
3109 JvOptionPane.WARNING_MESSAGE);
3111 ex.printStackTrace();
3113 if (progress != null)
3115 progress.setProgressBar(null, this.hashCode());
3121 public static WsParamSetManager wsparamManager = null;
3123 public static ParamManager getUserParameterStore()
3125 if (wsparamManager == null)
3127 wsparamManager = new WsParamSetManager();
3129 return wsparamManager;
3133 * static hyperlink handler proxy method for use by Jalview's internal windows
3137 public static void hyperlinkUpdate(HyperlinkEvent e)
3139 if (e.getEventType() == EventType.ACTIVATED)
3144 url = e.getURL().toString();
3145 Desktop.showUrl(url);
3146 } catch (Exception x)
3151 .error("Couldn't handle string " + url + " as a URL.");
3153 // ignore any exceptions due to dud links.
3160 * single thread that handles display of dialogs to user.
3162 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3165 * flag indicating if dialogExecutor should try to acquire a permit
3167 private volatile boolean dialogPause = true;
3172 private Semaphore block = new Semaphore(0);
3174 private static groovy.console.ui.Console groovyConsole;
3177 * add another dialog thread to the queue
3181 public void addDialogThread(final Runnable prompter)
3183 dialogExecutor.submit(new Runnable()
3190 acquireDialogQueue();
3192 if (instance == null)
3198 SwingUtilities.invokeAndWait(prompter);
3199 } catch (Exception q)
3201 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3208 private boolean dialogQueueStarted = false;
3210 public void startDialogQueue()
3212 if (dialogQueueStarted)
3216 // set the flag so we don't pause waiting for another permit and semaphore
3217 // the current task to begin
3218 releaseDialogQueue();
3219 dialogQueueStarted = true;
3222 public void acquireDialogQueue()
3228 } catch (InterruptedException e)
3230 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3235 public void releaseDialogQueue()
3242 dialogPause = false;
3246 * Outputs an image of the desktop to file in EPS format, after prompting the
3247 * user for choice of Text or Lineart character rendering (unless a preference
3248 * has been set). The file name is generated as
3251 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3255 protected void snapShotWindow_actionPerformed(ActionEvent e)
3257 // currently the menu option to do this is not shown
3260 int width = getWidth();
3261 int height = getHeight();
3263 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3264 ImageWriterI writer = new ImageWriterI()
3267 public void exportImage(Graphics g) throws Exception
3270 jalview.bin.Console.info("Successfully written snapshot to file "
3271 + of.getAbsolutePath());
3274 String title = "View of desktop";
3275 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3279 exporter.doExport(of, this, width, height, title);
3280 } catch (ImageOutputException ioex)
3282 jalview.bin.Console.error(
3283 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3289 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3290 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3291 * and location last time the view was expanded (if any). However it does not
3292 * remember the split pane divider location - this is set to match the
3293 * 'exploding' frame.
3297 public void explodeViews(SplitFrame sf)
3299 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3300 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3301 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3303 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3305 int viewCount = topPanels.size();
3312 * Processing in reverse order works, forwards order leaves the first panels not
3313 * visible. I don't know why!
3315 for (int i = viewCount - 1; i >= 0; i--)
3318 * Make new top and bottom frames. These take over the respective AlignmentPanel
3319 * objects, including their AlignmentViewports, so the cdna/protein
3320 * relationships between the viewports is carried over to the new split frames.
3322 * explodedGeometry holds the (x, y) position of the previously exploded
3323 * SplitFrame, and the (width, height) of the AlignFrame component
3325 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3326 AlignFrame newTopFrame = new AlignFrame(topPanel);
3327 newTopFrame.setSize(oldTopFrame.getSize());
3328 newTopFrame.setVisible(true);
3329 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3330 .getExplodedGeometry();
3331 if (geometry != null)
3333 newTopFrame.setSize(geometry.getSize());
3336 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3337 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3338 newBottomFrame.setSize(oldBottomFrame.getSize());
3339 newBottomFrame.setVisible(true);
3340 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3341 .getExplodedGeometry();
3342 if (geometry != null)
3344 newBottomFrame.setSize(geometry.getSize());
3347 topPanel.av.setGatherViewsHere(false);
3348 bottomPanel.av.setGatherViewsHere(false);
3349 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3351 if (geometry != null)
3353 splitFrame.setLocation(geometry.getLocation());
3355 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3359 * Clear references to the panels (now relocated in the new SplitFrames) before
3360 * closing the old SplitFrame.
3363 bottomPanels.clear();
3368 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3369 * back into the given SplitFrame as additional views. Note that the gathered
3370 * frames may themselves have multiple views.
3374 public void gatherViews(GSplitFrame source)
3377 * special handling of explodedGeometry for a view within a SplitFrame: - it
3378 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3379 * height) of the AlignFrame component
3381 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3382 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3383 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3384 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3385 myBottomFrame.viewport
3386 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3387 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3388 myTopFrame.viewport.setGatherViewsHere(true);
3389 myBottomFrame.viewport.setGatherViewsHere(true);
3390 String topViewId = myTopFrame.viewport.getSequenceSetId();
3391 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3393 JInternalFrame[] frames = desktop.getAllFrames();
3394 for (JInternalFrame frame : frames)
3396 if (frame instanceof SplitFrame && frame != source)
3398 SplitFrame sf = (SplitFrame) frame;
3399 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3400 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3401 boolean gatherThis = false;
3402 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3404 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3405 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3406 if (topViewId.equals(topPanel.av.getSequenceSetId())
3407 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3410 topPanel.av.setGatherViewsHere(false);
3411 bottomPanel.av.setGatherViewsHere(false);
3412 topPanel.av.setExplodedGeometry(
3413 new Rectangle(sf.getLocation(), topFrame.getSize()));
3414 bottomPanel.av.setExplodedGeometry(
3415 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3416 myTopFrame.addAlignmentPanel(topPanel, false);
3417 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3423 topFrame.getAlignPanels().clear();
3424 bottomFrame.getAlignPanels().clear();
3431 * The dust settles...give focus to the tab we did this from.
3433 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3436 public static groovy.console.ui.Console getGroovyConsole()
3438 return groovyConsole;
3442 * handles the payload of a drag and drop event.
3444 * TODO refactor to desktop utilities class
3447 * - Data source strings extracted from the drop event
3449 * - protocol for each data source extracted from the drop event
3453 * - the payload from the drop event
3456 public static void transferFromDropTarget(List<Object> files,
3457 List<DataSourceType> protocols, DropTargetDropEvent evt,
3458 Transferable t) throws Exception
3461 DataFlavor uriListFlavor = new DataFlavor(
3462 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3465 urlFlavour = new DataFlavor(
3466 "application/x-java-url; class=java.net.URL");
3467 } catch (ClassNotFoundException cfe)
3469 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3473 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3478 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3479 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3480 // means url may be null.
3483 protocols.add(DataSourceType.URL);
3484 files.add(url.toString());
3485 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3486 + files.get(files.size() - 1));
3491 if (Platform.isAMacAndNotJS())
3493 jalview.bin.Console.errPrintln(
3494 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3497 } catch (Throwable ex)
3499 jalview.bin.Console.debug("URL drop handler failed.", ex);
3502 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3504 // Works on Windows and MacOSX
3505 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3506 for (Object file : (List) t
3507 .getTransferData(DataFlavor.javaFileListFlavor))
3510 protocols.add(DataSourceType.FILE);
3515 // Unix like behaviour
3516 boolean added = false;
3518 if (t.isDataFlavorSupported(uriListFlavor))
3520 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3521 // This is used by Unix drag system
3522 data = (String) t.getTransferData(uriListFlavor);
3526 // fallback to text: workaround - on OSX where there's a JVM bug
3528 .debug("standard URIListFlavor failed. Trying text");
3529 // try text fallback
3530 DataFlavor textDf = new DataFlavor(
3531 "text/plain;class=java.lang.String");
3532 if (t.isDataFlavorSupported(textDf))
3534 data = (String) t.getTransferData(textDf);
3537 jalview.bin.Console.debug("Plain text drop content returned "
3538 + (data == null ? "Null - failed" : data));
3543 while (protocols.size() < files.size())
3545 jalview.bin.Console.debug("Adding missing FILE protocol for "
3546 + files.get(protocols.size()));
3547 protocols.add(DataSourceType.FILE);
3549 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3550 data, "\r\n"); st.hasMoreTokens();)
3553 String s = st.nextToken();
3554 if (s.startsWith("#"))
3556 // the line is a comment (as per the RFC 2483)
3559 java.net.URI uri = new java.net.URI(s);
3560 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3562 protocols.add(DataSourceType.URL);
3563 files.add(uri.toString());
3567 // otherwise preserve old behaviour: catch all for file objects
3568 java.io.File file = new java.io.File(uri);
3569 protocols.add(DataSourceType.FILE);
3570 files.add(file.toString());
3575 if (jalview.bin.Console.isDebugEnabled())
3577 if (data == null || !added)
3580 if (t.getTransferDataFlavors() != null
3581 && t.getTransferDataFlavors().length > 0)
3583 jalview.bin.Console.debug(
3584 "Couldn't resolve drop data. Here are the supported flavors:");
3585 for (DataFlavor fl : t.getTransferDataFlavors())
3587 jalview.bin.Console.debug(
3588 "Supported transfer dataflavor: " + fl.toString());
3589 Object df = t.getTransferData(fl);
3592 jalview.bin.Console.debug("Retrieves: " + df);
3596 jalview.bin.Console.debug("Retrieved nothing");
3603 .debug("Couldn't resolve dataflavor for drop: "
3609 if (Platform.isWindowsAndNotJS())
3612 .debug("Scanning dropped content for Windows Link Files");
3614 // resolve any .lnk files in the file drop
3615 for (int f = 0; f < files.size(); f++)
3617 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3618 if (protocols.get(f).equals(DataSourceType.FILE)
3619 && (source.endsWith(".lnk") || source.endsWith(".url")
3620 || source.endsWith(".site")))
3624 Object obj = files.get(f);
3625 File lf = (obj instanceof File ? (File) obj
3626 : new File((String) obj));
3627 // process link file to get a URL
3628 jalview.bin.Console.debug("Found potential link file: " + lf);
3629 WindowsShortcut wscfile = new WindowsShortcut(lf);
3630 String fullname = wscfile.getRealFilename();
3631 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3632 files.set(f, fullname);
3633 jalview.bin.Console.debug("Parsed real filename " + fullname
3634 + " to extract protocol: " + protocols.get(f));
3635 } catch (Exception ex)
3637 jalview.bin.Console.error(
3638 "Couldn't parse " + files.get(f) + " as a link file.",
3647 * Sets the Preferences property for experimental features to True or False
3648 * depending on the state of the controlling menu item
3651 protected void showExperimental_actionPerformed(boolean selected)
3653 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3657 * Answers a (possibly empty) list of any structure viewer frames (currently
3658 * for either Jmol or Chimera) which are currently open. This may optionally
3659 * be restricted to viewers of a specified class, or viewers linked to a
3660 * specified alignment panel.
3663 * if not null, only return viewers linked to this panel
3664 * @param structureViewerClass
3665 * if not null, only return viewers of this class
3668 public List<StructureViewerBase> getStructureViewers(
3669 AlignmentPanel apanel,
3670 Class<? extends StructureViewerBase> structureViewerClass)
3672 List<StructureViewerBase> result = new ArrayList<>();
3673 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3675 for (JInternalFrame frame : frames)
3677 if (frame instanceof StructureViewerBase)
3679 if (structureViewerClass == null
3680 || structureViewerClass.isInstance(frame))
3683 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3685 result.add((StructureViewerBase) frame);
3693 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3695 private static boolean debugScaleMessageDone = false;
3697 public static void debugScaleMessage(Graphics g)
3699 if (debugScaleMessageDone)
3703 // output used by tests to check HiDPI scaling settings in action
3706 Graphics2D gg = (Graphics2D) g;
3709 AffineTransform t = gg.getTransform();
3710 double scaleX = t.getScaleX();
3711 double scaleY = t.getScaleY();
3712 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3713 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3714 debugScaleMessageDone = true;
3718 jalview.bin.Console.debug("Desktop graphics null");
3720 } catch (Exception e)
3722 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3727 * closes the current instance window, but leaves the JVM running.
3728 * Bypasses any shutdown prompts, but does not set window dispose on close in case JVM terminates.
3730 public static void closeDesktop()
3732 if (Desktop.instance != null)
3734 Desktop us = Desktop.instance;
3735 Desktop.instance.quitTheDesktop(false, false);
3736 // call dispose in a separate thread - try to avoid indirect deadlocks
3739 new Thread(new Runnable()
3744 ExecutorService dex = us.dialogExecutor;
3748 us.dialogExecutor = null;
3749 us.block.drainPermits();
3759 * checks if any progress bars are being displayed in any of the windows
3760 * managed by the desktop
3764 public boolean operationsAreInProgress()
3766 JInternalFrame[] frames = getAllFrames();
3767 for (JInternalFrame frame : frames)
3769 if (frame instanceof IProgressIndicator)
3771 if (((IProgressIndicator) frame).operationInProgress())
3777 return operationInProgress();
3781 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3782 * The way the modal JInternalFrame is made means it cannot be a child of an
3783 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3785 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3787 protected static void addModal(AlignFrame af, JInternalFrame jif)
3789 alignFrameModalMap.put(af, jif);
3792 protected static void closeModal(AlignFrame af)
3794 if (!alignFrameModalMap.containsKey(af))
3798 JInternalFrame jif = alignFrameModalMap.get(af);
3803 jif.setClosed(true);
3804 } catch (PropertyVetoException e)
3806 e.printStackTrace();
3809 alignFrameModalMap.remove(af);
3812 public void nonBlockingDialog(String title, String message, String button,
3813 int type, boolean scrollable, boolean modal)
3815 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3819 public void nonBlockingDialog(String title, String message,
3820 String boxtext, String button, int type, boolean scrollable,
3821 boolean html, boolean modal, int timeout)
3823 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3824 scrollable, html, modal, timeout);
3827 public void nonBlockingDialog(int width, int height, String title,
3828 String message, String boxtext, String button, int type,
3829 boolean scrollable, boolean html, boolean modal, int timeout)
3833 type = JvOptionPane.WARNING_MESSAGE;
3835 JLabel jl = new JLabel(message);
3837 JTextComponent jtc = null;
3840 JTextPane jtp = new JTextPane();
3841 jtp.setContentType("text/html");
3842 jtp.setEditable(false);
3843 jtp.setAutoscrolls(true);
3844 jtp.setText(boxtext);
3850 JTextArea jta = new JTextArea(height, width);
3851 // jta.setLineWrap(true);
3852 jta.setEditable(false);
3853 jta.setWrapStyleWord(true);
3854 jta.setAutoscrolls(true);
3855 jta.setText(boxtext);
3860 JScrollPane jsp = scrollable
3861 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3862 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3865 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3867 JPanel jp = new JPanel();
3868 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3870 if (message != null)
3872 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3875 if (boxtext != null)
3879 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3884 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3889 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3891 jvp.setTimeout(timeout);
3892 JButton jb = new JButton(button);
3893 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3895 { button }, button, modal, new JButton[] { jb }, false);
3899 public AlignFrame getCurrentAlignFrame()
3901 return Jalview.getInstance().getCurrentAlignFrame();