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.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
54 import java.beans.PropertyVetoException;
56 import java.io.FileWriter;
57 import java.io.IOException;
58 import java.lang.reflect.Field;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashMap;
63 import java.util.Hashtable;
64 import java.util.List;
65 import java.util.ListIterator;
66 import java.util.Locale;
68 import java.util.Vector;
69 import java.util.concurrent.ExecutorService;
70 import java.util.concurrent.Executors;
71 import java.util.concurrent.Semaphore;
73 import javax.swing.AbstractAction;
74 import javax.swing.Action;
75 import javax.swing.ActionMap;
76 import javax.swing.Box;
77 import javax.swing.BoxLayout;
78 import javax.swing.DefaultDesktopManager;
79 import javax.swing.DesktopManager;
80 import javax.swing.InputMap;
81 import javax.swing.JButton;
82 import javax.swing.JCheckBox;
83 import javax.swing.JComboBox;
84 import javax.swing.JComponent;
85 import javax.swing.JDesktopPane;
86 import javax.swing.JFrame;
87 import javax.swing.JInternalFrame;
88 import javax.swing.JLabel;
89 import javax.swing.JMenuItem;
90 import javax.swing.JOptionPane;
91 import javax.swing.JPanel;
92 import javax.swing.JPopupMenu;
93 import javax.swing.JProgressBar;
94 import javax.swing.JScrollPane;
95 import javax.swing.JTextArea;
96 import javax.swing.JTextField;
97 import javax.swing.KeyStroke;
98 import javax.swing.SwingUtilities;
99 import javax.swing.WindowConstants;
100 import javax.swing.event.HyperlinkEvent;
101 import javax.swing.event.HyperlinkEvent.EventType;
102 import javax.swing.event.InternalFrameAdapter;
103 import javax.swing.event.InternalFrameEvent;
105 import org.stackoverflowusers.file.WindowsShortcut;
107 import jalview.api.AlignViewportI;
108 import jalview.api.AlignmentViewPanel;
109 import jalview.api.structures.JalviewStructureDisplayI;
110 import jalview.bin.Cache;
111 import jalview.bin.Jalview;
112 import jalview.bin.Jalview.ExitCode;
113 import jalview.datamodel.Alignment;
114 import jalview.datamodel.HiddenColumns;
115 import jalview.datamodel.Sequence;
116 import jalview.datamodel.SequenceI;
117 import jalview.gui.ImageExporter.ImageWriterI;
118 import jalview.gui.QuitHandler.QResponse;
119 import jalview.io.BackupFiles;
120 import jalview.io.DataSourceType;
121 import jalview.io.FileFormat;
122 import jalview.io.FileFormatException;
123 import jalview.io.FileFormatI;
124 import jalview.io.FileFormats;
125 import jalview.io.FileLoader;
126 import jalview.io.FormatAdapter;
127 import jalview.io.IdentifyFile;
128 import jalview.io.JalviewFileChooser;
129 import jalview.io.JalviewFileView;
130 import jalview.io.exceptions.ImageOutputException;
131 import jalview.jbgui.GSplitFrame;
132 import jalview.jbgui.GStructureViewer;
133 import jalview.project.Jalview2XML;
134 import jalview.structure.StructureSelectionManager;
135 import jalview.urls.IdOrgSettings;
136 import jalview.util.BrowserLauncher;
137 import jalview.util.ChannelProperties;
138 import jalview.util.ImageMaker.TYPE;
139 import jalview.util.LaunchUtils;
140 import jalview.util.MessageManager;
141 import jalview.util.Platform;
142 import jalview.util.ShortcutKeyMaskExWrapper;
143 import jalview.util.UrlConstants;
144 import jalview.viewmodel.AlignmentViewport;
145 import jalview.ws.params.ParamManager;
146 import jalview.ws.utils.UrlDownloadClient;
153 * @version $Revision: 1.155 $
155 public class Desktop extends jalview.jbgui.GDesktop
156 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
157 jalview.api.StructureSelectionManagerProvider
159 private static final String CITATION;
162 URL bg_logo_url = ChannelProperties.getImageURL(
163 "bg_logo." + String.valueOf(SplashScreen.logoSize));
164 URL uod_logo_url = ChannelProperties.getImageURL(
165 "uod_banner." + String.valueOf(SplashScreen.logoSize));
166 boolean logo = (bg_logo_url != null || uod_logo_url != null);
167 StringBuilder sb = new StringBuilder();
169 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
174 sb.append(bg_logo_url == null ? ""
175 : "<img alt=\"Barton Group logo\" src=\""
176 + bg_logo_url.toString() + "\">");
177 sb.append(uod_logo_url == null ? ""
178 : " <img alt=\"University of Dundee shield\" src=\""
179 + uod_logo_url.toString() + "\">");
181 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
182 sb.append("<br><br>If you use Jalview, please cite:"
183 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
184 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
185 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
186 CITATION = sb.toString();
189 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
191 private static int DEFAULT_MIN_WIDTH = 300;
193 private static int DEFAULT_MIN_HEIGHT = 250;
195 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
197 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
199 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
201 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
203 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
205 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
207 public static void setLiveDragMode(boolean b)
209 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
210 : JDesktopPane.OUTLINE_DRAG_MODE;
212 desktop.setDragMode(DRAG_MODE);
215 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
217 public static boolean nosplash = false;
220 * news reader - null if it was never started.
222 private BlogReader jvnews = null;
224 private File projectFile;
228 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
230 public void addJalviewPropertyChangeListener(
231 PropertyChangeListener listener)
233 changeSupport.addJalviewPropertyChangeListener(listener);
237 * @param propertyName
239 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
240 * java.beans.PropertyChangeListener)
242 public void addJalviewPropertyChangeListener(String propertyName,
243 PropertyChangeListener listener)
245 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
249 * @param propertyName
251 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
252 * java.beans.PropertyChangeListener)
254 public void removeJalviewPropertyChangeListener(String propertyName,
255 PropertyChangeListener listener)
257 changeSupport.removeJalviewPropertyChangeListener(propertyName,
261 /** Singleton Desktop instance */
262 public static Desktop instance;
264 public static MyDesktopPane desktop;
266 public static MyDesktopPane getDesktop()
268 // BH 2018 could use currentThread() here as a reference to a
269 // Hashtable<Thread, MyDesktopPane> in JavaScript
273 static int openFrameCount = 0;
275 static final int xOffset = 30;
277 static final int yOffset = 30;
279 public static jalview.ws.jws1.Discoverer discoverer;
281 public static Object[] jalviewClipboard;
283 public static boolean internalCopy = false;
285 static int fileLoadingCount = 0;
287 class MyDesktopManager implements DesktopManager
290 private DesktopManager delegate;
292 public MyDesktopManager(DesktopManager delegate)
294 this.delegate = delegate;
298 public void activateFrame(JInternalFrame f)
302 delegate.activateFrame(f);
303 } catch (NullPointerException npe)
305 Point p = getMousePosition();
306 instance.showPasteMenu(p.x, p.y);
311 public void beginDraggingFrame(JComponent f)
313 delegate.beginDraggingFrame(f);
317 public void beginResizingFrame(JComponent f, int direction)
319 delegate.beginResizingFrame(f, direction);
323 public void closeFrame(JInternalFrame f)
325 delegate.closeFrame(f);
329 public void deactivateFrame(JInternalFrame f)
331 delegate.deactivateFrame(f);
335 public void deiconifyFrame(JInternalFrame f)
337 delegate.deiconifyFrame(f);
341 public void dragFrame(JComponent f, int newX, int newY)
347 delegate.dragFrame(f, newX, newY);
351 public void endDraggingFrame(JComponent f)
353 delegate.endDraggingFrame(f);
358 public void endResizingFrame(JComponent f)
360 delegate.endResizingFrame(f);
365 public void iconifyFrame(JInternalFrame f)
367 delegate.iconifyFrame(f);
371 public void maximizeFrame(JInternalFrame f)
373 delegate.maximizeFrame(f);
377 public void minimizeFrame(JInternalFrame f)
379 delegate.minimizeFrame(f);
383 public void openFrame(JInternalFrame f)
385 delegate.openFrame(f);
389 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
396 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
400 public void setBoundsForFrame(JComponent f, int newX, int newY,
401 int newWidth, int newHeight)
403 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
406 // All other methods, simply delegate
411 * Creates a new Desktop object.
417 * A note to implementors. It is ESSENTIAL that any activities that might
418 * block are spawned off as threads rather than waited for during this
423 doConfigureStructurePrefs();
424 setTitle(ChannelProperties.getProperty("app_name") + " "
425 + Cache.getProperty("VERSION"));
428 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
429 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
430 * officially documented or guaranteed to exist, so we access it via
431 * reflection. There appear to be unfathomable criteria about what this
432 * string can contain, and it if doesn't meet those criteria then "java"
433 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
434 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
435 * not. The reflection access may generate a warning: WARNING: An illegal
436 * reflective access operation has occurred WARNING: Illegal reflective
437 * access by jalview.gui.Desktop () to field
438 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
440 if (Platform.isLinux())
442 if (LaunchUtils.getJavaVersion() >= 11)
445 * Send this message to stderr as the warning that follows (due to
446 * reflection) also goes to stderr.
448 jalview.bin.Console.errPrintln(
449 "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.");
451 final String awtAppClassName = "awtAppClassName";
454 Toolkit xToolkit = Toolkit.getDefaultToolkit();
455 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
456 Field awtAppClassNameField = null;
458 if (Arrays.stream(declaredFields)
459 .anyMatch(f -> f.getName().equals(awtAppClassName)))
461 awtAppClassNameField = xToolkit.getClass()
462 .getDeclaredField(awtAppClassName);
465 String title = ChannelProperties.getProperty("app_name");
466 if (awtAppClassNameField != null)
468 awtAppClassNameField.setAccessible(true);
469 awtAppClassNameField.set(xToolkit, title);
474 .debug("XToolkit: " + awtAppClassName + " not found");
476 } catch (Exception e)
478 jalview.bin.Console.debug("Error setting " + awtAppClassName);
479 jalview.bin.Console.trace(Cache.getStackTraceString(e));
483 setIconImages(ChannelProperties.getIconList());
485 // override quit handling when GUI OS close [X] button pressed
486 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
487 addWindowListener(new WindowAdapter()
490 public void windowClosing(WindowEvent ev)
492 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
496 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
498 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
499 desktop = new MyDesktopPane(selmemusage);
501 showMemusage.setSelected(selmemusage);
502 desktop.setBackground(Color.white);
504 getContentPane().setLayout(new BorderLayout());
505 // alternate config - have scrollbars - see notes in JAL-153
506 // JScrollPane sp = new JScrollPane();
507 // sp.getViewport().setView(desktop);
508 // getContentPane().add(sp, BorderLayout.CENTER);
510 // BH 2018 - just an experiment to try unclipped JInternalFrames.
513 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
516 getContentPane().add(desktop, BorderLayout.CENTER);
517 desktop.setDragMode(DRAG_MODE);
519 // This line prevents Windows Look&Feel resizing all new windows to maximum
520 // if previous window was maximised
521 desktop.setDesktopManager(new MyDesktopManager(
522 Platform.isJS() ? desktop.getDesktopManager()
523 : new DefaultDesktopManager()));
525 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
526 : Platform.isAMacAndNotJS()
527 ? new AquaInternalFrameManager(
528 desktop.getDesktopManager())
529 : desktop.getDesktopManager())));
532 Rectangle dims = getLastKnownDimensions("");
539 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
540 int xPos = Math.max(5, (screenSize.width - 900) / 2);
541 int yPos = Math.max(5, (screenSize.height - 650) / 2);
542 setBounds(xPos, yPos, 900, 650);
545 // start dialogue queue for single dialogues
548 if (!Platform.isJS())
555 jconsole = new Console(this, showjconsole);
556 jconsole.setHeader(Cache.getVersionDetailsForConsole());
557 showConsole(showjconsole);
559 showNews.setVisible(false);
561 experimentalFeatures.setSelected(showExperimental());
563 getIdentifiersOrgData();
567 // Spawn a thread that shows the splashscreen
570 SwingUtilities.invokeLater(new Runnable()
575 new SplashScreen(true);
580 // Thread off a new instance of the file chooser - this reduces the time
581 // it takes to open it later on.
582 new Thread(new Runnable()
587 jalview.bin.Console.debug("Filechooser init thread started.");
588 String fileFormat = FileLoader.getUseDefaultFileFormat()
589 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
591 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
593 jalview.bin.Console.debug("Filechooser init thread finished.");
596 // Add the service change listener
597 changeSupport.addJalviewPropertyChangeListener("services",
598 new PropertyChangeListener()
602 public void propertyChange(PropertyChangeEvent evt)
605 .debug("Firing service changed event for "
606 + evt.getNewValue());
607 JalviewServicesChanged(evt);
612 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
615 this.addMouseListener(ma = new MouseAdapter()
618 public void mousePressed(MouseEvent evt)
620 if (evt.isPopupTrigger()) // Mac
622 showPasteMenu(evt.getX(), evt.getY());
627 public void mouseReleased(MouseEvent evt)
629 if (evt.isPopupTrigger()) // Windows
631 showPasteMenu(evt.getX(), evt.getY());
635 desktop.addMouseListener(ma);
639 // used for jalviewjsTest
640 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
646 * Answers true if user preferences to enable experimental features is True
651 public boolean showExperimental()
653 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
654 Boolean.FALSE.toString());
655 return Boolean.valueOf(experimental).booleanValue();
658 public void doConfigureStructurePrefs()
660 // configure services
661 StructureSelectionManager ssm = StructureSelectionManager
662 .getStructureSelectionManager(this);
663 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
665 ssm.setAddTempFacAnnot(
666 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
667 ssm.setProcessSecondaryStructure(
668 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
669 // JAL-3915 - RNAView is no longer an option so this has no effect
670 ssm.setSecStructServices(
671 Cache.getDefault(Preferences.USE_RNAVIEW, false));
675 ssm.setAddTempFacAnnot(false);
676 ssm.setProcessSecondaryStructure(false);
677 ssm.setSecStructServices(false);
681 public void checkForNews()
683 final Desktop me = this;
684 // Thread off the news reader, in case there are connection problems.
685 new Thread(new Runnable()
690 jalview.bin.Console.debug("Starting news thread.");
691 jvnews = new BlogReader(me);
692 showNews.setVisible(true);
693 jalview.bin.Console.debug("Completed news thread.");
698 public void getIdentifiersOrgData()
700 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
701 {// Thread off the identifiers fetcher
702 new Thread(new Runnable()
708 .debug("Downloading data from identifiers.org");
711 UrlDownloadClient.download(IdOrgSettings.getUrl(),
712 IdOrgSettings.getDownloadLocation());
713 } catch (IOException e)
716 .debug("Exception downloading identifiers.org data"
726 protected void showNews_actionPerformed(ActionEvent e)
728 showNews(showNews.isSelected());
731 void showNews(boolean visible)
733 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
734 showNews.setSelected(visible);
735 if (visible && !jvnews.isVisible())
737 new Thread(new Runnable()
742 long now = System.currentTimeMillis();
743 Desktop.instance.setProgressBar(
744 MessageManager.getString("status.refreshing_news"), now);
745 jvnews.refreshNews();
746 Desktop.instance.setProgressBar(null, now);
754 * recover the last known dimensions for a jalview window
757 * - empty string is desktop, all other windows have unique prefix
758 * @return null or last known dimensions scaled to current geometry (if last
759 * window geom was known)
761 Rectangle getLastKnownDimensions(String windowName)
763 // TODO: lock aspect ratio for scaling desktop Bug #0058199
764 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
765 String x = Cache.getProperty(windowName + "SCREEN_X");
766 String y = Cache.getProperty(windowName + "SCREEN_Y");
767 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
768 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
769 if ((x != null) && (y != null) && (width != null) && (height != null))
771 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
772 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
773 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
775 // attempt #1 - try to cope with change in screen geometry - this
776 // version doesn't preserve original jv aspect ratio.
777 // take ratio of current screen size vs original screen size.
778 double sw = ((1f * screenSize.width) / (1f * Integer
779 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
780 double sh = ((1f * screenSize.height) / (1f * Integer
781 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
782 // rescale the bounds depending upon the current screen geometry.
783 ix = (int) (ix * sw);
784 iw = (int) (iw * sw);
785 iy = (int) (iy * sh);
786 ih = (int) (ih * sh);
787 while (ix >= screenSize.width)
789 jalview.bin.Console.debug(
790 "Window geometry location recall error: shifting horizontal to within screenbounds.");
791 ix -= screenSize.width;
793 while (iy >= screenSize.height)
795 jalview.bin.Console.debug(
796 "Window geometry location recall error: shifting vertical to within screenbounds.");
797 iy -= screenSize.height;
799 jalview.bin.Console.debug(
800 "Got last known dimensions for " + windowName + ": x:" + ix
801 + " y:" + iy + " width:" + iw + " height:" + ih);
803 // return dimensions for new instance
804 return new Rectangle(ix, iy, iw, ih);
809 void showPasteMenu(int x, int y)
811 JPopupMenu popup = new JPopupMenu();
812 JMenuItem item = new JMenuItem(
813 MessageManager.getString("label.paste_new_window"));
814 item.addActionListener(new ActionListener()
817 public void actionPerformed(ActionEvent evt)
824 popup.show(this, x, y);
829 // quick patch for JAL-4150 - needs some more work and test coverage
830 // TODO - unify below and AlignFrame.paste()
831 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
832 // clipboard has come from a different alignment window than the one where
833 // paste has been called! JAL-4151
835 if (Desktop.jalviewClipboard != null)
837 // The clipboard was filled from within Jalview, we must use the
839 // And dataset from the copied alignment
840 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
841 // be doubly sure that we create *new* sequence objects.
842 SequenceI[] sequences = new SequenceI[newseq.length];
843 for (int i = 0; i < newseq.length; i++)
845 sequences[i] = new Sequence(newseq[i]);
847 Alignment alignment = new Alignment(sequences);
848 // dataset is inherited
849 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
850 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
851 AlignFrame.DEFAULT_HEIGHT);
852 String newtitle = new String("Copied sequences");
854 if (Desktop.jalviewClipboard[2] != null)
856 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
857 af.viewport.setHiddenColumns(hc);
860 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
861 AlignFrame.DEFAULT_HEIGHT);
868 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
869 Transferable contents = c.getContents(this);
871 if (contents != null)
873 String file = (String) contents
874 .getTransferData(DataFlavor.stringFlavor);
876 FileFormatI format = new IdentifyFile().identify(file,
877 DataSourceType.PASTE);
879 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
882 } catch (Exception ex)
884 jalview.bin.Console.outPrintln(
885 "Unable to paste alignment from system clipboard:\n" + ex);
891 * Adds and opens the given frame to the desktop
902 public static synchronized void addInternalFrame(
903 final JInternalFrame frame, String title, int w, int h)
905 addInternalFrame(frame, title, true, w, h, true, false);
909 * Add an internal frame to the Jalview desktop
916 * When true, display frame immediately, otherwise, caller must call
917 * setVisible themselves.
923 public static synchronized void addInternalFrame(
924 final JInternalFrame frame, String title, boolean makeVisible,
927 addInternalFrame(frame, title, makeVisible, w, h, true, false);
931 * Add an internal frame to the Jalview desktop and make it visible
944 public static synchronized void addInternalFrame(
945 final JInternalFrame frame, String title, int w, int h,
948 addInternalFrame(frame, title, true, w, h, resizable, false);
952 * Add an internal frame to the Jalview desktop
959 * When true, display frame immediately, otherwise, caller must call
960 * setVisible themselves.
967 * @param ignoreMinSize
968 * Do not set the default minimum size for frame
970 public static synchronized void addInternalFrame(
971 final JInternalFrame frame, String title, boolean makeVisible,
972 int w, int h, boolean resizable, boolean ignoreMinSize)
975 // TODO: allow callers to determine X and Y position of frame (eg. via
977 // TODO: consider fixing method to update entries in the window submenu with
978 // the current window title
980 frame.setTitle(title);
981 if (frame.getWidth() < 1 || frame.getHeight() < 1)
985 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
986 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
987 // IF JALVIEW IS RUNNING HEADLESS
988 // ///////////////////////////////////////////////
989 if (instance == null || (System.getProperty("java.awt.headless") != null
990 && System.getProperty("java.awt.headless").equals("true")))
999 frame.setMinimumSize(
1000 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1002 // Set default dimension for Alignment Frame window.
1003 // The Alignment Frame window could be added from a number of places,
1005 // I did this here in order not to miss out on any Alignment frame.
1006 if (frame instanceof AlignFrame)
1008 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1009 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1013 frame.setVisible(makeVisible);
1014 frame.setClosable(true);
1015 frame.setResizable(resizable);
1016 frame.setMaximizable(resizable);
1017 frame.setIconifiable(resizable);
1018 frame.setOpaque(Platform.isJS());
1020 if (frame.getX() < 1 && frame.getY() < 1)
1022 frame.setLocation(xOffset * openFrameCount,
1023 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1027 * add an entry for the new frame in the Window menu (and remove it when the
1030 final JMenuItem menuItem = new JMenuItem(title);
1031 frame.addInternalFrameListener(new InternalFrameAdapter()
1034 public void internalFrameActivated(InternalFrameEvent evt)
1036 JInternalFrame itf = desktop.getSelectedFrame();
1039 if (itf instanceof AlignFrame)
1041 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1048 public void internalFrameClosed(InternalFrameEvent evt)
1050 PaintRefresher.RemoveComponent(frame);
1053 * defensive check to prevent frames being added half off the window
1055 if (openFrameCount > 0)
1061 * ensure no reference to alignFrame retained by menu item listener
1063 if (menuItem.getActionListeners().length > 0)
1065 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1067 windowMenu.remove(menuItem);
1071 menuItem.addActionListener(new ActionListener()
1074 public void actionPerformed(ActionEvent e)
1078 frame.setSelected(true);
1079 frame.setIcon(false);
1080 } catch (java.beans.PropertyVetoException ex)
1087 setKeyBindings(frame);
1089 // Since the latest FlatLaf patch, we occasionally have problems showing
1090 // structureViewer frames...
1092 boolean shown = false;
1093 Exception last = null;
1100 } catch (IllegalArgumentException iaex)
1104 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1105 + tries + " left) for " + frame.getTitle(), iaex);
1109 } catch (InterruptedException iex)
1114 } while (!shown && tries > 0);
1117 jalview.bin.Console.error(
1118 "Serious Problem whilst showing window " + frame.getTitle(),
1122 windowMenu.add(menuItem);
1127 frame.setSelected(true);
1128 frame.requestFocus();
1129 } catch (java.beans.PropertyVetoException ve)
1131 } catch (java.lang.ClassCastException cex)
1133 jalview.bin.Console.warn(
1134 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1140 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1145 private static void setKeyBindings(JInternalFrame frame)
1147 @SuppressWarnings("serial")
1148 final Action closeAction = new AbstractAction()
1151 public void actionPerformed(ActionEvent e)
1158 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1160 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1161 InputEvent.CTRL_DOWN_MASK);
1162 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1163 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1165 InputMap inputMap = frame
1166 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1167 String ctrlW = ctrlWKey.toString();
1168 inputMap.put(ctrlWKey, ctrlW);
1169 inputMap.put(cmdWKey, ctrlW);
1171 ActionMap actionMap = frame.getActionMap();
1172 actionMap.put(ctrlW, closeAction);
1176 public void lostOwnership(Clipboard clipboard, Transferable contents)
1180 Desktop.jalviewClipboard = null;
1183 internalCopy = false;
1187 public void dragEnter(DropTargetDragEvent evt)
1192 public void dragExit(DropTargetEvent evt)
1197 public void dragOver(DropTargetDragEvent evt)
1202 public void dropActionChanged(DropTargetDragEvent evt)
1213 public void drop(DropTargetDropEvent evt)
1215 boolean success = true;
1216 // JAL-1552 - acceptDrop required before getTransferable call for
1217 // Java's Transferable for native dnd
1218 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1219 Transferable t = evt.getTransferable();
1220 List<Object> files = new ArrayList<>();
1221 List<DataSourceType> protocols = new ArrayList<>();
1225 Desktop.transferFromDropTarget(files, protocols, evt, t);
1226 } catch (Exception e)
1228 e.printStackTrace();
1236 for (int i = 0; i < files.size(); i++)
1238 // BH 2018 File or String
1239 Object file = files.get(i);
1240 String fileName = file.toString();
1241 DataSourceType protocol = (protocols == null)
1242 ? DataSourceType.FILE
1244 FileFormatI format = null;
1246 if (fileName.endsWith(".jar"))
1248 format = FileFormat.Jalview;
1253 format = new IdentifyFile().identify(file, protocol);
1255 if (file instanceof File)
1257 Platform.cacheFileData((File) file);
1259 new FileLoader().LoadFile(null, file, protocol, format);
1262 } catch (Exception ex)
1267 evt.dropComplete(success); // need this to ensure input focus is properly
1268 // transfered to any new windows created
1278 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1280 String fileFormat = FileLoader.getUseDefaultFileFormat()
1281 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1283 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1284 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1285 BackupFiles.getEnabled());
1287 chooser.setFileView(new JalviewFileView());
1288 chooser.setDialogTitle(
1289 MessageManager.getString("label.open_local_file"));
1290 chooser.setToolTipText(MessageManager.getString("action.open"));
1292 chooser.setResponseHandler(0, () -> {
1293 File selectedFile = chooser.getSelectedFile();
1294 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1296 FileFormatI format = chooser.getSelectedFormat();
1299 * Call IdentifyFile to verify the file contains what its extension implies.
1300 * Skip this step for dynamically added file formats, because IdentifyFile does
1301 * not know how to recognise them.
1303 if (FileFormats.getInstance().isIdentifiable(format))
1307 format = new IdentifyFile().identify(selectedFile,
1308 DataSourceType.FILE);
1309 } catch (FileFormatException e)
1311 // format = null; //??
1315 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1318 chooser.showOpenDialog(this);
1322 * Shows a dialog for input of a URL at which to retrieve alignment data
1327 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1329 // This construct allows us to have a wider textfield
1331 JLabel label = new JLabel(
1332 MessageManager.getString("label.input_file_url"));
1334 JPanel panel = new JPanel(new GridLayout(2, 1));
1338 * the URL to fetch is input in Java: an editable combobox with history JS:
1339 * (pending JAL-3038) a plain text field
1342 String urlBase = "https://www.";
1343 if (Platform.isJS())
1345 history = new JTextField(urlBase, 35);
1354 JComboBox<String> asCombo = new JComboBox<>();
1355 asCombo.setPreferredSize(new Dimension(400, 20));
1356 asCombo.setEditable(true);
1357 asCombo.addItem(urlBase);
1358 String historyItems = Cache.getProperty("RECENT_URL");
1359 if (historyItems != null)
1361 for (String token : historyItems.split("\\t"))
1363 asCombo.addItem(token);
1370 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1371 MessageManager.getString("action.cancel") };
1372 Runnable action = () -> {
1373 @SuppressWarnings("unchecked")
1374 String url = (history instanceof JTextField
1375 ? ((JTextField) history).getText()
1376 : ((JComboBox<String>) history).getEditor().getItem()
1377 .toString().trim());
1379 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1381 if (viewport != null)
1383 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1384 FileFormat.Jalview);
1388 new FileLoader().LoadFile(url, DataSourceType.URL,
1389 FileFormat.Jalview);
1394 FileFormatI format = null;
1397 format = new IdentifyFile().identify(url, DataSourceType.URL);
1398 } catch (FileFormatException e)
1400 // TODO revise error handling, distinguish between
1401 // URL not found and response not valid
1406 String msg = MessageManager.formatMessage("label.couldnt_locate",
1408 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1409 MessageManager.getString("label.url_not_found"),
1410 JvOptionPane.WARNING_MESSAGE);
1414 if (viewport != null)
1416 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1421 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1425 String dialogOption = MessageManager
1426 .getString("label.input_alignment_from_url");
1427 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1428 .showInternalDialog(panel, dialogOption,
1429 JvOptionPane.YES_NO_CANCEL_OPTION,
1430 JvOptionPane.PLAIN_MESSAGE, null, options,
1431 MessageManager.getString("action.ok"));
1435 * Opens the CutAndPaste window for the user to paste an alignment in to
1438 * - if not null, the pasted alignment is added to the current
1439 * alignment; if null, to a new alignment window
1442 public void inputTextboxMenuItem_actionPerformed(
1443 AlignmentViewPanel viewPanel)
1445 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1446 cap.setForInput(viewPanel);
1447 Desktop.addInternalFrame(cap,
1448 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1453 * Check with user and saving files before actually quitting
1455 public void desktopQuit()
1457 desktopQuit(true, false);
1460 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1462 final Runnable doDesktopQuit = () -> {
1463 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1464 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1465 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1466 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1467 getBounds().y, getWidth(), getHeight()));
1469 if (jconsole != null)
1471 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1472 jconsole.stopConsole();
1477 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1480 // Frames should all close automatically. Keeping external
1481 // viewers open should already be decided by user.
1482 closeAll_actionPerformed(null);
1484 // check for aborted quit
1485 if (QuitHandler.quitCancelled())
1487 jalview.bin.Console.debug("Desktop aborting quit");
1491 if (dialogExecutor != null)
1493 dialogExecutor.shutdownNow();
1496 if (groovyConsole != null)
1498 // suppress a possible repeat prompt to save script
1499 groovyConsole.setDirty(false);
1500 groovyConsole.exit();
1503 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1505 // note that shutdown hook will not be run
1506 jalview.bin.Console.debug("Force Quit selected by user");
1507 Runtime.getRuntime().halt(0);
1510 jalview.bin.Console.debug("Quit selected by user");
1513 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1514 // instance.dispose();
1519 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1520 QuitHandler.defaultCancelQuit);
1524 * Don't call this directly, use desktopQuit() above. Exits the program.
1529 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1530 // not run a second time if gotQuitResponse flag has been set (i.e. user
1531 // confirmed quit of some kind).
1532 Jalview.exit("Desktop exiting.", ExitCode.OK);
1535 private void storeLastKnownDimensions(String string, Rectangle jc)
1537 jalview.bin.Console.debug("Storing last known dimensions for " + string
1538 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1539 + " height:" + jc.height);
1541 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1542 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1543 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1544 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1554 public void aboutMenuItem_actionPerformed(ActionEvent e)
1556 new Thread(new Runnable()
1561 new SplashScreen(false);
1567 * Returns the html text for the About screen, including any available version
1568 * number, build details, author details and citation reference, but without
1569 * the enclosing {@code html} tags
1573 public String getAboutMessage()
1575 StringBuilder message = new StringBuilder(1024);
1576 message.append("<div style=\"font-family: sans-serif;\">")
1577 .append("<h1><strong>Version: ")
1578 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1579 .append("<strong>Built: <em>")
1580 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1581 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1582 .append("</strong>");
1584 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1585 if (latestVersion.equals("Checking"))
1587 // JBP removed this message for 2.11: May be reinstated in future version
1588 // message.append("<br>...Checking latest version...</br>");
1590 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1592 boolean red = false;
1593 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1594 .indexOf("automated build") == -1)
1597 // Displayed when code version and jnlp version do not match and code
1598 // version is not a development build
1599 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1602 message.append("<br>!! Version ")
1603 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1604 .append(" is available for download from ")
1605 .append(Cache.getDefault("www.jalview.org",
1606 "https://www.jalview.org"))
1610 message.append("</div>");
1613 message.append("<br>Authors: ");
1614 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1615 message.append(CITATION);
1617 message.append("</div>");
1619 return message.toString();
1623 * Action on requesting Help documentation
1626 public void documentationMenuItem_actionPerformed()
1630 if (Platform.isJS())
1632 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1641 Help.showHelpWindow();
1643 } catch (Exception ex)
1646 .errPrintln("Error opening help: " + ex.getMessage());
1651 public void closeAll_actionPerformed(ActionEvent e)
1653 // TODO show a progress bar while closing?
1654 JInternalFrame[] frames = desktop.getAllFrames();
1655 for (int i = 0; i < frames.length; i++)
1659 frames[i].setClosed(true);
1660 } catch (java.beans.PropertyVetoException ex)
1664 Jalview.setCurrentAlignFrame(null);
1665 jalview.bin.Console.info("ALL CLOSED");
1668 * reset state of singleton objects as appropriate (clear down session state
1669 * when all windows are closed)
1671 StructureSelectionManager ssm = StructureSelectionManager
1672 .getStructureSelectionManager(this);
1679 public int structureViewersStillRunningCount()
1682 JInternalFrame[] frames = desktop.getAllFrames();
1683 for (int i = 0; i < frames.length; i++)
1685 if (frames[i] != null
1686 && frames[i] instanceof JalviewStructureDisplayI)
1688 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1696 public void raiseRelated_actionPerformed(ActionEvent e)
1698 reorderAssociatedWindows(false, false);
1702 public void minimizeAssociated_actionPerformed(ActionEvent e)
1704 reorderAssociatedWindows(true, false);
1707 void closeAssociatedWindows()
1709 reorderAssociatedWindows(false, true);
1715 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1719 protected void garbageCollect_actionPerformed(ActionEvent e)
1721 // We simply collect the garbage
1722 jalview.bin.Console.debug("Collecting garbage...");
1724 jalview.bin.Console.debug("Finished garbage collection.");
1730 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1734 protected void showMemusage_actionPerformed(ActionEvent e)
1736 desktop.showMemoryUsage(showMemusage.isSelected());
1743 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1747 protected void showConsole_actionPerformed(ActionEvent e)
1749 showConsole(showConsole.isSelected());
1752 Console jconsole = null;
1755 * control whether the java console is visible or not
1759 void showConsole(boolean selected)
1761 // TODO: decide if we should update properties file
1762 if (jconsole != null) // BH 2018
1764 showConsole.setSelected(selected);
1765 Cache.setProperty("SHOW_JAVA_CONSOLE",
1766 Boolean.valueOf(selected).toString());
1767 jconsole.setVisible(selected);
1771 void reorderAssociatedWindows(boolean minimize, boolean close)
1773 JInternalFrame[] frames = desktop.getAllFrames();
1774 if (frames == null || frames.length < 1)
1779 AlignmentViewport source = null, target = null;
1780 if (frames[0] instanceof AlignFrame)
1782 source = ((AlignFrame) frames[0]).getCurrentView();
1784 else if (frames[0] instanceof TreePanel)
1786 source = ((TreePanel) frames[0]).getViewPort();
1788 else if (frames[0] instanceof PCAPanel)
1790 source = ((PCAPanel) frames[0]).av;
1792 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1794 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1799 for (int i = 0; i < frames.length; i++)
1802 if (frames[i] == null)
1806 if (frames[i] instanceof AlignFrame)
1808 target = ((AlignFrame) frames[i]).getCurrentView();
1810 else if (frames[i] instanceof TreePanel)
1812 target = ((TreePanel) frames[i]).getViewPort();
1814 else if (frames[i] instanceof PCAPanel)
1816 target = ((PCAPanel) frames[i]).av;
1818 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1820 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1823 if (source == target)
1829 frames[i].setClosed(true);
1833 frames[i].setIcon(minimize);
1836 frames[i].toFront();
1840 } catch (java.beans.PropertyVetoException ex)
1855 protected void preferences_actionPerformed(ActionEvent e)
1857 Preferences.openPreferences();
1861 * Prompts the user to choose a file and then saves the Jalview state as a
1862 * Jalview project file
1865 public void saveState_actionPerformed()
1867 saveState_actionPerformed(false);
1870 public void saveState_actionPerformed(boolean saveAs)
1872 java.io.File projectFile = getProjectFile();
1873 // autoSave indicates we already have a file and don't need to ask
1874 boolean autoSave = projectFile != null && !saveAs
1875 && BackupFiles.getEnabled();
1877 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1878 // projectFile='"+projectFile+"',
1879 // saveAs="+saveAs+", Backups
1880 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1882 boolean approveSave = false;
1885 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1888 chooser.setFileView(new JalviewFileView());
1889 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1891 int value = chooser.showSaveDialog(this);
1893 if (value == JalviewFileChooser.APPROVE_OPTION)
1895 projectFile = chooser.getSelectedFile();
1896 setProjectFile(projectFile);
1901 if (approveSave || autoSave)
1903 final Desktop me = this;
1904 final java.io.File chosenFile = projectFile;
1905 new Thread(new Runnable()
1910 // TODO: refactor to Jalview desktop session controller action.
1911 setProgressBar(MessageManager.formatMessage(
1912 "label.saving_jalview_project", new Object[]
1913 { chosenFile.getName() }), chosenFile.hashCode());
1914 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1915 // TODO catch and handle errors for savestate
1916 // TODO prevent user from messing with the Desktop whilst we're saving
1919 boolean doBackup = BackupFiles.getEnabled();
1920 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1923 new Jalview2XML().saveState(
1924 doBackup ? backupfiles.getTempFile() : chosenFile);
1928 backupfiles.setWriteSuccess(true);
1929 backupfiles.rollBackupsAndRenameTempFile();
1931 } catch (OutOfMemoryError oom)
1933 new OOMWarning("Whilst saving current state to "
1934 + chosenFile.getName(), oom);
1935 } catch (Exception ex)
1937 jalview.bin.Console.error("Problems whilst trying to save to "
1938 + chosenFile.getName(), ex);
1939 JvOptionPane.showMessageDialog(me,
1940 MessageManager.formatMessage(
1941 "label.error_whilst_saving_current_state_to",
1943 { chosenFile.getName() }),
1944 MessageManager.getString("label.couldnt_save_project"),
1945 JvOptionPane.WARNING_MESSAGE);
1947 setProgressBar(null, chosenFile.hashCode());
1954 public void saveAsState_actionPerformed(ActionEvent e)
1956 saveState_actionPerformed(true);
1959 protected void setProjectFile(File choice)
1961 this.projectFile = choice;
1964 public File getProjectFile()
1966 return this.projectFile;
1970 * Shows a file chooser dialog and tries to read in the selected file as a
1974 public void loadState_actionPerformed()
1976 final String[] suffix = new String[] { "jvp", "jar" };
1977 final String[] desc = new String[] { "Jalview Project",
1978 "Jalview Project (old)" };
1979 JalviewFileChooser chooser = new JalviewFileChooser(
1980 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1981 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1985 chooser.setFileView(new JalviewFileView());
1986 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1987 chooser.setResponseHandler(0, () -> {
1988 File selectedFile = chooser.getSelectedFile();
1989 setProjectFile(selectedFile);
1990 String choice = selectedFile.getAbsolutePath();
1991 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1992 new Thread(new Runnable()
1999 new Jalview2XML().loadJalviewAlign(selectedFile);
2000 } catch (OutOfMemoryError oom)
2002 new OOMWarning("Whilst loading project from " + choice, oom);
2003 } catch (Exception ex)
2005 jalview.bin.Console.error(
2006 "Problems whilst loading project from " + choice, ex);
2007 JvOptionPane.showMessageDialog(Desktop.desktop,
2008 MessageManager.formatMessage(
2009 "label.error_whilst_loading_project_from",
2012 MessageManager.getString("label.couldnt_load_project"),
2013 JvOptionPane.WARNING_MESSAGE);
2016 }, "Project Loader").start();
2019 chooser.showOpenDialog(this);
2023 public void inputSequence_actionPerformed(ActionEvent e)
2025 new SequenceFetcher(this);
2028 JPanel progressPanel;
2030 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2032 public void startLoading(final Object fileName)
2034 if (fileLoadingCount == 0)
2036 fileLoadingPanels.add(addProgressPanel(MessageManager
2037 .formatMessage("label.loading_file", new Object[]
2043 private JPanel addProgressPanel(String string)
2045 if (progressPanel == null)
2047 progressPanel = new JPanel(new GridLayout(1, 1));
2048 totalProgressCount = 0;
2049 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2051 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2052 JProgressBar progressBar = new JProgressBar();
2053 progressBar.setIndeterminate(true);
2055 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2057 thisprogress.add(progressBar, BorderLayout.CENTER);
2058 progressPanel.add(thisprogress);
2059 ((GridLayout) progressPanel.getLayout()).setRows(
2060 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2061 ++totalProgressCount;
2062 instance.validate();
2063 return thisprogress;
2066 int totalProgressCount = 0;
2068 private void removeProgressPanel(JPanel progbar)
2070 if (progressPanel != null)
2072 synchronized (progressPanel)
2074 progressPanel.remove(progbar);
2075 GridLayout gl = (GridLayout) progressPanel.getLayout();
2076 gl.setRows(gl.getRows() - 1);
2077 if (--totalProgressCount < 1)
2079 this.getContentPane().remove(progressPanel);
2080 progressPanel = null;
2087 public void stopLoading()
2090 if (fileLoadingCount < 1)
2092 while (fileLoadingPanels.size() > 0)
2094 removeProgressPanel(fileLoadingPanels.remove(0));
2096 fileLoadingPanels.clear();
2097 fileLoadingCount = 0;
2102 public static int getViewCount(String alignmentId)
2104 AlignmentViewport[] aps = getViewports(alignmentId);
2105 return (aps == null) ? 0 : aps.length;
2110 * @param alignmentId
2111 * - if null, all sets are returned
2112 * @return all AlignmentPanels concerning the alignmentId sequence set
2114 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2116 if (Desktop.desktop == null)
2118 // no frames created and in headless mode
2119 // TODO: verify that frames are recoverable when in headless mode
2122 List<AlignmentPanel> aps = new ArrayList<>();
2123 AlignFrame[] frames = getAlignFrames();
2128 for (AlignFrame af : frames)
2130 for (AlignmentPanel ap : af.alignPanels)
2132 if (alignmentId == null
2133 || alignmentId.equals(ap.av.getSequenceSetId()))
2139 if (aps.size() == 0)
2143 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2148 * get all the viewports on an alignment.
2150 * @param sequenceSetId
2151 * unique alignment id (may be null - all viewports returned in that
2153 * @return all viewports on the alignment bound to sequenceSetId
2155 public static AlignmentViewport[] getViewports(String sequenceSetId)
2157 List<AlignmentViewport> viewp = new ArrayList<>();
2158 if (desktop != null)
2160 AlignFrame[] frames = Desktop.getAlignFrames();
2162 for (AlignFrame afr : frames)
2164 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2165 .equals(sequenceSetId))
2167 if (afr.alignPanels != null)
2169 for (AlignmentPanel ap : afr.alignPanels)
2171 if (sequenceSetId == null
2172 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2180 viewp.add(afr.getViewport());
2184 if (viewp.size() > 0)
2186 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2193 * Explode the views in the given frame into separate AlignFrame
2197 public static void explodeViews(AlignFrame af)
2199 int size = af.alignPanels.size();
2205 // FIXME: ideally should use UI interface API
2206 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2207 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2208 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2209 for (int i = 0; i < size; i++)
2211 AlignmentPanel ap = af.alignPanels.get(i);
2213 AlignFrame newaf = new AlignFrame(ap);
2215 // transfer reference for existing feature settings to new alignFrame
2216 if (ap == af.alignPanel)
2218 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2220 newaf.featureSettings = viewFeatureSettings;
2222 newaf.setFeatureSettingsGeometry(fsBounds);
2226 * Restore the view's last exploded frame geometry if known. Multiple views from
2227 * one exploded frame share and restore the same (frame) position and size.
2229 Rectangle geometry = ap.av.getExplodedGeometry();
2230 if (geometry != null)
2232 newaf.setBounds(geometry);
2235 ap.av.setGatherViewsHere(false);
2237 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2238 AlignFrame.DEFAULT_HEIGHT);
2239 // and materialise a new feature settings dialog instance for the new
2241 // (closes the old as if 'OK' was pressed)
2242 if (ap == af.alignPanel && newaf.featureSettings != null
2243 && newaf.featureSettings.isOpen()
2244 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2246 newaf.showFeatureSettingsUI();
2250 af.featureSettings = null;
2251 af.alignPanels.clear();
2252 af.closeMenuItem_actionPerformed(true);
2257 * Gather expanded views (separate AlignFrame's) with the same sequence set
2258 * identifier back in to this frame as additional views, and close the
2259 * expanded views. Note the expanded frames may themselves have multiple
2260 * views. We take the lot.
2264 public void gatherViews(AlignFrame source)
2266 source.viewport.setGatherViewsHere(true);
2267 source.viewport.setExplodedGeometry(source.getBounds());
2268 JInternalFrame[] frames = desktop.getAllFrames();
2269 String viewId = source.viewport.getSequenceSetId();
2270 for (int t = 0; t < frames.length; t++)
2272 if (frames[t] instanceof AlignFrame && frames[t] != source)
2274 AlignFrame af = (AlignFrame) frames[t];
2275 boolean gatherThis = false;
2276 for (int a = 0; a < af.alignPanels.size(); a++)
2278 AlignmentPanel ap = af.alignPanels.get(a);
2279 if (viewId.equals(ap.av.getSequenceSetId()))
2282 ap.av.setGatherViewsHere(false);
2283 ap.av.setExplodedGeometry(af.getBounds());
2284 source.addAlignmentPanel(ap, false);
2290 if (af.featureSettings != null && af.featureSettings.isOpen())
2292 if (source.featureSettings == null)
2294 // preserve the feature settings geometry for this frame
2295 source.featureSettings = af.featureSettings;
2296 source.setFeatureSettingsGeometry(
2297 af.getFeatureSettingsGeometry());
2301 // close it and forget
2302 af.featureSettings.close();
2305 af.alignPanels.clear();
2306 af.closeMenuItem_actionPerformed(true);
2311 // refresh the feature setting UI for the source frame if it exists
2312 if (source.featureSettings != null && source.featureSettings.isOpen())
2314 source.showFeatureSettingsUI();
2319 public JInternalFrame[] getAllFrames()
2321 return desktop.getAllFrames();
2325 * Checks the given url to see if it gives a response indicating that the user
2326 * should be informed of a new questionnaire.
2330 public void checkForQuestionnaire(String url)
2332 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2333 // javax.swing.SwingUtilities.invokeLater(jvq);
2334 new Thread(jvq).start();
2337 public void checkURLLinks()
2339 // Thread off the URL link checker
2340 addDialogThread(new Runnable()
2345 if (Cache.getDefault("CHECKURLLINKS", true))
2347 // check what the actual links are - if it's just the default don't
2348 // bother with the warning
2349 List<String> links = Preferences.sequenceUrlLinks
2352 // only need to check links if there is one with a
2353 // SEQUENCE_ID which is not the default EMBL_EBI link
2354 ListIterator<String> li = links.listIterator();
2355 boolean check = false;
2356 List<JLabel> urls = new ArrayList<>();
2357 while (li.hasNext())
2359 String link = li.next();
2360 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2361 && !UrlConstants.isDefaultString(link))
2364 int barPos = link.indexOf("|");
2365 String urlMsg = barPos == -1 ? link
2366 : link.substring(0, barPos) + ": "
2367 + link.substring(barPos + 1);
2368 urls.add(new JLabel(urlMsg));
2376 // ask user to check in case URL links use old style tokens
2377 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2378 JPanel msgPanel = new JPanel();
2379 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2380 msgPanel.add(Box.createVerticalGlue());
2381 JLabel msg = new JLabel(MessageManager
2382 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2383 JLabel msg2 = new JLabel(MessageManager
2384 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2386 for (JLabel url : urls)
2392 final JCheckBox jcb = new JCheckBox(
2393 MessageManager.getString("label.do_not_display_again"));
2394 jcb.addActionListener(new ActionListener()
2397 public void actionPerformed(ActionEvent e)
2399 // update Cache settings for "don't show this again"
2400 boolean showWarningAgain = !jcb.isSelected();
2401 Cache.setProperty("CHECKURLLINKS",
2402 Boolean.valueOf(showWarningAgain).toString());
2407 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2409 .getString("label.SEQUENCE_ID_no_longer_used"),
2410 JvOptionPane.WARNING_MESSAGE);
2417 * Proxy class for JDesktopPane which optionally displays the current memory
2418 * usage and highlights the desktop area with a red bar if free memory runs
2423 public class MyDesktopPane extends JDesktopPane implements Runnable
2425 private static final float ONE_MB = 1048576f;
2427 boolean showMemoryUsage = false;
2431 java.text.NumberFormat df;
2433 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2436 public MyDesktopPane(boolean showMemoryUsage)
2438 showMemoryUsage(showMemoryUsage);
2441 public void showMemoryUsage(boolean showMemory)
2443 this.showMemoryUsage = showMemory;
2446 Thread worker = new Thread(this);
2452 public boolean isShowMemoryUsage()
2454 return showMemoryUsage;
2460 df = java.text.NumberFormat.getNumberInstance();
2461 df.setMaximumFractionDigits(2);
2462 runtime = Runtime.getRuntime();
2464 while (showMemoryUsage)
2468 maxMemory = runtime.maxMemory() / ONE_MB;
2469 allocatedMemory = runtime.totalMemory() / ONE_MB;
2470 freeMemory = runtime.freeMemory() / ONE_MB;
2471 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2473 percentUsage = (totalFreeMemory / maxMemory) * 100;
2475 // if (percentUsage < 20)
2477 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2479 // instance.set.setBorder(border1);
2482 // sleep after showing usage
2484 } catch (Exception ex)
2486 ex.printStackTrace();
2492 public void paintComponent(Graphics g)
2494 if (showMemoryUsage && g != null && df != null)
2496 if (percentUsage < 20)
2498 g.setColor(Color.red);
2500 FontMetrics fm = g.getFontMetrics();
2503 g.drawString(MessageManager.formatMessage("label.memory_stats",
2505 { df.format(totalFreeMemory), df.format(maxMemory),
2506 df.format(percentUsage) }),
2507 10, getHeight() - fm.getHeight());
2511 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2512 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2517 * Accessor method to quickly get all the AlignmentFrames loaded.
2519 * @return an array of AlignFrame, or null if none found
2521 public static AlignFrame[] getAlignFrames()
2523 if (Jalview.isHeadlessMode())
2525 // Desktop.desktop is null in headless mode
2526 return new AlignFrame[] { Jalview.currentAlignFrame };
2529 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2535 List<AlignFrame> avp = new ArrayList<>();
2537 for (int i = frames.length - 1; i > -1; i--)
2539 if (frames[i] instanceof AlignFrame)
2541 avp.add((AlignFrame) frames[i]);
2543 else if (frames[i] instanceof SplitFrame)
2546 * Also check for a split frame containing an AlignFrame
2548 GSplitFrame sf = (GSplitFrame) frames[i];
2549 if (sf.getTopFrame() instanceof AlignFrame)
2551 avp.add((AlignFrame) sf.getTopFrame());
2553 if (sf.getBottomFrame() instanceof AlignFrame)
2555 avp.add((AlignFrame) sf.getBottomFrame());
2559 if (avp.size() == 0)
2563 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2568 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2572 public GStructureViewer[] getJmols()
2574 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2580 List<GStructureViewer> avp = new ArrayList<>();
2582 for (int i = frames.length - 1; i > -1; i--)
2584 if (frames[i] instanceof AppJmol)
2586 GStructureViewer af = (GStructureViewer) frames[i];
2590 if (avp.size() == 0)
2594 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2599 * Add Groovy Support to Jalview
2602 public void groovyShell_actionPerformed()
2606 openGroovyConsole();
2607 } catch (Exception ex)
2609 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2610 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2612 MessageManager.getString("label.couldnt_create_groovy_shell"),
2613 MessageManager.getString("label.groovy_support_failed"),
2614 JvOptionPane.ERROR_MESSAGE);
2619 * Open the Groovy console
2621 void openGroovyConsole()
2623 if (groovyConsole == null)
2625 groovyConsole = new groovy.ui.Console();
2626 groovyConsole.setVariable("Jalview", this);
2627 groovyConsole.run();
2630 * We allow only one console at a time, so that AlignFrame menu option
2631 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2632 * enable 'Run script', when the console is opened, and the reverse when it is
2635 Window window = (Window) groovyConsole.getFrame();
2636 window.addWindowListener(new WindowAdapter()
2639 public void windowClosed(WindowEvent e)
2642 * rebind CMD-Q from Groovy Console to Jalview Quit
2645 enableExecuteGroovy(false);
2651 * show Groovy console window (after close and reopen)
2653 ((Window) groovyConsole.getFrame()).setVisible(true);
2656 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2657 * opening a second console
2659 enableExecuteGroovy(true);
2663 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2664 * binding when opened
2666 protected void addQuitHandler()
2669 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2671 .getKeyStroke(KeyEvent.VK_Q,
2672 jalview.util.ShortcutKeyMaskExWrapper
2673 .getMenuShortcutKeyMaskEx()),
2675 getRootPane().getActionMap().put("Quit", new AbstractAction()
2678 public void actionPerformed(ActionEvent e)
2686 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2689 * true if Groovy console is open
2691 public void enableExecuteGroovy(boolean enabled)
2694 * disable opening a second Groovy console (or re-enable when the console is
2697 groovyShell.setEnabled(!enabled);
2699 AlignFrame[] alignFrames = getAlignFrames();
2700 if (alignFrames != null)
2702 for (AlignFrame af : alignFrames)
2704 af.setGroovyEnabled(enabled);
2710 * Progress bars managed by the IProgressIndicator method.
2712 private Hashtable<Long, JPanel> progressBars;
2714 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2719 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2722 public void setProgressBar(String message, long id)
2724 if (progressBars == null)
2726 progressBars = new Hashtable<>();
2727 progressBarHandlers = new Hashtable<>();
2730 if (progressBars.get(Long.valueOf(id)) != null)
2732 JPanel panel = progressBars.remove(Long.valueOf(id));
2733 if (progressBarHandlers.contains(Long.valueOf(id)))
2735 progressBarHandlers.remove(Long.valueOf(id));
2737 removeProgressPanel(panel);
2741 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2748 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2749 * jalview.gui.IProgressIndicatorHandler)
2752 public void registerHandler(final long id,
2753 final IProgressIndicatorHandler handler)
2755 if (progressBarHandlers == null
2756 || !progressBars.containsKey(Long.valueOf(id)))
2758 throw new Error(MessageManager.getString(
2759 "error.call_setprogressbar_before_registering_handler"));
2761 progressBarHandlers.put(Long.valueOf(id), handler);
2762 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2763 if (handler.canCancel())
2765 JButton cancel = new JButton(
2766 MessageManager.getString("action.cancel"));
2767 final IProgressIndicator us = this;
2768 cancel.addActionListener(new ActionListener()
2772 public void actionPerformed(ActionEvent e)
2774 handler.cancelActivity(id);
2775 us.setProgressBar(MessageManager
2776 .formatMessage("label.cancelled_params", new Object[]
2777 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2781 progressPanel.add(cancel, BorderLayout.EAST);
2787 * @return true if any progress bars are still active
2790 public boolean operationInProgress()
2792 if (progressBars != null && progressBars.size() > 0)
2800 * This will return the first AlignFrame holding the given viewport instance.
2801 * It will break if there are more than one AlignFrames viewing a particular
2805 * @return alignFrame for viewport
2807 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2809 if (desktop != null)
2811 AlignmentPanel[] aps = getAlignmentPanels(
2812 viewport.getSequenceSetId());
2813 for (int panel = 0; aps != null && panel < aps.length; panel++)
2815 if (aps[panel] != null && aps[panel].av == viewport)
2817 return aps[panel].alignFrame;
2824 public VamsasApplication getVamsasApplication()
2826 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2832 * flag set if jalview GUI is being operated programmatically
2834 private boolean inBatchMode = false;
2837 * check if jalview GUI is being operated programmatically
2839 * @return inBatchMode
2841 public boolean isInBatchMode()
2847 * set flag if jalview GUI is being operated programmatically
2849 * @param inBatchMode
2851 public void setInBatchMode(boolean inBatchMode)
2853 this.inBatchMode = inBatchMode;
2857 * start service discovery and wait till it is done
2859 public void startServiceDiscovery()
2861 startServiceDiscovery(false);
2865 * start service discovery threads - blocking or non-blocking
2869 public void startServiceDiscovery(boolean blocking)
2871 startServiceDiscovery(blocking, false);
2875 * start service discovery threads
2878 * - false means call returns immediately
2879 * @param ignore_SHOW_JWS2_SERVICES_preference
2880 * - when true JABA services are discovered regardless of user's JWS2
2881 * discovery preference setting
2883 public void startServiceDiscovery(boolean blocking,
2884 boolean ignore_SHOW_JWS2_SERVICES_preference)
2886 boolean alive = true;
2887 Thread t0 = null, t1 = null, t2 = null;
2888 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2891 // todo: changesupport handlers need to be transferred
2892 if (discoverer == null)
2894 discoverer = new jalview.ws.jws1.Discoverer();
2895 // register PCS handler for desktop.
2896 discoverer.addPropertyChangeListener(changeSupport);
2898 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2899 // until we phase out completely
2900 (t0 = new Thread(discoverer)).start();
2903 if (ignore_SHOW_JWS2_SERVICES_preference
2904 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2906 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2907 .startDiscoverer(changeSupport);
2911 // TODO: do rest service discovery
2920 } catch (Exception e)
2923 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2924 || (t3 != null && t3.isAlive())
2925 || (t0 != null && t0.isAlive());
2931 * called to check if the service discovery process completed successfully.
2935 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2937 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2939 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2940 .getErrorMessages();
2943 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2945 if (serviceChangedDialog == null)
2947 // only run if we aren't already displaying one of these.
2948 addDialogThread(serviceChangedDialog = new Runnable()
2955 * JalviewDialog jd =new JalviewDialog() {
2957 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2959 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2961 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2963 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2965 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2966 * + " or mis-configured HTTP proxy settings.<br/>" +
2967 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2968 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2969 * true, true, "Web Service Configuration Problem", 450, 400);
2971 * jd.waitForInput();
2973 JvOptionPane.showConfirmDialog(Desktop.desktop,
2974 new JLabel("<html><table width=\"450\"><tr><td>"
2975 + ermsg + "</td></tr></table>"
2976 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2977 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2978 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2979 + " Tools->Preferences dialog box to change them.</p></html>"),
2980 "Web Service Configuration Problem",
2981 JvOptionPane.DEFAULT_OPTION,
2982 JvOptionPane.ERROR_MESSAGE);
2983 serviceChangedDialog = null;
2991 jalview.bin.Console.error(
2992 "Errors reported by JABA discovery service. Check web services preferences.\n"
2999 private Runnable serviceChangedDialog = null;
3002 * start a thread to open a URL in the configured browser. Pops up a warning
3003 * dialog to the user if there is an exception when calling out to the browser
3008 public static void showUrl(final String url)
3010 showUrl(url, Desktop.instance);
3014 * Like showUrl but allows progress handler to be specified
3018 * (null) or object implementing IProgressIndicator
3020 public static void showUrl(final String url,
3021 final IProgressIndicator progress)
3023 new Thread(new Runnable()
3030 if (progress != null)
3032 progress.setProgressBar(MessageManager
3033 .formatMessage("status.opening_params", new Object[]
3034 { url }), this.hashCode());
3036 jalview.util.BrowserLauncher.openURL(url);
3037 } catch (Exception ex)
3039 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3041 .getString("label.web_browser_not_found_unix"),
3042 MessageManager.getString("label.web_browser_not_found"),
3043 JvOptionPane.WARNING_MESSAGE);
3045 ex.printStackTrace();
3047 if (progress != null)
3049 progress.setProgressBar(null, this.hashCode());
3055 public static WsParamSetManager wsparamManager = null;
3057 public static ParamManager getUserParameterStore()
3059 if (wsparamManager == null)
3061 wsparamManager = new WsParamSetManager();
3063 return wsparamManager;
3067 * static hyperlink handler proxy method for use by Jalview's internal windows
3071 public static void hyperlinkUpdate(HyperlinkEvent e)
3073 if (e.getEventType() == EventType.ACTIVATED)
3078 url = e.getURL().toString();
3079 Desktop.showUrl(url);
3080 } catch (Exception x)
3085 .error("Couldn't handle string " + url + " as a URL.");
3087 // ignore any exceptions due to dud links.
3094 * single thread that handles display of dialogs to user.
3096 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3099 * flag indicating if dialogExecutor should try to acquire a permit
3101 private volatile boolean dialogPause = true;
3106 private Semaphore block = new Semaphore(0);
3108 private static groovy.ui.Console groovyConsole;
3111 * add another dialog thread to the queue
3115 public void addDialogThread(final Runnable prompter)
3117 dialogExecutor.submit(new Runnable()
3124 acquireDialogQueue();
3126 if (instance == null)
3132 SwingUtilities.invokeAndWait(prompter);
3133 } catch (Exception q)
3135 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3142 private boolean dialogQueueStarted = false;
3144 public void startDialogQueue()
3146 if (dialogQueueStarted)
3150 // set the flag so we don't pause waiting for another permit and semaphore
3151 // the current task to begin
3152 releaseDialogQueue();
3153 dialogQueueStarted = true;
3156 public void acquireDialogQueue()
3162 } catch (InterruptedException e)
3164 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3169 public void releaseDialogQueue()
3176 dialogPause = false;
3180 * Outputs an image of the desktop to file in EPS format, after prompting the
3181 * user for choice of Text or Lineart character rendering (unless a preference
3182 * has been set). The file name is generated as
3185 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3189 protected void snapShotWindow_actionPerformed(ActionEvent e)
3191 // currently the menu option to do this is not shown
3194 int width = getWidth();
3195 int height = getHeight();
3197 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3198 ImageWriterI writer = new ImageWriterI()
3201 public void exportImage(Graphics g) throws Exception
3204 jalview.bin.Console.info("Successfully written snapshot to file "
3205 + of.getAbsolutePath());
3208 String title = "View of desktop";
3209 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3213 exporter.doExport(of, this, width, height, title);
3214 } catch (ImageOutputException ioex)
3216 jalview.bin.Console.error(
3217 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3223 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3224 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3225 * and location last time the view was expanded (if any). However it does not
3226 * remember the split pane divider location - this is set to match the
3227 * 'exploding' frame.
3231 public void explodeViews(SplitFrame sf)
3233 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3234 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3235 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3237 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3239 int viewCount = topPanels.size();
3246 * Processing in reverse order works, forwards order leaves the first panels not
3247 * visible. I don't know why!
3249 for (int i = viewCount - 1; i >= 0; i--)
3252 * Make new top and bottom frames. These take over the respective AlignmentPanel
3253 * objects, including their AlignmentViewports, so the cdna/protein
3254 * relationships between the viewports is carried over to the new split frames.
3256 * explodedGeometry holds the (x, y) position of the previously exploded
3257 * SplitFrame, and the (width, height) of the AlignFrame component
3259 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3260 AlignFrame newTopFrame = new AlignFrame(topPanel);
3261 newTopFrame.setSize(oldTopFrame.getSize());
3262 newTopFrame.setVisible(true);
3263 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3264 .getExplodedGeometry();
3265 if (geometry != null)
3267 newTopFrame.setSize(geometry.getSize());
3270 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3271 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3272 newBottomFrame.setSize(oldBottomFrame.getSize());
3273 newBottomFrame.setVisible(true);
3274 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3275 .getExplodedGeometry();
3276 if (geometry != null)
3278 newBottomFrame.setSize(geometry.getSize());
3281 topPanel.av.setGatherViewsHere(false);
3282 bottomPanel.av.setGatherViewsHere(false);
3283 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3285 if (geometry != null)
3287 splitFrame.setLocation(geometry.getLocation());
3289 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3293 * Clear references to the panels (now relocated in the new SplitFrames) before
3294 * closing the old SplitFrame.
3297 bottomPanels.clear();
3302 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3303 * back into the given SplitFrame as additional views. Note that the gathered
3304 * frames may themselves have multiple views.
3308 public void gatherViews(GSplitFrame source)
3311 * special handling of explodedGeometry for a view within a SplitFrame: - it
3312 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3313 * height) of the AlignFrame component
3315 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3316 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3317 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3318 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3319 myBottomFrame.viewport
3320 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3321 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3322 myTopFrame.viewport.setGatherViewsHere(true);
3323 myBottomFrame.viewport.setGatherViewsHere(true);
3324 String topViewId = myTopFrame.viewport.getSequenceSetId();
3325 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3327 JInternalFrame[] frames = desktop.getAllFrames();
3328 for (JInternalFrame frame : frames)
3330 if (frame instanceof SplitFrame && frame != source)
3332 SplitFrame sf = (SplitFrame) frame;
3333 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3334 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3335 boolean gatherThis = false;
3336 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3338 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3339 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3340 if (topViewId.equals(topPanel.av.getSequenceSetId())
3341 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3344 topPanel.av.setGatherViewsHere(false);
3345 bottomPanel.av.setGatherViewsHere(false);
3346 topPanel.av.setExplodedGeometry(
3347 new Rectangle(sf.getLocation(), topFrame.getSize()));
3348 bottomPanel.av.setExplodedGeometry(
3349 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3350 myTopFrame.addAlignmentPanel(topPanel, false);
3351 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3357 topFrame.getAlignPanels().clear();
3358 bottomFrame.getAlignPanels().clear();
3365 * The dust settles...give focus to the tab we did this from.
3367 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3370 public static groovy.ui.Console getGroovyConsole()
3372 return groovyConsole;
3376 * handles the payload of a drag and drop event.
3378 * TODO refactor to desktop utilities class
3381 * - Data source strings extracted from the drop event
3383 * - protocol for each data source extracted from the drop event
3387 * - the payload from the drop event
3390 public static void transferFromDropTarget(List<Object> files,
3391 List<DataSourceType> protocols, DropTargetDropEvent evt,
3392 Transferable t) throws Exception
3395 DataFlavor uriListFlavor = new DataFlavor(
3396 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3399 urlFlavour = new DataFlavor(
3400 "application/x-java-url; class=java.net.URL");
3401 } catch (ClassNotFoundException cfe)
3403 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3407 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3412 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3413 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3414 // means url may be null.
3417 protocols.add(DataSourceType.URL);
3418 files.add(url.toString());
3419 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3420 + files.get(files.size() - 1));
3425 if (Platform.isAMacAndNotJS())
3427 jalview.bin.Console.errPrintln(
3428 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3431 } catch (Throwable ex)
3433 jalview.bin.Console.debug("URL drop handler failed.", ex);
3436 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3438 // Works on Windows and MacOSX
3439 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3440 for (Object file : (List) t
3441 .getTransferData(DataFlavor.javaFileListFlavor))
3444 protocols.add(DataSourceType.FILE);
3449 // Unix like behaviour
3450 boolean added = false;
3452 if (t.isDataFlavorSupported(uriListFlavor))
3454 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3455 // This is used by Unix drag system
3456 data = (String) t.getTransferData(uriListFlavor);
3460 // fallback to text: workaround - on OSX where there's a JVM bug
3462 .debug("standard URIListFlavor failed. Trying text");
3463 // try text fallback
3464 DataFlavor textDf = new DataFlavor(
3465 "text/plain;class=java.lang.String");
3466 if (t.isDataFlavorSupported(textDf))
3468 data = (String) t.getTransferData(textDf);
3471 jalview.bin.Console.debug("Plain text drop content returned "
3472 + (data == null ? "Null - failed" : data));
3477 while (protocols.size() < files.size())
3479 jalview.bin.Console.debug("Adding missing FILE protocol for "
3480 + files.get(protocols.size()));
3481 protocols.add(DataSourceType.FILE);
3483 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3484 data, "\r\n"); st.hasMoreTokens();)
3487 String s = st.nextToken();
3488 if (s.startsWith("#"))
3490 // the line is a comment (as per the RFC 2483)
3493 java.net.URI uri = new java.net.URI(s);
3494 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3496 protocols.add(DataSourceType.URL);
3497 files.add(uri.toString());
3501 // otherwise preserve old behaviour: catch all for file objects
3502 java.io.File file = new java.io.File(uri);
3503 protocols.add(DataSourceType.FILE);
3504 files.add(file.toString());
3509 if (jalview.bin.Console.isDebugEnabled())
3511 if (data == null || !added)
3514 if (t.getTransferDataFlavors() != null
3515 && t.getTransferDataFlavors().length > 0)
3517 jalview.bin.Console.debug(
3518 "Couldn't resolve drop data. Here are the supported flavors:");
3519 for (DataFlavor fl : t.getTransferDataFlavors())
3521 jalview.bin.Console.debug(
3522 "Supported transfer dataflavor: " + fl.toString());
3523 Object df = t.getTransferData(fl);
3526 jalview.bin.Console.debug("Retrieves: " + df);
3530 jalview.bin.Console.debug("Retrieved nothing");
3537 .debug("Couldn't resolve dataflavor for drop: "
3543 if (Platform.isWindowsAndNotJS())
3546 .debug("Scanning dropped content for Windows Link Files");
3548 // resolve any .lnk files in the file drop
3549 for (int f = 0; f < files.size(); f++)
3551 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3552 if (protocols.get(f).equals(DataSourceType.FILE)
3553 && (source.endsWith(".lnk") || source.endsWith(".url")
3554 || source.endsWith(".site")))
3558 Object obj = files.get(f);
3559 File lf = (obj instanceof File ? (File) obj
3560 : new File((String) obj));
3561 // process link file to get a URL
3562 jalview.bin.Console.debug("Found potential link file: " + lf);
3563 WindowsShortcut wscfile = new WindowsShortcut(lf);
3564 String fullname = wscfile.getRealFilename();
3565 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3566 files.set(f, fullname);
3567 jalview.bin.Console.debug("Parsed real filename " + fullname
3568 + " to extract protocol: " + protocols.get(f));
3569 } catch (Exception ex)
3571 jalview.bin.Console.error(
3572 "Couldn't parse " + files.get(f) + " as a link file.",
3581 * Sets the Preferences property for experimental features to True or False
3582 * depending on the state of the controlling menu item
3585 protected void showExperimental_actionPerformed(boolean selected)
3587 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3591 * Answers a (possibly empty) list of any structure viewer frames (currently
3592 * for either Jmol or Chimera) which are currently open. This may optionally
3593 * be restricted to viewers of a specified class, or viewers linked to a
3594 * specified alignment panel.
3597 * if not null, only return viewers linked to this panel
3598 * @param structureViewerClass
3599 * if not null, only return viewers of this class
3602 public List<StructureViewerBase> getStructureViewers(
3603 AlignmentPanel apanel,
3604 Class<? extends StructureViewerBase> structureViewerClass)
3606 List<StructureViewerBase> result = new ArrayList<>();
3607 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3609 for (JInternalFrame frame : frames)
3611 if (frame instanceof StructureViewerBase)
3613 if (structureViewerClass == null
3614 || structureViewerClass.isInstance(frame))
3617 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3619 result.add((StructureViewerBase) frame);
3627 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3629 private static boolean debugScaleMessageDone = false;
3631 public static void debugScaleMessage(Graphics g)
3633 if (debugScaleMessageDone)
3637 // output used by tests to check HiDPI scaling settings in action
3640 Graphics2D gg = (Graphics2D) g;
3643 AffineTransform t = gg.getTransform();
3644 double scaleX = t.getScaleX();
3645 double scaleY = t.getScaleY();
3646 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3647 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3648 debugScaleMessageDone = true;
3652 jalview.bin.Console.debug("Desktop graphics null");
3654 } catch (Exception e)
3656 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3661 * closes the current instance window, disposes and forgets about it.
3663 public static void closeDesktop()
3665 if (Desktop.instance != null)
3667 Desktop.instance.closeAll_actionPerformed(null);
3668 Desktop.instance.setVisible(false);
3669 Desktop us = Desktop.instance;
3670 Desktop.instance = null;
3671 // call dispose in a separate thread - try to avoid indirect deadlocks
3672 new Thread(new Runnable()
3677 ExecutorService dex = us.dialogExecutor;
3681 us.dialogExecutor = null;
3682 us.block.drainPermits();
3691 * checks if any progress bars are being displayed in any of the windows
3692 * managed by the desktop
3696 public boolean operationsAreInProgress()
3698 JInternalFrame[] frames = getAllFrames();
3699 for (JInternalFrame frame : frames)
3701 if (frame instanceof IProgressIndicator)
3703 if (((IProgressIndicator) frame).operationInProgress())
3709 return operationInProgress();
3713 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3714 * The way the modal JInternalFrame is made means it cannot be a child of an
3715 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3717 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3719 protected static void addModal(AlignFrame af, JInternalFrame jif)
3721 alignFrameModalMap.put(af, jif);
3724 protected static void closeModal(AlignFrame af)
3726 if (!alignFrameModalMap.containsKey(af))
3730 JInternalFrame jif = alignFrameModalMap.get(af);
3735 jif.setClosed(true);
3736 } catch (PropertyVetoException e)
3738 e.printStackTrace();
3741 alignFrameModalMap.remove(af);
3744 public void nonBlockingDialog(String title, String message, String button,
3745 int type, boolean scrollable, boolean modal)
3747 nonBlockingDialog(32, 2, title, message, button, type, scrollable,
3751 public void nonBlockingDialog(int width, int height, String title,
3752 String message, String button, int type, boolean scrollable,
3757 type = JvOptionPane.WARNING_MESSAGE;
3759 JTextArea jta = new JTextArea(height, width);
3760 // jta.setLineWrap(true);
3761 jta.setEditable(false);
3762 jta.setWrapStyleWord(true);
3763 jta.setAutoscrolls(true);
3764 jta.setText(message);
3766 JScrollPane jsp = scrollable
3767 ? new JScrollPane(jta, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3768 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3771 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3772 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3774 jvp.showDialogOnTopAsync(this, scrollable ? jsp : jta, title,
3775 JOptionPane.YES_OPTION, type, null, new Object[]
3776 { button }, button, modal, null, false);