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;
54 import java.beans.PropertyVetoException;
56 import java.io.FileWriter;
57 import java.io.IOException;
58 import java.lang.reflect.Field;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashMap;
63 import java.util.Hashtable;
64 import java.util.List;
65 import java.util.ListIterator;
66 import java.util.Locale;
68 import java.util.Vector;
69 import java.util.concurrent.ExecutorService;
70 import java.util.concurrent.Executors;
71 import java.util.concurrent.Semaphore;
73 import javax.swing.AbstractAction;
74 import javax.swing.Action;
75 import javax.swing.ActionMap;
76 import javax.swing.Box;
77 import javax.swing.BoxLayout;
78 import javax.swing.DefaultDesktopManager;
79 import javax.swing.DesktopManager;
80 import javax.swing.InputMap;
81 import javax.swing.JButton;
82 import javax.swing.JCheckBox;
83 import javax.swing.JComboBox;
84 import javax.swing.JComponent;
85 import javax.swing.JDesktopPane;
86 import javax.swing.JFrame;
87 import javax.swing.JInternalFrame;
88 import javax.swing.JLabel;
89 import javax.swing.JMenuItem;
90 import javax.swing.JPanel;
91 import javax.swing.JPopupMenu;
92 import javax.swing.JProgressBar;
93 import javax.swing.JTextField;
94 import javax.swing.KeyStroke;
95 import javax.swing.SwingUtilities;
96 import javax.swing.WindowConstants;
97 import javax.swing.event.HyperlinkEvent;
98 import javax.swing.event.HyperlinkEvent.EventType;
99 import javax.swing.event.InternalFrameAdapter;
100 import javax.swing.event.InternalFrameEvent;
102 import org.stackoverflowusers.file.WindowsShortcut;
104 import jalview.api.AlignViewportI;
105 import jalview.api.AlignmentViewPanel;
106 import jalview.api.structures.JalviewStructureDisplayI;
107 import jalview.bin.Cache;
108 import jalview.bin.Jalview;
109 import jalview.datamodel.Alignment;
110 import jalview.datamodel.HiddenColumns;
111 import jalview.datamodel.Sequence;
112 import jalview.datamodel.SequenceI;
113 import jalview.gui.ImageExporter.ImageWriterI;
114 import jalview.gui.QuitHandler.QResponse;
115 import jalview.io.BackupFiles;
116 import jalview.io.DataSourceType;
117 import jalview.io.FileFormat;
118 import jalview.io.FileFormatException;
119 import jalview.io.FileFormatI;
120 import jalview.io.FileFormats;
121 import jalview.io.FileLoader;
122 import jalview.io.FormatAdapter;
123 import jalview.io.IdentifyFile;
124 import jalview.io.JalviewFileChooser;
125 import jalview.io.JalviewFileView;
126 import jalview.io.exceptions.ImageOutputException;
127 import jalview.jbgui.GSplitFrame;
128 import jalview.jbgui.GStructureViewer;
129 import jalview.project.Jalview2XML;
130 import jalview.structure.StructureSelectionManager;
131 import jalview.urls.IdOrgSettings;
132 import jalview.util.BrowserLauncher;
133 import jalview.util.ChannelProperties;
134 import jalview.util.ImageMaker.TYPE;
135 import jalview.util.LaunchUtils;
136 import jalview.util.MessageManager;
137 import jalview.util.Platform;
138 import jalview.util.ShortcutKeyMaskExWrapper;
139 import jalview.util.UrlConstants;
140 import jalview.viewmodel.AlignmentViewport;
141 import jalview.ws.params.ParamManager;
142 import jalview.ws.utils.UrlDownloadClient;
149 * @version $Revision: 1.155 $
151 public class Desktop extends jalview.jbgui.GDesktop
152 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
153 jalview.api.StructureSelectionManagerProvider
155 private static final String CITATION;
158 URL bg_logo_url = ChannelProperties.getImageURL(
159 "bg_logo." + String.valueOf(SplashScreen.logoSize));
160 URL uod_logo_url = ChannelProperties.getImageURL(
161 "uod_banner." + String.valueOf(SplashScreen.logoSize));
162 boolean logo = (bg_logo_url != null || uod_logo_url != null);
163 StringBuilder sb = new StringBuilder();
165 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
170 sb.append(bg_logo_url == null ? ""
171 : "<img alt=\"Barton Group logo\" src=\""
172 + bg_logo_url.toString() + "\">");
173 sb.append(uod_logo_url == null ? ""
174 : " <img alt=\"University of Dundee shield\" src=\""
175 + uod_logo_url.toString() + "\">");
177 "<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>");
178 sb.append("<br><br>If you use Jalview, please cite:"
179 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
180 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
181 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
182 CITATION = sb.toString();
185 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
187 private static int DEFAULT_MIN_WIDTH = 300;
189 private static int DEFAULT_MIN_HEIGHT = 250;
191 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
193 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
195 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
197 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
199 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
201 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
203 public static void setLiveDragMode(boolean b)
205 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
206 : JDesktopPane.OUTLINE_DRAG_MODE;
208 desktop.setDragMode(DRAG_MODE);
211 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
213 public static boolean nosplash = false;
216 * news reader - null if it was never started.
218 private BlogReader jvnews = null;
220 private File projectFile;
224 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
226 public void addJalviewPropertyChangeListener(
227 PropertyChangeListener listener)
229 changeSupport.addJalviewPropertyChangeListener(listener);
233 * @param propertyName
235 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
236 * java.beans.PropertyChangeListener)
238 public void addJalviewPropertyChangeListener(String propertyName,
239 PropertyChangeListener listener)
241 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
245 * @param propertyName
247 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
248 * java.beans.PropertyChangeListener)
250 public void removeJalviewPropertyChangeListener(String propertyName,
251 PropertyChangeListener listener)
253 changeSupport.removeJalviewPropertyChangeListener(propertyName,
257 /** Singleton Desktop instance */
258 public static Desktop instance;
260 public static MyDesktopPane desktop;
262 public static MyDesktopPane getDesktop()
264 // BH 2018 could use currentThread() here as a reference to a
265 // Hashtable<Thread, MyDesktopPane> in JavaScript
269 static int openFrameCount = 0;
271 static final int xOffset = 30;
273 static final int yOffset = 30;
275 public static jalview.ws.jws1.Discoverer discoverer;
277 public static Object[] jalviewClipboard;
279 public static boolean internalCopy = false;
281 static int fileLoadingCount = 0;
283 class MyDesktopManager implements DesktopManager
286 private DesktopManager delegate;
288 public MyDesktopManager(DesktopManager delegate)
290 this.delegate = delegate;
294 public void activateFrame(JInternalFrame f)
298 delegate.activateFrame(f);
299 } catch (NullPointerException npe)
301 Point p = getMousePosition();
302 instance.showPasteMenu(p.x, p.y);
307 public void beginDraggingFrame(JComponent f)
309 delegate.beginDraggingFrame(f);
313 public void beginResizingFrame(JComponent f, int direction)
315 delegate.beginResizingFrame(f, direction);
319 public void closeFrame(JInternalFrame f)
321 delegate.closeFrame(f);
325 public void deactivateFrame(JInternalFrame f)
327 delegate.deactivateFrame(f);
331 public void deiconifyFrame(JInternalFrame f)
333 delegate.deiconifyFrame(f);
337 public void dragFrame(JComponent f, int newX, int newY)
343 delegate.dragFrame(f, newX, newY);
347 public void endDraggingFrame(JComponent f)
349 delegate.endDraggingFrame(f);
354 public void endResizingFrame(JComponent f)
356 delegate.endResizingFrame(f);
361 public void iconifyFrame(JInternalFrame f)
363 delegate.iconifyFrame(f);
367 public void maximizeFrame(JInternalFrame f)
369 delegate.maximizeFrame(f);
373 public void minimizeFrame(JInternalFrame f)
375 delegate.minimizeFrame(f);
379 public void openFrame(JInternalFrame f)
381 delegate.openFrame(f);
385 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
392 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
396 public void setBoundsForFrame(JComponent f, int newX, int newY,
397 int newWidth, int newHeight)
399 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
402 // All other methods, simply delegate
407 * Creates a new Desktop object.
413 * A note to implementors. It is ESSENTIAL that any activities that might
414 * block are spawned off as threads rather than waited for during this
419 doConfigureStructurePrefs();
420 setTitle(ChannelProperties.getProperty("app_name") + " "
421 + Cache.getProperty("VERSION"));
424 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
425 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
426 * officially documented or guaranteed to exist, so we access it via
427 * reflection. There appear to be unfathomable criteria about what this
428 * string can contain, and it if doesn't meet those criteria then "java"
429 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
430 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
431 * not. The reflection access may generate a warning: WARNING: An illegal
432 * reflective access operation has occurred WARNING: Illegal reflective
433 * access by jalview.gui.Desktop () to field
434 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
436 if (Platform.isLinux())
438 if (LaunchUtils.getJavaVersion() >= 11)
441 * Send this message to stderr as the warning that follows (due to
442 * reflection) also goes to stderr.
445 "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.");
447 final String awtAppClassName = "awtAppClassName";
450 Toolkit xToolkit = Toolkit.getDefaultToolkit();
451 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
452 Field awtAppClassNameField = null;
454 if (Arrays.stream(declaredFields)
455 .anyMatch(f -> f.getName().equals(awtAppClassName)))
457 awtAppClassNameField = xToolkit.getClass()
458 .getDeclaredField(awtAppClassName);
461 String title = ChannelProperties.getProperty("app_name");
462 if (awtAppClassNameField != null)
464 awtAppClassNameField.setAccessible(true);
465 awtAppClassNameField.set(xToolkit, title);
470 .debug("XToolkit: " + awtAppClassName + " not found");
472 } catch (Exception e)
474 jalview.bin.Console.debug("Error setting " + awtAppClassName);
475 jalview.bin.Console.trace(Cache.getStackTraceString(e));
479 setIconImages(ChannelProperties.getIconList());
481 // override quit handling when GUI OS close [X] button pressed
482 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
483 addWindowListener(new WindowAdapter()
486 public void windowClosing(WindowEvent ev)
488 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
492 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
494 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
495 desktop = new MyDesktopPane(selmemusage);
497 showMemusage.setSelected(selmemusage);
498 desktop.setBackground(Color.white);
500 getContentPane().setLayout(new BorderLayout());
501 // alternate config - have scrollbars - see notes in JAL-153
502 // JScrollPane sp = new JScrollPane();
503 // sp.getViewport().setView(desktop);
504 // getContentPane().add(sp, BorderLayout.CENTER);
506 // BH 2018 - just an experiment to try unclipped JInternalFrames.
509 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
512 getContentPane().add(desktop, BorderLayout.CENTER);
513 desktop.setDragMode(DRAG_MODE);
515 // This line prevents Windows Look&Feel resizing all new windows to maximum
516 // if previous window was maximised
517 desktop.setDesktopManager(new MyDesktopManager(
518 Platform.isJS() ? desktop.getDesktopManager()
519 : new DefaultDesktopManager()));
521 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
522 : Platform.isAMacAndNotJS()
523 ? new AquaInternalFrameManager(
524 desktop.getDesktopManager())
525 : desktop.getDesktopManager())));
528 Rectangle dims = getLastKnownDimensions("");
535 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
536 int xPos = Math.max(5, (screenSize.width - 900) / 2);
537 int yPos = Math.max(5, (screenSize.height - 650) / 2);
538 setBounds(xPos, yPos, 900, 650);
541 // start dialogue queue for single dialogues
544 if (!Platform.isJS())
551 jconsole = new Console(this, showjconsole);
552 jconsole.setHeader(Cache.getVersionDetailsForConsole());
553 showConsole(showjconsole);
555 showNews.setVisible(false);
557 experimentalFeatures.setSelected(showExperimental());
559 getIdentifiersOrgData();
563 // Spawn a thread that shows the splashscreen
566 SwingUtilities.invokeLater(new Runnable()
571 new SplashScreen(true);
576 // Thread off a new instance of the file chooser - this reduces the time
578 // takes to open it later on.
579 new Thread(new Runnable()
584 jalview.bin.Console.debug("Filechooser init thread started.");
585 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
586 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
588 jalview.bin.Console.debug("Filechooser init thread finished.");
591 // Add the service change listener
592 changeSupport.addJalviewPropertyChangeListener("services",
593 new PropertyChangeListener()
597 public void propertyChange(PropertyChangeEvent evt)
600 .debug("Firing service changed event for "
601 + evt.getNewValue());
602 JalviewServicesChanged(evt);
607 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
610 this.addMouseListener(ma = new MouseAdapter()
613 public void mousePressed(MouseEvent evt)
615 if (evt.isPopupTrigger()) // Mac
617 showPasteMenu(evt.getX(), evt.getY());
622 public void mouseReleased(MouseEvent evt)
624 if (evt.isPopupTrigger()) // Windows
626 showPasteMenu(evt.getX(), evt.getY());
630 desktop.addMouseListener(ma);
634 // used for jalviewjsTest
635 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
640 * Answers true if user preferences to enable experimental features is True
645 public boolean showExperimental()
647 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
648 Boolean.FALSE.toString());
649 return Boolean.valueOf(experimental).booleanValue();
652 public void doConfigureStructurePrefs()
654 // configure services
655 StructureSelectionManager ssm = StructureSelectionManager
656 .getStructureSelectionManager(this);
657 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
659 ssm.setAddTempFacAnnot(
660 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
661 ssm.setProcessSecondaryStructure(
662 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
663 // JAL-3915 - RNAView is no longer an option so this has no effect
664 ssm.setSecStructServices(
665 Cache.getDefault(Preferences.USE_RNAVIEW, false));
669 ssm.setAddTempFacAnnot(false);
670 ssm.setProcessSecondaryStructure(false);
671 ssm.setSecStructServices(false);
675 public void checkForNews()
677 final Desktop me = this;
678 // Thread off the news reader, in case there are connection problems.
679 new Thread(new Runnable()
684 jalview.bin.Console.debug("Starting news thread.");
685 jvnews = new BlogReader(me);
686 showNews.setVisible(true);
687 jalview.bin.Console.debug("Completed news thread.");
692 public void getIdentifiersOrgData()
694 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
695 {// Thread off the identifiers fetcher
696 new Thread(new Runnable()
702 .debug("Downloading data from identifiers.org");
705 UrlDownloadClient.download(IdOrgSettings.getUrl(),
706 IdOrgSettings.getDownloadLocation());
707 } catch (IOException e)
710 .debug("Exception downloading identifiers.org data"
720 protected void showNews_actionPerformed(ActionEvent e)
722 showNews(showNews.isSelected());
725 void showNews(boolean visible)
727 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
728 showNews.setSelected(visible);
729 if (visible && !jvnews.isVisible())
731 new Thread(new Runnable()
736 long now = System.currentTimeMillis();
737 Desktop.instance.setProgressBar(
738 MessageManager.getString("status.refreshing_news"), now);
739 jvnews.refreshNews();
740 Desktop.instance.setProgressBar(null, now);
748 * recover the last known dimensions for a jalview window
751 * - empty string is desktop, all other windows have unique prefix
752 * @return null or last known dimensions scaled to current geometry (if last
753 * window geom was known)
755 Rectangle getLastKnownDimensions(String windowName)
757 // TODO: lock aspect ratio for scaling desktop Bug #0058199
758 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
759 String x = Cache.getProperty(windowName + "SCREEN_X");
760 String y = Cache.getProperty(windowName + "SCREEN_Y");
761 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
762 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
763 if ((x != null) && (y != null) && (width != null) && (height != null))
765 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
766 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
767 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
769 // attempt #1 - try to cope with change in screen geometry - this
770 // version doesn't preserve original jv aspect ratio.
771 // take ratio of current screen size vs original screen size.
772 double sw = ((1f * screenSize.width) / (1f * Integer
773 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
774 double sh = ((1f * screenSize.height) / (1f * Integer
775 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
776 // rescale the bounds depending upon the current screen geometry.
777 ix = (int) (ix * sw);
778 iw = (int) (iw * sw);
779 iy = (int) (iy * sh);
780 ih = (int) (ih * sh);
781 while (ix >= screenSize.width)
783 jalview.bin.Console.debug(
784 "Window geometry location recall error: shifting horizontal to within screenbounds.");
785 ix -= screenSize.width;
787 while (iy >= screenSize.height)
789 jalview.bin.Console.debug(
790 "Window geometry location recall error: shifting vertical to within screenbounds.");
791 iy -= screenSize.height;
793 jalview.bin.Console.debug(
794 "Got last known dimensions for " + windowName + ": x:" + ix
795 + " y:" + iy + " width:" + iw + " height:" + ih);
797 // return dimensions for new instance
798 return new Rectangle(ix, iy, iw, ih);
803 void showPasteMenu(int x, int y)
805 JPopupMenu popup = new JPopupMenu();
806 JMenuItem item = new JMenuItem(
807 MessageManager.getString("label.paste_new_window"));
808 item.addActionListener(new ActionListener()
811 public void actionPerformed(ActionEvent evt)
818 popup.show(this, x, y);
823 // quick patch for JAL-4150 - needs some more work and test coverage
824 // TODO - unify below and AlignFrame.paste()
825 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
826 // clipboard has come from a different alignment window than the one where
827 // paste has been called! JAL-4151
829 if (Desktop.jalviewClipboard != null)
831 // The clipboard was filled from within Jalview, we must use the
833 // And dataset from the copied alignment
834 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
835 // be doubly sure that we create *new* sequence objects.
836 SequenceI[] sequences = new SequenceI[newseq.length];
837 for (int i = 0; i < newseq.length; i++)
839 sequences[i] = new Sequence(newseq[i]);
841 Alignment alignment = new Alignment(sequences);
842 // dataset is inherited
843 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
844 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
845 AlignFrame.DEFAULT_HEIGHT);
846 String newtitle = new String("Copied sequences");
848 if (Desktop.jalviewClipboard[2] != null)
850 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
851 af.viewport.setHiddenColumns(hc);
854 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
855 AlignFrame.DEFAULT_HEIGHT);
862 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
863 Transferable contents = c.getContents(this);
865 if (contents != null)
867 String file = (String) contents
868 .getTransferData(DataFlavor.stringFlavor);
870 FileFormatI format = new IdentifyFile().identify(file,
871 DataSourceType.PASTE);
873 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
876 } catch (Exception ex)
879 "Unable to paste alignment from system clipboard:\n" + ex);
885 * Adds and opens the given frame to the desktop
896 public static synchronized void addInternalFrame(
897 final JInternalFrame frame, String title, int w, int h)
899 addInternalFrame(frame, title, true, w, h, true, false);
903 * Add an internal frame to the Jalview desktop
910 * When true, display frame immediately, otherwise, caller must call
911 * setVisible themselves.
917 public static synchronized void addInternalFrame(
918 final JInternalFrame frame, String title, boolean makeVisible,
921 addInternalFrame(frame, title, makeVisible, w, h, true, false);
925 * Add an internal frame to the Jalview desktop and make it visible
938 public static synchronized void addInternalFrame(
939 final JInternalFrame frame, String title, int w, int h,
942 addInternalFrame(frame, title, true, w, h, resizable, false);
946 * Add an internal frame to the Jalview desktop
953 * When true, display frame immediately, otherwise, caller must call
954 * setVisible themselves.
961 * @param ignoreMinSize
962 * Do not set the default minimum size for frame
964 public static synchronized void addInternalFrame(
965 final JInternalFrame frame, String title, boolean makeVisible,
966 int w, int h, boolean resizable, boolean ignoreMinSize)
969 // TODO: allow callers to determine X and Y position of frame (eg. via
971 // TODO: consider fixing method to update entries in the window submenu with
972 // the current window title
974 frame.setTitle(title);
975 if (frame.getWidth() < 1 || frame.getHeight() < 1)
979 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
980 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
981 // IF JALVIEW IS RUNNING HEADLESS
982 // ///////////////////////////////////////////////
983 if (instance == null || (System.getProperty("java.awt.headless") != null
984 && System.getProperty("java.awt.headless").equals("true")))
993 frame.setMinimumSize(
994 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
996 // Set default dimension for Alignment Frame window.
997 // The Alignment Frame window could be added from a number of places,
999 // I did this here in order not to miss out on any Alignment frame.
1000 if (frame instanceof AlignFrame)
1002 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1003 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1007 frame.setVisible(makeVisible);
1008 frame.setClosable(true);
1009 frame.setResizable(resizable);
1010 frame.setMaximizable(resizable);
1011 frame.setIconifiable(resizable);
1012 frame.setOpaque(Platform.isJS());
1014 if (frame.getX() < 1 && frame.getY() < 1)
1016 frame.setLocation(xOffset * openFrameCount,
1017 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1021 * add an entry for the new frame in the Window menu (and remove it when the
1024 final JMenuItem menuItem = new JMenuItem(title);
1025 frame.addInternalFrameListener(new InternalFrameAdapter()
1028 public void internalFrameActivated(InternalFrameEvent evt)
1030 JInternalFrame itf = desktop.getSelectedFrame();
1033 if (itf instanceof AlignFrame)
1035 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1042 public void internalFrameClosed(InternalFrameEvent evt)
1044 PaintRefresher.RemoveComponent(frame);
1047 * defensive check to prevent frames being added half off the window
1049 if (openFrameCount > 0)
1055 * ensure no reference to alignFrame retained by menu item listener
1057 if (menuItem.getActionListeners().length > 0)
1059 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1061 windowMenu.remove(menuItem);
1065 menuItem.addActionListener(new ActionListener()
1068 public void actionPerformed(ActionEvent e)
1072 frame.setSelected(true);
1073 frame.setIcon(false);
1074 } catch (java.beans.PropertyVetoException ex)
1081 setKeyBindings(frame);
1085 windowMenu.add(menuItem);
1090 frame.setSelected(true);
1091 frame.requestFocus();
1092 } catch (java.beans.PropertyVetoException ve)
1094 } catch (java.lang.ClassCastException cex)
1096 jalview.bin.Console.warn(
1097 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1103 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1108 private static void setKeyBindings(JInternalFrame frame)
1110 @SuppressWarnings("serial")
1111 final Action closeAction = new AbstractAction()
1114 public void actionPerformed(ActionEvent e)
1121 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1123 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1124 InputEvent.CTRL_DOWN_MASK);
1125 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1126 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1128 InputMap inputMap = frame
1129 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1130 String ctrlW = ctrlWKey.toString();
1131 inputMap.put(ctrlWKey, ctrlW);
1132 inputMap.put(cmdWKey, ctrlW);
1134 ActionMap actionMap = frame.getActionMap();
1135 actionMap.put(ctrlW, closeAction);
1139 public void lostOwnership(Clipboard clipboard, Transferable contents)
1143 Desktop.jalviewClipboard = null;
1146 internalCopy = false;
1150 public void dragEnter(DropTargetDragEvent evt)
1155 public void dragExit(DropTargetEvent evt)
1160 public void dragOver(DropTargetDragEvent evt)
1165 public void dropActionChanged(DropTargetDragEvent evt)
1176 public void drop(DropTargetDropEvent evt)
1178 boolean success = true;
1179 // JAL-1552 - acceptDrop required before getTransferable call for
1180 // Java's Transferable for native dnd
1181 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1182 Transferable t = evt.getTransferable();
1183 List<Object> files = new ArrayList<>();
1184 List<DataSourceType> protocols = new ArrayList<>();
1188 Desktop.transferFromDropTarget(files, protocols, evt, t);
1189 } catch (Exception e)
1191 e.printStackTrace();
1199 for (int i = 0; i < files.size(); i++)
1201 // BH 2018 File or String
1202 Object file = files.get(i);
1203 String fileName = file.toString();
1204 DataSourceType protocol = (protocols == null)
1205 ? DataSourceType.FILE
1207 FileFormatI format = null;
1209 if (fileName.endsWith(".jar"))
1211 format = FileFormat.Jalview;
1216 format = new IdentifyFile().identify(file, protocol);
1218 if (file instanceof File)
1220 Platform.cacheFileData((File) file);
1222 new FileLoader().LoadFile(null, file, protocol, format);
1225 } catch (Exception ex)
1230 evt.dropComplete(success); // need this to ensure input focus is properly
1231 // transfered to any new windows created
1241 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1243 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1244 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1245 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1246 BackupFiles.getEnabled());
1248 chooser.setFileView(new JalviewFileView());
1249 chooser.setDialogTitle(
1250 MessageManager.getString("label.open_local_file"));
1251 chooser.setToolTipText(MessageManager.getString("action.open"));
1253 chooser.setResponseHandler(0, () -> {
1254 File selectedFile = chooser.getSelectedFile();
1255 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1257 FileFormatI format = chooser.getSelectedFormat();
1260 * Call IdentifyFile to verify the file contains what its extension implies.
1261 * Skip this step for dynamically added file formats, because IdentifyFile does
1262 * not know how to recognise them.
1264 if (FileFormats.getInstance().isIdentifiable(format))
1268 format = new IdentifyFile().identify(selectedFile,
1269 DataSourceType.FILE);
1270 } catch (FileFormatException e)
1272 // format = null; //??
1276 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1279 chooser.showOpenDialog(this);
1283 * Shows a dialog for input of a URL at which to retrieve alignment data
1288 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1290 // This construct allows us to have a wider textfield
1292 JLabel label = new JLabel(
1293 MessageManager.getString("label.input_file_url"));
1295 JPanel panel = new JPanel(new GridLayout(2, 1));
1299 * the URL to fetch is input in Java: an editable combobox with history JS:
1300 * (pending JAL-3038) a plain text field
1303 String urlBase = "https://www.";
1304 if (Platform.isJS())
1306 history = new JTextField(urlBase, 35);
1315 JComboBox<String> asCombo = new JComboBox<>();
1316 asCombo.setPreferredSize(new Dimension(400, 20));
1317 asCombo.setEditable(true);
1318 asCombo.addItem(urlBase);
1319 String historyItems = Cache.getProperty("RECENT_URL");
1320 if (historyItems != null)
1322 for (String token : historyItems.split("\\t"))
1324 asCombo.addItem(token);
1331 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1332 MessageManager.getString("action.cancel") };
1333 Runnable action = () -> {
1334 @SuppressWarnings("unchecked")
1335 String url = (history instanceof JTextField
1336 ? ((JTextField) history).getText()
1337 : ((JComboBox<String>) history).getEditor().getItem()
1338 .toString().trim());
1340 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1342 if (viewport != null)
1344 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1345 FileFormat.Jalview);
1349 new FileLoader().LoadFile(url, DataSourceType.URL,
1350 FileFormat.Jalview);
1355 FileFormatI format = null;
1358 format = new IdentifyFile().identify(url, DataSourceType.URL);
1359 } catch (FileFormatException e)
1361 // TODO revise error handling, distinguish between
1362 // URL not found and response not valid
1367 String msg = MessageManager.formatMessage("label.couldnt_locate",
1369 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1370 MessageManager.getString("label.url_not_found"),
1371 JvOptionPane.WARNING_MESSAGE);
1375 if (viewport != null)
1377 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1382 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1386 String dialogOption = MessageManager
1387 .getString("label.input_alignment_from_url");
1388 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1389 .showInternalDialog(panel, dialogOption,
1390 JvOptionPane.YES_NO_CANCEL_OPTION,
1391 JvOptionPane.PLAIN_MESSAGE, null, options,
1392 MessageManager.getString("action.ok"));
1396 * Opens the CutAndPaste window for the user to paste an alignment in to
1399 * - if not null, the pasted alignment is added to the current
1400 * alignment; if null, to a new alignment window
1403 public void inputTextboxMenuItem_actionPerformed(
1404 AlignmentViewPanel viewPanel)
1406 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1407 cap.setForInput(viewPanel);
1408 Desktop.addInternalFrame(cap,
1409 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1414 * Check with user and saving files before actually quitting
1416 public void desktopQuit()
1418 desktopQuit(true, false);
1421 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1423 final Runnable doDesktopQuit = () -> {
1424 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1425 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1426 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1427 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1428 getBounds().y, getWidth(), getHeight()));
1430 if (jconsole != null)
1432 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1433 jconsole.stopConsole();
1438 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1441 // Frames should all close automatically. Keeping external
1442 // viewers open should already be decided by user.
1443 closeAll_actionPerformed(null);
1445 // check for aborted quit
1446 if (QuitHandler.quitCancelled())
1448 jalview.bin.Console.debug("Desktop aborting quit");
1452 if (dialogExecutor != null)
1454 dialogExecutor.shutdownNow();
1457 if (groovyConsole != null)
1459 // suppress a possible repeat prompt to save script
1460 groovyConsole.setDirty(false);
1461 groovyConsole.exit();
1464 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1466 // note that shutdown hook will not be run
1467 jalview.bin.Console.debug("Force Quit selected by user");
1468 Runtime.getRuntime().halt(0);
1471 jalview.bin.Console.debug("Quit selected by user");
1474 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1475 // instance.dispose();
1480 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1481 QuitHandler.defaultCancelQuit);
1485 * Don't call this directly, use desktopQuit() above. Exits the program.
1490 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1491 // not run a second time if gotQuitResponse flag has been set (i.e. user
1492 // confirmed quit of some kind).
1493 Jalview.exit("Desktop exiting.", 0);
1496 private void storeLastKnownDimensions(String string, Rectangle jc)
1498 jalview.bin.Console.debug("Storing last known dimensions for " + string
1499 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1500 + " height:" + jc.height);
1502 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1503 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1504 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1505 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1515 public void aboutMenuItem_actionPerformed(ActionEvent e)
1517 new Thread(new Runnable()
1522 new SplashScreen(false);
1528 * Returns the html text for the About screen, including any available version
1529 * number, build details, author details and citation reference, but without
1530 * the enclosing {@code html} tags
1534 public String getAboutMessage()
1536 StringBuilder message = new StringBuilder(1024);
1537 message.append("<div style=\"font-family: sans-serif;\">")
1538 .append("<h1><strong>Version: ")
1539 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1540 .append("<strong>Built: <em>")
1541 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1542 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1543 .append("</strong>");
1545 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1546 if (latestVersion.equals("Checking"))
1548 // JBP removed this message for 2.11: May be reinstated in future version
1549 // message.append("<br>...Checking latest version...</br>");
1551 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1553 boolean red = false;
1554 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1555 .indexOf("automated build") == -1)
1558 // Displayed when code version and jnlp version do not match and code
1559 // version is not a development build
1560 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1563 message.append("<br>!! Version ")
1564 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1565 .append(" is available for download from ")
1566 .append(Cache.getDefault("www.jalview.org",
1567 "https://www.jalview.org"))
1571 message.append("</div>");
1574 message.append("<br>Authors: ");
1575 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1576 message.append(CITATION);
1578 message.append("</div>");
1580 return message.toString();
1584 * Action on requesting Help documentation
1587 public void documentationMenuItem_actionPerformed()
1591 if (Platform.isJS())
1593 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1602 Help.showHelpWindow();
1604 } catch (Exception ex)
1606 System.err.println("Error opening help: " + ex.getMessage());
1611 public void closeAll_actionPerformed(ActionEvent e)
1613 // TODO show a progress bar while closing?
1614 JInternalFrame[] frames = desktop.getAllFrames();
1615 for (int i = 0; i < frames.length; i++)
1619 frames[i].setClosed(true);
1620 } catch (java.beans.PropertyVetoException ex)
1624 Jalview.setCurrentAlignFrame(null);
1625 jalview.bin.Console.info("ALL CLOSED");
1628 * reset state of singleton objects as appropriate (clear down session state
1629 * when all windows are closed)
1631 StructureSelectionManager ssm = StructureSelectionManager
1632 .getStructureSelectionManager(this);
1639 public int structureViewersStillRunningCount()
1642 JInternalFrame[] frames = desktop.getAllFrames();
1643 for (int i = 0; i < frames.length; i++)
1645 if (frames[i] != null
1646 && frames[i] instanceof JalviewStructureDisplayI)
1648 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1656 public void raiseRelated_actionPerformed(ActionEvent e)
1658 reorderAssociatedWindows(false, false);
1662 public void minimizeAssociated_actionPerformed(ActionEvent e)
1664 reorderAssociatedWindows(true, false);
1667 void closeAssociatedWindows()
1669 reorderAssociatedWindows(false, true);
1675 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1679 protected void garbageCollect_actionPerformed(ActionEvent e)
1681 // We simply collect the garbage
1682 jalview.bin.Console.debug("Collecting garbage...");
1684 jalview.bin.Console.debug("Finished garbage collection.");
1690 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1694 protected void showMemusage_actionPerformed(ActionEvent e)
1696 desktop.showMemoryUsage(showMemusage.isSelected());
1703 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1707 protected void showConsole_actionPerformed(ActionEvent e)
1709 showConsole(showConsole.isSelected());
1712 Console jconsole = null;
1715 * control whether the java console is visible or not
1719 void showConsole(boolean selected)
1721 // TODO: decide if we should update properties file
1722 if (jconsole != null) // BH 2018
1724 showConsole.setSelected(selected);
1725 Cache.setProperty("SHOW_JAVA_CONSOLE",
1726 Boolean.valueOf(selected).toString());
1727 jconsole.setVisible(selected);
1731 void reorderAssociatedWindows(boolean minimize, boolean close)
1733 JInternalFrame[] frames = desktop.getAllFrames();
1734 if (frames == null || frames.length < 1)
1739 AlignmentViewport source = null, target = null;
1740 if (frames[0] instanceof AlignFrame)
1742 source = ((AlignFrame) frames[0]).getCurrentView();
1744 else if (frames[0] instanceof TreePanel)
1746 source = ((TreePanel) frames[0]).getViewPort();
1748 else if (frames[0] instanceof PCAPanel)
1750 source = ((PCAPanel) frames[0]).av;
1752 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1754 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1759 for (int i = 0; i < frames.length; i++)
1762 if (frames[i] == null)
1766 if (frames[i] instanceof AlignFrame)
1768 target = ((AlignFrame) frames[i]).getCurrentView();
1770 else if (frames[i] instanceof TreePanel)
1772 target = ((TreePanel) frames[i]).getViewPort();
1774 else if (frames[i] instanceof PCAPanel)
1776 target = ((PCAPanel) frames[i]).av;
1778 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1780 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1783 if (source == target)
1789 frames[i].setClosed(true);
1793 frames[i].setIcon(minimize);
1796 frames[i].toFront();
1800 } catch (java.beans.PropertyVetoException ex)
1815 protected void preferences_actionPerformed(ActionEvent e)
1817 Preferences.openPreferences();
1821 * Prompts the user to choose a file and then saves the Jalview state as a
1822 * Jalview project file
1825 public void saveState_actionPerformed()
1827 saveState_actionPerformed(false);
1830 public void saveState_actionPerformed(boolean saveAs)
1832 java.io.File projectFile = getProjectFile();
1833 // autoSave indicates we already have a file and don't need to ask
1834 boolean autoSave = projectFile != null && !saveAs
1835 && BackupFiles.getEnabled();
1837 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1838 // saveAs="+saveAs+", Backups
1839 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1841 boolean approveSave = false;
1844 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1847 chooser.setFileView(new JalviewFileView());
1848 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1850 int value = chooser.showSaveDialog(this);
1852 if (value == JalviewFileChooser.APPROVE_OPTION)
1854 projectFile = chooser.getSelectedFile();
1855 setProjectFile(projectFile);
1860 if (approveSave || autoSave)
1862 final Desktop me = this;
1863 final java.io.File chosenFile = projectFile;
1864 new Thread(new Runnable()
1869 // TODO: refactor to Jalview desktop session controller action.
1870 setProgressBar(MessageManager.formatMessage(
1871 "label.saving_jalview_project", new Object[]
1872 { chosenFile.getName() }), chosenFile.hashCode());
1873 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1874 // TODO catch and handle errors for savestate
1875 // TODO prevent user from messing with the Desktop whilst we're saving
1878 boolean doBackup = BackupFiles.getEnabled();
1879 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1882 new Jalview2XML().saveState(
1883 doBackup ? backupfiles.getTempFile() : chosenFile);
1887 backupfiles.setWriteSuccess(true);
1888 backupfiles.rollBackupsAndRenameTempFile();
1890 } catch (OutOfMemoryError oom)
1892 new OOMWarning("Whilst saving current state to "
1893 + chosenFile.getName(), oom);
1894 } catch (Exception ex)
1896 jalview.bin.Console.error("Problems whilst trying to save to "
1897 + chosenFile.getName(), ex);
1898 JvOptionPane.showMessageDialog(me,
1899 MessageManager.formatMessage(
1900 "label.error_whilst_saving_current_state_to",
1902 { chosenFile.getName() }),
1903 MessageManager.getString("label.couldnt_save_project"),
1904 JvOptionPane.WARNING_MESSAGE);
1906 setProgressBar(null, chosenFile.hashCode());
1913 public void saveAsState_actionPerformed(ActionEvent e)
1915 saveState_actionPerformed(true);
1918 protected void setProjectFile(File choice)
1920 this.projectFile = choice;
1923 public File getProjectFile()
1925 return this.projectFile;
1929 * Shows a file chooser dialog and tries to read in the selected file as a
1933 public void loadState_actionPerformed()
1935 final String[] suffix = new String[] { "jvp", "jar" };
1936 final String[] desc = new String[] { "Jalview Project",
1937 "Jalview Project (old)" };
1938 JalviewFileChooser chooser = new JalviewFileChooser(
1939 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1940 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1944 chooser.setFileView(new JalviewFileView());
1945 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1946 chooser.setResponseHandler(0, () -> {
1947 File selectedFile = chooser.getSelectedFile();
1948 setProjectFile(selectedFile);
1949 String choice = selectedFile.getAbsolutePath();
1950 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1951 new Thread(new Runnable()
1958 new Jalview2XML().loadJalviewAlign(selectedFile);
1959 } catch (OutOfMemoryError oom)
1961 new OOMWarning("Whilst loading project from " + choice, oom);
1962 } catch (Exception ex)
1964 jalview.bin.Console.error(
1965 "Problems whilst loading project from " + choice, ex);
1966 JvOptionPane.showMessageDialog(Desktop.desktop,
1967 MessageManager.formatMessage(
1968 "label.error_whilst_loading_project_from",
1971 MessageManager.getString("label.couldnt_load_project"),
1972 JvOptionPane.WARNING_MESSAGE);
1975 }, "Project Loader").start();
1978 chooser.showOpenDialog(this);
1982 public void inputSequence_actionPerformed(ActionEvent e)
1984 new SequenceFetcher(this);
1987 JPanel progressPanel;
1989 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1991 public void startLoading(final Object fileName)
1993 if (fileLoadingCount == 0)
1995 fileLoadingPanels.add(addProgressPanel(MessageManager
1996 .formatMessage("label.loading_file", new Object[]
2002 private JPanel addProgressPanel(String string)
2004 if (progressPanel == null)
2006 progressPanel = new JPanel(new GridLayout(1, 1));
2007 totalProgressCount = 0;
2008 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2010 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2011 JProgressBar progressBar = new JProgressBar();
2012 progressBar.setIndeterminate(true);
2014 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2016 thisprogress.add(progressBar, BorderLayout.CENTER);
2017 progressPanel.add(thisprogress);
2018 ((GridLayout) progressPanel.getLayout()).setRows(
2019 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2020 ++totalProgressCount;
2021 instance.validate();
2022 return thisprogress;
2025 int totalProgressCount = 0;
2027 private void removeProgressPanel(JPanel progbar)
2029 if (progressPanel != null)
2031 synchronized (progressPanel)
2033 progressPanel.remove(progbar);
2034 GridLayout gl = (GridLayout) progressPanel.getLayout();
2035 gl.setRows(gl.getRows() - 1);
2036 if (--totalProgressCount < 1)
2038 this.getContentPane().remove(progressPanel);
2039 progressPanel = null;
2046 public void stopLoading()
2049 if (fileLoadingCount < 1)
2051 while (fileLoadingPanels.size() > 0)
2053 removeProgressPanel(fileLoadingPanels.remove(0));
2055 fileLoadingPanels.clear();
2056 fileLoadingCount = 0;
2061 public static int getViewCount(String alignmentId)
2063 AlignmentViewport[] aps = getViewports(alignmentId);
2064 return (aps == null) ? 0 : aps.length;
2069 * @param alignmentId
2070 * - if null, all sets are returned
2071 * @return all AlignmentPanels concerning the alignmentId sequence set
2073 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2075 if (Desktop.desktop == null)
2077 // no frames created and in headless mode
2078 // TODO: verify that frames are recoverable when in headless mode
2081 List<AlignmentPanel> aps = new ArrayList<>();
2082 AlignFrame[] frames = getAlignFrames();
2087 for (AlignFrame af : frames)
2089 for (AlignmentPanel ap : af.alignPanels)
2091 if (alignmentId == null
2092 || alignmentId.equals(ap.av.getSequenceSetId()))
2098 if (aps.size() == 0)
2102 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2107 * get all the viewports on an alignment.
2109 * @param sequenceSetId
2110 * unique alignment id (may be null - all viewports returned in that
2112 * @return all viewports on the alignment bound to sequenceSetId
2114 public static AlignmentViewport[] getViewports(String sequenceSetId)
2116 List<AlignmentViewport> viewp = new ArrayList<>();
2117 if (desktop != null)
2119 AlignFrame[] frames = Desktop.getAlignFrames();
2121 for (AlignFrame afr : frames)
2123 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2124 .equals(sequenceSetId))
2126 if (afr.alignPanels != null)
2128 for (AlignmentPanel ap : afr.alignPanels)
2130 if (sequenceSetId == null
2131 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2139 viewp.add(afr.getViewport());
2143 if (viewp.size() > 0)
2145 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2152 * Explode the views in the given frame into separate AlignFrame
2156 public static void explodeViews(AlignFrame af)
2158 int size = af.alignPanels.size();
2164 // FIXME: ideally should use UI interface API
2165 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2166 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2167 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2168 for (int i = 0; i < size; i++)
2170 AlignmentPanel ap = af.alignPanels.get(i);
2172 AlignFrame newaf = new AlignFrame(ap);
2174 // transfer reference for existing feature settings to new alignFrame
2175 if (ap == af.alignPanel)
2177 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2179 newaf.featureSettings = viewFeatureSettings;
2181 newaf.setFeatureSettingsGeometry(fsBounds);
2185 * Restore the view's last exploded frame geometry if known. Multiple views from
2186 * one exploded frame share and restore the same (frame) position and size.
2188 Rectangle geometry = ap.av.getExplodedGeometry();
2189 if (geometry != null)
2191 newaf.setBounds(geometry);
2194 ap.av.setGatherViewsHere(false);
2196 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2197 AlignFrame.DEFAULT_HEIGHT);
2198 // and materialise a new feature settings dialog instance for the new
2200 // (closes the old as if 'OK' was pressed)
2201 if (ap == af.alignPanel && newaf.featureSettings != null
2202 && newaf.featureSettings.isOpen()
2203 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2205 newaf.showFeatureSettingsUI();
2209 af.featureSettings = null;
2210 af.alignPanels.clear();
2211 af.closeMenuItem_actionPerformed(true);
2216 * Gather expanded views (separate AlignFrame's) with the same sequence set
2217 * identifier back in to this frame as additional views, and close the
2218 * expanded views. Note the expanded frames may themselves have multiple
2219 * views. We take the lot.
2223 public void gatherViews(AlignFrame source)
2225 source.viewport.setGatherViewsHere(true);
2226 source.viewport.setExplodedGeometry(source.getBounds());
2227 JInternalFrame[] frames = desktop.getAllFrames();
2228 String viewId = source.viewport.getSequenceSetId();
2229 for (int t = 0; t < frames.length; t++)
2231 if (frames[t] instanceof AlignFrame && frames[t] != source)
2233 AlignFrame af = (AlignFrame) frames[t];
2234 boolean gatherThis = false;
2235 for (int a = 0; a < af.alignPanels.size(); a++)
2237 AlignmentPanel ap = af.alignPanels.get(a);
2238 if (viewId.equals(ap.av.getSequenceSetId()))
2241 ap.av.setGatherViewsHere(false);
2242 ap.av.setExplodedGeometry(af.getBounds());
2243 source.addAlignmentPanel(ap, false);
2249 if (af.featureSettings != null && af.featureSettings.isOpen())
2251 if (source.featureSettings == null)
2253 // preserve the feature settings geometry for this frame
2254 source.featureSettings = af.featureSettings;
2255 source.setFeatureSettingsGeometry(
2256 af.getFeatureSettingsGeometry());
2260 // close it and forget
2261 af.featureSettings.close();
2264 af.alignPanels.clear();
2265 af.closeMenuItem_actionPerformed(true);
2270 // refresh the feature setting UI for the source frame if it exists
2271 if (source.featureSettings != null && source.featureSettings.isOpen())
2273 source.showFeatureSettingsUI();
2278 public JInternalFrame[] getAllFrames()
2280 return desktop.getAllFrames();
2284 * Checks the given url to see if it gives a response indicating that the user
2285 * should be informed of a new questionnaire.
2289 public void checkForQuestionnaire(String url)
2291 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2292 // javax.swing.SwingUtilities.invokeLater(jvq);
2293 new Thread(jvq).start();
2296 public void checkURLLinks()
2298 // Thread off the URL link checker
2299 addDialogThread(new Runnable()
2304 if (Cache.getDefault("CHECKURLLINKS", true))
2306 // check what the actual links are - if it's just the default don't
2307 // bother with the warning
2308 List<String> links = Preferences.sequenceUrlLinks
2311 // only need to check links if there is one with a
2312 // SEQUENCE_ID which is not the default EMBL_EBI link
2313 ListIterator<String> li = links.listIterator();
2314 boolean check = false;
2315 List<JLabel> urls = new ArrayList<>();
2316 while (li.hasNext())
2318 String link = li.next();
2319 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2320 && !UrlConstants.isDefaultString(link))
2323 int barPos = link.indexOf("|");
2324 String urlMsg = barPos == -1 ? link
2325 : link.substring(0, barPos) + ": "
2326 + link.substring(barPos + 1);
2327 urls.add(new JLabel(urlMsg));
2335 // ask user to check in case URL links use old style tokens
2336 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2337 JPanel msgPanel = new JPanel();
2338 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2339 msgPanel.add(Box.createVerticalGlue());
2340 JLabel msg = new JLabel(MessageManager
2341 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2342 JLabel msg2 = new JLabel(MessageManager
2343 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2345 for (JLabel url : urls)
2351 final JCheckBox jcb = new JCheckBox(
2352 MessageManager.getString("label.do_not_display_again"));
2353 jcb.addActionListener(new ActionListener()
2356 public void actionPerformed(ActionEvent e)
2358 // update Cache settings for "don't show this again"
2359 boolean showWarningAgain = !jcb.isSelected();
2360 Cache.setProperty("CHECKURLLINKS",
2361 Boolean.valueOf(showWarningAgain).toString());
2366 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2368 .getString("label.SEQUENCE_ID_no_longer_used"),
2369 JvOptionPane.WARNING_MESSAGE);
2376 * Proxy class for JDesktopPane which optionally displays the current memory
2377 * usage and highlights the desktop area with a red bar if free memory runs
2382 public class MyDesktopPane extends JDesktopPane implements Runnable
2384 private static final float ONE_MB = 1048576f;
2386 boolean showMemoryUsage = false;
2390 java.text.NumberFormat df;
2392 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2395 public MyDesktopPane(boolean showMemoryUsage)
2397 showMemoryUsage(showMemoryUsage);
2400 public void showMemoryUsage(boolean showMemory)
2402 this.showMemoryUsage = showMemory;
2405 Thread worker = new Thread(this);
2411 public boolean isShowMemoryUsage()
2413 return showMemoryUsage;
2419 df = java.text.NumberFormat.getNumberInstance();
2420 df.setMaximumFractionDigits(2);
2421 runtime = Runtime.getRuntime();
2423 while (showMemoryUsage)
2427 maxMemory = runtime.maxMemory() / ONE_MB;
2428 allocatedMemory = runtime.totalMemory() / ONE_MB;
2429 freeMemory = runtime.freeMemory() / ONE_MB;
2430 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2432 percentUsage = (totalFreeMemory / maxMemory) * 100;
2434 // if (percentUsage < 20)
2436 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2438 // instance.set.setBorder(border1);
2441 // sleep after showing usage
2443 } catch (Exception ex)
2445 ex.printStackTrace();
2451 public void paintComponent(Graphics g)
2453 if (showMemoryUsage && g != null && df != null)
2455 if (percentUsage < 20)
2457 g.setColor(Color.red);
2459 FontMetrics fm = g.getFontMetrics();
2462 g.drawString(MessageManager.formatMessage("label.memory_stats",
2464 { df.format(totalFreeMemory), df.format(maxMemory),
2465 df.format(percentUsage) }),
2466 10, getHeight() - fm.getHeight());
2470 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2471 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2476 * Accessor method to quickly get all the AlignmentFrames loaded.
2478 * @return an array of AlignFrame, or null if none found
2480 public static AlignFrame[] getAlignFrames()
2482 if (Jalview.isHeadlessMode())
2484 // Desktop.desktop is null in headless mode
2485 return new AlignFrame[] { Jalview.currentAlignFrame };
2488 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2494 List<AlignFrame> avp = new ArrayList<>();
2496 for (int i = frames.length - 1; i > -1; i--)
2498 if (frames[i] instanceof AlignFrame)
2500 avp.add((AlignFrame) frames[i]);
2502 else if (frames[i] instanceof SplitFrame)
2505 * Also check for a split frame containing an AlignFrame
2507 GSplitFrame sf = (GSplitFrame) frames[i];
2508 if (sf.getTopFrame() instanceof AlignFrame)
2510 avp.add((AlignFrame) sf.getTopFrame());
2512 if (sf.getBottomFrame() instanceof AlignFrame)
2514 avp.add((AlignFrame) sf.getBottomFrame());
2518 if (avp.size() == 0)
2522 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2527 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2531 public GStructureViewer[] getJmols()
2533 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2539 List<GStructureViewer> avp = new ArrayList<>();
2541 for (int i = frames.length - 1; i > -1; i--)
2543 if (frames[i] instanceof AppJmol)
2545 GStructureViewer af = (GStructureViewer) frames[i];
2549 if (avp.size() == 0)
2553 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2558 * Add Groovy Support to Jalview
2561 public void groovyShell_actionPerformed()
2565 openGroovyConsole();
2566 } catch (Exception ex)
2568 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2569 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2571 MessageManager.getString("label.couldnt_create_groovy_shell"),
2572 MessageManager.getString("label.groovy_support_failed"),
2573 JvOptionPane.ERROR_MESSAGE);
2578 * Open the Groovy console
2580 void openGroovyConsole()
2582 if (groovyConsole == null)
2584 groovyConsole = new groovy.ui.Console();
2585 groovyConsole.setVariable("Jalview", this);
2586 groovyConsole.run();
2589 * We allow only one console at a time, so that AlignFrame menu option
2590 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2591 * enable 'Run script', when the console is opened, and the reverse when it is
2594 Window window = (Window) groovyConsole.getFrame();
2595 window.addWindowListener(new WindowAdapter()
2598 public void windowClosed(WindowEvent e)
2601 * rebind CMD-Q from Groovy Console to Jalview Quit
2604 enableExecuteGroovy(false);
2610 * show Groovy console window (after close and reopen)
2612 ((Window) groovyConsole.getFrame()).setVisible(true);
2615 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2616 * opening a second console
2618 enableExecuteGroovy(true);
2622 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2623 * binding when opened
2625 protected void addQuitHandler()
2628 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2630 .getKeyStroke(KeyEvent.VK_Q,
2631 jalview.util.ShortcutKeyMaskExWrapper
2632 .getMenuShortcutKeyMaskEx()),
2634 getRootPane().getActionMap().put("Quit", new AbstractAction()
2637 public void actionPerformed(ActionEvent e)
2645 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2648 * true if Groovy console is open
2650 public void enableExecuteGroovy(boolean enabled)
2653 * disable opening a second Groovy console (or re-enable when the console is
2656 groovyShell.setEnabled(!enabled);
2658 AlignFrame[] alignFrames = getAlignFrames();
2659 if (alignFrames != null)
2661 for (AlignFrame af : alignFrames)
2663 af.setGroovyEnabled(enabled);
2669 * Progress bars managed by the IProgressIndicator method.
2671 private Hashtable<Long, JPanel> progressBars;
2673 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2678 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2681 public void setProgressBar(String message, long id)
2683 if (progressBars == null)
2685 progressBars = new Hashtable<>();
2686 progressBarHandlers = new Hashtable<>();
2689 if (progressBars.get(Long.valueOf(id)) != null)
2691 JPanel panel = progressBars.remove(Long.valueOf(id));
2692 if (progressBarHandlers.contains(Long.valueOf(id)))
2694 progressBarHandlers.remove(Long.valueOf(id));
2696 removeProgressPanel(panel);
2700 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2707 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2708 * jalview.gui.IProgressIndicatorHandler)
2711 public void registerHandler(final long id,
2712 final IProgressIndicatorHandler handler)
2714 if (progressBarHandlers == null
2715 || !progressBars.containsKey(Long.valueOf(id)))
2717 throw new Error(MessageManager.getString(
2718 "error.call_setprogressbar_before_registering_handler"));
2720 progressBarHandlers.put(Long.valueOf(id), handler);
2721 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2722 if (handler.canCancel())
2724 JButton cancel = new JButton(
2725 MessageManager.getString("action.cancel"));
2726 final IProgressIndicator us = this;
2727 cancel.addActionListener(new ActionListener()
2731 public void actionPerformed(ActionEvent e)
2733 handler.cancelActivity(id);
2734 us.setProgressBar(MessageManager
2735 .formatMessage("label.cancelled_params", new Object[]
2736 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2740 progressPanel.add(cancel, BorderLayout.EAST);
2746 * @return true if any progress bars are still active
2749 public boolean operationInProgress()
2751 if (progressBars != null && progressBars.size() > 0)
2759 * This will return the first AlignFrame holding the given viewport instance.
2760 * It will break if there are more than one AlignFrames viewing a particular
2764 * @return alignFrame for viewport
2766 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2768 if (desktop != null)
2770 AlignmentPanel[] aps = getAlignmentPanels(
2771 viewport.getSequenceSetId());
2772 for (int panel = 0; aps != null && panel < aps.length; panel++)
2774 if (aps[panel] != null && aps[panel].av == viewport)
2776 return aps[panel].alignFrame;
2783 public VamsasApplication getVamsasApplication()
2785 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2791 * flag set if jalview GUI is being operated programmatically
2793 private boolean inBatchMode = false;
2796 * check if jalview GUI is being operated programmatically
2798 * @return inBatchMode
2800 public boolean isInBatchMode()
2806 * set flag if jalview GUI is being operated programmatically
2808 * @param inBatchMode
2810 public void setInBatchMode(boolean inBatchMode)
2812 this.inBatchMode = inBatchMode;
2816 * start service discovery and wait till it is done
2818 public void startServiceDiscovery()
2820 startServiceDiscovery(false);
2824 * start service discovery threads - blocking or non-blocking
2828 public void startServiceDiscovery(boolean blocking)
2830 startServiceDiscovery(blocking, false);
2834 * start service discovery threads
2837 * - false means call returns immediately
2838 * @param ignore_SHOW_JWS2_SERVICES_preference
2839 * - when true JABA services are discovered regardless of user's JWS2
2840 * discovery preference setting
2842 public void startServiceDiscovery(boolean blocking,
2843 boolean ignore_SHOW_JWS2_SERVICES_preference)
2845 boolean alive = true;
2846 Thread t0 = null, t1 = null, t2 = null;
2847 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2850 // todo: changesupport handlers need to be transferred
2851 if (discoverer == null)
2853 discoverer = new jalview.ws.jws1.Discoverer();
2854 // register PCS handler for desktop.
2855 discoverer.addPropertyChangeListener(changeSupport);
2857 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2858 // until we phase out completely
2859 (t0 = new Thread(discoverer)).start();
2862 if (ignore_SHOW_JWS2_SERVICES_preference
2863 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2865 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2866 .startDiscoverer(changeSupport);
2870 // TODO: do rest service discovery
2879 } catch (Exception e)
2882 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2883 || (t3 != null && t3.isAlive())
2884 || (t0 != null && t0.isAlive());
2890 * called to check if the service discovery process completed successfully.
2894 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2896 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2898 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2899 .getErrorMessages();
2902 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2904 if (serviceChangedDialog == null)
2906 // only run if we aren't already displaying one of these.
2907 addDialogThread(serviceChangedDialog = new Runnable()
2914 * JalviewDialog jd =new JalviewDialog() {
2916 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2918 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2920 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2922 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2924 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2925 * + " or mis-configured HTTP proxy settings.<br/>" +
2926 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2927 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2928 * true, true, "Web Service Configuration Problem", 450, 400);
2930 * jd.waitForInput();
2932 JvOptionPane.showConfirmDialog(Desktop.desktop,
2933 new JLabel("<html><table width=\"450\"><tr><td>"
2934 + ermsg + "</td></tr></table>"
2935 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2936 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2937 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2938 + " Tools->Preferences dialog box to change them.</p></html>"),
2939 "Web Service Configuration Problem",
2940 JvOptionPane.DEFAULT_OPTION,
2941 JvOptionPane.ERROR_MESSAGE);
2942 serviceChangedDialog = null;
2950 jalview.bin.Console.error(
2951 "Errors reported by JABA discovery service. Check web services preferences.\n"
2958 private Runnable serviceChangedDialog = null;
2961 * start a thread to open a URL in the configured browser. Pops up a warning
2962 * dialog to the user if there is an exception when calling out to the browser
2967 public static void showUrl(final String url)
2969 showUrl(url, Desktop.instance);
2973 * Like showUrl but allows progress handler to be specified
2977 * (null) or object implementing IProgressIndicator
2979 public static void showUrl(final String url,
2980 final IProgressIndicator progress)
2982 new Thread(new Runnable()
2989 if (progress != null)
2991 progress.setProgressBar(MessageManager
2992 .formatMessage("status.opening_params", new Object[]
2993 { url }), this.hashCode());
2995 jalview.util.BrowserLauncher.openURL(url);
2996 } catch (Exception ex)
2998 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3000 .getString("label.web_browser_not_found_unix"),
3001 MessageManager.getString("label.web_browser_not_found"),
3002 JvOptionPane.WARNING_MESSAGE);
3004 ex.printStackTrace();
3006 if (progress != null)
3008 progress.setProgressBar(null, this.hashCode());
3014 public static WsParamSetManager wsparamManager = null;
3016 public static ParamManager getUserParameterStore()
3018 if (wsparamManager == null)
3020 wsparamManager = new WsParamSetManager();
3022 return wsparamManager;
3026 * static hyperlink handler proxy method for use by Jalview's internal windows
3030 public static void hyperlinkUpdate(HyperlinkEvent e)
3032 if (e.getEventType() == EventType.ACTIVATED)
3037 url = e.getURL().toString();
3038 Desktop.showUrl(url);
3039 } catch (Exception x)
3044 .error("Couldn't handle string " + url + " as a URL.");
3046 // ignore any exceptions due to dud links.
3053 * single thread that handles display of dialogs to user.
3055 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3058 * flag indicating if dialogExecutor should try to acquire a permit
3060 private volatile boolean dialogPause = true;
3065 private Semaphore block = new Semaphore(0);
3067 private static groovy.ui.Console groovyConsole;
3070 * add another dialog thread to the queue
3074 public void addDialogThread(final Runnable prompter)
3076 dialogExecutor.submit(new Runnable()
3083 acquireDialogQueue();
3085 if (instance == null)
3091 SwingUtilities.invokeAndWait(prompter);
3092 } catch (Exception q)
3094 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3101 private boolean dialogQueueStarted = false;
3103 public void startDialogQueue()
3105 if (dialogQueueStarted)
3109 // set the flag so we don't pause waiting for another permit and semaphore
3110 // the current task to begin
3111 releaseDialogQueue();
3112 dialogQueueStarted = true;
3115 public void acquireDialogQueue()
3121 } catch (InterruptedException e)
3123 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3128 public void releaseDialogQueue()
3135 dialogPause = false;
3139 * Outputs an image of the desktop to file in EPS format, after prompting the
3140 * user for choice of Text or Lineart character rendering (unless a preference
3141 * has been set). The file name is generated as
3144 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3148 protected void snapShotWindow_actionPerformed(ActionEvent e)
3150 // currently the menu option to do this is not shown
3153 int width = getWidth();
3154 int height = getHeight();
3156 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3157 ImageWriterI writer = new ImageWriterI()
3160 public void exportImage(Graphics g) throws Exception
3163 jalview.bin.Console.info("Successfully written snapshot to file "
3164 + of.getAbsolutePath());
3167 String title = "View of desktop";
3168 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3172 exporter.doExport(of, this, width, height, title);
3173 } catch (ImageOutputException ioex)
3175 jalview.bin.Console.error(
3176 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3182 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3183 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3184 * and location last time the view was expanded (if any). However it does not
3185 * remember the split pane divider location - this is set to match the
3186 * 'exploding' frame.
3190 public void explodeViews(SplitFrame sf)
3192 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3193 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3194 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3196 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3198 int viewCount = topPanels.size();
3205 * Processing in reverse order works, forwards order leaves the first panels not
3206 * visible. I don't know why!
3208 for (int i = viewCount - 1; i >= 0; i--)
3211 * Make new top and bottom frames. These take over the respective AlignmentPanel
3212 * objects, including their AlignmentViewports, so the cdna/protein
3213 * relationships between the viewports is carried over to the new split frames.
3215 * explodedGeometry holds the (x, y) position of the previously exploded
3216 * SplitFrame, and the (width, height) of the AlignFrame component
3218 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3219 AlignFrame newTopFrame = new AlignFrame(topPanel);
3220 newTopFrame.setSize(oldTopFrame.getSize());
3221 newTopFrame.setVisible(true);
3222 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3223 .getExplodedGeometry();
3224 if (geometry != null)
3226 newTopFrame.setSize(geometry.getSize());
3229 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3230 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3231 newBottomFrame.setSize(oldBottomFrame.getSize());
3232 newBottomFrame.setVisible(true);
3233 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3234 .getExplodedGeometry();
3235 if (geometry != null)
3237 newBottomFrame.setSize(geometry.getSize());
3240 topPanel.av.setGatherViewsHere(false);
3241 bottomPanel.av.setGatherViewsHere(false);
3242 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3244 if (geometry != null)
3246 splitFrame.setLocation(geometry.getLocation());
3248 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3252 * Clear references to the panels (now relocated in the new SplitFrames) before
3253 * closing the old SplitFrame.
3256 bottomPanels.clear();
3261 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3262 * back into the given SplitFrame as additional views. Note that the gathered
3263 * frames may themselves have multiple views.
3267 public void gatherViews(GSplitFrame source)
3270 * special handling of explodedGeometry for a view within a SplitFrame: - it
3271 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3272 * height) of the AlignFrame component
3274 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3275 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3276 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3277 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3278 myBottomFrame.viewport
3279 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3280 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3281 myTopFrame.viewport.setGatherViewsHere(true);
3282 myBottomFrame.viewport.setGatherViewsHere(true);
3283 String topViewId = myTopFrame.viewport.getSequenceSetId();
3284 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3286 JInternalFrame[] frames = desktop.getAllFrames();
3287 for (JInternalFrame frame : frames)
3289 if (frame instanceof SplitFrame && frame != source)
3291 SplitFrame sf = (SplitFrame) frame;
3292 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3293 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3294 boolean gatherThis = false;
3295 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3297 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3298 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3299 if (topViewId.equals(topPanel.av.getSequenceSetId())
3300 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3303 topPanel.av.setGatherViewsHere(false);
3304 bottomPanel.av.setGatherViewsHere(false);
3305 topPanel.av.setExplodedGeometry(
3306 new Rectangle(sf.getLocation(), topFrame.getSize()));
3307 bottomPanel.av.setExplodedGeometry(
3308 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3309 myTopFrame.addAlignmentPanel(topPanel, false);
3310 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3316 topFrame.getAlignPanels().clear();
3317 bottomFrame.getAlignPanels().clear();
3324 * The dust settles...give focus to the tab we did this from.
3326 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3329 public static groovy.ui.Console getGroovyConsole()
3331 return groovyConsole;
3335 * handles the payload of a drag and drop event.
3337 * TODO refactor to desktop utilities class
3340 * - Data source strings extracted from the drop event
3342 * - protocol for each data source extracted from the drop event
3346 * - the payload from the drop event
3349 public static void transferFromDropTarget(List<Object> files,
3350 List<DataSourceType> protocols, DropTargetDropEvent evt,
3351 Transferable t) throws Exception
3354 DataFlavor uriListFlavor = new DataFlavor(
3355 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3358 urlFlavour = new DataFlavor(
3359 "application/x-java-url; class=java.net.URL");
3360 } catch (ClassNotFoundException cfe)
3362 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3366 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3371 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3372 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3373 // means url may be null.
3376 protocols.add(DataSourceType.URL);
3377 files.add(url.toString());
3378 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3379 + files.get(files.size() - 1));
3384 if (Platform.isAMacAndNotJS())
3387 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3390 } catch (Throwable ex)
3392 jalview.bin.Console.debug("URL drop handler failed.", ex);
3395 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3397 // Works on Windows and MacOSX
3398 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3399 for (Object file : (List) t
3400 .getTransferData(DataFlavor.javaFileListFlavor))
3403 protocols.add(DataSourceType.FILE);
3408 // Unix like behaviour
3409 boolean added = false;
3411 if (t.isDataFlavorSupported(uriListFlavor))
3413 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3414 // This is used by Unix drag system
3415 data = (String) t.getTransferData(uriListFlavor);
3419 // fallback to text: workaround - on OSX where there's a JVM bug
3421 .debug("standard URIListFlavor failed. Trying text");
3422 // try text fallback
3423 DataFlavor textDf = new DataFlavor(
3424 "text/plain;class=java.lang.String");
3425 if (t.isDataFlavorSupported(textDf))
3427 data = (String) t.getTransferData(textDf);
3430 jalview.bin.Console.debug("Plain text drop content returned "
3431 + (data == null ? "Null - failed" : data));
3436 while (protocols.size() < files.size())
3438 jalview.bin.Console.debug("Adding missing FILE protocol for "
3439 + files.get(protocols.size()));
3440 protocols.add(DataSourceType.FILE);
3442 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3443 data, "\r\n"); st.hasMoreTokens();)
3446 String s = st.nextToken();
3447 if (s.startsWith("#"))
3449 // the line is a comment (as per the RFC 2483)
3452 java.net.URI uri = new java.net.URI(s);
3453 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3455 protocols.add(DataSourceType.URL);
3456 files.add(uri.toString());
3460 // otherwise preserve old behaviour: catch all for file objects
3461 java.io.File file = new java.io.File(uri);
3462 protocols.add(DataSourceType.FILE);
3463 files.add(file.toString());
3468 if (jalview.bin.Console.isDebugEnabled())
3470 if (data == null || !added)
3473 if (t.getTransferDataFlavors() != null
3474 && t.getTransferDataFlavors().length > 0)
3476 jalview.bin.Console.debug(
3477 "Couldn't resolve drop data. Here are the supported flavors:");
3478 for (DataFlavor fl : t.getTransferDataFlavors())
3480 jalview.bin.Console.debug(
3481 "Supported transfer dataflavor: " + fl.toString());
3482 Object df = t.getTransferData(fl);
3485 jalview.bin.Console.debug("Retrieves: " + df);
3489 jalview.bin.Console.debug("Retrieved nothing");
3496 .debug("Couldn't resolve dataflavor for drop: "
3502 if (Platform.isWindowsAndNotJS())
3505 .debug("Scanning dropped content for Windows Link Files");
3507 // resolve any .lnk files in the file drop
3508 for (int f = 0; f < files.size(); f++)
3510 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3511 if (protocols.get(f).equals(DataSourceType.FILE)
3512 && (source.endsWith(".lnk") || source.endsWith(".url")
3513 || source.endsWith(".site")))
3517 Object obj = files.get(f);
3518 File lf = (obj instanceof File ? (File) obj
3519 : new File((String) obj));
3520 // process link file to get a URL
3521 jalview.bin.Console.debug("Found potential link file: " + lf);
3522 WindowsShortcut wscfile = new WindowsShortcut(lf);
3523 String fullname = wscfile.getRealFilename();
3524 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3525 files.set(f, fullname);
3526 jalview.bin.Console.debug("Parsed real filename " + fullname
3527 + " to extract protocol: " + protocols.get(f));
3528 } catch (Exception ex)
3530 jalview.bin.Console.error(
3531 "Couldn't parse " + files.get(f) + " as a link file.",
3540 * Sets the Preferences property for experimental features to True or False
3541 * depending on the state of the controlling menu item
3544 protected void showExperimental_actionPerformed(boolean selected)
3546 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3550 * Answers a (possibly empty) list of any structure viewer frames (currently
3551 * for either Jmol or Chimera) which are currently open. This may optionally
3552 * be restricted to viewers of a specified class, or viewers linked to a
3553 * specified alignment panel.
3556 * if not null, only return viewers linked to this panel
3557 * @param structureViewerClass
3558 * if not null, only return viewers of this class
3561 public List<StructureViewerBase> getStructureViewers(
3562 AlignmentPanel apanel,
3563 Class<? extends StructureViewerBase> structureViewerClass)
3565 List<StructureViewerBase> result = new ArrayList<>();
3566 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3568 for (JInternalFrame frame : frames)
3570 if (frame instanceof StructureViewerBase)
3572 if (structureViewerClass == null
3573 || structureViewerClass.isInstance(frame))
3576 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3578 result.add((StructureViewerBase) frame);
3586 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3588 private static boolean debugScaleMessageDone = false;
3590 public static void debugScaleMessage(Graphics g)
3592 if (debugScaleMessageDone)
3596 // output used by tests to check HiDPI scaling settings in action
3599 Graphics2D gg = (Graphics2D) g;
3602 AffineTransform t = gg.getTransform();
3603 double scaleX = t.getScaleX();
3604 double scaleY = t.getScaleY();
3605 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3606 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3607 debugScaleMessageDone = true;
3611 jalview.bin.Console.debug("Desktop graphics null");
3613 } catch (Exception e)
3615 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3620 * closes the current instance window, disposes and forgets about it.
3622 public static void closeDesktop()
3624 if (Desktop.instance != null)
3626 Desktop.instance.closeAll_actionPerformed(null);
3627 Desktop.instance.setVisible(false);
3628 Desktop.instance.dispose();
3629 Desktop.instance = null;
3634 * checks if any progress bars are being displayed in any of the windows
3635 * managed by the desktop
3639 public boolean operationsAreInProgress()
3641 JInternalFrame[] frames = getAllFrames();
3642 for (JInternalFrame frame : frames)
3644 if (frame instanceof IProgressIndicator)
3646 if (((IProgressIndicator) frame).operationInProgress())
3652 return operationInProgress();
3656 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3657 * The way the modal JInternalFrame is made means it cannot be a child of an
3658 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3660 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3662 protected static void addModal(AlignFrame af, JInternalFrame jif)
3664 alignFrameModalMap.put(af, jif);
3667 protected static void closeModal(AlignFrame af)
3669 if (!alignFrameModalMap.containsKey(af))
3673 JInternalFrame jif = alignFrameModalMap.get(af);
3678 jif.setClosed(true);
3679 } catch (PropertyVetoException e)
3681 e.printStackTrace();
3684 alignFrameModalMap.remove(af);