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.FileLoader;
111 import jalview.io.FormatAdapter;
112 import jalview.io.IdentifyFile;
113 import jalview.io.JalviewFileChooser;
114 import jalview.io.JalviewFileView;
115 import jalview.jbgui.GSplitFrame;
116 import jalview.jbgui.GStructureViewer;
117 import jalview.project.Jalview2XML;
118 import jalview.structure.StructureSelectionManager;
119 import jalview.urls.IdOrgSettings;
120 import jalview.util.BrowserLauncher;
121 import jalview.util.ChannelProperties;
122 import jalview.util.ImageMaker.TYPE;
123 import jalview.util.LaunchUtils;
124 import jalview.util.MessageManager;
125 import jalview.util.Platform;
126 import jalview.util.ShortcutKeyMaskExWrapper;
127 import jalview.util.UrlConstants;
128 import jalview.viewmodel.AlignmentViewport;
129 import jalview.ws.params.ParamManager;
130 import jalview.ws.utils.UrlDownloadClient;
137 * @version $Revision: 1.155 $
139 public class Desktop extends jalview.jbgui.GDesktop
140 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
141 jalview.api.StructureSelectionManagerProvider
143 private static final String CITATION;
146 URL bg_logo_url = ChannelProperties.getImageURL(
147 "bg_logo." + String.valueOf(SplashScreen.logoSize));
148 URL uod_logo_url = ChannelProperties.getImageURL(
149 "uod_banner." + String.valueOf(SplashScreen.logoSize));
150 boolean logo = (bg_logo_url != null || uod_logo_url != null);
151 StringBuilder sb = new StringBuilder();
153 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
158 sb.append(bg_logo_url == null ? ""
159 : "<img alt=\"Barton Group logo\" src=\""
160 + bg_logo_url.toString() + "\">");
161 sb.append(uod_logo_url == null ? ""
162 : " <img alt=\"University of Dundee shield\" src=\""
163 + uod_logo_url.toString() + "\">");
165 "<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>");
166 sb.append("<br><br>If you use Jalview, please cite:"
167 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
168 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
169 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
170 CITATION = sb.toString();
173 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
175 private static int DEFAULT_MIN_WIDTH = 300;
177 private static int DEFAULT_MIN_HEIGHT = 250;
179 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
181 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
183 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
185 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
187 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
189 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
191 public static boolean nosplash = false;
194 * news reader - null if it was never started.
196 private BlogReader jvnews = null;
198 private File projectFile;
202 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
204 public void addJalviewPropertyChangeListener(
205 PropertyChangeListener listener)
207 changeSupport.addJalviewPropertyChangeListener(listener);
211 * @param propertyName
213 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
214 * java.beans.PropertyChangeListener)
216 public void addJalviewPropertyChangeListener(String propertyName,
217 PropertyChangeListener listener)
219 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
223 * @param propertyName
225 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
226 * java.beans.PropertyChangeListener)
228 public void removeJalviewPropertyChangeListener(String propertyName,
229 PropertyChangeListener listener)
231 changeSupport.removeJalviewPropertyChangeListener(propertyName,
235 /** Singleton Desktop instance */
236 public static Desktop instance;
238 public static MyDesktopPane desktop;
240 public static MyDesktopPane getDesktop()
242 // BH 2018 could use currentThread() here as a reference to a
243 // Hashtable<Thread, MyDesktopPane> in JavaScript
247 static int openFrameCount = 0;
249 static final int xOffset = 30;
251 static final int yOffset = 30;
253 public static jalview.ws.jws1.Discoverer discoverer;
255 public static Object[] jalviewClipboard;
257 public static boolean internalCopy = false;
259 static int fileLoadingCount = 0;
261 class MyDesktopManager implements DesktopManager
264 private DesktopManager delegate;
266 public MyDesktopManager(DesktopManager delegate)
268 this.delegate = delegate;
272 public void activateFrame(JInternalFrame f)
276 delegate.activateFrame(f);
277 } catch (NullPointerException npe)
279 Point p = getMousePosition();
280 instance.showPasteMenu(p.x, p.y);
285 public void beginDraggingFrame(JComponent f)
287 delegate.beginDraggingFrame(f);
291 public void beginResizingFrame(JComponent f, int direction)
293 delegate.beginResizingFrame(f, direction);
297 public void closeFrame(JInternalFrame f)
299 delegate.closeFrame(f);
303 public void deactivateFrame(JInternalFrame f)
305 delegate.deactivateFrame(f);
309 public void deiconifyFrame(JInternalFrame f)
311 delegate.deiconifyFrame(f);
315 public void dragFrame(JComponent f, int newX, int newY)
321 delegate.dragFrame(f, newX, newY);
325 public void endDraggingFrame(JComponent f)
327 delegate.endDraggingFrame(f);
332 public void endResizingFrame(JComponent f)
334 delegate.endResizingFrame(f);
339 public void iconifyFrame(JInternalFrame f)
341 delegate.iconifyFrame(f);
345 public void maximizeFrame(JInternalFrame f)
347 delegate.maximizeFrame(f);
351 public void minimizeFrame(JInternalFrame f)
353 delegate.minimizeFrame(f);
357 public void openFrame(JInternalFrame f)
359 delegate.openFrame(f);
363 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
370 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
374 public void setBoundsForFrame(JComponent f, int newX, int newY,
375 int newWidth, int newHeight)
377 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
380 // All other methods, simply delegate
385 * Creates a new Desktop object.
391 * A note to implementors. It is ESSENTIAL that any activities that might
392 * block are spawned off as threads rather than waited for during this
397 doConfigureStructurePrefs();
398 setTitle(ChannelProperties.getProperty("app_name") + " "
399 + Cache.getProperty("VERSION"));
402 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
403 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
404 * officially documented or guaranteed to exist, so we access it via
405 * reflection. There appear to be unfathomable criteria about what this
406 * string can contain, and it if doesn't meet those criteria then "java"
407 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
408 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
409 * not. The reflection access may generate a warning: WARNING: An illegal
410 * reflective access operation has occurred WARNING: Illegal reflective
411 * access by jalview.gui.Desktop () to field
412 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
414 if (Platform.isLinux())
416 if (LaunchUtils.getJavaVersion() >= 11)
418 jalview.bin.Console.info(
419 "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.");
423 Toolkit xToolkit = Toolkit.getDefaultToolkit();
424 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
425 Field awtAppClassNameField = null;
427 if (Arrays.stream(declaredFields)
428 .anyMatch(f -> f.getName().equals("awtAppClassName")))
430 awtAppClassNameField = xToolkit.getClass()
431 .getDeclaredField("awtAppClassName");
434 String title = ChannelProperties.getProperty("app_name");
435 if (awtAppClassNameField != null)
437 awtAppClassNameField.setAccessible(true);
438 awtAppClassNameField.set(xToolkit, title);
442 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
444 } catch (Exception e)
446 jalview.bin.Console.debug("Error setting awtAppClassName");
447 jalview.bin.Console.trace(Cache.getStackTraceString(e));
451 setIconImages(ChannelProperties.getIconList());
453 addWindowListener(new WindowAdapter()
457 public void windowClosing(WindowEvent ev)
463 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
465 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
466 desktop = new MyDesktopPane(selmemusage);
468 showMemusage.setSelected(selmemusage);
469 desktop.setBackground(Color.white);
471 getContentPane().setLayout(new BorderLayout());
472 // alternate config - have scrollbars - see notes in JAL-153
473 // JScrollPane sp = new JScrollPane();
474 // sp.getViewport().setView(desktop);
475 // getContentPane().add(sp, BorderLayout.CENTER);
477 // BH 2018 - just an experiment to try unclipped JInternalFrames.
480 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
483 getContentPane().add(desktop, BorderLayout.CENTER);
484 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
486 // This line prevents Windows Look&Feel resizing all new windows to maximum
487 // if previous window was maximised
488 desktop.setDesktopManager(new MyDesktopManager(
489 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
490 : Platform.isAMacAndNotJS()
491 ? new AquaInternalFrameManager(
492 desktop.getDesktopManager())
493 : desktop.getDesktopManager())));
495 Rectangle dims = getLastKnownDimensions("");
502 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
503 int xPos = Math.max(5, (screenSize.width - 900) / 2);
504 int yPos = Math.max(5, (screenSize.height - 650) / 2);
505 setBounds(xPos, yPos, 900, 650);
508 if (!Platform.isJS())
515 jconsole = new Console(this, showjconsole);
516 jconsole.setHeader(Cache.getVersionDetailsForConsole());
517 showConsole(showjconsole);
519 showNews.setVisible(false);
521 experimentalFeatures.setSelected(showExperimental());
523 getIdentifiersOrgData();
527 // Spawn a thread that shows the splashscreen
530 SwingUtilities.invokeLater(new Runnable()
535 new SplashScreen(true);
540 // Thread off a new instance of the file chooser - this reduces the time
542 // takes to open it later on.
543 new Thread(new Runnable()
548 jalview.bin.Console.debug("Filechooser init thread started.");
549 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
550 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
552 jalview.bin.Console.debug("Filechooser init thread finished.");
555 // Add the service change listener
556 changeSupport.addJalviewPropertyChangeListener("services",
557 new PropertyChangeListener()
561 public void propertyChange(PropertyChangeEvent evt)
564 .debug("Firing service changed event for "
565 + evt.getNewValue());
566 JalviewServicesChanged(evt);
571 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
573 this.addWindowListener(new WindowAdapter()
576 public void windowClosing(WindowEvent evt)
583 this.addMouseListener(ma = new MouseAdapter()
586 public void mousePressed(MouseEvent evt)
588 if (evt.isPopupTrigger()) // Mac
590 showPasteMenu(evt.getX(), evt.getY());
595 public void mouseReleased(MouseEvent evt)
597 if (evt.isPopupTrigger()) // Windows
599 showPasteMenu(evt.getX(), evt.getY());
603 desktop.addMouseListener(ma);
607 * Answers true if user preferences to enable experimental features is True
612 public boolean showExperimental()
614 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
615 Boolean.FALSE.toString());
616 return Boolean.valueOf(experimental).booleanValue();
619 public void doConfigureStructurePrefs()
621 // configure services
622 StructureSelectionManager ssm = StructureSelectionManager
623 .getStructureSelectionManager(this);
624 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
626 ssm.setAddTempFacAnnot(
627 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
628 ssm.setProcessSecondaryStructure(
629 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
630 // JAL-3915 - RNAView is no longer an option so this has no effect
631 ssm.setSecStructServices(
632 Cache.getDefault(Preferences.USE_RNAVIEW, false));
636 ssm.setAddTempFacAnnot(false);
637 ssm.setProcessSecondaryStructure(false);
638 ssm.setSecStructServices(false);
642 public void checkForNews()
644 final Desktop me = this;
645 // Thread off the news reader, in case there are connection problems.
646 new Thread(new Runnable()
651 jalview.bin.Console.debug("Starting news thread.");
652 jvnews = new BlogReader(me);
653 showNews.setVisible(true);
654 jalview.bin.Console.debug("Completed news thread.");
659 public void getIdentifiersOrgData()
661 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
662 {// Thread off the identifiers fetcher
663 new Thread(new Runnable()
669 .debug("Downloading data from identifiers.org");
672 UrlDownloadClient.download(IdOrgSettings.getUrl(),
673 IdOrgSettings.getDownloadLocation());
674 } catch (IOException e)
677 .debug("Exception downloading identifiers.org data"
687 protected void showNews_actionPerformed(ActionEvent e)
689 showNews(showNews.isSelected());
692 void showNews(boolean visible)
694 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
695 showNews.setSelected(visible);
696 if (visible && !jvnews.isVisible())
698 new Thread(new Runnable()
703 long now = System.currentTimeMillis();
704 Desktop.instance.setProgressBar(
705 MessageManager.getString("status.refreshing_news"), now);
706 jvnews.refreshNews();
707 Desktop.instance.setProgressBar(null, now);
715 * recover the last known dimensions for a jalview window
718 * - empty string is desktop, all other windows have unique prefix
719 * @return null or last known dimensions scaled to current geometry (if last
720 * window geom was known)
722 Rectangle getLastKnownDimensions(String windowName)
724 // TODO: lock aspect ratio for scaling desktop Bug #0058199
725 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
726 String x = Cache.getProperty(windowName + "SCREEN_X");
727 String y = Cache.getProperty(windowName + "SCREEN_Y");
728 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
729 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
730 if ((x != null) && (y != null) && (width != null) && (height != null))
732 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
733 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
734 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
736 // attempt #1 - try to cope with change in screen geometry - this
737 // version doesn't preserve original jv aspect ratio.
738 // take ratio of current screen size vs original screen size.
739 double sw = ((1f * screenSize.width) / (1f * Integer
740 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
741 double sh = ((1f * screenSize.height) / (1f * Integer
742 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
743 // rescale the bounds depending upon the current screen geometry.
744 ix = (int) (ix * sw);
745 iw = (int) (iw * sw);
746 iy = (int) (iy * sh);
747 ih = (int) (ih * sh);
748 while (ix >= screenSize.width)
750 jalview.bin.Console.debug(
751 "Window geometry location recall error: shifting horizontal to within screenbounds.");
752 ix -= screenSize.width;
754 while (iy >= screenSize.height)
756 jalview.bin.Console.debug(
757 "Window geometry location recall error: shifting vertical to within screenbounds.");
758 iy -= screenSize.height;
760 jalview.bin.Console.debug(
761 "Got last known dimensions for " + windowName + ": x:" + ix
762 + " y:" + iy + " width:" + iw + " height:" + ih);
764 // return dimensions for new instance
765 return new Rectangle(ix, iy, iw, ih);
770 void showPasteMenu(int x, int y)
772 JPopupMenu popup = new JPopupMenu();
773 JMenuItem item = new JMenuItem(
774 MessageManager.getString("label.paste_new_window"));
775 item.addActionListener(new ActionListener()
778 public void actionPerformed(ActionEvent evt)
785 popup.show(this, x, y);
792 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
793 Transferable contents = c.getContents(this);
795 if (contents != null)
797 String file = (String) contents
798 .getTransferData(DataFlavor.stringFlavor);
800 FileFormatI format = new IdentifyFile().identify(file,
801 DataSourceType.PASTE);
803 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
806 } catch (Exception ex)
809 "Unable to paste alignment from system clipboard:\n" + ex);
814 * Adds and opens the given frame to the desktop
825 public static synchronized void addInternalFrame(
826 final JInternalFrame frame, String title, int w, int h)
828 addInternalFrame(frame, title, true, w, h, true, false);
832 * Add an internal frame to the Jalview desktop
839 * When true, display frame immediately, otherwise, caller must call
840 * setVisible themselves.
846 public static synchronized void addInternalFrame(
847 final JInternalFrame frame, String title, boolean makeVisible,
850 addInternalFrame(frame, title, makeVisible, w, h, true, false);
854 * Add an internal frame to the Jalview desktop and make it visible
867 public static synchronized void addInternalFrame(
868 final JInternalFrame frame, String title, int w, int h,
871 addInternalFrame(frame, title, true, w, h, resizable, false);
875 * Add an internal frame to the Jalview desktop
882 * When true, display frame immediately, otherwise, caller must call
883 * setVisible themselves.
890 * @param ignoreMinSize
891 * Do not set the default minimum size for frame
893 public static synchronized void addInternalFrame(
894 final JInternalFrame frame, String title, boolean makeVisible,
895 int w, int h, boolean resizable, boolean ignoreMinSize)
898 // TODO: allow callers to determine X and Y position of frame (eg. via
900 // TODO: consider fixing method to update entries in the window submenu with
901 // the current window title
903 frame.setTitle(title);
904 if (frame.getWidth() < 1 || frame.getHeight() < 1)
908 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
909 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
910 // IF JALVIEW IS RUNNING HEADLESS
911 // ///////////////////////////////////////////////
912 if (instance == null || (System.getProperty("java.awt.headless") != null
913 && System.getProperty("java.awt.headless").equals("true")))
922 frame.setMinimumSize(
923 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
925 // Set default dimension for Alignment Frame window.
926 // The Alignment Frame window could be added from a number of places,
928 // I did this here in order not to miss out on any Alignment frame.
929 if (frame instanceof AlignFrame)
931 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
932 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
936 frame.setVisible(makeVisible);
937 frame.setClosable(true);
938 frame.setResizable(resizable);
939 frame.setMaximizable(resizable);
940 frame.setIconifiable(resizable);
941 frame.setOpaque(Platform.isJS());
943 if (frame.getX() < 1 && frame.getY() < 1)
945 frame.setLocation(xOffset * openFrameCount,
946 yOffset * ((openFrameCount - 1) % 10) + yOffset);
950 * add an entry for the new frame in the Window menu (and remove it when the
953 final JMenuItem menuItem = new JMenuItem(title);
954 frame.addInternalFrameListener(new InternalFrameAdapter()
957 public void internalFrameActivated(InternalFrameEvent evt)
959 JInternalFrame itf = desktop.getSelectedFrame();
962 if (itf instanceof AlignFrame)
964 Jalview.setCurrentAlignFrame((AlignFrame) itf);
971 public void internalFrameClosed(InternalFrameEvent evt)
973 PaintRefresher.RemoveComponent(frame);
976 * defensive check to prevent frames being added half off the window
978 if (openFrameCount > 0)
984 * ensure no reference to alignFrame retained by menu item listener
986 if (menuItem.getActionListeners().length > 0)
988 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
990 windowMenu.remove(menuItem);
994 menuItem.addActionListener(new ActionListener()
997 public void actionPerformed(ActionEvent e)
1001 frame.setSelected(true);
1002 frame.setIcon(false);
1003 } catch (java.beans.PropertyVetoException ex)
1010 setKeyBindings(frame);
1014 windowMenu.add(menuItem);
1019 frame.setSelected(true);
1020 frame.requestFocus();
1021 } catch (java.beans.PropertyVetoException ve)
1023 } catch (java.lang.ClassCastException cex)
1025 jalview.bin.Console.warn(
1026 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1032 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1037 private static void setKeyBindings(JInternalFrame frame)
1039 @SuppressWarnings("serial")
1040 final Action closeAction = new AbstractAction()
1043 public void actionPerformed(ActionEvent e)
1050 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1052 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1053 InputEvent.CTRL_DOWN_MASK);
1054 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1055 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1057 InputMap inputMap = frame
1058 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1059 String ctrlW = ctrlWKey.toString();
1060 inputMap.put(ctrlWKey, ctrlW);
1061 inputMap.put(cmdWKey, ctrlW);
1063 ActionMap actionMap = frame.getActionMap();
1064 actionMap.put(ctrlW, closeAction);
1068 public void lostOwnership(Clipboard clipboard, Transferable contents)
1072 Desktop.jalviewClipboard = null;
1075 internalCopy = false;
1079 public void dragEnter(DropTargetDragEvent evt)
1084 public void dragExit(DropTargetEvent evt)
1089 public void dragOver(DropTargetDragEvent evt)
1094 public void dropActionChanged(DropTargetDragEvent evt)
1105 public void drop(DropTargetDropEvent evt)
1107 boolean success = true;
1108 // JAL-1552 - acceptDrop required before getTransferable call for
1109 // Java's Transferable for native dnd
1110 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1111 Transferable t = evt.getTransferable();
1112 List<Object> files = new ArrayList<>();
1113 List<DataSourceType> protocols = new ArrayList<>();
1117 Desktop.transferFromDropTarget(files, protocols, evt, t);
1118 } catch (Exception e)
1120 e.printStackTrace();
1128 for (int i = 0; i < files.size(); i++)
1130 // BH 2018 File or String
1131 Object file = files.get(i);
1132 String fileName = file.toString();
1133 DataSourceType protocol = (protocols == null)
1134 ? DataSourceType.FILE
1136 FileFormatI format = null;
1138 if (fileName.endsWith(".jar"))
1140 format = FileFormat.Jalview;
1145 format = new IdentifyFile().identify(file, protocol);
1147 if (file instanceof File)
1149 Platform.cacheFileData((File) file);
1151 new FileLoader().LoadFile(null, file, protocol, format);
1154 } catch (Exception ex)
1159 evt.dropComplete(success); // need this to ensure input focus is properly
1160 // transfered to any new windows created
1170 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1172 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1173 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1174 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1175 BackupFiles.getEnabled());
1177 chooser.setFileView(new JalviewFileView());
1178 chooser.setDialogTitle(
1179 MessageManager.getString("label.open_local_file"));
1180 chooser.setToolTipText(MessageManager.getString("action.open"));
1182 chooser.setResponseHandler(0, new Runnable()
1187 File selectedFile = chooser.getSelectedFile();
1188 FileFormatI format = chooser.getSelectedFormat();
1189 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1190 viewport.addFile(selectedFile, format);
1193 chooser.showOpenDialog(this);
1197 * Shows a dialog for input of a URL at which to retrieve alignment data
1202 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1204 // This construct allows us to have a wider textfield
1206 JLabel label = new JLabel(
1207 MessageManager.getString("label.input_file_url"));
1209 JPanel panel = new JPanel(new GridLayout(2, 1));
1213 * the URL to fetch is input in Java: an editable combobox with history JS:
1214 * (pending JAL-3038) a plain text field
1217 String urlBase = "https://www.";
1218 if (Platform.isJS())
1220 history = new JTextField(urlBase, 35);
1229 JComboBox<String> asCombo = new JComboBox<>();
1230 asCombo.setPreferredSize(new Dimension(400, 20));
1231 asCombo.setEditable(true);
1232 asCombo.addItem(urlBase);
1233 String historyItems = Cache.getProperty("RECENT_URL");
1234 if (historyItems != null)
1236 for (String token : historyItems.split("\\t"))
1238 asCombo.addItem(token);
1245 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1246 MessageManager.getString("action.cancel") };
1247 Runnable action = new Runnable()
1252 @SuppressWarnings("unchecked")
1253 String url = (history instanceof JTextField
1254 ? ((JTextField) history).getText()
1255 : ((JComboBox<String>) history).getEditor().getItem()
1256 .toString().trim());
1258 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1260 if (viewport != null)
1262 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1263 FileFormat.Jalview);
1267 new FileLoader().LoadFile(url, DataSourceType.URL,
1268 FileFormat.Jalview);
1273 FileFormatI format = null;
1276 format = new IdentifyFile().identify(url, DataSourceType.URL);
1277 } catch (FileFormatException e)
1279 // TODO revise error handling, distinguish between
1280 // URL not found and response not valid
1285 String msg = MessageManager
1286 .formatMessage("label.couldnt_locate", url);
1287 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1288 MessageManager.getString("label.url_not_found"),
1289 JvOptionPane.WARNING_MESSAGE);
1294 if (viewport != null)
1296 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1301 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1306 String dialogOption = MessageManager
1307 .getString("label.input_alignment_from_url");
1308 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1309 .showInternalDialog(panel, dialogOption,
1310 JvOptionPane.YES_NO_CANCEL_OPTION,
1311 JvOptionPane.PLAIN_MESSAGE, null, options,
1312 MessageManager.getString("action.ok"));
1316 * Opens the CutAndPaste window for the user to paste an alignment in to
1319 * - if not null, the pasted alignment is added to the current
1320 * alignment; if null, to a new alignment window
1323 public void inputTextboxMenuItem_actionPerformed(
1324 AlignmentViewPanel viewPanel)
1326 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1327 cap.setForInput(viewPanel);
1328 Desktop.addInternalFrame(cap,
1329 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1339 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1340 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1341 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1342 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1343 getWidth(), getHeight()));
1345 if (jconsole != null)
1347 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1348 jconsole.stopConsole();
1352 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1355 if (dialogExecutor != null)
1357 dialogExecutor.shutdownNow();
1359 closeAll_actionPerformed(null);
1361 if (groovyConsole != null)
1363 // suppress a possible repeat prompt to save script
1364 groovyConsole.setDirty(false);
1365 groovyConsole.exit();
1370 private void storeLastKnownDimensions(String string, Rectangle jc)
1372 jalview.bin.Console.debug("Storing last known dimensions for " + string
1373 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1374 + " height:" + jc.height);
1376 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1377 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1378 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1379 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1389 public void aboutMenuItem_actionPerformed(ActionEvent e)
1391 new Thread(new Runnable()
1396 new SplashScreen(false);
1402 * Returns the html text for the About screen, including any available version
1403 * number, build details, author details and citation reference, but without
1404 * the enclosing {@code html} tags
1408 public String getAboutMessage()
1410 StringBuilder message = new StringBuilder(1024);
1411 message.append("<div style=\"font-family: sans-serif;\">")
1412 .append("<h1><strong>Version: ")
1413 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1414 .append("<strong>Built: <em>")
1415 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1416 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1417 .append("</strong>");
1419 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1420 if (latestVersion.equals("Checking"))
1422 // JBP removed this message for 2.11: May be reinstated in future version
1423 // message.append("<br>...Checking latest version...</br>");
1425 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1427 boolean red = false;
1428 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1429 .indexOf("automated build") == -1)
1432 // Displayed when code version and jnlp version do not match and code
1433 // version is not a development build
1434 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1437 message.append("<br>!! Version ")
1438 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1439 .append(" is available for download from ")
1440 .append(Cache.getDefault("www.jalview.org",
1441 "https://www.jalview.org"))
1445 message.append("</div>");
1448 message.append("<br>Authors: ");
1449 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1450 message.append(CITATION);
1452 message.append("</div>");
1454 return message.toString();
1458 * Action on requesting Help documentation
1461 public void documentationMenuItem_actionPerformed()
1465 if (Platform.isJS())
1467 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1476 Help.showHelpWindow();
1478 } catch (Exception ex)
1480 System.err.println("Error opening help: " + ex.getMessage());
1485 public void closeAll_actionPerformed(ActionEvent e)
1487 // TODO show a progress bar while closing?
1488 JInternalFrame[] frames = desktop.getAllFrames();
1489 for (int i = 0; i < frames.length; i++)
1493 frames[i].setClosed(true);
1494 } catch (java.beans.PropertyVetoException ex)
1498 Jalview.setCurrentAlignFrame(null);
1499 System.out.println("ALL CLOSED");
1502 * reset state of singleton objects as appropriate (clear down session state
1503 * when all windows are closed)
1505 StructureSelectionManager ssm = StructureSelectionManager
1506 .getStructureSelectionManager(this);
1514 public void raiseRelated_actionPerformed(ActionEvent e)
1516 reorderAssociatedWindows(false, false);
1520 public void minimizeAssociated_actionPerformed(ActionEvent e)
1522 reorderAssociatedWindows(true, false);
1525 void closeAssociatedWindows()
1527 reorderAssociatedWindows(false, true);
1533 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1537 protected void garbageCollect_actionPerformed(ActionEvent e)
1539 // We simply collect the garbage
1540 jalview.bin.Console.debug("Collecting garbage...");
1542 jalview.bin.Console.debug("Finished garbage collection.");
1548 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1552 protected void showMemusage_actionPerformed(ActionEvent e)
1554 desktop.showMemoryUsage(showMemusage.isSelected());
1561 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1565 protected void showConsole_actionPerformed(ActionEvent e)
1567 showConsole(showConsole.isSelected());
1570 Console jconsole = null;
1573 * control whether the java console is visible or not
1577 void showConsole(boolean selected)
1579 // TODO: decide if we should update properties file
1580 if (jconsole != null) // BH 2018
1582 showConsole.setSelected(selected);
1583 Cache.setProperty("SHOW_JAVA_CONSOLE",
1584 Boolean.valueOf(selected).toString());
1585 jconsole.setVisible(selected);
1589 void reorderAssociatedWindows(boolean minimize, boolean close)
1591 JInternalFrame[] frames = desktop.getAllFrames();
1592 if (frames == null || frames.length < 1)
1597 AlignmentViewport source = null, target = null;
1598 if (frames[0] instanceof AlignFrame)
1600 source = ((AlignFrame) frames[0]).getCurrentView();
1602 else if (frames[0] instanceof TreePanel)
1604 source = ((TreePanel) frames[0]).getViewPort();
1606 else if (frames[0] instanceof PCAPanel)
1608 source = ((PCAPanel) frames[0]).av;
1610 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1612 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1617 for (int i = 0; i < frames.length; i++)
1620 if (frames[i] == null)
1624 if (frames[i] instanceof AlignFrame)
1626 target = ((AlignFrame) frames[i]).getCurrentView();
1628 else if (frames[i] instanceof TreePanel)
1630 target = ((TreePanel) frames[i]).getViewPort();
1632 else if (frames[i] instanceof PCAPanel)
1634 target = ((PCAPanel) frames[i]).av;
1636 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1638 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1641 if (source == target)
1647 frames[i].setClosed(true);
1651 frames[i].setIcon(minimize);
1654 frames[i].toFront();
1658 } catch (java.beans.PropertyVetoException ex)
1673 protected void preferences_actionPerformed(ActionEvent e)
1675 Preferences.openPreferences();
1679 * Prompts the user to choose a file and then saves the Jalview state as a
1680 * Jalview project file
1683 public void saveState_actionPerformed()
1685 saveState_actionPerformed(false);
1688 public void saveState_actionPerformed(boolean saveAs)
1690 java.io.File projectFile = getProjectFile();
1691 // autoSave indicates we already have a file and don't need to ask
1692 boolean autoSave = projectFile != null && !saveAs
1693 && BackupFiles.getEnabled();
1695 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1696 // saveAs="+saveAs+", Backups
1697 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1699 boolean approveSave = false;
1702 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1705 chooser.setFileView(new JalviewFileView());
1706 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1708 int value = chooser.showSaveDialog(this);
1710 if (value == JalviewFileChooser.APPROVE_OPTION)
1712 projectFile = chooser.getSelectedFile();
1713 setProjectFile(projectFile);
1718 if (approveSave || autoSave)
1720 final Desktop me = this;
1721 final java.io.File chosenFile = projectFile;
1722 new Thread(new Runnable()
1727 // TODO: refactor to Jalview desktop session controller action.
1728 setProgressBar(MessageManager.formatMessage(
1729 "label.saving_jalview_project", new Object[]
1730 { chosenFile.getName() }), chosenFile.hashCode());
1731 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1732 // TODO catch and handle errors for savestate
1733 // TODO prevent user from messing with the Desktop whilst we're saving
1736 boolean doBackup = BackupFiles.getEnabled();
1737 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1740 new Jalview2XML().saveState(
1741 doBackup ? backupfiles.getTempFile() : chosenFile);
1745 backupfiles.setWriteSuccess(true);
1746 backupfiles.rollBackupsAndRenameTempFile();
1748 } catch (OutOfMemoryError oom)
1750 new OOMWarning("Whilst saving current state to "
1751 + chosenFile.getName(), oom);
1752 } catch (Exception ex)
1754 jalview.bin.Console.error("Problems whilst trying to save to "
1755 + chosenFile.getName(), ex);
1756 JvOptionPane.showMessageDialog(me,
1757 MessageManager.formatMessage(
1758 "label.error_whilst_saving_current_state_to",
1760 { chosenFile.getName() }),
1761 MessageManager.getString("label.couldnt_save_project"),
1762 JvOptionPane.WARNING_MESSAGE);
1764 setProgressBar(null, chosenFile.hashCode());
1771 public void saveAsState_actionPerformed(ActionEvent e)
1773 saveState_actionPerformed(true);
1776 private void setProjectFile(File choice)
1778 this.projectFile = choice;
1781 public File getProjectFile()
1783 return this.projectFile;
1787 * Shows a file chooser dialog and tries to read in the selected file as a
1791 public void loadState_actionPerformed()
1793 final String[] suffix = new String[] { "jvp", "jar" };
1794 final String[] desc = new String[] { "Jalview Project",
1795 "Jalview Project (old)" };
1796 JalviewFileChooser chooser = new JalviewFileChooser(
1797 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1798 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1802 chooser.setFileView(new JalviewFileView());
1803 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1804 chooser.setResponseHandler(0, new Runnable()
1809 File selectedFile = chooser.getSelectedFile();
1810 setProjectFile(selectedFile);
1811 String choice = selectedFile.getAbsolutePath();
1812 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1813 new Thread(new Runnable()
1820 new Jalview2XML().loadJalviewAlign(selectedFile);
1821 } catch (OutOfMemoryError oom)
1823 new OOMWarning("Whilst loading project from " + choice, oom);
1824 } catch (Exception ex)
1826 jalview.bin.Console.error(
1827 "Problems whilst loading project from " + choice, ex);
1828 JvOptionPane.showMessageDialog(Desktop.desktop,
1829 MessageManager.formatMessage(
1830 "label.error_whilst_loading_project_from",
1834 .getString("label.couldnt_load_project"),
1835 JvOptionPane.WARNING_MESSAGE);
1838 }, "Project Loader").start();
1842 chooser.showOpenDialog(this);
1846 public void inputSequence_actionPerformed(ActionEvent e)
1848 new SequenceFetcher(this);
1851 JPanel progressPanel;
1853 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1855 public void startLoading(final Object fileName)
1857 if (fileLoadingCount == 0)
1859 fileLoadingPanels.add(addProgressPanel(MessageManager
1860 .formatMessage("label.loading_file", new Object[]
1866 private JPanel addProgressPanel(String string)
1868 if (progressPanel == null)
1870 progressPanel = new JPanel(new GridLayout(1, 1));
1871 totalProgressCount = 0;
1872 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1874 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1875 JProgressBar progressBar = new JProgressBar();
1876 progressBar.setIndeterminate(true);
1878 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1880 thisprogress.add(progressBar, BorderLayout.CENTER);
1881 progressPanel.add(thisprogress);
1882 ((GridLayout) progressPanel.getLayout()).setRows(
1883 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1884 ++totalProgressCount;
1885 instance.validate();
1886 return thisprogress;
1889 int totalProgressCount = 0;
1891 private void removeProgressPanel(JPanel progbar)
1893 if (progressPanel != null)
1895 synchronized (progressPanel)
1897 progressPanel.remove(progbar);
1898 GridLayout gl = (GridLayout) progressPanel.getLayout();
1899 gl.setRows(gl.getRows() - 1);
1900 if (--totalProgressCount < 1)
1902 this.getContentPane().remove(progressPanel);
1903 progressPanel = null;
1910 public void stopLoading()
1913 if (fileLoadingCount < 1)
1915 while (fileLoadingPanels.size() > 0)
1917 removeProgressPanel(fileLoadingPanels.remove(0));
1919 fileLoadingPanels.clear();
1920 fileLoadingCount = 0;
1925 public static int getViewCount(String alignmentId)
1927 AlignmentViewport[] aps = getViewports(alignmentId);
1928 return (aps == null) ? 0 : aps.length;
1933 * @param alignmentId
1934 * - if null, all sets are returned
1935 * @return all AlignmentPanels concerning the alignmentId sequence set
1937 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1939 if (Desktop.desktop == null)
1941 // no frames created and in headless mode
1942 // TODO: verify that frames are recoverable when in headless mode
1945 List<AlignmentPanel> aps = new ArrayList<>();
1946 AlignFrame[] frames = getAlignFrames();
1951 for (AlignFrame af : frames)
1953 for (AlignmentPanel ap : af.alignPanels)
1955 if (alignmentId == null
1956 || alignmentId.equals(ap.av.getSequenceSetId()))
1962 if (aps.size() == 0)
1966 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1971 * get all the viewports on an alignment.
1973 * @param sequenceSetId
1974 * unique alignment id (may be null - all viewports returned in that
1976 * @return all viewports on the alignment bound to sequenceSetId
1978 public static AlignmentViewport[] getViewports(String sequenceSetId)
1980 List<AlignmentViewport> viewp = new ArrayList<>();
1981 if (desktop != null)
1983 AlignFrame[] frames = Desktop.getAlignFrames();
1985 for (AlignFrame afr : frames)
1987 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
1988 .equals(sequenceSetId))
1990 if (afr.alignPanels != null)
1992 for (AlignmentPanel ap : afr.alignPanels)
1994 if (sequenceSetId == null
1995 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2003 viewp.add(afr.getViewport());
2007 if (viewp.size() > 0)
2009 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2016 * Explode the views in the given frame into separate AlignFrame
2020 public static void explodeViews(AlignFrame af)
2022 int size = af.alignPanels.size();
2028 // FIXME: ideally should use UI interface API
2029 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2030 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2031 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2032 for (int i = 0; i < size; i++)
2034 AlignmentPanel ap = af.alignPanels.get(i);
2036 AlignFrame newaf = new AlignFrame(ap);
2038 // transfer reference for existing feature settings to new alignFrame
2039 if (ap == af.alignPanel)
2041 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2043 newaf.featureSettings = viewFeatureSettings;
2045 newaf.setFeatureSettingsGeometry(fsBounds);
2049 * Restore the view's last exploded frame geometry if known. Multiple views from
2050 * one exploded frame share and restore the same (frame) position and size.
2052 Rectangle geometry = ap.av.getExplodedGeometry();
2053 if (geometry != null)
2055 newaf.setBounds(geometry);
2058 ap.av.setGatherViewsHere(false);
2060 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2061 AlignFrame.DEFAULT_HEIGHT);
2062 // and materialise a new feature settings dialog instance for the new
2064 // (closes the old as if 'OK' was pressed)
2065 if (ap == af.alignPanel && newaf.featureSettings != null
2066 && newaf.featureSettings.isOpen()
2067 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2069 newaf.showFeatureSettingsUI();
2073 af.featureSettings = null;
2074 af.alignPanels.clear();
2075 af.closeMenuItem_actionPerformed(true);
2080 * Gather expanded views (separate AlignFrame's) with the same sequence set
2081 * identifier back in to this frame as additional views, and close the
2082 * expanded views. Note the expanded frames may themselves have multiple
2083 * views. We take the lot.
2087 public void gatherViews(AlignFrame source)
2089 source.viewport.setGatherViewsHere(true);
2090 source.viewport.setExplodedGeometry(source.getBounds());
2091 JInternalFrame[] frames = desktop.getAllFrames();
2092 String viewId = source.viewport.getSequenceSetId();
2093 for (int t = 0; t < frames.length; t++)
2095 if (frames[t] instanceof AlignFrame && frames[t] != source)
2097 AlignFrame af = (AlignFrame) frames[t];
2098 boolean gatherThis = false;
2099 for (int a = 0; a < af.alignPanels.size(); a++)
2101 AlignmentPanel ap = af.alignPanels.get(a);
2102 if (viewId.equals(ap.av.getSequenceSetId()))
2105 ap.av.setGatherViewsHere(false);
2106 ap.av.setExplodedGeometry(af.getBounds());
2107 source.addAlignmentPanel(ap, false);
2113 if (af.featureSettings != null && af.featureSettings.isOpen())
2115 if (source.featureSettings == null)
2117 // preserve the feature settings geometry for this frame
2118 source.featureSettings = af.featureSettings;
2119 source.setFeatureSettingsGeometry(
2120 af.getFeatureSettingsGeometry());
2124 // close it and forget
2125 af.featureSettings.close();
2128 af.alignPanels.clear();
2129 af.closeMenuItem_actionPerformed(true);
2134 // refresh the feature setting UI for the source frame if it exists
2135 if (source.featureSettings != null && source.featureSettings.isOpen())
2137 source.showFeatureSettingsUI();
2142 public JInternalFrame[] getAllFrames()
2144 return desktop.getAllFrames();
2148 * Checks the given url to see if it gives a response indicating that the user
2149 * should be informed of a new questionnaire.
2153 public void checkForQuestionnaire(String url)
2155 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2156 // javax.swing.SwingUtilities.invokeLater(jvq);
2157 new Thread(jvq).start();
2160 public void checkURLLinks()
2162 // Thread off the URL link checker
2163 addDialogThread(new Runnable()
2168 if (Cache.getDefault("CHECKURLLINKS", true))
2170 // check what the actual links are - if it's just the default don't
2171 // bother with the warning
2172 List<String> links = Preferences.sequenceUrlLinks
2175 // only need to check links if there is one with a
2176 // SEQUENCE_ID which is not the default EMBL_EBI link
2177 ListIterator<String> li = links.listIterator();
2178 boolean check = false;
2179 List<JLabel> urls = new ArrayList<>();
2180 while (li.hasNext())
2182 String link = li.next();
2183 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2184 && !UrlConstants.isDefaultString(link))
2187 int barPos = link.indexOf("|");
2188 String urlMsg = barPos == -1 ? link
2189 : link.substring(0, barPos) + ": "
2190 + link.substring(barPos + 1);
2191 urls.add(new JLabel(urlMsg));
2199 // ask user to check in case URL links use old style tokens
2200 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2201 JPanel msgPanel = new JPanel();
2202 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2203 msgPanel.add(Box.createVerticalGlue());
2204 JLabel msg = new JLabel(MessageManager
2205 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2206 JLabel msg2 = new JLabel(MessageManager
2207 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2209 for (JLabel url : urls)
2215 final JCheckBox jcb = new JCheckBox(
2216 MessageManager.getString("label.do_not_display_again"));
2217 jcb.addActionListener(new ActionListener()
2220 public void actionPerformed(ActionEvent e)
2222 // update Cache settings for "don't show this again"
2223 boolean showWarningAgain = !jcb.isSelected();
2224 Cache.setProperty("CHECKURLLINKS",
2225 Boolean.valueOf(showWarningAgain).toString());
2230 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2232 .getString("label.SEQUENCE_ID_no_longer_used"),
2233 JvOptionPane.WARNING_MESSAGE);
2240 * Proxy class for JDesktopPane which optionally displays the current memory
2241 * usage and highlights the desktop area with a red bar if free memory runs
2246 public class MyDesktopPane extends JDesktopPane implements Runnable
2248 private static final float ONE_MB = 1048576f;
2250 boolean showMemoryUsage = false;
2254 java.text.NumberFormat df;
2256 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2259 public MyDesktopPane(boolean showMemoryUsage)
2261 showMemoryUsage(showMemoryUsage);
2264 public void showMemoryUsage(boolean showMemory)
2266 this.showMemoryUsage = showMemory;
2269 Thread worker = new Thread(this);
2275 public boolean isShowMemoryUsage()
2277 return showMemoryUsage;
2283 df = java.text.NumberFormat.getNumberInstance();
2284 df.setMaximumFractionDigits(2);
2285 runtime = Runtime.getRuntime();
2287 while (showMemoryUsage)
2291 maxMemory = runtime.maxMemory() / ONE_MB;
2292 allocatedMemory = runtime.totalMemory() / ONE_MB;
2293 freeMemory = runtime.freeMemory() / ONE_MB;
2294 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2296 percentUsage = (totalFreeMemory / maxMemory) * 100;
2298 // if (percentUsage < 20)
2300 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2302 // instance.set.setBorder(border1);
2305 // sleep after showing usage
2307 } catch (Exception ex)
2309 ex.printStackTrace();
2315 public void paintComponent(Graphics g)
2317 if (showMemoryUsage && g != null && df != null)
2319 if (percentUsage < 20)
2321 g.setColor(Color.red);
2323 FontMetrics fm = g.getFontMetrics();
2326 g.drawString(MessageManager.formatMessage("label.memory_stats",
2328 { df.format(totalFreeMemory), df.format(maxMemory),
2329 df.format(percentUsage) }),
2330 10, getHeight() - fm.getHeight());
2334 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2335 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2340 * Accessor method to quickly get all the AlignmentFrames loaded.
2342 * @return an array of AlignFrame, or null if none found
2344 public static AlignFrame[] getAlignFrames()
2346 if (Jalview.isHeadlessMode())
2348 // Desktop.desktop is null in headless mode
2349 return new AlignFrame[] { Jalview.currentAlignFrame };
2352 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2358 List<AlignFrame> avp = new ArrayList<>();
2360 for (int i = frames.length - 1; i > -1; i--)
2362 if (frames[i] instanceof AlignFrame)
2364 avp.add((AlignFrame) frames[i]);
2366 else if (frames[i] instanceof SplitFrame)
2369 * Also check for a split frame containing an AlignFrame
2371 GSplitFrame sf = (GSplitFrame) frames[i];
2372 if (sf.getTopFrame() instanceof AlignFrame)
2374 avp.add((AlignFrame) sf.getTopFrame());
2376 if (sf.getBottomFrame() instanceof AlignFrame)
2378 avp.add((AlignFrame) sf.getBottomFrame());
2382 if (avp.size() == 0)
2386 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2391 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2395 public GStructureViewer[] getJmols()
2397 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2403 List<GStructureViewer> avp = new ArrayList<>();
2405 for (int i = frames.length - 1; i > -1; i--)
2407 if (frames[i] instanceof AppJmol)
2409 GStructureViewer af = (GStructureViewer) frames[i];
2413 if (avp.size() == 0)
2417 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2422 * Add Groovy Support to Jalview
2425 public void groovyShell_actionPerformed()
2429 openGroovyConsole();
2430 } catch (Exception ex)
2432 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2433 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2435 MessageManager.getString("label.couldnt_create_groovy_shell"),
2436 MessageManager.getString("label.groovy_support_failed"),
2437 JvOptionPane.ERROR_MESSAGE);
2442 * Open the Groovy console
2444 void openGroovyConsole()
2446 if (groovyConsole == null)
2448 groovyConsole = new groovy.ui.Console();
2449 groovyConsole.setVariable("Jalview", this);
2450 groovyConsole.run();
2453 * We allow only one console at a time, so that AlignFrame menu option
2454 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2455 * enable 'Run script', when the console is opened, and the reverse when it is
2458 Window window = (Window) groovyConsole.getFrame();
2459 window.addWindowListener(new WindowAdapter()
2462 public void windowClosed(WindowEvent e)
2465 * rebind CMD-Q from Groovy Console to Jalview Quit
2468 enableExecuteGroovy(false);
2474 * show Groovy console window (after close and reopen)
2476 ((Window) groovyConsole.getFrame()).setVisible(true);
2479 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2480 * opening a second console
2482 enableExecuteGroovy(true);
2486 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2487 * binding when opened
2489 protected void addQuitHandler()
2492 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2494 .getKeyStroke(KeyEvent.VK_Q,
2495 jalview.util.ShortcutKeyMaskExWrapper
2496 .getMenuShortcutKeyMaskEx()),
2498 getRootPane().getActionMap().put("Quit", new AbstractAction()
2501 public void actionPerformed(ActionEvent e)
2509 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2512 * true if Groovy console is open
2514 public void enableExecuteGroovy(boolean enabled)
2517 * disable opening a second Groovy console (or re-enable when the console is
2520 groovyShell.setEnabled(!enabled);
2522 AlignFrame[] alignFrames = getAlignFrames();
2523 if (alignFrames != null)
2525 for (AlignFrame af : alignFrames)
2527 af.setGroovyEnabled(enabled);
2533 * Progress bars managed by the IProgressIndicator method.
2535 private Hashtable<Long, JPanel> progressBars;
2537 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2542 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2545 public void setProgressBar(String message, long id)
2547 if (progressBars == null)
2549 progressBars = new Hashtable<>();
2550 progressBarHandlers = new Hashtable<>();
2553 if (progressBars.get(Long.valueOf(id)) != null)
2555 JPanel panel = progressBars.remove(Long.valueOf(id));
2556 if (progressBarHandlers.contains(Long.valueOf(id)))
2558 progressBarHandlers.remove(Long.valueOf(id));
2560 removeProgressPanel(panel);
2564 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2571 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2572 * jalview.gui.IProgressIndicatorHandler)
2575 public void registerHandler(final long id,
2576 final IProgressIndicatorHandler handler)
2578 if (progressBarHandlers == null
2579 || !progressBars.containsKey(Long.valueOf(id)))
2581 throw new Error(MessageManager.getString(
2582 "error.call_setprogressbar_before_registering_handler"));
2584 progressBarHandlers.put(Long.valueOf(id), handler);
2585 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2586 if (handler.canCancel())
2588 JButton cancel = new JButton(
2589 MessageManager.getString("action.cancel"));
2590 final IProgressIndicator us = this;
2591 cancel.addActionListener(new ActionListener()
2595 public void actionPerformed(ActionEvent e)
2597 handler.cancelActivity(id);
2598 us.setProgressBar(MessageManager
2599 .formatMessage("label.cancelled_params", new Object[]
2600 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2604 progressPanel.add(cancel, BorderLayout.EAST);
2610 * @return true if any progress bars are still active
2613 public boolean operationInProgress()
2615 if (progressBars != null && progressBars.size() > 0)
2623 * This will return the first AlignFrame holding the given viewport instance.
2624 * It will break if there are more than one AlignFrames viewing a particular
2628 * @return alignFrame for viewport
2630 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2632 if (desktop != null)
2634 AlignmentPanel[] aps = getAlignmentPanels(
2635 viewport.getSequenceSetId());
2636 for (int panel = 0; aps != null && panel < aps.length; panel++)
2638 if (aps[panel] != null && aps[panel].av == viewport)
2640 return aps[panel].alignFrame;
2647 public VamsasApplication getVamsasApplication()
2649 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2655 * flag set if jalview GUI is being operated programmatically
2657 private boolean inBatchMode = false;
2660 * check if jalview GUI is being operated programmatically
2662 * @return inBatchMode
2664 public boolean isInBatchMode()
2670 * set flag if jalview GUI is being operated programmatically
2672 * @param inBatchMode
2674 public void setInBatchMode(boolean inBatchMode)
2676 this.inBatchMode = inBatchMode;
2680 * start service discovery and wait till it is done
2682 public void startServiceDiscovery()
2684 startServiceDiscovery(false);
2688 * start service discovery threads - blocking or non-blocking
2692 public void startServiceDiscovery(boolean blocking)
2694 startServiceDiscovery(blocking, false);
2698 * start service discovery threads
2701 * - false means call returns immediately
2702 * @param ignore_SHOW_JWS2_SERVICES_preference
2703 * - when true JABA services are discovered regardless of user's JWS2
2704 * discovery preference setting
2706 public void startServiceDiscovery(boolean blocking,
2707 boolean ignore_SHOW_JWS2_SERVICES_preference)
2709 boolean alive = true;
2710 Thread t0 = null, t1 = null, t2 = null;
2711 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2714 // todo: changesupport handlers need to be transferred
2715 if (discoverer == null)
2717 discoverer = new jalview.ws.jws1.Discoverer();
2718 // register PCS handler for desktop.
2719 discoverer.addPropertyChangeListener(changeSupport);
2721 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2722 // until we phase out completely
2723 (t0 = new Thread(discoverer)).start();
2726 if (ignore_SHOW_JWS2_SERVICES_preference
2727 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2729 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2730 .startDiscoverer(changeSupport);
2734 // TODO: do rest service discovery
2743 } catch (Exception e)
2746 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2747 || (t3 != null && t3.isAlive())
2748 || (t0 != null && t0.isAlive());
2754 * called to check if the service discovery process completed successfully.
2758 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2760 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2762 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2763 .getErrorMessages();
2766 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2768 if (serviceChangedDialog == null)
2770 // only run if we aren't already displaying one of these.
2771 addDialogThread(serviceChangedDialog = new Runnable()
2778 * JalviewDialog jd =new JalviewDialog() {
2780 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2782 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2784 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2786 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2788 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2789 * + " or mis-configured HTTP proxy settings.<br/>" +
2790 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2791 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2792 * true, true, "Web Service Configuration Problem", 450, 400);
2794 * jd.waitForInput();
2796 JvOptionPane.showConfirmDialog(Desktop.desktop,
2797 new JLabel("<html><table width=\"450\"><tr><td>"
2798 + ermsg + "</td></tr></table>"
2799 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2800 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2801 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2802 + " Tools->Preferences dialog box to change them.</p></html>"),
2803 "Web Service Configuration Problem",
2804 JvOptionPane.DEFAULT_OPTION,
2805 JvOptionPane.ERROR_MESSAGE);
2806 serviceChangedDialog = null;
2814 jalview.bin.Console.error(
2815 "Errors reported by JABA discovery service. Check web services preferences.\n"
2822 private Runnable serviceChangedDialog = null;
2825 * start a thread to open a URL in the configured browser. Pops up a warning
2826 * dialog to the user if there is an exception when calling out to the browser
2831 public static void showUrl(final String url)
2833 showUrl(url, Desktop.instance);
2837 * Like showUrl but allows progress handler to be specified
2841 * (null) or object implementing IProgressIndicator
2843 public static void showUrl(final String url,
2844 final IProgressIndicator progress)
2846 new Thread(new Runnable()
2853 if (progress != null)
2855 progress.setProgressBar(MessageManager
2856 .formatMessage("status.opening_params", new Object[]
2857 { url }), this.hashCode());
2859 jalview.util.BrowserLauncher.openURL(url);
2860 } catch (Exception ex)
2862 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2864 .getString("label.web_browser_not_found_unix"),
2865 MessageManager.getString("label.web_browser_not_found"),
2866 JvOptionPane.WARNING_MESSAGE);
2868 ex.printStackTrace();
2870 if (progress != null)
2872 progress.setProgressBar(null, this.hashCode());
2878 public static WsParamSetManager wsparamManager = null;
2880 public static ParamManager getUserParameterStore()
2882 if (wsparamManager == null)
2884 wsparamManager = new WsParamSetManager();
2886 return wsparamManager;
2890 * static hyperlink handler proxy method for use by Jalview's internal windows
2894 public static void hyperlinkUpdate(HyperlinkEvent e)
2896 if (e.getEventType() == EventType.ACTIVATED)
2901 url = e.getURL().toString();
2902 Desktop.showUrl(url);
2903 } catch (Exception x)
2908 .error("Couldn't handle string " + url + " as a URL.");
2910 // ignore any exceptions due to dud links.
2917 * single thread that handles display of dialogs to user.
2919 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2922 * flag indicating if dialogExecutor should try to acquire a permit
2924 private volatile boolean dialogPause = true;
2929 private java.util.concurrent.Semaphore block = new Semaphore(0);
2931 private static groovy.ui.Console groovyConsole;
2934 * add another dialog thread to the queue
2938 public void addDialogThread(final Runnable prompter)
2940 dialogExecutor.submit(new Runnable()
2950 } catch (InterruptedException x)
2954 if (instance == null)
2960 SwingUtilities.invokeAndWait(prompter);
2961 } catch (Exception q)
2963 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
2970 public void startDialogQueue()
2972 // set the flag so we don't pause waiting for another permit and semaphore
2973 // the current task to begin
2974 dialogPause = false;
2979 * Outputs an image of the desktop to file in EPS format, after prompting the
2980 * user for choice of Text or Lineart character rendering (unless a preference
2981 * has been set). The file name is generated as
2984 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2988 protected void snapShotWindow_actionPerformed(ActionEvent e)
2990 // currently the menu option to do this is not shown
2993 int width = getWidth();
2994 int height = getHeight();
2996 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2997 ImageWriterI writer = new ImageWriterI()
3000 public void exportImage(Graphics g) throws Exception
3003 jalview.bin.Console.info("Successfully written snapshot to file "
3004 + of.getAbsolutePath());
3007 String title = "View of desktop";
3008 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3010 exporter.doExport(of, this, width, height, title);
3014 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3015 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3016 * and location last time the view was expanded (if any). However it does not
3017 * remember the split pane divider location - this is set to match the
3018 * 'exploding' frame.
3022 public void explodeViews(SplitFrame sf)
3024 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3025 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3026 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3028 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3030 int viewCount = topPanels.size();
3037 * Processing in reverse order works, forwards order leaves the first panels not
3038 * visible. I don't know why!
3040 for (int i = viewCount - 1; i >= 0; i--)
3043 * Make new top and bottom frames. These take over the respective AlignmentPanel
3044 * objects, including their AlignmentViewports, so the cdna/protein
3045 * relationships between the viewports is carried over to the new split frames.
3047 * explodedGeometry holds the (x, y) position of the previously exploded
3048 * SplitFrame, and the (width, height) of the AlignFrame component
3050 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3051 AlignFrame newTopFrame = new AlignFrame(topPanel);
3052 newTopFrame.setSize(oldTopFrame.getSize());
3053 newTopFrame.setVisible(true);
3054 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3055 .getExplodedGeometry();
3056 if (geometry != null)
3058 newTopFrame.setSize(geometry.getSize());
3061 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3062 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3063 newBottomFrame.setSize(oldBottomFrame.getSize());
3064 newBottomFrame.setVisible(true);
3065 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3066 .getExplodedGeometry();
3067 if (geometry != null)
3069 newBottomFrame.setSize(geometry.getSize());
3072 topPanel.av.setGatherViewsHere(false);
3073 bottomPanel.av.setGatherViewsHere(false);
3074 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3076 if (geometry != null)
3078 splitFrame.setLocation(geometry.getLocation());
3080 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3084 * Clear references to the panels (now relocated in the new SplitFrames) before
3085 * closing the old SplitFrame.
3088 bottomPanels.clear();
3093 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3094 * back into the given SplitFrame as additional views. Note that the gathered
3095 * frames may themselves have multiple views.
3099 public void gatherViews(GSplitFrame source)
3102 * special handling of explodedGeometry for a view within a SplitFrame: - it
3103 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3104 * height) of the AlignFrame component
3106 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3107 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3108 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3109 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3110 myBottomFrame.viewport
3111 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3112 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3113 myTopFrame.viewport.setGatherViewsHere(true);
3114 myBottomFrame.viewport.setGatherViewsHere(true);
3115 String topViewId = myTopFrame.viewport.getSequenceSetId();
3116 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3118 JInternalFrame[] frames = desktop.getAllFrames();
3119 for (JInternalFrame frame : frames)
3121 if (frame instanceof SplitFrame && frame != source)
3123 SplitFrame sf = (SplitFrame) frame;
3124 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3125 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3126 boolean gatherThis = false;
3127 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3129 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3130 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3131 if (topViewId.equals(topPanel.av.getSequenceSetId())
3132 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3135 topPanel.av.setGatherViewsHere(false);
3136 bottomPanel.av.setGatherViewsHere(false);
3137 topPanel.av.setExplodedGeometry(
3138 new Rectangle(sf.getLocation(), topFrame.getSize()));
3139 bottomPanel.av.setExplodedGeometry(
3140 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3141 myTopFrame.addAlignmentPanel(topPanel, false);
3142 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3148 topFrame.getAlignPanels().clear();
3149 bottomFrame.getAlignPanels().clear();
3156 * The dust settles...give focus to the tab we did this from.
3158 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3161 public static groovy.ui.Console getGroovyConsole()
3163 return groovyConsole;
3167 * handles the payload of a drag and drop event.
3169 * TODO refactor to desktop utilities class
3172 * - Data source strings extracted from the drop event
3174 * - protocol for each data source extracted from the drop event
3178 * - the payload from the drop event
3181 public static void transferFromDropTarget(List<Object> files,
3182 List<DataSourceType> protocols, DropTargetDropEvent evt,
3183 Transferable t) throws Exception
3186 DataFlavor uriListFlavor = new DataFlavor(
3187 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3190 urlFlavour = new DataFlavor(
3191 "application/x-java-url; class=java.net.URL");
3192 } catch (ClassNotFoundException cfe)
3194 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3198 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3203 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3204 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3205 // means url may be null.
3208 protocols.add(DataSourceType.URL);
3209 files.add(url.toString());
3210 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3211 + files.get(files.size() - 1));
3216 if (Platform.isAMacAndNotJS())
3219 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3222 } catch (Throwable ex)
3224 jalview.bin.Console.debug("URL drop handler failed.", ex);
3227 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3229 // Works on Windows and MacOSX
3230 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3231 for (Object file : (List) t
3232 .getTransferData(DataFlavor.javaFileListFlavor))
3235 protocols.add(DataSourceType.FILE);
3240 // Unix like behaviour
3241 boolean added = false;
3243 if (t.isDataFlavorSupported(uriListFlavor))
3245 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3246 // This is used by Unix drag system
3247 data = (String) t.getTransferData(uriListFlavor);
3251 // fallback to text: workaround - on OSX where there's a JVM bug
3253 .debug("standard URIListFlavor failed. Trying text");
3254 // try text fallback
3255 DataFlavor textDf = new DataFlavor(
3256 "text/plain;class=java.lang.String");
3257 if (t.isDataFlavorSupported(textDf))
3259 data = (String) t.getTransferData(textDf);
3262 jalview.bin.Console.debug("Plain text drop content returned "
3263 + (data == null ? "Null - failed" : data));
3268 while (protocols.size() < files.size())
3270 jalview.bin.Console.debug("Adding missing FILE protocol for "
3271 + files.get(protocols.size()));
3272 protocols.add(DataSourceType.FILE);
3274 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3275 data, "\r\n"); st.hasMoreTokens();)
3278 String s = st.nextToken();
3279 if (s.startsWith("#"))
3281 // the line is a comment (as per the RFC 2483)
3284 java.net.URI uri = new java.net.URI(s);
3285 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3287 protocols.add(DataSourceType.URL);
3288 files.add(uri.toString());
3292 // otherwise preserve old behaviour: catch all for file objects
3293 java.io.File file = new java.io.File(uri);
3294 protocols.add(DataSourceType.FILE);
3295 files.add(file.toString());
3300 if (jalview.bin.Console.isDebugEnabled())
3302 if (data == null || !added)
3305 if (t.getTransferDataFlavors() != null
3306 && t.getTransferDataFlavors().length > 0)
3308 jalview.bin.Console.debug(
3309 "Couldn't resolve drop data. Here are the supported flavors:");
3310 for (DataFlavor fl : t.getTransferDataFlavors())
3312 jalview.bin.Console.debug(
3313 "Supported transfer dataflavor: " + fl.toString());
3314 Object df = t.getTransferData(fl);
3317 jalview.bin.Console.debug("Retrieves: " + df);
3321 jalview.bin.Console.debug("Retrieved nothing");
3328 .debug("Couldn't resolve dataflavor for drop: "
3334 if (Platform.isWindowsAndNotJS())
3337 .debug("Scanning dropped content for Windows Link Files");
3339 // resolve any .lnk files in the file drop
3340 for (int f = 0; f < files.size(); f++)
3342 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3343 if (protocols.get(f).equals(DataSourceType.FILE)
3344 && (source.endsWith(".lnk") || source.endsWith(".url")
3345 || source.endsWith(".site")))
3349 Object obj = files.get(f);
3350 File lf = (obj instanceof File ? (File) obj
3351 : new File((String) obj));
3352 // process link file to get a URL
3353 jalview.bin.Console.debug("Found potential link file: " + lf);
3354 WindowsShortcut wscfile = new WindowsShortcut(lf);
3355 String fullname = wscfile.getRealFilename();
3356 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3357 files.set(f, fullname);
3358 jalview.bin.Console.debug("Parsed real filename " + fullname
3359 + " to extract protocol: " + protocols.get(f));
3360 } catch (Exception ex)
3362 jalview.bin.Console.error(
3363 "Couldn't parse " + files.get(f) + " as a link file.",
3372 * Sets the Preferences property for experimental features to True or False
3373 * depending on the state of the controlling menu item
3376 protected void showExperimental_actionPerformed(boolean selected)
3378 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3382 * Answers a (possibly empty) list of any structure viewer frames (currently
3383 * for either Jmol or Chimera) which are currently open. This may optionally
3384 * be restricted to viewers of a specified class, or viewers linked to a
3385 * specified alignment panel.
3388 * if not null, only return viewers linked to this panel
3389 * @param structureViewerClass
3390 * if not null, only return viewers of this class
3393 public List<StructureViewerBase> getStructureViewers(
3394 AlignmentPanel apanel,
3395 Class<? extends StructureViewerBase> structureViewerClass)
3397 List<StructureViewerBase> result = new ArrayList<>();
3398 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3400 for (JInternalFrame frame : frames)
3402 if (frame instanceof StructureViewerBase)
3404 if (structureViewerClass == null
3405 || structureViewerClass.isInstance(frame))
3408 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3410 result.add((StructureViewerBase) frame);
3418 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3420 private static boolean debugScaleMessageDone = false;
3422 public static void debugScaleMessage(Graphics g)
3424 if (debugScaleMessageDone)
3428 // output used by tests to check HiDPI scaling settings in action
3431 Graphics2D gg = (Graphics2D) g;
3434 AffineTransform t = gg.getTransform();
3435 double scaleX = t.getScaleX();
3436 double scaleY = t.getScaleY();
3437 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3438 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3439 debugScaleMessageDone = true;
3443 jalview.bin.Console.debug("Desktop graphics null");
3445 } catch (Exception e)
3447 jalview.bin.Console.debug(Cache.getStackTraceString(e));