2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JFrame;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.WindowConstants;
95 import javax.swing.event.HyperlinkEvent;
96 import javax.swing.event.HyperlinkEvent.EventType;
97 import javax.swing.event.InternalFrameAdapter;
98 import javax.swing.event.InternalFrameEvent;
100 import org.stackoverflowusers.file.WindowsShortcut;
102 import jalview.api.AlignViewportI;
103 import jalview.api.AlignmentViewPanel;
104 import jalview.api.structures.JalviewStructureDisplayI;
105 import jalview.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.datamodel.Alignment;
108 import jalview.datamodel.HiddenColumns;
109 import jalview.datamodel.Sequence;
110 import jalview.datamodel.SequenceI;
111 import jalview.gui.ImageExporter.ImageWriterI;
112 import jalview.gui.QuitHandler.QResponse;
113 import jalview.io.BackupFiles;
114 import jalview.io.DataSourceType;
115 import jalview.io.FileFormat;
116 import jalview.io.FileFormatException;
117 import jalview.io.FileFormatI;
118 import jalview.io.FileFormats;
119 import jalview.io.FileLoader;
120 import jalview.io.FormatAdapter;
121 import jalview.io.IdentifyFile;
122 import jalview.io.JalviewFileChooser;
123 import jalview.io.JalviewFileView;
124 import jalview.io.exceptions.ImageOutputException;
125 import jalview.jbgui.GSplitFrame;
126 import jalview.jbgui.GStructureViewer;
127 import jalview.project.Jalview2XML;
128 import jalview.structure.StructureSelectionManager;
129 import jalview.urls.IdOrgSettings;
130 import jalview.util.BrowserLauncher;
131 import jalview.util.ChannelProperties;
132 import jalview.util.ImageMaker.TYPE;
133 import jalview.util.LaunchUtils;
134 import jalview.util.MessageManager;
135 import jalview.util.Platform;
136 import jalview.util.ShortcutKeyMaskExWrapper;
137 import jalview.util.UrlConstants;
138 import jalview.viewmodel.AlignmentViewport;
139 import jalview.ws.params.ParamManager;
140 import jalview.ws.utils.UrlDownloadClient;
147 * @version $Revision: 1.155 $
149 public class Desktop extends jalview.jbgui.GDesktop
150 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
151 jalview.api.StructureSelectionManagerProvider
153 private static final String CITATION;
156 URL bg_logo_url = ChannelProperties.getImageURL(
157 "bg_logo." + String.valueOf(SplashScreen.logoSize));
158 URL uod_logo_url = ChannelProperties.getImageURL(
159 "uod_banner." + String.valueOf(SplashScreen.logoSize));
160 boolean logo = (bg_logo_url != null || uod_logo_url != null);
161 StringBuilder sb = new StringBuilder();
163 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
168 sb.append(bg_logo_url == null ? ""
169 : "<img alt=\"Barton Group logo\" src=\""
170 + bg_logo_url.toString() + "\">");
171 sb.append(uod_logo_url == null ? ""
172 : " <img alt=\"University of Dundee shield\" src=\""
173 + uod_logo_url.toString() + "\">");
175 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
176 sb.append("<br><br>If you use Jalview, please cite:"
177 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
178 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
179 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
180 CITATION = sb.toString();
183 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
185 private static int DEFAULT_MIN_WIDTH = 300;
187 private static int DEFAULT_MIN_HEIGHT = 250;
189 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
191 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
193 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
195 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
197 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
199 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
201 public static void setLiveDragMode(boolean b)
203 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
204 : JDesktopPane.OUTLINE_DRAG_MODE;
206 desktop.setDragMode(DRAG_MODE);
209 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
211 public static boolean nosplash = false;
214 * news reader - null if it was never started.
216 private BlogReader jvnews = null;
218 private File projectFile;
222 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
224 public void addJalviewPropertyChangeListener(
225 PropertyChangeListener listener)
227 changeSupport.addJalviewPropertyChangeListener(listener);
231 * @param propertyName
233 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
234 * java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(String propertyName,
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void removeJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.removeJalviewPropertyChangeListener(propertyName,
255 /** Singleton Desktop instance */
256 public static Desktop instance;
258 public static MyDesktopPane desktop;
260 public static MyDesktopPane getDesktop()
262 // BH 2018 could use currentThread() here as a reference to a
263 // Hashtable<Thread, MyDesktopPane> in JavaScript
267 static int openFrameCount = 0;
269 static final int xOffset = 30;
271 static final int yOffset = 30;
273 public static jalview.ws.jws1.Discoverer discoverer;
275 public static Object[] jalviewClipboard;
277 public static boolean internalCopy = false;
279 static int fileLoadingCount = 0;
281 class MyDesktopManager implements DesktopManager
284 private DesktopManager delegate;
286 public MyDesktopManager(DesktopManager delegate)
288 this.delegate = delegate;
292 public void activateFrame(JInternalFrame f)
296 delegate.activateFrame(f);
297 } catch (NullPointerException npe)
299 Point p = getMousePosition();
300 instance.showPasteMenu(p.x, p.y);
305 public void beginDraggingFrame(JComponent f)
307 delegate.beginDraggingFrame(f);
311 public void beginResizingFrame(JComponent f, int direction)
313 delegate.beginResizingFrame(f, direction);
317 public void closeFrame(JInternalFrame f)
319 delegate.closeFrame(f);
323 public void deactivateFrame(JInternalFrame f)
325 delegate.deactivateFrame(f);
329 public void deiconifyFrame(JInternalFrame f)
331 delegate.deiconifyFrame(f);
335 public void dragFrame(JComponent f, int newX, int newY)
341 delegate.dragFrame(f, newX, newY);
345 public void endDraggingFrame(JComponent f)
347 delegate.endDraggingFrame(f);
352 public void endResizingFrame(JComponent f)
354 delegate.endResizingFrame(f);
359 public void iconifyFrame(JInternalFrame f)
361 delegate.iconifyFrame(f);
365 public void maximizeFrame(JInternalFrame f)
367 delegate.maximizeFrame(f);
371 public void minimizeFrame(JInternalFrame f)
373 delegate.minimizeFrame(f);
377 public void openFrame(JInternalFrame f)
379 delegate.openFrame(f);
383 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
390 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
394 public void setBoundsForFrame(JComponent f, int newX, int newY,
395 int newWidth, int newHeight)
397 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
400 // All other methods, simply delegate
405 * Creates a new Desktop object.
411 * A note to implementors. It is ESSENTIAL that any activities that might
412 * block are spawned off as threads rather than waited for during this
417 doConfigureStructurePrefs();
418 setTitle(ChannelProperties.getProperty("app_name") + " "
419 + Cache.getProperty("VERSION"));
422 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
423 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
424 * officially documented or guaranteed to exist, so we access it via
425 * reflection. There appear to be unfathomable criteria about what this
426 * string can contain, and it if doesn't meet those criteria then "java"
427 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
428 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
429 * not. The reflection access may generate a warning: WARNING: An illegal
430 * reflective access operation has occurred WARNING: Illegal reflective
431 * access by jalview.gui.Desktop () to field
432 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
434 if (Platform.isLinux())
436 if (LaunchUtils.getJavaVersion() >= 11)
439 * Send this message to stderr as the warning that follows (due to
440 * reflection) also goes to stderr.
443 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
445 final String awtAppClassName = "awtAppClassName";
448 Toolkit xToolkit = Toolkit.getDefaultToolkit();
449 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
450 Field awtAppClassNameField = null;
452 if (Arrays.stream(declaredFields)
453 .anyMatch(f -> f.getName().equals(awtAppClassName)))
455 awtAppClassNameField = xToolkit.getClass()
456 .getDeclaredField(awtAppClassName);
459 String title = ChannelProperties.getProperty("app_name");
460 if (awtAppClassNameField != null)
462 awtAppClassNameField.setAccessible(true);
463 awtAppClassNameField.set(xToolkit, title);
468 .debug("XToolkit: " + awtAppClassName + " not found");
470 } catch (Exception e)
472 jalview.bin.Console.debug("Error setting " + awtAppClassName);
473 jalview.bin.Console.trace(Cache.getStackTraceString(e));
477 setIconImages(ChannelProperties.getIconList());
479 // override quit handling when GUI OS close [X] button pressed
480 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
481 addWindowListener(new WindowAdapter()
484 public void windowClosing(WindowEvent ev)
486 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
490 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
492 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
493 desktop = new MyDesktopPane(selmemusage);
495 showMemusage.setSelected(selmemusage);
496 desktop.setBackground(Color.white);
498 getContentPane().setLayout(new BorderLayout());
499 // alternate config - have scrollbars - see notes in JAL-153
500 // JScrollPane sp = new JScrollPane();
501 // sp.getViewport().setView(desktop);
502 // getContentPane().add(sp, BorderLayout.CENTER);
504 // BH 2018 - just an experiment to try unclipped JInternalFrames.
507 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
510 getContentPane().add(desktop, BorderLayout.CENTER);
511 desktop.setDragMode(DRAG_MODE);
513 // This line prevents Windows Look&Feel resizing all new windows to maximum
514 // if previous window was maximised
515 desktop.setDesktopManager(new MyDesktopManager(
516 Platform.isJS() ? desktop.getDesktopManager()
517 : new DefaultDesktopManager()));
519 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
520 : Platform.isAMacAndNotJS()
521 ? new AquaInternalFrameManager(
522 desktop.getDesktopManager())
523 : desktop.getDesktopManager())));
526 Rectangle dims = getLastKnownDimensions("");
533 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
534 int xPos = Math.max(5, (screenSize.width - 900) / 2);
535 int yPos = Math.max(5, (screenSize.height - 650) / 2);
536 setBounds(xPos, yPos, 900, 650);
539 if (!Platform.isJS())
546 jconsole = new Console(this, showjconsole);
547 jconsole.setHeader(Cache.getVersionDetailsForConsole());
548 showConsole(showjconsole);
550 showNews.setVisible(false);
552 experimentalFeatures.setSelected(showExperimental());
554 getIdentifiersOrgData();
558 // Spawn a thread that shows the splashscreen
561 SwingUtilities.invokeLater(new Runnable()
566 new SplashScreen(true);
571 // Thread off a new instance of the file chooser - this reduces the time
573 // takes to open it later on.
574 new Thread(new Runnable()
579 jalview.bin.Console.debug("Filechooser init thread started.");
580 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
581 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
583 jalview.bin.Console.debug("Filechooser init thread finished.");
586 // Add the service change listener
587 changeSupport.addJalviewPropertyChangeListener("services",
588 new PropertyChangeListener()
592 public void propertyChange(PropertyChangeEvent evt)
595 .debug("Firing service changed event for "
596 + evt.getNewValue());
597 JalviewServicesChanged(evt);
602 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
605 this.addMouseListener(ma = new MouseAdapter()
608 public void mousePressed(MouseEvent evt)
610 if (evt.isPopupTrigger()) // Mac
612 showPasteMenu(evt.getX(), evt.getY());
617 public void mouseReleased(MouseEvent evt)
619 if (evt.isPopupTrigger()) // Windows
621 showPasteMenu(evt.getX(), evt.getY());
625 desktop.addMouseListener(ma);
629 * Answers true if user preferences to enable experimental features is True
634 public boolean showExperimental()
636 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
637 Boolean.FALSE.toString());
638 return Boolean.valueOf(experimental).booleanValue();
641 public void doConfigureStructurePrefs()
643 // configure services
644 StructureSelectionManager ssm = StructureSelectionManager
645 .getStructureSelectionManager(this);
646 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
648 ssm.setAddTempFacAnnot(
649 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
650 ssm.setProcessSecondaryStructure(
651 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
652 // JAL-3915 - RNAView is no longer an option so this has no effect
653 ssm.setSecStructServices(
654 Cache.getDefault(Preferences.USE_RNAVIEW, false));
658 ssm.setAddTempFacAnnot(false);
659 ssm.setProcessSecondaryStructure(false);
660 ssm.setSecStructServices(false);
664 public void checkForNews()
666 final Desktop me = this;
667 // Thread off the news reader, in case there are connection problems.
668 new Thread(new Runnable()
673 jalview.bin.Console.debug("Starting news thread.");
674 jvnews = new BlogReader(me);
675 showNews.setVisible(true);
676 jalview.bin.Console.debug("Completed news thread.");
681 public void getIdentifiersOrgData()
683 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
684 {// Thread off the identifiers fetcher
685 new Thread(new Runnable()
691 .debug("Downloading data from identifiers.org");
694 UrlDownloadClient.download(IdOrgSettings.getUrl(),
695 IdOrgSettings.getDownloadLocation());
696 } catch (IOException e)
699 .debug("Exception downloading identifiers.org data"
709 protected void showNews_actionPerformed(ActionEvent e)
711 showNews(showNews.isSelected());
714 void showNews(boolean visible)
716 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
717 showNews.setSelected(visible);
718 if (visible && !jvnews.isVisible())
720 new Thread(new Runnable()
725 long now = System.currentTimeMillis();
726 Desktop.instance.setProgressBar(
727 MessageManager.getString("status.refreshing_news"), now);
728 jvnews.refreshNews();
729 Desktop.instance.setProgressBar(null, now);
737 * recover the last known dimensions for a jalview window
740 * - empty string is desktop, all other windows have unique prefix
741 * @return null or last known dimensions scaled to current geometry (if last
742 * window geom was known)
744 Rectangle getLastKnownDimensions(String windowName)
746 // TODO: lock aspect ratio for scaling desktop Bug #0058199
747 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
748 String x = Cache.getProperty(windowName + "SCREEN_X");
749 String y = Cache.getProperty(windowName + "SCREEN_Y");
750 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
751 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
752 if ((x != null) && (y != null) && (width != null) && (height != null))
754 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
755 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
756 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
758 // attempt #1 - try to cope with change in screen geometry - this
759 // version doesn't preserve original jv aspect ratio.
760 // take ratio of current screen size vs original screen size.
761 double sw = ((1f * screenSize.width) / (1f * Integer
762 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
763 double sh = ((1f * screenSize.height) / (1f * Integer
764 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
765 // rescale the bounds depending upon the current screen geometry.
766 ix = (int) (ix * sw);
767 iw = (int) (iw * sw);
768 iy = (int) (iy * sh);
769 ih = (int) (ih * sh);
770 while (ix >= screenSize.width)
772 jalview.bin.Console.debug(
773 "Window geometry location recall error: shifting horizontal to within screenbounds.");
774 ix -= screenSize.width;
776 while (iy >= screenSize.height)
778 jalview.bin.Console.debug(
779 "Window geometry location recall error: shifting vertical to within screenbounds.");
780 iy -= screenSize.height;
782 jalview.bin.Console.debug(
783 "Got last known dimensions for " + windowName + ": x:" + ix
784 + " y:" + iy + " width:" + iw + " height:" + ih);
786 // return dimensions for new instance
787 return new Rectangle(ix, iy, iw, ih);
792 void showPasteMenu(int x, int y)
794 JPopupMenu popup = new JPopupMenu();
795 JMenuItem item = new JMenuItem(
796 MessageManager.getString("label.paste_new_window"));
797 item.addActionListener(new ActionListener()
800 public void actionPerformed(ActionEvent evt)
807 popup.show(this, x, y);
812 // quick patch for JAL-4150 - needs some more work and test coverage
813 // TODO - unify below and AlignFrame.paste()
814 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
815 // clipboard has come from a different alignment window than the one where
816 // paste has been called! JAL-4151
818 if (Desktop.jalviewClipboard != null)
820 // The clipboard was filled from within Jalview, we must use the
822 // And dataset from the copied alignment
823 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
824 // be doubly sure that we create *new* sequence objects.
825 SequenceI[] sequences = new SequenceI[newseq.length];
826 for (int i = 0; i < newseq.length; i++)
828 sequences[i] = new Sequence(newseq[i]);
830 Alignment alignment = new Alignment(sequences);
831 // dataset is inherited
832 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
833 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
834 AlignFrame.DEFAULT_HEIGHT);
835 String newtitle = new String("Copied sequences");
837 if (Desktop.jalviewClipboard[2] != null)
839 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
840 af.viewport.setHiddenColumns(hc);
843 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
844 AlignFrame.DEFAULT_HEIGHT);
851 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
852 Transferable contents = c.getContents(this);
854 if (contents != null)
856 String file = (String) contents
857 .getTransferData(DataFlavor.stringFlavor);
859 FileFormatI format = new IdentifyFile().identify(file,
860 DataSourceType.PASTE);
862 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
865 } catch (Exception ex)
868 "Unable to paste alignment from system clipboard:\n" + ex);
874 * Adds and opens the given frame to the desktop
885 public static synchronized void addInternalFrame(
886 final JInternalFrame frame, String title, int w, int h)
888 addInternalFrame(frame, title, true, w, h, true, false);
892 * Add an internal frame to the Jalview desktop
899 * When true, display frame immediately, otherwise, caller must call
900 * setVisible themselves.
906 public static synchronized void addInternalFrame(
907 final JInternalFrame frame, String title, boolean makeVisible,
910 addInternalFrame(frame, title, makeVisible, w, h, true, false);
914 * Add an internal frame to the Jalview desktop and make it visible
927 public static synchronized void addInternalFrame(
928 final JInternalFrame frame, String title, int w, int h,
931 addInternalFrame(frame, title, true, w, h, resizable, false);
935 * Add an internal frame to the Jalview desktop
942 * When true, display frame immediately, otherwise, caller must call
943 * setVisible themselves.
950 * @param ignoreMinSize
951 * Do not set the default minimum size for frame
953 public static synchronized void addInternalFrame(
954 final JInternalFrame frame, String title, boolean makeVisible,
955 int w, int h, boolean resizable, boolean ignoreMinSize)
958 // TODO: allow callers to determine X and Y position of frame (eg. via
960 // TODO: consider fixing method to update entries in the window submenu with
961 // the current window title
963 frame.setTitle(title);
964 if (frame.getWidth() < 1 || frame.getHeight() < 1)
968 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
969 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
970 // IF JALVIEW IS RUNNING HEADLESS
971 // ///////////////////////////////////////////////
972 if (instance == null || (System.getProperty("java.awt.headless") != null
973 && System.getProperty("java.awt.headless").equals("true")))
982 frame.setMinimumSize(
983 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
985 // Set default dimension for Alignment Frame window.
986 // The Alignment Frame window could be added from a number of places,
988 // I did this here in order not to miss out on any Alignment frame.
989 if (frame instanceof AlignFrame)
991 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
992 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
996 frame.setVisible(makeVisible);
997 frame.setClosable(true);
998 frame.setResizable(resizable);
999 frame.setMaximizable(resizable);
1000 frame.setIconifiable(resizable);
1001 frame.setOpaque(Platform.isJS());
1003 if (frame.getX() < 1 && frame.getY() < 1)
1005 frame.setLocation(xOffset * openFrameCount,
1006 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1010 * add an entry for the new frame in the Window menu (and remove it when the
1013 final JMenuItem menuItem = new JMenuItem(title);
1014 frame.addInternalFrameListener(new InternalFrameAdapter()
1017 public void internalFrameActivated(InternalFrameEvent evt)
1019 JInternalFrame itf = desktop.getSelectedFrame();
1022 if (itf instanceof AlignFrame)
1024 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1031 public void internalFrameClosed(InternalFrameEvent evt)
1033 PaintRefresher.RemoveComponent(frame);
1036 * defensive check to prevent frames being added half off the window
1038 if (openFrameCount > 0)
1044 * ensure no reference to alignFrame retained by menu item listener
1046 if (menuItem.getActionListeners().length > 0)
1048 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1050 windowMenu.remove(menuItem);
1054 menuItem.addActionListener(new ActionListener()
1057 public void actionPerformed(ActionEvent e)
1061 frame.setSelected(true);
1062 frame.setIcon(false);
1063 } catch (java.beans.PropertyVetoException ex)
1070 setKeyBindings(frame);
1074 windowMenu.add(menuItem);
1079 frame.setSelected(true);
1080 frame.requestFocus();
1081 } catch (java.beans.PropertyVetoException ve)
1083 } catch (java.lang.ClassCastException cex)
1085 jalview.bin.Console.warn(
1086 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1092 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1097 private static void setKeyBindings(JInternalFrame frame)
1099 @SuppressWarnings("serial")
1100 final Action closeAction = new AbstractAction()
1103 public void actionPerformed(ActionEvent e)
1110 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1112 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1113 InputEvent.CTRL_DOWN_MASK);
1114 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1115 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1117 InputMap inputMap = frame
1118 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1119 String ctrlW = ctrlWKey.toString();
1120 inputMap.put(ctrlWKey, ctrlW);
1121 inputMap.put(cmdWKey, ctrlW);
1123 ActionMap actionMap = frame.getActionMap();
1124 actionMap.put(ctrlW, closeAction);
1128 public void lostOwnership(Clipboard clipboard, Transferable contents)
1132 Desktop.jalviewClipboard = null;
1135 internalCopy = false;
1139 public void dragEnter(DropTargetDragEvent evt)
1144 public void dragExit(DropTargetEvent evt)
1149 public void dragOver(DropTargetDragEvent evt)
1154 public void dropActionChanged(DropTargetDragEvent evt)
1165 public void drop(DropTargetDropEvent evt)
1167 boolean success = true;
1168 // JAL-1552 - acceptDrop required before getTransferable call for
1169 // Java's Transferable for native dnd
1170 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1171 Transferable t = evt.getTransferable();
1172 List<Object> files = new ArrayList<>();
1173 List<DataSourceType> protocols = new ArrayList<>();
1177 Desktop.transferFromDropTarget(files, protocols, evt, t);
1178 } catch (Exception e)
1180 e.printStackTrace();
1188 for (int i = 0; i < files.size(); i++)
1190 // BH 2018 File or String
1191 Object file = files.get(i);
1192 String fileName = file.toString();
1193 DataSourceType protocol = (protocols == null)
1194 ? DataSourceType.FILE
1196 FileFormatI format = null;
1198 if (fileName.endsWith(".jar"))
1200 format = FileFormat.Jalview;
1205 format = new IdentifyFile().identify(file, protocol);
1207 if (file instanceof File)
1209 Platform.cacheFileData((File) file);
1211 new FileLoader().LoadFile(null, file, protocol, format);
1214 } catch (Exception ex)
1219 evt.dropComplete(success); // need this to ensure input focus is properly
1220 // transfered to any new windows created
1230 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1232 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1233 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1234 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1235 BackupFiles.getEnabled());
1237 chooser.setFileView(new JalviewFileView());
1238 chooser.setDialogTitle(
1239 MessageManager.getString("label.open_local_file"));
1240 chooser.setToolTipText(MessageManager.getString("action.open"));
1242 chooser.setResponseHandler(0, () -> {
1243 File selectedFile = chooser.getSelectedFile();
1244 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1246 FileFormatI format = chooser.getSelectedFormat();
1249 * Call IdentifyFile to verify the file contains what its extension implies.
1250 * Skip this step for dynamically added file formats, because IdentifyFile does
1251 * not know how to recognise them.
1253 if (FileFormats.getInstance().isIdentifiable(format))
1257 format = new IdentifyFile().identify(selectedFile,
1258 DataSourceType.FILE);
1259 } catch (FileFormatException e)
1261 // format = null; //??
1265 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1268 chooser.showOpenDialog(this);
1272 * Shows a dialog for input of a URL at which to retrieve alignment data
1277 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1279 // This construct allows us to have a wider textfield
1281 JLabel label = new JLabel(
1282 MessageManager.getString("label.input_file_url"));
1284 JPanel panel = new JPanel(new GridLayout(2, 1));
1288 * the URL to fetch is input in Java: an editable combobox with history JS:
1289 * (pending JAL-3038) a plain text field
1292 String urlBase = "https://www.";
1293 if (Platform.isJS())
1295 history = new JTextField(urlBase, 35);
1304 JComboBox<String> asCombo = new JComboBox<>();
1305 asCombo.setPreferredSize(new Dimension(400, 20));
1306 asCombo.setEditable(true);
1307 asCombo.addItem(urlBase);
1308 String historyItems = Cache.getProperty("RECENT_URL");
1309 if (historyItems != null)
1311 for (String token : historyItems.split("\\t"))
1313 asCombo.addItem(token);
1320 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1321 MessageManager.getString("action.cancel") };
1322 Runnable action = () -> {
1323 @SuppressWarnings("unchecked")
1324 String url = (history instanceof JTextField
1325 ? ((JTextField) history).getText()
1326 : ((JComboBox<String>) history).getEditor().getItem()
1327 .toString().trim());
1329 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1331 if (viewport != null)
1333 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1334 FileFormat.Jalview);
1338 new FileLoader().LoadFile(url, DataSourceType.URL,
1339 FileFormat.Jalview);
1344 FileFormatI format = null;
1347 format = new IdentifyFile().identify(url, DataSourceType.URL);
1348 } catch (FileFormatException e)
1350 // TODO revise error handling, distinguish between
1351 // URL not found and response not valid
1356 String msg = MessageManager.formatMessage("label.couldnt_locate",
1358 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1359 MessageManager.getString("label.url_not_found"),
1360 JvOptionPane.WARNING_MESSAGE);
1364 if (viewport != null)
1366 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1371 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1375 String dialogOption = MessageManager
1376 .getString("label.input_alignment_from_url");
1377 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1378 .showInternalDialog(panel, dialogOption,
1379 JvOptionPane.YES_NO_CANCEL_OPTION,
1380 JvOptionPane.PLAIN_MESSAGE, null, options,
1381 MessageManager.getString("action.ok"));
1385 * Opens the CutAndPaste window for the user to paste an alignment in to
1388 * - if not null, the pasted alignment is added to the current
1389 * alignment; if null, to a new alignment window
1392 public void inputTextboxMenuItem_actionPerformed(
1393 AlignmentViewPanel viewPanel)
1395 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1396 cap.setForInput(viewPanel);
1397 Desktop.addInternalFrame(cap,
1398 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1403 * Check with user and saving files before actually quitting
1405 public void desktopQuit()
1407 desktopQuit(true, false);
1410 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1412 final Runnable doDesktopQuit = () -> {
1413 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1414 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1415 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1416 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1417 getBounds().y, getWidth(), getHeight()));
1419 if (jconsole != null)
1421 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1422 jconsole.stopConsole();
1427 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1430 // Frames should all close automatically. Keeping external
1431 // viewers open should already be decided by user.
1432 closeAll_actionPerformed(null);
1434 // check for aborted quit
1435 if (QuitHandler.quitCancelled())
1437 jalview.bin.Console.debug("Desktop aborting quit");
1441 if (dialogExecutor != null)
1443 dialogExecutor.shutdownNow();
1446 if (groovyConsole != null)
1448 // suppress a possible repeat prompt to save script
1449 groovyConsole.setDirty(false);
1450 groovyConsole.exit();
1453 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1455 // note that shutdown hook will not be run
1456 jalview.bin.Console.debug("Force Quit selected by user");
1457 Runtime.getRuntime().halt(0);
1460 jalview.bin.Console.debug("Quit selected by user");
1463 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1464 // instance.dispose();
1469 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1470 QuitHandler.defaultCancelQuit);
1474 * Don't call this directly, use desktopQuit() above. Exits the program.
1479 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1480 // not run a second time if gotQuitResponse flag has been set (i.e. user
1481 // confirmed quit of some kind).
1482 Jalview.exit("Desktop exiting.", 0);
1485 private void storeLastKnownDimensions(String string, Rectangle jc)
1487 jalview.bin.Console.debug("Storing last known dimensions for " + string
1488 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1489 + " height:" + jc.height);
1491 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1492 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1493 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1494 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1504 public void aboutMenuItem_actionPerformed(ActionEvent e)
1506 new Thread(new Runnable()
1511 new SplashScreen(false);
1517 * Returns the html text for the About screen, including any available version
1518 * number, build details, author details and citation reference, but without
1519 * the enclosing {@code html} tags
1523 public String getAboutMessage()
1525 StringBuilder message = new StringBuilder(1024);
1526 message.append("<div style=\"font-family: sans-serif;\">")
1527 .append("<h1><strong>Version: ")
1528 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1529 .append("<strong>Built: <em>")
1530 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1531 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1532 .append("</strong>");
1534 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1535 if (latestVersion.equals("Checking"))
1537 // JBP removed this message for 2.11: May be reinstated in future version
1538 // message.append("<br>...Checking latest version...</br>");
1540 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1542 boolean red = false;
1543 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1544 .indexOf("automated build") == -1)
1547 // Displayed when code version and jnlp version do not match and code
1548 // version is not a development build
1549 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1552 message.append("<br>!! Version ")
1553 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1554 .append(" is available for download from ")
1555 .append(Cache.getDefault("www.jalview.org",
1556 "https://www.jalview.org"))
1560 message.append("</div>");
1563 message.append("<br>Authors: ");
1564 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1565 message.append(CITATION);
1567 message.append("</div>");
1569 return message.toString();
1573 * Action on requesting Help documentation
1576 public void documentationMenuItem_actionPerformed()
1580 if (Platform.isJS())
1582 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1591 Help.showHelpWindow();
1593 } catch (Exception ex)
1595 System.err.println("Error opening help: " + ex.getMessage());
1600 public void closeAll_actionPerformed(ActionEvent e)
1602 // TODO show a progress bar while closing?
1603 JInternalFrame[] frames = desktop.getAllFrames();
1604 for (int i = 0; i < frames.length; i++)
1608 frames[i].setClosed(true);
1609 } catch (java.beans.PropertyVetoException ex)
1613 Jalview.setCurrentAlignFrame(null);
1614 jalview.bin.Console.info("ALL CLOSED");
1617 * reset state of singleton objects as appropriate (clear down session state
1618 * when all windows are closed)
1620 StructureSelectionManager ssm = StructureSelectionManager
1621 .getStructureSelectionManager(this);
1628 public int structureViewersStillRunningCount()
1631 JInternalFrame[] frames = desktop.getAllFrames();
1632 for (int i = 0; i < frames.length; i++)
1634 if (frames[i] != null
1635 && frames[i] instanceof JalviewStructureDisplayI)
1637 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1645 public void raiseRelated_actionPerformed(ActionEvent e)
1647 reorderAssociatedWindows(false, false);
1651 public void minimizeAssociated_actionPerformed(ActionEvent e)
1653 reorderAssociatedWindows(true, false);
1656 void closeAssociatedWindows()
1658 reorderAssociatedWindows(false, true);
1664 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1668 protected void garbageCollect_actionPerformed(ActionEvent e)
1670 // We simply collect the garbage
1671 jalview.bin.Console.debug("Collecting garbage...");
1673 jalview.bin.Console.debug("Finished garbage collection.");
1679 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1683 protected void showMemusage_actionPerformed(ActionEvent e)
1685 desktop.showMemoryUsage(showMemusage.isSelected());
1692 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1696 protected void showConsole_actionPerformed(ActionEvent e)
1698 showConsole(showConsole.isSelected());
1701 Console jconsole = null;
1704 * control whether the java console is visible or not
1708 void showConsole(boolean selected)
1710 // TODO: decide if we should update properties file
1711 if (jconsole != null) // BH 2018
1713 showConsole.setSelected(selected);
1714 Cache.setProperty("SHOW_JAVA_CONSOLE",
1715 Boolean.valueOf(selected).toString());
1716 jconsole.setVisible(selected);
1720 void reorderAssociatedWindows(boolean minimize, boolean close)
1722 JInternalFrame[] frames = desktop.getAllFrames();
1723 if (frames == null || frames.length < 1)
1728 AlignmentViewport source = null, target = null;
1729 if (frames[0] instanceof AlignFrame)
1731 source = ((AlignFrame) frames[0]).getCurrentView();
1733 else if (frames[0] instanceof TreePanel)
1735 source = ((TreePanel) frames[0]).getViewPort();
1737 else if (frames[0] instanceof PCAPanel)
1739 source = ((PCAPanel) frames[0]).av;
1741 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1743 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1748 for (int i = 0; i < frames.length; i++)
1751 if (frames[i] == null)
1755 if (frames[i] instanceof AlignFrame)
1757 target = ((AlignFrame) frames[i]).getCurrentView();
1759 else if (frames[i] instanceof TreePanel)
1761 target = ((TreePanel) frames[i]).getViewPort();
1763 else if (frames[i] instanceof PCAPanel)
1765 target = ((PCAPanel) frames[i]).av;
1767 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1769 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1772 if (source == target)
1778 frames[i].setClosed(true);
1782 frames[i].setIcon(minimize);
1785 frames[i].toFront();
1789 } catch (java.beans.PropertyVetoException ex)
1804 protected void preferences_actionPerformed(ActionEvent e)
1806 Preferences.openPreferences();
1810 * Prompts the user to choose a file and then saves the Jalview state as a
1811 * Jalview project file
1814 public void saveState_actionPerformed()
1816 saveState_actionPerformed(false);
1819 public void saveState_actionPerformed(boolean saveAs)
1821 java.io.File projectFile = getProjectFile();
1822 // autoSave indicates we already have a file and don't need to ask
1823 boolean autoSave = projectFile != null && !saveAs
1824 && BackupFiles.getEnabled();
1826 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1827 // saveAs="+saveAs+", Backups
1828 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1830 boolean approveSave = false;
1833 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1836 chooser.setFileView(new JalviewFileView());
1837 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1839 int value = chooser.showSaveDialog(this);
1841 if (value == JalviewFileChooser.APPROVE_OPTION)
1843 projectFile = chooser.getSelectedFile();
1844 setProjectFile(projectFile);
1849 if (approveSave || autoSave)
1851 final Desktop me = this;
1852 final java.io.File chosenFile = projectFile;
1853 new Thread(new Runnable()
1858 // TODO: refactor to Jalview desktop session controller action.
1859 setProgressBar(MessageManager.formatMessage(
1860 "label.saving_jalview_project", new Object[]
1861 { chosenFile.getName() }), chosenFile.hashCode());
1862 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1863 // TODO catch and handle errors for savestate
1864 // TODO prevent user from messing with the Desktop whilst we're saving
1867 boolean doBackup = BackupFiles.getEnabled();
1868 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1871 new Jalview2XML().saveState(
1872 doBackup ? backupfiles.getTempFile() : chosenFile);
1876 backupfiles.setWriteSuccess(true);
1877 backupfiles.rollBackupsAndRenameTempFile();
1879 } catch (OutOfMemoryError oom)
1881 new OOMWarning("Whilst saving current state to "
1882 + chosenFile.getName(), oom);
1883 } catch (Exception ex)
1885 jalview.bin.Console.error("Problems whilst trying to save to "
1886 + chosenFile.getName(), ex);
1887 JvOptionPane.showMessageDialog(me,
1888 MessageManager.formatMessage(
1889 "label.error_whilst_saving_current_state_to",
1891 { chosenFile.getName() }),
1892 MessageManager.getString("label.couldnt_save_project"),
1893 JvOptionPane.WARNING_MESSAGE);
1895 setProgressBar(null, chosenFile.hashCode());
1902 public void saveAsState_actionPerformed(ActionEvent e)
1904 saveState_actionPerformed(true);
1907 protected void setProjectFile(File choice)
1909 this.projectFile = choice;
1912 public File getProjectFile()
1914 return this.projectFile;
1918 * Shows a file chooser dialog and tries to read in the selected file as a
1922 public void loadState_actionPerformed()
1924 final String[] suffix = new String[] { "jvp", "jar" };
1925 final String[] desc = new String[] { "Jalview Project",
1926 "Jalview Project (old)" };
1927 JalviewFileChooser chooser = new JalviewFileChooser(
1928 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1929 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1933 chooser.setFileView(new JalviewFileView());
1934 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1935 chooser.setResponseHandler(0, () -> {
1936 File selectedFile = chooser.getSelectedFile();
1937 setProjectFile(selectedFile);
1938 String choice = selectedFile.getAbsolutePath();
1939 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1940 new Thread(new Runnable()
1947 new Jalview2XML().loadJalviewAlign(selectedFile);
1948 } catch (OutOfMemoryError oom)
1950 new OOMWarning("Whilst loading project from " + choice, oom);
1951 } catch (Exception ex)
1953 jalview.bin.Console.error(
1954 "Problems whilst loading project from " + choice, ex);
1955 JvOptionPane.showMessageDialog(Desktop.desktop,
1956 MessageManager.formatMessage(
1957 "label.error_whilst_loading_project_from",
1960 MessageManager.getString("label.couldnt_load_project"),
1961 JvOptionPane.WARNING_MESSAGE);
1964 }, "Project Loader").start();
1967 chooser.showOpenDialog(this);
1971 public void inputSequence_actionPerformed(ActionEvent e)
1973 new SequenceFetcher(this);
1976 JPanel progressPanel;
1978 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1980 public void startLoading(final Object fileName)
1982 if (fileLoadingCount == 0)
1984 fileLoadingPanels.add(addProgressPanel(MessageManager
1985 .formatMessage("label.loading_file", new Object[]
1991 private JPanel addProgressPanel(String string)
1993 if (progressPanel == null)
1995 progressPanel = new JPanel(new GridLayout(1, 1));
1996 totalProgressCount = 0;
1997 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1999 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2000 JProgressBar progressBar = new JProgressBar();
2001 progressBar.setIndeterminate(true);
2003 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2005 thisprogress.add(progressBar, BorderLayout.CENTER);
2006 progressPanel.add(thisprogress);
2007 ((GridLayout) progressPanel.getLayout()).setRows(
2008 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2009 ++totalProgressCount;
2010 instance.validate();
2011 return thisprogress;
2014 int totalProgressCount = 0;
2016 private void removeProgressPanel(JPanel progbar)
2018 if (progressPanel != null)
2020 synchronized (progressPanel)
2022 progressPanel.remove(progbar);
2023 GridLayout gl = (GridLayout) progressPanel.getLayout();
2024 gl.setRows(gl.getRows() - 1);
2025 if (--totalProgressCount < 1)
2027 this.getContentPane().remove(progressPanel);
2028 progressPanel = null;
2035 public void stopLoading()
2038 if (fileLoadingCount < 1)
2040 while (fileLoadingPanels.size() > 0)
2042 removeProgressPanel(fileLoadingPanels.remove(0));
2044 fileLoadingPanels.clear();
2045 fileLoadingCount = 0;
2050 public static int getViewCount(String alignmentId)
2052 AlignmentViewport[] aps = getViewports(alignmentId);
2053 return (aps == null) ? 0 : aps.length;
2058 * @param alignmentId
2059 * - if null, all sets are returned
2060 * @return all AlignmentPanels concerning the alignmentId sequence set
2062 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2064 if (Desktop.desktop == null)
2066 // no frames created and in headless mode
2067 // TODO: verify that frames are recoverable when in headless mode
2070 List<AlignmentPanel> aps = new ArrayList<>();
2071 AlignFrame[] frames = getAlignFrames();
2076 for (AlignFrame af : frames)
2078 for (AlignmentPanel ap : af.alignPanels)
2080 if (alignmentId == null
2081 || alignmentId.equals(ap.av.getSequenceSetId()))
2087 if (aps.size() == 0)
2091 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2096 * get all the viewports on an alignment.
2098 * @param sequenceSetId
2099 * unique alignment id (may be null - all viewports returned in that
2101 * @return all viewports on the alignment bound to sequenceSetId
2103 public static AlignmentViewport[] getViewports(String sequenceSetId)
2105 List<AlignmentViewport> viewp = new ArrayList<>();
2106 if (desktop != null)
2108 AlignFrame[] frames = Desktop.getAlignFrames();
2110 for (AlignFrame afr : frames)
2112 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2113 .equals(sequenceSetId))
2115 if (afr.alignPanels != null)
2117 for (AlignmentPanel ap : afr.alignPanels)
2119 if (sequenceSetId == null
2120 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2128 viewp.add(afr.getViewport());
2132 if (viewp.size() > 0)
2134 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2141 * Explode the views in the given frame into separate AlignFrame
2145 public static void explodeViews(AlignFrame af)
2147 int size = af.alignPanels.size();
2153 // FIXME: ideally should use UI interface API
2154 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2155 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2156 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2157 for (int i = 0; i < size; i++)
2159 AlignmentPanel ap = af.alignPanels.get(i);
2161 AlignFrame newaf = new AlignFrame(ap);
2163 // transfer reference for existing feature settings to new alignFrame
2164 if (ap == af.alignPanel)
2166 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2168 newaf.featureSettings = viewFeatureSettings;
2170 newaf.setFeatureSettingsGeometry(fsBounds);
2174 * Restore the view's last exploded frame geometry if known. Multiple views from
2175 * one exploded frame share and restore the same (frame) position and size.
2177 Rectangle geometry = ap.av.getExplodedGeometry();
2178 if (geometry != null)
2180 newaf.setBounds(geometry);
2183 ap.av.setGatherViewsHere(false);
2185 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2186 AlignFrame.DEFAULT_HEIGHT);
2187 // and materialise a new feature settings dialog instance for the new
2189 // (closes the old as if 'OK' was pressed)
2190 if (ap == af.alignPanel && newaf.featureSettings != null
2191 && newaf.featureSettings.isOpen()
2192 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2194 newaf.showFeatureSettingsUI();
2198 af.featureSettings = null;
2199 af.alignPanels.clear();
2200 af.closeMenuItem_actionPerformed(true);
2205 * Gather expanded views (separate AlignFrame's) with the same sequence set
2206 * identifier back in to this frame as additional views, and close the
2207 * expanded views. Note the expanded frames may themselves have multiple
2208 * views. We take the lot.
2212 public void gatherViews(AlignFrame source)
2214 source.viewport.setGatherViewsHere(true);
2215 source.viewport.setExplodedGeometry(source.getBounds());
2216 JInternalFrame[] frames = desktop.getAllFrames();
2217 String viewId = source.viewport.getSequenceSetId();
2218 for (int t = 0; t < frames.length; t++)
2220 if (frames[t] instanceof AlignFrame && frames[t] != source)
2222 AlignFrame af = (AlignFrame) frames[t];
2223 boolean gatherThis = false;
2224 for (int a = 0; a < af.alignPanels.size(); a++)
2226 AlignmentPanel ap = af.alignPanels.get(a);
2227 if (viewId.equals(ap.av.getSequenceSetId()))
2230 ap.av.setGatherViewsHere(false);
2231 ap.av.setExplodedGeometry(af.getBounds());
2232 source.addAlignmentPanel(ap, false);
2238 if (af.featureSettings != null && af.featureSettings.isOpen())
2240 if (source.featureSettings == null)
2242 // preserve the feature settings geometry for this frame
2243 source.featureSettings = af.featureSettings;
2244 source.setFeatureSettingsGeometry(
2245 af.getFeatureSettingsGeometry());
2249 // close it and forget
2250 af.featureSettings.close();
2253 af.alignPanels.clear();
2254 af.closeMenuItem_actionPerformed(true);
2259 // refresh the feature setting UI for the source frame if it exists
2260 if (source.featureSettings != null && source.featureSettings.isOpen())
2262 source.showFeatureSettingsUI();
2267 public JInternalFrame[] getAllFrames()
2269 return desktop.getAllFrames();
2273 * Checks the given url to see if it gives a response indicating that the user
2274 * should be informed of a new questionnaire.
2278 public void checkForQuestionnaire(String url)
2280 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2281 // javax.swing.SwingUtilities.invokeLater(jvq);
2282 new Thread(jvq).start();
2285 public void checkURLLinks()
2287 // Thread off the URL link checker
2288 addDialogThread(new Runnable()
2293 if (Cache.getDefault("CHECKURLLINKS", true))
2295 // check what the actual links are - if it's just the default don't
2296 // bother with the warning
2297 List<String> links = Preferences.sequenceUrlLinks
2300 // only need to check links if there is one with a
2301 // SEQUENCE_ID which is not the default EMBL_EBI link
2302 ListIterator<String> li = links.listIterator();
2303 boolean check = false;
2304 List<JLabel> urls = new ArrayList<>();
2305 while (li.hasNext())
2307 String link = li.next();
2308 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2309 && !UrlConstants.isDefaultString(link))
2312 int barPos = link.indexOf("|");
2313 String urlMsg = barPos == -1 ? link
2314 : link.substring(0, barPos) + ": "
2315 + link.substring(barPos + 1);
2316 urls.add(new JLabel(urlMsg));
2324 // ask user to check in case URL links use old style tokens
2325 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2326 JPanel msgPanel = new JPanel();
2327 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2328 msgPanel.add(Box.createVerticalGlue());
2329 JLabel msg = new JLabel(MessageManager
2330 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2331 JLabel msg2 = new JLabel(MessageManager
2332 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2334 for (JLabel url : urls)
2340 final JCheckBox jcb = new JCheckBox(
2341 MessageManager.getString("label.do_not_display_again"));
2342 jcb.addActionListener(new ActionListener()
2345 public void actionPerformed(ActionEvent e)
2347 // update Cache settings for "don't show this again"
2348 boolean showWarningAgain = !jcb.isSelected();
2349 Cache.setProperty("CHECKURLLINKS",
2350 Boolean.valueOf(showWarningAgain).toString());
2355 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2357 .getString("label.SEQUENCE_ID_no_longer_used"),
2358 JvOptionPane.WARNING_MESSAGE);
2365 * Proxy class for JDesktopPane which optionally displays the current memory
2366 * usage and highlights the desktop area with a red bar if free memory runs
2371 public class MyDesktopPane extends JDesktopPane implements Runnable
2373 private static final float ONE_MB = 1048576f;
2375 boolean showMemoryUsage = false;
2379 java.text.NumberFormat df;
2381 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2384 public MyDesktopPane(boolean showMemoryUsage)
2386 showMemoryUsage(showMemoryUsage);
2389 public void showMemoryUsage(boolean showMemory)
2391 this.showMemoryUsage = showMemory;
2394 Thread worker = new Thread(this);
2400 public boolean isShowMemoryUsage()
2402 return showMemoryUsage;
2408 df = java.text.NumberFormat.getNumberInstance();
2409 df.setMaximumFractionDigits(2);
2410 runtime = Runtime.getRuntime();
2412 while (showMemoryUsage)
2416 maxMemory = runtime.maxMemory() / ONE_MB;
2417 allocatedMemory = runtime.totalMemory() / ONE_MB;
2418 freeMemory = runtime.freeMemory() / ONE_MB;
2419 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2421 percentUsage = (totalFreeMemory / maxMemory) * 100;
2423 // if (percentUsage < 20)
2425 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2427 // instance.set.setBorder(border1);
2430 // sleep after showing usage
2432 } catch (Exception ex)
2434 ex.printStackTrace();
2440 public void paintComponent(Graphics g)
2442 if (showMemoryUsage && g != null && df != null)
2444 if (percentUsage < 20)
2446 g.setColor(Color.red);
2448 FontMetrics fm = g.getFontMetrics();
2451 g.drawString(MessageManager.formatMessage("label.memory_stats",
2453 { df.format(totalFreeMemory), df.format(maxMemory),
2454 df.format(percentUsage) }),
2455 10, getHeight() - fm.getHeight());
2459 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2460 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2465 * Accessor method to quickly get all the AlignmentFrames loaded.
2467 * @return an array of AlignFrame, or null if none found
2469 public static AlignFrame[] getAlignFrames()
2471 if (Jalview.isHeadlessMode())
2473 // Desktop.desktop is null in headless mode
2474 return new AlignFrame[] { Jalview.currentAlignFrame };
2477 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2483 List<AlignFrame> avp = new ArrayList<>();
2485 for (int i = frames.length - 1; i > -1; i--)
2487 if (frames[i] instanceof AlignFrame)
2489 avp.add((AlignFrame) frames[i]);
2491 else if (frames[i] instanceof SplitFrame)
2494 * Also check for a split frame containing an AlignFrame
2496 GSplitFrame sf = (GSplitFrame) frames[i];
2497 if (sf.getTopFrame() instanceof AlignFrame)
2499 avp.add((AlignFrame) sf.getTopFrame());
2501 if (sf.getBottomFrame() instanceof AlignFrame)
2503 avp.add((AlignFrame) sf.getBottomFrame());
2507 if (avp.size() == 0)
2511 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2516 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2520 public GStructureViewer[] getJmols()
2522 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2528 List<GStructureViewer> avp = new ArrayList<>();
2530 for (int i = frames.length - 1; i > -1; i--)
2532 if (frames[i] instanceof AppJmol)
2534 GStructureViewer af = (GStructureViewer) frames[i];
2538 if (avp.size() == 0)
2542 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2547 * Add Groovy Support to Jalview
2550 public void groovyShell_actionPerformed()
2554 openGroovyConsole();
2555 } catch (Exception ex)
2557 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2558 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2560 MessageManager.getString("label.couldnt_create_groovy_shell"),
2561 MessageManager.getString("label.groovy_support_failed"),
2562 JvOptionPane.ERROR_MESSAGE);
2567 * Open the Groovy console
2569 void openGroovyConsole()
2571 if (groovyConsole == null)
2573 groovyConsole = new groovy.ui.Console();
2574 groovyConsole.setVariable("Jalview", this);
2575 groovyConsole.run();
2578 * We allow only one console at a time, so that AlignFrame menu option
2579 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2580 * enable 'Run script', when the console is opened, and the reverse when it is
2583 Window window = (Window) groovyConsole.getFrame();
2584 window.addWindowListener(new WindowAdapter()
2587 public void windowClosed(WindowEvent e)
2590 * rebind CMD-Q from Groovy Console to Jalview Quit
2593 enableExecuteGroovy(false);
2599 * show Groovy console window (after close and reopen)
2601 ((Window) groovyConsole.getFrame()).setVisible(true);
2604 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2605 * opening a second console
2607 enableExecuteGroovy(true);
2611 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2612 * binding when opened
2614 protected void addQuitHandler()
2617 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2619 .getKeyStroke(KeyEvent.VK_Q,
2620 jalview.util.ShortcutKeyMaskExWrapper
2621 .getMenuShortcutKeyMaskEx()),
2623 getRootPane().getActionMap().put("Quit", new AbstractAction()
2626 public void actionPerformed(ActionEvent e)
2634 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2637 * true if Groovy console is open
2639 public void enableExecuteGroovy(boolean enabled)
2642 * disable opening a second Groovy console (or re-enable when the console is
2645 groovyShell.setEnabled(!enabled);
2647 AlignFrame[] alignFrames = getAlignFrames();
2648 if (alignFrames != null)
2650 for (AlignFrame af : alignFrames)
2652 af.setGroovyEnabled(enabled);
2658 * Progress bars managed by the IProgressIndicator method.
2660 private Hashtable<Long, JPanel> progressBars;
2662 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2667 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2670 public void setProgressBar(String message, long id)
2672 if (progressBars == null)
2674 progressBars = new Hashtable<>();
2675 progressBarHandlers = new Hashtable<>();
2678 if (progressBars.get(Long.valueOf(id)) != null)
2680 JPanel panel = progressBars.remove(Long.valueOf(id));
2681 if (progressBarHandlers.contains(Long.valueOf(id)))
2683 progressBarHandlers.remove(Long.valueOf(id));
2685 removeProgressPanel(panel);
2689 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2696 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2697 * jalview.gui.IProgressIndicatorHandler)
2700 public void registerHandler(final long id,
2701 final IProgressIndicatorHandler handler)
2703 if (progressBarHandlers == null
2704 || !progressBars.containsKey(Long.valueOf(id)))
2706 throw new Error(MessageManager.getString(
2707 "error.call_setprogressbar_before_registering_handler"));
2709 progressBarHandlers.put(Long.valueOf(id), handler);
2710 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2711 if (handler.canCancel())
2713 JButton cancel = new JButton(
2714 MessageManager.getString("action.cancel"));
2715 final IProgressIndicator us = this;
2716 cancel.addActionListener(new ActionListener()
2720 public void actionPerformed(ActionEvent e)
2722 handler.cancelActivity(id);
2723 us.setProgressBar(MessageManager
2724 .formatMessage("label.cancelled_params", new Object[]
2725 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2729 progressPanel.add(cancel, BorderLayout.EAST);
2735 * @return true if any progress bars are still active
2738 public boolean operationInProgress()
2740 if (progressBars != null && progressBars.size() > 0)
2748 * This will return the first AlignFrame holding the given viewport instance.
2749 * It will break if there are more than one AlignFrames viewing a particular
2753 * @return alignFrame for viewport
2755 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2757 if (desktop != null)
2759 AlignmentPanel[] aps = getAlignmentPanels(
2760 viewport.getSequenceSetId());
2761 for (int panel = 0; aps != null && panel < aps.length; panel++)
2763 if (aps[panel] != null && aps[panel].av == viewport)
2765 return aps[panel].alignFrame;
2772 public VamsasApplication getVamsasApplication()
2774 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2780 * flag set if jalview GUI is being operated programmatically
2782 private boolean inBatchMode = false;
2785 * check if jalview GUI is being operated programmatically
2787 * @return inBatchMode
2789 public boolean isInBatchMode()
2795 * set flag if jalview GUI is being operated programmatically
2797 * @param inBatchMode
2799 public void setInBatchMode(boolean inBatchMode)
2801 this.inBatchMode = inBatchMode;
2805 * start service discovery and wait till it is done
2807 public void startServiceDiscovery()
2809 startServiceDiscovery(false);
2813 * start service discovery threads - blocking or non-blocking
2817 public void startServiceDiscovery(boolean blocking)
2819 startServiceDiscovery(blocking, false);
2823 * start service discovery threads
2826 * - false means call returns immediately
2827 * @param ignore_SHOW_JWS2_SERVICES_preference
2828 * - when true JABA services are discovered regardless of user's JWS2
2829 * discovery preference setting
2831 public void startServiceDiscovery(boolean blocking,
2832 boolean ignore_SHOW_JWS2_SERVICES_preference)
2834 boolean alive = true;
2835 Thread t0 = null, t1 = null, t2 = null;
2836 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2839 // todo: changesupport handlers need to be transferred
2840 if (discoverer == null)
2842 discoverer = new jalview.ws.jws1.Discoverer();
2843 // register PCS handler for desktop.
2844 discoverer.addPropertyChangeListener(changeSupport);
2846 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2847 // until we phase out completely
2848 (t0 = new Thread(discoverer)).start();
2851 if (ignore_SHOW_JWS2_SERVICES_preference
2852 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2854 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2855 .startDiscoverer(changeSupport);
2859 // TODO: do rest service discovery
2868 } catch (Exception e)
2871 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2872 || (t3 != null && t3.isAlive())
2873 || (t0 != null && t0.isAlive());
2879 * called to check if the service discovery process completed successfully.
2883 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2885 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2887 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2888 .getErrorMessages();
2891 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2893 if (serviceChangedDialog == null)
2895 // only run if we aren't already displaying one of these.
2896 addDialogThread(serviceChangedDialog = new Runnable()
2903 * JalviewDialog jd =new JalviewDialog() {
2905 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2907 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2909 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2911 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2913 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2914 * + " or mis-configured HTTP proxy settings.<br/>" +
2915 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2916 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2917 * true, true, "Web Service Configuration Problem", 450, 400);
2919 * jd.waitForInput();
2921 JvOptionPane.showConfirmDialog(Desktop.desktop,
2922 new JLabel("<html><table width=\"450\"><tr><td>"
2923 + ermsg + "</td></tr></table>"
2924 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2925 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2926 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2927 + " Tools->Preferences dialog box to change them.</p></html>"),
2928 "Web Service Configuration Problem",
2929 JvOptionPane.DEFAULT_OPTION,
2930 JvOptionPane.ERROR_MESSAGE);
2931 serviceChangedDialog = null;
2939 jalview.bin.Console.error(
2940 "Errors reported by JABA discovery service. Check web services preferences.\n"
2947 private Runnable serviceChangedDialog = null;
2950 * start a thread to open a URL in the configured browser. Pops up a warning
2951 * dialog to the user if there is an exception when calling out to the browser
2956 public static void showUrl(final String url)
2958 showUrl(url, Desktop.instance);
2962 * Like showUrl but allows progress handler to be specified
2966 * (null) or object implementing IProgressIndicator
2968 public static void showUrl(final String url,
2969 final IProgressIndicator progress)
2971 new Thread(new Runnable()
2978 if (progress != null)
2980 progress.setProgressBar(MessageManager
2981 .formatMessage("status.opening_params", new Object[]
2982 { url }), this.hashCode());
2984 jalview.util.BrowserLauncher.openURL(url);
2985 } catch (Exception ex)
2987 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2989 .getString("label.web_browser_not_found_unix"),
2990 MessageManager.getString("label.web_browser_not_found"),
2991 JvOptionPane.WARNING_MESSAGE);
2993 ex.printStackTrace();
2995 if (progress != null)
2997 progress.setProgressBar(null, this.hashCode());
3003 public static WsParamSetManager wsparamManager = null;
3005 public static ParamManager getUserParameterStore()
3007 if (wsparamManager == null)
3009 wsparamManager = new WsParamSetManager();
3011 return wsparamManager;
3015 * static hyperlink handler proxy method for use by Jalview's internal windows
3019 public static void hyperlinkUpdate(HyperlinkEvent e)
3021 if (e.getEventType() == EventType.ACTIVATED)
3026 url = e.getURL().toString();
3027 Desktop.showUrl(url);
3028 } catch (Exception x)
3033 .error("Couldn't handle string " + url + " as a URL.");
3035 // ignore any exceptions due to dud links.
3042 * single thread that handles display of dialogs to user.
3044 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3047 * flag indicating if dialogExecutor should try to acquire a permit
3049 private volatile boolean dialogPause = true;
3054 private java.util.concurrent.Semaphore block = new Semaphore(0);
3056 private static groovy.ui.Console groovyConsole;
3059 * add another dialog thread to the queue
3063 public void addDialogThread(final Runnable prompter)
3065 dialogExecutor.submit(new Runnable()
3075 } catch (InterruptedException x)
3079 if (instance == null)
3085 SwingUtilities.invokeAndWait(prompter);
3086 } catch (Exception q)
3088 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3095 public void startDialogQueue()
3097 // set the flag so we don't pause waiting for another permit and semaphore
3098 // the current task to begin
3099 dialogPause = false;
3104 * Outputs an image of the desktop to file in EPS format, after prompting the
3105 * user for choice of Text or Lineart character rendering (unless a preference
3106 * has been set). The file name is generated as
3109 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3113 protected void snapShotWindow_actionPerformed(ActionEvent e)
3115 // currently the menu option to do this is not shown
3118 int width = getWidth();
3119 int height = getHeight();
3121 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3122 ImageWriterI writer = new ImageWriterI()
3125 public void exportImage(Graphics g) throws Exception
3128 jalview.bin.Console.info("Successfully written snapshot to file "
3129 + of.getAbsolutePath());
3132 String title = "View of desktop";
3133 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3136 exporter.doExport(of, this, width, height, title);
3137 } catch (ImageOutputException ioex) {
3138 jalview.bin.Console.error("Unexpected error whilst writing Jalview desktop snapshot as EPS",ioex);
3143 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3144 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3145 * and location last time the view was expanded (if any). However it does not
3146 * remember the split pane divider location - this is set to match the
3147 * 'exploding' frame.
3151 public void explodeViews(SplitFrame sf)
3153 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3154 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3155 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3157 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3159 int viewCount = topPanels.size();
3166 * Processing in reverse order works, forwards order leaves the first panels not
3167 * visible. I don't know why!
3169 for (int i = viewCount - 1; i >= 0; i--)
3172 * Make new top and bottom frames. These take over the respective AlignmentPanel
3173 * objects, including their AlignmentViewports, so the cdna/protein
3174 * relationships between the viewports is carried over to the new split frames.
3176 * explodedGeometry holds the (x, y) position of the previously exploded
3177 * SplitFrame, and the (width, height) of the AlignFrame component
3179 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3180 AlignFrame newTopFrame = new AlignFrame(topPanel);
3181 newTopFrame.setSize(oldTopFrame.getSize());
3182 newTopFrame.setVisible(true);
3183 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3184 .getExplodedGeometry();
3185 if (geometry != null)
3187 newTopFrame.setSize(geometry.getSize());
3190 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3191 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3192 newBottomFrame.setSize(oldBottomFrame.getSize());
3193 newBottomFrame.setVisible(true);
3194 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3195 .getExplodedGeometry();
3196 if (geometry != null)
3198 newBottomFrame.setSize(geometry.getSize());
3201 topPanel.av.setGatherViewsHere(false);
3202 bottomPanel.av.setGatherViewsHere(false);
3203 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3205 if (geometry != null)
3207 splitFrame.setLocation(geometry.getLocation());
3209 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3213 * Clear references to the panels (now relocated in the new SplitFrames) before
3214 * closing the old SplitFrame.
3217 bottomPanels.clear();
3222 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3223 * back into the given SplitFrame as additional views. Note that the gathered
3224 * frames may themselves have multiple views.
3228 public void gatherViews(GSplitFrame source)
3231 * special handling of explodedGeometry for a view within a SplitFrame: - it
3232 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3233 * height) of the AlignFrame component
3235 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3236 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3237 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3238 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3239 myBottomFrame.viewport
3240 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3241 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3242 myTopFrame.viewport.setGatherViewsHere(true);
3243 myBottomFrame.viewport.setGatherViewsHere(true);
3244 String topViewId = myTopFrame.viewport.getSequenceSetId();
3245 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3247 JInternalFrame[] frames = desktop.getAllFrames();
3248 for (JInternalFrame frame : frames)
3250 if (frame instanceof SplitFrame && frame != source)
3252 SplitFrame sf = (SplitFrame) frame;
3253 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3254 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3255 boolean gatherThis = false;
3256 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3258 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3259 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3260 if (topViewId.equals(topPanel.av.getSequenceSetId())
3261 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3264 topPanel.av.setGatherViewsHere(false);
3265 bottomPanel.av.setGatherViewsHere(false);
3266 topPanel.av.setExplodedGeometry(
3267 new Rectangle(sf.getLocation(), topFrame.getSize()));
3268 bottomPanel.av.setExplodedGeometry(
3269 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3270 myTopFrame.addAlignmentPanel(topPanel, false);
3271 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3277 topFrame.getAlignPanels().clear();
3278 bottomFrame.getAlignPanels().clear();
3285 * The dust settles...give focus to the tab we did this from.
3287 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3290 public static groovy.ui.Console getGroovyConsole()
3292 return groovyConsole;
3296 * handles the payload of a drag and drop event.
3298 * TODO refactor to desktop utilities class
3301 * - Data source strings extracted from the drop event
3303 * - protocol for each data source extracted from the drop event
3307 * - the payload from the drop event
3310 public static void transferFromDropTarget(List<Object> files,
3311 List<DataSourceType> protocols, DropTargetDropEvent evt,
3312 Transferable t) throws Exception
3315 DataFlavor uriListFlavor = new DataFlavor(
3316 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3319 urlFlavour = new DataFlavor(
3320 "application/x-java-url; class=java.net.URL");
3321 } catch (ClassNotFoundException cfe)
3323 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3327 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3332 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3333 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3334 // means url may be null.
3337 protocols.add(DataSourceType.URL);
3338 files.add(url.toString());
3339 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3340 + files.get(files.size() - 1));
3345 if (Platform.isAMacAndNotJS())
3348 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3351 } catch (Throwable ex)
3353 jalview.bin.Console.debug("URL drop handler failed.", ex);
3356 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3358 // Works on Windows and MacOSX
3359 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3360 for (Object file : (List) t
3361 .getTransferData(DataFlavor.javaFileListFlavor))
3364 protocols.add(DataSourceType.FILE);
3369 // Unix like behaviour
3370 boolean added = false;
3372 if (t.isDataFlavorSupported(uriListFlavor))
3374 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3375 // This is used by Unix drag system
3376 data = (String) t.getTransferData(uriListFlavor);
3380 // fallback to text: workaround - on OSX where there's a JVM bug
3382 .debug("standard URIListFlavor failed. Trying text");
3383 // try text fallback
3384 DataFlavor textDf = new DataFlavor(
3385 "text/plain;class=java.lang.String");
3386 if (t.isDataFlavorSupported(textDf))
3388 data = (String) t.getTransferData(textDf);
3391 jalview.bin.Console.debug("Plain text drop content returned "
3392 + (data == null ? "Null - failed" : data));
3397 while (protocols.size() < files.size())
3399 jalview.bin.Console.debug("Adding missing FILE protocol for "
3400 + files.get(protocols.size()));
3401 protocols.add(DataSourceType.FILE);
3403 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3404 data, "\r\n"); st.hasMoreTokens();)
3407 String s = st.nextToken();
3408 if (s.startsWith("#"))
3410 // the line is a comment (as per the RFC 2483)
3413 java.net.URI uri = new java.net.URI(s);
3414 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3416 protocols.add(DataSourceType.URL);
3417 files.add(uri.toString());
3421 // otherwise preserve old behaviour: catch all for file objects
3422 java.io.File file = new java.io.File(uri);
3423 protocols.add(DataSourceType.FILE);
3424 files.add(file.toString());
3429 if (jalview.bin.Console.isDebugEnabled())
3431 if (data == null || !added)
3434 if (t.getTransferDataFlavors() != null
3435 && t.getTransferDataFlavors().length > 0)
3437 jalview.bin.Console.debug(
3438 "Couldn't resolve drop data. Here are the supported flavors:");
3439 for (DataFlavor fl : t.getTransferDataFlavors())
3441 jalview.bin.Console.debug(
3442 "Supported transfer dataflavor: " + fl.toString());
3443 Object df = t.getTransferData(fl);
3446 jalview.bin.Console.debug("Retrieves: " + df);
3450 jalview.bin.Console.debug("Retrieved nothing");
3457 .debug("Couldn't resolve dataflavor for drop: "
3463 if (Platform.isWindowsAndNotJS())
3466 .debug("Scanning dropped content for Windows Link Files");
3468 // resolve any .lnk files in the file drop
3469 for (int f = 0; f < files.size(); f++)
3471 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3472 if (protocols.get(f).equals(DataSourceType.FILE)
3473 && (source.endsWith(".lnk") || source.endsWith(".url")
3474 || source.endsWith(".site")))
3478 Object obj = files.get(f);
3479 File lf = (obj instanceof File ? (File) obj
3480 : new File((String) obj));
3481 // process link file to get a URL
3482 jalview.bin.Console.debug("Found potential link file: " + lf);
3483 WindowsShortcut wscfile = new WindowsShortcut(lf);
3484 String fullname = wscfile.getRealFilename();
3485 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3486 files.set(f, fullname);
3487 jalview.bin.Console.debug("Parsed real filename " + fullname
3488 + " to extract protocol: " + protocols.get(f));
3489 } catch (Exception ex)
3491 jalview.bin.Console.error(
3492 "Couldn't parse " + files.get(f) + " as a link file.",
3501 * Sets the Preferences property for experimental features to True or False
3502 * depending on the state of the controlling menu item
3505 protected void showExperimental_actionPerformed(boolean selected)
3507 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3511 * Answers a (possibly empty) list of any structure viewer frames (currently
3512 * for either Jmol or Chimera) which are currently open. This may optionally
3513 * be restricted to viewers of a specified class, or viewers linked to a
3514 * specified alignment panel.
3517 * if not null, only return viewers linked to this panel
3518 * @param structureViewerClass
3519 * if not null, only return viewers of this class
3522 public List<StructureViewerBase> getStructureViewers(
3523 AlignmentPanel apanel,
3524 Class<? extends StructureViewerBase> structureViewerClass)
3526 List<StructureViewerBase> result = new ArrayList<>();
3527 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3529 for (JInternalFrame frame : frames)
3531 if (frame instanceof StructureViewerBase)
3533 if (structureViewerClass == null
3534 || structureViewerClass.isInstance(frame))
3537 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3539 result.add((StructureViewerBase) frame);
3547 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3549 private static boolean debugScaleMessageDone = false;
3551 public static void debugScaleMessage(Graphics g)
3553 if (debugScaleMessageDone)
3557 // output used by tests to check HiDPI scaling settings in action
3560 Graphics2D gg = (Graphics2D) g;
3563 AffineTransform t = gg.getTransform();
3564 double scaleX = t.getScaleX();
3565 double scaleY = t.getScaleY();
3566 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3567 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3568 debugScaleMessageDone = true;
3572 jalview.bin.Console.debug("Desktop graphics null");
3574 } catch (Exception e)
3576 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3581 * closes the current instance window, disposes and forgets about it.
3583 public static void closeDesktop()
3585 if (Desktop.instance != null) {
3586 Desktop.instance.closeAll_actionPerformed(null);
3587 Desktop.instance.setVisible(false);
3588 Desktop.instance.dispose();
3589 Desktop.instance = null;
3594 * checks if any progress bars are being displayed in any of the windows managed by the desktop
3597 public boolean operationsAreInProgress()
3599 JInternalFrame[] frames = getAllFrames();
3600 for (JInternalFrame frame:frames)
3602 if (frame instanceof IProgressIndicator)
3604 if (((IProgressIndicator)frame).operationInProgress())
3610 return operationInProgress();