2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.datatransfer.Clipboard;
36 import java.awt.datatransfer.ClipboardOwner;
37 import java.awt.datatransfer.DataFlavor;
38 import java.awt.datatransfer.Transferable;
39 import java.awt.dnd.DnDConstants;
40 import java.awt.dnd.DropTargetDragEvent;
41 import java.awt.dnd.DropTargetDropEvent;
42 import java.awt.dnd.DropTargetEvent;
43 import java.awt.dnd.DropTargetListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.WindowAdapter;
51 import java.awt.event.WindowEvent;
52 import java.awt.geom.AffineTransform;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
55 import java.beans.PropertyVetoException;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Locale;
69 import java.util.Vector;
70 import java.util.concurrent.ExecutorService;
71 import java.util.concurrent.Executors;
72 import java.util.concurrent.Semaphore;
74 import javax.swing.AbstractAction;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.Box;
78 import javax.swing.BoxLayout;
79 import javax.swing.DefaultDesktopManager;
80 import javax.swing.DesktopManager;
81 import javax.swing.InputMap;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JComboBox;
85 import javax.swing.JComponent;
86 import javax.swing.JDesktopPane;
87 import javax.swing.JFrame;
88 import javax.swing.JInternalFrame;
89 import javax.swing.JLabel;
90 import javax.swing.JMenuItem;
91 import javax.swing.JOptionPane;
92 import javax.swing.JPanel;
93 import javax.swing.JPopupMenu;
94 import javax.swing.JProgressBar;
95 import javax.swing.JScrollPane;
96 import javax.swing.JTextArea;
97 import javax.swing.JTextField;
98 import javax.swing.JTextPane;
99 import javax.swing.KeyStroke;
100 import javax.swing.SwingUtilities;
101 import javax.swing.WindowConstants;
102 import javax.swing.event.HyperlinkEvent;
103 import javax.swing.event.HyperlinkEvent.EventType;
104 import javax.swing.event.InternalFrameAdapter;
105 import javax.swing.event.InternalFrameEvent;
106 import javax.swing.text.JTextComponent;
108 import org.stackoverflowusers.file.WindowsShortcut;
110 import jalview.api.AlignViewportI;
111 import jalview.api.AlignmentViewPanel;
112 import jalview.api.structures.JalviewStructureDisplayI;
113 import jalview.bin.Cache;
114 import jalview.bin.Jalview;
115 import jalview.bin.Jalview.ExitCode;
116 import jalview.bin.argparser.Arg;
117 import jalview.datamodel.Alignment;
118 import jalview.datamodel.HiddenColumns;
119 import jalview.datamodel.Sequence;
120 import jalview.datamodel.SequenceI;
121 import jalview.gui.ImageExporter.ImageWriterI;
122 import jalview.gui.QuitHandler.QResponse;
123 import jalview.io.BackupFiles;
124 import jalview.io.DataSourceType;
125 import jalview.io.FileFormat;
126 import jalview.io.FileFormatException;
127 import jalview.io.FileFormatI;
128 import jalview.io.FileFormats;
129 import jalview.io.FileLoader;
130 import jalview.io.FormatAdapter;
131 import jalview.io.IdentifyFile;
132 import jalview.io.JalviewFileChooser;
133 import jalview.io.JalviewFileView;
134 import jalview.io.exceptions.ImageOutputException;
135 import jalview.jbgui.GSplitFrame;
136 import jalview.jbgui.GStructureViewer;
137 import jalview.project.Jalview2XML;
138 import jalview.structure.StructureSelectionManager;
139 import jalview.urls.IdOrgSettings;
140 import jalview.util.BrowserLauncher;
141 import jalview.util.ChannelProperties;
142 import jalview.util.ImageMaker.TYPE;
143 import jalview.util.LaunchUtils;
144 import jalview.util.MessageManager;
145 import jalview.util.Platform;
146 import jalview.util.ShortcutKeyMaskExWrapper;
147 import jalview.util.UrlConstants;
148 import jalview.viewmodel.AlignmentViewport;
149 import jalview.ws.params.ParamManager;
150 import jalview.ws.utils.UrlDownloadClient;
157 * @version $Revision: 1.155 $
159 public class Desktop extends jalview.jbgui.GDesktop
160 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
161 jalview.api.StructureSelectionManagerProvider
163 private static final String CITATION;
166 URL bg_logo_url = ChannelProperties.getImageURL(
167 "bg_logo." + String.valueOf(SplashScreen.logoSize));
168 URL uod_logo_url = ChannelProperties.getImageURL(
169 "uod_banner." + String.valueOf(SplashScreen.logoSize));
170 boolean logo = (bg_logo_url != null || uod_logo_url != null);
171 StringBuilder sb = new StringBuilder();
173 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
178 sb.append(bg_logo_url == null ? ""
179 : "<img alt=\"Barton Group logo\" src=\""
180 + bg_logo_url.toString() + "\">");
181 sb.append(uod_logo_url == null ? ""
182 : " <img alt=\"University of Dundee shield\" src=\""
183 + uod_logo_url.toString() + "\">");
185 "<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>");
186 sb.append("<br><br>If you use Jalview, please cite:"
187 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
188 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
189 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
190 CITATION = sb.toString();
193 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
195 private static int DEFAULT_MIN_WIDTH = 300;
197 private static int DEFAULT_MIN_HEIGHT = 250;
199 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
201 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
203 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
205 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
207 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
209 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
211 public static void setLiveDragMode(boolean b)
213 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
214 : JDesktopPane.OUTLINE_DRAG_MODE;
216 desktop.setDragMode(DRAG_MODE);
219 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
221 public static boolean nosplash = false;
224 * news reader - null if it was never started.
226 private BlogReader jvnews = null;
228 private File projectFile;
232 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
234 public void addJalviewPropertyChangeListener(
235 PropertyChangeListener listener)
237 changeSupport.addJalviewPropertyChangeListener(listener);
241 * @param propertyName
243 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
244 * java.beans.PropertyChangeListener)
246 public void addJalviewPropertyChangeListener(String propertyName,
247 PropertyChangeListener listener)
249 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
253 * @param propertyName
255 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
256 * java.beans.PropertyChangeListener)
258 public void removeJalviewPropertyChangeListener(String propertyName,
259 PropertyChangeListener listener)
261 changeSupport.removeJalviewPropertyChangeListener(propertyName,
265 /** Singleton Desktop instance */
266 public static Desktop instance;
268 public static MyDesktopPane desktop;
270 public static MyDesktopPane getDesktop()
272 // BH 2018 could use currentThread() here as a reference to a
273 // Hashtable<Thread, MyDesktopPane> in JavaScript
277 static int openFrameCount = 0;
279 static final int xOffset = 30;
281 static final int yOffset = 30;
283 public static jalview.ws.jws1.Discoverer discoverer;
285 public static Object[] jalviewClipboard;
287 public static boolean internalCopy = false;
289 static int fileLoadingCount = 0;
291 class MyDesktopManager implements DesktopManager
294 private DesktopManager delegate;
296 public MyDesktopManager(DesktopManager delegate)
298 this.delegate = delegate;
302 public void activateFrame(JInternalFrame f)
306 delegate.activateFrame(f);
307 } catch (NullPointerException npe)
309 Point p = getMousePosition();
310 instance.showPasteMenu(p.x, p.y);
315 public void beginDraggingFrame(JComponent f)
317 delegate.beginDraggingFrame(f);
321 public void beginResizingFrame(JComponent f, int direction)
323 delegate.beginResizingFrame(f, direction);
327 public void closeFrame(JInternalFrame f)
329 delegate.closeFrame(f);
333 public void deactivateFrame(JInternalFrame f)
335 delegate.deactivateFrame(f);
339 public void deiconifyFrame(JInternalFrame f)
341 delegate.deiconifyFrame(f);
345 public void dragFrame(JComponent f, int newX, int newY)
351 delegate.dragFrame(f, newX, newY);
355 public void endDraggingFrame(JComponent f)
357 delegate.endDraggingFrame(f);
362 public void endResizingFrame(JComponent f)
364 delegate.endResizingFrame(f);
369 public void iconifyFrame(JInternalFrame f)
371 delegate.iconifyFrame(f);
375 public void maximizeFrame(JInternalFrame f)
377 delegate.maximizeFrame(f);
381 public void minimizeFrame(JInternalFrame f)
383 delegate.minimizeFrame(f);
387 public void openFrame(JInternalFrame f)
389 delegate.openFrame(f);
393 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
400 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
404 public void setBoundsForFrame(JComponent f, int newX, int newY,
405 int newWidth, int newHeight)
407 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
410 // All other methods, simply delegate
415 * Creates a new Desktop object.
421 * A note to implementors. It is ESSENTIAL that any activities that might
422 * block are spawned off as threads rather than waited for during this
427 doConfigureStructurePrefs();
428 setTitle(ChannelProperties.getProperty("app_name") + " "
429 + Cache.getProperty("VERSION"));
432 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
433 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
434 * officially documented or guaranteed to exist, so we access it via
435 * reflection. There appear to be unfathomable criteria about what this
436 * string can contain, and it if doesn't meet those criteria then "java"
437 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
438 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
439 * not. The reflection access may generate a warning: WARNING: An illegal
440 * reflective access operation has occurred WARNING: Illegal reflective
441 * access by jalview.gui.Desktop () to field
442 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
444 if (Platform.isLinux())
446 if (LaunchUtils.getJavaVersion() >= 11)
449 * Send this message to stderr as the warning that follows (due to
450 * reflection) also goes to stderr.
452 jalview.bin.Console.errPrintln(
453 "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.");
455 final String awtAppClassName = "awtAppClassName";
458 Toolkit xToolkit = Toolkit.getDefaultToolkit();
459 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
460 Field awtAppClassNameField = null;
462 if (Arrays.stream(declaredFields)
463 .anyMatch(f -> f.getName().equals(awtAppClassName)))
465 awtAppClassNameField = xToolkit.getClass()
466 .getDeclaredField(awtAppClassName);
469 String title = ChannelProperties.getProperty("app_name");
470 if (awtAppClassNameField != null)
472 awtAppClassNameField.setAccessible(true);
473 awtAppClassNameField.set(xToolkit, title);
478 .debug("XToolkit: " + awtAppClassName + " not found");
480 } catch (Exception e)
482 jalview.bin.Console.debug("Error setting " + awtAppClassName);
483 jalview.bin.Console.trace(Cache.getStackTraceString(e));
487 setIconImages(ChannelProperties.getIconList());
489 // override quit handling when GUI OS close [X] button pressed
490 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
491 addWindowListener(new WindowAdapter()
494 public void windowClosing(WindowEvent ev)
496 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
500 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
502 boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE,
503 "SHOW_JAVA_CONSOLE", false);
505 // start dialogue queue for single dialogues
508 if (!Platform.isJS())
515 Desktop.instance.acquireDialogQueue();
517 jconsole = new Console(this);
518 jconsole.setHeader(Cache.getVersionDetailsForConsole());
519 showConsole(showjconsole);
521 Desktop.instance.releaseDialogQueue();
524 desktop = new MyDesktopPane(selmemusage);
526 showMemusage.setSelected(selmemusage);
527 desktop.setBackground(Color.white);
529 getContentPane().setLayout(new BorderLayout());
530 // alternate config - have scrollbars - see notes in JAL-153
531 // JScrollPane sp = new JScrollPane();
532 // sp.getViewport().setView(desktop);
533 // getContentPane().add(sp, BorderLayout.CENTER);
535 // BH 2018 - just an experiment to try unclipped JInternalFrames.
538 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
541 getContentPane().add(desktop, BorderLayout.CENTER);
542 desktop.setDragMode(DRAG_MODE);
544 // This line prevents Windows Look&Feel resizing all new windows to maximum
545 // if previous window was maximised
546 desktop.setDesktopManager(new MyDesktopManager(
547 Platform.isJS() ? desktop.getDesktopManager()
548 : new DefaultDesktopManager()));
550 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
551 : Platform.isAMacAndNotJS()
552 ? new AquaInternalFrameManager(
553 desktop.getDesktopManager())
554 : desktop.getDesktopManager())));
557 Rectangle dims = getLastKnownDimensions("");
564 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
565 int xPos = Math.max(5, (screenSize.width - 900) / 2);
566 int yPos = Math.max(5, (screenSize.height - 650) / 2);
567 setBounds(xPos, yPos, 900, 650);
570 if (!Platform.isJS())
577 showNews.setVisible(false);
579 experimentalFeatures.setSelected(showExperimental());
581 getIdentifiersOrgData();
585 // Spawn a thread that shows the splashscreen
588 SwingUtilities.invokeLater(new Runnable()
593 new SplashScreen(true);
598 // Thread off a new instance of the file chooser - this reduces the time
599 // it takes to open it later on.
600 new Thread(new Runnable()
605 jalview.bin.Console.debug("Filechooser init thread started.");
606 String fileFormat = FileLoader.getUseDefaultFileFormat()
607 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
609 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
611 jalview.bin.Console.debug("Filechooser init thread finished.");
614 // Add the service change listener
615 changeSupport.addJalviewPropertyChangeListener("services",
616 new PropertyChangeListener()
620 public void propertyChange(PropertyChangeEvent evt)
623 .debug("Firing service changed event for "
624 + evt.getNewValue());
625 JalviewServicesChanged(evt);
630 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
633 this.addMouseListener(ma = new MouseAdapter()
636 public void mousePressed(MouseEvent evt)
638 if (evt.isPopupTrigger()) // Mac
640 showPasteMenu(evt.getX(), evt.getY());
645 public void mouseReleased(MouseEvent evt)
647 if (evt.isPopupTrigger()) // Windows
649 showPasteMenu(evt.getX(), evt.getY());
653 desktop.addMouseListener(ma);
657 // used for jalviewjsTest
658 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
664 * Answers true if user preferences to enable experimental features is True
669 public boolean showExperimental()
671 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
672 Boolean.FALSE.toString());
673 return Boolean.valueOf(experimental).booleanValue();
676 public void doConfigureStructurePrefs()
678 // configure services
679 StructureSelectionManager ssm = StructureSelectionManager
680 .getStructureSelectionManager(this);
681 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
683 ssm.setAddTempFacAnnot(
684 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
685 ssm.setProcessSecondaryStructure(
686 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
687 // JAL-3915 - RNAView is no longer an option so this has no effect
688 ssm.setSecStructServices(
689 Cache.getDefault(Preferences.USE_RNAVIEW, false));
693 ssm.setAddTempFacAnnot(false);
694 ssm.setProcessSecondaryStructure(false);
695 ssm.setSecStructServices(false);
699 public void checkForNews()
701 final Desktop me = this;
702 // Thread off the news reader, in case there are connection problems.
703 new Thread(new Runnable()
708 jalview.bin.Console.debug("Starting news thread.");
709 jvnews = new BlogReader(me);
710 showNews.setVisible(true);
711 jalview.bin.Console.debug("Completed news thread.");
716 public void getIdentifiersOrgData()
718 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
719 {// Thread off the identifiers fetcher
720 new Thread(new Runnable()
726 .debug("Downloading data from identifiers.org");
729 UrlDownloadClient.download(IdOrgSettings.getUrl(),
730 IdOrgSettings.getDownloadLocation());
731 } catch (IOException e)
734 .debug("Exception downloading identifiers.org data"
744 protected void showNews_actionPerformed(ActionEvent e)
746 showNews(showNews.isSelected());
749 void showNews(boolean visible)
751 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
752 showNews.setSelected(visible);
753 if (visible && !jvnews.isVisible())
755 new Thread(new Runnable()
760 long now = System.currentTimeMillis();
761 Desktop.instance.setProgressBar(
762 MessageManager.getString("status.refreshing_news"), now);
763 jvnews.refreshNews();
764 Desktop.instance.setProgressBar(null, now);
772 * recover the last known dimensions for a jalview window
775 * - empty string is desktop, all other windows have unique prefix
776 * @return null or last known dimensions scaled to current geometry (if last
777 * window geom was known)
779 Rectangle getLastKnownDimensions(String windowName)
781 // TODO: lock aspect ratio for scaling desktop Bug #0058199
782 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
783 String x = Cache.getProperty(windowName + "SCREEN_X");
784 String y = Cache.getProperty(windowName + "SCREEN_Y");
785 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
786 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
787 if ((x != null) && (y != null) && (width != null) && (height != null))
789 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
790 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
791 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
793 // attempt #1 - try to cope with change in screen geometry - this
794 // version doesn't preserve original jv aspect ratio.
795 // take ratio of current screen size vs original screen size.
796 double sw = ((1f * screenSize.width) / (1f * Integer
797 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
798 double sh = ((1f * screenSize.height) / (1f * Integer
799 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
800 // rescale the bounds depending upon the current screen geometry.
801 ix = (int) (ix * sw);
802 iw = (int) (iw * sw);
803 iy = (int) (iy * sh);
804 ih = (int) (ih * sh);
805 if (ix >= screenSize.width)
807 jalview.bin.Console.debug(
808 "Window geometry location recall error: shifting horizontal to within screenbounds.");
809 ix = ix % screenSize.width;
811 if (iy >= screenSize.height)
813 jalview.bin.Console.debug(
814 "Window geometry location recall error: shifting vertical to within screenbounds.");
815 iy = iy % screenSize.height;
817 jalview.bin.Console.debug(
818 "Got last known dimensions for " + windowName + ": x:" + ix
819 + " y:" + iy + " width:" + iw + " height:" + ih);
821 // return dimensions for new instance
822 return new Rectangle(ix, iy, iw, ih);
827 void showPasteMenu(int x, int y)
829 JPopupMenu popup = new JPopupMenu();
830 JMenuItem item = new JMenuItem(
831 MessageManager.getString("label.paste_new_window"));
832 item.addActionListener(new ActionListener()
835 public void actionPerformed(ActionEvent evt)
842 popup.show(this, x, y);
847 // quick patch for JAL-4150 - needs some more work and test coverage
848 // TODO - unify below and AlignFrame.paste()
849 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
850 // clipboard has come from a different alignment window than the one where
851 // paste has been called! JAL-4151
853 if (Desktop.jalviewClipboard != null)
855 // The clipboard was filled from within Jalview, we must use the
857 // And dataset from the copied alignment
858 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
859 // be doubly sure that we create *new* sequence objects.
860 SequenceI[] sequences = new SequenceI[newseq.length];
861 for (int i = 0; i < newseq.length; i++)
863 sequences[i] = new Sequence(newseq[i]);
865 Alignment alignment = new Alignment(sequences);
866 // dataset is inherited
867 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
868 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
869 AlignFrame.DEFAULT_HEIGHT);
870 String newtitle = new String("Copied sequences");
872 if (Desktop.jalviewClipboard[2] != null)
874 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
875 af.viewport.setHiddenColumns(hc);
878 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
879 AlignFrame.DEFAULT_HEIGHT);
886 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
887 Transferable contents = c.getContents(this);
889 if (contents != null)
891 String file = (String) contents
892 .getTransferData(DataFlavor.stringFlavor);
894 FileFormatI format = new IdentifyFile().identify(file,
895 DataSourceType.PASTE);
897 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
900 } catch (Exception ex)
902 jalview.bin.Console.outPrintln(
903 "Unable to paste alignment from system clipboard:\n" + ex);
909 * Adds and opens the given frame to the desktop
920 public static synchronized void addInternalFrame(
921 final JInternalFrame frame, String title, int w, int h)
923 addInternalFrame(frame, title, true, w, h, true, false);
927 * Add an internal frame to the Jalview desktop
934 * When true, display frame immediately, otherwise, caller must call
935 * setVisible themselves.
941 public static synchronized void addInternalFrame(
942 final JInternalFrame frame, String title, boolean makeVisible,
945 addInternalFrame(frame, title, makeVisible, w, h, true, false);
949 * Add an internal frame to the Jalview desktop and make it visible
962 public static synchronized void addInternalFrame(
963 final JInternalFrame frame, String title, int w, int h,
966 addInternalFrame(frame, title, true, w, h, resizable, false);
970 * Add an internal frame to the Jalview desktop
977 * When true, display frame immediately, otherwise, caller must call
978 * setVisible themselves.
985 * @param ignoreMinSize
986 * Do not set the default minimum size for frame
988 public static synchronized void addInternalFrame(
989 final JInternalFrame frame, String title, boolean makeVisible,
990 int w, int h, boolean resizable, boolean ignoreMinSize)
993 // TODO: allow callers to determine X and Y position of frame (eg. via
995 // TODO: consider fixing method to update entries in the window submenu with
996 // the current window title
998 frame.setTitle(title);
999 if (frame.getWidth() < 1 || frame.getHeight() < 1)
1001 frame.setSize(w, h);
1003 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
1004 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
1005 // IF JALVIEW IS RUNNING HEADLESS
1006 // ///////////////////////////////////////////////
1007 if (instance == null || (System.getProperty("java.awt.headless") != null
1008 && System.getProperty("java.awt.headless").equals("true")))
1017 frame.setMinimumSize(
1018 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1020 // Set default dimension for Alignment Frame window.
1021 // The Alignment Frame window could be added from a number of places,
1023 // I did this here in order not to miss out on any Alignment frame.
1024 if (frame instanceof AlignFrame)
1026 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1027 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1031 frame.setVisible(makeVisible);
1032 frame.setClosable(true);
1033 frame.setResizable(resizable);
1034 frame.setMaximizable(resizable);
1035 frame.setIconifiable(resizable);
1036 frame.setOpaque(Platform.isJS());
1038 if (frame.getX() < 1 && frame.getY() < 1)
1040 frame.setLocation(xOffset * openFrameCount,
1041 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1045 * add an entry for the new frame in the Window menu (and remove it when the
1048 final JMenuItem menuItem = new JMenuItem(title);
1049 frame.addInternalFrameListener(new InternalFrameAdapter()
1052 public void internalFrameActivated(InternalFrameEvent evt)
1054 JInternalFrame itf = desktop.getSelectedFrame();
1057 if (itf instanceof AlignFrame)
1059 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1066 public void internalFrameClosed(InternalFrameEvent evt)
1068 PaintRefresher.RemoveComponent(frame);
1071 * defensive check to prevent frames being added half off the window
1073 if (openFrameCount > 0)
1079 * ensure no reference to alignFrame retained by menu item listener
1081 if (menuItem.getActionListeners().length > 0)
1083 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1085 windowMenu.remove(menuItem);
1089 menuItem.addActionListener(new ActionListener()
1092 public void actionPerformed(ActionEvent e)
1096 frame.setSelected(true);
1097 frame.setIcon(false);
1098 } catch (java.beans.PropertyVetoException ex)
1105 setKeyBindings(frame);
1107 // Since the latest FlatLaf patch, we occasionally have problems showing
1108 // structureViewer frames...
1110 boolean shown = false;
1111 Exception last = null;
1118 } catch (IllegalArgumentException iaex)
1122 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1123 + tries + " left) for " + frame.getTitle(), iaex);
1127 } catch (InterruptedException iex)
1132 } while (!shown && tries > 0);
1135 jalview.bin.Console.error(
1136 "Serious Problem whilst showing window " + frame.getTitle(),
1140 windowMenu.add(menuItem);
1145 frame.setSelected(true);
1146 frame.requestFocus();
1147 } catch (java.beans.PropertyVetoException ve)
1149 } catch (java.lang.ClassCastException cex)
1151 jalview.bin.Console.warn(
1152 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1158 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1163 private static void setKeyBindings(JInternalFrame frame)
1165 @SuppressWarnings("serial")
1166 final Action closeAction = new AbstractAction()
1169 public void actionPerformed(ActionEvent e)
1176 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1178 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1179 InputEvent.CTRL_DOWN_MASK);
1180 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1181 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1183 InputMap inputMap = frame
1184 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1185 String ctrlW = ctrlWKey.toString();
1186 inputMap.put(ctrlWKey, ctrlW);
1187 inputMap.put(cmdWKey, ctrlW);
1189 ActionMap actionMap = frame.getActionMap();
1190 actionMap.put(ctrlW, closeAction);
1194 public void lostOwnership(Clipboard clipboard, Transferable contents)
1198 Desktop.jalviewClipboard = null;
1201 internalCopy = false;
1205 public void dragEnter(DropTargetDragEvent evt)
1210 public void dragExit(DropTargetEvent evt)
1215 public void dragOver(DropTargetDragEvent evt)
1220 public void dropActionChanged(DropTargetDragEvent evt)
1231 public void drop(DropTargetDropEvent evt)
1233 boolean success = true;
1234 // JAL-1552 - acceptDrop required before getTransferable call for
1235 // Java's Transferable for native dnd
1236 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1237 Transferable t = evt.getTransferable();
1238 List<Object> files = new ArrayList<>();
1239 List<DataSourceType> protocols = new ArrayList<>();
1243 Desktop.transferFromDropTarget(files, protocols, evt, t);
1244 } catch (Exception e)
1246 e.printStackTrace();
1254 for (int i = 0; i < files.size(); i++)
1256 // BH 2018 File or String
1257 Object file = files.get(i);
1258 String fileName = file.toString();
1259 DataSourceType protocol = (protocols == null)
1260 ? DataSourceType.FILE
1262 FileFormatI format = null;
1264 if (fileName.endsWith(".jar"))
1266 format = FileFormat.Jalview;
1271 format = new IdentifyFile().identify(file, protocol);
1273 if (file instanceof File)
1275 Platform.cacheFileData((File) file);
1277 new FileLoader().LoadFile(null, file, protocol, format);
1280 } catch (Exception ex)
1285 evt.dropComplete(success); // need this to ensure input focus is properly
1286 // transfered to any new windows created
1296 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1298 String fileFormat = FileLoader.getUseDefaultFileFormat()
1299 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1301 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1302 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1303 BackupFiles.getEnabled());
1305 chooser.setFileView(new JalviewFileView());
1306 chooser.setDialogTitle(
1307 MessageManager.getString("label.open_local_file"));
1308 chooser.setToolTipText(MessageManager.getString("action.open"));
1310 chooser.setResponseHandler(0, () -> {
1311 File selectedFile = chooser.getSelectedFile();
1312 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1314 FileFormatI format = chooser.getSelectedFormat();
1317 * Call IdentifyFile to verify the file contains what its extension implies.
1318 * Skip this step for dynamically added file formats, because IdentifyFile does
1319 * not know how to recognise them.
1321 if (FileFormats.getInstance().isIdentifiable(format))
1325 format = new IdentifyFile().identify(selectedFile,
1326 DataSourceType.FILE);
1327 } catch (FileFormatException e)
1329 // format = null; //??
1333 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1336 chooser.showOpenDialog(this);
1340 * Shows a dialog for input of a URL at which to retrieve alignment data
1345 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1347 // This construct allows us to have a wider textfield
1349 JLabel label = new JLabel(
1350 MessageManager.getString("label.input_file_url"));
1352 JPanel panel = new JPanel(new GridLayout(2, 1));
1356 * the URL to fetch is input in Java: an editable combobox with history JS:
1357 * (pending JAL-3038) a plain text field
1360 String urlBase = "https://www.";
1361 if (Platform.isJS())
1363 history = new JTextField(urlBase, 35);
1372 JComboBox<String> asCombo = new JComboBox<>();
1373 asCombo.setPreferredSize(new Dimension(400, 20));
1374 asCombo.setEditable(true);
1375 asCombo.addItem(urlBase);
1376 String historyItems = Cache.getProperty("RECENT_URL");
1377 if (historyItems != null)
1379 for (String token : historyItems.split("\\t"))
1381 asCombo.addItem(token);
1388 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1389 MessageManager.getString("action.cancel") };
1390 Runnable action = () -> {
1391 @SuppressWarnings("unchecked")
1392 String url = (history instanceof JTextField
1393 ? ((JTextField) history).getText()
1394 : ((JComboBox<String>) history).getEditor().getItem()
1395 .toString().trim());
1397 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1399 if (viewport != null)
1401 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1402 FileFormat.Jalview);
1406 new FileLoader().LoadFile(url, DataSourceType.URL,
1407 FileFormat.Jalview);
1412 FileFormatI format = null;
1415 format = new IdentifyFile().identify(url, DataSourceType.URL);
1416 } catch (FileFormatException e)
1418 // TODO revise error handling, distinguish between
1419 // URL not found and response not valid
1424 String msg = MessageManager.formatMessage("label.couldnt_locate",
1426 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1427 MessageManager.getString("label.url_not_found"),
1428 JvOptionPane.WARNING_MESSAGE);
1432 if (viewport != null)
1434 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1439 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1443 String dialogOption = MessageManager
1444 .getString("label.input_alignment_from_url");
1445 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1446 .showInternalDialog(panel, dialogOption,
1447 JvOptionPane.YES_NO_CANCEL_OPTION,
1448 JvOptionPane.PLAIN_MESSAGE, null, options,
1449 MessageManager.getString("action.ok"));
1453 * Opens the CutAndPaste window for the user to paste an alignment in to
1456 * - if not null, the pasted alignment is added to the current
1457 * alignment; if null, to a new alignment window
1460 public void inputTextboxMenuItem_actionPerformed(
1461 AlignmentViewPanel viewPanel)
1463 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1464 cap.setForInput(viewPanel);
1465 Desktop.addInternalFrame(cap,
1466 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1471 * Check with user and saving files before actually quitting
1473 public void desktopQuit()
1475 desktopQuit(true, false);
1478 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1480 final Runnable doDesktopQuit = () -> {
1481 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1482 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1483 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1484 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1485 getBounds().y, getWidth(), getHeight()));
1487 if (jconsole != null)
1489 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1490 jconsole.stopConsole();
1495 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1498 // Frames should all close automatically. Keeping external
1499 // viewers open should already be decided by user.
1500 closeAll_actionPerformed(null);
1502 // check for aborted quit
1503 if (QuitHandler.quitCancelled())
1505 jalview.bin.Console.debug("Desktop aborting quit");
1509 if (dialogExecutor != null)
1511 dialogExecutor.shutdownNow();
1514 if (groovyConsole != null)
1516 // suppress a possible repeat prompt to save script
1517 groovyConsole.setDirty(false);
1518 groovyConsole.exit();
1521 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1523 // note that shutdown hook will not be run
1524 jalview.bin.Console.debug("Force Quit selected by user");
1525 Runtime.getRuntime().halt(0);
1528 jalview.bin.Console.debug("Quit selected by user");
1531 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1532 // instance.dispose();
1537 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1538 QuitHandler.defaultCancelQuit);
1542 * Don't call this directly, use desktopQuit() above. Exits the program.
1547 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1548 // not run a second time if gotQuitResponse flag has been set (i.e. user
1549 // confirmed quit of some kind).
1550 Jalview.exit("Desktop exiting.", ExitCode.OK);
1553 private void storeLastKnownDimensions(String string, Rectangle jc)
1555 jalview.bin.Console.debug("Storing last known dimensions for " + string
1556 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1557 + " height:" + jc.height);
1559 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1560 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1561 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1562 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1572 public void aboutMenuItem_actionPerformed(ActionEvent e)
1574 new Thread(new Runnable()
1579 new SplashScreen(false);
1585 * Returns the html text for the About screen, including any available version
1586 * number, build details, author details and citation reference, but without
1587 * the enclosing {@code html} tags
1591 public String getAboutMessage()
1593 StringBuilder message = new StringBuilder(1024);
1594 message.append("<div style=\"font-family: sans-serif;\">")
1595 .append("<h1><strong>Version: ")
1596 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1597 .append("<strong>Built: <em>")
1598 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1599 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1600 .append("</strong>");
1602 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1603 if (latestVersion.equals("Checking"))
1605 // JBP removed this message for 2.11: May be reinstated in future version
1606 // message.append("<br>...Checking latest version...</br>");
1608 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1610 boolean red = false;
1611 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1612 .indexOf("automated build") == -1)
1615 // Displayed when code version and jnlp version do not match and code
1616 // version is not a development build
1617 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1620 message.append("<br>!! Version ")
1621 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1622 .append(" is available for download from ")
1623 .append(Cache.getDefault("www.jalview.org",
1624 "https://www.jalview.org"))
1628 message.append("</div>");
1631 message.append("<br>Authors: ");
1632 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1633 message.append(CITATION);
1635 message.append("</div>");
1637 return message.toString();
1641 * Action on requesting Help documentation
1644 public void documentationMenuItem_actionPerformed()
1648 if (Platform.isJS())
1650 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1659 Help.showHelpWindow();
1661 } catch (Exception ex)
1664 .errPrintln("Error opening help: " + ex.getMessage());
1669 public void closeAll_actionPerformed(ActionEvent e)
1671 // TODO show a progress bar while closing?
1672 JInternalFrame[] frames = desktop.getAllFrames();
1673 for (int i = 0; i < frames.length; i++)
1677 frames[i].setClosed(true);
1678 } catch (java.beans.PropertyVetoException ex)
1682 Jalview.setCurrentAlignFrame(null);
1683 jalview.bin.Console.info("ALL CLOSED");
1686 * reset state of singleton objects as appropriate (clear down session state
1687 * when all windows are closed)
1689 StructureSelectionManager ssm = StructureSelectionManager
1690 .getStructureSelectionManager(this);
1697 public int structureViewersStillRunningCount()
1700 JInternalFrame[] frames = desktop.getAllFrames();
1701 for (int i = 0; i < frames.length; i++)
1703 if (frames[i] != null
1704 && frames[i] instanceof JalviewStructureDisplayI)
1706 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1714 public void raiseRelated_actionPerformed(ActionEvent e)
1716 reorderAssociatedWindows(false, false);
1720 public void minimizeAssociated_actionPerformed(ActionEvent e)
1722 reorderAssociatedWindows(true, false);
1725 void closeAssociatedWindows()
1727 reorderAssociatedWindows(false, true);
1733 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1737 protected void garbageCollect_actionPerformed(ActionEvent e)
1739 // We simply collect the garbage
1740 jalview.bin.Console.debug("Collecting garbage...");
1742 jalview.bin.Console.debug("Finished garbage collection.");
1748 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1752 protected void showMemusage_actionPerformed(ActionEvent e)
1754 desktop.showMemoryUsage(showMemusage.isSelected());
1761 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1765 protected void showConsole_actionPerformed(ActionEvent e)
1767 showConsole(showConsole.isSelected());
1770 Console jconsole = null;
1773 * control whether the java console is visible or not
1777 void showConsole(boolean selected)
1779 // TODO: decide if we should update properties file
1780 if (jconsole != null) // BH 2018
1782 showConsole.setSelected(selected);
1783 Cache.setProperty("SHOW_JAVA_CONSOLE",
1784 Boolean.valueOf(selected).toString());
1785 jconsole.setVisible(selected);
1789 void reorderAssociatedWindows(boolean minimize, boolean close)
1791 JInternalFrame[] frames = desktop.getAllFrames();
1792 if (frames == null || frames.length < 1)
1797 AlignmentViewport source = null, target = null;
1798 if (frames[0] instanceof AlignFrame)
1800 source = ((AlignFrame) frames[0]).getCurrentView();
1802 else if (frames[0] instanceof TreePanel)
1804 source = ((TreePanel) frames[0]).getViewPort();
1806 else if (frames[0] instanceof PCAPanel)
1808 source = ((PCAPanel) frames[0]).av;
1810 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1812 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1817 for (int i = 0; i < frames.length; i++)
1820 if (frames[i] == null)
1824 if (frames[i] instanceof AlignFrame)
1826 target = ((AlignFrame) frames[i]).getCurrentView();
1828 else if (frames[i] instanceof TreePanel)
1830 target = ((TreePanel) frames[i]).getViewPort();
1832 else if (frames[i] instanceof PCAPanel)
1834 target = ((PCAPanel) frames[i]).av;
1836 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1838 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1841 if (source == target)
1847 frames[i].setClosed(true);
1851 frames[i].setIcon(minimize);
1854 frames[i].toFront();
1858 } catch (java.beans.PropertyVetoException ex)
1873 protected void preferences_actionPerformed(ActionEvent e)
1875 Preferences.openPreferences();
1879 * Prompts the user to choose a file and then saves the Jalview state as a
1880 * Jalview project file
1883 public void saveState_actionPerformed()
1885 saveState_actionPerformed(false);
1888 public void saveState_actionPerformed(boolean saveAs)
1890 java.io.File projectFile = getProjectFile();
1891 // autoSave indicates we already have a file and don't need to ask
1892 boolean autoSave = projectFile != null && !saveAs
1893 && BackupFiles.getEnabled();
1895 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1896 // projectFile='"+projectFile+"',
1897 // saveAs="+saveAs+", Backups
1898 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1900 boolean approveSave = false;
1903 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1906 chooser.setFileView(new JalviewFileView());
1907 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1909 int value = chooser.showSaveDialog(this);
1911 if (value == JalviewFileChooser.APPROVE_OPTION)
1913 projectFile = chooser.getSelectedFile();
1914 setProjectFile(projectFile);
1919 if (approveSave || autoSave)
1921 final Desktop me = this;
1922 final java.io.File chosenFile = projectFile;
1923 new Thread(new Runnable()
1928 // TODO: refactor to Jalview desktop session controller action.
1929 setProgressBar(MessageManager.formatMessage(
1930 "label.saving_jalview_project", new Object[]
1931 { chosenFile.getName() }), chosenFile.hashCode());
1932 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1933 // TODO catch and handle errors for savestate
1934 // TODO prevent user from messing with the Desktop whilst we're saving
1937 boolean doBackup = BackupFiles.getEnabled();
1938 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1941 new Jalview2XML().saveState(
1942 doBackup ? backupfiles.getTempFile() : chosenFile);
1946 backupfiles.setWriteSuccess(true);
1947 backupfiles.rollBackupsAndRenameTempFile();
1949 } catch (OutOfMemoryError oom)
1951 new OOMWarning("Whilst saving current state to "
1952 + chosenFile.getName(), oom);
1953 } catch (Exception ex)
1955 jalview.bin.Console.error("Problems whilst trying to save to "
1956 + chosenFile.getName(), ex);
1957 JvOptionPane.showMessageDialog(me,
1958 MessageManager.formatMessage(
1959 "label.error_whilst_saving_current_state_to",
1961 { chosenFile.getName() }),
1962 MessageManager.getString("label.couldnt_save_project"),
1963 JvOptionPane.WARNING_MESSAGE);
1965 setProgressBar(null, chosenFile.hashCode());
1972 public void saveAsState_actionPerformed(ActionEvent e)
1974 saveState_actionPerformed(true);
1977 protected void setProjectFile(File choice)
1979 this.projectFile = choice;
1982 public File getProjectFile()
1984 return this.projectFile;
1988 * Shows a file chooser dialog and tries to read in the selected file as a
1992 public void loadState_actionPerformed()
1994 final String[] suffix = new String[] { "jvp", "jar" };
1995 final String[] desc = new String[] { "Jalview Project",
1996 "Jalview Project (old)" };
1997 JalviewFileChooser chooser = new JalviewFileChooser(
1998 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1999 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2003 chooser.setFileView(new JalviewFileView());
2004 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2005 chooser.setResponseHandler(0, () -> {
2006 File selectedFile = chooser.getSelectedFile();
2007 setProjectFile(selectedFile);
2008 String choice = selectedFile.getAbsolutePath();
2009 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2010 new Thread(new Runnable()
2017 new Jalview2XML().loadJalviewAlign(selectedFile);
2018 } catch (OutOfMemoryError oom)
2020 new OOMWarning("Whilst loading project from " + choice, oom);
2021 } catch (Exception ex)
2023 jalview.bin.Console.error(
2024 "Problems whilst loading project from " + choice, ex);
2025 JvOptionPane.showMessageDialog(Desktop.desktop,
2026 MessageManager.formatMessage(
2027 "label.error_whilst_loading_project_from",
2030 MessageManager.getString("label.couldnt_load_project"),
2031 JvOptionPane.WARNING_MESSAGE);
2034 }, "Project Loader").start();
2037 chooser.showOpenDialog(this);
2041 public void inputSequence_actionPerformed(ActionEvent e)
2043 new SequenceFetcher(this);
2046 JPanel progressPanel;
2048 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2050 public void startLoading(final Object fileName)
2052 if (fileLoadingCount == 0)
2054 fileLoadingPanels.add(addProgressPanel(MessageManager
2055 .formatMessage("label.loading_file", new Object[]
2061 private JPanel addProgressPanel(String string)
2063 if (progressPanel == null)
2065 progressPanel = new JPanel(new GridLayout(1, 1));
2066 totalProgressCount = 0;
2067 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2069 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2070 JProgressBar progressBar = new JProgressBar();
2071 progressBar.setIndeterminate(true);
2073 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2075 thisprogress.add(progressBar, BorderLayout.CENTER);
2076 progressPanel.add(thisprogress);
2077 ((GridLayout) progressPanel.getLayout()).setRows(
2078 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2079 ++totalProgressCount;
2080 instance.validate();
2081 return thisprogress;
2084 int totalProgressCount = 0;
2086 private void removeProgressPanel(JPanel progbar)
2088 if (progressPanel != null)
2090 synchronized (progressPanel)
2092 progressPanel.remove(progbar);
2093 GridLayout gl = (GridLayout) progressPanel.getLayout();
2094 gl.setRows(gl.getRows() - 1);
2095 if (--totalProgressCount < 1)
2097 this.getContentPane().remove(progressPanel);
2098 progressPanel = null;
2105 public void stopLoading()
2108 if (fileLoadingCount < 1)
2110 while (fileLoadingPanels.size() > 0)
2112 removeProgressPanel(fileLoadingPanels.remove(0));
2114 fileLoadingPanels.clear();
2115 fileLoadingCount = 0;
2120 public static int getViewCount(String alignmentId)
2122 AlignmentViewport[] aps = getViewports(alignmentId);
2123 return (aps == null) ? 0 : aps.length;
2128 * @param alignmentId
2129 * - if null, all sets are returned
2130 * @return all AlignmentPanels concerning the alignmentId sequence set
2132 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2134 if (Desktop.desktop == null)
2136 // no frames created and in headless mode
2137 // TODO: verify that frames are recoverable when in headless mode
2140 List<AlignmentPanel> aps = new ArrayList<>();
2141 AlignFrame[] frames = getAlignFrames();
2146 for (AlignFrame af : frames)
2148 for (AlignmentPanel ap : af.alignPanels)
2150 if (alignmentId == null
2151 || alignmentId.equals(ap.av.getSequenceSetId()))
2157 if (aps.size() == 0)
2161 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2166 * get all the viewports on an alignment.
2168 * @param sequenceSetId
2169 * unique alignment id (may be null - all viewports returned in that
2171 * @return all viewports on the alignment bound to sequenceSetId
2173 public static AlignmentViewport[] getViewports(String sequenceSetId)
2175 List<AlignmentViewport> viewp = new ArrayList<>();
2176 if (desktop != null)
2178 AlignFrame[] frames = Desktop.getAlignFrames();
2180 for (AlignFrame afr : frames)
2182 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2183 .equals(sequenceSetId))
2185 if (afr.alignPanels != null)
2187 for (AlignmentPanel ap : afr.alignPanels)
2189 if (sequenceSetId == null
2190 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2198 viewp.add(afr.getViewport());
2202 if (viewp.size() > 0)
2204 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2211 * Explode the views in the given frame into separate AlignFrame
2215 public static void explodeViews(AlignFrame af)
2217 int size = af.alignPanels.size();
2223 // FIXME: ideally should use UI interface API
2224 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2225 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2226 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2227 for (int i = 0; i < size; i++)
2229 AlignmentPanel ap = af.alignPanels.get(i);
2231 AlignFrame newaf = new AlignFrame(ap);
2233 // transfer reference for existing feature settings to new alignFrame
2234 if (ap == af.alignPanel)
2236 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2238 newaf.featureSettings = viewFeatureSettings;
2240 newaf.setFeatureSettingsGeometry(fsBounds);
2244 * Restore the view's last exploded frame geometry if known. Multiple views from
2245 * one exploded frame share and restore the same (frame) position and size.
2247 Rectangle geometry = ap.av.getExplodedGeometry();
2248 if (geometry != null)
2250 newaf.setBounds(geometry);
2253 ap.av.setGatherViewsHere(false);
2255 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2256 AlignFrame.DEFAULT_HEIGHT);
2257 // and materialise a new feature settings dialog instance for the new
2259 // (closes the old as if 'OK' was pressed)
2260 if (ap == af.alignPanel && newaf.featureSettings != null
2261 && newaf.featureSettings.isOpen()
2262 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2264 newaf.showFeatureSettingsUI();
2268 af.featureSettings = null;
2269 af.alignPanels.clear();
2270 af.closeMenuItem_actionPerformed(true);
2275 * Gather expanded views (separate AlignFrame's) with the same sequence set
2276 * identifier back in to this frame as additional views, and close the
2277 * expanded views. Note the expanded frames may themselves have multiple
2278 * views. We take the lot.
2282 public void gatherViews(AlignFrame source)
2284 source.viewport.setGatherViewsHere(true);
2285 source.viewport.setExplodedGeometry(source.getBounds());
2286 JInternalFrame[] frames = desktop.getAllFrames();
2287 String viewId = source.viewport.getSequenceSetId();
2288 for (int t = 0; t < frames.length; t++)
2290 if (frames[t] instanceof AlignFrame && frames[t] != source)
2292 AlignFrame af = (AlignFrame) frames[t];
2293 boolean gatherThis = false;
2294 for (int a = 0; a < af.alignPanels.size(); a++)
2296 AlignmentPanel ap = af.alignPanels.get(a);
2297 if (viewId.equals(ap.av.getSequenceSetId()))
2300 ap.av.setGatherViewsHere(false);
2301 ap.av.setExplodedGeometry(af.getBounds());
2302 source.addAlignmentPanel(ap, false);
2308 if (af.featureSettings != null && af.featureSettings.isOpen())
2310 if (source.featureSettings == null)
2312 // preserve the feature settings geometry for this frame
2313 source.featureSettings = af.featureSettings;
2314 source.setFeatureSettingsGeometry(
2315 af.getFeatureSettingsGeometry());
2319 // close it and forget
2320 af.featureSettings.close();
2323 af.alignPanels.clear();
2324 af.closeMenuItem_actionPerformed(true);
2329 // refresh the feature setting UI for the source frame if it exists
2330 if (source.featureSettings != null && source.featureSettings.isOpen())
2332 source.showFeatureSettingsUI();
2337 public JInternalFrame[] getAllFrames()
2339 return desktop.getAllFrames();
2343 * Checks the given url to see if it gives a response indicating that the user
2344 * should be informed of a new questionnaire.
2348 public void checkForQuestionnaire(String url)
2350 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2351 // javax.swing.SwingUtilities.invokeLater(jvq);
2352 new Thread(jvq).start();
2355 public void checkURLLinks()
2357 // Thread off the URL link checker
2358 addDialogThread(new Runnable()
2363 if (Cache.getDefault("CHECKURLLINKS", true))
2365 // check what the actual links are - if it's just the default don't
2366 // bother with the warning
2367 List<String> links = Preferences.sequenceUrlLinks
2370 // only need to check links if there is one with a
2371 // SEQUENCE_ID which is not the default EMBL_EBI link
2372 ListIterator<String> li = links.listIterator();
2373 boolean check = false;
2374 List<JLabel> urls = new ArrayList<>();
2375 while (li.hasNext())
2377 String link = li.next();
2378 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2379 && !UrlConstants.isDefaultString(link))
2382 int barPos = link.indexOf("|");
2383 String urlMsg = barPos == -1 ? link
2384 : link.substring(0, barPos) + ": "
2385 + link.substring(barPos + 1);
2386 urls.add(new JLabel(urlMsg));
2394 // ask user to check in case URL links use old style tokens
2395 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2396 JPanel msgPanel = new JPanel();
2397 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2398 msgPanel.add(Box.createVerticalGlue());
2399 JLabel msg = new JLabel(MessageManager
2400 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2401 JLabel msg2 = new JLabel(MessageManager
2402 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2404 for (JLabel url : urls)
2410 final JCheckBox jcb = new JCheckBox(
2411 MessageManager.getString("label.do_not_display_again"));
2412 jcb.addActionListener(new ActionListener()
2415 public void actionPerformed(ActionEvent e)
2417 // update Cache settings for "don't show this again"
2418 boolean showWarningAgain = !jcb.isSelected();
2419 Cache.setProperty("CHECKURLLINKS",
2420 Boolean.valueOf(showWarningAgain).toString());
2425 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2427 .getString("label.SEQUENCE_ID_no_longer_used"),
2428 JvOptionPane.WARNING_MESSAGE);
2435 * Proxy class for JDesktopPane which optionally displays the current memory
2436 * usage and highlights the desktop area with a red bar if free memory runs
2441 public class MyDesktopPane extends JDesktopPane implements Runnable
2443 private static final float ONE_MB = 1048576f;
2445 boolean showMemoryUsage = false;
2449 java.text.NumberFormat df;
2451 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2454 public MyDesktopPane(boolean showMemoryUsage)
2456 showMemoryUsage(showMemoryUsage);
2459 public void showMemoryUsage(boolean showMemory)
2461 this.showMemoryUsage = showMemory;
2464 Thread worker = new Thread(this);
2470 public boolean isShowMemoryUsage()
2472 return showMemoryUsage;
2478 df = java.text.NumberFormat.getNumberInstance();
2479 df.setMaximumFractionDigits(2);
2480 runtime = Runtime.getRuntime();
2482 while (showMemoryUsage)
2486 maxMemory = runtime.maxMemory() / ONE_MB;
2487 allocatedMemory = runtime.totalMemory() / ONE_MB;
2488 freeMemory = runtime.freeMemory() / ONE_MB;
2489 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2491 percentUsage = (totalFreeMemory / maxMemory) * 100;
2493 // if (percentUsage < 20)
2495 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2497 // instance.set.setBorder(border1);
2500 // sleep after showing usage
2502 } catch (Exception ex)
2504 ex.printStackTrace();
2510 public void paintComponent(Graphics g)
2512 if (showMemoryUsage && g != null && df != null)
2514 if (percentUsage < 20)
2516 g.setColor(Color.red);
2518 FontMetrics fm = g.getFontMetrics();
2521 g.drawString(MessageManager.formatMessage("label.memory_stats",
2523 { df.format(totalFreeMemory), df.format(maxMemory),
2524 df.format(percentUsage) }),
2525 10, getHeight() - fm.getHeight());
2529 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2530 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2535 * Accessor method to quickly get all the AlignmentFrames loaded.
2537 * @return an array of AlignFrame, or null if none found
2539 public static AlignFrame[] getAlignFrames()
2541 if (Jalview.isHeadlessMode())
2543 // Desktop.desktop is null in headless mode
2544 return new AlignFrame[] { Jalview.currentAlignFrame };
2547 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2553 List<AlignFrame> avp = new ArrayList<>();
2555 for (int i = frames.length - 1; i > -1; i--)
2557 if (frames[i] instanceof AlignFrame)
2559 avp.add((AlignFrame) frames[i]);
2561 else if (frames[i] instanceof SplitFrame)
2564 * Also check for a split frame containing an AlignFrame
2566 GSplitFrame sf = (GSplitFrame) frames[i];
2567 if (sf.getTopFrame() instanceof AlignFrame)
2569 avp.add((AlignFrame) sf.getTopFrame());
2571 if (sf.getBottomFrame() instanceof AlignFrame)
2573 avp.add((AlignFrame) sf.getBottomFrame());
2577 if (avp.size() == 0)
2581 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2586 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2590 public GStructureViewer[] getJmols()
2592 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2598 List<GStructureViewer> avp = new ArrayList<>();
2600 for (int i = frames.length - 1; i > -1; i--)
2602 if (frames[i] instanceof AppJmol)
2604 GStructureViewer af = (GStructureViewer) frames[i];
2608 if (avp.size() == 0)
2612 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2617 * Add Groovy Support to Jalview
2620 public void groovyShell_actionPerformed()
2624 openGroovyConsole();
2625 } catch (Exception ex)
2627 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2628 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2630 MessageManager.getString("label.couldnt_create_groovy_shell"),
2631 MessageManager.getString("label.groovy_support_failed"),
2632 JvOptionPane.ERROR_MESSAGE);
2637 * Open the Groovy console
2639 void openGroovyConsole()
2641 if (groovyConsole == null)
2643 groovyConsole = new groovy.ui.Console();
2644 groovyConsole.setVariable("Jalview", this);
2645 groovyConsole.run();
2648 * We allow only one console at a time, so that AlignFrame menu option
2649 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2650 * enable 'Run script', when the console is opened, and the reverse when it is
2653 Window window = (Window) groovyConsole.getFrame();
2654 window.addWindowListener(new WindowAdapter()
2657 public void windowClosed(WindowEvent e)
2660 * rebind CMD-Q from Groovy Console to Jalview Quit
2663 enableExecuteGroovy(false);
2669 * show Groovy console window (after close and reopen)
2671 ((Window) groovyConsole.getFrame()).setVisible(true);
2674 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2675 * opening a second console
2677 enableExecuteGroovy(true);
2681 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2682 * binding when opened
2684 protected void addQuitHandler()
2687 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2689 .getKeyStroke(KeyEvent.VK_Q,
2690 jalview.util.ShortcutKeyMaskExWrapper
2691 .getMenuShortcutKeyMaskEx()),
2693 getRootPane().getActionMap().put("Quit", new AbstractAction()
2696 public void actionPerformed(ActionEvent e)
2704 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2707 * true if Groovy console is open
2709 public void enableExecuteGroovy(boolean enabled)
2712 * disable opening a second Groovy console (or re-enable when the console is
2715 groovyShell.setEnabled(!enabled);
2717 AlignFrame[] alignFrames = getAlignFrames();
2718 if (alignFrames != null)
2720 for (AlignFrame af : alignFrames)
2722 af.setGroovyEnabled(enabled);
2728 * Progress bars managed by the IProgressIndicator method.
2730 private Hashtable<Long, JPanel> progressBars;
2732 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2737 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2740 public void setProgressBar(String message, long id)
2742 if (progressBars == null)
2744 progressBars = new Hashtable<>();
2745 progressBarHandlers = new Hashtable<>();
2748 if (progressBars.get(Long.valueOf(id)) != null)
2750 JPanel panel = progressBars.remove(Long.valueOf(id));
2751 if (progressBarHandlers.contains(Long.valueOf(id)))
2753 progressBarHandlers.remove(Long.valueOf(id));
2755 removeProgressPanel(panel);
2759 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2766 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2767 * jalview.gui.IProgressIndicatorHandler)
2770 public void registerHandler(final long id,
2771 final IProgressIndicatorHandler handler)
2773 if (progressBarHandlers == null
2774 || !progressBars.containsKey(Long.valueOf(id)))
2776 throw new Error(MessageManager.getString(
2777 "error.call_setprogressbar_before_registering_handler"));
2779 progressBarHandlers.put(Long.valueOf(id), handler);
2780 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2781 if (handler.canCancel())
2783 JButton cancel = new JButton(
2784 MessageManager.getString("action.cancel"));
2785 final IProgressIndicator us = this;
2786 cancel.addActionListener(new ActionListener()
2790 public void actionPerformed(ActionEvent e)
2792 handler.cancelActivity(id);
2793 us.setProgressBar(MessageManager
2794 .formatMessage("label.cancelled_params", new Object[]
2795 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2799 progressPanel.add(cancel, BorderLayout.EAST);
2805 * @return true if any progress bars are still active
2808 public boolean operationInProgress()
2810 if (progressBars != null && progressBars.size() > 0)
2818 * This will return the first AlignFrame holding the given viewport instance.
2819 * It will break if there are more than one AlignFrames viewing a particular
2823 * @return alignFrame for viewport
2825 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2827 if (desktop != null)
2829 AlignmentPanel[] aps = getAlignmentPanels(
2830 viewport.getSequenceSetId());
2831 for (int panel = 0; aps != null && panel < aps.length; panel++)
2833 if (aps[panel] != null && aps[panel].av == viewport)
2835 return aps[panel].alignFrame;
2842 public VamsasApplication getVamsasApplication()
2844 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2850 * flag set if jalview GUI is being operated programmatically
2852 private boolean inBatchMode = false;
2855 * check if jalview GUI is being operated programmatically
2857 * @return inBatchMode
2859 public boolean isInBatchMode()
2865 * set flag if jalview GUI is being operated programmatically
2867 * @param inBatchMode
2869 public void setInBatchMode(boolean inBatchMode)
2871 this.inBatchMode = inBatchMode;
2875 * start service discovery and wait till it is done
2877 public void startServiceDiscovery()
2879 startServiceDiscovery(false);
2883 * start service discovery threads - blocking or non-blocking
2887 public void startServiceDiscovery(boolean blocking)
2889 startServiceDiscovery(blocking, false);
2893 * start service discovery threads
2896 * - false means call returns immediately
2897 * @param ignore_SHOW_JWS2_SERVICES_preference
2898 * - when true JABA services are discovered regardless of user's JWS2
2899 * discovery preference setting
2901 public void startServiceDiscovery(boolean blocking,
2902 boolean ignore_SHOW_JWS2_SERVICES_preference)
2904 boolean alive = true;
2905 Thread t0 = null, t1 = null, t2 = null;
2906 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2909 // todo: changesupport handlers need to be transferred
2910 if (discoverer == null)
2912 discoverer = new jalview.ws.jws1.Discoverer();
2913 // register PCS handler for desktop.
2914 discoverer.addPropertyChangeListener(changeSupport);
2916 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2917 // until we phase out completely
2918 (t0 = new Thread(discoverer)).start();
2921 if (ignore_SHOW_JWS2_SERVICES_preference
2922 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2924 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2925 .startDiscoverer(changeSupport);
2929 // TODO: do rest service discovery
2938 } catch (Exception e)
2941 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2942 || (t3 != null && t3.isAlive())
2943 || (t0 != null && t0.isAlive());
2949 * called to check if the service discovery process completed successfully.
2953 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2955 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2957 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2958 .getErrorMessages();
2961 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2963 if (serviceChangedDialog == null)
2965 // only run if we aren't already displaying one of these.
2966 addDialogThread(serviceChangedDialog = new Runnable()
2973 * JalviewDialog jd =new JalviewDialog() {
2975 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2977 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2979 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2981 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2983 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2984 * + " or mis-configured HTTP proxy settings.<br/>" +
2985 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2986 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2987 * true, true, "Web Service Configuration Problem", 450, 400);
2989 * jd.waitForInput();
2991 JvOptionPane.showConfirmDialog(Desktop.desktop,
2992 new JLabel("<html><table width=\"450\"><tr><td>"
2993 + ermsg + "</td></tr></table>"
2994 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2995 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2996 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2997 + " Tools->Preferences dialog box to change them.</p></html>"),
2998 "Web Service Configuration Problem",
2999 JvOptionPane.DEFAULT_OPTION,
3000 JvOptionPane.ERROR_MESSAGE);
3001 serviceChangedDialog = null;
3009 jalview.bin.Console.error(
3010 "Errors reported by JABA discovery service. Check web services preferences.\n"
3017 private Runnable serviceChangedDialog = null;
3020 * start a thread to open a URL in the configured browser. Pops up a warning
3021 * dialog to the user if there is an exception when calling out to the browser
3026 public static void showUrl(final String url)
3028 showUrl(url, Desktop.instance);
3032 * Like showUrl but allows progress handler to be specified
3036 * (null) or object implementing IProgressIndicator
3038 public static void showUrl(final String url,
3039 final IProgressIndicator progress)
3041 new Thread(new Runnable()
3048 if (progress != null)
3050 progress.setProgressBar(MessageManager
3051 .formatMessage("status.opening_params", new Object[]
3052 { url }), this.hashCode());
3054 jalview.util.BrowserLauncher.openURL(url);
3055 } catch (Exception ex)
3057 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3059 .getString("label.web_browser_not_found_unix"),
3060 MessageManager.getString("label.web_browser_not_found"),
3061 JvOptionPane.WARNING_MESSAGE);
3063 ex.printStackTrace();
3065 if (progress != null)
3067 progress.setProgressBar(null, this.hashCode());
3073 public static WsParamSetManager wsparamManager = null;
3075 public static ParamManager getUserParameterStore()
3077 if (wsparamManager == null)
3079 wsparamManager = new WsParamSetManager();
3081 return wsparamManager;
3085 * static hyperlink handler proxy method for use by Jalview's internal windows
3089 public static void hyperlinkUpdate(HyperlinkEvent e)
3091 if (e.getEventType() == EventType.ACTIVATED)
3096 url = e.getURL().toString();
3097 Desktop.showUrl(url);
3098 } catch (Exception x)
3103 .error("Couldn't handle string " + url + " as a URL.");
3105 // ignore any exceptions due to dud links.
3112 * single thread that handles display of dialogs to user.
3114 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3117 * flag indicating if dialogExecutor should try to acquire a permit
3119 private volatile boolean dialogPause = true;
3124 private Semaphore block = new Semaphore(0);
3126 private static groovy.ui.Console groovyConsole;
3129 * add another dialog thread to the queue
3133 public void addDialogThread(final Runnable prompter)
3135 dialogExecutor.submit(new Runnable()
3142 acquireDialogQueue();
3144 if (instance == null)
3150 SwingUtilities.invokeAndWait(prompter);
3151 } catch (Exception q)
3153 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3160 private boolean dialogQueueStarted = false;
3162 public void startDialogQueue()
3164 if (dialogQueueStarted)
3168 // set the flag so we don't pause waiting for another permit and semaphore
3169 // the current task to begin
3170 releaseDialogQueue();
3171 dialogQueueStarted = true;
3174 public void acquireDialogQueue()
3180 } catch (InterruptedException e)
3182 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3187 public void releaseDialogQueue()
3194 dialogPause = false;
3198 * Outputs an image of the desktop to file in EPS format, after prompting the
3199 * user for choice of Text or Lineart character rendering (unless a preference
3200 * has been set). The file name is generated as
3203 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3207 protected void snapShotWindow_actionPerformed(ActionEvent e)
3209 // currently the menu option to do this is not shown
3212 int width = getWidth();
3213 int height = getHeight();
3215 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3216 ImageWriterI writer = new ImageWriterI()
3219 public void exportImage(Graphics g) throws Exception
3222 jalview.bin.Console.info("Successfully written snapshot to file "
3223 + of.getAbsolutePath());
3226 String title = "View of desktop";
3227 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3231 exporter.doExport(of, this, width, height, title);
3232 } catch (ImageOutputException ioex)
3234 jalview.bin.Console.error(
3235 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3241 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3242 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3243 * and location last time the view was expanded (if any). However it does not
3244 * remember the split pane divider location - this is set to match the
3245 * 'exploding' frame.
3249 public void explodeViews(SplitFrame sf)
3251 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3252 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3253 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3255 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3257 int viewCount = topPanels.size();
3264 * Processing in reverse order works, forwards order leaves the first panels not
3265 * visible. I don't know why!
3267 for (int i = viewCount - 1; i >= 0; i--)
3270 * Make new top and bottom frames. These take over the respective AlignmentPanel
3271 * objects, including their AlignmentViewports, so the cdna/protein
3272 * relationships between the viewports is carried over to the new split frames.
3274 * explodedGeometry holds the (x, y) position of the previously exploded
3275 * SplitFrame, and the (width, height) of the AlignFrame component
3277 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3278 AlignFrame newTopFrame = new AlignFrame(topPanel);
3279 newTopFrame.setSize(oldTopFrame.getSize());
3280 newTopFrame.setVisible(true);
3281 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3282 .getExplodedGeometry();
3283 if (geometry != null)
3285 newTopFrame.setSize(geometry.getSize());
3288 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3289 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3290 newBottomFrame.setSize(oldBottomFrame.getSize());
3291 newBottomFrame.setVisible(true);
3292 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3293 .getExplodedGeometry();
3294 if (geometry != null)
3296 newBottomFrame.setSize(geometry.getSize());
3299 topPanel.av.setGatherViewsHere(false);
3300 bottomPanel.av.setGatherViewsHere(false);
3301 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3303 if (geometry != null)
3305 splitFrame.setLocation(geometry.getLocation());
3307 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3311 * Clear references to the panels (now relocated in the new SplitFrames) before
3312 * closing the old SplitFrame.
3315 bottomPanels.clear();
3320 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3321 * back into the given SplitFrame as additional views. Note that the gathered
3322 * frames may themselves have multiple views.
3326 public void gatherViews(GSplitFrame source)
3329 * special handling of explodedGeometry for a view within a SplitFrame: - it
3330 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3331 * height) of the AlignFrame component
3333 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3334 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3335 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3336 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3337 myBottomFrame.viewport
3338 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3339 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3340 myTopFrame.viewport.setGatherViewsHere(true);
3341 myBottomFrame.viewport.setGatherViewsHere(true);
3342 String topViewId = myTopFrame.viewport.getSequenceSetId();
3343 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3345 JInternalFrame[] frames = desktop.getAllFrames();
3346 for (JInternalFrame frame : frames)
3348 if (frame instanceof SplitFrame && frame != source)
3350 SplitFrame sf = (SplitFrame) frame;
3351 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3352 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3353 boolean gatherThis = false;
3354 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3356 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3357 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3358 if (topViewId.equals(topPanel.av.getSequenceSetId())
3359 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3362 topPanel.av.setGatherViewsHere(false);
3363 bottomPanel.av.setGatherViewsHere(false);
3364 topPanel.av.setExplodedGeometry(
3365 new Rectangle(sf.getLocation(), topFrame.getSize()));
3366 bottomPanel.av.setExplodedGeometry(
3367 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3368 myTopFrame.addAlignmentPanel(topPanel, false);
3369 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3375 topFrame.getAlignPanels().clear();
3376 bottomFrame.getAlignPanels().clear();
3383 * The dust settles...give focus to the tab we did this from.
3385 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3388 public static groovy.ui.Console getGroovyConsole()
3390 return groovyConsole;
3394 * handles the payload of a drag and drop event.
3396 * TODO refactor to desktop utilities class
3399 * - Data source strings extracted from the drop event
3401 * - protocol for each data source extracted from the drop event
3405 * - the payload from the drop event
3408 public static void transferFromDropTarget(List<Object> files,
3409 List<DataSourceType> protocols, DropTargetDropEvent evt,
3410 Transferable t) throws Exception
3413 DataFlavor uriListFlavor = new DataFlavor(
3414 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3417 urlFlavour = new DataFlavor(
3418 "application/x-java-url; class=java.net.URL");
3419 } catch (ClassNotFoundException cfe)
3421 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3425 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3430 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3431 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3432 // means url may be null.
3435 protocols.add(DataSourceType.URL);
3436 files.add(url.toString());
3437 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3438 + files.get(files.size() - 1));
3443 if (Platform.isAMacAndNotJS())
3445 jalview.bin.Console.errPrintln(
3446 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3449 } catch (Throwable ex)
3451 jalview.bin.Console.debug("URL drop handler failed.", ex);
3454 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3456 // Works on Windows and MacOSX
3457 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3458 for (Object file : (List) t
3459 .getTransferData(DataFlavor.javaFileListFlavor))
3462 protocols.add(DataSourceType.FILE);
3467 // Unix like behaviour
3468 boolean added = false;
3470 if (t.isDataFlavorSupported(uriListFlavor))
3472 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3473 // This is used by Unix drag system
3474 data = (String) t.getTransferData(uriListFlavor);
3478 // fallback to text: workaround - on OSX where there's a JVM bug
3480 .debug("standard URIListFlavor failed. Trying text");
3481 // try text fallback
3482 DataFlavor textDf = new DataFlavor(
3483 "text/plain;class=java.lang.String");
3484 if (t.isDataFlavorSupported(textDf))
3486 data = (String) t.getTransferData(textDf);
3489 jalview.bin.Console.debug("Plain text drop content returned "
3490 + (data == null ? "Null - failed" : data));
3495 while (protocols.size() < files.size())
3497 jalview.bin.Console.debug("Adding missing FILE protocol for "
3498 + files.get(protocols.size()));
3499 protocols.add(DataSourceType.FILE);
3501 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3502 data, "\r\n"); st.hasMoreTokens();)
3505 String s = st.nextToken();
3506 if (s.startsWith("#"))
3508 // the line is a comment (as per the RFC 2483)
3511 java.net.URI uri = new java.net.URI(s);
3512 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3514 protocols.add(DataSourceType.URL);
3515 files.add(uri.toString());
3519 // otherwise preserve old behaviour: catch all for file objects
3520 java.io.File file = new java.io.File(uri);
3521 protocols.add(DataSourceType.FILE);
3522 files.add(file.toString());
3527 if (jalview.bin.Console.isDebugEnabled())
3529 if (data == null || !added)
3532 if (t.getTransferDataFlavors() != null
3533 && t.getTransferDataFlavors().length > 0)
3535 jalview.bin.Console.debug(
3536 "Couldn't resolve drop data. Here are the supported flavors:");
3537 for (DataFlavor fl : t.getTransferDataFlavors())
3539 jalview.bin.Console.debug(
3540 "Supported transfer dataflavor: " + fl.toString());
3541 Object df = t.getTransferData(fl);
3544 jalview.bin.Console.debug("Retrieves: " + df);
3548 jalview.bin.Console.debug("Retrieved nothing");
3555 .debug("Couldn't resolve dataflavor for drop: "
3561 if (Platform.isWindowsAndNotJS())
3564 .debug("Scanning dropped content for Windows Link Files");
3566 // resolve any .lnk files in the file drop
3567 for (int f = 0; f < files.size(); f++)
3569 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3570 if (protocols.get(f).equals(DataSourceType.FILE)
3571 && (source.endsWith(".lnk") || source.endsWith(".url")
3572 || source.endsWith(".site")))
3576 Object obj = files.get(f);
3577 File lf = (obj instanceof File ? (File) obj
3578 : new File((String) obj));
3579 // process link file to get a URL
3580 jalview.bin.Console.debug("Found potential link file: " + lf);
3581 WindowsShortcut wscfile = new WindowsShortcut(lf);
3582 String fullname = wscfile.getRealFilename();
3583 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3584 files.set(f, fullname);
3585 jalview.bin.Console.debug("Parsed real filename " + fullname
3586 + " to extract protocol: " + protocols.get(f));
3587 } catch (Exception ex)
3589 jalview.bin.Console.error(
3590 "Couldn't parse " + files.get(f) + " as a link file.",
3599 * Sets the Preferences property for experimental features to True or False
3600 * depending on the state of the controlling menu item
3603 protected void showExperimental_actionPerformed(boolean selected)
3605 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3609 * Answers a (possibly empty) list of any structure viewer frames (currently
3610 * for either Jmol or Chimera) which are currently open. This may optionally
3611 * be restricted to viewers of a specified class, or viewers linked to a
3612 * specified alignment panel.
3615 * if not null, only return viewers linked to this panel
3616 * @param structureViewerClass
3617 * if not null, only return viewers of this class
3620 public List<StructureViewerBase> getStructureViewers(
3621 AlignmentPanel apanel,
3622 Class<? extends StructureViewerBase> structureViewerClass)
3624 List<StructureViewerBase> result = new ArrayList<>();
3625 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3627 for (JInternalFrame frame : frames)
3629 if (frame instanceof StructureViewerBase)
3631 if (structureViewerClass == null
3632 || structureViewerClass.isInstance(frame))
3635 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3637 result.add((StructureViewerBase) frame);
3645 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3647 private static boolean debugScaleMessageDone = false;
3649 public static void debugScaleMessage(Graphics g)
3651 if (debugScaleMessageDone)
3655 // output used by tests to check HiDPI scaling settings in action
3658 Graphics2D gg = (Graphics2D) g;
3661 AffineTransform t = gg.getTransform();
3662 double scaleX = t.getScaleX();
3663 double scaleY = t.getScaleY();
3664 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3665 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3666 debugScaleMessageDone = true;
3670 jalview.bin.Console.debug("Desktop graphics null");
3672 } catch (Exception e)
3674 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3679 * closes the current instance window, disposes and forgets about it.
3681 public static void closeDesktop()
3683 if (Desktop.instance != null)
3685 Desktop.instance.closeAll_actionPerformed(null);
3686 Desktop.instance.setVisible(false);
3687 Desktop us = Desktop.instance;
3688 Desktop.instance = null;
3689 // call dispose in a separate thread - try to avoid indirect deadlocks
3690 new Thread(new Runnable()
3695 ExecutorService dex = us.dialogExecutor;
3699 us.dialogExecutor = null;
3700 us.block.drainPermits();
3709 * checks if any progress bars are being displayed in any of the windows
3710 * managed by the desktop
3714 public boolean operationsAreInProgress()
3716 JInternalFrame[] frames = getAllFrames();
3717 for (JInternalFrame frame : frames)
3719 if (frame instanceof IProgressIndicator)
3721 if (((IProgressIndicator) frame).operationInProgress())
3727 return operationInProgress();
3731 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3732 * The way the modal JInternalFrame is made means it cannot be a child of an
3733 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3735 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3737 protected static void addModal(AlignFrame af, JInternalFrame jif)
3739 alignFrameModalMap.put(af, jif);
3742 protected static void closeModal(AlignFrame af)
3744 if (!alignFrameModalMap.containsKey(af))
3748 JInternalFrame jif = alignFrameModalMap.get(af);
3753 jif.setClosed(true);
3754 } catch (PropertyVetoException e)
3756 e.printStackTrace();
3759 alignFrameModalMap.remove(af);
3762 public void nonBlockingDialog(String title, String message, String button,
3763 int type, boolean scrollable, boolean modal)
3765 nonBlockingDialog(32, 2, title, message, null, button, type, scrollable,
3769 public void nonBlockingDialog(int width, int height, String title,
3770 String message, String boxtext, String button, int type,
3771 boolean scrollable, boolean html, boolean modal)
3775 type = JvOptionPane.WARNING_MESSAGE;
3777 JLabel jl = new JLabel(message);
3779 JTextComponent jtc = null;
3782 JTextPane jtp = new JTextPane();
3783 jtp.setContentType("text/html");
3784 jtp.setEditable(false);
3785 jtp.setAutoscrolls(true);
3786 jtp.setText(boxtext);
3792 JTextArea jta = new JTextArea(height, width);
3793 // jta.setLineWrap(true);
3794 jta.setEditable(false);
3795 jta.setWrapStyleWord(true);
3796 jta.setAutoscrolls(true);
3797 jta.setText(boxtext);
3802 JScrollPane jsp = scrollable
3803 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3804 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3807 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3809 JPanel jp = new JPanel();
3810 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3812 if (message != null)
3814 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3817 if (boxtext != null)
3821 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3826 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3831 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3833 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3835 { button }, button, modal, null, false);