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
572 // it takes to open it later on.
573 new Thread(new Runnable()
578 jalview.bin.Console.debug("Filechooser init thread started.");
579 String fileFormat = FileLoader.getUseDefaultFileFormat()
580 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
582 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
584 jalview.bin.Console.debug("Filechooser init thread finished.");
587 // Add the service change listener
588 changeSupport.addJalviewPropertyChangeListener("services",
589 new PropertyChangeListener()
593 public void propertyChange(PropertyChangeEvent evt)
596 .debug("Firing service changed event for "
597 + evt.getNewValue());
598 JalviewServicesChanged(evt);
603 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
606 this.addMouseListener(ma = new MouseAdapter()
609 public void mousePressed(MouseEvent evt)
611 if (evt.isPopupTrigger()) // Mac
613 showPasteMenu(evt.getX(), evt.getY());
618 public void mouseReleased(MouseEvent evt)
620 if (evt.isPopupTrigger()) // Windows
622 showPasteMenu(evt.getX(), evt.getY());
626 desktop.addMouseListener(ma);
630 // used for jalviewjsTest
631 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
636 * Answers true if user preferences to enable experimental features is True
641 public boolean showExperimental()
643 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
644 Boolean.FALSE.toString());
645 return Boolean.valueOf(experimental).booleanValue();
648 public void doConfigureStructurePrefs()
650 // configure services
651 StructureSelectionManager ssm = StructureSelectionManager
652 .getStructureSelectionManager(this);
653 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
655 ssm.setAddTempFacAnnot(
656 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
657 ssm.setProcessSecondaryStructure(
658 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
659 // JAL-3915 - RNAView is no longer an option so this has no effect
660 ssm.setSecStructServices(
661 Cache.getDefault(Preferences.USE_RNAVIEW, false));
665 ssm.setAddTempFacAnnot(false);
666 ssm.setProcessSecondaryStructure(false);
667 ssm.setSecStructServices(false);
671 public void checkForNews()
673 final Desktop me = this;
674 // Thread off the news reader, in case there are connection problems.
675 new Thread(new Runnable()
680 jalview.bin.Console.debug("Starting news thread.");
681 jvnews = new BlogReader(me);
682 showNews.setVisible(true);
683 jalview.bin.Console.debug("Completed news thread.");
688 public void getIdentifiersOrgData()
690 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
691 {// Thread off the identifiers fetcher
692 new Thread(new Runnable()
698 .debug("Downloading data from identifiers.org");
701 UrlDownloadClient.download(IdOrgSettings.getUrl(),
702 IdOrgSettings.getDownloadLocation());
703 } catch (IOException e)
706 .debug("Exception downloading identifiers.org data"
716 protected void showNews_actionPerformed(ActionEvent e)
718 showNews(showNews.isSelected());
721 void showNews(boolean visible)
723 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
724 showNews.setSelected(visible);
725 if (visible && !jvnews.isVisible())
727 new Thread(new Runnable()
732 long now = System.currentTimeMillis();
733 Desktop.instance.setProgressBar(
734 MessageManager.getString("status.refreshing_news"), now);
735 jvnews.refreshNews();
736 Desktop.instance.setProgressBar(null, now);
744 * recover the last known dimensions for a jalview window
747 * - empty string is desktop, all other windows have unique prefix
748 * @return null or last known dimensions scaled to current geometry (if last
749 * window geom was known)
751 Rectangle getLastKnownDimensions(String windowName)
753 // TODO: lock aspect ratio for scaling desktop Bug #0058199
754 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
755 String x = Cache.getProperty(windowName + "SCREEN_X");
756 String y = Cache.getProperty(windowName + "SCREEN_Y");
757 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
758 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
759 if ((x != null) && (y != null) && (width != null) && (height != null))
761 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
762 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
763 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
765 // attempt #1 - try to cope with change in screen geometry - this
766 // version doesn't preserve original jv aspect ratio.
767 // take ratio of current screen size vs original screen size.
768 double sw = ((1f * screenSize.width) / (1f * Integer
769 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
770 double sh = ((1f * screenSize.height) / (1f * Integer
771 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
772 // rescale the bounds depending upon the current screen geometry.
773 ix = (int) (ix * sw);
774 iw = (int) (iw * sw);
775 iy = (int) (iy * sh);
776 ih = (int) (ih * sh);
777 while (ix >= screenSize.width)
779 jalview.bin.Console.debug(
780 "Window geometry location recall error: shifting horizontal to within screenbounds.");
781 ix -= screenSize.width;
783 while (iy >= screenSize.height)
785 jalview.bin.Console.debug(
786 "Window geometry location recall error: shifting vertical to within screenbounds.");
787 iy -= screenSize.height;
789 jalview.bin.Console.debug(
790 "Got last known dimensions for " + windowName + ": x:" + ix
791 + " y:" + iy + " width:" + iw + " height:" + ih);
793 // return dimensions for new instance
794 return new Rectangle(ix, iy, iw, ih);
799 void showPasteMenu(int x, int y)
801 JPopupMenu popup = new JPopupMenu();
802 JMenuItem item = new JMenuItem(
803 MessageManager.getString("label.paste_new_window"));
804 item.addActionListener(new ActionListener()
807 public void actionPerformed(ActionEvent evt)
814 popup.show(this, x, y);
819 // quick patch for JAL-4150 - needs some more work and test coverage
820 // TODO - unify below and AlignFrame.paste()
821 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
822 // clipboard has come from a different alignment window than the one where
823 // paste has been called! JAL-4151
825 if (Desktop.jalviewClipboard != null)
827 // The clipboard was filled from within Jalview, we must use the
829 // And dataset from the copied alignment
830 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
831 // be doubly sure that we create *new* sequence objects.
832 SequenceI[] sequences = new SequenceI[newseq.length];
833 for (int i = 0; i < newseq.length; i++)
835 sequences[i] = new Sequence(newseq[i]);
837 Alignment alignment = new Alignment(sequences);
838 // dataset is inherited
839 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
840 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
841 AlignFrame.DEFAULT_HEIGHT);
842 String newtitle = new String("Copied sequences");
844 if (Desktop.jalviewClipboard[2] != null)
846 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
847 af.viewport.setHiddenColumns(hc);
850 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
851 AlignFrame.DEFAULT_HEIGHT);
858 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
859 Transferable contents = c.getContents(this);
861 if (contents != null)
863 String file = (String) contents
864 .getTransferData(DataFlavor.stringFlavor);
866 FileFormatI format = new IdentifyFile().identify(file,
867 DataSourceType.PASTE);
869 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
872 } catch (Exception ex)
875 "Unable to paste alignment from system clipboard:\n" + ex);
881 * Adds and opens the given frame to the desktop
892 public static synchronized void addInternalFrame(
893 final JInternalFrame frame, String title, int w, int h)
895 addInternalFrame(frame, title, true, w, h, true, false);
899 * Add an internal frame to the Jalview desktop
906 * When true, display frame immediately, otherwise, caller must call
907 * setVisible themselves.
913 public static synchronized void addInternalFrame(
914 final JInternalFrame frame, String title, boolean makeVisible,
917 addInternalFrame(frame, title, makeVisible, w, h, true, false);
921 * Add an internal frame to the Jalview desktop and make it visible
934 public static synchronized void addInternalFrame(
935 final JInternalFrame frame, String title, int w, int h,
938 addInternalFrame(frame, title, true, w, h, resizable, false);
942 * Add an internal frame to the Jalview desktop
949 * When true, display frame immediately, otherwise, caller must call
950 * setVisible themselves.
957 * @param ignoreMinSize
958 * Do not set the default minimum size for frame
960 public static synchronized void addInternalFrame(
961 final JInternalFrame frame, String title, boolean makeVisible,
962 int w, int h, boolean resizable, boolean ignoreMinSize)
965 // TODO: allow callers to determine X and Y position of frame (eg. via
967 // TODO: consider fixing method to update entries in the window submenu with
968 // the current window title
970 frame.setTitle(title);
971 if (frame.getWidth() < 1 || frame.getHeight() < 1)
975 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
976 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
977 // IF JALVIEW IS RUNNING HEADLESS
978 // ///////////////////////////////////////////////
979 if (instance == null || (System.getProperty("java.awt.headless") != null
980 && System.getProperty("java.awt.headless").equals("true")))
989 frame.setMinimumSize(
990 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
992 // Set default dimension for Alignment Frame window.
993 // The Alignment Frame window could be added from a number of places,
995 // I did this here in order not to miss out on any Alignment frame.
996 if (frame instanceof AlignFrame)
998 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
999 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1003 frame.setVisible(makeVisible);
1004 frame.setClosable(true);
1005 frame.setResizable(resizable);
1006 frame.setMaximizable(resizable);
1007 frame.setIconifiable(resizable);
1008 frame.setOpaque(Platform.isJS());
1010 if (frame.getX() < 1 && frame.getY() < 1)
1012 frame.setLocation(xOffset * openFrameCount,
1013 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1017 * add an entry for the new frame in the Window menu (and remove it when the
1020 final JMenuItem menuItem = new JMenuItem(title);
1021 frame.addInternalFrameListener(new InternalFrameAdapter()
1024 public void internalFrameActivated(InternalFrameEvent evt)
1026 JInternalFrame itf = desktop.getSelectedFrame();
1029 if (itf instanceof AlignFrame)
1031 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1038 public void internalFrameClosed(InternalFrameEvent evt)
1040 PaintRefresher.RemoveComponent(frame);
1043 * defensive check to prevent frames being added half off the window
1045 if (openFrameCount > 0)
1051 * ensure no reference to alignFrame retained by menu item listener
1053 if (menuItem.getActionListeners().length > 0)
1055 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1057 windowMenu.remove(menuItem);
1061 menuItem.addActionListener(new ActionListener()
1064 public void actionPerformed(ActionEvent e)
1068 frame.setSelected(true);
1069 frame.setIcon(false);
1070 } catch (java.beans.PropertyVetoException ex)
1077 setKeyBindings(frame);
1081 windowMenu.add(menuItem);
1086 frame.setSelected(true);
1087 frame.requestFocus();
1088 } catch (java.beans.PropertyVetoException ve)
1090 } catch (java.lang.ClassCastException cex)
1092 jalview.bin.Console.warn(
1093 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1099 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1104 private static void setKeyBindings(JInternalFrame frame)
1106 @SuppressWarnings("serial")
1107 final Action closeAction = new AbstractAction()
1110 public void actionPerformed(ActionEvent e)
1117 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1119 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1120 InputEvent.CTRL_DOWN_MASK);
1121 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1122 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1124 InputMap inputMap = frame
1125 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1126 String ctrlW = ctrlWKey.toString();
1127 inputMap.put(ctrlWKey, ctrlW);
1128 inputMap.put(cmdWKey, ctrlW);
1130 ActionMap actionMap = frame.getActionMap();
1131 actionMap.put(ctrlW, closeAction);
1135 public void lostOwnership(Clipboard clipboard, Transferable contents)
1139 Desktop.jalviewClipboard = null;
1142 internalCopy = false;
1146 public void dragEnter(DropTargetDragEvent evt)
1151 public void dragExit(DropTargetEvent evt)
1156 public void dragOver(DropTargetDragEvent evt)
1161 public void dropActionChanged(DropTargetDragEvent evt)
1172 public void drop(DropTargetDropEvent evt)
1174 boolean success = true;
1175 // JAL-1552 - acceptDrop required before getTransferable call for
1176 // Java's Transferable for native dnd
1177 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1178 Transferable t = evt.getTransferable();
1179 List<Object> files = new ArrayList<>();
1180 List<DataSourceType> protocols = new ArrayList<>();
1184 Desktop.transferFromDropTarget(files, protocols, evt, t);
1185 } catch (Exception e)
1187 e.printStackTrace();
1195 for (int i = 0; i < files.size(); i++)
1197 // BH 2018 File or String
1198 Object file = files.get(i);
1199 String fileName = file.toString();
1200 DataSourceType protocol = (protocols == null)
1201 ? DataSourceType.FILE
1203 FileFormatI format = null;
1205 if (fileName.endsWith(".jar"))
1207 format = FileFormat.Jalview;
1212 format = new IdentifyFile().identify(file, protocol);
1214 if (file instanceof File)
1216 Platform.cacheFileData((File) file);
1218 new FileLoader().LoadFile(null, file, protocol, format);
1221 } catch (Exception ex)
1226 evt.dropComplete(success); // need this to ensure input focus is properly
1227 // transfered to any new windows created
1237 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1239 String fileFormat = FileLoader.getUseDefaultFileFormat()
1240 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1242 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1243 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1244 BackupFiles.getEnabled());
1246 chooser.setFileView(new JalviewFileView());
1247 chooser.setDialogTitle(
1248 MessageManager.getString("label.open_local_file"));
1249 chooser.setToolTipText(MessageManager.getString("action.open"));
1251 chooser.setResponseHandler(0, () -> {
1252 File selectedFile = chooser.getSelectedFile();
1253 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1255 FileFormatI format = chooser.getSelectedFormat();
1258 * Call IdentifyFile to verify the file contains what its extension implies.
1259 * Skip this step for dynamically added file formats, because IdentifyFile does
1260 * not know how to recognise them.
1262 if (FileFormats.getInstance().isIdentifiable(format))
1266 format = new IdentifyFile().identify(selectedFile,
1267 DataSourceType.FILE);
1268 } catch (FileFormatException e)
1270 // format = null; //??
1274 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1277 chooser.showOpenDialog(this);
1281 * Shows a dialog for input of a URL at which to retrieve alignment data
1286 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1288 // This construct allows us to have a wider textfield
1290 JLabel label = new JLabel(
1291 MessageManager.getString("label.input_file_url"));
1293 JPanel panel = new JPanel(new GridLayout(2, 1));
1297 * the URL to fetch is input in Java: an editable combobox with history JS:
1298 * (pending JAL-3038) a plain text field
1301 String urlBase = "https://www.";
1302 if (Platform.isJS())
1304 history = new JTextField(urlBase, 35);
1313 JComboBox<String> asCombo = new JComboBox<>();
1314 asCombo.setPreferredSize(new Dimension(400, 20));
1315 asCombo.setEditable(true);
1316 asCombo.addItem(urlBase);
1317 String historyItems = Cache.getProperty("RECENT_URL");
1318 if (historyItems != null)
1320 for (String token : historyItems.split("\\t"))
1322 asCombo.addItem(token);
1329 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1330 MessageManager.getString("action.cancel") };
1331 Runnable action = () -> {
1332 @SuppressWarnings("unchecked")
1333 String url = (history instanceof JTextField
1334 ? ((JTextField) history).getText()
1335 : ((JComboBox<String>) history).getEditor().getItem()
1336 .toString().trim());
1338 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1340 if (viewport != null)
1342 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1343 FileFormat.Jalview);
1347 new FileLoader().LoadFile(url, DataSourceType.URL,
1348 FileFormat.Jalview);
1353 FileFormatI format = null;
1356 format = new IdentifyFile().identify(url, DataSourceType.URL);
1357 } catch (FileFormatException e)
1359 // TODO revise error handling, distinguish between
1360 // URL not found and response not valid
1365 String msg = MessageManager.formatMessage("label.couldnt_locate",
1367 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1368 MessageManager.getString("label.url_not_found"),
1369 JvOptionPane.WARNING_MESSAGE);
1373 if (viewport != null)
1375 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1380 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1384 String dialogOption = MessageManager
1385 .getString("label.input_alignment_from_url");
1386 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1387 .showInternalDialog(panel, dialogOption,
1388 JvOptionPane.YES_NO_CANCEL_OPTION,
1389 JvOptionPane.PLAIN_MESSAGE, null, options,
1390 MessageManager.getString("action.ok"));
1394 * Opens the CutAndPaste window for the user to paste an alignment in to
1397 * - if not null, the pasted alignment is added to the current
1398 * alignment; if null, to a new alignment window
1401 public void inputTextboxMenuItem_actionPerformed(
1402 AlignmentViewPanel viewPanel)
1404 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1405 cap.setForInput(viewPanel);
1406 Desktop.addInternalFrame(cap,
1407 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1412 * Check with user and saving files before actually quitting
1414 public void desktopQuit()
1416 desktopQuit(true, false);
1419 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1421 final Runnable doDesktopQuit = () -> {
1422 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1423 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1424 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1425 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1426 getBounds().y, getWidth(), getHeight()));
1428 if (jconsole != null)
1430 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1431 jconsole.stopConsole();
1436 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1439 // Frames should all close automatically. Keeping external
1440 // viewers open should already be decided by user.
1441 closeAll_actionPerformed(null);
1443 // check for aborted quit
1444 if (QuitHandler.quitCancelled())
1446 jalview.bin.Console.debug("Desktop aborting quit");
1450 if (dialogExecutor != null)
1452 dialogExecutor.shutdownNow();
1455 if (groovyConsole != null)
1457 // suppress a possible repeat prompt to save script
1458 groovyConsole.setDirty(false);
1459 groovyConsole.exit();
1462 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1464 // note that shutdown hook will not be run
1465 jalview.bin.Console.debug("Force Quit selected by user");
1466 Runtime.getRuntime().halt(0);
1469 jalview.bin.Console.debug("Quit selected by user");
1472 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1473 // instance.dispose();
1478 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1479 QuitHandler.defaultCancelQuit);
1483 * Don't call this directly, use desktopQuit() above. Exits the program.
1488 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1489 // not run a second time if gotQuitResponse flag has been set (i.e. user
1490 // confirmed quit of some kind).
1491 Jalview.exit("Desktop exiting.", 0);
1494 private void storeLastKnownDimensions(String string, Rectangle jc)
1496 jalview.bin.Console.debug("Storing last known dimensions for " + string
1497 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1498 + " height:" + jc.height);
1500 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1501 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1502 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1503 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1513 public void aboutMenuItem_actionPerformed(ActionEvent e)
1515 new Thread(new Runnable()
1520 new SplashScreen(false);
1526 * Returns the html text for the About screen, including any available version
1527 * number, build details, author details and citation reference, but without
1528 * the enclosing {@code html} tags
1532 public String getAboutMessage()
1534 StringBuilder message = new StringBuilder(1024);
1535 message.append("<div style=\"font-family: sans-serif;\">")
1536 .append("<h1><strong>Version: ")
1537 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1538 .append("<strong>Built: <em>")
1539 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1540 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1541 .append("</strong>");
1543 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1544 if (latestVersion.equals("Checking"))
1546 // JBP removed this message for 2.11: May be reinstated in future version
1547 // message.append("<br>...Checking latest version...</br>");
1549 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1551 boolean red = false;
1552 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1553 .indexOf("automated build") == -1)
1556 // Displayed when code version and jnlp version do not match and code
1557 // version is not a development build
1558 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1561 message.append("<br>!! Version ")
1562 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1563 .append(" is available for download from ")
1564 .append(Cache.getDefault("www.jalview.org",
1565 "https://www.jalview.org"))
1569 message.append("</div>");
1572 message.append("<br>Authors: ");
1573 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1574 message.append(CITATION);
1576 message.append("</div>");
1578 return message.toString();
1582 * Action on requesting Help documentation
1585 public void documentationMenuItem_actionPerformed()
1589 if (Platform.isJS())
1591 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1600 Help.showHelpWindow();
1602 } catch (Exception ex)
1604 System.err.println("Error opening help: " + ex.getMessage());
1609 public void closeAll_actionPerformed(ActionEvent e)
1611 // TODO show a progress bar while closing?
1612 JInternalFrame[] frames = desktop.getAllFrames();
1613 for (int i = 0; i < frames.length; i++)
1617 frames[i].setClosed(true);
1618 } catch (java.beans.PropertyVetoException ex)
1622 Jalview.setCurrentAlignFrame(null);
1623 jalview.bin.Console.info("ALL CLOSED");
1626 * reset state of singleton objects as appropriate (clear down session state
1627 * when all windows are closed)
1629 StructureSelectionManager ssm = StructureSelectionManager
1630 .getStructureSelectionManager(this);
1637 public int structureViewersStillRunningCount()
1640 JInternalFrame[] frames = desktop.getAllFrames();
1641 for (int i = 0; i < frames.length; i++)
1643 if (frames[i] != null
1644 && frames[i] instanceof JalviewStructureDisplayI)
1646 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1654 public void raiseRelated_actionPerformed(ActionEvent e)
1656 reorderAssociatedWindows(false, false);
1660 public void minimizeAssociated_actionPerformed(ActionEvent e)
1662 reorderAssociatedWindows(true, false);
1665 void closeAssociatedWindows()
1667 reorderAssociatedWindows(false, true);
1673 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1677 protected void garbageCollect_actionPerformed(ActionEvent e)
1679 // We simply collect the garbage
1680 jalview.bin.Console.debug("Collecting garbage...");
1682 jalview.bin.Console.debug("Finished garbage collection.");
1688 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1692 protected void showMemusage_actionPerformed(ActionEvent e)
1694 desktop.showMemoryUsage(showMemusage.isSelected());
1701 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1705 protected void showConsole_actionPerformed(ActionEvent e)
1707 showConsole(showConsole.isSelected());
1710 Console jconsole = null;
1713 * control whether the java console is visible or not
1717 void showConsole(boolean selected)
1719 // TODO: decide if we should update properties file
1720 if (jconsole != null) // BH 2018
1722 showConsole.setSelected(selected);
1723 Cache.setProperty("SHOW_JAVA_CONSOLE",
1724 Boolean.valueOf(selected).toString());
1725 jconsole.setVisible(selected);
1729 void reorderAssociatedWindows(boolean minimize, boolean close)
1731 JInternalFrame[] frames = desktop.getAllFrames();
1732 if (frames == null || frames.length < 1)
1737 AlignmentViewport source = null, target = null;
1738 if (frames[0] instanceof AlignFrame)
1740 source = ((AlignFrame) frames[0]).getCurrentView();
1742 else if (frames[0] instanceof TreePanel)
1744 source = ((TreePanel) frames[0]).getViewPort();
1746 else if (frames[0] instanceof PCAPanel)
1748 source = ((PCAPanel) frames[0]).av;
1750 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1752 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1757 for (int i = 0; i < frames.length; i++)
1760 if (frames[i] == null)
1764 if (frames[i] instanceof AlignFrame)
1766 target = ((AlignFrame) frames[i]).getCurrentView();
1768 else if (frames[i] instanceof TreePanel)
1770 target = ((TreePanel) frames[i]).getViewPort();
1772 else if (frames[i] instanceof PCAPanel)
1774 target = ((PCAPanel) frames[i]).av;
1776 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1778 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1781 if (source == target)
1787 frames[i].setClosed(true);
1791 frames[i].setIcon(minimize);
1794 frames[i].toFront();
1798 } catch (java.beans.PropertyVetoException ex)
1813 protected void preferences_actionPerformed(ActionEvent e)
1815 Preferences.openPreferences();
1819 * Prompts the user to choose a file and then saves the Jalview state as a
1820 * Jalview project file
1823 public void saveState_actionPerformed()
1825 saveState_actionPerformed(false);
1828 public void saveState_actionPerformed(boolean saveAs)
1830 java.io.File projectFile = getProjectFile();
1831 // autoSave indicates we already have a file and don't need to ask
1832 boolean autoSave = projectFile != null && !saveAs
1833 && BackupFiles.getEnabled();
1835 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1836 // saveAs="+saveAs+", Backups
1837 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1839 boolean approveSave = false;
1842 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1845 chooser.setFileView(new JalviewFileView());
1846 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1848 int value = chooser.showSaveDialog(this);
1850 if (value == JalviewFileChooser.APPROVE_OPTION)
1852 projectFile = chooser.getSelectedFile();
1853 setProjectFile(projectFile);
1858 if (approveSave || autoSave)
1860 final Desktop me = this;
1861 final java.io.File chosenFile = projectFile;
1862 new Thread(new Runnable()
1867 // TODO: refactor to Jalview desktop session controller action.
1868 setProgressBar(MessageManager.formatMessage(
1869 "label.saving_jalview_project", new Object[]
1870 { chosenFile.getName() }), chosenFile.hashCode());
1871 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1872 // TODO catch and handle errors for savestate
1873 // TODO prevent user from messing with the Desktop whilst we're saving
1876 boolean doBackup = BackupFiles.getEnabled();
1877 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1880 new Jalview2XML().saveState(
1881 doBackup ? backupfiles.getTempFile() : chosenFile);
1885 backupfiles.setWriteSuccess(true);
1886 backupfiles.rollBackupsAndRenameTempFile();
1888 } catch (OutOfMemoryError oom)
1890 new OOMWarning("Whilst saving current state to "
1891 + chosenFile.getName(), oom);
1892 } catch (Exception ex)
1894 jalview.bin.Console.error("Problems whilst trying to save to "
1895 + chosenFile.getName(), ex);
1896 JvOptionPane.showMessageDialog(me,
1897 MessageManager.formatMessage(
1898 "label.error_whilst_saving_current_state_to",
1900 { chosenFile.getName() }),
1901 MessageManager.getString("label.couldnt_save_project"),
1902 JvOptionPane.WARNING_MESSAGE);
1904 setProgressBar(null, chosenFile.hashCode());
1911 public void saveAsState_actionPerformed(ActionEvent e)
1913 saveState_actionPerformed(true);
1916 protected void setProjectFile(File choice)
1918 this.projectFile = choice;
1921 public File getProjectFile()
1923 return this.projectFile;
1927 * Shows a file chooser dialog and tries to read in the selected file as a
1931 public void loadState_actionPerformed()
1933 final String[] suffix = new String[] { "jvp", "jar" };
1934 final String[] desc = new String[] { "Jalview Project",
1935 "Jalview Project (old)" };
1936 JalviewFileChooser chooser = new JalviewFileChooser(
1937 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1938 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1942 chooser.setFileView(new JalviewFileView());
1943 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1944 chooser.setResponseHandler(0, () -> {
1945 File selectedFile = chooser.getSelectedFile();
1946 setProjectFile(selectedFile);
1947 String choice = selectedFile.getAbsolutePath();
1948 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1949 new Thread(new Runnable()
1956 new Jalview2XML().loadJalviewAlign(selectedFile);
1957 } catch (OutOfMemoryError oom)
1959 new OOMWarning("Whilst loading project from " + choice, oom);
1960 } catch (Exception ex)
1962 jalview.bin.Console.error(
1963 "Problems whilst loading project from " + choice, ex);
1964 JvOptionPane.showMessageDialog(Desktop.desktop,
1965 MessageManager.formatMessage(
1966 "label.error_whilst_loading_project_from",
1969 MessageManager.getString("label.couldnt_load_project"),
1970 JvOptionPane.WARNING_MESSAGE);
1973 }, "Project Loader").start();
1976 chooser.showOpenDialog(this);
1980 public void inputSequence_actionPerformed(ActionEvent e)
1982 new SequenceFetcher(this);
1985 JPanel progressPanel;
1987 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1989 public void startLoading(final Object fileName)
1991 if (fileLoadingCount == 0)
1993 fileLoadingPanels.add(addProgressPanel(MessageManager
1994 .formatMessage("label.loading_file", new Object[]
2000 private JPanel addProgressPanel(String string)
2002 if (progressPanel == null)
2004 progressPanel = new JPanel(new GridLayout(1, 1));
2005 totalProgressCount = 0;
2006 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2008 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2009 JProgressBar progressBar = new JProgressBar();
2010 progressBar.setIndeterminate(true);
2012 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2014 thisprogress.add(progressBar, BorderLayout.CENTER);
2015 progressPanel.add(thisprogress);
2016 ((GridLayout) progressPanel.getLayout()).setRows(
2017 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2018 ++totalProgressCount;
2019 instance.validate();
2020 return thisprogress;
2023 int totalProgressCount = 0;
2025 private void removeProgressPanel(JPanel progbar)
2027 if (progressPanel != null)
2029 synchronized (progressPanel)
2031 progressPanel.remove(progbar);
2032 GridLayout gl = (GridLayout) progressPanel.getLayout();
2033 gl.setRows(gl.getRows() - 1);
2034 if (--totalProgressCount < 1)
2036 this.getContentPane().remove(progressPanel);
2037 progressPanel = null;
2044 public void stopLoading()
2047 if (fileLoadingCount < 1)
2049 while (fileLoadingPanels.size() > 0)
2051 removeProgressPanel(fileLoadingPanels.remove(0));
2053 fileLoadingPanels.clear();
2054 fileLoadingCount = 0;
2059 public static int getViewCount(String alignmentId)
2061 AlignmentViewport[] aps = getViewports(alignmentId);
2062 return (aps == null) ? 0 : aps.length;
2067 * @param alignmentId
2068 * - if null, all sets are returned
2069 * @return all AlignmentPanels concerning the alignmentId sequence set
2071 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2073 if (Desktop.desktop == null)
2075 // no frames created and in headless mode
2076 // TODO: verify that frames are recoverable when in headless mode
2079 List<AlignmentPanel> aps = new ArrayList<>();
2080 AlignFrame[] frames = getAlignFrames();
2085 for (AlignFrame af : frames)
2087 for (AlignmentPanel ap : af.alignPanels)
2089 if (alignmentId == null
2090 || alignmentId.equals(ap.av.getSequenceSetId()))
2096 if (aps.size() == 0)
2100 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2105 * get all the viewports on an alignment.
2107 * @param sequenceSetId
2108 * unique alignment id (may be null - all viewports returned in that
2110 * @return all viewports on the alignment bound to sequenceSetId
2112 public static AlignmentViewport[] getViewports(String sequenceSetId)
2114 List<AlignmentViewport> viewp = new ArrayList<>();
2115 if (desktop != null)
2117 AlignFrame[] frames = Desktop.getAlignFrames();
2119 for (AlignFrame afr : frames)
2121 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2122 .equals(sequenceSetId))
2124 if (afr.alignPanels != null)
2126 for (AlignmentPanel ap : afr.alignPanels)
2128 if (sequenceSetId == null
2129 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2137 viewp.add(afr.getViewport());
2141 if (viewp.size() > 0)
2143 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2150 * Explode the views in the given frame into separate AlignFrame
2154 public static void explodeViews(AlignFrame af)
2156 int size = af.alignPanels.size();
2162 // FIXME: ideally should use UI interface API
2163 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2164 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2165 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2166 for (int i = 0; i < size; i++)
2168 AlignmentPanel ap = af.alignPanels.get(i);
2170 AlignFrame newaf = new AlignFrame(ap);
2172 // transfer reference for existing feature settings to new alignFrame
2173 if (ap == af.alignPanel)
2175 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2177 newaf.featureSettings = viewFeatureSettings;
2179 newaf.setFeatureSettingsGeometry(fsBounds);
2183 * Restore the view's last exploded frame geometry if known. Multiple views from
2184 * one exploded frame share and restore the same (frame) position and size.
2186 Rectangle geometry = ap.av.getExplodedGeometry();
2187 if (geometry != null)
2189 newaf.setBounds(geometry);
2192 ap.av.setGatherViewsHere(false);
2194 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2195 AlignFrame.DEFAULT_HEIGHT);
2196 // and materialise a new feature settings dialog instance for the new
2198 // (closes the old as if 'OK' was pressed)
2199 if (ap == af.alignPanel && newaf.featureSettings != null
2200 && newaf.featureSettings.isOpen()
2201 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2203 newaf.showFeatureSettingsUI();
2207 af.featureSettings = null;
2208 af.alignPanels.clear();
2209 af.closeMenuItem_actionPerformed(true);
2214 * Gather expanded views (separate AlignFrame's) with the same sequence set
2215 * identifier back in to this frame as additional views, and close the
2216 * expanded views. Note the expanded frames may themselves have multiple
2217 * views. We take the lot.
2221 public void gatherViews(AlignFrame source)
2223 source.viewport.setGatherViewsHere(true);
2224 source.viewport.setExplodedGeometry(source.getBounds());
2225 JInternalFrame[] frames = desktop.getAllFrames();
2226 String viewId = source.viewport.getSequenceSetId();
2227 for (int t = 0; t < frames.length; t++)
2229 if (frames[t] instanceof AlignFrame && frames[t] != source)
2231 AlignFrame af = (AlignFrame) frames[t];
2232 boolean gatherThis = false;
2233 for (int a = 0; a < af.alignPanels.size(); a++)
2235 AlignmentPanel ap = af.alignPanels.get(a);
2236 if (viewId.equals(ap.av.getSequenceSetId()))
2239 ap.av.setGatherViewsHere(false);
2240 ap.av.setExplodedGeometry(af.getBounds());
2241 source.addAlignmentPanel(ap, false);
2247 if (af.featureSettings != null && af.featureSettings.isOpen())
2249 if (source.featureSettings == null)
2251 // preserve the feature settings geometry for this frame
2252 source.featureSettings = af.featureSettings;
2253 source.setFeatureSettingsGeometry(
2254 af.getFeatureSettingsGeometry());
2258 // close it and forget
2259 af.featureSettings.close();
2262 af.alignPanels.clear();
2263 af.closeMenuItem_actionPerformed(true);
2268 // refresh the feature setting UI for the source frame if it exists
2269 if (source.featureSettings != null && source.featureSettings.isOpen())
2271 source.showFeatureSettingsUI();
2276 public JInternalFrame[] getAllFrames()
2278 return desktop.getAllFrames();
2282 * Checks the given url to see if it gives a response indicating that the user
2283 * should be informed of a new questionnaire.
2287 public void checkForQuestionnaire(String url)
2289 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2290 // javax.swing.SwingUtilities.invokeLater(jvq);
2291 new Thread(jvq).start();
2294 public void checkURLLinks()
2296 // Thread off the URL link checker
2297 addDialogThread(new Runnable()
2302 if (Cache.getDefault("CHECKURLLINKS", true))
2304 // check what the actual links are - if it's just the default don't
2305 // bother with the warning
2306 List<String> links = Preferences.sequenceUrlLinks
2309 // only need to check links if there is one with a
2310 // SEQUENCE_ID which is not the default EMBL_EBI link
2311 ListIterator<String> li = links.listIterator();
2312 boolean check = false;
2313 List<JLabel> urls = new ArrayList<>();
2314 while (li.hasNext())
2316 String link = li.next();
2317 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2318 && !UrlConstants.isDefaultString(link))
2321 int barPos = link.indexOf("|");
2322 String urlMsg = barPos == -1 ? link
2323 : link.substring(0, barPos) + ": "
2324 + link.substring(barPos + 1);
2325 urls.add(new JLabel(urlMsg));
2333 // ask user to check in case URL links use old style tokens
2334 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2335 JPanel msgPanel = new JPanel();
2336 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2337 msgPanel.add(Box.createVerticalGlue());
2338 JLabel msg = new JLabel(MessageManager
2339 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2340 JLabel msg2 = new JLabel(MessageManager
2341 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2343 for (JLabel url : urls)
2349 final JCheckBox jcb = new JCheckBox(
2350 MessageManager.getString("label.do_not_display_again"));
2351 jcb.addActionListener(new ActionListener()
2354 public void actionPerformed(ActionEvent e)
2356 // update Cache settings for "don't show this again"
2357 boolean showWarningAgain = !jcb.isSelected();
2358 Cache.setProperty("CHECKURLLINKS",
2359 Boolean.valueOf(showWarningAgain).toString());
2364 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2366 .getString("label.SEQUENCE_ID_no_longer_used"),
2367 JvOptionPane.WARNING_MESSAGE);
2374 * Proxy class for JDesktopPane which optionally displays the current memory
2375 * usage and highlights the desktop area with a red bar if free memory runs
2380 public class MyDesktopPane extends JDesktopPane implements Runnable
2382 private static final float ONE_MB = 1048576f;
2384 boolean showMemoryUsage = false;
2388 java.text.NumberFormat df;
2390 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2393 public MyDesktopPane(boolean showMemoryUsage)
2395 showMemoryUsage(showMemoryUsage);
2398 public void showMemoryUsage(boolean showMemory)
2400 this.showMemoryUsage = showMemory;
2403 Thread worker = new Thread(this);
2409 public boolean isShowMemoryUsage()
2411 return showMemoryUsage;
2417 df = java.text.NumberFormat.getNumberInstance();
2418 df.setMaximumFractionDigits(2);
2419 runtime = Runtime.getRuntime();
2421 while (showMemoryUsage)
2425 maxMemory = runtime.maxMemory() / ONE_MB;
2426 allocatedMemory = runtime.totalMemory() / ONE_MB;
2427 freeMemory = runtime.freeMemory() / ONE_MB;
2428 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2430 percentUsage = (totalFreeMemory / maxMemory) * 100;
2432 // if (percentUsage < 20)
2434 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2436 // instance.set.setBorder(border1);
2439 // sleep after showing usage
2441 } catch (Exception ex)
2443 ex.printStackTrace();
2449 public void paintComponent(Graphics g)
2451 if (showMemoryUsage && g != null && df != null)
2453 if (percentUsage < 20)
2455 g.setColor(Color.red);
2457 FontMetrics fm = g.getFontMetrics();
2460 g.drawString(MessageManager.formatMessage("label.memory_stats",
2462 { df.format(totalFreeMemory), df.format(maxMemory),
2463 df.format(percentUsage) }),
2464 10, getHeight() - fm.getHeight());
2468 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2469 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2474 * Accessor method to quickly get all the AlignmentFrames loaded.
2476 * @return an array of AlignFrame, or null if none found
2478 public static AlignFrame[] getAlignFrames()
2480 if (Jalview.isHeadlessMode())
2482 // Desktop.desktop is null in headless mode
2483 return new AlignFrame[] { Jalview.currentAlignFrame };
2486 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2492 List<AlignFrame> avp = new ArrayList<>();
2494 for (int i = frames.length - 1; i > -1; i--)
2496 if (frames[i] instanceof AlignFrame)
2498 avp.add((AlignFrame) frames[i]);
2500 else if (frames[i] instanceof SplitFrame)
2503 * Also check for a split frame containing an AlignFrame
2505 GSplitFrame sf = (GSplitFrame) frames[i];
2506 if (sf.getTopFrame() instanceof AlignFrame)
2508 avp.add((AlignFrame) sf.getTopFrame());
2510 if (sf.getBottomFrame() instanceof AlignFrame)
2512 avp.add((AlignFrame) sf.getBottomFrame());
2516 if (avp.size() == 0)
2520 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2525 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2529 public GStructureViewer[] getJmols()
2531 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2537 List<GStructureViewer> avp = new ArrayList<>();
2539 for (int i = frames.length - 1; i > -1; i--)
2541 if (frames[i] instanceof AppJmol)
2543 GStructureViewer af = (GStructureViewer) frames[i];
2547 if (avp.size() == 0)
2551 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2556 * Add Groovy Support to Jalview
2559 public void groovyShell_actionPerformed()
2563 openGroovyConsole();
2564 } catch (Exception ex)
2566 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2567 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2569 MessageManager.getString("label.couldnt_create_groovy_shell"),
2570 MessageManager.getString("label.groovy_support_failed"),
2571 JvOptionPane.ERROR_MESSAGE);
2576 * Open the Groovy console
2578 void openGroovyConsole()
2580 if (groovyConsole == null)
2582 groovyConsole = new groovy.ui.Console();
2583 groovyConsole.setVariable("Jalview", this);
2584 groovyConsole.run();
2587 * We allow only one console at a time, so that AlignFrame menu option
2588 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2589 * enable 'Run script', when the console is opened, and the reverse when it is
2592 Window window = (Window) groovyConsole.getFrame();
2593 window.addWindowListener(new WindowAdapter()
2596 public void windowClosed(WindowEvent e)
2599 * rebind CMD-Q from Groovy Console to Jalview Quit
2602 enableExecuteGroovy(false);
2608 * show Groovy console window (after close and reopen)
2610 ((Window) groovyConsole.getFrame()).setVisible(true);
2613 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2614 * opening a second console
2616 enableExecuteGroovy(true);
2620 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2621 * binding when opened
2623 protected void addQuitHandler()
2626 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2628 .getKeyStroke(KeyEvent.VK_Q,
2629 jalview.util.ShortcutKeyMaskExWrapper
2630 .getMenuShortcutKeyMaskEx()),
2632 getRootPane().getActionMap().put("Quit", new AbstractAction()
2635 public void actionPerformed(ActionEvent e)
2643 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2646 * true if Groovy console is open
2648 public void enableExecuteGroovy(boolean enabled)
2651 * disable opening a second Groovy console (or re-enable when the console is
2654 groovyShell.setEnabled(!enabled);
2656 AlignFrame[] alignFrames = getAlignFrames();
2657 if (alignFrames != null)
2659 for (AlignFrame af : alignFrames)
2661 af.setGroovyEnabled(enabled);
2667 * Progress bars managed by the IProgressIndicator method.
2669 private Hashtable<Long, JPanel> progressBars;
2671 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2676 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2679 public void setProgressBar(String message, long id)
2681 if (progressBars == null)
2683 progressBars = new Hashtable<>();
2684 progressBarHandlers = new Hashtable<>();
2687 if (progressBars.get(Long.valueOf(id)) != null)
2689 JPanel panel = progressBars.remove(Long.valueOf(id));
2690 if (progressBarHandlers.contains(Long.valueOf(id)))
2692 progressBarHandlers.remove(Long.valueOf(id));
2694 removeProgressPanel(panel);
2698 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2705 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2706 * jalview.gui.IProgressIndicatorHandler)
2709 public void registerHandler(final long id,
2710 final IProgressIndicatorHandler handler)
2712 if (progressBarHandlers == null
2713 || !progressBars.containsKey(Long.valueOf(id)))
2715 throw new Error(MessageManager.getString(
2716 "error.call_setprogressbar_before_registering_handler"));
2718 progressBarHandlers.put(Long.valueOf(id), handler);
2719 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2720 if (handler.canCancel())
2722 JButton cancel = new JButton(
2723 MessageManager.getString("action.cancel"));
2724 final IProgressIndicator us = this;
2725 cancel.addActionListener(new ActionListener()
2729 public void actionPerformed(ActionEvent e)
2731 handler.cancelActivity(id);
2732 us.setProgressBar(MessageManager
2733 .formatMessage("label.cancelled_params", new Object[]
2734 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2738 progressPanel.add(cancel, BorderLayout.EAST);
2744 * @return true if any progress bars are still active
2747 public boolean operationInProgress()
2749 if (progressBars != null && progressBars.size() > 0)
2757 * This will return the first AlignFrame holding the given viewport instance.
2758 * It will break if there are more than one AlignFrames viewing a particular
2762 * @return alignFrame for viewport
2764 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2766 if (desktop != null)
2768 AlignmentPanel[] aps = getAlignmentPanels(
2769 viewport.getSequenceSetId());
2770 for (int panel = 0; aps != null && panel < aps.length; panel++)
2772 if (aps[panel] != null && aps[panel].av == viewport)
2774 return aps[panel].alignFrame;
2781 public VamsasApplication getVamsasApplication()
2783 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2789 * flag set if jalview GUI is being operated programmatically
2791 private boolean inBatchMode = false;
2794 * check if jalview GUI is being operated programmatically
2796 * @return inBatchMode
2798 public boolean isInBatchMode()
2804 * set flag if jalview GUI is being operated programmatically
2806 * @param inBatchMode
2808 public void setInBatchMode(boolean inBatchMode)
2810 this.inBatchMode = inBatchMode;
2814 * start service discovery and wait till it is done
2816 public void startServiceDiscovery()
2818 startServiceDiscovery(false);
2822 * start service discovery threads - blocking or non-blocking
2826 public void startServiceDiscovery(boolean blocking)
2828 startServiceDiscovery(blocking, false);
2832 * start service discovery threads
2835 * - false means call returns immediately
2836 * @param ignore_SHOW_JWS2_SERVICES_preference
2837 * - when true JABA services are discovered regardless of user's JWS2
2838 * discovery preference setting
2840 public void startServiceDiscovery(boolean blocking,
2841 boolean ignore_SHOW_JWS2_SERVICES_preference)
2843 boolean alive = true;
2844 Thread t0 = null, t1 = null, t2 = null;
2845 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2848 // todo: changesupport handlers need to be transferred
2849 if (discoverer == null)
2851 discoverer = new jalview.ws.jws1.Discoverer();
2852 // register PCS handler for desktop.
2853 discoverer.addPropertyChangeListener(changeSupport);
2855 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2856 // until we phase out completely
2857 (t0 = new Thread(discoverer)).start();
2860 if (ignore_SHOW_JWS2_SERVICES_preference
2861 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2863 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2864 .startDiscoverer(changeSupport);
2868 // TODO: do rest service discovery
2877 } catch (Exception e)
2880 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2881 || (t3 != null && t3.isAlive())
2882 || (t0 != null && t0.isAlive());
2888 * called to check if the service discovery process completed successfully.
2892 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2894 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2896 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2897 .getErrorMessages();
2900 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2902 if (serviceChangedDialog == null)
2904 // only run if we aren't already displaying one of these.
2905 addDialogThread(serviceChangedDialog = new Runnable()
2912 * JalviewDialog jd =new JalviewDialog() {
2914 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2916 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2918 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2920 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2922 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2923 * + " or mis-configured HTTP proxy settings.<br/>" +
2924 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2925 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2926 * true, true, "Web Service Configuration Problem", 450, 400);
2928 * jd.waitForInput();
2930 JvOptionPane.showConfirmDialog(Desktop.desktop,
2931 new JLabel("<html><table width=\"450\"><tr><td>"
2932 + ermsg + "</td></tr></table>"
2933 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2934 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2935 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2936 + " Tools->Preferences dialog box to change them.</p></html>"),
2937 "Web Service Configuration Problem",
2938 JvOptionPane.DEFAULT_OPTION,
2939 JvOptionPane.ERROR_MESSAGE);
2940 serviceChangedDialog = null;
2948 jalview.bin.Console.error(
2949 "Errors reported by JABA discovery service. Check web services preferences.\n"
2956 private Runnable serviceChangedDialog = null;
2959 * start a thread to open a URL in the configured browser. Pops up a warning
2960 * dialog to the user if there is an exception when calling out to the browser
2965 public static void showUrl(final String url)
2967 showUrl(url, Desktop.instance);
2971 * Like showUrl but allows progress handler to be specified
2975 * (null) or object implementing IProgressIndicator
2977 public static void showUrl(final String url,
2978 final IProgressIndicator progress)
2980 new Thread(new Runnable()
2987 if (progress != null)
2989 progress.setProgressBar(MessageManager
2990 .formatMessage("status.opening_params", new Object[]
2991 { url }), this.hashCode());
2993 jalview.util.BrowserLauncher.openURL(url);
2994 } catch (Exception ex)
2996 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2998 .getString("label.web_browser_not_found_unix"),
2999 MessageManager.getString("label.web_browser_not_found"),
3000 JvOptionPane.WARNING_MESSAGE);
3002 ex.printStackTrace();
3004 if (progress != null)
3006 progress.setProgressBar(null, this.hashCode());
3012 public static WsParamSetManager wsparamManager = null;
3014 public static ParamManager getUserParameterStore()
3016 if (wsparamManager == null)
3018 wsparamManager = new WsParamSetManager();
3020 return wsparamManager;
3024 * static hyperlink handler proxy method for use by Jalview's internal windows
3028 public static void hyperlinkUpdate(HyperlinkEvent e)
3030 if (e.getEventType() == EventType.ACTIVATED)
3035 url = e.getURL().toString();
3036 Desktop.showUrl(url);
3037 } catch (Exception x)
3042 .error("Couldn't handle string " + url + " as a URL.");
3044 // ignore any exceptions due to dud links.
3051 * single thread that handles display of dialogs to user.
3053 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3056 * flag indicating if dialogExecutor should try to acquire a permit
3058 private volatile boolean dialogPause = true;
3063 private java.util.concurrent.Semaphore block = new Semaphore(0);
3065 private static groovy.ui.Console groovyConsole;
3068 * add another dialog thread to the queue
3072 public void addDialogThread(final Runnable prompter)
3074 dialogExecutor.submit(new Runnable()
3084 } catch (InterruptedException x)
3088 if (instance == null)
3094 SwingUtilities.invokeAndWait(prompter);
3095 } catch (Exception q)
3097 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3104 public void startDialogQueue()
3106 // set the flag so we don't pause waiting for another permit and semaphore
3107 // the current task to begin
3108 dialogPause = false;
3113 * Outputs an image of the desktop to file in EPS format, after prompting the
3114 * user for choice of Text or Lineart character rendering (unless a preference
3115 * has been set). The file name is generated as
3118 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3122 protected void snapShotWindow_actionPerformed(ActionEvent e)
3124 // currently the menu option to do this is not shown
3127 int width = getWidth();
3128 int height = getHeight();
3130 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3131 ImageWriterI writer = new ImageWriterI()
3134 public void exportImage(Graphics g) throws Exception
3137 jalview.bin.Console.info("Successfully written snapshot to file "
3138 + of.getAbsolutePath());
3141 String title = "View of desktop";
3142 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3146 exporter.doExport(of, this, width, height, title);
3147 } catch (ImageOutputException ioex)
3149 jalview.bin.Console.error(
3150 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3156 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3157 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3158 * and location last time the view was expanded (if any). However it does not
3159 * remember the split pane divider location - this is set to match the
3160 * 'exploding' frame.
3164 public void explodeViews(SplitFrame sf)
3166 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3167 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3168 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3170 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3172 int viewCount = topPanels.size();
3179 * Processing in reverse order works, forwards order leaves the first panels not
3180 * visible. I don't know why!
3182 for (int i = viewCount - 1; i >= 0; i--)
3185 * Make new top and bottom frames. These take over the respective AlignmentPanel
3186 * objects, including their AlignmentViewports, so the cdna/protein
3187 * relationships between the viewports is carried over to the new split frames.
3189 * explodedGeometry holds the (x, y) position of the previously exploded
3190 * SplitFrame, and the (width, height) of the AlignFrame component
3192 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3193 AlignFrame newTopFrame = new AlignFrame(topPanel);
3194 newTopFrame.setSize(oldTopFrame.getSize());
3195 newTopFrame.setVisible(true);
3196 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3197 .getExplodedGeometry();
3198 if (geometry != null)
3200 newTopFrame.setSize(geometry.getSize());
3203 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3204 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3205 newBottomFrame.setSize(oldBottomFrame.getSize());
3206 newBottomFrame.setVisible(true);
3207 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3208 .getExplodedGeometry();
3209 if (geometry != null)
3211 newBottomFrame.setSize(geometry.getSize());
3214 topPanel.av.setGatherViewsHere(false);
3215 bottomPanel.av.setGatherViewsHere(false);
3216 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3218 if (geometry != null)
3220 splitFrame.setLocation(geometry.getLocation());
3222 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3226 * Clear references to the panels (now relocated in the new SplitFrames) before
3227 * closing the old SplitFrame.
3230 bottomPanels.clear();
3235 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3236 * back into the given SplitFrame as additional views. Note that the gathered
3237 * frames may themselves have multiple views.
3241 public void gatherViews(GSplitFrame source)
3244 * special handling of explodedGeometry for a view within a SplitFrame: - it
3245 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3246 * height) of the AlignFrame component
3248 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3249 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3250 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3251 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3252 myBottomFrame.viewport
3253 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3254 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3255 myTopFrame.viewport.setGatherViewsHere(true);
3256 myBottomFrame.viewport.setGatherViewsHere(true);
3257 String topViewId = myTopFrame.viewport.getSequenceSetId();
3258 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3260 JInternalFrame[] frames = desktop.getAllFrames();
3261 for (JInternalFrame frame : frames)
3263 if (frame instanceof SplitFrame && frame != source)
3265 SplitFrame sf = (SplitFrame) frame;
3266 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3267 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3268 boolean gatherThis = false;
3269 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3271 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3272 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3273 if (topViewId.equals(topPanel.av.getSequenceSetId())
3274 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3277 topPanel.av.setGatherViewsHere(false);
3278 bottomPanel.av.setGatherViewsHere(false);
3279 topPanel.av.setExplodedGeometry(
3280 new Rectangle(sf.getLocation(), topFrame.getSize()));
3281 bottomPanel.av.setExplodedGeometry(
3282 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3283 myTopFrame.addAlignmentPanel(topPanel, false);
3284 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3290 topFrame.getAlignPanels().clear();
3291 bottomFrame.getAlignPanels().clear();
3298 * The dust settles...give focus to the tab we did this from.
3300 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3303 public static groovy.ui.Console getGroovyConsole()
3305 return groovyConsole;
3309 * handles the payload of a drag and drop event.
3311 * TODO refactor to desktop utilities class
3314 * - Data source strings extracted from the drop event
3316 * - protocol for each data source extracted from the drop event
3320 * - the payload from the drop event
3323 public static void transferFromDropTarget(List<Object> files,
3324 List<DataSourceType> protocols, DropTargetDropEvent evt,
3325 Transferable t) throws Exception
3328 DataFlavor uriListFlavor = new DataFlavor(
3329 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3332 urlFlavour = new DataFlavor(
3333 "application/x-java-url; class=java.net.URL");
3334 } catch (ClassNotFoundException cfe)
3336 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3340 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3345 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3346 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3347 // means url may be null.
3350 protocols.add(DataSourceType.URL);
3351 files.add(url.toString());
3352 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3353 + files.get(files.size() - 1));
3358 if (Platform.isAMacAndNotJS())
3361 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3364 } catch (Throwable ex)
3366 jalview.bin.Console.debug("URL drop handler failed.", ex);
3369 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3371 // Works on Windows and MacOSX
3372 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3373 for (Object file : (List) t
3374 .getTransferData(DataFlavor.javaFileListFlavor))
3377 protocols.add(DataSourceType.FILE);
3382 // Unix like behaviour
3383 boolean added = false;
3385 if (t.isDataFlavorSupported(uriListFlavor))
3387 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3388 // This is used by Unix drag system
3389 data = (String) t.getTransferData(uriListFlavor);
3393 // fallback to text: workaround - on OSX where there's a JVM bug
3395 .debug("standard URIListFlavor failed. Trying text");
3396 // try text fallback
3397 DataFlavor textDf = new DataFlavor(
3398 "text/plain;class=java.lang.String");
3399 if (t.isDataFlavorSupported(textDf))
3401 data = (String) t.getTransferData(textDf);
3404 jalview.bin.Console.debug("Plain text drop content returned "
3405 + (data == null ? "Null - failed" : data));
3410 while (protocols.size() < files.size())
3412 jalview.bin.Console.debug("Adding missing FILE protocol for "
3413 + files.get(protocols.size()));
3414 protocols.add(DataSourceType.FILE);
3416 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3417 data, "\r\n"); st.hasMoreTokens();)
3420 String s = st.nextToken();
3421 if (s.startsWith("#"))
3423 // the line is a comment (as per the RFC 2483)
3426 java.net.URI uri = new java.net.URI(s);
3427 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3429 protocols.add(DataSourceType.URL);
3430 files.add(uri.toString());
3434 // otherwise preserve old behaviour: catch all for file objects
3435 java.io.File file = new java.io.File(uri);
3436 protocols.add(DataSourceType.FILE);
3437 files.add(file.toString());
3442 if (jalview.bin.Console.isDebugEnabled())
3444 if (data == null || !added)
3447 if (t.getTransferDataFlavors() != null
3448 && t.getTransferDataFlavors().length > 0)
3450 jalview.bin.Console.debug(
3451 "Couldn't resolve drop data. Here are the supported flavors:");
3452 for (DataFlavor fl : t.getTransferDataFlavors())
3454 jalview.bin.Console.debug(
3455 "Supported transfer dataflavor: " + fl.toString());
3456 Object df = t.getTransferData(fl);
3459 jalview.bin.Console.debug("Retrieves: " + df);
3463 jalview.bin.Console.debug("Retrieved nothing");
3470 .debug("Couldn't resolve dataflavor for drop: "
3476 if (Platform.isWindowsAndNotJS())
3479 .debug("Scanning dropped content for Windows Link Files");
3481 // resolve any .lnk files in the file drop
3482 for (int f = 0; f < files.size(); f++)
3484 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3485 if (protocols.get(f).equals(DataSourceType.FILE)
3486 && (source.endsWith(".lnk") || source.endsWith(".url")
3487 || source.endsWith(".site")))
3491 Object obj = files.get(f);
3492 File lf = (obj instanceof File ? (File) obj
3493 : new File((String) obj));
3494 // process link file to get a URL
3495 jalview.bin.Console.debug("Found potential link file: " + lf);
3496 WindowsShortcut wscfile = new WindowsShortcut(lf);
3497 String fullname = wscfile.getRealFilename();
3498 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3499 files.set(f, fullname);
3500 jalview.bin.Console.debug("Parsed real filename " + fullname
3501 + " to extract protocol: " + protocols.get(f));
3502 } catch (Exception ex)
3504 jalview.bin.Console.error(
3505 "Couldn't parse " + files.get(f) + " as a link file.",
3514 * Sets the Preferences property for experimental features to True or False
3515 * depending on the state of the controlling menu item
3518 protected void showExperimental_actionPerformed(boolean selected)
3520 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3524 * Answers a (possibly empty) list of any structure viewer frames (currently
3525 * for either Jmol or Chimera) which are currently open. This may optionally
3526 * be restricted to viewers of a specified class, or viewers linked to a
3527 * specified alignment panel.
3530 * if not null, only return viewers linked to this panel
3531 * @param structureViewerClass
3532 * if not null, only return viewers of this class
3535 public List<StructureViewerBase> getStructureViewers(
3536 AlignmentPanel apanel,
3537 Class<? extends StructureViewerBase> structureViewerClass)
3539 List<StructureViewerBase> result = new ArrayList<>();
3540 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3542 for (JInternalFrame frame : frames)
3544 if (frame instanceof StructureViewerBase)
3546 if (structureViewerClass == null
3547 || structureViewerClass.isInstance(frame))
3550 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3552 result.add((StructureViewerBase) frame);
3560 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3562 private static boolean debugScaleMessageDone = false;
3564 public static void debugScaleMessage(Graphics g)
3566 if (debugScaleMessageDone)
3570 // output used by tests to check HiDPI scaling settings in action
3573 Graphics2D gg = (Graphics2D) g;
3576 AffineTransform t = gg.getTransform();
3577 double scaleX = t.getScaleX();
3578 double scaleY = t.getScaleY();
3579 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3580 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3581 debugScaleMessageDone = true;
3585 jalview.bin.Console.debug("Desktop graphics null");
3587 } catch (Exception e)
3589 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3594 * closes the current instance window, disposes and forgets about it.
3596 public static void closeDesktop()
3598 if (Desktop.instance != null)
3600 Desktop.instance.closeAll_actionPerformed(null);
3601 Desktop.instance.setVisible(false);
3602 Desktop.instance.dispose();
3603 Desktop.instance = null;