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.jbgui.GSplitFrame;
125 import jalview.jbgui.GStructureViewer;
126 import jalview.project.Jalview2XML;
127 import jalview.structure.StructureSelectionManager;
128 import jalview.urls.IdOrgSettings;
129 import jalview.util.BrowserLauncher;
130 import jalview.util.ChannelProperties;
131 import jalview.util.ImageMaker.TYPE;
132 import jalview.util.LaunchUtils;
133 import jalview.util.MessageManager;
134 import jalview.util.Platform;
135 import jalview.util.ShortcutKeyMaskExWrapper;
136 import jalview.util.UrlConstants;
137 import jalview.viewmodel.AlignmentViewport;
138 import jalview.ws.params.ParamManager;
139 import jalview.ws.utils.UrlDownloadClient;
146 * @version $Revision: 1.155 $
148 public class Desktop extends jalview.jbgui.GDesktop
149 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
150 jalview.api.StructureSelectionManagerProvider
152 private static final String CITATION;
155 URL bg_logo_url = ChannelProperties.getImageURL(
156 "bg_logo." + String.valueOf(SplashScreen.logoSize));
157 URL uod_logo_url = ChannelProperties.getImageURL(
158 "uod_banner." + String.valueOf(SplashScreen.logoSize));
159 boolean logo = (bg_logo_url != null || uod_logo_url != null);
160 StringBuilder sb = new StringBuilder();
162 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
167 sb.append(bg_logo_url == null ? ""
168 : "<img alt=\"Barton Group logo\" src=\""
169 + bg_logo_url.toString() + "\">");
170 sb.append(uod_logo_url == null ? ""
171 : " <img alt=\"University of Dundee shield\" src=\""
172 + uod_logo_url.toString() + "\">");
174 "<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>");
175 sb.append("<br><br>If you use Jalview, please cite:"
176 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
177 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
178 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
179 CITATION = sb.toString();
182 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
184 private static int DEFAULT_MIN_WIDTH = 300;
186 private static int DEFAULT_MIN_HEIGHT = 250;
188 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
190 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
192 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
194 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
196 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
198 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
200 public static void setLiveDragMode(boolean b)
202 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
203 : JDesktopPane.OUTLINE_DRAG_MODE;
205 desktop.setDragMode(DRAG_MODE);
208 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
210 public static boolean nosplash = false;
213 * news reader - null if it was never started.
215 private BlogReader jvnews = null;
217 private File projectFile;
221 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
223 public void addJalviewPropertyChangeListener(
224 PropertyChangeListener listener)
226 changeSupport.addJalviewPropertyChangeListener(listener);
230 * @param propertyName
232 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
233 * java.beans.PropertyChangeListener)
235 public void addJalviewPropertyChangeListener(String propertyName,
236 PropertyChangeListener listener)
238 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
242 * @param propertyName
244 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
245 * java.beans.PropertyChangeListener)
247 public void removeJalviewPropertyChangeListener(String propertyName,
248 PropertyChangeListener listener)
250 changeSupport.removeJalviewPropertyChangeListener(propertyName,
254 /** Singleton Desktop instance */
255 public static Desktop instance;
257 public static MyDesktopPane desktop;
259 public static MyDesktopPane getDesktop()
261 // BH 2018 could use currentThread() here as a reference to a
262 // Hashtable<Thread, MyDesktopPane> in JavaScript
266 static int openFrameCount = 0;
268 static final int xOffset = 30;
270 static final int yOffset = 30;
272 public static jalview.ws.jws1.Discoverer discoverer;
274 public static Object[] jalviewClipboard;
276 public static boolean internalCopy = false;
278 static int fileLoadingCount = 0;
280 class MyDesktopManager implements DesktopManager
283 private DesktopManager delegate;
285 public MyDesktopManager(DesktopManager delegate)
287 this.delegate = delegate;
291 public void activateFrame(JInternalFrame f)
295 delegate.activateFrame(f);
296 } catch (NullPointerException npe)
298 Point p = getMousePosition();
299 instance.showPasteMenu(p.x, p.y);
304 public void beginDraggingFrame(JComponent f)
306 delegate.beginDraggingFrame(f);
310 public void beginResizingFrame(JComponent f, int direction)
312 delegate.beginResizingFrame(f, direction);
316 public void closeFrame(JInternalFrame f)
318 delegate.closeFrame(f);
322 public void deactivateFrame(JInternalFrame f)
324 delegate.deactivateFrame(f);
328 public void deiconifyFrame(JInternalFrame f)
330 delegate.deiconifyFrame(f);
334 public void dragFrame(JComponent f, int newX, int newY)
340 delegate.dragFrame(f, newX, newY);
344 public void endDraggingFrame(JComponent f)
346 delegate.endDraggingFrame(f);
351 public void endResizingFrame(JComponent f)
353 delegate.endResizingFrame(f);
358 public void iconifyFrame(JInternalFrame f)
360 delegate.iconifyFrame(f);
364 public void maximizeFrame(JInternalFrame f)
366 delegate.maximizeFrame(f);
370 public void minimizeFrame(JInternalFrame f)
372 delegate.minimizeFrame(f);
376 public void openFrame(JInternalFrame f)
378 delegate.openFrame(f);
382 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
389 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
393 public void setBoundsForFrame(JComponent f, int newX, int newY,
394 int newWidth, int newHeight)
396 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
399 // All other methods, simply delegate
404 * Creates a new Desktop object.
410 * A note to implementors. It is ESSENTIAL that any activities that might
411 * block are spawned off as threads rather than waited for during this
416 doConfigureStructurePrefs();
417 setTitle(ChannelProperties.getProperty("app_name") + " "
418 + Cache.getProperty("VERSION"));
421 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
422 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
423 * officially documented or guaranteed to exist, so we access it via
424 * reflection. There appear to be unfathomable criteria about what this
425 * string can contain, and it if doesn't meet those criteria then "java"
426 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
427 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
428 * not. The reflection access may generate a warning: WARNING: An illegal
429 * reflective access operation has occurred WARNING: Illegal reflective
430 * access by jalview.gui.Desktop () to field
431 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
433 if (Platform.isLinux())
435 if (LaunchUtils.getJavaVersion() >= 11)
438 * Send this message to stderr as the warning that follows (due to
439 * reflection) also goes to stderr.
442 "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.");
444 final String awtAppClassName = "awtAppClassName";
447 Toolkit xToolkit = Toolkit.getDefaultToolkit();
448 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
449 Field awtAppClassNameField = null;
451 if (Arrays.stream(declaredFields)
452 .anyMatch(f -> f.getName().equals(awtAppClassName)))
454 awtAppClassNameField = xToolkit.getClass()
455 .getDeclaredField(awtAppClassName);
458 String title = ChannelProperties.getProperty("app_name");
459 if (awtAppClassNameField != null)
461 awtAppClassNameField.setAccessible(true);
462 awtAppClassNameField.set(xToolkit, title);
467 .debug("XToolkit: " + awtAppClassName + " not found");
469 } catch (Exception e)
471 jalview.bin.Console.debug("Error setting " + awtAppClassName);
472 jalview.bin.Console.trace(Cache.getStackTraceString(e));
476 setIconImages(ChannelProperties.getIconList());
478 // override quit handling when GUI OS close [X] button pressed
479 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
480 addWindowListener(new WindowAdapter()
483 public void windowClosing(WindowEvent ev)
485 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
489 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
491 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
492 desktop = new MyDesktopPane(selmemusage);
494 showMemusage.setSelected(selmemusage);
495 desktop.setBackground(Color.white);
497 getContentPane().setLayout(new BorderLayout());
498 // alternate config - have scrollbars - see notes in JAL-153
499 // JScrollPane sp = new JScrollPane();
500 // sp.getViewport().setView(desktop);
501 // getContentPane().add(sp, BorderLayout.CENTER);
503 // BH 2018 - just an experiment to try unclipped JInternalFrames.
506 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
509 getContentPane().add(desktop, BorderLayout.CENTER);
510 desktop.setDragMode(DRAG_MODE);
512 // This line prevents Windows Look&Feel resizing all new windows to maximum
513 // if previous window was maximised
514 desktop.setDesktopManager(new MyDesktopManager(
515 Platform.isJS() ? desktop.getDesktopManager()
516 : new DefaultDesktopManager()));
518 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
519 : Platform.isAMacAndNotJS()
520 ? new AquaInternalFrameManager(
521 desktop.getDesktopManager())
522 : desktop.getDesktopManager())));
525 Rectangle dims = getLastKnownDimensions("");
532 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
533 int xPos = Math.max(5, (screenSize.width - 900) / 2);
534 int yPos = Math.max(5, (screenSize.height - 650) / 2);
535 setBounds(xPos, yPos, 900, 650);
538 // start dialogue queue for single dialogues
541 if (!Platform.isJS())
548 jconsole = new Console(this, showjconsole);
549 jconsole.setHeader(Cache.getVersionDetailsForConsole());
550 showConsole(showjconsole);
552 showNews.setVisible(false);
554 experimentalFeatures.setSelected(showExperimental());
556 getIdentifiersOrgData();
560 // Spawn a thread that shows the splashscreen
563 SwingUtilities.invokeLater(new Runnable()
568 new SplashScreen(true);
573 // Thread off a new instance of the file chooser - this reduces the time
575 // takes to open it later on.
576 new Thread(new Runnable()
581 jalview.bin.Console.debug("Filechooser init thread started.");
582 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
583 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
585 jalview.bin.Console.debug("Filechooser init thread finished.");
588 // Add the service change listener
589 changeSupport.addJalviewPropertyChangeListener("services",
590 new PropertyChangeListener()
594 public void propertyChange(PropertyChangeEvent evt)
597 .debug("Firing service changed event for "
598 + evt.getNewValue());
599 JalviewServicesChanged(evt);
604 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
607 this.addMouseListener(ma = new MouseAdapter()
610 public void mousePressed(MouseEvent evt)
612 if (evt.isPopupTrigger()) // Mac
614 showPasteMenu(evt.getX(), evt.getY());
619 public void mouseReleased(MouseEvent evt)
621 if (evt.isPopupTrigger()) // Windows
623 showPasteMenu(evt.getX(), evt.getY());
627 desktop.addMouseListener(ma);
631 * Answers true if user preferences to enable experimental features is True
636 public boolean showExperimental()
638 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
639 Boolean.FALSE.toString());
640 return Boolean.valueOf(experimental).booleanValue();
643 public void doConfigureStructurePrefs()
645 // configure services
646 StructureSelectionManager ssm = StructureSelectionManager
647 .getStructureSelectionManager(this);
648 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
650 ssm.setAddTempFacAnnot(
651 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
652 ssm.setProcessSecondaryStructure(
653 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
654 // JAL-3915 - RNAView is no longer an option so this has no effect
655 ssm.setSecStructServices(
656 Cache.getDefault(Preferences.USE_RNAVIEW, false));
660 ssm.setAddTempFacAnnot(false);
661 ssm.setProcessSecondaryStructure(false);
662 ssm.setSecStructServices(false);
666 public void checkForNews()
668 final Desktop me = this;
669 // Thread off the news reader, in case there are connection problems.
670 new Thread(new Runnable()
675 jalview.bin.Console.debug("Starting news thread.");
676 jvnews = new BlogReader(me);
677 showNews.setVisible(true);
678 jalview.bin.Console.debug("Completed news thread.");
683 public void getIdentifiersOrgData()
685 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
686 {// Thread off the identifiers fetcher
687 new Thread(new Runnable()
693 .debug("Downloading data from identifiers.org");
696 UrlDownloadClient.download(IdOrgSettings.getUrl(),
697 IdOrgSettings.getDownloadLocation());
698 } catch (IOException e)
701 .debug("Exception downloading identifiers.org data"
711 protected void showNews_actionPerformed(ActionEvent e)
713 showNews(showNews.isSelected());
716 void showNews(boolean visible)
718 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
719 showNews.setSelected(visible);
720 if (visible && !jvnews.isVisible())
722 new Thread(new Runnable()
727 long now = System.currentTimeMillis();
728 Desktop.instance.setProgressBar(
729 MessageManager.getString("status.refreshing_news"), now);
730 jvnews.refreshNews();
731 Desktop.instance.setProgressBar(null, now);
739 * recover the last known dimensions for a jalview window
742 * - empty string is desktop, all other windows have unique prefix
743 * @return null or last known dimensions scaled to current geometry (if last
744 * window geom was known)
746 Rectangle getLastKnownDimensions(String windowName)
748 // TODO: lock aspect ratio for scaling desktop Bug #0058199
749 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
750 String x = Cache.getProperty(windowName + "SCREEN_X");
751 String y = Cache.getProperty(windowName + "SCREEN_Y");
752 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
753 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
754 if ((x != null) && (y != null) && (width != null) && (height != null))
756 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
757 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
758 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
760 // attempt #1 - try to cope with change in screen geometry - this
761 // version doesn't preserve original jv aspect ratio.
762 // take ratio of current screen size vs original screen size.
763 double sw = ((1f * screenSize.width) / (1f * Integer
764 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
765 double sh = ((1f * screenSize.height) / (1f * Integer
766 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
767 // rescale the bounds depending upon the current screen geometry.
768 ix = (int) (ix * sw);
769 iw = (int) (iw * sw);
770 iy = (int) (iy * sh);
771 ih = (int) (ih * sh);
772 while (ix >= screenSize.width)
774 jalview.bin.Console.debug(
775 "Window geometry location recall error: shifting horizontal to within screenbounds.");
776 ix -= screenSize.width;
778 while (iy >= screenSize.height)
780 jalview.bin.Console.debug(
781 "Window geometry location recall error: shifting vertical to within screenbounds.");
782 iy -= screenSize.height;
784 jalview.bin.Console.debug(
785 "Got last known dimensions for " + windowName + ": x:" + ix
786 + " y:" + iy + " width:" + iw + " height:" + ih);
788 // return dimensions for new instance
789 return new Rectangle(ix, iy, iw, ih);
794 void showPasteMenu(int x, int y)
796 JPopupMenu popup = new JPopupMenu();
797 JMenuItem item = new JMenuItem(
798 MessageManager.getString("label.paste_new_window"));
799 item.addActionListener(new ActionListener()
802 public void actionPerformed(ActionEvent evt)
809 popup.show(this, x, y);
814 // quick patch for JAL-4150 - needs some more work and test coverage
815 // TODO - unify below and AlignFrame.paste()
816 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
817 // clipboard has come from a different alignment window than the one where
818 // paste has been called! JAL-4151
820 if (Desktop.jalviewClipboard != null)
822 // The clipboard was filled from within Jalview, we must use the
824 // And dataset from the copied alignment
825 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
826 // be doubly sure that we create *new* sequence objects.
827 SequenceI[] sequences = new SequenceI[newseq.length];
828 for (int i = 0; i < newseq.length; i++)
830 sequences[i] = new Sequence(newseq[i]);
832 Alignment alignment = new Alignment(sequences);
833 // dataset is inherited
834 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
835 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
836 AlignFrame.DEFAULT_HEIGHT);
837 String newtitle = new String("Copied sequences");
839 if (Desktop.jalviewClipboard[2] != null)
841 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
842 af.viewport.setHiddenColumns(hc);
845 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
846 AlignFrame.DEFAULT_HEIGHT);
853 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
854 Transferable contents = c.getContents(this);
856 if (contents != null)
858 String file = (String) contents
859 .getTransferData(DataFlavor.stringFlavor);
861 FileFormatI format = new IdentifyFile().identify(file,
862 DataSourceType.PASTE);
864 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
867 } catch (Exception ex)
870 "Unable to paste alignment from system clipboard:\n" + ex);
876 * Adds and opens the given frame to the desktop
887 public static synchronized void addInternalFrame(
888 final JInternalFrame frame, String title, int w, int h)
890 addInternalFrame(frame, title, true, w, h, true, false);
894 * Add an internal frame to the Jalview desktop
901 * When true, display frame immediately, otherwise, caller must call
902 * setVisible themselves.
908 public static synchronized void addInternalFrame(
909 final JInternalFrame frame, String title, boolean makeVisible,
912 addInternalFrame(frame, title, makeVisible, w, h, true, false);
916 * Add an internal frame to the Jalview desktop and make it visible
929 public static synchronized void addInternalFrame(
930 final JInternalFrame frame, String title, int w, int h,
933 addInternalFrame(frame, title, true, w, h, resizable, false);
937 * Add an internal frame to the Jalview desktop
944 * When true, display frame immediately, otherwise, caller must call
945 * setVisible themselves.
952 * @param ignoreMinSize
953 * Do not set the default minimum size for frame
955 public static synchronized void addInternalFrame(
956 final JInternalFrame frame, String title, boolean makeVisible,
957 int w, int h, boolean resizable, boolean ignoreMinSize)
960 // TODO: allow callers to determine X and Y position of frame (eg. via
962 // TODO: consider fixing method to update entries in the window submenu with
963 // the current window title
965 frame.setTitle(title);
966 if (frame.getWidth() < 1 || frame.getHeight() < 1)
970 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
971 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
972 // IF JALVIEW IS RUNNING HEADLESS
973 // ///////////////////////////////////////////////
974 if (instance == null || (System.getProperty("java.awt.headless") != null
975 && System.getProperty("java.awt.headless").equals("true")))
984 frame.setMinimumSize(
985 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
987 // Set default dimension for Alignment Frame window.
988 // The Alignment Frame window could be added from a number of places,
990 // I did this here in order not to miss out on any Alignment frame.
991 if (frame instanceof AlignFrame)
993 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
994 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
998 frame.setVisible(makeVisible);
999 frame.setClosable(true);
1000 frame.setResizable(resizable);
1001 frame.setMaximizable(resizable);
1002 frame.setIconifiable(resizable);
1003 frame.setOpaque(Platform.isJS());
1005 if (frame.getX() < 1 && frame.getY() < 1)
1007 frame.setLocation(xOffset * openFrameCount,
1008 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1012 * add an entry for the new frame in the Window menu (and remove it when the
1015 final JMenuItem menuItem = new JMenuItem(title);
1016 frame.addInternalFrameListener(new InternalFrameAdapter()
1019 public void internalFrameActivated(InternalFrameEvent evt)
1021 JInternalFrame itf = desktop.getSelectedFrame();
1024 if (itf instanceof AlignFrame)
1026 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1033 public void internalFrameClosed(InternalFrameEvent evt)
1035 PaintRefresher.RemoveComponent(frame);
1038 * defensive check to prevent frames being added half off the window
1040 if (openFrameCount > 0)
1046 * ensure no reference to alignFrame retained by menu item listener
1048 if (menuItem.getActionListeners().length > 0)
1050 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1052 windowMenu.remove(menuItem);
1056 menuItem.addActionListener(new ActionListener()
1059 public void actionPerformed(ActionEvent e)
1063 frame.setSelected(true);
1064 frame.setIcon(false);
1065 } catch (java.beans.PropertyVetoException ex)
1072 setKeyBindings(frame);
1076 windowMenu.add(menuItem);
1081 frame.setSelected(true);
1082 frame.requestFocus();
1083 } catch (java.beans.PropertyVetoException ve)
1085 } catch (java.lang.ClassCastException cex)
1087 jalview.bin.Console.warn(
1088 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1094 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1099 private static void setKeyBindings(JInternalFrame frame)
1101 @SuppressWarnings("serial")
1102 final Action closeAction = new AbstractAction()
1105 public void actionPerformed(ActionEvent e)
1112 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1114 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1115 InputEvent.CTRL_DOWN_MASK);
1116 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1117 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1119 InputMap inputMap = frame
1120 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1121 String ctrlW = ctrlWKey.toString();
1122 inputMap.put(ctrlWKey, ctrlW);
1123 inputMap.put(cmdWKey, ctrlW);
1125 ActionMap actionMap = frame.getActionMap();
1126 actionMap.put(ctrlW, closeAction);
1130 public void lostOwnership(Clipboard clipboard, Transferable contents)
1134 Desktop.jalviewClipboard = null;
1137 internalCopy = false;
1141 public void dragEnter(DropTargetDragEvent evt)
1146 public void dragExit(DropTargetEvent evt)
1151 public void dragOver(DropTargetDragEvent evt)
1156 public void dropActionChanged(DropTargetDragEvent evt)
1167 public void drop(DropTargetDropEvent evt)
1169 boolean success = true;
1170 // JAL-1552 - acceptDrop required before getTransferable call for
1171 // Java's Transferable for native dnd
1172 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1173 Transferable t = evt.getTransferable();
1174 List<Object> files = new ArrayList<>();
1175 List<DataSourceType> protocols = new ArrayList<>();
1179 Desktop.transferFromDropTarget(files, protocols, evt, t);
1180 } catch (Exception e)
1182 e.printStackTrace();
1190 for (int i = 0; i < files.size(); i++)
1192 // BH 2018 File or String
1193 Object file = files.get(i);
1194 String fileName = file.toString();
1195 DataSourceType protocol = (protocols == null)
1196 ? DataSourceType.FILE
1198 FileFormatI format = null;
1200 if (fileName.endsWith(".jar"))
1202 format = FileFormat.Jalview;
1207 format = new IdentifyFile().identify(file, protocol);
1209 if (file instanceof File)
1211 Platform.cacheFileData((File) file);
1213 new FileLoader().LoadFile(null, file, protocol, format);
1216 } catch (Exception ex)
1221 evt.dropComplete(success); // need this to ensure input focus is properly
1222 // transfered to any new windows created
1232 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1234 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1235 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1236 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1237 BackupFiles.getEnabled());
1239 chooser.setFileView(new JalviewFileView());
1240 chooser.setDialogTitle(
1241 MessageManager.getString("label.open_local_file"));
1242 chooser.setToolTipText(MessageManager.getString("action.open"));
1244 chooser.setResponseHandler(0, () -> {
1245 File selectedFile = chooser.getSelectedFile();
1246 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1248 FileFormatI format = chooser.getSelectedFormat();
1251 * Call IdentifyFile to verify the file contains what its extension implies.
1252 * Skip this step for dynamically added file formats, because IdentifyFile does
1253 * not know how to recognise them.
1255 if (FileFormats.getInstance().isIdentifiable(format))
1259 format = new IdentifyFile().identify(selectedFile,
1260 DataSourceType.FILE);
1261 } catch (FileFormatException e)
1263 // format = null; //??
1267 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1270 chooser.showOpenDialog(this);
1274 * Shows a dialog for input of a URL at which to retrieve alignment data
1279 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1281 // This construct allows us to have a wider textfield
1283 JLabel label = new JLabel(
1284 MessageManager.getString("label.input_file_url"));
1286 JPanel panel = new JPanel(new GridLayout(2, 1));
1290 * the URL to fetch is input in Java: an editable combobox with history JS:
1291 * (pending JAL-3038) a plain text field
1294 String urlBase = "https://www.";
1295 if (Platform.isJS())
1297 history = new JTextField(urlBase, 35);
1306 JComboBox<String> asCombo = new JComboBox<>();
1307 asCombo.setPreferredSize(new Dimension(400, 20));
1308 asCombo.setEditable(true);
1309 asCombo.addItem(urlBase);
1310 String historyItems = Cache.getProperty("RECENT_URL");
1311 if (historyItems != null)
1313 for (String token : historyItems.split("\\t"))
1315 asCombo.addItem(token);
1322 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1323 MessageManager.getString("action.cancel") };
1324 Runnable action = () -> {
1325 @SuppressWarnings("unchecked")
1326 String url = (history instanceof JTextField
1327 ? ((JTextField) history).getText()
1328 : ((JComboBox<String>) history).getEditor().getItem()
1329 .toString().trim());
1331 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1333 if (viewport != null)
1335 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1336 FileFormat.Jalview);
1340 new FileLoader().LoadFile(url, DataSourceType.URL,
1341 FileFormat.Jalview);
1346 FileFormatI format = null;
1349 format = new IdentifyFile().identify(url, DataSourceType.URL);
1350 } catch (FileFormatException e)
1352 // TODO revise error handling, distinguish between
1353 // URL not found and response not valid
1358 String msg = MessageManager.formatMessage("label.couldnt_locate",
1360 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1361 MessageManager.getString("label.url_not_found"),
1362 JvOptionPane.WARNING_MESSAGE);
1366 if (viewport != null)
1368 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1373 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1377 String dialogOption = MessageManager
1378 .getString("label.input_alignment_from_url");
1379 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1380 .showInternalDialog(panel, dialogOption,
1381 JvOptionPane.YES_NO_CANCEL_OPTION,
1382 JvOptionPane.PLAIN_MESSAGE, null, options,
1383 MessageManager.getString("action.ok"));
1387 * Opens the CutAndPaste window for the user to paste an alignment in to
1390 * - if not null, the pasted alignment is added to the current
1391 * alignment; if null, to a new alignment window
1394 public void inputTextboxMenuItem_actionPerformed(
1395 AlignmentViewPanel viewPanel)
1397 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1398 cap.setForInput(viewPanel);
1399 Desktop.addInternalFrame(cap,
1400 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1405 * Check with user and saving files before actually quitting
1407 public void desktopQuit()
1409 desktopQuit(true, false);
1412 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1414 final Runnable doDesktopQuit = () -> {
1415 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1416 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1417 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1418 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1419 getBounds().y, getWidth(), getHeight()));
1421 if (jconsole != null)
1423 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1424 jconsole.stopConsole();
1429 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1432 // Frames should all close automatically. Keeping external
1433 // viewers open should already be decided by user.
1434 closeAll_actionPerformed(null);
1436 // check for aborted quit
1437 if (QuitHandler.quitCancelled())
1439 jalview.bin.Console.debug("Desktop aborting quit");
1443 if (dialogExecutor != null)
1445 dialogExecutor.shutdownNow();
1448 if (groovyConsole != null)
1450 // suppress a possible repeat prompt to save script
1451 groovyConsole.setDirty(false);
1452 groovyConsole.exit();
1455 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1457 // note that shutdown hook will not be run
1458 jalview.bin.Console.debug("Force Quit selected by user");
1459 Runtime.getRuntime().halt(0);
1462 jalview.bin.Console.debug("Quit selected by user");
1465 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1466 // instance.dispose();
1471 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1472 QuitHandler.defaultCancelQuit);
1476 * Don't call this directly, use desktopQuit() above. Exits the program.
1481 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1482 // not run a second time if gotQuitResponse flag has been set (i.e. user
1483 // confirmed quit of some kind).
1484 Jalview.exit("Desktop exiting.", 0);
1487 private void storeLastKnownDimensions(String string, Rectangle jc)
1489 jalview.bin.Console.debug("Storing last known dimensions for " + string
1490 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1491 + " height:" + jc.height);
1493 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1494 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1495 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1496 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1506 public void aboutMenuItem_actionPerformed(ActionEvent e)
1508 new Thread(new Runnable()
1513 new SplashScreen(false);
1519 * Returns the html text for the About screen, including any available version
1520 * number, build details, author details and citation reference, but without
1521 * the enclosing {@code html} tags
1525 public String getAboutMessage()
1527 StringBuilder message = new StringBuilder(1024);
1528 message.append("<div style=\"font-family: sans-serif;\">")
1529 .append("<h1><strong>Version: ")
1530 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1531 .append("<strong>Built: <em>")
1532 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1533 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1534 .append("</strong>");
1536 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1537 if (latestVersion.equals("Checking"))
1539 // JBP removed this message for 2.11: May be reinstated in future version
1540 // message.append("<br>...Checking latest version...</br>");
1542 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1544 boolean red = false;
1545 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1546 .indexOf("automated build") == -1)
1549 // Displayed when code version and jnlp version do not match and code
1550 // version is not a development build
1551 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1554 message.append("<br>!! Version ")
1555 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1556 .append(" is available for download from ")
1557 .append(Cache.getDefault("www.jalview.org",
1558 "https://www.jalview.org"))
1562 message.append("</div>");
1565 message.append("<br>Authors: ");
1566 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1567 message.append(CITATION);
1569 message.append("</div>");
1571 return message.toString();
1575 * Action on requesting Help documentation
1578 public void documentationMenuItem_actionPerformed()
1582 if (Platform.isJS())
1584 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1593 Help.showHelpWindow();
1595 } catch (Exception ex)
1597 System.err.println("Error opening help: " + ex.getMessage());
1602 public void closeAll_actionPerformed(ActionEvent e)
1604 // TODO show a progress bar while closing?
1605 JInternalFrame[] frames = desktop.getAllFrames();
1606 for (int i = 0; i < frames.length; i++)
1610 frames[i].setClosed(true);
1611 } catch (java.beans.PropertyVetoException ex)
1615 Jalview.setCurrentAlignFrame(null);
1616 jalview.bin.Console.info("ALL CLOSED");
1619 * reset state of singleton objects as appropriate (clear down session state
1620 * when all windows are closed)
1622 StructureSelectionManager ssm = StructureSelectionManager
1623 .getStructureSelectionManager(this);
1630 public int structureViewersStillRunningCount()
1633 JInternalFrame[] frames = desktop.getAllFrames();
1634 for (int i = 0; i < frames.length; i++)
1636 if (frames[i] != null
1637 && frames[i] instanceof JalviewStructureDisplayI)
1639 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1647 public void raiseRelated_actionPerformed(ActionEvent e)
1649 reorderAssociatedWindows(false, false);
1653 public void minimizeAssociated_actionPerformed(ActionEvent e)
1655 reorderAssociatedWindows(true, false);
1658 void closeAssociatedWindows()
1660 reorderAssociatedWindows(false, true);
1666 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1670 protected void garbageCollect_actionPerformed(ActionEvent e)
1672 // We simply collect the garbage
1673 jalview.bin.Console.debug("Collecting garbage...");
1675 jalview.bin.Console.debug("Finished garbage collection.");
1681 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1685 protected void showMemusage_actionPerformed(ActionEvent e)
1687 desktop.showMemoryUsage(showMemusage.isSelected());
1694 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1698 protected void showConsole_actionPerformed(ActionEvent e)
1700 showConsole(showConsole.isSelected());
1703 Console jconsole = null;
1706 * control whether the java console is visible or not
1710 void showConsole(boolean selected)
1712 // TODO: decide if we should update properties file
1713 if (jconsole != null) // BH 2018
1715 showConsole.setSelected(selected);
1716 Cache.setProperty("SHOW_JAVA_CONSOLE",
1717 Boolean.valueOf(selected).toString());
1718 jconsole.setVisible(selected);
1722 void reorderAssociatedWindows(boolean minimize, boolean close)
1724 JInternalFrame[] frames = desktop.getAllFrames();
1725 if (frames == null || frames.length < 1)
1730 AlignmentViewport source = null, target = null;
1731 if (frames[0] instanceof AlignFrame)
1733 source = ((AlignFrame) frames[0]).getCurrentView();
1735 else if (frames[0] instanceof TreePanel)
1737 source = ((TreePanel) frames[0]).getViewPort();
1739 else if (frames[0] instanceof PCAPanel)
1741 source = ((PCAPanel) frames[0]).av;
1743 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1745 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1750 for (int i = 0; i < frames.length; i++)
1753 if (frames[i] == null)
1757 if (frames[i] instanceof AlignFrame)
1759 target = ((AlignFrame) frames[i]).getCurrentView();
1761 else if (frames[i] instanceof TreePanel)
1763 target = ((TreePanel) frames[i]).getViewPort();
1765 else if (frames[i] instanceof PCAPanel)
1767 target = ((PCAPanel) frames[i]).av;
1769 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1771 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1774 if (source == target)
1780 frames[i].setClosed(true);
1784 frames[i].setIcon(minimize);
1787 frames[i].toFront();
1791 } catch (java.beans.PropertyVetoException ex)
1806 protected void preferences_actionPerformed(ActionEvent e)
1808 Preferences.openPreferences();
1812 * Prompts the user to choose a file and then saves the Jalview state as a
1813 * Jalview project file
1816 public void saveState_actionPerformed()
1818 saveState_actionPerformed(false);
1821 public void saveState_actionPerformed(boolean saveAs)
1823 java.io.File projectFile = getProjectFile();
1824 // autoSave indicates we already have a file and don't need to ask
1825 boolean autoSave = projectFile != null && !saveAs
1826 && BackupFiles.getEnabled();
1828 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1829 // saveAs="+saveAs+", Backups
1830 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1832 boolean approveSave = false;
1835 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1838 chooser.setFileView(new JalviewFileView());
1839 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1841 int value = chooser.showSaveDialog(this);
1843 if (value == JalviewFileChooser.APPROVE_OPTION)
1845 projectFile = chooser.getSelectedFile();
1846 setProjectFile(projectFile);
1851 if (approveSave || autoSave)
1853 final Desktop me = this;
1854 final java.io.File chosenFile = projectFile;
1855 new Thread(new Runnable()
1860 // TODO: refactor to Jalview desktop session controller action.
1861 setProgressBar(MessageManager.formatMessage(
1862 "label.saving_jalview_project", new Object[]
1863 { chosenFile.getName() }), chosenFile.hashCode());
1864 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1865 // TODO catch and handle errors for savestate
1866 // TODO prevent user from messing with the Desktop whilst we're saving
1869 boolean doBackup = BackupFiles.getEnabled();
1870 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1873 new Jalview2XML().saveState(
1874 doBackup ? backupfiles.getTempFile() : chosenFile);
1878 backupfiles.setWriteSuccess(true);
1879 backupfiles.rollBackupsAndRenameTempFile();
1881 } catch (OutOfMemoryError oom)
1883 new OOMWarning("Whilst saving current state to "
1884 + chosenFile.getName(), oom);
1885 } catch (Exception ex)
1887 jalview.bin.Console.error("Problems whilst trying to save to "
1888 + chosenFile.getName(), ex);
1889 JvOptionPane.showMessageDialog(me,
1890 MessageManager.formatMessage(
1891 "label.error_whilst_saving_current_state_to",
1893 { chosenFile.getName() }),
1894 MessageManager.getString("label.couldnt_save_project"),
1895 JvOptionPane.WARNING_MESSAGE);
1897 setProgressBar(null, chosenFile.hashCode());
1904 public void saveAsState_actionPerformed(ActionEvent e)
1906 saveState_actionPerformed(true);
1909 protected void setProjectFile(File choice)
1911 this.projectFile = choice;
1914 public File getProjectFile()
1916 return this.projectFile;
1920 * Shows a file chooser dialog and tries to read in the selected file as a
1924 public void loadState_actionPerformed()
1926 final String[] suffix = new String[] { "jvp", "jar" };
1927 final String[] desc = new String[] { "Jalview Project",
1928 "Jalview Project (old)" };
1929 JalviewFileChooser chooser = new JalviewFileChooser(
1930 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1931 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1935 chooser.setFileView(new JalviewFileView());
1936 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1937 chooser.setResponseHandler(0, () -> {
1938 File selectedFile = chooser.getSelectedFile();
1939 setProjectFile(selectedFile);
1940 String choice = selectedFile.getAbsolutePath();
1941 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1942 new Thread(new Runnable()
1949 new Jalview2XML().loadJalviewAlign(selectedFile);
1950 } catch (OutOfMemoryError oom)
1952 new OOMWarning("Whilst loading project from " + choice, oom);
1953 } catch (Exception ex)
1955 jalview.bin.Console.error(
1956 "Problems whilst loading project from " + choice, ex);
1957 JvOptionPane.showMessageDialog(Desktop.desktop,
1958 MessageManager.formatMessage(
1959 "label.error_whilst_loading_project_from",
1962 MessageManager.getString("label.couldnt_load_project"),
1963 JvOptionPane.WARNING_MESSAGE);
1966 }, "Project Loader").start();
1969 chooser.showOpenDialog(this);
1973 public void inputSequence_actionPerformed(ActionEvent e)
1975 new SequenceFetcher(this);
1978 JPanel progressPanel;
1980 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1982 public void startLoading(final Object fileName)
1984 if (fileLoadingCount == 0)
1986 fileLoadingPanels.add(addProgressPanel(MessageManager
1987 .formatMessage("label.loading_file", new Object[]
1993 private JPanel addProgressPanel(String string)
1995 if (progressPanel == null)
1997 progressPanel = new JPanel(new GridLayout(1, 1));
1998 totalProgressCount = 0;
1999 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2001 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2002 JProgressBar progressBar = new JProgressBar();
2003 progressBar.setIndeterminate(true);
2005 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2007 thisprogress.add(progressBar, BorderLayout.CENTER);
2008 progressPanel.add(thisprogress);
2009 ((GridLayout) progressPanel.getLayout()).setRows(
2010 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2011 ++totalProgressCount;
2012 instance.validate();
2013 return thisprogress;
2016 int totalProgressCount = 0;
2018 private void removeProgressPanel(JPanel progbar)
2020 if (progressPanel != null)
2022 synchronized (progressPanel)
2024 progressPanel.remove(progbar);
2025 GridLayout gl = (GridLayout) progressPanel.getLayout();
2026 gl.setRows(gl.getRows() - 1);
2027 if (--totalProgressCount < 1)
2029 this.getContentPane().remove(progressPanel);
2030 progressPanel = null;
2037 public void stopLoading()
2040 if (fileLoadingCount < 1)
2042 while (fileLoadingPanels.size() > 0)
2044 removeProgressPanel(fileLoadingPanels.remove(0));
2046 fileLoadingPanels.clear();
2047 fileLoadingCount = 0;
2052 public static int getViewCount(String alignmentId)
2054 AlignmentViewport[] aps = getViewports(alignmentId);
2055 return (aps == null) ? 0 : aps.length;
2060 * @param alignmentId
2061 * - if null, all sets are returned
2062 * @return all AlignmentPanels concerning the alignmentId sequence set
2064 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2066 if (Desktop.desktop == null)
2068 // no frames created and in headless mode
2069 // TODO: verify that frames are recoverable when in headless mode
2072 List<AlignmentPanel> aps = new ArrayList<>();
2073 AlignFrame[] frames = getAlignFrames();
2078 for (AlignFrame af : frames)
2080 for (AlignmentPanel ap : af.alignPanels)
2082 if (alignmentId == null
2083 || alignmentId.equals(ap.av.getSequenceSetId()))
2089 if (aps.size() == 0)
2093 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2098 * get all the viewports on an alignment.
2100 * @param sequenceSetId
2101 * unique alignment id (may be null - all viewports returned in that
2103 * @return all viewports on the alignment bound to sequenceSetId
2105 public static AlignmentViewport[] getViewports(String sequenceSetId)
2107 List<AlignmentViewport> viewp = new ArrayList<>();
2108 if (desktop != null)
2110 AlignFrame[] frames = Desktop.getAlignFrames();
2112 for (AlignFrame afr : frames)
2114 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2115 .equals(sequenceSetId))
2117 if (afr.alignPanels != null)
2119 for (AlignmentPanel ap : afr.alignPanels)
2121 if (sequenceSetId == null
2122 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2130 viewp.add(afr.getViewport());
2134 if (viewp.size() > 0)
2136 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2143 * Explode the views in the given frame into separate AlignFrame
2147 public static void explodeViews(AlignFrame af)
2149 int size = af.alignPanels.size();
2155 // FIXME: ideally should use UI interface API
2156 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2157 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2158 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2159 for (int i = 0; i < size; i++)
2161 AlignmentPanel ap = af.alignPanels.get(i);
2163 AlignFrame newaf = new AlignFrame(ap);
2165 // transfer reference for existing feature settings to new alignFrame
2166 if (ap == af.alignPanel)
2168 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2170 newaf.featureSettings = viewFeatureSettings;
2172 newaf.setFeatureSettingsGeometry(fsBounds);
2176 * Restore the view's last exploded frame geometry if known. Multiple views from
2177 * one exploded frame share and restore the same (frame) position and size.
2179 Rectangle geometry = ap.av.getExplodedGeometry();
2180 if (geometry != null)
2182 newaf.setBounds(geometry);
2185 ap.av.setGatherViewsHere(false);
2187 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2188 AlignFrame.DEFAULT_HEIGHT);
2189 // and materialise a new feature settings dialog instance for the new
2191 // (closes the old as if 'OK' was pressed)
2192 if (ap == af.alignPanel && newaf.featureSettings != null
2193 && newaf.featureSettings.isOpen()
2194 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2196 newaf.showFeatureSettingsUI();
2200 af.featureSettings = null;
2201 af.alignPanels.clear();
2202 af.closeMenuItem_actionPerformed(true);
2207 * Gather expanded views (separate AlignFrame's) with the same sequence set
2208 * identifier back in to this frame as additional views, and close the
2209 * expanded views. Note the expanded frames may themselves have multiple
2210 * views. We take the lot.
2214 public void gatherViews(AlignFrame source)
2216 source.viewport.setGatherViewsHere(true);
2217 source.viewport.setExplodedGeometry(source.getBounds());
2218 JInternalFrame[] frames = desktop.getAllFrames();
2219 String viewId = source.viewport.getSequenceSetId();
2220 for (int t = 0; t < frames.length; t++)
2222 if (frames[t] instanceof AlignFrame && frames[t] != source)
2224 AlignFrame af = (AlignFrame) frames[t];
2225 boolean gatherThis = false;
2226 for (int a = 0; a < af.alignPanels.size(); a++)
2228 AlignmentPanel ap = af.alignPanels.get(a);
2229 if (viewId.equals(ap.av.getSequenceSetId()))
2232 ap.av.setGatherViewsHere(false);
2233 ap.av.setExplodedGeometry(af.getBounds());
2234 source.addAlignmentPanel(ap, false);
2240 if (af.featureSettings != null && af.featureSettings.isOpen())
2242 if (source.featureSettings == null)
2244 // preserve the feature settings geometry for this frame
2245 source.featureSettings = af.featureSettings;
2246 source.setFeatureSettingsGeometry(
2247 af.getFeatureSettingsGeometry());
2251 // close it and forget
2252 af.featureSettings.close();
2255 af.alignPanels.clear();
2256 af.closeMenuItem_actionPerformed(true);
2261 // refresh the feature setting UI for the source frame if it exists
2262 if (source.featureSettings != null && source.featureSettings.isOpen())
2264 source.showFeatureSettingsUI();
2269 public JInternalFrame[] getAllFrames()
2271 return desktop.getAllFrames();
2275 * Checks the given url to see if it gives a response indicating that the user
2276 * should be informed of a new questionnaire.
2280 public void checkForQuestionnaire(String url)
2282 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2283 // javax.swing.SwingUtilities.invokeLater(jvq);
2284 new Thread(jvq).start();
2287 public void checkURLLinks()
2289 // Thread off the URL link checker
2290 addDialogThread(new Runnable()
2295 if (Cache.getDefault("CHECKURLLINKS", true))
2297 // check what the actual links are - if it's just the default don't
2298 // bother with the warning
2299 List<String> links = Preferences.sequenceUrlLinks
2302 // only need to check links if there is one with a
2303 // SEQUENCE_ID which is not the default EMBL_EBI link
2304 ListIterator<String> li = links.listIterator();
2305 boolean check = false;
2306 List<JLabel> urls = new ArrayList<>();
2307 while (li.hasNext())
2309 String link = li.next();
2310 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2311 && !UrlConstants.isDefaultString(link))
2314 int barPos = link.indexOf("|");
2315 String urlMsg = barPos == -1 ? link
2316 : link.substring(0, barPos) + ": "
2317 + link.substring(barPos + 1);
2318 urls.add(new JLabel(urlMsg));
2326 // ask user to check in case URL links use old style tokens
2327 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2328 JPanel msgPanel = new JPanel();
2329 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2330 msgPanel.add(Box.createVerticalGlue());
2331 JLabel msg = new JLabel(MessageManager
2332 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2333 JLabel msg2 = new JLabel(MessageManager
2334 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2336 for (JLabel url : urls)
2342 final JCheckBox jcb = new JCheckBox(
2343 MessageManager.getString("label.do_not_display_again"));
2344 jcb.addActionListener(new ActionListener()
2347 public void actionPerformed(ActionEvent e)
2349 // update Cache settings for "don't show this again"
2350 boolean showWarningAgain = !jcb.isSelected();
2351 Cache.setProperty("CHECKURLLINKS",
2352 Boolean.valueOf(showWarningAgain).toString());
2357 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2359 .getString("label.SEQUENCE_ID_no_longer_used"),
2360 JvOptionPane.WARNING_MESSAGE);
2367 * Proxy class for JDesktopPane which optionally displays the current memory
2368 * usage and highlights the desktop area with a red bar if free memory runs
2373 public class MyDesktopPane extends JDesktopPane implements Runnable
2375 private static final float ONE_MB = 1048576f;
2377 boolean showMemoryUsage = false;
2381 java.text.NumberFormat df;
2383 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2386 public MyDesktopPane(boolean showMemoryUsage)
2388 showMemoryUsage(showMemoryUsage);
2391 public void showMemoryUsage(boolean showMemory)
2393 this.showMemoryUsage = showMemory;
2396 Thread worker = new Thread(this);
2402 public boolean isShowMemoryUsage()
2404 return showMemoryUsage;
2410 df = java.text.NumberFormat.getNumberInstance();
2411 df.setMaximumFractionDigits(2);
2412 runtime = Runtime.getRuntime();
2414 while (showMemoryUsage)
2418 maxMemory = runtime.maxMemory() / ONE_MB;
2419 allocatedMemory = runtime.totalMemory() / ONE_MB;
2420 freeMemory = runtime.freeMemory() / ONE_MB;
2421 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2423 percentUsage = (totalFreeMemory / maxMemory) * 100;
2425 // if (percentUsage < 20)
2427 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2429 // instance.set.setBorder(border1);
2432 // sleep after showing usage
2434 } catch (Exception ex)
2436 ex.printStackTrace();
2442 public void paintComponent(Graphics g)
2444 if (showMemoryUsage && g != null && df != null)
2446 if (percentUsage < 20)
2448 g.setColor(Color.red);
2450 FontMetrics fm = g.getFontMetrics();
2453 g.drawString(MessageManager.formatMessage("label.memory_stats",
2455 { df.format(totalFreeMemory), df.format(maxMemory),
2456 df.format(percentUsage) }),
2457 10, getHeight() - fm.getHeight());
2461 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2462 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2467 * Accessor method to quickly get all the AlignmentFrames loaded.
2469 * @return an array of AlignFrame, or null if none found
2471 public static AlignFrame[] getAlignFrames()
2473 if (Jalview.isHeadlessMode())
2475 // Desktop.desktop is null in headless mode
2476 return new AlignFrame[] { Jalview.currentAlignFrame };
2479 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2485 List<AlignFrame> avp = new ArrayList<>();
2487 for (int i = frames.length - 1; i > -1; i--)
2489 if (frames[i] instanceof AlignFrame)
2491 avp.add((AlignFrame) frames[i]);
2493 else if (frames[i] instanceof SplitFrame)
2496 * Also check for a split frame containing an AlignFrame
2498 GSplitFrame sf = (GSplitFrame) frames[i];
2499 if (sf.getTopFrame() instanceof AlignFrame)
2501 avp.add((AlignFrame) sf.getTopFrame());
2503 if (sf.getBottomFrame() instanceof AlignFrame)
2505 avp.add((AlignFrame) sf.getBottomFrame());
2509 if (avp.size() == 0)
2513 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2518 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2522 public GStructureViewer[] getJmols()
2524 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2530 List<GStructureViewer> avp = new ArrayList<>();
2532 for (int i = frames.length - 1; i > -1; i--)
2534 if (frames[i] instanceof AppJmol)
2536 GStructureViewer af = (GStructureViewer) frames[i];
2540 if (avp.size() == 0)
2544 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2549 * Add Groovy Support to Jalview
2552 public void groovyShell_actionPerformed()
2556 openGroovyConsole();
2557 } catch (Exception ex)
2559 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2560 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2562 MessageManager.getString("label.couldnt_create_groovy_shell"),
2563 MessageManager.getString("label.groovy_support_failed"),
2564 JvOptionPane.ERROR_MESSAGE);
2569 * Open the Groovy console
2571 void openGroovyConsole()
2573 if (groovyConsole == null)
2575 groovyConsole = new groovy.ui.Console();
2576 groovyConsole.setVariable("Jalview", this);
2577 groovyConsole.run();
2580 * We allow only one console at a time, so that AlignFrame menu option
2581 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2582 * enable 'Run script', when the console is opened, and the reverse when it is
2585 Window window = (Window) groovyConsole.getFrame();
2586 window.addWindowListener(new WindowAdapter()
2589 public void windowClosed(WindowEvent e)
2592 * rebind CMD-Q from Groovy Console to Jalview Quit
2595 enableExecuteGroovy(false);
2601 * show Groovy console window (after close and reopen)
2603 ((Window) groovyConsole.getFrame()).setVisible(true);
2606 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2607 * opening a second console
2609 enableExecuteGroovy(true);
2613 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2614 * binding when opened
2616 protected void addQuitHandler()
2619 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2621 .getKeyStroke(KeyEvent.VK_Q,
2622 jalview.util.ShortcutKeyMaskExWrapper
2623 .getMenuShortcutKeyMaskEx()),
2625 getRootPane().getActionMap().put("Quit", new AbstractAction()
2628 public void actionPerformed(ActionEvent e)
2636 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2639 * true if Groovy console is open
2641 public void enableExecuteGroovy(boolean enabled)
2644 * disable opening a second Groovy console (or re-enable when the console is
2647 groovyShell.setEnabled(!enabled);
2649 AlignFrame[] alignFrames = getAlignFrames();
2650 if (alignFrames != null)
2652 for (AlignFrame af : alignFrames)
2654 af.setGroovyEnabled(enabled);
2660 * Progress bars managed by the IProgressIndicator method.
2662 private Hashtable<Long, JPanel> progressBars;
2664 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2669 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2672 public void setProgressBar(String message, long id)
2674 if (progressBars == null)
2676 progressBars = new Hashtable<>();
2677 progressBarHandlers = new Hashtable<>();
2680 if (progressBars.get(Long.valueOf(id)) != null)
2682 JPanel panel = progressBars.remove(Long.valueOf(id));
2683 if (progressBarHandlers.contains(Long.valueOf(id)))
2685 progressBarHandlers.remove(Long.valueOf(id));
2687 removeProgressPanel(panel);
2691 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2698 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2699 * jalview.gui.IProgressIndicatorHandler)
2702 public void registerHandler(final long id,
2703 final IProgressIndicatorHandler handler)
2705 if (progressBarHandlers == null
2706 || !progressBars.containsKey(Long.valueOf(id)))
2708 throw new Error(MessageManager.getString(
2709 "error.call_setprogressbar_before_registering_handler"));
2711 progressBarHandlers.put(Long.valueOf(id), handler);
2712 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2713 if (handler.canCancel())
2715 JButton cancel = new JButton(
2716 MessageManager.getString("action.cancel"));
2717 final IProgressIndicator us = this;
2718 cancel.addActionListener(new ActionListener()
2722 public void actionPerformed(ActionEvent e)
2724 handler.cancelActivity(id);
2725 us.setProgressBar(MessageManager
2726 .formatMessage("label.cancelled_params", new Object[]
2727 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2731 progressPanel.add(cancel, BorderLayout.EAST);
2737 * @return true if any progress bars are still active
2740 public boolean operationInProgress()
2742 if (progressBars != null && progressBars.size() > 0)
2750 * This will return the first AlignFrame holding the given viewport instance.
2751 * It will break if there are more than one AlignFrames viewing a particular
2755 * @return alignFrame for viewport
2757 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2759 if (desktop != null)
2761 AlignmentPanel[] aps = getAlignmentPanels(
2762 viewport.getSequenceSetId());
2763 for (int panel = 0; aps != null && panel < aps.length; panel++)
2765 if (aps[panel] != null && aps[panel].av == viewport)
2767 return aps[panel].alignFrame;
2774 public VamsasApplication getVamsasApplication()
2776 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2782 * flag set if jalview GUI is being operated programmatically
2784 private boolean inBatchMode = false;
2787 * check if jalview GUI is being operated programmatically
2789 * @return inBatchMode
2791 public boolean isInBatchMode()
2797 * set flag if jalview GUI is being operated programmatically
2799 * @param inBatchMode
2801 public void setInBatchMode(boolean inBatchMode)
2803 this.inBatchMode = inBatchMode;
2807 * start service discovery and wait till it is done
2809 public void startServiceDiscovery()
2811 startServiceDiscovery(false);
2815 * start service discovery threads - blocking or non-blocking
2819 public void startServiceDiscovery(boolean blocking)
2821 startServiceDiscovery(blocking, false);
2825 * start service discovery threads
2828 * - false means call returns immediately
2829 * @param ignore_SHOW_JWS2_SERVICES_preference
2830 * - when true JABA services are discovered regardless of user's JWS2
2831 * discovery preference setting
2833 public void startServiceDiscovery(boolean blocking,
2834 boolean ignore_SHOW_JWS2_SERVICES_preference)
2836 boolean alive = true;
2837 Thread t0 = null, t1 = null, t2 = null;
2838 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2841 // todo: changesupport handlers need to be transferred
2842 if (discoverer == null)
2844 discoverer = new jalview.ws.jws1.Discoverer();
2845 // register PCS handler for desktop.
2846 discoverer.addPropertyChangeListener(changeSupport);
2848 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2849 // until we phase out completely
2850 (t0 = new Thread(discoverer)).start();
2853 if (ignore_SHOW_JWS2_SERVICES_preference
2854 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2856 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2857 .startDiscoverer(changeSupport);
2861 // TODO: do rest service discovery
2870 } catch (Exception e)
2873 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2874 || (t3 != null && t3.isAlive())
2875 || (t0 != null && t0.isAlive());
2881 * called to check if the service discovery process completed successfully.
2885 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2887 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2889 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2890 .getErrorMessages();
2893 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2895 if (serviceChangedDialog == null)
2897 // only run if we aren't already displaying one of these.
2898 addDialogThread(serviceChangedDialog = new Runnable()
2905 * JalviewDialog jd =new JalviewDialog() {
2907 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2909 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2911 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2913 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2915 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2916 * + " or mis-configured HTTP proxy settings.<br/>" +
2917 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2918 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2919 * true, true, "Web Service Configuration Problem", 450, 400);
2921 * jd.waitForInput();
2923 JvOptionPane.showConfirmDialog(Desktop.desktop,
2924 new JLabel("<html><table width=\"450\"><tr><td>"
2925 + ermsg + "</td></tr></table>"
2926 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2927 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2928 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2929 + " Tools->Preferences dialog box to change them.</p></html>"),
2930 "Web Service Configuration Problem",
2931 JvOptionPane.DEFAULT_OPTION,
2932 JvOptionPane.ERROR_MESSAGE);
2933 serviceChangedDialog = null;
2941 jalview.bin.Console.error(
2942 "Errors reported by JABA discovery service. Check web services preferences.\n"
2949 private Runnable serviceChangedDialog = null;
2952 * start a thread to open a URL in the configured browser. Pops up a warning
2953 * dialog to the user if there is an exception when calling out to the browser
2958 public static void showUrl(final String url)
2960 showUrl(url, Desktop.instance);
2964 * Like showUrl but allows progress handler to be specified
2968 * (null) or object implementing IProgressIndicator
2970 public static void showUrl(final String url,
2971 final IProgressIndicator progress)
2973 new Thread(new Runnable()
2980 if (progress != null)
2982 progress.setProgressBar(MessageManager
2983 .formatMessage("status.opening_params", new Object[]
2984 { url }), this.hashCode());
2986 jalview.util.BrowserLauncher.openURL(url);
2987 } catch (Exception ex)
2989 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2991 .getString("label.web_browser_not_found_unix"),
2992 MessageManager.getString("label.web_browser_not_found"),
2993 JvOptionPane.WARNING_MESSAGE);
2995 ex.printStackTrace();
2997 if (progress != null)
2999 progress.setProgressBar(null, this.hashCode());
3005 public static WsParamSetManager wsparamManager = null;
3007 public static ParamManager getUserParameterStore()
3009 if (wsparamManager == null)
3011 wsparamManager = new WsParamSetManager();
3013 return wsparamManager;
3017 * static hyperlink handler proxy method for use by Jalview's internal windows
3021 public static void hyperlinkUpdate(HyperlinkEvent e)
3023 if (e.getEventType() == EventType.ACTIVATED)
3028 url = e.getURL().toString();
3029 Desktop.showUrl(url);
3030 } catch (Exception x)
3035 .error("Couldn't handle string " + url + " as a URL.");
3037 // ignore any exceptions due to dud links.
3044 * single thread that handles display of dialogs to user.
3046 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3049 * flag indicating if dialogExecutor should try to acquire a permit
3051 private volatile boolean dialogPause = true;
3056 private Semaphore block = new Semaphore(0);
3058 private static groovy.ui.Console groovyConsole;
3061 * add another dialog thread to the queue
3065 public void addDialogThread(final Runnable prompter)
3067 dialogExecutor.submit(new Runnable()
3074 acquireDialogQueue();
3076 if (instance == null)
3082 SwingUtilities.invokeAndWait(prompter);
3083 } catch (Exception q)
3085 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3092 private boolean dialogQueueStarted = false;
3094 public void startDialogQueue()
3096 if (dialogQueueStarted)
3100 // set the flag so we don't pause waiting for another permit and semaphore
3101 // the current task to begin
3102 releaseDialogQueue();
3103 dialogQueueStarted = true;
3106 public void acquireDialogQueue()
3112 } catch (InterruptedException e)
3114 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3119 public void releaseDialogQueue()
3126 dialogPause = false;
3130 * Outputs an image of the desktop to file in EPS format, after prompting the
3131 * user for choice of Text or Lineart character rendering (unless a preference
3132 * has been set). The file name is generated as
3135 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3139 protected void snapShotWindow_actionPerformed(ActionEvent e)
3141 // currently the menu option to do this is not shown
3144 int width = getWidth();
3145 int height = getHeight();
3147 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3148 ImageWriterI writer = new ImageWriterI()
3151 public void exportImage(Graphics g) throws Exception
3154 jalview.bin.Console.info("Successfully written snapshot to file "
3155 + of.getAbsolutePath());
3158 String title = "View of desktop";
3159 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3161 exporter.doExport(of, this, width, height, title);
3165 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3166 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3167 * and location last time the view was expanded (if any). However it does not
3168 * remember the split pane divider location - this is set to match the
3169 * 'exploding' frame.
3173 public void explodeViews(SplitFrame sf)
3175 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3176 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3177 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3179 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3181 int viewCount = topPanels.size();
3188 * Processing in reverse order works, forwards order leaves the first panels not
3189 * visible. I don't know why!
3191 for (int i = viewCount - 1; i >= 0; i--)
3194 * Make new top and bottom frames. These take over the respective AlignmentPanel
3195 * objects, including their AlignmentViewports, so the cdna/protein
3196 * relationships between the viewports is carried over to the new split frames.
3198 * explodedGeometry holds the (x, y) position of the previously exploded
3199 * SplitFrame, and the (width, height) of the AlignFrame component
3201 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3202 AlignFrame newTopFrame = new AlignFrame(topPanel);
3203 newTopFrame.setSize(oldTopFrame.getSize());
3204 newTopFrame.setVisible(true);
3205 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3206 .getExplodedGeometry();
3207 if (geometry != null)
3209 newTopFrame.setSize(geometry.getSize());
3212 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3213 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3214 newBottomFrame.setSize(oldBottomFrame.getSize());
3215 newBottomFrame.setVisible(true);
3216 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3217 .getExplodedGeometry();
3218 if (geometry != null)
3220 newBottomFrame.setSize(geometry.getSize());
3223 topPanel.av.setGatherViewsHere(false);
3224 bottomPanel.av.setGatherViewsHere(false);
3225 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3227 if (geometry != null)
3229 splitFrame.setLocation(geometry.getLocation());
3231 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3235 * Clear references to the panels (now relocated in the new SplitFrames) before
3236 * closing the old SplitFrame.
3239 bottomPanels.clear();
3244 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3245 * back into the given SplitFrame as additional views. Note that the gathered
3246 * frames may themselves have multiple views.
3250 public void gatherViews(GSplitFrame source)
3253 * special handling of explodedGeometry for a view within a SplitFrame: - it
3254 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3255 * height) of the AlignFrame component
3257 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3258 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3259 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3260 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3261 myBottomFrame.viewport
3262 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3263 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3264 myTopFrame.viewport.setGatherViewsHere(true);
3265 myBottomFrame.viewport.setGatherViewsHere(true);
3266 String topViewId = myTopFrame.viewport.getSequenceSetId();
3267 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3269 JInternalFrame[] frames = desktop.getAllFrames();
3270 for (JInternalFrame frame : frames)
3272 if (frame instanceof SplitFrame && frame != source)
3274 SplitFrame sf = (SplitFrame) frame;
3275 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3276 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3277 boolean gatherThis = false;
3278 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3280 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3281 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3282 if (topViewId.equals(topPanel.av.getSequenceSetId())
3283 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3286 topPanel.av.setGatherViewsHere(false);
3287 bottomPanel.av.setGatherViewsHere(false);
3288 topPanel.av.setExplodedGeometry(
3289 new Rectangle(sf.getLocation(), topFrame.getSize()));
3290 bottomPanel.av.setExplodedGeometry(
3291 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3292 myTopFrame.addAlignmentPanel(topPanel, false);
3293 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3299 topFrame.getAlignPanels().clear();
3300 bottomFrame.getAlignPanels().clear();
3307 * The dust settles...give focus to the tab we did this from.
3309 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3312 public static groovy.ui.Console getGroovyConsole()
3314 return groovyConsole;
3318 * handles the payload of a drag and drop event.
3320 * TODO refactor to desktop utilities class
3323 * - Data source strings extracted from the drop event
3325 * - protocol for each data source extracted from the drop event
3329 * - the payload from the drop event
3332 public static void transferFromDropTarget(List<Object> files,
3333 List<DataSourceType> protocols, DropTargetDropEvent evt,
3334 Transferable t) throws Exception
3337 DataFlavor uriListFlavor = new DataFlavor(
3338 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3341 urlFlavour = new DataFlavor(
3342 "application/x-java-url; class=java.net.URL");
3343 } catch (ClassNotFoundException cfe)
3345 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3349 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3354 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3355 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3356 // means url may be null.
3359 protocols.add(DataSourceType.URL);
3360 files.add(url.toString());
3361 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3362 + files.get(files.size() - 1));
3367 if (Platform.isAMacAndNotJS())
3370 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3373 } catch (Throwable ex)
3375 jalview.bin.Console.debug("URL drop handler failed.", ex);
3378 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3380 // Works on Windows and MacOSX
3381 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3382 for (Object file : (List) t
3383 .getTransferData(DataFlavor.javaFileListFlavor))
3386 protocols.add(DataSourceType.FILE);
3391 // Unix like behaviour
3392 boolean added = false;
3394 if (t.isDataFlavorSupported(uriListFlavor))
3396 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3397 // This is used by Unix drag system
3398 data = (String) t.getTransferData(uriListFlavor);
3402 // fallback to text: workaround - on OSX where there's a JVM bug
3404 .debug("standard URIListFlavor failed. Trying text");
3405 // try text fallback
3406 DataFlavor textDf = new DataFlavor(
3407 "text/plain;class=java.lang.String");
3408 if (t.isDataFlavorSupported(textDf))
3410 data = (String) t.getTransferData(textDf);
3413 jalview.bin.Console.debug("Plain text drop content returned "
3414 + (data == null ? "Null - failed" : data));
3419 while (protocols.size() < files.size())
3421 jalview.bin.Console.debug("Adding missing FILE protocol for "
3422 + files.get(protocols.size()));
3423 protocols.add(DataSourceType.FILE);
3425 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3426 data, "\r\n"); st.hasMoreTokens();)
3429 String s = st.nextToken();
3430 if (s.startsWith("#"))
3432 // the line is a comment (as per the RFC 2483)
3435 java.net.URI uri = new java.net.URI(s);
3436 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3438 protocols.add(DataSourceType.URL);
3439 files.add(uri.toString());
3443 // otherwise preserve old behaviour: catch all for file objects
3444 java.io.File file = new java.io.File(uri);
3445 protocols.add(DataSourceType.FILE);
3446 files.add(file.toString());
3451 if (jalview.bin.Console.isDebugEnabled())
3453 if (data == null || !added)
3456 if (t.getTransferDataFlavors() != null
3457 && t.getTransferDataFlavors().length > 0)
3459 jalview.bin.Console.debug(
3460 "Couldn't resolve drop data. Here are the supported flavors:");
3461 for (DataFlavor fl : t.getTransferDataFlavors())
3463 jalview.bin.Console.debug(
3464 "Supported transfer dataflavor: " + fl.toString());
3465 Object df = t.getTransferData(fl);
3468 jalview.bin.Console.debug("Retrieves: " + df);
3472 jalview.bin.Console.debug("Retrieved nothing");
3479 .debug("Couldn't resolve dataflavor for drop: "
3485 if (Platform.isWindowsAndNotJS())
3488 .debug("Scanning dropped content for Windows Link Files");
3490 // resolve any .lnk files in the file drop
3491 for (int f = 0; f < files.size(); f++)
3493 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3494 if (protocols.get(f).equals(DataSourceType.FILE)
3495 && (source.endsWith(".lnk") || source.endsWith(".url")
3496 || source.endsWith(".site")))
3500 Object obj = files.get(f);
3501 File lf = (obj instanceof File ? (File) obj
3502 : new File((String) obj));
3503 // process link file to get a URL
3504 jalview.bin.Console.debug("Found potential link file: " + lf);
3505 WindowsShortcut wscfile = new WindowsShortcut(lf);
3506 String fullname = wscfile.getRealFilename();
3507 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3508 files.set(f, fullname);
3509 jalview.bin.Console.debug("Parsed real filename " + fullname
3510 + " to extract protocol: " + protocols.get(f));
3511 } catch (Exception ex)
3513 jalview.bin.Console.error(
3514 "Couldn't parse " + files.get(f) + " as a link file.",
3523 * Sets the Preferences property for experimental features to True or False
3524 * depending on the state of the controlling menu item
3527 protected void showExperimental_actionPerformed(boolean selected)
3529 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3533 * Answers a (possibly empty) list of any structure viewer frames (currently
3534 * for either Jmol or Chimera) which are currently open. This may optionally
3535 * be restricted to viewers of a specified class, or viewers linked to a
3536 * specified alignment panel.
3539 * if not null, only return viewers linked to this panel
3540 * @param structureViewerClass
3541 * if not null, only return viewers of this class
3544 public List<StructureViewerBase> getStructureViewers(
3545 AlignmentPanel apanel,
3546 Class<? extends StructureViewerBase> structureViewerClass)
3548 List<StructureViewerBase> result = new ArrayList<>();
3549 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3551 for (JInternalFrame frame : frames)
3553 if (frame instanceof StructureViewerBase)
3555 if (structureViewerClass == null
3556 || structureViewerClass.isInstance(frame))
3559 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3561 result.add((StructureViewerBase) frame);
3569 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3571 private static boolean debugScaleMessageDone = false;
3573 public static void debugScaleMessage(Graphics g)
3575 if (debugScaleMessageDone)
3579 // output used by tests to check HiDPI scaling settings in action
3582 Graphics2D gg = (Graphics2D) g;
3585 AffineTransform t = gg.getTransform();
3586 double scaleX = t.getScaleX();
3587 double scaleY = t.getScaleY();
3588 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3589 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3590 debugScaleMessageDone = true;
3594 jalview.bin.Console.debug("Desktop graphics null");
3596 } catch (Exception e)
3598 jalview.bin.Console.debug(Cache.getStackTraceString(e));