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.JInternalFrame;
85 import javax.swing.JLabel;
86 import javax.swing.JMenuItem;
87 import javax.swing.JPanel;
88 import javax.swing.JPopupMenu;
89 import javax.swing.JProgressBar;
90 import javax.swing.JTextField;
91 import javax.swing.KeyStroke;
92 import javax.swing.SwingUtilities;
93 import javax.swing.event.HyperlinkEvent;
94 import javax.swing.event.HyperlinkEvent.EventType;
95 import javax.swing.event.InternalFrameAdapter;
96 import javax.swing.event.InternalFrameEvent;
98 import org.stackoverflowusers.file.WindowsShortcut;
100 import jalview.api.AlignViewportI;
101 import jalview.api.AlignmentViewPanel;
102 import jalview.bin.Cache;
103 import jalview.bin.Jalview;
104 import jalview.gui.ImageExporter.ImageWriterI;
105 import jalview.io.BackupFiles;
106 import jalview.io.DataSourceType;
107 import jalview.io.FileFormat;
108 import jalview.io.FileFormatException;
109 import jalview.io.FileFormatI;
110 import jalview.io.FileFormats;
111 import jalview.io.FileLoader;
112 import jalview.io.FormatAdapter;
113 import jalview.io.IdentifyFile;
114 import jalview.io.JalviewFileChooser;
115 import jalview.io.JalviewFileView;
116 import jalview.jbgui.GSplitFrame;
117 import jalview.jbgui.GStructureViewer;
118 import jalview.project.Jalview2XML;
119 import jalview.structure.StructureSelectionManager;
120 import jalview.urls.IdOrgSettings;
121 import jalview.util.BrowserLauncher;
122 import jalview.util.ChannelProperties;
123 import jalview.util.ImageMaker.TYPE;
124 import jalview.util.LaunchUtils;
125 import jalview.util.MessageManager;
126 import jalview.util.Platform;
127 import jalview.util.ShortcutKeyMaskExWrapper;
128 import jalview.util.UrlConstants;
129 import jalview.viewmodel.AlignmentViewport;
130 import jalview.ws.params.ParamManager;
131 import jalview.ws.utils.UrlDownloadClient;
138 * @version $Revision: 1.155 $
140 public class Desktop extends jalview.jbgui.GDesktop
141 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
142 jalview.api.StructureSelectionManagerProvider
144 private static final String CITATION;
147 URL bg_logo_url = ChannelProperties.getImageURL(
148 "bg_logo." + String.valueOf(SplashScreen.logoSize));
149 URL uod_logo_url = ChannelProperties.getImageURL(
150 "uod_banner." + String.valueOf(SplashScreen.logoSize));
151 boolean logo = (bg_logo_url != null || uod_logo_url != null);
152 StringBuilder sb = new StringBuilder();
154 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
159 sb.append(bg_logo_url == null ? ""
160 : "<img alt=\"Barton Group logo\" src=\""
161 + bg_logo_url.toString() + "\">");
162 sb.append(uod_logo_url == null ? ""
163 : " <img alt=\"University of Dundee shield\" src=\""
164 + uod_logo_url.toString() + "\">");
166 "<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>");
167 sb.append("<br><br>If you use Jalview, please cite:"
168 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
169 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
170 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
171 CITATION = sb.toString();
174 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
176 private static int DEFAULT_MIN_WIDTH = 300;
178 private static int DEFAULT_MIN_HEIGHT = 250;
180 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
182 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
184 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
186 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
188 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
190 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
192 public static boolean nosplash = false;
195 * news reader - null if it was never started.
197 private BlogReader jvnews = null;
199 private File projectFile;
203 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
205 public void addJalviewPropertyChangeListener(
206 PropertyChangeListener listener)
208 changeSupport.addJalviewPropertyChangeListener(listener);
212 * @param propertyName
214 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
215 * java.beans.PropertyChangeListener)
217 public void addJalviewPropertyChangeListener(String propertyName,
218 PropertyChangeListener listener)
220 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
224 * @param propertyName
226 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
227 * java.beans.PropertyChangeListener)
229 public void removeJalviewPropertyChangeListener(String propertyName,
230 PropertyChangeListener listener)
232 changeSupport.removeJalviewPropertyChangeListener(propertyName,
236 /** Singleton Desktop instance */
237 public static Desktop instance;
239 public static MyDesktopPane desktop;
241 public static MyDesktopPane getDesktop()
243 // BH 2018 could use currentThread() here as a reference to a
244 // Hashtable<Thread, MyDesktopPane> in JavaScript
248 static int openFrameCount = 0;
250 static final int xOffset = 30;
252 static final int yOffset = 30;
254 public static jalview.ws.jws1.Discoverer discoverer;
256 public static Object[] jalviewClipboard;
258 public static boolean internalCopy = false;
260 static int fileLoadingCount = 0;
262 class MyDesktopManager implements DesktopManager
265 private DesktopManager delegate;
267 public MyDesktopManager(DesktopManager delegate)
269 this.delegate = delegate;
273 public void activateFrame(JInternalFrame f)
277 delegate.activateFrame(f);
278 } catch (NullPointerException npe)
280 Point p = getMousePosition();
281 instance.showPasteMenu(p.x, p.y);
286 public void beginDraggingFrame(JComponent f)
288 delegate.beginDraggingFrame(f);
292 public void beginResizingFrame(JComponent f, int direction)
294 delegate.beginResizingFrame(f, direction);
298 public void closeFrame(JInternalFrame f)
300 delegate.closeFrame(f);
304 public void deactivateFrame(JInternalFrame f)
306 delegate.deactivateFrame(f);
310 public void deiconifyFrame(JInternalFrame f)
312 delegate.deiconifyFrame(f);
316 public void dragFrame(JComponent f, int newX, int newY)
322 delegate.dragFrame(f, newX, newY);
326 public void endDraggingFrame(JComponent f)
328 delegate.endDraggingFrame(f);
333 public void endResizingFrame(JComponent f)
335 delegate.endResizingFrame(f);
340 public void iconifyFrame(JInternalFrame f)
342 delegate.iconifyFrame(f);
346 public void maximizeFrame(JInternalFrame f)
348 delegate.maximizeFrame(f);
352 public void minimizeFrame(JInternalFrame f)
354 delegate.minimizeFrame(f);
358 public void openFrame(JInternalFrame f)
360 delegate.openFrame(f);
364 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
371 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
375 public void setBoundsForFrame(JComponent f, int newX, int newY,
376 int newWidth, int newHeight)
378 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
381 // All other methods, simply delegate
386 * Creates a new Desktop object.
392 * A note to implementors. It is ESSENTIAL that any activities that might
393 * block are spawned off as threads rather than waited for during this
398 doConfigureStructurePrefs();
399 setTitle(ChannelProperties.getProperty("app_name") + " "
400 + Cache.getProperty("VERSION"));
403 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
404 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
405 * officially documented or guaranteed to exist, so we access it via
406 * reflection. There appear to be unfathomable criteria about what this
407 * string can contain, and it if doesn't meet those criteria then "java"
408 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
409 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
410 * not. The reflection access may generate a warning: WARNING: An illegal
411 * reflective access operation has occurred WARNING: Illegal reflective
412 * access by jalview.gui.Desktop () to field
413 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
415 if (Platform.isLinux())
417 if (LaunchUtils.getJavaVersion() >= 11)
419 jalview.bin.Console.info(
420 "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.");
424 Toolkit xToolkit = Toolkit.getDefaultToolkit();
425 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
426 Field awtAppClassNameField = null;
428 if (Arrays.stream(declaredFields)
429 .anyMatch(f -> f.getName().equals("awtAppClassName")))
431 awtAppClassNameField = xToolkit.getClass()
432 .getDeclaredField("awtAppClassName");
435 String title = ChannelProperties.getProperty("app_name");
436 if (awtAppClassNameField != null)
438 awtAppClassNameField.setAccessible(true);
439 awtAppClassNameField.set(xToolkit, title);
443 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
445 } catch (Exception e)
447 jalview.bin.Console.debug("Error setting awtAppClassName");
448 jalview.bin.Console.trace(Cache.getStackTraceString(e));
452 setIconImages(ChannelProperties.getIconList());
454 addWindowListener(new WindowAdapter()
458 public void windowClosing(WindowEvent ev)
464 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
466 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
467 desktop = new MyDesktopPane(selmemusage);
469 showMemusage.setSelected(selmemusage);
470 desktop.setBackground(Color.white);
472 getContentPane().setLayout(new BorderLayout());
473 // alternate config - have scrollbars - see notes in JAL-153
474 // JScrollPane sp = new JScrollPane();
475 // sp.getViewport().setView(desktop);
476 // getContentPane().add(sp, BorderLayout.CENTER);
478 // BH 2018 - just an experiment to try unclipped JInternalFrames.
481 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
484 getContentPane().add(desktop, BorderLayout.CENTER);
485 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
487 // This line prevents Windows Look&Feel resizing all new windows to maximum
488 // if previous window was maximised
489 desktop.setDesktopManager(new MyDesktopManager(
490 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
491 : Platform.isAMacAndNotJS()
492 ? new AquaInternalFrameManager(
493 desktop.getDesktopManager())
494 : desktop.getDesktopManager())));
496 Rectangle dims = getLastKnownDimensions("");
503 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
504 int xPos = Math.max(5, (screenSize.width - 900) / 2);
505 int yPos = Math.max(5, (screenSize.height - 650) / 2);
506 setBounds(xPos, yPos, 900, 650);
509 if (!Platform.isJS())
516 jconsole = new Console(this, showjconsole);
517 jconsole.setHeader(Cache.getVersionDetailsForConsole());
518 showConsole(showjconsole);
520 showNews.setVisible(false);
522 experimentalFeatures.setSelected(showExperimental());
524 getIdentifiersOrgData();
528 // Spawn a thread that shows the splashscreen
531 SwingUtilities.invokeLater(new Runnable()
536 new SplashScreen(true);
541 // Thread off a new instance of the file chooser - this reduces the time
543 // takes to open it later on.
544 new Thread(new Runnable()
549 jalview.bin.Console.debug("Filechooser init thread started.");
550 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
551 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
553 jalview.bin.Console.debug("Filechooser init thread finished.");
556 // Add the service change listener
557 changeSupport.addJalviewPropertyChangeListener("services",
558 new PropertyChangeListener()
562 public void propertyChange(PropertyChangeEvent evt)
565 .debug("Firing service changed event for "
566 + evt.getNewValue());
567 JalviewServicesChanged(evt);
572 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
574 this.addWindowListener(new WindowAdapter()
577 public void windowClosing(WindowEvent evt)
584 this.addMouseListener(ma = new MouseAdapter()
587 public void mousePressed(MouseEvent evt)
589 if (evt.isPopupTrigger()) // Mac
591 showPasteMenu(evt.getX(), evt.getY());
596 public void mouseReleased(MouseEvent evt)
598 if (evt.isPopupTrigger()) // Windows
600 showPasteMenu(evt.getX(), evt.getY());
604 desktop.addMouseListener(ma);
608 * Answers true if user preferences to enable experimental features is True
613 public boolean showExperimental()
615 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
616 Boolean.FALSE.toString());
617 return Boolean.valueOf(experimental).booleanValue();
620 public void doConfigureStructurePrefs()
622 // configure services
623 StructureSelectionManager ssm = StructureSelectionManager
624 .getStructureSelectionManager(this);
625 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
627 ssm.setAddTempFacAnnot(
628 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
629 ssm.setProcessSecondaryStructure(
630 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
631 // JAL-3915 - RNAView is no longer an option so this has no effect
632 ssm.setSecStructServices(
633 Cache.getDefault(Preferences.USE_RNAVIEW, false));
637 ssm.setAddTempFacAnnot(false);
638 ssm.setProcessSecondaryStructure(false);
639 ssm.setSecStructServices(false);
643 public void checkForNews()
645 final Desktop me = this;
646 // Thread off the news reader, in case there are connection problems.
647 new Thread(new Runnable()
652 jalview.bin.Console.debug("Starting news thread.");
653 jvnews = new BlogReader(me);
654 showNews.setVisible(true);
655 jalview.bin.Console.debug("Completed news thread.");
660 public void getIdentifiersOrgData()
662 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
663 {// Thread off the identifiers fetcher
664 new Thread(new Runnable()
670 .debug("Downloading data from identifiers.org");
673 UrlDownloadClient.download(IdOrgSettings.getUrl(),
674 IdOrgSettings.getDownloadLocation());
675 } catch (IOException e)
678 .debug("Exception downloading identifiers.org data"
688 protected void showNews_actionPerformed(ActionEvent e)
690 showNews(showNews.isSelected());
693 void showNews(boolean visible)
695 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
696 showNews.setSelected(visible);
697 if (visible && !jvnews.isVisible())
699 new Thread(new Runnable()
704 long now = System.currentTimeMillis();
705 Desktop.instance.setProgressBar(
706 MessageManager.getString("status.refreshing_news"), now);
707 jvnews.refreshNews();
708 Desktop.instance.setProgressBar(null, now);
716 * recover the last known dimensions for a jalview window
719 * - empty string is desktop, all other windows have unique prefix
720 * @return null or last known dimensions scaled to current geometry (if last
721 * window geom was known)
723 Rectangle getLastKnownDimensions(String windowName)
725 // TODO: lock aspect ratio for scaling desktop Bug #0058199
726 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
727 String x = Cache.getProperty(windowName + "SCREEN_X");
728 String y = Cache.getProperty(windowName + "SCREEN_Y");
729 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
730 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
731 if ((x != null) && (y != null) && (width != null) && (height != null))
733 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
734 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
735 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
737 // attempt #1 - try to cope with change in screen geometry - this
738 // version doesn't preserve original jv aspect ratio.
739 // take ratio of current screen size vs original screen size.
740 double sw = ((1f * screenSize.width) / (1f * Integer
741 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
742 double sh = ((1f * screenSize.height) / (1f * Integer
743 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
744 // rescale the bounds depending upon the current screen geometry.
745 ix = (int) (ix * sw);
746 iw = (int) (iw * sw);
747 iy = (int) (iy * sh);
748 ih = (int) (ih * sh);
749 while (ix >= screenSize.width)
751 jalview.bin.Console.debug(
752 "Window geometry location recall error: shifting horizontal to within screenbounds.");
753 ix -= screenSize.width;
755 while (iy >= screenSize.height)
757 jalview.bin.Console.debug(
758 "Window geometry location recall error: shifting vertical to within screenbounds.");
759 iy -= screenSize.height;
761 jalview.bin.Console.debug(
762 "Got last known dimensions for " + windowName + ": x:" + ix
763 + " y:" + iy + " width:" + iw + " height:" + ih);
765 // return dimensions for new instance
766 return new Rectangle(ix, iy, iw, ih);
771 void showPasteMenu(int x, int y)
773 JPopupMenu popup = new JPopupMenu();
774 JMenuItem item = new JMenuItem(
775 MessageManager.getString("label.paste_new_window"));
776 item.addActionListener(new ActionListener()
779 public void actionPerformed(ActionEvent evt)
786 popup.show(this, x, y);
793 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
794 Transferable contents = c.getContents(this);
796 if (contents != null)
798 String file = (String) contents
799 .getTransferData(DataFlavor.stringFlavor);
801 FileFormatI format = new IdentifyFile().identify(file,
802 DataSourceType.PASTE);
804 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
807 } catch (Exception ex)
810 "Unable to paste alignment from system clipboard:\n" + ex);
815 * Adds and opens the given frame to the desktop
826 public static synchronized void addInternalFrame(
827 final JInternalFrame frame, String title, int w, int h)
829 addInternalFrame(frame, title, true, w, h, true, false);
833 * Add an internal frame to the Jalview desktop
840 * When true, display frame immediately, otherwise, caller must call
841 * setVisible themselves.
847 public static synchronized void addInternalFrame(
848 final JInternalFrame frame, String title, boolean makeVisible,
851 addInternalFrame(frame, title, makeVisible, w, h, true, false);
855 * Add an internal frame to the Jalview desktop and make it visible
868 public static synchronized void addInternalFrame(
869 final JInternalFrame frame, String title, int w, int h,
872 addInternalFrame(frame, title, true, w, h, resizable, false);
876 * Add an internal frame to the Jalview desktop
883 * When true, display frame immediately, otherwise, caller must call
884 * setVisible themselves.
891 * @param ignoreMinSize
892 * Do not set the default minimum size for frame
894 public static synchronized void addInternalFrame(
895 final JInternalFrame frame, String title, boolean makeVisible,
896 int w, int h, boolean resizable, boolean ignoreMinSize)
899 // TODO: allow callers to determine X and Y position of frame (eg. via
901 // TODO: consider fixing method to update entries in the window submenu with
902 // the current window title
904 frame.setTitle(title);
905 if (frame.getWidth() < 1 || frame.getHeight() < 1)
909 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
910 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
911 // IF JALVIEW IS RUNNING HEADLESS
912 // ///////////////////////////////////////////////
913 if (instance == null || (System.getProperty("java.awt.headless") != null
914 && System.getProperty("java.awt.headless").equals("true")))
923 frame.setMinimumSize(
924 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
926 // Set default dimension for Alignment Frame window.
927 // The Alignment Frame window could be added from a number of places,
929 // I did this here in order not to miss out on any Alignment frame.
930 if (frame instanceof AlignFrame)
932 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
933 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
937 frame.setVisible(makeVisible);
938 frame.setClosable(true);
939 frame.setResizable(resizable);
940 frame.setMaximizable(resizable);
941 frame.setIconifiable(resizable);
942 frame.setOpaque(Platform.isJS());
944 if (frame.getX() < 1 && frame.getY() < 1)
946 frame.setLocation(xOffset * openFrameCount,
947 yOffset * ((openFrameCount - 1) % 10) + yOffset);
951 * add an entry for the new frame in the Window menu (and remove it when the
954 final JMenuItem menuItem = new JMenuItem(title);
955 frame.addInternalFrameListener(new InternalFrameAdapter()
958 public void internalFrameActivated(InternalFrameEvent evt)
960 JInternalFrame itf = desktop.getSelectedFrame();
963 if (itf instanceof AlignFrame)
965 Jalview.setCurrentAlignFrame((AlignFrame) itf);
972 public void internalFrameClosed(InternalFrameEvent evt)
974 PaintRefresher.RemoveComponent(frame);
977 * defensive check to prevent frames being added half off the window
979 if (openFrameCount > 0)
985 * ensure no reference to alignFrame retained by menu item listener
987 if (menuItem.getActionListeners().length > 0)
989 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
991 windowMenu.remove(menuItem);
995 menuItem.addActionListener(new ActionListener()
998 public void actionPerformed(ActionEvent e)
1002 frame.setSelected(true);
1003 frame.setIcon(false);
1004 } catch (java.beans.PropertyVetoException ex)
1011 setKeyBindings(frame);
1015 windowMenu.add(menuItem);
1020 frame.setSelected(true);
1021 frame.requestFocus();
1022 } catch (java.beans.PropertyVetoException ve)
1024 } catch (java.lang.ClassCastException cex)
1026 jalview.bin.Console.warn(
1027 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1033 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1038 private static void setKeyBindings(JInternalFrame frame)
1040 @SuppressWarnings("serial")
1041 final Action closeAction = new AbstractAction()
1044 public void actionPerformed(ActionEvent e)
1051 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1053 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1054 InputEvent.CTRL_DOWN_MASK);
1055 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1056 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1058 InputMap inputMap = frame
1059 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1060 String ctrlW = ctrlWKey.toString();
1061 inputMap.put(ctrlWKey, ctrlW);
1062 inputMap.put(cmdWKey, ctrlW);
1064 ActionMap actionMap = frame.getActionMap();
1065 actionMap.put(ctrlW, closeAction);
1069 public void lostOwnership(Clipboard clipboard, Transferable contents)
1073 Desktop.jalviewClipboard = null;
1076 internalCopy = false;
1080 public void dragEnter(DropTargetDragEvent evt)
1085 public void dragExit(DropTargetEvent evt)
1090 public void dragOver(DropTargetDragEvent evt)
1095 public void dropActionChanged(DropTargetDragEvent evt)
1106 public void drop(DropTargetDropEvent evt)
1108 boolean success = true;
1109 // JAL-1552 - acceptDrop required before getTransferable call for
1110 // Java's Transferable for native dnd
1111 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1112 Transferable t = evt.getTransferable();
1113 List<Object> files = new ArrayList<>();
1114 List<DataSourceType> protocols = new ArrayList<>();
1118 Desktop.transferFromDropTarget(files, protocols, evt, t);
1119 } catch (Exception e)
1121 e.printStackTrace();
1129 for (int i = 0; i < files.size(); i++)
1131 // BH 2018 File or String
1132 Object file = files.get(i);
1133 String fileName = file.toString();
1134 DataSourceType protocol = (protocols == null)
1135 ? DataSourceType.FILE
1137 FileFormatI format = null;
1139 if (fileName.endsWith(".jar"))
1141 format = FileFormat.Jalview;
1146 format = new IdentifyFile().identify(file, protocol);
1148 if (file instanceof File)
1150 Platform.cacheFileData((File) file);
1152 new FileLoader().LoadFile(null, file, protocol, format);
1155 } catch (Exception ex)
1160 evt.dropComplete(success); // need this to ensure input focus is properly
1161 // transfered to any new windows created
1171 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1173 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1174 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1175 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1176 BackupFiles.getEnabled());
1178 chooser.setFileView(new JalviewFileView());
1179 chooser.setDialogTitle(
1180 MessageManager.getString("label.open_local_file"));
1181 chooser.setToolTipText(MessageManager.getString("action.open"));
1183 chooser.setResponseHandler(0, new Runnable()
1188 File selectedFile = chooser.getSelectedFile();
1189 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1191 FileFormatI format = chooser.getSelectedFormat();
1194 * Call IdentifyFile to verify the file contains what its extension implies.
1195 * Skip this step for dynamically added file formats, because IdentifyFile does
1196 * not know how to recognise them.
1198 if (FileFormats.getInstance().isIdentifiable(format))
1202 format = new IdentifyFile().identify(selectedFile,
1203 DataSourceType.FILE);
1204 } catch (FileFormatException e)
1206 // format = null; //??
1210 new FileLoader().LoadFile(viewport, selectedFile,
1211 DataSourceType.FILE, format);
1214 chooser.showOpenDialog(this);
1218 * Shows a dialog for input of a URL at which to retrieve alignment data
1223 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1225 // This construct allows us to have a wider textfield
1227 JLabel label = new JLabel(
1228 MessageManager.getString("label.input_file_url"));
1230 JPanel panel = new JPanel(new GridLayout(2, 1));
1234 * the URL to fetch is input in Java: an editable combobox with history JS:
1235 * (pending JAL-3038) a plain text field
1238 String urlBase = "https://www.";
1239 if (Platform.isJS())
1241 history = new JTextField(urlBase, 35);
1250 JComboBox<String> asCombo = new JComboBox<>();
1251 asCombo.setPreferredSize(new Dimension(400, 20));
1252 asCombo.setEditable(true);
1253 asCombo.addItem(urlBase);
1254 String historyItems = Cache.getProperty("RECENT_URL");
1255 if (historyItems != null)
1257 for (String token : historyItems.split("\\t"))
1259 asCombo.addItem(token);
1266 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1267 MessageManager.getString("action.cancel") };
1268 Runnable action = new Runnable()
1273 @SuppressWarnings("unchecked")
1274 String url = (history instanceof JTextField
1275 ? ((JTextField) history).getText()
1276 : ((JComboBox<String>) history).getEditor().getItem()
1277 .toString().trim());
1279 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1281 if (viewport != null)
1283 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1284 FileFormat.Jalview);
1288 new FileLoader().LoadFile(url, DataSourceType.URL,
1289 FileFormat.Jalview);
1294 FileFormatI format = null;
1297 format = new IdentifyFile().identify(url, DataSourceType.URL);
1298 } catch (FileFormatException e)
1300 // TODO revise error handling, distinguish between
1301 // URL not found and response not valid
1306 String msg = MessageManager
1307 .formatMessage("label.couldnt_locate", url);
1308 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1309 MessageManager.getString("label.url_not_found"),
1310 JvOptionPane.WARNING_MESSAGE);
1315 if (viewport != null)
1317 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1322 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1327 String dialogOption = MessageManager
1328 .getString("label.input_alignment_from_url");
1329 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1330 .showInternalDialog(panel, dialogOption,
1331 JvOptionPane.YES_NO_CANCEL_OPTION,
1332 JvOptionPane.PLAIN_MESSAGE, null, options,
1333 MessageManager.getString("action.ok"));
1337 * Opens the CutAndPaste window for the user to paste an alignment in to
1340 * - if not null, the pasted alignment is added to the current
1341 * alignment; if null, to a new alignment window
1344 public void inputTextboxMenuItem_actionPerformed(
1345 AlignmentViewPanel viewPanel)
1347 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1348 cap.setForInput(viewPanel);
1349 Desktop.addInternalFrame(cap,
1350 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1360 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1361 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1362 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1363 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1364 getWidth(), getHeight()));
1366 if (jconsole != null)
1368 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1369 jconsole.stopConsole();
1373 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1376 if (dialogExecutor != null)
1378 dialogExecutor.shutdownNow();
1380 closeAll_actionPerformed(null);
1382 if (groovyConsole != null)
1384 // suppress a possible repeat prompt to save script
1385 groovyConsole.setDirty(false);
1386 groovyConsole.exit();
1391 private void storeLastKnownDimensions(String string, Rectangle jc)
1393 jalview.bin.Console.debug("Storing last known dimensions for " + string
1394 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1395 + " height:" + jc.height);
1397 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1398 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1399 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1400 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1410 public void aboutMenuItem_actionPerformed(ActionEvent e)
1412 new Thread(new Runnable()
1417 new SplashScreen(false);
1423 * Returns the html text for the About screen, including any available version
1424 * number, build details, author details and citation reference, but without
1425 * the enclosing {@code html} tags
1429 public String getAboutMessage()
1431 StringBuilder message = new StringBuilder(1024);
1432 message.append("<div style=\"font-family: sans-serif;\">")
1433 .append("<h1><strong>Version: ")
1434 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1435 .append("<strong>Built: <em>")
1436 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1437 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1438 .append("</strong>");
1440 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1441 if (latestVersion.equals("Checking"))
1443 // JBP removed this message for 2.11: May be reinstated in future version
1444 // message.append("<br>...Checking latest version...</br>");
1446 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1448 boolean red = false;
1449 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1450 .indexOf("automated build") == -1)
1453 // Displayed when code version and jnlp version do not match and code
1454 // version is not a development build
1455 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1458 message.append("<br>!! Version ")
1459 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1460 .append(" is available for download from ")
1461 .append(Cache.getDefault("www.jalview.org",
1462 "https://www.jalview.org"))
1466 message.append("</div>");
1469 message.append("<br>Authors: ");
1470 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1471 message.append(CITATION);
1473 message.append("</div>");
1475 return message.toString();
1479 * Action on requesting Help documentation
1482 public void documentationMenuItem_actionPerformed()
1486 if (Platform.isJS())
1488 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1497 Help.showHelpWindow();
1499 } catch (Exception ex)
1501 System.err.println("Error opening help: " + ex.getMessage());
1506 public void closeAll_actionPerformed(ActionEvent e)
1508 // TODO show a progress bar while closing?
1509 JInternalFrame[] frames = desktop.getAllFrames();
1510 for (int i = 0; i < frames.length; i++)
1514 frames[i].setClosed(true);
1515 } catch (java.beans.PropertyVetoException ex)
1519 Jalview.setCurrentAlignFrame(null);
1520 System.out.println("ALL CLOSED");
1523 * reset state of singleton objects as appropriate (clear down session state
1524 * when all windows are closed)
1526 StructureSelectionManager ssm = StructureSelectionManager
1527 .getStructureSelectionManager(this);
1535 public void raiseRelated_actionPerformed(ActionEvent e)
1537 reorderAssociatedWindows(false, false);
1541 public void minimizeAssociated_actionPerformed(ActionEvent e)
1543 reorderAssociatedWindows(true, false);
1546 void closeAssociatedWindows()
1548 reorderAssociatedWindows(false, true);
1554 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1558 protected void garbageCollect_actionPerformed(ActionEvent e)
1560 // We simply collect the garbage
1561 jalview.bin.Console.debug("Collecting garbage...");
1563 jalview.bin.Console.debug("Finished garbage collection.");
1569 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1573 protected void showMemusage_actionPerformed(ActionEvent e)
1575 desktop.showMemoryUsage(showMemusage.isSelected());
1582 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1586 protected void showConsole_actionPerformed(ActionEvent e)
1588 showConsole(showConsole.isSelected());
1591 Console jconsole = null;
1594 * control whether the java console is visible or not
1598 void showConsole(boolean selected)
1600 // TODO: decide if we should update properties file
1601 if (jconsole != null) // BH 2018
1603 showConsole.setSelected(selected);
1604 Cache.setProperty("SHOW_JAVA_CONSOLE",
1605 Boolean.valueOf(selected).toString());
1606 jconsole.setVisible(selected);
1610 void reorderAssociatedWindows(boolean minimize, boolean close)
1612 JInternalFrame[] frames = desktop.getAllFrames();
1613 if (frames == null || frames.length < 1)
1618 AlignmentViewport source = null, target = null;
1619 if (frames[0] instanceof AlignFrame)
1621 source = ((AlignFrame) frames[0]).getCurrentView();
1623 else if (frames[0] instanceof TreePanel)
1625 source = ((TreePanel) frames[0]).getViewPort();
1627 else if (frames[0] instanceof PCAPanel)
1629 source = ((PCAPanel) frames[0]).av;
1631 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1633 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1638 for (int i = 0; i < frames.length; i++)
1641 if (frames[i] == null)
1645 if (frames[i] instanceof AlignFrame)
1647 target = ((AlignFrame) frames[i]).getCurrentView();
1649 else if (frames[i] instanceof TreePanel)
1651 target = ((TreePanel) frames[i]).getViewPort();
1653 else if (frames[i] instanceof PCAPanel)
1655 target = ((PCAPanel) frames[i]).av;
1657 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1659 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1662 if (source == target)
1668 frames[i].setClosed(true);
1672 frames[i].setIcon(minimize);
1675 frames[i].toFront();
1679 } catch (java.beans.PropertyVetoException ex)
1694 protected void preferences_actionPerformed(ActionEvent e)
1696 Preferences.openPreferences();
1700 * Prompts the user to choose a file and then saves the Jalview state as a
1701 * Jalview project file
1704 public void saveState_actionPerformed()
1706 saveState_actionPerformed(false);
1709 public void saveState_actionPerformed(boolean saveAs)
1711 java.io.File projectFile = getProjectFile();
1712 // autoSave indicates we already have a file and don't need to ask
1713 boolean autoSave = projectFile != null && !saveAs
1714 && BackupFiles.getEnabled();
1716 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1717 // saveAs="+saveAs+", Backups
1718 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1720 boolean approveSave = false;
1723 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1726 chooser.setFileView(new JalviewFileView());
1727 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1729 int value = chooser.showSaveDialog(this);
1731 if (value == JalviewFileChooser.APPROVE_OPTION)
1733 projectFile = chooser.getSelectedFile();
1734 setProjectFile(projectFile);
1739 if (approveSave || autoSave)
1741 final Desktop me = this;
1742 final java.io.File chosenFile = projectFile;
1743 new Thread(new Runnable()
1748 // TODO: refactor to Jalview desktop session controller action.
1749 setProgressBar(MessageManager.formatMessage(
1750 "label.saving_jalview_project", new Object[]
1751 { chosenFile.getName() }), chosenFile.hashCode());
1752 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1753 // TODO catch and handle errors for savestate
1754 // TODO prevent user from messing with the Desktop whilst we're saving
1757 boolean doBackup = BackupFiles.getEnabled();
1758 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1761 new Jalview2XML().saveState(
1762 doBackup ? backupfiles.getTempFile() : chosenFile);
1766 backupfiles.setWriteSuccess(true);
1767 backupfiles.rollBackupsAndRenameTempFile();
1769 } catch (OutOfMemoryError oom)
1771 new OOMWarning("Whilst saving current state to "
1772 + chosenFile.getName(), oom);
1773 } catch (Exception ex)
1775 jalview.bin.Console.error("Problems whilst trying to save to "
1776 + chosenFile.getName(), ex);
1777 JvOptionPane.showMessageDialog(me,
1778 MessageManager.formatMessage(
1779 "label.error_whilst_saving_current_state_to",
1781 { chosenFile.getName() }),
1782 MessageManager.getString("label.couldnt_save_project"),
1783 JvOptionPane.WARNING_MESSAGE);
1785 setProgressBar(null, chosenFile.hashCode());
1792 public void saveAsState_actionPerformed(ActionEvent e)
1794 saveState_actionPerformed(true);
1797 private void setProjectFile(File choice)
1799 this.projectFile = choice;
1802 public File getProjectFile()
1804 return this.projectFile;
1808 * Shows a file chooser dialog and tries to read in the selected file as a
1812 public void loadState_actionPerformed()
1814 final String[] suffix = new String[] { "jvp", "jar" };
1815 final String[] desc = new String[] { "Jalview Project",
1816 "Jalview Project (old)" };
1817 JalviewFileChooser chooser = new JalviewFileChooser(
1818 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1819 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1823 chooser.setFileView(new JalviewFileView());
1824 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1825 chooser.setResponseHandler(0, new Runnable()
1830 File selectedFile = chooser.getSelectedFile();
1831 setProjectFile(selectedFile);
1832 String choice = selectedFile.getAbsolutePath();
1833 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1834 new Thread(new Runnable()
1841 new Jalview2XML().loadJalviewAlign(selectedFile);
1842 } catch (OutOfMemoryError oom)
1844 new OOMWarning("Whilst loading project from " + choice, oom);
1845 } catch (Exception ex)
1847 jalview.bin.Console.error(
1848 "Problems whilst loading project from " + choice, ex);
1849 JvOptionPane.showMessageDialog(Desktop.desktop,
1850 MessageManager.formatMessage(
1851 "label.error_whilst_loading_project_from",
1855 .getString("label.couldnt_load_project"),
1856 JvOptionPane.WARNING_MESSAGE);
1859 }, "Project Loader").start();
1863 chooser.showOpenDialog(this);
1867 public void inputSequence_actionPerformed(ActionEvent e)
1869 new SequenceFetcher(this);
1872 JPanel progressPanel;
1874 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1876 public void startLoading(final Object fileName)
1878 if (fileLoadingCount == 0)
1880 fileLoadingPanels.add(addProgressPanel(MessageManager
1881 .formatMessage("label.loading_file", new Object[]
1887 private JPanel addProgressPanel(String string)
1889 if (progressPanel == null)
1891 progressPanel = new JPanel(new GridLayout(1, 1));
1892 totalProgressCount = 0;
1893 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1895 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1896 JProgressBar progressBar = new JProgressBar();
1897 progressBar.setIndeterminate(true);
1899 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1901 thisprogress.add(progressBar, BorderLayout.CENTER);
1902 progressPanel.add(thisprogress);
1903 ((GridLayout) progressPanel.getLayout()).setRows(
1904 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1905 ++totalProgressCount;
1906 instance.validate();
1907 return thisprogress;
1910 int totalProgressCount = 0;
1912 private void removeProgressPanel(JPanel progbar)
1914 if (progressPanel != null)
1916 synchronized (progressPanel)
1918 progressPanel.remove(progbar);
1919 GridLayout gl = (GridLayout) progressPanel.getLayout();
1920 gl.setRows(gl.getRows() - 1);
1921 if (--totalProgressCount < 1)
1923 this.getContentPane().remove(progressPanel);
1924 progressPanel = null;
1931 public void stopLoading()
1934 if (fileLoadingCount < 1)
1936 while (fileLoadingPanels.size() > 0)
1938 removeProgressPanel(fileLoadingPanels.remove(0));
1940 fileLoadingPanels.clear();
1941 fileLoadingCount = 0;
1946 public static int getViewCount(String alignmentId)
1948 AlignmentViewport[] aps = getViewports(alignmentId);
1949 return (aps == null) ? 0 : aps.length;
1954 * @param alignmentId
1955 * - if null, all sets are returned
1956 * @return all AlignmentPanels concerning the alignmentId sequence set
1958 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1960 if (Desktop.desktop == null)
1962 // no frames created and in headless mode
1963 // TODO: verify that frames are recoverable when in headless mode
1966 List<AlignmentPanel> aps = new ArrayList<>();
1967 AlignFrame[] frames = getAlignFrames();
1972 for (AlignFrame af : frames)
1974 for (AlignmentPanel ap : af.alignPanels)
1976 if (alignmentId == null
1977 || alignmentId.equals(ap.av.getSequenceSetId()))
1983 if (aps.size() == 0)
1987 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1992 * get all the viewports on an alignment.
1994 * @param sequenceSetId
1995 * unique alignment id (may be null - all viewports returned in that
1997 * @return all viewports on the alignment bound to sequenceSetId
1999 public static AlignmentViewport[] getViewports(String sequenceSetId)
2001 List<AlignmentViewport> viewp = new ArrayList<>();
2002 if (desktop != null)
2004 AlignFrame[] frames = Desktop.getAlignFrames();
2006 for (AlignFrame afr : frames)
2008 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2009 .equals(sequenceSetId))
2011 if (afr.alignPanels != null)
2013 for (AlignmentPanel ap : afr.alignPanels)
2015 if (sequenceSetId == null
2016 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2024 viewp.add(afr.getViewport());
2028 if (viewp.size() > 0)
2030 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2037 * Explode the views in the given frame into separate AlignFrame
2041 public static void explodeViews(AlignFrame af)
2043 int size = af.alignPanels.size();
2049 // FIXME: ideally should use UI interface API
2050 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2051 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2052 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2053 for (int i = 0; i < size; i++)
2055 AlignmentPanel ap = af.alignPanels.get(i);
2057 AlignFrame newaf = new AlignFrame(ap);
2059 // transfer reference for existing feature settings to new alignFrame
2060 if (ap == af.alignPanel)
2062 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2064 newaf.featureSettings = viewFeatureSettings;
2066 newaf.setFeatureSettingsGeometry(fsBounds);
2070 * Restore the view's last exploded frame geometry if known. Multiple views from
2071 * one exploded frame share and restore the same (frame) position and size.
2073 Rectangle geometry = ap.av.getExplodedGeometry();
2074 if (geometry != null)
2076 newaf.setBounds(geometry);
2079 ap.av.setGatherViewsHere(false);
2081 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2082 AlignFrame.DEFAULT_HEIGHT);
2083 // and materialise a new feature settings dialog instance for the new
2085 // (closes the old as if 'OK' was pressed)
2086 if (ap == af.alignPanel && newaf.featureSettings != null
2087 && newaf.featureSettings.isOpen()
2088 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2090 newaf.showFeatureSettingsUI();
2094 af.featureSettings = null;
2095 af.alignPanels.clear();
2096 af.closeMenuItem_actionPerformed(true);
2101 * Gather expanded views (separate AlignFrame's) with the same sequence set
2102 * identifier back in to this frame as additional views, and close the
2103 * expanded views. Note the expanded frames may themselves have multiple
2104 * views. We take the lot.
2108 public void gatherViews(AlignFrame source)
2110 source.viewport.setGatherViewsHere(true);
2111 source.viewport.setExplodedGeometry(source.getBounds());
2112 JInternalFrame[] frames = desktop.getAllFrames();
2113 String viewId = source.viewport.getSequenceSetId();
2114 for (int t = 0; t < frames.length; t++)
2116 if (frames[t] instanceof AlignFrame && frames[t] != source)
2118 AlignFrame af = (AlignFrame) frames[t];
2119 boolean gatherThis = false;
2120 for (int a = 0; a < af.alignPanels.size(); a++)
2122 AlignmentPanel ap = af.alignPanels.get(a);
2123 if (viewId.equals(ap.av.getSequenceSetId()))
2126 ap.av.setGatherViewsHere(false);
2127 ap.av.setExplodedGeometry(af.getBounds());
2128 source.addAlignmentPanel(ap, false);
2134 if (af.featureSettings != null && af.featureSettings.isOpen())
2136 if (source.featureSettings == null)
2138 // preserve the feature settings geometry for this frame
2139 source.featureSettings = af.featureSettings;
2140 source.setFeatureSettingsGeometry(
2141 af.getFeatureSettingsGeometry());
2145 // close it and forget
2146 af.featureSettings.close();
2149 af.alignPanels.clear();
2150 af.closeMenuItem_actionPerformed(true);
2155 // refresh the feature setting UI for the source frame if it exists
2156 if (source.featureSettings != null && source.featureSettings.isOpen())
2158 source.showFeatureSettingsUI();
2163 public JInternalFrame[] getAllFrames()
2165 return desktop.getAllFrames();
2169 * Checks the given url to see if it gives a response indicating that the user
2170 * should be informed of a new questionnaire.
2174 public void checkForQuestionnaire(String url)
2176 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2177 // javax.swing.SwingUtilities.invokeLater(jvq);
2178 new Thread(jvq).start();
2181 public void checkURLLinks()
2183 // Thread off the URL link checker
2184 addDialogThread(new Runnable()
2189 if (Cache.getDefault("CHECKURLLINKS", true))
2191 // check what the actual links are - if it's just the default don't
2192 // bother with the warning
2193 List<String> links = Preferences.sequenceUrlLinks
2196 // only need to check links if there is one with a
2197 // SEQUENCE_ID which is not the default EMBL_EBI link
2198 ListIterator<String> li = links.listIterator();
2199 boolean check = false;
2200 List<JLabel> urls = new ArrayList<>();
2201 while (li.hasNext())
2203 String link = li.next();
2204 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2205 && !UrlConstants.isDefaultString(link))
2208 int barPos = link.indexOf("|");
2209 String urlMsg = barPos == -1 ? link
2210 : link.substring(0, barPos) + ": "
2211 + link.substring(barPos + 1);
2212 urls.add(new JLabel(urlMsg));
2220 // ask user to check in case URL links use old style tokens
2221 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2222 JPanel msgPanel = new JPanel();
2223 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2224 msgPanel.add(Box.createVerticalGlue());
2225 JLabel msg = new JLabel(MessageManager
2226 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2227 JLabel msg2 = new JLabel(MessageManager
2228 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2230 for (JLabel url : urls)
2236 final JCheckBox jcb = new JCheckBox(
2237 MessageManager.getString("label.do_not_display_again"));
2238 jcb.addActionListener(new ActionListener()
2241 public void actionPerformed(ActionEvent e)
2243 // update Cache settings for "don't show this again"
2244 boolean showWarningAgain = !jcb.isSelected();
2245 Cache.setProperty("CHECKURLLINKS",
2246 Boolean.valueOf(showWarningAgain).toString());
2251 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2253 .getString("label.SEQUENCE_ID_no_longer_used"),
2254 JvOptionPane.WARNING_MESSAGE);
2261 * Proxy class for JDesktopPane which optionally displays the current memory
2262 * usage and highlights the desktop area with a red bar if free memory runs
2267 public class MyDesktopPane extends JDesktopPane implements Runnable
2269 private static final float ONE_MB = 1048576f;
2271 boolean showMemoryUsage = false;
2275 java.text.NumberFormat df;
2277 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2280 public MyDesktopPane(boolean showMemoryUsage)
2282 showMemoryUsage(showMemoryUsage);
2285 public void showMemoryUsage(boolean showMemory)
2287 this.showMemoryUsage = showMemory;
2290 Thread worker = new Thread(this);
2296 public boolean isShowMemoryUsage()
2298 return showMemoryUsage;
2304 df = java.text.NumberFormat.getNumberInstance();
2305 df.setMaximumFractionDigits(2);
2306 runtime = Runtime.getRuntime();
2308 while (showMemoryUsage)
2312 maxMemory = runtime.maxMemory() / ONE_MB;
2313 allocatedMemory = runtime.totalMemory() / ONE_MB;
2314 freeMemory = runtime.freeMemory() / ONE_MB;
2315 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2317 percentUsage = (totalFreeMemory / maxMemory) * 100;
2319 // if (percentUsage < 20)
2321 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2323 // instance.set.setBorder(border1);
2326 // sleep after showing usage
2328 } catch (Exception ex)
2330 ex.printStackTrace();
2336 public void paintComponent(Graphics g)
2338 if (showMemoryUsage && g != null && df != null)
2340 if (percentUsage < 20)
2342 g.setColor(Color.red);
2344 FontMetrics fm = g.getFontMetrics();
2347 g.drawString(MessageManager.formatMessage("label.memory_stats",
2349 { df.format(totalFreeMemory), df.format(maxMemory),
2350 df.format(percentUsage) }),
2351 10, getHeight() - fm.getHeight());
2355 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2356 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2361 * Accessor method to quickly get all the AlignmentFrames loaded.
2363 * @return an array of AlignFrame, or null if none found
2365 public static AlignFrame[] getAlignFrames()
2367 if (Jalview.isHeadlessMode())
2369 // Desktop.desktop is null in headless mode
2370 return new AlignFrame[] { Jalview.currentAlignFrame };
2373 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2379 List<AlignFrame> avp = new ArrayList<>();
2381 for (int i = frames.length - 1; i > -1; i--)
2383 if (frames[i] instanceof AlignFrame)
2385 avp.add((AlignFrame) frames[i]);
2387 else if (frames[i] instanceof SplitFrame)
2390 * Also check for a split frame containing an AlignFrame
2392 GSplitFrame sf = (GSplitFrame) frames[i];
2393 if (sf.getTopFrame() instanceof AlignFrame)
2395 avp.add((AlignFrame) sf.getTopFrame());
2397 if (sf.getBottomFrame() instanceof AlignFrame)
2399 avp.add((AlignFrame) sf.getBottomFrame());
2403 if (avp.size() == 0)
2407 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2412 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2416 public GStructureViewer[] getJmols()
2418 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2424 List<GStructureViewer> avp = new ArrayList<>();
2426 for (int i = frames.length - 1; i > -1; i--)
2428 if (frames[i] instanceof AppJmol)
2430 GStructureViewer af = (GStructureViewer) frames[i];
2434 if (avp.size() == 0)
2438 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2443 * Add Groovy Support to Jalview
2446 public void groovyShell_actionPerformed()
2450 openGroovyConsole();
2451 } catch (Exception ex)
2453 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2454 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2456 MessageManager.getString("label.couldnt_create_groovy_shell"),
2457 MessageManager.getString("label.groovy_support_failed"),
2458 JvOptionPane.ERROR_MESSAGE);
2463 * Open the Groovy console
2465 void openGroovyConsole()
2467 if (groovyConsole == null)
2469 groovyConsole = new groovy.ui.Console();
2470 groovyConsole.setVariable("Jalview", this);
2471 groovyConsole.run();
2474 * We allow only one console at a time, so that AlignFrame menu option
2475 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2476 * enable 'Run script', when the console is opened, and the reverse when it is
2479 Window window = (Window) groovyConsole.getFrame();
2480 window.addWindowListener(new WindowAdapter()
2483 public void windowClosed(WindowEvent e)
2486 * rebind CMD-Q from Groovy Console to Jalview Quit
2489 enableExecuteGroovy(false);
2495 * show Groovy console window (after close and reopen)
2497 ((Window) groovyConsole.getFrame()).setVisible(true);
2500 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2501 * opening a second console
2503 enableExecuteGroovy(true);
2507 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2508 * binding when opened
2510 protected void addQuitHandler()
2513 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2515 .getKeyStroke(KeyEvent.VK_Q,
2516 jalview.util.ShortcutKeyMaskExWrapper
2517 .getMenuShortcutKeyMaskEx()),
2519 getRootPane().getActionMap().put("Quit", new AbstractAction()
2522 public void actionPerformed(ActionEvent e)
2530 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2533 * true if Groovy console is open
2535 public void enableExecuteGroovy(boolean enabled)
2538 * disable opening a second Groovy console (or re-enable when the console is
2541 groovyShell.setEnabled(!enabled);
2543 AlignFrame[] alignFrames = getAlignFrames();
2544 if (alignFrames != null)
2546 for (AlignFrame af : alignFrames)
2548 af.setGroovyEnabled(enabled);
2554 * Progress bars managed by the IProgressIndicator method.
2556 private Hashtable<Long, JPanel> progressBars;
2558 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2563 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2566 public void setProgressBar(String message, long id)
2568 if (progressBars == null)
2570 progressBars = new Hashtable<>();
2571 progressBarHandlers = new Hashtable<>();
2574 if (progressBars.get(Long.valueOf(id)) != null)
2576 JPanel panel = progressBars.remove(Long.valueOf(id));
2577 if (progressBarHandlers.contains(Long.valueOf(id)))
2579 progressBarHandlers.remove(Long.valueOf(id));
2581 removeProgressPanel(panel);
2585 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2592 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2593 * jalview.gui.IProgressIndicatorHandler)
2596 public void registerHandler(final long id,
2597 final IProgressIndicatorHandler handler)
2599 if (progressBarHandlers == null
2600 || !progressBars.containsKey(Long.valueOf(id)))
2602 throw new Error(MessageManager.getString(
2603 "error.call_setprogressbar_before_registering_handler"));
2605 progressBarHandlers.put(Long.valueOf(id), handler);
2606 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2607 if (handler.canCancel())
2609 JButton cancel = new JButton(
2610 MessageManager.getString("action.cancel"));
2611 final IProgressIndicator us = this;
2612 cancel.addActionListener(new ActionListener()
2616 public void actionPerformed(ActionEvent e)
2618 handler.cancelActivity(id);
2619 us.setProgressBar(MessageManager
2620 .formatMessage("label.cancelled_params", new Object[]
2621 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2625 progressPanel.add(cancel, BorderLayout.EAST);
2631 * @return true if any progress bars are still active
2634 public boolean operationInProgress()
2636 if (progressBars != null && progressBars.size() > 0)
2644 * This will return the first AlignFrame holding the given viewport instance.
2645 * It will break if there are more than one AlignFrames viewing a particular
2649 * @return alignFrame for viewport
2651 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2653 if (desktop != null)
2655 AlignmentPanel[] aps = getAlignmentPanels(
2656 viewport.getSequenceSetId());
2657 for (int panel = 0; aps != null && panel < aps.length; panel++)
2659 if (aps[panel] != null && aps[panel].av == viewport)
2661 return aps[panel].alignFrame;
2668 public VamsasApplication getVamsasApplication()
2670 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2676 * flag set if jalview GUI is being operated programmatically
2678 private boolean inBatchMode = false;
2681 * check if jalview GUI is being operated programmatically
2683 * @return inBatchMode
2685 public boolean isInBatchMode()
2691 * set flag if jalview GUI is being operated programmatically
2693 * @param inBatchMode
2695 public void setInBatchMode(boolean inBatchMode)
2697 this.inBatchMode = inBatchMode;
2701 * start service discovery and wait till it is done
2703 public void startServiceDiscovery()
2705 startServiceDiscovery(false);
2709 * start service discovery threads - blocking or non-blocking
2713 public void startServiceDiscovery(boolean blocking)
2715 startServiceDiscovery(blocking, false);
2719 * start service discovery threads
2722 * - false means call returns immediately
2723 * @param ignore_SHOW_JWS2_SERVICES_preference
2724 * - when true JABA services are discovered regardless of user's JWS2
2725 * discovery preference setting
2727 public void startServiceDiscovery(boolean blocking,
2728 boolean ignore_SHOW_JWS2_SERVICES_preference)
2730 boolean alive = true;
2731 Thread t0 = null, t1 = null, t2 = null;
2732 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2735 // todo: changesupport handlers need to be transferred
2736 if (discoverer == null)
2738 discoverer = new jalview.ws.jws1.Discoverer();
2739 // register PCS handler for desktop.
2740 discoverer.addPropertyChangeListener(changeSupport);
2742 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2743 // until we phase out completely
2744 (t0 = new Thread(discoverer)).start();
2747 if (ignore_SHOW_JWS2_SERVICES_preference
2748 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2750 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2751 .startDiscoverer(changeSupport);
2755 // TODO: do rest service discovery
2764 } catch (Exception e)
2767 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2768 || (t3 != null && t3.isAlive())
2769 || (t0 != null && t0.isAlive());
2775 * called to check if the service discovery process completed successfully.
2779 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2781 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2783 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2784 .getErrorMessages();
2787 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2789 if (serviceChangedDialog == null)
2791 // only run if we aren't already displaying one of these.
2792 addDialogThread(serviceChangedDialog = new Runnable()
2799 * JalviewDialog jd =new JalviewDialog() {
2801 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2803 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2805 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2807 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2809 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2810 * + " or mis-configured HTTP proxy settings.<br/>" +
2811 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2812 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2813 * true, true, "Web Service Configuration Problem", 450, 400);
2815 * jd.waitForInput();
2817 JvOptionPane.showConfirmDialog(Desktop.desktop,
2818 new JLabel("<html><table width=\"450\"><tr><td>"
2819 + ermsg + "</td></tr></table>"
2820 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2821 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2822 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2823 + " Tools->Preferences dialog box to change them.</p></html>"),
2824 "Web Service Configuration Problem",
2825 JvOptionPane.DEFAULT_OPTION,
2826 JvOptionPane.ERROR_MESSAGE);
2827 serviceChangedDialog = null;
2835 jalview.bin.Console.error(
2836 "Errors reported by JABA discovery service. Check web services preferences.\n"
2843 private Runnable serviceChangedDialog = null;
2846 * start a thread to open a URL in the configured browser. Pops up a warning
2847 * dialog to the user if there is an exception when calling out to the browser
2852 public static void showUrl(final String url)
2854 showUrl(url, Desktop.instance);
2858 * Like showUrl but allows progress handler to be specified
2862 * (null) or object implementing IProgressIndicator
2864 public static void showUrl(final String url,
2865 final IProgressIndicator progress)
2867 new Thread(new Runnable()
2874 if (progress != null)
2876 progress.setProgressBar(MessageManager
2877 .formatMessage("status.opening_params", new Object[]
2878 { url }), this.hashCode());
2880 jalview.util.BrowserLauncher.openURL(url);
2881 } catch (Exception ex)
2883 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2885 .getString("label.web_browser_not_found_unix"),
2886 MessageManager.getString("label.web_browser_not_found"),
2887 JvOptionPane.WARNING_MESSAGE);
2889 ex.printStackTrace();
2891 if (progress != null)
2893 progress.setProgressBar(null, this.hashCode());
2899 public static WsParamSetManager wsparamManager = null;
2901 public static ParamManager getUserParameterStore()
2903 if (wsparamManager == null)
2905 wsparamManager = new WsParamSetManager();
2907 return wsparamManager;
2911 * static hyperlink handler proxy method for use by Jalview's internal windows
2915 public static void hyperlinkUpdate(HyperlinkEvent e)
2917 if (e.getEventType() == EventType.ACTIVATED)
2922 url = e.getURL().toString();
2923 Desktop.showUrl(url);
2924 } catch (Exception x)
2929 .error("Couldn't handle string " + url + " as a URL.");
2931 // ignore any exceptions due to dud links.
2938 * single thread that handles display of dialogs to user.
2940 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2943 * flag indicating if dialogExecutor should try to acquire a permit
2945 private volatile boolean dialogPause = true;
2950 private java.util.concurrent.Semaphore block = new Semaphore(0);
2952 private static groovy.ui.Console groovyConsole;
2955 * add another dialog thread to the queue
2959 public void addDialogThread(final Runnable prompter)
2961 dialogExecutor.submit(new Runnable()
2971 } catch (InterruptedException x)
2975 if (instance == null)
2981 SwingUtilities.invokeAndWait(prompter);
2982 } catch (Exception q)
2984 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
2991 public void startDialogQueue()
2993 // set the flag so we don't pause waiting for another permit and semaphore
2994 // the current task to begin
2995 dialogPause = false;
3000 * Outputs an image of the desktop to file in EPS format, after prompting the
3001 * user for choice of Text or Lineart character rendering (unless a preference
3002 * has been set). The file name is generated as
3005 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3009 protected void snapShotWindow_actionPerformed(ActionEvent e)
3011 // currently the menu option to do this is not shown
3014 int width = getWidth();
3015 int height = getHeight();
3017 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3018 ImageWriterI writer = new ImageWriterI()
3021 public void exportImage(Graphics g) throws Exception
3024 jalview.bin.Console.info("Successfully written snapshot to file "
3025 + of.getAbsolutePath());
3028 String title = "View of desktop";
3029 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3031 exporter.doExport(of, this, width, height, title);
3035 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3036 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3037 * and location last time the view was expanded (if any). However it does not
3038 * remember the split pane divider location - this is set to match the
3039 * 'exploding' frame.
3043 public void explodeViews(SplitFrame sf)
3045 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3046 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3047 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3049 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3051 int viewCount = topPanels.size();
3058 * Processing in reverse order works, forwards order leaves the first panels not
3059 * visible. I don't know why!
3061 for (int i = viewCount - 1; i >= 0; i--)
3064 * Make new top and bottom frames. These take over the respective AlignmentPanel
3065 * objects, including their AlignmentViewports, so the cdna/protein
3066 * relationships between the viewports is carried over to the new split frames.
3068 * explodedGeometry holds the (x, y) position of the previously exploded
3069 * SplitFrame, and the (width, height) of the AlignFrame component
3071 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3072 AlignFrame newTopFrame = new AlignFrame(topPanel);
3073 newTopFrame.setSize(oldTopFrame.getSize());
3074 newTopFrame.setVisible(true);
3075 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3076 .getExplodedGeometry();
3077 if (geometry != null)
3079 newTopFrame.setSize(geometry.getSize());
3082 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3083 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3084 newBottomFrame.setSize(oldBottomFrame.getSize());
3085 newBottomFrame.setVisible(true);
3086 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3087 .getExplodedGeometry();
3088 if (geometry != null)
3090 newBottomFrame.setSize(geometry.getSize());
3093 topPanel.av.setGatherViewsHere(false);
3094 bottomPanel.av.setGatherViewsHere(false);
3095 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3097 if (geometry != null)
3099 splitFrame.setLocation(geometry.getLocation());
3101 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3105 * Clear references to the panels (now relocated in the new SplitFrames) before
3106 * closing the old SplitFrame.
3109 bottomPanels.clear();
3114 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3115 * back into the given SplitFrame as additional views. Note that the gathered
3116 * frames may themselves have multiple views.
3120 public void gatherViews(GSplitFrame source)
3123 * special handling of explodedGeometry for a view within a SplitFrame: - it
3124 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3125 * height) of the AlignFrame component
3127 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3128 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3129 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3130 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3131 myBottomFrame.viewport
3132 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3133 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3134 myTopFrame.viewport.setGatherViewsHere(true);
3135 myBottomFrame.viewport.setGatherViewsHere(true);
3136 String topViewId = myTopFrame.viewport.getSequenceSetId();
3137 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3139 JInternalFrame[] frames = desktop.getAllFrames();
3140 for (JInternalFrame frame : frames)
3142 if (frame instanceof SplitFrame && frame != source)
3144 SplitFrame sf = (SplitFrame) frame;
3145 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3146 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3147 boolean gatherThis = false;
3148 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3150 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3151 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3152 if (topViewId.equals(topPanel.av.getSequenceSetId())
3153 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3156 topPanel.av.setGatherViewsHere(false);
3157 bottomPanel.av.setGatherViewsHere(false);
3158 topPanel.av.setExplodedGeometry(
3159 new Rectangle(sf.getLocation(), topFrame.getSize()));
3160 bottomPanel.av.setExplodedGeometry(
3161 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3162 myTopFrame.addAlignmentPanel(topPanel, false);
3163 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3169 topFrame.getAlignPanels().clear();
3170 bottomFrame.getAlignPanels().clear();
3177 * The dust settles...give focus to the tab we did this from.
3179 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3182 public static groovy.ui.Console getGroovyConsole()
3184 return groovyConsole;
3188 * handles the payload of a drag and drop event.
3190 * TODO refactor to desktop utilities class
3193 * - Data source strings extracted from the drop event
3195 * - protocol for each data source extracted from the drop event
3199 * - the payload from the drop event
3202 public static void transferFromDropTarget(List<Object> files,
3203 List<DataSourceType> protocols, DropTargetDropEvent evt,
3204 Transferable t) throws Exception
3207 DataFlavor uriListFlavor = new DataFlavor(
3208 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3211 urlFlavour = new DataFlavor(
3212 "application/x-java-url; class=java.net.URL");
3213 } catch (ClassNotFoundException cfe)
3215 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3219 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3224 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3225 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3226 // means url may be null.
3229 protocols.add(DataSourceType.URL);
3230 files.add(url.toString());
3231 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3232 + files.get(files.size() - 1));
3237 if (Platform.isAMacAndNotJS())
3240 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3243 } catch (Throwable ex)
3245 jalview.bin.Console.debug("URL drop handler failed.", ex);
3248 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3250 // Works on Windows and MacOSX
3251 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3252 for (Object file : (List) t
3253 .getTransferData(DataFlavor.javaFileListFlavor))
3256 protocols.add(DataSourceType.FILE);
3261 // Unix like behaviour
3262 boolean added = false;
3264 if (t.isDataFlavorSupported(uriListFlavor))
3266 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3267 // This is used by Unix drag system
3268 data = (String) t.getTransferData(uriListFlavor);
3272 // fallback to text: workaround - on OSX where there's a JVM bug
3274 .debug("standard URIListFlavor failed. Trying text");
3275 // try text fallback
3276 DataFlavor textDf = new DataFlavor(
3277 "text/plain;class=java.lang.String");
3278 if (t.isDataFlavorSupported(textDf))
3280 data = (String) t.getTransferData(textDf);
3283 jalview.bin.Console.debug("Plain text drop content returned "
3284 + (data == null ? "Null - failed" : data));
3289 while (protocols.size() < files.size())
3291 jalview.bin.Console.debug("Adding missing FILE protocol for "
3292 + files.get(protocols.size()));
3293 protocols.add(DataSourceType.FILE);
3295 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3296 data, "\r\n"); st.hasMoreTokens();)
3299 String s = st.nextToken();
3300 if (s.startsWith("#"))
3302 // the line is a comment (as per the RFC 2483)
3305 java.net.URI uri = new java.net.URI(s);
3306 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3308 protocols.add(DataSourceType.URL);
3309 files.add(uri.toString());
3313 // otherwise preserve old behaviour: catch all for file objects
3314 java.io.File file = new java.io.File(uri);
3315 protocols.add(DataSourceType.FILE);
3316 files.add(file.toString());
3321 if (jalview.bin.Console.isDebugEnabled())
3323 if (data == null || !added)
3326 if (t.getTransferDataFlavors() != null
3327 && t.getTransferDataFlavors().length > 0)
3329 jalview.bin.Console.debug(
3330 "Couldn't resolve drop data. Here are the supported flavors:");
3331 for (DataFlavor fl : t.getTransferDataFlavors())
3333 jalview.bin.Console.debug(
3334 "Supported transfer dataflavor: " + fl.toString());
3335 Object df = t.getTransferData(fl);
3338 jalview.bin.Console.debug("Retrieves: " + df);
3342 jalview.bin.Console.debug("Retrieved nothing");
3349 .debug("Couldn't resolve dataflavor for drop: "
3355 if (Platform.isWindowsAndNotJS())
3358 .debug("Scanning dropped content for Windows Link Files");
3360 // resolve any .lnk files in the file drop
3361 for (int f = 0; f < files.size(); f++)
3363 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3364 if (protocols.get(f).equals(DataSourceType.FILE)
3365 && (source.endsWith(".lnk") || source.endsWith(".url")
3366 || source.endsWith(".site")))
3370 Object obj = files.get(f);
3371 File lf = (obj instanceof File ? (File) obj
3372 : new File((String) obj));
3373 // process link file to get a URL
3374 jalview.bin.Console.debug("Found potential link file: " + lf);
3375 WindowsShortcut wscfile = new WindowsShortcut(lf);
3376 String fullname = wscfile.getRealFilename();
3377 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3378 files.set(f, fullname);
3379 jalview.bin.Console.debug("Parsed real filename " + fullname
3380 + " to extract protocol: " + protocols.get(f));
3381 } catch (Exception ex)
3383 jalview.bin.Console.error(
3384 "Couldn't parse " + files.get(f) + " as a link file.",
3393 * Sets the Preferences property for experimental features to True or False
3394 * depending on the state of the controlling menu item
3397 protected void showExperimental_actionPerformed(boolean selected)
3399 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3403 * Answers a (possibly empty) list of any structure viewer frames (currently
3404 * for either Jmol or Chimera) which are currently open. This may optionally
3405 * be restricted to viewers of a specified class, or viewers linked to a
3406 * specified alignment panel.
3409 * if not null, only return viewers linked to this panel
3410 * @param structureViewerClass
3411 * if not null, only return viewers of this class
3414 public List<StructureViewerBase> getStructureViewers(
3415 AlignmentPanel apanel,
3416 Class<? extends StructureViewerBase> structureViewerClass)
3418 List<StructureViewerBase> result = new ArrayList<>();
3419 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3421 for (JInternalFrame frame : frames)
3423 if (frame instanceof StructureViewerBase)
3425 if (structureViewerClass == null
3426 || structureViewerClass.isInstance(frame))
3429 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3431 result.add((StructureViewerBase) frame);
3439 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3441 private static boolean debugScaleMessageDone = false;
3443 public static void debugScaleMessage(Graphics g)
3445 if (debugScaleMessageDone)
3449 // output used by tests to check HiDPI scaling settings in action
3452 Graphics2D gg = (Graphics2D) g;
3455 AffineTransform t = gg.getTransform();
3456 double scaleX = t.getScaleX();
3457 double scaleY = t.getScaleY();
3458 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3459 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3460 debugScaleMessageDone = true;
3464 jalview.bin.Console.debug("Desktop graphics null");
3466 } catch (Exception e)
3468 jalview.bin.Console.debug(Cache.getStackTraceString(e));