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.JalviewJSUtil;
148 import jalview.util.LaunchUtils;
149 import jalview.util.MessageManager;
150 import jalview.util.Platform;
151 import jalview.util.ShortcutKeyMaskExWrapper;
152 import jalview.util.UrlConstants;
153 import jalview.viewmodel.AlignmentViewport;
154 import jalview.ws.params.ParamManager;
155 import jalview.ws.utils.UrlDownloadClient;
162 * @version $Revision: 1.155 $
164 public class Desktop extends jalview.jbgui.GDesktop
165 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
166 jalview.api.StructureSelectionManagerProvider, JalviewObjectI
168 private static final String CITATION;
171 URL bg_logo_url = ChannelProperties.getImageURL(
172 "bg_logo." + String.valueOf(SplashScreen.logoSize));
173 URL uod_logo_url = ChannelProperties.getImageURL(
174 "uod_banner." + String.valueOf(SplashScreen.logoSize));
175 boolean logo = (bg_logo_url != null || uod_logo_url != null);
176 StringBuilder sb = new StringBuilder();
178 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
183 sb.append(bg_logo_url == null ? ""
184 : "<img alt=\"Barton Group logo\" src=\""
185 + bg_logo_url.toString() + "\">");
186 sb.append(uod_logo_url == null ? ""
187 : " <img alt=\"University of Dundee shield\" src=\""
188 + uod_logo_url.toString() + "\">");
190 "<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>");
191 sb.append("<br><br>If you use Jalview, please cite:"
192 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
193 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
194 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
195 CITATION = sb.toString();
198 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
200 private static int DEFAULT_MIN_WIDTH = 300;
202 private static int DEFAULT_MIN_HEIGHT = 250;
204 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
206 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
208 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
210 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
212 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
214 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
216 public static void setLiveDragMode(boolean b)
218 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
219 : JDesktopPane.OUTLINE_DRAG_MODE;
221 desktop.setDragMode(DRAG_MODE);
224 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
226 public static boolean nosplash = false;
229 * news reader - null if it was never started.
231 private BlogReader jvnews = null;
233 private File projectFile;
237 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
239 public void addJalviewPropertyChangeListener(
240 PropertyChangeListener listener)
242 changeSupport.addJalviewPropertyChangeListener(listener);
246 * @param propertyName
248 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
249 * java.beans.PropertyChangeListener)
251 public void addJalviewPropertyChangeListener(String propertyName,
252 PropertyChangeListener listener)
254 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
258 * @param propertyName
260 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
261 * java.beans.PropertyChangeListener)
263 public void removeJalviewPropertyChangeListener(String propertyName,
264 PropertyChangeListener listener)
266 changeSupport.removeJalviewPropertyChangeListener(propertyName,
270 /** Singleton Desktop instance */
271 public static Desktop instance;
273 public static MyDesktopPane desktop;
275 public static MyDesktopPane getDesktop()
277 // BH 2018 could use currentThread() here as a reference to a
278 // Hashtable<Thread, MyDesktopPane> in JavaScript
282 static int openFrameCount = 0;
284 static final int xOffset = 30;
286 static final int yOffset = 30;
288 public static jalview.ws.jws1.Discoverer discoverer;
290 public static Object[] jalviewClipboard;
292 public static boolean internalCopy = false;
294 static int fileLoadingCount = 0;
296 class MyDesktopManager implements DesktopManager
299 private DesktopManager delegate;
301 public MyDesktopManager(DesktopManager delegate)
303 this.delegate = delegate;
307 public void activateFrame(JInternalFrame f)
311 delegate.activateFrame(f);
312 } catch (NullPointerException npe)
314 Point p = getMousePosition();
315 instance.showPasteMenu(p.x, p.y);
320 public void beginDraggingFrame(JComponent f)
322 delegate.beginDraggingFrame(f);
326 public void beginResizingFrame(JComponent f, int direction)
328 delegate.beginResizingFrame(f, direction);
332 public void closeFrame(JInternalFrame f)
334 delegate.closeFrame(f);
338 public void deactivateFrame(JInternalFrame f)
340 delegate.deactivateFrame(f);
344 public void deiconifyFrame(JInternalFrame f)
346 delegate.deiconifyFrame(f);
350 public void dragFrame(JComponent f, int newX, int newY)
356 delegate.dragFrame(f, newX, newY);
360 public void endDraggingFrame(JComponent f)
362 delegate.endDraggingFrame(f);
367 public void endResizingFrame(JComponent f)
369 delegate.endResizingFrame(f);
374 public void iconifyFrame(JInternalFrame f)
376 delegate.iconifyFrame(f);
380 public void maximizeFrame(JInternalFrame f)
382 delegate.maximizeFrame(f);
386 public void minimizeFrame(JInternalFrame f)
388 delegate.minimizeFrame(f);
392 public void openFrame(JInternalFrame f)
394 delegate.openFrame(f);
398 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
405 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
409 public void setBoundsForFrame(JComponent f, int newX, int newY,
410 int newWidth, int newHeight)
412 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
415 // All other methods, simply delegate
420 * Creates a new Desktop object.
426 * A note to implementors. It is ESSENTIAL that any activities that might
427 * block are spawned off as threads rather than waited for during this
432 doConfigureStructurePrefs();
433 setTitle(ChannelProperties.getProperty("app_name") + " "
434 + Cache.getProperty("VERSION"));
437 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
438 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
439 * officially documented or guaranteed to exist, so we access it via
440 * reflection. There appear to be unfathomable criteria about what this
441 * string can contain, and it if doesn't meet those criteria then "java"
442 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
443 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
444 * not. The reflection access may generate a warning: WARNING: An illegal
445 * reflective access operation has occurred WARNING: Illegal reflective
446 * access by jalview.gui.Desktop () to field
447 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
449 if (Platform.isLinux())
451 if (LaunchUtils.getJavaVersion() >= 11)
454 * Send this message to stderr as the warning that follows (due to reflection)
455 * also goes to stderr.
457 jalview.bin.Console.errPrintln(
458 "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.");
460 final String awtAppClassName = "awtAppClassName";
463 Toolkit xToolkit = Toolkit.getDefaultToolkit();
464 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
465 Field awtAppClassNameField = null;
467 if (Arrays.stream(declaredFields)
468 .anyMatch(f -> f.getName().equals(awtAppClassName)))
470 awtAppClassNameField = xToolkit.getClass()
471 .getDeclaredField(awtAppClassName);
474 String title = ChannelProperties.getProperty("app_name");
475 if (awtAppClassNameField != null)
477 awtAppClassNameField.setAccessible(true);
478 awtAppClassNameField.set(xToolkit, title);
483 .debug("XToolkit: " + awtAppClassName + " not found");
485 } catch (Exception e)
487 jalview.bin.Console.debug("Error setting " + awtAppClassName);
488 jalview.bin.Console.trace(Cache.getStackTraceString(e));
492 setIconImages(ChannelProperties.getIconList());
494 // override quit handling when GUI OS close [X] button pressed
495 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
496 addWindowListener(new WindowAdapter()
499 public void windowClosing(WindowEvent ev)
501 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
505 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
507 boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE,
508 "SHOW_JAVA_CONSOLE", false);
510 // start dialogue queue for single dialogues
513 if (!Platform.isJS())
520 Desktop.instance.acquireDialogQueue();
522 jconsole = new Console(this);
523 jconsole.setHeader(Cache.getVersionDetailsForConsole());
524 showConsole(showjconsole);
526 Desktop.instance.releaseDialogQueue();
529 desktop = new MyDesktopPane(selmemusage);
531 showMemusage.setSelected(selmemusage);
532 desktop.setBackground(Color.white);
534 getContentPane().setLayout(new BorderLayout());
535 // alternate config - have scrollbars - see notes in JAL-153
536 // JScrollPane sp = new JScrollPane();
537 // sp.getViewport().setView(desktop);
538 // getContentPane().add(sp, BorderLayout.CENTER);
540 // BH 2018 - just an experiment to try unclipped JInternalFrames.
543 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
546 getContentPane().add(desktop, BorderLayout.CENTER);
547 desktop.setDragMode(DRAG_MODE);
549 // This line prevents Windows Look&Feel resizing all new windows to maximum
550 // if previous window was maximised
551 desktop.setDesktopManager(new MyDesktopManager(
552 Platform.isJS() ? desktop.getDesktopManager()
553 : new DefaultDesktopManager()));
555 * (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager() :
556 * Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(
557 * desktop.getDesktopManager()) : desktop.getDesktopManager())));
560 Rectangle dims = getLastKnownDimensions("");
567 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
568 int xPos = Math.max(5, (screenSize.width - 900) / 2);
569 int yPos = Math.max(5, (screenSize.height - 650) / 2);
570 setBounds(xPos, yPos, 900, 650);
573 if (!Platform.isJS())
580 showNews.setVisible(false);
582 experimentalFeatures.setSelected(showExperimental());
584 getIdentifiersOrgData();
588 // Spawn a thread that shows the splashscreen
591 SwingUtilities.invokeLater(new Runnable()
596 new SplashScreen(true);
601 // Thread off a new instance of the file chooser - this reduces the time
602 // it takes to open it later on.
603 new Thread(new Runnable()
608 jalview.bin.Console.debug("Filechooser init thread started.");
609 String fileFormat = FileLoader.getUseDefaultFileFormat()
610 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
612 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
614 jalview.bin.Console.debug("Filechooser init thread finished.");
617 // Add the service change listener
618 changeSupport.addJalviewPropertyChangeListener("services",
619 new PropertyChangeListener()
623 public void propertyChange(PropertyChangeEvent evt)
626 .debug("Firing service changed event for "
627 + evt.getNewValue());
628 JalviewServicesChanged(evt);
633 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
636 this.addMouseListener(ma = new MouseAdapter()
639 public void mousePressed(MouseEvent evt)
641 if (evt.isPopupTrigger()) // Mac
643 showPasteMenu(evt.getX(), evt.getY());
648 public void mouseReleased(MouseEvent evt)
650 if (evt.isPopupTrigger()) // Windows
652 showPasteMenu(evt.getX(), evt.getY());
656 desktop.addMouseListener(ma);
660 final String ns = JalviewJSUtil.getJ2sNamespace();
663 final String jalviewjsDesktopElementId = "testApplet_LayeredPaneUI_10_8div";
664 final String nsc = ns + (ns.length() > 0 ? ":" : "");
665 final String nsu = ns + (ns.length() > 0 ? "_" : "");
666 final String splashId = nsc + "jalviewjsSplash";
667 final String splashClassActive = nsu + "jalviewjsSplashActive";
668 final String splashClassInactive = nsu + "jalviewjsSplashInactive";
669 final String splashClassHidden = nsu + "jalviewjsSplashHidden";
670 final String j2s_overflow = JalviewJSUtil
671 .getJ2sInfoValue("overflow");
673 * @j2sNative // splash element disappearance
675 * function sleep(ms) {
677 * return new Promise(resolve => setTimeout(resolve, ms));
681 * var splashElement = document.getElementById(splashId);
683 * if (splashElement !== undefined) {
685 * splashElement.classList.remove(splashClassActive);
687 * splashElement.classList.add(splashClassInactive);
689 * async function hideSplash() {
693 * splashElement.classList.add(splashClassHidden);
701 * // overflow setting
703 * async function changeVisibility() {
705 * var desktopElement = null;
711 * var stayedSetCount = 0;
713 * while ((desktopElement == null || setCount < 5) &&
714 * timeCount < 50 && stayedSetCount < 5) {
718 * if (desktopElement == null) {
721 * document.getElementById(jalviewjsDesktopElementId);
725 * if (desktopElement !== undefined && desktopElement !==
728 * if (desktopElement.style.overflow == "hidden") {
730 * desktopElement.style.overflow = "visible";
734 * stayedSetCount = 0;
750 * if (new String(j2s_overflow).substring(0,4) === "true") {
752 * changeVisibility();
757 // used for jalviewjsTest
758 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
764 * Answers true if user preferences to enable experimental features is True
769 public boolean showExperimental()
771 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
772 Boolean.FALSE.toString());
773 return Boolean.valueOf(experimental).booleanValue();
776 public void doConfigureStructurePrefs()
778 // configure services
779 StructureSelectionManager ssm = StructureSelectionManager
780 .getStructureSelectionManager(this);
781 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
783 ssm.setAddTempFacAnnot(
784 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
785 ssm.setProcessSecondaryStructure(
786 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
787 // JAL-3915 - RNAView is no longer an option so this has no effect
788 ssm.setSecStructServices(
789 Cache.getDefault(Preferences.USE_RNAVIEW, false));
793 ssm.setAddTempFacAnnot(false);
794 ssm.setProcessSecondaryStructure(false);
795 ssm.setSecStructServices(false);
799 public void checkForNews()
801 final Desktop me = this;
802 // Thread off the news reader, in case there are connection problems.
803 new Thread(new Runnable()
808 jalview.bin.Console.debug("Starting news thread.");
809 jvnews = new BlogReader(me);
810 showNews.setVisible(true);
811 jalview.bin.Console.debug("Completed news thread.");
816 public void getIdentifiersOrgData()
818 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
819 {// Thread off the identifiers fetcher
820 new Thread(new Runnable()
826 .debug("Downloading data from identifiers.org");
829 UrlDownloadClient.download(IdOrgSettings.getUrl(),
830 IdOrgSettings.getDownloadLocation());
831 } catch (IOException e)
834 .debug("Exception downloading identifiers.org data"
844 protected void showNews_actionPerformed(ActionEvent e)
846 showNews(showNews.isSelected());
849 void showNews(boolean visible)
851 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
852 showNews.setSelected(visible);
853 if (visible && !jvnews.isVisible())
855 new Thread(new Runnable()
860 long progressId = IdUtils.newId(IdType.PROGRESS);
861 Desktop.instance.setProgressBar(
862 MessageManager.getString("status.refreshing_news"),
864 jvnews.refreshNews();
865 Desktop.instance.setProgressBar(null, progressId);
873 * recover the last known dimensions for a jalview window
876 * - empty string is desktop, all other windows have unique prefix
877 * @return null or last known dimensions scaled to current geometry (if last
878 * window geom was known)
880 Rectangle getLastKnownDimensions(String windowName)
882 // TODO: lock aspect ratio for scaling desktop Bug #0058199
883 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
884 String x = Cache.getProperty(windowName + "SCREEN_X");
885 String y = Cache.getProperty(windowName + "SCREEN_Y");
886 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
887 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
888 if ((x != null) && (y != null) && (width != null) && (height != null))
890 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
891 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
892 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
894 // attempt #1 - try to cope with change in screen geometry - this
895 // version doesn't preserve original jv aspect ratio.
896 // take ratio of current screen size vs original screen size.
897 double sw = ((1f * screenSize.width) / (1f * Integer
898 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
899 double sh = ((1f * screenSize.height) / (1f * Integer
900 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
901 // rescale the bounds depending upon the current screen geometry.
902 ix = (int) (ix * sw);
903 iw = (int) (iw * sw);
904 iy = (int) (iy * sh);
905 ih = (int) (ih * sh);
906 if (ix >= screenSize.width)
908 jalview.bin.Console.debug(
909 "Window geometry location recall error: shifting horizontal to within screenbounds.");
910 ix = ix % screenSize.width;
912 if (iy >= screenSize.height)
914 jalview.bin.Console.debug(
915 "Window geometry location recall error: shifting vertical to within screenbounds.");
916 iy = iy % screenSize.height;
918 jalview.bin.Console.debug(
919 "Got last known dimensions for " + windowName + ": x:" + ix
920 + " y:" + iy + " width:" + iw + " height:" + ih);
922 // return dimensions for new instance
923 return new Rectangle(ix, iy, iw, ih);
928 void showPasteMenu(int x, int y)
930 JPopupMenu popup = new JPopupMenu();
931 JMenuItem item = new JMenuItem(
932 MessageManager.getString("label.paste_new_window"));
933 item.addActionListener(new ActionListener()
936 public void actionPerformed(ActionEvent evt)
943 popup.show(this, x, y);
948 // quick patch for JAL-4150 - needs some more work and test coverage
949 // TODO - unify below and AlignFrame.paste()
950 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
951 // clipboard has come from a different alignment window than the one where
952 // paste has been called! JAL-4151
954 if (Desktop.jalviewClipboard != null)
956 // The clipboard was filled from within Jalview, we must use the
958 // And dataset from the copied alignment
959 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
960 // be doubly sure that we create *new* sequence objects.
961 SequenceI[] sequences = new SequenceI[newseq.length];
962 for (int i = 0; i < newseq.length; i++)
964 sequences[i] = new Sequence(newseq[i]);
966 Alignment alignment = new Alignment(sequences);
967 // dataset is inherited
968 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
969 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
970 AlignFrame.DEFAULT_HEIGHT);
971 String newtitle = new String("Copied sequences");
973 if (Desktop.jalviewClipboard[2] != null)
975 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
976 af.viewport.setHiddenColumns(hc);
979 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
980 AlignFrame.DEFAULT_HEIGHT);
987 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
988 Transferable contents = c.getContents(this);
990 if (contents != null)
992 String file = (String) contents
993 .getTransferData(DataFlavor.stringFlavor);
995 FileFormatI format = new IdentifyFile().identify(file,
996 DataSourceType.PASTE);
998 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
1001 } catch (Exception ex)
1003 jalview.bin.Console.outPrintln(
1004 "Unable to paste alignment from system clipboard:\n" + ex);
1010 * Adds and opens the given frame to the desktop
1021 public static synchronized void addInternalFrame(
1022 final JInternalFrame frame, String title, int w, int h)
1024 addInternalFrame(frame, title, true, w, h, true, false);
1028 * Add an internal frame to the Jalview desktop
1034 * @param makeVisible
1035 * When true, display frame immediately, otherwise, caller must call
1036 * setVisible themselves.
1042 public static synchronized void addInternalFrame(
1043 final JInternalFrame frame, String title, boolean makeVisible,
1046 addInternalFrame(frame, title, makeVisible, w, h, true, false);
1050 * Add an internal frame to the Jalview desktop and make it visible
1063 public static synchronized void addInternalFrame(
1064 final JInternalFrame frame, String title, int w, int h,
1067 addInternalFrame(frame, title, true, w, h, resizable, false);
1071 * Add an internal frame to the Jalview desktop
1077 * @param makeVisible
1078 * When true, display frame immediately, otherwise, caller must call
1079 * setVisible themselves.
1086 * @param ignoreMinSize
1087 * Do not set the default minimum size for frame
1089 public static synchronized void addInternalFrame(
1090 final JInternalFrame frame, String title, boolean makeVisible,
1091 int w, int h, boolean resizable, boolean ignoreMinSize)
1094 // TODO: allow callers to determine X and Y position of frame (eg. via
1096 // TODO: consider fixing method to update entries in the window submenu with
1097 // the current window title
1099 frame.setTitle(title);
1100 if (frame.getWidth() < 1 || frame.getHeight() < 1)
1102 frame.setSize(w, h);
1104 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
1105 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
1106 // IF JALVIEW IS RUNNING HEADLESS
1107 // ///////////////////////////////////////////////
1108 if (instance == null || (System.getProperty("java.awt.headless") != null
1109 && System.getProperty("java.awt.headless").equals("true")))
1118 frame.setMinimumSize(
1119 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1121 // Set default dimension for Alignment Frame window.
1122 // The Alignment Frame window could be added from a number of places,
1124 // I did this here in order not to miss out on any Alignment frame.
1125 if (frame instanceof AlignFrame)
1127 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1128 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1132 frame.setVisible(makeVisible);
1133 frame.setClosable(true);
1134 frame.setResizable(resizable);
1135 frame.setMaximizable(resizable);
1136 frame.setIconifiable(resizable);
1137 frame.setOpaque(Platform.isJS());
1139 if (frame.getX() < 1 && frame.getY() < 1)
1141 frame.setLocation(xOffset * openFrameCount,
1142 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1146 * add an entry for the new frame in the Window menu (and remove it when the
1149 final JMenuItem menuItem = new JMenuItem(title);
1150 frame.addInternalFrameListener(new InternalFrameAdapter()
1153 public void internalFrameActivated(InternalFrameEvent evt)
1155 JInternalFrame itf = desktop.getSelectedFrame();
1158 if (itf instanceof AlignFrame)
1160 Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf);
1167 public void internalFrameClosed(InternalFrameEvent evt)
1169 PaintRefresher.RemoveComponent(frame);
1172 * defensive check to prevent frames being added half off the window
1174 if (openFrameCount > 0)
1180 * ensure no reference to alignFrame retained by menu item listener
1182 if (menuItem.getActionListeners().length > 0)
1184 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1186 windowMenu.remove(menuItem);
1190 menuItem.addActionListener(new ActionListener()
1193 public void actionPerformed(ActionEvent e)
1197 frame.setSelected(true);
1198 frame.setIcon(false);
1199 } catch (java.beans.PropertyVetoException ex)
1206 setKeyBindings(frame);
1208 // Since the latest FlatLaf patch, we occasionally have problems showing
1209 // structureViewer frames...
1211 boolean shown = false;
1212 Exception last = null;
1219 } catch (IllegalArgumentException iaex)
1223 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1224 + tries + " left) for " + frame.getTitle(), iaex);
1228 } catch (InterruptedException iex)
1233 } while (!shown && tries > 0);
1236 jalview.bin.Console.error(
1237 "Serious Problem whilst showing window " + frame.getTitle(),
1241 windowMenu.add(menuItem);
1246 frame.setSelected(true);
1247 frame.requestFocus();
1248 } catch (java.beans.PropertyVetoException ve)
1250 } catch (java.lang.ClassCastException cex)
1252 jalview.bin.Console.warn(
1253 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1259 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1264 private static void setKeyBindings(JInternalFrame frame)
1266 @SuppressWarnings("serial")
1267 final Action closeAction = new AbstractAction()
1270 public void actionPerformed(ActionEvent e)
1277 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1279 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1280 InputEvent.CTRL_DOWN_MASK);
1281 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1282 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1284 InputMap inputMap = frame
1285 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1286 String ctrlW = ctrlWKey.toString();
1287 inputMap.put(ctrlWKey, ctrlW);
1288 inputMap.put(cmdWKey, ctrlW);
1290 ActionMap actionMap = frame.getActionMap();
1291 actionMap.put(ctrlW, closeAction);
1295 public void lostOwnership(Clipboard clipboard, Transferable contents)
1299 Desktop.jalviewClipboard = null;
1302 internalCopy = false;
1306 public void dragEnter(DropTargetDragEvent evt)
1311 public void dragExit(DropTargetEvent evt)
1316 public void dragOver(DropTargetDragEvent evt)
1321 public void dropActionChanged(DropTargetDragEvent evt)
1332 public void drop(DropTargetDropEvent evt)
1334 boolean success = true;
1335 // JAL-1552 - acceptDrop required before getTransferable call for
1336 // Java's Transferable for native dnd
1337 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1338 Transferable t = evt.getTransferable();
1339 List<Object> files = new ArrayList<>();
1340 List<DataSourceType> protocols = new ArrayList<>();
1344 Desktop.transferFromDropTarget(files, protocols, evt, t);
1345 } catch (Exception e)
1347 e.printStackTrace();
1355 for (int i = 0; i < files.size(); i++)
1357 // BH 2018 File or String
1358 Object file = files.get(i);
1359 String fileName = file.toString();
1360 DataSourceType protocol = (protocols == null)
1361 ? DataSourceType.FILE
1363 FileFormatI format = null;
1365 if (fileName.endsWith(".jar"))
1367 format = FileFormat.Jalview;
1372 format = new IdentifyFile().identify(file, protocol);
1374 if (file instanceof File)
1376 Platform.cacheFileData((File) file);
1378 new FileLoader().LoadFile(null, file, protocol, format);
1381 } catch (Exception ex)
1386 evt.dropComplete(success); // need this to ensure input focus is properly
1387 // transfered to any new windows created
1397 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1399 String fileFormat = FileLoader.getUseDefaultFileFormat()
1400 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1402 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1403 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1404 BackupFiles.getEnabled());
1406 chooser.setFileView(new JalviewFileView());
1407 chooser.setDialogTitle(
1408 MessageManager.getString("label.open_local_file"));
1409 chooser.setToolTipText(MessageManager.getString("action.open"));
1411 chooser.setResponseHandler(0, () -> {
1412 File selectedFile = chooser.getSelectedFile();
1413 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1415 FileFormatI format = chooser.getSelectedFormat();
1418 * Call IdentifyFile to verify the file contains what its extension implies.
1419 * Skip this step for dynamically added file formats, because IdentifyFile does
1420 * not know how to recognise them.
1422 if (FileFormats.getInstance().isIdentifiable(format))
1426 format = new IdentifyFile().identify(selectedFile,
1427 DataSourceType.FILE);
1428 } catch (FileFormatException e)
1430 // format = null; //??
1434 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1437 chooser.showOpenDialog(this);
1441 * Shows a dialog for input of a URL at which to retrieve alignment data
1446 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1448 // This construct allows us to have a wider textfield
1450 JLabel label = new JLabel(
1451 MessageManager.getString("label.input_file_url"));
1453 JPanel panel = new JPanel(new GridLayout(2, 1));
1457 * the URL to fetch is input in Java: an editable combobox with history JS:
1458 * (pending JAL-3038) a plain text field
1461 String urlBase = "https://www.";
1462 if (Platform.isJS())
1464 history = new JTextField(urlBase, 35);
1473 JComboBox<String> asCombo = new JComboBox<>();
1474 asCombo.setPreferredSize(new Dimension(400, 20));
1475 asCombo.setEditable(true);
1476 asCombo.addItem(urlBase);
1477 String historyItems = Cache.getProperty("RECENT_URL");
1478 if (historyItems != null)
1480 for (String token : historyItems.split("\\t"))
1482 asCombo.addItem(token);
1489 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1490 MessageManager.getString("action.cancel") };
1491 Runnable action = () -> {
1492 @SuppressWarnings("unchecked")
1493 String url = (history instanceof JTextField
1494 ? ((JTextField) history).getText()
1495 : ((JComboBox<String>) history).getEditor().getItem()
1496 .toString().trim());
1498 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1500 if (viewport != null)
1502 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1503 FileFormat.Jalview);
1507 new FileLoader().LoadFile(url, DataSourceType.URL,
1508 FileFormat.Jalview);
1513 FileFormatI format = null;
1516 format = new IdentifyFile().identify(url, DataSourceType.URL);
1517 } catch (FileFormatException e)
1519 // TODO revise error handling, distinguish between
1520 // URL not found and response not valid
1525 String msg = MessageManager.formatMessage("label.couldnt_locate",
1527 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1528 MessageManager.getString("label.url_not_found"),
1529 JvOptionPane.WARNING_MESSAGE);
1533 if (viewport != null)
1535 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1540 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1544 String dialogOption = MessageManager
1545 .getString("label.input_alignment_from_url");
1546 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1547 .showInternalDialog(panel, dialogOption,
1548 JvOptionPane.YES_NO_CANCEL_OPTION,
1549 JvOptionPane.PLAIN_MESSAGE, null, options,
1550 MessageManager.getString("action.ok"));
1554 * Opens the CutAndPaste window for the user to paste an alignment in to
1557 * - if not null, the pasted alignment is added to the current
1558 * alignment; if null, to a new alignment window
1561 public void inputTextboxMenuItem_actionPerformed(
1562 AlignmentViewPanel viewPanel)
1564 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1565 cap.setForInput(viewPanel);
1566 Desktop.addInternalFrame(cap,
1567 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1572 * Check with user and saving files before actually quitting
1574 public void desktopQuit()
1576 desktopQuit(true, false);
1580 * close everything, stash window geometries, and shut down all associated
1584 * - sets the dispose on close flag - JVM may terminate when set
1585 * @param terminateJvm
1586 * - quit with prejudice - stops the JVM.
1588 public void quitTheDesktop(boolean dispose, boolean terminateJvm)
1590 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1591 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1592 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1593 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1594 getWidth(), getHeight()));
1596 if (jconsole != null)
1598 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1599 jconsole.stopConsole();
1604 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1607 // Frames should all close automatically. Keeping external
1608 // viewers open should already be decided by user.
1609 closeAll_actionPerformed(null);
1611 if (dialogExecutor != null)
1613 dialogExecutor.shutdownNow();
1616 if (groovyConsole != null)
1618 // suppress a possible repeat prompt to save script
1619 groovyConsole.setDirty(false);
1622 if (((Window) groovyConsole.getFrame()) != null
1623 && ((Window) groovyConsole.getFrame()).isVisible())
1625 // console is visible -- FIXME JAL-4327
1626 groovyConsole.exit();
1630 // console is not, so just let it dispose itself when we shutdown
1631 // we don't call groovyConsole.exit() because it calls the shutdown
1632 // handler with invokeAndWait() causing deadlock
1633 groovyConsole = null;
1639 // note that shutdown hook will not be run
1640 jalview.bin.Console.debug("Force Quit selected by user");
1641 Runtime.getRuntime().halt(0);
1644 jalview.bin.Console.debug("Quit selected by user");
1647 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1648 // instance.dispose();
1652 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1654 final Runnable doDesktopQuit = () -> {
1656 // FIRST !! check for aborted quit
1657 if (QuitHandler.quitCancelled())
1660 .debug("Quit was cancelled - Desktop aborting quit");
1664 // Proceed with quitting
1665 quitTheDesktop(disposeFlag,
1666 QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT);
1671 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1672 QuitHandler.defaultCancelQuit);
1676 * Exits the program and the JVM.
1678 * Don't call this directly
1680 * - use desktopQuit() above to tidy up first.
1682 * - use closeDesktop() to shutdown Jalview without shutting down the JVM
1688 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1689 // not run a second time if gotQuitResponse flag has been set (i.e. user
1690 // confirmed quit of some kind).
1691 Jalview.exit("Desktop exiting.", ExitCode.OK);
1694 private void storeLastKnownDimensions(String string, Rectangle jc)
1696 jalview.bin.Console.debug("Storing last known dimensions for " + string
1697 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1698 + " height:" + jc.height);
1700 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1701 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1702 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1703 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1713 public void aboutMenuItem_actionPerformed(ActionEvent e)
1715 new Thread(new Runnable()
1720 new SplashScreen(false);
1726 * Returns the html text for the About screen, including any available version
1727 * number, build details, author details and citation reference, but without
1728 * the enclosing {@code html} tags
1732 public String getAboutMessage()
1734 StringBuilder message = new StringBuilder(1024);
1735 message.append("<div style=\"font-family: sans-serif;\">")
1736 .append("<h1><strong>Version: ")
1737 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1738 .append("<strong>Built: <em>")
1739 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1740 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1741 .append("</strong>");
1743 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1744 if (latestVersion.equals("Checking"))
1746 // JBP removed this message for 2.11: May be reinstated in future version
1747 // message.append("<br>...Checking latest version...</br>");
1749 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1751 boolean red = false;
1752 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1753 .indexOf("automated build") == -1)
1756 // Displayed when code version and jnlp version do not match and code
1757 // version is not a development build
1758 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1761 message.append("<br>!! Version ")
1762 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1763 .append(" is available for download from ")
1764 .append(Cache.getDefault("www.jalview.org",
1765 "https://www.jalview.org"))
1769 message.append("</div>");
1772 message.append("<br>Authors: ");
1773 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1774 message.append(CITATION);
1776 message.append("</div>");
1778 return message.toString();
1782 * Action on requesting Help documentation
1785 public void documentationMenuItem_actionPerformed()
1789 if (Platform.isJS())
1791 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1800 Help.showHelpWindow();
1802 } catch (Exception ex)
1805 .errPrintln("Error opening help: " + ex.getMessage());
1810 public void closeAll_actionPerformed(ActionEvent e)
1812 // TODO show a progress bar while closing?
1813 JInternalFrame[] frames = desktop.getAllFrames();
1814 for (int i = 0; i < frames.length; i++)
1818 frames[i].setClosed(true);
1819 } catch (java.beans.PropertyVetoException ex)
1823 Jalview.getInstance().setCurrentAlignFrame(null);
1824 jalview.bin.Console.info("ALL CLOSED");
1827 * reset state of singleton objects as appropriate (clear down session state
1828 * when all windows are closed)
1830 StructureSelectionManager ssm = StructureSelectionManager
1831 .getStructureSelectionManager(this);
1838 public int structureViewersStillRunningCount()
1841 JInternalFrame[] frames = desktop.getAllFrames();
1842 for (int i = 0; i < frames.length; i++)
1844 if (frames[i] != null
1845 && frames[i] instanceof JalviewStructureDisplayI)
1847 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1855 public void raiseRelated_actionPerformed(ActionEvent e)
1857 reorderAssociatedWindows(false, false);
1861 public void minimizeAssociated_actionPerformed(ActionEvent e)
1863 reorderAssociatedWindows(true, false);
1866 void closeAssociatedWindows()
1868 reorderAssociatedWindows(false, true);
1874 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1878 protected void garbageCollect_actionPerformed(ActionEvent e)
1880 // We simply collect the garbage
1881 jalview.bin.Console.debug("Collecting garbage...");
1883 jalview.bin.Console.debug("Finished garbage collection.");
1889 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1893 protected void showMemusage_actionPerformed(ActionEvent e)
1895 desktop.showMemoryUsage(showMemusage.isSelected());
1902 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1906 protected void showConsole_actionPerformed(ActionEvent e)
1908 showConsole(showConsole.isSelected());
1911 Console jconsole = null;
1914 * control whether the java console is visible or not
1918 void showConsole(boolean selected)
1920 // TODO: decide if we should update properties file
1921 if (jconsole != null) // BH 2018
1923 showConsole.setSelected(selected);
1924 Cache.setProperty("SHOW_JAVA_CONSOLE",
1925 Boolean.valueOf(selected).toString());
1926 jconsole.setVisible(selected);
1930 void reorderAssociatedWindows(boolean minimize, boolean close)
1932 JInternalFrame[] frames = desktop.getAllFrames();
1933 if (frames == null || frames.length < 1)
1938 AlignmentViewport source = null, target = null;
1939 if (frames[0] instanceof AlignFrame)
1941 source = ((AlignFrame) frames[0]).getCurrentView();
1943 else if (frames[0] instanceof TreePanel)
1945 source = ((TreePanel) frames[0]).getViewPort();
1947 else if (frames[0] instanceof PCAPanel)
1949 source = ((PCAPanel) frames[0]).av;
1951 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1953 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1958 for (int i = 0; i < frames.length; i++)
1961 if (frames[i] == null)
1965 if (frames[i] instanceof AlignFrame)
1967 target = ((AlignFrame) frames[i]).getCurrentView();
1969 else if (frames[i] instanceof TreePanel)
1971 target = ((TreePanel) frames[i]).getViewPort();
1973 else if (frames[i] instanceof PCAPanel)
1975 target = ((PCAPanel) frames[i]).av;
1977 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1979 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1982 if (source == target)
1988 frames[i].setClosed(true);
1992 frames[i].setIcon(minimize);
1995 frames[i].toFront();
1999 } catch (java.beans.PropertyVetoException ex)
2014 protected void preferences_actionPerformed(ActionEvent e)
2016 Preferences.openPreferences();
2020 * Prompts the user to choose a file and then saves the Jalview state as a
2021 * Jalview project file
2024 public void saveState_actionPerformed()
2026 saveState_actionPerformed(false);
2029 public void saveState_actionPerformed(boolean saveAs)
2031 java.io.File projectFile = getProjectFile();
2032 // autoSave indicates we already have a file and don't need to ask
2033 boolean autoSave = projectFile != null && !saveAs
2034 && BackupFiles.getEnabled();
2036 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
2037 // projectFile='"+projectFile+"',
2038 // saveAs="+saveAs+", Backups
2039 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
2041 boolean approveSave = false;
2044 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
2047 chooser.setFileView(new JalviewFileView());
2048 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
2050 int value = chooser.showSaveDialog(this);
2052 if (value == JalviewFileChooser.APPROVE_OPTION)
2054 projectFile = chooser.getSelectedFile();
2055 setProjectFile(projectFile);
2060 if (approveSave || autoSave)
2062 final Desktop me = this;
2063 final java.io.File chosenFile = projectFile;
2064 new Thread(new Runnable()
2069 // TODO: refactor to Jalview desktop session controller action.
2070 setProgressBar(MessageManager.formatMessage(
2071 "label.saving_jalview_project", new Object[]
2072 { chosenFile.getName() }),
2073 IdUtils.newId(IdType.PROGRESS, chosenFile));
2074 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
2075 // TODO catch and handle errors for savestate
2076 // TODO prevent user from messing with the Desktop whilst we're saving
2079 boolean doBackup = BackupFiles.getEnabled();
2080 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
2083 new Jalview2XML().saveState(
2084 doBackup ? backupfiles.getTempFile() : chosenFile);
2088 backupfiles.setWriteSuccess(true);
2089 backupfiles.rollBackupsAndRenameTempFile();
2091 } catch (OutOfMemoryError oom)
2093 new OOMWarning("Whilst saving current state to "
2094 + chosenFile.getName(), oom);
2095 } catch (Exception ex)
2097 jalview.bin.Console.error("Problems whilst trying to save to "
2098 + chosenFile.getName(), ex);
2099 JvOptionPane.showMessageDialog(me,
2100 MessageManager.formatMessage(
2101 "label.error_whilst_saving_current_state_to",
2103 { chosenFile.getName() }),
2104 MessageManager.getString("label.couldnt_save_project"),
2105 JvOptionPane.WARNING_MESSAGE);
2107 setProgressBar(null, IdUtils.newId(IdType.PROGRESS, chosenFile));
2114 public void saveAsState_actionPerformed(ActionEvent e)
2116 saveState_actionPerformed(true);
2119 protected void setProjectFile(File choice)
2121 this.projectFile = choice;
2124 public File getProjectFile()
2126 return this.projectFile;
2130 * Shows a file chooser dialog and tries to read in the selected file as a
2134 public void loadState_actionPerformed()
2136 final String[] suffix = new String[] { "jvp", "jar" };
2137 final String[] desc = new String[] { "Jalview Project",
2138 "Jalview Project (old)" };
2139 JalviewFileChooser chooser = new JalviewFileChooser(
2140 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2141 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2145 chooser.setFileView(new JalviewFileView());
2146 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2147 chooser.setResponseHandler(0, () -> {
2148 File selectedFile = chooser.getSelectedFile();
2149 setProjectFile(selectedFile);
2150 String choice = selectedFile.getAbsolutePath();
2151 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2152 new Thread(new Runnable()
2159 new Jalview2XML().loadJalviewAlign(selectedFile);
2160 } catch (OutOfMemoryError oom)
2162 new OOMWarning("Whilst loading project from " + choice, oom);
2163 } catch (Exception ex)
2165 jalview.bin.Console.error(
2166 "Problems whilst loading project from " + choice, ex);
2167 JvOptionPane.showMessageDialog(Desktop.desktop,
2168 MessageManager.formatMessage(
2169 "label.error_whilst_loading_project_from",
2172 MessageManager.getString("label.couldnt_load_project"),
2173 JvOptionPane.WARNING_MESSAGE);
2176 }, "Project Loader").start();
2179 chooser.showOpenDialog(this);
2183 public void inputSequence_actionPerformed(ActionEvent e)
2185 new SequenceFetcher(this);
2188 JPanel progressPanel;
2190 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2192 public void startLoading(final Object fileName)
2194 if (fileLoadingCount == 0)
2196 fileLoadingPanels.add(addProgressPanel(MessageManager
2197 .formatMessage("label.loading_file", new Object[]
2203 private JPanel addProgressPanel(String string)
2205 if (progressPanel == null)
2207 progressPanel = new JPanel(new GridLayout(1, 1));
2208 totalProgressCount = 0;
2209 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2211 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2212 JProgressBar progressBar = new JProgressBar();
2213 progressBar.setIndeterminate(true);
2215 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2217 thisprogress.add(progressBar, BorderLayout.CENTER);
2218 progressPanel.add(thisprogress);
2219 ((GridLayout) progressPanel.getLayout()).setRows(
2220 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2221 ++totalProgressCount;
2222 instance.validate();
2223 return thisprogress;
2226 int totalProgressCount = 0;
2228 private void removeProgressPanel(JPanel progbar)
2230 if (progressPanel != null)
2232 synchronized (progressPanel)
2234 progressPanel.remove(progbar);
2235 GridLayout gl = (GridLayout) progressPanel.getLayout();
2236 gl.setRows(gl.getRows() - 1);
2237 if (--totalProgressCount < 1)
2239 this.getContentPane().remove(progressPanel);
2240 progressPanel = null;
2247 public void stopLoading()
2250 if (fileLoadingCount < 1)
2252 while (fileLoadingPanels.size() > 0)
2254 removeProgressPanel(fileLoadingPanels.remove(0));
2256 fileLoadingPanels.clear();
2257 fileLoadingCount = 0;
2262 public static int getViewCount(String alignmentId)
2264 AlignmentViewport[] aps = getViewports(alignmentId);
2265 return (aps == null) ? 0 : aps.length;
2270 * @param alignmentId
2271 * - if null, all sets are returned
2272 * @return all AlignmentPanels concerning the alignmentId sequence set
2274 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2276 if (Desktop.desktop == null)
2278 // no frames created and in headless mode
2279 // TODO: verify that frames are recoverable when in headless mode
2282 List<AlignmentPanel> aps = new ArrayList<>();
2283 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2288 for (AlignFrame af : frames)
2290 for (AlignmentPanel ap : af.alignPanels)
2292 if (alignmentId == null
2293 || alignmentId.equals(ap.av.getSequenceSetId()))
2299 if (aps.size() == 0)
2303 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2308 * get all the viewports on an alignment.
2310 * @param sequenceSetId
2311 * unique alignment id (may be null - all viewports returned in that
2313 * @return all viewports on the alignment bound to sequenceSetId
2315 public static AlignmentViewport[] getViewports(String sequenceSetId)
2317 List<AlignmentViewport> viewp = new ArrayList<>();
2318 if (desktop != null)
2320 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2322 for (AlignFrame afr : frames)
2324 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2325 .equals(sequenceSetId))
2327 if (afr.alignPanels != null)
2329 for (AlignmentPanel ap : afr.alignPanels)
2331 if (sequenceSetId == null
2332 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2340 viewp.add(afr.getViewport());
2344 if (viewp.size() > 0)
2346 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2353 * Explode the views in the given frame into separate AlignFrame
2357 public static void explodeViews(AlignFrame af)
2359 int size = af.alignPanels.size();
2365 // FIXME: ideally should use UI interface API
2366 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2367 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2368 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2369 for (int i = 0; i < size; i++)
2371 AlignmentPanel ap = af.alignPanels.get(i);
2373 AlignFrame newaf = new AlignFrame(ap);
2375 // transfer reference for existing feature settings to new alignFrame
2376 if (ap == af.alignPanel)
2378 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2380 newaf.featureSettings = viewFeatureSettings;
2382 newaf.setFeatureSettingsGeometry(fsBounds);
2386 * Restore the view's last exploded frame geometry if known. Multiple views from
2387 * one exploded frame share and restore the same (frame) position and size.
2389 Rectangle geometry = ap.av.getExplodedGeometry();
2390 if (geometry != null)
2392 newaf.setBounds(geometry);
2395 ap.av.setGatherViewsHere(false);
2397 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2398 AlignFrame.DEFAULT_HEIGHT);
2399 // and materialise a new feature settings dialog instance for the new
2401 // (closes the old as if 'OK' was pressed)
2402 if (ap == af.alignPanel && newaf.featureSettings != null
2403 && newaf.featureSettings.isOpen()
2404 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2406 newaf.showFeatureSettingsUI();
2410 af.featureSettings = null;
2411 af.alignPanels.clear();
2412 af.closeMenuItem_actionPerformed(true);
2417 * Gather expanded views (separate AlignFrame's) with the same sequence set
2418 * identifier back in to this frame as additional views, and close the
2419 * expanded views. Note the expanded frames may themselves have multiple
2420 * views. We take the lot.
2424 public void gatherViews(AlignFrame source)
2426 source.viewport.setGatherViewsHere(true);
2427 source.viewport.setExplodedGeometry(source.getBounds());
2428 JInternalFrame[] frames = desktop.getAllFrames();
2429 String viewId = source.viewport.getSequenceSetId();
2430 for (int t = 0; t < frames.length; t++)
2432 if (frames[t] instanceof AlignFrame && frames[t] != source)
2434 AlignFrame af = (AlignFrame) frames[t];
2435 boolean gatherThis = false;
2436 for (int a = 0; a < af.alignPanels.size(); a++)
2438 AlignmentPanel ap = af.alignPanels.get(a);
2439 if (viewId.equals(ap.av.getSequenceSetId()))
2442 ap.av.setGatherViewsHere(false);
2443 ap.av.setExplodedGeometry(af.getBounds());
2444 source.addAlignmentPanel(ap, false);
2450 if (af.featureSettings != null && af.featureSettings.isOpen())
2452 if (source.featureSettings == null)
2454 // preserve the feature settings geometry for this frame
2455 source.featureSettings = af.featureSettings;
2456 source.setFeatureSettingsGeometry(
2457 af.getFeatureSettingsGeometry());
2461 // close it and forget
2462 af.featureSettings.close();
2465 af.alignPanels.clear();
2466 af.closeMenuItem_actionPerformed(true);
2471 // refresh the feature setting UI for the source frame if it exists
2472 if (source.featureSettings != null && source.featureSettings.isOpen())
2474 source.showFeatureSettingsUI();
2479 public JInternalFrame[] getAllFrames()
2481 return desktop.getAllFrames();
2485 * Checks the given url to see if it gives a response indicating that the user
2486 * should be informed of a new questionnaire.
2490 public void checkForQuestionnaire(String url)
2492 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2493 // javax.swing.SwingUtilities.invokeLater(jvq);
2494 new Thread(jvq).start();
2497 public void checkURLLinks()
2499 // Thread off the URL link checker
2500 addDialogThread(new Runnable()
2505 if (Cache.getDefault("CHECKURLLINKS", true))
2507 // check what the actual links are - if it's just the default don't
2508 // bother with the warning
2509 List<String> links = Preferences.sequenceUrlLinks
2512 // only need to check links if there is one with a
2513 // SEQUENCE_ID which is not the default EMBL_EBI link
2514 ListIterator<String> li = links.listIterator();
2515 boolean check = false;
2516 List<JLabel> urls = new ArrayList<>();
2517 while (li.hasNext())
2519 String link = li.next();
2520 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2521 && !UrlConstants.isDefaultString(link))
2524 int barPos = link.indexOf("|");
2525 String urlMsg = barPos == -1 ? link
2526 : link.substring(0, barPos) + ": "
2527 + link.substring(barPos + 1);
2528 urls.add(new JLabel(urlMsg));
2536 // ask user to check in case URL links use old style tokens
2537 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2538 JPanel msgPanel = new JPanel();
2539 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2540 msgPanel.add(Box.createVerticalGlue());
2541 JLabel msg = new JLabel(MessageManager
2542 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2543 JLabel msg2 = new JLabel(MessageManager
2544 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2546 for (JLabel url : urls)
2552 final JCheckBox jcb = new JCheckBox(
2553 MessageManager.getString("label.do_not_display_again"));
2554 jcb.addActionListener(new ActionListener()
2557 public void actionPerformed(ActionEvent e)
2559 // update Cache settings for "don't show this again"
2560 boolean showWarningAgain = !jcb.isSelected();
2561 Cache.setProperty("CHECKURLLINKS",
2562 Boolean.valueOf(showWarningAgain).toString());
2567 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2569 .getString("label.SEQUENCE_ID_no_longer_used"),
2570 JvOptionPane.WARNING_MESSAGE);
2577 * Proxy class for JDesktopPane which optionally displays the current memory
2578 * usage and highlights the desktop area with a red bar if free memory runs
2583 public class MyDesktopPane extends JDesktopPane implements Runnable
2585 private static final float ONE_MB = 1048576f;
2587 boolean showMemoryUsage = false;
2591 java.text.NumberFormat df;
2593 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2596 public MyDesktopPane(boolean showMemoryUsage)
2598 showMemoryUsage(showMemoryUsage);
2601 public void showMemoryUsage(boolean showMemory)
2603 this.showMemoryUsage = showMemory;
2606 Thread worker = new Thread(this);
2612 public boolean isShowMemoryUsage()
2614 return showMemoryUsage;
2620 df = java.text.NumberFormat.getNumberInstance();
2621 df.setMaximumFractionDigits(2);
2622 runtime = Runtime.getRuntime();
2624 while (showMemoryUsage)
2628 maxMemory = runtime.maxMemory() / ONE_MB;
2629 allocatedMemory = runtime.totalMemory() / ONE_MB;
2630 freeMemory = runtime.freeMemory() / ONE_MB;
2631 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2633 percentUsage = (totalFreeMemory / maxMemory) * 100;
2635 // if (percentUsage < 20)
2637 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2639 // instance.set.setBorder(border1);
2642 // sleep after showing usage
2644 } catch (Exception ex)
2646 ex.printStackTrace();
2652 public void paintComponent(Graphics g)
2654 if (showMemoryUsage && g != null && df != null)
2656 if (percentUsage < 20)
2658 g.setColor(Color.red);
2660 FontMetrics fm = g.getFontMetrics();
2663 g.drawString(MessageManager.formatMessage("label.memory_stats",
2665 { df.format(totalFreeMemory), df.format(maxMemory),
2666 df.format(percentUsage) }),
2667 10, getHeight() - fm.getHeight());
2671 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2672 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2677 * Accessor method to quickly get all the AlignmentFrames loaded.
2679 * @return an array of AlignFrame, or null if none found
2682 public AlignFrame[] getAlignFrames()
2684 if (desktop == null)
2689 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2695 List<AlignFrame> avp = new ArrayList<>();
2697 for (int i = frames.length - 1; i > -1; i--)
2699 if (frames[i] instanceof AlignFrame)
2701 avp.add((AlignFrame) frames[i]);
2703 else if (frames[i] instanceof SplitFrame)
2706 * Also check for a split frame containing an AlignFrame
2708 GSplitFrame sf = (GSplitFrame) frames[i];
2709 if (sf.getTopFrame() instanceof AlignFrame)
2711 avp.add((AlignFrame) sf.getTopFrame());
2713 if (sf.getBottomFrame() instanceof AlignFrame)
2715 avp.add((AlignFrame) sf.getBottomFrame());
2719 if (avp.size() == 0)
2723 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2730 public static AlignFrame[] getDesktopAlignFrames()
2732 if (Jalview.isHeadlessMode())
2734 // Desktop.desktop is null in headless mode
2735 return Jalview.getInstance().getAlignFrames();
2738 if (instance != null && desktop != null)
2740 return instance.getAlignFrames();
2747 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2751 public GStructureViewer[] getJmols()
2753 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2759 List<GStructureViewer> avp = new ArrayList<>();
2761 for (int i = frames.length - 1; i > -1; i--)
2763 if (frames[i] instanceof AppJmol)
2765 GStructureViewer af = (GStructureViewer) frames[i];
2769 if (avp.size() == 0)
2773 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2778 * Add Groovy Support to Jalview
2781 public void groovyShell_actionPerformed()
2785 openGroovyConsole();
2786 } catch (Exception ex)
2788 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2789 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2791 MessageManager.getString("label.couldnt_create_groovy_shell"),
2792 MessageManager.getString("label.groovy_support_failed"),
2793 JvOptionPane.ERROR_MESSAGE);
2798 * Open the Groovy console
2800 void openGroovyConsole()
2802 if (groovyConsole == null)
2804 JalviewObjectI j = new JalviewObject(this);
2805 groovyConsole = new groovy.console.ui.Console();
2806 groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j);
2807 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2808 getCurrentAlignFrame());
2809 groovyConsole.run();
2812 * We allow only one console at a time, so that AlignFrame menu option
2813 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2814 * enable 'Run script', when the console is opened, and the reverse when it is
2817 Window window = (Window) groovyConsole.getFrame();
2818 window.addWindowListener(new WindowAdapter()
2821 public void windowClosed(WindowEvent e)
2824 * rebind CMD-Q from Groovy Console to Jalview Quit
2827 enableExecuteGroovy(false);
2833 * show Groovy console window (after close and reopen)
2835 ((Window) groovyConsole.getFrame()).setVisible(true);
2838 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2839 * opening a second console
2841 enableExecuteGroovy(true);
2845 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2846 * binding when opened
2848 protected void addQuitHandler()
2851 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2853 .getKeyStroke(KeyEvent.VK_Q,
2854 jalview.util.ShortcutKeyMaskExWrapper
2855 .getMenuShortcutKeyMaskEx()),
2857 getRootPane().getActionMap().put("Quit", new AbstractAction()
2860 public void actionPerformed(ActionEvent e)
2868 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2871 * true if Groovy console is open
2873 public void enableExecuteGroovy(boolean enabled)
2876 * disable opening a second Groovy console (or re-enable when the console is
2879 groovyShell.setEnabled(!enabled);
2881 AlignFrame[] alignFrames = getDesktopAlignFrames();
2882 if (alignFrames != null)
2884 for (AlignFrame af : alignFrames)
2886 af.setGroovyEnabled(enabled);
2892 * Progress bars managed by the IProgressIndicator method.
2894 private Hashtable<Long, JPanel> progressBars;
2896 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2901 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2904 public void setProgressBar(String message, long id)
2906 if (progressBars == null)
2908 progressBars = new Hashtable<>();
2909 progressBarHandlers = new Hashtable<>();
2912 if (progressBars.get(Long.valueOf(id)) != null)
2914 JPanel panel = progressBars.remove(Long.valueOf(id));
2915 if (progressBarHandlers.contains(Long.valueOf(id)))
2917 progressBarHandlers.remove(Long.valueOf(id));
2919 removeProgressPanel(panel);
2923 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2930 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2931 * jalview.gui.IProgressIndicatorHandler)
2934 public void registerHandler(final long id,
2935 final IProgressIndicatorHandler handler)
2937 if (progressBarHandlers == null
2938 || !progressBars.containsKey(Long.valueOf(id)))
2940 throw new Error(MessageManager.getString(
2941 "error.call_setprogressbar_before_registering_handler"));
2943 progressBarHandlers.put(Long.valueOf(id), handler);
2944 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2945 if (handler.canCancel())
2947 JButton cancel = new JButton(
2948 MessageManager.getString("action.cancel"));
2949 final IProgressIndicator us = this;
2950 cancel.addActionListener(new ActionListener()
2954 public void actionPerformed(ActionEvent e)
2956 handler.cancelActivity(id);
2957 us.setProgressBar(MessageManager
2958 .formatMessage("label.cancelled_params", new Object[]
2959 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2963 progressPanel.add(cancel, BorderLayout.EAST);
2969 * @return true if any progress bars are still active
2972 public boolean operationInProgress()
2974 if (progressBars != null && progressBars.size() > 0)
2982 * This will return the first AlignFrame holding the given viewport instance.
2983 * It will break if there are more than one AlignFrames viewing a particular
2987 * @return alignFrame for viewport
2989 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2991 if (desktop != null)
2993 AlignmentPanel[] aps = getAlignmentPanels(
2994 viewport.getSequenceSetId());
2995 for (int panel = 0; aps != null && panel < aps.length; panel++)
2997 if (aps[panel] != null && aps[panel].av == viewport)
2999 return aps[panel].alignFrame;
3006 public VamsasApplication getVamsasApplication()
3008 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
3014 * flag set if jalview GUI is being operated programmatically
3016 private boolean inBatchMode = false;
3019 * check if jalview GUI is being operated programmatically
3021 * @return inBatchMode
3023 public boolean isInBatchMode()
3029 * set flag if jalview GUI is being operated programmatically
3031 * @param inBatchMode
3033 public void setInBatchMode(boolean inBatchMode)
3035 this.inBatchMode = inBatchMode;
3039 * start service discovery and wait till it is done
3041 public void startServiceDiscovery()
3043 startServiceDiscovery(false);
3047 * start service discovery threads - blocking or non-blocking
3051 public void startServiceDiscovery(boolean blocking)
3053 startServiceDiscovery(blocking, false);
3057 * start service discovery threads
3060 * - false means call returns immediately
3061 * @param ignore_SHOW_JWS2_SERVICES_preference
3062 * - when true JABA services are discovered regardless of user's JWS2
3063 * discovery preference setting
3065 public void startServiceDiscovery(boolean blocking,
3066 boolean ignore_SHOW_JWS2_SERVICES_preference)
3068 boolean alive = true;
3069 Thread t0 = null, t1 = null, t2 = null;
3070 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
3073 // todo: changesupport handlers need to be transferred
3074 if (discoverer == null)
3076 discoverer = new jalview.ws.jws1.Discoverer();
3077 // register PCS handler for desktop.
3078 discoverer.addPropertyChangeListener(changeSupport);
3080 // JAL-940 - disabled JWS1 service configuration - always start discoverer
3081 // until we phase out completely
3082 (t0 = new Thread(discoverer)).start();
3085 if (ignore_SHOW_JWS2_SERVICES_preference
3086 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
3088 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3089 .startDiscoverer(changeSupport);
3093 // TODO: do rest service discovery
3102 } catch (Exception e)
3105 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
3106 || (t3 != null && t3.isAlive())
3107 || (t0 != null && t0.isAlive());
3113 * called to check if the service discovery process completed successfully.
3117 protected void JalviewServicesChanged(PropertyChangeEvent evt)
3119 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
3121 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
3122 .getErrorMessages();
3125 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
3127 if (serviceChangedDialog == null)
3129 // only run if we aren't already displaying one of these.
3130 addDialogThread(serviceChangedDialog = new Runnable()
3137 * JalviewDialog jd =new JalviewDialog() {
3139 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3141 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3143 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3145 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3147 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3148 * + " or mis-configured HTTP proxy settings.<br/>" +
3149 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3150 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3151 * true, true, "Web Service Configuration Problem", 450, 400);
3153 * jd.waitForInput();
3155 JvOptionPane.showConfirmDialog(Desktop.desktop,
3156 new JLabel("<html><table width=\"450\"><tr><td>"
3157 + ermsg + "</td></tr></table>"
3158 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3159 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3160 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3161 + " Tools->Preferences dialog box to change them.</p></html>"),
3162 "Web Service Configuration Problem",
3163 JvOptionPane.DEFAULT_OPTION,
3164 JvOptionPane.ERROR_MESSAGE);
3165 serviceChangedDialog = null;
3173 jalview.bin.Console.error(
3174 "Errors reported by JABA discovery service. Check web services preferences.\n"
3181 private Runnable serviceChangedDialog = null;
3184 * start a thread to open a URL in the configured browser. Pops up a warning
3185 * dialog to the user if there is an exception when calling out to the browser
3190 public static void showUrl(final String url)
3192 if (url != null && !url.trim().equals(""))
3194 jalview.bin.Console.info("Opening URL: " + url);
3195 showUrl(url, Desktop.instance);
3199 jalview.bin.Console.warn("Ignoring attempt to show an empty URL.");
3205 * Like showUrl but allows progress handler to be specified
3209 * (null) or object implementing IProgressIndicator
3211 public static void showUrl(final String url,
3212 final IProgressIndicator progress)
3214 new Thread(new Runnable()
3221 if (progress != null)
3223 progress.setProgressBar(MessageManager
3224 .formatMessage("status.opening_params", new Object[]
3225 { url }), IdUtils.newId(IdType.PROGRESS, this));
3227 jalview.util.BrowserLauncher.openURL(url);
3228 } catch (Exception ex)
3230 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3232 .getString("label.web_browser_not_found_unix"),
3233 MessageManager.getString("label.web_browser_not_found"),
3234 JvOptionPane.WARNING_MESSAGE);
3236 ex.printStackTrace();
3238 if (progress != null)
3240 progress.setProgressBar(null,
3241 IdUtils.newId(IdType.PROGRESS, this));
3247 public static WsParamSetManager wsparamManager = null;
3249 public static ParamManager getUserParameterStore()
3251 if (wsparamManager == null)
3253 wsparamManager = new WsParamSetManager();
3255 return wsparamManager;
3259 * static hyperlink handler proxy method for use by Jalview's internal windows
3263 public static void hyperlinkUpdate(HyperlinkEvent e)
3265 if (e.getEventType() == EventType.ACTIVATED)
3270 url = e.getURL().toString();
3271 Desktop.showUrl(url);
3272 } catch (Exception x)
3277 .error("Couldn't handle string " + url + " as a URL.");
3279 // ignore any exceptions due to dud links.
3286 * single thread that handles display of dialogs to user.
3288 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3291 * flag indicating if dialogExecutor should try to acquire a permit
3293 private volatile boolean dialogPause = true;
3298 private Semaphore block = new Semaphore(0);
3300 private static groovy.console.ui.Console groovyConsole;
3303 * add another dialog thread to the queue
3307 public void addDialogThread(final Runnable prompter)
3309 dialogExecutor.submit(new Runnable()
3316 acquireDialogQueue();
3318 if (instance == null)
3324 SwingUtilities.invokeAndWait(prompter);
3325 } catch (Exception q)
3327 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3334 private boolean dialogQueueStarted = false;
3336 public void startDialogQueue()
3338 if (dialogQueueStarted)
3342 // set the flag so we don't pause waiting for another permit and semaphore
3343 // the current task to begin
3344 releaseDialogQueue();
3345 dialogQueueStarted = true;
3348 public void acquireDialogQueue()
3354 } catch (InterruptedException e)
3356 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3361 public void releaseDialogQueue()
3368 dialogPause = false;
3372 * Outputs an image of the desktop to file in EPS format, after prompting the
3373 * user for choice of Text or Lineart character rendering (unless a preference
3374 * has been set). The file name is generated as
3377 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3381 protected void snapShotWindow_actionPerformed(ActionEvent e)
3383 // currently the menu option to do this is not shown
3386 int width = getWidth();
3387 int height = getHeight();
3389 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3390 ImageWriterI writer = new ImageWriterI()
3393 public void exportImage(Graphics g) throws Exception
3396 jalview.bin.Console.info("Successfully written snapshot to file "
3397 + of.getAbsolutePath());
3400 String title = "View of desktop";
3401 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3405 exporter.doExport(of, this, width, height, title);
3406 } catch (ImageOutputException ioex)
3408 jalview.bin.Console.error(
3409 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3415 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3416 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3417 * and location last time the view was expanded (if any). However it does not
3418 * remember the split pane divider location - this is set to match the
3419 * 'exploding' frame.
3423 public void explodeViews(SplitFrame sf)
3425 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3426 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3427 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3429 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3431 int viewCount = topPanels.size();
3438 * Processing in reverse order works, forwards order leaves the first panels not
3439 * visible. I don't know why!
3441 for (int i = viewCount - 1; i >= 0; i--)
3444 * Make new top and bottom frames. These take over the respective AlignmentPanel
3445 * objects, including their AlignmentViewports, so the cdna/protein
3446 * relationships between the viewports is carried over to the new split frames.
3448 * explodedGeometry holds the (x, y) position of the previously exploded
3449 * SplitFrame, and the (width, height) of the AlignFrame component
3451 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3452 AlignFrame newTopFrame = new AlignFrame(topPanel);
3453 newTopFrame.setSize(oldTopFrame.getSize());
3454 newTopFrame.setVisible(true);
3455 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3456 .getExplodedGeometry();
3457 if (geometry != null)
3459 newTopFrame.setSize(geometry.getSize());
3462 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3463 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3464 newBottomFrame.setSize(oldBottomFrame.getSize());
3465 newBottomFrame.setVisible(true);
3466 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3467 .getExplodedGeometry();
3468 if (geometry != null)
3470 newBottomFrame.setSize(geometry.getSize());
3473 topPanel.av.setGatherViewsHere(false);
3474 bottomPanel.av.setGatherViewsHere(false);
3475 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3477 if (geometry != null)
3479 splitFrame.setLocation(geometry.getLocation());
3481 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3485 * Clear references to the panels (now relocated in the new SplitFrames) before
3486 * closing the old SplitFrame.
3489 bottomPanels.clear();
3494 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3495 * back into the given SplitFrame as additional views. Note that the gathered
3496 * frames may themselves have multiple views.
3500 public void gatherViews(GSplitFrame source)
3503 * special handling of explodedGeometry for a view within a SplitFrame: - it
3504 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3505 * height) of the AlignFrame component
3507 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3508 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3509 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3510 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3511 myBottomFrame.viewport
3512 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3513 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3514 myTopFrame.viewport.setGatherViewsHere(true);
3515 myBottomFrame.viewport.setGatherViewsHere(true);
3516 String topViewId = myTopFrame.viewport.getSequenceSetId();
3517 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3519 JInternalFrame[] frames = desktop.getAllFrames();
3520 for (JInternalFrame frame : frames)
3522 if (frame instanceof SplitFrame && frame != source)
3524 SplitFrame sf = (SplitFrame) frame;
3525 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3526 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3527 boolean gatherThis = false;
3528 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3530 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3531 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3532 if (topViewId.equals(topPanel.av.getSequenceSetId())
3533 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3536 topPanel.av.setGatherViewsHere(false);
3537 bottomPanel.av.setGatherViewsHere(false);
3538 topPanel.av.setExplodedGeometry(
3539 new Rectangle(sf.getLocation(), topFrame.getSize()));
3540 bottomPanel.av.setExplodedGeometry(
3541 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3542 myTopFrame.addAlignmentPanel(topPanel, false);
3543 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3549 topFrame.getAlignPanels().clear();
3550 bottomFrame.getAlignPanels().clear();
3557 * The dust settles...give focus to the tab we did this from.
3559 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3562 public static groovy.console.ui.Console getGroovyConsole()
3564 return groovyConsole;
3568 * handles the payload of a drag and drop event.
3570 * TODO refactor to desktop utilities class
3573 * - Data source strings extracted from the drop event
3575 * - protocol for each data source extracted from the drop event
3579 * - the payload from the drop event
3582 public static void transferFromDropTarget(List<Object> files,
3583 List<DataSourceType> protocols, DropTargetDropEvent evt,
3584 Transferable t) throws Exception
3587 DataFlavor uriListFlavor = new DataFlavor(
3588 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3591 urlFlavour = new DataFlavor(
3592 "application/x-java-url; class=java.net.URL");
3593 } catch (ClassNotFoundException cfe)
3595 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3599 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3604 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3605 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3606 // means url may be null.
3609 protocols.add(DataSourceType.URL);
3610 files.add(url.toString());
3611 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3612 + files.get(files.size() - 1));
3617 if (Platform.isAMacAndNotJS())
3619 jalview.bin.Console.errPrintln(
3620 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3623 } catch (Throwable ex)
3625 jalview.bin.Console.debug("URL drop handler failed.", ex);
3628 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3630 // Works on Windows and MacOSX
3631 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3632 for (Object file : (List) t
3633 .getTransferData(DataFlavor.javaFileListFlavor))
3636 protocols.add(DataSourceType.FILE);
3641 // Unix like behaviour
3642 boolean added = false;
3644 if (t.isDataFlavorSupported(uriListFlavor))
3646 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3647 // This is used by Unix drag system
3648 data = (String) t.getTransferData(uriListFlavor);
3652 // fallback to text: workaround - on OSX where there's a JVM bug
3654 .debug("standard URIListFlavor failed. Trying text");
3655 // try text fallback
3656 DataFlavor textDf = new DataFlavor(
3657 "text/plain;class=java.lang.String");
3658 if (t.isDataFlavorSupported(textDf))
3660 data = (String) t.getTransferData(textDf);
3663 jalview.bin.Console.debug("Plain text drop content returned "
3664 + (data == null ? "Null - failed" : data));
3669 while (protocols.size() < files.size())
3671 jalview.bin.Console.debug("Adding missing FILE protocol for "
3672 + files.get(protocols.size()));
3673 protocols.add(DataSourceType.FILE);
3675 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3676 data, "\r\n"); st.hasMoreTokens();)
3679 String s = st.nextToken();
3680 if (s.startsWith("#"))
3682 // the line is a comment (as per the RFC 2483)
3685 java.net.URI uri = new java.net.URI(s);
3686 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3688 protocols.add(DataSourceType.URL);
3689 files.add(uri.toString());
3693 // otherwise preserve old behaviour: catch all for file objects
3694 java.io.File file = new java.io.File(uri);
3695 protocols.add(DataSourceType.FILE);
3696 files.add(file.toString());
3701 if (jalview.bin.Console.isDebugEnabled())
3703 if (data == null || !added)
3706 if (t.getTransferDataFlavors() != null
3707 && t.getTransferDataFlavors().length > 0)
3709 jalview.bin.Console.debug(
3710 "Couldn't resolve drop data. Here are the supported flavors:");
3711 for (DataFlavor fl : t.getTransferDataFlavors())
3713 jalview.bin.Console.debug(
3714 "Supported transfer dataflavor: " + fl.toString());
3715 Object df = t.getTransferData(fl);
3718 jalview.bin.Console.debug("Retrieves: " + df);
3722 jalview.bin.Console.debug("Retrieved nothing");
3729 .debug("Couldn't resolve dataflavor for drop: "
3735 if (Platform.isWindowsAndNotJS())
3738 .debug("Scanning dropped content for Windows Link Files");
3740 // resolve any .lnk files in the file drop
3741 for (int f = 0; f < files.size(); f++)
3743 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3744 if (protocols.get(f).equals(DataSourceType.FILE)
3745 && (source.endsWith(".lnk") || source.endsWith(".url")
3746 || source.endsWith(".site")))
3750 Object obj = files.get(f);
3751 File lf = (obj instanceof File ? (File) obj
3752 : new File((String) obj));
3753 // process link file to get a URL
3754 jalview.bin.Console.debug("Found potential link file: " + lf);
3755 WindowsShortcut wscfile = new WindowsShortcut(lf);
3756 String fullname = wscfile.getRealFilename();
3757 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3758 files.set(f, fullname);
3759 jalview.bin.Console.debug("Parsed real filename " + fullname
3760 + " to extract protocol: " + protocols.get(f));
3761 } catch (Exception ex)
3763 jalview.bin.Console.error(
3764 "Couldn't parse " + files.get(f) + " as a link file.",
3773 * Sets the Preferences property for experimental features to True or False
3774 * depending on the state of the controlling menu item
3777 protected void showExperimental_actionPerformed(boolean selected)
3779 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3783 * Answers a (possibly empty) list of any structure viewer frames (currently
3784 * for either Jmol or Chimera) which are currently open. This may optionally
3785 * be restricted to viewers of a specified class, or viewers linked to a
3786 * specified alignment panel.
3789 * if not null, only return viewers linked to this panel
3790 * @param structureViewerClass
3791 * if not null, only return viewers of this class
3794 public List<StructureViewerBase> getStructureViewers(
3795 AlignmentPanel apanel,
3796 Class<? extends StructureViewerBase> structureViewerClass)
3798 List<StructureViewerBase> result = new ArrayList<>();
3799 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3801 for (JInternalFrame frame : frames)
3803 if (frame instanceof StructureViewerBase)
3805 if (structureViewerClass == null
3806 || structureViewerClass.isInstance(frame))
3809 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3811 result.add((StructureViewerBase) frame);
3819 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3821 private static boolean debugScaleMessageDone = false;
3823 public static void debugScaleMessage(Graphics g)
3825 if (debugScaleMessageDone)
3829 // output used by tests to check HiDPI scaling settings in action
3832 Graphics2D gg = (Graphics2D) g;
3835 AffineTransform t = gg.getTransform();
3836 double scaleX = t.getScaleX();
3837 double scaleY = t.getScaleY();
3838 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3839 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3840 debugScaleMessageDone = true;
3844 jalview.bin.Console.debug("Desktop graphics null");
3846 } catch (Exception e)
3848 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3853 * closes the current instance window, but leaves the JVM running. Bypasses
3854 * any shutdown prompts, but does not set window dispose on close in case JVM
3857 public static void closeDesktop()
3859 if (Desktop.instance != null)
3861 Desktop us = Desktop.instance;
3862 Desktop.instance.quitTheDesktop(false, false);
3863 // call dispose in a separate thread - try to avoid indirect deadlocks
3866 new Thread(new Runnable()
3871 ExecutorService dex = us.dialogExecutor;
3875 us.dialogExecutor = null;
3876 us.block.drainPermits();
3886 * checks if any progress bars are being displayed in any of the windows
3887 * managed by the desktop
3891 public boolean operationsAreInProgress()
3893 JInternalFrame[] frames = getAllFrames();
3894 for (JInternalFrame frame : frames)
3896 if (frame instanceof IProgressIndicator)
3898 if (((IProgressIndicator) frame).operationInProgress())
3904 return operationInProgress();
3908 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3909 * The way the modal JInternalFrame is made means it cannot be a child of an
3910 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3912 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3914 protected static void addModal(AlignFrame af, JInternalFrame jif)
3916 alignFrameModalMap.put(af, jif);
3919 protected static void closeModal(AlignFrame af)
3921 if (!alignFrameModalMap.containsKey(af))
3925 JInternalFrame jif = alignFrameModalMap.get(af);
3930 jif.setClosed(true);
3931 } catch (PropertyVetoException e)
3933 e.printStackTrace();
3936 alignFrameModalMap.remove(af);
3939 public void nonBlockingDialog(String title, String message, String button,
3940 int type, boolean scrollable, boolean modal)
3942 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3946 public void nonBlockingDialog(String title, String message,
3947 String boxtext, String button, int type, boolean scrollable,
3948 boolean html, boolean modal, int timeout)
3950 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3951 scrollable, html, modal, timeout);
3954 public void nonBlockingDialog(int width, int height, String title,
3955 String message, String boxtext, String button, int type,
3956 boolean scrollable, boolean html, boolean modal, int timeout)
3960 type = JvOptionPane.WARNING_MESSAGE;
3962 JLabel jl = new JLabel(message);
3964 JTextComponent jtc = null;
3967 JTextPane jtp = new JTextPane();
3968 jtp.setContentType("text/html");
3969 jtp.setEditable(false);
3970 jtp.setAutoscrolls(true);
3971 jtp.setText(boxtext);
3977 JTextArea jta = new JTextArea(height, width);
3978 // jta.setLineWrap(true);
3979 jta.setEditable(false);
3980 jta.setWrapStyleWord(true);
3981 jta.setAutoscrolls(true);
3982 jta.setText(boxtext);
3987 JScrollPane jsp = scrollable
3988 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3989 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3992 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3994 JPanel jp = new JPanel();
3995 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3997 if (message != null)
3999 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
4002 if (boxtext != null)
4006 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
4011 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
4016 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
4018 jvp.setTimeout(timeout);
4019 JButton jb = new JButton(button);
4020 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
4022 { button }, button, modal, new JButton[] { jb }, false);
4026 public AlignFrame getCurrentAlignFrame()
4028 return Jalview.getInstance().getCurrentAlignFrame();