2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.datatransfer.Clipboard;
36 import java.awt.datatransfer.ClipboardOwner;
37 import java.awt.datatransfer.DataFlavor;
38 import java.awt.datatransfer.Transferable;
39 import java.awt.dnd.DnDConstants;
40 import java.awt.dnd.DropTargetDragEvent;
41 import java.awt.dnd.DropTargetDropEvent;
42 import java.awt.dnd.DropTargetEvent;
43 import java.awt.dnd.DropTargetListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.WindowAdapter;
51 import java.awt.event.WindowEvent;
52 import java.awt.geom.AffineTransform;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
55 import java.beans.PropertyVetoException;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Locale;
69 import java.util.Vector;
70 import java.util.concurrent.ExecutorService;
71 import java.util.concurrent.Executors;
72 import java.util.concurrent.Semaphore;
74 import javax.swing.AbstractAction;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.Box;
78 import javax.swing.BoxLayout;
79 import javax.swing.DefaultDesktopManager;
80 import javax.swing.DesktopManager;
81 import javax.swing.InputMap;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JComboBox;
85 import javax.swing.JComponent;
86 import javax.swing.JDesktopPane;
87 import javax.swing.JFrame;
88 import javax.swing.JInternalFrame;
89 import javax.swing.JLabel;
90 import javax.swing.JMenuItem;
91 import javax.swing.JOptionPane;
92 import javax.swing.JPanel;
93 import javax.swing.JPopupMenu;
94 import javax.swing.JProgressBar;
95 import javax.swing.JScrollPane;
96 import javax.swing.JTextArea;
97 import javax.swing.JTextField;
98 import javax.swing.JTextPane;
99 import javax.swing.KeyStroke;
100 import javax.swing.SwingUtilities;
101 import javax.swing.WindowConstants;
102 import javax.swing.event.HyperlinkEvent;
103 import javax.swing.event.HyperlinkEvent.EventType;
104 import javax.swing.event.InternalFrameAdapter;
105 import javax.swing.event.InternalFrameEvent;
106 import javax.swing.text.JTextComponent;
108 import org.stackoverflowusers.file.WindowsShortcut;
110 import jalview.api.AlignViewportI;
111 import jalview.api.AlignmentViewPanel;
112 import jalview.api.structures.JalviewStructureDisplayI;
113 import jalview.bin.Cache;
114 import jalview.bin.Jalview;
115 import jalview.bin.Jalview.ExitCode;
116 import jalview.bin.argparser.Arg;
117 import jalview.bin.groovy.JalviewObject;
118 import jalview.bin.groovy.JalviewObjectI;
119 import jalview.datamodel.Alignment;
120 import jalview.datamodel.HiddenColumns;
121 import jalview.datamodel.Sequence;
122 import jalview.datamodel.SequenceI;
123 import jalview.gui.ImageExporter.ImageWriterI;
124 import jalview.gui.QuitHandler.QResponse;
125 import jalview.io.BackupFiles;
126 import jalview.io.DataSourceType;
127 import jalview.io.FileFormat;
128 import jalview.io.FileFormatException;
129 import jalview.io.FileFormatI;
130 import jalview.io.FileFormats;
131 import jalview.io.FileLoader;
132 import jalview.io.FormatAdapter;
133 import jalview.io.IdentifyFile;
134 import jalview.io.JalviewFileChooser;
135 import jalview.io.JalviewFileView;
136 import jalview.io.exceptions.ImageOutputException;
137 import jalview.jbgui.GSplitFrame;
138 import jalview.jbgui.GStructureViewer;
139 import jalview.project.Jalview2XML;
140 import jalview.structure.StructureSelectionManager;
141 import jalview.urls.IdOrgSettings;
142 import jalview.util.BrowserLauncher;
143 import jalview.util.ChannelProperties;
144 import jalview.util.ImageMaker.TYPE;
145 import jalview.util.LaunchUtils;
146 import jalview.util.MessageManager;
147 import jalview.util.Platform;
148 import jalview.util.ShortcutKeyMaskExWrapper;
149 import jalview.util.UrlConstants;
150 import jalview.viewmodel.AlignmentViewport;
151 import jalview.ws.params.ParamManager;
152 import jalview.ws.utils.UrlDownloadClient;
159 * @version $Revision: 1.155 $
161 public class Desktop extends jalview.jbgui.GDesktop
162 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
163 jalview.api.StructureSelectionManagerProvider, JalviewObjectI
165 private static final String CITATION;
168 URL bg_logo_url = ChannelProperties.getImageURL(
169 "bg_logo." + String.valueOf(SplashScreen.logoSize));
170 URL uod_logo_url = ChannelProperties.getImageURL(
171 "uod_banner." + String.valueOf(SplashScreen.logoSize));
172 boolean logo = (bg_logo_url != null || uod_logo_url != null);
173 StringBuilder sb = new StringBuilder();
175 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
180 sb.append(bg_logo_url == null ? ""
181 : "<img alt=\"Barton Group logo\" src=\""
182 + bg_logo_url.toString() + "\">");
183 sb.append(uod_logo_url == null ? ""
184 : " <img alt=\"University of Dundee shield\" src=\""
185 + uod_logo_url.toString() + "\">");
187 "<br><br>For help, see <a href=\"https://www.jalview.org/help/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
188 sb.append("<br><br>If you use Jalview, please cite:"
189 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
190 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
191 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
192 CITATION = sb.toString();
195 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
197 private static int DEFAULT_MIN_WIDTH = 300;
199 private static int DEFAULT_MIN_HEIGHT = 250;
201 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
203 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
205 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
207 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
209 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
211 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
213 public static void setLiveDragMode(boolean b)
215 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
216 : JDesktopPane.OUTLINE_DRAG_MODE;
218 desktop.setDragMode(DRAG_MODE);
221 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
223 public static boolean nosplash = false;
226 * news reader - null if it was never started.
228 private BlogReader jvnews = null;
230 private File projectFile;
234 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void addJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
255 * @param propertyName
257 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
258 * java.beans.PropertyChangeListener)
260 public void removeJalviewPropertyChangeListener(String propertyName,
261 PropertyChangeListener listener)
263 changeSupport.removeJalviewPropertyChangeListener(propertyName,
267 /** Singleton Desktop instance */
268 public static Desktop instance;
270 public static MyDesktopPane desktop;
272 public static MyDesktopPane getDesktop()
274 // BH 2018 could use currentThread() here as a reference to a
275 // Hashtable<Thread, MyDesktopPane> in JavaScript
279 static int openFrameCount = 0;
281 static final int xOffset = 30;
283 static final int yOffset = 30;
285 public static jalview.ws.jws1.Discoverer discoverer;
287 public static Object[] jalviewClipboard;
289 public static boolean internalCopy = false;
291 static int fileLoadingCount = 0;
293 class MyDesktopManager implements DesktopManager
296 private DesktopManager delegate;
298 public MyDesktopManager(DesktopManager delegate)
300 this.delegate = delegate;
304 public void activateFrame(JInternalFrame f)
308 delegate.activateFrame(f);
309 } catch (NullPointerException npe)
311 Point p = getMousePosition();
312 instance.showPasteMenu(p.x, p.y);
317 public void beginDraggingFrame(JComponent f)
319 delegate.beginDraggingFrame(f);
323 public void beginResizingFrame(JComponent f, int direction)
325 delegate.beginResizingFrame(f, direction);
329 public void closeFrame(JInternalFrame f)
331 delegate.closeFrame(f);
335 public void deactivateFrame(JInternalFrame f)
337 delegate.deactivateFrame(f);
341 public void deiconifyFrame(JInternalFrame f)
343 delegate.deiconifyFrame(f);
347 public void dragFrame(JComponent f, int newX, int newY)
353 delegate.dragFrame(f, newX, newY);
357 public void endDraggingFrame(JComponent f)
359 delegate.endDraggingFrame(f);
364 public void endResizingFrame(JComponent f)
366 delegate.endResizingFrame(f);
371 public void iconifyFrame(JInternalFrame f)
373 delegate.iconifyFrame(f);
377 public void maximizeFrame(JInternalFrame f)
379 delegate.maximizeFrame(f);
383 public void minimizeFrame(JInternalFrame f)
385 delegate.minimizeFrame(f);
389 public void openFrame(JInternalFrame f)
391 delegate.openFrame(f);
395 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
402 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
406 public void setBoundsForFrame(JComponent f, int newX, int newY,
407 int newWidth, int newHeight)
409 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
412 // All other methods, simply delegate
417 * Creates a new Desktop object.
423 * A note to implementors. It is ESSENTIAL that any activities that might
424 * block are spawned off as threads rather than waited for during this
429 doConfigureStructurePrefs();
430 setTitle(ChannelProperties.getProperty("app_name") + " "
431 + Cache.getProperty("VERSION"));
434 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
435 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
436 * officially documented or guaranteed to exist, so we access it via
437 * reflection. There appear to be unfathomable criteria about what this
438 * string can contain, and it if doesn't meet those criteria then "java"
439 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
440 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
441 * not. The reflection access may generate a warning: WARNING: An illegal
442 * reflective access operation has occurred WARNING: Illegal reflective
443 * access by jalview.gui.Desktop () to field
444 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
446 if (Platform.isLinux())
448 if (LaunchUtils.getJavaVersion() >= 11)
451 * Send this message to stderr as the warning that follows (due to
452 * reflection) also goes to stderr.
454 jalview.bin.Console.errPrintln(
455 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
457 final String awtAppClassName = "awtAppClassName";
460 Toolkit xToolkit = Toolkit.getDefaultToolkit();
461 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
462 Field awtAppClassNameField = null;
464 if (Arrays.stream(declaredFields)
465 .anyMatch(f -> f.getName().equals(awtAppClassName)))
467 awtAppClassNameField = xToolkit.getClass()
468 .getDeclaredField(awtAppClassName);
471 String title = ChannelProperties.getProperty("app_name");
472 if (awtAppClassNameField != null)
474 awtAppClassNameField.setAccessible(true);
475 awtAppClassNameField.set(xToolkit, title);
480 .debug("XToolkit: " + awtAppClassName + " not found");
482 } catch (Exception e)
484 jalview.bin.Console.debug("Error setting " + awtAppClassName);
485 jalview.bin.Console.trace(Cache.getStackTraceString(e));
489 setIconImages(ChannelProperties.getIconList());
491 // override quit handling when GUI OS close [X] button pressed
492 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
493 addWindowListener(new WindowAdapter()
496 public void windowClosing(WindowEvent ev)
498 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
502 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
504 boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE,
505 "SHOW_JAVA_CONSOLE", false);
507 // start dialogue queue for single dialogues
510 if (!Platform.isJS())
517 Desktop.instance.acquireDialogQueue();
519 jconsole = new Console(this);
520 jconsole.setHeader(Cache.getVersionDetailsForConsole());
521 showConsole(showjconsole);
523 Desktop.instance.releaseDialogQueue();
526 desktop = new MyDesktopPane(selmemusage);
528 showMemusage.setSelected(selmemusage);
529 desktop.setBackground(Color.white);
531 getContentPane().setLayout(new BorderLayout());
532 // alternate config - have scrollbars - see notes in JAL-153
533 // JScrollPane sp = new JScrollPane();
534 // sp.getViewport().setView(desktop);
535 // getContentPane().add(sp, BorderLayout.CENTER);
537 // BH 2018 - just an experiment to try unclipped JInternalFrames.
540 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
543 getContentPane().add(desktop, BorderLayout.CENTER);
544 desktop.setDragMode(DRAG_MODE);
546 // This line prevents Windows Look&Feel resizing all new windows to maximum
547 // if previous window was maximised
548 desktop.setDesktopManager(new MyDesktopManager(
549 Platform.isJS() ? desktop.getDesktopManager()
550 : new DefaultDesktopManager()));
552 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
553 : Platform.isAMacAndNotJS()
554 ? new AquaInternalFrameManager(
555 desktop.getDesktopManager())
556 : desktop.getDesktopManager())));
559 Rectangle dims = getLastKnownDimensions("");
566 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
567 int xPos = Math.max(5, (screenSize.width - 900) / 2);
568 int yPos = Math.max(5, (screenSize.height - 650) / 2);
569 setBounds(xPos, yPos, 900, 650);
572 if (!Platform.isJS())
579 showNews.setVisible(false);
581 experimentalFeatures.setSelected(showExperimental());
583 getIdentifiersOrgData();
587 // Spawn a thread that shows the splashscreen
590 SwingUtilities.invokeLater(new Runnable()
595 new SplashScreen(true);
600 // Thread off a new instance of the file chooser - this reduces the time
601 // it takes to open it later on.
602 new Thread(new Runnable()
607 jalview.bin.Console.debug("Filechooser init thread started.");
608 String fileFormat = FileLoader.getUseDefaultFileFormat()
609 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
611 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
613 jalview.bin.Console.debug("Filechooser init thread finished.");
616 // Add the service change listener
617 changeSupport.addJalviewPropertyChangeListener("services",
618 new PropertyChangeListener()
622 public void propertyChange(PropertyChangeEvent evt)
625 .debug("Firing service changed event for "
626 + evt.getNewValue());
627 JalviewServicesChanged(evt);
632 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
635 this.addMouseListener(ma = new MouseAdapter()
638 public void mousePressed(MouseEvent evt)
640 if (evt.isPopupTrigger()) // Mac
642 showPasteMenu(evt.getX(), evt.getY());
647 public void mouseReleased(MouseEvent evt)
649 if (evt.isPopupTrigger()) // Windows
651 showPasteMenu(evt.getX(), evt.getY());
655 desktop.addMouseListener(ma);
659 // used for jalviewjsTest
660 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
666 * Answers true if user preferences to enable experimental features is True
671 public boolean showExperimental()
673 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
674 Boolean.FALSE.toString());
675 return Boolean.valueOf(experimental).booleanValue();
678 public void doConfigureStructurePrefs()
680 // configure services
681 StructureSelectionManager ssm = StructureSelectionManager
682 .getStructureSelectionManager(this);
683 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
685 ssm.setAddTempFacAnnot(
686 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
687 ssm.setProcessSecondaryStructure(
688 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
689 // JAL-3915 - RNAView is no longer an option so this has no effect
690 ssm.setSecStructServices(
691 Cache.getDefault(Preferences.USE_RNAVIEW, false));
695 ssm.setAddTempFacAnnot(false);
696 ssm.setProcessSecondaryStructure(false);
697 ssm.setSecStructServices(false);
701 public void checkForNews()
703 final Desktop me = this;
704 // Thread off the news reader, in case there are connection problems.
705 new Thread(new Runnable()
710 jalview.bin.Console.debug("Starting news thread.");
711 jvnews = new BlogReader(me);
712 showNews.setVisible(true);
713 jalview.bin.Console.debug("Completed news thread.");
718 public void getIdentifiersOrgData()
720 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
721 {// Thread off the identifiers fetcher
722 new Thread(new Runnable()
728 .debug("Downloading data from identifiers.org");
731 UrlDownloadClient.download(IdOrgSettings.getUrl(),
732 IdOrgSettings.getDownloadLocation());
733 } catch (IOException e)
736 .debug("Exception downloading identifiers.org data"
746 protected void showNews_actionPerformed(ActionEvent e)
748 showNews(showNews.isSelected());
751 void showNews(boolean visible)
753 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
754 showNews.setSelected(visible);
755 if (visible && !jvnews.isVisible())
757 new Thread(new Runnable()
762 long now = System.currentTimeMillis();
763 Desktop.instance.setProgressBar(
764 MessageManager.getString("status.refreshing_news"), now);
765 jvnews.refreshNews();
766 Desktop.instance.setProgressBar(null, now);
774 * recover the last known dimensions for a jalview window
777 * - empty string is desktop, all other windows have unique prefix
778 * @return null or last known dimensions scaled to current geometry (if last
779 * window geom was known)
781 Rectangle getLastKnownDimensions(String windowName)
783 // TODO: lock aspect ratio for scaling desktop Bug #0058199
784 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
785 String x = Cache.getProperty(windowName + "SCREEN_X");
786 String y = Cache.getProperty(windowName + "SCREEN_Y");
787 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
788 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
789 if ((x != null) && (y != null) && (width != null) && (height != null))
791 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
792 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
793 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
795 // attempt #1 - try to cope with change in screen geometry - this
796 // version doesn't preserve original jv aspect ratio.
797 // take ratio of current screen size vs original screen size.
798 double sw = ((1f * screenSize.width) / (1f * Integer
799 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
800 double sh = ((1f * screenSize.height) / (1f * Integer
801 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
802 // rescale the bounds depending upon the current screen geometry.
803 ix = (int) (ix * sw);
804 iw = (int) (iw * sw);
805 iy = (int) (iy * sh);
806 ih = (int) (ih * sh);
807 if (ix >= screenSize.width)
809 jalview.bin.Console.debug(
810 "Window geometry location recall error: shifting horizontal to within screenbounds.");
811 ix = ix % screenSize.width;
813 if (iy >= screenSize.height)
815 jalview.bin.Console.debug(
816 "Window geometry location recall error: shifting vertical to within screenbounds.");
817 iy = iy % screenSize.height;
819 jalview.bin.Console.debug(
820 "Got last known dimensions for " + windowName + ": x:" + ix
821 + " y:" + iy + " width:" + iw + " height:" + ih);
823 // return dimensions for new instance
824 return new Rectangle(ix, iy, iw, ih);
829 void showPasteMenu(int x, int y)
831 JPopupMenu popup = new JPopupMenu();
832 JMenuItem item = new JMenuItem(
833 MessageManager.getString("label.paste_new_window"));
834 item.addActionListener(new ActionListener()
837 public void actionPerformed(ActionEvent evt)
844 popup.show(this, x, y);
849 // quick patch for JAL-4150 - needs some more work and test coverage
850 // TODO - unify below and AlignFrame.paste()
851 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
852 // clipboard has come from a different alignment window than the one where
853 // paste has been called! JAL-4151
855 if (Desktop.jalviewClipboard != null)
857 // The clipboard was filled from within Jalview, we must use the
859 // And dataset from the copied alignment
860 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
861 // be doubly sure that we create *new* sequence objects.
862 SequenceI[] sequences = new SequenceI[newseq.length];
863 for (int i = 0; i < newseq.length; i++)
865 sequences[i] = new Sequence(newseq[i]);
867 Alignment alignment = new Alignment(sequences);
868 // dataset is inherited
869 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
870 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
871 AlignFrame.DEFAULT_HEIGHT);
872 String newtitle = new String("Copied sequences");
874 if (Desktop.jalviewClipboard[2] != null)
876 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
877 af.viewport.setHiddenColumns(hc);
880 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
881 AlignFrame.DEFAULT_HEIGHT);
888 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
889 Transferable contents = c.getContents(this);
891 if (contents != null)
893 String file = (String) contents
894 .getTransferData(DataFlavor.stringFlavor);
896 FileFormatI format = new IdentifyFile().identify(file,
897 DataSourceType.PASTE);
899 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
902 } catch (Exception ex)
904 jalview.bin.Console.outPrintln(
905 "Unable to paste alignment from system clipboard:\n" + ex);
911 * Adds and opens the given frame to the desktop
922 public static synchronized void addInternalFrame(
923 final JInternalFrame frame, String title, int w, int h)
925 addInternalFrame(frame, title, true, w, h, true, false);
929 * Add an internal frame to the Jalview desktop
936 * When true, display frame immediately, otherwise, caller must call
937 * setVisible themselves.
943 public static synchronized void addInternalFrame(
944 final JInternalFrame frame, String title, boolean makeVisible,
947 addInternalFrame(frame, title, makeVisible, w, h, true, false);
951 * Add an internal frame to the Jalview desktop and make it visible
964 public static synchronized void addInternalFrame(
965 final JInternalFrame frame, String title, int w, int h,
968 addInternalFrame(frame, title, true, w, h, resizable, false);
972 * Add an internal frame to the Jalview desktop
979 * When true, display frame immediately, otherwise, caller must call
980 * setVisible themselves.
987 * @param ignoreMinSize
988 * Do not set the default minimum size for frame
990 public static synchronized void addInternalFrame(
991 final JInternalFrame frame, String title, boolean makeVisible,
992 int w, int h, boolean resizable, boolean ignoreMinSize)
995 // TODO: allow callers to determine X and Y position of frame (eg. via
997 // TODO: consider fixing method to update entries in the window submenu with
998 // the current window title
1000 frame.setTitle(title);
1001 if (frame.getWidth() < 1 || frame.getHeight() < 1)
1003 frame.setSize(w, h);
1005 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
1006 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
1007 // IF JALVIEW IS RUNNING HEADLESS
1008 // ///////////////////////////////////////////////
1009 if (instance == null || (System.getProperty("java.awt.headless") != null
1010 && System.getProperty("java.awt.headless").equals("true")))
1019 frame.setMinimumSize(
1020 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1022 // Set default dimension for Alignment Frame window.
1023 // The Alignment Frame window could be added from a number of places,
1025 // I did this here in order not to miss out on any Alignment frame.
1026 if (frame instanceof AlignFrame)
1028 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1029 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1033 frame.setVisible(makeVisible);
1034 frame.setClosable(true);
1035 frame.setResizable(resizable);
1036 frame.setMaximizable(resizable);
1037 frame.setIconifiable(resizable);
1038 frame.setOpaque(Platform.isJS());
1040 if (frame.getX() < 1 && frame.getY() < 1)
1042 frame.setLocation(xOffset * openFrameCount,
1043 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1047 * add an entry for the new frame in the Window menu (and remove it when the
1050 final JMenuItem menuItem = new JMenuItem(title);
1051 frame.addInternalFrameListener(new InternalFrameAdapter()
1054 public void internalFrameActivated(InternalFrameEvent evt)
1056 JInternalFrame itf = desktop.getSelectedFrame();
1059 if (itf instanceof AlignFrame)
1061 Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf);
1068 public void internalFrameClosed(InternalFrameEvent evt)
1070 PaintRefresher.RemoveComponent(frame);
1073 * defensive check to prevent frames being added half off the window
1075 if (openFrameCount > 0)
1081 * ensure no reference to alignFrame retained by menu item listener
1083 if (menuItem.getActionListeners().length > 0)
1085 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1087 windowMenu.remove(menuItem);
1091 menuItem.addActionListener(new ActionListener()
1094 public void actionPerformed(ActionEvent e)
1098 frame.setSelected(true);
1099 frame.setIcon(false);
1100 } catch (java.beans.PropertyVetoException ex)
1107 setKeyBindings(frame);
1109 // Since the latest FlatLaf patch, we occasionally have problems showing
1110 // structureViewer frames...
1112 boolean shown = false;
1113 Exception last = null;
1120 } catch (IllegalArgumentException iaex)
1124 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1125 + tries + " left) for " + frame.getTitle(), iaex);
1129 } catch (InterruptedException iex)
1134 } while (!shown && tries > 0);
1137 jalview.bin.Console.error(
1138 "Serious Problem whilst showing window " + frame.getTitle(),
1142 windowMenu.add(menuItem);
1147 frame.setSelected(true);
1148 frame.requestFocus();
1149 } catch (java.beans.PropertyVetoException ve)
1151 } catch (java.lang.ClassCastException cex)
1153 jalview.bin.Console.warn(
1154 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1160 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1165 private static void setKeyBindings(JInternalFrame frame)
1167 @SuppressWarnings("serial")
1168 final Action closeAction = new AbstractAction()
1171 public void actionPerformed(ActionEvent e)
1178 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1180 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1181 InputEvent.CTRL_DOWN_MASK);
1182 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1183 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1185 InputMap inputMap = frame
1186 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1187 String ctrlW = ctrlWKey.toString();
1188 inputMap.put(ctrlWKey, ctrlW);
1189 inputMap.put(cmdWKey, ctrlW);
1191 ActionMap actionMap = frame.getActionMap();
1192 actionMap.put(ctrlW, closeAction);
1196 public void lostOwnership(Clipboard clipboard, Transferable contents)
1200 Desktop.jalviewClipboard = null;
1203 internalCopy = false;
1207 public void dragEnter(DropTargetDragEvent evt)
1212 public void dragExit(DropTargetEvent evt)
1217 public void dragOver(DropTargetDragEvent evt)
1222 public void dropActionChanged(DropTargetDragEvent evt)
1233 public void drop(DropTargetDropEvent evt)
1235 boolean success = true;
1236 // JAL-1552 - acceptDrop required before getTransferable call for
1237 // Java's Transferable for native dnd
1238 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1239 Transferable t = evt.getTransferable();
1240 List<Object> files = new ArrayList<>();
1241 List<DataSourceType> protocols = new ArrayList<>();
1245 Desktop.transferFromDropTarget(files, protocols, evt, t);
1246 } catch (Exception e)
1248 e.printStackTrace();
1256 for (int i = 0; i < files.size(); i++)
1258 // BH 2018 File or String
1259 Object file = files.get(i);
1260 String fileName = file.toString();
1261 DataSourceType protocol = (protocols == null)
1262 ? DataSourceType.FILE
1264 FileFormatI format = null;
1266 if (fileName.endsWith(".jar"))
1268 format = FileFormat.Jalview;
1273 format = new IdentifyFile().identify(file, protocol);
1275 if (file instanceof File)
1277 Platform.cacheFileData((File) file);
1279 new FileLoader().LoadFile(null, file, protocol, format);
1282 } catch (Exception ex)
1287 evt.dropComplete(success); // need this to ensure input focus is properly
1288 // transfered to any new windows created
1298 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1300 String fileFormat = FileLoader.getUseDefaultFileFormat()
1301 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1303 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1304 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1305 BackupFiles.getEnabled());
1307 chooser.setFileView(new JalviewFileView());
1308 chooser.setDialogTitle(
1309 MessageManager.getString("label.open_local_file"));
1310 chooser.setToolTipText(MessageManager.getString("action.open"));
1312 chooser.setResponseHandler(0, () -> {
1313 File selectedFile = chooser.getSelectedFile();
1314 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1316 FileFormatI format = chooser.getSelectedFormat();
1319 * Call IdentifyFile to verify the file contains what its extension implies.
1320 * Skip this step for dynamically added file formats, because IdentifyFile does
1321 * not know how to recognise them.
1323 if (FileFormats.getInstance().isIdentifiable(format))
1327 format = new IdentifyFile().identify(selectedFile,
1328 DataSourceType.FILE);
1329 } catch (FileFormatException e)
1331 // format = null; //??
1335 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1338 chooser.showOpenDialog(this);
1342 * Shows a dialog for input of a URL at which to retrieve alignment data
1347 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1349 // This construct allows us to have a wider textfield
1351 JLabel label = new JLabel(
1352 MessageManager.getString("label.input_file_url"));
1354 JPanel panel = new JPanel(new GridLayout(2, 1));
1358 * the URL to fetch is input in Java: an editable combobox with history JS:
1359 * (pending JAL-3038) a plain text field
1362 String urlBase = "https://www.";
1363 if (Platform.isJS())
1365 history = new JTextField(urlBase, 35);
1374 JComboBox<String> asCombo = new JComboBox<>();
1375 asCombo.setPreferredSize(new Dimension(400, 20));
1376 asCombo.setEditable(true);
1377 asCombo.addItem(urlBase);
1378 String historyItems = Cache.getProperty("RECENT_URL");
1379 if (historyItems != null)
1381 for (String token : historyItems.split("\\t"))
1383 asCombo.addItem(token);
1390 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1391 MessageManager.getString("action.cancel") };
1392 Runnable action = () -> {
1393 @SuppressWarnings("unchecked")
1394 String url = (history instanceof JTextField
1395 ? ((JTextField) history).getText()
1396 : ((JComboBox<String>) history).getEditor().getItem()
1397 .toString().trim());
1399 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1401 if (viewport != null)
1403 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1404 FileFormat.Jalview);
1408 new FileLoader().LoadFile(url, DataSourceType.URL,
1409 FileFormat.Jalview);
1414 FileFormatI format = null;
1417 format = new IdentifyFile().identify(url, DataSourceType.URL);
1418 } catch (FileFormatException e)
1420 // TODO revise error handling, distinguish between
1421 // URL not found and response not valid
1426 String msg = MessageManager.formatMessage("label.couldnt_locate",
1428 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1429 MessageManager.getString("label.url_not_found"),
1430 JvOptionPane.WARNING_MESSAGE);
1434 if (viewport != null)
1436 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1441 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1445 String dialogOption = MessageManager
1446 .getString("label.input_alignment_from_url");
1447 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1448 .showInternalDialog(panel, dialogOption,
1449 JvOptionPane.YES_NO_CANCEL_OPTION,
1450 JvOptionPane.PLAIN_MESSAGE, null, options,
1451 MessageManager.getString("action.ok"));
1455 * Opens the CutAndPaste window for the user to paste an alignment in to
1458 * - if not null, the pasted alignment is added to the current
1459 * alignment; if null, to a new alignment window
1462 public void inputTextboxMenuItem_actionPerformed(
1463 AlignmentViewPanel viewPanel)
1465 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1466 cap.setForInput(viewPanel);
1467 Desktop.addInternalFrame(cap,
1468 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1473 * Check with user and saving files before actually quitting
1475 public void desktopQuit()
1477 desktopQuit(true, false);
1481 * close everything, stash window geometries, and shut down all associated
1485 * - sets the dispose on close flag - JVM may terminate when set
1486 * @param terminateJvm
1487 * - quit with prejudice - stops the JVM.
1489 public void quitTheDesktop(boolean dispose, boolean terminateJvm)
1491 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1492 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1493 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1494 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1495 getWidth(), getHeight()));
1497 if (jconsole != null)
1499 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1500 jconsole.stopConsole();
1505 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1508 // Frames should all close automatically. Keeping external
1509 // viewers open should already be decided by user.
1510 closeAll_actionPerformed(null);
1512 if (dialogExecutor != null)
1514 dialogExecutor.shutdownNow();
1517 if (groovyConsole != null)
1519 // suppress a possible repeat prompt to save script
1520 groovyConsole.setDirty(false);
1521 groovyConsole.exit();
1526 // note that shutdown hook will not be run
1527 jalview.bin.Console.debug("Force Quit selected by user");
1528 Runtime.getRuntime().halt(0);
1531 jalview.bin.Console.debug("Quit selected by user");
1534 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1535 // instance.dispose();
1539 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1541 final Runnable doDesktopQuit = () -> {
1543 // FIRST !! check for aborted quit
1544 if (QuitHandler.quitCancelled())
1547 .debug("Quit was cancelled - Desktop aborting quit");
1551 // Proceed with quitting
1552 quitTheDesktop(disposeFlag,
1553 QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT);
1558 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1559 QuitHandler.defaultCancelQuit);
1563 * Exits the program and the JVM.
1565 * Don't call this directly
1567 * - use desktopQuit() above to tidy up first.
1569 * - use closeDesktop() to shutdown Jalview without shutting down the JVM
1575 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1576 // not run a second time if gotQuitResponse flag has been set (i.e. user
1577 // confirmed quit of some kind).
1578 Jalview.exit("Desktop exiting.", ExitCode.OK);
1581 private void storeLastKnownDimensions(String string, Rectangle jc)
1583 jalview.bin.Console.debug("Storing last known dimensions for " + string
1584 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1585 + " height:" + jc.height);
1587 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1588 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1589 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1590 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1600 public void aboutMenuItem_actionPerformed(ActionEvent e)
1602 new Thread(new Runnable()
1607 new SplashScreen(false);
1613 * Returns the html text for the About screen, including any available version
1614 * number, build details, author details and citation reference, but without
1615 * the enclosing {@code html} tags
1619 public String getAboutMessage()
1621 StringBuilder message = new StringBuilder(1024);
1622 message.append("<div style=\"font-family: sans-serif;\">")
1623 .append("<h1><strong>Version: ")
1624 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1625 .append("<strong>Built: <em>")
1626 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1627 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1628 .append("</strong>");
1630 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1631 if (latestVersion.equals("Checking"))
1633 // JBP removed this message for 2.11: May be reinstated in future version
1634 // message.append("<br>...Checking latest version...</br>");
1636 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1638 boolean red = false;
1639 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1640 .indexOf("automated build") == -1)
1643 // Displayed when code version and jnlp version do not match and code
1644 // version is not a development build
1645 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1648 message.append("<br>!! Version ")
1649 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1650 .append(" is available for download from ")
1651 .append(Cache.getDefault("www.jalview.org",
1652 "https://www.jalview.org"))
1656 message.append("</div>");
1659 message.append("<br>Authors: ");
1660 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1661 message.append(CITATION);
1663 message.append("</div>");
1665 return message.toString();
1669 * Action on requesting Help documentation
1672 public void documentationMenuItem_actionPerformed()
1676 if (Platform.isJS())
1678 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1687 Help.showHelpWindow();
1689 } catch (Exception ex)
1692 .errPrintln("Error opening help: " + ex.getMessage());
1697 public void closeAll_actionPerformed(ActionEvent e)
1699 // TODO show a progress bar while closing?
1700 JInternalFrame[] frames = desktop.getAllFrames();
1701 for (int i = 0; i < frames.length; i++)
1705 frames[i].setClosed(true);
1706 } catch (java.beans.PropertyVetoException ex)
1710 Jalview.getInstance().setCurrentAlignFrame(null);
1711 jalview.bin.Console.info("ALL CLOSED");
1714 * reset state of singleton objects as appropriate (clear down session state
1715 * when all windows are closed)
1717 StructureSelectionManager ssm = StructureSelectionManager
1718 .getStructureSelectionManager(this);
1725 public int structureViewersStillRunningCount()
1728 JInternalFrame[] frames = desktop.getAllFrames();
1729 for (int i = 0; i < frames.length; i++)
1731 if (frames[i] != null
1732 && frames[i] instanceof JalviewStructureDisplayI)
1734 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1742 public void raiseRelated_actionPerformed(ActionEvent e)
1744 reorderAssociatedWindows(false, false);
1748 public void minimizeAssociated_actionPerformed(ActionEvent e)
1750 reorderAssociatedWindows(true, false);
1753 void closeAssociatedWindows()
1755 reorderAssociatedWindows(false, true);
1761 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1765 protected void garbageCollect_actionPerformed(ActionEvent e)
1767 // We simply collect the garbage
1768 jalview.bin.Console.debug("Collecting garbage...");
1770 jalview.bin.Console.debug("Finished garbage collection.");
1776 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1780 protected void showMemusage_actionPerformed(ActionEvent e)
1782 desktop.showMemoryUsage(showMemusage.isSelected());
1789 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1793 protected void showConsole_actionPerformed(ActionEvent e)
1795 showConsole(showConsole.isSelected());
1798 Console jconsole = null;
1801 * control whether the java console is visible or not
1805 void showConsole(boolean selected)
1807 // TODO: decide if we should update properties file
1808 if (jconsole != null) // BH 2018
1810 showConsole.setSelected(selected);
1811 Cache.setProperty("SHOW_JAVA_CONSOLE",
1812 Boolean.valueOf(selected).toString());
1813 jconsole.setVisible(selected);
1817 void reorderAssociatedWindows(boolean minimize, boolean close)
1819 JInternalFrame[] frames = desktop.getAllFrames();
1820 if (frames == null || frames.length < 1)
1825 AlignmentViewport source = null, target = null;
1826 if (frames[0] instanceof AlignFrame)
1828 source = ((AlignFrame) frames[0]).getCurrentView();
1830 else if (frames[0] instanceof TreePanel)
1832 source = ((TreePanel) frames[0]).getViewPort();
1834 else if (frames[0] instanceof PCAPanel)
1836 source = ((PCAPanel) frames[0]).av;
1838 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1840 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1845 for (int i = 0; i < frames.length; i++)
1848 if (frames[i] == null)
1852 if (frames[i] instanceof AlignFrame)
1854 target = ((AlignFrame) frames[i]).getCurrentView();
1856 else if (frames[i] instanceof TreePanel)
1858 target = ((TreePanel) frames[i]).getViewPort();
1860 else if (frames[i] instanceof PCAPanel)
1862 target = ((PCAPanel) frames[i]).av;
1864 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1866 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1869 if (source == target)
1875 frames[i].setClosed(true);
1879 frames[i].setIcon(minimize);
1882 frames[i].toFront();
1886 } catch (java.beans.PropertyVetoException ex)
1901 protected void preferences_actionPerformed(ActionEvent e)
1903 Preferences.openPreferences();
1907 * Prompts the user to choose a file and then saves the Jalview state as a
1908 * Jalview project file
1911 public void saveState_actionPerformed()
1913 saveState_actionPerformed(false);
1916 public void saveState_actionPerformed(boolean saveAs)
1918 java.io.File projectFile = getProjectFile();
1919 // autoSave indicates we already have a file and don't need to ask
1920 boolean autoSave = projectFile != null && !saveAs
1921 && BackupFiles.getEnabled();
1923 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1924 // projectFile='"+projectFile+"',
1925 // saveAs="+saveAs+", Backups
1926 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1928 boolean approveSave = false;
1931 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1934 chooser.setFileView(new JalviewFileView());
1935 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1937 int value = chooser.showSaveDialog(this);
1939 if (value == JalviewFileChooser.APPROVE_OPTION)
1941 projectFile = chooser.getSelectedFile();
1942 setProjectFile(projectFile);
1947 if (approveSave || autoSave)
1949 final Desktop me = this;
1950 final java.io.File chosenFile = projectFile;
1951 new Thread(new Runnable()
1956 // TODO: refactor to Jalview desktop session controller action.
1957 setProgressBar(MessageManager.formatMessage(
1958 "label.saving_jalview_project", new Object[]
1959 { chosenFile.getName() }), chosenFile.hashCode());
1960 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1961 // TODO catch and handle errors for savestate
1962 // TODO prevent user from messing with the Desktop whilst we're saving
1965 boolean doBackup = BackupFiles.getEnabled();
1966 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1969 new Jalview2XML().saveState(
1970 doBackup ? backupfiles.getTempFile() : chosenFile);
1974 backupfiles.setWriteSuccess(true);
1975 backupfiles.rollBackupsAndRenameTempFile();
1977 } catch (OutOfMemoryError oom)
1979 new OOMWarning("Whilst saving current state to "
1980 + chosenFile.getName(), oom);
1981 } catch (Exception ex)
1983 jalview.bin.Console.error("Problems whilst trying to save to "
1984 + chosenFile.getName(), ex);
1985 JvOptionPane.showMessageDialog(me,
1986 MessageManager.formatMessage(
1987 "label.error_whilst_saving_current_state_to",
1989 { chosenFile.getName() }),
1990 MessageManager.getString("label.couldnt_save_project"),
1991 JvOptionPane.WARNING_MESSAGE);
1993 setProgressBar(null, chosenFile.hashCode());
2000 public void saveAsState_actionPerformed(ActionEvent e)
2002 saveState_actionPerformed(true);
2005 protected void setProjectFile(File choice)
2007 this.projectFile = choice;
2010 public File getProjectFile()
2012 return this.projectFile;
2016 * Shows a file chooser dialog and tries to read in the selected file as a
2020 public void loadState_actionPerformed()
2022 final String[] suffix = new String[] { "jvp", "jar" };
2023 final String[] desc = new String[] { "Jalview Project",
2024 "Jalview Project (old)" };
2025 JalviewFileChooser chooser = new JalviewFileChooser(
2026 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2027 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2031 chooser.setFileView(new JalviewFileView());
2032 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2033 chooser.setResponseHandler(0, () -> {
2034 File selectedFile = chooser.getSelectedFile();
2035 setProjectFile(selectedFile);
2036 String choice = selectedFile.getAbsolutePath();
2037 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2038 new Thread(new Runnable()
2045 new Jalview2XML().loadJalviewAlign(selectedFile);
2046 } catch (OutOfMemoryError oom)
2048 new OOMWarning("Whilst loading project from " + choice, oom);
2049 } catch (Exception ex)
2051 jalview.bin.Console.error(
2052 "Problems whilst loading project from " + choice, ex);
2053 JvOptionPane.showMessageDialog(Desktop.desktop,
2054 MessageManager.formatMessage(
2055 "label.error_whilst_loading_project_from",
2058 MessageManager.getString("label.couldnt_load_project"),
2059 JvOptionPane.WARNING_MESSAGE);
2062 }, "Project Loader").start();
2065 chooser.showOpenDialog(this);
2069 public void inputSequence_actionPerformed(ActionEvent e)
2071 new SequenceFetcher(this);
2074 JPanel progressPanel;
2076 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2078 public void startLoading(final Object fileName)
2080 if (fileLoadingCount == 0)
2082 fileLoadingPanels.add(addProgressPanel(MessageManager
2083 .formatMessage("label.loading_file", new Object[]
2089 private JPanel addProgressPanel(String string)
2091 if (progressPanel == null)
2093 progressPanel = new JPanel(new GridLayout(1, 1));
2094 totalProgressCount = 0;
2095 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2097 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2098 JProgressBar progressBar = new JProgressBar();
2099 progressBar.setIndeterminate(true);
2101 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2103 thisprogress.add(progressBar, BorderLayout.CENTER);
2104 progressPanel.add(thisprogress);
2105 ((GridLayout) progressPanel.getLayout()).setRows(
2106 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2107 ++totalProgressCount;
2108 instance.validate();
2109 return thisprogress;
2112 int totalProgressCount = 0;
2114 private void removeProgressPanel(JPanel progbar)
2116 if (progressPanel != null)
2118 synchronized (progressPanel)
2120 progressPanel.remove(progbar);
2121 GridLayout gl = (GridLayout) progressPanel.getLayout();
2122 gl.setRows(gl.getRows() - 1);
2123 if (--totalProgressCount < 1)
2125 this.getContentPane().remove(progressPanel);
2126 progressPanel = null;
2133 public void stopLoading()
2136 if (fileLoadingCount < 1)
2138 while (fileLoadingPanels.size() > 0)
2140 removeProgressPanel(fileLoadingPanels.remove(0));
2142 fileLoadingPanels.clear();
2143 fileLoadingCount = 0;
2148 public static int getViewCount(String alignmentId)
2150 AlignmentViewport[] aps = getViewports(alignmentId);
2151 return (aps == null) ? 0 : aps.length;
2156 * @param alignmentId
2157 * - if null, all sets are returned
2158 * @return all AlignmentPanels concerning the alignmentId sequence set
2160 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2162 if (Desktop.desktop == null)
2164 // no frames created and in headless mode
2165 // TODO: verify that frames are recoverable when in headless mode
2168 List<AlignmentPanel> aps = new ArrayList<>();
2169 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2174 for (AlignFrame af : frames)
2176 for (AlignmentPanel ap : af.alignPanels)
2178 if (alignmentId == null
2179 || alignmentId.equals(ap.av.getSequenceSetId()))
2185 if (aps.size() == 0)
2189 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2194 * get all the viewports on an alignment.
2196 * @param sequenceSetId
2197 * unique alignment id (may be null - all viewports returned in that
2199 * @return all viewports on the alignment bound to sequenceSetId
2201 public static AlignmentViewport[] getViewports(String sequenceSetId)
2203 List<AlignmentViewport> viewp = new ArrayList<>();
2204 if (desktop != null)
2206 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2208 for (AlignFrame afr : frames)
2210 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2211 .equals(sequenceSetId))
2213 if (afr.alignPanels != null)
2215 for (AlignmentPanel ap : afr.alignPanels)
2217 if (sequenceSetId == null
2218 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2226 viewp.add(afr.getViewport());
2230 if (viewp.size() > 0)
2232 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2239 * Explode the views in the given frame into separate AlignFrame
2243 public static void explodeViews(AlignFrame af)
2245 int size = af.alignPanels.size();
2251 // FIXME: ideally should use UI interface API
2252 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2253 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2254 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2255 for (int i = 0; i < size; i++)
2257 AlignmentPanel ap = af.alignPanels.get(i);
2259 AlignFrame newaf = new AlignFrame(ap);
2261 // transfer reference for existing feature settings to new alignFrame
2262 if (ap == af.alignPanel)
2264 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2266 newaf.featureSettings = viewFeatureSettings;
2268 newaf.setFeatureSettingsGeometry(fsBounds);
2272 * Restore the view's last exploded frame geometry if known. Multiple views from
2273 * one exploded frame share and restore the same (frame) position and size.
2275 Rectangle geometry = ap.av.getExplodedGeometry();
2276 if (geometry != null)
2278 newaf.setBounds(geometry);
2281 ap.av.setGatherViewsHere(false);
2283 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2284 AlignFrame.DEFAULT_HEIGHT);
2285 // and materialise a new feature settings dialog instance for the new
2287 // (closes the old as if 'OK' was pressed)
2288 if (ap == af.alignPanel && newaf.featureSettings != null
2289 && newaf.featureSettings.isOpen()
2290 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2292 newaf.showFeatureSettingsUI();
2296 af.featureSettings = null;
2297 af.alignPanels.clear();
2298 af.closeMenuItem_actionPerformed(true);
2303 * Gather expanded views (separate AlignFrame's) with the same sequence set
2304 * identifier back in to this frame as additional views, and close the
2305 * expanded views. Note the expanded frames may themselves have multiple
2306 * views. We take the lot.
2310 public void gatherViews(AlignFrame source)
2312 source.viewport.setGatherViewsHere(true);
2313 source.viewport.setExplodedGeometry(source.getBounds());
2314 JInternalFrame[] frames = desktop.getAllFrames();
2315 String viewId = source.viewport.getSequenceSetId();
2316 for (int t = 0; t < frames.length; t++)
2318 if (frames[t] instanceof AlignFrame && frames[t] != source)
2320 AlignFrame af = (AlignFrame) frames[t];
2321 boolean gatherThis = false;
2322 for (int a = 0; a < af.alignPanels.size(); a++)
2324 AlignmentPanel ap = af.alignPanels.get(a);
2325 if (viewId.equals(ap.av.getSequenceSetId()))
2328 ap.av.setGatherViewsHere(false);
2329 ap.av.setExplodedGeometry(af.getBounds());
2330 source.addAlignmentPanel(ap, false);
2336 if (af.featureSettings != null && af.featureSettings.isOpen())
2338 if (source.featureSettings == null)
2340 // preserve the feature settings geometry for this frame
2341 source.featureSettings = af.featureSettings;
2342 source.setFeatureSettingsGeometry(
2343 af.getFeatureSettingsGeometry());
2347 // close it and forget
2348 af.featureSettings.close();
2351 af.alignPanels.clear();
2352 af.closeMenuItem_actionPerformed(true);
2357 // refresh the feature setting UI for the source frame if it exists
2358 if (source.featureSettings != null && source.featureSettings.isOpen())
2360 source.showFeatureSettingsUI();
2365 public JInternalFrame[] getAllFrames()
2367 return desktop.getAllFrames();
2371 * Checks the given url to see if it gives a response indicating that the user
2372 * should be informed of a new questionnaire.
2376 public void checkForQuestionnaire(String url)
2378 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2379 // javax.swing.SwingUtilities.invokeLater(jvq);
2380 new Thread(jvq).start();
2383 public void checkURLLinks()
2385 // Thread off the URL link checker
2386 addDialogThread(new Runnable()
2391 if (Cache.getDefault("CHECKURLLINKS", true))
2393 // check what the actual links are - if it's just the default don't
2394 // bother with the warning
2395 List<String> links = Preferences.sequenceUrlLinks
2398 // only need to check links if there is one with a
2399 // SEQUENCE_ID which is not the default EMBL_EBI link
2400 ListIterator<String> li = links.listIterator();
2401 boolean check = false;
2402 List<JLabel> urls = new ArrayList<>();
2403 while (li.hasNext())
2405 String link = li.next();
2406 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2407 && !UrlConstants.isDefaultString(link))
2410 int barPos = link.indexOf("|");
2411 String urlMsg = barPos == -1 ? link
2412 : link.substring(0, barPos) + ": "
2413 + link.substring(barPos + 1);
2414 urls.add(new JLabel(urlMsg));
2422 // ask user to check in case URL links use old style tokens
2423 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2424 JPanel msgPanel = new JPanel();
2425 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2426 msgPanel.add(Box.createVerticalGlue());
2427 JLabel msg = new JLabel(MessageManager
2428 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2429 JLabel msg2 = new JLabel(MessageManager
2430 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2432 for (JLabel url : urls)
2438 final JCheckBox jcb = new JCheckBox(
2439 MessageManager.getString("label.do_not_display_again"));
2440 jcb.addActionListener(new ActionListener()
2443 public void actionPerformed(ActionEvent e)
2445 // update Cache settings for "don't show this again"
2446 boolean showWarningAgain = !jcb.isSelected();
2447 Cache.setProperty("CHECKURLLINKS",
2448 Boolean.valueOf(showWarningAgain).toString());
2453 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2455 .getString("label.SEQUENCE_ID_no_longer_used"),
2456 JvOptionPane.WARNING_MESSAGE);
2463 * Proxy class for JDesktopPane which optionally displays the current memory
2464 * usage and highlights the desktop area with a red bar if free memory runs
2469 public class MyDesktopPane extends JDesktopPane implements Runnable
2471 private static final float ONE_MB = 1048576f;
2473 boolean showMemoryUsage = false;
2477 java.text.NumberFormat df;
2479 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2482 public MyDesktopPane(boolean showMemoryUsage)
2484 showMemoryUsage(showMemoryUsage);
2487 public void showMemoryUsage(boolean showMemory)
2489 this.showMemoryUsage = showMemory;
2492 Thread worker = new Thread(this);
2498 public boolean isShowMemoryUsage()
2500 return showMemoryUsage;
2506 df = java.text.NumberFormat.getNumberInstance();
2507 df.setMaximumFractionDigits(2);
2508 runtime = Runtime.getRuntime();
2510 while (showMemoryUsage)
2514 maxMemory = runtime.maxMemory() / ONE_MB;
2515 allocatedMemory = runtime.totalMemory() / ONE_MB;
2516 freeMemory = runtime.freeMemory() / ONE_MB;
2517 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2519 percentUsage = (totalFreeMemory / maxMemory) * 100;
2521 // if (percentUsage < 20)
2523 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2525 // instance.set.setBorder(border1);
2528 // sleep after showing usage
2530 } catch (Exception ex)
2532 ex.printStackTrace();
2538 public void paintComponent(Graphics g)
2540 if (showMemoryUsage && g != null && df != null)
2542 if (percentUsage < 20)
2544 g.setColor(Color.red);
2546 FontMetrics fm = g.getFontMetrics();
2549 g.drawString(MessageManager.formatMessage("label.memory_stats",
2551 { df.format(totalFreeMemory), df.format(maxMemory),
2552 df.format(percentUsage) }),
2553 10, getHeight() - fm.getHeight());
2557 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2558 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2563 * Accessor method to quickly get all the AlignmentFrames loaded.
2565 * @return an array of AlignFrame, or null if none found
2568 public AlignFrame[] getAlignFrames()
2570 if (desktop == null)
2575 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2581 List<AlignFrame> avp = new ArrayList<>();
2583 for (int i = frames.length - 1; i > -1; i--)
2585 if (frames[i] instanceof AlignFrame)
2587 avp.add((AlignFrame) frames[i]);
2589 else if (frames[i] instanceof SplitFrame)
2592 * Also check for a split frame containing an AlignFrame
2594 GSplitFrame sf = (GSplitFrame) frames[i];
2595 if (sf.getTopFrame() instanceof AlignFrame)
2597 avp.add((AlignFrame) sf.getTopFrame());
2599 if (sf.getBottomFrame() instanceof AlignFrame)
2601 avp.add((AlignFrame) sf.getBottomFrame());
2605 if (avp.size() == 0)
2609 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2616 public static AlignFrame[] getDesktopAlignFrames()
2618 if (Jalview.isHeadlessMode())
2620 // Desktop.desktop is null in headless mode
2621 return Jalview.getInstance().getAlignFrames();
2624 if (instance != null && desktop != null)
2626 return instance.getAlignFrames();
2633 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2637 public GStructureViewer[] getJmols()
2639 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2645 List<GStructureViewer> avp = new ArrayList<>();
2647 for (int i = frames.length - 1; i > -1; i--)
2649 if (frames[i] instanceof AppJmol)
2651 GStructureViewer af = (GStructureViewer) frames[i];
2655 if (avp.size() == 0)
2659 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2664 * Add Groovy Support to Jalview
2667 public void groovyShell_actionPerformed()
2671 openGroovyConsole();
2672 } catch (Exception ex)
2674 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2675 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2677 MessageManager.getString("label.couldnt_create_groovy_shell"),
2678 MessageManager.getString("label.groovy_support_failed"),
2679 JvOptionPane.ERROR_MESSAGE);
2684 * Open the Groovy console
2686 void openGroovyConsole()
2688 if (groovyConsole == null)
2690 JalviewObjectI j = new JalviewObject(this);
2691 groovyConsole = new groovy.console.ui.Console();
2692 groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j);
2693 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2694 getCurrentAlignFrame());
2695 groovyConsole.run();
2698 * We allow only one console at a time, so that AlignFrame menu option
2699 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2700 * enable 'Run script', when the console is opened, and the reverse when it is
2703 Window window = (Window) groovyConsole.getFrame();
2704 window.addWindowListener(new WindowAdapter()
2707 public void windowClosed(WindowEvent e)
2710 * rebind CMD-Q from Groovy Console to Jalview Quit
2713 enableExecuteGroovy(false);
2719 * show Groovy console window (after close and reopen)
2721 ((Window) groovyConsole.getFrame()).setVisible(true);
2724 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2725 * opening a second console
2727 enableExecuteGroovy(true);
2731 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2732 * binding when opened
2734 protected void addQuitHandler()
2737 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2739 .getKeyStroke(KeyEvent.VK_Q,
2740 jalview.util.ShortcutKeyMaskExWrapper
2741 .getMenuShortcutKeyMaskEx()),
2743 getRootPane().getActionMap().put("Quit", new AbstractAction()
2746 public void actionPerformed(ActionEvent e)
2754 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2757 * true if Groovy console is open
2759 public void enableExecuteGroovy(boolean enabled)
2762 * disable opening a second Groovy console (or re-enable when the console is
2765 groovyShell.setEnabled(!enabled);
2767 AlignFrame[] alignFrames = getDesktopAlignFrames();
2768 if (alignFrames != null)
2770 for (AlignFrame af : alignFrames)
2772 af.setGroovyEnabled(enabled);
2778 * Progress bars managed by the IProgressIndicator method.
2780 private Hashtable<Long, JPanel> progressBars;
2782 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2787 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2790 public void setProgressBar(String message, long id)
2792 if (progressBars == null)
2794 progressBars = new Hashtable<>();
2795 progressBarHandlers = new Hashtable<>();
2798 if (progressBars.get(Long.valueOf(id)) != null)
2800 JPanel panel = progressBars.remove(Long.valueOf(id));
2801 if (progressBarHandlers.contains(Long.valueOf(id)))
2803 progressBarHandlers.remove(Long.valueOf(id));
2805 removeProgressPanel(panel);
2809 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2816 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2817 * jalview.gui.IProgressIndicatorHandler)
2820 public void registerHandler(final long id,
2821 final IProgressIndicatorHandler handler)
2823 if (progressBarHandlers == null
2824 || !progressBars.containsKey(Long.valueOf(id)))
2826 throw new Error(MessageManager.getString(
2827 "error.call_setprogressbar_before_registering_handler"));
2829 progressBarHandlers.put(Long.valueOf(id), handler);
2830 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2831 if (handler.canCancel())
2833 JButton cancel = new JButton(
2834 MessageManager.getString("action.cancel"));
2835 final IProgressIndicator us = this;
2836 cancel.addActionListener(new ActionListener()
2840 public void actionPerformed(ActionEvent e)
2842 handler.cancelActivity(id);
2843 us.setProgressBar(MessageManager
2844 .formatMessage("label.cancelled_params", new Object[]
2845 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2849 progressPanel.add(cancel, BorderLayout.EAST);
2855 * @return true if any progress bars are still active
2858 public boolean operationInProgress()
2860 if (progressBars != null && progressBars.size() > 0)
2868 * This will return the first AlignFrame holding the given viewport instance.
2869 * It will break if there are more than one AlignFrames viewing a particular
2873 * @return alignFrame for viewport
2875 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2877 if (desktop != null)
2879 AlignmentPanel[] aps = getAlignmentPanels(
2880 viewport.getSequenceSetId());
2881 for (int panel = 0; aps != null && panel < aps.length; panel++)
2883 if (aps[panel] != null && aps[panel].av == viewport)
2885 return aps[panel].alignFrame;
2892 public VamsasApplication getVamsasApplication()
2894 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2900 * flag set if jalview GUI is being operated programmatically
2902 private boolean inBatchMode = false;
2905 * check if jalview GUI is being operated programmatically
2907 * @return inBatchMode
2909 public boolean isInBatchMode()
2915 * set flag if jalview GUI is being operated programmatically
2917 * @param inBatchMode
2919 public void setInBatchMode(boolean inBatchMode)
2921 this.inBatchMode = inBatchMode;
2925 * start service discovery and wait till it is done
2927 public void startServiceDiscovery()
2929 startServiceDiscovery(false);
2933 * start service discovery threads - blocking or non-blocking
2937 public void startServiceDiscovery(boolean blocking)
2939 startServiceDiscovery(blocking, false);
2943 * start service discovery threads
2946 * - false means call returns immediately
2947 * @param ignore_SHOW_JWS2_SERVICES_preference
2948 * - when true JABA services are discovered regardless of user's JWS2
2949 * discovery preference setting
2951 public void startServiceDiscovery(boolean blocking,
2952 boolean ignore_SHOW_JWS2_SERVICES_preference)
2954 boolean alive = true;
2955 Thread t0 = null, t1 = null, t2 = null;
2956 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2959 // todo: changesupport handlers need to be transferred
2960 if (discoverer == null)
2962 discoverer = new jalview.ws.jws1.Discoverer();
2963 // register PCS handler for desktop.
2964 discoverer.addPropertyChangeListener(changeSupport);
2966 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2967 // until we phase out completely
2968 (t0 = new Thread(discoverer)).start();
2971 if (ignore_SHOW_JWS2_SERVICES_preference
2972 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2974 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2975 .startDiscoverer(changeSupport);
2979 // TODO: do rest service discovery
2988 } catch (Exception e)
2991 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2992 || (t3 != null && t3.isAlive())
2993 || (t0 != null && t0.isAlive());
2999 * called to check if the service discovery process completed successfully.
3003 protected void JalviewServicesChanged(PropertyChangeEvent evt)
3005 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
3007 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3008 .getErrorMessages();
3011 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3013 if (serviceChangedDialog == null)
3015 // only run if we aren't already displaying one of these.
3016 addDialogThread(serviceChangedDialog = new Runnable()
3023 * JalviewDialog jd =new JalviewDialog() {
3025 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3027 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3029 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3031 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3033 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3034 * + " or mis-configured HTTP proxy settings.<br/>" +
3035 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3036 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3037 * true, true, "Web Service Configuration Problem", 450, 400);
3039 * jd.waitForInput();
3041 JvOptionPane.showConfirmDialog(Desktop.desktop,
3042 new JLabel("<html><table width=\"450\"><tr><td>"
3043 + ermsg + "</td></tr></table>"
3044 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3045 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3046 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3047 + " Tools->Preferences dialog box to change them.</p></html>"),
3048 "Web Service Configuration Problem",
3049 JvOptionPane.DEFAULT_OPTION,
3050 JvOptionPane.ERROR_MESSAGE);
3051 serviceChangedDialog = null;
3059 jalview.bin.Console.error(
3060 "Errors reported by JABA discovery service. Check web services preferences.\n"
3067 private Runnable serviceChangedDialog = null;
3070 * start a thread to open a URL in the configured browser. Pops up a warning
3071 * dialog to the user if there is an exception when calling out to the browser
3076 public static void showUrl(final String url)
3078 if (url != null && !url.trim().equals(""))
3080 jalview.bin.Console.info("Opening URL: " + url);
3081 showUrl(url, Desktop.instance);
3085 jalview.bin.Console.warn("Ignoring attempt to show an empty URL.");
3091 * Like showUrl but allows progress handler to be specified
3095 * (null) or object implementing IProgressIndicator
3097 public static void showUrl(final String url,
3098 final IProgressIndicator progress)
3100 new Thread(new Runnable()
3107 if (progress != null)
3109 progress.setProgressBar(MessageManager
3110 .formatMessage("status.opening_params", new Object[]
3111 { url }), this.hashCode());
3113 jalview.util.BrowserLauncher.openURL(url);
3114 } catch (Exception ex)
3116 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3118 .getString("label.web_browser_not_found_unix"),
3119 MessageManager.getString("label.web_browser_not_found"),
3120 JvOptionPane.WARNING_MESSAGE);
3122 ex.printStackTrace();
3124 if (progress != null)
3126 progress.setProgressBar(null, this.hashCode());
3132 public static WsParamSetManager wsparamManager = null;
3134 public static ParamManager getUserParameterStore()
3136 if (wsparamManager == null)
3138 wsparamManager = new WsParamSetManager();
3140 return wsparamManager;
3144 * static hyperlink handler proxy method for use by Jalview's internal windows
3148 public static void hyperlinkUpdate(HyperlinkEvent e)
3150 if (e.getEventType() == EventType.ACTIVATED)
3155 url = e.getURL().toString();
3156 Desktop.showUrl(url);
3157 } catch (Exception x)
3162 .error("Couldn't handle string " + url + " as a URL.");
3164 // ignore any exceptions due to dud links.
3171 * single thread that handles display of dialogs to user.
3173 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3176 * flag indicating if dialogExecutor should try to acquire a permit
3178 private volatile boolean dialogPause = true;
3183 private Semaphore block = new Semaphore(0);
3185 private static groovy.console.ui.Console groovyConsole;
3188 * add another dialog thread to the queue
3192 public void addDialogThread(final Runnable prompter)
3194 dialogExecutor.submit(new Runnable()
3201 acquireDialogQueue();
3203 if (instance == null)
3209 SwingUtilities.invokeAndWait(prompter);
3210 } catch (Exception q)
3212 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3219 private boolean dialogQueueStarted = false;
3221 public void startDialogQueue()
3223 if (dialogQueueStarted)
3227 // set the flag so we don't pause waiting for another permit and semaphore
3228 // the current task to begin
3229 releaseDialogQueue();
3230 dialogQueueStarted = true;
3233 public void acquireDialogQueue()
3239 } catch (InterruptedException e)
3241 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3246 public void releaseDialogQueue()
3253 dialogPause = false;
3257 * Outputs an image of the desktop to file in EPS format, after prompting the
3258 * user for choice of Text or Lineart character rendering (unless a preference
3259 * has been set). The file name is generated as
3262 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3266 protected void snapShotWindow_actionPerformed(ActionEvent e)
3268 // currently the menu option to do this is not shown
3271 int width = getWidth();
3272 int height = getHeight();
3274 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3275 ImageWriterI writer = new ImageWriterI()
3278 public void exportImage(Graphics g) throws Exception
3281 jalview.bin.Console.info("Successfully written snapshot to file "
3282 + of.getAbsolutePath());
3285 String title = "View of desktop";
3286 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3290 exporter.doExport(of, this, width, height, title);
3291 } catch (ImageOutputException ioex)
3293 jalview.bin.Console.error(
3294 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3300 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3301 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3302 * and location last time the view was expanded (if any). However it does not
3303 * remember the split pane divider location - this is set to match the
3304 * 'exploding' frame.
3308 public void explodeViews(SplitFrame sf)
3310 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3311 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3312 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3314 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3316 int viewCount = topPanels.size();
3323 * Processing in reverse order works, forwards order leaves the first panels not
3324 * visible. I don't know why!
3326 for (int i = viewCount - 1; i >= 0; i--)
3329 * Make new top and bottom frames. These take over the respective AlignmentPanel
3330 * objects, including their AlignmentViewports, so the cdna/protein
3331 * relationships between the viewports is carried over to the new split frames.
3333 * explodedGeometry holds the (x, y) position of the previously exploded
3334 * SplitFrame, and the (width, height) of the AlignFrame component
3336 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3337 AlignFrame newTopFrame = new AlignFrame(topPanel);
3338 newTopFrame.setSize(oldTopFrame.getSize());
3339 newTopFrame.setVisible(true);
3340 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3341 .getExplodedGeometry();
3342 if (geometry != null)
3344 newTopFrame.setSize(geometry.getSize());
3347 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3348 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3349 newBottomFrame.setSize(oldBottomFrame.getSize());
3350 newBottomFrame.setVisible(true);
3351 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3352 .getExplodedGeometry();
3353 if (geometry != null)
3355 newBottomFrame.setSize(geometry.getSize());
3358 topPanel.av.setGatherViewsHere(false);
3359 bottomPanel.av.setGatherViewsHere(false);
3360 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3362 if (geometry != null)
3364 splitFrame.setLocation(geometry.getLocation());
3366 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3370 * Clear references to the panels (now relocated in the new SplitFrames) before
3371 * closing the old SplitFrame.
3374 bottomPanels.clear();
3379 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3380 * back into the given SplitFrame as additional views. Note that the gathered
3381 * frames may themselves have multiple views.
3385 public void gatherViews(GSplitFrame source)
3388 * special handling of explodedGeometry for a view within a SplitFrame: - it
3389 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3390 * height) of the AlignFrame component
3392 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3393 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3394 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3395 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3396 myBottomFrame.viewport
3397 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3398 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3399 myTopFrame.viewport.setGatherViewsHere(true);
3400 myBottomFrame.viewport.setGatherViewsHere(true);
3401 String topViewId = myTopFrame.viewport.getSequenceSetId();
3402 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3404 JInternalFrame[] frames = desktop.getAllFrames();
3405 for (JInternalFrame frame : frames)
3407 if (frame instanceof SplitFrame && frame != source)
3409 SplitFrame sf = (SplitFrame) frame;
3410 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3411 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3412 boolean gatherThis = false;
3413 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3415 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3416 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3417 if (topViewId.equals(topPanel.av.getSequenceSetId())
3418 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3421 topPanel.av.setGatherViewsHere(false);
3422 bottomPanel.av.setGatherViewsHere(false);
3423 topPanel.av.setExplodedGeometry(
3424 new Rectangle(sf.getLocation(), topFrame.getSize()));
3425 bottomPanel.av.setExplodedGeometry(
3426 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3427 myTopFrame.addAlignmentPanel(topPanel, false);
3428 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3434 topFrame.getAlignPanels().clear();
3435 bottomFrame.getAlignPanels().clear();
3442 * The dust settles...give focus to the tab we did this from.
3444 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3447 public static groovy.console.ui.Console getGroovyConsole()
3449 return groovyConsole;
3453 * handles the payload of a drag and drop event.
3455 * TODO refactor to desktop utilities class
3458 * - Data source strings extracted from the drop event
3460 * - protocol for each data source extracted from the drop event
3464 * - the payload from the drop event
3467 public static void transferFromDropTarget(List<Object> files,
3468 List<DataSourceType> protocols, DropTargetDropEvent evt,
3469 Transferable t) throws Exception
3472 DataFlavor uriListFlavor = new DataFlavor(
3473 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3476 urlFlavour = new DataFlavor(
3477 "application/x-java-url; class=java.net.URL");
3478 } catch (ClassNotFoundException cfe)
3480 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3484 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3489 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3490 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3491 // means url may be null.
3494 protocols.add(DataSourceType.URL);
3495 files.add(url.toString());
3496 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3497 + files.get(files.size() - 1));
3502 if (Platform.isAMacAndNotJS())
3504 jalview.bin.Console.errPrintln(
3505 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3508 } catch (Throwable ex)
3510 jalview.bin.Console.debug("URL drop handler failed.", ex);
3513 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3515 // Works on Windows and MacOSX
3516 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3517 for (Object file : (List) t
3518 .getTransferData(DataFlavor.javaFileListFlavor))
3521 protocols.add(DataSourceType.FILE);
3526 // Unix like behaviour
3527 boolean added = false;
3529 if (t.isDataFlavorSupported(uriListFlavor))
3531 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3532 // This is used by Unix drag system
3533 data = (String) t.getTransferData(uriListFlavor);
3537 // fallback to text: workaround - on OSX where there's a JVM bug
3539 .debug("standard URIListFlavor failed. Trying text");
3540 // try text fallback
3541 DataFlavor textDf = new DataFlavor(
3542 "text/plain;class=java.lang.String");
3543 if (t.isDataFlavorSupported(textDf))
3545 data = (String) t.getTransferData(textDf);
3548 jalview.bin.Console.debug("Plain text drop content returned "
3549 + (data == null ? "Null - failed" : data));
3554 while (protocols.size() < files.size())
3556 jalview.bin.Console.debug("Adding missing FILE protocol for "
3557 + files.get(protocols.size()));
3558 protocols.add(DataSourceType.FILE);
3560 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3561 data, "\r\n"); st.hasMoreTokens();)
3564 String s = st.nextToken();
3565 if (s.startsWith("#"))
3567 // the line is a comment (as per the RFC 2483)
3570 java.net.URI uri = new java.net.URI(s);
3571 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3573 protocols.add(DataSourceType.URL);
3574 files.add(uri.toString());
3578 // otherwise preserve old behaviour: catch all for file objects
3579 java.io.File file = new java.io.File(uri);
3580 protocols.add(DataSourceType.FILE);
3581 files.add(file.toString());
3586 if (jalview.bin.Console.isDebugEnabled())
3588 if (data == null || !added)
3591 if (t.getTransferDataFlavors() != null
3592 && t.getTransferDataFlavors().length > 0)
3594 jalview.bin.Console.debug(
3595 "Couldn't resolve drop data. Here are the supported flavors:");
3596 for (DataFlavor fl : t.getTransferDataFlavors())
3598 jalview.bin.Console.debug(
3599 "Supported transfer dataflavor: " + fl.toString());
3600 Object df = t.getTransferData(fl);
3603 jalview.bin.Console.debug("Retrieves: " + df);
3607 jalview.bin.Console.debug("Retrieved nothing");
3614 .debug("Couldn't resolve dataflavor for drop: "
3620 if (Platform.isWindowsAndNotJS())
3623 .debug("Scanning dropped content for Windows Link Files");
3625 // resolve any .lnk files in the file drop
3626 for (int f = 0; f < files.size(); f++)
3628 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3629 if (protocols.get(f).equals(DataSourceType.FILE)
3630 && (source.endsWith(".lnk") || source.endsWith(".url")
3631 || source.endsWith(".site")))
3635 Object obj = files.get(f);
3636 File lf = (obj instanceof File ? (File) obj
3637 : new File((String) obj));
3638 // process link file to get a URL
3639 jalview.bin.Console.debug("Found potential link file: " + lf);
3640 WindowsShortcut wscfile = new WindowsShortcut(lf);
3641 String fullname = wscfile.getRealFilename();
3642 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3643 files.set(f, fullname);
3644 jalview.bin.Console.debug("Parsed real filename " + fullname
3645 + " to extract protocol: " + protocols.get(f));
3646 } catch (Exception ex)
3648 jalview.bin.Console.error(
3649 "Couldn't parse " + files.get(f) + " as a link file.",
3658 * Sets the Preferences property for experimental features to True or False
3659 * depending on the state of the controlling menu item
3662 protected void showExperimental_actionPerformed(boolean selected)
3664 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3668 * Answers a (possibly empty) list of any structure viewer frames (currently
3669 * for either Jmol or Chimera) which are currently open. This may optionally
3670 * be restricted to viewers of a specified class, or viewers linked to a
3671 * specified alignment panel.
3674 * if not null, only return viewers linked to this panel
3675 * @param structureViewerClass
3676 * if not null, only return viewers of this class
3679 public List<StructureViewerBase> getStructureViewers(
3680 AlignmentPanel apanel,
3681 Class<? extends StructureViewerBase> structureViewerClass)
3683 List<StructureViewerBase> result = new ArrayList<>();
3684 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3686 for (JInternalFrame frame : frames)
3688 if (frame instanceof StructureViewerBase)
3690 if (structureViewerClass == null
3691 || structureViewerClass.isInstance(frame))
3694 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3696 result.add((StructureViewerBase) frame);
3704 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3706 private static boolean debugScaleMessageDone = false;
3708 public static void debugScaleMessage(Graphics g)
3710 if (debugScaleMessageDone)
3714 // output used by tests to check HiDPI scaling settings in action
3717 Graphics2D gg = (Graphics2D) g;
3720 AffineTransform t = gg.getTransform();
3721 double scaleX = t.getScaleX();
3722 double scaleY = t.getScaleY();
3723 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3724 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3725 debugScaleMessageDone = true;
3729 jalview.bin.Console.debug("Desktop graphics null");
3731 } catch (Exception e)
3733 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3738 * closes the current instance window, but leaves the JVM running. Bypasses
3739 * any shutdown prompts, but does not set window dispose on close in case JVM
3742 public static void closeDesktop()
3744 if (Desktop.instance != null)
3746 Desktop us = Desktop.instance;
3747 Desktop.instance.quitTheDesktop(false, false);
3748 // call dispose in a separate thread - try to avoid indirect deadlocks
3751 new Thread(new Runnable()
3756 ExecutorService dex = us.dialogExecutor;
3760 us.dialogExecutor = null;
3761 us.block.drainPermits();
3771 * checks if any progress bars are being displayed in any of the windows
3772 * managed by the desktop
3776 public boolean operationsAreInProgress()
3778 JInternalFrame[] frames = getAllFrames();
3779 for (JInternalFrame frame : frames)
3781 if (frame instanceof IProgressIndicator)
3783 if (((IProgressIndicator) frame).operationInProgress())
3789 return operationInProgress();
3793 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3794 * The way the modal JInternalFrame is made means it cannot be a child of an
3795 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3797 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3799 protected static void addModal(AlignFrame af, JInternalFrame jif)
3801 alignFrameModalMap.put(af, jif);
3804 protected static void closeModal(AlignFrame af)
3806 if (!alignFrameModalMap.containsKey(af))
3810 JInternalFrame jif = alignFrameModalMap.get(af);
3815 jif.setClosed(true);
3816 } catch (PropertyVetoException e)
3818 e.printStackTrace();
3821 alignFrameModalMap.remove(af);
3824 public void nonBlockingDialog(String title, String message, String button,
3825 int type, boolean scrollable, boolean modal)
3827 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3831 public void nonBlockingDialog(String title, String message,
3832 String boxtext, String button, int type, boolean scrollable,
3833 boolean html, boolean modal, int timeout)
3835 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3836 scrollable, html, modal, timeout);
3839 public void nonBlockingDialog(int width, int height, String title,
3840 String message, String boxtext, String button, int type,
3841 boolean scrollable, boolean html, boolean modal, int timeout)
3845 type = JvOptionPane.WARNING_MESSAGE;
3847 JLabel jl = new JLabel(message);
3849 JTextComponent jtc = null;
3852 JTextPane jtp = new JTextPane();
3853 jtp.setContentType("text/html");
3854 jtp.setEditable(false);
3855 jtp.setAutoscrolls(true);
3856 jtp.setText(boxtext);
3862 JTextArea jta = new JTextArea(height, width);
3863 // jta.setLineWrap(true);
3864 jta.setEditable(false);
3865 jta.setWrapStyleWord(true);
3866 jta.setAutoscrolls(true);
3867 jta.setText(boxtext);
3872 JScrollPane jsp = scrollable
3873 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3874 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3877 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3879 JPanel jp = new JPanel();
3880 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3882 if (message != null)
3884 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3887 if (boxtext != null)
3891 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3896 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3901 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3903 jvp.setTimeout(timeout);
3904 JButton jb = new JButton(button);
3905 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3907 { button }, button, modal, new JButton[] { jb }, false);
3911 public AlignFrame getCurrentAlignFrame()
3913 return Jalview.getInstance().getCurrentAlignFrame();