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.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 protected 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 jalview.bin.Console.info(
417 "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.");
420 Toolkit xToolkit = Toolkit.getDefaultToolkit();
421 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
422 Field awtAppClassNameField = null;
424 if (Arrays.stream(declaredFields)
425 .anyMatch(f -> f.getName().equals("awtAppClassName")))
427 awtAppClassNameField = xToolkit.getClass()
428 .getDeclaredField("awtAppClassName");
431 String title = ChannelProperties.getProperty("app_name");
432 if (awtAppClassNameField != null)
434 awtAppClassNameField.setAccessible(true);
435 awtAppClassNameField.set(xToolkit, title);
439 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
441 } catch (Exception e)
443 jalview.bin.Console.debug("Error setting awtAppClassName");
444 jalview.bin.Console.trace(Cache.getStackTraceString(e));
449 * APQHandlers sets handlers for About, Preferences and Quit actions
450 * peculiar to macOS's application menu. APQHandlers will check to see if a
451 * handler is supported before setting it.
455 APQHandlers.setAPQHandlers(this);
456 } catch (Exception e)
458 System.out.println("Cannot set APQHandlers");
459 // e.printStackTrace();
460 } catch (Throwable t)
463 .warn("Error setting APQHandlers: " + t.toString());
464 jalview.bin.Console.trace(Cache.getStackTraceString(t));
466 setIconImages(ChannelProperties.getIconList());
468 addWindowListener(new WindowAdapter()
472 public void windowClosing(WindowEvent ev)
478 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
480 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
481 desktop = new MyDesktopPane(selmemusage);
483 showMemusage.setSelected(selmemusage);
484 desktop.setBackground(Color.white);
486 getContentPane().setLayout(new BorderLayout());
487 // alternate config - have scrollbars - see notes in JAL-153
488 // JScrollPane sp = new JScrollPane();
489 // sp.getViewport().setView(desktop);
490 // getContentPane().add(sp, BorderLayout.CENTER);
492 // BH 2018 - just an experiment to try unclipped JInternalFrames.
495 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
498 getContentPane().add(desktop, BorderLayout.CENTER);
499 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
501 // This line prevents Windows Look&Feel resizing all new windows to maximum
502 // if previous window was maximised
503 desktop.setDesktopManager(new MyDesktopManager(
504 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
505 : Platform.isAMacAndNotJS()
506 ? new AquaInternalFrameManager(
507 desktop.getDesktopManager())
508 : desktop.getDesktopManager())));
510 Rectangle dims = getLastKnownDimensions("");
517 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
518 int xPos = Math.max(5, (screenSize.width - 900) / 2);
519 int yPos = Math.max(5, (screenSize.height - 650) / 2);
520 setBounds(xPos, yPos, 900, 650);
523 if (!Platform.isJS())
530 jconsole = new Console(this, showjconsole);
531 jconsole.setHeader(Cache.getVersionDetailsForConsole());
532 showConsole(showjconsole);
534 showNews.setVisible(false);
536 experimentalFeatures.setSelected(showExperimental());
538 getIdentifiersOrgData();
542 // Spawn a thread that shows the splashscreen
545 SwingUtilities.invokeLater(new Runnable()
550 new SplashScreen(true);
555 // Thread off a new instance of the file chooser - this reduces the time
557 // takes to open it later on.
558 new Thread(new Runnable()
563 jalview.bin.Console.debug("Filechooser init thread started.");
564 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
565 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
567 jalview.bin.Console.debug("Filechooser init thread finished.");
570 // Add the service change listener
571 changeSupport.addJalviewPropertyChangeListener("services",
572 new PropertyChangeListener()
576 public void propertyChange(PropertyChangeEvent evt)
579 .debug("Firing service changed event for "
580 + evt.getNewValue());
581 JalviewServicesChanged(evt);
586 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
588 this.addWindowListener(new WindowAdapter()
591 public void windowClosing(WindowEvent evt)
598 this.addMouseListener(ma = new MouseAdapter()
601 public void mousePressed(MouseEvent evt)
603 if (evt.isPopupTrigger()) // Mac
605 showPasteMenu(evt.getX(), evt.getY());
610 public void mouseReleased(MouseEvent evt)
612 if (evt.isPopupTrigger()) // Windows
614 showPasteMenu(evt.getX(), evt.getY());
618 desktop.addMouseListener(ma);
622 * Answers true if user preferences to enable experimental features is True
627 public boolean showExperimental()
629 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
630 Boolean.FALSE.toString());
631 return Boolean.valueOf(experimental).booleanValue();
634 public void doConfigureStructurePrefs()
636 // configure services
637 StructureSelectionManager ssm = StructureSelectionManager
638 .getStructureSelectionManager(this);
639 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
641 ssm.setAddTempFacAnnot(
642 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
643 ssm.setProcessSecondaryStructure(
644 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
645 // JAL-3915 - RNAView is no longer an option so this has no effect
646 ssm.setSecStructServices(
647 Cache.getDefault(Preferences.USE_RNAVIEW, false));
651 ssm.setAddTempFacAnnot(false);
652 ssm.setProcessSecondaryStructure(false);
653 ssm.setSecStructServices(false);
657 public void checkForNews()
659 final Desktop me = this;
660 // Thread off the news reader, in case there are connection problems.
661 new Thread(new Runnable()
666 jalview.bin.Console.debug("Starting news thread.");
667 jvnews = new BlogReader(me);
668 showNews.setVisible(true);
669 jalview.bin.Console.debug("Completed news thread.");
674 public void getIdentifiersOrgData()
676 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
677 {// Thread off the identifiers fetcher
678 new Thread(new Runnable()
684 .debug("Downloading data from identifiers.org");
687 UrlDownloadClient.download(IdOrgSettings.getUrl(),
688 IdOrgSettings.getDownloadLocation());
689 } catch (IOException e)
692 .debug("Exception downloading identifiers.org data"
702 protected void showNews_actionPerformed(ActionEvent e)
704 showNews(showNews.isSelected());
707 void showNews(boolean visible)
709 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
710 showNews.setSelected(visible);
711 if (visible && !jvnews.isVisible())
713 new Thread(new Runnable()
718 long now = System.currentTimeMillis();
719 Desktop.instance.setProgressBar(
720 MessageManager.getString("status.refreshing_news"), now);
721 jvnews.refreshNews();
722 Desktop.instance.setProgressBar(null, now);
730 * recover the last known dimensions for a jalview window
733 * - empty string is desktop, all other windows have unique prefix
734 * @return null or last known dimensions scaled to current geometry (if last
735 * window geom was known)
737 Rectangle getLastKnownDimensions(String windowName)
739 // TODO: lock aspect ratio for scaling desktop Bug #0058199
740 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
741 String x = Cache.getProperty(windowName + "SCREEN_X");
742 String y = Cache.getProperty(windowName + "SCREEN_Y");
743 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
744 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
745 if ((x != null) && (y != null) && (width != null) && (height != null))
747 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
748 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
749 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
751 // attempt #1 - try to cope with change in screen geometry - this
752 // version doesn't preserve original jv aspect ratio.
753 // take ratio of current screen size vs original screen size.
754 double sw = ((1f * screenSize.width) / (1f * Integer
755 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
756 double sh = ((1f * screenSize.height) / (1f * Integer
757 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
758 // rescale the bounds depending upon the current screen geometry.
759 ix = (int) (ix * sw);
760 iw = (int) (iw * sw);
761 iy = (int) (iy * sh);
762 ih = (int) (ih * sh);
763 while (ix >= screenSize.width)
765 jalview.bin.Console.debug(
766 "Window geometry location recall error: shifting horizontal to within screenbounds.");
767 ix -= screenSize.width;
769 while (iy >= screenSize.height)
771 jalview.bin.Console.debug(
772 "Window geometry location recall error: shifting vertical to within screenbounds.");
773 iy -= screenSize.height;
775 jalview.bin.Console.debug(
776 "Got last known dimensions for " + windowName + ": x:" + ix
777 + " y:" + iy + " width:" + iw + " height:" + ih);
779 // return dimensions for new instance
780 return new Rectangle(ix, iy, iw, ih);
785 void showPasteMenu(int x, int y)
787 JPopupMenu popup = new JPopupMenu();
788 JMenuItem item = new JMenuItem(
789 MessageManager.getString("label.paste_new_window"));
790 item.addActionListener(new ActionListener()
793 public void actionPerformed(ActionEvent evt)
800 popup.show(this, x, y);
807 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
808 Transferable contents = c.getContents(this);
810 if (contents != null)
812 String file = (String) contents
813 .getTransferData(DataFlavor.stringFlavor);
815 FileFormatI format = new IdentifyFile().identify(file,
816 DataSourceType.PASTE);
818 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
821 } catch (Exception ex)
824 "Unable to paste alignment from system clipboard:\n" + ex);
829 * Adds and opens the given frame to the desktop
840 public static synchronized void addInternalFrame(
841 final JInternalFrame frame, String title, int w, int h)
843 addInternalFrame(frame, title, true, w, h, true, false);
847 * Add an internal frame to the Jalview desktop
854 * When true, display frame immediately, otherwise, caller must call
855 * setVisible themselves.
861 public static synchronized void addInternalFrame(
862 final JInternalFrame frame, String title, boolean makeVisible,
865 addInternalFrame(frame, title, makeVisible, w, h, true, false);
869 * Add an internal frame to the Jalview desktop and make it visible
882 public static synchronized void addInternalFrame(
883 final JInternalFrame frame, String title, int w, int h,
886 addInternalFrame(frame, title, true, w, h, resizable, false);
890 * Add an internal frame to the Jalview desktop
897 * When true, display frame immediately, otherwise, caller must call
898 * setVisible themselves.
905 * @param ignoreMinSize
906 * Do not set the default minimum size for frame
908 public static synchronized void addInternalFrame(
909 final JInternalFrame frame, String title, boolean makeVisible,
910 int w, int h, boolean resizable, boolean ignoreMinSize)
913 // TODO: allow callers to determine X and Y position of frame (eg. via
915 // TODO: consider fixing method to update entries in the window submenu with
916 // the current window title
918 frame.setTitle(title);
919 if (frame.getWidth() < 1 || frame.getHeight() < 1)
923 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
924 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
925 // IF JALVIEW IS RUNNING HEADLESS
926 // ///////////////////////////////////////////////
927 if (instance == null || (System.getProperty("java.awt.headless") != null
928 && System.getProperty("java.awt.headless").equals("true")))
937 frame.setMinimumSize(
938 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
940 // Set default dimension for Alignment Frame window.
941 // The Alignment Frame window could be added from a number of places,
943 // I did this here in order not to miss out on any Alignment frame.
944 if (frame instanceof AlignFrame)
946 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
947 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
951 frame.setVisible(makeVisible);
952 frame.setClosable(true);
953 frame.setResizable(resizable);
954 frame.setMaximizable(resizable);
955 frame.setIconifiable(resizable);
956 frame.setOpaque(Platform.isJS());
958 if (frame.getX() < 1 && frame.getY() < 1)
960 frame.setLocation(xOffset * openFrameCount,
961 yOffset * ((openFrameCount - 1) % 10) + yOffset);
965 * add an entry for the new frame in the Window menu (and remove it when the
968 final JMenuItem menuItem = new JMenuItem(title);
969 frame.addInternalFrameListener(new InternalFrameAdapter()
972 public void internalFrameActivated(InternalFrameEvent evt)
974 JInternalFrame itf = desktop.getSelectedFrame();
977 if (itf instanceof AlignFrame)
979 Jalview.setCurrentAlignFrame((AlignFrame) itf);
986 public void internalFrameClosed(InternalFrameEvent evt)
988 PaintRefresher.RemoveComponent(frame);
991 * defensive check to prevent frames being added half off the window
993 if (openFrameCount > 0)
999 * ensure no reference to alignFrame retained by menu item listener
1001 if (menuItem.getActionListeners().length > 0)
1003 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1005 windowMenu.remove(menuItem);
1009 menuItem.addActionListener(new ActionListener()
1012 public void actionPerformed(ActionEvent e)
1016 frame.setSelected(true);
1017 frame.setIcon(false);
1018 } catch (java.beans.PropertyVetoException ex)
1025 setKeyBindings(frame);
1029 windowMenu.add(menuItem);
1034 frame.setSelected(true);
1035 frame.requestFocus();
1036 } catch (java.beans.PropertyVetoException ve)
1038 } catch (java.lang.ClassCastException cex)
1040 jalview.bin.Console.warn(
1041 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1047 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1052 private static void setKeyBindings(JInternalFrame frame)
1054 @SuppressWarnings("serial")
1055 final Action closeAction = new AbstractAction()
1058 public void actionPerformed(ActionEvent e)
1065 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1067 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1068 InputEvent.CTRL_DOWN_MASK);
1069 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1070 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1072 InputMap inputMap = frame
1073 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1074 String ctrlW = ctrlWKey.toString();
1075 inputMap.put(ctrlWKey, ctrlW);
1076 inputMap.put(cmdWKey, ctrlW);
1078 ActionMap actionMap = frame.getActionMap();
1079 actionMap.put(ctrlW, closeAction);
1083 public void lostOwnership(Clipboard clipboard, Transferable contents)
1087 Desktop.jalviewClipboard = null;
1090 internalCopy = false;
1094 public void dragEnter(DropTargetDragEvent evt)
1099 public void dragExit(DropTargetEvent evt)
1104 public void dragOver(DropTargetDragEvent evt)
1109 public void dropActionChanged(DropTargetDragEvent evt)
1120 public void drop(DropTargetDropEvent evt)
1122 boolean success = true;
1123 // JAL-1552 - acceptDrop required before getTransferable call for
1124 // Java's Transferable for native dnd
1125 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1126 Transferable t = evt.getTransferable();
1127 List<Object> files = new ArrayList<>();
1128 List<DataSourceType> protocols = new ArrayList<>();
1132 Desktop.transferFromDropTarget(files, protocols, evt, t);
1133 } catch (Exception e)
1135 e.printStackTrace();
1143 for (int i = 0; i < files.size(); i++)
1145 // BH 2018 File or String
1146 Object file = files.get(i);
1147 String fileName = file.toString();
1148 DataSourceType protocol = (protocols == null)
1149 ? DataSourceType.FILE
1151 FileFormatI format = null;
1153 if (fileName.endsWith(".jar"))
1155 format = FileFormat.Jalview;
1160 format = new IdentifyFile().identify(file, protocol);
1162 if (file instanceof File)
1164 Platform.cacheFileData((File) file);
1166 new FileLoader().LoadFile(null, file, protocol, format);
1169 } catch (Exception ex)
1174 evt.dropComplete(success); // need this to ensure input focus is properly
1175 // transfered to any new windows created
1185 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1187 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1188 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1189 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1190 BackupFiles.getEnabled());
1192 chooser.setFileView(new JalviewFileView());
1193 chooser.setDialogTitle(
1194 MessageManager.getString("label.open_local_file"));
1195 chooser.setToolTipText(MessageManager.getString("action.open"));
1197 chooser.setResponseHandler(0, new Runnable()
1202 File selectedFile = chooser.getSelectedFile();
1203 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1205 FileFormatI format = chooser.getSelectedFormat();
1208 * Call IdentifyFile to verify the file contains what its extension implies.
1209 * Skip this step for dynamically added file formats, because IdentifyFile does
1210 * not know how to recognise them.
1212 if (FileFormats.getInstance().isIdentifiable(format))
1216 format = new IdentifyFile().identify(selectedFile,
1217 DataSourceType.FILE);
1218 } catch (FileFormatException e)
1220 // format = null; //??
1224 new FileLoader().LoadFile(viewport, selectedFile,
1225 DataSourceType.FILE, format);
1228 chooser.showOpenDialog(this);
1232 * Shows a dialog for input of a URL at which to retrieve alignment data
1237 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1239 // This construct allows us to have a wider textfield
1241 JLabel label = new JLabel(
1242 MessageManager.getString("label.input_file_url"));
1244 JPanel panel = new JPanel(new GridLayout(2, 1));
1248 * the URL to fetch is input in Java: an editable combobox with history JS:
1249 * (pending JAL-3038) a plain text field
1252 String urlBase = "https://www.";
1253 if (Platform.isJS())
1255 history = new JTextField(urlBase, 35);
1264 JComboBox<String> asCombo = new JComboBox<>();
1265 asCombo.setPreferredSize(new Dimension(400, 20));
1266 asCombo.setEditable(true);
1267 asCombo.addItem(urlBase);
1268 String historyItems = Cache.getProperty("RECENT_URL");
1269 if (historyItems != null)
1271 for (String token : historyItems.split("\\t"))
1273 asCombo.addItem(token);
1280 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1281 MessageManager.getString("action.cancel") };
1282 Runnable action = new Runnable()
1287 @SuppressWarnings("unchecked")
1288 String url = (history instanceof JTextField
1289 ? ((JTextField) history).getText()
1290 : ((JComboBox<String>) history).getEditor().getItem()
1291 .toString().trim());
1293 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1295 if (viewport != null)
1297 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1298 FileFormat.Jalview);
1302 new FileLoader().LoadFile(url, DataSourceType.URL,
1303 FileFormat.Jalview);
1308 FileFormatI format = null;
1311 format = new IdentifyFile().identify(url, DataSourceType.URL);
1312 } catch (FileFormatException e)
1314 // TODO revise error handling, distinguish between
1315 // URL not found and response not valid
1320 String msg = MessageManager
1321 .formatMessage("label.couldnt_locate", url);
1322 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1323 MessageManager.getString("label.url_not_found"),
1324 JvOptionPane.WARNING_MESSAGE);
1329 if (viewport != null)
1331 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1336 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1341 String dialogOption = MessageManager
1342 .getString("label.input_alignment_from_url");
1343 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1344 .showInternalDialog(panel, dialogOption,
1345 JvOptionPane.YES_NO_CANCEL_OPTION,
1346 JvOptionPane.PLAIN_MESSAGE, null, options,
1347 MessageManager.getString("action.ok"));
1351 * Opens the CutAndPaste window for the user to paste an alignment in to
1354 * - if not null, the pasted alignment is added to the current
1355 * alignment; if null, to a new alignment window
1358 public void inputTextboxMenuItem_actionPerformed(
1359 AlignmentViewPanel viewPanel)
1361 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1362 cap.setForInput(viewPanel);
1363 Desktop.addInternalFrame(cap,
1364 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1374 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1375 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1376 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1377 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1378 getWidth(), getHeight()));
1380 if (jconsole != null)
1382 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1383 jconsole.stopConsole();
1387 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1390 if (dialogExecutor != null)
1392 dialogExecutor.shutdownNow();
1394 closeAll_actionPerformed(null);
1396 if (groovyConsole != null)
1398 // suppress a possible repeat prompt to save script
1399 groovyConsole.setDirty(false);
1400 groovyConsole.exit();
1405 private void storeLastKnownDimensions(String string, Rectangle jc)
1407 jalview.bin.Console.debug("Storing last known dimensions for " + string
1408 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1409 + " height:" + jc.height);
1411 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1412 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1413 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1414 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1424 public void aboutMenuItem_actionPerformed(ActionEvent e)
1426 new Thread(new Runnable()
1431 new SplashScreen(false);
1437 * Returns the html text for the About screen, including any available version
1438 * number, build details, author details and citation reference, but without
1439 * the enclosing {@code html} tags
1443 public String getAboutMessage()
1445 StringBuilder message = new StringBuilder(1024);
1446 message.append("<div style=\"font-family: sans-serif;\">")
1447 .append("<h1><strong>Version: ")
1448 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1449 .append("<strong>Built: <em>")
1450 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1451 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1452 .append("</strong>");
1454 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1455 if (latestVersion.equals("Checking"))
1457 // JBP removed this message for 2.11: May be reinstated in future version
1458 // message.append("<br>...Checking latest version...</br>");
1460 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1462 boolean red = false;
1463 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1464 .indexOf("automated build") == -1)
1467 // Displayed when code version and jnlp version do not match and code
1468 // version is not a development build
1469 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1472 message.append("<br>!! Version ")
1473 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1474 .append(" is available for download from ")
1475 .append(Cache.getDefault("www.jalview.org",
1476 "https://www.jalview.org"))
1480 message.append("</div>");
1483 message.append("<br>Authors: ");
1484 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1485 message.append(CITATION);
1487 message.append("</div>");
1489 return message.toString();
1493 * Action on requesting Help documentation
1496 public void documentationMenuItem_actionPerformed()
1500 if (Platform.isJS())
1502 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1511 Help.showHelpWindow();
1513 } catch (Exception ex)
1515 System.err.println("Error opening help: " + ex.getMessage());
1520 public void closeAll_actionPerformed(ActionEvent e)
1522 // TODO show a progress bar while closing?
1523 JInternalFrame[] frames = desktop.getAllFrames();
1524 for (int i = 0; i < frames.length; i++)
1528 frames[i].setClosed(true);
1529 } catch (java.beans.PropertyVetoException ex)
1533 Jalview.setCurrentAlignFrame(null);
1534 System.out.println("ALL CLOSED");
1537 * reset state of singleton objects as appropriate (clear down session state
1538 * when all windows are closed)
1540 StructureSelectionManager ssm = StructureSelectionManager
1541 .getStructureSelectionManager(this);
1549 public void raiseRelated_actionPerformed(ActionEvent e)
1551 reorderAssociatedWindows(false, false);
1555 public void minimizeAssociated_actionPerformed(ActionEvent e)
1557 reorderAssociatedWindows(true, false);
1560 void closeAssociatedWindows()
1562 reorderAssociatedWindows(false, true);
1568 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1572 protected void garbageCollect_actionPerformed(ActionEvent e)
1574 // We simply collect the garbage
1575 jalview.bin.Console.debug("Collecting garbage...");
1577 jalview.bin.Console.debug("Finished garbage collection.");
1583 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1587 protected void showMemusage_actionPerformed(ActionEvent e)
1589 desktop.showMemoryUsage(showMemusage.isSelected());
1596 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1600 protected void showConsole_actionPerformed(ActionEvent e)
1602 showConsole(showConsole.isSelected());
1605 Console jconsole = null;
1608 * control whether the java console is visible or not
1612 void showConsole(boolean selected)
1614 // TODO: decide if we should update properties file
1615 if (jconsole != null) // BH 2018
1617 showConsole.setSelected(selected);
1618 Cache.setProperty("SHOW_JAVA_CONSOLE",
1619 Boolean.valueOf(selected).toString());
1620 jconsole.setVisible(selected);
1624 void reorderAssociatedWindows(boolean minimize, boolean close)
1626 JInternalFrame[] frames = desktop.getAllFrames();
1627 if (frames == null || frames.length < 1)
1632 AlignmentViewport source = null, target = null;
1633 if (frames[0] instanceof AlignFrame)
1635 source = ((AlignFrame) frames[0]).getCurrentView();
1637 else if (frames[0] instanceof TreePanel)
1639 source = ((TreePanel) frames[0]).getViewPort();
1641 else if (frames[0] instanceof PCAPanel)
1643 source = ((PCAPanel) frames[0]).av;
1645 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1647 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1652 for (int i = 0; i < frames.length; i++)
1655 if (frames[i] == null)
1659 if (frames[i] instanceof AlignFrame)
1661 target = ((AlignFrame) frames[i]).getCurrentView();
1663 else if (frames[i] instanceof TreePanel)
1665 target = ((TreePanel) frames[i]).getViewPort();
1667 else if (frames[i] instanceof PCAPanel)
1669 target = ((PCAPanel) frames[i]).av;
1671 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1673 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1676 if (source == target)
1682 frames[i].setClosed(true);
1686 frames[i].setIcon(minimize);
1689 frames[i].toFront();
1693 } catch (java.beans.PropertyVetoException ex)
1708 protected void preferences_actionPerformed(ActionEvent e)
1710 Preferences.openPreferences();
1714 * Prompts the user to choose a file and then saves the Jalview state as a
1715 * Jalview project file
1718 public void saveState_actionPerformed()
1720 saveState_actionPerformed(false);
1723 public void saveState_actionPerformed(boolean saveAs)
1725 java.io.File projectFile = getProjectFile();
1726 // autoSave indicates we already have a file and don't need to ask
1727 boolean autoSave = projectFile != null && !saveAs
1728 && BackupFiles.getEnabled();
1730 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1731 // saveAs="+saveAs+", Backups
1732 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1734 boolean approveSave = false;
1737 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1740 chooser.setFileView(new JalviewFileView());
1741 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1743 int value = chooser.showSaveDialog(this);
1745 if (value == JalviewFileChooser.APPROVE_OPTION)
1747 projectFile = chooser.getSelectedFile();
1748 setProjectFile(projectFile);
1753 if (approveSave || autoSave)
1755 final Desktop me = this;
1756 final java.io.File chosenFile = projectFile;
1757 new Thread(new Runnable()
1762 // TODO: refactor to Jalview desktop session controller action.
1763 setProgressBar(MessageManager.formatMessage(
1764 "label.saving_jalview_project", new Object[]
1765 { chosenFile.getName() }), chosenFile.hashCode());
1766 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1767 // TODO catch and handle errors for savestate
1768 // TODO prevent user from messing with the Desktop whilst we're saving
1771 boolean doBackup = BackupFiles.getEnabled();
1772 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1775 new Jalview2XML().saveState(
1776 doBackup ? backupfiles.getTempFile() : chosenFile);
1780 backupfiles.setWriteSuccess(true);
1781 backupfiles.rollBackupsAndRenameTempFile();
1783 } catch (OutOfMemoryError oom)
1785 new OOMWarning("Whilst saving current state to "
1786 + chosenFile.getName(), oom);
1787 } catch (Exception ex)
1789 jalview.bin.Console.error("Problems whilst trying to save to "
1790 + chosenFile.getName(), ex);
1791 JvOptionPane.showMessageDialog(me,
1792 MessageManager.formatMessage(
1793 "label.error_whilst_saving_current_state_to",
1795 { chosenFile.getName() }),
1796 MessageManager.getString("label.couldnt_save_project"),
1797 JvOptionPane.WARNING_MESSAGE);
1799 setProgressBar(null, chosenFile.hashCode());
1806 public void saveAsState_actionPerformed(ActionEvent e)
1808 saveState_actionPerformed(true);
1811 private void setProjectFile(File choice)
1813 this.projectFile = choice;
1816 public File getProjectFile()
1818 return this.projectFile;
1822 * Shows a file chooser dialog and tries to read in the selected file as a
1826 public void loadState_actionPerformed()
1828 final String[] suffix = new String[] { "jvp", "jar" };
1829 final String[] desc = new String[] { "Jalview Project",
1830 "Jalview Project (old)" };
1831 JalviewFileChooser chooser = new JalviewFileChooser(
1832 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1833 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1837 chooser.setFileView(new JalviewFileView());
1838 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1839 chooser.setResponseHandler(0, new Runnable()
1844 File selectedFile = chooser.getSelectedFile();
1845 setProjectFile(selectedFile);
1846 String choice = selectedFile.getAbsolutePath();
1847 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1848 new Thread(new Runnable()
1855 new Jalview2XML().loadJalviewAlign(selectedFile);
1856 } catch (OutOfMemoryError oom)
1858 new OOMWarning("Whilst loading project from " + choice, oom);
1859 } catch (Exception ex)
1861 jalview.bin.Console.error(
1862 "Problems whilst loading project from " + choice, ex);
1863 JvOptionPane.showMessageDialog(Desktop.desktop,
1864 MessageManager.formatMessage(
1865 "label.error_whilst_loading_project_from",
1869 .getString("label.couldnt_load_project"),
1870 JvOptionPane.WARNING_MESSAGE);
1873 }, "Project Loader").start();
1877 chooser.showOpenDialog(this);
1881 public void inputSequence_actionPerformed(ActionEvent e)
1883 new SequenceFetcher(this);
1886 JPanel progressPanel;
1888 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1890 public void startLoading(final Object fileName)
1892 if (fileLoadingCount == 0)
1894 fileLoadingPanels.add(addProgressPanel(MessageManager
1895 .formatMessage("label.loading_file", new Object[]
1901 private JPanel addProgressPanel(String string)
1903 if (progressPanel == null)
1905 progressPanel = new JPanel(new GridLayout(1, 1));
1906 totalProgressCount = 0;
1907 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1909 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1910 JProgressBar progressBar = new JProgressBar();
1911 progressBar.setIndeterminate(true);
1913 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1915 thisprogress.add(progressBar, BorderLayout.CENTER);
1916 progressPanel.add(thisprogress);
1917 ((GridLayout) progressPanel.getLayout()).setRows(
1918 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1919 ++totalProgressCount;
1920 instance.validate();
1921 return thisprogress;
1924 int totalProgressCount = 0;
1926 private void removeProgressPanel(JPanel progbar)
1928 if (progressPanel != null)
1930 synchronized (progressPanel)
1932 progressPanel.remove(progbar);
1933 GridLayout gl = (GridLayout) progressPanel.getLayout();
1934 gl.setRows(gl.getRows() - 1);
1935 if (--totalProgressCount < 1)
1937 this.getContentPane().remove(progressPanel);
1938 progressPanel = null;
1945 public void stopLoading()
1948 if (fileLoadingCount < 1)
1950 while (fileLoadingPanels.size() > 0)
1952 removeProgressPanel(fileLoadingPanels.remove(0));
1954 fileLoadingPanels.clear();
1955 fileLoadingCount = 0;
1960 public static int getViewCount(String alignmentId)
1962 AlignmentViewport[] aps = getViewports(alignmentId);
1963 return (aps == null) ? 0 : aps.length;
1968 * @param alignmentId
1969 * - if null, all sets are returned
1970 * @return all AlignmentPanels concerning the alignmentId sequence set
1972 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1974 if (Desktop.desktop == null)
1976 // no frames created and in headless mode
1977 // TODO: verify that frames are recoverable when in headless mode
1980 List<AlignmentPanel> aps = new ArrayList<>();
1981 AlignFrame[] frames = getAlignFrames();
1986 for (AlignFrame af : frames)
1988 for (AlignmentPanel ap : af.alignPanels)
1990 if (alignmentId == null
1991 || alignmentId.equals(ap.av.getSequenceSetId()))
1997 if (aps.size() == 0)
2001 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2006 * get all the viewports on an alignment.
2008 * @param sequenceSetId
2009 * unique alignment id (may be null - all viewports returned in that
2011 * @return all viewports on the alignment bound to sequenceSetId
2013 public static AlignmentViewport[] getViewports(String sequenceSetId)
2015 List<AlignmentViewport> viewp = new ArrayList<>();
2016 if (desktop != null)
2018 AlignFrame[] frames = Desktop.getAlignFrames();
2020 for (AlignFrame afr : frames)
2022 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2023 .equals(sequenceSetId))
2025 if (afr.alignPanels != null)
2027 for (AlignmentPanel ap : afr.alignPanels)
2029 if (sequenceSetId == null
2030 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2038 viewp.add(afr.getViewport());
2042 if (viewp.size() > 0)
2044 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2051 * Explode the views in the given frame into separate AlignFrame
2055 public static void explodeViews(AlignFrame af)
2057 int size = af.alignPanels.size();
2063 // FIXME: ideally should use UI interface API
2064 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2065 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2066 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2067 for (int i = 0; i < size; i++)
2069 AlignmentPanel ap = af.alignPanels.get(i);
2071 AlignFrame newaf = new AlignFrame(ap);
2073 // transfer reference for existing feature settings to new alignFrame
2074 if (ap == af.alignPanel)
2076 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2078 newaf.featureSettings = viewFeatureSettings;
2080 newaf.setFeatureSettingsGeometry(fsBounds);
2084 * Restore the view's last exploded frame geometry if known. Multiple views from
2085 * one exploded frame share and restore the same (frame) position and size.
2087 Rectangle geometry = ap.av.getExplodedGeometry();
2088 if (geometry != null)
2090 newaf.setBounds(geometry);
2093 ap.av.setGatherViewsHere(false);
2095 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2096 AlignFrame.DEFAULT_HEIGHT);
2097 // and materialise a new feature settings dialog instance for the new
2099 // (closes the old as if 'OK' was pressed)
2100 if (ap == af.alignPanel && newaf.featureSettings != null
2101 && newaf.featureSettings.isOpen()
2102 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2104 newaf.showFeatureSettingsUI();
2108 af.featureSettings = null;
2109 af.alignPanels.clear();
2110 af.closeMenuItem_actionPerformed(true);
2115 * Gather expanded views (separate AlignFrame's) with the same sequence set
2116 * identifier back in to this frame as additional views, and close the
2117 * expanded views. Note the expanded frames may themselves have multiple
2118 * views. We take the lot.
2122 public void gatherViews(AlignFrame source)
2124 source.viewport.setGatherViewsHere(true);
2125 source.viewport.setExplodedGeometry(source.getBounds());
2126 JInternalFrame[] frames = desktop.getAllFrames();
2127 String viewId = source.viewport.getSequenceSetId();
2128 for (int t = 0; t < frames.length; t++)
2130 if (frames[t] instanceof AlignFrame && frames[t] != source)
2132 AlignFrame af = (AlignFrame) frames[t];
2133 boolean gatherThis = false;
2134 for (int a = 0; a < af.alignPanels.size(); a++)
2136 AlignmentPanel ap = af.alignPanels.get(a);
2137 if (viewId.equals(ap.av.getSequenceSetId()))
2140 ap.av.setGatherViewsHere(false);
2141 ap.av.setExplodedGeometry(af.getBounds());
2142 source.addAlignmentPanel(ap, false);
2148 if (af.featureSettings != null && af.featureSettings.isOpen())
2150 if (source.featureSettings == null)
2152 // preserve the feature settings geometry for this frame
2153 source.featureSettings = af.featureSettings;
2154 source.setFeatureSettingsGeometry(
2155 af.getFeatureSettingsGeometry());
2159 // close it and forget
2160 af.featureSettings.close();
2163 af.alignPanels.clear();
2164 af.closeMenuItem_actionPerformed(true);
2169 // refresh the feature setting UI for the source frame if it exists
2170 if (source.featureSettings != null && source.featureSettings.isOpen())
2172 source.showFeatureSettingsUI();
2177 public JInternalFrame[] getAllFrames()
2179 return desktop.getAllFrames();
2183 * Checks the given url to see if it gives a response indicating that the user
2184 * should be informed of a new questionnaire.
2188 public void checkForQuestionnaire(String url)
2190 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2191 // javax.swing.SwingUtilities.invokeLater(jvq);
2192 new Thread(jvq).start();
2195 public void checkURLLinks()
2197 // Thread off the URL link checker
2198 addDialogThread(new Runnable()
2203 if (Cache.getDefault("CHECKURLLINKS", true))
2205 // check what the actual links are - if it's just the default don't
2206 // bother with the warning
2207 List<String> links = Preferences.sequenceUrlLinks
2210 // only need to check links if there is one with a
2211 // SEQUENCE_ID which is not the default EMBL_EBI link
2212 ListIterator<String> li = links.listIterator();
2213 boolean check = false;
2214 List<JLabel> urls = new ArrayList<>();
2215 while (li.hasNext())
2217 String link = li.next();
2218 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2219 && !UrlConstants.isDefaultString(link))
2222 int barPos = link.indexOf("|");
2223 String urlMsg = barPos == -1 ? link
2224 : link.substring(0, barPos) + ": "
2225 + link.substring(barPos + 1);
2226 urls.add(new JLabel(urlMsg));
2234 // ask user to check in case URL links use old style tokens
2235 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2236 JPanel msgPanel = new JPanel();
2237 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2238 msgPanel.add(Box.createVerticalGlue());
2239 JLabel msg = new JLabel(MessageManager
2240 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2241 JLabel msg2 = new JLabel(MessageManager
2242 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2244 for (JLabel url : urls)
2250 final JCheckBox jcb = new JCheckBox(
2251 MessageManager.getString("label.do_not_display_again"));
2252 jcb.addActionListener(new ActionListener()
2255 public void actionPerformed(ActionEvent e)
2257 // update Cache settings for "don't show this again"
2258 boolean showWarningAgain = !jcb.isSelected();
2259 Cache.setProperty("CHECKURLLINKS",
2260 Boolean.valueOf(showWarningAgain).toString());
2265 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2267 .getString("label.SEQUENCE_ID_no_longer_used"),
2268 JvOptionPane.WARNING_MESSAGE);
2275 * Proxy class for JDesktopPane which optionally displays the current memory
2276 * usage and highlights the desktop area with a red bar if free memory runs
2281 public class MyDesktopPane extends JDesktopPane implements Runnable
2283 private static final float ONE_MB = 1048576f;
2285 boolean showMemoryUsage = false;
2289 java.text.NumberFormat df;
2291 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2294 public MyDesktopPane(boolean showMemoryUsage)
2296 showMemoryUsage(showMemoryUsage);
2299 public void showMemoryUsage(boolean showMemory)
2301 this.showMemoryUsage = showMemory;
2304 Thread worker = new Thread(this);
2310 public boolean isShowMemoryUsage()
2312 return showMemoryUsage;
2318 df = java.text.NumberFormat.getNumberInstance();
2319 df.setMaximumFractionDigits(2);
2320 runtime = Runtime.getRuntime();
2322 while (showMemoryUsage)
2326 maxMemory = runtime.maxMemory() / ONE_MB;
2327 allocatedMemory = runtime.totalMemory() / ONE_MB;
2328 freeMemory = runtime.freeMemory() / ONE_MB;
2329 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2331 percentUsage = (totalFreeMemory / maxMemory) * 100;
2333 // if (percentUsage < 20)
2335 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2337 // instance.set.setBorder(border1);
2340 // sleep after showing usage
2342 } catch (Exception ex)
2344 ex.printStackTrace();
2350 public void paintComponent(Graphics g)
2352 if (showMemoryUsage && g != null && df != null)
2354 if (percentUsage < 20)
2356 g.setColor(Color.red);
2358 FontMetrics fm = g.getFontMetrics();
2361 g.drawString(MessageManager.formatMessage("label.memory_stats",
2363 { df.format(totalFreeMemory), df.format(maxMemory),
2364 df.format(percentUsage) }),
2365 10, getHeight() - fm.getHeight());
2369 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2370 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2375 * Accessor method to quickly get all the AlignmentFrames loaded.
2377 * @return an array of AlignFrame, or null if none found
2379 public static AlignFrame[] getAlignFrames()
2381 if (Jalview.isHeadlessMode())
2383 // Desktop.desktop is null in headless mode
2384 return new AlignFrame[] { Jalview.currentAlignFrame };
2387 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2393 List<AlignFrame> avp = new ArrayList<>();
2395 for (int i = frames.length - 1; i > -1; i--)
2397 if (frames[i] instanceof AlignFrame)
2399 avp.add((AlignFrame) frames[i]);
2401 else if (frames[i] instanceof SplitFrame)
2404 * Also check for a split frame containing an AlignFrame
2406 GSplitFrame sf = (GSplitFrame) frames[i];
2407 if (sf.getTopFrame() instanceof AlignFrame)
2409 avp.add((AlignFrame) sf.getTopFrame());
2411 if (sf.getBottomFrame() instanceof AlignFrame)
2413 avp.add((AlignFrame) sf.getBottomFrame());
2417 if (avp.size() == 0)
2421 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2426 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2430 public GStructureViewer[] getJmols()
2432 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2438 List<GStructureViewer> avp = new ArrayList<>();
2440 for (int i = frames.length - 1; i > -1; i--)
2442 if (frames[i] instanceof AppJmol)
2444 GStructureViewer af = (GStructureViewer) frames[i];
2448 if (avp.size() == 0)
2452 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2457 * Add Groovy Support to Jalview
2460 public void groovyShell_actionPerformed()
2464 openGroovyConsole();
2465 } catch (Exception ex)
2467 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2468 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2470 MessageManager.getString("label.couldnt_create_groovy_shell"),
2471 MessageManager.getString("label.groovy_support_failed"),
2472 JvOptionPane.ERROR_MESSAGE);
2477 * Open the Groovy console
2479 void openGroovyConsole()
2481 if (groovyConsole == null)
2483 groovyConsole = new groovy.ui.Console();
2484 groovyConsole.setVariable("Jalview", this);
2485 groovyConsole.run();
2488 * We allow only one console at a time, so that AlignFrame menu option
2489 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2490 * enable 'Run script', when the console is opened, and the reverse when it is
2493 Window window = (Window) groovyConsole.getFrame();
2494 window.addWindowListener(new WindowAdapter()
2497 public void windowClosed(WindowEvent e)
2500 * rebind CMD-Q from Groovy Console to Jalview Quit
2503 enableExecuteGroovy(false);
2509 * show Groovy console window (after close and reopen)
2511 ((Window) groovyConsole.getFrame()).setVisible(true);
2514 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2515 * opening a second console
2517 enableExecuteGroovy(true);
2521 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2522 * binding when opened
2524 protected void addQuitHandler()
2527 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2529 .getKeyStroke(KeyEvent.VK_Q,
2530 jalview.util.ShortcutKeyMaskExWrapper
2531 .getMenuShortcutKeyMaskEx()),
2533 getRootPane().getActionMap().put("Quit", new AbstractAction()
2536 public void actionPerformed(ActionEvent e)
2544 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2547 * true if Groovy console is open
2549 public void enableExecuteGroovy(boolean enabled)
2552 * disable opening a second Groovy console (or re-enable when the console is
2555 groovyShell.setEnabled(!enabled);
2557 AlignFrame[] alignFrames = getAlignFrames();
2558 if (alignFrames != null)
2560 for (AlignFrame af : alignFrames)
2562 af.setGroovyEnabled(enabled);
2568 * Progress bars managed by the IProgressIndicator method.
2570 private Hashtable<Long, JPanel> progressBars;
2572 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2577 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2580 public void setProgressBar(String message, long id)
2582 if (progressBars == null)
2584 progressBars = new Hashtable<>();
2585 progressBarHandlers = new Hashtable<>();
2588 if (progressBars.get(Long.valueOf(id)) != null)
2590 JPanel panel = progressBars.remove(Long.valueOf(id));
2591 if (progressBarHandlers.contains(Long.valueOf(id)))
2593 progressBarHandlers.remove(Long.valueOf(id));
2595 removeProgressPanel(panel);
2599 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2606 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2607 * jalview.gui.IProgressIndicatorHandler)
2610 public void registerHandler(final long id,
2611 final IProgressIndicatorHandler handler)
2613 if (progressBarHandlers == null
2614 || !progressBars.containsKey(Long.valueOf(id)))
2616 throw new Error(MessageManager.getString(
2617 "error.call_setprogressbar_before_registering_handler"));
2619 progressBarHandlers.put(Long.valueOf(id), handler);
2620 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2621 if (handler.canCancel())
2623 JButton cancel = new JButton(
2624 MessageManager.getString("action.cancel"));
2625 final IProgressIndicator us = this;
2626 cancel.addActionListener(new ActionListener()
2630 public void actionPerformed(ActionEvent e)
2632 handler.cancelActivity(id);
2633 us.setProgressBar(MessageManager
2634 .formatMessage("label.cancelled_params", new Object[]
2635 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2639 progressPanel.add(cancel, BorderLayout.EAST);
2645 * @return true if any progress bars are still active
2648 public boolean operationInProgress()
2650 if (progressBars != null && progressBars.size() > 0)
2658 * This will return the first AlignFrame holding the given viewport instance.
2659 * It will break if there are more than one AlignFrames viewing a particular
2663 * @return alignFrame for viewport
2665 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2667 if (desktop != null)
2669 AlignmentPanel[] aps = getAlignmentPanels(
2670 viewport.getSequenceSetId());
2671 for (int panel = 0; aps != null && panel < aps.length; panel++)
2673 if (aps[panel] != null && aps[panel].av == viewport)
2675 return aps[panel].alignFrame;
2682 public VamsasApplication getVamsasApplication()
2684 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2690 * flag set if jalview GUI is being operated programmatically
2692 private boolean inBatchMode = false;
2695 * check if jalview GUI is being operated programmatically
2697 * @return inBatchMode
2699 public boolean isInBatchMode()
2705 * set flag if jalview GUI is being operated programmatically
2707 * @param inBatchMode
2709 public void setInBatchMode(boolean inBatchMode)
2711 this.inBatchMode = inBatchMode;
2715 * start service discovery and wait till it is done
2717 public void startServiceDiscovery()
2719 startServiceDiscovery(false);
2723 * start service discovery threads - blocking or non-blocking
2727 public void startServiceDiscovery(boolean blocking)
2729 startServiceDiscovery(blocking, false);
2733 * start service discovery threads
2736 * - false means call returns immediately
2737 * @param ignore_SHOW_JWS2_SERVICES_preference
2738 * - when true JABA services are discovered regardless of user's JWS2
2739 * discovery preference setting
2741 public void startServiceDiscovery(boolean blocking,
2742 boolean ignore_SHOW_JWS2_SERVICES_preference)
2744 boolean alive = true;
2745 Thread t0 = null, t1 = null, t2 = null;
2746 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2749 // todo: changesupport handlers need to be transferred
2750 if (discoverer == null)
2752 discoverer = new jalview.ws.jws1.Discoverer();
2753 // register PCS handler for desktop.
2754 discoverer.addPropertyChangeListener(changeSupport);
2756 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2757 // until we phase out completely
2758 (t0 = new Thread(discoverer)).start();
2761 if (ignore_SHOW_JWS2_SERVICES_preference
2762 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2764 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2765 .startDiscoverer(changeSupport);
2769 // TODO: do rest service discovery
2778 } catch (Exception e)
2781 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2782 || (t3 != null && t3.isAlive())
2783 || (t0 != null && t0.isAlive());
2789 * called to check if the service discovery process completed successfully.
2793 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2795 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2797 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2798 .getErrorMessages();
2801 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2803 if (serviceChangedDialog == null)
2805 // only run if we aren't already displaying one of these.
2806 addDialogThread(serviceChangedDialog = new Runnable()
2813 * JalviewDialog jd =new JalviewDialog() {
2815 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2817 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2819 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2821 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2823 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2824 * + " or mis-configured HTTP proxy settings.<br/>" +
2825 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2826 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2827 * true, true, "Web Service Configuration Problem", 450, 400);
2829 * jd.waitForInput();
2831 JvOptionPane.showConfirmDialog(Desktop.desktop,
2832 new JLabel("<html><table width=\"450\"><tr><td>"
2833 + ermsg + "</td></tr></table>"
2834 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2835 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2836 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2837 + " Tools->Preferences dialog box to change them.</p></html>"),
2838 "Web Service Configuration Problem",
2839 JvOptionPane.DEFAULT_OPTION,
2840 JvOptionPane.ERROR_MESSAGE);
2841 serviceChangedDialog = null;
2849 jalview.bin.Console.error(
2850 "Errors reported by JABA discovery service. Check web services preferences.\n"
2857 private Runnable serviceChangedDialog = null;
2860 * start a thread to open a URL in the configured browser. Pops up a warning
2861 * dialog to the user if there is an exception when calling out to the browser
2866 public static void showUrl(final String url)
2868 showUrl(url, Desktop.instance);
2872 * Like showUrl but allows progress handler to be specified
2876 * (null) or object implementing IProgressIndicator
2878 public static void showUrl(final String url,
2879 final IProgressIndicator progress)
2881 new Thread(new Runnable()
2888 if (progress != null)
2890 progress.setProgressBar(MessageManager
2891 .formatMessage("status.opening_params", new Object[]
2892 { url }), this.hashCode());
2894 jalview.util.BrowserLauncher.openURL(url);
2895 } catch (Exception ex)
2897 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2899 .getString("label.web_browser_not_found_unix"),
2900 MessageManager.getString("label.web_browser_not_found"),
2901 JvOptionPane.WARNING_MESSAGE);
2903 ex.printStackTrace();
2905 if (progress != null)
2907 progress.setProgressBar(null, this.hashCode());
2913 public static WsParamSetManager wsparamManager = null;
2915 public static ParamManager getUserParameterStore()
2917 if (wsparamManager == null)
2919 wsparamManager = new WsParamSetManager();
2921 return wsparamManager;
2925 * static hyperlink handler proxy method for use by Jalview's internal windows
2929 public static void hyperlinkUpdate(HyperlinkEvent e)
2931 if (e.getEventType() == EventType.ACTIVATED)
2936 url = e.getURL().toString();
2937 Desktop.showUrl(url);
2938 } catch (Exception x)
2943 .error("Couldn't handle string " + url + " as a URL.");
2945 // ignore any exceptions due to dud links.
2952 * single thread that handles display of dialogs to user.
2954 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2957 * flag indicating if dialogExecutor should try to acquire a permit
2959 private volatile boolean dialogPause = true;
2964 private java.util.concurrent.Semaphore block = new Semaphore(0);
2966 private static groovy.ui.Console groovyConsole;
2969 * add another dialog thread to the queue
2973 public void addDialogThread(final Runnable prompter)
2975 dialogExecutor.submit(new Runnable()
2985 } catch (InterruptedException x)
2989 if (instance == null)
2995 SwingUtilities.invokeAndWait(prompter);
2996 } catch (Exception q)
2998 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3005 public void startDialogQueue()
3007 // set the flag so we don't pause waiting for another permit and semaphore
3008 // the current task to begin
3009 dialogPause = false;
3014 * Outputs an image of the desktop to file in EPS format, after prompting the
3015 * user for choice of Text or Lineart character rendering (unless a preference
3016 * has been set). The file name is generated as
3019 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3023 protected void snapShotWindow_actionPerformed(ActionEvent e)
3025 // currently the menu option to do this is not shown
3028 int width = getWidth();
3029 int height = getHeight();
3031 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3032 ImageWriterI writer = new ImageWriterI()
3035 public void exportImage(Graphics g) throws Exception
3038 jalview.bin.Console.info("Successfully written snapshot to file "
3039 + of.getAbsolutePath());
3042 String title = "View of desktop";
3043 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3045 exporter.doExport(of, this, width, height, title);
3049 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3050 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3051 * and location last time the view was expanded (if any). However it does not
3052 * remember the split pane divider location - this is set to match the
3053 * 'exploding' frame.
3057 public void explodeViews(SplitFrame sf)
3059 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3060 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3061 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3063 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3065 int viewCount = topPanels.size();
3072 * Processing in reverse order works, forwards order leaves the first panels not
3073 * visible. I don't know why!
3075 for (int i = viewCount - 1; i >= 0; i--)
3078 * Make new top and bottom frames. These take over the respective AlignmentPanel
3079 * objects, including their AlignmentViewports, so the cdna/protein
3080 * relationships between the viewports is carried over to the new split frames.
3082 * explodedGeometry holds the (x, y) position of the previously exploded
3083 * SplitFrame, and the (width, height) of the AlignFrame component
3085 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3086 AlignFrame newTopFrame = new AlignFrame(topPanel);
3087 newTopFrame.setSize(oldTopFrame.getSize());
3088 newTopFrame.setVisible(true);
3089 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3090 .getExplodedGeometry();
3091 if (geometry != null)
3093 newTopFrame.setSize(geometry.getSize());
3096 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3097 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3098 newBottomFrame.setSize(oldBottomFrame.getSize());
3099 newBottomFrame.setVisible(true);
3100 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3101 .getExplodedGeometry();
3102 if (geometry != null)
3104 newBottomFrame.setSize(geometry.getSize());
3107 topPanel.av.setGatherViewsHere(false);
3108 bottomPanel.av.setGatherViewsHere(false);
3109 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3111 if (geometry != null)
3113 splitFrame.setLocation(geometry.getLocation());
3115 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3119 * Clear references to the panels (now relocated in the new SplitFrames) before
3120 * closing the old SplitFrame.
3123 bottomPanels.clear();
3128 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3129 * back into the given SplitFrame as additional views. Note that the gathered
3130 * frames may themselves have multiple views.
3134 public void gatherViews(GSplitFrame source)
3137 * special handling of explodedGeometry for a view within a SplitFrame: - it
3138 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3139 * height) of the AlignFrame component
3141 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3142 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3143 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3144 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3145 myBottomFrame.viewport
3146 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3147 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3148 myTopFrame.viewport.setGatherViewsHere(true);
3149 myBottomFrame.viewport.setGatherViewsHere(true);
3150 String topViewId = myTopFrame.viewport.getSequenceSetId();
3151 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3153 JInternalFrame[] frames = desktop.getAllFrames();
3154 for (JInternalFrame frame : frames)
3156 if (frame instanceof SplitFrame && frame != source)
3158 SplitFrame sf = (SplitFrame) frame;
3159 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3160 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3161 boolean gatherThis = false;
3162 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3164 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3165 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3166 if (topViewId.equals(topPanel.av.getSequenceSetId())
3167 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3170 topPanel.av.setGatherViewsHere(false);
3171 bottomPanel.av.setGatherViewsHere(false);
3172 topPanel.av.setExplodedGeometry(
3173 new Rectangle(sf.getLocation(), topFrame.getSize()));
3174 bottomPanel.av.setExplodedGeometry(
3175 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3176 myTopFrame.addAlignmentPanel(topPanel, false);
3177 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3183 topFrame.getAlignPanels().clear();
3184 bottomFrame.getAlignPanels().clear();
3191 * The dust settles...give focus to the tab we did this from.
3193 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3196 public static groovy.ui.Console getGroovyConsole()
3198 return groovyConsole;
3202 * handles the payload of a drag and drop event.
3204 * TODO refactor to desktop utilities class
3207 * - Data source strings extracted from the drop event
3209 * - protocol for each data source extracted from the drop event
3213 * - the payload from the drop event
3216 public static void transferFromDropTarget(List<Object> files,
3217 List<DataSourceType> protocols, DropTargetDropEvent evt,
3218 Transferable t) throws Exception
3221 DataFlavor uriListFlavor = new DataFlavor(
3222 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3225 urlFlavour = new DataFlavor(
3226 "application/x-java-url; class=java.net.URL");
3227 } catch (ClassNotFoundException cfe)
3229 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3233 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3238 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3239 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3240 // means url may be null.
3243 protocols.add(DataSourceType.URL);
3244 files.add(url.toString());
3245 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3246 + files.get(files.size() - 1));
3251 if (Platform.isAMacAndNotJS())
3254 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3257 } catch (Throwable ex)
3259 jalview.bin.Console.debug("URL drop handler failed.", ex);
3262 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3264 // Works on Windows and MacOSX
3265 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3266 for (Object file : (List) t
3267 .getTransferData(DataFlavor.javaFileListFlavor))
3270 protocols.add(DataSourceType.FILE);
3275 // Unix like behaviour
3276 boolean added = false;
3278 if (t.isDataFlavorSupported(uriListFlavor))
3280 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3281 // This is used by Unix drag system
3282 data = (String) t.getTransferData(uriListFlavor);
3286 // fallback to text: workaround - on OSX where there's a JVM bug
3288 .debug("standard URIListFlavor failed. Trying text");
3289 // try text fallback
3290 DataFlavor textDf = new DataFlavor(
3291 "text/plain;class=java.lang.String");
3292 if (t.isDataFlavorSupported(textDf))
3294 data = (String) t.getTransferData(textDf);
3297 jalview.bin.Console.debug("Plain text drop content returned "
3298 + (data == null ? "Null - failed" : data));
3303 while (protocols.size() < files.size())
3305 jalview.bin.Console.debug("Adding missing FILE protocol for "
3306 + files.get(protocols.size()));
3307 protocols.add(DataSourceType.FILE);
3309 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3310 data, "\r\n"); st.hasMoreTokens();)
3313 String s = st.nextToken();
3314 if (s.startsWith("#"))
3316 // the line is a comment (as per the RFC 2483)
3319 java.net.URI uri = new java.net.URI(s);
3320 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3322 protocols.add(DataSourceType.URL);
3323 files.add(uri.toString());
3327 // otherwise preserve old behaviour: catch all for file objects
3328 java.io.File file = new java.io.File(uri);
3329 protocols.add(DataSourceType.FILE);
3330 files.add(file.toString());
3335 if (jalview.bin.Console.isDebugEnabled())
3337 if (data == null || !added)
3340 if (t.getTransferDataFlavors() != null
3341 && t.getTransferDataFlavors().length > 0)
3343 jalview.bin.Console.debug(
3344 "Couldn't resolve drop data. Here are the supported flavors:");
3345 for (DataFlavor fl : t.getTransferDataFlavors())
3347 jalview.bin.Console.debug(
3348 "Supported transfer dataflavor: " + fl.toString());
3349 Object df = t.getTransferData(fl);
3352 jalview.bin.Console.debug("Retrieves: " + df);
3356 jalview.bin.Console.debug("Retrieved nothing");
3363 .debug("Couldn't resolve dataflavor for drop: "
3369 if (Platform.isWindowsAndNotJS())
3372 .debug("Scanning dropped content for Windows Link Files");
3374 // resolve any .lnk files in the file drop
3375 for (int f = 0; f < files.size(); f++)
3377 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3378 if (protocols.get(f).equals(DataSourceType.FILE)
3379 && (source.endsWith(".lnk") || source.endsWith(".url")
3380 || source.endsWith(".site")))
3384 Object obj = files.get(f);
3385 File lf = (obj instanceof File ? (File) obj
3386 : new File((String) obj));
3387 // process link file to get a URL
3388 jalview.bin.Console.debug("Found potential link file: " + lf);
3389 WindowsShortcut wscfile = new WindowsShortcut(lf);
3390 String fullname = wscfile.getRealFilename();
3391 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3392 files.set(f, fullname);
3393 jalview.bin.Console.debug("Parsed real filename " + fullname
3394 + " to extract protocol: " + protocols.get(f));
3395 } catch (Exception ex)
3397 jalview.bin.Console.error(
3398 "Couldn't parse " + files.get(f) + " as a link file.",
3407 * Sets the Preferences property for experimental features to True or False
3408 * depending on the state of the controlling menu item
3411 protected void showExperimental_actionPerformed(boolean selected)
3413 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3417 * Answers a (possibly empty) list of any structure viewer frames (currently
3418 * for either Jmol or Chimera) which are currently open. This may optionally
3419 * be restricted to viewers of a specified class, or viewers linked to a
3420 * specified alignment panel.
3423 * if not null, only return viewers linked to this panel
3424 * @param structureViewerClass
3425 * if not null, only return viewers of this class
3428 public List<StructureViewerBase> getStructureViewers(
3429 AlignmentPanel apanel,
3430 Class<? extends StructureViewerBase> structureViewerClass)
3432 List<StructureViewerBase> result = new ArrayList<>();
3433 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3435 for (JInternalFrame frame : frames)
3437 if (frame instanceof StructureViewerBase)
3439 if (structureViewerClass == null
3440 || structureViewerClass.isInstance(frame))
3443 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3445 result.add((StructureViewerBase) frame);
3453 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3455 private static boolean debugScaleMessageDone = false;
3457 public static void debugScaleMessage(Graphics g)
3459 if (debugScaleMessageDone)
3463 // output used by tests to check HiDPI scaling settings in action
3466 Graphics2D gg = (Graphics2D) g;
3469 AffineTransform t = gg.getTransform();
3470 double scaleX = t.getScaleX();
3471 double scaleY = t.getScaleY();
3472 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3473 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3474 debugScaleMessageDone = true;
3478 jalview.bin.Console.debug("Desktop graphics null");
3480 } catch (Exception e)
3482 jalview.bin.Console.debug(Cache.getStackTraceString(e));