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())
418 Toolkit xToolkit = Toolkit.getDefaultToolkit();
419 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
420 Field awtAppClassNameField = null;
422 if (Arrays.stream(declaredFields)
423 .anyMatch(f -> f.getName().equals("awtAppClassName")))
425 awtAppClassNameField = xToolkit.getClass()
426 .getDeclaredField("awtAppClassName");
429 String title = ChannelProperties.getProperty("app_name");
430 if (awtAppClassNameField != null)
432 awtAppClassNameField.setAccessible(true);
433 awtAppClassNameField.set(xToolkit, title);
437 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
439 } catch (Exception e)
441 jalview.bin.Console.debug("Error setting awtAppClassName");
442 jalview.bin.Console.trace(Cache.getStackTraceString(e));
447 * APQHandlers sets handlers for About, Preferences and Quit actions
448 * peculiar to macOS's application menu. APQHandlers will check to see if a
449 * handler is supported before setting it.
453 APQHandlers.setAPQHandlers(this);
454 } catch (Exception e)
456 System.out.println("Cannot set APQHandlers");
457 // e.printStackTrace();
458 } catch (Throwable t)
461 .warn("Error setting APQHandlers: " + t.toString());
462 jalview.bin.Console.trace(Cache.getStackTraceString(t));
464 setIconImages(ChannelProperties.getIconList());
466 addWindowListener(new WindowAdapter()
470 public void windowClosing(WindowEvent ev)
476 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
478 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
479 desktop = new MyDesktopPane(selmemusage);
481 showMemusage.setSelected(selmemusage);
482 desktop.setBackground(Color.white);
484 this.setIconImages(ChannelProperties.getIconList());
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)
1020 // System.err.println(ex.toString());
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.toLowerCase(Locale.ROOT).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();
1206 openFile(selectedFile, format, viewport);
1209 chooser.showOpenDialog(this);
1212 public void openFile(File selectedFile, FileFormatI format,
1213 AlignViewport viewport)
1217 * Call IdentifyFile to verify the file contains what its extension implies.
1218 * Skip this step for dynamically added file formats, because
1219 * IdentifyFile does not know how to recognise them.
1221 if (FileFormats.getInstance().isIdentifiable(format))
1225 format = new IdentifyFile().identify(selectedFile,
1226 DataSourceType.FILE);
1227 } catch (FileFormatException e)
1229 // format = null; //??
1233 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1238 * Shows a dialog for input of a URL at which to retrieve alignment data
1243 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1245 // This construct allows us to have a wider textfield
1247 JLabel label = new JLabel(
1248 MessageManager.getString("label.input_file_url"));
1250 JPanel panel = new JPanel(new GridLayout(2, 1));
1254 * the URL to fetch is input in Java: an editable combobox with history JS:
1255 * (pending JAL-3038) a plain text field
1258 String urlBase = "https://www.";
1259 if (Platform.isJS())
1261 history = new JTextField(urlBase, 35);
1270 JComboBox<String> asCombo = new JComboBox<>();
1271 asCombo.setPreferredSize(new Dimension(400, 20));
1272 asCombo.setEditable(true);
1273 asCombo.addItem(urlBase);
1274 String historyItems = Cache.getProperty("RECENT_URL");
1275 if (historyItems != null)
1277 for (String token : historyItems.split("\\t"))
1279 asCombo.addItem(token);
1286 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1287 MessageManager.getString("action.cancel") };
1288 Runnable action = new Runnable()
1293 @SuppressWarnings("unchecked")
1294 String url = (history instanceof JTextField
1295 ? ((JTextField) history).getText()
1296 : ((JComboBox<String>) history).getEditor().getItem()
1297 .toString().trim());
1299 if (!loadUrl(url, viewport))
1301 String msg = MessageManager.formatMessage("label.couldnt_locate",
1303 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1304 MessageManager.getString("label.url_not_found"),
1305 JvOptionPane.WARNING_MESSAGE);
1309 String dialogOption = MessageManager
1310 .getString("label.input_alignment_from_url");
1311 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1312 .showInternalDialog(panel, dialogOption,
1313 JvOptionPane.YES_NO_CANCEL_OPTION,
1314 JvOptionPane.PLAIN_MESSAGE, null, options,
1315 MessageManager.getString("action.ok"));
1318 public boolean loadUrl(String url, AlignViewport viewport)
1320 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1322 if (viewport != null)
1324 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1325 FileFormat.Jalview);
1329 new FileLoader().LoadFile(url, DataSourceType.URL,
1330 FileFormat.Jalview);
1335 FileFormatI format = null;
1338 format = new IdentifyFile().identify(url, DataSourceType.URL);
1339 } catch (FileFormatException e)
1341 // TODO revise error handling, distinguish between
1342 // URL not found and response not valid
1351 if (viewport != null)
1353 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1358 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1365 * Opens the CutAndPaste window for the user to paste an alignment in to
1368 * - if not null, the pasted alignment is added to the current
1369 * alignment; if null, to a new alignment window
1372 public void inputTextboxMenuItem_actionPerformed(
1373 AlignmentViewPanel viewPanel)
1375 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1376 cap.setForInput(viewPanel);
1377 Desktop.addInternalFrame(cap,
1378 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1388 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1389 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1390 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1391 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1392 getWidth(), getHeight()));
1394 if (jconsole != null)
1396 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1397 jconsole.stopConsole();
1401 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1404 if (dialogExecutor != null)
1406 dialogExecutor.shutdownNow();
1408 closeAll_actionPerformed(null);
1410 if (groovyConsole != null)
1412 // suppress a possible repeat prompt to save script
1413 groovyConsole.setDirty(false);
1414 groovyConsole.exit();
1419 private void storeLastKnownDimensions(String string, Rectangle jc)
1421 jalview.bin.Console.debug("Storing last known dimensions for " + string
1422 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1423 + " height:" + jc.height);
1425 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1426 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1427 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1428 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1438 public void aboutMenuItem_actionPerformed(ActionEvent e)
1440 new Thread(new Runnable()
1445 new SplashScreen(false);
1451 * Returns the html text for the About screen, including any available version
1452 * number, build details, author details and citation reference, but without
1453 * the enclosing {@code html} tags
1457 public String getAboutMessage()
1459 StringBuilder message = new StringBuilder(1024);
1460 message.append("<div style=\"font-family: sans-serif;\">")
1461 .append("<h1><strong>Version: ")
1462 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1463 .append("<strong>Built: <em>")
1464 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1465 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1466 .append("</strong>");
1468 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1469 if (latestVersion.equals("Checking"))
1471 // JBP removed this message for 2.11: May be reinstated in future version
1472 // message.append("<br>...Checking latest version...</br>");
1474 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1476 boolean red = false;
1477 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1478 .indexOf("automated build") == -1)
1481 // Displayed when code version and jnlp version do not match and code
1482 // version is not a development build
1483 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1486 message.append("<br>!! Version ")
1487 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1488 .append(" is available for download from ")
1489 .append(Cache.getDefault("www.jalview.org",
1490 "https://www.jalview.org"))
1494 message.append("</div>");
1497 message.append("<br>Authors: ");
1498 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1499 message.append(CITATION);
1501 message.append("</div>");
1503 return message.toString();
1507 * Action on requesting Help documentation
1510 public void documentationMenuItem_actionPerformed()
1514 if (Platform.isJS())
1516 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1525 Help.showHelpWindow();
1527 } catch (Exception ex)
1529 System.err.println("Error opening help: " + ex.getMessage());
1534 public void closeAll_actionPerformed(ActionEvent e)
1536 // TODO show a progress bar while closing?
1537 JInternalFrame[] frames = desktop.getAllFrames();
1538 for (int i = 0; i < frames.length; i++)
1542 frames[i].setClosed(true);
1543 } catch (java.beans.PropertyVetoException ex)
1547 Jalview.setCurrentAlignFrame(null);
1548 System.out.println("ALL CLOSED");
1551 * reset state of singleton objects as appropriate (clear down session state
1552 * when all windows are closed)
1554 StructureSelectionManager ssm = StructureSelectionManager
1555 .getStructureSelectionManager(this);
1563 public void raiseRelated_actionPerformed(ActionEvent e)
1565 reorderAssociatedWindows(false, false);
1569 public void minimizeAssociated_actionPerformed(ActionEvent e)
1571 reorderAssociatedWindows(true, false);
1574 void closeAssociatedWindows()
1576 reorderAssociatedWindows(false, true);
1582 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1586 protected void garbageCollect_actionPerformed(ActionEvent e)
1588 // We simply collect the garbage
1589 jalview.bin.Console.debug("Collecting garbage...");
1591 jalview.bin.Console.debug("Finished garbage collection.");
1597 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1601 protected void showMemusage_actionPerformed(ActionEvent e)
1603 desktop.showMemoryUsage(showMemusage.isSelected());
1610 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1614 protected void showConsole_actionPerformed(ActionEvent e)
1616 showConsole(showConsole.isSelected());
1619 Console jconsole = null;
1622 * control whether the java console is visible or not
1626 void showConsole(boolean selected)
1628 // TODO: decide if we should update properties file
1629 if (jconsole != null) // BH 2018
1631 showConsole.setSelected(selected);
1632 Cache.setProperty("SHOW_JAVA_CONSOLE",
1633 Boolean.valueOf(selected).toString());
1634 jconsole.setVisible(selected);
1638 void reorderAssociatedWindows(boolean minimize, boolean close)
1640 JInternalFrame[] frames = desktop.getAllFrames();
1641 if (frames == null || frames.length < 1)
1646 AlignmentViewport source = null, target = null;
1647 if (frames[0] instanceof AlignFrame)
1649 source = ((AlignFrame) frames[0]).getCurrentView();
1651 else if (frames[0] instanceof TreePanel)
1653 source = ((TreePanel) frames[0]).getViewPort();
1655 else if (frames[0] instanceof PCAPanel)
1657 source = ((PCAPanel) frames[0]).av;
1659 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1661 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1666 for (int i = 0; i < frames.length; i++)
1669 if (frames[i] == null)
1673 if (frames[i] instanceof AlignFrame)
1675 target = ((AlignFrame) frames[i]).getCurrentView();
1677 else if (frames[i] instanceof TreePanel)
1679 target = ((TreePanel) frames[i]).getViewPort();
1681 else if (frames[i] instanceof PCAPanel)
1683 target = ((PCAPanel) frames[i]).av;
1685 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1687 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1690 if (source == target)
1696 frames[i].setClosed(true);
1700 frames[i].setIcon(minimize);
1703 frames[i].toFront();
1707 } catch (java.beans.PropertyVetoException ex)
1722 protected void preferences_actionPerformed(ActionEvent e)
1724 Preferences.openPreferences();
1728 * Prompts the user to choose a file and then saves the Jalview state as a
1729 * Jalview project file
1732 public void saveState_actionPerformed()
1734 saveState_actionPerformed(false);
1737 public void saveState_actionPerformed(boolean saveAs)
1739 java.io.File projectFile = getProjectFile();
1740 // autoSave indicates we already have a file and don't need to ask
1741 boolean autoSave = projectFile != null && !saveAs
1742 && BackupFiles.getEnabled();
1744 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1745 // saveAs="+saveAs+", Backups
1746 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1748 boolean approveSave = false;
1751 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1754 chooser.setFileView(new JalviewFileView());
1755 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1757 int value = chooser.showSaveDialog(this);
1759 if (value == JalviewFileChooser.APPROVE_OPTION)
1761 projectFile = chooser.getSelectedFile();
1762 setProjectFile(projectFile);
1767 if (approveSave || autoSave)
1769 final Desktop me = this;
1770 final java.io.File chosenFile = projectFile;
1771 new Thread(new Runnable()
1776 // TODO: refactor to Jalview desktop session controller action.
1777 setProgressBar(MessageManager.formatMessage(
1778 "label.saving_jalview_project", new Object[]
1779 { chosenFile.getName() }), chosenFile.hashCode());
1780 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1781 // TODO catch and handle errors for savestate
1782 // TODO prevent user from messing with the Desktop whilst we're saving
1785 boolean doBackup = BackupFiles.getEnabled();
1786 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1789 new Jalview2XML().saveState(
1790 doBackup ? backupfiles.getTempFile() : chosenFile);
1794 backupfiles.setWriteSuccess(true);
1795 backupfiles.rollBackupsAndRenameTempFile();
1797 } catch (OutOfMemoryError oom)
1799 new OOMWarning("Whilst saving current state to "
1800 + chosenFile.getName(), oom);
1801 } catch (Exception ex)
1803 jalview.bin.Console.error("Problems whilst trying to save to "
1804 + chosenFile.getName(), ex);
1805 JvOptionPane.showMessageDialog(me,
1806 MessageManager.formatMessage(
1807 "label.error_whilst_saving_current_state_to",
1809 { chosenFile.getName() }),
1810 MessageManager.getString("label.couldnt_save_project"),
1811 JvOptionPane.WARNING_MESSAGE);
1813 setProgressBar(null, chosenFile.hashCode());
1820 public void saveAsState_actionPerformed(ActionEvent e)
1822 saveState_actionPerformed(true);
1825 private void setProjectFile(File choice)
1827 this.projectFile = choice;
1830 public File getProjectFile()
1832 return this.projectFile;
1836 * Shows a file chooser dialog and tries to read in the selected file as a
1840 public void loadState_actionPerformed()
1842 final String[] suffix = new String[] { "jvp", "jar" };
1843 final String[] desc = new String[] { "Jalview Project",
1844 "Jalview Project (old)" };
1845 JalviewFileChooser chooser = new JalviewFileChooser(
1846 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1847 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1851 chooser.setFileView(new JalviewFileView());
1852 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1853 chooser.setResponseHandler(0, new Runnable()
1858 File selectedFile = chooser.getSelectedFile();
1859 setProjectFile(selectedFile);
1860 String choice = selectedFile.getAbsolutePath();
1861 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1862 new Thread(new Runnable()
1869 new Jalview2XML().loadJalviewAlign(selectedFile);
1870 } catch (OutOfMemoryError oom)
1872 new OOMWarning("Whilst loading project from " + choice, oom);
1873 } catch (Exception ex)
1875 jalview.bin.Console.error(
1876 "Problems whilst loading project from " + choice, ex);
1877 JvOptionPane.showMessageDialog(Desktop.desktop,
1878 MessageManager.formatMessage(
1879 "label.error_whilst_loading_project_from",
1883 .getString("label.couldnt_load_project"),
1884 JvOptionPane.WARNING_MESSAGE);
1887 }, "Project Loader").start();
1891 chooser.showOpenDialog(this);
1895 public void inputSequence_actionPerformed(ActionEvent e)
1897 new SequenceFetcher(this);
1900 JPanel progressPanel;
1902 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1904 public void startLoading(final Object fileName)
1906 if (fileLoadingCount == 0)
1908 fileLoadingPanels.add(addProgressPanel(MessageManager
1909 .formatMessage("label.loading_file", new Object[]
1915 private JPanel addProgressPanel(String string)
1917 if (progressPanel == null)
1919 progressPanel = new JPanel(new GridLayout(1, 1));
1920 totalProgressCount = 0;
1921 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1923 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1924 JProgressBar progressBar = new JProgressBar();
1925 progressBar.setIndeterminate(true);
1927 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1929 thisprogress.add(progressBar, BorderLayout.CENTER);
1930 progressPanel.add(thisprogress);
1931 ((GridLayout) progressPanel.getLayout()).setRows(
1932 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1933 ++totalProgressCount;
1934 instance.validate();
1935 return thisprogress;
1938 int totalProgressCount = 0;
1940 private void removeProgressPanel(JPanel progbar)
1942 if (progressPanel != null)
1944 synchronized (progressPanel)
1946 progressPanel.remove(progbar);
1947 GridLayout gl = (GridLayout) progressPanel.getLayout();
1948 gl.setRows(gl.getRows() - 1);
1949 if (--totalProgressCount < 1)
1951 this.getContentPane().remove(progressPanel);
1952 progressPanel = null;
1959 public void stopLoading()
1962 if (fileLoadingCount < 1)
1964 while (fileLoadingPanels.size() > 0)
1966 removeProgressPanel(fileLoadingPanels.remove(0));
1968 fileLoadingPanels.clear();
1969 fileLoadingCount = 0;
1974 public static int getViewCount(String alignmentId)
1976 AlignmentViewport[] aps = getViewports(alignmentId);
1977 return (aps == null) ? 0 : aps.length;
1982 * @param alignmentId
1983 * - if null, all sets are returned
1984 * @return all AlignmentPanels concerning the alignmentId sequence set
1986 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1988 if (Desktop.desktop == null)
1990 // no frames created and in headless mode
1991 // TODO: verify that frames are recoverable when in headless mode
1994 List<AlignmentPanel> aps = new ArrayList<>();
1995 AlignFrame[] frames = getAlignFrames();
2000 for (AlignFrame af : frames)
2002 for (AlignmentPanel ap : af.alignPanels)
2004 if (alignmentId == null
2005 || alignmentId.equals(ap.av.getSequenceSetId()))
2011 if (aps.size() == 0)
2015 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2020 * get all the viewports on an alignment.
2022 * @param sequenceSetId
2023 * unique alignment id (may be null - all viewports returned in that
2025 * @return all viewports on the alignment bound to sequenceSetId
2027 public static AlignmentViewport[] getViewports(String sequenceSetId)
2029 List<AlignmentViewport> viewp = new ArrayList<>();
2030 if (desktop != null)
2032 AlignFrame[] frames = Desktop.getAlignFrames();
2034 for (AlignFrame afr : frames)
2036 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2037 .equals(sequenceSetId))
2039 if (afr.alignPanels != null)
2041 for (AlignmentPanel ap : afr.alignPanels)
2043 if (sequenceSetId == null
2044 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2052 viewp.add(afr.getViewport());
2056 if (viewp.size() > 0)
2058 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2065 * Explode the views in the given frame into separate AlignFrame
2069 public static void explodeViews(AlignFrame af)
2071 int size = af.alignPanels.size();
2077 // FIXME: ideally should use UI interface API
2078 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2079 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2080 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2081 for (int i = 0; i < size; i++)
2083 AlignmentPanel ap = af.alignPanels.get(i);
2085 AlignFrame newaf = new AlignFrame(ap);
2087 // transfer reference for existing feature settings to new alignFrame
2088 if (ap == af.alignPanel)
2090 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2092 newaf.featureSettings = viewFeatureSettings;
2094 newaf.setFeatureSettingsGeometry(fsBounds);
2098 * Restore the view's last exploded frame geometry if known. Multiple views from
2099 * one exploded frame share and restore the same (frame) position and size.
2101 Rectangle geometry = ap.av.getExplodedGeometry();
2102 if (geometry != null)
2104 newaf.setBounds(geometry);
2107 ap.av.setGatherViewsHere(false);
2109 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2110 AlignFrame.DEFAULT_HEIGHT);
2111 // and materialise a new feature settings dialog instance for the new
2113 // (closes the old as if 'OK' was pressed)
2114 if (ap == af.alignPanel && newaf.featureSettings != null
2115 && newaf.featureSettings.isOpen()
2116 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2118 newaf.showFeatureSettingsUI();
2122 af.featureSettings = null;
2123 af.alignPanels.clear();
2124 af.closeMenuItem_actionPerformed(true);
2129 * Gather expanded views (separate AlignFrame's) with the same sequence set
2130 * identifier back in to this frame as additional views, and close the
2131 * expanded views. Note the expanded frames may themselves have multiple
2132 * views. We take the lot.
2136 public void gatherViews(AlignFrame source)
2138 source.viewport.setGatherViewsHere(true);
2139 source.viewport.setExplodedGeometry(source.getBounds());
2140 JInternalFrame[] frames = desktop.getAllFrames();
2141 String viewId = source.viewport.getSequenceSetId();
2142 for (int t = 0; t < frames.length; t++)
2144 if (frames[t] instanceof AlignFrame && frames[t] != source)
2146 AlignFrame af = (AlignFrame) frames[t];
2147 boolean gatherThis = false;
2148 for (int a = 0; a < af.alignPanels.size(); a++)
2150 AlignmentPanel ap = af.alignPanels.get(a);
2151 if (viewId.equals(ap.av.getSequenceSetId()))
2154 ap.av.setGatherViewsHere(false);
2155 ap.av.setExplodedGeometry(af.getBounds());
2156 source.addAlignmentPanel(ap, false);
2162 if (af.featureSettings != null && af.featureSettings.isOpen())
2164 if (source.featureSettings == null)
2166 // preserve the feature settings geometry for this frame
2167 source.featureSettings = af.featureSettings;
2168 source.setFeatureSettingsGeometry(
2169 af.getFeatureSettingsGeometry());
2173 // close it and forget
2174 af.featureSettings.close();
2177 af.alignPanels.clear();
2178 af.closeMenuItem_actionPerformed(true);
2183 // refresh the feature setting UI for the source frame if it exists
2184 if (source.featureSettings != null && source.featureSettings.isOpen())
2186 source.showFeatureSettingsUI();
2191 public JInternalFrame[] getAllFrames()
2193 return desktop.getAllFrames();
2197 * Checks the given url to see if it gives a response indicating that the user
2198 * should be informed of a new questionnaire.
2202 public void checkForQuestionnaire(String url)
2204 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2205 // javax.swing.SwingUtilities.invokeLater(jvq);
2206 new Thread(jvq).start();
2209 public void checkURLLinks()
2211 // Thread off the URL link checker
2212 addDialogThread(new Runnable()
2217 if (Cache.getDefault("CHECKURLLINKS", true))
2219 // check what the actual links are - if it's just the default don't
2220 // bother with the warning
2221 List<String> links = Preferences.sequenceUrlLinks
2224 // only need to check links if there is one with a
2225 // SEQUENCE_ID which is not the default EMBL_EBI link
2226 ListIterator<String> li = links.listIterator();
2227 boolean check = false;
2228 List<JLabel> urls = new ArrayList<>();
2229 while (li.hasNext())
2231 String link = li.next();
2232 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2233 && !UrlConstants.isDefaultString(link))
2236 int barPos = link.indexOf("|");
2237 String urlMsg = barPos == -1 ? link
2238 : link.substring(0, barPos) + ": "
2239 + link.substring(barPos + 1);
2240 urls.add(new JLabel(urlMsg));
2248 // ask user to check in case URL links use old style tokens
2249 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2250 JPanel msgPanel = new JPanel();
2251 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2252 msgPanel.add(Box.createVerticalGlue());
2253 JLabel msg = new JLabel(MessageManager
2254 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2255 JLabel msg2 = new JLabel(MessageManager
2256 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2258 for (JLabel url : urls)
2264 final JCheckBox jcb = new JCheckBox(
2265 MessageManager.getString("label.do_not_display_again"));
2266 jcb.addActionListener(new ActionListener()
2269 public void actionPerformed(ActionEvent e)
2271 // update Cache settings for "don't show this again"
2272 boolean showWarningAgain = !jcb.isSelected();
2273 Cache.setProperty("CHECKURLLINKS",
2274 Boolean.valueOf(showWarningAgain).toString());
2279 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2281 .getString("label.SEQUENCE_ID_no_longer_used"),
2282 JvOptionPane.WARNING_MESSAGE);
2289 * Proxy class for JDesktopPane which optionally displays the current memory
2290 * usage and highlights the desktop area with a red bar if free memory runs
2295 public class MyDesktopPane extends JDesktopPane implements Runnable
2297 private static final float ONE_MB = 1048576f;
2299 boolean showMemoryUsage = false;
2303 java.text.NumberFormat df;
2305 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2308 public MyDesktopPane(boolean showMemoryUsage)
2310 showMemoryUsage(showMemoryUsage);
2313 public void showMemoryUsage(boolean showMemory)
2315 this.showMemoryUsage = showMemory;
2318 Thread worker = new Thread(this);
2324 public boolean isShowMemoryUsage()
2326 return showMemoryUsage;
2332 df = java.text.NumberFormat.getNumberInstance();
2333 df.setMaximumFractionDigits(2);
2334 runtime = Runtime.getRuntime();
2336 while (showMemoryUsage)
2340 maxMemory = runtime.maxMemory() / ONE_MB;
2341 allocatedMemory = runtime.totalMemory() / ONE_MB;
2342 freeMemory = runtime.freeMemory() / ONE_MB;
2343 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2345 percentUsage = (totalFreeMemory / maxMemory) * 100;
2347 // if (percentUsage < 20)
2349 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2351 // instance.set.setBorder(border1);
2354 // sleep after showing usage
2356 } catch (Exception ex)
2358 ex.printStackTrace();
2364 public void paintComponent(Graphics g)
2366 if (showMemoryUsage && g != null && df != null)
2368 if (percentUsage < 20)
2370 g.setColor(Color.red);
2372 FontMetrics fm = g.getFontMetrics();
2375 g.drawString(MessageManager.formatMessage("label.memory_stats",
2377 { df.format(totalFreeMemory), df.format(maxMemory),
2378 df.format(percentUsage) }),
2379 10, getHeight() - fm.getHeight());
2383 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2384 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2389 * Accessor method to quickly get all the AlignmentFrames loaded.
2391 * @return an array of AlignFrame, or null if none found
2393 public static AlignFrame[] getAlignFrames()
2395 if (Jalview.isHeadlessMode())
2397 // Desktop.desktop is null in headless mode
2398 return new AlignFrame[] { Jalview.currentAlignFrame };
2401 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2407 List<AlignFrame> avp = new ArrayList<>();
2409 for (int i = frames.length - 1; i > -1; i--)
2411 if (frames[i] instanceof AlignFrame)
2413 avp.add((AlignFrame) frames[i]);
2415 else if (frames[i] instanceof SplitFrame)
2418 * Also check for a split frame containing an AlignFrame
2420 GSplitFrame sf = (GSplitFrame) frames[i];
2421 if (sf.getTopFrame() instanceof AlignFrame)
2423 avp.add((AlignFrame) sf.getTopFrame());
2425 if (sf.getBottomFrame() instanceof AlignFrame)
2427 avp.add((AlignFrame) sf.getBottomFrame());
2431 if (avp.size() == 0)
2435 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2440 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2444 public GStructureViewer[] getJmols()
2446 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2452 List<GStructureViewer> avp = new ArrayList<>();
2454 for (int i = frames.length - 1; i > -1; i--)
2456 if (frames[i] instanceof AppJmol)
2458 GStructureViewer af = (GStructureViewer) frames[i];
2462 if (avp.size() == 0)
2466 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2471 * Add Groovy Support to Jalview
2474 public void groovyShell_actionPerformed()
2478 openGroovyConsole();
2479 } catch (Exception ex)
2481 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2482 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2484 MessageManager.getString("label.couldnt_create_groovy_shell"),
2485 MessageManager.getString("label.groovy_support_failed"),
2486 JvOptionPane.ERROR_MESSAGE);
2491 * Open the Groovy console
2493 void openGroovyConsole()
2495 if (groovyConsole == null)
2497 groovyConsole = new groovy.ui.Console();
2498 groovyConsole.setVariable("Jalview", this);
2499 groovyConsole.run();
2502 * We allow only one console at a time, so that AlignFrame menu option
2503 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2504 * enable 'Run script', when the console is opened, and the reverse when it is
2507 Window window = (Window) groovyConsole.getFrame();
2508 window.addWindowListener(new WindowAdapter()
2511 public void windowClosed(WindowEvent e)
2514 * rebind CMD-Q from Groovy Console to Jalview Quit
2517 enableExecuteGroovy(false);
2523 * show Groovy console window (after close and reopen)
2525 ((Window) groovyConsole.getFrame()).setVisible(true);
2528 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2529 * opening a second console
2531 enableExecuteGroovy(true);
2535 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2536 * binding when opened
2538 protected void addQuitHandler()
2541 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2543 .getKeyStroke(KeyEvent.VK_Q,
2544 jalview.util.ShortcutKeyMaskExWrapper
2545 .getMenuShortcutKeyMaskEx()),
2547 getRootPane().getActionMap().put("Quit", new AbstractAction()
2550 public void actionPerformed(ActionEvent e)
2558 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2561 * true if Groovy console is open
2563 public void enableExecuteGroovy(boolean enabled)
2566 * disable opening a second Groovy console (or re-enable when the console is
2569 groovyShell.setEnabled(!enabled);
2571 AlignFrame[] alignFrames = getAlignFrames();
2572 if (alignFrames != null)
2574 for (AlignFrame af : alignFrames)
2576 af.setGroovyEnabled(enabled);
2582 * Progress bars managed by the IProgressIndicator method.
2584 private Hashtable<Long, JPanel> progressBars;
2586 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2591 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2594 public void setProgressBar(String message, long id)
2596 if (progressBars == null)
2598 progressBars = new Hashtable<>();
2599 progressBarHandlers = new Hashtable<>();
2602 if (progressBars.get(Long.valueOf(id)) != null)
2604 JPanel panel = progressBars.remove(Long.valueOf(id));
2605 if (progressBarHandlers.contains(Long.valueOf(id)))
2607 progressBarHandlers.remove(Long.valueOf(id));
2609 removeProgressPanel(panel);
2613 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2620 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2621 * jalview.gui.IProgressIndicatorHandler)
2624 public void registerHandler(final long id,
2625 final IProgressIndicatorHandler handler)
2627 if (progressBarHandlers == null
2628 || !progressBars.containsKey(Long.valueOf(id)))
2630 throw new Error(MessageManager.getString(
2631 "error.call_setprogressbar_before_registering_handler"));
2633 progressBarHandlers.put(Long.valueOf(id), handler);
2634 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2635 if (handler.canCancel())
2637 JButton cancel = new JButton(
2638 MessageManager.getString("action.cancel"));
2639 final IProgressIndicator us = this;
2640 cancel.addActionListener(new ActionListener()
2644 public void actionPerformed(ActionEvent e)
2646 handler.cancelActivity(id);
2647 us.setProgressBar(MessageManager
2648 .formatMessage("label.cancelled_params", new Object[]
2649 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2653 progressPanel.add(cancel, BorderLayout.EAST);
2659 * @return true if any progress bars are still active
2662 public boolean operationInProgress()
2664 if (progressBars != null && progressBars.size() > 0)
2672 * This will return the first AlignFrame holding the given viewport instance.
2673 * It will break if there are more than one AlignFrames viewing a particular
2677 * @return alignFrame for viewport
2679 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2681 if (desktop != null)
2683 AlignmentPanel[] aps = getAlignmentPanels(
2684 viewport.getSequenceSetId());
2685 for (int panel = 0; aps != null && panel < aps.length; panel++)
2687 if (aps[panel] != null && aps[panel].av == viewport)
2689 return aps[panel].alignFrame;
2696 public VamsasApplication getVamsasApplication()
2698 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2704 * flag set if jalview GUI is being operated programmatically
2706 private boolean inBatchMode = false;
2709 * check if jalview GUI is being operated programmatically
2711 * @return inBatchMode
2713 public boolean isInBatchMode()
2719 * set flag if jalview GUI is being operated programmatically
2721 * @param inBatchMode
2723 public void setInBatchMode(boolean inBatchMode)
2725 this.inBatchMode = inBatchMode;
2729 * start service discovery and wait till it is done
2731 public void startServiceDiscovery()
2733 startServiceDiscovery(false);
2737 * start service discovery threads - blocking or non-blocking
2741 public void startServiceDiscovery(boolean blocking)
2743 startServiceDiscovery(blocking, false);
2747 * start service discovery threads
2750 * - false means call returns immediately
2751 * @param ignore_SHOW_JWS2_SERVICES_preference
2752 * - when true JABA services are discovered regardless of user's JWS2
2753 * discovery preference setting
2755 public void startServiceDiscovery(boolean blocking,
2756 boolean ignore_SHOW_JWS2_SERVICES_preference)
2758 boolean alive = true;
2759 Thread t0 = null, t1 = null, t2 = null;
2760 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2763 // todo: changesupport handlers need to be transferred
2764 if (discoverer == null)
2766 discoverer = new jalview.ws.jws1.Discoverer();
2767 // register PCS handler for desktop.
2768 discoverer.addPropertyChangeListener(changeSupport);
2770 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2771 // until we phase out completely
2772 (t0 = new Thread(discoverer)).start();
2775 if (ignore_SHOW_JWS2_SERVICES_preference
2776 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2778 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2779 .startDiscoverer(changeSupport);
2783 // TODO: do rest service discovery
2792 } catch (Exception e)
2795 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2796 || (t3 != null && t3.isAlive())
2797 || (t0 != null && t0.isAlive());
2803 * called to check if the service discovery process completed successfully.
2807 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2809 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2811 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2812 .getErrorMessages();
2815 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2817 if (serviceChangedDialog == null)
2819 // only run if we aren't already displaying one of these.
2820 addDialogThread(serviceChangedDialog = new Runnable()
2827 * JalviewDialog jd =new JalviewDialog() {
2829 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2831 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2833 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2835 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2837 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2838 * + " or mis-configured HTTP proxy settings.<br/>" +
2839 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2840 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2841 * true, true, "Web Service Configuration Problem", 450, 400);
2843 * jd.waitForInput();
2845 JvOptionPane.showConfirmDialog(Desktop.desktop,
2846 new JLabel("<html><table width=\"450\"><tr><td>"
2847 + ermsg + "</td></tr></table>"
2848 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2849 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2850 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2851 + " Tools->Preferences dialog box to change them.</p></html>"),
2852 "Web Service Configuration Problem",
2853 JvOptionPane.DEFAULT_OPTION,
2854 JvOptionPane.ERROR_MESSAGE);
2855 serviceChangedDialog = null;
2863 jalview.bin.Console.error(
2864 "Errors reported by JABA discovery service. Check web services preferences.\n"
2871 private Runnable serviceChangedDialog = null;
2874 * start a thread to open a URL in the configured browser. Pops up a warning
2875 * dialog to the user if there is an exception when calling out to the browser
2880 public static void showUrl(final String url)
2882 showUrl(url, Desktop.instance);
2886 * Like showUrl but allows progress handler to be specified
2890 * (null) or object implementing IProgressIndicator
2892 public static void showUrl(final String url,
2893 final IProgressIndicator progress)
2895 new Thread(new Runnable()
2902 if (progress != null)
2904 progress.setProgressBar(MessageManager
2905 .formatMessage("status.opening_params", new Object[]
2906 { url }), this.hashCode());
2908 jalview.util.BrowserLauncher.openURL(url);
2909 } catch (Exception ex)
2911 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2913 .getString("label.web_browser_not_found_unix"),
2914 MessageManager.getString("label.web_browser_not_found"),
2915 JvOptionPane.WARNING_MESSAGE);
2917 ex.printStackTrace();
2919 if (progress != null)
2921 progress.setProgressBar(null, this.hashCode());
2927 public static WsParamSetManager wsparamManager = null;
2929 public static ParamManager getUserParameterStore()
2931 if (wsparamManager == null)
2933 wsparamManager = new WsParamSetManager();
2935 return wsparamManager;
2939 * static hyperlink handler proxy method for use by Jalview's internal windows
2943 public static void hyperlinkUpdate(HyperlinkEvent e)
2945 if (e.getEventType() == EventType.ACTIVATED)
2950 url = e.getURL().toString();
2951 Desktop.showUrl(url);
2952 } catch (Exception x)
2957 .error("Couldn't handle string " + url + " as a URL.");
2959 // ignore any exceptions due to dud links.
2966 * single thread that handles display of dialogs to user.
2968 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2971 * flag indicating if dialogExecutor should try to acquire a permit
2973 private volatile boolean dialogPause = true;
2978 private java.util.concurrent.Semaphore block = new Semaphore(0);
2980 private static groovy.ui.Console groovyConsole;
2983 * add another dialog thread to the queue
2987 public void addDialogThread(final Runnable prompter)
2989 dialogExecutor.submit(new Runnable()
2999 } catch (InterruptedException x)
3003 if (instance == null)
3009 SwingUtilities.invokeAndWait(prompter);
3010 } catch (Exception q)
3012 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3019 public void startDialogQueue()
3021 // set the flag so we don't pause waiting for another permit and semaphore
3022 // the current task to begin
3023 dialogPause = false;
3028 * Outputs an image of the desktop to file in EPS format, after prompting the
3029 * user for choice of Text or Lineart character rendering (unless a preference
3030 * has been set). The file name is generated as
3033 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3037 protected void snapShotWindow_actionPerformed(ActionEvent e)
3039 // currently the menu option to do this is not shown
3042 int width = getWidth();
3043 int height = getHeight();
3045 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3046 ImageWriterI writer = new ImageWriterI()
3049 public void exportImage(Graphics g) throws Exception
3052 jalview.bin.Console.info("Successfully written snapshot to file "
3053 + of.getAbsolutePath());
3056 String title = "View of desktop";
3057 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3059 exporter.doExport(of, this, width, height, title);
3063 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3064 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3065 * and location last time the view was expanded (if any). However it does not
3066 * remember the split pane divider location - this is set to match the
3067 * 'exploding' frame.
3071 public void explodeViews(SplitFrame sf)
3073 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3074 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3075 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3077 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3079 int viewCount = topPanels.size();
3086 * Processing in reverse order works, forwards order leaves the first panels not
3087 * visible. I don't know why!
3089 for (int i = viewCount - 1; i >= 0; i--)
3092 * Make new top and bottom frames. These take over the respective AlignmentPanel
3093 * objects, including their AlignmentViewports, so the cdna/protein
3094 * relationships between the viewports is carried over to the new split frames.
3096 * explodedGeometry holds the (x, y) position of the previously exploded
3097 * SplitFrame, and the (width, height) of the AlignFrame component
3099 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3100 AlignFrame newTopFrame = new AlignFrame(topPanel);
3101 newTopFrame.setSize(oldTopFrame.getSize());
3102 newTopFrame.setVisible(true);
3103 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3104 .getExplodedGeometry();
3105 if (geometry != null)
3107 newTopFrame.setSize(geometry.getSize());
3110 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3111 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3112 newBottomFrame.setSize(oldBottomFrame.getSize());
3113 newBottomFrame.setVisible(true);
3114 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3115 .getExplodedGeometry();
3116 if (geometry != null)
3118 newBottomFrame.setSize(geometry.getSize());
3121 topPanel.av.setGatherViewsHere(false);
3122 bottomPanel.av.setGatherViewsHere(false);
3123 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3125 if (geometry != null)
3127 splitFrame.setLocation(geometry.getLocation());
3129 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3133 * Clear references to the panels (now relocated in the new SplitFrames) before
3134 * closing the old SplitFrame.
3137 bottomPanels.clear();
3142 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3143 * back into the given SplitFrame as additional views. Note that the gathered
3144 * frames may themselves have multiple views.
3148 public void gatherViews(GSplitFrame source)
3151 * special handling of explodedGeometry for a view within a SplitFrame: - it
3152 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3153 * height) of the AlignFrame component
3155 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3156 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3157 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3158 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3159 myBottomFrame.viewport
3160 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3161 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3162 myTopFrame.viewport.setGatherViewsHere(true);
3163 myBottomFrame.viewport.setGatherViewsHere(true);
3164 String topViewId = myTopFrame.viewport.getSequenceSetId();
3165 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3167 JInternalFrame[] frames = desktop.getAllFrames();
3168 for (JInternalFrame frame : frames)
3170 if (frame instanceof SplitFrame && frame != source)
3172 SplitFrame sf = (SplitFrame) frame;
3173 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3174 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3175 boolean gatherThis = false;
3176 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3178 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3179 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3180 if (topViewId.equals(topPanel.av.getSequenceSetId())
3181 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3184 topPanel.av.setGatherViewsHere(false);
3185 bottomPanel.av.setGatherViewsHere(false);
3186 topPanel.av.setExplodedGeometry(
3187 new Rectangle(sf.getLocation(), topFrame.getSize()));
3188 bottomPanel.av.setExplodedGeometry(
3189 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3190 myTopFrame.addAlignmentPanel(topPanel, false);
3191 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3197 topFrame.getAlignPanels().clear();
3198 bottomFrame.getAlignPanels().clear();
3205 * The dust settles...give focus to the tab we did this from.
3207 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3210 public static groovy.ui.Console getGroovyConsole()
3212 return groovyConsole;
3216 * handles the payload of a drag and drop event.
3218 * TODO refactor to desktop utilities class
3221 * - Data source strings extracted from the drop event
3223 * - protocol for each data source extracted from the drop event
3227 * - the payload from the drop event
3230 public static void transferFromDropTarget(List<Object> files,
3231 List<DataSourceType> protocols, DropTargetDropEvent evt,
3232 Transferable t) throws Exception
3235 // BH 2018 changed List<String> to List<Object> to allow for File from
3238 // DataFlavor[] flavors = t.getTransferDataFlavors();
3239 // for (int i = 0; i < flavors.length; i++) {
3240 // if (flavors[i].isFlavorJavaFileListType()) {
3241 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3242 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3243 // for (int j = 0; j < list.size(); j++) {
3244 // File file = (File) list.get(j);
3245 // byte[] data = getDroppedFileBytes(file);
3246 // fileName.setText(file.getName() + " - " + data.length + " " +
3247 // evt.getLocation());
3248 // JTextArea target = (JTextArea) ((DropTarget)
3249 // evt.getSource()).getComponent();
3250 // target.setText(new String(data));
3252 // dtde.dropComplete(true);
3257 DataFlavor uriListFlavor = new DataFlavor(
3258 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3261 urlFlavour = new DataFlavor(
3262 "application/x-java-url; class=java.net.URL");
3263 } catch (ClassNotFoundException cfe)
3265 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3269 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3274 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3275 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3276 // means url may be null.
3279 protocols.add(DataSourceType.URL);
3280 files.add(url.toString());
3281 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3282 + files.get(files.size() - 1));
3287 if (Platform.isAMacAndNotJS())
3290 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3293 } catch (Throwable ex)
3295 jalview.bin.Console.debug("URL drop handler failed.", ex);
3298 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3300 // Works on Windows and MacOSX
3301 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3302 for (Object file : (List) t
3303 .getTransferData(DataFlavor.javaFileListFlavor))
3306 protocols.add(DataSourceType.FILE);
3311 // Unix like behaviour
3312 boolean added = false;
3314 if (t.isDataFlavorSupported(uriListFlavor))
3316 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3317 // This is used by Unix drag system
3318 data = (String) t.getTransferData(uriListFlavor);
3322 // fallback to text: workaround - on OSX where there's a JVM bug
3324 .debug("standard URIListFlavor failed. Trying text");
3325 // try text fallback
3326 DataFlavor textDf = new DataFlavor(
3327 "text/plain;class=java.lang.String");
3328 if (t.isDataFlavorSupported(textDf))
3330 data = (String) t.getTransferData(textDf);
3333 jalview.bin.Console.debug("Plain text drop content returned "
3334 + (data == null ? "Null - failed" : data));
3339 while (protocols.size() < files.size())
3341 jalview.bin.Console.debug("Adding missing FILE protocol for "
3342 + files.get(protocols.size()));
3343 protocols.add(DataSourceType.FILE);
3345 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3346 data, "\r\n"); st.hasMoreTokens();)
3349 String s = st.nextToken();
3350 if (s.startsWith("#"))
3352 // the line is a comment (as per the RFC 2483)
3355 java.net.URI uri = new java.net.URI(s);
3356 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3358 protocols.add(DataSourceType.URL);
3359 files.add(uri.toString());
3363 // otherwise preserve old behaviour: catch all for file objects
3364 java.io.File file = new java.io.File(uri);
3365 protocols.add(DataSourceType.FILE);
3366 files.add(file.toString());
3371 if (jalview.bin.Console.isDebugEnabled())
3373 if (data == null || !added)
3376 if (t.getTransferDataFlavors() != null
3377 && t.getTransferDataFlavors().length > 0)
3379 jalview.bin.Console.debug(
3380 "Couldn't resolve drop data. Here are the supported flavors:");
3381 for (DataFlavor fl : t.getTransferDataFlavors())
3383 jalview.bin.Console.debug(
3384 "Supported transfer dataflavor: " + fl.toString());
3385 Object df = t.getTransferData(fl);
3388 jalview.bin.Console.debug("Retrieves: " + df);
3392 jalview.bin.Console.debug("Retrieved nothing");
3399 .debug("Couldn't resolve dataflavor for drop: "
3405 if (Platform.isWindowsAndNotJS())
3408 .debug("Scanning dropped content for Windows Link Files");
3410 // resolve any .lnk files in the file drop
3411 for (int f = 0; f < files.size(); f++)
3413 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3414 if (protocols.get(f).equals(DataSourceType.FILE)
3415 && (source.endsWith(".lnk") || source.endsWith(".url")
3416 || source.endsWith(".site")))
3420 Object obj = files.get(f);
3421 File lf = (obj instanceof File ? (File) obj
3422 : new File((String) obj));
3423 // process link file to get a URL
3424 jalview.bin.Console.debug("Found potential link file: " + lf);
3425 WindowsShortcut wscfile = new WindowsShortcut(lf);
3426 String fullname = wscfile.getRealFilename();
3427 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3428 files.set(f, fullname);
3429 jalview.bin.Console.debug("Parsed real filename " + fullname
3430 + " to extract protocol: " + protocols.get(f));
3431 } catch (Exception ex)
3433 jalview.bin.Console.error(
3434 "Couldn't parse " + files.get(f) + " as a link file.",
3443 * Sets the Preferences property for experimental features to True or False
3444 * depending on the state of the controlling menu item
3447 protected void showExperimental_actionPerformed(boolean selected)
3449 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3453 * Answers a (possibly empty) list of any structure viewer frames (currently
3454 * for either Jmol or Chimera) which are currently open. This may optionally
3455 * be restricted to viewers of a specified class, or viewers linked to a
3456 * specified alignment panel.
3459 * if not null, only return viewers linked to this panel
3460 * @param structureViewerClass
3461 * if not null, only return viewers of this class
3464 public List<StructureViewerBase> getStructureViewers(
3465 AlignmentPanel apanel,
3466 Class<? extends StructureViewerBase> structureViewerClass)
3468 List<StructureViewerBase> result = new ArrayList<>();
3469 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3471 for (JInternalFrame frame : frames)
3473 if (frame instanceof StructureViewerBase)
3475 if (structureViewerClass == null
3476 || structureViewerClass.isInstance(frame))
3479 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3481 result.add((StructureViewerBase) frame);
3489 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3491 private static boolean debugScaleMessageDone = false;
3493 public static void debugScaleMessage(Graphics g)
3495 if (debugScaleMessageDone)
3499 // output used by tests to check HiDPI scaling settings in action
3502 Graphics2D gg = (Graphics2D) g;
3505 AffineTransform t = gg.getTransform();
3506 double scaleX = t.getScaleX();
3507 double scaleY = t.getScaleY();
3508 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3509 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3510 debugScaleMessageDone = true;
3514 jalview.bin.Console.debug("Desktop graphics null");
3516 } catch (Exception e)
3518 jalview.bin.Console.debug(Cache.getStackTraceString(e));