2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JFrame;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.WindowConstants;
95 import javax.swing.event.HyperlinkEvent;
96 import javax.swing.event.HyperlinkEvent.EventType;
97 import javax.swing.event.InternalFrameAdapter;
98 import javax.swing.event.InternalFrameEvent;
100 import org.stackoverflowusers.file.WindowsShortcut;
102 import jalview.api.AlignViewportI;
103 import jalview.api.AlignmentViewPanel;
104 import jalview.api.structures.JalviewStructureDisplayI;
105 import jalview.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.datamodel.Alignment;
108 import jalview.datamodel.HiddenColumns;
109 import jalview.datamodel.Sequence;
110 import jalview.datamodel.SequenceI;
111 import jalview.gui.ImageExporter.ImageWriterI;
112 import jalview.gui.QuitHandler.QResponse;
113 import jalview.io.BackupFiles;
114 import jalview.io.DataSourceType;
115 import jalview.io.FileFormat;
116 import jalview.io.FileFormatException;
117 import jalview.io.FileFormatI;
118 import jalview.io.FileFormats;
119 import jalview.io.FileLoader;
120 import jalview.io.FormatAdapter;
121 import jalview.io.IdentifyFile;
122 import jalview.io.JalviewFileChooser;
123 import jalview.io.JalviewFileView;
124 import jalview.io.exceptions.ImageOutputException;
125 import jalview.jbgui.GSplitFrame;
126 import jalview.jbgui.GStructureViewer;
127 import jalview.project.Jalview2XML;
128 import jalview.structure.StructureSelectionManager;
129 import jalview.urls.IdOrgSettings;
130 import jalview.util.BrowserLauncher;
131 import jalview.util.ChannelProperties;
132 import jalview.util.ImageMaker.TYPE;
133 import jalview.util.LaunchUtils;
134 import jalview.util.MessageManager;
135 import jalview.util.Platform;
136 import jalview.util.ShortcutKeyMaskExWrapper;
137 import jalview.util.UrlConstants;
138 import jalview.viewmodel.AlignmentViewport;
139 import jalview.ws.params.ParamManager;
140 import jalview.ws.utils.UrlDownloadClient;
147 * @version $Revision: 1.155 $
149 public class Desktop extends jalview.jbgui.GDesktop
150 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
151 jalview.api.StructureSelectionManagerProvider
153 private static final String CITATION;
156 URL bg_logo_url = ChannelProperties.getImageURL(
157 "bg_logo." + String.valueOf(SplashScreen.logoSize));
158 URL uod_logo_url = ChannelProperties.getImageURL(
159 "uod_banner." + String.valueOf(SplashScreen.logoSize));
160 boolean logo = (bg_logo_url != null || uod_logo_url != null);
161 StringBuilder sb = new StringBuilder();
163 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
168 sb.append(bg_logo_url == null ? ""
169 : "<img alt=\"Barton Group logo\" src=\""
170 + bg_logo_url.toString() + "\">");
171 sb.append(uod_logo_url == null ? ""
172 : " <img alt=\"University of Dundee shield\" src=\""
173 + uod_logo_url.toString() + "\">");
175 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
176 sb.append("<br><br>If you use Jalview, please cite:"
177 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
178 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
179 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
180 CITATION = sb.toString();
183 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
185 private static int DEFAULT_MIN_WIDTH = 300;
187 private static int DEFAULT_MIN_HEIGHT = 250;
189 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
191 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
193 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
195 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
197 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
199 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
201 public static void setLiveDragMode(boolean b)
203 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
204 : JDesktopPane.OUTLINE_DRAG_MODE;
206 desktop.setDragMode(DRAG_MODE);
209 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
211 public static boolean nosplash = false;
214 * news reader - null if it was never started.
216 private BlogReader jvnews = null;
218 private File projectFile;
222 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
224 public void addJalviewPropertyChangeListener(
225 PropertyChangeListener listener)
227 changeSupport.addJalviewPropertyChangeListener(listener);
231 * @param propertyName
233 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
234 * java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(String propertyName,
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void removeJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.removeJalviewPropertyChangeListener(propertyName,
255 /** Singleton Desktop instance */
256 public static Desktop instance;
258 public static MyDesktopPane desktop;
260 public static MyDesktopPane getDesktop()
262 // BH 2018 could use currentThread() here as a reference to a
263 // Hashtable<Thread, MyDesktopPane> in JavaScript
267 static int openFrameCount = 0;
269 static final int xOffset = 30;
271 static final int yOffset = 30;
273 public static jalview.ws.jws1.Discoverer discoverer;
275 public static Object[] jalviewClipboard;
277 public static boolean internalCopy = false;
279 static int fileLoadingCount = 0;
281 class MyDesktopManager implements DesktopManager
284 private DesktopManager delegate;
286 public MyDesktopManager(DesktopManager delegate)
288 this.delegate = delegate;
292 public void activateFrame(JInternalFrame f)
296 delegate.activateFrame(f);
297 } catch (NullPointerException npe)
299 Point p = getMousePosition();
300 instance.showPasteMenu(p.x, p.y);
305 public void beginDraggingFrame(JComponent f)
307 delegate.beginDraggingFrame(f);
311 public void beginResizingFrame(JComponent f, int direction)
313 delegate.beginResizingFrame(f, direction);
317 public void closeFrame(JInternalFrame f)
319 delegate.closeFrame(f);
323 public void deactivateFrame(JInternalFrame f)
325 delegate.deactivateFrame(f);
329 public void deiconifyFrame(JInternalFrame f)
331 delegate.deiconifyFrame(f);
335 public void dragFrame(JComponent f, int newX, int newY)
341 delegate.dragFrame(f, newX, newY);
345 public void endDraggingFrame(JComponent f)
347 delegate.endDraggingFrame(f);
352 public void endResizingFrame(JComponent f)
354 delegate.endResizingFrame(f);
359 public void iconifyFrame(JInternalFrame f)
361 delegate.iconifyFrame(f);
365 public void maximizeFrame(JInternalFrame f)
367 delegate.maximizeFrame(f);
371 public void minimizeFrame(JInternalFrame f)
373 delegate.minimizeFrame(f);
377 public void openFrame(JInternalFrame f)
379 delegate.openFrame(f);
383 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
390 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
394 public void setBoundsForFrame(JComponent f, int newX, int newY,
395 int newWidth, int newHeight)
397 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
400 // All other methods, simply delegate
405 * Creates a new Desktop object.
411 * A note to implementors. It is ESSENTIAL that any activities that might
412 * block are spawned off as threads rather than waited for during this
417 doConfigureStructurePrefs();
418 setTitle(ChannelProperties.getProperty("app_name") + " "
419 + Cache.getProperty("VERSION"));
422 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
423 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
424 * officially documented or guaranteed to exist, so we access it via
425 * reflection. There appear to be unfathomable criteria about what this
426 * string can contain, and it if doesn't meet those criteria then "java"
427 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
428 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
429 * not. The reflection access may generate a warning: WARNING: An illegal
430 * reflective access operation has occurred WARNING: Illegal reflective
431 * access by jalview.gui.Desktop () to field
432 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
434 if (Platform.isLinux())
436 if (LaunchUtils.getJavaVersion() >= 11)
439 * Send this message to stderr as the warning that follows (due to
440 * reflection) also goes to stderr.
443 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
445 final String awtAppClassName = "awtAppClassName";
448 Toolkit xToolkit = Toolkit.getDefaultToolkit();
449 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
450 Field awtAppClassNameField = null;
452 if (Arrays.stream(declaredFields)
453 .anyMatch(f -> f.getName().equals(awtAppClassName)))
455 awtAppClassNameField = xToolkit.getClass()
456 .getDeclaredField(awtAppClassName);
459 String title = ChannelProperties.getProperty("app_name");
460 if (awtAppClassNameField != null)
462 awtAppClassNameField.setAccessible(true);
463 awtAppClassNameField.set(xToolkit, title);
468 .debug("XToolkit: " + awtAppClassName + " not found");
470 } catch (Exception e)
472 jalview.bin.Console.debug("Error setting " + awtAppClassName);
473 jalview.bin.Console.trace(Cache.getStackTraceString(e));
477 setIconImages(ChannelProperties.getIconList());
479 // override quit handling when GUI OS close [X] button pressed
480 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
481 addWindowListener(new WindowAdapter()
484 public void windowClosing(WindowEvent ev)
486 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
490 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
492 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
493 desktop = new MyDesktopPane(selmemusage);
495 showMemusage.setSelected(selmemusage);
496 desktop.setBackground(Color.white);
498 getContentPane().setLayout(new BorderLayout());
499 // alternate config - have scrollbars - see notes in JAL-153
500 // JScrollPane sp = new JScrollPane();
501 // sp.getViewport().setView(desktop);
502 // getContentPane().add(sp, BorderLayout.CENTER);
504 // BH 2018 - just an experiment to try unclipped JInternalFrames.
507 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
510 getContentPane().add(desktop, BorderLayout.CENTER);
511 desktop.setDragMode(DRAG_MODE);
513 // This line prevents Windows Look&Feel resizing all new windows to maximum
514 // if previous window was maximised
515 desktop.setDesktopManager(new MyDesktopManager(
516 Platform.isJS() ? desktop.getDesktopManager()
517 : new DefaultDesktopManager()));
519 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
520 : Platform.isAMacAndNotJS()
521 ? new AquaInternalFrameManager(
522 desktop.getDesktopManager())
523 : desktop.getDesktopManager())));
526 Rectangle dims = getLastKnownDimensions("");
533 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
534 int xPos = Math.max(5, (screenSize.width - 900) / 2);
535 int yPos = Math.max(5, (screenSize.height - 650) / 2);
536 setBounds(xPos, yPos, 900, 650);
539 // start dialogue queue for single dialogues
542 if (!Platform.isJS())
549 jconsole = new Console(this, showjconsole);
550 jconsole.setHeader(Cache.getVersionDetailsForConsole());
551 showConsole(showjconsole);
553 showNews.setVisible(false);
555 experimentalFeatures.setSelected(showExperimental());
557 getIdentifiersOrgData();
561 // Spawn a thread that shows the splashscreen
564 SwingUtilities.invokeLater(new Runnable()
569 new SplashScreen(true);
574 // Thread off a new instance of the file chooser - this reduces the time
576 // takes to open it later on.
577 new Thread(new Runnable()
582 jalview.bin.Console.debug("Filechooser init thread started.");
583 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
584 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
586 jalview.bin.Console.debug("Filechooser init thread finished.");
589 // Add the service change listener
590 changeSupport.addJalviewPropertyChangeListener("services",
591 new PropertyChangeListener()
595 public void propertyChange(PropertyChangeEvent evt)
598 .debug("Firing service changed event for "
599 + evt.getNewValue());
600 JalviewServicesChanged(evt);
605 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
608 this.addMouseListener(ma = new MouseAdapter()
611 public void mousePressed(MouseEvent evt)
613 if (evt.isPopupTrigger()) // Mac
615 showPasteMenu(evt.getX(), evt.getY());
620 public void mouseReleased(MouseEvent evt)
622 if (evt.isPopupTrigger()) // Windows
624 showPasteMenu(evt.getX(), evt.getY());
628 desktop.addMouseListener(ma);
632 * Answers true if user preferences to enable experimental features is True
637 public boolean showExperimental()
639 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
640 Boolean.FALSE.toString());
641 return Boolean.valueOf(experimental).booleanValue();
644 public void doConfigureStructurePrefs()
646 // configure services
647 StructureSelectionManager ssm = StructureSelectionManager
648 .getStructureSelectionManager(this);
649 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
651 ssm.setAddTempFacAnnot(
652 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
653 ssm.setProcessSecondaryStructure(
654 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
655 // JAL-3915 - RNAView is no longer an option so this has no effect
656 ssm.setSecStructServices(
657 Cache.getDefault(Preferences.USE_RNAVIEW, false));
661 ssm.setAddTempFacAnnot(false);
662 ssm.setProcessSecondaryStructure(false);
663 ssm.setSecStructServices(false);
667 public void checkForNews()
669 final Desktop me = this;
670 // Thread off the news reader, in case there are connection problems.
671 new Thread(new Runnable()
676 jalview.bin.Console.debug("Starting news thread.");
677 jvnews = new BlogReader(me);
678 showNews.setVisible(true);
679 jalview.bin.Console.debug("Completed news thread.");
684 public void getIdentifiersOrgData()
686 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
687 {// Thread off the identifiers fetcher
688 new Thread(new Runnable()
694 .debug("Downloading data from identifiers.org");
697 UrlDownloadClient.download(IdOrgSettings.getUrl(),
698 IdOrgSettings.getDownloadLocation());
699 } catch (IOException e)
702 .debug("Exception downloading identifiers.org data"
712 protected void showNews_actionPerformed(ActionEvent e)
714 showNews(showNews.isSelected());
717 void showNews(boolean visible)
719 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
720 showNews.setSelected(visible);
721 if (visible && !jvnews.isVisible())
723 new Thread(new Runnable()
728 long now = System.currentTimeMillis();
729 Desktop.instance.setProgressBar(
730 MessageManager.getString("status.refreshing_news"), now);
731 jvnews.refreshNews();
732 Desktop.instance.setProgressBar(null, now);
740 * recover the last known dimensions for a jalview window
743 * - empty string is desktop, all other windows have unique prefix
744 * @return null or last known dimensions scaled to current geometry (if last
745 * window geom was known)
747 Rectangle getLastKnownDimensions(String windowName)
749 // TODO: lock aspect ratio for scaling desktop Bug #0058199
750 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
751 String x = Cache.getProperty(windowName + "SCREEN_X");
752 String y = Cache.getProperty(windowName + "SCREEN_Y");
753 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
754 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
755 if ((x != null) && (y != null) && (width != null) && (height != null))
757 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
758 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
759 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
761 // attempt #1 - try to cope with change in screen geometry - this
762 // version doesn't preserve original jv aspect ratio.
763 // take ratio of current screen size vs original screen size.
764 double sw = ((1f * screenSize.width) / (1f * Integer
765 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
766 double sh = ((1f * screenSize.height) / (1f * Integer
767 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
768 // rescale the bounds depending upon the current screen geometry.
769 ix = (int) (ix * sw);
770 iw = (int) (iw * sw);
771 iy = (int) (iy * sh);
772 ih = (int) (ih * sh);
773 while (ix >= screenSize.width)
775 jalview.bin.Console.debug(
776 "Window geometry location recall error: shifting horizontal to within screenbounds.");
777 ix -= screenSize.width;
779 while (iy >= screenSize.height)
781 jalview.bin.Console.debug(
782 "Window geometry location recall error: shifting vertical to within screenbounds.");
783 iy -= screenSize.height;
785 jalview.bin.Console.debug(
786 "Got last known dimensions for " + windowName + ": x:" + ix
787 + " y:" + iy + " width:" + iw + " height:" + ih);
789 // return dimensions for new instance
790 return new Rectangle(ix, iy, iw, ih);
795 void showPasteMenu(int x, int y)
797 JPopupMenu popup = new JPopupMenu();
798 JMenuItem item = new JMenuItem(
799 MessageManager.getString("label.paste_new_window"));
800 item.addActionListener(new ActionListener()
803 public void actionPerformed(ActionEvent evt)
810 popup.show(this, x, y);
815 // quick patch for JAL-4150 - needs some more work and test coverage
816 // TODO - unify below and AlignFrame.paste()
817 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
818 // clipboard has come from a different alignment window than the one where
819 // paste has been called! JAL-4151
821 if (Desktop.jalviewClipboard != null)
823 // The clipboard was filled from within Jalview, we must use the
825 // And dataset from the copied alignment
826 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
827 // be doubly sure that we create *new* sequence objects.
828 SequenceI[] sequences = new SequenceI[newseq.length];
829 for (int i = 0; i < newseq.length; i++)
831 sequences[i] = new Sequence(newseq[i]);
833 Alignment alignment = new Alignment(sequences);
834 // dataset is inherited
835 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
836 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
837 AlignFrame.DEFAULT_HEIGHT);
838 String newtitle = new String("Copied sequences");
840 if (Desktop.jalviewClipboard[2] != null)
842 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
843 af.viewport.setHiddenColumns(hc);
846 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
847 AlignFrame.DEFAULT_HEIGHT);
854 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
855 Transferable contents = c.getContents(this);
857 if (contents != null)
859 String file = (String) contents
860 .getTransferData(DataFlavor.stringFlavor);
862 FileFormatI format = new IdentifyFile().identify(file,
863 DataSourceType.PASTE);
865 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
868 } catch (Exception ex)
871 "Unable to paste alignment from system clipboard:\n" + ex);
877 * Adds and opens the given frame to the desktop
888 public static synchronized void addInternalFrame(
889 final JInternalFrame frame, String title, int w, int h)
891 addInternalFrame(frame, title, true, w, h, true, false);
895 * Add an internal frame to the Jalview desktop
902 * When true, display frame immediately, otherwise, caller must call
903 * setVisible themselves.
909 public static synchronized void addInternalFrame(
910 final JInternalFrame frame, String title, boolean makeVisible,
913 addInternalFrame(frame, title, makeVisible, w, h, true, false);
917 * Add an internal frame to the Jalview desktop and make it visible
930 public static synchronized void addInternalFrame(
931 final JInternalFrame frame, String title, int w, int h,
934 addInternalFrame(frame, title, true, w, h, resizable, false);
938 * Add an internal frame to the Jalview desktop
945 * When true, display frame immediately, otherwise, caller must call
946 * setVisible themselves.
953 * @param ignoreMinSize
954 * Do not set the default minimum size for frame
956 public static synchronized void addInternalFrame(
957 final JInternalFrame frame, String title, boolean makeVisible,
958 int w, int h, boolean resizable, boolean ignoreMinSize)
961 // TODO: allow callers to determine X and Y position of frame (eg. via
963 // TODO: consider fixing method to update entries in the window submenu with
964 // the current window title
966 frame.setTitle(title);
967 if (frame.getWidth() < 1 || frame.getHeight() < 1)
971 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
972 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
973 // IF JALVIEW IS RUNNING HEADLESS
974 // ///////////////////////////////////////////////
975 if (instance == null || (System.getProperty("java.awt.headless") != null
976 && System.getProperty("java.awt.headless").equals("true")))
985 frame.setMinimumSize(
986 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
988 // Set default dimension for Alignment Frame window.
989 // The Alignment Frame window could be added from a number of places,
991 // I did this here in order not to miss out on any Alignment frame.
992 if (frame instanceof AlignFrame)
994 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
995 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
999 frame.setVisible(makeVisible);
1000 frame.setClosable(true);
1001 frame.setResizable(resizable);
1002 frame.setMaximizable(resizable);
1003 frame.setIconifiable(resizable);
1004 frame.setOpaque(Platform.isJS());
1006 if (frame.getX() < 1 && frame.getY() < 1)
1008 frame.setLocation(xOffset * openFrameCount,
1009 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1013 * add an entry for the new frame in the Window menu (and remove it when the
1016 final JMenuItem menuItem = new JMenuItem(title);
1017 frame.addInternalFrameListener(new InternalFrameAdapter()
1020 public void internalFrameActivated(InternalFrameEvent evt)
1022 JInternalFrame itf = desktop.getSelectedFrame();
1025 if (itf instanceof AlignFrame)
1027 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1034 public void internalFrameClosed(InternalFrameEvent evt)
1036 PaintRefresher.RemoveComponent(frame);
1039 * defensive check to prevent frames being added half off the window
1041 if (openFrameCount > 0)
1047 * ensure no reference to alignFrame retained by menu item listener
1049 if (menuItem.getActionListeners().length > 0)
1051 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1053 windowMenu.remove(menuItem);
1057 menuItem.addActionListener(new ActionListener()
1060 public void actionPerformed(ActionEvent e)
1064 frame.setSelected(true);
1065 frame.setIcon(false);
1066 } catch (java.beans.PropertyVetoException ex)
1073 setKeyBindings(frame);
1077 windowMenu.add(menuItem);
1082 frame.setSelected(true);
1083 frame.requestFocus();
1084 } catch (java.beans.PropertyVetoException ve)
1086 } catch (java.lang.ClassCastException cex)
1088 jalview.bin.Console.warn(
1089 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1095 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1100 private static void setKeyBindings(JInternalFrame frame)
1102 @SuppressWarnings("serial")
1103 final Action closeAction = new AbstractAction()
1106 public void actionPerformed(ActionEvent e)
1113 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1115 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1116 InputEvent.CTRL_DOWN_MASK);
1117 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1118 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1120 InputMap inputMap = frame
1121 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1122 String ctrlW = ctrlWKey.toString();
1123 inputMap.put(ctrlWKey, ctrlW);
1124 inputMap.put(cmdWKey, ctrlW);
1126 ActionMap actionMap = frame.getActionMap();
1127 actionMap.put(ctrlW, closeAction);
1131 public void lostOwnership(Clipboard clipboard, Transferable contents)
1135 Desktop.jalviewClipboard = null;
1138 internalCopy = false;
1142 public void dragEnter(DropTargetDragEvent evt)
1147 public void dragExit(DropTargetEvent evt)
1152 public void dragOver(DropTargetDragEvent evt)
1157 public void dropActionChanged(DropTargetDragEvent evt)
1168 public void drop(DropTargetDropEvent evt)
1170 boolean success = true;
1171 // JAL-1552 - acceptDrop required before getTransferable call for
1172 // Java's Transferable for native dnd
1173 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1174 Transferable t = evt.getTransferable();
1175 List<Object> files = new ArrayList<>();
1176 List<DataSourceType> protocols = new ArrayList<>();
1180 Desktop.transferFromDropTarget(files, protocols, evt, t);
1181 } catch (Exception e)
1183 e.printStackTrace();
1191 for (int i = 0; i < files.size(); i++)
1193 // BH 2018 File or String
1194 Object file = files.get(i);
1195 String fileName = file.toString();
1196 DataSourceType protocol = (protocols == null)
1197 ? DataSourceType.FILE
1199 FileFormatI format = null;
1201 if (fileName.endsWith(".jar"))
1203 format = FileFormat.Jalview;
1208 format = new IdentifyFile().identify(file, protocol);
1210 if (file instanceof File)
1212 Platform.cacheFileData((File) file);
1214 new FileLoader().LoadFile(null, file, protocol, format);
1217 } catch (Exception ex)
1222 evt.dropComplete(success); // need this to ensure input focus is properly
1223 // transfered to any new windows created
1233 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1235 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1236 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1237 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1238 BackupFiles.getEnabled());
1240 chooser.setFileView(new JalviewFileView());
1241 chooser.setDialogTitle(
1242 MessageManager.getString("label.open_local_file"));
1243 chooser.setToolTipText(MessageManager.getString("action.open"));
1245 chooser.setResponseHandler(0, () -> {
1246 File selectedFile = chooser.getSelectedFile();
1247 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1249 FileFormatI format = chooser.getSelectedFormat();
1252 * Call IdentifyFile to verify the file contains what its extension implies.
1253 * Skip this step for dynamically added file formats, because IdentifyFile does
1254 * not know how to recognise them.
1256 if (FileFormats.getInstance().isIdentifiable(format))
1260 format = new IdentifyFile().identify(selectedFile,
1261 DataSourceType.FILE);
1262 } catch (FileFormatException e)
1264 // format = null; //??
1268 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1271 chooser.showOpenDialog(this);
1275 * Shows a dialog for input of a URL at which to retrieve alignment data
1280 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1282 // This construct allows us to have a wider textfield
1284 JLabel label = new JLabel(
1285 MessageManager.getString("label.input_file_url"));
1287 JPanel panel = new JPanel(new GridLayout(2, 1));
1291 * the URL to fetch is input in Java: an editable combobox with history JS:
1292 * (pending JAL-3038) a plain text field
1295 String urlBase = "https://www.";
1296 if (Platform.isJS())
1298 history = new JTextField(urlBase, 35);
1307 JComboBox<String> asCombo = new JComboBox<>();
1308 asCombo.setPreferredSize(new Dimension(400, 20));
1309 asCombo.setEditable(true);
1310 asCombo.addItem(urlBase);
1311 String historyItems = Cache.getProperty("RECENT_URL");
1312 if (historyItems != null)
1314 for (String token : historyItems.split("\\t"))
1316 asCombo.addItem(token);
1323 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1324 MessageManager.getString("action.cancel") };
1325 Runnable action = () -> {
1326 @SuppressWarnings("unchecked")
1327 String url = (history instanceof JTextField
1328 ? ((JTextField) history).getText()
1329 : ((JComboBox<String>) history).getEditor().getItem()
1330 .toString().trim());
1332 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1334 if (viewport != null)
1336 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1337 FileFormat.Jalview);
1341 new FileLoader().LoadFile(url, DataSourceType.URL,
1342 FileFormat.Jalview);
1347 FileFormatI format = null;
1350 format = new IdentifyFile().identify(url, DataSourceType.URL);
1351 } catch (FileFormatException e)
1353 // TODO revise error handling, distinguish between
1354 // URL not found and response not valid
1359 String msg = MessageManager.formatMessage("label.couldnt_locate",
1361 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1362 MessageManager.getString("label.url_not_found"),
1363 JvOptionPane.WARNING_MESSAGE);
1367 if (viewport != null)
1369 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1374 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1378 String dialogOption = MessageManager
1379 .getString("label.input_alignment_from_url");
1380 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1381 .showInternalDialog(panel, dialogOption,
1382 JvOptionPane.YES_NO_CANCEL_OPTION,
1383 JvOptionPane.PLAIN_MESSAGE, null, options,
1384 MessageManager.getString("action.ok"));
1388 * Opens the CutAndPaste window for the user to paste an alignment in to
1391 * - if not null, the pasted alignment is added to the current
1392 * alignment; if null, to a new alignment window
1395 public void inputTextboxMenuItem_actionPerformed(
1396 AlignmentViewPanel viewPanel)
1398 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1399 cap.setForInput(viewPanel);
1400 Desktop.addInternalFrame(cap,
1401 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1406 * Check with user and saving files before actually quitting
1408 public void desktopQuit()
1410 desktopQuit(true, false);
1413 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1415 final Runnable doDesktopQuit = () -> {
1416 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1417 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1418 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1419 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1420 getBounds().y, getWidth(), getHeight()));
1422 if (jconsole != null)
1424 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1425 jconsole.stopConsole();
1430 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1433 // Frames should all close automatically. Keeping external
1434 // viewers open should already be decided by user.
1435 closeAll_actionPerformed(null);
1437 // check for aborted quit
1438 if (QuitHandler.quitCancelled())
1440 jalview.bin.Console.debug("Desktop aborting quit");
1444 if (dialogExecutor != null)
1446 dialogExecutor.shutdownNow();
1449 if (groovyConsole != null)
1451 // suppress a possible repeat prompt to save script
1452 groovyConsole.setDirty(false);
1453 groovyConsole.exit();
1456 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1458 // note that shutdown hook will not be run
1459 jalview.bin.Console.debug("Force Quit selected by user");
1460 Runtime.getRuntime().halt(0);
1463 jalview.bin.Console.debug("Quit selected by user");
1466 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1467 // instance.dispose();
1472 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1473 QuitHandler.defaultCancelQuit);
1477 * Don't call this directly, use desktopQuit() above. Exits the program.
1482 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1483 // not run a second time if gotQuitResponse flag has been set (i.e. user
1484 // confirmed quit of some kind).
1485 Jalview.exit("Desktop exiting.", 0);
1488 private void storeLastKnownDimensions(String string, Rectangle jc)
1490 jalview.bin.Console.debug("Storing last known dimensions for " + string
1491 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1492 + " height:" + jc.height);
1494 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1495 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1496 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1497 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1507 public void aboutMenuItem_actionPerformed(ActionEvent e)
1509 new Thread(new Runnable()
1514 new SplashScreen(false);
1520 * Returns the html text for the About screen, including any available version
1521 * number, build details, author details and citation reference, but without
1522 * the enclosing {@code html} tags
1526 public String getAboutMessage()
1528 StringBuilder message = new StringBuilder(1024);
1529 message.append("<div style=\"font-family: sans-serif;\">")
1530 .append("<h1><strong>Version: ")
1531 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1532 .append("<strong>Built: <em>")
1533 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1534 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1535 .append("</strong>");
1537 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1538 if (latestVersion.equals("Checking"))
1540 // JBP removed this message for 2.11: May be reinstated in future version
1541 // message.append("<br>...Checking latest version...</br>");
1543 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1545 boolean red = false;
1546 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1547 .indexOf("automated build") == -1)
1550 // Displayed when code version and jnlp version do not match and code
1551 // version is not a development build
1552 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1555 message.append("<br>!! Version ")
1556 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1557 .append(" is available for download from ")
1558 .append(Cache.getDefault("www.jalview.org",
1559 "https://www.jalview.org"))
1563 message.append("</div>");
1566 message.append("<br>Authors: ");
1567 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1568 message.append(CITATION);
1570 message.append("</div>");
1572 return message.toString();
1576 * Action on requesting Help documentation
1579 public void documentationMenuItem_actionPerformed()
1583 if (Platform.isJS())
1585 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1594 Help.showHelpWindow();
1596 } catch (Exception ex)
1598 System.err.println("Error opening help: " + ex.getMessage());
1603 public void closeAll_actionPerformed(ActionEvent e)
1605 // TODO show a progress bar while closing?
1606 JInternalFrame[] frames = desktop.getAllFrames();
1607 for (int i = 0; i < frames.length; i++)
1611 frames[i].setClosed(true);
1612 } catch (java.beans.PropertyVetoException ex)
1616 Jalview.setCurrentAlignFrame(null);
1617 jalview.bin.Console.info("ALL CLOSED");
1620 * reset state of singleton objects as appropriate (clear down session state
1621 * when all windows are closed)
1623 StructureSelectionManager ssm = StructureSelectionManager
1624 .getStructureSelectionManager(this);
1631 public int structureViewersStillRunningCount()
1634 JInternalFrame[] frames = desktop.getAllFrames();
1635 for (int i = 0; i < frames.length; i++)
1637 if (frames[i] != null
1638 && frames[i] instanceof JalviewStructureDisplayI)
1640 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1648 public void raiseRelated_actionPerformed(ActionEvent e)
1650 reorderAssociatedWindows(false, false);
1654 public void minimizeAssociated_actionPerformed(ActionEvent e)
1656 reorderAssociatedWindows(true, false);
1659 void closeAssociatedWindows()
1661 reorderAssociatedWindows(false, true);
1667 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1671 protected void garbageCollect_actionPerformed(ActionEvent e)
1673 // We simply collect the garbage
1674 jalview.bin.Console.debug("Collecting garbage...");
1676 jalview.bin.Console.debug("Finished garbage collection.");
1682 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1686 protected void showMemusage_actionPerformed(ActionEvent e)
1688 desktop.showMemoryUsage(showMemusage.isSelected());
1695 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1699 protected void showConsole_actionPerformed(ActionEvent e)
1701 showConsole(showConsole.isSelected());
1704 Console jconsole = null;
1707 * control whether the java console is visible or not
1711 void showConsole(boolean selected)
1713 // TODO: decide if we should update properties file
1714 if (jconsole != null) // BH 2018
1716 showConsole.setSelected(selected);
1717 Cache.setProperty("SHOW_JAVA_CONSOLE",
1718 Boolean.valueOf(selected).toString());
1719 jconsole.setVisible(selected);
1723 void reorderAssociatedWindows(boolean minimize, boolean close)
1725 JInternalFrame[] frames = desktop.getAllFrames();
1726 if (frames == null || frames.length < 1)
1731 AlignmentViewport source = null, target = null;
1732 if (frames[0] instanceof AlignFrame)
1734 source = ((AlignFrame) frames[0]).getCurrentView();
1736 else if (frames[0] instanceof TreePanel)
1738 source = ((TreePanel) frames[0]).getViewPort();
1740 else if (frames[0] instanceof PCAPanel)
1742 source = ((PCAPanel) frames[0]).av;
1744 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1746 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1751 for (int i = 0; i < frames.length; i++)
1754 if (frames[i] == null)
1758 if (frames[i] instanceof AlignFrame)
1760 target = ((AlignFrame) frames[i]).getCurrentView();
1762 else if (frames[i] instanceof TreePanel)
1764 target = ((TreePanel) frames[i]).getViewPort();
1766 else if (frames[i] instanceof PCAPanel)
1768 target = ((PCAPanel) frames[i]).av;
1770 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1772 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1775 if (source == target)
1781 frames[i].setClosed(true);
1785 frames[i].setIcon(minimize);
1788 frames[i].toFront();
1792 } catch (java.beans.PropertyVetoException ex)
1807 protected void preferences_actionPerformed(ActionEvent e)
1809 Preferences.openPreferences();
1813 * Prompts the user to choose a file and then saves the Jalview state as a
1814 * Jalview project file
1817 public void saveState_actionPerformed()
1819 saveState_actionPerformed(false);
1822 public void saveState_actionPerformed(boolean saveAs)
1824 java.io.File projectFile = getProjectFile();
1825 // autoSave indicates we already have a file and don't need to ask
1826 boolean autoSave = projectFile != null && !saveAs
1827 && BackupFiles.getEnabled();
1829 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1830 // saveAs="+saveAs+", Backups
1831 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1833 boolean approveSave = false;
1836 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1839 chooser.setFileView(new JalviewFileView());
1840 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1842 int value = chooser.showSaveDialog(this);
1844 if (value == JalviewFileChooser.APPROVE_OPTION)
1846 projectFile = chooser.getSelectedFile();
1847 setProjectFile(projectFile);
1852 if (approveSave || autoSave)
1854 final Desktop me = this;
1855 final java.io.File chosenFile = projectFile;
1856 new Thread(new Runnable()
1861 // TODO: refactor to Jalview desktop session controller action.
1862 setProgressBar(MessageManager.formatMessage(
1863 "label.saving_jalview_project", new Object[]
1864 { chosenFile.getName() }), chosenFile.hashCode());
1865 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1866 // TODO catch and handle errors for savestate
1867 // TODO prevent user from messing with the Desktop whilst we're saving
1870 boolean doBackup = BackupFiles.getEnabled();
1871 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1874 new Jalview2XML().saveState(
1875 doBackup ? backupfiles.getTempFile() : chosenFile);
1879 backupfiles.setWriteSuccess(true);
1880 backupfiles.rollBackupsAndRenameTempFile();
1882 } catch (OutOfMemoryError oom)
1884 new OOMWarning("Whilst saving current state to "
1885 + chosenFile.getName(), oom);
1886 } catch (Exception ex)
1888 jalview.bin.Console.error("Problems whilst trying to save to "
1889 + chosenFile.getName(), ex);
1890 JvOptionPane.showMessageDialog(me,
1891 MessageManager.formatMessage(
1892 "label.error_whilst_saving_current_state_to",
1894 { chosenFile.getName() }),
1895 MessageManager.getString("label.couldnt_save_project"),
1896 JvOptionPane.WARNING_MESSAGE);
1898 setProgressBar(null, chosenFile.hashCode());
1905 public void saveAsState_actionPerformed(ActionEvent e)
1907 saveState_actionPerformed(true);
1910 protected void setProjectFile(File choice)
1912 this.projectFile = choice;
1915 public File getProjectFile()
1917 return this.projectFile;
1921 * Shows a file chooser dialog and tries to read in the selected file as a
1925 public void loadState_actionPerformed()
1927 final String[] suffix = new String[] { "jvp", "jar" };
1928 final String[] desc = new String[] { "Jalview Project",
1929 "Jalview Project (old)" };
1930 JalviewFileChooser chooser = new JalviewFileChooser(
1931 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1932 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1936 chooser.setFileView(new JalviewFileView());
1937 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1938 chooser.setResponseHandler(0, () -> {
1939 File selectedFile = chooser.getSelectedFile();
1940 setProjectFile(selectedFile);
1941 String choice = selectedFile.getAbsolutePath();
1942 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1943 new Thread(new Runnable()
1950 new Jalview2XML().loadJalviewAlign(selectedFile);
1951 } catch (OutOfMemoryError oom)
1953 new OOMWarning("Whilst loading project from " + choice, oom);
1954 } catch (Exception ex)
1956 jalview.bin.Console.error(
1957 "Problems whilst loading project from " + choice, ex);
1958 JvOptionPane.showMessageDialog(Desktop.desktop,
1959 MessageManager.formatMessage(
1960 "label.error_whilst_loading_project_from",
1963 MessageManager.getString("label.couldnt_load_project"),
1964 JvOptionPane.WARNING_MESSAGE);
1967 }, "Project Loader").start();
1970 chooser.showOpenDialog(this);
1974 public void inputSequence_actionPerformed(ActionEvent e)
1976 new SequenceFetcher(this);
1979 JPanel progressPanel;
1981 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1983 public void startLoading(final Object fileName)
1985 if (fileLoadingCount == 0)
1987 fileLoadingPanels.add(addProgressPanel(MessageManager
1988 .formatMessage("label.loading_file", new Object[]
1994 private JPanel addProgressPanel(String string)
1996 if (progressPanel == null)
1998 progressPanel = new JPanel(new GridLayout(1, 1));
1999 totalProgressCount = 0;
2000 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2002 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2003 JProgressBar progressBar = new JProgressBar();
2004 progressBar.setIndeterminate(true);
2006 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2008 thisprogress.add(progressBar, BorderLayout.CENTER);
2009 progressPanel.add(thisprogress);
2010 ((GridLayout) progressPanel.getLayout()).setRows(
2011 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2012 ++totalProgressCount;
2013 instance.validate();
2014 return thisprogress;
2017 int totalProgressCount = 0;
2019 private void removeProgressPanel(JPanel progbar)
2021 if (progressPanel != null)
2023 synchronized (progressPanel)
2025 progressPanel.remove(progbar);
2026 GridLayout gl = (GridLayout) progressPanel.getLayout();
2027 gl.setRows(gl.getRows() - 1);
2028 if (--totalProgressCount < 1)
2030 this.getContentPane().remove(progressPanel);
2031 progressPanel = null;
2038 public void stopLoading()
2041 if (fileLoadingCount < 1)
2043 while (fileLoadingPanels.size() > 0)
2045 removeProgressPanel(fileLoadingPanels.remove(0));
2047 fileLoadingPanels.clear();
2048 fileLoadingCount = 0;
2053 public static int getViewCount(String alignmentId)
2055 AlignmentViewport[] aps = getViewports(alignmentId);
2056 return (aps == null) ? 0 : aps.length;
2061 * @param alignmentId
2062 * - if null, all sets are returned
2063 * @return all AlignmentPanels concerning the alignmentId sequence set
2065 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2067 if (Desktop.desktop == null)
2069 // no frames created and in headless mode
2070 // TODO: verify that frames are recoverable when in headless mode
2073 List<AlignmentPanel> aps = new ArrayList<>();
2074 AlignFrame[] frames = getAlignFrames();
2079 for (AlignFrame af : frames)
2081 for (AlignmentPanel ap : af.alignPanels)
2083 if (alignmentId == null
2084 || alignmentId.equals(ap.av.getSequenceSetId()))
2090 if (aps.size() == 0)
2094 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2099 * get all the viewports on an alignment.
2101 * @param sequenceSetId
2102 * unique alignment id (may be null - all viewports returned in that
2104 * @return all viewports on the alignment bound to sequenceSetId
2106 public static AlignmentViewport[] getViewports(String sequenceSetId)
2108 List<AlignmentViewport> viewp = new ArrayList<>();
2109 if (desktop != null)
2111 AlignFrame[] frames = Desktop.getAlignFrames();
2113 for (AlignFrame afr : frames)
2115 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2116 .equals(sequenceSetId))
2118 if (afr.alignPanels != null)
2120 for (AlignmentPanel ap : afr.alignPanels)
2122 if (sequenceSetId == null
2123 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2131 viewp.add(afr.getViewport());
2135 if (viewp.size() > 0)
2137 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2144 * Explode the views in the given frame into separate AlignFrame
2148 public static void explodeViews(AlignFrame af)
2150 int size = af.alignPanels.size();
2156 // FIXME: ideally should use UI interface API
2157 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2158 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2159 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2160 for (int i = 0; i < size; i++)
2162 AlignmentPanel ap = af.alignPanels.get(i);
2164 AlignFrame newaf = new AlignFrame(ap);
2166 // transfer reference for existing feature settings to new alignFrame
2167 if (ap == af.alignPanel)
2169 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2171 newaf.featureSettings = viewFeatureSettings;
2173 newaf.setFeatureSettingsGeometry(fsBounds);
2177 * Restore the view's last exploded frame geometry if known. Multiple views from
2178 * one exploded frame share and restore the same (frame) position and size.
2180 Rectangle geometry = ap.av.getExplodedGeometry();
2181 if (geometry != null)
2183 newaf.setBounds(geometry);
2186 ap.av.setGatherViewsHere(false);
2188 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2189 AlignFrame.DEFAULT_HEIGHT);
2190 // and materialise a new feature settings dialog instance for the new
2192 // (closes the old as if 'OK' was pressed)
2193 if (ap == af.alignPanel && newaf.featureSettings != null
2194 && newaf.featureSettings.isOpen()
2195 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2197 newaf.showFeatureSettingsUI();
2201 af.featureSettings = null;
2202 af.alignPanels.clear();
2203 af.closeMenuItem_actionPerformed(true);
2208 * Gather expanded views (separate AlignFrame's) with the same sequence set
2209 * identifier back in to this frame as additional views, and close the
2210 * expanded views. Note the expanded frames may themselves have multiple
2211 * views. We take the lot.
2215 public void gatherViews(AlignFrame source)
2217 source.viewport.setGatherViewsHere(true);
2218 source.viewport.setExplodedGeometry(source.getBounds());
2219 JInternalFrame[] frames = desktop.getAllFrames();
2220 String viewId = source.viewport.getSequenceSetId();
2221 for (int t = 0; t < frames.length; t++)
2223 if (frames[t] instanceof AlignFrame && frames[t] != source)
2225 AlignFrame af = (AlignFrame) frames[t];
2226 boolean gatherThis = false;
2227 for (int a = 0; a < af.alignPanels.size(); a++)
2229 AlignmentPanel ap = af.alignPanels.get(a);
2230 if (viewId.equals(ap.av.getSequenceSetId()))
2233 ap.av.setGatherViewsHere(false);
2234 ap.av.setExplodedGeometry(af.getBounds());
2235 source.addAlignmentPanel(ap, false);
2241 if (af.featureSettings != null && af.featureSettings.isOpen())
2243 if (source.featureSettings == null)
2245 // preserve the feature settings geometry for this frame
2246 source.featureSettings = af.featureSettings;
2247 source.setFeatureSettingsGeometry(
2248 af.getFeatureSettingsGeometry());
2252 // close it and forget
2253 af.featureSettings.close();
2256 af.alignPanels.clear();
2257 af.closeMenuItem_actionPerformed(true);
2262 // refresh the feature setting UI for the source frame if it exists
2263 if (source.featureSettings != null && source.featureSettings.isOpen())
2265 source.showFeatureSettingsUI();
2270 public JInternalFrame[] getAllFrames()
2272 return desktop.getAllFrames();
2276 * Checks the given url to see if it gives a response indicating that the user
2277 * should be informed of a new questionnaire.
2281 public void checkForQuestionnaire(String url)
2283 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2284 // javax.swing.SwingUtilities.invokeLater(jvq);
2285 new Thread(jvq).start();
2288 public void checkURLLinks()
2290 // Thread off the URL link checker
2291 addDialogThread(new Runnable()
2296 if (Cache.getDefault("CHECKURLLINKS", true))
2298 // check what the actual links are - if it's just the default don't
2299 // bother with the warning
2300 List<String> links = Preferences.sequenceUrlLinks
2303 // only need to check links if there is one with a
2304 // SEQUENCE_ID which is not the default EMBL_EBI link
2305 ListIterator<String> li = links.listIterator();
2306 boolean check = false;
2307 List<JLabel> urls = new ArrayList<>();
2308 while (li.hasNext())
2310 String link = li.next();
2311 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2312 && !UrlConstants.isDefaultString(link))
2315 int barPos = link.indexOf("|");
2316 String urlMsg = barPos == -1 ? link
2317 : link.substring(0, barPos) + ": "
2318 + link.substring(barPos + 1);
2319 urls.add(new JLabel(urlMsg));
2327 // ask user to check in case URL links use old style tokens
2328 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2329 JPanel msgPanel = new JPanel();
2330 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2331 msgPanel.add(Box.createVerticalGlue());
2332 JLabel msg = new JLabel(MessageManager
2333 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2334 JLabel msg2 = new JLabel(MessageManager
2335 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2337 for (JLabel url : urls)
2343 final JCheckBox jcb = new JCheckBox(
2344 MessageManager.getString("label.do_not_display_again"));
2345 jcb.addActionListener(new ActionListener()
2348 public void actionPerformed(ActionEvent e)
2350 // update Cache settings for "don't show this again"
2351 boolean showWarningAgain = !jcb.isSelected();
2352 Cache.setProperty("CHECKURLLINKS",
2353 Boolean.valueOf(showWarningAgain).toString());
2358 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2360 .getString("label.SEQUENCE_ID_no_longer_used"),
2361 JvOptionPane.WARNING_MESSAGE);
2368 * Proxy class for JDesktopPane which optionally displays the current memory
2369 * usage and highlights the desktop area with a red bar if free memory runs
2374 public class MyDesktopPane extends JDesktopPane implements Runnable
2376 private static final float ONE_MB = 1048576f;
2378 boolean showMemoryUsage = false;
2382 java.text.NumberFormat df;
2384 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2387 public MyDesktopPane(boolean showMemoryUsage)
2389 showMemoryUsage(showMemoryUsage);
2392 public void showMemoryUsage(boolean showMemory)
2394 this.showMemoryUsage = showMemory;
2397 Thread worker = new Thread(this);
2403 public boolean isShowMemoryUsage()
2405 return showMemoryUsage;
2411 df = java.text.NumberFormat.getNumberInstance();
2412 df.setMaximumFractionDigits(2);
2413 runtime = Runtime.getRuntime();
2415 while (showMemoryUsage)
2419 maxMemory = runtime.maxMemory() / ONE_MB;
2420 allocatedMemory = runtime.totalMemory() / ONE_MB;
2421 freeMemory = runtime.freeMemory() / ONE_MB;
2422 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2424 percentUsage = (totalFreeMemory / maxMemory) * 100;
2426 // if (percentUsage < 20)
2428 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2430 // instance.set.setBorder(border1);
2433 // sleep after showing usage
2435 } catch (Exception ex)
2437 ex.printStackTrace();
2443 public void paintComponent(Graphics g)
2445 if (showMemoryUsage && g != null && df != null)
2447 if (percentUsage < 20)
2449 g.setColor(Color.red);
2451 FontMetrics fm = g.getFontMetrics();
2454 g.drawString(MessageManager.formatMessage("label.memory_stats",
2456 { df.format(totalFreeMemory), df.format(maxMemory),
2457 df.format(percentUsage) }),
2458 10, getHeight() - fm.getHeight());
2462 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2463 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2468 * Accessor method to quickly get all the AlignmentFrames loaded.
2470 * @return an array of AlignFrame, or null if none found
2472 public static AlignFrame[] getAlignFrames()
2474 if (Jalview.isHeadlessMode())
2476 // Desktop.desktop is null in headless mode
2477 return new AlignFrame[] { Jalview.currentAlignFrame };
2480 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2486 List<AlignFrame> avp = new ArrayList<>();
2488 for (int i = frames.length - 1; i > -1; i--)
2490 if (frames[i] instanceof AlignFrame)
2492 avp.add((AlignFrame) frames[i]);
2494 else if (frames[i] instanceof SplitFrame)
2497 * Also check for a split frame containing an AlignFrame
2499 GSplitFrame sf = (GSplitFrame) frames[i];
2500 if (sf.getTopFrame() instanceof AlignFrame)
2502 avp.add((AlignFrame) sf.getTopFrame());
2504 if (sf.getBottomFrame() instanceof AlignFrame)
2506 avp.add((AlignFrame) sf.getBottomFrame());
2510 if (avp.size() == 0)
2514 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2519 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2523 public GStructureViewer[] getJmols()
2525 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2531 List<GStructureViewer> avp = new ArrayList<>();
2533 for (int i = frames.length - 1; i > -1; i--)
2535 if (frames[i] instanceof AppJmol)
2537 GStructureViewer af = (GStructureViewer) frames[i];
2541 if (avp.size() == 0)
2545 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2550 * Add Groovy Support to Jalview
2553 public void groovyShell_actionPerformed()
2557 openGroovyConsole();
2558 } catch (Exception ex)
2560 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2561 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2563 MessageManager.getString("label.couldnt_create_groovy_shell"),
2564 MessageManager.getString("label.groovy_support_failed"),
2565 JvOptionPane.ERROR_MESSAGE);
2570 * Open the Groovy console
2572 void openGroovyConsole()
2574 if (groovyConsole == null)
2576 groovyConsole = new groovy.ui.Console();
2577 groovyConsole.setVariable("Jalview", this);
2578 groovyConsole.run();
2581 * We allow only one console at a time, so that AlignFrame menu option
2582 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2583 * enable 'Run script', when the console is opened, and the reverse when it is
2586 Window window = (Window) groovyConsole.getFrame();
2587 window.addWindowListener(new WindowAdapter()
2590 public void windowClosed(WindowEvent e)
2593 * rebind CMD-Q from Groovy Console to Jalview Quit
2596 enableExecuteGroovy(false);
2602 * show Groovy console window (after close and reopen)
2604 ((Window) groovyConsole.getFrame()).setVisible(true);
2607 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2608 * opening a second console
2610 enableExecuteGroovy(true);
2614 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2615 * binding when opened
2617 protected void addQuitHandler()
2620 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2622 .getKeyStroke(KeyEvent.VK_Q,
2623 jalview.util.ShortcutKeyMaskExWrapper
2624 .getMenuShortcutKeyMaskEx()),
2626 getRootPane().getActionMap().put("Quit", new AbstractAction()
2629 public void actionPerformed(ActionEvent e)
2637 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2640 * true if Groovy console is open
2642 public void enableExecuteGroovy(boolean enabled)
2645 * disable opening a second Groovy console (or re-enable when the console is
2648 groovyShell.setEnabled(!enabled);
2650 AlignFrame[] alignFrames = getAlignFrames();
2651 if (alignFrames != null)
2653 for (AlignFrame af : alignFrames)
2655 af.setGroovyEnabled(enabled);
2661 * Progress bars managed by the IProgressIndicator method.
2663 private Hashtable<Long, JPanel> progressBars;
2665 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2670 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2673 public void setProgressBar(String message, long id)
2675 if (progressBars == null)
2677 progressBars = new Hashtable<>();
2678 progressBarHandlers = new Hashtable<>();
2681 if (progressBars.get(Long.valueOf(id)) != null)
2683 JPanel panel = progressBars.remove(Long.valueOf(id));
2684 if (progressBarHandlers.contains(Long.valueOf(id)))
2686 progressBarHandlers.remove(Long.valueOf(id));
2688 removeProgressPanel(panel);
2692 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2699 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2700 * jalview.gui.IProgressIndicatorHandler)
2703 public void registerHandler(final long id,
2704 final IProgressIndicatorHandler handler)
2706 if (progressBarHandlers == null
2707 || !progressBars.containsKey(Long.valueOf(id)))
2709 throw new Error(MessageManager.getString(
2710 "error.call_setprogressbar_before_registering_handler"));
2712 progressBarHandlers.put(Long.valueOf(id), handler);
2713 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2714 if (handler.canCancel())
2716 JButton cancel = new JButton(
2717 MessageManager.getString("action.cancel"));
2718 final IProgressIndicator us = this;
2719 cancel.addActionListener(new ActionListener()
2723 public void actionPerformed(ActionEvent e)
2725 handler.cancelActivity(id);
2726 us.setProgressBar(MessageManager
2727 .formatMessage("label.cancelled_params", new Object[]
2728 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2732 progressPanel.add(cancel, BorderLayout.EAST);
2738 * @return true if any progress bars are still active
2741 public boolean operationInProgress()
2743 if (progressBars != null && progressBars.size() > 0)
2751 * This will return the first AlignFrame holding the given viewport instance.
2752 * It will break if there are more than one AlignFrames viewing a particular
2756 * @return alignFrame for viewport
2758 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2760 if (desktop != null)
2762 AlignmentPanel[] aps = getAlignmentPanels(
2763 viewport.getSequenceSetId());
2764 for (int panel = 0; aps != null && panel < aps.length; panel++)
2766 if (aps[panel] != null && aps[panel].av == viewport)
2768 return aps[panel].alignFrame;
2775 public VamsasApplication getVamsasApplication()
2777 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2783 * flag set if jalview GUI is being operated programmatically
2785 private boolean inBatchMode = false;
2788 * check if jalview GUI is being operated programmatically
2790 * @return inBatchMode
2792 public boolean isInBatchMode()
2798 * set flag if jalview GUI is being operated programmatically
2800 * @param inBatchMode
2802 public void setInBatchMode(boolean inBatchMode)
2804 this.inBatchMode = inBatchMode;
2808 * start service discovery and wait till it is done
2810 public void startServiceDiscovery()
2812 startServiceDiscovery(false);
2816 * start service discovery threads - blocking or non-blocking
2820 public void startServiceDiscovery(boolean blocking)
2822 startServiceDiscovery(blocking, false);
2826 * start service discovery threads
2829 * - false means call returns immediately
2830 * @param ignore_SHOW_JWS2_SERVICES_preference
2831 * - when true JABA services are discovered regardless of user's JWS2
2832 * discovery preference setting
2834 public void startServiceDiscovery(boolean blocking,
2835 boolean ignore_SHOW_JWS2_SERVICES_preference)
2837 boolean alive = true;
2838 Thread t0 = null, t1 = null, t2 = null;
2839 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2842 // todo: changesupport handlers need to be transferred
2843 if (discoverer == null)
2845 discoverer = new jalview.ws.jws1.Discoverer();
2846 // register PCS handler for desktop.
2847 discoverer.addPropertyChangeListener(changeSupport);
2849 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2850 // until we phase out completely
2851 (t0 = new Thread(discoverer)).start();
2854 if (ignore_SHOW_JWS2_SERVICES_preference
2855 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2857 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2858 .startDiscoverer(changeSupport);
2862 // TODO: do rest service discovery
2871 } catch (Exception e)
2874 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2875 || (t3 != null && t3.isAlive())
2876 || (t0 != null && t0.isAlive());
2882 * called to check if the service discovery process completed successfully.
2886 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2888 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2890 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2891 .getErrorMessages();
2894 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2896 if (serviceChangedDialog == null)
2898 // only run if we aren't already displaying one of these.
2899 addDialogThread(serviceChangedDialog = new Runnable()
2906 * JalviewDialog jd =new JalviewDialog() {
2908 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2910 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2912 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2914 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2916 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2917 * + " or mis-configured HTTP proxy settings.<br/>" +
2918 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2919 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2920 * true, true, "Web Service Configuration Problem", 450, 400);
2922 * jd.waitForInput();
2924 JvOptionPane.showConfirmDialog(Desktop.desktop,
2925 new JLabel("<html><table width=\"450\"><tr><td>"
2926 + ermsg + "</td></tr></table>"
2927 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2928 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2929 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2930 + " Tools->Preferences dialog box to change them.</p></html>"),
2931 "Web Service Configuration Problem",
2932 JvOptionPane.DEFAULT_OPTION,
2933 JvOptionPane.ERROR_MESSAGE);
2934 serviceChangedDialog = null;
2942 jalview.bin.Console.error(
2943 "Errors reported by JABA discovery service. Check web services preferences.\n"
2950 private Runnable serviceChangedDialog = null;
2953 * start a thread to open a URL in the configured browser. Pops up a warning
2954 * dialog to the user if there is an exception when calling out to the browser
2959 public static void showUrl(final String url)
2961 showUrl(url, Desktop.instance);
2965 * Like showUrl but allows progress handler to be specified
2969 * (null) or object implementing IProgressIndicator
2971 public static void showUrl(final String url,
2972 final IProgressIndicator progress)
2974 new Thread(new Runnable()
2981 if (progress != null)
2983 progress.setProgressBar(MessageManager
2984 .formatMessage("status.opening_params", new Object[]
2985 { url }), this.hashCode());
2987 jalview.util.BrowserLauncher.openURL(url);
2988 } catch (Exception ex)
2990 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2992 .getString("label.web_browser_not_found_unix"),
2993 MessageManager.getString("label.web_browser_not_found"),
2994 JvOptionPane.WARNING_MESSAGE);
2996 ex.printStackTrace();
2998 if (progress != null)
3000 progress.setProgressBar(null, this.hashCode());
3006 public static WsParamSetManager wsparamManager = null;
3008 public static ParamManager getUserParameterStore()
3010 if (wsparamManager == null)
3012 wsparamManager = new WsParamSetManager();
3014 return wsparamManager;
3018 * static hyperlink handler proxy method for use by Jalview's internal windows
3022 public static void hyperlinkUpdate(HyperlinkEvent e)
3024 if (e.getEventType() == EventType.ACTIVATED)
3029 url = e.getURL().toString();
3030 Desktop.showUrl(url);
3031 } catch (Exception x)
3036 .error("Couldn't handle string " + url + " as a URL.");
3038 // ignore any exceptions due to dud links.
3045 * single thread that handles display of dialogs to user.
3047 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3050 * flag indicating if dialogExecutor should try to acquire a permit
3052 private volatile boolean dialogPause = true;
3057 private Semaphore block = new Semaphore(0);
3059 private static groovy.ui.Console groovyConsole;
3062 * add another dialog thread to the queue
3066 public void addDialogThread(final Runnable prompter)
3068 dialogExecutor.submit(new Runnable()
3075 acquireDialogQueue();
3077 if (instance == null)
3083 SwingUtilities.invokeAndWait(prompter);
3084 } catch (Exception q)
3086 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3093 private boolean dialogQueueStarted = false;
3095 public void startDialogQueue()
3097 if (dialogQueueStarted)
3101 // set the flag so we don't pause waiting for another permit and semaphore
3102 // the current task to begin
3103 releaseDialogQueue();
3104 dialogQueueStarted = true;
3107 public void acquireDialogQueue()
3113 } catch (InterruptedException e)
3115 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3120 public void releaseDialogQueue()
3127 dialogPause = false;
3131 * Outputs an image of the desktop to file in EPS format, after prompting the
3132 * user for choice of Text or Lineart character rendering (unless a preference
3133 * has been set). The file name is generated as
3136 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3140 protected void snapShotWindow_actionPerformed(ActionEvent e)
3142 // currently the menu option to do this is not shown
3145 int width = getWidth();
3146 int height = getHeight();
3148 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3149 ImageWriterI writer = new ImageWriterI()
3152 public void exportImage(Graphics g) throws Exception
3155 jalview.bin.Console.info("Successfully written snapshot to file "
3156 + of.getAbsolutePath());
3159 String title = "View of desktop";
3160 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3163 exporter.doExport(of, this, width, height, title);
3164 } catch (ImageOutputException ioex) {
3165 jalview.bin.Console.error("Unexpected error whilst writing Jalview desktop snapshot as EPS",ioex);
3170 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3171 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3172 * and location last time the view was expanded (if any). However it does not
3173 * remember the split pane divider location - this is set to match the
3174 * 'exploding' frame.
3178 public void explodeViews(SplitFrame sf)
3180 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3181 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3182 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3184 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3186 int viewCount = topPanels.size();
3193 * Processing in reverse order works, forwards order leaves the first panels not
3194 * visible. I don't know why!
3196 for (int i = viewCount - 1; i >= 0; i--)
3199 * Make new top and bottom frames. These take over the respective AlignmentPanel
3200 * objects, including their AlignmentViewports, so the cdna/protein
3201 * relationships between the viewports is carried over to the new split frames.
3203 * explodedGeometry holds the (x, y) position of the previously exploded
3204 * SplitFrame, and the (width, height) of the AlignFrame component
3206 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3207 AlignFrame newTopFrame = new AlignFrame(topPanel);
3208 newTopFrame.setSize(oldTopFrame.getSize());
3209 newTopFrame.setVisible(true);
3210 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3211 .getExplodedGeometry();
3212 if (geometry != null)
3214 newTopFrame.setSize(geometry.getSize());
3217 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3218 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3219 newBottomFrame.setSize(oldBottomFrame.getSize());
3220 newBottomFrame.setVisible(true);
3221 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3222 .getExplodedGeometry();
3223 if (geometry != null)
3225 newBottomFrame.setSize(geometry.getSize());
3228 topPanel.av.setGatherViewsHere(false);
3229 bottomPanel.av.setGatherViewsHere(false);
3230 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3232 if (geometry != null)
3234 splitFrame.setLocation(geometry.getLocation());
3236 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3240 * Clear references to the panels (now relocated in the new SplitFrames) before
3241 * closing the old SplitFrame.
3244 bottomPanels.clear();
3249 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3250 * back into the given SplitFrame as additional views. Note that the gathered
3251 * frames may themselves have multiple views.
3255 public void gatherViews(GSplitFrame source)
3258 * special handling of explodedGeometry for a view within a SplitFrame: - it
3259 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3260 * height) of the AlignFrame component
3262 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3263 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3264 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3265 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3266 myBottomFrame.viewport
3267 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3268 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3269 myTopFrame.viewport.setGatherViewsHere(true);
3270 myBottomFrame.viewport.setGatherViewsHere(true);
3271 String topViewId = myTopFrame.viewport.getSequenceSetId();
3272 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3274 JInternalFrame[] frames = desktop.getAllFrames();
3275 for (JInternalFrame frame : frames)
3277 if (frame instanceof SplitFrame && frame != source)
3279 SplitFrame sf = (SplitFrame) frame;
3280 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3281 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3282 boolean gatherThis = false;
3283 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3285 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3286 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3287 if (topViewId.equals(topPanel.av.getSequenceSetId())
3288 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3291 topPanel.av.setGatherViewsHere(false);
3292 bottomPanel.av.setGatherViewsHere(false);
3293 topPanel.av.setExplodedGeometry(
3294 new Rectangle(sf.getLocation(), topFrame.getSize()));
3295 bottomPanel.av.setExplodedGeometry(
3296 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3297 myTopFrame.addAlignmentPanel(topPanel, false);
3298 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3304 topFrame.getAlignPanels().clear();
3305 bottomFrame.getAlignPanels().clear();
3312 * The dust settles...give focus to the tab we did this from.
3314 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3317 public static groovy.ui.Console getGroovyConsole()
3319 return groovyConsole;
3323 * handles the payload of a drag and drop event.
3325 * TODO refactor to desktop utilities class
3328 * - Data source strings extracted from the drop event
3330 * - protocol for each data source extracted from the drop event
3334 * - the payload from the drop event
3337 public static void transferFromDropTarget(List<Object> files,
3338 List<DataSourceType> protocols, DropTargetDropEvent evt,
3339 Transferable t) throws Exception
3342 DataFlavor uriListFlavor = new DataFlavor(
3343 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3346 urlFlavour = new DataFlavor(
3347 "application/x-java-url; class=java.net.URL");
3348 } catch (ClassNotFoundException cfe)
3350 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3354 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3359 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3360 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3361 // means url may be null.
3364 protocols.add(DataSourceType.URL);
3365 files.add(url.toString());
3366 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3367 + files.get(files.size() - 1));
3372 if (Platform.isAMacAndNotJS())
3375 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3378 } catch (Throwable ex)
3380 jalview.bin.Console.debug("URL drop handler failed.", ex);
3383 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3385 // Works on Windows and MacOSX
3386 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3387 for (Object file : (List) t
3388 .getTransferData(DataFlavor.javaFileListFlavor))
3391 protocols.add(DataSourceType.FILE);
3396 // Unix like behaviour
3397 boolean added = false;
3399 if (t.isDataFlavorSupported(uriListFlavor))
3401 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3402 // This is used by Unix drag system
3403 data = (String) t.getTransferData(uriListFlavor);
3407 // fallback to text: workaround - on OSX where there's a JVM bug
3409 .debug("standard URIListFlavor failed. Trying text");
3410 // try text fallback
3411 DataFlavor textDf = new DataFlavor(
3412 "text/plain;class=java.lang.String");
3413 if (t.isDataFlavorSupported(textDf))
3415 data = (String) t.getTransferData(textDf);
3418 jalview.bin.Console.debug("Plain text drop content returned "
3419 + (data == null ? "Null - failed" : data));
3424 while (protocols.size() < files.size())
3426 jalview.bin.Console.debug("Adding missing FILE protocol for "
3427 + files.get(protocols.size()));
3428 protocols.add(DataSourceType.FILE);
3430 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3431 data, "\r\n"); st.hasMoreTokens();)
3434 String s = st.nextToken();
3435 if (s.startsWith("#"))
3437 // the line is a comment (as per the RFC 2483)
3440 java.net.URI uri = new java.net.URI(s);
3441 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3443 protocols.add(DataSourceType.URL);
3444 files.add(uri.toString());
3448 // otherwise preserve old behaviour: catch all for file objects
3449 java.io.File file = new java.io.File(uri);
3450 protocols.add(DataSourceType.FILE);
3451 files.add(file.toString());
3456 if (jalview.bin.Console.isDebugEnabled())
3458 if (data == null || !added)
3461 if (t.getTransferDataFlavors() != null
3462 && t.getTransferDataFlavors().length > 0)
3464 jalview.bin.Console.debug(
3465 "Couldn't resolve drop data. Here are the supported flavors:");
3466 for (DataFlavor fl : t.getTransferDataFlavors())
3468 jalview.bin.Console.debug(
3469 "Supported transfer dataflavor: " + fl.toString());
3470 Object df = t.getTransferData(fl);
3473 jalview.bin.Console.debug("Retrieves: " + df);
3477 jalview.bin.Console.debug("Retrieved nothing");
3484 .debug("Couldn't resolve dataflavor for drop: "
3490 if (Platform.isWindowsAndNotJS())
3493 .debug("Scanning dropped content for Windows Link Files");
3495 // resolve any .lnk files in the file drop
3496 for (int f = 0; f < files.size(); f++)
3498 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3499 if (protocols.get(f).equals(DataSourceType.FILE)
3500 && (source.endsWith(".lnk") || source.endsWith(".url")
3501 || source.endsWith(".site")))
3505 Object obj = files.get(f);
3506 File lf = (obj instanceof File ? (File) obj
3507 : new File((String) obj));
3508 // process link file to get a URL
3509 jalview.bin.Console.debug("Found potential link file: " + lf);
3510 WindowsShortcut wscfile = new WindowsShortcut(lf);
3511 String fullname = wscfile.getRealFilename();
3512 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3513 files.set(f, fullname);
3514 jalview.bin.Console.debug("Parsed real filename " + fullname
3515 + " to extract protocol: " + protocols.get(f));
3516 } catch (Exception ex)
3518 jalview.bin.Console.error(
3519 "Couldn't parse " + files.get(f) + " as a link file.",
3528 * Sets the Preferences property for experimental features to True or False
3529 * depending on the state of the controlling menu item
3532 protected void showExperimental_actionPerformed(boolean selected)
3534 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3538 * Answers a (possibly empty) list of any structure viewer frames (currently
3539 * for either Jmol or Chimera) which are currently open. This may optionally
3540 * be restricted to viewers of a specified class, or viewers linked to a
3541 * specified alignment panel.
3544 * if not null, only return viewers linked to this panel
3545 * @param structureViewerClass
3546 * if not null, only return viewers of this class
3549 public List<StructureViewerBase> getStructureViewers(
3550 AlignmentPanel apanel,
3551 Class<? extends StructureViewerBase> structureViewerClass)
3553 List<StructureViewerBase> result = new ArrayList<>();
3554 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3556 for (JInternalFrame frame : frames)
3558 if (frame instanceof StructureViewerBase)
3560 if (structureViewerClass == null
3561 || structureViewerClass.isInstance(frame))
3564 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3566 result.add((StructureViewerBase) frame);
3574 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3576 private static boolean debugScaleMessageDone = false;
3578 public static void debugScaleMessage(Graphics g)
3580 if (debugScaleMessageDone)
3584 // output used by tests to check HiDPI scaling settings in action
3587 Graphics2D gg = (Graphics2D) g;
3590 AffineTransform t = gg.getTransform();
3591 double scaleX = t.getScaleX();
3592 double scaleY = t.getScaleY();
3593 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3594 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3595 debugScaleMessageDone = true;
3599 jalview.bin.Console.debug("Desktop graphics null");
3601 } catch (Exception e)
3603 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3608 * closes the current instance window, disposes and forgets about it.
3610 public static void closeDesktop()
3612 if (Desktop.instance != null) {
3613 Desktop.instance.closeAll_actionPerformed(null);
3614 Desktop.instance.setVisible(false);
3615 Desktop.instance.dispose();
3616 Desktop.instance = null;