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
575 // it takes to open it later on.
576 new Thread(new Runnable()
581 jalview.bin.Console.debug("Filechooser init thread started.");
582 String fileFormat = FileLoader.getUseDefaultFileFormat()
583 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
585 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
587 jalview.bin.Console.debug("Filechooser init thread finished.");
590 // Add the service change listener
591 changeSupport.addJalviewPropertyChangeListener("services",
592 new PropertyChangeListener()
596 public void propertyChange(PropertyChangeEvent evt)
599 .debug("Firing service changed event for "
600 + evt.getNewValue());
601 JalviewServicesChanged(evt);
606 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
609 this.addMouseListener(ma = new MouseAdapter()
612 public void mousePressed(MouseEvent evt)
614 if (evt.isPopupTrigger()) // Mac
616 showPasteMenu(evt.getX(), evt.getY());
621 public void mouseReleased(MouseEvent evt)
623 if (evt.isPopupTrigger()) // Windows
625 showPasteMenu(evt.getX(), evt.getY());
629 desktop.addMouseListener(ma);
633 // used for jalviewjsTest
634 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
639 * Answers true if user preferences to enable experimental features is True
644 public boolean showExperimental()
646 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
647 Boolean.FALSE.toString());
648 return Boolean.valueOf(experimental).booleanValue();
651 public void doConfigureStructurePrefs()
653 // configure services
654 StructureSelectionManager ssm = StructureSelectionManager
655 .getStructureSelectionManager(this);
656 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
658 ssm.setAddTempFacAnnot(
659 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
660 ssm.setProcessSecondaryStructure(
661 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
662 // JAL-3915 - RNAView is no longer an option so this has no effect
663 ssm.setSecStructServices(
664 Cache.getDefault(Preferences.USE_RNAVIEW, false));
668 ssm.setAddTempFacAnnot(false);
669 ssm.setProcessSecondaryStructure(false);
670 ssm.setSecStructServices(false);
674 public void checkForNews()
676 final Desktop me = this;
677 // Thread off the news reader, in case there are connection problems.
678 new Thread(new Runnable()
683 jalview.bin.Console.debug("Starting news thread.");
684 jvnews = new BlogReader(me);
685 showNews.setVisible(true);
686 jalview.bin.Console.debug("Completed news thread.");
691 public void getIdentifiersOrgData()
693 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
694 {// Thread off the identifiers fetcher
695 new Thread(new Runnable()
701 .debug("Downloading data from identifiers.org");
704 UrlDownloadClient.download(IdOrgSettings.getUrl(),
705 IdOrgSettings.getDownloadLocation());
706 } catch (IOException e)
709 .debug("Exception downloading identifiers.org data"
719 protected void showNews_actionPerformed(ActionEvent e)
721 showNews(showNews.isSelected());
724 void showNews(boolean visible)
726 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
727 showNews.setSelected(visible);
728 if (visible && !jvnews.isVisible())
730 new Thread(new Runnable()
735 long now = System.currentTimeMillis();
736 Desktop.instance.setProgressBar(
737 MessageManager.getString("status.refreshing_news"), now);
738 jvnews.refreshNews();
739 Desktop.instance.setProgressBar(null, now);
747 * recover the last known dimensions for a jalview window
750 * - empty string is desktop, all other windows have unique prefix
751 * @return null or last known dimensions scaled to current geometry (if last
752 * window geom was known)
754 Rectangle getLastKnownDimensions(String windowName)
756 // TODO: lock aspect ratio for scaling desktop Bug #0058199
757 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
758 String x = Cache.getProperty(windowName + "SCREEN_X");
759 String y = Cache.getProperty(windowName + "SCREEN_Y");
760 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
761 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
762 if ((x != null) && (y != null) && (width != null) && (height != null))
764 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
765 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
766 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
768 // attempt #1 - try to cope with change in screen geometry - this
769 // version doesn't preserve original jv aspect ratio.
770 // take ratio of current screen size vs original screen size.
771 double sw = ((1f * screenSize.width) / (1f * Integer
772 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
773 double sh = ((1f * screenSize.height) / (1f * Integer
774 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
775 // rescale the bounds depending upon the current screen geometry.
776 ix = (int) (ix * sw);
777 iw = (int) (iw * sw);
778 iy = (int) (iy * sh);
779 ih = (int) (ih * sh);
780 while (ix >= screenSize.width)
782 jalview.bin.Console.debug(
783 "Window geometry location recall error: shifting horizontal to within screenbounds.");
784 ix -= screenSize.width;
786 while (iy >= screenSize.height)
788 jalview.bin.Console.debug(
789 "Window geometry location recall error: shifting vertical to within screenbounds.");
790 iy -= screenSize.height;
792 jalview.bin.Console.debug(
793 "Got last known dimensions for " + windowName + ": x:" + ix
794 + " y:" + iy + " width:" + iw + " height:" + ih);
796 // return dimensions for new instance
797 return new Rectangle(ix, iy, iw, ih);
802 void showPasteMenu(int x, int y)
804 JPopupMenu popup = new JPopupMenu();
805 JMenuItem item = new JMenuItem(
806 MessageManager.getString("label.paste_new_window"));
807 item.addActionListener(new ActionListener()
810 public void actionPerformed(ActionEvent evt)
817 popup.show(this, x, y);
822 // quick patch for JAL-4150 - needs some more work and test coverage
823 // TODO - unify below and AlignFrame.paste()
824 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
825 // clipboard has come from a different alignment window than the one where
826 // paste has been called! JAL-4151
828 if (Desktop.jalviewClipboard != null)
830 // The clipboard was filled from within Jalview, we must use the
832 // And dataset from the copied alignment
833 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
834 // be doubly sure that we create *new* sequence objects.
835 SequenceI[] sequences = new SequenceI[newseq.length];
836 for (int i = 0; i < newseq.length; i++)
838 sequences[i] = new Sequence(newseq[i]);
840 Alignment alignment = new Alignment(sequences);
841 // dataset is inherited
842 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
843 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
844 AlignFrame.DEFAULT_HEIGHT);
845 String newtitle = new String("Copied sequences");
847 if (Desktop.jalviewClipboard[2] != null)
849 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
850 af.viewport.setHiddenColumns(hc);
853 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
854 AlignFrame.DEFAULT_HEIGHT);
861 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
862 Transferable contents = c.getContents(this);
864 if (contents != null)
866 String file = (String) contents
867 .getTransferData(DataFlavor.stringFlavor);
869 FileFormatI format = new IdentifyFile().identify(file,
870 DataSourceType.PASTE);
872 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
875 } catch (Exception ex)
878 "Unable to paste alignment from system clipboard:\n" + ex);
884 * Adds and opens the given frame to the desktop
895 public static synchronized void addInternalFrame(
896 final JInternalFrame frame, String title, int w, int h)
898 addInternalFrame(frame, title, true, w, h, true, false);
902 * Add an internal frame to the Jalview desktop
909 * When true, display frame immediately, otherwise, caller must call
910 * setVisible themselves.
916 public static synchronized void addInternalFrame(
917 final JInternalFrame frame, String title, boolean makeVisible,
920 addInternalFrame(frame, title, makeVisible, w, h, true, false);
924 * Add an internal frame to the Jalview desktop and make it visible
937 public static synchronized void addInternalFrame(
938 final JInternalFrame frame, String title, int w, int h,
941 addInternalFrame(frame, title, true, w, h, resizable, false);
945 * Add an internal frame to the Jalview desktop
952 * When true, display frame immediately, otherwise, caller must call
953 * setVisible themselves.
960 * @param ignoreMinSize
961 * Do not set the default minimum size for frame
963 public static synchronized void addInternalFrame(
964 final JInternalFrame frame, String title, boolean makeVisible,
965 int w, int h, boolean resizable, boolean ignoreMinSize)
968 // TODO: allow callers to determine X and Y position of frame (eg. via
970 // TODO: consider fixing method to update entries in the window submenu with
971 // the current window title
973 frame.setTitle(title);
974 if (frame.getWidth() < 1 || frame.getHeight() < 1)
978 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
979 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
980 // IF JALVIEW IS RUNNING HEADLESS
981 // ///////////////////////////////////////////////
982 if (instance == null || (System.getProperty("java.awt.headless") != null
983 && System.getProperty("java.awt.headless").equals("true")))
992 frame.setMinimumSize(
993 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
995 // Set default dimension for Alignment Frame window.
996 // The Alignment Frame window could be added from a number of places,
998 // I did this here in order not to miss out on any Alignment frame.
999 if (frame instanceof AlignFrame)
1001 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1002 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1006 frame.setVisible(makeVisible);
1007 frame.setClosable(true);
1008 frame.setResizable(resizable);
1009 frame.setMaximizable(resizable);
1010 frame.setIconifiable(resizable);
1011 frame.setOpaque(Platform.isJS());
1013 if (frame.getX() < 1 && frame.getY() < 1)
1015 frame.setLocation(xOffset * openFrameCount,
1016 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1020 * add an entry for the new frame in the Window menu (and remove it when the
1023 final JMenuItem menuItem = new JMenuItem(title);
1024 frame.addInternalFrameListener(new InternalFrameAdapter()
1027 public void internalFrameActivated(InternalFrameEvent evt)
1029 JInternalFrame itf = desktop.getSelectedFrame();
1032 if (itf instanceof AlignFrame)
1034 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1041 public void internalFrameClosed(InternalFrameEvent evt)
1043 PaintRefresher.RemoveComponent(frame);
1046 * defensive check to prevent frames being added half off the window
1048 if (openFrameCount > 0)
1054 * ensure no reference to alignFrame retained by menu item listener
1056 if (menuItem.getActionListeners().length > 0)
1058 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1060 windowMenu.remove(menuItem);
1064 menuItem.addActionListener(new ActionListener()
1067 public void actionPerformed(ActionEvent e)
1071 frame.setSelected(true);
1072 frame.setIcon(false);
1073 } catch (java.beans.PropertyVetoException ex)
1080 setKeyBindings(frame);
1084 windowMenu.add(menuItem);
1089 frame.setSelected(true);
1090 frame.requestFocus();
1091 } catch (java.beans.PropertyVetoException ve)
1093 } catch (java.lang.ClassCastException cex)
1095 jalview.bin.Console.warn(
1096 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1102 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1107 private static void setKeyBindings(JInternalFrame frame)
1109 @SuppressWarnings("serial")
1110 final Action closeAction = new AbstractAction()
1113 public void actionPerformed(ActionEvent e)
1120 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1122 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1123 InputEvent.CTRL_DOWN_MASK);
1124 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1125 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1127 InputMap inputMap = frame
1128 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1129 String ctrlW = ctrlWKey.toString();
1130 inputMap.put(ctrlWKey, ctrlW);
1131 inputMap.put(cmdWKey, ctrlW);
1133 ActionMap actionMap = frame.getActionMap();
1134 actionMap.put(ctrlW, closeAction);
1138 public void lostOwnership(Clipboard clipboard, Transferable contents)
1142 Desktop.jalviewClipboard = null;
1145 internalCopy = false;
1149 public void dragEnter(DropTargetDragEvent evt)
1154 public void dragExit(DropTargetEvent evt)
1159 public void dragOver(DropTargetDragEvent evt)
1164 public void dropActionChanged(DropTargetDragEvent evt)
1175 public void drop(DropTargetDropEvent evt)
1177 boolean success = true;
1178 // JAL-1552 - acceptDrop required before getTransferable call for
1179 // Java's Transferable for native dnd
1180 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1181 Transferable t = evt.getTransferable();
1182 List<Object> files = new ArrayList<>();
1183 List<DataSourceType> protocols = new ArrayList<>();
1187 Desktop.transferFromDropTarget(files, protocols, evt, t);
1188 } catch (Exception e)
1190 e.printStackTrace();
1198 for (int i = 0; i < files.size(); i++)
1200 // BH 2018 File or String
1201 Object file = files.get(i);
1202 String fileName = file.toString();
1203 DataSourceType protocol = (protocols == null)
1204 ? DataSourceType.FILE
1206 FileFormatI format = null;
1208 if (fileName.endsWith(".jar"))
1210 format = FileFormat.Jalview;
1215 format = new IdentifyFile().identify(file, protocol);
1217 if (file instanceof File)
1219 Platform.cacheFileData((File) file);
1221 new FileLoader().LoadFile(null, file, protocol, format);
1224 } catch (Exception ex)
1229 evt.dropComplete(success); // need this to ensure input focus is properly
1230 // transfered to any new windows created
1240 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1242 String fileFormat = FileLoader.getUseDefaultFileFormat()
1243 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1245 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1246 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1247 BackupFiles.getEnabled());
1249 chooser.setFileView(new JalviewFileView());
1250 chooser.setDialogTitle(
1251 MessageManager.getString("label.open_local_file"));
1252 chooser.setToolTipText(MessageManager.getString("action.open"));
1254 chooser.setResponseHandler(0, () -> {
1255 File selectedFile = chooser.getSelectedFile();
1256 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1258 FileFormatI format = chooser.getSelectedFormat();
1261 * Call IdentifyFile to verify the file contains what its extension implies.
1262 * Skip this step for dynamically added file formats, because IdentifyFile does
1263 * not know how to recognise them.
1265 if (FileFormats.getInstance().isIdentifiable(format))
1269 format = new IdentifyFile().identify(selectedFile,
1270 DataSourceType.FILE);
1271 } catch (FileFormatException e)
1273 // format = null; //??
1277 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1280 chooser.showOpenDialog(this);
1284 * Shows a dialog for input of a URL at which to retrieve alignment data
1289 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1291 // This construct allows us to have a wider textfield
1293 JLabel label = new JLabel(
1294 MessageManager.getString("label.input_file_url"));
1296 JPanel panel = new JPanel(new GridLayout(2, 1));
1300 * the URL to fetch is input in Java: an editable combobox with history JS:
1301 * (pending JAL-3038) a plain text field
1304 String urlBase = "https://www.";
1305 if (Platform.isJS())
1307 history = new JTextField(urlBase, 35);
1316 JComboBox<String> asCombo = new JComboBox<>();
1317 asCombo.setPreferredSize(new Dimension(400, 20));
1318 asCombo.setEditable(true);
1319 asCombo.addItem(urlBase);
1320 String historyItems = Cache.getProperty("RECENT_URL");
1321 if (historyItems != null)
1323 for (String token : historyItems.split("\\t"))
1325 asCombo.addItem(token);
1332 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1333 MessageManager.getString("action.cancel") };
1334 Runnable action = () -> {
1335 @SuppressWarnings("unchecked")
1336 String url = (history instanceof JTextField
1337 ? ((JTextField) history).getText()
1338 : ((JComboBox<String>) history).getEditor().getItem()
1339 .toString().trim());
1341 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1343 if (viewport != null)
1345 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1346 FileFormat.Jalview);
1350 new FileLoader().LoadFile(url, DataSourceType.URL,
1351 FileFormat.Jalview);
1356 FileFormatI format = null;
1359 format = new IdentifyFile().identify(url, DataSourceType.URL);
1360 } catch (FileFormatException e)
1362 // TODO revise error handling, distinguish between
1363 // URL not found and response not valid
1368 String msg = MessageManager.formatMessage("label.couldnt_locate",
1370 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1371 MessageManager.getString("label.url_not_found"),
1372 JvOptionPane.WARNING_MESSAGE);
1376 if (viewport != null)
1378 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1383 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1387 String dialogOption = MessageManager
1388 .getString("label.input_alignment_from_url");
1389 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1390 .showInternalDialog(panel, dialogOption,
1391 JvOptionPane.YES_NO_CANCEL_OPTION,
1392 JvOptionPane.PLAIN_MESSAGE, null, options,
1393 MessageManager.getString("action.ok"));
1397 * Opens the CutAndPaste window for the user to paste an alignment in to
1400 * - if not null, the pasted alignment is added to the current
1401 * alignment; if null, to a new alignment window
1404 public void inputTextboxMenuItem_actionPerformed(
1405 AlignmentViewPanel viewPanel)
1407 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1408 cap.setForInput(viewPanel);
1409 Desktop.addInternalFrame(cap,
1410 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1415 * Check with user and saving files before actually quitting
1417 public void desktopQuit()
1419 desktopQuit(true, false);
1422 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1424 final Runnable doDesktopQuit = () -> {
1425 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1426 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1427 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1428 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1429 getBounds().y, getWidth(), getHeight()));
1431 if (jconsole != null)
1433 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1434 jconsole.stopConsole();
1439 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1442 // Frames should all close automatically. Keeping external
1443 // viewers open should already be decided by user.
1444 closeAll_actionPerformed(null);
1446 // check for aborted quit
1447 if (QuitHandler.quitCancelled())
1449 jalview.bin.Console.debug("Desktop aborting quit");
1453 if (dialogExecutor != null)
1455 dialogExecutor.shutdownNow();
1458 if (groovyConsole != null)
1460 // suppress a possible repeat prompt to save script
1461 groovyConsole.setDirty(false);
1462 groovyConsole.exit();
1465 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1467 // note that shutdown hook will not be run
1468 jalview.bin.Console.debug("Force Quit selected by user");
1469 Runtime.getRuntime().halt(0);
1472 jalview.bin.Console.debug("Quit selected by user");
1475 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1476 // instance.dispose();
1481 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1482 QuitHandler.defaultCancelQuit);
1486 * Don't call this directly, use desktopQuit() above. Exits the program.
1491 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1492 // not run a second time if gotQuitResponse flag has been set (i.e. user
1493 // confirmed quit of some kind).
1494 Jalview.exit("Desktop exiting.", 0);
1497 private void storeLastKnownDimensions(String string, Rectangle jc)
1499 jalview.bin.Console.debug("Storing last known dimensions for " + string
1500 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1501 + " height:" + jc.height);
1503 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1504 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1505 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1506 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1516 public void aboutMenuItem_actionPerformed(ActionEvent e)
1518 new Thread(new Runnable()
1523 new SplashScreen(false);
1529 * Returns the html text for the About screen, including any available version
1530 * number, build details, author details and citation reference, but without
1531 * the enclosing {@code html} tags
1535 public String getAboutMessage()
1537 StringBuilder message = new StringBuilder(1024);
1538 message.append("<div style=\"font-family: sans-serif;\">")
1539 .append("<h1><strong>Version: ")
1540 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1541 .append("<strong>Built: <em>")
1542 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1543 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1544 .append("</strong>");
1546 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1547 if (latestVersion.equals("Checking"))
1549 // JBP removed this message for 2.11: May be reinstated in future version
1550 // message.append("<br>...Checking latest version...</br>");
1552 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1554 boolean red = false;
1555 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1556 .indexOf("automated build") == -1)
1559 // Displayed when code version and jnlp version do not match and code
1560 // version is not a development build
1561 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1564 message.append("<br>!! Version ")
1565 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1566 .append(" is available for download from ")
1567 .append(Cache.getDefault("www.jalview.org",
1568 "https://www.jalview.org"))
1572 message.append("</div>");
1575 message.append("<br>Authors: ");
1576 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1577 message.append(CITATION);
1579 message.append("</div>");
1581 return message.toString();
1585 * Action on requesting Help documentation
1588 public void documentationMenuItem_actionPerformed()
1592 if (Platform.isJS())
1594 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1603 Help.showHelpWindow();
1605 } catch (Exception ex)
1607 System.err.println("Error opening help: " + ex.getMessage());
1612 public void closeAll_actionPerformed(ActionEvent e)
1614 // TODO show a progress bar while closing?
1615 JInternalFrame[] frames = desktop.getAllFrames();
1616 for (int i = 0; i < frames.length; i++)
1620 frames[i].setClosed(true);
1621 } catch (java.beans.PropertyVetoException ex)
1625 Jalview.setCurrentAlignFrame(null);
1626 jalview.bin.Console.info("ALL CLOSED");
1629 * reset state of singleton objects as appropriate (clear down session state
1630 * when all windows are closed)
1632 StructureSelectionManager ssm = StructureSelectionManager
1633 .getStructureSelectionManager(this);
1640 public int structureViewersStillRunningCount()
1643 JInternalFrame[] frames = desktop.getAllFrames();
1644 for (int i = 0; i < frames.length; i++)
1646 if (frames[i] != null
1647 && frames[i] instanceof JalviewStructureDisplayI)
1649 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1657 public void raiseRelated_actionPerformed(ActionEvent e)
1659 reorderAssociatedWindows(false, false);
1663 public void minimizeAssociated_actionPerformed(ActionEvent e)
1665 reorderAssociatedWindows(true, false);
1668 void closeAssociatedWindows()
1670 reorderAssociatedWindows(false, true);
1676 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1680 protected void garbageCollect_actionPerformed(ActionEvent e)
1682 // We simply collect the garbage
1683 jalview.bin.Console.debug("Collecting garbage...");
1685 jalview.bin.Console.debug("Finished garbage collection.");
1691 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1695 protected void showMemusage_actionPerformed(ActionEvent e)
1697 desktop.showMemoryUsage(showMemusage.isSelected());
1704 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1708 protected void showConsole_actionPerformed(ActionEvent e)
1710 showConsole(showConsole.isSelected());
1713 Console jconsole = null;
1716 * control whether the java console is visible or not
1720 void showConsole(boolean selected)
1722 // TODO: decide if we should update properties file
1723 if (jconsole != null) // BH 2018
1725 showConsole.setSelected(selected);
1726 Cache.setProperty("SHOW_JAVA_CONSOLE",
1727 Boolean.valueOf(selected).toString());
1728 jconsole.setVisible(selected);
1732 void reorderAssociatedWindows(boolean minimize, boolean close)
1734 JInternalFrame[] frames = desktop.getAllFrames();
1735 if (frames == null || frames.length < 1)
1740 AlignmentViewport source = null, target = null;
1741 if (frames[0] instanceof AlignFrame)
1743 source = ((AlignFrame) frames[0]).getCurrentView();
1745 else if (frames[0] instanceof TreePanel)
1747 source = ((TreePanel) frames[0]).getViewPort();
1749 else if (frames[0] instanceof PCAPanel)
1751 source = ((PCAPanel) frames[0]).av;
1753 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1755 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1760 for (int i = 0; i < frames.length; i++)
1763 if (frames[i] == null)
1767 if (frames[i] instanceof AlignFrame)
1769 target = ((AlignFrame) frames[i]).getCurrentView();
1771 else if (frames[i] instanceof TreePanel)
1773 target = ((TreePanel) frames[i]).getViewPort();
1775 else if (frames[i] instanceof PCAPanel)
1777 target = ((PCAPanel) frames[i]).av;
1779 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1781 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1784 if (source == target)
1790 frames[i].setClosed(true);
1794 frames[i].setIcon(minimize);
1797 frames[i].toFront();
1801 } catch (java.beans.PropertyVetoException ex)
1816 protected void preferences_actionPerformed(ActionEvent e)
1818 Preferences.openPreferences();
1822 * Prompts the user to choose a file and then saves the Jalview state as a
1823 * Jalview project file
1826 public void saveState_actionPerformed()
1828 saveState_actionPerformed(false);
1831 public void saveState_actionPerformed(boolean saveAs)
1833 java.io.File projectFile = getProjectFile();
1834 // autoSave indicates we already have a file and don't need to ask
1835 boolean autoSave = projectFile != null && !saveAs
1836 && BackupFiles.getEnabled();
1838 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1839 // saveAs="+saveAs+", Backups
1840 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1842 boolean approveSave = false;
1845 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1848 chooser.setFileView(new JalviewFileView());
1849 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1851 int value = chooser.showSaveDialog(this);
1853 if (value == JalviewFileChooser.APPROVE_OPTION)
1855 projectFile = chooser.getSelectedFile();
1856 setProjectFile(projectFile);
1861 if (approveSave || autoSave)
1863 final Desktop me = this;
1864 final java.io.File chosenFile = projectFile;
1865 new Thread(new Runnable()
1870 // TODO: refactor to Jalview desktop session controller action.
1871 setProgressBar(MessageManager.formatMessage(
1872 "label.saving_jalview_project", new Object[]
1873 { chosenFile.getName() }), chosenFile.hashCode());
1874 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1875 // TODO catch and handle errors for savestate
1876 // TODO prevent user from messing with the Desktop whilst we're saving
1879 boolean doBackup = BackupFiles.getEnabled();
1880 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1883 new Jalview2XML().saveState(
1884 doBackup ? backupfiles.getTempFile() : chosenFile);
1888 backupfiles.setWriteSuccess(true);
1889 backupfiles.rollBackupsAndRenameTempFile();
1891 } catch (OutOfMemoryError oom)
1893 new OOMWarning("Whilst saving current state to "
1894 + chosenFile.getName(), oom);
1895 } catch (Exception ex)
1897 jalview.bin.Console.error("Problems whilst trying to save to "
1898 + chosenFile.getName(), ex);
1899 JvOptionPane.showMessageDialog(me,
1900 MessageManager.formatMessage(
1901 "label.error_whilst_saving_current_state_to",
1903 { chosenFile.getName() }),
1904 MessageManager.getString("label.couldnt_save_project"),
1905 JvOptionPane.WARNING_MESSAGE);
1907 setProgressBar(null, chosenFile.hashCode());
1914 public void saveAsState_actionPerformed(ActionEvent e)
1916 saveState_actionPerformed(true);
1919 protected void setProjectFile(File choice)
1921 this.projectFile = choice;
1924 public File getProjectFile()
1926 return this.projectFile;
1930 * Shows a file chooser dialog and tries to read in the selected file as a
1934 public void loadState_actionPerformed()
1936 final String[] suffix = new String[] { "jvp", "jar" };
1937 final String[] desc = new String[] { "Jalview Project",
1938 "Jalview Project (old)" };
1939 JalviewFileChooser chooser = new JalviewFileChooser(
1940 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1941 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1945 chooser.setFileView(new JalviewFileView());
1946 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1947 chooser.setResponseHandler(0, () -> {
1948 File selectedFile = chooser.getSelectedFile();
1949 setProjectFile(selectedFile);
1950 String choice = selectedFile.getAbsolutePath();
1951 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1952 new Thread(new Runnable()
1959 new Jalview2XML().loadJalviewAlign(selectedFile);
1960 } catch (OutOfMemoryError oom)
1962 new OOMWarning("Whilst loading project from " + choice, oom);
1963 } catch (Exception ex)
1965 jalview.bin.Console.error(
1966 "Problems whilst loading project from " + choice, ex);
1967 JvOptionPane.showMessageDialog(Desktop.desktop,
1968 MessageManager.formatMessage(
1969 "label.error_whilst_loading_project_from",
1972 MessageManager.getString("label.couldnt_load_project"),
1973 JvOptionPane.WARNING_MESSAGE);
1976 }, "Project Loader").start();
1979 chooser.showOpenDialog(this);
1983 public void inputSequence_actionPerformed(ActionEvent e)
1985 new SequenceFetcher(this);
1988 JPanel progressPanel;
1990 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1992 public void startLoading(final Object fileName)
1994 if (fileLoadingCount == 0)
1996 fileLoadingPanels.add(addProgressPanel(MessageManager
1997 .formatMessage("label.loading_file", new Object[]
2003 private JPanel addProgressPanel(String string)
2005 if (progressPanel == null)
2007 progressPanel = new JPanel(new GridLayout(1, 1));
2008 totalProgressCount = 0;
2009 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2011 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2012 JProgressBar progressBar = new JProgressBar();
2013 progressBar.setIndeterminate(true);
2015 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2017 thisprogress.add(progressBar, BorderLayout.CENTER);
2018 progressPanel.add(thisprogress);
2019 ((GridLayout) progressPanel.getLayout()).setRows(
2020 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2021 ++totalProgressCount;
2022 instance.validate();
2023 return thisprogress;
2026 int totalProgressCount = 0;
2028 private void removeProgressPanel(JPanel progbar)
2030 if (progressPanel != null)
2032 synchronized (progressPanel)
2034 progressPanel.remove(progbar);
2035 GridLayout gl = (GridLayout) progressPanel.getLayout();
2036 gl.setRows(gl.getRows() - 1);
2037 if (--totalProgressCount < 1)
2039 this.getContentPane().remove(progressPanel);
2040 progressPanel = null;
2047 public void stopLoading()
2050 if (fileLoadingCount < 1)
2052 while (fileLoadingPanels.size() > 0)
2054 removeProgressPanel(fileLoadingPanels.remove(0));
2056 fileLoadingPanels.clear();
2057 fileLoadingCount = 0;
2062 public static int getViewCount(String alignmentId)
2064 AlignmentViewport[] aps = getViewports(alignmentId);
2065 return (aps == null) ? 0 : aps.length;
2070 * @param alignmentId
2071 * - if null, all sets are returned
2072 * @return all AlignmentPanels concerning the alignmentId sequence set
2074 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2076 if (Desktop.desktop == null)
2078 // no frames created and in headless mode
2079 // TODO: verify that frames are recoverable when in headless mode
2082 List<AlignmentPanel> aps = new ArrayList<>();
2083 AlignFrame[] frames = getAlignFrames();
2088 for (AlignFrame af : frames)
2090 for (AlignmentPanel ap : af.alignPanels)
2092 if (alignmentId == null
2093 || alignmentId.equals(ap.av.getSequenceSetId()))
2099 if (aps.size() == 0)
2103 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2108 * get all the viewports on an alignment.
2110 * @param sequenceSetId
2111 * unique alignment id (may be null - all viewports returned in that
2113 * @return all viewports on the alignment bound to sequenceSetId
2115 public static AlignmentViewport[] getViewports(String sequenceSetId)
2117 List<AlignmentViewport> viewp = new ArrayList<>();
2118 if (desktop != null)
2120 AlignFrame[] frames = Desktop.getAlignFrames();
2122 for (AlignFrame afr : frames)
2124 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2125 .equals(sequenceSetId))
2127 if (afr.alignPanels != null)
2129 for (AlignmentPanel ap : afr.alignPanels)
2131 if (sequenceSetId == null
2132 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2140 viewp.add(afr.getViewport());
2144 if (viewp.size() > 0)
2146 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2153 * Explode the views in the given frame into separate AlignFrame
2157 public static void explodeViews(AlignFrame af)
2159 int size = af.alignPanels.size();
2165 // FIXME: ideally should use UI interface API
2166 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2167 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2168 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2169 for (int i = 0; i < size; i++)
2171 AlignmentPanel ap = af.alignPanels.get(i);
2173 AlignFrame newaf = new AlignFrame(ap);
2175 // transfer reference for existing feature settings to new alignFrame
2176 if (ap == af.alignPanel)
2178 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2180 newaf.featureSettings = viewFeatureSettings;
2182 newaf.setFeatureSettingsGeometry(fsBounds);
2186 * Restore the view's last exploded frame geometry if known. Multiple views from
2187 * one exploded frame share and restore the same (frame) position and size.
2189 Rectangle geometry = ap.av.getExplodedGeometry();
2190 if (geometry != null)
2192 newaf.setBounds(geometry);
2195 ap.av.setGatherViewsHere(false);
2197 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2198 AlignFrame.DEFAULT_HEIGHT);
2199 // and materialise a new feature settings dialog instance for the new
2201 // (closes the old as if 'OK' was pressed)
2202 if (ap == af.alignPanel && newaf.featureSettings != null
2203 && newaf.featureSettings.isOpen()
2204 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2206 newaf.showFeatureSettingsUI();
2210 af.featureSettings = null;
2211 af.alignPanels.clear();
2212 af.closeMenuItem_actionPerformed(true);
2217 * Gather expanded views (separate AlignFrame's) with the same sequence set
2218 * identifier back in to this frame as additional views, and close the
2219 * expanded views. Note the expanded frames may themselves have multiple
2220 * views. We take the lot.
2224 public void gatherViews(AlignFrame source)
2226 source.viewport.setGatherViewsHere(true);
2227 source.viewport.setExplodedGeometry(source.getBounds());
2228 JInternalFrame[] frames = desktop.getAllFrames();
2229 String viewId = source.viewport.getSequenceSetId();
2230 for (int t = 0; t < frames.length; t++)
2232 if (frames[t] instanceof AlignFrame && frames[t] != source)
2234 AlignFrame af = (AlignFrame) frames[t];
2235 boolean gatherThis = false;
2236 for (int a = 0; a < af.alignPanels.size(); a++)
2238 AlignmentPanel ap = af.alignPanels.get(a);
2239 if (viewId.equals(ap.av.getSequenceSetId()))
2242 ap.av.setGatherViewsHere(false);
2243 ap.av.setExplodedGeometry(af.getBounds());
2244 source.addAlignmentPanel(ap, false);
2250 if (af.featureSettings != null && af.featureSettings.isOpen())
2252 if (source.featureSettings == null)
2254 // preserve the feature settings geometry for this frame
2255 source.featureSettings = af.featureSettings;
2256 source.setFeatureSettingsGeometry(
2257 af.getFeatureSettingsGeometry());
2261 // close it and forget
2262 af.featureSettings.close();
2265 af.alignPanels.clear();
2266 af.closeMenuItem_actionPerformed(true);
2271 // refresh the feature setting UI for the source frame if it exists
2272 if (source.featureSettings != null && source.featureSettings.isOpen())
2274 source.showFeatureSettingsUI();
2279 public JInternalFrame[] getAllFrames()
2281 return desktop.getAllFrames();
2285 * Checks the given url to see if it gives a response indicating that the user
2286 * should be informed of a new questionnaire.
2290 public void checkForQuestionnaire(String url)
2292 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2293 // javax.swing.SwingUtilities.invokeLater(jvq);
2294 new Thread(jvq).start();
2297 public void checkURLLinks()
2299 // Thread off the URL link checker
2300 addDialogThread(new Runnable()
2305 if (Cache.getDefault("CHECKURLLINKS", true))
2307 // check what the actual links are - if it's just the default don't
2308 // bother with the warning
2309 List<String> links = Preferences.sequenceUrlLinks
2312 // only need to check links if there is one with a
2313 // SEQUENCE_ID which is not the default EMBL_EBI link
2314 ListIterator<String> li = links.listIterator();
2315 boolean check = false;
2316 List<JLabel> urls = new ArrayList<>();
2317 while (li.hasNext())
2319 String link = li.next();
2320 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2321 && !UrlConstants.isDefaultString(link))
2324 int barPos = link.indexOf("|");
2325 String urlMsg = barPos == -1 ? link
2326 : link.substring(0, barPos) + ": "
2327 + link.substring(barPos + 1);
2328 urls.add(new JLabel(urlMsg));
2336 // ask user to check in case URL links use old style tokens
2337 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2338 JPanel msgPanel = new JPanel();
2339 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2340 msgPanel.add(Box.createVerticalGlue());
2341 JLabel msg = new JLabel(MessageManager
2342 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2343 JLabel msg2 = new JLabel(MessageManager
2344 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2346 for (JLabel url : urls)
2352 final JCheckBox jcb = new JCheckBox(
2353 MessageManager.getString("label.do_not_display_again"));
2354 jcb.addActionListener(new ActionListener()
2357 public void actionPerformed(ActionEvent e)
2359 // update Cache settings for "don't show this again"
2360 boolean showWarningAgain = !jcb.isSelected();
2361 Cache.setProperty("CHECKURLLINKS",
2362 Boolean.valueOf(showWarningAgain).toString());
2367 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2369 .getString("label.SEQUENCE_ID_no_longer_used"),
2370 JvOptionPane.WARNING_MESSAGE);
2377 * Proxy class for JDesktopPane which optionally displays the current memory
2378 * usage and highlights the desktop area with a red bar if free memory runs
2383 public class MyDesktopPane extends JDesktopPane implements Runnable
2385 private static final float ONE_MB = 1048576f;
2387 boolean showMemoryUsage = false;
2391 java.text.NumberFormat df;
2393 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2396 public MyDesktopPane(boolean showMemoryUsage)
2398 showMemoryUsage(showMemoryUsage);
2401 public void showMemoryUsage(boolean showMemory)
2403 this.showMemoryUsage = showMemory;
2406 Thread worker = new Thread(this);
2412 public boolean isShowMemoryUsage()
2414 return showMemoryUsage;
2420 df = java.text.NumberFormat.getNumberInstance();
2421 df.setMaximumFractionDigits(2);
2422 runtime = Runtime.getRuntime();
2424 while (showMemoryUsage)
2428 maxMemory = runtime.maxMemory() / ONE_MB;
2429 allocatedMemory = runtime.totalMemory() / ONE_MB;
2430 freeMemory = runtime.freeMemory() / ONE_MB;
2431 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2433 percentUsage = (totalFreeMemory / maxMemory) * 100;
2435 // if (percentUsage < 20)
2437 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2439 // instance.set.setBorder(border1);
2442 // sleep after showing usage
2444 } catch (Exception ex)
2446 ex.printStackTrace();
2452 public void paintComponent(Graphics g)
2454 if (showMemoryUsage && g != null && df != null)
2456 if (percentUsage < 20)
2458 g.setColor(Color.red);
2460 FontMetrics fm = g.getFontMetrics();
2463 g.drawString(MessageManager.formatMessage("label.memory_stats",
2465 { df.format(totalFreeMemory), df.format(maxMemory),
2466 df.format(percentUsage) }),
2467 10, getHeight() - fm.getHeight());
2471 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2472 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2477 * Accessor method to quickly get all the AlignmentFrames loaded.
2479 * @return an array of AlignFrame, or null if none found
2481 public static AlignFrame[] getAlignFrames()
2483 if (Jalview.isHeadlessMode())
2485 // Desktop.desktop is null in headless mode
2486 return new AlignFrame[] { Jalview.currentAlignFrame };
2489 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2495 List<AlignFrame> avp = new ArrayList<>();
2497 for (int i = frames.length - 1; i > -1; i--)
2499 if (frames[i] instanceof AlignFrame)
2501 avp.add((AlignFrame) frames[i]);
2503 else if (frames[i] instanceof SplitFrame)
2506 * Also check for a split frame containing an AlignFrame
2508 GSplitFrame sf = (GSplitFrame) frames[i];
2509 if (sf.getTopFrame() instanceof AlignFrame)
2511 avp.add((AlignFrame) sf.getTopFrame());
2513 if (sf.getBottomFrame() instanceof AlignFrame)
2515 avp.add((AlignFrame) sf.getBottomFrame());
2519 if (avp.size() == 0)
2523 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2528 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2532 public GStructureViewer[] getJmols()
2534 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2540 List<GStructureViewer> avp = new ArrayList<>();
2542 for (int i = frames.length - 1; i > -1; i--)
2544 if (frames[i] instanceof AppJmol)
2546 GStructureViewer af = (GStructureViewer) frames[i];
2550 if (avp.size() == 0)
2554 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2559 * Add Groovy Support to Jalview
2562 public void groovyShell_actionPerformed()
2566 openGroovyConsole();
2567 } catch (Exception ex)
2569 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2570 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2572 MessageManager.getString("label.couldnt_create_groovy_shell"),
2573 MessageManager.getString("label.groovy_support_failed"),
2574 JvOptionPane.ERROR_MESSAGE);
2579 * Open the Groovy console
2581 void openGroovyConsole()
2583 if (groovyConsole == null)
2585 groovyConsole = new groovy.ui.Console();
2586 groovyConsole.setVariable("Jalview", this);
2587 groovyConsole.run();
2590 * We allow only one console at a time, so that AlignFrame menu option
2591 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2592 * enable 'Run script', when the console is opened, and the reverse when it is
2595 Window window = (Window) groovyConsole.getFrame();
2596 window.addWindowListener(new WindowAdapter()
2599 public void windowClosed(WindowEvent e)
2602 * rebind CMD-Q from Groovy Console to Jalview Quit
2605 enableExecuteGroovy(false);
2611 * show Groovy console window (after close and reopen)
2613 ((Window) groovyConsole.getFrame()).setVisible(true);
2616 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2617 * opening a second console
2619 enableExecuteGroovy(true);
2623 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2624 * binding when opened
2626 protected void addQuitHandler()
2629 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2631 .getKeyStroke(KeyEvent.VK_Q,
2632 jalview.util.ShortcutKeyMaskExWrapper
2633 .getMenuShortcutKeyMaskEx()),
2635 getRootPane().getActionMap().put("Quit", new AbstractAction()
2638 public void actionPerformed(ActionEvent e)
2646 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2649 * true if Groovy console is open
2651 public void enableExecuteGroovy(boolean enabled)
2654 * disable opening a second Groovy console (or re-enable when the console is
2657 groovyShell.setEnabled(!enabled);
2659 AlignFrame[] alignFrames = getAlignFrames();
2660 if (alignFrames != null)
2662 for (AlignFrame af : alignFrames)
2664 af.setGroovyEnabled(enabled);
2670 * Progress bars managed by the IProgressIndicator method.
2672 private Hashtable<Long, JPanel> progressBars;
2674 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2679 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2682 public void setProgressBar(String message, long id)
2684 if (progressBars == null)
2686 progressBars = new Hashtable<>();
2687 progressBarHandlers = new Hashtable<>();
2690 if (progressBars.get(Long.valueOf(id)) != null)
2692 JPanel panel = progressBars.remove(Long.valueOf(id));
2693 if (progressBarHandlers.contains(Long.valueOf(id)))
2695 progressBarHandlers.remove(Long.valueOf(id));
2697 removeProgressPanel(panel);
2701 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2708 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2709 * jalview.gui.IProgressIndicatorHandler)
2712 public void registerHandler(final long id,
2713 final IProgressIndicatorHandler handler)
2715 if (progressBarHandlers == null
2716 || !progressBars.containsKey(Long.valueOf(id)))
2718 throw new Error(MessageManager.getString(
2719 "error.call_setprogressbar_before_registering_handler"));
2721 progressBarHandlers.put(Long.valueOf(id), handler);
2722 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2723 if (handler.canCancel())
2725 JButton cancel = new JButton(
2726 MessageManager.getString("action.cancel"));
2727 final IProgressIndicator us = this;
2728 cancel.addActionListener(new ActionListener()
2732 public void actionPerformed(ActionEvent e)
2734 handler.cancelActivity(id);
2735 us.setProgressBar(MessageManager
2736 .formatMessage("label.cancelled_params", new Object[]
2737 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2741 progressPanel.add(cancel, BorderLayout.EAST);
2747 * @return true if any progress bars are still active
2750 public boolean operationInProgress()
2752 if (progressBars != null && progressBars.size() > 0)
2760 * This will return the first AlignFrame holding the given viewport instance.
2761 * It will break if there are more than one AlignFrames viewing a particular
2765 * @return alignFrame for viewport
2767 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2769 if (desktop != null)
2771 AlignmentPanel[] aps = getAlignmentPanels(
2772 viewport.getSequenceSetId());
2773 for (int panel = 0; aps != null && panel < aps.length; panel++)
2775 if (aps[panel] != null && aps[panel].av == viewport)
2777 return aps[panel].alignFrame;
2784 public VamsasApplication getVamsasApplication()
2786 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2792 * flag set if jalview GUI is being operated programmatically
2794 private boolean inBatchMode = false;
2797 * check if jalview GUI is being operated programmatically
2799 * @return inBatchMode
2801 public boolean isInBatchMode()
2807 * set flag if jalview GUI is being operated programmatically
2809 * @param inBatchMode
2811 public void setInBatchMode(boolean inBatchMode)
2813 this.inBatchMode = inBatchMode;
2817 * start service discovery and wait till it is done
2819 public void startServiceDiscovery()
2821 startServiceDiscovery(false);
2825 * start service discovery threads - blocking or non-blocking
2829 public void startServiceDiscovery(boolean blocking)
2831 startServiceDiscovery(blocking, false);
2835 * start service discovery threads
2838 * - false means call returns immediately
2839 * @param ignore_SHOW_JWS2_SERVICES_preference
2840 * - when true JABA services are discovered regardless of user's JWS2
2841 * discovery preference setting
2843 public void startServiceDiscovery(boolean blocking,
2844 boolean ignore_SHOW_JWS2_SERVICES_preference)
2846 boolean alive = true;
2847 Thread t0 = null, t1 = null, t2 = null;
2848 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2851 // todo: changesupport handlers need to be transferred
2852 if (discoverer == null)
2854 discoverer = new jalview.ws.jws1.Discoverer();
2855 // register PCS handler for desktop.
2856 discoverer.addPropertyChangeListener(changeSupport);
2858 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2859 // until we phase out completely
2860 (t0 = new Thread(discoverer)).start();
2863 if (ignore_SHOW_JWS2_SERVICES_preference
2864 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2866 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2867 .startDiscoverer(changeSupport);
2871 // TODO: do rest service discovery
2880 } catch (Exception e)
2883 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2884 || (t3 != null && t3.isAlive())
2885 || (t0 != null && t0.isAlive());
2891 * called to check if the service discovery process completed successfully.
2895 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2897 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2899 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2900 .getErrorMessages();
2903 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2905 if (serviceChangedDialog == null)
2907 // only run if we aren't already displaying one of these.
2908 addDialogThread(serviceChangedDialog = new Runnable()
2915 * JalviewDialog jd =new JalviewDialog() {
2917 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2919 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2921 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2923 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2925 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2926 * + " or mis-configured HTTP proxy settings.<br/>" +
2927 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2928 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2929 * true, true, "Web Service Configuration Problem", 450, 400);
2931 * jd.waitForInput();
2933 JvOptionPane.showConfirmDialog(Desktop.desktop,
2934 new JLabel("<html><table width=\"450\"><tr><td>"
2935 + ermsg + "</td></tr></table>"
2936 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2937 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2938 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2939 + " Tools->Preferences dialog box to change them.</p></html>"),
2940 "Web Service Configuration Problem",
2941 JvOptionPane.DEFAULT_OPTION,
2942 JvOptionPane.ERROR_MESSAGE);
2943 serviceChangedDialog = null;
2951 jalview.bin.Console.error(
2952 "Errors reported by JABA discovery service. Check web services preferences.\n"
2959 private Runnable serviceChangedDialog = null;
2962 * start a thread to open a URL in the configured browser. Pops up a warning
2963 * dialog to the user if there is an exception when calling out to the browser
2968 public static void showUrl(final String url)
2970 showUrl(url, Desktop.instance);
2974 * Like showUrl but allows progress handler to be specified
2978 * (null) or object implementing IProgressIndicator
2980 public static void showUrl(final String url,
2981 final IProgressIndicator progress)
2983 new Thread(new Runnable()
2990 if (progress != null)
2992 progress.setProgressBar(MessageManager
2993 .formatMessage("status.opening_params", new Object[]
2994 { url }), this.hashCode());
2996 jalview.util.BrowserLauncher.openURL(url);
2997 } catch (Exception ex)
2999 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3001 .getString("label.web_browser_not_found_unix"),
3002 MessageManager.getString("label.web_browser_not_found"),
3003 JvOptionPane.WARNING_MESSAGE);
3005 ex.printStackTrace();
3007 if (progress != null)
3009 progress.setProgressBar(null, this.hashCode());
3015 public static WsParamSetManager wsparamManager = null;
3017 public static ParamManager getUserParameterStore()
3019 if (wsparamManager == null)
3021 wsparamManager = new WsParamSetManager();
3023 return wsparamManager;
3027 * static hyperlink handler proxy method for use by Jalview's internal windows
3031 public static void hyperlinkUpdate(HyperlinkEvent e)
3033 if (e.getEventType() == EventType.ACTIVATED)
3038 url = e.getURL().toString();
3039 Desktop.showUrl(url);
3040 } catch (Exception x)
3045 .error("Couldn't handle string " + url + " as a URL.");
3047 // ignore any exceptions due to dud links.
3054 * single thread that handles display of dialogs to user.
3056 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3059 * flag indicating if dialogExecutor should try to acquire a permit
3061 private volatile boolean dialogPause = true;
3066 private Semaphore block = new Semaphore(0);
3068 private static groovy.ui.Console groovyConsole;
3071 * add another dialog thread to the queue
3075 public void addDialogThread(final Runnable prompter)
3077 dialogExecutor.submit(new Runnable()
3084 acquireDialogQueue();
3086 if (instance == null)
3092 SwingUtilities.invokeAndWait(prompter);
3093 } catch (Exception q)
3095 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3102 private boolean dialogQueueStarted = false;
3104 public void startDialogQueue()
3106 if (dialogQueueStarted)
3110 // set the flag so we don't pause waiting for another permit and semaphore
3111 // the current task to begin
3112 releaseDialogQueue();
3113 dialogQueueStarted = true;
3116 public void acquireDialogQueue()
3122 } catch (InterruptedException e)
3124 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3129 public void releaseDialogQueue()
3136 dialogPause = false;
3140 * Outputs an image of the desktop to file in EPS format, after prompting the
3141 * user for choice of Text or Lineart character rendering (unless a preference
3142 * has been set). The file name is generated as
3145 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3149 protected void snapShotWindow_actionPerformed(ActionEvent e)
3151 // currently the menu option to do this is not shown
3154 int width = getWidth();
3155 int height = getHeight();
3157 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3158 ImageWriterI writer = new ImageWriterI()
3161 public void exportImage(Graphics g) throws Exception
3164 jalview.bin.Console.info("Successfully written snapshot to file "
3165 + of.getAbsolutePath());
3168 String title = "View of desktop";
3169 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3173 exporter.doExport(of, this, width, height, title);
3174 } catch (ImageOutputException ioex)
3176 jalview.bin.Console.error(
3177 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3183 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3184 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3185 * and location last time the view was expanded (if any). However it does not
3186 * remember the split pane divider location - this is set to match the
3187 * 'exploding' frame.
3191 public void explodeViews(SplitFrame sf)
3193 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3194 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3195 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3197 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3199 int viewCount = topPanels.size();
3206 * Processing in reverse order works, forwards order leaves the first panels not
3207 * visible. I don't know why!
3209 for (int i = viewCount - 1; i >= 0; i--)
3212 * Make new top and bottom frames. These take over the respective AlignmentPanel
3213 * objects, including their AlignmentViewports, so the cdna/protein
3214 * relationships between the viewports is carried over to the new split frames.
3216 * explodedGeometry holds the (x, y) position of the previously exploded
3217 * SplitFrame, and the (width, height) of the AlignFrame component
3219 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3220 AlignFrame newTopFrame = new AlignFrame(topPanel);
3221 newTopFrame.setSize(oldTopFrame.getSize());
3222 newTopFrame.setVisible(true);
3223 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3224 .getExplodedGeometry();
3225 if (geometry != null)
3227 newTopFrame.setSize(geometry.getSize());
3230 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3231 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3232 newBottomFrame.setSize(oldBottomFrame.getSize());
3233 newBottomFrame.setVisible(true);
3234 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3235 .getExplodedGeometry();
3236 if (geometry != null)
3238 newBottomFrame.setSize(geometry.getSize());
3241 topPanel.av.setGatherViewsHere(false);
3242 bottomPanel.av.setGatherViewsHere(false);
3243 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3245 if (geometry != null)
3247 splitFrame.setLocation(geometry.getLocation());
3249 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3253 * Clear references to the panels (now relocated in the new SplitFrames) before
3254 * closing the old SplitFrame.
3257 bottomPanels.clear();
3262 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3263 * back into the given SplitFrame as additional views. Note that the gathered
3264 * frames may themselves have multiple views.
3268 public void gatherViews(GSplitFrame source)
3271 * special handling of explodedGeometry for a view within a SplitFrame: - it
3272 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3273 * height) of the AlignFrame component
3275 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3276 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3277 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3278 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3279 myBottomFrame.viewport
3280 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3281 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3282 myTopFrame.viewport.setGatherViewsHere(true);
3283 myBottomFrame.viewport.setGatherViewsHere(true);
3284 String topViewId = myTopFrame.viewport.getSequenceSetId();
3285 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3287 JInternalFrame[] frames = desktop.getAllFrames();
3288 for (JInternalFrame frame : frames)
3290 if (frame instanceof SplitFrame && frame != source)
3292 SplitFrame sf = (SplitFrame) frame;
3293 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3294 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3295 boolean gatherThis = false;
3296 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3298 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3299 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3300 if (topViewId.equals(topPanel.av.getSequenceSetId())
3301 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3304 topPanel.av.setGatherViewsHere(false);
3305 bottomPanel.av.setGatherViewsHere(false);
3306 topPanel.av.setExplodedGeometry(
3307 new Rectangle(sf.getLocation(), topFrame.getSize()));
3308 bottomPanel.av.setExplodedGeometry(
3309 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3310 myTopFrame.addAlignmentPanel(topPanel, false);
3311 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3317 topFrame.getAlignPanels().clear();
3318 bottomFrame.getAlignPanels().clear();
3325 * The dust settles...give focus to the tab we did this from.
3327 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3330 public static groovy.ui.Console getGroovyConsole()
3332 return groovyConsole;
3336 * handles the payload of a drag and drop event.
3338 * TODO refactor to desktop utilities class
3341 * - Data source strings extracted from the drop event
3343 * - protocol for each data source extracted from the drop event
3347 * - the payload from the drop event
3350 public static void transferFromDropTarget(List<Object> files,
3351 List<DataSourceType> protocols, DropTargetDropEvent evt,
3352 Transferable t) throws Exception
3355 DataFlavor uriListFlavor = new DataFlavor(
3356 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3359 urlFlavour = new DataFlavor(
3360 "application/x-java-url; class=java.net.URL");
3361 } catch (ClassNotFoundException cfe)
3363 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3367 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3372 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3373 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3374 // means url may be null.
3377 protocols.add(DataSourceType.URL);
3378 files.add(url.toString());
3379 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3380 + files.get(files.size() - 1));
3385 if (Platform.isAMacAndNotJS())
3388 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3391 } catch (Throwable ex)
3393 jalview.bin.Console.debug("URL drop handler failed.", ex);
3396 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3398 // Works on Windows and MacOSX
3399 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3400 for (Object file : (List) t
3401 .getTransferData(DataFlavor.javaFileListFlavor))
3404 protocols.add(DataSourceType.FILE);
3409 // Unix like behaviour
3410 boolean added = false;
3412 if (t.isDataFlavorSupported(uriListFlavor))
3414 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3415 // This is used by Unix drag system
3416 data = (String) t.getTransferData(uriListFlavor);
3420 // fallback to text: workaround - on OSX where there's a JVM bug
3422 .debug("standard URIListFlavor failed. Trying text");
3423 // try text fallback
3424 DataFlavor textDf = new DataFlavor(
3425 "text/plain;class=java.lang.String");
3426 if (t.isDataFlavorSupported(textDf))
3428 data = (String) t.getTransferData(textDf);
3431 jalview.bin.Console.debug("Plain text drop content returned "
3432 + (data == null ? "Null - failed" : data));
3437 while (protocols.size() < files.size())
3439 jalview.bin.Console.debug("Adding missing FILE protocol for "
3440 + files.get(protocols.size()));
3441 protocols.add(DataSourceType.FILE);
3443 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3444 data, "\r\n"); st.hasMoreTokens();)
3447 String s = st.nextToken();
3448 if (s.startsWith("#"))
3450 // the line is a comment (as per the RFC 2483)
3453 java.net.URI uri = new java.net.URI(s);
3454 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3456 protocols.add(DataSourceType.URL);
3457 files.add(uri.toString());
3461 // otherwise preserve old behaviour: catch all for file objects
3462 java.io.File file = new java.io.File(uri);
3463 protocols.add(DataSourceType.FILE);
3464 files.add(file.toString());
3469 if (jalview.bin.Console.isDebugEnabled())
3471 if (data == null || !added)
3474 if (t.getTransferDataFlavors() != null
3475 && t.getTransferDataFlavors().length > 0)
3477 jalview.bin.Console.debug(
3478 "Couldn't resolve drop data. Here are the supported flavors:");
3479 for (DataFlavor fl : t.getTransferDataFlavors())
3481 jalview.bin.Console.debug(
3482 "Supported transfer dataflavor: " + fl.toString());
3483 Object df = t.getTransferData(fl);
3486 jalview.bin.Console.debug("Retrieves: " + df);
3490 jalview.bin.Console.debug("Retrieved nothing");
3497 .debug("Couldn't resolve dataflavor for drop: "
3503 if (Platform.isWindowsAndNotJS())
3506 .debug("Scanning dropped content for Windows Link Files");
3508 // resolve any .lnk files in the file drop
3509 for (int f = 0; f < files.size(); f++)
3511 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3512 if (protocols.get(f).equals(DataSourceType.FILE)
3513 && (source.endsWith(".lnk") || source.endsWith(".url")
3514 || source.endsWith(".site")))
3518 Object obj = files.get(f);
3519 File lf = (obj instanceof File ? (File) obj
3520 : new File((String) obj));
3521 // process link file to get a URL
3522 jalview.bin.Console.debug("Found potential link file: " + lf);
3523 WindowsShortcut wscfile = new WindowsShortcut(lf);
3524 String fullname = wscfile.getRealFilename();
3525 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3526 files.set(f, fullname);
3527 jalview.bin.Console.debug("Parsed real filename " + fullname
3528 + " to extract protocol: " + protocols.get(f));
3529 } catch (Exception ex)
3531 jalview.bin.Console.error(
3532 "Couldn't parse " + files.get(f) + " as a link file.",
3541 * Sets the Preferences property for experimental features to True or False
3542 * depending on the state of the controlling menu item
3545 protected void showExperimental_actionPerformed(boolean selected)
3547 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3551 * Answers a (possibly empty) list of any structure viewer frames (currently
3552 * for either Jmol or Chimera) which are currently open. This may optionally
3553 * be restricted to viewers of a specified class, or viewers linked to a
3554 * specified alignment panel.
3557 * if not null, only return viewers linked to this panel
3558 * @param structureViewerClass
3559 * if not null, only return viewers of this class
3562 public List<StructureViewerBase> getStructureViewers(
3563 AlignmentPanel apanel,
3564 Class<? extends StructureViewerBase> structureViewerClass)
3566 List<StructureViewerBase> result = new ArrayList<>();
3567 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3569 for (JInternalFrame frame : frames)
3571 if (frame instanceof StructureViewerBase)
3573 if (structureViewerClass == null
3574 || structureViewerClass.isInstance(frame))
3577 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3579 result.add((StructureViewerBase) frame);
3587 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3589 private static boolean debugScaleMessageDone = false;
3591 public static void debugScaleMessage(Graphics g)
3593 if (debugScaleMessageDone)
3597 // output used by tests to check HiDPI scaling settings in action
3600 Graphics2D gg = (Graphics2D) g;
3603 AffineTransform t = gg.getTransform();
3604 double scaleX = t.getScaleX();
3605 double scaleY = t.getScaleY();
3606 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3607 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3608 debugScaleMessageDone = true;
3612 jalview.bin.Console.debug("Desktop graphics null");
3614 } catch (Exception e)
3616 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3621 * closes the current instance window, disposes and forgets about it.
3623 public static void closeDesktop()
3625 if (Desktop.instance != null)
3627 Desktop.instance.closeAll_actionPerformed(null);
3628 Desktop.instance.setVisible(false);
3629 Desktop.instance.dispose();
3630 Desktop.instance = null;
3635 * checks if any progress bars are being displayed in any of the windows managed by the desktop
3638 public boolean operationsAreInProgress()
3640 JInternalFrame[] frames = getAllFrames();
3641 for (JInternalFrame frame:frames)
3643 if (frame instanceof IProgressIndicator)
3645 if (((IProgressIndicator)frame).operationInProgress())
3651 return operationInProgress();