2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JFrame;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.WindowConstants;
95 import javax.swing.event.HyperlinkEvent;
96 import javax.swing.event.HyperlinkEvent.EventType;
97 import javax.swing.event.InternalFrameAdapter;
98 import javax.swing.event.InternalFrameEvent;
100 import org.stackoverflowusers.file.WindowsShortcut;
102 import jalview.api.AlignViewportI;
103 import jalview.api.AlignmentViewPanel;
104 import jalview.api.structures.JalviewStructureDisplayI;
105 import jalview.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.datamodel.Alignment;
108 import jalview.datamodel.HiddenColumns;
109 import jalview.datamodel.Sequence;
110 import jalview.datamodel.SequenceI;
111 import jalview.gui.ImageExporter.ImageWriterI;
112 import jalview.gui.QuitHandler.QResponse;
113 import jalview.io.BackupFiles;
114 import jalview.io.DataSourceType;
115 import jalview.io.FileFormat;
116 import jalview.io.FileFormatException;
117 import jalview.io.FileFormatI;
118 import jalview.io.FileFormats;
119 import jalview.io.FileLoader;
120 import jalview.io.FormatAdapter;
121 import jalview.io.IdentifyFile;
122 import jalview.io.JalviewFileChooser;
123 import jalview.io.JalviewFileView;
124 import jalview.io.exceptions.ImageOutputException;
125 import jalview.jbgui.GSplitFrame;
126 import jalview.jbgui.GStructureViewer;
127 import jalview.project.Jalview2XML;
128 import jalview.structure.StructureSelectionManager;
129 import jalview.urls.IdOrgSettings;
130 import jalview.util.BrowserLauncher;
131 import jalview.util.ChannelProperties;
132 import jalview.util.ImageMaker.TYPE;
133 import jalview.util.LaunchUtils;
134 import jalview.util.MessageManager;
135 import jalview.util.Platform;
136 import jalview.util.ShortcutKeyMaskExWrapper;
137 import jalview.util.UrlConstants;
138 import jalview.viewmodel.AlignmentViewport;
139 import jalview.ws.params.ParamManager;
140 import jalview.ws.utils.UrlDownloadClient;
147 * @version $Revision: 1.155 $
149 public class Desktop extends jalview.jbgui.GDesktop
150 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
151 jalview.api.StructureSelectionManagerProvider
153 private static final String CITATION;
156 URL bg_logo_url = ChannelProperties.getImageURL(
157 "bg_logo." + String.valueOf(SplashScreen.logoSize));
158 URL uod_logo_url = ChannelProperties.getImageURL(
159 "uod_banner." + String.valueOf(SplashScreen.logoSize));
160 boolean logo = (bg_logo_url != null || uod_logo_url != null);
161 StringBuilder sb = new StringBuilder();
163 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
168 sb.append(bg_logo_url == null ? ""
169 : "<img alt=\"Barton Group logo\" src=\""
170 + bg_logo_url.toString() + "\">");
171 sb.append(uod_logo_url == null ? ""
172 : " <img alt=\"University of Dundee shield\" src=\""
173 + uod_logo_url.toString() + "\">");
175 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
176 sb.append("<br><br>If you use Jalview, please cite:"
177 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
178 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
179 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
180 CITATION = sb.toString();
183 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
185 private static int DEFAULT_MIN_WIDTH = 300;
187 private static int DEFAULT_MIN_HEIGHT = 250;
189 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
191 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
193 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
195 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
197 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
199 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
201 public static void setLiveDragMode(boolean b)
203 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
204 : JDesktopPane.OUTLINE_DRAG_MODE;
206 desktop.setDragMode(DRAG_MODE);
209 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
211 public static boolean nosplash = false;
214 * news reader - null if it was never started.
216 private BlogReader jvnews = null;
218 private File projectFile;
222 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
224 public void addJalviewPropertyChangeListener(
225 PropertyChangeListener listener)
227 changeSupport.addJalviewPropertyChangeListener(listener);
231 * @param propertyName
233 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
234 * java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(String propertyName,
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void removeJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.removeJalviewPropertyChangeListener(propertyName,
255 /** Singleton Desktop instance */
256 public static Desktop instance;
258 public static MyDesktopPane desktop;
260 public static MyDesktopPane getDesktop()
262 // BH 2018 could use currentThread() here as a reference to a
263 // Hashtable<Thread, MyDesktopPane> in JavaScript
267 static int openFrameCount = 0;
269 static final int xOffset = 30;
271 static final int yOffset = 30;
273 public static jalview.ws.jws1.Discoverer discoverer;
275 public static Object[] jalviewClipboard;
277 public static boolean internalCopy = false;
279 static int fileLoadingCount = 0;
281 class MyDesktopManager implements DesktopManager
284 private DesktopManager delegate;
286 public MyDesktopManager(DesktopManager delegate)
288 this.delegate = delegate;
292 public void activateFrame(JInternalFrame f)
296 delegate.activateFrame(f);
297 } catch (NullPointerException npe)
299 Point p = getMousePosition();
300 instance.showPasteMenu(p.x, p.y);
305 public void beginDraggingFrame(JComponent f)
307 delegate.beginDraggingFrame(f);
311 public void beginResizingFrame(JComponent f, int direction)
313 delegate.beginResizingFrame(f, direction);
317 public void closeFrame(JInternalFrame f)
319 delegate.closeFrame(f);
323 public void deactivateFrame(JInternalFrame f)
325 delegate.deactivateFrame(f);
329 public void deiconifyFrame(JInternalFrame f)
331 delegate.deiconifyFrame(f);
335 public void dragFrame(JComponent f, int newX, int newY)
341 delegate.dragFrame(f, newX, newY);
345 public void endDraggingFrame(JComponent f)
347 delegate.endDraggingFrame(f);
352 public void endResizingFrame(JComponent f)
354 delegate.endResizingFrame(f);
359 public void iconifyFrame(JInternalFrame f)
361 delegate.iconifyFrame(f);
365 public void maximizeFrame(JInternalFrame f)
367 delegate.maximizeFrame(f);
371 public void minimizeFrame(JInternalFrame f)
373 delegate.minimizeFrame(f);
377 public void openFrame(JInternalFrame f)
379 delegate.openFrame(f);
383 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
390 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
394 public void setBoundsForFrame(JComponent f, int newX, int newY,
395 int newWidth, int newHeight)
397 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
400 // All other methods, simply delegate
405 * Creates a new Desktop object.
411 * A note to implementors. It is ESSENTIAL that any activities that might
412 * block are spawned off as threads rather than waited for during this
417 doConfigureStructurePrefs();
418 setTitle(ChannelProperties.getProperty("app_name") + " "
419 + Cache.getProperty("VERSION"));
422 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
423 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
424 * officially documented or guaranteed to exist, so we access it via
425 * reflection. There appear to be unfathomable criteria about what this
426 * string can contain, and it if doesn't meet those criteria then "java"
427 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
428 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
429 * not. The reflection access may generate a warning: WARNING: An illegal
430 * reflective access operation has occurred WARNING: Illegal reflective
431 * access by jalview.gui.Desktop () to field
432 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
434 if (Platform.isLinux())
436 if (LaunchUtils.getJavaVersion() >= 11)
439 * Send this message to stderr as the warning that follows (due to
440 * reflection) also goes to stderr.
443 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
445 final String awtAppClassName = "awtAppClassName";
448 Toolkit xToolkit = Toolkit.getDefaultToolkit();
449 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
450 Field awtAppClassNameField = null;
452 if (Arrays.stream(declaredFields)
453 .anyMatch(f -> f.getName().equals(awtAppClassName)))
455 awtAppClassNameField = xToolkit.getClass()
456 .getDeclaredField(awtAppClassName);
459 String title = ChannelProperties.getProperty("app_name");
460 if (awtAppClassNameField != null)
462 awtAppClassNameField.setAccessible(true);
463 awtAppClassNameField.set(xToolkit, title);
468 .debug("XToolkit: " + awtAppClassName + " not found");
470 } catch (Exception e)
472 jalview.bin.Console.debug("Error setting " + awtAppClassName);
473 jalview.bin.Console.trace(Cache.getStackTraceString(e));
477 setIconImages(ChannelProperties.getIconList());
479 // override quit handling when GUI OS close [X] button pressed
480 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
481 addWindowListener(new WindowAdapter()
484 public void windowClosing(WindowEvent ev)
486 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
490 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
492 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
493 desktop = new MyDesktopPane(selmemusage);
495 showMemusage.setSelected(selmemusage);
496 desktop.setBackground(Color.white);
498 getContentPane().setLayout(new BorderLayout());
499 // alternate config - have scrollbars - see notes in JAL-153
500 // JScrollPane sp = new JScrollPane();
501 // sp.getViewport().setView(desktop);
502 // getContentPane().add(sp, BorderLayout.CENTER);
504 // BH 2018 - just an experiment to try unclipped JInternalFrames.
507 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
510 getContentPane().add(desktop, BorderLayout.CENTER);
511 desktop.setDragMode(DRAG_MODE);
513 // This line prevents Windows Look&Feel resizing all new windows to maximum
514 // if previous window was maximised
515 desktop.setDesktopManager(new MyDesktopManager(
516 Platform.isJS() ? desktop.getDesktopManager()
517 : new DefaultDesktopManager()));
519 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
520 : Platform.isAMacAndNotJS()
521 ? new AquaInternalFrameManager(
522 desktop.getDesktopManager())
523 : desktop.getDesktopManager())));
526 Rectangle dims = getLastKnownDimensions("");
533 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
534 int xPos = Math.max(5, (screenSize.width - 900) / 2);
535 int yPos = Math.max(5, (screenSize.height - 650) / 2);
536 setBounds(xPos, yPos, 900, 650);
539 // start dialogue queue for single dialogues
542 if (!Platform.isJS())
549 jconsole = new Console(this, showjconsole);
550 jconsole.setHeader(Cache.getVersionDetailsForConsole());
551 showConsole(showjconsole);
553 showNews.setVisible(false);
555 experimentalFeatures.setSelected(showExperimental());
557 getIdentifiersOrgData();
561 // Spawn a thread that shows the splashscreen
564 SwingUtilities.invokeLater(new Runnable()
569 new SplashScreen(true);
574 // Thread off a new instance of the file chooser - this reduces the time
576 // takes to open it later on.
577 new Thread(new Runnable()
582 jalview.bin.Console.debug("Filechooser init thread started.");
583 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
584 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
586 jalview.bin.Console.debug("Filechooser init thread finished.");
589 // Add the service change listener
590 changeSupport.addJalviewPropertyChangeListener("services",
591 new PropertyChangeListener()
595 public void propertyChange(PropertyChangeEvent evt)
598 .debug("Firing service changed event for "
599 + evt.getNewValue());
600 JalviewServicesChanged(evt);
605 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
608 this.addMouseListener(ma = new MouseAdapter()
611 public void mousePressed(MouseEvent evt)
613 if (evt.isPopupTrigger()) // Mac
615 showPasteMenu(evt.getX(), evt.getY());
620 public void mouseReleased(MouseEvent evt)
622 if (evt.isPopupTrigger()) // Windows
624 showPasteMenu(evt.getX(), evt.getY());
628 desktop.addMouseListener(ma);
632 // used for jalviewjsTest
633 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
638 * Answers true if user preferences to enable experimental features is True
643 public boolean showExperimental()
645 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
646 Boolean.FALSE.toString());
647 return Boolean.valueOf(experimental).booleanValue();
650 public void doConfigureStructurePrefs()
652 // configure services
653 StructureSelectionManager ssm = StructureSelectionManager
654 .getStructureSelectionManager(this);
655 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
657 ssm.setAddTempFacAnnot(
658 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
659 ssm.setProcessSecondaryStructure(
660 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
661 // JAL-3915 - RNAView is no longer an option so this has no effect
662 ssm.setSecStructServices(
663 Cache.getDefault(Preferences.USE_RNAVIEW, false));
667 ssm.setAddTempFacAnnot(false);
668 ssm.setProcessSecondaryStructure(false);
669 ssm.setSecStructServices(false);
673 public void checkForNews()
675 final Desktop me = this;
676 // Thread off the news reader, in case there are connection problems.
677 new Thread(new Runnable()
682 jalview.bin.Console.debug("Starting news thread.");
683 jvnews = new BlogReader(me);
684 showNews.setVisible(true);
685 jalview.bin.Console.debug("Completed news thread.");
690 public void getIdentifiersOrgData()
692 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
693 {// Thread off the identifiers fetcher
694 new Thread(new Runnable()
700 .debug("Downloading data from identifiers.org");
703 UrlDownloadClient.download(IdOrgSettings.getUrl(),
704 IdOrgSettings.getDownloadLocation());
705 } catch (IOException e)
708 .debug("Exception downloading identifiers.org data"
718 protected void showNews_actionPerformed(ActionEvent e)
720 showNews(showNews.isSelected());
723 void showNews(boolean visible)
725 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
726 showNews.setSelected(visible);
727 if (visible && !jvnews.isVisible())
729 new Thread(new Runnable()
734 long now = System.currentTimeMillis();
735 Desktop.instance.setProgressBar(
736 MessageManager.getString("status.refreshing_news"), now);
737 jvnews.refreshNews();
738 Desktop.instance.setProgressBar(null, now);
746 * recover the last known dimensions for a jalview window
749 * - empty string is desktop, all other windows have unique prefix
750 * @return null or last known dimensions scaled to current geometry (if last
751 * window geom was known)
753 Rectangle getLastKnownDimensions(String windowName)
755 // TODO: lock aspect ratio for scaling desktop Bug #0058199
756 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
757 String x = Cache.getProperty(windowName + "SCREEN_X");
758 String y = Cache.getProperty(windowName + "SCREEN_Y");
759 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
760 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
761 if ((x != null) && (y != null) && (width != null) && (height != null))
763 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
764 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
765 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
767 // attempt #1 - try to cope with change in screen geometry - this
768 // version doesn't preserve original jv aspect ratio.
769 // take ratio of current screen size vs original screen size.
770 double sw = ((1f * screenSize.width) / (1f * Integer
771 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
772 double sh = ((1f * screenSize.height) / (1f * Integer
773 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
774 // rescale the bounds depending upon the current screen geometry.
775 ix = (int) (ix * sw);
776 iw = (int) (iw * sw);
777 iy = (int) (iy * sh);
778 ih = (int) (ih * sh);
779 while (ix >= screenSize.width)
781 jalview.bin.Console.debug(
782 "Window geometry location recall error: shifting horizontal to within screenbounds.");
783 ix -= screenSize.width;
785 while (iy >= screenSize.height)
787 jalview.bin.Console.debug(
788 "Window geometry location recall error: shifting vertical to within screenbounds.");
789 iy -= screenSize.height;
791 jalview.bin.Console.debug(
792 "Got last known dimensions for " + windowName + ": x:" + ix
793 + " y:" + iy + " width:" + iw + " height:" + ih);
795 // return dimensions for new instance
796 return new Rectangle(ix, iy, iw, ih);
801 void showPasteMenu(int x, int y)
803 JPopupMenu popup = new JPopupMenu();
804 JMenuItem item = new JMenuItem(
805 MessageManager.getString("label.paste_new_window"));
806 item.addActionListener(new ActionListener()
809 public void actionPerformed(ActionEvent evt)
816 popup.show(this, x, y);
821 // quick patch for JAL-4150 - needs some more work and test coverage
822 // TODO - unify below and AlignFrame.paste()
823 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
824 // clipboard has come from a different alignment window than the one where
825 // paste has been called! JAL-4151
827 if (Desktop.jalviewClipboard != null)
829 // The clipboard was filled from within Jalview, we must use the
831 // And dataset from the copied alignment
832 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
833 // be doubly sure that we create *new* sequence objects.
834 SequenceI[] sequences = new SequenceI[newseq.length];
835 for (int i = 0; i < newseq.length; i++)
837 sequences[i] = new Sequence(newseq[i]);
839 Alignment alignment = new Alignment(sequences);
840 // dataset is inherited
841 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
842 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
843 AlignFrame.DEFAULT_HEIGHT);
844 String newtitle = new String("Copied sequences");
846 if (Desktop.jalviewClipboard[2] != null)
848 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
849 af.viewport.setHiddenColumns(hc);
852 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
853 AlignFrame.DEFAULT_HEIGHT);
860 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
861 Transferable contents = c.getContents(this);
863 if (contents != null)
865 String file = (String) contents
866 .getTransferData(DataFlavor.stringFlavor);
868 FileFormatI format = new IdentifyFile().identify(file,
869 DataSourceType.PASTE);
871 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
874 } catch (Exception ex)
877 "Unable to paste alignment from system clipboard:\n" + ex);
883 * Adds and opens the given frame to the desktop
894 public static synchronized void addInternalFrame(
895 final JInternalFrame frame, String title, int w, int h)
897 addInternalFrame(frame, title, true, w, h, true, false);
901 * Add an internal frame to the Jalview desktop
908 * When true, display frame immediately, otherwise, caller must call
909 * setVisible themselves.
915 public static synchronized void addInternalFrame(
916 final JInternalFrame frame, String title, boolean makeVisible,
919 addInternalFrame(frame, title, makeVisible, w, h, true, false);
923 * Add an internal frame to the Jalview desktop and make it visible
936 public static synchronized void addInternalFrame(
937 final JInternalFrame frame, String title, int w, int h,
940 addInternalFrame(frame, title, true, w, h, resizable, false);
944 * Add an internal frame to the Jalview desktop
951 * When true, display frame immediately, otherwise, caller must call
952 * setVisible themselves.
959 * @param ignoreMinSize
960 * Do not set the default minimum size for frame
962 public static synchronized void addInternalFrame(
963 final JInternalFrame frame, String title, boolean makeVisible,
964 int w, int h, boolean resizable, boolean ignoreMinSize)
967 // TODO: allow callers to determine X and Y position of frame (eg. via
969 // TODO: consider fixing method to update entries in the window submenu with
970 // the current window title
972 frame.setTitle(title);
973 if (frame.getWidth() < 1 || frame.getHeight() < 1)
977 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
978 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
979 // IF JALVIEW IS RUNNING HEADLESS
980 // ///////////////////////////////////////////////
981 if (instance == null || (System.getProperty("java.awt.headless") != null
982 && System.getProperty("java.awt.headless").equals("true")))
991 frame.setMinimumSize(
992 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
994 // Set default dimension for Alignment Frame window.
995 // The Alignment Frame window could be added from a number of places,
997 // I did this here in order not to miss out on any Alignment frame.
998 if (frame instanceof AlignFrame)
1000 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1001 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1005 frame.setVisible(makeVisible);
1006 frame.setClosable(true);
1007 frame.setResizable(resizable);
1008 frame.setMaximizable(resizable);
1009 frame.setIconifiable(resizable);
1010 frame.setOpaque(Platform.isJS());
1012 if (frame.getX() < 1 && frame.getY() < 1)
1014 frame.setLocation(xOffset * openFrameCount,
1015 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1019 * add an entry for the new frame in the Window menu (and remove it when the
1022 final JMenuItem menuItem = new JMenuItem(title);
1023 frame.addInternalFrameListener(new InternalFrameAdapter()
1026 public void internalFrameActivated(InternalFrameEvent evt)
1028 JInternalFrame itf = desktop.getSelectedFrame();
1031 if (itf instanceof AlignFrame)
1033 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1040 public void internalFrameClosed(InternalFrameEvent evt)
1042 PaintRefresher.RemoveComponent(frame);
1045 * defensive check to prevent frames being added half off the window
1047 if (openFrameCount > 0)
1053 * ensure no reference to alignFrame retained by menu item listener
1055 if (menuItem.getActionListeners().length > 0)
1057 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1059 windowMenu.remove(menuItem);
1063 menuItem.addActionListener(new ActionListener()
1066 public void actionPerformed(ActionEvent e)
1070 frame.setSelected(true);
1071 frame.setIcon(false);
1072 } catch (java.beans.PropertyVetoException ex)
1079 setKeyBindings(frame);
1081 // Since the latest FlatLaf patch, we occasionally have problems showing structureViewer frames...
1083 boolean shown=false;
1084 Exception last=null;
1091 } catch (IllegalArgumentException iaex)
1095 jalview.bin.Console.info(
1096 "Squashed IllegalArgument Exception (" + tries + " left) for "+frame.getTitle(),
1101 } catch (InterruptedException iex)
1106 } while (!shown && tries > 0);
1109 jalview.bin.Console.error("Serious Problem whilst showing window "+frame.getTitle(),last);
1112 windowMenu.add(menuItem);
1117 frame.setSelected(true);
1118 frame.requestFocus();
1119 } catch (java.beans.PropertyVetoException ve)
1121 } catch (java.lang.ClassCastException cex)
1123 jalview.bin.Console.warn(
1124 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1130 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1135 private static void setKeyBindings(JInternalFrame frame)
1137 @SuppressWarnings("serial")
1138 final Action closeAction = new AbstractAction()
1141 public void actionPerformed(ActionEvent e)
1148 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1150 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1151 InputEvent.CTRL_DOWN_MASK);
1152 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1153 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1155 InputMap inputMap = frame
1156 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1157 String ctrlW = ctrlWKey.toString();
1158 inputMap.put(ctrlWKey, ctrlW);
1159 inputMap.put(cmdWKey, ctrlW);
1161 ActionMap actionMap = frame.getActionMap();
1162 actionMap.put(ctrlW, closeAction);
1166 public void lostOwnership(Clipboard clipboard, Transferable contents)
1170 Desktop.jalviewClipboard = null;
1173 internalCopy = false;
1177 public void dragEnter(DropTargetDragEvent evt)
1182 public void dragExit(DropTargetEvent evt)
1187 public void dragOver(DropTargetDragEvent evt)
1192 public void dropActionChanged(DropTargetDragEvent evt)
1203 public void drop(DropTargetDropEvent evt)
1205 boolean success = true;
1206 // JAL-1552 - acceptDrop required before getTransferable call for
1207 // Java's Transferable for native dnd
1208 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1209 Transferable t = evt.getTransferable();
1210 List<Object> files = new ArrayList<>();
1211 List<DataSourceType> protocols = new ArrayList<>();
1215 Desktop.transferFromDropTarget(files, protocols, evt, t);
1216 } catch (Exception e)
1218 e.printStackTrace();
1226 for (int i = 0; i < files.size(); i++)
1228 // BH 2018 File or String
1229 Object file = files.get(i);
1230 String fileName = file.toString();
1231 DataSourceType protocol = (protocols == null)
1232 ? DataSourceType.FILE
1234 FileFormatI format = null;
1236 if (fileName.endsWith(".jar"))
1238 format = FileFormat.Jalview;
1243 format = new IdentifyFile().identify(file, protocol);
1245 if (file instanceof File)
1247 Platform.cacheFileData((File) file);
1249 new FileLoader().LoadFile(null, file, protocol, format);
1252 } catch (Exception ex)
1257 evt.dropComplete(success); // need this to ensure input focus is properly
1258 // transfered to any new windows created
1268 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1270 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1271 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1272 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1273 BackupFiles.getEnabled());
1275 chooser.setFileView(new JalviewFileView());
1276 chooser.setDialogTitle(
1277 MessageManager.getString("label.open_local_file"));
1278 chooser.setToolTipText(MessageManager.getString("action.open"));
1280 chooser.setResponseHandler(0, () -> {
1281 File selectedFile = chooser.getSelectedFile();
1282 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1284 FileFormatI format = chooser.getSelectedFormat();
1287 * Call IdentifyFile to verify the file contains what its extension implies.
1288 * Skip this step for dynamically added file formats, because IdentifyFile does
1289 * not know how to recognise them.
1291 if (FileFormats.getInstance().isIdentifiable(format))
1295 format = new IdentifyFile().identify(selectedFile,
1296 DataSourceType.FILE);
1297 } catch (FileFormatException e)
1299 // format = null; //??
1303 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1306 chooser.showOpenDialog(this);
1310 * Shows a dialog for input of a URL at which to retrieve alignment data
1315 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1317 // This construct allows us to have a wider textfield
1319 JLabel label = new JLabel(
1320 MessageManager.getString("label.input_file_url"));
1322 JPanel panel = new JPanel(new GridLayout(2, 1));
1326 * the URL to fetch is input in Java: an editable combobox with history JS:
1327 * (pending JAL-3038) a plain text field
1330 String urlBase = "https://www.";
1331 if (Platform.isJS())
1333 history = new JTextField(urlBase, 35);
1342 JComboBox<String> asCombo = new JComboBox<>();
1343 asCombo.setPreferredSize(new Dimension(400, 20));
1344 asCombo.setEditable(true);
1345 asCombo.addItem(urlBase);
1346 String historyItems = Cache.getProperty("RECENT_URL");
1347 if (historyItems != null)
1349 for (String token : historyItems.split("\\t"))
1351 asCombo.addItem(token);
1358 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1359 MessageManager.getString("action.cancel") };
1360 Runnable action = () -> {
1361 @SuppressWarnings("unchecked")
1362 String url = (history instanceof JTextField
1363 ? ((JTextField) history).getText()
1364 : ((JComboBox<String>) history).getEditor().getItem()
1365 .toString().trim());
1367 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1369 if (viewport != null)
1371 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1372 FileFormat.Jalview);
1376 new FileLoader().LoadFile(url, DataSourceType.URL,
1377 FileFormat.Jalview);
1382 FileFormatI format = null;
1385 format = new IdentifyFile().identify(url, DataSourceType.URL);
1386 } catch (FileFormatException e)
1388 // TODO revise error handling, distinguish between
1389 // URL not found and response not valid
1394 String msg = MessageManager.formatMessage("label.couldnt_locate",
1396 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1397 MessageManager.getString("label.url_not_found"),
1398 JvOptionPane.WARNING_MESSAGE);
1402 if (viewport != null)
1404 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1409 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1413 String dialogOption = MessageManager
1414 .getString("label.input_alignment_from_url");
1415 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1416 .showInternalDialog(panel, dialogOption,
1417 JvOptionPane.YES_NO_CANCEL_OPTION,
1418 JvOptionPane.PLAIN_MESSAGE, null, options,
1419 MessageManager.getString("action.ok"));
1423 * Opens the CutAndPaste window for the user to paste an alignment in to
1426 * - if not null, the pasted alignment is added to the current
1427 * alignment; if null, to a new alignment window
1430 public void inputTextboxMenuItem_actionPerformed(
1431 AlignmentViewPanel viewPanel)
1433 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1434 cap.setForInput(viewPanel);
1435 Desktop.addInternalFrame(cap,
1436 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1441 * Check with user and saving files before actually quitting
1443 public void desktopQuit()
1445 desktopQuit(true, false);
1448 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1450 final Runnable doDesktopQuit = () -> {
1451 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1452 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1453 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1454 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1455 getBounds().y, getWidth(), getHeight()));
1457 if (jconsole != null)
1459 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1460 jconsole.stopConsole();
1465 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1468 // Frames should all close automatically. Keeping external
1469 // viewers open should already be decided by user.
1470 closeAll_actionPerformed(null);
1472 // check for aborted quit
1473 if (QuitHandler.quitCancelled())
1475 jalview.bin.Console.debug("Desktop aborting quit");
1479 if (dialogExecutor != null)
1481 dialogExecutor.shutdownNow();
1484 if (groovyConsole != null)
1486 // suppress a possible repeat prompt to save script
1487 groovyConsole.setDirty(false);
1488 groovyConsole.exit();
1491 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1493 // note that shutdown hook will not be run
1494 jalview.bin.Console.debug("Force Quit selected by user");
1495 Runtime.getRuntime().halt(0);
1498 jalview.bin.Console.debug("Quit selected by user");
1501 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1502 // instance.dispose();
1507 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1508 QuitHandler.defaultCancelQuit);
1512 * Don't call this directly, use desktopQuit() above. Exits the program.
1517 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1518 // not run a second time if gotQuitResponse flag has been set (i.e. user
1519 // confirmed quit of some kind).
1520 Jalview.exit("Desktop exiting.", 0);
1523 private void storeLastKnownDimensions(String string, Rectangle jc)
1525 jalview.bin.Console.debug("Storing last known dimensions for " + string
1526 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1527 + " height:" + jc.height);
1529 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1530 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1531 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1532 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1542 public void aboutMenuItem_actionPerformed(ActionEvent e)
1544 new Thread(new Runnable()
1549 new SplashScreen(false);
1555 * Returns the html text for the About screen, including any available version
1556 * number, build details, author details and citation reference, but without
1557 * the enclosing {@code html} tags
1561 public String getAboutMessage()
1563 StringBuilder message = new StringBuilder(1024);
1564 message.append("<div style=\"font-family: sans-serif;\">")
1565 .append("<h1><strong>Version: ")
1566 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1567 .append("<strong>Built: <em>")
1568 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1569 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1570 .append("</strong>");
1572 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1573 if (latestVersion.equals("Checking"))
1575 // JBP removed this message for 2.11: May be reinstated in future version
1576 // message.append("<br>...Checking latest version...</br>");
1578 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1580 boolean red = false;
1581 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1582 .indexOf("automated build") == -1)
1585 // Displayed when code version and jnlp version do not match and code
1586 // version is not a development build
1587 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1590 message.append("<br>!! Version ")
1591 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1592 .append(" is available for download from ")
1593 .append(Cache.getDefault("www.jalview.org",
1594 "https://www.jalview.org"))
1598 message.append("</div>");
1601 message.append("<br>Authors: ");
1602 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1603 message.append(CITATION);
1605 message.append("</div>");
1607 return message.toString();
1611 * Action on requesting Help documentation
1614 public void documentationMenuItem_actionPerformed()
1618 if (Platform.isJS())
1620 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1629 Help.showHelpWindow();
1631 } catch (Exception ex)
1633 System.err.println("Error opening help: " + ex.getMessage());
1638 public void closeAll_actionPerformed(ActionEvent e)
1640 // TODO show a progress bar while closing?
1641 JInternalFrame[] frames = desktop.getAllFrames();
1642 for (int i = 0; i < frames.length; i++)
1646 frames[i].setClosed(true);
1647 } catch (java.beans.PropertyVetoException ex)
1651 Jalview.setCurrentAlignFrame(null);
1652 jalview.bin.Console.info("ALL CLOSED");
1655 * reset state of singleton objects as appropriate (clear down session state
1656 * when all windows are closed)
1658 StructureSelectionManager ssm = StructureSelectionManager
1659 .getStructureSelectionManager(this);
1666 public int structureViewersStillRunningCount()
1669 JInternalFrame[] frames = desktop.getAllFrames();
1670 for (int i = 0; i < frames.length; i++)
1672 if (frames[i] != null
1673 && frames[i] instanceof JalviewStructureDisplayI)
1675 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1683 public void raiseRelated_actionPerformed(ActionEvent e)
1685 reorderAssociatedWindows(false, false);
1689 public void minimizeAssociated_actionPerformed(ActionEvent e)
1691 reorderAssociatedWindows(true, false);
1694 void closeAssociatedWindows()
1696 reorderAssociatedWindows(false, true);
1702 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1706 protected void garbageCollect_actionPerformed(ActionEvent e)
1708 // We simply collect the garbage
1709 jalview.bin.Console.debug("Collecting garbage...");
1711 jalview.bin.Console.debug("Finished garbage collection.");
1717 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1721 protected void showMemusage_actionPerformed(ActionEvent e)
1723 desktop.showMemoryUsage(showMemusage.isSelected());
1730 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1734 protected void showConsole_actionPerformed(ActionEvent e)
1736 showConsole(showConsole.isSelected());
1739 Console jconsole = null;
1742 * control whether the java console is visible or not
1746 void showConsole(boolean selected)
1748 // TODO: decide if we should update properties file
1749 if (jconsole != null) // BH 2018
1751 showConsole.setSelected(selected);
1752 Cache.setProperty("SHOW_JAVA_CONSOLE",
1753 Boolean.valueOf(selected).toString());
1754 jconsole.setVisible(selected);
1758 void reorderAssociatedWindows(boolean minimize, boolean close)
1760 JInternalFrame[] frames = desktop.getAllFrames();
1761 if (frames == null || frames.length < 1)
1766 AlignmentViewport source = null, target = null;
1767 if (frames[0] instanceof AlignFrame)
1769 source = ((AlignFrame) frames[0]).getCurrentView();
1771 else if (frames[0] instanceof TreePanel)
1773 source = ((TreePanel) frames[0]).getViewPort();
1775 else if (frames[0] instanceof PCAPanel)
1777 source = ((PCAPanel) frames[0]).av;
1779 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1781 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1786 for (int i = 0; i < frames.length; i++)
1789 if (frames[i] == null)
1793 if (frames[i] instanceof AlignFrame)
1795 target = ((AlignFrame) frames[i]).getCurrentView();
1797 else if (frames[i] instanceof TreePanel)
1799 target = ((TreePanel) frames[i]).getViewPort();
1801 else if (frames[i] instanceof PCAPanel)
1803 target = ((PCAPanel) frames[i]).av;
1805 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1807 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1810 if (source == target)
1816 frames[i].setClosed(true);
1820 frames[i].setIcon(minimize);
1823 frames[i].toFront();
1827 } catch (java.beans.PropertyVetoException ex)
1842 protected void preferences_actionPerformed(ActionEvent e)
1844 Preferences.openPreferences();
1848 * Prompts the user to choose a file and then saves the Jalview state as a
1849 * Jalview project file
1852 public void saveState_actionPerformed()
1854 saveState_actionPerformed(false);
1857 public void saveState_actionPerformed(boolean saveAs)
1859 java.io.File projectFile = getProjectFile();
1860 // autoSave indicates we already have a file and don't need to ask
1861 boolean autoSave = projectFile != null && !saveAs
1862 && BackupFiles.getEnabled();
1864 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1865 // saveAs="+saveAs+", Backups
1866 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1868 boolean approveSave = false;
1871 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1874 chooser.setFileView(new JalviewFileView());
1875 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1877 int value = chooser.showSaveDialog(this);
1879 if (value == JalviewFileChooser.APPROVE_OPTION)
1881 projectFile = chooser.getSelectedFile();
1882 setProjectFile(projectFile);
1887 if (approveSave || autoSave)
1889 final Desktop me = this;
1890 final java.io.File chosenFile = projectFile;
1891 new Thread(new Runnable()
1896 // TODO: refactor to Jalview desktop session controller action.
1897 setProgressBar(MessageManager.formatMessage(
1898 "label.saving_jalview_project", new Object[]
1899 { chosenFile.getName() }), chosenFile.hashCode());
1900 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1901 // TODO catch and handle errors for savestate
1902 // TODO prevent user from messing with the Desktop whilst we're saving
1905 boolean doBackup = BackupFiles.getEnabled();
1906 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1909 new Jalview2XML().saveState(
1910 doBackup ? backupfiles.getTempFile() : chosenFile);
1914 backupfiles.setWriteSuccess(true);
1915 backupfiles.rollBackupsAndRenameTempFile();
1917 } catch (OutOfMemoryError oom)
1919 new OOMWarning("Whilst saving current state to "
1920 + chosenFile.getName(), oom);
1921 } catch (Exception ex)
1923 jalview.bin.Console.error("Problems whilst trying to save to "
1924 + chosenFile.getName(), ex);
1925 JvOptionPane.showMessageDialog(me,
1926 MessageManager.formatMessage(
1927 "label.error_whilst_saving_current_state_to",
1929 { chosenFile.getName() }),
1930 MessageManager.getString("label.couldnt_save_project"),
1931 JvOptionPane.WARNING_MESSAGE);
1933 setProgressBar(null, chosenFile.hashCode());
1940 public void saveAsState_actionPerformed(ActionEvent e)
1942 saveState_actionPerformed(true);
1945 protected void setProjectFile(File choice)
1947 this.projectFile = choice;
1950 public File getProjectFile()
1952 return this.projectFile;
1956 * Shows a file chooser dialog and tries to read in the selected file as a
1960 public void loadState_actionPerformed()
1962 final String[] suffix = new String[] { "jvp", "jar" };
1963 final String[] desc = new String[] { "Jalview Project",
1964 "Jalview Project (old)" };
1965 JalviewFileChooser chooser = new JalviewFileChooser(
1966 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1967 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1971 chooser.setFileView(new JalviewFileView());
1972 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1973 chooser.setResponseHandler(0, () -> {
1974 File selectedFile = chooser.getSelectedFile();
1975 setProjectFile(selectedFile);
1976 String choice = selectedFile.getAbsolutePath();
1977 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1978 new Thread(new Runnable()
1985 new Jalview2XML().loadJalviewAlign(selectedFile);
1986 } catch (OutOfMemoryError oom)
1988 new OOMWarning("Whilst loading project from " + choice, oom);
1989 } catch (Exception ex)
1991 jalview.bin.Console.error(
1992 "Problems whilst loading project from " + choice, ex);
1993 JvOptionPane.showMessageDialog(Desktop.desktop,
1994 MessageManager.formatMessage(
1995 "label.error_whilst_loading_project_from",
1998 MessageManager.getString("label.couldnt_load_project"),
1999 JvOptionPane.WARNING_MESSAGE);
2002 }, "Project Loader").start();
2005 chooser.showOpenDialog(this);
2009 public void inputSequence_actionPerformed(ActionEvent e)
2011 new SequenceFetcher(this);
2014 JPanel progressPanel;
2016 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2018 public void startLoading(final Object fileName)
2020 if (fileLoadingCount == 0)
2022 fileLoadingPanels.add(addProgressPanel(MessageManager
2023 .formatMessage("label.loading_file", new Object[]
2029 private JPanel addProgressPanel(String string)
2031 if (progressPanel == null)
2033 progressPanel = new JPanel(new GridLayout(1, 1));
2034 totalProgressCount = 0;
2035 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2037 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2038 JProgressBar progressBar = new JProgressBar();
2039 progressBar.setIndeterminate(true);
2041 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2043 thisprogress.add(progressBar, BorderLayout.CENTER);
2044 progressPanel.add(thisprogress);
2045 ((GridLayout) progressPanel.getLayout()).setRows(
2046 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2047 ++totalProgressCount;
2048 instance.validate();
2049 return thisprogress;
2052 int totalProgressCount = 0;
2054 private void removeProgressPanel(JPanel progbar)
2056 if (progressPanel != null)
2058 synchronized (progressPanel)
2060 progressPanel.remove(progbar);
2061 GridLayout gl = (GridLayout) progressPanel.getLayout();
2062 gl.setRows(gl.getRows() - 1);
2063 if (--totalProgressCount < 1)
2065 this.getContentPane().remove(progressPanel);
2066 progressPanel = null;
2073 public void stopLoading()
2076 if (fileLoadingCount < 1)
2078 while (fileLoadingPanels.size() > 0)
2080 removeProgressPanel(fileLoadingPanels.remove(0));
2082 fileLoadingPanels.clear();
2083 fileLoadingCount = 0;
2088 public static int getViewCount(String alignmentId)
2090 AlignmentViewport[] aps = getViewports(alignmentId);
2091 return (aps == null) ? 0 : aps.length;
2096 * @param alignmentId
2097 * - if null, all sets are returned
2098 * @return all AlignmentPanels concerning the alignmentId sequence set
2100 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2102 if (Desktop.desktop == null)
2104 // no frames created and in headless mode
2105 // TODO: verify that frames are recoverable when in headless mode
2108 List<AlignmentPanel> aps = new ArrayList<>();
2109 AlignFrame[] frames = getAlignFrames();
2114 for (AlignFrame af : frames)
2116 for (AlignmentPanel ap : af.alignPanels)
2118 if (alignmentId == null
2119 || alignmentId.equals(ap.av.getSequenceSetId()))
2125 if (aps.size() == 0)
2129 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2134 * get all the viewports on an alignment.
2136 * @param sequenceSetId
2137 * unique alignment id (may be null - all viewports returned in that
2139 * @return all viewports on the alignment bound to sequenceSetId
2141 public static AlignmentViewport[] getViewports(String sequenceSetId)
2143 List<AlignmentViewport> viewp = new ArrayList<>();
2144 if (desktop != null)
2146 AlignFrame[] frames = Desktop.getAlignFrames();
2148 for (AlignFrame afr : frames)
2150 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2151 .equals(sequenceSetId))
2153 if (afr.alignPanels != null)
2155 for (AlignmentPanel ap : afr.alignPanels)
2157 if (sequenceSetId == null
2158 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2166 viewp.add(afr.getViewport());
2170 if (viewp.size() > 0)
2172 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2179 * Explode the views in the given frame into separate AlignFrame
2183 public static void explodeViews(AlignFrame af)
2185 int size = af.alignPanels.size();
2191 // FIXME: ideally should use UI interface API
2192 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2193 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2194 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2195 for (int i = 0; i < size; i++)
2197 AlignmentPanel ap = af.alignPanels.get(i);
2199 AlignFrame newaf = new AlignFrame(ap);
2201 // transfer reference for existing feature settings to new alignFrame
2202 if (ap == af.alignPanel)
2204 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2206 newaf.featureSettings = viewFeatureSettings;
2208 newaf.setFeatureSettingsGeometry(fsBounds);
2212 * Restore the view's last exploded frame geometry if known. Multiple views from
2213 * one exploded frame share and restore the same (frame) position and size.
2215 Rectangle geometry = ap.av.getExplodedGeometry();
2216 if (geometry != null)
2218 newaf.setBounds(geometry);
2221 ap.av.setGatherViewsHere(false);
2223 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2224 AlignFrame.DEFAULT_HEIGHT);
2225 // and materialise a new feature settings dialog instance for the new
2227 // (closes the old as if 'OK' was pressed)
2228 if (ap == af.alignPanel && newaf.featureSettings != null
2229 && newaf.featureSettings.isOpen()
2230 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2232 newaf.showFeatureSettingsUI();
2236 af.featureSettings = null;
2237 af.alignPanels.clear();
2238 af.closeMenuItem_actionPerformed(true);
2243 * Gather expanded views (separate AlignFrame's) with the same sequence set
2244 * identifier back in to this frame as additional views, and close the
2245 * expanded views. Note the expanded frames may themselves have multiple
2246 * views. We take the lot.
2250 public void gatherViews(AlignFrame source)
2252 source.viewport.setGatherViewsHere(true);
2253 source.viewport.setExplodedGeometry(source.getBounds());
2254 JInternalFrame[] frames = desktop.getAllFrames();
2255 String viewId = source.viewport.getSequenceSetId();
2256 for (int t = 0; t < frames.length; t++)
2258 if (frames[t] instanceof AlignFrame && frames[t] != source)
2260 AlignFrame af = (AlignFrame) frames[t];
2261 boolean gatherThis = false;
2262 for (int a = 0; a < af.alignPanels.size(); a++)
2264 AlignmentPanel ap = af.alignPanels.get(a);
2265 if (viewId.equals(ap.av.getSequenceSetId()))
2268 ap.av.setGatherViewsHere(false);
2269 ap.av.setExplodedGeometry(af.getBounds());
2270 source.addAlignmentPanel(ap, false);
2276 if (af.featureSettings != null && af.featureSettings.isOpen())
2278 if (source.featureSettings == null)
2280 // preserve the feature settings geometry for this frame
2281 source.featureSettings = af.featureSettings;
2282 source.setFeatureSettingsGeometry(
2283 af.getFeatureSettingsGeometry());
2287 // close it and forget
2288 af.featureSettings.close();
2291 af.alignPanels.clear();
2292 af.closeMenuItem_actionPerformed(true);
2297 // refresh the feature setting UI for the source frame if it exists
2298 if (source.featureSettings != null && source.featureSettings.isOpen())
2300 source.showFeatureSettingsUI();
2305 public JInternalFrame[] getAllFrames()
2307 return desktop.getAllFrames();
2311 * Checks the given url to see if it gives a response indicating that the user
2312 * should be informed of a new questionnaire.
2316 public void checkForQuestionnaire(String url)
2318 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2319 // javax.swing.SwingUtilities.invokeLater(jvq);
2320 new Thread(jvq).start();
2323 public void checkURLLinks()
2325 // Thread off the URL link checker
2326 addDialogThread(new Runnable()
2331 if (Cache.getDefault("CHECKURLLINKS", true))
2333 // check what the actual links are - if it's just the default don't
2334 // bother with the warning
2335 List<String> links = Preferences.sequenceUrlLinks
2338 // only need to check links if there is one with a
2339 // SEQUENCE_ID which is not the default EMBL_EBI link
2340 ListIterator<String> li = links.listIterator();
2341 boolean check = false;
2342 List<JLabel> urls = new ArrayList<>();
2343 while (li.hasNext())
2345 String link = li.next();
2346 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2347 && !UrlConstants.isDefaultString(link))
2350 int barPos = link.indexOf("|");
2351 String urlMsg = barPos == -1 ? link
2352 : link.substring(0, barPos) + ": "
2353 + link.substring(barPos + 1);
2354 urls.add(new JLabel(urlMsg));
2362 // ask user to check in case URL links use old style tokens
2363 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2364 JPanel msgPanel = new JPanel();
2365 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2366 msgPanel.add(Box.createVerticalGlue());
2367 JLabel msg = new JLabel(MessageManager
2368 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2369 JLabel msg2 = new JLabel(MessageManager
2370 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2372 for (JLabel url : urls)
2378 final JCheckBox jcb = new JCheckBox(
2379 MessageManager.getString("label.do_not_display_again"));
2380 jcb.addActionListener(new ActionListener()
2383 public void actionPerformed(ActionEvent e)
2385 // update Cache settings for "don't show this again"
2386 boolean showWarningAgain = !jcb.isSelected();
2387 Cache.setProperty("CHECKURLLINKS",
2388 Boolean.valueOf(showWarningAgain).toString());
2393 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2395 .getString("label.SEQUENCE_ID_no_longer_used"),
2396 JvOptionPane.WARNING_MESSAGE);
2403 * Proxy class for JDesktopPane which optionally displays the current memory
2404 * usage and highlights the desktop area with a red bar if free memory runs
2409 public class MyDesktopPane extends JDesktopPane implements Runnable
2411 private static final float ONE_MB = 1048576f;
2413 boolean showMemoryUsage = false;
2417 java.text.NumberFormat df;
2419 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2422 public MyDesktopPane(boolean showMemoryUsage)
2424 showMemoryUsage(showMemoryUsage);
2427 public void showMemoryUsage(boolean showMemory)
2429 this.showMemoryUsage = showMemory;
2432 Thread worker = new Thread(this);
2438 public boolean isShowMemoryUsage()
2440 return showMemoryUsage;
2446 df = java.text.NumberFormat.getNumberInstance();
2447 df.setMaximumFractionDigits(2);
2448 runtime = Runtime.getRuntime();
2450 while (showMemoryUsage)
2454 maxMemory = runtime.maxMemory() / ONE_MB;
2455 allocatedMemory = runtime.totalMemory() / ONE_MB;
2456 freeMemory = runtime.freeMemory() / ONE_MB;
2457 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2459 percentUsage = (totalFreeMemory / maxMemory) * 100;
2461 // if (percentUsage < 20)
2463 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2465 // instance.set.setBorder(border1);
2468 // sleep after showing usage
2470 } catch (Exception ex)
2472 ex.printStackTrace();
2478 public void paintComponent(Graphics g)
2480 if (showMemoryUsage && g != null && df != null)
2482 if (percentUsage < 20)
2484 g.setColor(Color.red);
2486 FontMetrics fm = g.getFontMetrics();
2489 g.drawString(MessageManager.formatMessage("label.memory_stats",
2491 { df.format(totalFreeMemory), df.format(maxMemory),
2492 df.format(percentUsage) }),
2493 10, getHeight() - fm.getHeight());
2497 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2498 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2503 * Accessor method to quickly get all the AlignmentFrames loaded.
2505 * @return an array of AlignFrame, or null if none found
2507 public static AlignFrame[] getAlignFrames()
2509 if (Jalview.isHeadlessMode())
2511 // Desktop.desktop is null in headless mode
2512 return new AlignFrame[] { Jalview.currentAlignFrame };
2515 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2521 List<AlignFrame> avp = new ArrayList<>();
2523 for (int i = frames.length - 1; i > -1; i--)
2525 if (frames[i] instanceof AlignFrame)
2527 avp.add((AlignFrame) frames[i]);
2529 else if (frames[i] instanceof SplitFrame)
2532 * Also check for a split frame containing an AlignFrame
2534 GSplitFrame sf = (GSplitFrame) frames[i];
2535 if (sf.getTopFrame() instanceof AlignFrame)
2537 avp.add((AlignFrame) sf.getTopFrame());
2539 if (sf.getBottomFrame() instanceof AlignFrame)
2541 avp.add((AlignFrame) sf.getBottomFrame());
2545 if (avp.size() == 0)
2549 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2554 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2558 public GStructureViewer[] getJmols()
2560 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2566 List<GStructureViewer> avp = new ArrayList<>();
2568 for (int i = frames.length - 1; i > -1; i--)
2570 if (frames[i] instanceof AppJmol)
2572 GStructureViewer af = (GStructureViewer) frames[i];
2576 if (avp.size() == 0)
2580 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2585 * Add Groovy Support to Jalview
2588 public void groovyShell_actionPerformed()
2592 openGroovyConsole();
2593 } catch (Exception ex)
2595 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2596 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2598 MessageManager.getString("label.couldnt_create_groovy_shell"),
2599 MessageManager.getString("label.groovy_support_failed"),
2600 JvOptionPane.ERROR_MESSAGE);
2605 * Open the Groovy console
2607 void openGroovyConsole()
2609 if (groovyConsole == null)
2611 groovyConsole = new groovy.ui.Console();
2612 groovyConsole.setVariable("Jalview", this);
2613 groovyConsole.run();
2616 * We allow only one console at a time, so that AlignFrame menu option
2617 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2618 * enable 'Run script', when the console is opened, and the reverse when it is
2621 Window window = (Window) groovyConsole.getFrame();
2622 window.addWindowListener(new WindowAdapter()
2625 public void windowClosed(WindowEvent e)
2628 * rebind CMD-Q from Groovy Console to Jalview Quit
2631 enableExecuteGroovy(false);
2637 * show Groovy console window (after close and reopen)
2639 ((Window) groovyConsole.getFrame()).setVisible(true);
2642 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2643 * opening a second console
2645 enableExecuteGroovy(true);
2649 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2650 * binding when opened
2652 protected void addQuitHandler()
2655 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2657 .getKeyStroke(KeyEvent.VK_Q,
2658 jalview.util.ShortcutKeyMaskExWrapper
2659 .getMenuShortcutKeyMaskEx()),
2661 getRootPane().getActionMap().put("Quit", new AbstractAction()
2664 public void actionPerformed(ActionEvent e)
2672 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2675 * true if Groovy console is open
2677 public void enableExecuteGroovy(boolean enabled)
2680 * disable opening a second Groovy console (or re-enable when the console is
2683 groovyShell.setEnabled(!enabled);
2685 AlignFrame[] alignFrames = getAlignFrames();
2686 if (alignFrames != null)
2688 for (AlignFrame af : alignFrames)
2690 af.setGroovyEnabled(enabled);
2696 * Progress bars managed by the IProgressIndicator method.
2698 private Hashtable<Long, JPanel> progressBars;
2700 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2705 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2708 public void setProgressBar(String message, long id)
2710 if (progressBars == null)
2712 progressBars = new Hashtable<>();
2713 progressBarHandlers = new Hashtable<>();
2716 if (progressBars.get(Long.valueOf(id)) != null)
2718 JPanel panel = progressBars.remove(Long.valueOf(id));
2719 if (progressBarHandlers.contains(Long.valueOf(id)))
2721 progressBarHandlers.remove(Long.valueOf(id));
2723 removeProgressPanel(panel);
2727 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2734 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2735 * jalview.gui.IProgressIndicatorHandler)
2738 public void registerHandler(final long id,
2739 final IProgressIndicatorHandler handler)
2741 if (progressBarHandlers == null
2742 || !progressBars.containsKey(Long.valueOf(id)))
2744 throw new Error(MessageManager.getString(
2745 "error.call_setprogressbar_before_registering_handler"));
2747 progressBarHandlers.put(Long.valueOf(id), handler);
2748 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2749 if (handler.canCancel())
2751 JButton cancel = new JButton(
2752 MessageManager.getString("action.cancel"));
2753 final IProgressIndicator us = this;
2754 cancel.addActionListener(new ActionListener()
2758 public void actionPerformed(ActionEvent e)
2760 handler.cancelActivity(id);
2761 us.setProgressBar(MessageManager
2762 .formatMessage("label.cancelled_params", new Object[]
2763 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2767 progressPanel.add(cancel, BorderLayout.EAST);
2773 * @return true if any progress bars are still active
2776 public boolean operationInProgress()
2778 if (progressBars != null && progressBars.size() > 0)
2786 * This will return the first AlignFrame holding the given viewport instance.
2787 * It will break if there are more than one AlignFrames viewing a particular
2791 * @return alignFrame for viewport
2793 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2795 if (desktop != null)
2797 AlignmentPanel[] aps = getAlignmentPanels(
2798 viewport.getSequenceSetId());
2799 for (int panel = 0; aps != null && panel < aps.length; panel++)
2801 if (aps[panel] != null && aps[panel].av == viewport)
2803 return aps[panel].alignFrame;
2810 public VamsasApplication getVamsasApplication()
2812 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2818 * flag set if jalview GUI is being operated programmatically
2820 private boolean inBatchMode = false;
2823 * check if jalview GUI is being operated programmatically
2825 * @return inBatchMode
2827 public boolean isInBatchMode()
2833 * set flag if jalview GUI is being operated programmatically
2835 * @param inBatchMode
2837 public void setInBatchMode(boolean inBatchMode)
2839 this.inBatchMode = inBatchMode;
2843 * start service discovery and wait till it is done
2845 public void startServiceDiscovery()
2847 startServiceDiscovery(false);
2851 * start service discovery threads - blocking or non-blocking
2855 public void startServiceDiscovery(boolean blocking)
2857 startServiceDiscovery(blocking, false);
2861 * start service discovery threads
2864 * - false means call returns immediately
2865 * @param ignore_SHOW_JWS2_SERVICES_preference
2866 * - when true JABA services are discovered regardless of user's JWS2
2867 * discovery preference setting
2869 public void startServiceDiscovery(boolean blocking,
2870 boolean ignore_SHOW_JWS2_SERVICES_preference)
2872 boolean alive = true;
2873 Thread t0 = null, t1 = null, t2 = null;
2874 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2877 // todo: changesupport handlers need to be transferred
2878 if (discoverer == null)
2880 discoverer = new jalview.ws.jws1.Discoverer();
2881 // register PCS handler for desktop.
2882 discoverer.addPropertyChangeListener(changeSupport);
2884 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2885 // until we phase out completely
2886 (t0 = new Thread(discoverer)).start();
2889 if (ignore_SHOW_JWS2_SERVICES_preference
2890 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2892 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2893 .startDiscoverer(changeSupport);
2897 // TODO: do rest service discovery
2906 } catch (Exception e)
2909 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2910 || (t3 != null && t3.isAlive())
2911 || (t0 != null && t0.isAlive());
2917 * called to check if the service discovery process completed successfully.
2921 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2923 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2925 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2926 .getErrorMessages();
2929 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2931 if (serviceChangedDialog == null)
2933 // only run if we aren't already displaying one of these.
2934 addDialogThread(serviceChangedDialog = new Runnable()
2941 * JalviewDialog jd =new JalviewDialog() {
2943 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2945 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2947 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2949 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2951 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2952 * + " or mis-configured HTTP proxy settings.<br/>" +
2953 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2954 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2955 * true, true, "Web Service Configuration Problem", 450, 400);
2957 * jd.waitForInput();
2959 JvOptionPane.showConfirmDialog(Desktop.desktop,
2960 new JLabel("<html><table width=\"450\"><tr><td>"
2961 + ermsg + "</td></tr></table>"
2962 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2963 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2964 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2965 + " Tools->Preferences dialog box to change them.</p></html>"),
2966 "Web Service Configuration Problem",
2967 JvOptionPane.DEFAULT_OPTION,
2968 JvOptionPane.ERROR_MESSAGE);
2969 serviceChangedDialog = null;
2977 jalview.bin.Console.error(
2978 "Errors reported by JABA discovery service. Check web services preferences.\n"
2985 private Runnable serviceChangedDialog = null;
2988 * start a thread to open a URL in the configured browser. Pops up a warning
2989 * dialog to the user if there is an exception when calling out to the browser
2994 public static void showUrl(final String url)
2996 showUrl(url, Desktop.instance);
3000 * Like showUrl but allows progress handler to be specified
3004 * (null) or object implementing IProgressIndicator
3006 public static void showUrl(final String url,
3007 final IProgressIndicator progress)
3009 new Thread(new Runnable()
3016 if (progress != null)
3018 progress.setProgressBar(MessageManager
3019 .formatMessage("status.opening_params", new Object[]
3020 { url }), this.hashCode());
3022 jalview.util.BrowserLauncher.openURL(url);
3023 } catch (Exception ex)
3025 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3027 .getString("label.web_browser_not_found_unix"),
3028 MessageManager.getString("label.web_browser_not_found"),
3029 JvOptionPane.WARNING_MESSAGE);
3031 ex.printStackTrace();
3033 if (progress != null)
3035 progress.setProgressBar(null, this.hashCode());
3041 public static WsParamSetManager wsparamManager = null;
3043 public static ParamManager getUserParameterStore()
3045 if (wsparamManager == null)
3047 wsparamManager = new WsParamSetManager();
3049 return wsparamManager;
3053 * static hyperlink handler proxy method for use by Jalview's internal windows
3057 public static void hyperlinkUpdate(HyperlinkEvent e)
3059 if (e.getEventType() == EventType.ACTIVATED)
3064 url = e.getURL().toString();
3065 Desktop.showUrl(url);
3066 } catch (Exception x)
3071 .error("Couldn't handle string " + url + " as a URL.");
3073 // ignore any exceptions due to dud links.
3080 * single thread that handles display of dialogs to user.
3082 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3085 * flag indicating if dialogExecutor should try to acquire a permit
3087 private volatile boolean dialogPause = true;
3092 private Semaphore block = new Semaphore(0);
3094 private static groovy.ui.Console groovyConsole;
3097 * add another dialog thread to the queue
3101 public void addDialogThread(final Runnable prompter)
3103 dialogExecutor.submit(new Runnable()
3110 acquireDialogQueue();
3112 if (instance == null)
3118 SwingUtilities.invokeAndWait(prompter);
3119 } catch (Exception q)
3121 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3128 private boolean dialogQueueStarted = false;
3130 public void startDialogQueue()
3132 if (dialogQueueStarted)
3136 // set the flag so we don't pause waiting for another permit and semaphore
3137 // the current task to begin
3138 releaseDialogQueue();
3139 dialogQueueStarted = true;
3142 public void acquireDialogQueue()
3148 } catch (InterruptedException e)
3150 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3155 public void releaseDialogQueue()
3162 dialogPause = false;
3166 * Outputs an image of the desktop to file in EPS format, after prompting the
3167 * user for choice of Text or Lineart character rendering (unless a preference
3168 * has been set). The file name is generated as
3171 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3175 protected void snapShotWindow_actionPerformed(ActionEvent e)
3177 // currently the menu option to do this is not shown
3180 int width = getWidth();
3181 int height = getHeight();
3183 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3184 ImageWriterI writer = new ImageWriterI()
3187 public void exportImage(Graphics g) throws Exception
3190 jalview.bin.Console.info("Successfully written snapshot to file "
3191 + of.getAbsolutePath());
3194 String title = "View of desktop";
3195 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3199 exporter.doExport(of, this, width, height, title);
3200 } catch (ImageOutputException ioex)
3202 jalview.bin.Console.error(
3203 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3209 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3210 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3211 * and location last time the view was expanded (if any). However it does not
3212 * remember the split pane divider location - this is set to match the
3213 * 'exploding' frame.
3217 public void explodeViews(SplitFrame sf)
3219 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3220 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3221 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3223 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3225 int viewCount = topPanels.size();
3232 * Processing in reverse order works, forwards order leaves the first panels not
3233 * visible. I don't know why!
3235 for (int i = viewCount - 1; i >= 0; i--)
3238 * Make new top and bottom frames. These take over the respective AlignmentPanel
3239 * objects, including their AlignmentViewports, so the cdna/protein
3240 * relationships between the viewports is carried over to the new split frames.
3242 * explodedGeometry holds the (x, y) position of the previously exploded
3243 * SplitFrame, and the (width, height) of the AlignFrame component
3245 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3246 AlignFrame newTopFrame = new AlignFrame(topPanel);
3247 newTopFrame.setSize(oldTopFrame.getSize());
3248 newTopFrame.setVisible(true);
3249 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3250 .getExplodedGeometry();
3251 if (geometry != null)
3253 newTopFrame.setSize(geometry.getSize());
3256 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3257 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3258 newBottomFrame.setSize(oldBottomFrame.getSize());
3259 newBottomFrame.setVisible(true);
3260 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3261 .getExplodedGeometry();
3262 if (geometry != null)
3264 newBottomFrame.setSize(geometry.getSize());
3267 topPanel.av.setGatherViewsHere(false);
3268 bottomPanel.av.setGatherViewsHere(false);
3269 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3271 if (geometry != null)
3273 splitFrame.setLocation(geometry.getLocation());
3275 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3279 * Clear references to the panels (now relocated in the new SplitFrames) before
3280 * closing the old SplitFrame.
3283 bottomPanels.clear();
3288 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3289 * back into the given SplitFrame as additional views. Note that the gathered
3290 * frames may themselves have multiple views.
3294 public void gatherViews(GSplitFrame source)
3297 * special handling of explodedGeometry for a view within a SplitFrame: - it
3298 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3299 * height) of the AlignFrame component
3301 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3302 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3303 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3304 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3305 myBottomFrame.viewport
3306 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3307 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3308 myTopFrame.viewport.setGatherViewsHere(true);
3309 myBottomFrame.viewport.setGatherViewsHere(true);
3310 String topViewId = myTopFrame.viewport.getSequenceSetId();
3311 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3313 JInternalFrame[] frames = desktop.getAllFrames();
3314 for (JInternalFrame frame : frames)
3316 if (frame instanceof SplitFrame && frame != source)
3318 SplitFrame sf = (SplitFrame) frame;
3319 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3320 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3321 boolean gatherThis = false;
3322 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3324 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3325 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3326 if (topViewId.equals(topPanel.av.getSequenceSetId())
3327 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3330 topPanel.av.setGatherViewsHere(false);
3331 bottomPanel.av.setGatherViewsHere(false);
3332 topPanel.av.setExplodedGeometry(
3333 new Rectangle(sf.getLocation(), topFrame.getSize()));
3334 bottomPanel.av.setExplodedGeometry(
3335 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3336 myTopFrame.addAlignmentPanel(topPanel, false);
3337 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3343 topFrame.getAlignPanels().clear();
3344 bottomFrame.getAlignPanels().clear();
3351 * The dust settles...give focus to the tab we did this from.
3353 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3356 public static groovy.ui.Console getGroovyConsole()
3358 return groovyConsole;
3362 * handles the payload of a drag and drop event.
3364 * TODO refactor to desktop utilities class
3367 * - Data source strings extracted from the drop event
3369 * - protocol for each data source extracted from the drop event
3373 * - the payload from the drop event
3376 public static void transferFromDropTarget(List<Object> files,
3377 List<DataSourceType> protocols, DropTargetDropEvent evt,
3378 Transferable t) throws Exception
3381 DataFlavor uriListFlavor = new DataFlavor(
3382 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3385 urlFlavour = new DataFlavor(
3386 "application/x-java-url; class=java.net.URL");
3387 } catch (ClassNotFoundException cfe)
3389 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3393 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3398 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3399 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3400 // means url may be null.
3403 protocols.add(DataSourceType.URL);
3404 files.add(url.toString());
3405 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3406 + files.get(files.size() - 1));
3411 if (Platform.isAMacAndNotJS())
3414 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3417 } catch (Throwable ex)
3419 jalview.bin.Console.debug("URL drop handler failed.", ex);
3422 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3424 // Works on Windows and MacOSX
3425 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3426 for (Object file : (List) t
3427 .getTransferData(DataFlavor.javaFileListFlavor))
3430 protocols.add(DataSourceType.FILE);
3435 // Unix like behaviour
3436 boolean added = false;
3438 if (t.isDataFlavorSupported(uriListFlavor))
3440 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3441 // This is used by Unix drag system
3442 data = (String) t.getTransferData(uriListFlavor);
3446 // fallback to text: workaround - on OSX where there's a JVM bug
3448 .debug("standard URIListFlavor failed. Trying text");
3449 // try text fallback
3450 DataFlavor textDf = new DataFlavor(
3451 "text/plain;class=java.lang.String");
3452 if (t.isDataFlavorSupported(textDf))
3454 data = (String) t.getTransferData(textDf);
3457 jalview.bin.Console.debug("Plain text drop content returned "
3458 + (data == null ? "Null - failed" : data));
3463 while (protocols.size() < files.size())
3465 jalview.bin.Console.debug("Adding missing FILE protocol for "
3466 + files.get(protocols.size()));
3467 protocols.add(DataSourceType.FILE);
3469 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3470 data, "\r\n"); st.hasMoreTokens();)
3473 String s = st.nextToken();
3474 if (s.startsWith("#"))
3476 // the line is a comment (as per the RFC 2483)
3479 java.net.URI uri = new java.net.URI(s);
3480 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3482 protocols.add(DataSourceType.URL);
3483 files.add(uri.toString());
3487 // otherwise preserve old behaviour: catch all for file objects
3488 java.io.File file = new java.io.File(uri);
3489 protocols.add(DataSourceType.FILE);
3490 files.add(file.toString());
3495 if (jalview.bin.Console.isDebugEnabled())
3497 if (data == null || !added)
3500 if (t.getTransferDataFlavors() != null
3501 && t.getTransferDataFlavors().length > 0)
3503 jalview.bin.Console.debug(
3504 "Couldn't resolve drop data. Here are the supported flavors:");
3505 for (DataFlavor fl : t.getTransferDataFlavors())
3507 jalview.bin.Console.debug(
3508 "Supported transfer dataflavor: " + fl.toString());
3509 Object df = t.getTransferData(fl);
3512 jalview.bin.Console.debug("Retrieves: " + df);
3516 jalview.bin.Console.debug("Retrieved nothing");
3523 .debug("Couldn't resolve dataflavor for drop: "
3529 if (Platform.isWindowsAndNotJS())
3532 .debug("Scanning dropped content for Windows Link Files");
3534 // resolve any .lnk files in the file drop
3535 for (int f = 0; f < files.size(); f++)
3537 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3538 if (protocols.get(f).equals(DataSourceType.FILE)
3539 && (source.endsWith(".lnk") || source.endsWith(".url")
3540 || source.endsWith(".site")))
3544 Object obj = files.get(f);
3545 File lf = (obj instanceof File ? (File) obj
3546 : new File((String) obj));
3547 // process link file to get a URL
3548 jalview.bin.Console.debug("Found potential link file: " + lf);
3549 WindowsShortcut wscfile = new WindowsShortcut(lf);
3550 String fullname = wscfile.getRealFilename();
3551 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3552 files.set(f, fullname);
3553 jalview.bin.Console.debug("Parsed real filename " + fullname
3554 + " to extract protocol: " + protocols.get(f));
3555 } catch (Exception ex)
3557 jalview.bin.Console.error(
3558 "Couldn't parse " + files.get(f) + " as a link file.",
3567 * Sets the Preferences property for experimental features to True or False
3568 * depending on the state of the controlling menu item
3571 protected void showExperimental_actionPerformed(boolean selected)
3573 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3577 * Answers a (possibly empty) list of any structure viewer frames (currently
3578 * for either Jmol or Chimera) which are currently open. This may optionally
3579 * be restricted to viewers of a specified class, or viewers linked to a
3580 * specified alignment panel.
3583 * if not null, only return viewers linked to this panel
3584 * @param structureViewerClass
3585 * if not null, only return viewers of this class
3588 public List<StructureViewerBase> getStructureViewers(
3589 AlignmentPanel apanel,
3590 Class<? extends StructureViewerBase> structureViewerClass)
3592 List<StructureViewerBase> result = new ArrayList<>();
3593 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3595 for (JInternalFrame frame : frames)
3597 if (frame instanceof StructureViewerBase)
3599 if (structureViewerClass == null
3600 || structureViewerClass.isInstance(frame))
3603 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3605 result.add((StructureViewerBase) frame);
3613 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3615 private static boolean debugScaleMessageDone = false;
3617 public static void debugScaleMessage(Graphics g)
3619 if (debugScaleMessageDone)
3623 // output used by tests to check HiDPI scaling settings in action
3626 Graphics2D gg = (Graphics2D) g;
3629 AffineTransform t = gg.getTransform();
3630 double scaleX = t.getScaleX();
3631 double scaleY = t.getScaleY();
3632 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3633 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3634 debugScaleMessageDone = true;
3638 jalview.bin.Console.debug("Desktop graphics null");
3640 } catch (Exception e)
3642 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3647 * closes the current instance window, disposes and forgets about it.
3649 public static void closeDesktop()
3651 if (Desktop.instance != null)
3653 Desktop.instance.closeAll_actionPerformed(null);
3654 Desktop.instance.setVisible(false);
3655 Desktop us = Desktop.instance;
3656 Desktop.instance = null;
3657 // call dispose in a separate thread - try to avoid indirect deadlocks
3658 new Thread(new Runnable() {
3662 ExecutorService dex = us.dialogExecutor;
3665 us.dialogExecutor=null;
3666 us.block.drainPermits();
3675 * checks if any progress bars are being displayed in any of the windows managed by the desktop
3678 public boolean operationsAreInProgress()
3680 JInternalFrame[] frames = getAllFrames();
3681 for (JInternalFrame frame:frames)
3683 if (frame instanceof IProgressIndicator)
3685 if (((IProgressIndicator)frame).operationInProgress())
3691 return operationInProgress();