2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.datatransfer.Clipboard;
36 import java.awt.datatransfer.ClipboardOwner;
37 import java.awt.datatransfer.DataFlavor;
38 import java.awt.datatransfer.Transferable;
39 import java.awt.dnd.DnDConstants;
40 import java.awt.dnd.DropTargetDragEvent;
41 import java.awt.dnd.DropTargetDropEvent;
42 import java.awt.dnd.DropTargetEvent;
43 import java.awt.dnd.DropTargetListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.WindowAdapter;
51 import java.awt.event.WindowEvent;
52 import java.awt.geom.AffineTransform;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
55 import java.beans.PropertyVetoException;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Locale;
69 import java.util.Vector;
70 import java.util.concurrent.ExecutorService;
71 import java.util.concurrent.Executors;
72 import java.util.concurrent.Semaphore;
74 import javax.swing.AbstractAction;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.Box;
78 import javax.swing.BoxLayout;
79 import javax.swing.DefaultDesktopManager;
80 import javax.swing.DesktopManager;
81 import javax.swing.InputMap;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JComboBox;
85 import javax.swing.JComponent;
86 import javax.swing.JDesktopPane;
87 import javax.swing.JFrame;
88 import javax.swing.JInternalFrame;
89 import javax.swing.JLabel;
90 import javax.swing.JMenuItem;
91 import javax.swing.JOptionPane;
92 import javax.swing.JPanel;
93 import javax.swing.JPopupMenu;
94 import javax.swing.JProgressBar;
95 import javax.swing.JScrollPane;
96 import javax.swing.JTextArea;
97 import javax.swing.JTextField;
98 import javax.swing.JTextPane;
99 import javax.swing.KeyStroke;
100 import javax.swing.SwingUtilities;
101 import javax.swing.WindowConstants;
102 import javax.swing.event.HyperlinkEvent;
103 import javax.swing.event.HyperlinkEvent.EventType;
104 import javax.swing.event.InternalFrameAdapter;
105 import javax.swing.event.InternalFrameEvent;
106 import javax.swing.text.JTextComponent;
108 import org.stackoverflowusers.file.WindowsShortcut;
110 import jalview.api.AlignViewportI;
111 import jalview.api.AlignmentViewPanel;
112 import jalview.api.structures.JalviewStructureDisplayI;
113 import jalview.bin.Cache;
114 import jalview.bin.Jalview;
115 import jalview.bin.Jalview.ExitCode;
116 import jalview.bin.argparser.Arg;
117 import jalview.bin.groovy.JalviewObject;
118 import jalview.bin.groovy.JalviewObjectI;
119 import jalview.datamodel.Alignment;
120 import jalview.datamodel.HiddenColumns;
121 import jalview.datamodel.Sequence;
122 import jalview.datamodel.SequenceI;
123 import jalview.gui.ImageExporter.ImageWriterI;
124 import jalview.gui.QuitHandler.QResponse;
125 import jalview.io.BackupFiles;
126 import jalview.io.DataSourceType;
127 import jalview.io.FileFormat;
128 import jalview.io.FileFormatException;
129 import jalview.io.FileFormatI;
130 import jalview.io.FileFormats;
131 import jalview.io.FileLoader;
132 import jalview.io.FormatAdapter;
133 import jalview.io.IdentifyFile;
134 import jalview.io.JalviewFileChooser;
135 import jalview.io.JalviewFileView;
136 import jalview.io.exceptions.ImageOutputException;
137 import jalview.jbgui.GSplitFrame;
138 import jalview.jbgui.GStructureViewer;
139 import jalview.project.Jalview2XML;
140 import jalview.structure.StructureSelectionManager;
141 import jalview.urls.IdOrgSettings;
142 import jalview.util.BrowserLauncher;
143 import jalview.util.ChannelProperties;
144 import jalview.util.IdUtils;
145 import jalview.util.IdUtils.IdType;
146 import jalview.util.ImageMaker.TYPE;
147 import jalview.util.LaunchUtils;
148 import jalview.util.MessageManager;
149 import jalview.util.Platform;
150 import jalview.util.ShortcutKeyMaskExWrapper;
151 import jalview.util.UrlConstants;
152 import jalview.viewmodel.AlignmentViewport;
153 import jalview.ws.params.ParamManager;
154 import jalview.ws.utils.UrlDownloadClient;
161 * @version $Revision: 1.155 $
163 public class Desktop extends jalview.jbgui.GDesktop
164 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
165 jalview.api.StructureSelectionManagerProvider, JalviewObjectI
167 private static final String CITATION;
170 URL bg_logo_url = ChannelProperties.getImageURL(
171 "bg_logo." + String.valueOf(SplashScreen.logoSize));
172 URL uod_logo_url = ChannelProperties.getImageURL(
173 "uod_banner." + String.valueOf(SplashScreen.logoSize));
174 boolean logo = (bg_logo_url != null || uod_logo_url != null);
175 StringBuilder sb = new StringBuilder();
177 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
182 sb.append(bg_logo_url == null ? ""
183 : "<img alt=\"Barton Group logo\" src=\""
184 + bg_logo_url.toString() + "\">");
185 sb.append(uod_logo_url == null ? ""
186 : " <img alt=\"University of Dundee shield\" src=\""
187 + uod_logo_url.toString() + "\">");
189 "<br><br>For help, see <a href=\"https://www.jalview.org/help/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
190 sb.append("<br><br>If you use Jalview, please cite:"
191 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
192 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
193 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
194 CITATION = sb.toString();
197 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
199 private static int DEFAULT_MIN_WIDTH = 300;
201 private static int DEFAULT_MIN_HEIGHT = 250;
203 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
205 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
207 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
209 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
211 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
213 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
215 public static void setLiveDragMode(boolean b)
217 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
218 : JDesktopPane.OUTLINE_DRAG_MODE;
220 desktop.setDragMode(DRAG_MODE);
223 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
225 public static boolean nosplash = false;
228 * news reader - null if it was never started.
230 private BlogReader jvnews = null;
232 private File projectFile;
236 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
238 public void addJalviewPropertyChangeListener(
239 PropertyChangeListener listener)
241 changeSupport.addJalviewPropertyChangeListener(listener);
245 * @param propertyName
247 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
248 * java.beans.PropertyChangeListener)
250 public void addJalviewPropertyChangeListener(String propertyName,
251 PropertyChangeListener listener)
253 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
257 * @param propertyName
259 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
260 * java.beans.PropertyChangeListener)
262 public void removeJalviewPropertyChangeListener(String propertyName,
263 PropertyChangeListener listener)
265 changeSupport.removeJalviewPropertyChangeListener(propertyName,
269 /** Singleton Desktop instance */
270 public static Desktop instance;
272 public static MyDesktopPane desktop;
274 public static MyDesktopPane getDesktop()
276 // BH 2018 could use currentThread() here as a reference to a
277 // Hashtable<Thread, MyDesktopPane> in JavaScript
281 static int openFrameCount = 0;
283 static final int xOffset = 30;
285 static final int yOffset = 30;
287 public static jalview.ws.jws1.Discoverer discoverer;
289 public static Object[] jalviewClipboard;
291 public static boolean internalCopy = false;
293 static int fileLoadingCount = 0;
295 class MyDesktopManager implements DesktopManager
298 private DesktopManager delegate;
300 public MyDesktopManager(DesktopManager delegate)
302 this.delegate = delegate;
306 public void activateFrame(JInternalFrame f)
310 delegate.activateFrame(f);
311 } catch (NullPointerException npe)
313 Point p = getMousePosition();
314 instance.showPasteMenu(p.x, p.y);
319 public void beginDraggingFrame(JComponent f)
321 delegate.beginDraggingFrame(f);
325 public void beginResizingFrame(JComponent f, int direction)
327 delegate.beginResizingFrame(f, direction);
331 public void closeFrame(JInternalFrame f)
333 delegate.closeFrame(f);
337 public void deactivateFrame(JInternalFrame f)
339 delegate.deactivateFrame(f);
343 public void deiconifyFrame(JInternalFrame f)
345 delegate.deiconifyFrame(f);
349 public void dragFrame(JComponent f, int newX, int newY)
355 delegate.dragFrame(f, newX, newY);
359 public void endDraggingFrame(JComponent f)
361 delegate.endDraggingFrame(f);
366 public void endResizingFrame(JComponent f)
368 delegate.endResizingFrame(f);
373 public void iconifyFrame(JInternalFrame f)
375 delegate.iconifyFrame(f);
379 public void maximizeFrame(JInternalFrame f)
381 delegate.maximizeFrame(f);
385 public void minimizeFrame(JInternalFrame f)
387 delegate.minimizeFrame(f);
391 public void openFrame(JInternalFrame f)
393 delegate.openFrame(f);
397 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
404 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
408 public void setBoundsForFrame(JComponent f, int newX, int newY,
409 int newWidth, int newHeight)
411 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
414 // All other methods, simply delegate
419 * Creates a new Desktop object.
425 * A note to implementors. It is ESSENTIAL that any activities that might
426 * block are spawned off as threads rather than waited for during this
431 doConfigureStructurePrefs();
432 setTitle(ChannelProperties.getProperty("app_name") + " "
433 + Cache.getProperty("VERSION"));
436 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
437 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
438 * officially documented or guaranteed to exist, so we access it via
439 * reflection. There appear to be unfathomable criteria about what this
440 * string can contain, and it if doesn't meet those criteria then "java"
441 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
442 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
443 * not. The reflection access may generate a warning: WARNING: An illegal
444 * reflective access operation has occurred WARNING: Illegal reflective
445 * access by jalview.gui.Desktop () to field
446 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
448 if (Platform.isLinux())
450 if (LaunchUtils.getJavaVersion() >= 11)
453 * Send this message to stderr as the warning that follows (due to reflection)
454 * also goes to stderr.
456 jalview.bin.Console.errPrintln(
457 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
459 final String awtAppClassName = "awtAppClassName";
462 Toolkit xToolkit = Toolkit.getDefaultToolkit();
463 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
464 Field awtAppClassNameField = null;
466 if (Arrays.stream(declaredFields)
467 .anyMatch(f -> f.getName().equals(awtAppClassName)))
469 awtAppClassNameField = xToolkit.getClass()
470 .getDeclaredField(awtAppClassName);
473 String title = ChannelProperties.getProperty("app_name");
474 if (awtAppClassNameField != null)
476 awtAppClassNameField.setAccessible(true);
477 awtAppClassNameField.set(xToolkit, title);
482 .debug("XToolkit: " + awtAppClassName + " not found");
484 } catch (Exception e)
486 jalview.bin.Console.debug("Error setting " + awtAppClassName);
487 jalview.bin.Console.trace(Cache.getStackTraceString(e));
491 setIconImages(ChannelProperties.getIconList());
493 // override quit handling when GUI OS close [X] button pressed
494 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
495 addWindowListener(new WindowAdapter()
498 public void windowClosing(WindowEvent ev)
500 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
504 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
506 boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE,
507 "SHOW_JAVA_CONSOLE", false);
509 // start dialogue queue for single dialogues
512 if (!Platform.isJS())
519 Desktop.instance.acquireDialogQueue();
521 jconsole = new Console(this);
522 jconsole.setHeader(Cache.getVersionDetailsForConsole());
523 showConsole(showjconsole);
525 Desktop.instance.releaseDialogQueue();
528 desktop = new MyDesktopPane(selmemusage);
530 showMemusage.setSelected(selmemusage);
531 desktop.setBackground(Color.white);
533 getContentPane().setLayout(new BorderLayout());
534 // alternate config - have scrollbars - see notes in JAL-153
535 // JScrollPane sp = new JScrollPane();
536 // sp.getViewport().setView(desktop);
537 // getContentPane().add(sp, BorderLayout.CENTER);
539 // BH 2018 - just an experiment to try unclipped JInternalFrames.
542 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
545 getContentPane().add(desktop, BorderLayout.CENTER);
546 desktop.setDragMode(DRAG_MODE);
548 // This line prevents Windows Look&Feel resizing all new windows to maximum
549 // if previous window was maximised
550 desktop.setDesktopManager(new MyDesktopManager(
551 Platform.isJS() ? desktop.getDesktopManager()
552 : new DefaultDesktopManager()));
554 * (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager() :
555 * Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(
556 * desktop.getDesktopManager()) : desktop.getDesktopManager())));
559 Rectangle dims = getLastKnownDimensions("");
566 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
567 int xPos = Math.max(5, (screenSize.width - 900) / 2);
568 int yPos = Math.max(5, (screenSize.height - 650) / 2);
569 setBounds(xPos, yPos, 900, 650);
572 if (!Platform.isJS())
579 showNews.setVisible(false);
581 experimentalFeatures.setSelected(showExperimental());
583 getIdentifiersOrgData();
587 // Spawn a thread that shows the splashscreen
590 SwingUtilities.invokeLater(new Runnable()
595 new SplashScreen(true);
600 // Thread off a new instance of the file chooser - this reduces the time
601 // it takes to open it later on.
602 new Thread(new Runnable()
607 jalview.bin.Console.debug("Filechooser init thread started.");
608 String fileFormat = FileLoader.getUseDefaultFileFormat()
609 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
611 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
613 jalview.bin.Console.debug("Filechooser init thread finished.");
616 // Add the service change listener
617 changeSupport.addJalviewPropertyChangeListener("services",
618 new PropertyChangeListener()
622 public void propertyChange(PropertyChangeEvent evt)
625 .debug("Firing service changed event for "
626 + evt.getNewValue());
627 JalviewServicesChanged(evt);
632 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
635 this.addMouseListener(ma = new MouseAdapter()
638 public void mousePressed(MouseEvent evt)
640 if (evt.isPopupTrigger()) // Mac
642 showPasteMenu(evt.getX(), evt.getY());
647 public void mouseReleased(MouseEvent evt)
649 if (evt.isPopupTrigger()) // Windows
651 showPasteMenu(evt.getX(), evt.getY());
655 desktop.addMouseListener(ma);
659 final String ns = Jalview.getInstance().getJ2sNamespace();
662 final String jalviewjsDesktopElementId = "testApplet_LayeredPaneUI_10_8div";
663 final String nsc = ns + (ns.length() > 0 ? ":" : "");
664 final String nsu = ns + (ns.length() > 0 ? "_" : "");
665 final String splashId = nsc + "jalviewjsSplash";
666 final String splashClassActive = nsu + "jalviewjsSplashActive";
667 final String splashClassInactive = nsu + "jalviewjsSplashInactive";
668 final String splashClassHidden = nsu + "jalviewjsSplashHidden";
669 final String j2s_overflow = Jalview.getInstance()
670 .getJ2sInfoValue("overflow");
672 * @j2sNative // splash element disappearance
674 * var splashElement = document.getElementById(splashId);
676 * if (splashElement !== undefined) {
678 * splashElement.classList.remove(splashClassActive);
680 * splashElement.classList.add(splashClassInactive);
682 * function sleep(ms) {
684 * return new Promise(resolve => setTimeout(resolve, ms));
688 * async function hideSplash() {
692 * splashElement.classList.add(splashClassHidden);
700 * // overflow setting
702 * async function changeVisibility() {
704 * var desktopElement = null;
710 * var stayedSetCount = 0;
712 * while ((desktopElement == null || setCount < 5) &&
713 * timeCount < 50 && stayedSetCount < 5) {
717 * if (desktopElement == null) {
720 * document.getElementById(jalviewjsDesktopElementId);
724 * if (desktopElement !== undefined && desktopElement !==
727 * if (desktopElement.style.overflow == "hidden") {
729 * desktopElement.style.overflow = "visible";
733 * stayedSetCount = 0;
749 * if (new String(j2s_overflow).substring(0,4) === "true") {
751 * changeVisibility();
756 // used for jalviewjsTest
757 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
763 * Answers true if user preferences to enable experimental features is True
768 public boolean showExperimental()
770 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
771 Boolean.FALSE.toString());
772 return Boolean.valueOf(experimental).booleanValue();
775 public void doConfigureStructurePrefs()
777 // configure services
778 StructureSelectionManager ssm = StructureSelectionManager
779 .getStructureSelectionManager(this);
780 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
782 ssm.setAddTempFacAnnot(
783 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
784 ssm.setProcessSecondaryStructure(
785 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
786 // JAL-3915 - RNAView is no longer an option so this has no effect
787 ssm.setSecStructServices(
788 Cache.getDefault(Preferences.USE_RNAVIEW, false));
792 ssm.setAddTempFacAnnot(false);
793 ssm.setProcessSecondaryStructure(false);
794 ssm.setSecStructServices(false);
798 public void checkForNews()
800 final Desktop me = this;
801 // Thread off the news reader, in case there are connection problems.
802 new Thread(new Runnable()
807 jalview.bin.Console.debug("Starting news thread.");
808 jvnews = new BlogReader(me);
809 showNews.setVisible(true);
810 jalview.bin.Console.debug("Completed news thread.");
815 public void getIdentifiersOrgData()
817 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
818 {// Thread off the identifiers fetcher
819 new Thread(new Runnable()
825 .debug("Downloading data from identifiers.org");
828 UrlDownloadClient.download(IdOrgSettings.getUrl(),
829 IdOrgSettings.getDownloadLocation());
830 } catch (IOException e)
833 .debug("Exception downloading identifiers.org data"
843 protected void showNews_actionPerformed(ActionEvent e)
845 showNews(showNews.isSelected());
848 void showNews(boolean visible)
850 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
851 showNews.setSelected(visible);
852 if (visible && !jvnews.isVisible())
854 new Thread(new Runnable()
859 long progressId = IdUtils.newId(IdType.PROGRESS);
860 Desktop.instance.setProgressBar(
861 MessageManager.getString("status.refreshing_news"),
863 jvnews.refreshNews();
864 Desktop.instance.setProgressBar(null, progressId);
872 * recover the last known dimensions for a jalview window
875 * - empty string is desktop, all other windows have unique prefix
876 * @return null or last known dimensions scaled to current geometry (if last
877 * window geom was known)
879 Rectangle getLastKnownDimensions(String windowName)
881 // TODO: lock aspect ratio for scaling desktop Bug #0058199
882 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
883 String x = Cache.getProperty(windowName + "SCREEN_X");
884 String y = Cache.getProperty(windowName + "SCREEN_Y");
885 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
886 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
887 if ((x != null) && (y != null) && (width != null) && (height != null))
889 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
890 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
891 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
893 // attempt #1 - try to cope with change in screen geometry - this
894 // version doesn't preserve original jv aspect ratio.
895 // take ratio of current screen size vs original screen size.
896 double sw = ((1f * screenSize.width) / (1f * Integer
897 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
898 double sh = ((1f * screenSize.height) / (1f * Integer
899 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
900 // rescale the bounds depending upon the current screen geometry.
901 ix = (int) (ix * sw);
902 iw = (int) (iw * sw);
903 iy = (int) (iy * sh);
904 ih = (int) (ih * sh);
905 if (ix >= screenSize.width)
907 jalview.bin.Console.debug(
908 "Window geometry location recall error: shifting horizontal to within screenbounds.");
909 ix = ix % screenSize.width;
911 if (iy >= screenSize.height)
913 jalview.bin.Console.debug(
914 "Window geometry location recall error: shifting vertical to within screenbounds.");
915 iy = iy % screenSize.height;
917 jalview.bin.Console.debug(
918 "Got last known dimensions for " + windowName + ": x:" + ix
919 + " y:" + iy + " width:" + iw + " height:" + ih);
921 // return dimensions for new instance
922 return new Rectangle(ix, iy, iw, ih);
927 void showPasteMenu(int x, int y)
929 JPopupMenu popup = new JPopupMenu();
930 JMenuItem item = new JMenuItem(
931 MessageManager.getString("label.paste_new_window"));
932 item.addActionListener(new ActionListener()
935 public void actionPerformed(ActionEvent evt)
942 popup.show(this, x, y);
947 // quick patch for JAL-4150 - needs some more work and test coverage
948 // TODO - unify below and AlignFrame.paste()
949 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
950 // clipboard has come from a different alignment window than the one where
951 // paste has been called! JAL-4151
953 if (Desktop.jalviewClipboard != null)
955 // The clipboard was filled from within Jalview, we must use the
957 // And dataset from the copied alignment
958 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
959 // be doubly sure that we create *new* sequence objects.
960 SequenceI[] sequences = new SequenceI[newseq.length];
961 for (int i = 0; i < newseq.length; i++)
963 sequences[i] = new Sequence(newseq[i]);
965 Alignment alignment = new Alignment(sequences);
966 // dataset is inherited
967 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
968 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
969 AlignFrame.DEFAULT_HEIGHT);
970 String newtitle = new String("Copied sequences");
972 if (Desktop.jalviewClipboard[2] != null)
974 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
975 af.viewport.setHiddenColumns(hc);
978 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
979 AlignFrame.DEFAULT_HEIGHT);
986 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
987 Transferable contents = c.getContents(this);
989 if (contents != null)
991 String file = (String) contents
992 .getTransferData(DataFlavor.stringFlavor);
994 FileFormatI format = new IdentifyFile().identify(file,
995 DataSourceType.PASTE);
997 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
1000 } catch (Exception ex)
1002 jalview.bin.Console.outPrintln(
1003 "Unable to paste alignment from system clipboard:\n" + ex);
1009 * Adds and opens the given frame to the desktop
1020 public static synchronized void addInternalFrame(
1021 final JInternalFrame frame, String title, int w, int h)
1023 addInternalFrame(frame, title, true, w, h, true, false);
1027 * Add an internal frame to the Jalview desktop
1033 * @param makeVisible
1034 * When true, display frame immediately, otherwise, caller must call
1035 * setVisible themselves.
1041 public static synchronized void addInternalFrame(
1042 final JInternalFrame frame, String title, boolean makeVisible,
1045 addInternalFrame(frame, title, makeVisible, w, h, true, false);
1049 * Add an internal frame to the Jalview desktop and make it visible
1062 public static synchronized void addInternalFrame(
1063 final JInternalFrame frame, String title, int w, int h,
1066 addInternalFrame(frame, title, true, w, h, resizable, false);
1070 * Add an internal frame to the Jalview desktop
1076 * @param makeVisible
1077 * When true, display frame immediately, otherwise, caller must call
1078 * setVisible themselves.
1085 * @param ignoreMinSize
1086 * Do not set the default minimum size for frame
1088 public static synchronized void addInternalFrame(
1089 final JInternalFrame frame, String title, boolean makeVisible,
1090 int w, int h, boolean resizable, boolean ignoreMinSize)
1093 // TODO: allow callers to determine X and Y position of frame (eg. via
1095 // TODO: consider fixing method to update entries in the window submenu with
1096 // the current window title
1098 frame.setTitle(title);
1099 if (frame.getWidth() < 1 || frame.getHeight() < 1)
1101 frame.setSize(w, h);
1103 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
1104 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
1105 // IF JALVIEW IS RUNNING HEADLESS
1106 // ///////////////////////////////////////////////
1107 if (instance == null || (System.getProperty("java.awt.headless") != null
1108 && System.getProperty("java.awt.headless").equals("true")))
1117 frame.setMinimumSize(
1118 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1120 // Set default dimension for Alignment Frame window.
1121 // The Alignment Frame window could be added from a number of places,
1123 // I did this here in order not to miss out on any Alignment frame.
1124 if (frame instanceof AlignFrame)
1126 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1127 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1131 frame.setVisible(makeVisible);
1132 frame.setClosable(true);
1133 frame.setResizable(resizable);
1134 frame.setMaximizable(resizable);
1135 frame.setIconifiable(resizable);
1136 frame.setOpaque(Platform.isJS());
1138 if (frame.getX() < 1 && frame.getY() < 1)
1140 frame.setLocation(xOffset * openFrameCount,
1141 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1145 * add an entry for the new frame in the Window menu (and remove it when the
1148 final JMenuItem menuItem = new JMenuItem(title);
1149 frame.addInternalFrameListener(new InternalFrameAdapter()
1152 public void internalFrameActivated(InternalFrameEvent evt)
1154 JInternalFrame itf = desktop.getSelectedFrame();
1157 if (itf instanceof AlignFrame)
1159 Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf);
1166 public void internalFrameClosed(InternalFrameEvent evt)
1168 PaintRefresher.RemoveComponent(frame);
1171 * defensive check to prevent frames being added half off the window
1173 if (openFrameCount > 0)
1179 * ensure no reference to alignFrame retained by menu item listener
1181 if (menuItem.getActionListeners().length > 0)
1183 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1185 windowMenu.remove(menuItem);
1189 menuItem.addActionListener(new ActionListener()
1192 public void actionPerformed(ActionEvent e)
1196 frame.setSelected(true);
1197 frame.setIcon(false);
1198 } catch (java.beans.PropertyVetoException ex)
1205 setKeyBindings(frame);
1207 // Since the latest FlatLaf patch, we occasionally have problems showing
1208 // structureViewer frames...
1210 boolean shown = false;
1211 Exception last = null;
1218 } catch (IllegalArgumentException iaex)
1222 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1223 + tries + " left) for " + frame.getTitle(), iaex);
1227 } catch (InterruptedException iex)
1232 } while (!shown && tries > 0);
1235 jalview.bin.Console.error(
1236 "Serious Problem whilst showing window " + frame.getTitle(),
1240 windowMenu.add(menuItem);
1245 frame.setSelected(true);
1246 frame.requestFocus();
1247 } catch (java.beans.PropertyVetoException ve)
1249 } catch (java.lang.ClassCastException cex)
1251 jalview.bin.Console.warn(
1252 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1258 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1263 private static void setKeyBindings(JInternalFrame frame)
1265 @SuppressWarnings("serial")
1266 final Action closeAction = new AbstractAction()
1269 public void actionPerformed(ActionEvent e)
1276 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1278 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1279 InputEvent.CTRL_DOWN_MASK);
1280 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1281 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1283 InputMap inputMap = frame
1284 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1285 String ctrlW = ctrlWKey.toString();
1286 inputMap.put(ctrlWKey, ctrlW);
1287 inputMap.put(cmdWKey, ctrlW);
1289 ActionMap actionMap = frame.getActionMap();
1290 actionMap.put(ctrlW, closeAction);
1294 public void lostOwnership(Clipboard clipboard, Transferable contents)
1298 Desktop.jalviewClipboard = null;
1301 internalCopy = false;
1305 public void dragEnter(DropTargetDragEvent evt)
1310 public void dragExit(DropTargetEvent evt)
1315 public void dragOver(DropTargetDragEvent evt)
1320 public void dropActionChanged(DropTargetDragEvent evt)
1331 public void drop(DropTargetDropEvent evt)
1333 boolean success = true;
1334 // JAL-1552 - acceptDrop required before getTransferable call for
1335 // Java's Transferable for native dnd
1336 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1337 Transferable t = evt.getTransferable();
1338 List<Object> files = new ArrayList<>();
1339 List<DataSourceType> protocols = new ArrayList<>();
1343 Desktop.transferFromDropTarget(files, protocols, evt, t);
1344 } catch (Exception e)
1346 e.printStackTrace();
1354 for (int i = 0; i < files.size(); i++)
1356 // BH 2018 File or String
1357 Object file = files.get(i);
1358 String fileName = file.toString();
1359 DataSourceType protocol = (protocols == null)
1360 ? DataSourceType.FILE
1362 FileFormatI format = null;
1364 if (fileName.endsWith(".jar"))
1366 format = FileFormat.Jalview;
1371 format = new IdentifyFile().identify(file, protocol);
1373 if (file instanceof File)
1375 Platform.cacheFileData((File) file);
1377 new FileLoader().LoadFile(null, file, protocol, format);
1380 } catch (Exception ex)
1385 evt.dropComplete(success); // need this to ensure input focus is properly
1386 // transfered to any new windows created
1396 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1398 String fileFormat = FileLoader.getUseDefaultFileFormat()
1399 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1401 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1402 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1403 BackupFiles.getEnabled());
1405 chooser.setFileView(new JalviewFileView());
1406 chooser.setDialogTitle(
1407 MessageManager.getString("label.open_local_file"));
1408 chooser.setToolTipText(MessageManager.getString("action.open"));
1410 chooser.setResponseHandler(0, () -> {
1411 File selectedFile = chooser.getSelectedFile();
1412 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1414 FileFormatI format = chooser.getSelectedFormat();
1417 * Call IdentifyFile to verify the file contains what its extension implies.
1418 * Skip this step for dynamically added file formats, because IdentifyFile does
1419 * not know how to recognise them.
1421 if (FileFormats.getInstance().isIdentifiable(format))
1425 format = new IdentifyFile().identify(selectedFile,
1426 DataSourceType.FILE);
1427 } catch (FileFormatException e)
1429 // format = null; //??
1433 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1436 chooser.showOpenDialog(this);
1440 * Shows a dialog for input of a URL at which to retrieve alignment data
1445 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1447 // This construct allows us to have a wider textfield
1449 JLabel label = new JLabel(
1450 MessageManager.getString("label.input_file_url"));
1452 JPanel panel = new JPanel(new GridLayout(2, 1));
1456 * the URL to fetch is input in Java: an editable combobox with history JS:
1457 * (pending JAL-3038) a plain text field
1460 String urlBase = "https://www.";
1461 if (Platform.isJS())
1463 history = new JTextField(urlBase, 35);
1472 JComboBox<String> asCombo = new JComboBox<>();
1473 asCombo.setPreferredSize(new Dimension(400, 20));
1474 asCombo.setEditable(true);
1475 asCombo.addItem(urlBase);
1476 String historyItems = Cache.getProperty("RECENT_URL");
1477 if (historyItems != null)
1479 for (String token : historyItems.split("\\t"))
1481 asCombo.addItem(token);
1488 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1489 MessageManager.getString("action.cancel") };
1490 Runnable action = () -> {
1491 @SuppressWarnings("unchecked")
1492 String url = (history instanceof JTextField
1493 ? ((JTextField) history).getText()
1494 : ((JComboBox<String>) history).getEditor().getItem()
1495 .toString().trim());
1497 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1499 if (viewport != null)
1501 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1502 FileFormat.Jalview);
1506 new FileLoader().LoadFile(url, DataSourceType.URL,
1507 FileFormat.Jalview);
1512 FileFormatI format = null;
1515 format = new IdentifyFile().identify(url, DataSourceType.URL);
1516 } catch (FileFormatException e)
1518 // TODO revise error handling, distinguish between
1519 // URL not found and response not valid
1524 String msg = MessageManager.formatMessage("label.couldnt_locate",
1526 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1527 MessageManager.getString("label.url_not_found"),
1528 JvOptionPane.WARNING_MESSAGE);
1532 if (viewport != null)
1534 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1539 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1543 String dialogOption = MessageManager
1544 .getString("label.input_alignment_from_url");
1545 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1546 .showInternalDialog(panel, dialogOption,
1547 JvOptionPane.YES_NO_CANCEL_OPTION,
1548 JvOptionPane.PLAIN_MESSAGE, null, options,
1549 MessageManager.getString("action.ok"));
1553 * Opens the CutAndPaste window for the user to paste an alignment in to
1556 * - if not null, the pasted alignment is added to the current
1557 * alignment; if null, to a new alignment window
1560 public void inputTextboxMenuItem_actionPerformed(
1561 AlignmentViewPanel viewPanel)
1563 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1564 cap.setForInput(viewPanel);
1565 Desktop.addInternalFrame(cap,
1566 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1571 * Check with user and saving files before actually quitting
1573 public void desktopQuit()
1575 desktopQuit(true, false);
1579 * close everything, stash window geometries, and shut down all associated
1583 * - sets the dispose on close flag - JVM may terminate when set
1584 * @param terminateJvm
1585 * - quit with prejudice - stops the JVM.
1587 public void quitTheDesktop(boolean dispose, boolean terminateJvm)
1589 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1590 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1591 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1592 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1593 getWidth(), getHeight()));
1595 if (jconsole != null)
1597 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1598 jconsole.stopConsole();
1603 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1606 // Frames should all close automatically. Keeping external
1607 // viewers open should already be decided by user.
1608 closeAll_actionPerformed(null);
1610 if (dialogExecutor != null)
1612 dialogExecutor.shutdownNow();
1615 if (groovyConsole != null)
1617 // suppress a possible repeat prompt to save script
1618 groovyConsole.setDirty(false);
1621 if (((Window) groovyConsole.getFrame()) != null
1622 && ((Window) groovyConsole.getFrame()).isVisible())
1624 // console is visible -- FIXME JAL-4327
1625 groovyConsole.exit();
1629 // console is not, so just let it dispose itself when we shutdown
1630 // we don't call groovyConsole.exit() because it calls the shutdown
1631 // handler with invokeAndWait() causing deadlock
1632 groovyConsole = null;
1638 // note that shutdown hook will not be run
1639 jalview.bin.Console.debug("Force Quit selected by user");
1640 Runtime.getRuntime().halt(0);
1643 jalview.bin.Console.debug("Quit selected by user");
1646 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1647 // instance.dispose();
1651 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1653 final Runnable doDesktopQuit = () -> {
1655 // FIRST !! check for aborted quit
1656 if (QuitHandler.quitCancelled())
1659 .debug("Quit was cancelled - Desktop aborting quit");
1663 // Proceed with quitting
1664 quitTheDesktop(disposeFlag,
1665 QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT);
1670 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1671 QuitHandler.defaultCancelQuit);
1675 * Exits the program and the JVM.
1677 * Don't call this directly
1679 * - use desktopQuit() above to tidy up first.
1681 * - use closeDesktop() to shutdown Jalview without shutting down the JVM
1687 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1688 // not run a second time if gotQuitResponse flag has been set (i.e. user
1689 // confirmed quit of some kind).
1690 Jalview.exit("Desktop exiting.", ExitCode.OK);
1693 private void storeLastKnownDimensions(String string, Rectangle jc)
1695 jalview.bin.Console.debug("Storing last known dimensions for " + string
1696 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1697 + " height:" + jc.height);
1699 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1700 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1701 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1702 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1712 public void aboutMenuItem_actionPerformed(ActionEvent e)
1714 new Thread(new Runnable()
1719 new SplashScreen(false);
1725 * Returns the html text for the About screen, including any available version
1726 * number, build details, author details and citation reference, but without
1727 * the enclosing {@code html} tags
1731 public String getAboutMessage()
1733 StringBuilder message = new StringBuilder(1024);
1734 message.append("<div style=\"font-family: sans-serif;\">")
1735 .append("<h1><strong>Version: ")
1736 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1737 .append("<strong>Built: <em>")
1738 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1739 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1740 .append("</strong>");
1742 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1743 if (latestVersion.equals("Checking"))
1745 // JBP removed this message for 2.11: May be reinstated in future version
1746 // message.append("<br>...Checking latest version...</br>");
1748 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1750 boolean red = false;
1751 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1752 .indexOf("automated build") == -1)
1755 // Displayed when code version and jnlp version do not match and code
1756 // version is not a development build
1757 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1760 message.append("<br>!! Version ")
1761 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1762 .append(" is available for download from ")
1763 .append(Cache.getDefault("www.jalview.org",
1764 "https://www.jalview.org"))
1768 message.append("</div>");
1771 message.append("<br>Authors: ");
1772 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1773 message.append(CITATION);
1775 message.append("</div>");
1777 return message.toString();
1781 * Action on requesting Help documentation
1784 public void documentationMenuItem_actionPerformed()
1788 if (Platform.isJS())
1790 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1799 Help.showHelpWindow();
1801 } catch (Exception ex)
1804 .errPrintln("Error opening help: " + ex.getMessage());
1809 public void closeAll_actionPerformed(ActionEvent e)
1811 // TODO show a progress bar while closing?
1812 JInternalFrame[] frames = desktop.getAllFrames();
1813 for (int i = 0; i < frames.length; i++)
1817 frames[i].setClosed(true);
1818 } catch (java.beans.PropertyVetoException ex)
1822 Jalview.getInstance().setCurrentAlignFrame(null);
1823 jalview.bin.Console.info("ALL CLOSED");
1826 * reset state of singleton objects as appropriate (clear down session state
1827 * when all windows are closed)
1829 StructureSelectionManager ssm = StructureSelectionManager
1830 .getStructureSelectionManager(this);
1837 public int structureViewersStillRunningCount()
1840 JInternalFrame[] frames = desktop.getAllFrames();
1841 for (int i = 0; i < frames.length; i++)
1843 if (frames[i] != null
1844 && frames[i] instanceof JalviewStructureDisplayI)
1846 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1854 public void raiseRelated_actionPerformed(ActionEvent e)
1856 reorderAssociatedWindows(false, false);
1860 public void minimizeAssociated_actionPerformed(ActionEvent e)
1862 reorderAssociatedWindows(true, false);
1865 void closeAssociatedWindows()
1867 reorderAssociatedWindows(false, true);
1873 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1877 protected void garbageCollect_actionPerformed(ActionEvent e)
1879 // We simply collect the garbage
1880 jalview.bin.Console.debug("Collecting garbage...");
1882 jalview.bin.Console.debug("Finished garbage collection.");
1888 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1892 protected void showMemusage_actionPerformed(ActionEvent e)
1894 desktop.showMemoryUsage(showMemusage.isSelected());
1901 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1905 protected void showConsole_actionPerformed(ActionEvent e)
1907 showConsole(showConsole.isSelected());
1910 Console jconsole = null;
1913 * control whether the java console is visible or not
1917 void showConsole(boolean selected)
1919 // TODO: decide if we should update properties file
1920 if (jconsole != null) // BH 2018
1922 showConsole.setSelected(selected);
1923 Cache.setProperty("SHOW_JAVA_CONSOLE",
1924 Boolean.valueOf(selected).toString());
1925 jconsole.setVisible(selected);
1929 void reorderAssociatedWindows(boolean minimize, boolean close)
1931 JInternalFrame[] frames = desktop.getAllFrames();
1932 if (frames == null || frames.length < 1)
1937 AlignmentViewport source = null, target = null;
1938 if (frames[0] instanceof AlignFrame)
1940 source = ((AlignFrame) frames[0]).getCurrentView();
1942 else if (frames[0] instanceof TreePanel)
1944 source = ((TreePanel) frames[0]).getViewPort();
1946 else if (frames[0] instanceof PCAPanel)
1948 source = ((PCAPanel) frames[0]).av;
1950 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1952 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1957 for (int i = 0; i < frames.length; i++)
1960 if (frames[i] == null)
1964 if (frames[i] instanceof AlignFrame)
1966 target = ((AlignFrame) frames[i]).getCurrentView();
1968 else if (frames[i] instanceof TreePanel)
1970 target = ((TreePanel) frames[i]).getViewPort();
1972 else if (frames[i] instanceof PCAPanel)
1974 target = ((PCAPanel) frames[i]).av;
1976 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1978 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1981 if (source == target)
1987 frames[i].setClosed(true);
1991 frames[i].setIcon(minimize);
1994 frames[i].toFront();
1998 } catch (java.beans.PropertyVetoException ex)
2013 protected void preferences_actionPerformed(ActionEvent e)
2015 Preferences.openPreferences();
2019 * Prompts the user to choose a file and then saves the Jalview state as a
2020 * Jalview project file
2023 public void saveState_actionPerformed()
2025 saveState_actionPerformed(false);
2028 public void saveState_actionPerformed(boolean saveAs)
2030 java.io.File projectFile = getProjectFile();
2031 // autoSave indicates we already have a file and don't need to ask
2032 boolean autoSave = projectFile != null && !saveAs
2033 && BackupFiles.getEnabled();
2035 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
2036 // projectFile='"+projectFile+"',
2037 // saveAs="+saveAs+", Backups
2038 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
2040 boolean approveSave = false;
2043 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
2046 chooser.setFileView(new JalviewFileView());
2047 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
2049 int value = chooser.showSaveDialog(this);
2051 if (value == JalviewFileChooser.APPROVE_OPTION)
2053 projectFile = chooser.getSelectedFile();
2054 setProjectFile(projectFile);
2059 if (approveSave || autoSave)
2061 final Desktop me = this;
2062 final java.io.File chosenFile = projectFile;
2063 new Thread(new Runnable()
2068 // TODO: refactor to Jalview desktop session controller action.
2069 setProgressBar(MessageManager.formatMessage(
2070 "label.saving_jalview_project", new Object[]
2071 { chosenFile.getName() }),
2072 IdUtils.newId(IdType.PROGRESS, chosenFile));
2073 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
2074 // TODO catch and handle errors for savestate
2075 // TODO prevent user from messing with the Desktop whilst we're saving
2078 boolean doBackup = BackupFiles.getEnabled();
2079 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
2082 new Jalview2XML().saveState(
2083 doBackup ? backupfiles.getTempFile() : chosenFile);
2087 backupfiles.setWriteSuccess(true);
2088 backupfiles.rollBackupsAndRenameTempFile();
2090 } catch (OutOfMemoryError oom)
2092 new OOMWarning("Whilst saving current state to "
2093 + chosenFile.getName(), oom);
2094 } catch (Exception ex)
2096 jalview.bin.Console.error("Problems whilst trying to save to "
2097 + chosenFile.getName(), ex);
2098 JvOptionPane.showMessageDialog(me,
2099 MessageManager.formatMessage(
2100 "label.error_whilst_saving_current_state_to",
2102 { chosenFile.getName() }),
2103 MessageManager.getString("label.couldnt_save_project"),
2104 JvOptionPane.WARNING_MESSAGE);
2106 setProgressBar(null, IdUtils.newId(IdType.PROGRESS, chosenFile));
2113 public void saveAsState_actionPerformed(ActionEvent e)
2115 saveState_actionPerformed(true);
2118 protected void setProjectFile(File choice)
2120 this.projectFile = choice;
2123 public File getProjectFile()
2125 return this.projectFile;
2129 * Shows a file chooser dialog and tries to read in the selected file as a
2133 public void loadState_actionPerformed()
2135 final String[] suffix = new String[] { "jvp", "jar" };
2136 final String[] desc = new String[] { "Jalview Project",
2137 "Jalview Project (old)" };
2138 JalviewFileChooser chooser = new JalviewFileChooser(
2139 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2140 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2144 chooser.setFileView(new JalviewFileView());
2145 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2146 chooser.setResponseHandler(0, () -> {
2147 File selectedFile = chooser.getSelectedFile();
2148 setProjectFile(selectedFile);
2149 String choice = selectedFile.getAbsolutePath();
2150 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2151 new Thread(new Runnable()
2158 new Jalview2XML().loadJalviewAlign(selectedFile);
2159 } catch (OutOfMemoryError oom)
2161 new OOMWarning("Whilst loading project from " + choice, oom);
2162 } catch (Exception ex)
2164 jalview.bin.Console.error(
2165 "Problems whilst loading project from " + choice, ex);
2166 JvOptionPane.showMessageDialog(Desktop.desktop,
2167 MessageManager.formatMessage(
2168 "label.error_whilst_loading_project_from",
2171 MessageManager.getString("label.couldnt_load_project"),
2172 JvOptionPane.WARNING_MESSAGE);
2175 }, "Project Loader").start();
2178 chooser.showOpenDialog(this);
2182 public void inputSequence_actionPerformed(ActionEvent e)
2184 new SequenceFetcher(this);
2187 JPanel progressPanel;
2189 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2191 public void startLoading(final Object fileName)
2193 if (fileLoadingCount == 0)
2195 fileLoadingPanels.add(addProgressPanel(MessageManager
2196 .formatMessage("label.loading_file", new Object[]
2202 private JPanel addProgressPanel(String string)
2204 if (progressPanel == null)
2206 progressPanel = new JPanel(new GridLayout(1, 1));
2207 totalProgressCount = 0;
2208 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2210 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2211 JProgressBar progressBar = new JProgressBar();
2212 progressBar.setIndeterminate(true);
2214 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2216 thisprogress.add(progressBar, BorderLayout.CENTER);
2217 progressPanel.add(thisprogress);
2218 ((GridLayout) progressPanel.getLayout()).setRows(
2219 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2220 ++totalProgressCount;
2221 instance.validate();
2222 return thisprogress;
2225 int totalProgressCount = 0;
2227 private void removeProgressPanel(JPanel progbar)
2229 if (progressPanel != null)
2231 synchronized (progressPanel)
2233 progressPanel.remove(progbar);
2234 GridLayout gl = (GridLayout) progressPanel.getLayout();
2235 gl.setRows(gl.getRows() - 1);
2236 if (--totalProgressCount < 1)
2238 this.getContentPane().remove(progressPanel);
2239 progressPanel = null;
2246 public void stopLoading()
2249 if (fileLoadingCount < 1)
2251 while (fileLoadingPanels.size() > 0)
2253 removeProgressPanel(fileLoadingPanels.remove(0));
2255 fileLoadingPanels.clear();
2256 fileLoadingCount = 0;
2261 public static int getViewCount(String alignmentId)
2263 AlignmentViewport[] aps = getViewports(alignmentId);
2264 return (aps == null) ? 0 : aps.length;
2269 * @param alignmentId
2270 * - if null, all sets are returned
2271 * @return all AlignmentPanels concerning the alignmentId sequence set
2273 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2275 if (Desktop.desktop == null)
2277 // no frames created and in headless mode
2278 // TODO: verify that frames are recoverable when in headless mode
2281 List<AlignmentPanel> aps = new ArrayList<>();
2282 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2287 for (AlignFrame af : frames)
2289 for (AlignmentPanel ap : af.alignPanels)
2291 if (alignmentId == null
2292 || alignmentId.equals(ap.av.getSequenceSetId()))
2298 if (aps.size() == 0)
2302 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2307 * get all the viewports on an alignment.
2309 * @param sequenceSetId
2310 * unique alignment id (may be null - all viewports returned in that
2312 * @return all viewports on the alignment bound to sequenceSetId
2314 public static AlignmentViewport[] getViewports(String sequenceSetId)
2316 List<AlignmentViewport> viewp = new ArrayList<>();
2317 if (desktop != null)
2319 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2321 for (AlignFrame afr : frames)
2323 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2324 .equals(sequenceSetId))
2326 if (afr.alignPanels != null)
2328 for (AlignmentPanel ap : afr.alignPanels)
2330 if (sequenceSetId == null
2331 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2339 viewp.add(afr.getViewport());
2343 if (viewp.size() > 0)
2345 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2352 * Explode the views in the given frame into separate AlignFrame
2356 public static void explodeViews(AlignFrame af)
2358 int size = af.alignPanels.size();
2364 // FIXME: ideally should use UI interface API
2365 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2366 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2367 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2368 for (int i = 0; i < size; i++)
2370 AlignmentPanel ap = af.alignPanels.get(i);
2372 AlignFrame newaf = new AlignFrame(ap);
2374 // transfer reference for existing feature settings to new alignFrame
2375 if (ap == af.alignPanel)
2377 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2379 newaf.featureSettings = viewFeatureSettings;
2381 newaf.setFeatureSettingsGeometry(fsBounds);
2385 * Restore the view's last exploded frame geometry if known. Multiple views from
2386 * one exploded frame share and restore the same (frame) position and size.
2388 Rectangle geometry = ap.av.getExplodedGeometry();
2389 if (geometry != null)
2391 newaf.setBounds(geometry);
2394 ap.av.setGatherViewsHere(false);
2396 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2397 AlignFrame.DEFAULT_HEIGHT);
2398 // and materialise a new feature settings dialog instance for the new
2400 // (closes the old as if 'OK' was pressed)
2401 if (ap == af.alignPanel && newaf.featureSettings != null
2402 && newaf.featureSettings.isOpen()
2403 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2405 newaf.showFeatureSettingsUI();
2409 af.featureSettings = null;
2410 af.alignPanels.clear();
2411 af.closeMenuItem_actionPerformed(true);
2416 * Gather expanded views (separate AlignFrame's) with the same sequence set
2417 * identifier back in to this frame as additional views, and close the
2418 * expanded views. Note the expanded frames may themselves have multiple
2419 * views. We take the lot.
2423 public void gatherViews(AlignFrame source)
2425 source.viewport.setGatherViewsHere(true);
2426 source.viewport.setExplodedGeometry(source.getBounds());
2427 JInternalFrame[] frames = desktop.getAllFrames();
2428 String viewId = source.viewport.getSequenceSetId();
2429 for (int t = 0; t < frames.length; t++)
2431 if (frames[t] instanceof AlignFrame && frames[t] != source)
2433 AlignFrame af = (AlignFrame) frames[t];
2434 boolean gatherThis = false;
2435 for (int a = 0; a < af.alignPanels.size(); a++)
2437 AlignmentPanel ap = af.alignPanels.get(a);
2438 if (viewId.equals(ap.av.getSequenceSetId()))
2441 ap.av.setGatherViewsHere(false);
2442 ap.av.setExplodedGeometry(af.getBounds());
2443 source.addAlignmentPanel(ap, false);
2449 if (af.featureSettings != null && af.featureSettings.isOpen())
2451 if (source.featureSettings == null)
2453 // preserve the feature settings geometry for this frame
2454 source.featureSettings = af.featureSettings;
2455 source.setFeatureSettingsGeometry(
2456 af.getFeatureSettingsGeometry());
2460 // close it and forget
2461 af.featureSettings.close();
2464 af.alignPanels.clear();
2465 af.closeMenuItem_actionPerformed(true);
2470 // refresh the feature setting UI for the source frame if it exists
2471 if (source.featureSettings != null && source.featureSettings.isOpen())
2473 source.showFeatureSettingsUI();
2478 public JInternalFrame[] getAllFrames()
2480 return desktop.getAllFrames();
2484 * Checks the given url to see if it gives a response indicating that the user
2485 * should be informed of a new questionnaire.
2489 public void checkForQuestionnaire(String url)
2491 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2492 // javax.swing.SwingUtilities.invokeLater(jvq);
2493 new Thread(jvq).start();
2496 public void checkURLLinks()
2498 // Thread off the URL link checker
2499 addDialogThread(new Runnable()
2504 if (Cache.getDefault("CHECKURLLINKS", true))
2506 // check what the actual links are - if it's just the default don't
2507 // bother with the warning
2508 List<String> links = Preferences.sequenceUrlLinks
2511 // only need to check links if there is one with a
2512 // SEQUENCE_ID which is not the default EMBL_EBI link
2513 ListIterator<String> li = links.listIterator();
2514 boolean check = false;
2515 List<JLabel> urls = new ArrayList<>();
2516 while (li.hasNext())
2518 String link = li.next();
2519 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2520 && !UrlConstants.isDefaultString(link))
2523 int barPos = link.indexOf("|");
2524 String urlMsg = barPos == -1 ? link
2525 : link.substring(0, barPos) + ": "
2526 + link.substring(barPos + 1);
2527 urls.add(new JLabel(urlMsg));
2535 // ask user to check in case URL links use old style tokens
2536 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2537 JPanel msgPanel = new JPanel();
2538 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2539 msgPanel.add(Box.createVerticalGlue());
2540 JLabel msg = new JLabel(MessageManager
2541 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2542 JLabel msg2 = new JLabel(MessageManager
2543 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2545 for (JLabel url : urls)
2551 final JCheckBox jcb = new JCheckBox(
2552 MessageManager.getString("label.do_not_display_again"));
2553 jcb.addActionListener(new ActionListener()
2556 public void actionPerformed(ActionEvent e)
2558 // update Cache settings for "don't show this again"
2559 boolean showWarningAgain = !jcb.isSelected();
2560 Cache.setProperty("CHECKURLLINKS",
2561 Boolean.valueOf(showWarningAgain).toString());
2566 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2568 .getString("label.SEQUENCE_ID_no_longer_used"),
2569 JvOptionPane.WARNING_MESSAGE);
2576 * Proxy class for JDesktopPane which optionally displays the current memory
2577 * usage and highlights the desktop area with a red bar if free memory runs
2582 public class MyDesktopPane extends JDesktopPane implements Runnable
2584 private static final float ONE_MB = 1048576f;
2586 boolean showMemoryUsage = false;
2590 java.text.NumberFormat df;
2592 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2595 public MyDesktopPane(boolean showMemoryUsage)
2597 showMemoryUsage(showMemoryUsage);
2600 public void showMemoryUsage(boolean showMemory)
2602 this.showMemoryUsage = showMemory;
2605 Thread worker = new Thread(this);
2611 public boolean isShowMemoryUsage()
2613 return showMemoryUsage;
2619 df = java.text.NumberFormat.getNumberInstance();
2620 df.setMaximumFractionDigits(2);
2621 runtime = Runtime.getRuntime();
2623 while (showMemoryUsage)
2627 maxMemory = runtime.maxMemory() / ONE_MB;
2628 allocatedMemory = runtime.totalMemory() / ONE_MB;
2629 freeMemory = runtime.freeMemory() / ONE_MB;
2630 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2632 percentUsage = (totalFreeMemory / maxMemory) * 100;
2634 // if (percentUsage < 20)
2636 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2638 // instance.set.setBorder(border1);
2641 // sleep after showing usage
2643 } catch (Exception ex)
2645 ex.printStackTrace();
2651 public void paintComponent(Graphics g)
2653 if (showMemoryUsage && g != null && df != null)
2655 if (percentUsage < 20)
2657 g.setColor(Color.red);
2659 FontMetrics fm = g.getFontMetrics();
2662 g.drawString(MessageManager.formatMessage("label.memory_stats",
2664 { df.format(totalFreeMemory), df.format(maxMemory),
2665 df.format(percentUsage) }),
2666 10, getHeight() - fm.getHeight());
2670 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2671 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2676 * Accessor method to quickly get all the AlignmentFrames loaded.
2678 * @return an array of AlignFrame, or null if none found
2681 public AlignFrame[] getAlignFrames()
2683 if (desktop == null)
2688 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2694 List<AlignFrame> avp = new ArrayList<>();
2696 for (int i = frames.length - 1; i > -1; i--)
2698 if (frames[i] instanceof AlignFrame)
2700 avp.add((AlignFrame) frames[i]);
2702 else if (frames[i] instanceof SplitFrame)
2705 * Also check for a split frame containing an AlignFrame
2707 GSplitFrame sf = (GSplitFrame) frames[i];
2708 if (sf.getTopFrame() instanceof AlignFrame)
2710 avp.add((AlignFrame) sf.getTopFrame());
2712 if (sf.getBottomFrame() instanceof AlignFrame)
2714 avp.add((AlignFrame) sf.getBottomFrame());
2718 if (avp.size() == 0)
2722 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2729 public static AlignFrame[] getDesktopAlignFrames()
2731 if (Jalview.isHeadlessMode())
2733 // Desktop.desktop is null in headless mode
2734 return Jalview.getInstance().getAlignFrames();
2737 if (instance != null && desktop != null)
2739 return instance.getAlignFrames();
2746 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2750 public GStructureViewer[] getJmols()
2752 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2758 List<GStructureViewer> avp = new ArrayList<>();
2760 for (int i = frames.length - 1; i > -1; i--)
2762 if (frames[i] instanceof AppJmol)
2764 GStructureViewer af = (GStructureViewer) frames[i];
2768 if (avp.size() == 0)
2772 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2777 * Add Groovy Support to Jalview
2780 public void groovyShell_actionPerformed()
2784 openGroovyConsole();
2785 } catch (Exception ex)
2787 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2788 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2790 MessageManager.getString("label.couldnt_create_groovy_shell"),
2791 MessageManager.getString("label.groovy_support_failed"),
2792 JvOptionPane.ERROR_MESSAGE);
2797 * Open the Groovy console
2799 void openGroovyConsole()
2801 if (groovyConsole == null)
2803 JalviewObjectI j = new JalviewObject(this);
2804 groovyConsole = new groovy.console.ui.Console();
2805 groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j);
2806 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2807 getCurrentAlignFrame());
2808 groovyConsole.run();
2811 * We allow only one console at a time, so that AlignFrame menu option
2812 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2813 * enable 'Run script', when the console is opened, and the reverse when it is
2816 Window window = (Window) groovyConsole.getFrame();
2817 window.addWindowListener(new WindowAdapter()
2820 public void windowClosed(WindowEvent e)
2823 * rebind CMD-Q from Groovy Console to Jalview Quit
2826 enableExecuteGroovy(false);
2832 * show Groovy console window (after close and reopen)
2834 ((Window) groovyConsole.getFrame()).setVisible(true);
2837 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2838 * opening a second console
2840 enableExecuteGroovy(true);
2844 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2845 * binding when opened
2847 protected void addQuitHandler()
2850 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2852 .getKeyStroke(KeyEvent.VK_Q,
2853 jalview.util.ShortcutKeyMaskExWrapper
2854 .getMenuShortcutKeyMaskEx()),
2856 getRootPane().getActionMap().put("Quit", new AbstractAction()
2859 public void actionPerformed(ActionEvent e)
2867 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2870 * true if Groovy console is open
2872 public void enableExecuteGroovy(boolean enabled)
2875 * disable opening a second Groovy console (or re-enable when the console is
2878 groovyShell.setEnabled(!enabled);
2880 AlignFrame[] alignFrames = getDesktopAlignFrames();
2881 if (alignFrames != null)
2883 for (AlignFrame af : alignFrames)
2885 af.setGroovyEnabled(enabled);
2891 * Progress bars managed by the IProgressIndicator method.
2893 private Hashtable<Long, JPanel> progressBars;
2895 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2900 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2903 public void setProgressBar(String message, long id)
2905 if (progressBars == null)
2907 progressBars = new Hashtable<>();
2908 progressBarHandlers = new Hashtable<>();
2911 if (progressBars.get(Long.valueOf(id)) != null)
2913 JPanel panel = progressBars.remove(Long.valueOf(id));
2914 if (progressBarHandlers.contains(Long.valueOf(id)))
2916 progressBarHandlers.remove(Long.valueOf(id));
2918 removeProgressPanel(panel);
2922 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2929 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2930 * jalview.gui.IProgressIndicatorHandler)
2933 public void registerHandler(final long id,
2934 final IProgressIndicatorHandler handler)
2936 if (progressBarHandlers == null
2937 || !progressBars.containsKey(Long.valueOf(id)))
2939 throw new Error(MessageManager.getString(
2940 "error.call_setprogressbar_before_registering_handler"));
2942 progressBarHandlers.put(Long.valueOf(id), handler);
2943 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2944 if (handler.canCancel())
2946 JButton cancel = new JButton(
2947 MessageManager.getString("action.cancel"));
2948 final IProgressIndicator us = this;
2949 cancel.addActionListener(new ActionListener()
2953 public void actionPerformed(ActionEvent e)
2955 handler.cancelActivity(id);
2956 us.setProgressBar(MessageManager
2957 .formatMessage("label.cancelled_params", new Object[]
2958 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2962 progressPanel.add(cancel, BorderLayout.EAST);
2968 * @return true if any progress bars are still active
2971 public boolean operationInProgress()
2973 if (progressBars != null && progressBars.size() > 0)
2981 * This will return the first AlignFrame holding the given viewport instance.
2982 * It will break if there are more than one AlignFrames viewing a particular
2986 * @return alignFrame for viewport
2988 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2990 if (desktop != null)
2992 AlignmentPanel[] aps = getAlignmentPanels(
2993 viewport.getSequenceSetId());
2994 for (int panel = 0; aps != null && panel < aps.length; panel++)
2996 if (aps[panel] != null && aps[panel].av == viewport)
2998 return aps[panel].alignFrame;
3005 public VamsasApplication getVamsasApplication()
3007 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
3013 * flag set if jalview GUI is being operated programmatically
3015 private boolean inBatchMode = false;
3018 * check if jalview GUI is being operated programmatically
3020 * @return inBatchMode
3022 public boolean isInBatchMode()
3028 * set flag if jalview GUI is being operated programmatically
3030 * @param inBatchMode
3032 public void setInBatchMode(boolean inBatchMode)
3034 this.inBatchMode = inBatchMode;
3038 * start service discovery and wait till it is done
3040 public void startServiceDiscovery()
3042 startServiceDiscovery(false);
3046 * start service discovery threads - blocking or non-blocking
3050 public void startServiceDiscovery(boolean blocking)
3052 startServiceDiscovery(blocking, false);
3056 * start service discovery threads
3059 * - false means call returns immediately
3060 * @param ignore_SHOW_JWS2_SERVICES_preference
3061 * - when true JABA services are discovered regardless of user's JWS2
3062 * discovery preference setting
3064 public void startServiceDiscovery(boolean blocking,
3065 boolean ignore_SHOW_JWS2_SERVICES_preference)
3067 boolean alive = true;
3068 Thread t0 = null, t1 = null, t2 = null;
3069 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
3072 // todo: changesupport handlers need to be transferred
3073 if (discoverer == null)
3075 discoverer = new jalview.ws.jws1.Discoverer();
3076 // register PCS handler for desktop.
3077 discoverer.addPropertyChangeListener(changeSupport);
3079 // JAL-940 - disabled JWS1 service configuration - always start discoverer
3080 // until we phase out completely
3081 (t0 = new Thread(discoverer)).start();
3084 if (ignore_SHOW_JWS2_SERVICES_preference
3085 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
3087 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3088 .startDiscoverer(changeSupport);
3092 // TODO: do rest service discovery
3101 } catch (Exception e)
3104 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
3105 || (t3 != null && t3.isAlive())
3106 || (t0 != null && t0.isAlive());
3112 * called to check if the service discovery process completed successfully.
3116 protected void JalviewServicesChanged(PropertyChangeEvent evt)
3118 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
3120 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3121 .getErrorMessages();
3124 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3126 if (serviceChangedDialog == null)
3128 // only run if we aren't already displaying one of these.
3129 addDialogThread(serviceChangedDialog = new Runnable()
3136 * JalviewDialog jd =new JalviewDialog() {
3138 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3140 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3142 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3144 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3146 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3147 * + " or mis-configured HTTP proxy settings.<br/>" +
3148 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3149 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3150 * true, true, "Web Service Configuration Problem", 450, 400);
3152 * jd.waitForInput();
3154 JvOptionPane.showConfirmDialog(Desktop.desktop,
3155 new JLabel("<html><table width=\"450\"><tr><td>"
3156 + ermsg + "</td></tr></table>"
3157 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3158 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3159 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3160 + " Tools->Preferences dialog box to change them.</p></html>"),
3161 "Web Service Configuration Problem",
3162 JvOptionPane.DEFAULT_OPTION,
3163 JvOptionPane.ERROR_MESSAGE);
3164 serviceChangedDialog = null;
3172 jalview.bin.Console.error(
3173 "Errors reported by JABA discovery service. Check web services preferences.\n"
3180 private Runnable serviceChangedDialog = null;
3183 * start a thread to open a URL in the configured browser. Pops up a warning
3184 * dialog to the user if there is an exception when calling out to the browser
3189 public static void showUrl(final String url)
3191 if (url != null && !url.trim().equals(""))
3193 jalview.bin.Console.info("Opening URL: " + url);
3194 showUrl(url, Desktop.instance);
3198 jalview.bin.Console.warn("Ignoring attempt to show an empty URL.");
3204 * Like showUrl but allows progress handler to be specified
3208 * (null) or object implementing IProgressIndicator
3210 public static void showUrl(final String url,
3211 final IProgressIndicator progress)
3213 new Thread(new Runnable()
3220 if (progress != null)
3222 progress.setProgressBar(MessageManager
3223 .formatMessage("status.opening_params", new Object[]
3224 { url }), IdUtils.newId(IdType.PROGRESS, this));
3226 jalview.util.BrowserLauncher.openURL(url);
3227 } catch (Exception ex)
3229 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3231 .getString("label.web_browser_not_found_unix"),
3232 MessageManager.getString("label.web_browser_not_found"),
3233 JvOptionPane.WARNING_MESSAGE);
3235 ex.printStackTrace();
3237 if (progress != null)
3239 progress.setProgressBar(null,
3240 IdUtils.newId(IdType.PROGRESS, this));
3246 public static WsParamSetManager wsparamManager = null;
3248 public static ParamManager getUserParameterStore()
3250 if (wsparamManager == null)
3252 wsparamManager = new WsParamSetManager();
3254 return wsparamManager;
3258 * static hyperlink handler proxy method for use by Jalview's internal windows
3262 public static void hyperlinkUpdate(HyperlinkEvent e)
3264 if (e.getEventType() == EventType.ACTIVATED)
3269 url = e.getURL().toString();
3270 Desktop.showUrl(url);
3271 } catch (Exception x)
3276 .error("Couldn't handle string " + url + " as a URL.");
3278 // ignore any exceptions due to dud links.
3285 * single thread that handles display of dialogs to user.
3287 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3290 * flag indicating if dialogExecutor should try to acquire a permit
3292 private volatile boolean dialogPause = true;
3297 private Semaphore block = new Semaphore(0);
3299 private static groovy.console.ui.Console groovyConsole;
3302 * add another dialog thread to the queue
3306 public void addDialogThread(final Runnable prompter)
3308 dialogExecutor.submit(new Runnable()
3315 acquireDialogQueue();
3317 if (instance == null)
3323 SwingUtilities.invokeAndWait(prompter);
3324 } catch (Exception q)
3326 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3333 private boolean dialogQueueStarted = false;
3335 public void startDialogQueue()
3337 if (dialogQueueStarted)
3341 // set the flag so we don't pause waiting for another permit and semaphore
3342 // the current task to begin
3343 releaseDialogQueue();
3344 dialogQueueStarted = true;
3347 public void acquireDialogQueue()
3353 } catch (InterruptedException e)
3355 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3360 public void releaseDialogQueue()
3367 dialogPause = false;
3371 * Outputs an image of the desktop to file in EPS format, after prompting the
3372 * user for choice of Text or Lineart character rendering (unless a preference
3373 * has been set). The file name is generated as
3376 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3380 protected void snapShotWindow_actionPerformed(ActionEvent e)
3382 // currently the menu option to do this is not shown
3385 int width = getWidth();
3386 int height = getHeight();
3388 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3389 ImageWriterI writer = new ImageWriterI()
3392 public void exportImage(Graphics g) throws Exception
3395 jalview.bin.Console.info("Successfully written snapshot to file "
3396 + of.getAbsolutePath());
3399 String title = "View of desktop";
3400 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3404 exporter.doExport(of, this, width, height, title);
3405 } catch (ImageOutputException ioex)
3407 jalview.bin.Console.error(
3408 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3414 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3415 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3416 * and location last time the view was expanded (if any). However it does not
3417 * remember the split pane divider location - this is set to match the
3418 * 'exploding' frame.
3422 public void explodeViews(SplitFrame sf)
3424 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3425 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3426 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3428 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3430 int viewCount = topPanels.size();
3437 * Processing in reverse order works, forwards order leaves the first panels not
3438 * visible. I don't know why!
3440 for (int i = viewCount - 1; i >= 0; i--)
3443 * Make new top and bottom frames. These take over the respective AlignmentPanel
3444 * objects, including their AlignmentViewports, so the cdna/protein
3445 * relationships between the viewports is carried over to the new split frames.
3447 * explodedGeometry holds the (x, y) position of the previously exploded
3448 * SplitFrame, and the (width, height) of the AlignFrame component
3450 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3451 AlignFrame newTopFrame = new AlignFrame(topPanel);
3452 newTopFrame.setSize(oldTopFrame.getSize());
3453 newTopFrame.setVisible(true);
3454 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3455 .getExplodedGeometry();
3456 if (geometry != null)
3458 newTopFrame.setSize(geometry.getSize());
3461 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3462 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3463 newBottomFrame.setSize(oldBottomFrame.getSize());
3464 newBottomFrame.setVisible(true);
3465 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3466 .getExplodedGeometry();
3467 if (geometry != null)
3469 newBottomFrame.setSize(geometry.getSize());
3472 topPanel.av.setGatherViewsHere(false);
3473 bottomPanel.av.setGatherViewsHere(false);
3474 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3476 if (geometry != null)
3478 splitFrame.setLocation(geometry.getLocation());
3480 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3484 * Clear references to the panels (now relocated in the new SplitFrames) before
3485 * closing the old SplitFrame.
3488 bottomPanels.clear();
3493 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3494 * back into the given SplitFrame as additional views. Note that the gathered
3495 * frames may themselves have multiple views.
3499 public void gatherViews(GSplitFrame source)
3502 * special handling of explodedGeometry for a view within a SplitFrame: - it
3503 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3504 * height) of the AlignFrame component
3506 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3507 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3508 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3509 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3510 myBottomFrame.viewport
3511 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3512 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3513 myTopFrame.viewport.setGatherViewsHere(true);
3514 myBottomFrame.viewport.setGatherViewsHere(true);
3515 String topViewId = myTopFrame.viewport.getSequenceSetId();
3516 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3518 JInternalFrame[] frames = desktop.getAllFrames();
3519 for (JInternalFrame frame : frames)
3521 if (frame instanceof SplitFrame && frame != source)
3523 SplitFrame sf = (SplitFrame) frame;
3524 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3525 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3526 boolean gatherThis = false;
3527 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3529 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3530 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3531 if (topViewId.equals(topPanel.av.getSequenceSetId())
3532 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3535 topPanel.av.setGatherViewsHere(false);
3536 bottomPanel.av.setGatherViewsHere(false);
3537 topPanel.av.setExplodedGeometry(
3538 new Rectangle(sf.getLocation(), topFrame.getSize()));
3539 bottomPanel.av.setExplodedGeometry(
3540 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3541 myTopFrame.addAlignmentPanel(topPanel, false);
3542 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3548 topFrame.getAlignPanels().clear();
3549 bottomFrame.getAlignPanels().clear();
3556 * The dust settles...give focus to the tab we did this from.
3558 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3561 public static groovy.console.ui.Console getGroovyConsole()
3563 return groovyConsole;
3567 * handles the payload of a drag and drop event.
3569 * TODO refactor to desktop utilities class
3572 * - Data source strings extracted from the drop event
3574 * - protocol for each data source extracted from the drop event
3578 * - the payload from the drop event
3581 public static void transferFromDropTarget(List<Object> files,
3582 List<DataSourceType> protocols, DropTargetDropEvent evt,
3583 Transferable t) throws Exception
3586 DataFlavor uriListFlavor = new DataFlavor(
3587 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3590 urlFlavour = new DataFlavor(
3591 "application/x-java-url; class=java.net.URL");
3592 } catch (ClassNotFoundException cfe)
3594 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3598 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3603 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3604 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3605 // means url may be null.
3608 protocols.add(DataSourceType.URL);
3609 files.add(url.toString());
3610 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3611 + files.get(files.size() - 1));
3616 if (Platform.isAMacAndNotJS())
3618 jalview.bin.Console.errPrintln(
3619 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3622 } catch (Throwable ex)
3624 jalview.bin.Console.debug("URL drop handler failed.", ex);
3627 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3629 // Works on Windows and MacOSX
3630 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3631 for (Object file : (List) t
3632 .getTransferData(DataFlavor.javaFileListFlavor))
3635 protocols.add(DataSourceType.FILE);
3640 // Unix like behaviour
3641 boolean added = false;
3643 if (t.isDataFlavorSupported(uriListFlavor))
3645 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3646 // This is used by Unix drag system
3647 data = (String) t.getTransferData(uriListFlavor);
3651 // fallback to text: workaround - on OSX where there's a JVM bug
3653 .debug("standard URIListFlavor failed. Trying text");
3654 // try text fallback
3655 DataFlavor textDf = new DataFlavor(
3656 "text/plain;class=java.lang.String");
3657 if (t.isDataFlavorSupported(textDf))
3659 data = (String) t.getTransferData(textDf);
3662 jalview.bin.Console.debug("Plain text drop content returned "
3663 + (data == null ? "Null - failed" : data));
3668 while (protocols.size() < files.size())
3670 jalview.bin.Console.debug("Adding missing FILE protocol for "
3671 + files.get(protocols.size()));
3672 protocols.add(DataSourceType.FILE);
3674 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3675 data, "\r\n"); st.hasMoreTokens();)
3678 String s = st.nextToken();
3679 if (s.startsWith("#"))
3681 // the line is a comment (as per the RFC 2483)
3684 java.net.URI uri = new java.net.URI(s);
3685 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3687 protocols.add(DataSourceType.URL);
3688 files.add(uri.toString());
3692 // otherwise preserve old behaviour: catch all for file objects
3693 java.io.File file = new java.io.File(uri);
3694 protocols.add(DataSourceType.FILE);
3695 files.add(file.toString());
3700 if (jalview.bin.Console.isDebugEnabled())
3702 if (data == null || !added)
3705 if (t.getTransferDataFlavors() != null
3706 && t.getTransferDataFlavors().length > 0)
3708 jalview.bin.Console.debug(
3709 "Couldn't resolve drop data. Here are the supported flavors:");
3710 for (DataFlavor fl : t.getTransferDataFlavors())
3712 jalview.bin.Console.debug(
3713 "Supported transfer dataflavor: " + fl.toString());
3714 Object df = t.getTransferData(fl);
3717 jalview.bin.Console.debug("Retrieves: " + df);
3721 jalview.bin.Console.debug("Retrieved nothing");
3728 .debug("Couldn't resolve dataflavor for drop: "
3734 if (Platform.isWindowsAndNotJS())
3737 .debug("Scanning dropped content for Windows Link Files");
3739 // resolve any .lnk files in the file drop
3740 for (int f = 0; f < files.size(); f++)
3742 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3743 if (protocols.get(f).equals(DataSourceType.FILE)
3744 && (source.endsWith(".lnk") || source.endsWith(".url")
3745 || source.endsWith(".site")))
3749 Object obj = files.get(f);
3750 File lf = (obj instanceof File ? (File) obj
3751 : new File((String) obj));
3752 // process link file to get a URL
3753 jalview.bin.Console.debug("Found potential link file: " + lf);
3754 WindowsShortcut wscfile = new WindowsShortcut(lf);
3755 String fullname = wscfile.getRealFilename();
3756 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3757 files.set(f, fullname);
3758 jalview.bin.Console.debug("Parsed real filename " + fullname
3759 + " to extract protocol: " + protocols.get(f));
3760 } catch (Exception ex)
3762 jalview.bin.Console.error(
3763 "Couldn't parse " + files.get(f) + " as a link file.",
3772 * Sets the Preferences property for experimental features to True or False
3773 * depending on the state of the controlling menu item
3776 protected void showExperimental_actionPerformed(boolean selected)
3778 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3782 * Answers a (possibly empty) list of any structure viewer frames (currently
3783 * for either Jmol or Chimera) which are currently open. This may optionally
3784 * be restricted to viewers of a specified class, or viewers linked to a
3785 * specified alignment panel.
3788 * if not null, only return viewers linked to this panel
3789 * @param structureViewerClass
3790 * if not null, only return viewers of this class
3793 public List<StructureViewerBase> getStructureViewers(
3794 AlignmentPanel apanel,
3795 Class<? extends StructureViewerBase> structureViewerClass)
3797 List<StructureViewerBase> result = new ArrayList<>();
3798 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3800 for (JInternalFrame frame : frames)
3802 if (frame instanceof StructureViewerBase)
3804 if (structureViewerClass == null
3805 || structureViewerClass.isInstance(frame))
3808 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3810 result.add((StructureViewerBase) frame);
3818 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3820 private static boolean debugScaleMessageDone = false;
3822 public static void debugScaleMessage(Graphics g)
3824 if (debugScaleMessageDone)
3828 // output used by tests to check HiDPI scaling settings in action
3831 Graphics2D gg = (Graphics2D) g;
3834 AffineTransform t = gg.getTransform();
3835 double scaleX = t.getScaleX();
3836 double scaleY = t.getScaleY();
3837 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3838 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3839 debugScaleMessageDone = true;
3843 jalview.bin.Console.debug("Desktop graphics null");
3845 } catch (Exception e)
3847 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3852 * closes the current instance window, but leaves the JVM running. Bypasses
3853 * any shutdown prompts, but does not set window dispose on close in case JVM
3856 public static void closeDesktop()
3858 if (Desktop.instance != null)
3860 Desktop us = Desktop.instance;
3861 Desktop.instance.quitTheDesktop(false, false);
3862 // call dispose in a separate thread - try to avoid indirect deadlocks
3865 new Thread(new Runnable()
3870 ExecutorService dex = us.dialogExecutor;
3874 us.dialogExecutor = null;
3875 us.block.drainPermits();
3885 * checks if any progress bars are being displayed in any of the windows
3886 * managed by the desktop
3890 public boolean operationsAreInProgress()
3892 JInternalFrame[] frames = getAllFrames();
3893 for (JInternalFrame frame : frames)
3895 if (frame instanceof IProgressIndicator)
3897 if (((IProgressIndicator) frame).operationInProgress())
3903 return operationInProgress();
3907 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3908 * The way the modal JInternalFrame is made means it cannot be a child of an
3909 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3911 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3913 protected static void addModal(AlignFrame af, JInternalFrame jif)
3915 alignFrameModalMap.put(af, jif);
3918 protected static void closeModal(AlignFrame af)
3920 if (!alignFrameModalMap.containsKey(af))
3924 JInternalFrame jif = alignFrameModalMap.get(af);
3929 jif.setClosed(true);
3930 } catch (PropertyVetoException e)
3932 e.printStackTrace();
3935 alignFrameModalMap.remove(af);
3938 public void nonBlockingDialog(String title, String message, String button,
3939 int type, boolean scrollable, boolean modal)
3941 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3945 public void nonBlockingDialog(String title, String message,
3946 String boxtext, String button, int type, boolean scrollable,
3947 boolean html, boolean modal, int timeout)
3949 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3950 scrollable, html, modal, timeout);
3953 public void nonBlockingDialog(int width, int height, String title,
3954 String message, String boxtext, String button, int type,
3955 boolean scrollable, boolean html, boolean modal, int timeout)
3959 type = JvOptionPane.WARNING_MESSAGE;
3961 JLabel jl = new JLabel(message);
3963 JTextComponent jtc = null;
3966 JTextPane jtp = new JTextPane();
3967 jtp.setContentType("text/html");
3968 jtp.setEditable(false);
3969 jtp.setAutoscrolls(true);
3970 jtp.setText(boxtext);
3976 JTextArea jta = new JTextArea(height, width);
3977 // jta.setLineWrap(true);
3978 jta.setEditable(false);
3979 jta.setWrapStyleWord(true);
3980 jta.setAutoscrolls(true);
3981 jta.setText(boxtext);
3986 JScrollPane jsp = scrollable
3987 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3988 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3991 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3993 JPanel jp = new JPanel();
3994 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3996 if (message != null)
3998 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
4001 if (boxtext != null)
4005 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
4010 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
4015 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
4017 jvp.setTimeout(timeout);
4018 JButton jb = new JButton(button);
4019 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
4021 { button }, button, modal, new JButton[] { jb }, false);
4025 public AlignFrame getCurrentAlignFrame()
4027 return Jalview.getInstance().getCurrentAlignFrame();