2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.datatransfer.Clipboard;
36 import java.awt.datatransfer.ClipboardOwner;
37 import java.awt.datatransfer.DataFlavor;
38 import java.awt.datatransfer.Transferable;
39 import java.awt.dnd.DnDConstants;
40 import java.awt.dnd.DropTargetDragEvent;
41 import java.awt.dnd.DropTargetDropEvent;
42 import java.awt.dnd.DropTargetEvent;
43 import java.awt.dnd.DropTargetListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.WindowAdapter;
51 import java.awt.event.WindowEvent;
52 import java.awt.geom.AffineTransform;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
55 import java.beans.PropertyVetoException;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Locale;
69 import java.util.Vector;
70 import java.util.concurrent.ExecutorService;
71 import java.util.concurrent.Executors;
72 import java.util.concurrent.Semaphore;
74 import javax.swing.AbstractAction;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.Box;
78 import javax.swing.BoxLayout;
79 import javax.swing.DefaultDesktopManager;
80 import javax.swing.DesktopManager;
81 import javax.swing.InputMap;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JComboBox;
85 import javax.swing.JComponent;
86 import javax.swing.JDesktopPane;
87 import javax.swing.JFrame;
88 import javax.swing.JInternalFrame;
89 import javax.swing.JLabel;
90 import javax.swing.JMenuItem;
91 import javax.swing.JOptionPane;
92 import javax.swing.JPanel;
93 import javax.swing.JPopupMenu;
94 import javax.swing.JProgressBar;
95 import javax.swing.JScrollPane;
96 import javax.swing.JTextArea;
97 import javax.swing.JTextField;
98 import javax.swing.JTextPane;
99 import javax.swing.KeyStroke;
100 import javax.swing.SwingUtilities;
101 import javax.swing.WindowConstants;
102 import javax.swing.event.HyperlinkEvent;
103 import javax.swing.event.HyperlinkEvent.EventType;
104 import javax.swing.event.InternalFrameAdapter;
105 import javax.swing.event.InternalFrameEvent;
106 import javax.swing.text.JTextComponent;
108 import org.stackoverflowusers.file.WindowsShortcut;
110 import jalview.api.AlignViewportI;
111 import jalview.api.AlignmentViewPanel;
112 import jalview.api.structures.JalviewStructureDisplayI;
113 import jalview.bin.Cache;
114 import jalview.bin.Jalview;
115 import jalview.bin.Jalview.ExitCode;
116 import jalview.datamodel.Alignment;
117 import jalview.datamodel.HiddenColumns;
118 import jalview.datamodel.Sequence;
119 import jalview.datamodel.SequenceI;
120 import jalview.gui.ImageExporter.ImageWriterI;
121 import jalview.gui.QuitHandler.QResponse;
122 import jalview.io.BackupFiles;
123 import jalview.io.DataSourceType;
124 import jalview.io.FileFormat;
125 import jalview.io.FileFormatException;
126 import jalview.io.FileFormatI;
127 import jalview.io.FileFormats;
128 import jalview.io.FileLoader;
129 import jalview.io.FormatAdapter;
130 import jalview.io.IdentifyFile;
131 import jalview.io.JalviewFileChooser;
132 import jalview.io.JalviewFileView;
133 import jalview.io.exceptions.ImageOutputException;
134 import jalview.jbgui.GSplitFrame;
135 import jalview.jbgui.GStructureViewer;
136 import jalview.project.Jalview2XML;
137 import jalview.structure.StructureSelectionManager;
138 import jalview.urls.IdOrgSettings;
139 import jalview.util.BrowserLauncher;
140 import jalview.util.ChannelProperties;
141 import jalview.util.ImageMaker.TYPE;
142 import jalview.util.LaunchUtils;
143 import jalview.util.MessageManager;
144 import jalview.util.Platform;
145 import jalview.util.ShortcutKeyMaskExWrapper;
146 import jalview.util.UrlConstants;
147 import jalview.viewmodel.AlignmentViewport;
148 import jalview.ws.params.ParamManager;
149 import jalview.ws.utils.UrlDownloadClient;
156 * @version $Revision: 1.155 $
158 public class Desktop extends jalview.jbgui.GDesktop
159 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
160 jalview.api.StructureSelectionManagerProvider
162 private static final String CITATION;
165 URL bg_logo_url = ChannelProperties.getImageURL(
166 "bg_logo." + String.valueOf(SplashScreen.logoSize));
167 URL uod_logo_url = ChannelProperties.getImageURL(
168 "uod_banner." + String.valueOf(SplashScreen.logoSize));
169 boolean logo = (bg_logo_url != null || uod_logo_url != null);
170 StringBuilder sb = new StringBuilder();
172 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
177 sb.append(bg_logo_url == null ? ""
178 : "<img alt=\"Barton Group logo\" src=\""
179 + bg_logo_url.toString() + "\">");
180 sb.append(uod_logo_url == null ? ""
181 : " <img alt=\"University of Dundee shield\" src=\""
182 + uod_logo_url.toString() + "\">");
184 "<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>");
185 sb.append("<br><br>If you use Jalview, please cite:"
186 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
187 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
188 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
189 CITATION = sb.toString();
192 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
194 private static int DEFAULT_MIN_WIDTH = 300;
196 private static int DEFAULT_MIN_HEIGHT = 250;
198 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
200 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
202 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
204 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
206 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
208 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
210 public static void setLiveDragMode(boolean b)
212 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
213 : JDesktopPane.OUTLINE_DRAG_MODE;
215 desktop.setDragMode(DRAG_MODE);
218 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
220 public static boolean nosplash = false;
223 * news reader - null if it was never started.
225 private BlogReader jvnews = null;
227 private File projectFile;
231 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
233 public void addJalviewPropertyChangeListener(
234 PropertyChangeListener listener)
236 changeSupport.addJalviewPropertyChangeListener(listener);
240 * @param propertyName
242 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
243 * java.beans.PropertyChangeListener)
245 public void addJalviewPropertyChangeListener(String propertyName,
246 PropertyChangeListener listener)
248 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
252 * @param propertyName
254 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
255 * java.beans.PropertyChangeListener)
257 public void removeJalviewPropertyChangeListener(String propertyName,
258 PropertyChangeListener listener)
260 changeSupport.removeJalviewPropertyChangeListener(propertyName,
264 /** Singleton Desktop instance */
265 public static Desktop instance;
267 public static MyDesktopPane desktop;
269 public static MyDesktopPane getDesktop()
271 // BH 2018 could use currentThread() here as a reference to a
272 // Hashtable<Thread, MyDesktopPane> in JavaScript
276 static int openFrameCount = 0;
278 static final int xOffset = 30;
280 static final int yOffset = 30;
282 public static jalview.ws.jws1.Discoverer discoverer;
284 public static Object[] jalviewClipboard;
286 public static boolean internalCopy = false;
288 static int fileLoadingCount = 0;
290 class MyDesktopManager implements DesktopManager
293 private DesktopManager delegate;
295 public MyDesktopManager(DesktopManager delegate)
297 this.delegate = delegate;
301 public void activateFrame(JInternalFrame f)
305 delegate.activateFrame(f);
306 } catch (NullPointerException npe)
308 Point p = getMousePosition();
309 instance.showPasteMenu(p.x, p.y);
314 public void beginDraggingFrame(JComponent f)
316 delegate.beginDraggingFrame(f);
320 public void beginResizingFrame(JComponent f, int direction)
322 delegate.beginResizingFrame(f, direction);
326 public void closeFrame(JInternalFrame f)
328 delegate.closeFrame(f);
332 public void deactivateFrame(JInternalFrame f)
334 delegate.deactivateFrame(f);
338 public void deiconifyFrame(JInternalFrame f)
340 delegate.deiconifyFrame(f);
344 public void dragFrame(JComponent f, int newX, int newY)
350 delegate.dragFrame(f, newX, newY);
354 public void endDraggingFrame(JComponent f)
356 delegate.endDraggingFrame(f);
361 public void endResizingFrame(JComponent f)
363 delegate.endResizingFrame(f);
368 public void iconifyFrame(JInternalFrame f)
370 delegate.iconifyFrame(f);
374 public void maximizeFrame(JInternalFrame f)
376 delegate.maximizeFrame(f);
380 public void minimizeFrame(JInternalFrame f)
382 delegate.minimizeFrame(f);
386 public void openFrame(JInternalFrame f)
388 delegate.openFrame(f);
392 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
399 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
403 public void setBoundsForFrame(JComponent f, int newX, int newY,
404 int newWidth, int newHeight)
406 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
409 // All other methods, simply delegate
414 * Creates a new Desktop object.
420 * A note to implementors. It is ESSENTIAL that any activities that might
421 * block are spawned off as threads rather than waited for during this
426 doConfigureStructurePrefs();
427 setTitle(ChannelProperties.getProperty("app_name") + " "
428 + Cache.getProperty("VERSION"));
431 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
432 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
433 * officially documented or guaranteed to exist, so we access it via
434 * reflection. There appear to be unfathomable criteria about what this
435 * string can contain, and it if doesn't meet those criteria then "java"
436 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
437 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
438 * not. The reflection access may generate a warning: WARNING: An illegal
439 * reflective access operation has occurred WARNING: Illegal reflective
440 * access by jalview.gui.Desktop () to field
441 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
443 if (Platform.isLinux())
445 if (LaunchUtils.getJavaVersion() >= 11)
448 * Send this message to stderr as the warning that follows (due to
449 * reflection) also goes to stderr.
451 jalview.bin.Console.errPrintln(
452 "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.");
454 final String awtAppClassName = "awtAppClassName";
457 Toolkit xToolkit = Toolkit.getDefaultToolkit();
458 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
459 Field awtAppClassNameField = null;
461 if (Arrays.stream(declaredFields)
462 .anyMatch(f -> f.getName().equals(awtAppClassName)))
464 awtAppClassNameField = xToolkit.getClass()
465 .getDeclaredField(awtAppClassName);
468 String title = ChannelProperties.getProperty("app_name");
469 if (awtAppClassNameField != null)
471 awtAppClassNameField.setAccessible(true);
472 awtAppClassNameField.set(xToolkit, title);
477 .debug("XToolkit: " + awtAppClassName + " not found");
479 } catch (Exception e)
481 jalview.bin.Console.debug("Error setting " + awtAppClassName);
482 jalview.bin.Console.trace(Cache.getStackTraceString(e));
486 setIconImages(ChannelProperties.getIconList());
488 // override quit handling when GUI OS close [X] button pressed
489 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
490 addWindowListener(new WindowAdapter()
493 public void windowClosing(WindowEvent ev)
495 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
499 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
501 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
502 desktop = new MyDesktopPane(selmemusage);
504 showMemusage.setSelected(selmemusage);
505 desktop.setBackground(Color.white);
507 getContentPane().setLayout(new BorderLayout());
508 // alternate config - have scrollbars - see notes in JAL-153
509 // JScrollPane sp = new JScrollPane();
510 // sp.getViewport().setView(desktop);
511 // getContentPane().add(sp, BorderLayout.CENTER);
513 // BH 2018 - just an experiment to try unclipped JInternalFrames.
516 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
519 getContentPane().add(desktop, BorderLayout.CENTER);
520 desktop.setDragMode(DRAG_MODE);
522 // This line prevents Windows Look&Feel resizing all new windows to maximum
523 // if previous window was maximised
524 desktop.setDesktopManager(new MyDesktopManager(
525 Platform.isJS() ? desktop.getDesktopManager()
526 : new DefaultDesktopManager()));
528 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
529 : Platform.isAMacAndNotJS()
530 ? new AquaInternalFrameManager(
531 desktop.getDesktopManager())
532 : desktop.getDesktopManager())));
535 Rectangle dims = getLastKnownDimensions("");
542 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
543 int xPos = Math.max(5, (screenSize.width - 900) / 2);
544 int yPos = Math.max(5, (screenSize.height - 650) / 2);
545 setBounds(xPos, yPos, 900, 650);
548 // start dialogue queue for single dialogues
551 if (!Platform.isJS())
558 jconsole = new Console(this, showjconsole);
559 jconsole.setHeader(Cache.getVersionDetailsForConsole());
560 showConsole(showjconsole);
562 showNews.setVisible(false);
564 experimentalFeatures.setSelected(showExperimental());
566 getIdentifiersOrgData();
570 // Spawn a thread that shows the splashscreen
573 SwingUtilities.invokeLater(new Runnable()
578 new SplashScreen(true);
583 // Thread off a new instance of the file chooser - this reduces the time
584 // it takes to open it later on.
585 new Thread(new Runnable()
590 jalview.bin.Console.debug("Filechooser init thread started.");
591 String fileFormat = FileLoader.getUseDefaultFileFormat()
592 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
594 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
596 jalview.bin.Console.debug("Filechooser init thread finished.");
599 // Add the service change listener
600 changeSupport.addJalviewPropertyChangeListener("services",
601 new PropertyChangeListener()
605 public void propertyChange(PropertyChangeEvent evt)
608 .debug("Firing service changed event for "
609 + evt.getNewValue());
610 JalviewServicesChanged(evt);
615 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
618 this.addMouseListener(ma = new MouseAdapter()
621 public void mousePressed(MouseEvent evt)
623 if (evt.isPopupTrigger()) // Mac
625 showPasteMenu(evt.getX(), evt.getY());
630 public void mouseReleased(MouseEvent evt)
632 if (evt.isPopupTrigger()) // Windows
634 showPasteMenu(evt.getX(), evt.getY());
638 desktop.addMouseListener(ma);
642 // used for jalviewjsTest
643 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
649 * Answers true if user preferences to enable experimental features is True
654 public boolean showExperimental()
656 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
657 Boolean.FALSE.toString());
658 return Boolean.valueOf(experimental).booleanValue();
661 public void doConfigureStructurePrefs()
663 // configure services
664 StructureSelectionManager ssm = StructureSelectionManager
665 .getStructureSelectionManager(this);
666 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
668 ssm.setAddTempFacAnnot(
669 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
670 ssm.setProcessSecondaryStructure(
671 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
672 // JAL-3915 - RNAView is no longer an option so this has no effect
673 ssm.setSecStructServices(
674 Cache.getDefault(Preferences.USE_RNAVIEW, false));
678 ssm.setAddTempFacAnnot(false);
679 ssm.setProcessSecondaryStructure(false);
680 ssm.setSecStructServices(false);
684 public void checkForNews()
686 final Desktop me = this;
687 // Thread off the news reader, in case there are connection problems.
688 new Thread(new Runnable()
693 jalview.bin.Console.debug("Starting news thread.");
694 jvnews = new BlogReader(me);
695 showNews.setVisible(true);
696 jalview.bin.Console.debug("Completed news thread.");
701 public void getIdentifiersOrgData()
703 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
704 {// Thread off the identifiers fetcher
705 new Thread(new Runnable()
711 .debug("Downloading data from identifiers.org");
714 UrlDownloadClient.download(IdOrgSettings.getUrl(),
715 IdOrgSettings.getDownloadLocation());
716 } catch (IOException e)
719 .debug("Exception downloading identifiers.org data"
729 protected void showNews_actionPerformed(ActionEvent e)
731 showNews(showNews.isSelected());
734 void showNews(boolean visible)
736 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
737 showNews.setSelected(visible);
738 if (visible && !jvnews.isVisible())
740 new Thread(new Runnable()
745 long now = System.currentTimeMillis();
746 Desktop.instance.setProgressBar(
747 MessageManager.getString("status.refreshing_news"), now);
748 jvnews.refreshNews();
749 Desktop.instance.setProgressBar(null, now);
757 * recover the last known dimensions for a jalview window
760 * - empty string is desktop, all other windows have unique prefix
761 * @return null or last known dimensions scaled to current geometry (if last
762 * window geom was known)
764 Rectangle getLastKnownDimensions(String windowName)
766 // TODO: lock aspect ratio for scaling desktop Bug #0058199
767 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
768 String x = Cache.getProperty(windowName + "SCREEN_X");
769 String y = Cache.getProperty(windowName + "SCREEN_Y");
770 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
771 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
772 if ((x != null) && (y != null) && (width != null) && (height != null))
774 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
775 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
776 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
778 // attempt #1 - try to cope with change in screen geometry - this
779 // version doesn't preserve original jv aspect ratio.
780 // take ratio of current screen size vs original screen size.
781 double sw = ((1f * screenSize.width) / (1f * Integer
782 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
783 double sh = ((1f * screenSize.height) / (1f * Integer
784 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
785 // rescale the bounds depending upon the current screen geometry.
786 ix = (int) (ix * sw);
787 iw = (int) (iw * sw);
788 iy = (int) (iy * sh);
789 ih = (int) (ih * sh);
790 while (ix >= screenSize.width)
792 jalview.bin.Console.debug(
793 "Window geometry location recall error: shifting horizontal to within screenbounds.");
794 ix -= screenSize.width;
796 while (iy >= screenSize.height)
798 jalview.bin.Console.debug(
799 "Window geometry location recall error: shifting vertical to within screenbounds.");
800 iy -= screenSize.height;
802 jalview.bin.Console.debug(
803 "Got last known dimensions for " + windowName + ": x:" + ix
804 + " y:" + iy + " width:" + iw + " height:" + ih);
806 // return dimensions for new instance
807 return new Rectangle(ix, iy, iw, ih);
812 void showPasteMenu(int x, int y)
814 JPopupMenu popup = new JPopupMenu();
815 JMenuItem item = new JMenuItem(
816 MessageManager.getString("label.paste_new_window"));
817 item.addActionListener(new ActionListener()
820 public void actionPerformed(ActionEvent evt)
827 popup.show(this, x, y);
832 // quick patch for JAL-4150 - needs some more work and test coverage
833 // TODO - unify below and AlignFrame.paste()
834 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
835 // clipboard has come from a different alignment window than the one where
836 // paste has been called! JAL-4151
838 if (Desktop.jalviewClipboard != null)
840 // The clipboard was filled from within Jalview, we must use the
842 // And dataset from the copied alignment
843 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
844 // be doubly sure that we create *new* sequence objects.
845 SequenceI[] sequences = new SequenceI[newseq.length];
846 for (int i = 0; i < newseq.length; i++)
848 sequences[i] = new Sequence(newseq[i]);
850 Alignment alignment = new Alignment(sequences);
851 // dataset is inherited
852 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
853 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
854 AlignFrame.DEFAULT_HEIGHT);
855 String newtitle = new String("Copied sequences");
857 if (Desktop.jalviewClipboard[2] != null)
859 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
860 af.viewport.setHiddenColumns(hc);
863 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
864 AlignFrame.DEFAULT_HEIGHT);
871 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
872 Transferable contents = c.getContents(this);
874 if (contents != null)
876 String file = (String) contents
877 .getTransferData(DataFlavor.stringFlavor);
879 FileFormatI format = new IdentifyFile().identify(file,
880 DataSourceType.PASTE);
882 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
885 } catch (Exception ex)
887 jalview.bin.Console.outPrintln(
888 "Unable to paste alignment from system clipboard:\n" + ex);
894 * Adds and opens the given frame to the desktop
905 public static synchronized void addInternalFrame(
906 final JInternalFrame frame, String title, int w, int h)
908 addInternalFrame(frame, title, true, w, h, true, false);
912 * Add an internal frame to the Jalview desktop
919 * When true, display frame immediately, otherwise, caller must call
920 * setVisible themselves.
926 public static synchronized void addInternalFrame(
927 final JInternalFrame frame, String title, boolean makeVisible,
930 addInternalFrame(frame, title, makeVisible, w, h, true, false);
934 * Add an internal frame to the Jalview desktop and make it visible
947 public static synchronized void addInternalFrame(
948 final JInternalFrame frame, String title, int w, int h,
951 addInternalFrame(frame, title, true, w, h, resizable, false);
955 * Add an internal frame to the Jalview desktop
962 * When true, display frame immediately, otherwise, caller must call
963 * setVisible themselves.
970 * @param ignoreMinSize
971 * Do not set the default minimum size for frame
973 public static synchronized void addInternalFrame(
974 final JInternalFrame frame, String title, boolean makeVisible,
975 int w, int h, boolean resizable, boolean ignoreMinSize)
978 // TODO: allow callers to determine X and Y position of frame (eg. via
980 // TODO: consider fixing method to update entries in the window submenu with
981 // the current window title
983 frame.setTitle(title);
984 if (frame.getWidth() < 1 || frame.getHeight() < 1)
988 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
989 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
990 // IF JALVIEW IS RUNNING HEADLESS
991 // ///////////////////////////////////////////////
992 if (instance == null || (System.getProperty("java.awt.headless") != null
993 && System.getProperty("java.awt.headless").equals("true")))
1002 frame.setMinimumSize(
1003 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1005 // Set default dimension for Alignment Frame window.
1006 // The Alignment Frame window could be added from a number of places,
1008 // I did this here in order not to miss out on any Alignment frame.
1009 if (frame instanceof AlignFrame)
1011 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1012 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1016 frame.setVisible(makeVisible);
1017 frame.setClosable(true);
1018 frame.setResizable(resizable);
1019 frame.setMaximizable(resizable);
1020 frame.setIconifiable(resizable);
1021 frame.setOpaque(Platform.isJS());
1023 if (frame.getX() < 1 && frame.getY() < 1)
1025 frame.setLocation(xOffset * openFrameCount,
1026 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1030 * add an entry for the new frame in the Window menu (and remove it when the
1033 final JMenuItem menuItem = new JMenuItem(title);
1034 frame.addInternalFrameListener(new InternalFrameAdapter()
1037 public void internalFrameActivated(InternalFrameEvent evt)
1039 JInternalFrame itf = desktop.getSelectedFrame();
1042 if (itf instanceof AlignFrame)
1044 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1051 public void internalFrameClosed(InternalFrameEvent evt)
1053 PaintRefresher.RemoveComponent(frame);
1056 * defensive check to prevent frames being added half off the window
1058 if (openFrameCount > 0)
1064 * ensure no reference to alignFrame retained by menu item listener
1066 if (menuItem.getActionListeners().length > 0)
1068 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1070 windowMenu.remove(menuItem);
1074 menuItem.addActionListener(new ActionListener()
1077 public void actionPerformed(ActionEvent e)
1081 frame.setSelected(true);
1082 frame.setIcon(false);
1083 } catch (java.beans.PropertyVetoException ex)
1090 setKeyBindings(frame);
1092 // Since the latest FlatLaf patch, we occasionally have problems showing
1093 // structureViewer frames...
1095 boolean shown = false;
1096 Exception last = null;
1103 } catch (IllegalArgumentException iaex)
1107 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1108 + tries + " left) for " + frame.getTitle(), iaex);
1112 } catch (InterruptedException iex)
1117 } while (!shown && tries > 0);
1120 jalview.bin.Console.error(
1121 "Serious Problem whilst showing window " + frame.getTitle(),
1125 windowMenu.add(menuItem);
1130 frame.setSelected(true);
1131 frame.requestFocus();
1132 } catch (java.beans.PropertyVetoException ve)
1134 } catch (java.lang.ClassCastException cex)
1136 jalview.bin.Console.warn(
1137 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1143 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1148 private static void setKeyBindings(JInternalFrame frame)
1150 @SuppressWarnings("serial")
1151 final Action closeAction = new AbstractAction()
1154 public void actionPerformed(ActionEvent e)
1161 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1163 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1164 InputEvent.CTRL_DOWN_MASK);
1165 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1166 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1168 InputMap inputMap = frame
1169 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1170 String ctrlW = ctrlWKey.toString();
1171 inputMap.put(ctrlWKey, ctrlW);
1172 inputMap.put(cmdWKey, ctrlW);
1174 ActionMap actionMap = frame.getActionMap();
1175 actionMap.put(ctrlW, closeAction);
1179 public void lostOwnership(Clipboard clipboard, Transferable contents)
1183 Desktop.jalviewClipboard = null;
1186 internalCopy = false;
1190 public void dragEnter(DropTargetDragEvent evt)
1195 public void dragExit(DropTargetEvent evt)
1200 public void dragOver(DropTargetDragEvent evt)
1205 public void dropActionChanged(DropTargetDragEvent evt)
1216 public void drop(DropTargetDropEvent evt)
1218 boolean success = true;
1219 // JAL-1552 - acceptDrop required before getTransferable call for
1220 // Java's Transferable for native dnd
1221 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1222 Transferable t = evt.getTransferable();
1223 List<Object> files = new ArrayList<>();
1224 List<DataSourceType> protocols = new ArrayList<>();
1228 Desktop.transferFromDropTarget(files, protocols, evt, t);
1229 } catch (Exception e)
1231 e.printStackTrace();
1239 for (int i = 0; i < files.size(); i++)
1241 // BH 2018 File or String
1242 Object file = files.get(i);
1243 String fileName = file.toString();
1244 DataSourceType protocol = (protocols == null)
1245 ? DataSourceType.FILE
1247 FileFormatI format = null;
1249 if (fileName.endsWith(".jar"))
1251 format = FileFormat.Jalview;
1256 format = new IdentifyFile().identify(file, protocol);
1258 if (file instanceof File)
1260 Platform.cacheFileData((File) file);
1262 new FileLoader().LoadFile(null, file, protocol, format);
1265 } catch (Exception ex)
1270 evt.dropComplete(success); // need this to ensure input focus is properly
1271 // transfered to any new windows created
1281 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1283 String fileFormat = FileLoader.getUseDefaultFileFormat()
1284 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1286 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1287 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1288 BackupFiles.getEnabled());
1290 chooser.setFileView(new JalviewFileView());
1291 chooser.setDialogTitle(
1292 MessageManager.getString("label.open_local_file"));
1293 chooser.setToolTipText(MessageManager.getString("action.open"));
1295 chooser.setResponseHandler(0, () -> {
1296 File selectedFile = chooser.getSelectedFile();
1297 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1299 FileFormatI format = chooser.getSelectedFormat();
1302 * Call IdentifyFile to verify the file contains what its extension implies.
1303 * Skip this step for dynamically added file formats, because IdentifyFile does
1304 * not know how to recognise them.
1306 if (FileFormats.getInstance().isIdentifiable(format))
1310 format = new IdentifyFile().identify(selectedFile,
1311 DataSourceType.FILE);
1312 } catch (FileFormatException e)
1314 // format = null; //??
1318 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1321 chooser.showOpenDialog(this);
1325 * Shows a dialog for input of a URL at which to retrieve alignment data
1330 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1332 // This construct allows us to have a wider textfield
1334 JLabel label = new JLabel(
1335 MessageManager.getString("label.input_file_url"));
1337 JPanel panel = new JPanel(new GridLayout(2, 1));
1341 * the URL to fetch is input in Java: an editable combobox with history JS:
1342 * (pending JAL-3038) a plain text field
1345 String urlBase = "https://www.";
1346 if (Platform.isJS())
1348 history = new JTextField(urlBase, 35);
1357 JComboBox<String> asCombo = new JComboBox<>();
1358 asCombo.setPreferredSize(new Dimension(400, 20));
1359 asCombo.setEditable(true);
1360 asCombo.addItem(urlBase);
1361 String historyItems = Cache.getProperty("RECENT_URL");
1362 if (historyItems != null)
1364 for (String token : historyItems.split("\\t"))
1366 asCombo.addItem(token);
1373 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1374 MessageManager.getString("action.cancel") };
1375 Runnable action = () -> {
1376 @SuppressWarnings("unchecked")
1377 String url = (history instanceof JTextField
1378 ? ((JTextField) history).getText()
1379 : ((JComboBox<String>) history).getEditor().getItem()
1380 .toString().trim());
1382 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1384 if (viewport != null)
1386 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1387 FileFormat.Jalview);
1391 new FileLoader().LoadFile(url, DataSourceType.URL,
1392 FileFormat.Jalview);
1397 FileFormatI format = null;
1400 format = new IdentifyFile().identify(url, DataSourceType.URL);
1401 } catch (FileFormatException e)
1403 // TODO revise error handling, distinguish between
1404 // URL not found and response not valid
1409 String msg = MessageManager.formatMessage("label.couldnt_locate",
1411 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1412 MessageManager.getString("label.url_not_found"),
1413 JvOptionPane.WARNING_MESSAGE);
1417 if (viewport != null)
1419 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1424 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1428 String dialogOption = MessageManager
1429 .getString("label.input_alignment_from_url");
1430 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1431 .showInternalDialog(panel, dialogOption,
1432 JvOptionPane.YES_NO_CANCEL_OPTION,
1433 JvOptionPane.PLAIN_MESSAGE, null, options,
1434 MessageManager.getString("action.ok"));
1438 * Opens the CutAndPaste window for the user to paste an alignment in to
1441 * - if not null, the pasted alignment is added to the current
1442 * alignment; if null, to a new alignment window
1445 public void inputTextboxMenuItem_actionPerformed(
1446 AlignmentViewPanel viewPanel)
1448 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1449 cap.setForInput(viewPanel);
1450 Desktop.addInternalFrame(cap,
1451 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1456 * Check with user and saving files before actually quitting
1458 public void desktopQuit()
1460 desktopQuit(true, false);
1463 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1465 final Runnable doDesktopQuit = () -> {
1466 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1467 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1468 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1469 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1470 getBounds().y, getWidth(), getHeight()));
1472 if (jconsole != null)
1474 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1475 jconsole.stopConsole();
1480 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1483 // Frames should all close automatically. Keeping external
1484 // viewers open should already be decided by user.
1485 closeAll_actionPerformed(null);
1487 // check for aborted quit
1488 if (QuitHandler.quitCancelled())
1490 jalview.bin.Console.debug("Desktop aborting quit");
1494 if (dialogExecutor != null)
1496 dialogExecutor.shutdownNow();
1499 if (groovyConsole != null)
1501 // suppress a possible repeat prompt to save script
1502 groovyConsole.setDirty(false);
1503 groovyConsole.exit();
1506 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1508 // note that shutdown hook will not be run
1509 jalview.bin.Console.debug("Force Quit selected by user");
1510 Runtime.getRuntime().halt(0);
1513 jalview.bin.Console.debug("Quit selected by user");
1516 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1517 // instance.dispose();
1522 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1523 QuitHandler.defaultCancelQuit);
1527 * Don't call this directly, use desktopQuit() above. Exits the program.
1532 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1533 // not run a second time if gotQuitResponse flag has been set (i.e. user
1534 // confirmed quit of some kind).
1535 Jalview.exit("Desktop exiting.", ExitCode.OK);
1538 private void storeLastKnownDimensions(String string, Rectangle jc)
1540 jalview.bin.Console.debug("Storing last known dimensions for " + string
1541 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1542 + " height:" + jc.height);
1544 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1545 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1546 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1547 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1557 public void aboutMenuItem_actionPerformed(ActionEvent e)
1559 new Thread(new Runnable()
1564 new SplashScreen(false);
1570 * Returns the html text for the About screen, including any available version
1571 * number, build details, author details and citation reference, but without
1572 * the enclosing {@code html} tags
1576 public String getAboutMessage()
1578 StringBuilder message = new StringBuilder(1024);
1579 message.append("<div style=\"font-family: sans-serif;\">")
1580 .append("<h1><strong>Version: ")
1581 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1582 .append("<strong>Built: <em>")
1583 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1584 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1585 .append("</strong>");
1587 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1588 if (latestVersion.equals("Checking"))
1590 // JBP removed this message for 2.11: May be reinstated in future version
1591 // message.append("<br>...Checking latest version...</br>");
1593 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1595 boolean red = false;
1596 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1597 .indexOf("automated build") == -1)
1600 // Displayed when code version and jnlp version do not match and code
1601 // version is not a development build
1602 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1605 message.append("<br>!! Version ")
1606 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1607 .append(" is available for download from ")
1608 .append(Cache.getDefault("www.jalview.org",
1609 "https://www.jalview.org"))
1613 message.append("</div>");
1616 message.append("<br>Authors: ");
1617 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1618 message.append(CITATION);
1620 message.append("</div>");
1622 return message.toString();
1626 * Action on requesting Help documentation
1629 public void documentationMenuItem_actionPerformed()
1633 if (Platform.isJS())
1635 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1644 Help.showHelpWindow();
1646 } catch (Exception ex)
1649 .errPrintln("Error opening help: " + ex.getMessage());
1654 public void closeAll_actionPerformed(ActionEvent e)
1656 // TODO show a progress bar while closing?
1657 JInternalFrame[] frames = desktop.getAllFrames();
1658 for (int i = 0; i < frames.length; i++)
1662 frames[i].setClosed(true);
1663 } catch (java.beans.PropertyVetoException ex)
1667 Jalview.setCurrentAlignFrame(null);
1668 jalview.bin.Console.info("ALL CLOSED");
1671 * reset state of singleton objects as appropriate (clear down session state
1672 * when all windows are closed)
1674 StructureSelectionManager ssm = StructureSelectionManager
1675 .getStructureSelectionManager(this);
1682 public int structureViewersStillRunningCount()
1685 JInternalFrame[] frames = desktop.getAllFrames();
1686 for (int i = 0; i < frames.length; i++)
1688 if (frames[i] != null
1689 && frames[i] instanceof JalviewStructureDisplayI)
1691 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1699 public void raiseRelated_actionPerformed(ActionEvent e)
1701 reorderAssociatedWindows(false, false);
1705 public void minimizeAssociated_actionPerformed(ActionEvent e)
1707 reorderAssociatedWindows(true, false);
1710 void closeAssociatedWindows()
1712 reorderAssociatedWindows(false, true);
1718 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1722 protected void garbageCollect_actionPerformed(ActionEvent e)
1724 // We simply collect the garbage
1725 jalview.bin.Console.debug("Collecting garbage...");
1727 jalview.bin.Console.debug("Finished garbage collection.");
1733 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1737 protected void showMemusage_actionPerformed(ActionEvent e)
1739 desktop.showMemoryUsage(showMemusage.isSelected());
1746 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1750 protected void showConsole_actionPerformed(ActionEvent e)
1752 showConsole(showConsole.isSelected());
1755 Console jconsole = null;
1758 * control whether the java console is visible or not
1762 void showConsole(boolean selected)
1764 // TODO: decide if we should update properties file
1765 if (jconsole != null) // BH 2018
1767 showConsole.setSelected(selected);
1768 Cache.setProperty("SHOW_JAVA_CONSOLE",
1769 Boolean.valueOf(selected).toString());
1770 jconsole.setVisible(selected);
1774 void reorderAssociatedWindows(boolean minimize, boolean close)
1776 JInternalFrame[] frames = desktop.getAllFrames();
1777 if (frames == null || frames.length < 1)
1782 AlignmentViewport source = null, target = null;
1783 if (frames[0] instanceof AlignFrame)
1785 source = ((AlignFrame) frames[0]).getCurrentView();
1787 else if (frames[0] instanceof TreePanel)
1789 source = ((TreePanel) frames[0]).getViewPort();
1791 else if (frames[0] instanceof PCAPanel)
1793 source = ((PCAPanel) frames[0]).av;
1795 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1797 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1802 for (int i = 0; i < frames.length; i++)
1805 if (frames[i] == null)
1809 if (frames[i] instanceof AlignFrame)
1811 target = ((AlignFrame) frames[i]).getCurrentView();
1813 else if (frames[i] instanceof TreePanel)
1815 target = ((TreePanel) frames[i]).getViewPort();
1817 else if (frames[i] instanceof PCAPanel)
1819 target = ((PCAPanel) frames[i]).av;
1821 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1823 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1826 if (source == target)
1832 frames[i].setClosed(true);
1836 frames[i].setIcon(minimize);
1839 frames[i].toFront();
1843 } catch (java.beans.PropertyVetoException ex)
1858 protected void preferences_actionPerformed(ActionEvent e)
1860 Preferences.openPreferences();
1864 * Prompts the user to choose a file and then saves the Jalview state as a
1865 * Jalview project file
1868 public void saveState_actionPerformed()
1870 saveState_actionPerformed(false);
1873 public void saveState_actionPerformed(boolean saveAs)
1875 java.io.File projectFile = getProjectFile();
1876 // autoSave indicates we already have a file and don't need to ask
1877 boolean autoSave = projectFile != null && !saveAs
1878 && BackupFiles.getEnabled();
1880 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1881 // projectFile='"+projectFile+"',
1882 // saveAs="+saveAs+", Backups
1883 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1885 boolean approveSave = false;
1888 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1891 chooser.setFileView(new JalviewFileView());
1892 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1894 int value = chooser.showSaveDialog(this);
1896 if (value == JalviewFileChooser.APPROVE_OPTION)
1898 projectFile = chooser.getSelectedFile();
1899 setProjectFile(projectFile);
1904 if (approveSave || autoSave)
1906 final Desktop me = this;
1907 final java.io.File chosenFile = projectFile;
1908 new Thread(new Runnable()
1913 // TODO: refactor to Jalview desktop session controller action.
1914 setProgressBar(MessageManager.formatMessage(
1915 "label.saving_jalview_project", new Object[]
1916 { chosenFile.getName() }), chosenFile.hashCode());
1917 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1918 // TODO catch and handle errors for savestate
1919 // TODO prevent user from messing with the Desktop whilst we're saving
1922 boolean doBackup = BackupFiles.getEnabled();
1923 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1926 new Jalview2XML().saveState(
1927 doBackup ? backupfiles.getTempFile() : chosenFile);
1931 backupfiles.setWriteSuccess(true);
1932 backupfiles.rollBackupsAndRenameTempFile();
1934 } catch (OutOfMemoryError oom)
1936 new OOMWarning("Whilst saving current state to "
1937 + chosenFile.getName(), oom);
1938 } catch (Exception ex)
1940 jalview.bin.Console.error("Problems whilst trying to save to "
1941 + chosenFile.getName(), ex);
1942 JvOptionPane.showMessageDialog(me,
1943 MessageManager.formatMessage(
1944 "label.error_whilst_saving_current_state_to",
1946 { chosenFile.getName() }),
1947 MessageManager.getString("label.couldnt_save_project"),
1948 JvOptionPane.WARNING_MESSAGE);
1950 setProgressBar(null, chosenFile.hashCode());
1957 public void saveAsState_actionPerformed(ActionEvent e)
1959 saveState_actionPerformed(true);
1962 protected void setProjectFile(File choice)
1964 this.projectFile = choice;
1967 public File getProjectFile()
1969 return this.projectFile;
1973 * Shows a file chooser dialog and tries to read in the selected file as a
1977 public void loadState_actionPerformed()
1979 final String[] suffix = new String[] { "jvp", "jar" };
1980 final String[] desc = new String[] { "Jalview Project",
1981 "Jalview Project (old)" };
1982 JalviewFileChooser chooser = new JalviewFileChooser(
1983 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1984 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1988 chooser.setFileView(new JalviewFileView());
1989 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1990 chooser.setResponseHandler(0, () -> {
1991 File selectedFile = chooser.getSelectedFile();
1992 setProjectFile(selectedFile);
1993 String choice = selectedFile.getAbsolutePath();
1994 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1995 new Thread(new Runnable()
2002 new Jalview2XML().loadJalviewAlign(selectedFile);
2003 } catch (OutOfMemoryError oom)
2005 new OOMWarning("Whilst loading project from " + choice, oom);
2006 } catch (Exception ex)
2008 jalview.bin.Console.error(
2009 "Problems whilst loading project from " + choice, ex);
2010 JvOptionPane.showMessageDialog(Desktop.desktop,
2011 MessageManager.formatMessage(
2012 "label.error_whilst_loading_project_from",
2015 MessageManager.getString("label.couldnt_load_project"),
2016 JvOptionPane.WARNING_MESSAGE);
2019 }, "Project Loader").start();
2022 chooser.showOpenDialog(this);
2026 public void inputSequence_actionPerformed(ActionEvent e)
2028 new SequenceFetcher(this);
2031 JPanel progressPanel;
2033 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2035 public void startLoading(final Object fileName)
2037 if (fileLoadingCount == 0)
2039 fileLoadingPanels.add(addProgressPanel(MessageManager
2040 .formatMessage("label.loading_file", new Object[]
2046 private JPanel addProgressPanel(String string)
2048 if (progressPanel == null)
2050 progressPanel = new JPanel(new GridLayout(1, 1));
2051 totalProgressCount = 0;
2052 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2054 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2055 JProgressBar progressBar = new JProgressBar();
2056 progressBar.setIndeterminate(true);
2058 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2060 thisprogress.add(progressBar, BorderLayout.CENTER);
2061 progressPanel.add(thisprogress);
2062 ((GridLayout) progressPanel.getLayout()).setRows(
2063 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2064 ++totalProgressCount;
2065 instance.validate();
2066 return thisprogress;
2069 int totalProgressCount = 0;
2071 private void removeProgressPanel(JPanel progbar)
2073 if (progressPanel != null)
2075 synchronized (progressPanel)
2077 progressPanel.remove(progbar);
2078 GridLayout gl = (GridLayout) progressPanel.getLayout();
2079 gl.setRows(gl.getRows() - 1);
2080 if (--totalProgressCount < 1)
2082 this.getContentPane().remove(progressPanel);
2083 progressPanel = null;
2090 public void stopLoading()
2093 if (fileLoadingCount < 1)
2095 while (fileLoadingPanels.size() > 0)
2097 removeProgressPanel(fileLoadingPanels.remove(0));
2099 fileLoadingPanels.clear();
2100 fileLoadingCount = 0;
2105 public static int getViewCount(String alignmentId)
2107 AlignmentViewport[] aps = getViewports(alignmentId);
2108 return (aps == null) ? 0 : aps.length;
2113 * @param alignmentId
2114 * - if null, all sets are returned
2115 * @return all AlignmentPanels concerning the alignmentId sequence set
2117 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2119 if (Desktop.desktop == null)
2121 // no frames created and in headless mode
2122 // TODO: verify that frames are recoverable when in headless mode
2125 List<AlignmentPanel> aps = new ArrayList<>();
2126 AlignFrame[] frames = getAlignFrames();
2131 for (AlignFrame af : frames)
2133 for (AlignmentPanel ap : af.alignPanels)
2135 if (alignmentId == null
2136 || alignmentId.equals(ap.av.getSequenceSetId()))
2142 if (aps.size() == 0)
2146 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2151 * get all the viewports on an alignment.
2153 * @param sequenceSetId
2154 * unique alignment id (may be null - all viewports returned in that
2156 * @return all viewports on the alignment bound to sequenceSetId
2158 public static AlignmentViewport[] getViewports(String sequenceSetId)
2160 List<AlignmentViewport> viewp = new ArrayList<>();
2161 if (desktop != null)
2163 AlignFrame[] frames = Desktop.getAlignFrames();
2165 for (AlignFrame afr : frames)
2167 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2168 .equals(sequenceSetId))
2170 if (afr.alignPanels != null)
2172 for (AlignmentPanel ap : afr.alignPanels)
2174 if (sequenceSetId == null
2175 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2183 viewp.add(afr.getViewport());
2187 if (viewp.size() > 0)
2189 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2196 * Explode the views in the given frame into separate AlignFrame
2200 public static void explodeViews(AlignFrame af)
2202 int size = af.alignPanels.size();
2208 // FIXME: ideally should use UI interface API
2209 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2210 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2211 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2212 for (int i = 0; i < size; i++)
2214 AlignmentPanel ap = af.alignPanels.get(i);
2216 AlignFrame newaf = new AlignFrame(ap);
2218 // transfer reference for existing feature settings to new alignFrame
2219 if (ap == af.alignPanel)
2221 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2223 newaf.featureSettings = viewFeatureSettings;
2225 newaf.setFeatureSettingsGeometry(fsBounds);
2229 * Restore the view's last exploded frame geometry if known. Multiple views from
2230 * one exploded frame share and restore the same (frame) position and size.
2232 Rectangle geometry = ap.av.getExplodedGeometry();
2233 if (geometry != null)
2235 newaf.setBounds(geometry);
2238 ap.av.setGatherViewsHere(false);
2240 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2241 AlignFrame.DEFAULT_HEIGHT);
2242 // and materialise a new feature settings dialog instance for the new
2244 // (closes the old as if 'OK' was pressed)
2245 if (ap == af.alignPanel && newaf.featureSettings != null
2246 && newaf.featureSettings.isOpen()
2247 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2249 newaf.showFeatureSettingsUI();
2253 af.featureSettings = null;
2254 af.alignPanels.clear();
2255 af.closeMenuItem_actionPerformed(true);
2260 * Gather expanded views (separate AlignFrame's) with the same sequence set
2261 * identifier back in to this frame as additional views, and close the
2262 * expanded views. Note the expanded frames may themselves have multiple
2263 * views. We take the lot.
2267 public void gatherViews(AlignFrame source)
2269 source.viewport.setGatherViewsHere(true);
2270 source.viewport.setExplodedGeometry(source.getBounds());
2271 JInternalFrame[] frames = desktop.getAllFrames();
2272 String viewId = source.viewport.getSequenceSetId();
2273 for (int t = 0; t < frames.length; t++)
2275 if (frames[t] instanceof AlignFrame && frames[t] != source)
2277 AlignFrame af = (AlignFrame) frames[t];
2278 boolean gatherThis = false;
2279 for (int a = 0; a < af.alignPanels.size(); a++)
2281 AlignmentPanel ap = af.alignPanels.get(a);
2282 if (viewId.equals(ap.av.getSequenceSetId()))
2285 ap.av.setGatherViewsHere(false);
2286 ap.av.setExplodedGeometry(af.getBounds());
2287 source.addAlignmentPanel(ap, false);
2293 if (af.featureSettings != null && af.featureSettings.isOpen())
2295 if (source.featureSettings == null)
2297 // preserve the feature settings geometry for this frame
2298 source.featureSettings = af.featureSettings;
2299 source.setFeatureSettingsGeometry(
2300 af.getFeatureSettingsGeometry());
2304 // close it and forget
2305 af.featureSettings.close();
2308 af.alignPanels.clear();
2309 af.closeMenuItem_actionPerformed(true);
2314 // refresh the feature setting UI for the source frame if it exists
2315 if (source.featureSettings != null && source.featureSettings.isOpen())
2317 source.showFeatureSettingsUI();
2322 public JInternalFrame[] getAllFrames()
2324 return desktop.getAllFrames();
2328 * Checks the given url to see if it gives a response indicating that the user
2329 * should be informed of a new questionnaire.
2333 public void checkForQuestionnaire(String url)
2335 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2336 // javax.swing.SwingUtilities.invokeLater(jvq);
2337 new Thread(jvq).start();
2340 public void checkURLLinks()
2342 // Thread off the URL link checker
2343 addDialogThread(new Runnable()
2348 if (Cache.getDefault("CHECKURLLINKS", true))
2350 // check what the actual links are - if it's just the default don't
2351 // bother with the warning
2352 List<String> links = Preferences.sequenceUrlLinks
2355 // only need to check links if there is one with a
2356 // SEQUENCE_ID which is not the default EMBL_EBI link
2357 ListIterator<String> li = links.listIterator();
2358 boolean check = false;
2359 List<JLabel> urls = new ArrayList<>();
2360 while (li.hasNext())
2362 String link = li.next();
2363 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2364 && !UrlConstants.isDefaultString(link))
2367 int barPos = link.indexOf("|");
2368 String urlMsg = barPos == -1 ? link
2369 : link.substring(0, barPos) + ": "
2370 + link.substring(barPos + 1);
2371 urls.add(new JLabel(urlMsg));
2379 // ask user to check in case URL links use old style tokens
2380 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2381 JPanel msgPanel = new JPanel();
2382 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2383 msgPanel.add(Box.createVerticalGlue());
2384 JLabel msg = new JLabel(MessageManager
2385 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2386 JLabel msg2 = new JLabel(MessageManager
2387 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2389 for (JLabel url : urls)
2395 final JCheckBox jcb = new JCheckBox(
2396 MessageManager.getString("label.do_not_display_again"));
2397 jcb.addActionListener(new ActionListener()
2400 public void actionPerformed(ActionEvent e)
2402 // update Cache settings for "don't show this again"
2403 boolean showWarningAgain = !jcb.isSelected();
2404 Cache.setProperty("CHECKURLLINKS",
2405 Boolean.valueOf(showWarningAgain).toString());
2410 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2412 .getString("label.SEQUENCE_ID_no_longer_used"),
2413 JvOptionPane.WARNING_MESSAGE);
2420 * Proxy class for JDesktopPane which optionally displays the current memory
2421 * usage and highlights the desktop area with a red bar if free memory runs
2426 public class MyDesktopPane extends JDesktopPane implements Runnable
2428 private static final float ONE_MB = 1048576f;
2430 boolean showMemoryUsage = false;
2434 java.text.NumberFormat df;
2436 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2439 public MyDesktopPane(boolean showMemoryUsage)
2441 showMemoryUsage(showMemoryUsage);
2444 public void showMemoryUsage(boolean showMemory)
2446 this.showMemoryUsage = showMemory;
2449 Thread worker = new Thread(this);
2455 public boolean isShowMemoryUsage()
2457 return showMemoryUsage;
2463 df = java.text.NumberFormat.getNumberInstance();
2464 df.setMaximumFractionDigits(2);
2465 runtime = Runtime.getRuntime();
2467 while (showMemoryUsage)
2471 maxMemory = runtime.maxMemory() / ONE_MB;
2472 allocatedMemory = runtime.totalMemory() / ONE_MB;
2473 freeMemory = runtime.freeMemory() / ONE_MB;
2474 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2476 percentUsage = (totalFreeMemory / maxMemory) * 100;
2478 // if (percentUsage < 20)
2480 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2482 // instance.set.setBorder(border1);
2485 // sleep after showing usage
2487 } catch (Exception ex)
2489 ex.printStackTrace();
2495 public void paintComponent(Graphics g)
2497 if (showMemoryUsage && g != null && df != null)
2499 if (percentUsage < 20)
2501 g.setColor(Color.red);
2503 FontMetrics fm = g.getFontMetrics();
2506 g.drawString(MessageManager.formatMessage("label.memory_stats",
2508 { df.format(totalFreeMemory), df.format(maxMemory),
2509 df.format(percentUsage) }),
2510 10, getHeight() - fm.getHeight());
2514 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2515 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2520 * Accessor method to quickly get all the AlignmentFrames loaded.
2522 * @return an array of AlignFrame, or null if none found
2524 public static AlignFrame[] getAlignFrames()
2526 if (Jalview.isHeadlessMode())
2528 // Desktop.desktop is null in headless mode
2529 return new AlignFrame[] { Jalview.currentAlignFrame };
2532 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2538 List<AlignFrame> avp = new ArrayList<>();
2540 for (int i = frames.length - 1; i > -1; i--)
2542 if (frames[i] instanceof AlignFrame)
2544 avp.add((AlignFrame) frames[i]);
2546 else if (frames[i] instanceof SplitFrame)
2549 * Also check for a split frame containing an AlignFrame
2551 GSplitFrame sf = (GSplitFrame) frames[i];
2552 if (sf.getTopFrame() instanceof AlignFrame)
2554 avp.add((AlignFrame) sf.getTopFrame());
2556 if (sf.getBottomFrame() instanceof AlignFrame)
2558 avp.add((AlignFrame) sf.getBottomFrame());
2562 if (avp.size() == 0)
2566 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2571 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2575 public GStructureViewer[] getJmols()
2577 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2583 List<GStructureViewer> avp = new ArrayList<>();
2585 for (int i = frames.length - 1; i > -1; i--)
2587 if (frames[i] instanceof AppJmol)
2589 GStructureViewer af = (GStructureViewer) frames[i];
2593 if (avp.size() == 0)
2597 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2602 * Add Groovy Support to Jalview
2605 public void groovyShell_actionPerformed()
2609 openGroovyConsole();
2610 } catch (Exception ex)
2612 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2613 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2615 MessageManager.getString("label.couldnt_create_groovy_shell"),
2616 MessageManager.getString("label.groovy_support_failed"),
2617 JvOptionPane.ERROR_MESSAGE);
2622 * Open the Groovy console
2624 void openGroovyConsole()
2626 if (groovyConsole == null)
2628 groovyConsole = new groovy.ui.Console();
2629 groovyConsole.setVariable("Jalview", this);
2630 groovyConsole.run();
2633 * We allow only one console at a time, so that AlignFrame menu option
2634 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2635 * enable 'Run script', when the console is opened, and the reverse when it is
2638 Window window = (Window) groovyConsole.getFrame();
2639 window.addWindowListener(new WindowAdapter()
2642 public void windowClosed(WindowEvent e)
2645 * rebind CMD-Q from Groovy Console to Jalview Quit
2648 enableExecuteGroovy(false);
2654 * show Groovy console window (after close and reopen)
2656 ((Window) groovyConsole.getFrame()).setVisible(true);
2659 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2660 * opening a second console
2662 enableExecuteGroovy(true);
2666 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2667 * binding when opened
2669 protected void addQuitHandler()
2672 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2674 .getKeyStroke(KeyEvent.VK_Q,
2675 jalview.util.ShortcutKeyMaskExWrapper
2676 .getMenuShortcutKeyMaskEx()),
2678 getRootPane().getActionMap().put("Quit", new AbstractAction()
2681 public void actionPerformed(ActionEvent e)
2689 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2692 * true if Groovy console is open
2694 public void enableExecuteGroovy(boolean enabled)
2697 * disable opening a second Groovy console (or re-enable when the console is
2700 groovyShell.setEnabled(!enabled);
2702 AlignFrame[] alignFrames = getAlignFrames();
2703 if (alignFrames != null)
2705 for (AlignFrame af : alignFrames)
2707 af.setGroovyEnabled(enabled);
2713 * Progress bars managed by the IProgressIndicator method.
2715 private Hashtable<Long, JPanel> progressBars;
2717 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2722 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2725 public void setProgressBar(String message, long id)
2727 if (progressBars == null)
2729 progressBars = new Hashtable<>();
2730 progressBarHandlers = new Hashtable<>();
2733 if (progressBars.get(Long.valueOf(id)) != null)
2735 JPanel panel = progressBars.remove(Long.valueOf(id));
2736 if (progressBarHandlers.contains(Long.valueOf(id)))
2738 progressBarHandlers.remove(Long.valueOf(id));
2740 removeProgressPanel(panel);
2744 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2751 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2752 * jalview.gui.IProgressIndicatorHandler)
2755 public void registerHandler(final long id,
2756 final IProgressIndicatorHandler handler)
2758 if (progressBarHandlers == null
2759 || !progressBars.containsKey(Long.valueOf(id)))
2761 throw new Error(MessageManager.getString(
2762 "error.call_setprogressbar_before_registering_handler"));
2764 progressBarHandlers.put(Long.valueOf(id), handler);
2765 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2766 if (handler.canCancel())
2768 JButton cancel = new JButton(
2769 MessageManager.getString("action.cancel"));
2770 final IProgressIndicator us = this;
2771 cancel.addActionListener(new ActionListener()
2775 public void actionPerformed(ActionEvent e)
2777 handler.cancelActivity(id);
2778 us.setProgressBar(MessageManager
2779 .formatMessage("label.cancelled_params", new Object[]
2780 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2784 progressPanel.add(cancel, BorderLayout.EAST);
2790 * @return true if any progress bars are still active
2793 public boolean operationInProgress()
2795 if (progressBars != null && progressBars.size() > 0)
2803 * This will return the first AlignFrame holding the given viewport instance.
2804 * It will break if there are more than one AlignFrames viewing a particular
2808 * @return alignFrame for viewport
2810 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2812 if (desktop != null)
2814 AlignmentPanel[] aps = getAlignmentPanels(
2815 viewport.getSequenceSetId());
2816 for (int panel = 0; aps != null && panel < aps.length; panel++)
2818 if (aps[panel] != null && aps[panel].av == viewport)
2820 return aps[panel].alignFrame;
2827 public VamsasApplication getVamsasApplication()
2829 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2835 * flag set if jalview GUI is being operated programmatically
2837 private boolean inBatchMode = false;
2840 * check if jalview GUI is being operated programmatically
2842 * @return inBatchMode
2844 public boolean isInBatchMode()
2850 * set flag if jalview GUI is being operated programmatically
2852 * @param inBatchMode
2854 public void setInBatchMode(boolean inBatchMode)
2856 this.inBatchMode = inBatchMode;
2860 * start service discovery and wait till it is done
2862 public void startServiceDiscovery()
2864 startServiceDiscovery(false);
2868 * start service discovery threads - blocking or non-blocking
2872 public void startServiceDiscovery(boolean blocking)
2874 startServiceDiscovery(blocking, false);
2878 * start service discovery threads
2881 * - false means call returns immediately
2882 * @param ignore_SHOW_JWS2_SERVICES_preference
2883 * - when true JABA services are discovered regardless of user's JWS2
2884 * discovery preference setting
2886 public void startServiceDiscovery(boolean blocking,
2887 boolean ignore_SHOW_JWS2_SERVICES_preference)
2889 boolean alive = true;
2890 Thread t0 = null, t1 = null, t2 = null;
2891 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2894 // todo: changesupport handlers need to be transferred
2895 if (discoverer == null)
2897 discoverer = new jalview.ws.jws1.Discoverer();
2898 // register PCS handler for desktop.
2899 discoverer.addPropertyChangeListener(changeSupport);
2901 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2902 // until we phase out completely
2903 (t0 = new Thread(discoverer)).start();
2906 if (ignore_SHOW_JWS2_SERVICES_preference
2907 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2909 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2910 .startDiscoverer(changeSupport);
2914 // TODO: do rest service discovery
2923 } catch (Exception e)
2926 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2927 || (t3 != null && t3.isAlive())
2928 || (t0 != null && t0.isAlive());
2934 * called to check if the service discovery process completed successfully.
2938 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2940 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2942 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2943 .getErrorMessages();
2946 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2948 if (serviceChangedDialog == null)
2950 // only run if we aren't already displaying one of these.
2951 addDialogThread(serviceChangedDialog = new Runnable()
2958 * JalviewDialog jd =new JalviewDialog() {
2960 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2962 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2964 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2966 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2968 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2969 * + " or mis-configured HTTP proxy settings.<br/>" +
2970 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2971 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2972 * true, true, "Web Service Configuration Problem", 450, 400);
2974 * jd.waitForInput();
2976 JvOptionPane.showConfirmDialog(Desktop.desktop,
2977 new JLabel("<html><table width=\"450\"><tr><td>"
2978 + ermsg + "</td></tr></table>"
2979 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2980 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2981 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2982 + " Tools->Preferences dialog box to change them.</p></html>"),
2983 "Web Service Configuration Problem",
2984 JvOptionPane.DEFAULT_OPTION,
2985 JvOptionPane.ERROR_MESSAGE);
2986 serviceChangedDialog = null;
2994 jalview.bin.Console.error(
2995 "Errors reported by JABA discovery service. Check web services preferences.\n"
3002 private Runnable serviceChangedDialog = null;
3005 * start a thread to open a URL in the configured browser. Pops up a warning
3006 * dialog to the user if there is an exception when calling out to the browser
3011 public static void showUrl(final String url)
3013 showUrl(url, Desktop.instance);
3017 * Like showUrl but allows progress handler to be specified
3021 * (null) or object implementing IProgressIndicator
3023 public static void showUrl(final String url,
3024 final IProgressIndicator progress)
3026 new Thread(new Runnable()
3033 if (progress != null)
3035 progress.setProgressBar(MessageManager
3036 .formatMessage("status.opening_params", new Object[]
3037 { url }), this.hashCode());
3039 jalview.util.BrowserLauncher.openURL(url);
3040 } catch (Exception ex)
3042 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3044 .getString("label.web_browser_not_found_unix"),
3045 MessageManager.getString("label.web_browser_not_found"),
3046 JvOptionPane.WARNING_MESSAGE);
3048 ex.printStackTrace();
3050 if (progress != null)
3052 progress.setProgressBar(null, this.hashCode());
3058 public static WsParamSetManager wsparamManager = null;
3060 public static ParamManager getUserParameterStore()
3062 if (wsparamManager == null)
3064 wsparamManager = new WsParamSetManager();
3066 return wsparamManager;
3070 * static hyperlink handler proxy method for use by Jalview's internal windows
3074 public static void hyperlinkUpdate(HyperlinkEvent e)
3076 if (e.getEventType() == EventType.ACTIVATED)
3081 url = e.getURL().toString();
3082 Desktop.showUrl(url);
3083 } catch (Exception x)
3088 .error("Couldn't handle string " + url + " as a URL.");
3090 // ignore any exceptions due to dud links.
3097 * single thread that handles display of dialogs to user.
3099 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3102 * flag indicating if dialogExecutor should try to acquire a permit
3104 private volatile boolean dialogPause = true;
3109 private Semaphore block = new Semaphore(0);
3111 private static groovy.ui.Console groovyConsole;
3114 * add another dialog thread to the queue
3118 public void addDialogThread(final Runnable prompter)
3120 dialogExecutor.submit(new Runnable()
3127 acquireDialogQueue();
3129 if (instance == null)
3135 SwingUtilities.invokeAndWait(prompter);
3136 } catch (Exception q)
3138 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3145 private boolean dialogQueueStarted = false;
3147 public void startDialogQueue()
3149 if (dialogQueueStarted)
3153 // set the flag so we don't pause waiting for another permit and semaphore
3154 // the current task to begin
3155 releaseDialogQueue();
3156 dialogQueueStarted = true;
3159 public void acquireDialogQueue()
3165 } catch (InterruptedException e)
3167 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3172 public void releaseDialogQueue()
3179 dialogPause = false;
3183 * Outputs an image of the desktop to file in EPS format, after prompting the
3184 * user for choice of Text or Lineart character rendering (unless a preference
3185 * has been set). The file name is generated as
3188 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3192 protected void snapShotWindow_actionPerformed(ActionEvent e)
3194 // currently the menu option to do this is not shown
3197 int width = getWidth();
3198 int height = getHeight();
3200 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3201 ImageWriterI writer = new ImageWriterI()
3204 public void exportImage(Graphics g) throws Exception
3207 jalview.bin.Console.info("Successfully written snapshot to file "
3208 + of.getAbsolutePath());
3211 String title = "View of desktop";
3212 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3216 exporter.doExport(of, this, width, height, title);
3217 } catch (ImageOutputException ioex)
3219 jalview.bin.Console.error(
3220 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3226 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3227 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3228 * and location last time the view was expanded (if any). However it does not
3229 * remember the split pane divider location - this is set to match the
3230 * 'exploding' frame.
3234 public void explodeViews(SplitFrame sf)
3236 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3237 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3238 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3240 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3242 int viewCount = topPanels.size();
3249 * Processing in reverse order works, forwards order leaves the first panels not
3250 * visible. I don't know why!
3252 for (int i = viewCount - 1; i >= 0; i--)
3255 * Make new top and bottom frames. These take over the respective AlignmentPanel
3256 * objects, including their AlignmentViewports, so the cdna/protein
3257 * relationships between the viewports is carried over to the new split frames.
3259 * explodedGeometry holds the (x, y) position of the previously exploded
3260 * SplitFrame, and the (width, height) of the AlignFrame component
3262 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3263 AlignFrame newTopFrame = new AlignFrame(topPanel);
3264 newTopFrame.setSize(oldTopFrame.getSize());
3265 newTopFrame.setVisible(true);
3266 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3267 .getExplodedGeometry();
3268 if (geometry != null)
3270 newTopFrame.setSize(geometry.getSize());
3273 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3274 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3275 newBottomFrame.setSize(oldBottomFrame.getSize());
3276 newBottomFrame.setVisible(true);
3277 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3278 .getExplodedGeometry();
3279 if (geometry != null)
3281 newBottomFrame.setSize(geometry.getSize());
3284 topPanel.av.setGatherViewsHere(false);
3285 bottomPanel.av.setGatherViewsHere(false);
3286 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3288 if (geometry != null)
3290 splitFrame.setLocation(geometry.getLocation());
3292 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3296 * Clear references to the panels (now relocated in the new SplitFrames) before
3297 * closing the old SplitFrame.
3300 bottomPanels.clear();
3305 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3306 * back into the given SplitFrame as additional views. Note that the gathered
3307 * frames may themselves have multiple views.
3311 public void gatherViews(GSplitFrame source)
3314 * special handling of explodedGeometry for a view within a SplitFrame: - it
3315 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3316 * height) of the AlignFrame component
3318 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3319 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3320 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3321 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3322 myBottomFrame.viewport
3323 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3324 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3325 myTopFrame.viewport.setGatherViewsHere(true);
3326 myBottomFrame.viewport.setGatherViewsHere(true);
3327 String topViewId = myTopFrame.viewport.getSequenceSetId();
3328 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3330 JInternalFrame[] frames = desktop.getAllFrames();
3331 for (JInternalFrame frame : frames)
3333 if (frame instanceof SplitFrame && frame != source)
3335 SplitFrame sf = (SplitFrame) frame;
3336 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3337 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3338 boolean gatherThis = false;
3339 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3341 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3342 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3343 if (topViewId.equals(topPanel.av.getSequenceSetId())
3344 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3347 topPanel.av.setGatherViewsHere(false);
3348 bottomPanel.av.setGatherViewsHere(false);
3349 topPanel.av.setExplodedGeometry(
3350 new Rectangle(sf.getLocation(), topFrame.getSize()));
3351 bottomPanel.av.setExplodedGeometry(
3352 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3353 myTopFrame.addAlignmentPanel(topPanel, false);
3354 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3360 topFrame.getAlignPanels().clear();
3361 bottomFrame.getAlignPanels().clear();
3368 * The dust settles...give focus to the tab we did this from.
3370 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3373 public static groovy.ui.Console getGroovyConsole()
3375 return groovyConsole;
3379 * handles the payload of a drag and drop event.
3381 * TODO refactor to desktop utilities class
3384 * - Data source strings extracted from the drop event
3386 * - protocol for each data source extracted from the drop event
3390 * - the payload from the drop event
3393 public static void transferFromDropTarget(List<Object> files,
3394 List<DataSourceType> protocols, DropTargetDropEvent evt,
3395 Transferable t) throws Exception
3398 DataFlavor uriListFlavor = new DataFlavor(
3399 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3402 urlFlavour = new DataFlavor(
3403 "application/x-java-url; class=java.net.URL");
3404 } catch (ClassNotFoundException cfe)
3406 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3410 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3415 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3416 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3417 // means url may be null.
3420 protocols.add(DataSourceType.URL);
3421 files.add(url.toString());
3422 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3423 + files.get(files.size() - 1));
3428 if (Platform.isAMacAndNotJS())
3430 jalview.bin.Console.errPrintln(
3431 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3434 } catch (Throwable ex)
3436 jalview.bin.Console.debug("URL drop handler failed.", ex);
3439 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3441 // Works on Windows and MacOSX
3442 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3443 for (Object file : (List) t
3444 .getTransferData(DataFlavor.javaFileListFlavor))
3447 protocols.add(DataSourceType.FILE);
3452 // Unix like behaviour
3453 boolean added = false;
3455 if (t.isDataFlavorSupported(uriListFlavor))
3457 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3458 // This is used by Unix drag system
3459 data = (String) t.getTransferData(uriListFlavor);
3463 // fallback to text: workaround - on OSX where there's a JVM bug
3465 .debug("standard URIListFlavor failed. Trying text");
3466 // try text fallback
3467 DataFlavor textDf = new DataFlavor(
3468 "text/plain;class=java.lang.String");
3469 if (t.isDataFlavorSupported(textDf))
3471 data = (String) t.getTransferData(textDf);
3474 jalview.bin.Console.debug("Plain text drop content returned "
3475 + (data == null ? "Null - failed" : data));
3480 while (protocols.size() < files.size())
3482 jalview.bin.Console.debug("Adding missing FILE protocol for "
3483 + files.get(protocols.size()));
3484 protocols.add(DataSourceType.FILE);
3486 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3487 data, "\r\n"); st.hasMoreTokens();)
3490 String s = st.nextToken();
3491 if (s.startsWith("#"))
3493 // the line is a comment (as per the RFC 2483)
3496 java.net.URI uri = new java.net.URI(s);
3497 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3499 protocols.add(DataSourceType.URL);
3500 files.add(uri.toString());
3504 // otherwise preserve old behaviour: catch all for file objects
3505 java.io.File file = new java.io.File(uri);
3506 protocols.add(DataSourceType.FILE);
3507 files.add(file.toString());
3512 if (jalview.bin.Console.isDebugEnabled())
3514 if (data == null || !added)
3517 if (t.getTransferDataFlavors() != null
3518 && t.getTransferDataFlavors().length > 0)
3520 jalview.bin.Console.debug(
3521 "Couldn't resolve drop data. Here are the supported flavors:");
3522 for (DataFlavor fl : t.getTransferDataFlavors())
3524 jalview.bin.Console.debug(
3525 "Supported transfer dataflavor: " + fl.toString());
3526 Object df = t.getTransferData(fl);
3529 jalview.bin.Console.debug("Retrieves: " + df);
3533 jalview.bin.Console.debug("Retrieved nothing");
3540 .debug("Couldn't resolve dataflavor for drop: "
3546 if (Platform.isWindowsAndNotJS())
3549 .debug("Scanning dropped content for Windows Link Files");
3551 // resolve any .lnk files in the file drop
3552 for (int f = 0; f < files.size(); f++)
3554 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3555 if (protocols.get(f).equals(DataSourceType.FILE)
3556 && (source.endsWith(".lnk") || source.endsWith(".url")
3557 || source.endsWith(".site")))
3561 Object obj = files.get(f);
3562 File lf = (obj instanceof File ? (File) obj
3563 : new File((String) obj));
3564 // process link file to get a URL
3565 jalview.bin.Console.debug("Found potential link file: " + lf);
3566 WindowsShortcut wscfile = new WindowsShortcut(lf);
3567 String fullname = wscfile.getRealFilename();
3568 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3569 files.set(f, fullname);
3570 jalview.bin.Console.debug("Parsed real filename " + fullname
3571 + " to extract protocol: " + protocols.get(f));
3572 } catch (Exception ex)
3574 jalview.bin.Console.error(
3575 "Couldn't parse " + files.get(f) + " as a link file.",
3584 * Sets the Preferences property for experimental features to True or False
3585 * depending on the state of the controlling menu item
3588 protected void showExperimental_actionPerformed(boolean selected)
3590 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3594 * Answers a (possibly empty) list of any structure viewer frames (currently
3595 * for either Jmol or Chimera) which are currently open. This may optionally
3596 * be restricted to viewers of a specified class, or viewers linked to a
3597 * specified alignment panel.
3600 * if not null, only return viewers linked to this panel
3601 * @param structureViewerClass
3602 * if not null, only return viewers of this class
3605 public List<StructureViewerBase> getStructureViewers(
3606 AlignmentPanel apanel,
3607 Class<? extends StructureViewerBase> structureViewerClass)
3609 List<StructureViewerBase> result = new ArrayList<>();
3610 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3612 for (JInternalFrame frame : frames)
3614 if (frame instanceof StructureViewerBase)
3616 if (structureViewerClass == null
3617 || structureViewerClass.isInstance(frame))
3620 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3622 result.add((StructureViewerBase) frame);
3630 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3632 private static boolean debugScaleMessageDone = false;
3634 public static void debugScaleMessage(Graphics g)
3636 if (debugScaleMessageDone)
3640 // output used by tests to check HiDPI scaling settings in action
3643 Graphics2D gg = (Graphics2D) g;
3646 AffineTransform t = gg.getTransform();
3647 double scaleX = t.getScaleX();
3648 double scaleY = t.getScaleY();
3649 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3650 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3651 debugScaleMessageDone = true;
3655 jalview.bin.Console.debug("Desktop graphics null");
3657 } catch (Exception e)
3659 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3664 * closes the current instance window, disposes and forgets about it.
3666 public static void closeDesktop()
3668 if (Desktop.instance != null)
3670 Desktop.instance.closeAll_actionPerformed(null);
3671 Desktop.instance.setVisible(false);
3672 Desktop us = Desktop.instance;
3673 Desktop.instance = null;
3674 // call dispose in a separate thread - try to avoid indirect deadlocks
3675 new Thread(new Runnable()
3680 ExecutorService dex = us.dialogExecutor;
3684 us.dialogExecutor = null;
3685 us.block.drainPermits();
3694 * checks if any progress bars are being displayed in any of the windows
3695 * managed by the desktop
3699 public boolean operationsAreInProgress()
3701 JInternalFrame[] frames = getAllFrames();
3702 for (JInternalFrame frame : frames)
3704 if (frame instanceof IProgressIndicator)
3706 if (((IProgressIndicator) frame).operationInProgress())
3712 return operationInProgress();
3716 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3717 * The way the modal JInternalFrame is made means it cannot be a child of an
3718 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3720 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3722 protected static void addModal(AlignFrame af, JInternalFrame jif)
3724 alignFrameModalMap.put(af, jif);
3727 protected static void closeModal(AlignFrame af)
3729 if (!alignFrameModalMap.containsKey(af))
3733 JInternalFrame jif = alignFrameModalMap.get(af);
3738 jif.setClosed(true);
3739 } catch (PropertyVetoException e)
3741 e.printStackTrace();
3744 alignFrameModalMap.remove(af);
3747 public void nonBlockingDialog(String title, String message, String button,
3748 int type, boolean scrollable, boolean modal)
3750 nonBlockingDialog(32, 2, title, message, null, button, type, scrollable,
3754 public void nonBlockingDialog(int width, int height, String title,
3755 String message, String boxtext, String button, int type,
3756 boolean scrollable, boolean html, boolean modal)
3760 type = JvOptionPane.WARNING_MESSAGE;
3762 JLabel jl = new JLabel(message);
3764 JTextComponent jtc = null;
3767 JTextPane jtp = new JTextPane();
3768 jtp.setContentType("text/html");
3769 jtp.setEditable(false);
3770 jtp.setAutoscrolls(true);
3771 jtp.setText(boxtext);
3777 JTextArea jta = new JTextArea(height, width);
3778 // jta.setLineWrap(true);
3779 jta.setEditable(false);
3780 jta.setWrapStyleWord(true);
3781 jta.setAutoscrolls(true);
3782 jta.setText(boxtext);
3787 JScrollPane jsp = scrollable
3788 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3789 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3792 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3794 JPanel jp = new JPanel();
3795 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3797 if (message != null)
3799 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3802 if (boxtext != null)
3806 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3811 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3816 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3818 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3820 { button }, button, modal, null, false);