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 if (!Platform.isJS())
546 jconsole = new Console(this, showjconsole);
547 jconsole.setHeader(Cache.getVersionDetailsForConsole());
548 showConsole(showjconsole);
550 showNews.setVisible(false);
552 experimentalFeatures.setSelected(showExperimental());
554 getIdentifiersOrgData();
558 // Spawn a thread that shows the splashscreen
561 SwingUtilities.invokeLater(new Runnable()
566 new SplashScreen(true);
571 // Thread off a new instance of the file chooser - this reduces the time
573 // takes to open it later on.
574 new Thread(new Runnable()
579 jalview.bin.Console.debug("Filechooser init thread started.");
580 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
581 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
583 jalview.bin.Console.debug("Filechooser init thread finished.");
586 // Add the service change listener
587 changeSupport.addJalviewPropertyChangeListener("services",
588 new PropertyChangeListener()
592 public void propertyChange(PropertyChangeEvent evt)
595 .debug("Firing service changed event for "
596 + evt.getNewValue());
597 JalviewServicesChanged(evt);
602 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
605 this.addMouseListener(ma = new MouseAdapter()
608 public void mousePressed(MouseEvent evt)
610 if (evt.isPopupTrigger()) // Mac
612 showPasteMenu(evt.getX(), evt.getY());
617 public void mouseReleased(MouseEvent evt)
619 if (evt.isPopupTrigger()) // Windows
621 showPasteMenu(evt.getX(), evt.getY());
625 desktop.addMouseListener(ma);
629 // used for jalviewjsTest
630 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
635 * Answers true if user preferences to enable experimental features is True
640 public boolean showExperimental()
642 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
643 Boolean.FALSE.toString());
644 return Boolean.valueOf(experimental).booleanValue();
647 public void doConfigureStructurePrefs()
649 // configure services
650 StructureSelectionManager ssm = StructureSelectionManager
651 .getStructureSelectionManager(this);
652 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
654 ssm.setAddTempFacAnnot(
655 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
656 ssm.setProcessSecondaryStructure(
657 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
658 // JAL-3915 - RNAView is no longer an option so this has no effect
659 ssm.setSecStructServices(
660 Cache.getDefault(Preferences.USE_RNAVIEW, false));
664 ssm.setAddTempFacAnnot(false);
665 ssm.setProcessSecondaryStructure(false);
666 ssm.setSecStructServices(false);
670 public void checkForNews()
672 final Desktop me = this;
673 // Thread off the news reader, in case there are connection problems.
674 new Thread(new Runnable()
679 jalview.bin.Console.debug("Starting news thread.");
680 jvnews = new BlogReader(me);
681 showNews.setVisible(true);
682 jalview.bin.Console.debug("Completed news thread.");
687 public void getIdentifiersOrgData()
689 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
690 {// Thread off the identifiers fetcher
691 new Thread(new Runnable()
697 .debug("Downloading data from identifiers.org");
700 UrlDownloadClient.download(IdOrgSettings.getUrl(),
701 IdOrgSettings.getDownloadLocation());
702 } catch (IOException e)
705 .debug("Exception downloading identifiers.org data"
715 protected void showNews_actionPerformed(ActionEvent e)
717 showNews(showNews.isSelected());
720 void showNews(boolean visible)
722 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
723 showNews.setSelected(visible);
724 if (visible && !jvnews.isVisible())
726 new Thread(new Runnable()
731 long now = System.currentTimeMillis();
732 Desktop.instance.setProgressBar(
733 MessageManager.getString("status.refreshing_news"), now);
734 jvnews.refreshNews();
735 Desktop.instance.setProgressBar(null, now);
743 * recover the last known dimensions for a jalview window
746 * - empty string is desktop, all other windows have unique prefix
747 * @return null or last known dimensions scaled to current geometry (if last
748 * window geom was known)
750 Rectangle getLastKnownDimensions(String windowName)
752 // TODO: lock aspect ratio for scaling desktop Bug #0058199
753 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
754 String x = Cache.getProperty(windowName + "SCREEN_X");
755 String y = Cache.getProperty(windowName + "SCREEN_Y");
756 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
757 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
758 if ((x != null) && (y != null) && (width != null) && (height != null))
760 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
761 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
762 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
764 // attempt #1 - try to cope with change in screen geometry - this
765 // version doesn't preserve original jv aspect ratio.
766 // take ratio of current screen size vs original screen size.
767 double sw = ((1f * screenSize.width) / (1f * Integer
768 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
769 double sh = ((1f * screenSize.height) / (1f * Integer
770 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
771 // rescale the bounds depending upon the current screen geometry.
772 ix = (int) (ix * sw);
773 iw = (int) (iw * sw);
774 iy = (int) (iy * sh);
775 ih = (int) (ih * sh);
776 while (ix >= screenSize.width)
778 jalview.bin.Console.debug(
779 "Window geometry location recall error: shifting horizontal to within screenbounds.");
780 ix -= screenSize.width;
782 while (iy >= screenSize.height)
784 jalview.bin.Console.debug(
785 "Window geometry location recall error: shifting vertical to within screenbounds.");
786 iy -= screenSize.height;
788 jalview.bin.Console.debug(
789 "Got last known dimensions for " + windowName + ": x:" + ix
790 + " y:" + iy + " width:" + iw + " height:" + ih);
792 // return dimensions for new instance
793 return new Rectangle(ix, iy, iw, ih);
798 void showPasteMenu(int x, int y)
800 JPopupMenu popup = new JPopupMenu();
801 JMenuItem item = new JMenuItem(
802 MessageManager.getString("label.paste_new_window"));
803 item.addActionListener(new ActionListener()
806 public void actionPerformed(ActionEvent evt)
813 popup.show(this, x, y);
818 // quick patch for JAL-4150 - needs some more work and test coverage
819 // TODO - unify below and AlignFrame.paste()
820 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
821 // clipboard has come from a different alignment window than the one where
822 // paste has been called! JAL-4151
824 if (Desktop.jalviewClipboard != null)
826 // The clipboard was filled from within Jalview, we must use the
828 // And dataset from the copied alignment
829 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
830 // be doubly sure that we create *new* sequence objects.
831 SequenceI[] sequences = new SequenceI[newseq.length];
832 for (int i = 0; i < newseq.length; i++)
834 sequences[i] = new Sequence(newseq[i]);
836 Alignment alignment = new Alignment(sequences);
837 // dataset is inherited
838 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
839 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
840 AlignFrame.DEFAULT_HEIGHT);
841 String newtitle = new String("Copied sequences");
843 if (Desktop.jalviewClipboard[2] != null)
845 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
846 af.viewport.setHiddenColumns(hc);
849 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
850 AlignFrame.DEFAULT_HEIGHT);
857 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
858 Transferable contents = c.getContents(this);
860 if (contents != null)
862 String file = (String) contents
863 .getTransferData(DataFlavor.stringFlavor);
865 FileFormatI format = new IdentifyFile().identify(file,
866 DataSourceType.PASTE);
868 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
871 } catch (Exception ex)
874 "Unable to paste alignment from system clipboard:\n" + ex);
880 * Adds and opens the given frame to the desktop
891 public static synchronized void addInternalFrame(
892 final JInternalFrame frame, String title, int w, int h)
894 addInternalFrame(frame, title, true, w, h, true, false);
898 * Add an internal frame to the Jalview desktop
905 * When true, display frame immediately, otherwise, caller must call
906 * setVisible themselves.
912 public static synchronized void addInternalFrame(
913 final JInternalFrame frame, String title, boolean makeVisible,
916 addInternalFrame(frame, title, makeVisible, w, h, true, false);
920 * Add an internal frame to the Jalview desktop and make it visible
933 public static synchronized void addInternalFrame(
934 final JInternalFrame frame, String title, int w, int h,
937 addInternalFrame(frame, title, true, w, h, resizable, false);
941 * Add an internal frame to the Jalview desktop
948 * When true, display frame immediately, otherwise, caller must call
949 * setVisible themselves.
956 * @param ignoreMinSize
957 * Do not set the default minimum size for frame
959 public static synchronized void addInternalFrame(
960 final JInternalFrame frame, String title, boolean makeVisible,
961 int w, int h, boolean resizable, boolean ignoreMinSize)
964 // TODO: allow callers to determine X and Y position of frame (eg. via
966 // TODO: consider fixing method to update entries in the window submenu with
967 // the current window title
969 frame.setTitle(title);
970 if (frame.getWidth() < 1 || frame.getHeight() < 1)
974 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
975 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
976 // IF JALVIEW IS RUNNING HEADLESS
977 // ///////////////////////////////////////////////
978 if (instance == null || (System.getProperty("java.awt.headless") != null
979 && System.getProperty("java.awt.headless").equals("true")))
988 frame.setMinimumSize(
989 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
991 // Set default dimension for Alignment Frame window.
992 // The Alignment Frame window could be added from a number of places,
994 // I did this here in order not to miss out on any Alignment frame.
995 if (frame instanceof AlignFrame)
997 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
998 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1002 frame.setVisible(makeVisible);
1003 frame.setClosable(true);
1004 frame.setResizable(resizable);
1005 frame.setMaximizable(resizable);
1006 frame.setIconifiable(resizable);
1007 frame.setOpaque(Platform.isJS());
1009 if (frame.getX() < 1 && frame.getY() < 1)
1011 frame.setLocation(xOffset * openFrameCount,
1012 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1016 * add an entry for the new frame in the Window menu (and remove it when the
1019 final JMenuItem menuItem = new JMenuItem(title);
1020 frame.addInternalFrameListener(new InternalFrameAdapter()
1023 public void internalFrameActivated(InternalFrameEvent evt)
1025 JInternalFrame itf = desktop.getSelectedFrame();
1028 if (itf instanceof AlignFrame)
1030 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1037 public void internalFrameClosed(InternalFrameEvent evt)
1039 PaintRefresher.RemoveComponent(frame);
1042 * defensive check to prevent frames being added half off the window
1044 if (openFrameCount > 0)
1050 * ensure no reference to alignFrame retained by menu item listener
1052 if (menuItem.getActionListeners().length > 0)
1054 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1056 windowMenu.remove(menuItem);
1060 menuItem.addActionListener(new ActionListener()
1063 public void actionPerformed(ActionEvent e)
1067 frame.setSelected(true);
1068 frame.setIcon(false);
1069 } catch (java.beans.PropertyVetoException ex)
1076 setKeyBindings(frame);
1080 windowMenu.add(menuItem);
1085 frame.setSelected(true);
1086 frame.requestFocus();
1087 } catch (java.beans.PropertyVetoException ve)
1089 } catch (java.lang.ClassCastException cex)
1091 jalview.bin.Console.warn(
1092 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1098 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1103 private static void setKeyBindings(JInternalFrame frame)
1105 @SuppressWarnings("serial")
1106 final Action closeAction = new AbstractAction()
1109 public void actionPerformed(ActionEvent e)
1116 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1118 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1119 InputEvent.CTRL_DOWN_MASK);
1120 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1121 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1123 InputMap inputMap = frame
1124 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1125 String ctrlW = ctrlWKey.toString();
1126 inputMap.put(ctrlWKey, ctrlW);
1127 inputMap.put(cmdWKey, ctrlW);
1129 ActionMap actionMap = frame.getActionMap();
1130 actionMap.put(ctrlW, closeAction);
1134 public void lostOwnership(Clipboard clipboard, Transferable contents)
1138 Desktop.jalviewClipboard = null;
1141 internalCopy = false;
1145 public void dragEnter(DropTargetDragEvent evt)
1150 public void dragExit(DropTargetEvent evt)
1155 public void dragOver(DropTargetDragEvent evt)
1160 public void dropActionChanged(DropTargetDragEvent evt)
1171 public void drop(DropTargetDropEvent evt)
1173 boolean success = true;
1174 // JAL-1552 - acceptDrop required before getTransferable call for
1175 // Java's Transferable for native dnd
1176 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1177 Transferable t = evt.getTransferable();
1178 List<Object> files = new ArrayList<>();
1179 List<DataSourceType> protocols = new ArrayList<>();
1183 Desktop.transferFromDropTarget(files, protocols, evt, t);
1184 } catch (Exception e)
1186 e.printStackTrace();
1194 for (int i = 0; i < files.size(); i++)
1196 // BH 2018 File or String
1197 Object file = files.get(i);
1198 String fileName = file.toString();
1199 DataSourceType protocol = (protocols == null)
1200 ? DataSourceType.FILE
1202 FileFormatI format = null;
1204 if (fileName.endsWith(".jar"))
1206 format = FileFormat.Jalview;
1211 format = new IdentifyFile().identify(file, protocol);
1213 if (file instanceof File)
1215 Platform.cacheFileData((File) file);
1217 new FileLoader().LoadFile(null, file, protocol, format);
1220 } catch (Exception ex)
1225 evt.dropComplete(success); // need this to ensure input focus is properly
1226 // transfered to any new windows created
1236 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1238 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1239 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1240 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1241 BackupFiles.getEnabled());
1243 chooser.setFileView(new JalviewFileView());
1244 chooser.setDialogTitle(
1245 MessageManager.getString("label.open_local_file"));
1246 chooser.setToolTipText(MessageManager.getString("action.open"));
1248 chooser.setResponseHandler(0, () -> {
1249 File selectedFile = chooser.getSelectedFile();
1250 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1252 FileFormatI format = chooser.getSelectedFormat();
1255 * Call IdentifyFile to verify the file contains what its extension implies.
1256 * Skip this step for dynamically added file formats, because IdentifyFile does
1257 * not know how to recognise them.
1259 if (FileFormats.getInstance().isIdentifiable(format))
1263 format = new IdentifyFile().identify(selectedFile,
1264 DataSourceType.FILE);
1265 } catch (FileFormatException e)
1267 // format = null; //??
1271 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1274 chooser.showOpenDialog(this);
1278 * Shows a dialog for input of a URL at which to retrieve alignment data
1283 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1285 // This construct allows us to have a wider textfield
1287 JLabel label = new JLabel(
1288 MessageManager.getString("label.input_file_url"));
1290 JPanel panel = new JPanel(new GridLayout(2, 1));
1294 * the URL to fetch is input in Java: an editable combobox with history JS:
1295 * (pending JAL-3038) a plain text field
1298 String urlBase = "https://www.";
1299 if (Platform.isJS())
1301 history = new JTextField(urlBase, 35);
1310 JComboBox<String> asCombo = new JComboBox<>();
1311 asCombo.setPreferredSize(new Dimension(400, 20));
1312 asCombo.setEditable(true);
1313 asCombo.addItem(urlBase);
1314 String historyItems = Cache.getProperty("RECENT_URL");
1315 if (historyItems != null)
1317 for (String token : historyItems.split("\\t"))
1319 asCombo.addItem(token);
1326 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1327 MessageManager.getString("action.cancel") };
1328 Runnable action = () -> {
1329 @SuppressWarnings("unchecked")
1330 String url = (history instanceof JTextField
1331 ? ((JTextField) history).getText()
1332 : ((JComboBox<String>) history).getEditor().getItem()
1333 .toString().trim());
1335 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1337 if (viewport != null)
1339 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1340 FileFormat.Jalview);
1344 new FileLoader().LoadFile(url, DataSourceType.URL,
1345 FileFormat.Jalview);
1350 FileFormatI format = null;
1353 format = new IdentifyFile().identify(url, DataSourceType.URL);
1354 } catch (FileFormatException e)
1356 // TODO revise error handling, distinguish between
1357 // URL not found and response not valid
1362 String msg = MessageManager.formatMessage("label.couldnt_locate",
1364 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1365 MessageManager.getString("label.url_not_found"),
1366 JvOptionPane.WARNING_MESSAGE);
1370 if (viewport != null)
1372 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1377 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1381 String dialogOption = MessageManager
1382 .getString("label.input_alignment_from_url");
1383 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1384 .showInternalDialog(panel, dialogOption,
1385 JvOptionPane.YES_NO_CANCEL_OPTION,
1386 JvOptionPane.PLAIN_MESSAGE, null, options,
1387 MessageManager.getString("action.ok"));
1391 * Opens the CutAndPaste window for the user to paste an alignment in to
1394 * - if not null, the pasted alignment is added to the current
1395 * alignment; if null, to a new alignment window
1398 public void inputTextboxMenuItem_actionPerformed(
1399 AlignmentViewPanel viewPanel)
1401 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1402 cap.setForInput(viewPanel);
1403 Desktop.addInternalFrame(cap,
1404 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1409 * Check with user and saving files before actually quitting
1411 public void desktopQuit()
1413 desktopQuit(true, false);
1416 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1418 final Runnable doDesktopQuit = () -> {
1419 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1420 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1421 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1422 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1423 getBounds().y, getWidth(), getHeight()));
1425 if (jconsole != null)
1427 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1428 jconsole.stopConsole();
1433 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1436 // Frames should all close automatically. Keeping external
1437 // viewers open should already be decided by user.
1438 closeAll_actionPerformed(null);
1440 // check for aborted quit
1441 if (QuitHandler.quitCancelled())
1443 jalview.bin.Console.debug("Desktop aborting quit");
1447 if (dialogExecutor != null)
1449 dialogExecutor.shutdownNow();
1452 if (groovyConsole != null)
1454 // suppress a possible repeat prompt to save script
1455 groovyConsole.setDirty(false);
1456 groovyConsole.exit();
1459 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1461 // note that shutdown hook will not be run
1462 jalview.bin.Console.debug("Force Quit selected by user");
1463 Runtime.getRuntime().halt(0);
1466 jalview.bin.Console.debug("Quit selected by user");
1469 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1470 // instance.dispose();
1475 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1476 QuitHandler.defaultCancelQuit);
1480 * Don't call this directly, use desktopQuit() above. Exits the program.
1485 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1486 // not run a second time if gotQuitResponse flag has been set (i.e. user
1487 // confirmed quit of some kind).
1488 Jalview.exit("Desktop exiting.", 0);
1491 private void storeLastKnownDimensions(String string, Rectangle jc)
1493 jalview.bin.Console.debug("Storing last known dimensions for " + string
1494 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1495 + " height:" + jc.height);
1497 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1498 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1499 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1500 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1510 public void aboutMenuItem_actionPerformed(ActionEvent e)
1512 new Thread(new Runnable()
1517 new SplashScreen(false);
1523 * Returns the html text for the About screen, including any available version
1524 * number, build details, author details and citation reference, but without
1525 * the enclosing {@code html} tags
1529 public String getAboutMessage()
1531 StringBuilder message = new StringBuilder(1024);
1532 message.append("<div style=\"font-family: sans-serif;\">")
1533 .append("<h1><strong>Version: ")
1534 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1535 .append("<strong>Built: <em>")
1536 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1537 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1538 .append("</strong>");
1540 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1541 if (latestVersion.equals("Checking"))
1543 // JBP removed this message for 2.11: May be reinstated in future version
1544 // message.append("<br>...Checking latest version...</br>");
1546 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1548 boolean red = false;
1549 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1550 .indexOf("automated build") == -1)
1553 // Displayed when code version and jnlp version do not match and code
1554 // version is not a development build
1555 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1558 message.append("<br>!! Version ")
1559 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1560 .append(" is available for download from ")
1561 .append(Cache.getDefault("www.jalview.org",
1562 "https://www.jalview.org"))
1566 message.append("</div>");
1569 message.append("<br>Authors: ");
1570 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1571 message.append(CITATION);
1573 message.append("</div>");
1575 return message.toString();
1579 * Action on requesting Help documentation
1582 public void documentationMenuItem_actionPerformed()
1586 if (Platform.isJS())
1588 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1597 Help.showHelpWindow();
1599 } catch (Exception ex)
1601 System.err.println("Error opening help: " + ex.getMessage());
1606 public void closeAll_actionPerformed(ActionEvent e)
1608 // TODO show a progress bar while closing?
1609 JInternalFrame[] frames = desktop.getAllFrames();
1610 for (int i = 0; i < frames.length; i++)
1614 frames[i].setClosed(true);
1615 } catch (java.beans.PropertyVetoException ex)
1619 Jalview.setCurrentAlignFrame(null);
1620 jalview.bin.Console.info("ALL CLOSED");
1623 * reset state of singleton objects as appropriate (clear down session state
1624 * when all windows are closed)
1626 StructureSelectionManager ssm = StructureSelectionManager
1627 .getStructureSelectionManager(this);
1634 public int structureViewersStillRunningCount()
1637 JInternalFrame[] frames = desktop.getAllFrames();
1638 for (int i = 0; i < frames.length; i++)
1640 if (frames[i] != null
1641 && frames[i] instanceof JalviewStructureDisplayI)
1643 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1651 public void raiseRelated_actionPerformed(ActionEvent e)
1653 reorderAssociatedWindows(false, false);
1657 public void minimizeAssociated_actionPerformed(ActionEvent e)
1659 reorderAssociatedWindows(true, false);
1662 void closeAssociatedWindows()
1664 reorderAssociatedWindows(false, true);
1670 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1674 protected void garbageCollect_actionPerformed(ActionEvent e)
1676 // We simply collect the garbage
1677 jalview.bin.Console.debug("Collecting garbage...");
1679 jalview.bin.Console.debug("Finished garbage collection.");
1685 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1689 protected void showMemusage_actionPerformed(ActionEvent e)
1691 desktop.showMemoryUsage(showMemusage.isSelected());
1698 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1702 protected void showConsole_actionPerformed(ActionEvent e)
1704 showConsole(showConsole.isSelected());
1707 Console jconsole = null;
1710 * control whether the java console is visible or not
1714 void showConsole(boolean selected)
1716 // TODO: decide if we should update properties file
1717 if (jconsole != null) // BH 2018
1719 showConsole.setSelected(selected);
1720 Cache.setProperty("SHOW_JAVA_CONSOLE",
1721 Boolean.valueOf(selected).toString());
1722 jconsole.setVisible(selected);
1726 void reorderAssociatedWindows(boolean minimize, boolean close)
1728 JInternalFrame[] frames = desktop.getAllFrames();
1729 if (frames == null || frames.length < 1)
1734 AlignmentViewport source = null, target = null;
1735 if (frames[0] instanceof AlignFrame)
1737 source = ((AlignFrame) frames[0]).getCurrentView();
1739 else if (frames[0] instanceof TreePanel)
1741 source = ((TreePanel) frames[0]).getViewPort();
1743 else if (frames[0] instanceof PCAPanel)
1745 source = ((PCAPanel) frames[0]).av;
1747 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1749 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1754 for (int i = 0; i < frames.length; i++)
1757 if (frames[i] == null)
1761 if (frames[i] instanceof AlignFrame)
1763 target = ((AlignFrame) frames[i]).getCurrentView();
1765 else if (frames[i] instanceof TreePanel)
1767 target = ((TreePanel) frames[i]).getViewPort();
1769 else if (frames[i] instanceof PCAPanel)
1771 target = ((PCAPanel) frames[i]).av;
1773 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1775 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1778 if (source == target)
1784 frames[i].setClosed(true);
1788 frames[i].setIcon(minimize);
1791 frames[i].toFront();
1795 } catch (java.beans.PropertyVetoException ex)
1810 protected void preferences_actionPerformed(ActionEvent e)
1812 Preferences.openPreferences();
1816 * Prompts the user to choose a file and then saves the Jalview state as a
1817 * Jalview project file
1820 public void saveState_actionPerformed()
1822 saveState_actionPerformed(false);
1825 public void saveState_actionPerformed(boolean saveAs)
1827 java.io.File projectFile = getProjectFile();
1828 // autoSave indicates we already have a file and don't need to ask
1829 boolean autoSave = projectFile != null && !saveAs
1830 && BackupFiles.getEnabled();
1832 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1833 // saveAs="+saveAs+", Backups
1834 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1836 boolean approveSave = false;
1839 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1842 chooser.setFileView(new JalviewFileView());
1843 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1845 int value = chooser.showSaveDialog(this);
1847 if (value == JalviewFileChooser.APPROVE_OPTION)
1849 projectFile = chooser.getSelectedFile();
1850 setProjectFile(projectFile);
1855 if (approveSave || autoSave)
1857 final Desktop me = this;
1858 final java.io.File chosenFile = projectFile;
1859 new Thread(new Runnable()
1864 // TODO: refactor to Jalview desktop session controller action.
1865 setProgressBar(MessageManager.formatMessage(
1866 "label.saving_jalview_project", new Object[]
1867 { chosenFile.getName() }), chosenFile.hashCode());
1868 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1869 // TODO catch and handle errors for savestate
1870 // TODO prevent user from messing with the Desktop whilst we're saving
1873 boolean doBackup = BackupFiles.getEnabled();
1874 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1877 new Jalview2XML().saveState(
1878 doBackup ? backupfiles.getTempFile() : chosenFile);
1882 backupfiles.setWriteSuccess(true);
1883 backupfiles.rollBackupsAndRenameTempFile();
1885 } catch (OutOfMemoryError oom)
1887 new OOMWarning("Whilst saving current state to "
1888 + chosenFile.getName(), oom);
1889 } catch (Exception ex)
1891 jalview.bin.Console.error("Problems whilst trying to save to "
1892 + chosenFile.getName(), ex);
1893 JvOptionPane.showMessageDialog(me,
1894 MessageManager.formatMessage(
1895 "label.error_whilst_saving_current_state_to",
1897 { chosenFile.getName() }),
1898 MessageManager.getString("label.couldnt_save_project"),
1899 JvOptionPane.WARNING_MESSAGE);
1901 setProgressBar(null, chosenFile.hashCode());
1908 public void saveAsState_actionPerformed(ActionEvent e)
1910 saveState_actionPerformed(true);
1913 protected void setProjectFile(File choice)
1915 this.projectFile = choice;
1918 public File getProjectFile()
1920 return this.projectFile;
1924 * Shows a file chooser dialog and tries to read in the selected file as a
1928 public void loadState_actionPerformed()
1930 final String[] suffix = new String[] { "jvp", "jar" };
1931 final String[] desc = new String[] { "Jalview Project",
1932 "Jalview Project (old)" };
1933 JalviewFileChooser chooser = new JalviewFileChooser(
1934 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1935 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1939 chooser.setFileView(new JalviewFileView());
1940 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1941 chooser.setResponseHandler(0, () -> {
1942 File selectedFile = chooser.getSelectedFile();
1943 setProjectFile(selectedFile);
1944 String choice = selectedFile.getAbsolutePath();
1945 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1946 new Thread(new Runnable()
1953 new Jalview2XML().loadJalviewAlign(selectedFile);
1954 } catch (OutOfMemoryError oom)
1956 new OOMWarning("Whilst loading project from " + choice, oom);
1957 } catch (Exception ex)
1959 jalview.bin.Console.error(
1960 "Problems whilst loading project from " + choice, ex);
1961 JvOptionPane.showMessageDialog(Desktop.desktop,
1962 MessageManager.formatMessage(
1963 "label.error_whilst_loading_project_from",
1966 MessageManager.getString("label.couldnt_load_project"),
1967 JvOptionPane.WARNING_MESSAGE);
1970 }, "Project Loader").start();
1973 chooser.showOpenDialog(this);
1977 public void inputSequence_actionPerformed(ActionEvent e)
1979 new SequenceFetcher(this);
1982 JPanel progressPanel;
1984 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1986 public void startLoading(final Object fileName)
1988 if (fileLoadingCount == 0)
1990 fileLoadingPanels.add(addProgressPanel(MessageManager
1991 .formatMessage("label.loading_file", new Object[]
1997 private JPanel addProgressPanel(String string)
1999 if (progressPanel == null)
2001 progressPanel = new JPanel(new GridLayout(1, 1));
2002 totalProgressCount = 0;
2003 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2005 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2006 JProgressBar progressBar = new JProgressBar();
2007 progressBar.setIndeterminate(true);
2009 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2011 thisprogress.add(progressBar, BorderLayout.CENTER);
2012 progressPanel.add(thisprogress);
2013 ((GridLayout) progressPanel.getLayout()).setRows(
2014 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2015 ++totalProgressCount;
2016 instance.validate();
2017 return thisprogress;
2020 int totalProgressCount = 0;
2022 private void removeProgressPanel(JPanel progbar)
2024 if (progressPanel != null)
2026 synchronized (progressPanel)
2028 progressPanel.remove(progbar);
2029 GridLayout gl = (GridLayout) progressPanel.getLayout();
2030 gl.setRows(gl.getRows() - 1);
2031 if (--totalProgressCount < 1)
2033 this.getContentPane().remove(progressPanel);
2034 progressPanel = null;
2041 public void stopLoading()
2044 if (fileLoadingCount < 1)
2046 while (fileLoadingPanels.size() > 0)
2048 removeProgressPanel(fileLoadingPanels.remove(0));
2050 fileLoadingPanels.clear();
2051 fileLoadingCount = 0;
2056 public static int getViewCount(String alignmentId)
2058 AlignmentViewport[] aps = getViewports(alignmentId);
2059 return (aps == null) ? 0 : aps.length;
2064 * @param alignmentId
2065 * - if null, all sets are returned
2066 * @return all AlignmentPanels concerning the alignmentId sequence set
2068 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2070 if (Desktop.desktop == null)
2072 // no frames created and in headless mode
2073 // TODO: verify that frames are recoverable when in headless mode
2076 List<AlignmentPanel> aps = new ArrayList<>();
2077 AlignFrame[] frames = getAlignFrames();
2082 for (AlignFrame af : frames)
2084 for (AlignmentPanel ap : af.alignPanels)
2086 if (alignmentId == null
2087 || alignmentId.equals(ap.av.getSequenceSetId()))
2093 if (aps.size() == 0)
2097 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2102 * get all the viewports on an alignment.
2104 * @param sequenceSetId
2105 * unique alignment id (may be null - all viewports returned in that
2107 * @return all viewports on the alignment bound to sequenceSetId
2109 public static AlignmentViewport[] getViewports(String sequenceSetId)
2111 List<AlignmentViewport> viewp = new ArrayList<>();
2112 if (desktop != null)
2114 AlignFrame[] frames = Desktop.getAlignFrames();
2116 for (AlignFrame afr : frames)
2118 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2119 .equals(sequenceSetId))
2121 if (afr.alignPanels != null)
2123 for (AlignmentPanel ap : afr.alignPanels)
2125 if (sequenceSetId == null
2126 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2134 viewp.add(afr.getViewport());
2138 if (viewp.size() > 0)
2140 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2147 * Explode the views in the given frame into separate AlignFrame
2151 public static void explodeViews(AlignFrame af)
2153 int size = af.alignPanels.size();
2159 // FIXME: ideally should use UI interface API
2160 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2161 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2162 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2163 for (int i = 0; i < size; i++)
2165 AlignmentPanel ap = af.alignPanels.get(i);
2167 AlignFrame newaf = new AlignFrame(ap);
2169 // transfer reference for existing feature settings to new alignFrame
2170 if (ap == af.alignPanel)
2172 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2174 newaf.featureSettings = viewFeatureSettings;
2176 newaf.setFeatureSettingsGeometry(fsBounds);
2180 * Restore the view's last exploded frame geometry if known. Multiple views from
2181 * one exploded frame share and restore the same (frame) position and size.
2183 Rectangle geometry = ap.av.getExplodedGeometry();
2184 if (geometry != null)
2186 newaf.setBounds(geometry);
2189 ap.av.setGatherViewsHere(false);
2191 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2192 AlignFrame.DEFAULT_HEIGHT);
2193 // and materialise a new feature settings dialog instance for the new
2195 // (closes the old as if 'OK' was pressed)
2196 if (ap == af.alignPanel && newaf.featureSettings != null
2197 && newaf.featureSettings.isOpen()
2198 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2200 newaf.showFeatureSettingsUI();
2204 af.featureSettings = null;
2205 af.alignPanels.clear();
2206 af.closeMenuItem_actionPerformed(true);
2211 * Gather expanded views (separate AlignFrame's) with the same sequence set
2212 * identifier back in to this frame as additional views, and close the
2213 * expanded views. Note the expanded frames may themselves have multiple
2214 * views. We take the lot.
2218 public void gatherViews(AlignFrame source)
2220 source.viewport.setGatherViewsHere(true);
2221 source.viewport.setExplodedGeometry(source.getBounds());
2222 JInternalFrame[] frames = desktop.getAllFrames();
2223 String viewId = source.viewport.getSequenceSetId();
2224 for (int t = 0; t < frames.length; t++)
2226 if (frames[t] instanceof AlignFrame && frames[t] != source)
2228 AlignFrame af = (AlignFrame) frames[t];
2229 boolean gatherThis = false;
2230 for (int a = 0; a < af.alignPanels.size(); a++)
2232 AlignmentPanel ap = af.alignPanels.get(a);
2233 if (viewId.equals(ap.av.getSequenceSetId()))
2236 ap.av.setGatherViewsHere(false);
2237 ap.av.setExplodedGeometry(af.getBounds());
2238 source.addAlignmentPanel(ap, false);
2244 if (af.featureSettings != null && af.featureSettings.isOpen())
2246 if (source.featureSettings == null)
2248 // preserve the feature settings geometry for this frame
2249 source.featureSettings = af.featureSettings;
2250 source.setFeatureSettingsGeometry(
2251 af.getFeatureSettingsGeometry());
2255 // close it and forget
2256 af.featureSettings.close();
2259 af.alignPanels.clear();
2260 af.closeMenuItem_actionPerformed(true);
2265 // refresh the feature setting UI for the source frame if it exists
2266 if (source.featureSettings != null && source.featureSettings.isOpen())
2268 source.showFeatureSettingsUI();
2273 public JInternalFrame[] getAllFrames()
2275 return desktop.getAllFrames();
2279 * Checks the given url to see if it gives a response indicating that the user
2280 * should be informed of a new questionnaire.
2284 public void checkForQuestionnaire(String url)
2286 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2287 // javax.swing.SwingUtilities.invokeLater(jvq);
2288 new Thread(jvq).start();
2291 public void checkURLLinks()
2293 // Thread off the URL link checker
2294 addDialogThread(new Runnable()
2299 if (Cache.getDefault("CHECKURLLINKS", true))
2301 // check what the actual links are - if it's just the default don't
2302 // bother with the warning
2303 List<String> links = Preferences.sequenceUrlLinks
2306 // only need to check links if there is one with a
2307 // SEQUENCE_ID which is not the default EMBL_EBI link
2308 ListIterator<String> li = links.listIterator();
2309 boolean check = false;
2310 List<JLabel> urls = new ArrayList<>();
2311 while (li.hasNext())
2313 String link = li.next();
2314 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2315 && !UrlConstants.isDefaultString(link))
2318 int barPos = link.indexOf("|");
2319 String urlMsg = barPos == -1 ? link
2320 : link.substring(0, barPos) + ": "
2321 + link.substring(barPos + 1);
2322 urls.add(new JLabel(urlMsg));
2330 // ask user to check in case URL links use old style tokens
2331 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2332 JPanel msgPanel = new JPanel();
2333 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2334 msgPanel.add(Box.createVerticalGlue());
2335 JLabel msg = new JLabel(MessageManager
2336 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2337 JLabel msg2 = new JLabel(MessageManager
2338 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2340 for (JLabel url : urls)
2346 final JCheckBox jcb = new JCheckBox(
2347 MessageManager.getString("label.do_not_display_again"));
2348 jcb.addActionListener(new ActionListener()
2351 public void actionPerformed(ActionEvent e)
2353 // update Cache settings for "don't show this again"
2354 boolean showWarningAgain = !jcb.isSelected();
2355 Cache.setProperty("CHECKURLLINKS",
2356 Boolean.valueOf(showWarningAgain).toString());
2361 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2363 .getString("label.SEQUENCE_ID_no_longer_used"),
2364 JvOptionPane.WARNING_MESSAGE);
2371 * Proxy class for JDesktopPane which optionally displays the current memory
2372 * usage and highlights the desktop area with a red bar if free memory runs
2377 public class MyDesktopPane extends JDesktopPane implements Runnable
2379 private static final float ONE_MB = 1048576f;
2381 boolean showMemoryUsage = false;
2385 java.text.NumberFormat df;
2387 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2390 public MyDesktopPane(boolean showMemoryUsage)
2392 showMemoryUsage(showMemoryUsage);
2395 public void showMemoryUsage(boolean showMemory)
2397 this.showMemoryUsage = showMemory;
2400 Thread worker = new Thread(this);
2406 public boolean isShowMemoryUsage()
2408 return showMemoryUsage;
2414 df = java.text.NumberFormat.getNumberInstance();
2415 df.setMaximumFractionDigits(2);
2416 runtime = Runtime.getRuntime();
2418 while (showMemoryUsage)
2422 maxMemory = runtime.maxMemory() / ONE_MB;
2423 allocatedMemory = runtime.totalMemory() / ONE_MB;
2424 freeMemory = runtime.freeMemory() / ONE_MB;
2425 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2427 percentUsage = (totalFreeMemory / maxMemory) * 100;
2429 // if (percentUsage < 20)
2431 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2433 // instance.set.setBorder(border1);
2436 // sleep after showing usage
2438 } catch (Exception ex)
2440 ex.printStackTrace();
2446 public void paintComponent(Graphics g)
2448 if (showMemoryUsage && g != null && df != null)
2450 if (percentUsage < 20)
2452 g.setColor(Color.red);
2454 FontMetrics fm = g.getFontMetrics();
2457 g.drawString(MessageManager.formatMessage("label.memory_stats",
2459 { df.format(totalFreeMemory), df.format(maxMemory),
2460 df.format(percentUsage) }),
2461 10, getHeight() - fm.getHeight());
2465 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2466 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2471 * Accessor method to quickly get all the AlignmentFrames loaded.
2473 * @return an array of AlignFrame, or null if none found
2475 public static AlignFrame[] getAlignFrames()
2477 if (Jalview.isHeadlessMode())
2479 // Desktop.desktop is null in headless mode
2480 return new AlignFrame[] { Jalview.currentAlignFrame };
2483 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2489 List<AlignFrame> avp = new ArrayList<>();
2491 for (int i = frames.length - 1; i > -1; i--)
2493 if (frames[i] instanceof AlignFrame)
2495 avp.add((AlignFrame) frames[i]);
2497 else if (frames[i] instanceof SplitFrame)
2500 * Also check for a split frame containing an AlignFrame
2502 GSplitFrame sf = (GSplitFrame) frames[i];
2503 if (sf.getTopFrame() instanceof AlignFrame)
2505 avp.add((AlignFrame) sf.getTopFrame());
2507 if (sf.getBottomFrame() instanceof AlignFrame)
2509 avp.add((AlignFrame) sf.getBottomFrame());
2513 if (avp.size() == 0)
2517 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2522 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2526 public GStructureViewer[] getJmols()
2528 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2534 List<GStructureViewer> avp = new ArrayList<>();
2536 for (int i = frames.length - 1; i > -1; i--)
2538 if (frames[i] instanceof AppJmol)
2540 GStructureViewer af = (GStructureViewer) frames[i];
2544 if (avp.size() == 0)
2548 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2553 * Add Groovy Support to Jalview
2556 public void groovyShell_actionPerformed()
2560 openGroovyConsole();
2561 } catch (Exception ex)
2563 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2564 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2566 MessageManager.getString("label.couldnt_create_groovy_shell"),
2567 MessageManager.getString("label.groovy_support_failed"),
2568 JvOptionPane.ERROR_MESSAGE);
2573 * Open the Groovy console
2575 void openGroovyConsole()
2577 if (groovyConsole == null)
2579 groovyConsole = new groovy.ui.Console();
2580 groovyConsole.setVariable("Jalview", this);
2581 groovyConsole.run();
2584 * We allow only one console at a time, so that AlignFrame menu option
2585 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2586 * enable 'Run script', when the console is opened, and the reverse when it is
2589 Window window = (Window) groovyConsole.getFrame();
2590 window.addWindowListener(new WindowAdapter()
2593 public void windowClosed(WindowEvent e)
2596 * rebind CMD-Q from Groovy Console to Jalview Quit
2599 enableExecuteGroovy(false);
2605 * show Groovy console window (after close and reopen)
2607 ((Window) groovyConsole.getFrame()).setVisible(true);
2610 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2611 * opening a second console
2613 enableExecuteGroovy(true);
2617 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2618 * binding when opened
2620 protected void addQuitHandler()
2623 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2625 .getKeyStroke(KeyEvent.VK_Q,
2626 jalview.util.ShortcutKeyMaskExWrapper
2627 .getMenuShortcutKeyMaskEx()),
2629 getRootPane().getActionMap().put("Quit", new AbstractAction()
2632 public void actionPerformed(ActionEvent e)
2640 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2643 * true if Groovy console is open
2645 public void enableExecuteGroovy(boolean enabled)
2648 * disable opening a second Groovy console (or re-enable when the console is
2651 groovyShell.setEnabled(!enabled);
2653 AlignFrame[] alignFrames = getAlignFrames();
2654 if (alignFrames != null)
2656 for (AlignFrame af : alignFrames)
2658 af.setGroovyEnabled(enabled);
2664 * Progress bars managed by the IProgressIndicator method.
2666 private Hashtable<Long, JPanel> progressBars;
2668 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2673 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2676 public void setProgressBar(String message, long id)
2678 if (progressBars == null)
2680 progressBars = new Hashtable<>();
2681 progressBarHandlers = new Hashtable<>();
2684 if (progressBars.get(Long.valueOf(id)) != null)
2686 JPanel panel = progressBars.remove(Long.valueOf(id));
2687 if (progressBarHandlers.contains(Long.valueOf(id)))
2689 progressBarHandlers.remove(Long.valueOf(id));
2691 removeProgressPanel(panel);
2695 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2702 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2703 * jalview.gui.IProgressIndicatorHandler)
2706 public void registerHandler(final long id,
2707 final IProgressIndicatorHandler handler)
2709 if (progressBarHandlers == null
2710 || !progressBars.containsKey(Long.valueOf(id)))
2712 throw new Error(MessageManager.getString(
2713 "error.call_setprogressbar_before_registering_handler"));
2715 progressBarHandlers.put(Long.valueOf(id), handler);
2716 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2717 if (handler.canCancel())
2719 JButton cancel = new JButton(
2720 MessageManager.getString("action.cancel"));
2721 final IProgressIndicator us = this;
2722 cancel.addActionListener(new ActionListener()
2726 public void actionPerformed(ActionEvent e)
2728 handler.cancelActivity(id);
2729 us.setProgressBar(MessageManager
2730 .formatMessage("label.cancelled_params", new Object[]
2731 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2735 progressPanel.add(cancel, BorderLayout.EAST);
2741 * @return true if any progress bars are still active
2744 public boolean operationInProgress()
2746 if (progressBars != null && progressBars.size() > 0)
2754 * This will return the first AlignFrame holding the given viewport instance.
2755 * It will break if there are more than one AlignFrames viewing a particular
2759 * @return alignFrame for viewport
2761 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2763 if (desktop != null)
2765 AlignmentPanel[] aps = getAlignmentPanels(
2766 viewport.getSequenceSetId());
2767 for (int panel = 0; aps != null && panel < aps.length; panel++)
2769 if (aps[panel] != null && aps[panel].av == viewport)
2771 return aps[panel].alignFrame;
2778 public VamsasApplication getVamsasApplication()
2780 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2786 * flag set if jalview GUI is being operated programmatically
2788 private boolean inBatchMode = false;
2791 * check if jalview GUI is being operated programmatically
2793 * @return inBatchMode
2795 public boolean isInBatchMode()
2801 * set flag if jalview GUI is being operated programmatically
2803 * @param inBatchMode
2805 public void setInBatchMode(boolean inBatchMode)
2807 this.inBatchMode = inBatchMode;
2811 * start service discovery and wait till it is done
2813 public void startServiceDiscovery()
2815 startServiceDiscovery(false);
2819 * start service discovery threads - blocking or non-blocking
2823 public void startServiceDiscovery(boolean blocking)
2825 startServiceDiscovery(blocking, false);
2829 * start service discovery threads
2832 * - false means call returns immediately
2833 * @param ignore_SHOW_JWS2_SERVICES_preference
2834 * - when true JABA services are discovered regardless of user's JWS2
2835 * discovery preference setting
2837 public void startServiceDiscovery(boolean blocking,
2838 boolean ignore_SHOW_JWS2_SERVICES_preference)
2840 boolean alive = true;
2841 Thread t0 = null, t1 = null, t2 = null;
2842 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2845 // todo: changesupport handlers need to be transferred
2846 if (discoverer == null)
2848 discoverer = new jalview.ws.jws1.Discoverer();
2849 // register PCS handler for desktop.
2850 discoverer.addPropertyChangeListener(changeSupport);
2852 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2853 // until we phase out completely
2854 (t0 = new Thread(discoverer)).start();
2857 if (ignore_SHOW_JWS2_SERVICES_preference
2858 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2860 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2861 .startDiscoverer(changeSupport);
2865 // TODO: do rest service discovery
2874 } catch (Exception e)
2877 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2878 || (t3 != null && t3.isAlive())
2879 || (t0 != null && t0.isAlive());
2885 * called to check if the service discovery process completed successfully.
2889 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2891 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2893 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2894 .getErrorMessages();
2897 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2899 if (serviceChangedDialog == null)
2901 // only run if we aren't already displaying one of these.
2902 addDialogThread(serviceChangedDialog = new Runnable()
2909 * JalviewDialog jd =new JalviewDialog() {
2911 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2913 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2915 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2917 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2919 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2920 * + " or mis-configured HTTP proxy settings.<br/>" +
2921 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2922 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2923 * true, true, "Web Service Configuration Problem", 450, 400);
2925 * jd.waitForInput();
2927 JvOptionPane.showConfirmDialog(Desktop.desktop,
2928 new JLabel("<html><table width=\"450\"><tr><td>"
2929 + ermsg + "</td></tr></table>"
2930 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2931 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2932 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2933 + " Tools->Preferences dialog box to change them.</p></html>"),
2934 "Web Service Configuration Problem",
2935 JvOptionPane.DEFAULT_OPTION,
2936 JvOptionPane.ERROR_MESSAGE);
2937 serviceChangedDialog = null;
2945 jalview.bin.Console.error(
2946 "Errors reported by JABA discovery service. Check web services preferences.\n"
2953 private Runnable serviceChangedDialog = null;
2956 * start a thread to open a URL in the configured browser. Pops up a warning
2957 * dialog to the user if there is an exception when calling out to the browser
2962 public static void showUrl(final String url)
2964 showUrl(url, Desktop.instance);
2968 * Like showUrl but allows progress handler to be specified
2972 * (null) or object implementing IProgressIndicator
2974 public static void showUrl(final String url,
2975 final IProgressIndicator progress)
2977 new Thread(new Runnable()
2984 if (progress != null)
2986 progress.setProgressBar(MessageManager
2987 .formatMessage("status.opening_params", new Object[]
2988 { url }), this.hashCode());
2990 jalview.util.BrowserLauncher.openURL(url);
2991 } catch (Exception ex)
2993 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2995 .getString("label.web_browser_not_found_unix"),
2996 MessageManager.getString("label.web_browser_not_found"),
2997 JvOptionPane.WARNING_MESSAGE);
2999 ex.printStackTrace();
3001 if (progress != null)
3003 progress.setProgressBar(null, this.hashCode());
3009 public static WsParamSetManager wsparamManager = null;
3011 public static ParamManager getUserParameterStore()
3013 if (wsparamManager == null)
3015 wsparamManager = new WsParamSetManager();
3017 return wsparamManager;
3021 * static hyperlink handler proxy method for use by Jalview's internal windows
3025 public static void hyperlinkUpdate(HyperlinkEvent e)
3027 if (e.getEventType() == EventType.ACTIVATED)
3032 url = e.getURL().toString();
3033 Desktop.showUrl(url);
3034 } catch (Exception x)
3039 .error("Couldn't handle string " + url + " as a URL.");
3041 // ignore any exceptions due to dud links.
3048 * single thread that handles display of dialogs to user.
3050 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3053 * flag indicating if dialogExecutor should try to acquire a permit
3055 private volatile boolean dialogPause = true;
3060 private java.util.concurrent.Semaphore block = new Semaphore(0);
3062 private static groovy.ui.Console groovyConsole;
3065 * add another dialog thread to the queue
3069 public void addDialogThread(final Runnable prompter)
3071 dialogExecutor.submit(new Runnable()
3081 } catch (InterruptedException x)
3085 if (instance == null)
3091 SwingUtilities.invokeAndWait(prompter);
3092 } catch (Exception q)
3094 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3101 public void startDialogQueue()
3103 // set the flag so we don't pause waiting for another permit and semaphore
3104 // the current task to begin
3105 dialogPause = false;
3110 * Outputs an image of the desktop to file in EPS format, after prompting the
3111 * user for choice of Text or Lineart character rendering (unless a preference
3112 * has been set). The file name is generated as
3115 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3119 protected void snapShotWindow_actionPerformed(ActionEvent e)
3121 // currently the menu option to do this is not shown
3124 int width = getWidth();
3125 int height = getHeight();
3127 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3128 ImageWriterI writer = new ImageWriterI()
3131 public void exportImage(Graphics g) throws Exception
3134 jalview.bin.Console.info("Successfully written snapshot to file "
3135 + of.getAbsolutePath());
3138 String title = "View of desktop";
3139 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3143 exporter.doExport(of, this, width, height, title);
3144 } catch (ImageOutputException ioex)
3146 jalview.bin.Console.error(
3147 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3153 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3154 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3155 * and location last time the view was expanded (if any). However it does not
3156 * remember the split pane divider location - this is set to match the
3157 * 'exploding' frame.
3161 public void explodeViews(SplitFrame sf)
3163 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3164 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3165 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3167 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3169 int viewCount = topPanels.size();
3176 * Processing in reverse order works, forwards order leaves the first panels not
3177 * visible. I don't know why!
3179 for (int i = viewCount - 1; i >= 0; i--)
3182 * Make new top and bottom frames. These take over the respective AlignmentPanel
3183 * objects, including their AlignmentViewports, so the cdna/protein
3184 * relationships between the viewports is carried over to the new split frames.
3186 * explodedGeometry holds the (x, y) position of the previously exploded
3187 * SplitFrame, and the (width, height) of the AlignFrame component
3189 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3190 AlignFrame newTopFrame = new AlignFrame(topPanel);
3191 newTopFrame.setSize(oldTopFrame.getSize());
3192 newTopFrame.setVisible(true);
3193 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3194 .getExplodedGeometry();
3195 if (geometry != null)
3197 newTopFrame.setSize(geometry.getSize());
3200 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3201 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3202 newBottomFrame.setSize(oldBottomFrame.getSize());
3203 newBottomFrame.setVisible(true);
3204 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3205 .getExplodedGeometry();
3206 if (geometry != null)
3208 newBottomFrame.setSize(geometry.getSize());
3211 topPanel.av.setGatherViewsHere(false);
3212 bottomPanel.av.setGatherViewsHere(false);
3213 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3215 if (geometry != null)
3217 splitFrame.setLocation(geometry.getLocation());
3219 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3223 * Clear references to the panels (now relocated in the new SplitFrames) before
3224 * closing the old SplitFrame.
3227 bottomPanels.clear();
3232 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3233 * back into the given SplitFrame as additional views. Note that the gathered
3234 * frames may themselves have multiple views.
3238 public void gatherViews(GSplitFrame source)
3241 * special handling of explodedGeometry for a view within a SplitFrame: - it
3242 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3243 * height) of the AlignFrame component
3245 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3246 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3247 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3248 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3249 myBottomFrame.viewport
3250 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3251 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3252 myTopFrame.viewport.setGatherViewsHere(true);
3253 myBottomFrame.viewport.setGatherViewsHere(true);
3254 String topViewId = myTopFrame.viewport.getSequenceSetId();
3255 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3257 JInternalFrame[] frames = desktop.getAllFrames();
3258 for (JInternalFrame frame : frames)
3260 if (frame instanceof SplitFrame && frame != source)
3262 SplitFrame sf = (SplitFrame) frame;
3263 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3264 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3265 boolean gatherThis = false;
3266 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3268 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3269 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3270 if (topViewId.equals(topPanel.av.getSequenceSetId())
3271 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3274 topPanel.av.setGatherViewsHere(false);
3275 bottomPanel.av.setGatherViewsHere(false);
3276 topPanel.av.setExplodedGeometry(
3277 new Rectangle(sf.getLocation(), topFrame.getSize()));
3278 bottomPanel.av.setExplodedGeometry(
3279 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3280 myTopFrame.addAlignmentPanel(topPanel, false);
3281 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3287 topFrame.getAlignPanels().clear();
3288 bottomFrame.getAlignPanels().clear();
3295 * The dust settles...give focus to the tab we did this from.
3297 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3300 public static groovy.ui.Console getGroovyConsole()
3302 return groovyConsole;
3306 * handles the payload of a drag and drop event.
3308 * TODO refactor to desktop utilities class
3311 * - Data source strings extracted from the drop event
3313 * - protocol for each data source extracted from the drop event
3317 * - the payload from the drop event
3320 public static void transferFromDropTarget(List<Object> files,
3321 List<DataSourceType> protocols, DropTargetDropEvent evt,
3322 Transferable t) throws Exception
3325 DataFlavor uriListFlavor = new DataFlavor(
3326 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3329 urlFlavour = new DataFlavor(
3330 "application/x-java-url; class=java.net.URL");
3331 } catch (ClassNotFoundException cfe)
3333 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3337 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3342 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3343 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3344 // means url may be null.
3347 protocols.add(DataSourceType.URL);
3348 files.add(url.toString());
3349 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3350 + files.get(files.size() - 1));
3355 if (Platform.isAMacAndNotJS())
3358 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3361 } catch (Throwable ex)
3363 jalview.bin.Console.debug("URL drop handler failed.", ex);
3366 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3368 // Works on Windows and MacOSX
3369 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3370 for (Object file : (List) t
3371 .getTransferData(DataFlavor.javaFileListFlavor))
3374 protocols.add(DataSourceType.FILE);
3379 // Unix like behaviour
3380 boolean added = false;
3382 if (t.isDataFlavorSupported(uriListFlavor))
3384 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3385 // This is used by Unix drag system
3386 data = (String) t.getTransferData(uriListFlavor);
3390 // fallback to text: workaround - on OSX where there's a JVM bug
3392 .debug("standard URIListFlavor failed. Trying text");
3393 // try text fallback
3394 DataFlavor textDf = new DataFlavor(
3395 "text/plain;class=java.lang.String");
3396 if (t.isDataFlavorSupported(textDf))
3398 data = (String) t.getTransferData(textDf);
3401 jalview.bin.Console.debug("Plain text drop content returned "
3402 + (data == null ? "Null - failed" : data));
3407 while (protocols.size() < files.size())
3409 jalview.bin.Console.debug("Adding missing FILE protocol for "
3410 + files.get(protocols.size()));
3411 protocols.add(DataSourceType.FILE);
3413 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3414 data, "\r\n"); st.hasMoreTokens();)
3417 String s = st.nextToken();
3418 if (s.startsWith("#"))
3420 // the line is a comment (as per the RFC 2483)
3423 java.net.URI uri = new java.net.URI(s);
3424 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3426 protocols.add(DataSourceType.URL);
3427 files.add(uri.toString());
3431 // otherwise preserve old behaviour: catch all for file objects
3432 java.io.File file = new java.io.File(uri);
3433 protocols.add(DataSourceType.FILE);
3434 files.add(file.toString());
3439 if (jalview.bin.Console.isDebugEnabled())
3441 if (data == null || !added)
3444 if (t.getTransferDataFlavors() != null
3445 && t.getTransferDataFlavors().length > 0)
3447 jalview.bin.Console.debug(
3448 "Couldn't resolve drop data. Here are the supported flavors:");
3449 for (DataFlavor fl : t.getTransferDataFlavors())
3451 jalview.bin.Console.debug(
3452 "Supported transfer dataflavor: " + fl.toString());
3453 Object df = t.getTransferData(fl);
3456 jalview.bin.Console.debug("Retrieves: " + df);
3460 jalview.bin.Console.debug("Retrieved nothing");
3467 .debug("Couldn't resolve dataflavor for drop: "
3473 if (Platform.isWindowsAndNotJS())
3476 .debug("Scanning dropped content for Windows Link Files");
3478 // resolve any .lnk files in the file drop
3479 for (int f = 0; f < files.size(); f++)
3481 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3482 if (protocols.get(f).equals(DataSourceType.FILE)
3483 && (source.endsWith(".lnk") || source.endsWith(".url")
3484 || source.endsWith(".site")))
3488 Object obj = files.get(f);
3489 File lf = (obj instanceof File ? (File) obj
3490 : new File((String) obj));
3491 // process link file to get a URL
3492 jalview.bin.Console.debug("Found potential link file: " + lf);
3493 WindowsShortcut wscfile = new WindowsShortcut(lf);
3494 String fullname = wscfile.getRealFilename();
3495 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3496 files.set(f, fullname);
3497 jalview.bin.Console.debug("Parsed real filename " + fullname
3498 + " to extract protocol: " + protocols.get(f));
3499 } catch (Exception ex)
3501 jalview.bin.Console.error(
3502 "Couldn't parse " + files.get(f) + " as a link file.",
3511 * Sets the Preferences property for experimental features to True or False
3512 * depending on the state of the controlling menu item
3515 protected void showExperimental_actionPerformed(boolean selected)
3517 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3521 * Answers a (possibly empty) list of any structure viewer frames (currently
3522 * for either Jmol or Chimera) which are currently open. This may optionally
3523 * be restricted to viewers of a specified class, or viewers linked to a
3524 * specified alignment panel.
3527 * if not null, only return viewers linked to this panel
3528 * @param structureViewerClass
3529 * if not null, only return viewers of this class
3532 public List<StructureViewerBase> getStructureViewers(
3533 AlignmentPanel apanel,
3534 Class<? extends StructureViewerBase> structureViewerClass)
3536 List<StructureViewerBase> result = new ArrayList<>();
3537 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3539 for (JInternalFrame frame : frames)
3541 if (frame instanceof StructureViewerBase)
3543 if (structureViewerClass == null
3544 || structureViewerClass.isInstance(frame))
3547 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3549 result.add((StructureViewerBase) frame);
3557 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3559 private static boolean debugScaleMessageDone = false;
3561 public static void debugScaleMessage(Graphics g)
3563 if (debugScaleMessageDone)
3567 // output used by tests to check HiDPI scaling settings in action
3570 Graphics2D gg = (Graphics2D) g;
3573 AffineTransform t = gg.getTransform();
3574 double scaleX = t.getScaleX();
3575 double scaleY = t.getScaleY();
3576 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3577 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3578 debugScaleMessageDone = true;
3582 jalview.bin.Console.debug("Desktop graphics null");
3584 } catch (Exception e)
3586 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3591 * closes the current instance window, disposes and forgets about it.
3593 public static void closeDesktop()
3595 if (Desktop.instance != null)
3597 Desktop.instance.closeAll_actionPerformed(null);
3598 Desktop.instance.setVisible(false);
3599 Desktop.instance.dispose();
3600 Desktop.instance = null;