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.Component;
26 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.datatransfer.Clipboard;
36 import java.awt.datatransfer.ClipboardOwner;
37 import java.awt.datatransfer.DataFlavor;
38 import java.awt.datatransfer.Transferable;
39 import java.awt.dnd.DnDConstants;
40 import java.awt.dnd.DropTargetDragEvent;
41 import java.awt.dnd.DropTargetDropEvent;
42 import java.awt.dnd.DropTargetEvent;
43 import java.awt.dnd.DropTargetListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.WindowAdapter;
51 import java.awt.event.WindowEvent;
52 import java.awt.geom.AffineTransform;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
55 import java.beans.PropertyVetoException;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Locale;
69 import java.util.Vector;
70 import java.util.concurrent.ExecutorService;
71 import java.util.concurrent.Executors;
72 import java.util.concurrent.Semaphore;
74 import javax.swing.AbstractAction;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.Box;
78 import javax.swing.BoxLayout;
79 import javax.swing.DefaultDesktopManager;
80 import javax.swing.DesktopManager;
81 import javax.swing.InputMap;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JComboBox;
85 import javax.swing.JComponent;
86 import javax.swing.JDesktopPane;
87 import javax.swing.JFrame;
88 import javax.swing.JInternalFrame;
89 import javax.swing.JLabel;
90 import javax.swing.JMenuItem;
91 import javax.swing.JOptionPane;
92 import javax.swing.JPanel;
93 import javax.swing.JPopupMenu;
94 import javax.swing.JProgressBar;
95 import javax.swing.JScrollPane;
96 import javax.swing.JTextArea;
97 import javax.swing.JTextField;
98 import javax.swing.JTextPane;
99 import javax.swing.KeyStroke;
100 import javax.swing.SwingUtilities;
101 import javax.swing.WindowConstants;
102 import javax.swing.event.HyperlinkEvent;
103 import javax.swing.event.HyperlinkEvent.EventType;
104 import javax.swing.event.InternalFrameAdapter;
105 import javax.swing.event.InternalFrameEvent;
106 import javax.swing.text.JTextComponent;
108 import org.stackoverflowusers.file.WindowsShortcut;
110 import jalview.api.AlignViewportI;
111 import jalview.api.AlignmentViewPanel;
112 import jalview.api.structures.JalviewStructureDisplayI;
113 import jalview.bin.Cache;
114 import jalview.bin.Jalview;
115 import jalview.bin.Jalview.ExitCode;
116 import jalview.datamodel.Alignment;
117 import jalview.datamodel.HiddenColumns;
118 import jalview.datamodel.Sequence;
119 import jalview.datamodel.SequenceI;
120 import jalview.gui.ImageExporter.ImageWriterI;
121 import jalview.gui.QuitHandler.QResponse;
122 import jalview.io.BackupFiles;
123 import jalview.io.DataSourceType;
124 import jalview.io.FileFormat;
125 import jalview.io.FileFormatException;
126 import jalview.io.FileFormatI;
127 import jalview.io.FileFormats;
128 import jalview.io.FileLoader;
129 import jalview.io.FormatAdapter;
130 import jalview.io.IdentifyFile;
131 import jalview.io.JalviewFileChooser;
132 import jalview.io.JalviewFileView;
133 import jalview.io.exceptions.ImageOutputException;
134 import jalview.jbgui.GSplitFrame;
135 import jalview.jbgui.GStructureViewer;
136 import jalview.project.Jalview2XML;
137 import jalview.structure.StructureSelectionManager;
138 import jalview.urls.IdOrgSettings;
139 import jalview.util.BrowserLauncher;
140 import jalview.util.ChannelProperties;
141 import jalview.util.ImageMaker.TYPE;
142 import jalview.util.LaunchUtils;
143 import jalview.util.MessageManager;
144 import jalview.util.Platform;
145 import jalview.util.ShortcutKeyMaskExWrapper;
146 import jalview.util.UrlConstants;
147 import jalview.viewmodel.AlignmentViewport;
148 import jalview.ws.params.ParamManager;
149 import jalview.ws.utils.UrlDownloadClient;
156 * @version $Revision: 1.155 $
158 public class Desktop extends jalview.jbgui.GDesktop
159 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
160 jalview.api.StructureSelectionManagerProvider
162 private static final String CITATION;
165 URL bg_logo_url = ChannelProperties.getImageURL(
166 "bg_logo." + String.valueOf(SplashScreen.logoSize));
167 URL uod_logo_url = ChannelProperties.getImageURL(
168 "uod_banner." + String.valueOf(SplashScreen.logoSize));
169 boolean logo = (bg_logo_url != null || uod_logo_url != null);
170 StringBuilder sb = new StringBuilder();
172 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
177 sb.append(bg_logo_url == null ? ""
178 : "<img alt=\"Barton Group logo\" src=\""
179 + bg_logo_url.toString() + "\">");
180 sb.append(uod_logo_url == null ? ""
181 : " <img alt=\"University of Dundee shield\" src=\""
182 + uod_logo_url.toString() + "\">");
184 "<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>");
185 sb.append("<br><br>If you use Jalview, please cite:"
186 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
187 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
188 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
189 CITATION = sb.toString();
192 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
194 private static int DEFAULT_MIN_WIDTH = 300;
196 private static int DEFAULT_MIN_HEIGHT = 250;
198 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
200 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
202 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
204 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
206 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
208 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
210 public static void setLiveDragMode(boolean b)
212 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
213 : JDesktopPane.OUTLINE_DRAG_MODE;
215 desktop.setDragMode(DRAG_MODE);
218 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
220 public static boolean nosplash = false;
223 * news reader - null if it was never started.
225 private BlogReader jvnews = null;
227 private File projectFile;
231 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
233 public void addJalviewPropertyChangeListener(
234 PropertyChangeListener listener)
236 changeSupport.addJalviewPropertyChangeListener(listener);
240 * @param propertyName
242 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
243 * java.beans.PropertyChangeListener)
245 public void addJalviewPropertyChangeListener(String propertyName,
246 PropertyChangeListener listener)
248 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
252 * @param propertyName
254 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
255 * java.beans.PropertyChangeListener)
257 public void removeJalviewPropertyChangeListener(String propertyName,
258 PropertyChangeListener listener)
260 changeSupport.removeJalviewPropertyChangeListener(propertyName,
264 /** Singleton Desktop instance */
265 public static Desktop instance;
267 public static MyDesktopPane desktop;
269 public static MyDesktopPane getDesktop()
271 // BH 2018 could use currentThread() here as a reference to a
272 // Hashtable<Thread, MyDesktopPane> in JavaScript
276 static int openFrameCount = 0;
278 static final int xOffset = 30;
280 static final int yOffset = 30;
282 public static jalview.ws.jws1.Discoverer discoverer;
284 public static Object[] jalviewClipboard;
286 public static boolean internalCopy = false;
288 static int fileLoadingCount = 0;
290 class MyDesktopManager implements DesktopManager
293 private DesktopManager delegate;
295 public MyDesktopManager(DesktopManager delegate)
297 this.delegate = delegate;
301 public void activateFrame(JInternalFrame f)
305 delegate.activateFrame(f);
306 } catch (NullPointerException npe)
308 Point p = getMousePosition();
309 instance.showPasteMenu(p.x, p.y);
314 public void beginDraggingFrame(JComponent f)
316 delegate.beginDraggingFrame(f);
320 public void beginResizingFrame(JComponent f, int direction)
322 delegate.beginResizingFrame(f, direction);
326 public void closeFrame(JInternalFrame f)
328 delegate.closeFrame(f);
332 public void deactivateFrame(JInternalFrame f)
334 delegate.deactivateFrame(f);
338 public void deiconifyFrame(JInternalFrame f)
340 delegate.deiconifyFrame(f);
344 public void dragFrame(JComponent f, int newX, int newY)
350 delegate.dragFrame(f, newX, newY);
354 public void endDraggingFrame(JComponent f)
356 delegate.endDraggingFrame(f);
361 public void endResizingFrame(JComponent f)
363 delegate.endResizingFrame(f);
368 public void iconifyFrame(JInternalFrame f)
370 delegate.iconifyFrame(f);
374 public void maximizeFrame(JInternalFrame f)
376 delegate.maximizeFrame(f);
380 public void minimizeFrame(JInternalFrame f)
382 delegate.minimizeFrame(f);
386 public void openFrame(JInternalFrame f)
388 delegate.openFrame(f);
392 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
399 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
403 public void setBoundsForFrame(JComponent f, int newX, int newY,
404 int newWidth, int newHeight)
406 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
409 // All other methods, simply delegate
414 * Creates a new Desktop object.
420 * A note to implementors. It is ESSENTIAL that any activities that might
421 * block are spawned off as threads rather than waited for during this
426 doConfigureStructurePrefs();
427 setTitle(ChannelProperties.getProperty("app_name") + " "
428 + Cache.getProperty("VERSION"));
431 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
432 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
433 * officially documented or guaranteed to exist, so we access it via
434 * reflection. There appear to be unfathomable criteria about what this
435 * string can contain, and it if doesn't meet those criteria then "java"
436 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
437 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
438 * not. The reflection access may generate a warning: WARNING: An illegal
439 * reflective access operation has occurred WARNING: Illegal reflective
440 * access by jalview.gui.Desktop () to field
441 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
443 if (Platform.isLinux())
445 if (LaunchUtils.getJavaVersion() >= 11)
448 * Send this message to stderr as the warning that follows (due to
449 * reflection) also goes to stderr.
451 jalview.bin.Console.errPrintln(
452 "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.");
454 final String awtAppClassName = "awtAppClassName";
457 Toolkit xToolkit = Toolkit.getDefaultToolkit();
458 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
459 Field awtAppClassNameField = null;
461 if (Arrays.stream(declaredFields)
462 .anyMatch(f -> f.getName().equals(awtAppClassName)))
464 awtAppClassNameField = xToolkit.getClass()
465 .getDeclaredField(awtAppClassName);
468 String title = ChannelProperties.getProperty("app_name");
469 if (awtAppClassNameField != null)
471 awtAppClassNameField.setAccessible(true);
472 awtAppClassNameField.set(xToolkit, title);
477 .debug("XToolkit: " + awtAppClassName + " not found");
479 } catch (Exception e)
481 jalview.bin.Console.debug("Error setting " + awtAppClassName);
482 jalview.bin.Console.trace(Cache.getStackTraceString(e));
486 setIconImages(ChannelProperties.getIconList());
488 // override quit handling when GUI OS close [X] button pressed
489 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
490 addWindowListener(new WindowAdapter()
493 public void windowClosing(WindowEvent ev)
495 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
499 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
501 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
503 // start dialogue queue for single dialogues
506 if (!Platform.isJS())
513 Desktop.instance.acquireDialogQueue();
515 jconsole = new Console(this);
516 jconsole.setHeader(Cache.getVersionDetailsForConsole());
517 showConsole(showjconsole);
519 Desktop.instance.releaseDialogQueue();
522 desktop = new MyDesktopPane(selmemusage);
524 showMemusage.setSelected(selmemusage);
525 desktop.setBackground(Color.white);
527 getContentPane().setLayout(new BorderLayout());
528 // alternate config - have scrollbars - see notes in JAL-153
529 // JScrollPane sp = new JScrollPane();
530 // sp.getViewport().setView(desktop);
531 // getContentPane().add(sp, BorderLayout.CENTER);
533 // BH 2018 - just an experiment to try unclipped JInternalFrames.
536 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
539 getContentPane().add(desktop, BorderLayout.CENTER);
540 desktop.setDragMode(DRAG_MODE);
542 // This line prevents Windows Look&Feel resizing all new windows to maximum
543 // if previous window was maximised
544 desktop.setDesktopManager(new MyDesktopManager(
545 Platform.isJS() ? desktop.getDesktopManager()
546 : new DefaultDesktopManager()));
548 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
549 : Platform.isAMacAndNotJS()
550 ? new AquaInternalFrameManager(
551 desktop.getDesktopManager())
552 : desktop.getDesktopManager())));
555 Rectangle dims = getLastKnownDimensions("");
562 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
563 int xPos = Math.max(5, (screenSize.width - 900) / 2);
564 int yPos = Math.max(5, (screenSize.height - 650) / 2);
565 setBounds(xPos, yPos, 900, 650);
568 if (!Platform.isJS())
575 showNews.setVisible(false);
577 experimentalFeatures.setSelected(showExperimental());
579 getIdentifiersOrgData();
583 // Spawn a thread that shows the splashscreen
586 SwingUtilities.invokeLater(new Runnable()
591 new SplashScreen(true);
596 // Thread off a new instance of the file chooser - this reduces the time
597 // it takes to open it later on.
598 new Thread(new Runnable()
603 jalview.bin.Console.debug("Filechooser init thread started.");
604 String fileFormat = FileLoader.getUseDefaultFileFormat()
605 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
607 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
609 jalview.bin.Console.debug("Filechooser init thread finished.");
612 // Add the service change listener
613 changeSupport.addJalviewPropertyChangeListener("services",
614 new PropertyChangeListener()
618 public void propertyChange(PropertyChangeEvent evt)
621 .debug("Firing service changed event for "
622 + evt.getNewValue());
623 JalviewServicesChanged(evt);
628 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
631 this.addMouseListener(ma = new MouseAdapter()
634 public void mousePressed(MouseEvent evt)
636 if (evt.isPopupTrigger()) // Mac
638 showPasteMenu(evt.getX(), evt.getY());
643 public void mouseReleased(MouseEvent evt)
645 if (evt.isPopupTrigger()) // Windows
647 showPasteMenu(evt.getX(), evt.getY());
651 desktop.addMouseListener(ma);
655 // used for jalviewjsTest
656 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
662 * Answers true if user preferences to enable experimental features is True
667 public boolean showExperimental()
669 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
670 Boolean.FALSE.toString());
671 return Boolean.valueOf(experimental).booleanValue();
674 public void doConfigureStructurePrefs()
676 // configure services
677 StructureSelectionManager ssm = StructureSelectionManager
678 .getStructureSelectionManager(this);
679 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
681 ssm.setAddTempFacAnnot(
682 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
683 ssm.setProcessSecondaryStructure(
684 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
685 // JAL-3915 - RNAView is no longer an option so this has no effect
686 ssm.setSecStructServices(
687 Cache.getDefault(Preferences.USE_RNAVIEW, false));
691 ssm.setAddTempFacAnnot(false);
692 ssm.setProcessSecondaryStructure(false);
693 ssm.setSecStructServices(false);
697 public void checkForNews()
699 final Desktop me = this;
700 // Thread off the news reader, in case there are connection problems.
701 new Thread(new Runnable()
706 jalview.bin.Console.debug("Starting news thread.");
707 jvnews = new BlogReader(me);
708 showNews.setVisible(true);
709 jalview.bin.Console.debug("Completed news thread.");
714 public void getIdentifiersOrgData()
716 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
717 {// Thread off the identifiers fetcher
718 new Thread(new Runnable()
724 .debug("Downloading data from identifiers.org");
727 UrlDownloadClient.download(IdOrgSettings.getUrl(),
728 IdOrgSettings.getDownloadLocation());
729 } catch (IOException e)
732 .debug("Exception downloading identifiers.org data"
742 protected void showNews_actionPerformed(ActionEvent e)
744 showNews(showNews.isSelected());
747 void showNews(boolean visible)
749 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
750 showNews.setSelected(visible);
751 if (visible && !jvnews.isVisible())
753 new Thread(new Runnable()
758 long now = System.currentTimeMillis();
759 Desktop.instance.setProgressBar(
760 MessageManager.getString("status.refreshing_news"), now);
761 jvnews.refreshNews();
762 Desktop.instance.setProgressBar(null, now);
770 * recover the last known dimensions for a jalview window
773 * - empty string is desktop, all other windows have unique prefix
774 * @return null or last known dimensions scaled to current geometry (if last
775 * window geom was known)
777 Rectangle getLastKnownDimensions(String windowName)
779 // TODO: lock aspect ratio for scaling desktop Bug #0058199
780 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
781 String x = Cache.getProperty(windowName + "SCREEN_X");
782 String y = Cache.getProperty(windowName + "SCREEN_Y");
783 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
784 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
785 if ((x != null) && (y != null) && (width != null) && (height != null))
787 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
788 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
789 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
791 // attempt #1 - try to cope with change in screen geometry - this
792 // version doesn't preserve original jv aspect ratio.
793 // take ratio of current screen size vs original screen size.
794 double sw = ((1f * screenSize.width) / (1f * Integer
795 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
796 double sh = ((1f * screenSize.height) / (1f * Integer
797 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
798 // rescale the bounds depending upon the current screen geometry.
799 ix = (int) (ix * sw);
800 iw = (int) (iw * sw);
801 iy = (int) (iy * sh);
802 ih = (int) (ih * sh);
803 while (ix >= screenSize.width)
805 jalview.bin.Console.debug(
806 "Window geometry location recall error: shifting horizontal to within screenbounds.");
807 ix -= screenSize.width;
809 while (iy >= screenSize.height)
811 jalview.bin.Console.debug(
812 "Window geometry location recall error: shifting vertical to within screenbounds.");
813 iy -= screenSize.height;
815 jalview.bin.Console.debug(
816 "Got last known dimensions for " + windowName + ": x:" + ix
817 + " y:" + iy + " width:" + iw + " height:" + ih);
819 // return dimensions for new instance
820 return new Rectangle(ix, iy, iw, ih);
825 void showPasteMenu(int x, int y)
827 JPopupMenu popup = new JPopupMenu();
828 JMenuItem item = new JMenuItem(
829 MessageManager.getString("label.paste_new_window"));
830 item.addActionListener(new ActionListener()
833 public void actionPerformed(ActionEvent evt)
840 popup.show(this, x, y);
845 // quick patch for JAL-4150 - needs some more work and test coverage
846 // TODO - unify below and AlignFrame.paste()
847 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
848 // clipboard has come from a different alignment window than the one where
849 // paste has been called! JAL-4151
851 if (Desktop.jalviewClipboard != null)
853 // The clipboard was filled from within Jalview, we must use the
855 // And dataset from the copied alignment
856 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
857 // be doubly sure that we create *new* sequence objects.
858 SequenceI[] sequences = new SequenceI[newseq.length];
859 for (int i = 0; i < newseq.length; i++)
861 sequences[i] = new Sequence(newseq[i]);
863 Alignment alignment = new Alignment(sequences);
864 // dataset is inherited
865 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
866 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
867 AlignFrame.DEFAULT_HEIGHT);
868 String newtitle = new String("Copied sequences");
870 if (Desktop.jalviewClipboard[2] != null)
872 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
873 af.viewport.setHiddenColumns(hc);
876 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
877 AlignFrame.DEFAULT_HEIGHT);
884 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
885 Transferable contents = c.getContents(this);
887 if (contents != null)
889 String file = (String) contents
890 .getTransferData(DataFlavor.stringFlavor);
892 FileFormatI format = new IdentifyFile().identify(file,
893 DataSourceType.PASTE);
895 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
898 } catch (Exception ex)
900 jalview.bin.Console.outPrintln(
901 "Unable to paste alignment from system clipboard:\n" + ex);
907 * Adds and opens the given frame to the desktop
918 public static synchronized void addInternalFrame(
919 final JInternalFrame frame, String title, int w, int h)
921 addInternalFrame(frame, title, true, w, h, true, false);
925 * Add an internal frame to the Jalview desktop
932 * When true, display frame immediately, otherwise, caller must call
933 * setVisible themselves.
939 public static synchronized void addInternalFrame(
940 final JInternalFrame frame, String title, boolean makeVisible,
943 addInternalFrame(frame, title, makeVisible, w, h, true, false);
947 * Add an internal frame to the Jalview desktop and make it visible
960 public static synchronized void addInternalFrame(
961 final JInternalFrame frame, String title, int w, int h,
964 addInternalFrame(frame, title, true, w, h, resizable, false);
968 * Add an internal frame to the Jalview desktop
975 * When true, display frame immediately, otherwise, caller must call
976 * setVisible themselves.
983 * @param ignoreMinSize
984 * Do not set the default minimum size for frame
986 public static synchronized void addInternalFrame(
987 final JInternalFrame frame, String title, boolean makeVisible,
988 int w, int h, boolean resizable, boolean ignoreMinSize)
991 // TODO: allow callers to determine X and Y position of frame (eg. via
993 // TODO: consider fixing method to update entries in the window submenu with
994 // the current window title
996 frame.setTitle(title);
997 if (frame.getWidth() < 1 || frame.getHeight() < 1)
1001 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
1002 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
1003 // IF JALVIEW IS RUNNING HEADLESS
1004 // ///////////////////////////////////////////////
1005 if (instance == null || (System.getProperty("java.awt.headless") != null
1006 && System.getProperty("java.awt.headless").equals("true")))
1015 frame.setMinimumSize(
1016 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1018 // Set default dimension for Alignment Frame window.
1019 // The Alignment Frame window could be added from a number of places,
1021 // I did this here in order not to miss out on any Alignment frame.
1022 if (frame instanceof AlignFrame)
1024 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1025 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1029 frame.setVisible(makeVisible);
1030 frame.setClosable(true);
1031 frame.setResizable(resizable);
1032 frame.setMaximizable(resizable);
1033 frame.setIconifiable(resizable);
1034 frame.setOpaque(Platform.isJS());
1036 if (frame.getX() < 1 && frame.getY() < 1)
1038 frame.setLocation(xOffset * openFrameCount,
1039 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1043 * add an entry for the new frame in the Window menu (and remove it when the
1046 final JMenuItem menuItem = new JMenuItem(title);
1047 frame.addInternalFrameListener(new InternalFrameAdapter()
1050 public void internalFrameActivated(InternalFrameEvent evt)
1052 JInternalFrame itf = desktop.getSelectedFrame();
1055 if (itf instanceof AlignFrame)
1057 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1064 public void internalFrameClosed(InternalFrameEvent evt)
1066 PaintRefresher.RemoveComponent(frame);
1069 * defensive check to prevent frames being added half off the window
1071 if (openFrameCount > 0)
1077 * ensure no reference to alignFrame retained by menu item listener
1079 if (menuItem.getActionListeners().length > 0)
1081 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1083 windowMenu.remove(menuItem);
1087 menuItem.addActionListener(new ActionListener()
1090 public void actionPerformed(ActionEvent e)
1094 frame.setSelected(true);
1095 frame.setIcon(false);
1096 } catch (java.beans.PropertyVetoException ex)
1103 setKeyBindings(frame);
1105 // Since the latest FlatLaf patch, we occasionally have problems showing
1106 // structureViewer frames...
1108 boolean shown = false;
1109 Exception last = null;
1116 } catch (IllegalArgumentException iaex)
1120 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1121 + tries + " left) for " + frame.getTitle(), iaex);
1125 } catch (InterruptedException iex)
1130 } while (!shown && tries > 0);
1133 jalview.bin.Console.error(
1134 "Serious Problem whilst showing window " + frame.getTitle(),
1138 windowMenu.add(menuItem);
1143 frame.setSelected(true);
1144 frame.requestFocus();
1145 } catch (java.beans.PropertyVetoException ve)
1147 } catch (java.lang.ClassCastException cex)
1149 jalview.bin.Console.warn(
1150 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1156 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1161 private static void setKeyBindings(JInternalFrame frame)
1163 @SuppressWarnings("serial")
1164 final Action closeAction = new AbstractAction()
1167 public void actionPerformed(ActionEvent e)
1174 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1176 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1177 InputEvent.CTRL_DOWN_MASK);
1178 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1179 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1181 InputMap inputMap = frame
1182 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1183 String ctrlW = ctrlWKey.toString();
1184 inputMap.put(ctrlWKey, ctrlW);
1185 inputMap.put(cmdWKey, ctrlW);
1187 ActionMap actionMap = frame.getActionMap();
1188 actionMap.put(ctrlW, closeAction);
1192 public void lostOwnership(Clipboard clipboard, Transferable contents)
1196 Desktop.jalviewClipboard = null;
1199 internalCopy = false;
1203 public void dragEnter(DropTargetDragEvent evt)
1208 public void dragExit(DropTargetEvent evt)
1213 public void dragOver(DropTargetDragEvent evt)
1218 public void dropActionChanged(DropTargetDragEvent evt)
1229 public void drop(DropTargetDropEvent evt)
1231 boolean success = true;
1232 // JAL-1552 - acceptDrop required before getTransferable call for
1233 // Java's Transferable for native dnd
1234 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1235 Transferable t = evt.getTransferable();
1236 List<Object> files = new ArrayList<>();
1237 List<DataSourceType> protocols = new ArrayList<>();
1241 Desktop.transferFromDropTarget(files, protocols, evt, t);
1242 } catch (Exception e)
1244 e.printStackTrace();
1252 for (int i = 0; i < files.size(); i++)
1254 // BH 2018 File or String
1255 Object file = files.get(i);
1256 String fileName = file.toString();
1257 DataSourceType protocol = (protocols == null)
1258 ? DataSourceType.FILE
1260 FileFormatI format = null;
1262 if (fileName.endsWith(".jar"))
1264 format = FileFormat.Jalview;
1269 format = new IdentifyFile().identify(file, protocol);
1271 if (file instanceof File)
1273 Platform.cacheFileData((File) file);
1275 new FileLoader().LoadFile(null, file, protocol, format);
1278 } catch (Exception ex)
1283 evt.dropComplete(success); // need this to ensure input focus is properly
1284 // transfered to any new windows created
1294 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1296 String fileFormat = FileLoader.getUseDefaultFileFormat()
1297 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1299 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1300 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1301 BackupFiles.getEnabled());
1303 chooser.setFileView(new JalviewFileView());
1304 chooser.setDialogTitle(
1305 MessageManager.getString("label.open_local_file"));
1306 chooser.setToolTipText(MessageManager.getString("action.open"));
1308 chooser.setResponseHandler(0, () -> {
1309 File selectedFile = chooser.getSelectedFile();
1310 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1312 FileFormatI format = chooser.getSelectedFormat();
1315 * Call IdentifyFile to verify the file contains what its extension implies.
1316 * Skip this step for dynamically added file formats, because IdentifyFile does
1317 * not know how to recognise them.
1319 if (FileFormats.getInstance().isIdentifiable(format))
1323 format = new IdentifyFile().identify(selectedFile,
1324 DataSourceType.FILE);
1325 } catch (FileFormatException e)
1327 // format = null; //??
1331 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1334 chooser.showOpenDialog(this);
1338 * Shows a dialog for input of a URL at which to retrieve alignment data
1343 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1345 // This construct allows us to have a wider textfield
1347 JLabel label = new JLabel(
1348 MessageManager.getString("label.input_file_url"));
1350 JPanel panel = new JPanel(new GridLayout(2, 1));
1354 * the URL to fetch is input in Java: an editable combobox with history JS:
1355 * (pending JAL-3038) a plain text field
1358 String urlBase = "https://www.";
1359 if (Platform.isJS())
1361 history = new JTextField(urlBase, 35);
1370 JComboBox<String> asCombo = new JComboBox<>();
1371 asCombo.setPreferredSize(new Dimension(400, 20));
1372 asCombo.setEditable(true);
1373 asCombo.addItem(urlBase);
1374 String historyItems = Cache.getProperty("RECENT_URL");
1375 if (historyItems != null)
1377 for (String token : historyItems.split("\\t"))
1379 asCombo.addItem(token);
1386 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1387 MessageManager.getString("action.cancel") };
1388 Runnable action = () -> {
1389 @SuppressWarnings("unchecked")
1390 String url = (history instanceof JTextField
1391 ? ((JTextField) history).getText()
1392 : ((JComboBox<String>) history).getEditor().getItem()
1393 .toString().trim());
1395 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1397 if (viewport != null)
1399 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1400 FileFormat.Jalview);
1404 new FileLoader().LoadFile(url, DataSourceType.URL,
1405 FileFormat.Jalview);
1410 FileFormatI format = null;
1413 format = new IdentifyFile().identify(url, DataSourceType.URL);
1414 } catch (FileFormatException e)
1416 // TODO revise error handling, distinguish between
1417 // URL not found and response not valid
1422 String msg = MessageManager.formatMessage("label.couldnt_locate",
1424 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1425 MessageManager.getString("label.url_not_found"),
1426 JvOptionPane.WARNING_MESSAGE);
1430 if (viewport != null)
1432 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1437 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1441 String dialogOption = MessageManager
1442 .getString("label.input_alignment_from_url");
1443 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1444 .showInternalDialog(panel, dialogOption,
1445 JvOptionPane.YES_NO_CANCEL_OPTION,
1446 JvOptionPane.PLAIN_MESSAGE, null, options,
1447 MessageManager.getString("action.ok"));
1451 * Opens the CutAndPaste window for the user to paste an alignment in to
1454 * - if not null, the pasted alignment is added to the current
1455 * alignment; if null, to a new alignment window
1458 public void inputTextboxMenuItem_actionPerformed(
1459 AlignmentViewPanel viewPanel)
1461 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1462 cap.setForInput(viewPanel);
1463 Desktop.addInternalFrame(cap,
1464 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1469 * Check with user and saving files before actually quitting
1471 public void desktopQuit()
1473 desktopQuit(true, false);
1476 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1478 final Runnable doDesktopQuit = () -> {
1479 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1480 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1481 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1482 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1483 getBounds().y, getWidth(), getHeight()));
1485 if (jconsole != null)
1487 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1488 jconsole.stopConsole();
1493 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1496 // Frames should all close automatically. Keeping external
1497 // viewers open should already be decided by user.
1498 closeAll_actionPerformed(null);
1500 // check for aborted quit
1501 if (QuitHandler.quitCancelled())
1503 jalview.bin.Console.debug("Desktop aborting quit");
1507 if (dialogExecutor != null)
1509 dialogExecutor.shutdownNow();
1512 if (groovyConsole != null)
1514 // suppress a possible repeat prompt to save script
1515 groovyConsole.setDirty(false);
1516 groovyConsole.exit();
1519 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1521 // note that shutdown hook will not be run
1522 jalview.bin.Console.debug("Force Quit selected by user");
1523 Runtime.getRuntime().halt(0);
1526 jalview.bin.Console.debug("Quit selected by user");
1529 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1530 // instance.dispose();
1535 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1536 QuitHandler.defaultCancelQuit);
1540 * Don't call this directly, use desktopQuit() above. Exits the program.
1545 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1546 // not run a second time if gotQuitResponse flag has been set (i.e. user
1547 // confirmed quit of some kind).
1548 Jalview.exit("Desktop exiting.", ExitCode.OK);
1551 private void storeLastKnownDimensions(String string, Rectangle jc)
1553 jalview.bin.Console.debug("Storing last known dimensions for " + string
1554 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1555 + " height:" + jc.height);
1557 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1558 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1559 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1560 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1570 public void aboutMenuItem_actionPerformed(ActionEvent e)
1572 new Thread(new Runnable()
1577 new SplashScreen(false);
1583 * Returns the html text for the About screen, including any available version
1584 * number, build details, author details and citation reference, but without
1585 * the enclosing {@code html} tags
1589 public String getAboutMessage()
1591 StringBuilder message = new StringBuilder(1024);
1592 message.append("<div style=\"font-family: sans-serif;\">")
1593 .append("<h1><strong>Version: ")
1594 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1595 .append("<strong>Built: <em>")
1596 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1597 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1598 .append("</strong>");
1600 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1601 if (latestVersion.equals("Checking"))
1603 // JBP removed this message for 2.11: May be reinstated in future version
1604 // message.append("<br>...Checking latest version...</br>");
1606 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1608 boolean red = false;
1609 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1610 .indexOf("automated build") == -1)
1613 // Displayed when code version and jnlp version do not match and code
1614 // version is not a development build
1615 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1618 message.append("<br>!! Version ")
1619 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1620 .append(" is available for download from ")
1621 .append(Cache.getDefault("www.jalview.org",
1622 "https://www.jalview.org"))
1626 message.append("</div>");
1629 message.append("<br>Authors: ");
1630 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1631 message.append(CITATION);
1633 message.append("</div>");
1635 return message.toString();
1639 * Action on requesting Help documentation
1642 public void documentationMenuItem_actionPerformed()
1646 if (Platform.isJS())
1648 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1657 Help.showHelpWindow();
1659 } catch (Exception ex)
1662 .errPrintln("Error opening help: " + ex.getMessage());
1667 public void closeAll_actionPerformed(ActionEvent e)
1669 // TODO show a progress bar while closing?
1670 JInternalFrame[] frames = desktop.getAllFrames();
1671 for (int i = 0; i < frames.length; i++)
1675 frames[i].setClosed(true);
1676 } catch (java.beans.PropertyVetoException ex)
1680 Jalview.setCurrentAlignFrame(null);
1681 jalview.bin.Console.info("ALL CLOSED");
1684 * reset state of singleton objects as appropriate (clear down session state
1685 * when all windows are closed)
1687 StructureSelectionManager ssm = StructureSelectionManager
1688 .getStructureSelectionManager(this);
1695 public int structureViewersStillRunningCount()
1698 JInternalFrame[] frames = desktop.getAllFrames();
1699 for (int i = 0; i < frames.length; i++)
1701 if (frames[i] != null
1702 && frames[i] instanceof JalviewStructureDisplayI)
1704 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1712 public void raiseRelated_actionPerformed(ActionEvent e)
1714 reorderAssociatedWindows(false, false);
1718 public void minimizeAssociated_actionPerformed(ActionEvent e)
1720 reorderAssociatedWindows(true, false);
1723 void closeAssociatedWindows()
1725 reorderAssociatedWindows(false, true);
1731 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1735 protected void garbageCollect_actionPerformed(ActionEvent e)
1737 // We simply collect the garbage
1738 jalview.bin.Console.debug("Collecting garbage...");
1740 jalview.bin.Console.debug("Finished garbage collection.");
1746 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1750 protected void showMemusage_actionPerformed(ActionEvent e)
1752 desktop.showMemoryUsage(showMemusage.isSelected());
1759 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1763 protected void showConsole_actionPerformed(ActionEvent e)
1765 showConsole(showConsole.isSelected());
1768 Console jconsole = null;
1771 * control whether the java console is visible or not
1775 void showConsole(boolean selected)
1777 // TODO: decide if we should update properties file
1778 if (jconsole != null) // BH 2018
1780 showConsole.setSelected(selected);
1781 Cache.setProperty("SHOW_JAVA_CONSOLE",
1782 Boolean.valueOf(selected).toString());
1783 jconsole.setVisible(selected);
1787 void reorderAssociatedWindows(boolean minimize, boolean close)
1789 JInternalFrame[] frames = desktop.getAllFrames();
1790 if (frames == null || frames.length < 1)
1795 AlignmentViewport source = null, target = null;
1796 if (frames[0] instanceof AlignFrame)
1798 source = ((AlignFrame) frames[0]).getCurrentView();
1800 else if (frames[0] instanceof TreePanel)
1802 source = ((TreePanel) frames[0]).getViewPort();
1804 else if (frames[0] instanceof PCAPanel)
1806 source = ((PCAPanel) frames[0]).av;
1808 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1810 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1815 for (int i = 0; i < frames.length; i++)
1818 if (frames[i] == null)
1822 if (frames[i] instanceof AlignFrame)
1824 target = ((AlignFrame) frames[i]).getCurrentView();
1826 else if (frames[i] instanceof TreePanel)
1828 target = ((TreePanel) frames[i]).getViewPort();
1830 else if (frames[i] instanceof PCAPanel)
1832 target = ((PCAPanel) frames[i]).av;
1834 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1836 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1839 if (source == target)
1845 frames[i].setClosed(true);
1849 frames[i].setIcon(minimize);
1852 frames[i].toFront();
1856 } catch (java.beans.PropertyVetoException ex)
1871 protected void preferences_actionPerformed(ActionEvent e)
1873 Preferences.openPreferences();
1877 * Prompts the user to choose a file and then saves the Jalview state as a
1878 * Jalview project file
1881 public void saveState_actionPerformed()
1883 saveState_actionPerformed(false);
1886 public void saveState_actionPerformed(boolean saveAs)
1888 java.io.File projectFile = getProjectFile();
1889 // autoSave indicates we already have a file and don't need to ask
1890 boolean autoSave = projectFile != null && !saveAs
1891 && BackupFiles.getEnabled();
1893 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1894 // projectFile='"+projectFile+"',
1895 // saveAs="+saveAs+", Backups
1896 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1898 boolean approveSave = false;
1901 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1904 chooser.setFileView(new JalviewFileView());
1905 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1907 int value = chooser.showSaveDialog(this);
1909 if (value == JalviewFileChooser.APPROVE_OPTION)
1911 projectFile = chooser.getSelectedFile();
1912 setProjectFile(projectFile);
1917 if (approveSave || autoSave)
1919 final Desktop me = this;
1920 final java.io.File chosenFile = projectFile;
1921 new Thread(new Runnable()
1926 // TODO: refactor to Jalview desktop session controller action.
1927 setProgressBar(MessageManager.formatMessage(
1928 "label.saving_jalview_project", new Object[]
1929 { chosenFile.getName() }), chosenFile.hashCode());
1930 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1931 // TODO catch and handle errors for savestate
1932 // TODO prevent user from messing with the Desktop whilst we're saving
1935 boolean doBackup = BackupFiles.getEnabled();
1936 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1939 new Jalview2XML().saveState(
1940 doBackup ? backupfiles.getTempFile() : chosenFile);
1944 backupfiles.setWriteSuccess(true);
1945 backupfiles.rollBackupsAndRenameTempFile();
1947 } catch (OutOfMemoryError oom)
1949 new OOMWarning("Whilst saving current state to "
1950 + chosenFile.getName(), oom);
1951 } catch (Exception ex)
1953 jalview.bin.Console.error("Problems whilst trying to save to "
1954 + chosenFile.getName(), ex);
1955 JvOptionPane.showMessageDialog(me,
1956 MessageManager.formatMessage(
1957 "label.error_whilst_saving_current_state_to",
1959 { chosenFile.getName() }),
1960 MessageManager.getString("label.couldnt_save_project"),
1961 JvOptionPane.WARNING_MESSAGE);
1963 setProgressBar(null, chosenFile.hashCode());
1970 public void saveAsState_actionPerformed(ActionEvent e)
1972 saveState_actionPerformed(true);
1975 protected void setProjectFile(File choice)
1977 this.projectFile = choice;
1980 public File getProjectFile()
1982 return this.projectFile;
1986 * Shows a file chooser dialog and tries to read in the selected file as a
1990 public void loadState_actionPerformed()
1992 final String[] suffix = new String[] { "jvp", "jar" };
1993 final String[] desc = new String[] { "Jalview Project",
1994 "Jalview Project (old)" };
1995 JalviewFileChooser chooser = new JalviewFileChooser(
1996 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1997 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2001 chooser.setFileView(new JalviewFileView());
2002 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2003 chooser.setResponseHandler(0, () -> {
2004 File selectedFile = chooser.getSelectedFile();
2005 setProjectFile(selectedFile);
2006 String choice = selectedFile.getAbsolutePath();
2007 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2008 new Thread(new Runnable()
2015 new Jalview2XML().loadJalviewAlign(selectedFile);
2016 } catch (OutOfMemoryError oom)
2018 new OOMWarning("Whilst loading project from " + choice, oom);
2019 } catch (Exception ex)
2021 jalview.bin.Console.error(
2022 "Problems whilst loading project from " + choice, ex);
2023 JvOptionPane.showMessageDialog(Desktop.desktop,
2024 MessageManager.formatMessage(
2025 "label.error_whilst_loading_project_from",
2028 MessageManager.getString("label.couldnt_load_project"),
2029 JvOptionPane.WARNING_MESSAGE);
2032 }, "Project Loader").start();
2035 chooser.showOpenDialog(this);
2039 public void inputSequence_actionPerformed(ActionEvent e)
2041 new SequenceFetcher(this);
2044 JPanel progressPanel;
2046 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2048 public void startLoading(final Object fileName)
2050 if (fileLoadingCount == 0)
2052 fileLoadingPanels.add(addProgressPanel(MessageManager
2053 .formatMessage("label.loading_file", new Object[]
2059 private JPanel addProgressPanel(String string)
2061 if (progressPanel == null)
2063 progressPanel = new JPanel(new GridLayout(1, 1));
2064 totalProgressCount = 0;
2065 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2067 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2068 JProgressBar progressBar = new JProgressBar();
2069 progressBar.setIndeterminate(true);
2071 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2073 thisprogress.add(progressBar, BorderLayout.CENTER);
2074 progressPanel.add(thisprogress);
2075 ((GridLayout) progressPanel.getLayout()).setRows(
2076 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2077 ++totalProgressCount;
2078 instance.validate();
2079 return thisprogress;
2082 int totalProgressCount = 0;
2084 private void removeProgressPanel(JPanel progbar)
2086 if (progressPanel != null)
2088 synchronized (progressPanel)
2090 progressPanel.remove(progbar);
2091 GridLayout gl = (GridLayout) progressPanel.getLayout();
2092 gl.setRows(gl.getRows() - 1);
2093 if (--totalProgressCount < 1)
2095 this.getContentPane().remove(progressPanel);
2096 progressPanel = null;
2103 public void stopLoading()
2106 if (fileLoadingCount < 1)
2108 while (fileLoadingPanels.size() > 0)
2110 removeProgressPanel(fileLoadingPanels.remove(0));
2112 fileLoadingPanels.clear();
2113 fileLoadingCount = 0;
2118 public static int getViewCount(String alignmentId)
2120 AlignmentViewport[] aps = getViewports(alignmentId);
2121 return (aps == null) ? 0 : aps.length;
2126 * @param alignmentId
2127 * - if null, all sets are returned
2128 * @return all AlignmentPanels concerning the alignmentId sequence set
2130 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2132 if (Desktop.desktop == null)
2134 // no frames created and in headless mode
2135 // TODO: verify that frames are recoverable when in headless mode
2138 List<AlignmentPanel> aps = new ArrayList<>();
2139 AlignFrame[] frames = getAlignFrames();
2144 for (AlignFrame af : frames)
2146 for (AlignmentPanel ap : af.alignPanels)
2148 if (alignmentId == null
2149 || alignmentId.equals(ap.av.getSequenceSetId()))
2155 if (aps.size() == 0)
2159 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2164 * get all the viewports on an alignment.
2166 * @param sequenceSetId
2167 * unique alignment id (may be null - all viewports returned in that
2169 * @return all viewports on the alignment bound to sequenceSetId
2171 public static AlignmentViewport[] getViewports(String sequenceSetId)
2173 List<AlignmentViewport> viewp = new ArrayList<>();
2174 if (desktop != null)
2176 AlignFrame[] frames = Desktop.getAlignFrames();
2178 for (AlignFrame afr : frames)
2180 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2181 .equals(sequenceSetId))
2183 if (afr.alignPanels != null)
2185 for (AlignmentPanel ap : afr.alignPanels)
2187 if (sequenceSetId == null
2188 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2196 viewp.add(afr.getViewport());
2200 if (viewp.size() > 0)
2202 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2209 * Explode the views in the given frame into separate AlignFrame
2213 public static void explodeViews(AlignFrame af)
2215 int size = af.alignPanels.size();
2221 // FIXME: ideally should use UI interface API
2222 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2223 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2224 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2225 for (int i = 0; i < size; i++)
2227 AlignmentPanel ap = af.alignPanels.get(i);
2229 AlignFrame newaf = new AlignFrame(ap);
2231 // transfer reference for existing feature settings to new alignFrame
2232 if (ap == af.alignPanel)
2234 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2236 newaf.featureSettings = viewFeatureSettings;
2238 newaf.setFeatureSettingsGeometry(fsBounds);
2242 * Restore the view's last exploded frame geometry if known. Multiple views from
2243 * one exploded frame share and restore the same (frame) position and size.
2245 Rectangle geometry = ap.av.getExplodedGeometry();
2246 if (geometry != null)
2248 newaf.setBounds(geometry);
2251 ap.av.setGatherViewsHere(false);
2253 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2254 AlignFrame.DEFAULT_HEIGHT);
2255 // and materialise a new feature settings dialog instance for the new
2257 // (closes the old as if 'OK' was pressed)
2258 if (ap == af.alignPanel && newaf.featureSettings != null
2259 && newaf.featureSettings.isOpen()
2260 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2262 newaf.showFeatureSettingsUI();
2266 af.featureSettings = null;
2267 af.alignPanels.clear();
2268 af.closeMenuItem_actionPerformed(true);
2273 * Gather expanded views (separate AlignFrame's) with the same sequence set
2274 * identifier back in to this frame as additional views, and close the
2275 * expanded views. Note the expanded frames may themselves have multiple
2276 * views. We take the lot.
2280 public void gatherViews(AlignFrame source)
2282 source.viewport.setGatherViewsHere(true);
2283 source.viewport.setExplodedGeometry(source.getBounds());
2284 JInternalFrame[] frames = desktop.getAllFrames();
2285 String viewId = source.viewport.getSequenceSetId();
2286 for (int t = 0; t < frames.length; t++)
2288 if (frames[t] instanceof AlignFrame && frames[t] != source)
2290 AlignFrame af = (AlignFrame) frames[t];
2291 boolean gatherThis = false;
2292 for (int a = 0; a < af.alignPanels.size(); a++)
2294 AlignmentPanel ap = af.alignPanels.get(a);
2295 if (viewId.equals(ap.av.getSequenceSetId()))
2298 ap.av.setGatherViewsHere(false);
2299 ap.av.setExplodedGeometry(af.getBounds());
2300 source.addAlignmentPanel(ap, false);
2306 if (af.featureSettings != null && af.featureSettings.isOpen())
2308 if (source.featureSettings == null)
2310 // preserve the feature settings geometry for this frame
2311 source.featureSettings = af.featureSettings;
2312 source.setFeatureSettingsGeometry(
2313 af.getFeatureSettingsGeometry());
2317 // close it and forget
2318 af.featureSettings.close();
2321 af.alignPanels.clear();
2322 af.closeMenuItem_actionPerformed(true);
2327 // refresh the feature setting UI for the source frame if it exists
2328 if (source.featureSettings != null && source.featureSettings.isOpen())
2330 source.showFeatureSettingsUI();
2335 public JInternalFrame[] getAllFrames()
2337 return desktop.getAllFrames();
2341 * Checks the given url to see if it gives a response indicating that the user
2342 * should be informed of a new questionnaire.
2346 public void checkForQuestionnaire(String url)
2348 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2349 // javax.swing.SwingUtilities.invokeLater(jvq);
2350 new Thread(jvq).start();
2353 public void checkURLLinks()
2355 // Thread off the URL link checker
2356 addDialogThread(new Runnable()
2361 if (Cache.getDefault("CHECKURLLINKS", true))
2363 // check what the actual links are - if it's just the default don't
2364 // bother with the warning
2365 List<String> links = Preferences.sequenceUrlLinks
2368 // only need to check links if there is one with a
2369 // SEQUENCE_ID which is not the default EMBL_EBI link
2370 ListIterator<String> li = links.listIterator();
2371 boolean check = false;
2372 List<JLabel> urls = new ArrayList<>();
2373 while (li.hasNext())
2375 String link = li.next();
2376 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2377 && !UrlConstants.isDefaultString(link))
2380 int barPos = link.indexOf("|");
2381 String urlMsg = barPos == -1 ? link
2382 : link.substring(0, barPos) + ": "
2383 + link.substring(barPos + 1);
2384 urls.add(new JLabel(urlMsg));
2392 // ask user to check in case URL links use old style tokens
2393 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2394 JPanel msgPanel = new JPanel();
2395 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2396 msgPanel.add(Box.createVerticalGlue());
2397 JLabel msg = new JLabel(MessageManager
2398 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2399 JLabel msg2 = new JLabel(MessageManager
2400 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2402 for (JLabel url : urls)
2408 final JCheckBox jcb = new JCheckBox(
2409 MessageManager.getString("label.do_not_display_again"));
2410 jcb.addActionListener(new ActionListener()
2413 public void actionPerformed(ActionEvent e)
2415 // update Cache settings for "don't show this again"
2416 boolean showWarningAgain = !jcb.isSelected();
2417 Cache.setProperty("CHECKURLLINKS",
2418 Boolean.valueOf(showWarningAgain).toString());
2423 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2425 .getString("label.SEQUENCE_ID_no_longer_used"),
2426 JvOptionPane.WARNING_MESSAGE);
2433 * Proxy class for JDesktopPane which optionally displays the current memory
2434 * usage and highlights the desktop area with a red bar if free memory runs
2439 public class MyDesktopPane extends JDesktopPane implements Runnable
2441 private static final float ONE_MB = 1048576f;
2443 boolean showMemoryUsage = false;
2447 java.text.NumberFormat df;
2449 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2452 public MyDesktopPane(boolean showMemoryUsage)
2454 showMemoryUsage(showMemoryUsage);
2457 public void showMemoryUsage(boolean showMemory)
2459 this.showMemoryUsage = showMemory;
2462 Thread worker = new Thread(this);
2468 public boolean isShowMemoryUsage()
2470 return showMemoryUsage;
2476 df = java.text.NumberFormat.getNumberInstance();
2477 df.setMaximumFractionDigits(2);
2478 runtime = Runtime.getRuntime();
2480 while (showMemoryUsage)
2484 maxMemory = runtime.maxMemory() / ONE_MB;
2485 allocatedMemory = runtime.totalMemory() / ONE_MB;
2486 freeMemory = runtime.freeMemory() / ONE_MB;
2487 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2489 percentUsage = (totalFreeMemory / maxMemory) * 100;
2491 // if (percentUsage < 20)
2493 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2495 // instance.set.setBorder(border1);
2498 // sleep after showing usage
2500 } catch (Exception ex)
2502 ex.printStackTrace();
2508 public void paintComponent(Graphics g)
2510 if (showMemoryUsage && g != null && df != null)
2512 if (percentUsage < 20)
2514 g.setColor(Color.red);
2516 FontMetrics fm = g.getFontMetrics();
2519 g.drawString(MessageManager.formatMessage("label.memory_stats",
2521 { df.format(totalFreeMemory), df.format(maxMemory),
2522 df.format(percentUsage) }),
2523 10, getHeight() - fm.getHeight());
2527 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2528 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2533 * Accessor method to quickly get all the AlignmentFrames loaded.
2535 * @return an array of AlignFrame, or null if none found
2537 public static AlignFrame[] getAlignFrames()
2539 if (Jalview.isHeadlessMode())
2541 // Desktop.desktop is null in headless mode
2542 return new AlignFrame[] { Jalview.currentAlignFrame };
2545 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2551 List<AlignFrame> avp = new ArrayList<>();
2553 for (int i = frames.length - 1; i > -1; i--)
2555 if (frames[i] instanceof AlignFrame)
2557 avp.add((AlignFrame) frames[i]);
2559 else if (frames[i] instanceof SplitFrame)
2562 * Also check for a split frame containing an AlignFrame
2564 GSplitFrame sf = (GSplitFrame) frames[i];
2565 if (sf.getTopFrame() instanceof AlignFrame)
2567 avp.add((AlignFrame) sf.getTopFrame());
2569 if (sf.getBottomFrame() instanceof AlignFrame)
2571 avp.add((AlignFrame) sf.getBottomFrame());
2575 if (avp.size() == 0)
2579 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2584 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2588 public GStructureViewer[] getJmols()
2590 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2596 List<GStructureViewer> avp = new ArrayList<>();
2598 for (int i = frames.length - 1; i > -1; i--)
2600 if (frames[i] instanceof AppJmol)
2602 GStructureViewer af = (GStructureViewer) frames[i];
2606 if (avp.size() == 0)
2610 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2615 * Add Groovy Support to Jalview
2618 public void groovyShell_actionPerformed()
2622 openGroovyConsole();
2623 } catch (Exception ex)
2625 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2626 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2628 MessageManager.getString("label.couldnt_create_groovy_shell"),
2629 MessageManager.getString("label.groovy_support_failed"),
2630 JvOptionPane.ERROR_MESSAGE);
2635 * Open the Groovy console
2637 void openGroovyConsole()
2639 if (groovyConsole == null)
2641 groovyConsole = new groovy.ui.Console();
2642 groovyConsole.setVariable("Jalview", this);
2643 groovyConsole.run();
2646 * We allow only one console at a time, so that AlignFrame menu option
2647 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2648 * enable 'Run script', when the console is opened, and the reverse when it is
2651 Window window = (Window) groovyConsole.getFrame();
2652 window.addWindowListener(new WindowAdapter()
2655 public void windowClosed(WindowEvent e)
2658 * rebind CMD-Q from Groovy Console to Jalview Quit
2661 enableExecuteGroovy(false);
2667 * show Groovy console window (after close and reopen)
2669 ((Window) groovyConsole.getFrame()).setVisible(true);
2672 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2673 * opening a second console
2675 enableExecuteGroovy(true);
2679 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2680 * binding when opened
2682 protected void addQuitHandler()
2685 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2687 .getKeyStroke(KeyEvent.VK_Q,
2688 jalview.util.ShortcutKeyMaskExWrapper
2689 .getMenuShortcutKeyMaskEx()),
2691 getRootPane().getActionMap().put("Quit", new AbstractAction()
2694 public void actionPerformed(ActionEvent e)
2702 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2705 * true if Groovy console is open
2707 public void enableExecuteGroovy(boolean enabled)
2710 * disable opening a second Groovy console (or re-enable when the console is
2713 groovyShell.setEnabled(!enabled);
2715 AlignFrame[] alignFrames = getAlignFrames();
2716 if (alignFrames != null)
2718 for (AlignFrame af : alignFrames)
2720 af.setGroovyEnabled(enabled);
2726 * Progress bars managed by the IProgressIndicator method.
2728 private Hashtable<Long, JPanel> progressBars;
2730 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2735 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2738 public void setProgressBar(String message, long id)
2740 if (progressBars == null)
2742 progressBars = new Hashtable<>();
2743 progressBarHandlers = new Hashtable<>();
2746 if (progressBars.get(Long.valueOf(id)) != null)
2748 JPanel panel = progressBars.remove(Long.valueOf(id));
2749 if (progressBarHandlers.contains(Long.valueOf(id)))
2751 progressBarHandlers.remove(Long.valueOf(id));
2753 removeProgressPanel(panel);
2757 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2764 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2765 * jalview.gui.IProgressIndicatorHandler)
2768 public void registerHandler(final long id,
2769 final IProgressIndicatorHandler handler)
2771 if (progressBarHandlers == null
2772 || !progressBars.containsKey(Long.valueOf(id)))
2774 throw new Error(MessageManager.getString(
2775 "error.call_setprogressbar_before_registering_handler"));
2777 progressBarHandlers.put(Long.valueOf(id), handler);
2778 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2779 if (handler.canCancel())
2781 JButton cancel = new JButton(
2782 MessageManager.getString("action.cancel"));
2783 final IProgressIndicator us = this;
2784 cancel.addActionListener(new ActionListener()
2788 public void actionPerformed(ActionEvent e)
2790 handler.cancelActivity(id);
2791 us.setProgressBar(MessageManager
2792 .formatMessage("label.cancelled_params", new Object[]
2793 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2797 progressPanel.add(cancel, BorderLayout.EAST);
2803 * @return true if any progress bars are still active
2806 public boolean operationInProgress()
2808 if (progressBars != null && progressBars.size() > 0)
2816 * This will return the first AlignFrame holding the given viewport instance.
2817 * It will break if there are more than one AlignFrames viewing a particular
2821 * @return alignFrame for viewport
2823 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2825 if (desktop != null)
2827 AlignmentPanel[] aps = getAlignmentPanels(
2828 viewport.getSequenceSetId());
2829 for (int panel = 0; aps != null && panel < aps.length; panel++)
2831 if (aps[panel] != null && aps[panel].av == viewport)
2833 return aps[panel].alignFrame;
2840 public VamsasApplication getVamsasApplication()
2842 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2848 * flag set if jalview GUI is being operated programmatically
2850 private boolean inBatchMode = false;
2853 * check if jalview GUI is being operated programmatically
2855 * @return inBatchMode
2857 public boolean isInBatchMode()
2863 * set flag if jalview GUI is being operated programmatically
2865 * @param inBatchMode
2867 public void setInBatchMode(boolean inBatchMode)
2869 this.inBatchMode = inBatchMode;
2873 * start service discovery and wait till it is done
2875 public void startServiceDiscovery()
2877 startServiceDiscovery(false);
2881 * start service discovery threads - blocking or non-blocking
2885 public void startServiceDiscovery(boolean blocking)
2887 startServiceDiscovery(blocking, false);
2891 * start service discovery threads
2894 * - false means call returns immediately
2895 * @param ignore_SHOW_JWS2_SERVICES_preference
2896 * - when true JABA services are discovered regardless of user's JWS2
2897 * discovery preference setting
2899 public void startServiceDiscovery(boolean blocking,
2900 boolean ignore_SHOW_JWS2_SERVICES_preference)
2902 boolean alive = true;
2903 Thread t0 = null, t1 = null, t2 = null;
2904 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2907 // todo: changesupport handlers need to be transferred
2908 if (discoverer == null)
2910 discoverer = new jalview.ws.jws1.Discoverer();
2911 // register PCS handler for desktop.
2912 discoverer.addPropertyChangeListener(changeSupport);
2914 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2915 // until we phase out completely
2916 (t0 = new Thread(discoverer)).start();
2919 if (ignore_SHOW_JWS2_SERVICES_preference
2920 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2922 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2923 .startDiscoverer(changeSupport);
2927 // TODO: do rest service discovery
2936 } catch (Exception e)
2939 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2940 || (t3 != null && t3.isAlive())
2941 || (t0 != null && t0.isAlive());
2947 * called to check if the service discovery process completed successfully.
2951 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2953 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2955 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2956 .getErrorMessages();
2959 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2961 if (serviceChangedDialog == null)
2963 // only run if we aren't already displaying one of these.
2964 addDialogThread(serviceChangedDialog = new Runnable()
2971 * JalviewDialog jd =new JalviewDialog() {
2973 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2975 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2977 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2979 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2981 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2982 * + " or mis-configured HTTP proxy settings.<br/>" +
2983 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2984 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2985 * true, true, "Web Service Configuration Problem", 450, 400);
2987 * jd.waitForInput();
2989 JvOptionPane.showConfirmDialog(Desktop.desktop,
2990 new JLabel("<html><table width=\"450\"><tr><td>"
2991 + ermsg + "</td></tr></table>"
2992 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2993 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2994 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2995 + " Tools->Preferences dialog box to change them.</p></html>"),
2996 "Web Service Configuration Problem",
2997 JvOptionPane.DEFAULT_OPTION,
2998 JvOptionPane.ERROR_MESSAGE);
2999 serviceChangedDialog = null;
3007 jalview.bin.Console.error(
3008 "Errors reported by JABA discovery service. Check web services preferences.\n"
3015 private Runnable serviceChangedDialog = null;
3018 * start a thread to open a URL in the configured browser. Pops up a warning
3019 * dialog to the user if there is an exception when calling out to the browser
3024 public static void showUrl(final String url)
3026 showUrl(url, Desktop.instance);
3030 * Like showUrl but allows progress handler to be specified
3034 * (null) or object implementing IProgressIndicator
3036 public static void showUrl(final String url,
3037 final IProgressIndicator progress)
3039 new Thread(new Runnable()
3046 if (progress != null)
3048 progress.setProgressBar(MessageManager
3049 .formatMessage("status.opening_params", new Object[]
3050 { url }), this.hashCode());
3052 jalview.util.BrowserLauncher.openURL(url);
3053 } catch (Exception ex)
3055 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3057 .getString("label.web_browser_not_found_unix"),
3058 MessageManager.getString("label.web_browser_not_found"),
3059 JvOptionPane.WARNING_MESSAGE);
3061 ex.printStackTrace();
3063 if (progress != null)
3065 progress.setProgressBar(null, this.hashCode());
3071 public static WsParamSetManager wsparamManager = null;
3073 public static ParamManager getUserParameterStore()
3075 if (wsparamManager == null)
3077 wsparamManager = new WsParamSetManager();
3079 return wsparamManager;
3083 * static hyperlink handler proxy method for use by Jalview's internal windows
3087 public static void hyperlinkUpdate(HyperlinkEvent e)
3089 if (e.getEventType() == EventType.ACTIVATED)
3094 url = e.getURL().toString();
3095 Desktop.showUrl(url);
3096 } catch (Exception x)
3101 .error("Couldn't handle string " + url + " as a URL.");
3103 // ignore any exceptions due to dud links.
3110 * single thread that handles display of dialogs to user.
3112 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3115 * flag indicating if dialogExecutor should try to acquire a permit
3117 private volatile boolean dialogPause = true;
3122 private Semaphore block = new Semaphore(0);
3124 private static groovy.ui.Console groovyConsole;
3127 * add another dialog thread to the queue
3131 public void addDialogThread(final Runnable prompter)
3133 dialogExecutor.submit(new Runnable()
3140 acquireDialogQueue();
3142 if (instance == null)
3148 SwingUtilities.invokeAndWait(prompter);
3149 } catch (Exception q)
3151 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3158 private boolean dialogQueueStarted = false;
3160 public void startDialogQueue()
3162 if (dialogQueueStarted)
3166 // set the flag so we don't pause waiting for another permit and semaphore
3167 // the current task to begin
3168 releaseDialogQueue();
3169 dialogQueueStarted = true;
3172 public void acquireDialogQueue()
3178 } catch (InterruptedException e)
3180 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3185 public void releaseDialogQueue()
3192 dialogPause = false;
3196 * Outputs an image of the desktop to file in EPS format, after prompting the
3197 * user for choice of Text or Lineart character rendering (unless a preference
3198 * has been set). The file name is generated as
3201 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3205 protected void snapShotWindow_actionPerformed(ActionEvent e)
3207 // currently the menu option to do this is not shown
3210 int width = getWidth();
3211 int height = getHeight();
3213 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3214 ImageWriterI writer = new ImageWriterI()
3217 public void exportImage(Graphics g) throws Exception
3220 jalview.bin.Console.info("Successfully written snapshot to file "
3221 + of.getAbsolutePath());
3224 String title = "View of desktop";
3225 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3229 exporter.doExport(of, this, width, height, title);
3230 } catch (ImageOutputException ioex)
3232 jalview.bin.Console.error(
3233 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3239 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3240 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3241 * and location last time the view was expanded (if any). However it does not
3242 * remember the split pane divider location - this is set to match the
3243 * 'exploding' frame.
3247 public void explodeViews(SplitFrame sf)
3249 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3250 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3251 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3253 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3255 int viewCount = topPanels.size();
3262 * Processing in reverse order works, forwards order leaves the first panels not
3263 * visible. I don't know why!
3265 for (int i = viewCount - 1; i >= 0; i--)
3268 * Make new top and bottom frames. These take over the respective AlignmentPanel
3269 * objects, including their AlignmentViewports, so the cdna/protein
3270 * relationships between the viewports is carried over to the new split frames.
3272 * explodedGeometry holds the (x, y) position of the previously exploded
3273 * SplitFrame, and the (width, height) of the AlignFrame component
3275 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3276 AlignFrame newTopFrame = new AlignFrame(topPanel);
3277 newTopFrame.setSize(oldTopFrame.getSize());
3278 newTopFrame.setVisible(true);
3279 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3280 .getExplodedGeometry();
3281 if (geometry != null)
3283 newTopFrame.setSize(geometry.getSize());
3286 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3287 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3288 newBottomFrame.setSize(oldBottomFrame.getSize());
3289 newBottomFrame.setVisible(true);
3290 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3291 .getExplodedGeometry();
3292 if (geometry != null)
3294 newBottomFrame.setSize(geometry.getSize());
3297 topPanel.av.setGatherViewsHere(false);
3298 bottomPanel.av.setGatherViewsHere(false);
3299 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3301 if (geometry != null)
3303 splitFrame.setLocation(geometry.getLocation());
3305 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3309 * Clear references to the panels (now relocated in the new SplitFrames) before
3310 * closing the old SplitFrame.
3313 bottomPanels.clear();
3318 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3319 * back into the given SplitFrame as additional views. Note that the gathered
3320 * frames may themselves have multiple views.
3324 public void gatherViews(GSplitFrame source)
3327 * special handling of explodedGeometry for a view within a SplitFrame: - it
3328 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3329 * height) of the AlignFrame component
3331 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3332 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3333 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3334 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3335 myBottomFrame.viewport
3336 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3337 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3338 myTopFrame.viewport.setGatherViewsHere(true);
3339 myBottomFrame.viewport.setGatherViewsHere(true);
3340 String topViewId = myTopFrame.viewport.getSequenceSetId();
3341 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3343 JInternalFrame[] frames = desktop.getAllFrames();
3344 for (JInternalFrame frame : frames)
3346 if (frame instanceof SplitFrame && frame != source)
3348 SplitFrame sf = (SplitFrame) frame;
3349 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3350 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3351 boolean gatherThis = false;
3352 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3354 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3355 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3356 if (topViewId.equals(topPanel.av.getSequenceSetId())
3357 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3360 topPanel.av.setGatherViewsHere(false);
3361 bottomPanel.av.setGatherViewsHere(false);
3362 topPanel.av.setExplodedGeometry(
3363 new Rectangle(sf.getLocation(), topFrame.getSize()));
3364 bottomPanel.av.setExplodedGeometry(
3365 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3366 myTopFrame.addAlignmentPanel(topPanel, false);
3367 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3373 topFrame.getAlignPanels().clear();
3374 bottomFrame.getAlignPanels().clear();
3381 * The dust settles...give focus to the tab we did this from.
3383 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3386 public static groovy.ui.Console getGroovyConsole()
3388 return groovyConsole;
3392 * handles the payload of a drag and drop event.
3394 * TODO refactor to desktop utilities class
3397 * - Data source strings extracted from the drop event
3399 * - protocol for each data source extracted from the drop event
3403 * - the payload from the drop event
3406 public static void transferFromDropTarget(List<Object> files,
3407 List<DataSourceType> protocols, DropTargetDropEvent evt,
3408 Transferable t) throws Exception
3411 DataFlavor uriListFlavor = new DataFlavor(
3412 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3415 urlFlavour = new DataFlavor(
3416 "application/x-java-url; class=java.net.URL");
3417 } catch (ClassNotFoundException cfe)
3419 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3423 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3428 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3429 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3430 // means url may be null.
3433 protocols.add(DataSourceType.URL);
3434 files.add(url.toString());
3435 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3436 + files.get(files.size() - 1));
3441 if (Platform.isAMacAndNotJS())
3443 jalview.bin.Console.errPrintln(
3444 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3447 } catch (Throwable ex)
3449 jalview.bin.Console.debug("URL drop handler failed.", ex);
3452 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3454 // Works on Windows and MacOSX
3455 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3456 for (Object file : (List) t
3457 .getTransferData(DataFlavor.javaFileListFlavor))
3460 protocols.add(DataSourceType.FILE);
3465 // Unix like behaviour
3466 boolean added = false;
3468 if (t.isDataFlavorSupported(uriListFlavor))
3470 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3471 // This is used by Unix drag system
3472 data = (String) t.getTransferData(uriListFlavor);
3476 // fallback to text: workaround - on OSX where there's a JVM bug
3478 .debug("standard URIListFlavor failed. Trying text");
3479 // try text fallback
3480 DataFlavor textDf = new DataFlavor(
3481 "text/plain;class=java.lang.String");
3482 if (t.isDataFlavorSupported(textDf))
3484 data = (String) t.getTransferData(textDf);
3487 jalview.bin.Console.debug("Plain text drop content returned "
3488 + (data == null ? "Null - failed" : data));
3493 while (protocols.size() < files.size())
3495 jalview.bin.Console.debug("Adding missing FILE protocol for "
3496 + files.get(protocols.size()));
3497 protocols.add(DataSourceType.FILE);
3499 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3500 data, "\r\n"); st.hasMoreTokens();)
3503 String s = st.nextToken();
3504 if (s.startsWith("#"))
3506 // the line is a comment (as per the RFC 2483)
3509 java.net.URI uri = new java.net.URI(s);
3510 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3512 protocols.add(DataSourceType.URL);
3513 files.add(uri.toString());
3517 // otherwise preserve old behaviour: catch all for file objects
3518 java.io.File file = new java.io.File(uri);
3519 protocols.add(DataSourceType.FILE);
3520 files.add(file.toString());
3525 if (jalview.bin.Console.isDebugEnabled())
3527 if (data == null || !added)
3530 if (t.getTransferDataFlavors() != null
3531 && t.getTransferDataFlavors().length > 0)
3533 jalview.bin.Console.debug(
3534 "Couldn't resolve drop data. Here are the supported flavors:");
3535 for (DataFlavor fl : t.getTransferDataFlavors())
3537 jalview.bin.Console.debug(
3538 "Supported transfer dataflavor: " + fl.toString());
3539 Object df = t.getTransferData(fl);
3542 jalview.bin.Console.debug("Retrieves: " + df);
3546 jalview.bin.Console.debug("Retrieved nothing");
3553 .debug("Couldn't resolve dataflavor for drop: "
3559 if (Platform.isWindowsAndNotJS())
3562 .debug("Scanning dropped content for Windows Link Files");
3564 // resolve any .lnk files in the file drop
3565 for (int f = 0; f < files.size(); f++)
3567 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3568 if (protocols.get(f).equals(DataSourceType.FILE)
3569 && (source.endsWith(".lnk") || source.endsWith(".url")
3570 || source.endsWith(".site")))
3574 Object obj = files.get(f);
3575 File lf = (obj instanceof File ? (File) obj
3576 : new File((String) obj));
3577 // process link file to get a URL
3578 jalview.bin.Console.debug("Found potential link file: " + lf);
3579 WindowsShortcut wscfile = new WindowsShortcut(lf);
3580 String fullname = wscfile.getRealFilename();
3581 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3582 files.set(f, fullname);
3583 jalview.bin.Console.debug("Parsed real filename " + fullname
3584 + " to extract protocol: " + protocols.get(f));
3585 } catch (Exception ex)
3587 jalview.bin.Console.error(
3588 "Couldn't parse " + files.get(f) + " as a link file.",
3597 * Sets the Preferences property for experimental features to True or False
3598 * depending on the state of the controlling menu item
3601 protected void showExperimental_actionPerformed(boolean selected)
3603 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3607 * Answers a (possibly empty) list of any structure viewer frames (currently
3608 * for either Jmol or Chimera) which are currently open. This may optionally
3609 * be restricted to viewers of a specified class, or viewers linked to a
3610 * specified alignment panel.
3613 * if not null, only return viewers linked to this panel
3614 * @param structureViewerClass
3615 * if not null, only return viewers of this class
3618 public List<StructureViewerBase> getStructureViewers(
3619 AlignmentPanel apanel,
3620 Class<? extends StructureViewerBase> structureViewerClass)
3622 List<StructureViewerBase> result = new ArrayList<>();
3623 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3625 for (JInternalFrame frame : frames)
3627 if (frame instanceof StructureViewerBase)
3629 if (structureViewerClass == null
3630 || structureViewerClass.isInstance(frame))
3633 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3635 result.add((StructureViewerBase) frame);
3643 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3645 private static boolean debugScaleMessageDone = false;
3647 public static void debugScaleMessage(Graphics g)
3649 if (debugScaleMessageDone)
3653 // output used by tests to check HiDPI scaling settings in action
3656 Graphics2D gg = (Graphics2D) g;
3659 AffineTransform t = gg.getTransform();
3660 double scaleX = t.getScaleX();
3661 double scaleY = t.getScaleY();
3662 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3663 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3664 debugScaleMessageDone = true;
3668 jalview.bin.Console.debug("Desktop graphics null");
3670 } catch (Exception e)
3672 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3677 * closes the current instance window, disposes and forgets about it.
3679 public static void closeDesktop()
3681 if (Desktop.instance != null)
3683 Desktop.instance.closeAll_actionPerformed(null);
3684 Desktop.instance.setVisible(false);
3685 Desktop us = Desktop.instance;
3686 Desktop.instance = null;
3687 // call dispose in a separate thread - try to avoid indirect deadlocks
3688 new Thread(new Runnable()
3693 ExecutorService dex = us.dialogExecutor;
3697 us.dialogExecutor = null;
3698 us.block.drainPermits();
3707 * checks if any progress bars are being displayed in any of the windows
3708 * managed by the desktop
3712 public boolean operationsAreInProgress()
3714 JInternalFrame[] frames = getAllFrames();
3715 for (JInternalFrame frame : frames)
3717 if (frame instanceof IProgressIndicator)
3719 if (((IProgressIndicator) frame).operationInProgress())
3725 return operationInProgress();
3729 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3730 * The way the modal JInternalFrame is made means it cannot be a child of an
3731 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3733 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3735 protected static void addModal(AlignFrame af, JInternalFrame jif)
3737 alignFrameModalMap.put(af, jif);
3740 protected static void closeModal(AlignFrame af)
3742 if (!alignFrameModalMap.containsKey(af))
3746 JInternalFrame jif = alignFrameModalMap.get(af);
3751 jif.setClosed(true);
3752 } catch (PropertyVetoException e)
3754 e.printStackTrace();
3757 alignFrameModalMap.remove(af);
3760 public void nonBlockingDialog(String title, String message, String button,
3761 int type, boolean scrollable, boolean modal)
3763 nonBlockingDialog(32, 2, title, message, null, button, type, scrollable,
3767 public void nonBlockingDialog(int width, int height, String title,
3768 String message, String boxtext, String button, int type,
3769 boolean scrollable, boolean html, boolean modal)
3773 type = JvOptionPane.WARNING_MESSAGE;
3775 JLabel jl = new JLabel(message);
3777 JTextComponent jtc = null;
3780 JTextPane jtp = new JTextPane();
3781 jtp.setContentType("text/html");
3782 jtp.setEditable(false);
3783 jtp.setAutoscrolls(true);
3784 jtp.setText(boxtext);
3790 JTextArea jta = new JTextArea(height, width);
3791 // jta.setLineWrap(true);
3792 jta.setEditable(false);
3793 jta.setWrapStyleWord(true);
3794 jta.setAutoscrolls(true);
3795 jta.setText(boxtext);
3800 JScrollPane jsp = scrollable
3801 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3802 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3805 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3807 JPanel jp = new JPanel();
3808 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3810 if (message != null)
3812 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3815 if (boxtext != null)
3819 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3824 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3829 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3831 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3833 { button }, button, modal, null, false);