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;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JFrame;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.WindowConstants;
95 import javax.swing.event.HyperlinkEvent;
96 import javax.swing.event.HyperlinkEvent.EventType;
97 import javax.swing.event.InternalFrameAdapter;
98 import javax.swing.event.InternalFrameEvent;
100 import org.stackoverflowusers.file.WindowsShortcut;
102 import jalview.api.AlignViewportI;
103 import jalview.api.AlignmentViewPanel;
104 import jalview.api.structures.JalviewStructureDisplayI;
105 import jalview.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.datamodel.Alignment;
108 import jalview.datamodel.HiddenColumns;
109 import jalview.datamodel.Sequence;
110 import jalview.datamodel.SequenceI;
111 import jalview.gui.ImageExporter.ImageWriterI;
112 import jalview.gui.QuitHandler.QResponse;
113 import jalview.io.BackupFiles;
114 import jalview.io.DataSourceType;
115 import jalview.io.FileFormat;
116 import jalview.io.FileFormatException;
117 import jalview.io.FileFormatI;
118 import jalview.io.FileFormats;
119 import jalview.io.FileLoader;
120 import jalview.io.FormatAdapter;
121 import jalview.io.IdentifyFile;
122 import jalview.io.JalviewFileChooser;
123 import jalview.io.JalviewFileView;
124 import jalview.io.exceptions.ImageOutputException;
125 import jalview.jbgui.GSplitFrame;
126 import jalview.jbgui.GStructureViewer;
127 import jalview.project.Jalview2XML;
128 import jalview.structure.StructureSelectionManager;
129 import jalview.urls.IdOrgSettings;
130 import jalview.util.BrowserLauncher;
131 import jalview.util.ChannelProperties;
132 import jalview.util.ImageMaker.TYPE;
133 import jalview.util.LaunchUtils;
134 import jalview.util.MessageManager;
135 import jalview.util.Platform;
136 import jalview.util.ShortcutKeyMaskExWrapper;
137 import jalview.util.UrlConstants;
138 import jalview.viewmodel.AlignmentViewport;
139 import jalview.ws.params.ParamManager;
140 import jalview.ws.utils.UrlDownloadClient;
147 * @version $Revision: 1.155 $
149 public class Desktop extends jalview.jbgui.GDesktop
150 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
151 jalview.api.StructureSelectionManagerProvider
153 private static final String CITATION;
156 URL bg_logo_url = ChannelProperties.getImageURL(
157 "bg_logo." + String.valueOf(SplashScreen.logoSize));
158 URL uod_logo_url = ChannelProperties.getImageURL(
159 "uod_banner." + String.valueOf(SplashScreen.logoSize));
160 boolean logo = (bg_logo_url != null || uod_logo_url != null);
161 StringBuilder sb = new StringBuilder();
163 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
168 sb.append(bg_logo_url == null ? ""
169 : "<img alt=\"Barton Group logo\" src=\""
170 + bg_logo_url.toString() + "\">");
171 sb.append(uod_logo_url == null ? ""
172 : " <img alt=\"University of Dundee shield\" src=\""
173 + uod_logo_url.toString() + "\">");
175 "<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>");
176 sb.append("<br><br>If you use Jalview, please cite:"
177 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
178 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
179 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
180 CITATION = sb.toString();
183 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
185 private static int DEFAULT_MIN_WIDTH = 300;
187 private static int DEFAULT_MIN_HEIGHT = 250;
189 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
191 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
193 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
195 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
197 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
199 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
201 public static void setLiveDragMode(boolean b)
203 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
204 : JDesktopPane.OUTLINE_DRAG_MODE;
206 desktop.setDragMode(DRAG_MODE);
209 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
211 public static boolean nosplash = false;
214 * news reader - null if it was never started.
216 private BlogReader jvnews = null;
218 private File projectFile;
222 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
224 public void addJalviewPropertyChangeListener(
225 PropertyChangeListener listener)
227 changeSupport.addJalviewPropertyChangeListener(listener);
231 * @param propertyName
233 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
234 * java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(String propertyName,
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void removeJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.removeJalviewPropertyChangeListener(propertyName,
255 /** Singleton Desktop instance */
256 public static Desktop instance;
258 public static MyDesktopPane desktop;
260 public static MyDesktopPane getDesktop()
262 // BH 2018 could use currentThread() here as a reference to a
263 // Hashtable<Thread, MyDesktopPane> in JavaScript
267 static int openFrameCount = 0;
269 static final int xOffset = 30;
271 static final int yOffset = 30;
273 public static jalview.ws.jws1.Discoverer discoverer;
275 public static Object[] jalviewClipboard;
277 public static boolean internalCopy = false;
279 static int fileLoadingCount = 0;
281 class MyDesktopManager implements DesktopManager
284 private DesktopManager delegate;
286 public MyDesktopManager(DesktopManager delegate)
288 this.delegate = delegate;
292 public void activateFrame(JInternalFrame f)
296 delegate.activateFrame(f);
297 } catch (NullPointerException npe)
299 Point p = getMousePosition();
300 instance.showPasteMenu(p.x, p.y);
305 public void beginDraggingFrame(JComponent f)
307 delegate.beginDraggingFrame(f);
311 public void beginResizingFrame(JComponent f, int direction)
313 delegate.beginResizingFrame(f, direction);
317 public void closeFrame(JInternalFrame f)
319 delegate.closeFrame(f);
323 public void deactivateFrame(JInternalFrame f)
325 delegate.deactivateFrame(f);
329 public void deiconifyFrame(JInternalFrame f)
331 delegate.deiconifyFrame(f);
335 public void dragFrame(JComponent f, int newX, int newY)
341 delegate.dragFrame(f, newX, newY);
345 public void endDraggingFrame(JComponent f)
347 delegate.endDraggingFrame(f);
352 public void endResizingFrame(JComponent f)
354 delegate.endResizingFrame(f);
359 public void iconifyFrame(JInternalFrame f)
361 delegate.iconifyFrame(f);
365 public void maximizeFrame(JInternalFrame f)
367 delegate.maximizeFrame(f);
371 public void minimizeFrame(JInternalFrame f)
373 delegate.minimizeFrame(f);
377 public void openFrame(JInternalFrame f)
379 delegate.openFrame(f);
383 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
390 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
394 public void setBoundsForFrame(JComponent f, int newX, int newY,
395 int newWidth, int newHeight)
397 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
400 // All other methods, simply delegate
405 * Creates a new Desktop object.
411 * A note to implementors. It is ESSENTIAL that any activities that might
412 * block are spawned off as threads rather than waited for during this
417 doConfigureStructurePrefs();
418 setTitle(ChannelProperties.getProperty("app_name") + " "
419 + Cache.getProperty("VERSION"));
422 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
423 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
424 * officially documented or guaranteed to exist, so we access it via
425 * reflection. There appear to be unfathomable criteria about what this
426 * string can contain, and it if doesn't meet those criteria then "java"
427 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
428 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
429 * not. The reflection access may generate a warning: WARNING: An illegal
430 * reflective access operation has occurred WARNING: Illegal reflective
431 * access by jalview.gui.Desktop () to field
432 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
434 if (Platform.isLinux())
436 if (LaunchUtils.getJavaVersion() >= 11)
439 * Send this message to stderr as the warning that follows (due to
440 * reflection) also goes to stderr.
442 jalview.bin.Console.errPrintln(
443 "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.");
445 final String awtAppClassName = "awtAppClassName";
448 Toolkit xToolkit = Toolkit.getDefaultToolkit();
449 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
450 Field awtAppClassNameField = null;
452 if (Arrays.stream(declaredFields)
453 .anyMatch(f -> f.getName().equals(awtAppClassName)))
455 awtAppClassNameField = xToolkit.getClass()
456 .getDeclaredField(awtAppClassName);
459 String title = ChannelProperties.getProperty("app_name");
460 if (awtAppClassNameField != null)
462 awtAppClassNameField.setAccessible(true);
463 awtAppClassNameField.set(xToolkit, title);
468 .debug("XToolkit: " + awtAppClassName + " not found");
470 } catch (Exception e)
472 jalview.bin.Console.debug("Error setting " + awtAppClassName);
473 jalview.bin.Console.trace(Cache.getStackTraceString(e));
477 setIconImages(ChannelProperties.getIconList());
479 // override quit handling when GUI OS close [X] button pressed
480 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
481 addWindowListener(new WindowAdapter()
484 public void windowClosing(WindowEvent ev)
486 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
490 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
492 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
493 desktop = new MyDesktopPane(selmemusage);
495 showMemusage.setSelected(selmemusage);
496 desktop.setBackground(Color.white);
498 getContentPane().setLayout(new BorderLayout());
499 // alternate config - have scrollbars - see notes in JAL-153
500 // JScrollPane sp = new JScrollPane();
501 // sp.getViewport().setView(desktop);
502 // getContentPane().add(sp, BorderLayout.CENTER);
504 // BH 2018 - just an experiment to try unclipped JInternalFrames.
507 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
510 getContentPane().add(desktop, BorderLayout.CENTER);
511 desktop.setDragMode(DRAG_MODE);
513 // This line prevents Windows Look&Feel resizing all new windows to maximum
514 // if previous window was maximised
515 desktop.setDesktopManager(new MyDesktopManager(
516 Platform.isJS() ? desktop.getDesktopManager()
517 : new DefaultDesktopManager()));
519 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
520 : Platform.isAMacAndNotJS()
521 ? new AquaInternalFrameManager(
522 desktop.getDesktopManager())
523 : desktop.getDesktopManager())));
526 Rectangle dims = getLastKnownDimensions("");
533 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
534 int xPos = Math.max(5, (screenSize.width - 900) / 2);
535 int yPos = Math.max(5, (screenSize.height - 650) / 2);
536 setBounds(xPos, yPos, 900, 650);
539 // start dialogue queue for single dialogues
542 if (!Platform.isJS())
549 jconsole = new Console(this, showjconsole);
550 jconsole.setHeader(Cache.getVersionDetailsForConsole());
551 showConsole(showjconsole);
553 showNews.setVisible(false);
555 experimentalFeatures.setSelected(showExperimental());
557 getIdentifiersOrgData();
561 // Spawn a thread that shows the splashscreen
564 SwingUtilities.invokeLater(new Runnable()
569 new SplashScreen(true);
574 // Thread off a new instance of the file chooser - this reduces the time
575 // it takes to open it later on.
576 new Thread(new Runnable()
581 jalview.bin.Console.debug("Filechooser init thread started.");
582 String fileFormat = FileLoader.getUseDefaultFileFormat()
583 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
585 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
587 jalview.bin.Console.debug("Filechooser init thread finished.");
590 // Add the service change listener
591 changeSupport.addJalviewPropertyChangeListener("services",
592 new PropertyChangeListener()
596 public void propertyChange(PropertyChangeEvent evt)
599 .debug("Firing service changed event for "
600 + evt.getNewValue());
601 JalviewServicesChanged(evt);
606 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
609 this.addMouseListener(ma = new MouseAdapter()
612 public void mousePressed(MouseEvent evt)
614 if (evt.isPopupTrigger()) // Mac
616 showPasteMenu(evt.getX(), evt.getY());
621 public void mouseReleased(MouseEvent evt)
623 if (evt.isPopupTrigger()) // Windows
625 showPasteMenu(evt.getX(), evt.getY());
629 desktop.addMouseListener(ma);
633 // used for jalviewjsTest
634 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
639 * Answers true if user preferences to enable experimental features is True
644 public boolean showExperimental()
646 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
647 Boolean.FALSE.toString());
648 return Boolean.valueOf(experimental).booleanValue();
651 public void doConfigureStructurePrefs()
653 // configure services
654 StructureSelectionManager ssm = StructureSelectionManager
655 .getStructureSelectionManager(this);
656 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
658 ssm.setAddTempFacAnnot(
659 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
660 ssm.setProcessSecondaryStructure(
661 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
662 // JAL-3915 - RNAView is no longer an option so this has no effect
663 ssm.setSecStructServices(
664 Cache.getDefault(Preferences.USE_RNAVIEW, false));
668 ssm.setAddTempFacAnnot(false);
669 ssm.setProcessSecondaryStructure(false);
670 ssm.setSecStructServices(false);
674 public void checkForNews()
676 final Desktop me = this;
677 // Thread off the news reader, in case there are connection problems.
678 new Thread(new Runnable()
683 jalview.bin.Console.debug("Starting news thread.");
684 jvnews = new BlogReader(me);
685 showNews.setVisible(true);
686 jalview.bin.Console.debug("Completed news thread.");
691 public void getIdentifiersOrgData()
693 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
694 {// Thread off the identifiers fetcher
695 new Thread(new Runnable()
701 .debug("Downloading data from identifiers.org");
704 UrlDownloadClient.download(IdOrgSettings.getUrl(),
705 IdOrgSettings.getDownloadLocation());
706 } catch (IOException e)
709 .debug("Exception downloading identifiers.org data"
719 protected void showNews_actionPerformed(ActionEvent e)
721 showNews(showNews.isSelected());
724 void showNews(boolean visible)
726 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
727 showNews.setSelected(visible);
728 if (visible && !jvnews.isVisible())
730 new Thread(new Runnable()
735 long now = System.currentTimeMillis();
736 Desktop.instance.setProgressBar(
737 MessageManager.getString("status.refreshing_news"), now);
738 jvnews.refreshNews();
739 Desktop.instance.setProgressBar(null, now);
747 * recover the last known dimensions for a jalview window
750 * - empty string is desktop, all other windows have unique prefix
751 * @return null or last known dimensions scaled to current geometry (if last
752 * window geom was known)
754 Rectangle getLastKnownDimensions(String windowName)
756 // TODO: lock aspect ratio for scaling desktop Bug #0058199
757 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
758 String x = Cache.getProperty(windowName + "SCREEN_X");
759 String y = Cache.getProperty(windowName + "SCREEN_Y");
760 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
761 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
762 if ((x != null) && (y != null) && (width != null) && (height != null))
764 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
765 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
766 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
768 // attempt #1 - try to cope with change in screen geometry - this
769 // version doesn't preserve original jv aspect ratio.
770 // take ratio of current screen size vs original screen size.
771 double sw = ((1f * screenSize.width) / (1f * Integer
772 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
773 double sh = ((1f * screenSize.height) / (1f * Integer
774 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
775 // rescale the bounds depending upon the current screen geometry.
776 ix = (int) (ix * sw);
777 iw = (int) (iw * sw);
778 iy = (int) (iy * sh);
779 ih = (int) (ih * sh);
780 while (ix >= screenSize.width)
782 jalview.bin.Console.debug(
783 "Window geometry location recall error: shifting horizontal to within screenbounds.");
784 ix -= screenSize.width;
786 while (iy >= screenSize.height)
788 jalview.bin.Console.debug(
789 "Window geometry location recall error: shifting vertical to within screenbounds.");
790 iy -= screenSize.height;
792 jalview.bin.Console.debug(
793 "Got last known dimensions for " + windowName + ": x:" + ix
794 + " y:" + iy + " width:" + iw + " height:" + ih);
796 // return dimensions for new instance
797 return new Rectangle(ix, iy, iw, ih);
802 void showPasteMenu(int x, int y)
804 JPopupMenu popup = new JPopupMenu();
805 JMenuItem item = new JMenuItem(
806 MessageManager.getString("label.paste_new_window"));
807 item.addActionListener(new ActionListener()
810 public void actionPerformed(ActionEvent evt)
817 popup.show(this, x, y);
822 // quick patch for JAL-4150 - needs some more work and test coverage
823 // TODO - unify below and AlignFrame.paste()
824 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
825 // clipboard has come from a different alignment window than the one where
826 // paste has been called! JAL-4151
828 if (Desktop.jalviewClipboard != null)
830 // The clipboard was filled from within Jalview, we must use the
832 // And dataset from the copied alignment
833 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
834 // be doubly sure that we create *new* sequence objects.
835 SequenceI[] sequences = new SequenceI[newseq.length];
836 for (int i = 0; i < newseq.length; i++)
838 sequences[i] = new Sequence(newseq[i]);
840 Alignment alignment = new Alignment(sequences);
841 // dataset is inherited
842 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
843 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
844 AlignFrame.DEFAULT_HEIGHT);
845 String newtitle = new String("Copied sequences");
847 if (Desktop.jalviewClipboard[2] != null)
849 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
850 af.viewport.setHiddenColumns(hc);
853 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
854 AlignFrame.DEFAULT_HEIGHT);
861 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
862 Transferable contents = c.getContents(this);
864 if (contents != null)
866 String file = (String) contents
867 .getTransferData(DataFlavor.stringFlavor);
869 FileFormatI format = new IdentifyFile().identify(file,
870 DataSourceType.PASTE);
872 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
875 } catch (Exception ex)
877 jalview.bin.Console.outPrintln(
878 "Unable to paste alignment from system clipboard:\n" + ex);
884 * Adds and opens the given frame to the desktop
895 public static synchronized void addInternalFrame(
896 final JInternalFrame frame, String title, int w, int h)
898 addInternalFrame(frame, title, true, w, h, true, false);
902 * Add an internal frame to the Jalview desktop
909 * When true, display frame immediately, otherwise, caller must call
910 * setVisible themselves.
916 public static synchronized void addInternalFrame(
917 final JInternalFrame frame, String title, boolean makeVisible,
920 addInternalFrame(frame, title, makeVisible, w, h, true, false);
924 * Add an internal frame to the Jalview desktop and make it visible
937 public static synchronized void addInternalFrame(
938 final JInternalFrame frame, String title, int w, int h,
941 addInternalFrame(frame, title, true, w, h, resizable, false);
945 * Add an internal frame to the Jalview desktop
952 * When true, display frame immediately, otherwise, caller must call
953 * setVisible themselves.
960 * @param ignoreMinSize
961 * Do not set the default minimum size for frame
963 public static synchronized void addInternalFrame(
964 final JInternalFrame frame, String title, boolean makeVisible,
965 int w, int h, boolean resizable, boolean ignoreMinSize)
968 // TODO: allow callers to determine X and Y position of frame (eg. via
970 // TODO: consider fixing method to update entries in the window submenu with
971 // the current window title
973 frame.setTitle(title);
974 if (frame.getWidth() < 1 || frame.getHeight() < 1)
978 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
979 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
980 // IF JALVIEW IS RUNNING HEADLESS
981 // ///////////////////////////////////////////////
982 if (instance == null || (System.getProperty("java.awt.headless") != null
983 && System.getProperty("java.awt.headless").equals("true")))
992 frame.setMinimumSize(
993 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
995 // Set default dimension for Alignment Frame window.
996 // The Alignment Frame window could be added from a number of places,
998 // I did this here in order not to miss out on any Alignment frame.
999 if (frame instanceof AlignFrame)
1001 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1002 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1006 frame.setVisible(makeVisible);
1007 frame.setClosable(true);
1008 frame.setResizable(resizable);
1009 frame.setMaximizable(resizable);
1010 frame.setIconifiable(resizable);
1011 frame.setOpaque(Platform.isJS());
1013 if (frame.getX() < 1 && frame.getY() < 1)
1015 frame.setLocation(xOffset * openFrameCount,
1016 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1020 * add an entry for the new frame in the Window menu (and remove it when the
1023 final JMenuItem menuItem = new JMenuItem(title);
1024 frame.addInternalFrameListener(new InternalFrameAdapter()
1027 public void internalFrameActivated(InternalFrameEvent evt)
1029 JInternalFrame itf = desktop.getSelectedFrame();
1032 if (itf instanceof AlignFrame)
1034 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1041 public void internalFrameClosed(InternalFrameEvent evt)
1043 PaintRefresher.RemoveComponent(frame);
1046 * defensive check to prevent frames being added half off the window
1048 if (openFrameCount > 0)
1054 * ensure no reference to alignFrame retained by menu item listener
1056 if (menuItem.getActionListeners().length > 0)
1058 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1060 windowMenu.remove(menuItem);
1064 menuItem.addActionListener(new ActionListener()
1067 public void actionPerformed(ActionEvent e)
1071 frame.setSelected(true);
1072 frame.setIcon(false);
1073 } catch (java.beans.PropertyVetoException ex)
1080 setKeyBindings(frame);
1082 // Since the latest FlatLaf patch, we occasionally have problems showing structureViewer frames...
1084 boolean shown=false;
1085 Exception last=null;
1092 } catch (IllegalArgumentException iaex)
1096 jalview.bin.Console.info(
1097 "Squashed IllegalArgument Exception (" + tries + " left) for "+frame.getTitle(),
1102 } catch (InterruptedException iex)
1107 } while (!shown && tries > 0);
1110 jalview.bin.Console.error("Serious Problem whilst showing window "+frame.getTitle(),last);
1113 windowMenu.add(menuItem);
1118 frame.setSelected(true);
1119 frame.requestFocus();
1120 } catch (java.beans.PropertyVetoException ve)
1122 } catch (java.lang.ClassCastException cex)
1124 jalview.bin.Console.warn(
1125 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1131 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1136 private static void setKeyBindings(JInternalFrame frame)
1138 @SuppressWarnings("serial")
1139 final Action closeAction = new AbstractAction()
1142 public void actionPerformed(ActionEvent e)
1149 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1151 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1152 InputEvent.CTRL_DOWN_MASK);
1153 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1154 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1156 InputMap inputMap = frame
1157 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1158 String ctrlW = ctrlWKey.toString();
1159 inputMap.put(ctrlWKey, ctrlW);
1160 inputMap.put(cmdWKey, ctrlW);
1162 ActionMap actionMap = frame.getActionMap();
1163 actionMap.put(ctrlW, closeAction);
1167 public void lostOwnership(Clipboard clipboard, Transferable contents)
1171 Desktop.jalviewClipboard = null;
1174 internalCopy = false;
1178 public void dragEnter(DropTargetDragEvent evt)
1183 public void dragExit(DropTargetEvent evt)
1188 public void dragOver(DropTargetDragEvent evt)
1193 public void dropActionChanged(DropTargetDragEvent evt)
1204 public void drop(DropTargetDropEvent evt)
1206 boolean success = true;
1207 // JAL-1552 - acceptDrop required before getTransferable call for
1208 // Java's Transferable for native dnd
1209 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1210 Transferable t = evt.getTransferable();
1211 List<Object> files = new ArrayList<>();
1212 List<DataSourceType> protocols = new ArrayList<>();
1216 Desktop.transferFromDropTarget(files, protocols, evt, t);
1217 } catch (Exception e)
1219 e.printStackTrace();
1227 for (int i = 0; i < files.size(); i++)
1229 // BH 2018 File or String
1230 Object file = files.get(i);
1231 String fileName = file.toString();
1232 DataSourceType protocol = (protocols == null)
1233 ? DataSourceType.FILE
1235 FileFormatI format = null;
1237 if (fileName.endsWith(".jar"))
1239 format = FileFormat.Jalview;
1244 format = new IdentifyFile().identify(file, protocol);
1246 if (file instanceof File)
1248 Platform.cacheFileData((File) file);
1250 new FileLoader().LoadFile(null, file, protocol, format);
1253 } catch (Exception ex)
1258 evt.dropComplete(success); // need this to ensure input focus is properly
1259 // transfered to any new windows created
1269 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1271 String fileFormat = FileLoader.getUseDefaultFileFormat()
1272 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1274 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1275 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1276 BackupFiles.getEnabled());
1278 chooser.setFileView(new JalviewFileView());
1279 chooser.setDialogTitle(
1280 MessageManager.getString("label.open_local_file"));
1281 chooser.setToolTipText(MessageManager.getString("action.open"));
1283 chooser.setResponseHandler(0, () -> {
1284 File selectedFile = chooser.getSelectedFile();
1285 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1287 FileFormatI format = chooser.getSelectedFormat();
1290 * Call IdentifyFile to verify the file contains what its extension implies.
1291 * Skip this step for dynamically added file formats, because IdentifyFile does
1292 * not know how to recognise them.
1294 if (FileFormats.getInstance().isIdentifiable(format))
1298 format = new IdentifyFile().identify(selectedFile,
1299 DataSourceType.FILE);
1300 } catch (FileFormatException e)
1302 // format = null; //??
1306 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1309 chooser.showOpenDialog(this);
1313 * Shows a dialog for input of a URL at which to retrieve alignment data
1318 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1320 // This construct allows us to have a wider textfield
1322 JLabel label = new JLabel(
1323 MessageManager.getString("label.input_file_url"));
1325 JPanel panel = new JPanel(new GridLayout(2, 1));
1329 * the URL to fetch is input in Java: an editable combobox with history JS:
1330 * (pending JAL-3038) a plain text field
1333 String urlBase = "https://www.";
1334 if (Platform.isJS())
1336 history = new JTextField(urlBase, 35);
1345 JComboBox<String> asCombo = new JComboBox<>();
1346 asCombo.setPreferredSize(new Dimension(400, 20));
1347 asCombo.setEditable(true);
1348 asCombo.addItem(urlBase);
1349 String historyItems = Cache.getProperty("RECENT_URL");
1350 if (historyItems != null)
1352 for (String token : historyItems.split("\\t"))
1354 asCombo.addItem(token);
1361 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1362 MessageManager.getString("action.cancel") };
1363 Runnable action = () -> {
1364 @SuppressWarnings("unchecked")
1365 String url = (history instanceof JTextField
1366 ? ((JTextField) history).getText()
1367 : ((JComboBox<String>) history).getEditor().getItem()
1368 .toString().trim());
1370 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1372 if (viewport != null)
1374 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1375 FileFormat.Jalview);
1379 new FileLoader().LoadFile(url, DataSourceType.URL,
1380 FileFormat.Jalview);
1385 FileFormatI format = null;
1388 format = new IdentifyFile().identify(url, DataSourceType.URL);
1389 } catch (FileFormatException e)
1391 // TODO revise error handling, distinguish between
1392 // URL not found and response not valid
1397 String msg = MessageManager.formatMessage("label.couldnt_locate",
1399 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1400 MessageManager.getString("label.url_not_found"),
1401 JvOptionPane.WARNING_MESSAGE);
1405 if (viewport != null)
1407 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1412 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1416 String dialogOption = MessageManager
1417 .getString("label.input_alignment_from_url");
1418 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1419 .showInternalDialog(panel, dialogOption,
1420 JvOptionPane.YES_NO_CANCEL_OPTION,
1421 JvOptionPane.PLAIN_MESSAGE, null, options,
1422 MessageManager.getString("action.ok"));
1426 * Opens the CutAndPaste window for the user to paste an alignment in to
1429 * - if not null, the pasted alignment is added to the current
1430 * alignment; if null, to a new alignment window
1433 public void inputTextboxMenuItem_actionPerformed(
1434 AlignmentViewPanel viewPanel)
1436 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1437 cap.setForInput(viewPanel);
1438 Desktop.addInternalFrame(cap,
1439 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1444 * Check with user and saving files before actually quitting
1446 public void desktopQuit()
1448 desktopQuit(true, false);
1451 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1453 final Runnable doDesktopQuit = () -> {
1454 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1455 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1456 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1457 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1458 getBounds().y, getWidth(), getHeight()));
1460 if (jconsole != null)
1462 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1463 jconsole.stopConsole();
1468 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1471 // Frames should all close automatically. Keeping external
1472 // viewers open should already be decided by user.
1473 closeAll_actionPerformed(null);
1475 // check for aborted quit
1476 if (QuitHandler.quitCancelled())
1478 jalview.bin.Console.debug("Desktop aborting quit");
1482 if (dialogExecutor != null)
1484 dialogExecutor.shutdownNow();
1487 if (groovyConsole != null)
1489 // suppress a possible repeat prompt to save script
1490 groovyConsole.setDirty(false);
1491 groovyConsole.exit();
1494 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1496 // note that shutdown hook will not be run
1497 jalview.bin.Console.debug("Force Quit selected by user");
1498 Runtime.getRuntime().halt(0);
1501 jalview.bin.Console.debug("Quit selected by user");
1504 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1505 // instance.dispose();
1510 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1511 QuitHandler.defaultCancelQuit);
1515 * Don't call this directly, use desktopQuit() above. Exits the program.
1520 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1521 // not run a second time if gotQuitResponse flag has been set (i.e. user
1522 // confirmed quit of some kind).
1523 Jalview.exit("Desktop exiting.", 0);
1526 private void storeLastKnownDimensions(String string, Rectangle jc)
1528 jalview.bin.Console.debug("Storing last known dimensions for " + string
1529 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1530 + " height:" + jc.height);
1532 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1533 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1534 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1535 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1545 public void aboutMenuItem_actionPerformed(ActionEvent e)
1547 new Thread(new Runnable()
1552 new SplashScreen(false);
1558 * Returns the html text for the About screen, including any available version
1559 * number, build details, author details and citation reference, but without
1560 * the enclosing {@code html} tags
1564 public String getAboutMessage()
1566 StringBuilder message = new StringBuilder(1024);
1567 message.append("<div style=\"font-family: sans-serif;\">")
1568 .append("<h1><strong>Version: ")
1569 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1570 .append("<strong>Built: <em>")
1571 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1572 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1573 .append("</strong>");
1575 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1576 if (latestVersion.equals("Checking"))
1578 // JBP removed this message for 2.11: May be reinstated in future version
1579 // message.append("<br>...Checking latest version...</br>");
1581 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1583 boolean red = false;
1584 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1585 .indexOf("automated build") == -1)
1588 // Displayed when code version and jnlp version do not match and code
1589 // version is not a development build
1590 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1593 message.append("<br>!! Version ")
1594 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1595 .append(" is available for download from ")
1596 .append(Cache.getDefault("www.jalview.org",
1597 "https://www.jalview.org"))
1601 message.append("</div>");
1604 message.append("<br>Authors: ");
1605 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1606 message.append(CITATION);
1608 message.append("</div>");
1610 return message.toString();
1614 * Action on requesting Help documentation
1617 public void documentationMenuItem_actionPerformed()
1621 if (Platform.isJS())
1623 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1632 Help.showHelpWindow();
1634 } catch (Exception ex)
1636 jalview.bin.Console.errPrintln("Error opening help: " + ex.getMessage());
1641 public void closeAll_actionPerformed(ActionEvent e)
1643 // TODO show a progress bar while closing?
1644 JInternalFrame[] frames = desktop.getAllFrames();
1645 for (int i = 0; i < frames.length; i++)
1649 frames[i].setClosed(true);
1650 } catch (java.beans.PropertyVetoException ex)
1654 Jalview.setCurrentAlignFrame(null);
1655 jalview.bin.Console.info("ALL CLOSED");
1658 * reset state of singleton objects as appropriate (clear down session state
1659 * when all windows are closed)
1661 StructureSelectionManager ssm = StructureSelectionManager
1662 .getStructureSelectionManager(this);
1669 public int structureViewersStillRunningCount()
1672 JInternalFrame[] frames = desktop.getAllFrames();
1673 for (int i = 0; i < frames.length; i++)
1675 if (frames[i] != null
1676 && frames[i] instanceof JalviewStructureDisplayI)
1678 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1686 public void raiseRelated_actionPerformed(ActionEvent e)
1688 reorderAssociatedWindows(false, false);
1692 public void minimizeAssociated_actionPerformed(ActionEvent e)
1694 reorderAssociatedWindows(true, false);
1697 void closeAssociatedWindows()
1699 reorderAssociatedWindows(false, true);
1705 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1709 protected void garbageCollect_actionPerformed(ActionEvent e)
1711 // We simply collect the garbage
1712 jalview.bin.Console.debug("Collecting garbage...");
1714 jalview.bin.Console.debug("Finished garbage collection.");
1720 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1724 protected void showMemusage_actionPerformed(ActionEvent e)
1726 desktop.showMemoryUsage(showMemusage.isSelected());
1733 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1737 protected void showConsole_actionPerformed(ActionEvent e)
1739 showConsole(showConsole.isSelected());
1742 Console jconsole = null;
1745 * control whether the java console is visible or not
1749 void showConsole(boolean selected)
1751 // TODO: decide if we should update properties file
1752 if (jconsole != null) // BH 2018
1754 showConsole.setSelected(selected);
1755 Cache.setProperty("SHOW_JAVA_CONSOLE",
1756 Boolean.valueOf(selected).toString());
1757 jconsole.setVisible(selected);
1761 void reorderAssociatedWindows(boolean minimize, boolean close)
1763 JInternalFrame[] frames = desktop.getAllFrames();
1764 if (frames == null || frames.length < 1)
1769 AlignmentViewport source = null, target = null;
1770 if (frames[0] instanceof AlignFrame)
1772 source = ((AlignFrame) frames[0]).getCurrentView();
1774 else if (frames[0] instanceof TreePanel)
1776 source = ((TreePanel) frames[0]).getViewPort();
1778 else if (frames[0] instanceof PCAPanel)
1780 source = ((PCAPanel) frames[0]).av;
1782 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1784 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1789 for (int i = 0; i < frames.length; i++)
1792 if (frames[i] == null)
1796 if (frames[i] instanceof AlignFrame)
1798 target = ((AlignFrame) frames[i]).getCurrentView();
1800 else if (frames[i] instanceof TreePanel)
1802 target = ((TreePanel) frames[i]).getViewPort();
1804 else if (frames[i] instanceof PCAPanel)
1806 target = ((PCAPanel) frames[i]).av;
1808 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1810 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1813 if (source == target)
1819 frames[i].setClosed(true);
1823 frames[i].setIcon(minimize);
1826 frames[i].toFront();
1830 } catch (java.beans.PropertyVetoException ex)
1845 protected void preferences_actionPerformed(ActionEvent e)
1847 Preferences.openPreferences();
1851 * Prompts the user to choose a file and then saves the Jalview state as a
1852 * Jalview project file
1855 public void saveState_actionPerformed()
1857 saveState_actionPerformed(false);
1860 public void saveState_actionPerformed(boolean saveAs)
1862 java.io.File projectFile = getProjectFile();
1863 // autoSave indicates we already have a file and don't need to ask
1864 boolean autoSave = projectFile != null && !saveAs
1865 && BackupFiles.getEnabled();
1867 // jalview.bin.Console.outPrintln("autoSave="+autoSave+", projectFile='"+projectFile+"',
1868 // saveAs="+saveAs+", Backups
1869 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1871 boolean approveSave = false;
1874 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1877 chooser.setFileView(new JalviewFileView());
1878 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1880 int value = chooser.showSaveDialog(this);
1882 if (value == JalviewFileChooser.APPROVE_OPTION)
1884 projectFile = chooser.getSelectedFile();
1885 setProjectFile(projectFile);
1890 if (approveSave || autoSave)
1892 final Desktop me = this;
1893 final java.io.File chosenFile = projectFile;
1894 new Thread(new Runnable()
1899 // TODO: refactor to Jalview desktop session controller action.
1900 setProgressBar(MessageManager.formatMessage(
1901 "label.saving_jalview_project", new Object[]
1902 { chosenFile.getName() }), chosenFile.hashCode());
1903 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1904 // TODO catch and handle errors for savestate
1905 // TODO prevent user from messing with the Desktop whilst we're saving
1908 boolean doBackup = BackupFiles.getEnabled();
1909 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1912 new Jalview2XML().saveState(
1913 doBackup ? backupfiles.getTempFile() : chosenFile);
1917 backupfiles.setWriteSuccess(true);
1918 backupfiles.rollBackupsAndRenameTempFile();
1920 } catch (OutOfMemoryError oom)
1922 new OOMWarning("Whilst saving current state to "
1923 + chosenFile.getName(), oom);
1924 } catch (Exception ex)
1926 jalview.bin.Console.error("Problems whilst trying to save to "
1927 + chosenFile.getName(), ex);
1928 JvOptionPane.showMessageDialog(me,
1929 MessageManager.formatMessage(
1930 "label.error_whilst_saving_current_state_to",
1932 { chosenFile.getName() }),
1933 MessageManager.getString("label.couldnt_save_project"),
1934 JvOptionPane.WARNING_MESSAGE);
1936 setProgressBar(null, chosenFile.hashCode());
1943 public void saveAsState_actionPerformed(ActionEvent e)
1945 saveState_actionPerformed(true);
1948 protected void setProjectFile(File choice)
1950 this.projectFile = choice;
1953 public File getProjectFile()
1955 return this.projectFile;
1959 * Shows a file chooser dialog and tries to read in the selected file as a
1963 public void loadState_actionPerformed()
1965 final String[] suffix = new String[] { "jvp", "jar" };
1966 final String[] desc = new String[] { "Jalview Project",
1967 "Jalview Project (old)" };
1968 JalviewFileChooser chooser = new JalviewFileChooser(
1969 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1970 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1974 chooser.setFileView(new JalviewFileView());
1975 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1976 chooser.setResponseHandler(0, () -> {
1977 File selectedFile = chooser.getSelectedFile();
1978 setProjectFile(selectedFile);
1979 String choice = selectedFile.getAbsolutePath();
1980 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1981 new Thread(new Runnable()
1988 new Jalview2XML().loadJalviewAlign(selectedFile);
1989 } catch (OutOfMemoryError oom)
1991 new OOMWarning("Whilst loading project from " + choice, oom);
1992 } catch (Exception ex)
1994 jalview.bin.Console.error(
1995 "Problems whilst loading project from " + choice, ex);
1996 JvOptionPane.showMessageDialog(Desktop.desktop,
1997 MessageManager.formatMessage(
1998 "label.error_whilst_loading_project_from",
2001 MessageManager.getString("label.couldnt_load_project"),
2002 JvOptionPane.WARNING_MESSAGE);
2005 }, "Project Loader").start();
2008 chooser.showOpenDialog(this);
2012 public void inputSequence_actionPerformed(ActionEvent e)
2014 new SequenceFetcher(this);
2017 JPanel progressPanel;
2019 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2021 public void startLoading(final Object fileName)
2023 if (fileLoadingCount == 0)
2025 fileLoadingPanels.add(addProgressPanel(MessageManager
2026 .formatMessage("label.loading_file", new Object[]
2032 private JPanel addProgressPanel(String string)
2034 if (progressPanel == null)
2036 progressPanel = new JPanel(new GridLayout(1, 1));
2037 totalProgressCount = 0;
2038 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2040 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2041 JProgressBar progressBar = new JProgressBar();
2042 progressBar.setIndeterminate(true);
2044 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2046 thisprogress.add(progressBar, BorderLayout.CENTER);
2047 progressPanel.add(thisprogress);
2048 ((GridLayout) progressPanel.getLayout()).setRows(
2049 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2050 ++totalProgressCount;
2051 instance.validate();
2052 return thisprogress;
2055 int totalProgressCount = 0;
2057 private void removeProgressPanel(JPanel progbar)
2059 if (progressPanel != null)
2061 synchronized (progressPanel)
2063 progressPanel.remove(progbar);
2064 GridLayout gl = (GridLayout) progressPanel.getLayout();
2065 gl.setRows(gl.getRows() - 1);
2066 if (--totalProgressCount < 1)
2068 this.getContentPane().remove(progressPanel);
2069 progressPanel = null;
2076 public void stopLoading()
2079 if (fileLoadingCount < 1)
2081 while (fileLoadingPanels.size() > 0)
2083 removeProgressPanel(fileLoadingPanels.remove(0));
2085 fileLoadingPanels.clear();
2086 fileLoadingCount = 0;
2091 public static int getViewCount(String alignmentId)
2093 AlignmentViewport[] aps = getViewports(alignmentId);
2094 return (aps == null) ? 0 : aps.length;
2099 * @param alignmentId
2100 * - if null, all sets are returned
2101 * @return all AlignmentPanels concerning the alignmentId sequence set
2103 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2105 if (Desktop.desktop == null)
2107 // no frames created and in headless mode
2108 // TODO: verify that frames are recoverable when in headless mode
2111 List<AlignmentPanel> aps = new ArrayList<>();
2112 AlignFrame[] frames = getAlignFrames();
2117 for (AlignFrame af : frames)
2119 for (AlignmentPanel ap : af.alignPanels)
2121 if (alignmentId == null
2122 || alignmentId.equals(ap.av.getSequenceSetId()))
2128 if (aps.size() == 0)
2132 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2137 * get all the viewports on an alignment.
2139 * @param sequenceSetId
2140 * unique alignment id (may be null - all viewports returned in that
2142 * @return all viewports on the alignment bound to sequenceSetId
2144 public static AlignmentViewport[] getViewports(String sequenceSetId)
2146 List<AlignmentViewport> viewp = new ArrayList<>();
2147 if (desktop != null)
2149 AlignFrame[] frames = Desktop.getAlignFrames();
2151 for (AlignFrame afr : frames)
2153 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2154 .equals(sequenceSetId))
2156 if (afr.alignPanels != null)
2158 for (AlignmentPanel ap : afr.alignPanels)
2160 if (sequenceSetId == null
2161 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2169 viewp.add(afr.getViewport());
2173 if (viewp.size() > 0)
2175 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2182 * Explode the views in the given frame into separate AlignFrame
2186 public static void explodeViews(AlignFrame af)
2188 int size = af.alignPanels.size();
2194 // FIXME: ideally should use UI interface API
2195 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2196 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2197 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2198 for (int i = 0; i < size; i++)
2200 AlignmentPanel ap = af.alignPanels.get(i);
2202 AlignFrame newaf = new AlignFrame(ap);
2204 // transfer reference for existing feature settings to new alignFrame
2205 if (ap == af.alignPanel)
2207 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2209 newaf.featureSettings = viewFeatureSettings;
2211 newaf.setFeatureSettingsGeometry(fsBounds);
2215 * Restore the view's last exploded frame geometry if known. Multiple views from
2216 * one exploded frame share and restore the same (frame) position and size.
2218 Rectangle geometry = ap.av.getExplodedGeometry();
2219 if (geometry != null)
2221 newaf.setBounds(geometry);
2224 ap.av.setGatherViewsHere(false);
2226 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2227 AlignFrame.DEFAULT_HEIGHT);
2228 // and materialise a new feature settings dialog instance for the new
2230 // (closes the old as if 'OK' was pressed)
2231 if (ap == af.alignPanel && newaf.featureSettings != null
2232 && newaf.featureSettings.isOpen()
2233 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2235 newaf.showFeatureSettingsUI();
2239 af.featureSettings = null;
2240 af.alignPanels.clear();
2241 af.closeMenuItem_actionPerformed(true);
2246 * Gather expanded views (separate AlignFrame's) with the same sequence set
2247 * identifier back in to this frame as additional views, and close the
2248 * expanded views. Note the expanded frames may themselves have multiple
2249 * views. We take the lot.
2253 public void gatherViews(AlignFrame source)
2255 source.viewport.setGatherViewsHere(true);
2256 source.viewport.setExplodedGeometry(source.getBounds());
2257 JInternalFrame[] frames = desktop.getAllFrames();
2258 String viewId = source.viewport.getSequenceSetId();
2259 for (int t = 0; t < frames.length; t++)
2261 if (frames[t] instanceof AlignFrame && frames[t] != source)
2263 AlignFrame af = (AlignFrame) frames[t];
2264 boolean gatherThis = false;
2265 for (int a = 0; a < af.alignPanels.size(); a++)
2267 AlignmentPanel ap = af.alignPanels.get(a);
2268 if (viewId.equals(ap.av.getSequenceSetId()))
2271 ap.av.setGatherViewsHere(false);
2272 ap.av.setExplodedGeometry(af.getBounds());
2273 source.addAlignmentPanel(ap, false);
2279 if (af.featureSettings != null && af.featureSettings.isOpen())
2281 if (source.featureSettings == null)
2283 // preserve the feature settings geometry for this frame
2284 source.featureSettings = af.featureSettings;
2285 source.setFeatureSettingsGeometry(
2286 af.getFeatureSettingsGeometry());
2290 // close it and forget
2291 af.featureSettings.close();
2294 af.alignPanels.clear();
2295 af.closeMenuItem_actionPerformed(true);
2300 // refresh the feature setting UI for the source frame if it exists
2301 if (source.featureSettings != null && source.featureSettings.isOpen())
2303 source.showFeatureSettingsUI();
2308 public JInternalFrame[] getAllFrames()
2310 return desktop.getAllFrames();
2314 * Checks the given url to see if it gives a response indicating that the user
2315 * should be informed of a new questionnaire.
2319 public void checkForQuestionnaire(String url)
2321 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2322 // javax.swing.SwingUtilities.invokeLater(jvq);
2323 new Thread(jvq).start();
2326 public void checkURLLinks()
2328 // Thread off the URL link checker
2329 addDialogThread(new Runnable()
2334 if (Cache.getDefault("CHECKURLLINKS", true))
2336 // check what the actual links are - if it's just the default don't
2337 // bother with the warning
2338 List<String> links = Preferences.sequenceUrlLinks
2341 // only need to check links if there is one with a
2342 // SEQUENCE_ID which is not the default EMBL_EBI link
2343 ListIterator<String> li = links.listIterator();
2344 boolean check = false;
2345 List<JLabel> urls = new ArrayList<>();
2346 while (li.hasNext())
2348 String link = li.next();
2349 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2350 && !UrlConstants.isDefaultString(link))
2353 int barPos = link.indexOf("|");
2354 String urlMsg = barPos == -1 ? link
2355 : link.substring(0, barPos) + ": "
2356 + link.substring(barPos + 1);
2357 urls.add(new JLabel(urlMsg));
2365 // ask user to check in case URL links use old style tokens
2366 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2367 JPanel msgPanel = new JPanel();
2368 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2369 msgPanel.add(Box.createVerticalGlue());
2370 JLabel msg = new JLabel(MessageManager
2371 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2372 JLabel msg2 = new JLabel(MessageManager
2373 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2375 for (JLabel url : urls)
2381 final JCheckBox jcb = new JCheckBox(
2382 MessageManager.getString("label.do_not_display_again"));
2383 jcb.addActionListener(new ActionListener()
2386 public void actionPerformed(ActionEvent e)
2388 // update Cache settings for "don't show this again"
2389 boolean showWarningAgain = !jcb.isSelected();
2390 Cache.setProperty("CHECKURLLINKS",
2391 Boolean.valueOf(showWarningAgain).toString());
2396 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2398 .getString("label.SEQUENCE_ID_no_longer_used"),
2399 JvOptionPane.WARNING_MESSAGE);
2406 * Proxy class for JDesktopPane which optionally displays the current memory
2407 * usage and highlights the desktop area with a red bar if free memory runs
2412 public class MyDesktopPane extends JDesktopPane implements Runnable
2414 private static final float ONE_MB = 1048576f;
2416 boolean showMemoryUsage = false;
2420 java.text.NumberFormat df;
2422 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2425 public MyDesktopPane(boolean showMemoryUsage)
2427 showMemoryUsage(showMemoryUsage);
2430 public void showMemoryUsage(boolean showMemory)
2432 this.showMemoryUsage = showMemory;
2435 Thread worker = new Thread(this);
2441 public boolean isShowMemoryUsage()
2443 return showMemoryUsage;
2449 df = java.text.NumberFormat.getNumberInstance();
2450 df.setMaximumFractionDigits(2);
2451 runtime = Runtime.getRuntime();
2453 while (showMemoryUsage)
2457 maxMemory = runtime.maxMemory() / ONE_MB;
2458 allocatedMemory = runtime.totalMemory() / ONE_MB;
2459 freeMemory = runtime.freeMemory() / ONE_MB;
2460 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2462 percentUsage = (totalFreeMemory / maxMemory) * 100;
2464 // if (percentUsage < 20)
2466 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2468 // instance.set.setBorder(border1);
2471 // sleep after showing usage
2473 } catch (Exception ex)
2475 ex.printStackTrace();
2481 public void paintComponent(Graphics g)
2483 if (showMemoryUsage && g != null && df != null)
2485 if (percentUsage < 20)
2487 g.setColor(Color.red);
2489 FontMetrics fm = g.getFontMetrics();
2492 g.drawString(MessageManager.formatMessage("label.memory_stats",
2494 { df.format(totalFreeMemory), df.format(maxMemory),
2495 df.format(percentUsage) }),
2496 10, getHeight() - fm.getHeight());
2500 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2501 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2506 * Accessor method to quickly get all the AlignmentFrames loaded.
2508 * @return an array of AlignFrame, or null if none found
2510 public static AlignFrame[] getAlignFrames()
2512 if (Jalview.isHeadlessMode())
2514 // Desktop.desktop is null in headless mode
2515 return new AlignFrame[] { Jalview.currentAlignFrame };
2518 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2524 List<AlignFrame> avp = new ArrayList<>();
2526 for (int i = frames.length - 1; i > -1; i--)
2528 if (frames[i] instanceof AlignFrame)
2530 avp.add((AlignFrame) frames[i]);
2532 else if (frames[i] instanceof SplitFrame)
2535 * Also check for a split frame containing an AlignFrame
2537 GSplitFrame sf = (GSplitFrame) frames[i];
2538 if (sf.getTopFrame() instanceof AlignFrame)
2540 avp.add((AlignFrame) sf.getTopFrame());
2542 if (sf.getBottomFrame() instanceof AlignFrame)
2544 avp.add((AlignFrame) sf.getBottomFrame());
2548 if (avp.size() == 0)
2552 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2557 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2561 public GStructureViewer[] getJmols()
2563 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2569 List<GStructureViewer> avp = new ArrayList<>();
2571 for (int i = frames.length - 1; i > -1; i--)
2573 if (frames[i] instanceof AppJmol)
2575 GStructureViewer af = (GStructureViewer) frames[i];
2579 if (avp.size() == 0)
2583 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2588 * Add Groovy Support to Jalview
2591 public void groovyShell_actionPerformed()
2595 openGroovyConsole();
2596 } catch (Exception ex)
2598 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2599 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2601 MessageManager.getString("label.couldnt_create_groovy_shell"),
2602 MessageManager.getString("label.groovy_support_failed"),
2603 JvOptionPane.ERROR_MESSAGE);
2608 * Open the Groovy console
2610 void openGroovyConsole()
2612 if (groovyConsole == null)
2614 groovyConsole = new groovy.ui.Console();
2615 groovyConsole.setVariable("Jalview", this);
2616 groovyConsole.run();
2619 * We allow only one console at a time, so that AlignFrame menu option
2620 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2621 * enable 'Run script', when the console is opened, and the reverse when it is
2624 Window window = (Window) groovyConsole.getFrame();
2625 window.addWindowListener(new WindowAdapter()
2628 public void windowClosed(WindowEvent e)
2631 * rebind CMD-Q from Groovy Console to Jalview Quit
2634 enableExecuteGroovy(false);
2640 * show Groovy console window (after close and reopen)
2642 ((Window) groovyConsole.getFrame()).setVisible(true);
2645 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2646 * opening a second console
2648 enableExecuteGroovy(true);
2652 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2653 * binding when opened
2655 protected void addQuitHandler()
2658 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2660 .getKeyStroke(KeyEvent.VK_Q,
2661 jalview.util.ShortcutKeyMaskExWrapper
2662 .getMenuShortcutKeyMaskEx()),
2664 getRootPane().getActionMap().put("Quit", new AbstractAction()
2667 public void actionPerformed(ActionEvent e)
2675 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2678 * true if Groovy console is open
2680 public void enableExecuteGroovy(boolean enabled)
2683 * disable opening a second Groovy console (or re-enable when the console is
2686 groovyShell.setEnabled(!enabled);
2688 AlignFrame[] alignFrames = getAlignFrames();
2689 if (alignFrames != null)
2691 for (AlignFrame af : alignFrames)
2693 af.setGroovyEnabled(enabled);
2699 * Progress bars managed by the IProgressIndicator method.
2701 private Hashtable<Long, JPanel> progressBars;
2703 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2708 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2711 public void setProgressBar(String message, long id)
2713 if (progressBars == null)
2715 progressBars = new Hashtable<>();
2716 progressBarHandlers = new Hashtable<>();
2719 if (progressBars.get(Long.valueOf(id)) != null)
2721 JPanel panel = progressBars.remove(Long.valueOf(id));
2722 if (progressBarHandlers.contains(Long.valueOf(id)))
2724 progressBarHandlers.remove(Long.valueOf(id));
2726 removeProgressPanel(panel);
2730 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2737 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2738 * jalview.gui.IProgressIndicatorHandler)
2741 public void registerHandler(final long id,
2742 final IProgressIndicatorHandler handler)
2744 if (progressBarHandlers == null
2745 || !progressBars.containsKey(Long.valueOf(id)))
2747 throw new Error(MessageManager.getString(
2748 "error.call_setprogressbar_before_registering_handler"));
2750 progressBarHandlers.put(Long.valueOf(id), handler);
2751 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2752 if (handler.canCancel())
2754 JButton cancel = new JButton(
2755 MessageManager.getString("action.cancel"));
2756 final IProgressIndicator us = this;
2757 cancel.addActionListener(new ActionListener()
2761 public void actionPerformed(ActionEvent e)
2763 handler.cancelActivity(id);
2764 us.setProgressBar(MessageManager
2765 .formatMessage("label.cancelled_params", new Object[]
2766 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2770 progressPanel.add(cancel, BorderLayout.EAST);
2776 * @return true if any progress bars are still active
2779 public boolean operationInProgress()
2781 if (progressBars != null && progressBars.size() > 0)
2789 * This will return the first AlignFrame holding the given viewport instance.
2790 * It will break if there are more than one AlignFrames viewing a particular
2794 * @return alignFrame for viewport
2796 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2798 if (desktop != null)
2800 AlignmentPanel[] aps = getAlignmentPanels(
2801 viewport.getSequenceSetId());
2802 for (int panel = 0; aps != null && panel < aps.length; panel++)
2804 if (aps[panel] != null && aps[panel].av == viewport)
2806 return aps[panel].alignFrame;
2813 public VamsasApplication getVamsasApplication()
2815 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2821 * flag set if jalview GUI is being operated programmatically
2823 private boolean inBatchMode = false;
2826 * check if jalview GUI is being operated programmatically
2828 * @return inBatchMode
2830 public boolean isInBatchMode()
2836 * set flag if jalview GUI is being operated programmatically
2838 * @param inBatchMode
2840 public void setInBatchMode(boolean inBatchMode)
2842 this.inBatchMode = inBatchMode;
2846 * start service discovery and wait till it is done
2848 public void startServiceDiscovery()
2850 startServiceDiscovery(false);
2854 * start service discovery threads - blocking or non-blocking
2858 public void startServiceDiscovery(boolean blocking)
2860 startServiceDiscovery(blocking, false);
2864 * start service discovery threads
2867 * - false means call returns immediately
2868 * @param ignore_SHOW_JWS2_SERVICES_preference
2869 * - when true JABA services are discovered regardless of user's JWS2
2870 * discovery preference setting
2872 public void startServiceDiscovery(boolean blocking,
2873 boolean ignore_SHOW_JWS2_SERVICES_preference)
2875 boolean alive = true;
2876 Thread t0 = null, t1 = null, t2 = null;
2877 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2880 // todo: changesupport handlers need to be transferred
2881 if (discoverer == null)
2883 discoverer = new jalview.ws.jws1.Discoverer();
2884 // register PCS handler for desktop.
2885 discoverer.addPropertyChangeListener(changeSupport);
2887 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2888 // until we phase out completely
2889 (t0 = new Thread(discoverer)).start();
2892 if (ignore_SHOW_JWS2_SERVICES_preference
2893 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2895 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2896 .startDiscoverer(changeSupport);
2900 // TODO: do rest service discovery
2909 } catch (Exception e)
2912 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2913 || (t3 != null && t3.isAlive())
2914 || (t0 != null && t0.isAlive());
2920 * called to check if the service discovery process completed successfully.
2924 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2926 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2928 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2929 .getErrorMessages();
2932 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2934 if (serviceChangedDialog == null)
2936 // only run if we aren't already displaying one of these.
2937 addDialogThread(serviceChangedDialog = new Runnable()
2944 * JalviewDialog jd =new JalviewDialog() {
2946 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2948 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2950 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2952 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2954 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2955 * + " or mis-configured HTTP proxy settings.<br/>" +
2956 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2957 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2958 * true, true, "Web Service Configuration Problem", 450, 400);
2960 * jd.waitForInput();
2962 JvOptionPane.showConfirmDialog(Desktop.desktop,
2963 new JLabel("<html><table width=\"450\"><tr><td>"
2964 + ermsg + "</td></tr></table>"
2965 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2966 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2967 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2968 + " Tools->Preferences dialog box to change them.</p></html>"),
2969 "Web Service Configuration Problem",
2970 JvOptionPane.DEFAULT_OPTION,
2971 JvOptionPane.ERROR_MESSAGE);
2972 serviceChangedDialog = null;
2980 jalview.bin.Console.error(
2981 "Errors reported by JABA discovery service. Check web services preferences.\n"
2988 private Runnable serviceChangedDialog = null;
2991 * start a thread to open a URL in the configured browser. Pops up a warning
2992 * dialog to the user if there is an exception when calling out to the browser
2997 public static void showUrl(final String url)
2999 showUrl(url, Desktop.instance);
3003 * Like showUrl but allows progress handler to be specified
3007 * (null) or object implementing IProgressIndicator
3009 public static void showUrl(final String url,
3010 final IProgressIndicator progress)
3012 new Thread(new Runnable()
3019 if (progress != null)
3021 progress.setProgressBar(MessageManager
3022 .formatMessage("status.opening_params", new Object[]
3023 { url }), this.hashCode());
3025 jalview.util.BrowserLauncher.openURL(url);
3026 } catch (Exception ex)
3028 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3030 .getString("label.web_browser_not_found_unix"),
3031 MessageManager.getString("label.web_browser_not_found"),
3032 JvOptionPane.WARNING_MESSAGE);
3034 ex.printStackTrace();
3036 if (progress != null)
3038 progress.setProgressBar(null, this.hashCode());
3044 public static WsParamSetManager wsparamManager = null;
3046 public static ParamManager getUserParameterStore()
3048 if (wsparamManager == null)
3050 wsparamManager = new WsParamSetManager();
3052 return wsparamManager;
3056 * static hyperlink handler proxy method for use by Jalview's internal windows
3060 public static void hyperlinkUpdate(HyperlinkEvent e)
3062 if (e.getEventType() == EventType.ACTIVATED)
3067 url = e.getURL().toString();
3068 Desktop.showUrl(url);
3069 } catch (Exception x)
3074 .error("Couldn't handle string " + url + " as a URL.");
3076 // ignore any exceptions due to dud links.
3083 * single thread that handles display of dialogs to user.
3085 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3088 * flag indicating if dialogExecutor should try to acquire a permit
3090 private volatile boolean dialogPause = true;
3095 private Semaphore block = new Semaphore(0);
3097 private static groovy.ui.Console groovyConsole;
3100 * add another dialog thread to the queue
3104 public void addDialogThread(final Runnable prompter)
3106 dialogExecutor.submit(new Runnable()
3113 acquireDialogQueue();
3115 if (instance == null)
3121 SwingUtilities.invokeAndWait(prompter);
3122 } catch (Exception q)
3124 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3131 private boolean dialogQueueStarted = false;
3133 public void startDialogQueue()
3135 if (dialogQueueStarted)
3139 // set the flag so we don't pause waiting for another permit and semaphore
3140 // the current task to begin
3141 releaseDialogQueue();
3142 dialogQueueStarted = true;
3145 public void acquireDialogQueue()
3151 } catch (InterruptedException e)
3153 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3158 public void releaseDialogQueue()
3165 dialogPause = false;
3169 * Outputs an image of the desktop to file in EPS format, after prompting the
3170 * user for choice of Text or Lineart character rendering (unless a preference
3171 * has been set). The file name is generated as
3174 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3178 protected void snapShotWindow_actionPerformed(ActionEvent e)
3180 // currently the menu option to do this is not shown
3183 int width = getWidth();
3184 int height = getHeight();
3186 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3187 ImageWriterI writer = new ImageWriterI()
3190 public void exportImage(Graphics g) throws Exception
3193 jalview.bin.Console.info("Successfully written snapshot to file "
3194 + of.getAbsolutePath());
3197 String title = "View of desktop";
3198 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3202 exporter.doExport(of, this, width, height, title);
3203 } catch (ImageOutputException ioex)
3205 jalview.bin.Console.error(
3206 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3212 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3213 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3214 * and location last time the view was expanded (if any). However it does not
3215 * remember the split pane divider location - this is set to match the
3216 * 'exploding' frame.
3220 public void explodeViews(SplitFrame sf)
3222 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3223 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3224 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3226 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3228 int viewCount = topPanels.size();
3235 * Processing in reverse order works, forwards order leaves the first panels not
3236 * visible. I don't know why!
3238 for (int i = viewCount - 1; i >= 0; i--)
3241 * Make new top and bottom frames. These take over the respective AlignmentPanel
3242 * objects, including their AlignmentViewports, so the cdna/protein
3243 * relationships between the viewports is carried over to the new split frames.
3245 * explodedGeometry holds the (x, y) position of the previously exploded
3246 * SplitFrame, and the (width, height) of the AlignFrame component
3248 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3249 AlignFrame newTopFrame = new AlignFrame(topPanel);
3250 newTopFrame.setSize(oldTopFrame.getSize());
3251 newTopFrame.setVisible(true);
3252 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3253 .getExplodedGeometry();
3254 if (geometry != null)
3256 newTopFrame.setSize(geometry.getSize());
3259 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3260 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3261 newBottomFrame.setSize(oldBottomFrame.getSize());
3262 newBottomFrame.setVisible(true);
3263 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3264 .getExplodedGeometry();
3265 if (geometry != null)
3267 newBottomFrame.setSize(geometry.getSize());
3270 topPanel.av.setGatherViewsHere(false);
3271 bottomPanel.av.setGatherViewsHere(false);
3272 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3274 if (geometry != null)
3276 splitFrame.setLocation(geometry.getLocation());
3278 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3282 * Clear references to the panels (now relocated in the new SplitFrames) before
3283 * closing the old SplitFrame.
3286 bottomPanels.clear();
3291 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3292 * back into the given SplitFrame as additional views. Note that the gathered
3293 * frames may themselves have multiple views.
3297 public void gatherViews(GSplitFrame source)
3300 * special handling of explodedGeometry for a view within a SplitFrame: - it
3301 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3302 * height) of the AlignFrame component
3304 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3305 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3306 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3307 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3308 myBottomFrame.viewport
3309 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3310 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3311 myTopFrame.viewport.setGatherViewsHere(true);
3312 myBottomFrame.viewport.setGatherViewsHere(true);
3313 String topViewId = myTopFrame.viewport.getSequenceSetId();
3314 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3316 JInternalFrame[] frames = desktop.getAllFrames();
3317 for (JInternalFrame frame : frames)
3319 if (frame instanceof SplitFrame && frame != source)
3321 SplitFrame sf = (SplitFrame) frame;
3322 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3323 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3324 boolean gatherThis = false;
3325 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3327 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3328 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3329 if (topViewId.equals(topPanel.av.getSequenceSetId())
3330 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3333 topPanel.av.setGatherViewsHere(false);
3334 bottomPanel.av.setGatherViewsHere(false);
3335 topPanel.av.setExplodedGeometry(
3336 new Rectangle(sf.getLocation(), topFrame.getSize()));
3337 bottomPanel.av.setExplodedGeometry(
3338 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3339 myTopFrame.addAlignmentPanel(topPanel, false);
3340 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3346 topFrame.getAlignPanels().clear();
3347 bottomFrame.getAlignPanels().clear();
3354 * The dust settles...give focus to the tab we did this from.
3356 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3359 public static groovy.ui.Console getGroovyConsole()
3361 return groovyConsole;
3365 * handles the payload of a drag and drop event.
3367 * TODO refactor to desktop utilities class
3370 * - Data source strings extracted from the drop event
3372 * - protocol for each data source extracted from the drop event
3376 * - the payload from the drop event
3379 public static void transferFromDropTarget(List<Object> files,
3380 List<DataSourceType> protocols, DropTargetDropEvent evt,
3381 Transferable t) throws Exception
3384 DataFlavor uriListFlavor = new DataFlavor(
3385 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3388 urlFlavour = new DataFlavor(
3389 "application/x-java-url; class=java.net.URL");
3390 } catch (ClassNotFoundException cfe)
3392 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3396 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3401 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3402 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3403 // means url may be null.
3406 protocols.add(DataSourceType.URL);
3407 files.add(url.toString());
3408 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3409 + files.get(files.size() - 1));
3414 if (Platform.isAMacAndNotJS())
3416 jalview.bin.Console.errPrintln(
3417 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3420 } catch (Throwable ex)
3422 jalview.bin.Console.debug("URL drop handler failed.", ex);
3425 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3427 // Works on Windows and MacOSX
3428 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3429 for (Object file : (List) t
3430 .getTransferData(DataFlavor.javaFileListFlavor))
3433 protocols.add(DataSourceType.FILE);
3438 // Unix like behaviour
3439 boolean added = false;
3441 if (t.isDataFlavorSupported(uriListFlavor))
3443 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3444 // This is used by Unix drag system
3445 data = (String) t.getTransferData(uriListFlavor);
3449 // fallback to text: workaround - on OSX where there's a JVM bug
3451 .debug("standard URIListFlavor failed. Trying text");
3452 // try text fallback
3453 DataFlavor textDf = new DataFlavor(
3454 "text/plain;class=java.lang.String");
3455 if (t.isDataFlavorSupported(textDf))
3457 data = (String) t.getTransferData(textDf);
3460 jalview.bin.Console.debug("Plain text drop content returned "
3461 + (data == null ? "Null - failed" : data));
3466 while (protocols.size() < files.size())
3468 jalview.bin.Console.debug("Adding missing FILE protocol for "
3469 + files.get(protocols.size()));
3470 protocols.add(DataSourceType.FILE);
3472 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3473 data, "\r\n"); st.hasMoreTokens();)
3476 String s = st.nextToken();
3477 if (s.startsWith("#"))
3479 // the line is a comment (as per the RFC 2483)
3482 java.net.URI uri = new java.net.URI(s);
3483 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3485 protocols.add(DataSourceType.URL);
3486 files.add(uri.toString());
3490 // otherwise preserve old behaviour: catch all for file objects
3491 java.io.File file = new java.io.File(uri);
3492 protocols.add(DataSourceType.FILE);
3493 files.add(file.toString());
3498 if (jalview.bin.Console.isDebugEnabled())
3500 if (data == null || !added)
3503 if (t.getTransferDataFlavors() != null
3504 && t.getTransferDataFlavors().length > 0)
3506 jalview.bin.Console.debug(
3507 "Couldn't resolve drop data. Here are the supported flavors:");
3508 for (DataFlavor fl : t.getTransferDataFlavors())
3510 jalview.bin.Console.debug(
3511 "Supported transfer dataflavor: " + fl.toString());
3512 Object df = t.getTransferData(fl);
3515 jalview.bin.Console.debug("Retrieves: " + df);
3519 jalview.bin.Console.debug("Retrieved nothing");
3526 .debug("Couldn't resolve dataflavor for drop: "
3532 if (Platform.isWindowsAndNotJS())
3535 .debug("Scanning dropped content for Windows Link Files");
3537 // resolve any .lnk files in the file drop
3538 for (int f = 0; f < files.size(); f++)
3540 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3541 if (protocols.get(f).equals(DataSourceType.FILE)
3542 && (source.endsWith(".lnk") || source.endsWith(".url")
3543 || source.endsWith(".site")))
3547 Object obj = files.get(f);
3548 File lf = (obj instanceof File ? (File) obj
3549 : new File((String) obj));
3550 // process link file to get a URL
3551 jalview.bin.Console.debug("Found potential link file: " + lf);
3552 WindowsShortcut wscfile = new WindowsShortcut(lf);
3553 String fullname = wscfile.getRealFilename();
3554 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3555 files.set(f, fullname);
3556 jalview.bin.Console.debug("Parsed real filename " + fullname
3557 + " to extract protocol: " + protocols.get(f));
3558 } catch (Exception ex)
3560 jalview.bin.Console.error(
3561 "Couldn't parse " + files.get(f) + " as a link file.",
3570 * Sets the Preferences property for experimental features to True or False
3571 * depending on the state of the controlling menu item
3574 protected void showExperimental_actionPerformed(boolean selected)
3576 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3580 * Answers a (possibly empty) list of any structure viewer frames (currently
3581 * for either Jmol or Chimera) which are currently open. This may optionally
3582 * be restricted to viewers of a specified class, or viewers linked to a
3583 * specified alignment panel.
3586 * if not null, only return viewers linked to this panel
3587 * @param structureViewerClass
3588 * if not null, only return viewers of this class
3591 public List<StructureViewerBase> getStructureViewers(
3592 AlignmentPanel apanel,
3593 Class<? extends StructureViewerBase> structureViewerClass)
3595 List<StructureViewerBase> result = new ArrayList<>();
3596 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3598 for (JInternalFrame frame : frames)
3600 if (frame instanceof StructureViewerBase)
3602 if (structureViewerClass == null
3603 || structureViewerClass.isInstance(frame))
3606 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3608 result.add((StructureViewerBase) frame);
3616 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3618 private static boolean debugScaleMessageDone = false;
3620 public static void debugScaleMessage(Graphics g)
3622 if (debugScaleMessageDone)
3626 // output used by tests to check HiDPI scaling settings in action
3629 Graphics2D gg = (Graphics2D) g;
3632 AffineTransform t = gg.getTransform();
3633 double scaleX = t.getScaleX();
3634 double scaleY = t.getScaleY();
3635 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3636 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3637 debugScaleMessageDone = true;
3641 jalview.bin.Console.debug("Desktop graphics null");
3643 } catch (Exception e)
3645 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3650 * closes the current instance window, disposes and forgets about it.
3652 public static void closeDesktop()
3654 if (Desktop.instance != null)
3656 Desktop.instance.closeAll_actionPerformed(null);
3657 Desktop.instance.setVisible(false);
3658 Desktop us = Desktop.instance;
3659 Desktop.instance = null;
3660 // call dispose in a separate thread - try to avoid indirect deadlocks
3661 new Thread(new Runnable() {
3665 ExecutorService dex = us.dialogExecutor;
3668 us.dialogExecutor=null;
3669 us.block.drainPermits();
3678 * checks if any progress bars are being displayed in any of the windows managed by the desktop
3681 public boolean operationsAreInProgress()
3683 JInternalFrame[] frames = getAllFrames();
3684 for (JInternalFrame frame:frames)
3686 if (frame instanceof IProgressIndicator)
3688 if (((IProgressIndicator)frame).operationInProgress())
3694 return operationInProgress();