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.util.Locale;
25 import java.awt.BorderLayout;
26 import java.awt.Color;
27 import java.awt.Dimension;
28 import java.awt.FontMetrics;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.GridLayout;
32 import java.awt.Point;
33 import java.awt.Rectangle;
34 import java.awt.Toolkit;
35 import java.awt.Window;
36 import java.awt.datatransfer.Clipboard;
37 import java.awt.datatransfer.ClipboardOwner;
38 import java.awt.datatransfer.DataFlavor;
39 import java.awt.datatransfer.Transferable;
40 import java.awt.dnd.DnDConstants;
41 import java.awt.dnd.DropTargetDragEvent;
42 import java.awt.dnd.DropTargetDropEvent;
43 import java.awt.dnd.DropTargetEvent;
44 import java.awt.dnd.DropTargetListener;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ActionListener;
47 import java.awt.event.InputEvent;
48 import java.awt.event.KeyEvent;
49 import java.awt.event.MouseAdapter;
50 import java.awt.event.MouseEvent;
51 import java.awt.event.WindowAdapter;
52 import java.awt.event.WindowEvent;
53 import java.awt.geom.AffineTransform;
54 import java.beans.PropertyChangeEvent;
55 import java.beans.PropertyChangeListener;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Vector;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Semaphore;
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.Box;
76 import javax.swing.BoxLayout;
77 import javax.swing.DefaultDesktopManager;
78 import javax.swing.DesktopManager;
79 import javax.swing.InputMap;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JComboBox;
83 import javax.swing.JComponent;
84 import javax.swing.JDesktopPane;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.event.HyperlinkEvent;
95 import javax.swing.event.HyperlinkEvent.EventType;
96 import javax.swing.event.InternalFrameAdapter;
97 import javax.swing.event.InternalFrameEvent;
99 import org.stackoverflowusers.file.WindowsShortcut;
101 import jalview.api.AlignViewportI;
102 import jalview.api.AlignmentViewPanel;
103 import jalview.bin.Cache;
104 import jalview.bin.Jalview;
105 import jalview.gui.ImageExporter.ImageWriterI;
106 import jalview.io.BackupFiles;
107 import jalview.io.DataSourceType;
108 import jalview.io.FileFormat;
109 import jalview.io.FileFormatException;
110 import jalview.io.FileFormatI;
111 import jalview.io.FileFormats;
112 import jalview.io.FileLoader;
113 import jalview.io.FormatAdapter;
114 import jalview.io.IdentifyFile;
115 import jalview.io.JalviewFileChooser;
116 import jalview.io.JalviewFileView;
117 import jalview.jbgui.GSplitFrame;
118 import jalview.jbgui.GStructureViewer;
119 import jalview.project.Jalview2XML;
120 import jalview.structure.StructureSelectionManager;
121 import jalview.urls.IdOrgSettings;
122 import jalview.util.BrowserLauncher;
123 import jalview.util.ChannelProperties;
124 import jalview.util.ImageMaker.TYPE;
125 import jalview.util.MessageManager;
126 import jalview.util.Platform;
127 import jalview.util.ShortcutKeyMaskExWrapper;
128 import jalview.util.UrlConstants;
129 import jalview.viewmodel.AlignmentViewport;
130 import jalview.ws.params.ParamManager;
131 import jalview.ws.utils.UrlDownloadClient;
138 * @version $Revision: 1.155 $
140 public class Desktop extends jalview.jbgui.GDesktop
141 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
142 jalview.api.StructureSelectionManagerProvider
144 private static final String CITATION;
147 URL bg_logo_url = ChannelProperties.getImageURL(
148 "bg_logo." + String.valueOf(SplashScreen.logoSize));
149 URL uod_logo_url = ChannelProperties.getImageURL(
150 "uod_banner." + String.valueOf(SplashScreen.logoSize));
151 boolean logo = (bg_logo_url != null || uod_logo_url != null);
152 StringBuilder sb = new StringBuilder();
154 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
159 sb.append(bg_logo_url == null ? ""
160 : "<img alt=\"Barton Group logo\" src=\""
161 + bg_logo_url.toString() + "\">");
162 sb.append(uod_logo_url == null ? ""
163 : " <img alt=\"University of Dundee shield\" src=\""
164 + uod_logo_url.toString() + "\">");
166 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
167 sb.append("<br><br>If you use Jalview, please cite:"
168 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
169 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
170 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
171 CITATION = sb.toString();
174 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
176 private static int DEFAULT_MIN_WIDTH = 300;
178 private static int DEFAULT_MIN_HEIGHT = 250;
180 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
182 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
184 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
186 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
188 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
190 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
192 public static boolean nosplash = false;
195 * news reader - null if it was never started.
197 private BlogReader jvnews = null;
199 private File projectFile;
203 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
205 public void addJalviewPropertyChangeListener(
206 PropertyChangeListener listener)
208 changeSupport.addJalviewPropertyChangeListener(listener);
212 * @param propertyName
214 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
215 * java.beans.PropertyChangeListener)
217 public void addJalviewPropertyChangeListener(String propertyName,
218 PropertyChangeListener listener)
220 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
224 * @param propertyName
226 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
227 * java.beans.PropertyChangeListener)
229 public void removeJalviewPropertyChangeListener(String propertyName,
230 PropertyChangeListener listener)
232 changeSupport.removeJalviewPropertyChangeListener(propertyName,
236 /** Singleton Desktop instance */
237 public static Desktop instance;
239 public static MyDesktopPane desktop;
241 public static MyDesktopPane getDesktop()
243 // BH 2018 could use currentThread() here as a reference to a
244 // Hashtable<Thread, MyDesktopPane> in JavaScript
248 static int openFrameCount = 0;
250 static final int xOffset = 30;
252 static final int yOffset = 30;
254 public static jalview.ws.jws1.Discoverer discoverer;
256 public static Object[] jalviewClipboard;
258 public static boolean internalCopy = false;
260 static int fileLoadingCount = 0;
262 class MyDesktopManager implements DesktopManager
265 private DesktopManager delegate;
267 public MyDesktopManager(DesktopManager delegate)
269 this.delegate = delegate;
273 public void activateFrame(JInternalFrame f)
277 delegate.activateFrame(f);
278 } catch (NullPointerException npe)
280 Point p = getMousePosition();
281 instance.showPasteMenu(p.x, p.y);
286 public void beginDraggingFrame(JComponent f)
288 delegate.beginDraggingFrame(f);
292 public void beginResizingFrame(JComponent f, int direction)
294 delegate.beginResizingFrame(f, direction);
298 public void closeFrame(JInternalFrame f)
300 delegate.closeFrame(f);
304 public void deactivateFrame(JInternalFrame f)
306 delegate.deactivateFrame(f);
310 public void deiconifyFrame(JInternalFrame f)
312 delegate.deiconifyFrame(f);
316 public void dragFrame(JComponent f, int newX, int newY)
322 delegate.dragFrame(f, newX, newY);
326 public void endDraggingFrame(JComponent f)
328 delegate.endDraggingFrame(f);
333 public void endResizingFrame(JComponent f)
335 delegate.endResizingFrame(f);
340 public void iconifyFrame(JInternalFrame f)
342 delegate.iconifyFrame(f);
346 public void maximizeFrame(JInternalFrame f)
348 delegate.maximizeFrame(f);
352 public void minimizeFrame(JInternalFrame f)
354 delegate.minimizeFrame(f);
358 public void openFrame(JInternalFrame f)
360 delegate.openFrame(f);
364 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
371 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
375 public void setBoundsForFrame(JComponent f, int newX, int newY,
376 int newWidth, int newHeight)
378 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
381 // All other methods, simply delegate
386 * Creates a new Desktop object.
392 * A note to implementors. It is ESSENTIAL that any activities that might
393 * block are spawned off as threads rather than waited for during this
398 doConfigureStructurePrefs();
399 setTitle(ChannelProperties.getProperty("app_name") + " "
400 + Cache.getProperty("VERSION"));
403 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
404 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
405 * officially documented or guaranteed to exist, so we access it via
406 * reflection. There appear to be unfathomable criteria about what this
407 * string can contain, and it if doesn't meet those criteria then "java"
408 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
409 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
410 * not. The reflection access may generate a warning: WARNING: An illegal
411 * reflective access operation has occurred WARNING: Illegal reflective
412 * access by jalview.gui.Desktop () to field
413 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
415 if (Platform.isLinux())
419 Toolkit xToolkit = Toolkit.getDefaultToolkit();
420 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
421 Field awtAppClassNameField = null;
423 if (Arrays.stream(declaredFields)
424 .anyMatch(f -> f.getName().equals("awtAppClassName")))
426 awtAppClassNameField = xToolkit.getClass()
427 .getDeclaredField("awtAppClassName");
430 String title = ChannelProperties.getProperty("app_name");
431 if (awtAppClassNameField != null)
433 awtAppClassNameField.setAccessible(true);
434 awtAppClassNameField.set(xToolkit, title);
438 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
440 } catch (Exception e)
442 jalview.bin.Console.debug("Error setting awtAppClassName");
443 jalview.bin.Console.trace(Cache.getStackTraceString(e));
448 * APQHandlers sets handlers for About, Preferences and Quit actions
449 * peculiar to macOS's application menu. APQHandlers will check to see if a
450 * handler is supported before setting it.
454 APQHandlers.setAPQHandlers(this);
455 } catch (Exception e)
457 System.out.println("Cannot set APQHandlers");
458 // e.printStackTrace();
459 } catch (Throwable t)
462 .warn("Error setting APQHandlers: " + t.toString());
463 jalview.bin.Console.trace(Cache.getStackTraceString(t));
465 setIconImages(ChannelProperties.getIconList());
467 addWindowListener(new WindowAdapter()
471 public void windowClosing(WindowEvent ev)
477 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
479 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
480 desktop = new MyDesktopPane(selmemusage);
482 showMemusage.setSelected(selmemusage);
483 desktop.setBackground(Color.white);
485 getContentPane().setLayout(new BorderLayout());
486 // alternate config - have scrollbars - see notes in JAL-153
487 // JScrollPane sp = new JScrollPane();
488 // sp.getViewport().setView(desktop);
489 // getContentPane().add(sp, BorderLayout.CENTER);
491 // BH 2018 - just an experiment to try unclipped JInternalFrames.
494 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
497 getContentPane().add(desktop, BorderLayout.CENTER);
498 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
500 // This line prevents Windows Look&Feel resizing all new windows to maximum
501 // if previous window was maximised
502 desktop.setDesktopManager(new MyDesktopManager(
503 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
504 : Platform.isAMacAndNotJS()
505 ? new AquaInternalFrameManager(
506 desktop.getDesktopManager())
507 : desktop.getDesktopManager())));
509 Rectangle dims = getLastKnownDimensions("");
516 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
517 int xPos = Math.max(5, (screenSize.width - 900) / 2);
518 int yPos = Math.max(5, (screenSize.height - 650) / 2);
519 setBounds(xPos, yPos, 900, 650);
522 if (!Platform.isJS())
529 jconsole = new Console(this, showjconsole);
530 jconsole.setHeader(Cache.getVersionDetailsForConsole());
531 showConsole(showjconsole);
533 showNews.setVisible(false);
535 experimentalFeatures.setSelected(showExperimental());
537 getIdentifiersOrgData();
541 // Spawn a thread that shows the splashscreen
544 SwingUtilities.invokeLater(new Runnable()
549 new SplashScreen(true);
554 // Thread off a new instance of the file chooser - this reduces the time
556 // takes to open it later on.
557 new Thread(new Runnable()
562 jalview.bin.Console.debug("Filechooser init thread started.");
563 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
564 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
566 jalview.bin.Console.debug("Filechooser init thread finished.");
569 // Add the service change listener
570 changeSupport.addJalviewPropertyChangeListener("services",
571 new PropertyChangeListener()
575 public void propertyChange(PropertyChangeEvent evt)
578 .debug("Firing service changed event for "
579 + evt.getNewValue());
580 JalviewServicesChanged(evt);
585 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
587 this.addWindowListener(new WindowAdapter()
590 public void windowClosing(WindowEvent evt)
597 this.addMouseListener(ma = new MouseAdapter()
600 public void mousePressed(MouseEvent evt)
602 if (evt.isPopupTrigger()) // Mac
604 showPasteMenu(evt.getX(), evt.getY());
609 public void mouseReleased(MouseEvent evt)
611 if (evt.isPopupTrigger()) // Windows
613 showPasteMenu(evt.getX(), evt.getY());
617 desktop.addMouseListener(ma);
621 * Answers true if user preferences to enable experimental features is True
626 public boolean showExperimental()
628 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
629 Boolean.FALSE.toString());
630 return Boolean.valueOf(experimental).booleanValue();
633 public void doConfigureStructurePrefs()
635 // configure services
636 StructureSelectionManager ssm = StructureSelectionManager
637 .getStructureSelectionManager(this);
638 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
640 ssm.setAddTempFacAnnot(
641 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
642 ssm.setProcessSecondaryStructure(
643 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
644 // JAL-3915 - RNAView is no longer an option so this has no effect
645 ssm.setSecStructServices(
646 Cache.getDefault(Preferences.USE_RNAVIEW, false));
650 ssm.setAddTempFacAnnot(false);
651 ssm.setProcessSecondaryStructure(false);
652 ssm.setSecStructServices(false);
656 public void checkForNews()
658 final Desktop me = this;
659 // Thread off the news reader, in case there are connection problems.
660 new Thread(new Runnable()
665 jalview.bin.Console.debug("Starting news thread.");
666 jvnews = new BlogReader(me);
667 showNews.setVisible(true);
668 jalview.bin.Console.debug("Completed news thread.");
673 public void getIdentifiersOrgData()
675 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
676 {// Thread off the identifiers fetcher
677 new Thread(new Runnable()
683 .debug("Downloading data from identifiers.org");
686 UrlDownloadClient.download(IdOrgSettings.getUrl(),
687 IdOrgSettings.getDownloadLocation());
688 } catch (IOException e)
691 .debug("Exception downloading identifiers.org data"
701 protected void showNews_actionPerformed(ActionEvent e)
703 showNews(showNews.isSelected());
706 void showNews(boolean visible)
708 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
709 showNews.setSelected(visible);
710 if (visible && !jvnews.isVisible())
712 new Thread(new Runnable()
717 long now = System.currentTimeMillis();
718 Desktop.instance.setProgressBar(
719 MessageManager.getString("status.refreshing_news"), now);
720 jvnews.refreshNews();
721 Desktop.instance.setProgressBar(null, now);
724 }, "ShowNewsWindowThread").start();
729 * recover the last known dimensions for a jalview window
732 * - empty string is desktop, all other windows have unique prefix
733 * @return null or last known dimensions scaled to current geometry (if last
734 * window geom was known)
736 Rectangle getLastKnownDimensions(String windowName)
738 // TODO: lock aspect ratio for scaling desktop Bug #0058199
739 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
740 String x = Cache.getProperty(windowName + "SCREEN_X");
741 String y = Cache.getProperty(windowName + "SCREEN_Y");
742 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
743 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
744 if ((x != null) && (y != null) && (width != null) && (height != null))
746 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
747 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
748 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
750 // attempt #1 - try to cope with change in screen geometry - this
751 // version doesn't preserve original jv aspect ratio.
752 // take ratio of current screen size vs original screen size.
753 double sw = ((1f * screenSize.width) / (1f * Integer
754 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
755 double sh = ((1f * screenSize.height) / (1f * Integer
756 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
757 // rescale the bounds depending upon the current screen geometry.
758 ix = (int) (ix * sw);
759 iw = (int) (iw * sw);
760 iy = (int) (iy * sh);
761 ih = (int) (ih * sh);
762 while (ix >= screenSize.width)
764 jalview.bin.Console.debug(
765 "Window geometry location recall error: shifting horizontal to within screenbounds.");
766 ix -= screenSize.width;
768 while (iy >= screenSize.height)
770 jalview.bin.Console.debug(
771 "Window geometry location recall error: shifting vertical to within screenbounds.");
772 iy -= screenSize.height;
774 jalview.bin.Console.debug(
775 "Got last known dimensions for " + windowName + ": x:" + ix
776 + " y:" + iy + " width:" + iw + " height:" + ih);
778 // return dimensions for new instance
779 return new Rectangle(ix, iy, iw, ih);
784 void showPasteMenu(int x, int y)
786 JPopupMenu popup = new JPopupMenu();
787 JMenuItem item = new JMenuItem(
788 MessageManager.getString("label.paste_new_window"));
789 item.addActionListener(new ActionListener()
792 public void actionPerformed(ActionEvent evt)
799 popup.show(this, x, y);
806 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
807 Transferable contents = c.getContents(this);
809 if (contents != null)
811 String file = (String) contents
812 .getTransferData(DataFlavor.stringFlavor);
814 FileFormatI format = new IdentifyFile().identify(file,
815 DataSourceType.PASTE);
817 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
820 } catch (Exception ex)
823 "Unable to paste alignment from system clipboard:\n" + ex);
828 * Adds and opens the given frame to the desktop
839 public static synchronized void addInternalFrame(
840 final JInternalFrame frame, String title, int w, int h)
842 addInternalFrame(frame, title, true, w, h, true, false);
846 * Add an internal frame to the Jalview desktop
853 * When true, display frame immediately, otherwise, caller must call
854 * setVisible themselves.
860 public static synchronized void addInternalFrame(
861 final JInternalFrame frame, String title, boolean makeVisible,
864 addInternalFrame(frame, title, makeVisible, w, h, true, false);
868 * Add an internal frame to the Jalview desktop and make it visible
881 public static synchronized void addInternalFrame(
882 final JInternalFrame frame, String title, int w, int h,
885 addInternalFrame(frame, title, true, w, h, resizable, false);
889 * Add an internal frame to the Jalview desktop
896 * When true, display frame immediately, otherwise, caller must call
897 * setVisible themselves.
904 * @param ignoreMinSize
905 * Do not set the default minimum size for frame
907 public static synchronized void addInternalFrame(
908 final JInternalFrame frame, String title, boolean makeVisible,
909 int w, int h, boolean resizable, boolean ignoreMinSize)
912 // TODO: allow callers to determine X and Y position of frame (eg. via
914 // TODO: consider fixing method to update entries in the window submenu with
915 // the current window title
917 frame.setTitle(title);
918 if (frame.getWidth() < 1 || frame.getHeight() < 1)
922 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
923 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
924 // IF JALVIEW IS RUNNING HEADLESS
925 // ///////////////////////////////////////////////
926 if (instance == null || (System.getProperty("java.awt.headless") != null
927 && System.getProperty("java.awt.headless").equals("true")))
936 frame.setMinimumSize(
937 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
939 // Set default dimension for Alignment Frame window.
940 // The Alignment Frame window could be added from a number of places,
942 // I did this here in order not to miss out on any Alignment frame.
943 if (frame instanceof AlignFrame)
945 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
946 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
950 frame.setVisible(makeVisible);
951 frame.setClosable(true);
952 frame.setResizable(resizable);
953 frame.setMaximizable(resizable);
954 frame.setIconifiable(resizable);
955 frame.setOpaque(Platform.isJS());
957 if (frame.getX() < 1 && frame.getY() < 1)
959 frame.setLocation(xOffset * openFrameCount,
960 yOffset * ((openFrameCount - 1) % 10) + yOffset);
964 * add an entry for the new frame in the Window menu (and remove it when the
967 final JMenuItem menuItem = new JMenuItem(title);
968 frame.addInternalFrameListener(new InternalFrameAdapter()
971 public void internalFrameActivated(InternalFrameEvent evt)
973 JInternalFrame itf = desktop.getSelectedFrame();
976 if (itf instanceof AlignFrame)
978 Jalview.setCurrentAlignFrame((AlignFrame) itf);
985 public void internalFrameClosed(InternalFrameEvent evt)
987 PaintRefresher.RemoveComponent(frame);
990 * defensive check to prevent frames being added half off the window
992 if (openFrameCount > 0)
998 * ensure no reference to alignFrame retained by menu item listener
1000 if (menuItem.getActionListeners().length > 0)
1002 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1004 windowMenu.remove(menuItem);
1008 menuItem.addActionListener(new ActionListener()
1011 public void actionPerformed(ActionEvent e)
1015 frame.setSelected(true);
1016 frame.setIcon(false);
1017 } catch (java.beans.PropertyVetoException ex)
1024 setKeyBindings(frame);
1028 windowMenu.add(menuItem);
1033 frame.setSelected(true);
1034 frame.requestFocus();
1035 } catch (java.beans.PropertyVetoException ve)
1037 } catch (java.lang.ClassCastException cex)
1039 jalview.bin.Console.warn(
1040 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1046 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1051 private static void setKeyBindings(JInternalFrame frame)
1053 @SuppressWarnings("serial")
1054 final Action closeAction = new AbstractAction()
1057 public void actionPerformed(ActionEvent e)
1064 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1066 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1067 InputEvent.CTRL_DOWN_MASK);
1068 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1069 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1071 InputMap inputMap = frame
1072 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1073 String ctrlW = ctrlWKey.toString();
1074 inputMap.put(ctrlWKey, ctrlW);
1075 inputMap.put(cmdWKey, ctrlW);
1077 ActionMap actionMap = frame.getActionMap();
1078 actionMap.put(ctrlW, closeAction);
1082 public void lostOwnership(Clipboard clipboard, Transferable contents)
1086 Desktop.jalviewClipboard = null;
1089 internalCopy = false;
1093 public void dragEnter(DropTargetDragEvent evt)
1098 public void dragExit(DropTargetEvent evt)
1103 public void dragOver(DropTargetDragEvent evt)
1108 public void dropActionChanged(DropTargetDragEvent evt)
1119 public void drop(DropTargetDropEvent evt)
1121 boolean success = true;
1122 // JAL-1552 - acceptDrop required before getTransferable call for
1123 // Java's Transferable for native dnd
1124 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1125 Transferable t = evt.getTransferable();
1126 List<Object> files = new ArrayList<>();
1127 List<DataSourceType> protocols = new ArrayList<>();
1131 Desktop.transferFromDropTarget(files, protocols, evt, t);
1132 } catch (Exception e)
1134 e.printStackTrace();
1142 for (int i = 0; i < files.size(); i++)
1144 // BH 2018 File or String
1145 Object file = files.get(i);
1146 String fileName = file.toString();
1147 DataSourceType protocol = (protocols == null)
1148 ? DataSourceType.FILE
1150 FileFormatI format = null;
1152 if (fileName.endsWith(".jar"))
1154 format = FileFormat.Jalview;
1159 format = new IdentifyFile().identify(file, protocol);
1161 if (file instanceof File)
1163 Platform.cacheFileData((File) file);
1165 new FileLoader().LoadFile(null, file, protocol, format);
1168 } catch (Exception ex)
1173 evt.dropComplete(success); // need this to ensure input focus is properly
1174 // transfered to any new windows created
1184 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1186 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1187 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1188 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1189 BackupFiles.getEnabled());
1191 chooser.setFileView(new JalviewFileView());
1192 chooser.setDialogTitle(
1193 MessageManager.getString("label.open_local_file"));
1194 chooser.setToolTipText(MessageManager.getString("action.open"));
1196 chooser.setResponseHandler(0, new Runnable()
1201 File selectedFile = chooser.getSelectedFile();
1202 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1204 FileFormatI format = chooser.getSelectedFormat();
1207 * Call IdentifyFile to verify the file contains what its extension implies.
1208 * Skip this step for dynamically added file formats, because IdentifyFile does
1209 * not know how to recognise them.
1211 if (FileFormats.getInstance().isIdentifiable(format))
1215 format = new IdentifyFile().identify(selectedFile,
1216 DataSourceType.FILE);
1217 } catch (FileFormatException e)
1219 // format = null; //??
1223 new FileLoader().LoadFile(viewport, selectedFile,
1224 DataSourceType.FILE, format);
1227 chooser.showOpenDialog(this);
1231 * Shows a dialog for input of a URL at which to retrieve alignment data
1236 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1238 // This construct allows us to have a wider textfield
1240 JLabel label = new JLabel(
1241 MessageManager.getString("label.input_file_url"));
1243 JPanel panel = new JPanel(new GridLayout(2, 1));
1247 * the URL to fetch is input in Java: an editable combobox with history JS:
1248 * (pending JAL-3038) a plain text field
1251 String urlBase = "https://www.";
1252 if (Platform.isJS())
1254 history = new JTextField(urlBase, 35);
1263 JComboBox<String> asCombo = new JComboBox<>();
1264 asCombo.setPreferredSize(new Dimension(400, 20));
1265 asCombo.setEditable(true);
1266 asCombo.addItem(urlBase);
1267 String historyItems = Cache.getProperty("RECENT_URL");
1268 if (historyItems != null)
1270 for (String token : historyItems.split("\\t"))
1272 asCombo.addItem(token);
1279 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1280 MessageManager.getString("action.cancel") };
1281 Runnable action = new Runnable()
1286 @SuppressWarnings("unchecked")
1287 String url = (history instanceof JTextField
1288 ? ((JTextField) history).getText()
1289 : ((JComboBox<String>) history).getEditor().getItem()
1290 .toString().trim());
1292 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1294 if (viewport != null)
1296 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1297 FileFormat.Jalview);
1301 new FileLoader().LoadFile(url, DataSourceType.URL,
1302 FileFormat.Jalview);
1307 FileFormatI format = null;
1310 format = new IdentifyFile().identify(url, DataSourceType.URL);
1311 } catch (FileFormatException e)
1313 // TODO revise error handling, distinguish between
1314 // URL not found and response not valid
1319 String msg = MessageManager
1320 .formatMessage("label.couldnt_locate", url);
1321 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1322 MessageManager.getString("label.url_not_found"),
1323 JvOptionPane.WARNING_MESSAGE);
1328 if (viewport != null)
1330 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1335 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1340 String dialogOption = MessageManager
1341 .getString("label.input_alignment_from_url");
1342 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1343 .showInternalDialog(panel, dialogOption,
1344 JvOptionPane.YES_NO_CANCEL_OPTION,
1345 JvOptionPane.PLAIN_MESSAGE, null, options,
1346 MessageManager.getString("action.ok"));
1350 * Opens the CutAndPaste window for the user to paste an alignment in to
1353 * - if not null, the pasted alignment is added to the current
1354 * alignment; if null, to a new alignment window
1357 public void inputTextboxMenuItem_actionPerformed(
1358 AlignmentViewPanel viewPanel)
1360 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1361 cap.setForInput(viewPanel);
1362 Desktop.addInternalFrame(cap,
1363 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1373 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1374 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1375 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1376 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1377 getWidth(), getHeight()));
1379 if (jconsole != null)
1381 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1382 jconsole.stopConsole();
1386 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1389 if (dialogExecutor != null)
1391 dialogExecutor.shutdownNow();
1393 closeAll_actionPerformed(null);
1395 if (groovyConsole != null)
1397 // suppress a possible repeat prompt to save script
1398 groovyConsole.setDirty(false);
1399 groovyConsole.exit();
1404 private void storeLastKnownDimensions(String string, Rectangle jc)
1406 jalview.bin.Console.debug("Storing last known dimensions for " + string
1407 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1408 + " height:" + jc.height);
1410 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1411 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1412 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1413 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1423 public void aboutMenuItem_actionPerformed(ActionEvent e)
1425 new Thread(new Runnable()
1430 new SplashScreen(false);
1432 }, "ShowAboutMenu").start();
1436 * Returns the html text for the About screen, including any available version
1437 * number, build details, author details and citation reference, but without
1438 * the enclosing {@code html} tags
1442 public String getAboutMessage()
1444 StringBuilder message = new StringBuilder(1024);
1445 message.append("<div style=\"font-family: sans-serif;\">")
1446 .append("<h1><strong>Version: ")
1447 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1448 .append("<strong>Built: <em>")
1449 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1450 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1451 .append("</strong>");
1453 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1454 if (latestVersion.equals("Checking"))
1456 // JBP removed this message for 2.11: May be reinstated in future version
1457 // message.append("<br>...Checking latest version...</br>");
1459 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1461 boolean red = false;
1462 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1463 .indexOf("automated build") == -1)
1466 // Displayed when code version and jnlp version do not match and code
1467 // version is not a development build
1468 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1471 message.append("<br>!! Version ")
1472 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1473 .append(" is available for download from ")
1474 .append(Cache.getDefault("www.jalview.org",
1475 "https://www.jalview.org"))
1479 message.append("</div>");
1482 message.append("<br>Authors: ");
1483 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1484 message.append(CITATION);
1486 message.append("</div>");
1488 return message.toString();
1492 * Action on requesting Help documentation
1495 public void documentationMenuItem_actionPerformed()
1499 if (Platform.isJS())
1501 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1510 Help.showHelpWindow();
1512 } catch (Exception ex)
1514 System.err.println("Error opening help: " + ex.getMessage());
1519 public void closeAll_actionPerformed(ActionEvent e)
1521 // TODO show a progress bar while closing?
1522 JInternalFrame[] frames = desktop.getAllFrames();
1523 for (int i = 0; i < frames.length; i++)
1527 frames[i].setClosed(true);
1528 } catch (java.beans.PropertyVetoException ex)
1532 Jalview.setCurrentAlignFrame(null);
1533 System.out.println("ALL CLOSED");
1536 * reset state of singleton objects as appropriate (clear down session state
1537 * when all windows are closed)
1539 StructureSelectionManager ssm = StructureSelectionManager
1540 .getStructureSelectionManager(this);
1548 public void raiseRelated_actionPerformed(ActionEvent e)
1550 reorderAssociatedWindows(false, false);
1554 public void minimizeAssociated_actionPerformed(ActionEvent e)
1556 reorderAssociatedWindows(true, false);
1559 void closeAssociatedWindows()
1561 reorderAssociatedWindows(false, true);
1567 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1571 protected void garbageCollect_actionPerformed(ActionEvent e)
1573 // We simply collect the garbage
1574 jalview.bin.Console.debug("Collecting garbage...");
1576 jalview.bin.Console.debug("Finished garbage collection.");
1582 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1586 protected void showMemusage_actionPerformed(ActionEvent e)
1588 desktop.showMemoryUsage(showMemusage.isSelected());
1595 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1599 protected void showConsole_actionPerformed(ActionEvent e)
1601 showConsole(showConsole.isSelected());
1604 Console jconsole = null;
1607 * control whether the java console is visible or not
1611 void showConsole(boolean selected)
1613 // TODO: decide if we should update properties file
1614 if (jconsole != null) // BH 2018
1616 showConsole.setSelected(selected);
1617 Cache.setProperty("SHOW_JAVA_CONSOLE",
1618 Boolean.valueOf(selected).toString());
1619 jconsole.setVisible(selected);
1623 void reorderAssociatedWindows(boolean minimize, boolean close)
1625 JInternalFrame[] frames = desktop.getAllFrames();
1626 if (frames == null || frames.length < 1)
1631 AlignmentViewport source = null, target = null;
1632 if (frames[0] instanceof AlignFrame)
1634 source = ((AlignFrame) frames[0]).getCurrentView();
1636 else if (frames[0] instanceof TreePanel)
1638 source = ((TreePanel) frames[0]).getViewPort();
1640 else if (frames[0] instanceof PCAPanel)
1642 source = ((PCAPanel) frames[0]).av;
1644 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1646 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1651 for (int i = 0; i < frames.length; i++)
1654 if (frames[i] == null)
1658 if (frames[i] instanceof AlignFrame)
1660 target = ((AlignFrame) frames[i]).getCurrentView();
1662 else if (frames[i] instanceof TreePanel)
1664 target = ((TreePanel) frames[i]).getViewPort();
1666 else if (frames[i] instanceof PCAPanel)
1668 target = ((PCAPanel) frames[i]).av;
1670 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1672 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1675 if (source == target)
1681 frames[i].setClosed(true);
1685 frames[i].setIcon(minimize);
1688 frames[i].toFront();
1692 } catch (java.beans.PropertyVetoException ex)
1707 protected void preferences_actionPerformed(ActionEvent e)
1709 Preferences.openPreferences();
1713 * Prompts the user to choose a file and then saves the Jalview state as a
1714 * Jalview project file
1717 public void saveState_actionPerformed()
1719 saveState_actionPerformed(false);
1722 public void saveState_actionPerformed(boolean saveAs)
1724 java.io.File projectFile = getProjectFile();
1725 // autoSave indicates we already have a file and don't need to ask
1726 boolean autoSave = projectFile != null && !saveAs
1727 && BackupFiles.getEnabled();
1729 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1730 // saveAs="+saveAs+", Backups
1731 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1733 boolean approveSave = false;
1736 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1739 chooser.setFileView(new JalviewFileView());
1740 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1742 int value = chooser.showSaveDialog(this);
1744 if (value == JalviewFileChooser.APPROVE_OPTION)
1746 projectFile = chooser.getSelectedFile();
1747 setProjectFile(projectFile);
1752 if (approveSave || autoSave)
1754 final Desktop me = this;
1755 final java.io.File chosenFile = projectFile;
1756 new Thread(new Runnable()
1761 // TODO: refactor to Jalview desktop session controller action.
1762 setProgressBar(MessageManager.formatMessage(
1763 "label.saving_jalview_project", new Object[]
1764 { chosenFile.getName() }), chosenFile.hashCode());
1765 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1766 // TODO catch and handle errors for savestate
1767 // TODO prevent user from messing with the Desktop whilst we're saving
1770 boolean doBackup = BackupFiles.getEnabled();
1771 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1774 new Jalview2XML().saveState(
1775 doBackup ? backupfiles.getTempFile() : chosenFile);
1779 backupfiles.setWriteSuccess(true);
1780 backupfiles.rollBackupsAndRenameTempFile();
1782 } catch (OutOfMemoryError oom)
1784 new OOMWarning("Whilst saving current state to "
1785 + chosenFile.getName(), oom);
1786 } catch (Exception ex)
1788 jalview.bin.Console.error("Problems whilst trying to save to "
1789 + chosenFile.getName(), ex);
1790 JvOptionPane.showMessageDialog(me,
1791 MessageManager.formatMessage(
1792 "label.error_whilst_saving_current_state_to",
1794 { chosenFile.getName() }),
1795 MessageManager.getString("label.couldnt_save_project"),
1796 JvOptionPane.WARNING_MESSAGE);
1798 setProgressBar(null, chosenFile.hashCode());
1800 }, "SaveJalviewProject").start();
1805 public void saveAsState_actionPerformed(ActionEvent e)
1807 saveState_actionPerformed(true);
1810 private void setProjectFile(File choice)
1812 this.projectFile = choice;
1815 public File getProjectFile()
1817 return this.projectFile;
1821 * Shows a file chooser dialog and tries to read in the selected file as a
1825 public void loadState_actionPerformed()
1827 final String[] suffix = new String[] { "jvp", "jar" };
1828 final String[] desc = new String[] { "Jalview Project",
1829 "Jalview Project (old)" };
1830 JalviewFileChooser chooser = new JalviewFileChooser(
1831 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1832 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1836 chooser.setFileView(new JalviewFileView());
1837 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1838 chooser.setResponseHandler(0, new Runnable()
1843 File selectedFile = chooser.getSelectedFile();
1844 setProjectFile(selectedFile);
1845 final String choice = selectedFile.getAbsolutePath();
1846 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1847 new Thread(new Runnable()
1852 setProgressBar(MessageManager.formatMessage(
1853 "label.loading_jalview_project", new Object[]
1854 { choice }), choice.hashCode());
1858 new Jalview2XML().loadJalviewAlign(selectedFile);
1859 } catch (OutOfMemoryError oom)
1861 new OOMWarning("Whilst loading project from " + choice, oom);
1862 } catch (Exception ex)
1864 jalview.bin.Console.error(
1865 "Problems whilst loading project from " + choice, ex);
1866 JvOptionPane.showMessageDialog(Desktop.desktop,
1867 MessageManager.formatMessage(
1868 "label.error_whilst_loading_project_from",
1872 .getString("label.couldnt_load_project"),
1873 JvOptionPane.WARNING_MESSAGE);
1875 setProgressBar(null, choice.hashCode());
1877 }, "Project Loader").start();
1881 chooser.showOpenDialog(this);
1885 public void inputSequence_actionPerformed(ActionEvent e)
1887 new SequenceFetcher(this);
1890 JPanel progressPanel;
1892 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1894 public void startLoading(final Object fileName)
1896 if (fileLoadingCount == 0)
1898 fileLoadingPanels.add(addProgressPanel(MessageManager
1899 .formatMessage("label.loading_file", new Object[]
1905 private JPanel addProgressPanel(String string)
1907 if (progressPanel == null)
1909 progressPanel = new JPanel(new GridLayout(1, 1));
1910 totalProgressCount = 0;
1911 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1913 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1914 JProgressBar progressBar = new JProgressBar();
1915 progressBar.setIndeterminate(true);
1917 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1919 thisprogress.add(progressBar, BorderLayout.CENTER);
1920 progressPanel.add(thisprogress);
1921 ((GridLayout) progressPanel.getLayout()).setRows(
1922 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1923 ++totalProgressCount;
1924 instance.validate();
1925 return thisprogress;
1928 int totalProgressCount = 0;
1930 private void removeProgressPanel(JPanel progbar)
1932 if (progressPanel != null)
1934 synchronized (progressPanel)
1936 progressPanel.remove(progbar);
1937 GridLayout gl = (GridLayout) progressPanel.getLayout();
1938 gl.setRows(gl.getRows() - 1);
1939 if (--totalProgressCount < 1)
1941 this.getContentPane().remove(progressPanel);
1942 progressPanel = null;
1949 public void stopLoading()
1952 if (fileLoadingCount < 1)
1954 while (fileLoadingPanels.size() > 0)
1956 removeProgressPanel(fileLoadingPanels.remove(0));
1958 fileLoadingPanels.clear();
1959 fileLoadingCount = 0;
1964 public static int getViewCount(String alignmentId)
1966 AlignmentViewport[] aps = getViewports(alignmentId);
1967 return (aps == null) ? 0 : aps.length;
1972 * @param alignmentId
1973 * - if null, all sets are returned
1974 * @return all AlignmentPanels concerning the alignmentId sequence set
1976 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1978 if (Desktop.desktop == null)
1980 // no frames created and in headless mode
1981 // TODO: verify that frames are recoverable when in headless mode
1984 List<AlignmentPanel> aps = new ArrayList<>();
1985 AlignFrame[] frames = getAlignFrames();
1990 for (AlignFrame af : frames)
1992 for (AlignmentPanel ap : af.alignPanels)
1994 if (alignmentId == null
1995 || alignmentId.equals(ap.av.getSequenceSetId()))
2001 if (aps.size() == 0)
2005 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2010 * get all the viewports on an alignment.
2012 * @param sequenceSetId
2013 * unique alignment id (may be null - all viewports returned in that
2015 * @return all viewports on the alignment bound to sequenceSetId
2017 public static AlignmentViewport[] getViewports(String sequenceSetId)
2019 List<AlignmentViewport> viewp = new ArrayList<>();
2020 if (desktop != null)
2022 AlignFrame[] frames = Desktop.getAlignFrames();
2024 for (AlignFrame afr : frames)
2026 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2027 .equals(sequenceSetId))
2029 if (afr.alignPanels != null)
2031 for (AlignmentPanel ap : afr.alignPanels)
2033 if (sequenceSetId == null
2034 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2042 viewp.add(afr.getViewport());
2046 if (viewp.size() > 0)
2048 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2055 * Explode the views in the given frame into separate AlignFrame
2059 public static void explodeViews(AlignFrame af)
2061 int size = af.alignPanels.size();
2067 // FIXME: ideally should use UI interface API
2068 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2069 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2070 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2071 for (int i = 0; i < size; i++)
2073 AlignmentPanel ap = af.alignPanels.get(i);
2075 AlignFrame newaf = new AlignFrame(ap);
2077 // transfer reference for existing feature settings to new alignFrame
2078 if (ap == af.alignPanel)
2080 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2082 newaf.featureSettings = viewFeatureSettings;
2084 newaf.setFeatureSettingsGeometry(fsBounds);
2088 * Restore the view's last exploded frame geometry if known. Multiple views from
2089 * one exploded frame share and restore the same (frame) position and size.
2091 Rectangle geometry = ap.av.getExplodedGeometry();
2092 if (geometry != null)
2094 newaf.setBounds(geometry);
2097 ap.av.setGatherViewsHere(false);
2099 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2100 AlignFrame.DEFAULT_HEIGHT);
2101 // and materialise a new feature settings dialog instance for the new
2103 // (closes the old as if 'OK' was pressed)
2104 if (ap == af.alignPanel && newaf.featureSettings != null
2105 && newaf.featureSettings.isOpen()
2106 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2108 newaf.showFeatureSettingsUI();
2112 af.featureSettings = null;
2113 af.alignPanels.clear();
2114 af.closeMenuItem_actionPerformed(true);
2119 * Gather expanded views (separate AlignFrame's) with the same sequence set
2120 * identifier back in to this frame as additional views, and close the
2121 * expanded views. Note the expanded frames may themselves have multiple
2122 * views. We take the lot.
2126 public void gatherViews(AlignFrame source)
2128 source.viewport.setGatherViewsHere(true);
2129 source.viewport.setExplodedGeometry(source.getBounds());
2130 JInternalFrame[] frames = desktop.getAllFrames();
2131 String viewId = source.viewport.getSequenceSetId();
2132 for (int t = 0; t < frames.length; t++)
2134 if (frames[t] instanceof AlignFrame && frames[t] != source)
2136 AlignFrame af = (AlignFrame) frames[t];
2137 boolean gatherThis = false;
2138 for (int a = 0; a < af.alignPanels.size(); a++)
2140 AlignmentPanel ap = af.alignPanels.get(a);
2141 if (viewId.equals(ap.av.getSequenceSetId()))
2144 ap.av.setGatherViewsHere(false);
2145 ap.av.setExplodedGeometry(af.getBounds());
2146 source.addAlignmentPanel(ap, false);
2152 if (af.featureSettings != null && af.featureSettings.isOpen())
2154 if (source.featureSettings == null)
2156 // preserve the feature settings geometry for this frame
2157 source.featureSettings = af.featureSettings;
2158 source.setFeatureSettingsGeometry(
2159 af.getFeatureSettingsGeometry());
2163 // close it and forget
2164 af.featureSettings.close();
2167 af.alignPanels.clear();
2168 af.closeMenuItem_actionPerformed(true);
2173 // refresh the feature setting UI for the source frame if it exists
2174 if (source.featureSettings != null && source.featureSettings.isOpen())
2176 source.showFeatureSettingsUI();
2180 public JInternalFrame[] getAllFrames()
2182 return desktop.getAllFrames();
2186 * Checks the given url to see if it gives a response indicating that the user
2187 * should be informed of a new questionnaire.
2191 public void checkForQuestionnaire(String url)
2193 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2194 // javax.swing.SwingUtilities.invokeLater(jvq);
2195 new Thread(jvq, "CheckQuestionnaire").start();
2198 public void checkURLLinks()
2200 // Thread off the URL link checker
2201 addDialogThread(new Runnable()
2206 if (Cache.getDefault("CHECKURLLINKS", true))
2208 // check what the actual links are - if it's just the default don't
2209 // bother with the warning
2210 List<String> links = Preferences.sequenceUrlLinks
2213 // only need to check links if there is one with a
2214 // SEQUENCE_ID which is not the default EMBL_EBI link
2215 ListIterator<String> li = links.listIterator();
2216 boolean check = false;
2217 List<JLabel> urls = new ArrayList<>();
2218 while (li.hasNext())
2220 String link = li.next();
2221 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2222 && !UrlConstants.isDefaultString(link))
2225 int barPos = link.indexOf("|");
2226 String urlMsg = barPos == -1 ? link
2227 : link.substring(0, barPos) + ": "
2228 + link.substring(barPos + 1);
2229 urls.add(new JLabel(urlMsg));
2237 // ask user to check in case URL links use old style tokens
2238 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2239 JPanel msgPanel = new JPanel();
2240 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2241 msgPanel.add(Box.createVerticalGlue());
2242 JLabel msg = new JLabel(MessageManager
2243 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2244 JLabel msg2 = new JLabel(MessageManager
2245 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2247 for (JLabel url : urls)
2253 final JCheckBox jcb = new JCheckBox(
2254 MessageManager.getString("label.do_not_display_again"));
2255 jcb.addActionListener(new ActionListener()
2258 public void actionPerformed(ActionEvent e)
2260 // update Cache settings for "don't show this again"
2261 boolean showWarningAgain = !jcb.isSelected();
2262 Cache.setProperty("CHECKURLLINKS",
2263 Boolean.valueOf(showWarningAgain).toString());
2268 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2270 .getString("label.SEQUENCE_ID_no_longer_used"),
2271 JvOptionPane.WARNING_MESSAGE);
2278 * Proxy class for JDesktopPane which optionally displays the current memory
2279 * usage and highlights the desktop area with a red bar if free memory runs
2284 public class MyDesktopPane extends JDesktopPane implements Runnable
2286 private static final float ONE_MB = 1048576f;
2288 boolean showMemoryUsage = false;
2292 java.text.NumberFormat df;
2294 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2297 public MyDesktopPane(boolean showMemoryUsage)
2299 showMemoryUsage(showMemoryUsage);
2302 public void showMemoryUsage(boolean showMemory)
2304 this.showMemoryUsage = showMemory;
2307 Thread worker = new Thread(this, "ShowMemoryUsage");
2313 public boolean isShowMemoryUsage()
2315 return showMemoryUsage;
2321 df = java.text.NumberFormat.getNumberInstance();
2322 df.setMaximumFractionDigits(2);
2323 runtime = Runtime.getRuntime();
2325 while (showMemoryUsage)
2329 maxMemory = runtime.maxMemory() / ONE_MB;
2330 allocatedMemory = runtime.totalMemory() / ONE_MB;
2331 freeMemory = runtime.freeMemory() / ONE_MB;
2332 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2334 percentUsage = (totalFreeMemory / maxMemory) * 100;
2336 // if (percentUsage < 20)
2338 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2340 // instance.set.setBorder(border1);
2343 // sleep after showing usage
2345 } catch (Exception ex)
2347 ex.printStackTrace();
2353 public void paintComponent(Graphics g)
2355 if (showMemoryUsage && g != null && df != null)
2357 if (percentUsage < 20)
2359 g.setColor(Color.red);
2361 FontMetrics fm = g.getFontMetrics();
2364 g.drawString(MessageManager.formatMessage("label.memory_stats",
2366 { df.format(totalFreeMemory), df.format(maxMemory),
2367 df.format(percentUsage) }),
2368 10, getHeight() - fm.getHeight());
2372 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2373 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2378 * Accessor method to quickly get all the AlignmentFrames loaded.
2380 * @return an array of AlignFrame, or null if none found
2382 public static AlignFrame[] getAlignFrames()
2384 if (Jalview.isHeadlessMode())
2386 // Desktop.desktop is null in headless mode
2387 return new AlignFrame[] { Jalview.currentAlignFrame };
2390 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2396 List<AlignFrame> avp = new ArrayList<>();
2398 for (int i = frames.length - 1; i > -1; i--)
2400 if (frames[i] instanceof AlignFrame)
2402 avp.add((AlignFrame) frames[i]);
2404 else if (frames[i] instanceof SplitFrame)
2407 * Also check for a split frame containing an AlignFrame
2409 GSplitFrame sf = (GSplitFrame) frames[i];
2410 if (sf.getTopFrame() instanceof AlignFrame)
2412 avp.add((AlignFrame) sf.getTopFrame());
2414 if (sf.getBottomFrame() instanceof AlignFrame)
2416 avp.add((AlignFrame) sf.getBottomFrame());
2420 if (avp.size() == 0)
2424 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2429 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2433 public GStructureViewer[] getJmols()
2435 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2441 List<GStructureViewer> avp = new ArrayList<>();
2443 for (int i = frames.length - 1; i > -1; i--)
2445 if (frames[i] instanceof AppJmol)
2447 GStructureViewer af = (GStructureViewer) frames[i];
2451 if (avp.size() == 0)
2455 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2460 * Add Groovy Support to Jalview
2463 public void groovyShell_actionPerformed()
2467 openGroovyConsole();
2468 } catch (Exception ex)
2470 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2471 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2473 MessageManager.getString("label.couldnt_create_groovy_shell"),
2474 MessageManager.getString("label.groovy_support_failed"),
2475 JvOptionPane.ERROR_MESSAGE);
2480 * Open the Groovy console
2482 void openGroovyConsole()
2484 if (groovyConsole == null)
2486 groovyConsole = new groovy.ui.Console();
2487 groovyConsole.setVariable("Jalview", this);
2488 groovyConsole.run();
2491 * We allow only one console at a time, so that AlignFrame menu option
2492 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2493 * enable 'Run script', when the console is opened, and the reverse when it is
2496 Window window = (Window) groovyConsole.getFrame();
2497 window.addWindowListener(new WindowAdapter()
2500 public void windowClosed(WindowEvent e)
2503 * rebind CMD-Q from Groovy Console to Jalview Quit
2506 enableExecuteGroovy(false);
2512 * show Groovy console window (after close and reopen)
2514 ((Window) groovyConsole.getFrame()).setVisible(true);
2517 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2518 * opening a second console
2520 enableExecuteGroovy(true);
2524 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2525 * binding when opened
2527 protected void addQuitHandler()
2530 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2532 .getKeyStroke(KeyEvent.VK_Q,
2533 jalview.util.ShortcutKeyMaskExWrapper
2534 .getMenuShortcutKeyMaskEx()),
2536 getRootPane().getActionMap().put("Quit", new AbstractAction()
2539 public void actionPerformed(ActionEvent e)
2547 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2550 * true if Groovy console is open
2552 public void enableExecuteGroovy(boolean enabled)
2555 * disable opening a second Groovy console (or re-enable when the console is
2558 groovyShell.setEnabled(!enabled);
2560 AlignFrame[] alignFrames = getAlignFrames();
2561 if (alignFrames != null)
2563 for (AlignFrame af : alignFrames)
2565 af.setGroovyEnabled(enabled);
2571 * Progress bars managed by the IProgressIndicator method.
2573 private Hashtable<Long, JPanel> progressBars;
2575 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2580 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2583 public void setProgressBar(String message, long id)
2585 if (progressBars == null)
2587 progressBars = new Hashtable<>();
2588 progressBarHandlers = new Hashtable<>();
2591 if (progressBars.get(Long.valueOf(id)) != null)
2593 JPanel panel = progressBars.remove(Long.valueOf(id));
2594 if (progressBarHandlers.contains(Long.valueOf(id)))
2596 progressBarHandlers.remove(Long.valueOf(id));
2598 removeProgressPanel(panel);
2602 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2609 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2610 * jalview.gui.IProgressIndicatorHandler)
2613 public void registerHandler(final long id,
2614 final IProgressIndicatorHandler handler)
2616 if (progressBarHandlers == null
2617 || !progressBars.containsKey(Long.valueOf(id)))
2619 throw new Error(MessageManager.getString(
2620 "error.call_setprogressbar_before_registering_handler"));
2622 progressBarHandlers.put(Long.valueOf(id), handler);
2623 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2624 if (handler.canCancel())
2626 JButton cancel = new JButton(
2627 MessageManager.getString("action.cancel"));
2628 final IProgressIndicator us = this;
2629 cancel.addActionListener(new ActionListener()
2633 public void actionPerformed(ActionEvent e)
2635 handler.cancelActivity(id);
2636 us.setProgressBar(MessageManager
2637 .formatMessage("label.cancelled_params", new Object[]
2638 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2642 progressPanel.add(cancel, BorderLayout.EAST);
2648 * @return true if any progress bars are still active
2651 public boolean operationInProgress()
2653 if (progressBars != null && progressBars.size() > 0)
2661 * This will return the first AlignFrame holding the given viewport instance.
2662 * It will break if there are more than one AlignFrames viewing a particular
2666 * @return alignFrame for viewport
2668 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2670 if (desktop != null)
2672 AlignmentPanel[] aps = getAlignmentPanels(
2673 viewport.getSequenceSetId());
2674 for (int panel = 0; aps != null && panel < aps.length; panel++)
2676 if (aps[panel] != null && aps[panel].av == viewport)
2678 return aps[panel].alignFrame;
2685 public VamsasApplication getVamsasApplication()
2687 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2693 * flag set if jalview GUI is being operated programmatically
2695 private boolean inBatchMode = false;
2698 * check if jalview GUI is being operated programmatically
2700 * @return inBatchMode
2702 public boolean isInBatchMode()
2708 * set flag if jalview GUI is being operated programmatically
2710 * @param inBatchMode
2712 public void setInBatchMode(boolean inBatchMode)
2714 this.inBatchMode = inBatchMode;
2718 * start service discovery and wait till it is done
2720 public void startServiceDiscovery()
2722 startServiceDiscovery(false);
2726 * start service discovery threads - blocking or non-blocking
2730 public void startServiceDiscovery(boolean blocking)
2732 startServiceDiscovery(blocking, false);
2736 * start service discovery threads
2739 * - false means call returns immediately
2740 * @param ignore_SHOW_JWS2_SERVICES_preference
2741 * - when true JABA services are discovered regardless of user's JWS2
2742 * discovery preference setting
2744 public void startServiceDiscovery(boolean blocking,
2745 boolean ignore_SHOW_JWS2_SERVICES_preference)
2747 boolean alive = true;
2748 Thread t0 = null, t1 = null, t2 = null;
2749 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2752 // todo: changesupport handlers need to be transferred
2753 if (discoverer == null)
2755 discoverer = new jalview.ws.jws1.Discoverer();
2756 // register PCS handler for desktop.
2757 discoverer.addPropertyChangeListener(changeSupport);
2759 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2760 // until we phase out completely
2761 (t0 = new Thread(discoverer, "Discoverer")).start();
2764 if (ignore_SHOW_JWS2_SERVICES_preference
2765 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2767 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2768 .startDiscoverer(changeSupport);
2772 // TODO: do rest service discovery
2781 } catch (Exception e)
2784 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2785 || (t3 != null && t3.isAlive())
2786 || (t0 != null && t0.isAlive());
2792 * called to check if the service discovery process completed successfully.
2796 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2798 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2800 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2801 .getErrorMessages();
2804 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2806 if (serviceChangedDialog == null)
2808 // only run if we aren't already displaying one of these.
2809 addDialogThread(serviceChangedDialog = new Runnable()
2816 * JalviewDialog jd =new JalviewDialog() {
2818 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2820 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2822 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2824 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2826 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2827 * + " or mis-configured HTTP proxy settings.<br/>" +
2828 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2829 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2830 * true, true, "Web Service Configuration Problem", 450, 400);
2832 * jd.waitForInput();
2834 JvOptionPane.showConfirmDialog(Desktop.desktop,
2835 new JLabel("<html><table width=\"450\"><tr><td>"
2836 + ermsg + "</td></tr></table>"
2837 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2838 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2839 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2840 + " Tools->Preferences dialog box to change them.</p></html>"),
2841 "Web Service Configuration Problem",
2842 JvOptionPane.DEFAULT_OPTION,
2843 JvOptionPane.ERROR_MESSAGE);
2844 serviceChangedDialog = null;
2852 jalview.bin.Console.error(
2853 "Errors reported by JABA discovery service. Check web services preferences.\n"
2860 private Runnable serviceChangedDialog = null;
2863 * start a thread to open a URL in the configured browser. Pops up a warning
2864 * dialog to the user if there is an exception when calling out to the browser
2869 public static void showUrl(final String url)
2871 showUrl(url, Desktop.instance);
2875 * Like showUrl but allows progress handler to be specified
2879 * (null) or object implementing IProgressIndicator
2881 public static void showUrl(final String url,
2882 final IProgressIndicator progress)
2884 new Thread(new Runnable()
2891 if (progress != null)
2893 progress.setProgressBar(MessageManager
2894 .formatMessage("status.opening_params", new Object[]
2895 { url }), this.hashCode());
2897 jalview.util.BrowserLauncher.openURL(url);
2898 } catch (Exception ex)
2900 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2902 .getString("label.web_browser_not_found_unix"),
2903 MessageManager.getString("label.web_browser_not_found"),
2904 JvOptionPane.WARNING_MESSAGE);
2906 ex.printStackTrace();
2908 if (progress != null)
2910 progress.setProgressBar(null, this.hashCode());
2913 }, "OpenURL").start();
2916 public static WsParamSetManager wsparamManager = null;
2918 public static ParamManager getUserParameterStore()
2920 if (wsparamManager == null)
2922 wsparamManager = new WsParamSetManager();
2924 return wsparamManager;
2928 * static hyperlink handler proxy method for use by Jalview's internal windows
2932 public static void hyperlinkUpdate(HyperlinkEvent e)
2934 if (e.getEventType() == EventType.ACTIVATED)
2939 url = e.getURL().toString();
2940 Desktop.showUrl(url);
2941 } catch (Exception x)
2946 .error("Couldn't handle string " + url + " as a URL.");
2948 // ignore any exceptions due to dud links.
2955 * single thread that handles display of dialogs to user.
2957 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2960 * flag indicating if dialogExecutor should try to acquire a permit
2962 private volatile boolean dialogPause = true;
2967 private java.util.concurrent.Semaphore block = new Semaphore(0);
2969 private static groovy.ui.Console groovyConsole;
2972 * add another dialog thread to the queue
2976 public void addDialogThread(final Runnable prompter)
2978 dialogExecutor.submit(new Runnable()
2988 } catch (InterruptedException x)
2992 if (instance == null)
2998 SwingUtilities.invokeAndWait(prompter);
2999 } catch (Exception q)
3001 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3008 public void startDialogQueue()
3010 // set the flag so we don't pause waiting for another permit and semaphore
3011 // the current task to begin
3012 dialogPause = false;
3017 * Outputs an image of the desktop to file in EPS format, after prompting the
3018 * user for choice of Text or Lineart character rendering (unless a preference
3019 * has been set). The file name is generated as
3022 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3026 protected void snapShotWindow_actionPerformed(ActionEvent e)
3028 // currently the menu option to do this is not shown
3031 int width = getWidth();
3032 int height = getHeight();
3034 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3035 ImageWriterI writer = new ImageWriterI()
3038 public void exportImage(Graphics g) throws Exception
3041 jalview.bin.Console.info("Successfully written snapshot to file "
3042 + of.getAbsolutePath());
3045 String title = "View of desktop";
3046 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3048 exporter.doExport(of, this, width, height, title);
3052 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3053 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3054 * and location last time the view was expanded (if any). However it does not
3055 * remember the split pane divider location - this is set to match the
3056 * 'exploding' frame.
3060 public void explodeViews(SplitFrame sf)
3062 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3063 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3064 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3066 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3068 int viewCount = topPanels.size();
3075 * Processing in reverse order works, forwards order leaves the first panels not
3076 * visible. I don't know why!
3078 for (int i = viewCount - 1; i >= 0; i--)
3081 * Make new top and bottom frames. These take over the respective AlignmentPanel
3082 * objects, including their AlignmentViewports, so the cdna/protein
3083 * relationships between the viewports is carried over to the new split frames.
3085 * explodedGeometry holds the (x, y) position of the previously exploded
3086 * SplitFrame, and the (width, height) of the AlignFrame component
3088 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3089 AlignFrame newTopFrame = new AlignFrame(topPanel);
3090 newTopFrame.setSize(oldTopFrame.getSize());
3091 newTopFrame.setVisible(true);
3092 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3093 .getExplodedGeometry();
3094 if (geometry != null)
3096 newTopFrame.setSize(geometry.getSize());
3099 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3100 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3101 newBottomFrame.setSize(oldBottomFrame.getSize());
3102 newBottomFrame.setVisible(true);
3103 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3104 .getExplodedGeometry();
3105 if (geometry != null)
3107 newBottomFrame.setSize(geometry.getSize());
3110 topPanel.av.setGatherViewsHere(false);
3111 bottomPanel.av.setGatherViewsHere(false);
3112 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3114 if (geometry != null)
3116 splitFrame.setLocation(geometry.getLocation());
3118 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3122 * Clear references to the panels (now relocated in the new SplitFrames) before
3123 * closing the old SplitFrame.
3126 bottomPanels.clear();
3131 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3132 * back into the given SplitFrame as additional views. Note that the gathered
3133 * frames may themselves have multiple views.
3137 public void gatherViews(GSplitFrame source)
3140 * special handling of explodedGeometry for a view within a SplitFrame: - it
3141 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3142 * height) of the AlignFrame component
3144 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3145 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3146 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3147 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3148 myBottomFrame.viewport
3149 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3150 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3151 myTopFrame.viewport.setGatherViewsHere(true);
3152 myBottomFrame.viewport.setGatherViewsHere(true);
3153 String topViewId = myTopFrame.viewport.getSequenceSetId();
3154 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3156 JInternalFrame[] frames = desktop.getAllFrames();
3157 for (JInternalFrame frame : frames)
3159 if (frame instanceof SplitFrame && frame != source)
3161 SplitFrame sf = (SplitFrame) frame;
3162 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3163 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3164 boolean gatherThis = false;
3165 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3167 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3168 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3169 if (topViewId.equals(topPanel.av.getSequenceSetId())
3170 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3173 topPanel.av.setGatherViewsHere(false);
3174 bottomPanel.av.setGatherViewsHere(false);
3175 topPanel.av.setExplodedGeometry(
3176 new Rectangle(sf.getLocation(), topFrame.getSize()));
3177 bottomPanel.av.setExplodedGeometry(
3178 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3179 myTopFrame.addAlignmentPanel(topPanel, false);
3180 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3186 topFrame.getAlignPanels().clear();
3187 bottomFrame.getAlignPanels().clear();
3194 * The dust settles...give focus to the tab we did this from.
3196 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3199 public static groovy.ui.Console getGroovyConsole()
3201 return groovyConsole;
3205 * handles the payload of a drag and drop event.
3207 * TODO refactor to desktop utilities class
3210 * - Data source strings extracted from the drop event
3212 * - protocol for each data source extracted from the drop event
3216 * - the payload from the drop event
3219 public static void transferFromDropTarget(List<Object> files,
3220 List<DataSourceType> protocols, DropTargetDropEvent evt,
3221 Transferable t) throws Exception
3224 DataFlavor uriListFlavor = new DataFlavor(
3225 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3228 urlFlavour = new DataFlavor(
3229 "application/x-java-url; class=java.net.URL");
3230 } catch (ClassNotFoundException cfe)
3232 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3236 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3241 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3242 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3243 // means url may be null.
3246 protocols.add(DataSourceType.URL);
3247 files.add(url.toString());
3248 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3249 + files.get(files.size() - 1));
3254 if (Platform.isAMacAndNotJS())
3257 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3260 } catch (Throwable ex)
3262 jalview.bin.Console.debug("URL drop handler failed.", ex);
3265 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3267 // Works on Windows and MacOSX
3268 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3269 for (Object file : (List) t
3270 .getTransferData(DataFlavor.javaFileListFlavor))
3273 protocols.add(DataSourceType.FILE);
3278 // Unix like behaviour
3279 boolean added = false;
3281 if (t.isDataFlavorSupported(uriListFlavor))
3283 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3284 // This is used by Unix drag system
3285 data = (String) t.getTransferData(uriListFlavor);
3289 // fallback to text: workaround - on OSX where there's a JVM bug
3291 .debug("standard URIListFlavor failed. Trying text");
3292 // try text fallback
3293 DataFlavor textDf = new DataFlavor(
3294 "text/plain;class=java.lang.String");
3295 if (t.isDataFlavorSupported(textDf))
3297 data = (String) t.getTransferData(textDf);
3300 jalview.bin.Console.debug("Plain text drop content returned "
3301 + (data == null ? "Null - failed" : data));
3306 while (protocols.size() < files.size())
3308 jalview.bin.Console.debug("Adding missing FILE protocol for "
3309 + files.get(protocols.size()));
3310 protocols.add(DataSourceType.FILE);
3312 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3313 data, "\r\n"); st.hasMoreTokens();)
3316 String s = st.nextToken();
3317 if (s.startsWith("#"))
3319 // the line is a comment (as per the RFC 2483)
3322 java.net.URI uri = new java.net.URI(s);
3323 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3325 protocols.add(DataSourceType.URL);
3326 files.add(uri.toString());
3330 // otherwise preserve old behaviour: catch all for file objects
3331 java.io.File file = new java.io.File(uri);
3332 protocols.add(DataSourceType.FILE);
3333 files.add(file.toString());
3338 if (jalview.bin.Console.isDebugEnabled())
3340 if (data == null || !added)
3343 if (t.getTransferDataFlavors() != null
3344 && t.getTransferDataFlavors().length > 0)
3346 jalview.bin.Console.debug(
3347 "Couldn't resolve drop data. Here are the supported flavors:");
3348 for (DataFlavor fl : t.getTransferDataFlavors())
3350 jalview.bin.Console.debug(
3351 "Supported transfer dataflavor: " + fl.toString());
3352 Object df = t.getTransferData(fl);
3355 jalview.bin.Console.debug("Retrieves: " + df);
3359 jalview.bin.Console.debug("Retrieved nothing");
3366 .debug("Couldn't resolve dataflavor for drop: "
3372 if (Platform.isWindowsAndNotJS())
3375 .debug("Scanning dropped content for Windows Link Files");
3377 // resolve any .lnk files in the file drop
3378 for (int f = 0; f < files.size(); f++)
3380 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3381 if (protocols.get(f).equals(DataSourceType.FILE)
3382 && (source.endsWith(".lnk") || source.endsWith(".url")
3383 || source.endsWith(".site")))
3387 Object obj = files.get(f);
3388 File lf = (obj instanceof File ? (File) obj
3389 : new File((String) obj));
3390 // process link file to get a URL
3391 jalview.bin.Console.debug("Found potential link file: " + lf);
3392 WindowsShortcut wscfile = new WindowsShortcut(lf);
3393 String fullname = wscfile.getRealFilename();
3394 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3395 files.set(f, fullname);
3396 jalview.bin.Console.debug("Parsed real filename " + fullname
3397 + " to extract protocol: " + protocols.get(f));
3398 } catch (Exception ex)
3400 jalview.bin.Console.error(
3401 "Couldn't parse " + files.get(f) + " as a link file.",
3410 * Sets the Preferences property for experimental features to True or False
3411 * depending on the state of the controlling menu item
3414 protected void showExperimental_actionPerformed(boolean selected)
3416 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3420 * Answers a (possibly empty) list of any structure viewer frames (currently
3421 * for either Jmol or Chimera) which are currently open. This may optionally
3422 * be restricted to viewers of a specified class, or viewers linked to a
3423 * specified alignment panel.
3426 * if not null, only return viewers linked to this panel
3427 * @param structureViewerClass
3428 * if not null, only return viewers of this class
3431 public List<StructureViewerBase> getStructureViewers(
3432 AlignmentPanel apanel,
3433 Class<? extends StructureViewerBase> structureViewerClass)
3435 List<StructureViewerBase> result = new ArrayList<>();
3436 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3438 for (JInternalFrame frame : frames)
3440 if (frame instanceof StructureViewerBase)
3442 if (structureViewerClass == null
3443 || structureViewerClass.isInstance(frame))
3446 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3448 result.add((StructureViewerBase) frame);
3456 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3458 private static boolean debugScaleMessageDone = false;
3460 public static void debugScaleMessage(Graphics g)
3462 if (debugScaleMessageDone)
3466 // output used by tests to check HiDPI scaling settings in action
3469 Graphics2D gg = (Graphics2D) g;
3472 AffineTransform t = gg.getTransform();
3473 double scaleX = t.getScaleX();
3474 double scaleY = t.getScaleY();
3475 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3476 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3477 debugScaleMessageDone = true;
3481 jalview.bin.Console.debug("Desktop graphics null");
3483 } catch (Exception e)
3485 jalview.bin.Console.debug(Cache.getStackTraceString(e));