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.Callable;
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.JFrame;
86 import javax.swing.JInternalFrame;
87 import javax.swing.JLabel;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JProgressBar;
92 import javax.swing.JTextField;
93 import javax.swing.KeyStroke;
94 import javax.swing.SwingUtilities;
95 import javax.swing.WindowConstants;
96 import javax.swing.event.HyperlinkEvent;
97 import javax.swing.event.HyperlinkEvent.EventType;
98 import javax.swing.event.InternalFrameAdapter;
99 import javax.swing.event.InternalFrameEvent;
101 import org.stackoverflowusers.file.WindowsShortcut;
103 import jalview.api.AlignViewportI;
104 import jalview.api.AlignmentViewPanel;
105 import jalview.api.structures.JalviewStructureDisplayI;
106 import jalview.bin.Cache;
107 import jalview.bin.Jalview;
108 import jalview.gui.ImageExporter.ImageWriterI;
109 import jalview.gui.QuitHandler.QResponse;
110 import jalview.io.BackupFiles;
111 import jalview.io.DataSourceType;
112 import jalview.io.FileFormat;
113 import jalview.io.FileFormatException;
114 import jalview.io.FileFormatI;
115 import jalview.io.FileFormats;
116 import jalview.io.FileLoader;
117 import jalview.io.FormatAdapter;
118 import jalview.io.IdentifyFile;
119 import jalview.io.JalviewFileChooser;
120 import jalview.io.JalviewFileView;
121 import jalview.jbgui.GSplitFrame;
122 import jalview.jbgui.GStructureViewer;
123 import jalview.project.Jalview2XML;
124 import jalview.structure.StructureSelectionManager;
125 import jalview.urls.IdOrgSettings;
126 import jalview.util.BrowserLauncher;
127 import jalview.util.ChannelProperties;
128 import jalview.util.ImageMaker.TYPE;
129 import jalview.util.LaunchUtils;
130 import jalview.util.MessageManager;
131 import jalview.util.Platform;
132 import jalview.util.ShortcutKeyMaskExWrapper;
133 import jalview.util.UrlConstants;
134 import jalview.viewmodel.AlignmentViewport;
135 import jalview.ws.params.ParamManager;
136 import jalview.ws.utils.UrlDownloadClient;
143 * @version $Revision: 1.155 $
145 public class Desktop extends jalview.jbgui.GDesktop
146 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
147 jalview.api.StructureSelectionManagerProvider
149 private static final String CITATION;
152 URL bg_logo_url = ChannelProperties.getImageURL(
153 "bg_logo." + String.valueOf(SplashScreen.logoSize));
154 URL uod_logo_url = ChannelProperties.getImageURL(
155 "uod_banner." + String.valueOf(SplashScreen.logoSize));
156 boolean logo = (bg_logo_url != null || uod_logo_url != null);
157 StringBuilder sb = new StringBuilder();
159 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
164 sb.append(bg_logo_url == null ? ""
165 : "<img alt=\"Barton Group logo\" src=\""
166 + bg_logo_url.toString() + "\">");
167 sb.append(uod_logo_url == null ? ""
168 : " <img alt=\"University of Dundee shield\" src=\""
169 + uod_logo_url.toString() + "\">");
171 "<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>");
172 sb.append("<br><br>If you use Jalview, please cite:"
173 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
174 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
175 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
176 CITATION = sb.toString();
179 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
181 private static int DEFAULT_MIN_WIDTH = 300;
183 private static int DEFAULT_MIN_HEIGHT = 250;
185 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
187 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
189 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
191 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
193 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
195 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
197 public static void setLiveDragMode(boolean b)
199 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
200 : JDesktopPane.OUTLINE_DRAG_MODE;
202 desktop.setDragMode(DRAG_MODE);
205 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
207 public static boolean nosplash = false;
210 * news reader - null if it was never started.
212 private BlogReader jvnews = null;
214 private File projectFile;
218 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
220 public void addJalviewPropertyChangeListener(
221 PropertyChangeListener listener)
223 changeSupport.addJalviewPropertyChangeListener(listener);
227 * @param propertyName
229 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
230 * java.beans.PropertyChangeListener)
232 public void addJalviewPropertyChangeListener(String propertyName,
233 PropertyChangeListener listener)
235 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
239 * @param propertyName
241 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
242 * java.beans.PropertyChangeListener)
244 public void removeJalviewPropertyChangeListener(String propertyName,
245 PropertyChangeListener listener)
247 changeSupport.removeJalviewPropertyChangeListener(propertyName,
251 /** Singleton Desktop instance */
252 public static Desktop instance;
254 public static MyDesktopPane desktop;
256 public static MyDesktopPane getDesktop()
258 // BH 2018 could use currentThread() here as a reference to a
259 // Hashtable<Thread, MyDesktopPane> in JavaScript
263 static int openFrameCount = 0;
265 static final int xOffset = 30;
267 static final int yOffset = 30;
269 public static jalview.ws.jws1.Discoverer discoverer;
271 public static Object[] jalviewClipboard;
273 public static boolean internalCopy = false;
275 static int fileLoadingCount = 0;
277 class MyDesktopManager implements DesktopManager
280 private DesktopManager delegate;
282 public MyDesktopManager(DesktopManager delegate)
284 this.delegate = delegate;
288 public void activateFrame(JInternalFrame f)
292 delegate.activateFrame(f);
293 } catch (NullPointerException npe)
295 Point p = getMousePosition();
296 instance.showPasteMenu(p.x, p.y);
301 public void beginDraggingFrame(JComponent f)
303 delegate.beginDraggingFrame(f);
307 public void beginResizingFrame(JComponent f, int direction)
309 delegate.beginResizingFrame(f, direction);
313 public void closeFrame(JInternalFrame f)
315 delegate.closeFrame(f);
319 public void deactivateFrame(JInternalFrame f)
321 delegate.deactivateFrame(f);
325 public void deiconifyFrame(JInternalFrame f)
327 delegate.deiconifyFrame(f);
331 public void dragFrame(JComponent f, int newX, int newY)
337 delegate.dragFrame(f, newX, newY);
341 public void endDraggingFrame(JComponent f)
343 delegate.endDraggingFrame(f);
348 public void endResizingFrame(JComponent f)
350 delegate.endResizingFrame(f);
355 public void iconifyFrame(JInternalFrame f)
357 delegate.iconifyFrame(f);
361 public void maximizeFrame(JInternalFrame f)
363 delegate.maximizeFrame(f);
367 public void minimizeFrame(JInternalFrame f)
369 delegate.minimizeFrame(f);
373 public void openFrame(JInternalFrame f)
375 delegate.openFrame(f);
379 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
386 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
390 public void setBoundsForFrame(JComponent f, int newX, int newY,
391 int newWidth, int newHeight)
393 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
396 // All other methods, simply delegate
401 * Creates a new Desktop object.
407 * A note to implementors. It is ESSENTIAL that any activities that might
408 * block are spawned off as threads rather than waited for during this
413 doConfigureStructurePrefs();
414 setTitle(ChannelProperties.getProperty("app_name") + " "
415 + Cache.getProperty("VERSION"));
418 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
419 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
420 * officially documented or guaranteed to exist, so we access it via
421 * reflection. There appear to be unfathomable criteria about what this
422 * string can contain, and it if doesn't meet those criteria then "java"
423 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
424 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
425 * not. The reflection access may generate a warning: WARNING: An illegal
426 * reflective access operation has occurred WARNING: Illegal reflective
427 * access by jalview.gui.Desktop () to field
428 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
430 if (Platform.isLinux())
432 if (LaunchUtils.getJavaVersion() >= 11)
434 jalview.bin.Console.info(
435 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
439 Toolkit xToolkit = Toolkit.getDefaultToolkit();
440 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
441 Field awtAppClassNameField = null;
443 if (Arrays.stream(declaredFields)
444 .anyMatch(f -> f.getName().equals("awtAppClassName")))
446 awtAppClassNameField = xToolkit.getClass()
447 .getDeclaredField("awtAppClassName");
450 String title = ChannelProperties.getProperty("app_name");
451 if (awtAppClassNameField != null)
453 awtAppClassNameField.setAccessible(true);
454 awtAppClassNameField.set(xToolkit, title);
458 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
460 } catch (Exception e)
462 jalview.bin.Console.debug("Error setting awtAppClassName");
463 jalview.bin.Console.trace(Cache.getStackTraceString(e));
467 setIconImages(ChannelProperties.getIconList());
469 // override quit handling when GUI OS close [X] button pressed
470 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
471 addWindowListener(new WindowAdapter()
474 public void windowClosing(WindowEvent ev)
476 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
480 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
482 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
483 desktop = new MyDesktopPane(selmemusage);
485 showMemusage.setSelected(selmemusage);
486 desktop.setBackground(Color.white);
488 getContentPane().setLayout(new BorderLayout());
489 // alternate config - have scrollbars - see notes in JAL-153
490 // JScrollPane sp = new JScrollPane();
491 // sp.getViewport().setView(desktop);
492 // getContentPane().add(sp, BorderLayout.CENTER);
494 // BH 2018 - just an experiment to try unclipped JInternalFrames.
497 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
500 getContentPane().add(desktop, BorderLayout.CENTER);
501 desktop.setDragMode(DRAG_MODE);
503 // This line prevents Windows Look&Feel resizing all new windows to maximum
504 // if previous window was maximised
505 desktop.setDesktopManager(new MyDesktopManager(
506 Platform.isJS() ? desktop.getDesktopManager()
507 : new DefaultDesktopManager()));
509 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
510 : Platform.isAMacAndNotJS()
511 ? new AquaInternalFrameManager(
512 desktop.getDesktopManager())
513 : desktop.getDesktopManager())));
516 Rectangle dims = getLastKnownDimensions("");
523 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
524 int xPos = Math.max(5, (screenSize.width - 900) / 2);
525 int yPos = Math.max(5, (screenSize.height - 650) / 2);
526 setBounds(xPos, yPos, 900, 650);
529 if (!Platform.isJS())
536 jconsole = new Console(this, showjconsole);
537 jconsole.setHeader(Cache.getVersionDetailsForConsole());
538 showConsole(showjconsole);
540 showNews.setVisible(false);
542 experimentalFeatures.setSelected(showExperimental());
544 getIdentifiersOrgData();
548 // Spawn a thread that shows the splashscreen
551 SwingUtilities.invokeLater(new Runnable()
556 new SplashScreen(true);
561 // Thread off a new instance of the file chooser - this reduces the time
563 // takes to open it later on.
564 new Thread(new Runnable()
569 jalview.bin.Console.debug("Filechooser init thread started.");
570 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
571 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
573 jalview.bin.Console.debug("Filechooser init thread finished.");
576 // Add the service change listener
577 changeSupport.addJalviewPropertyChangeListener("services",
578 new PropertyChangeListener()
582 public void propertyChange(PropertyChangeEvent evt)
585 .debug("Firing service changed event for "
586 + evt.getNewValue());
587 JalviewServicesChanged(evt);
592 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
595 this.addMouseListener(ma = new MouseAdapter()
598 public void mousePressed(MouseEvent evt)
600 if (evt.isPopupTrigger()) // Mac
602 showPasteMenu(evt.getX(), evt.getY());
607 public void mouseReleased(MouseEvent evt)
609 if (evt.isPopupTrigger()) // Windows
611 showPasteMenu(evt.getX(), evt.getY());
615 desktop.addMouseListener(ma);
619 * Answers true if user preferences to enable experimental features is True
624 public boolean showExperimental()
626 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
627 Boolean.FALSE.toString());
628 return Boolean.valueOf(experimental).booleanValue();
631 public void doConfigureStructurePrefs()
633 // configure services
634 StructureSelectionManager ssm = StructureSelectionManager
635 .getStructureSelectionManager(this);
636 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
638 ssm.setAddTempFacAnnot(
639 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
640 ssm.setProcessSecondaryStructure(
641 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
642 // JAL-3915 - RNAView is no longer an option so this has no effect
643 ssm.setSecStructServices(
644 Cache.getDefault(Preferences.USE_RNAVIEW, false));
648 ssm.setAddTempFacAnnot(false);
649 ssm.setProcessSecondaryStructure(false);
650 ssm.setSecStructServices(false);
654 public void checkForNews()
656 final Desktop me = this;
657 // Thread off the news reader, in case there are connection problems.
658 new Thread(new Runnable()
663 jalview.bin.Console.debug("Starting news thread.");
664 jvnews = new BlogReader(me);
665 showNews.setVisible(true);
666 jalview.bin.Console.debug("Completed news thread.");
671 public void getIdentifiersOrgData()
673 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
674 {// Thread off the identifiers fetcher
675 new Thread(new Runnable()
681 .debug("Downloading data from identifiers.org");
684 UrlDownloadClient.download(IdOrgSettings.getUrl(),
685 IdOrgSettings.getDownloadLocation());
686 } catch (IOException e)
689 .debug("Exception downloading identifiers.org data"
699 protected void showNews_actionPerformed(ActionEvent e)
701 showNews(showNews.isSelected());
704 void showNews(boolean visible)
706 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
707 showNews.setSelected(visible);
708 if (visible && !jvnews.isVisible())
710 new Thread(new Runnable()
715 long now = System.currentTimeMillis();
716 Desktop.instance.setProgressBar(
717 MessageManager.getString("status.refreshing_news"), now);
718 jvnews.refreshNews();
719 Desktop.instance.setProgressBar(null, now);
727 * recover the last known dimensions for a jalview window
730 * - empty string is desktop, all other windows have unique prefix
731 * @return null or last known dimensions scaled to current geometry (if last
732 * window geom was known)
734 Rectangle getLastKnownDimensions(String windowName)
736 // TODO: lock aspect ratio for scaling desktop Bug #0058199
737 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
738 String x = Cache.getProperty(windowName + "SCREEN_X");
739 String y = Cache.getProperty(windowName + "SCREEN_Y");
740 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
741 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
742 if ((x != null) && (y != null) && (width != null) && (height != null))
744 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
745 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
746 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
748 // attempt #1 - try to cope with change in screen geometry - this
749 // version doesn't preserve original jv aspect ratio.
750 // take ratio of current screen size vs original screen size.
751 double sw = ((1f * screenSize.width) / (1f * Integer
752 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
753 double sh = ((1f * screenSize.height) / (1f * Integer
754 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
755 // rescale the bounds depending upon the current screen geometry.
756 ix = (int) (ix * sw);
757 iw = (int) (iw * sw);
758 iy = (int) (iy * sh);
759 ih = (int) (ih * sh);
760 while (ix >= screenSize.width)
762 jalview.bin.Console.debug(
763 "Window geometry location recall error: shifting horizontal to within screenbounds.");
764 ix -= screenSize.width;
766 while (iy >= screenSize.height)
768 jalview.bin.Console.debug(
769 "Window geometry location recall error: shifting vertical to within screenbounds.");
770 iy -= screenSize.height;
772 jalview.bin.Console.debug(
773 "Got last known dimensions for " + windowName + ": x:" + ix
774 + " y:" + iy + " width:" + iw + " height:" + ih);
776 // return dimensions for new instance
777 return new Rectangle(ix, iy, iw, ih);
782 void showPasteMenu(int x, int y)
784 JPopupMenu popup = new JPopupMenu();
785 JMenuItem item = new JMenuItem(
786 MessageManager.getString("label.paste_new_window"));
787 item.addActionListener(new ActionListener()
790 public void actionPerformed(ActionEvent evt)
797 popup.show(this, x, y);
804 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
805 Transferable contents = c.getContents(this);
807 if (contents != null)
809 String file = (String) contents
810 .getTransferData(DataFlavor.stringFlavor);
812 FileFormatI format = new IdentifyFile().identify(file,
813 DataSourceType.PASTE);
815 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
818 } catch (Exception ex)
821 "Unable to paste alignment from system clipboard:\n" + ex);
826 * Adds and opens the given frame to the desktop
837 public static synchronized void addInternalFrame(
838 final JInternalFrame frame, String title, int w, int h)
840 addInternalFrame(frame, title, true, w, h, true, false);
844 * Add an internal frame to the Jalview desktop
851 * When true, display frame immediately, otherwise, caller must call
852 * setVisible themselves.
858 public static synchronized void addInternalFrame(
859 final JInternalFrame frame, String title, boolean makeVisible,
862 addInternalFrame(frame, title, makeVisible, w, h, true, false);
866 * Add an internal frame to the Jalview desktop and make it visible
879 public static synchronized void addInternalFrame(
880 final JInternalFrame frame, String title, int w, int h,
883 addInternalFrame(frame, title, true, w, h, resizable, false);
887 * Add an internal frame to the Jalview desktop
894 * When true, display frame immediately, otherwise, caller must call
895 * setVisible themselves.
902 * @param ignoreMinSize
903 * Do not set the default minimum size for frame
905 public static synchronized void addInternalFrame(
906 final JInternalFrame frame, String title, boolean makeVisible,
907 int w, int h, boolean resizable, boolean ignoreMinSize)
910 // TODO: allow callers to determine X and Y position of frame (eg. via
912 // TODO: consider fixing method to update entries in the window submenu with
913 // the current window title
915 frame.setTitle(title);
916 if (frame.getWidth() < 1 || frame.getHeight() < 1)
920 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
921 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
922 // IF JALVIEW IS RUNNING HEADLESS
923 // ///////////////////////////////////////////////
924 if (instance == null || (System.getProperty("java.awt.headless") != null
925 && System.getProperty("java.awt.headless").equals("true")))
934 frame.setMinimumSize(
935 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
937 // Set default dimension for Alignment Frame window.
938 // The Alignment Frame window could be added from a number of places,
940 // I did this here in order not to miss out on any Alignment frame.
941 if (frame instanceof AlignFrame)
943 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
944 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
948 frame.setVisible(makeVisible);
949 frame.setClosable(true);
950 frame.setResizable(resizable);
951 frame.setMaximizable(resizable);
952 frame.setIconifiable(resizable);
953 frame.setOpaque(Platform.isJS());
955 if (frame.getX() < 1 && frame.getY() < 1)
957 frame.setLocation(xOffset * openFrameCount,
958 yOffset * ((openFrameCount - 1) % 10) + yOffset);
962 * add an entry for the new frame in the Window menu (and remove it when the
965 final JMenuItem menuItem = new JMenuItem(title);
966 frame.addInternalFrameListener(new InternalFrameAdapter()
969 public void internalFrameActivated(InternalFrameEvent evt)
971 JInternalFrame itf = desktop.getSelectedFrame();
974 if (itf instanceof AlignFrame)
976 Jalview.setCurrentAlignFrame((AlignFrame) itf);
983 public void internalFrameClosed(InternalFrameEvent evt)
985 PaintRefresher.RemoveComponent(frame);
988 * defensive check to prevent frames being added half off the window
990 if (openFrameCount > 0)
996 * ensure no reference to alignFrame retained by menu item listener
998 if (menuItem.getActionListeners().length > 0)
1000 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1002 windowMenu.remove(menuItem);
1006 menuItem.addActionListener(new ActionListener()
1009 public void actionPerformed(ActionEvent e)
1013 frame.setSelected(true);
1014 frame.setIcon(false);
1015 } catch (java.beans.PropertyVetoException ex)
1022 setKeyBindings(frame);
1026 windowMenu.add(menuItem);
1031 frame.setSelected(true);
1032 frame.requestFocus();
1033 } catch (java.beans.PropertyVetoException ve)
1035 } catch (java.lang.ClassCastException cex)
1037 jalview.bin.Console.warn(
1038 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1044 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1049 private static void setKeyBindings(JInternalFrame frame)
1051 @SuppressWarnings("serial")
1052 final Action closeAction = new AbstractAction()
1055 public void actionPerformed(ActionEvent e)
1062 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1064 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1065 InputEvent.CTRL_DOWN_MASK);
1066 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1067 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1069 InputMap inputMap = frame
1070 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1071 String ctrlW = ctrlWKey.toString();
1072 inputMap.put(ctrlWKey, ctrlW);
1073 inputMap.put(cmdWKey, ctrlW);
1075 ActionMap actionMap = frame.getActionMap();
1076 actionMap.put(ctrlW, closeAction);
1080 public void lostOwnership(Clipboard clipboard, Transferable contents)
1084 Desktop.jalviewClipboard = null;
1087 internalCopy = false;
1091 public void dragEnter(DropTargetDragEvent evt)
1096 public void dragExit(DropTargetEvent evt)
1101 public void dragOver(DropTargetDragEvent evt)
1106 public void dropActionChanged(DropTargetDragEvent evt)
1117 public void drop(DropTargetDropEvent evt)
1119 boolean success = true;
1120 // JAL-1552 - acceptDrop required before getTransferable call for
1121 // Java's Transferable for native dnd
1122 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1123 Transferable t = evt.getTransferable();
1124 List<Object> files = new ArrayList<>();
1125 List<DataSourceType> protocols = new ArrayList<>();
1129 Desktop.transferFromDropTarget(files, protocols, evt, t);
1130 } catch (Exception e)
1132 e.printStackTrace();
1140 for (int i = 0; i < files.size(); i++)
1142 // BH 2018 File or String
1143 Object file = files.get(i);
1144 String fileName = file.toString();
1145 DataSourceType protocol = (protocols == null)
1146 ? DataSourceType.FILE
1148 FileFormatI format = null;
1150 if (fileName.endsWith(".jar"))
1152 format = FileFormat.Jalview;
1157 format = new IdentifyFile().identify(file, protocol);
1159 if (file instanceof File)
1161 Platform.cacheFileData((File) file);
1163 new FileLoader().LoadFile(null, file, protocol, format);
1166 } catch (Exception ex)
1171 evt.dropComplete(success); // need this to ensure input focus is properly
1172 // transfered to any new windows created
1182 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1184 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1185 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1186 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1187 BackupFiles.getEnabled());
1189 chooser.setFileView(new JalviewFileView());
1190 chooser.setDialogTitle(
1191 MessageManager.getString("label.open_local_file"));
1192 chooser.setToolTipText(MessageManager.getString("action.open"));
1194 chooser.setResponseHandler(0, () -> {
1195 File selectedFile = chooser.getSelectedFile();
1196 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1198 FileFormatI format = chooser.getSelectedFormat();
1201 * Call IdentifyFile to verify the file contains what its extension implies.
1202 * Skip this step for dynamically added file formats, because IdentifyFile does
1203 * not know how to recognise them.
1205 if (FileFormats.getInstance().isIdentifiable(format))
1209 format = new IdentifyFile().identify(selectedFile,
1210 DataSourceType.FILE);
1211 } catch (FileFormatException e)
1213 // format = null; //??
1217 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1221 chooser.showOpenDialog(this);
1225 * Shows a dialog for input of a URL at which to retrieve alignment data
1230 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1232 // This construct allows us to have a wider textfield
1234 JLabel label = new JLabel(
1235 MessageManager.getString("label.input_file_url"));
1237 JPanel panel = new JPanel(new GridLayout(2, 1));
1241 * the URL to fetch is input in Java: an editable combobox with history JS:
1242 * (pending JAL-3038) a plain text field
1245 String urlBase = "https://www.";
1246 if (Platform.isJS())
1248 history = new JTextField(urlBase, 35);
1257 JComboBox<String> asCombo = new JComboBox<>();
1258 asCombo.setPreferredSize(new Dimension(400, 20));
1259 asCombo.setEditable(true);
1260 asCombo.addItem(urlBase);
1261 String historyItems = Cache.getProperty("RECENT_URL");
1262 if (historyItems != null)
1264 for (String token : historyItems.split("\\t"))
1266 asCombo.addItem(token);
1273 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1274 MessageManager.getString("action.cancel") };
1275 Callable<Void> action = () -> {
1276 @SuppressWarnings("unchecked")
1277 String url = (history instanceof JTextField
1278 ? ((JTextField) history).getText()
1279 : ((JComboBox<String>) history).getEditor().getItem()
1280 .toString().trim());
1282 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1284 if (viewport != null)
1286 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1287 FileFormat.Jalview);
1291 new FileLoader().LoadFile(url, DataSourceType.URL,
1292 FileFormat.Jalview);
1297 FileFormatI format = null;
1300 format = new IdentifyFile().identify(url, DataSourceType.URL);
1301 } catch (FileFormatException e)
1303 // TODO revise error handling, distinguish between
1304 // URL not found and response not valid
1309 String msg = MessageManager.formatMessage("label.couldnt_locate",
1311 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1312 MessageManager.getString("label.url_not_found"),
1313 JvOptionPane.WARNING_MESSAGE);
1315 return null; // Void
1318 if (viewport != null)
1320 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1325 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1328 return null; // Void
1330 String dialogOption = MessageManager
1331 .getString("label.input_alignment_from_url");
1332 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1333 .showInternalDialog(panel, dialogOption,
1334 JvOptionPane.YES_NO_CANCEL_OPTION,
1335 JvOptionPane.PLAIN_MESSAGE, null, options,
1336 MessageManager.getString("action.ok"));
1340 * Opens the CutAndPaste window for the user to paste an alignment in to
1343 * - if not null, the pasted alignment is added to the current
1344 * alignment; if null, to a new alignment window
1347 public void inputTextboxMenuItem_actionPerformed(
1348 AlignmentViewPanel viewPanel)
1350 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1351 cap.setForInput(viewPanel);
1352 Desktop.addInternalFrame(cap,
1353 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1358 * Check with user and saving files before actually quitting
1360 public void desktopQuit()
1362 desktopQuit(true, false);
1365 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1367 final Callable<Void> doDesktopQuit = () -> {
1368 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1369 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1370 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1371 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1372 getBounds().y, getWidth(), getHeight()));
1374 if (jconsole != null)
1376 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1377 jconsole.stopConsole();
1382 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1385 // Frames should all close automatically. Keeping external
1386 // viewers open should already be decided by user.
1387 closeAll_actionPerformed(null);
1389 // check for aborted quit
1390 if (QuitHandler.quitCancelled())
1392 jalview.bin.Console.debug("Desktop aborting quit");
1396 if (dialogExecutor != null)
1398 dialogExecutor.shutdownNow();
1401 if (groovyConsole != null)
1403 // suppress a possible repeat prompt to save script
1404 groovyConsole.setDirty(false);
1405 groovyConsole.exit();
1408 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1410 // note that shutdown hook will not be run
1411 jalview.bin.Console.debug("Force Quit selected by user");
1412 Runtime.getRuntime().halt(0);
1415 jalview.bin.Console.debug("Quit selected by user");
1418 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1419 // instance.dispose();
1423 return null; // Void
1426 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1427 QuitHandler.defaultCancelQuit);
1431 * Don't call this directly, use desktopQuit() above. Exits the program.
1436 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1437 // not run a second time if gotQuitResponse flag has been set (i.e. user
1438 // confirmed quit of some kind).
1439 Jalview.exit("Desktop exiting.", 0);
1442 private void storeLastKnownDimensions(String string, Rectangle jc)
1444 jalview.bin.Console.debug("Storing last known dimensions for " + string
1445 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1446 + " height:" + jc.height);
1448 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1449 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1450 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1451 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1461 public void aboutMenuItem_actionPerformed(ActionEvent e)
1463 new Thread(new Runnable()
1468 new SplashScreen(false);
1474 * Returns the html text for the About screen, including any available version
1475 * number, build details, author details and citation reference, but without
1476 * the enclosing {@code html} tags
1480 public String getAboutMessage()
1482 StringBuilder message = new StringBuilder(1024);
1483 message.append("<div style=\"font-family: sans-serif;\">")
1484 .append("<h1><strong>Version: ")
1485 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1486 .append("<strong>Built: <em>")
1487 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1488 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1489 .append("</strong>");
1491 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1492 if (latestVersion.equals("Checking"))
1494 // JBP removed this message for 2.11: May be reinstated in future version
1495 // message.append("<br>...Checking latest version...</br>");
1497 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1499 boolean red = false;
1500 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1501 .indexOf("automated build") == -1)
1504 // Displayed when code version and jnlp version do not match and code
1505 // version is not a development build
1506 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1509 message.append("<br>!! Version ")
1510 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1511 .append(" is available for download from ")
1512 .append(Cache.getDefault("www.jalview.org",
1513 "https://www.jalview.org"))
1517 message.append("</div>");
1520 message.append("<br>Authors: ");
1521 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1522 message.append(CITATION);
1524 message.append("</div>");
1526 return message.toString();
1530 * Action on requesting Help documentation
1533 public void documentationMenuItem_actionPerformed()
1537 if (Platform.isJS())
1539 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1548 Help.showHelpWindow();
1550 } catch (Exception ex)
1552 System.err.println("Error opening help: " + ex.getMessage());
1557 public void closeAll_actionPerformed(ActionEvent e)
1559 // TODO show a progress bar while closing?
1560 JInternalFrame[] frames = desktop.getAllFrames();
1561 for (int i = 0; i < frames.length; i++)
1565 frames[i].setClosed(true);
1566 } catch (java.beans.PropertyVetoException ex)
1570 Jalview.setCurrentAlignFrame(null);
1571 System.out.println("ALL CLOSED");
1574 * reset state of singleton objects as appropriate (clear down session state
1575 * when all windows are closed)
1577 StructureSelectionManager ssm = StructureSelectionManager
1578 .getStructureSelectionManager(this);
1585 public int structureViewersStillRunningCount()
1588 JInternalFrame[] frames = desktop.getAllFrames();
1589 for (int i = 0; i < frames.length; i++)
1591 if (frames[i] != null
1592 && frames[i] instanceof JalviewStructureDisplayI)
1594 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1602 public void raiseRelated_actionPerformed(ActionEvent e)
1604 reorderAssociatedWindows(false, false);
1608 public void minimizeAssociated_actionPerformed(ActionEvent e)
1610 reorderAssociatedWindows(true, false);
1613 void closeAssociatedWindows()
1615 reorderAssociatedWindows(false, true);
1621 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1625 protected void garbageCollect_actionPerformed(ActionEvent e)
1627 // We simply collect the garbage
1628 jalview.bin.Console.debug("Collecting garbage...");
1630 jalview.bin.Console.debug("Finished garbage collection.");
1636 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1640 protected void showMemusage_actionPerformed(ActionEvent e)
1642 desktop.showMemoryUsage(showMemusage.isSelected());
1649 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1653 protected void showConsole_actionPerformed(ActionEvent e)
1655 showConsole(showConsole.isSelected());
1658 Console jconsole = null;
1661 * control whether the java console is visible or not
1665 void showConsole(boolean selected)
1667 // TODO: decide if we should update properties file
1668 if (jconsole != null) // BH 2018
1670 showConsole.setSelected(selected);
1671 Cache.setProperty("SHOW_JAVA_CONSOLE",
1672 Boolean.valueOf(selected).toString());
1673 jconsole.setVisible(selected);
1677 void reorderAssociatedWindows(boolean minimize, boolean close)
1679 JInternalFrame[] frames = desktop.getAllFrames();
1680 if (frames == null || frames.length < 1)
1685 AlignmentViewport source = null, target = null;
1686 if (frames[0] instanceof AlignFrame)
1688 source = ((AlignFrame) frames[0]).getCurrentView();
1690 else if (frames[0] instanceof TreePanel)
1692 source = ((TreePanel) frames[0]).getViewPort();
1694 else if (frames[0] instanceof PCAPanel)
1696 source = ((PCAPanel) frames[0]).av;
1698 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1700 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1705 for (int i = 0; i < frames.length; i++)
1708 if (frames[i] == null)
1712 if (frames[i] instanceof AlignFrame)
1714 target = ((AlignFrame) frames[i]).getCurrentView();
1716 else if (frames[i] instanceof TreePanel)
1718 target = ((TreePanel) frames[i]).getViewPort();
1720 else if (frames[i] instanceof PCAPanel)
1722 target = ((PCAPanel) frames[i]).av;
1724 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1726 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1729 if (source == target)
1735 frames[i].setClosed(true);
1739 frames[i].setIcon(minimize);
1742 frames[i].toFront();
1746 } catch (java.beans.PropertyVetoException ex)
1761 protected void preferences_actionPerformed(ActionEvent e)
1763 Preferences.openPreferences();
1767 * Prompts the user to choose a file and then saves the Jalview state as a
1768 * Jalview project file
1771 public void saveState_actionPerformed()
1773 saveState_actionPerformed(false);
1776 public void saveState_actionPerformed(boolean saveAs)
1778 java.io.File projectFile = getProjectFile();
1779 // autoSave indicates we already have a file and don't need to ask
1780 boolean autoSave = projectFile != null && !saveAs
1781 && BackupFiles.getEnabled();
1783 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1784 // saveAs="+saveAs+", Backups
1785 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1787 boolean approveSave = false;
1790 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1793 chooser.setFileView(new JalviewFileView());
1794 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1796 int value = chooser.showSaveDialog(this);
1798 if (value == JalviewFileChooser.APPROVE_OPTION)
1800 projectFile = chooser.getSelectedFile();
1801 setProjectFile(projectFile);
1806 if (approveSave || autoSave)
1808 final Desktop me = this;
1809 final java.io.File chosenFile = projectFile;
1810 new Thread(new Runnable()
1815 // TODO: refactor to Jalview desktop session controller action.
1816 setProgressBar(MessageManager.formatMessage(
1817 "label.saving_jalview_project", new Object[]
1818 { chosenFile.getName() }), chosenFile.hashCode());
1819 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1820 // TODO catch and handle errors for savestate
1821 // TODO prevent user from messing with the Desktop whilst we're saving
1824 boolean doBackup = BackupFiles.getEnabled();
1825 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1828 new Jalview2XML().saveState(
1829 doBackup ? backupfiles.getTempFile() : chosenFile);
1833 backupfiles.setWriteSuccess(true);
1834 backupfiles.rollBackupsAndRenameTempFile();
1836 } catch (OutOfMemoryError oom)
1838 new OOMWarning("Whilst saving current state to "
1839 + chosenFile.getName(), oom);
1840 } catch (Exception ex)
1842 jalview.bin.Console.error("Problems whilst trying to save to "
1843 + chosenFile.getName(), ex);
1844 JvOptionPane.showMessageDialog(me,
1845 MessageManager.formatMessage(
1846 "label.error_whilst_saving_current_state_to",
1848 { chosenFile.getName() }),
1849 MessageManager.getString("label.couldnt_save_project"),
1850 JvOptionPane.WARNING_MESSAGE);
1852 setProgressBar(null, chosenFile.hashCode());
1859 public void saveAsState_actionPerformed(ActionEvent e)
1861 saveState_actionPerformed(true);
1864 protected void setProjectFile(File choice)
1866 this.projectFile = choice;
1869 public File getProjectFile()
1871 return this.projectFile;
1875 * Shows a file chooser dialog and tries to read in the selected file as a
1879 public void loadState_actionPerformed()
1881 final String[] suffix = new String[] { "jvp", "jar" };
1882 final String[] desc = new String[] { "Jalview Project",
1883 "Jalview Project (old)" };
1884 JalviewFileChooser chooser = new JalviewFileChooser(
1885 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1886 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1890 chooser.setFileView(new JalviewFileView());
1891 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1892 chooser.setResponseHandler(0, () -> {
1893 File selectedFile = chooser.getSelectedFile();
1894 setProjectFile(selectedFile);
1895 String choice = selectedFile.getAbsolutePath();
1896 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1897 new Thread(new Runnable()
1904 new Jalview2XML().loadJalviewAlign(selectedFile);
1905 } catch (OutOfMemoryError oom)
1907 new OOMWarning("Whilst loading project from " + choice, oom);
1908 } catch (Exception ex)
1910 jalview.bin.Console.error(
1911 "Problems whilst loading project from " + choice, ex);
1912 JvOptionPane.showMessageDialog(Desktop.desktop,
1913 MessageManager.formatMessage(
1914 "label.error_whilst_loading_project_from",
1917 MessageManager.getString("label.couldnt_load_project"),
1918 JvOptionPane.WARNING_MESSAGE);
1921 }, "Project Loader").start();
1925 chooser.showOpenDialog(this);
1929 public void inputSequence_actionPerformed(ActionEvent e)
1931 new SequenceFetcher(this);
1934 JPanel progressPanel;
1936 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1938 public void startLoading(final Object fileName)
1940 if (fileLoadingCount == 0)
1942 fileLoadingPanels.add(addProgressPanel(MessageManager
1943 .formatMessage("label.loading_file", new Object[]
1949 private JPanel addProgressPanel(String string)
1951 if (progressPanel == null)
1953 progressPanel = new JPanel(new GridLayout(1, 1));
1954 totalProgressCount = 0;
1955 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1957 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1958 JProgressBar progressBar = new JProgressBar();
1959 progressBar.setIndeterminate(true);
1961 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1963 thisprogress.add(progressBar, BorderLayout.CENTER);
1964 progressPanel.add(thisprogress);
1965 ((GridLayout) progressPanel.getLayout()).setRows(
1966 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1967 ++totalProgressCount;
1968 instance.validate();
1969 return thisprogress;
1972 int totalProgressCount = 0;
1974 private void removeProgressPanel(JPanel progbar)
1976 if (progressPanel != null)
1978 synchronized (progressPanel)
1980 progressPanel.remove(progbar);
1981 GridLayout gl = (GridLayout) progressPanel.getLayout();
1982 gl.setRows(gl.getRows() - 1);
1983 if (--totalProgressCount < 1)
1985 this.getContentPane().remove(progressPanel);
1986 progressPanel = null;
1993 public void stopLoading()
1996 if (fileLoadingCount < 1)
1998 while (fileLoadingPanels.size() > 0)
2000 removeProgressPanel(fileLoadingPanels.remove(0));
2002 fileLoadingPanels.clear();
2003 fileLoadingCount = 0;
2008 public static int getViewCount(String alignmentId)
2010 AlignmentViewport[] aps = getViewports(alignmentId);
2011 return (aps == null) ? 0 : aps.length;
2016 * @param alignmentId
2017 * - if null, all sets are returned
2018 * @return all AlignmentPanels concerning the alignmentId sequence set
2020 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2022 if (Desktop.desktop == null)
2024 // no frames created and in headless mode
2025 // TODO: verify that frames are recoverable when in headless mode
2028 List<AlignmentPanel> aps = new ArrayList<>();
2029 AlignFrame[] frames = getAlignFrames();
2034 for (AlignFrame af : frames)
2036 System.out.println("###### frames=" + frames);
2037 System.out.println("###### af=" + af);
2038 System.out.println("###### af.alignPanels=" + af.alignPanels);
2039 for (AlignmentPanel ap : af.alignPanels)
2041 if (alignmentId == null
2042 || alignmentId.equals(ap.av.getSequenceSetId()))
2048 if (aps.size() == 0)
2052 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2057 * get all the viewports on an alignment.
2059 * @param sequenceSetId
2060 * unique alignment id (may be null - all viewports returned in that
2062 * @return all viewports on the alignment bound to sequenceSetId
2064 public static AlignmentViewport[] getViewports(String sequenceSetId)
2066 List<AlignmentViewport> viewp = new ArrayList<>();
2067 if (desktop != null)
2069 AlignFrame[] frames = Desktop.getAlignFrames();
2071 for (AlignFrame afr : frames)
2073 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2074 .equals(sequenceSetId))
2076 if (afr.alignPanels != null)
2078 for (AlignmentPanel ap : afr.alignPanels)
2080 if (sequenceSetId == null
2081 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2089 viewp.add(afr.getViewport());
2093 if (viewp.size() > 0)
2095 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2102 * Explode the views in the given frame into separate AlignFrame
2106 public static void explodeViews(AlignFrame af)
2108 int size = af.alignPanels.size();
2114 // FIXME: ideally should use UI interface API
2115 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2116 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2117 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2118 for (int i = 0; i < size; i++)
2120 AlignmentPanel ap = af.alignPanels.get(i);
2122 AlignFrame newaf = new AlignFrame(ap);
2124 // transfer reference for existing feature settings to new alignFrame
2125 if (ap == af.alignPanel)
2127 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2129 newaf.featureSettings = viewFeatureSettings;
2131 newaf.setFeatureSettingsGeometry(fsBounds);
2135 * Restore the view's last exploded frame geometry if known. Multiple views from
2136 * one exploded frame share and restore the same (frame) position and size.
2138 Rectangle geometry = ap.av.getExplodedGeometry();
2139 if (geometry != null)
2141 newaf.setBounds(geometry);
2144 ap.av.setGatherViewsHere(false);
2146 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2147 AlignFrame.DEFAULT_HEIGHT);
2148 // and materialise a new feature settings dialog instance for the new
2150 // (closes the old as if 'OK' was pressed)
2151 if (ap == af.alignPanel && newaf.featureSettings != null
2152 && newaf.featureSettings.isOpen()
2153 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2155 newaf.showFeatureSettingsUI();
2159 af.featureSettings = null;
2160 af.alignPanels.clear();
2161 af.closeMenuItem_actionPerformed(true);
2166 * Gather expanded views (separate AlignFrame's) with the same sequence set
2167 * identifier back in to this frame as additional views, and close the
2168 * expanded views. Note the expanded frames may themselves have multiple
2169 * views. We take the lot.
2173 public void gatherViews(AlignFrame source)
2175 source.viewport.setGatherViewsHere(true);
2176 source.viewport.setExplodedGeometry(source.getBounds());
2177 JInternalFrame[] frames = desktop.getAllFrames();
2178 String viewId = source.viewport.getSequenceSetId();
2179 for (int t = 0; t < frames.length; t++)
2181 if (frames[t] instanceof AlignFrame && frames[t] != source)
2183 AlignFrame af = (AlignFrame) frames[t];
2184 boolean gatherThis = false;
2185 for (int a = 0; a < af.alignPanels.size(); a++)
2187 AlignmentPanel ap = af.alignPanels.get(a);
2188 if (viewId.equals(ap.av.getSequenceSetId()))
2191 ap.av.setGatherViewsHere(false);
2192 ap.av.setExplodedGeometry(af.getBounds());
2193 source.addAlignmentPanel(ap, false);
2199 if (af.featureSettings != null && af.featureSettings.isOpen())
2201 if (source.featureSettings == null)
2203 // preserve the feature settings geometry for this frame
2204 source.featureSettings = af.featureSettings;
2205 source.setFeatureSettingsGeometry(
2206 af.getFeatureSettingsGeometry());
2210 // close it and forget
2211 af.featureSettings.close();
2214 af.alignPanels.clear();
2215 af.closeMenuItem_actionPerformed(true);
2220 // refresh the feature setting UI for the source frame if it exists
2221 if (source.featureSettings != null && source.featureSettings.isOpen())
2223 source.showFeatureSettingsUI();
2228 public JInternalFrame[] getAllFrames()
2230 return desktop.getAllFrames();
2234 * Checks the given url to see if it gives a response indicating that the user
2235 * should be informed of a new questionnaire.
2239 public void checkForQuestionnaire(String url)
2241 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2242 // javax.swing.SwingUtilities.invokeLater(jvq);
2243 new Thread(jvq).start();
2246 public void checkURLLinks()
2248 // Thread off the URL link checker
2249 addDialogThread(new Runnable()
2254 if (Cache.getDefault("CHECKURLLINKS", true))
2256 // check what the actual links are - if it's just the default don't
2257 // bother with the warning
2258 List<String> links = Preferences.sequenceUrlLinks
2261 // only need to check links if there is one with a
2262 // SEQUENCE_ID which is not the default EMBL_EBI link
2263 ListIterator<String> li = links.listIterator();
2264 boolean check = false;
2265 List<JLabel> urls = new ArrayList<>();
2266 while (li.hasNext())
2268 String link = li.next();
2269 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2270 && !UrlConstants.isDefaultString(link))
2273 int barPos = link.indexOf("|");
2274 String urlMsg = barPos == -1 ? link
2275 : link.substring(0, barPos) + ": "
2276 + link.substring(barPos + 1);
2277 urls.add(new JLabel(urlMsg));
2285 // ask user to check in case URL links use old style tokens
2286 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2287 JPanel msgPanel = new JPanel();
2288 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2289 msgPanel.add(Box.createVerticalGlue());
2290 JLabel msg = new JLabel(MessageManager
2291 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2292 JLabel msg2 = new JLabel(MessageManager
2293 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2295 for (JLabel url : urls)
2301 final JCheckBox jcb = new JCheckBox(
2302 MessageManager.getString("label.do_not_display_again"));
2303 jcb.addActionListener(new ActionListener()
2306 public void actionPerformed(ActionEvent e)
2308 // update Cache settings for "don't show this again"
2309 boolean showWarningAgain = !jcb.isSelected();
2310 Cache.setProperty("CHECKURLLINKS",
2311 Boolean.valueOf(showWarningAgain).toString());
2316 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2318 .getString("label.SEQUENCE_ID_no_longer_used"),
2319 JvOptionPane.WARNING_MESSAGE);
2326 * Proxy class for JDesktopPane which optionally displays the current memory
2327 * usage and highlights the desktop area with a red bar if free memory runs
2332 public class MyDesktopPane extends JDesktopPane implements Runnable
2334 private static final float ONE_MB = 1048576f;
2336 boolean showMemoryUsage = false;
2340 java.text.NumberFormat df;
2342 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2345 public MyDesktopPane(boolean showMemoryUsage)
2347 showMemoryUsage(showMemoryUsage);
2350 public void showMemoryUsage(boolean showMemory)
2352 this.showMemoryUsage = showMemory;
2355 Thread worker = new Thread(this);
2361 public boolean isShowMemoryUsage()
2363 return showMemoryUsage;
2369 df = java.text.NumberFormat.getNumberInstance();
2370 df.setMaximumFractionDigits(2);
2371 runtime = Runtime.getRuntime();
2373 while (showMemoryUsage)
2377 maxMemory = runtime.maxMemory() / ONE_MB;
2378 allocatedMemory = runtime.totalMemory() / ONE_MB;
2379 freeMemory = runtime.freeMemory() / ONE_MB;
2380 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2382 percentUsage = (totalFreeMemory / maxMemory) * 100;
2384 // if (percentUsage < 20)
2386 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2388 // instance.set.setBorder(border1);
2391 // sleep after showing usage
2393 } catch (Exception ex)
2395 ex.printStackTrace();
2401 public void paintComponent(Graphics g)
2403 if (showMemoryUsage && g != null && df != null)
2405 if (percentUsage < 20)
2407 g.setColor(Color.red);
2409 FontMetrics fm = g.getFontMetrics();
2412 g.drawString(MessageManager.formatMessage("label.memory_stats",
2414 { df.format(totalFreeMemory), df.format(maxMemory),
2415 df.format(percentUsage) }),
2416 10, getHeight() - fm.getHeight());
2420 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2421 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2426 * Accessor method to quickly get all the AlignmentFrames loaded.
2428 * @return an array of AlignFrame, or null if none found
2430 public static AlignFrame[] getAlignFrames()
2432 if (Jalview.isHeadlessMode())
2434 // Desktop.desktop is null in headless mode
2435 return new AlignFrame[] { Jalview.currentAlignFrame };
2438 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2444 List<AlignFrame> avp = new ArrayList<>();
2446 for (int i = frames.length - 1; i > -1; i--)
2448 if (frames[i] instanceof AlignFrame)
2450 avp.add((AlignFrame) frames[i]);
2452 else if (frames[i] instanceof SplitFrame)
2455 * Also check for a split frame containing an AlignFrame
2457 GSplitFrame sf = (GSplitFrame) frames[i];
2458 if (sf.getTopFrame() instanceof AlignFrame)
2460 avp.add((AlignFrame) sf.getTopFrame());
2462 if (sf.getBottomFrame() instanceof AlignFrame)
2464 avp.add((AlignFrame) sf.getBottomFrame());
2468 if (avp.size() == 0)
2472 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2477 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2481 public GStructureViewer[] getJmols()
2483 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2489 List<GStructureViewer> avp = new ArrayList<>();
2491 for (int i = frames.length - 1; i > -1; i--)
2493 if (frames[i] instanceof AppJmol)
2495 GStructureViewer af = (GStructureViewer) frames[i];
2499 if (avp.size() == 0)
2503 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2508 * Add Groovy Support to Jalview
2511 public void groovyShell_actionPerformed()
2515 openGroovyConsole();
2516 } catch (Exception ex)
2518 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2519 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2521 MessageManager.getString("label.couldnt_create_groovy_shell"),
2522 MessageManager.getString("label.groovy_support_failed"),
2523 JvOptionPane.ERROR_MESSAGE);
2528 * Open the Groovy console
2530 void openGroovyConsole()
2532 if (groovyConsole == null)
2534 groovyConsole = new groovy.ui.Console();
2535 groovyConsole.setVariable("Jalview", this);
2536 groovyConsole.run();
2539 * We allow only one console at a time, so that AlignFrame menu option
2540 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2541 * enable 'Run script', when the console is opened, and the reverse when it is
2544 Window window = (Window) groovyConsole.getFrame();
2545 window.addWindowListener(new WindowAdapter()
2548 public void windowClosed(WindowEvent e)
2551 * rebind CMD-Q from Groovy Console to Jalview Quit
2554 enableExecuteGroovy(false);
2560 * show Groovy console window (after close and reopen)
2562 ((Window) groovyConsole.getFrame()).setVisible(true);
2565 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2566 * opening a second console
2568 enableExecuteGroovy(true);
2572 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2573 * binding when opened
2575 protected void addQuitHandler()
2578 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2580 .getKeyStroke(KeyEvent.VK_Q,
2581 jalview.util.ShortcutKeyMaskExWrapper
2582 .getMenuShortcutKeyMaskEx()),
2584 getRootPane().getActionMap().put("Quit", new AbstractAction()
2587 public void actionPerformed(ActionEvent e)
2595 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2598 * true if Groovy console is open
2600 public void enableExecuteGroovy(boolean enabled)
2603 * disable opening a second Groovy console (or re-enable when the console is
2606 groovyShell.setEnabled(!enabled);
2608 AlignFrame[] alignFrames = getAlignFrames();
2609 if (alignFrames != null)
2611 for (AlignFrame af : alignFrames)
2613 af.setGroovyEnabled(enabled);
2619 * Progress bars managed by the IProgressIndicator method.
2621 private Hashtable<Long, JPanel> progressBars;
2623 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2628 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2631 public void setProgressBar(String message, long id)
2633 if (progressBars == null)
2635 progressBars = new Hashtable<>();
2636 progressBarHandlers = new Hashtable<>();
2639 if (progressBars.get(Long.valueOf(id)) != null)
2641 JPanel panel = progressBars.remove(Long.valueOf(id));
2642 if (progressBarHandlers.contains(Long.valueOf(id)))
2644 progressBarHandlers.remove(Long.valueOf(id));
2646 removeProgressPanel(panel);
2650 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2657 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2658 * jalview.gui.IProgressIndicatorHandler)
2661 public void registerHandler(final long id,
2662 final IProgressIndicatorHandler handler)
2664 if (progressBarHandlers == null
2665 || !progressBars.containsKey(Long.valueOf(id)))
2667 throw new Error(MessageManager.getString(
2668 "error.call_setprogressbar_before_registering_handler"));
2670 progressBarHandlers.put(Long.valueOf(id), handler);
2671 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2672 if (handler.canCancel())
2674 JButton cancel = new JButton(
2675 MessageManager.getString("action.cancel"));
2676 final IProgressIndicator us = this;
2677 cancel.addActionListener(new ActionListener()
2681 public void actionPerformed(ActionEvent e)
2683 handler.cancelActivity(id);
2684 us.setProgressBar(MessageManager
2685 .formatMessage("label.cancelled_params", new Object[]
2686 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2690 progressPanel.add(cancel, BorderLayout.EAST);
2696 * @return true if any progress bars are still active
2699 public boolean operationInProgress()
2701 if (progressBars != null && progressBars.size() > 0)
2709 * This will return the first AlignFrame holding the given viewport instance.
2710 * It will break if there are more than one AlignFrames viewing a particular
2714 * @return alignFrame for viewport
2716 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2718 if (desktop != null)
2720 AlignmentPanel[] aps = getAlignmentPanels(
2721 viewport.getSequenceSetId());
2722 for (int panel = 0; aps != null && panel < aps.length; panel++)
2724 if (aps[panel] != null && aps[panel].av == viewport)
2726 return aps[panel].alignFrame;
2733 public VamsasApplication getVamsasApplication()
2735 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2741 * flag set if jalview GUI is being operated programmatically
2743 private boolean inBatchMode = false;
2746 * check if jalview GUI is being operated programmatically
2748 * @return inBatchMode
2750 public boolean isInBatchMode()
2756 * set flag if jalview GUI is being operated programmatically
2758 * @param inBatchMode
2760 public void setInBatchMode(boolean inBatchMode)
2762 this.inBatchMode = inBatchMode;
2766 * start service discovery and wait till it is done
2768 public void startServiceDiscovery()
2770 startServiceDiscovery(false);
2774 * start service discovery threads - blocking or non-blocking
2778 public void startServiceDiscovery(boolean blocking)
2780 startServiceDiscovery(blocking, false);
2784 * start service discovery threads
2787 * - false means call returns immediately
2788 * @param ignore_SHOW_JWS2_SERVICES_preference
2789 * - when true JABA services are discovered regardless of user's JWS2
2790 * discovery preference setting
2792 public void startServiceDiscovery(boolean blocking,
2793 boolean ignore_SHOW_JWS2_SERVICES_preference)
2795 boolean alive = true;
2796 Thread t0 = null, t1 = null, t2 = null;
2797 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2800 // todo: changesupport handlers need to be transferred
2801 if (discoverer == null)
2803 discoverer = new jalview.ws.jws1.Discoverer();
2804 // register PCS handler for desktop.
2805 discoverer.addPropertyChangeListener(changeSupport);
2807 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2808 // until we phase out completely
2809 (t0 = new Thread(discoverer)).start();
2812 if (ignore_SHOW_JWS2_SERVICES_preference
2813 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2815 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2816 .startDiscoverer(changeSupport);
2820 // TODO: do rest service discovery
2829 } catch (Exception e)
2832 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2833 || (t3 != null && t3.isAlive())
2834 || (t0 != null && t0.isAlive());
2840 * called to check if the service discovery process completed successfully.
2844 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2846 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2848 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2849 .getErrorMessages();
2852 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2854 if (serviceChangedDialog == null)
2856 // only run if we aren't already displaying one of these.
2857 addDialogThread(serviceChangedDialog = new Runnable()
2864 * JalviewDialog jd =new JalviewDialog() {
2866 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2868 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2870 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2872 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2874 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2875 * + " or mis-configured HTTP proxy settings.<br/>" +
2876 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2877 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2878 * true, true, "Web Service Configuration Problem", 450, 400);
2880 * jd.waitForInput();
2882 JvOptionPane.showConfirmDialog(Desktop.desktop,
2883 new JLabel("<html><table width=\"450\"><tr><td>"
2884 + ermsg + "</td></tr></table>"
2885 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2886 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2887 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2888 + " Tools->Preferences dialog box to change them.</p></html>"),
2889 "Web Service Configuration Problem",
2890 JvOptionPane.DEFAULT_OPTION,
2891 JvOptionPane.ERROR_MESSAGE);
2892 serviceChangedDialog = null;
2900 jalview.bin.Console.error(
2901 "Errors reported by JABA discovery service. Check web services preferences.\n"
2908 private Runnable serviceChangedDialog = null;
2911 * start a thread to open a URL in the configured browser. Pops up a warning
2912 * dialog to the user if there is an exception when calling out to the browser
2917 public static void showUrl(final String url)
2919 showUrl(url, Desktop.instance);
2923 * Like showUrl but allows progress handler to be specified
2927 * (null) or object implementing IProgressIndicator
2929 public static void showUrl(final String url,
2930 final IProgressIndicator progress)
2932 new Thread(new Runnable()
2939 if (progress != null)
2941 progress.setProgressBar(MessageManager
2942 .formatMessage("status.opening_params", new Object[]
2943 { url }), this.hashCode());
2945 jalview.util.BrowserLauncher.openURL(url);
2946 } catch (Exception ex)
2948 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2950 .getString("label.web_browser_not_found_unix"),
2951 MessageManager.getString("label.web_browser_not_found"),
2952 JvOptionPane.WARNING_MESSAGE);
2954 ex.printStackTrace();
2956 if (progress != null)
2958 progress.setProgressBar(null, this.hashCode());
2964 public static WsParamSetManager wsparamManager = null;
2966 public static ParamManager getUserParameterStore()
2968 if (wsparamManager == null)
2970 wsparamManager = new WsParamSetManager();
2972 return wsparamManager;
2976 * static hyperlink handler proxy method for use by Jalview's internal windows
2980 public static void hyperlinkUpdate(HyperlinkEvent e)
2982 if (e.getEventType() == EventType.ACTIVATED)
2987 url = e.getURL().toString();
2988 Desktop.showUrl(url);
2989 } catch (Exception x)
2994 .error("Couldn't handle string " + url + " as a URL.");
2996 // ignore any exceptions due to dud links.
3003 * single thread that handles display of dialogs to user.
3005 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3008 * flag indicating if dialogExecutor should try to acquire a permit
3010 private volatile boolean dialogPause = true;
3015 private java.util.concurrent.Semaphore block = new Semaphore(0);
3017 private static groovy.ui.Console groovyConsole;
3020 * add another dialog thread to the queue
3024 public void addDialogThread(final Runnable prompter)
3026 dialogExecutor.submit(new Runnable()
3036 } catch (InterruptedException x)
3040 if (instance == null)
3046 SwingUtilities.invokeAndWait(prompter);
3047 } catch (Exception q)
3049 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3056 public void startDialogQueue()
3058 // set the flag so we don't pause waiting for another permit and semaphore
3059 // the current task to begin
3060 dialogPause = false;
3065 * Outputs an image of the desktop to file in EPS format, after prompting the
3066 * user for choice of Text or Lineart character rendering (unless a preference
3067 * has been set). The file name is generated as
3070 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3074 protected void snapShotWindow_actionPerformed(ActionEvent e)
3076 // currently the menu option to do this is not shown
3079 int width = getWidth();
3080 int height = getHeight();
3082 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3083 ImageWriterI writer = new ImageWriterI()
3086 public void exportImage(Graphics g) throws Exception
3089 jalview.bin.Console.info("Successfully written snapshot to file "
3090 + of.getAbsolutePath());
3093 String title = "View of desktop";
3094 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3096 exporter.doExport(of, this, width, height, title);
3100 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3101 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3102 * and location last time the view was expanded (if any). However it does not
3103 * remember the split pane divider location - this is set to match the
3104 * 'exploding' frame.
3108 public void explodeViews(SplitFrame sf)
3110 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3111 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3112 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3114 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3116 int viewCount = topPanels.size();
3123 * Processing in reverse order works, forwards order leaves the first panels not
3124 * visible. I don't know why!
3126 for (int i = viewCount - 1; i >= 0; i--)
3129 * Make new top and bottom frames. These take over the respective AlignmentPanel
3130 * objects, including their AlignmentViewports, so the cdna/protein
3131 * relationships between the viewports is carried over to the new split frames.
3133 * explodedGeometry holds the (x, y) position of the previously exploded
3134 * SplitFrame, and the (width, height) of the AlignFrame component
3136 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3137 AlignFrame newTopFrame = new AlignFrame(topPanel);
3138 newTopFrame.setSize(oldTopFrame.getSize());
3139 newTopFrame.setVisible(true);
3140 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3141 .getExplodedGeometry();
3142 if (geometry != null)
3144 newTopFrame.setSize(geometry.getSize());
3147 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3148 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3149 newBottomFrame.setSize(oldBottomFrame.getSize());
3150 newBottomFrame.setVisible(true);
3151 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3152 .getExplodedGeometry();
3153 if (geometry != null)
3155 newBottomFrame.setSize(geometry.getSize());
3158 topPanel.av.setGatherViewsHere(false);
3159 bottomPanel.av.setGatherViewsHere(false);
3160 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3162 if (geometry != null)
3164 splitFrame.setLocation(geometry.getLocation());
3166 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3170 * Clear references to the panels (now relocated in the new SplitFrames) before
3171 * closing the old SplitFrame.
3174 bottomPanels.clear();
3179 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3180 * back into the given SplitFrame as additional views. Note that the gathered
3181 * frames may themselves have multiple views.
3185 public void gatherViews(GSplitFrame source)
3188 * special handling of explodedGeometry for a view within a SplitFrame: - it
3189 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3190 * height) of the AlignFrame component
3192 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3193 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3194 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3195 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3196 myBottomFrame.viewport
3197 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3198 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3199 myTopFrame.viewport.setGatherViewsHere(true);
3200 myBottomFrame.viewport.setGatherViewsHere(true);
3201 String topViewId = myTopFrame.viewport.getSequenceSetId();
3202 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3204 JInternalFrame[] frames = desktop.getAllFrames();
3205 for (JInternalFrame frame : frames)
3207 if (frame instanceof SplitFrame && frame != source)
3209 SplitFrame sf = (SplitFrame) frame;
3210 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3211 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3212 boolean gatherThis = false;
3213 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3215 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3216 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3217 if (topViewId.equals(topPanel.av.getSequenceSetId())
3218 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3221 topPanel.av.setGatherViewsHere(false);
3222 bottomPanel.av.setGatherViewsHere(false);
3223 topPanel.av.setExplodedGeometry(
3224 new Rectangle(sf.getLocation(), topFrame.getSize()));
3225 bottomPanel.av.setExplodedGeometry(
3226 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3227 myTopFrame.addAlignmentPanel(topPanel, false);
3228 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3234 topFrame.getAlignPanels().clear();
3235 bottomFrame.getAlignPanels().clear();
3242 * The dust settles...give focus to the tab we did this from.
3244 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3247 public static groovy.ui.Console getGroovyConsole()
3249 return groovyConsole;
3253 * handles the payload of a drag and drop event.
3255 * TODO refactor to desktop utilities class
3258 * - Data source strings extracted from the drop event
3260 * - protocol for each data source extracted from the drop event
3264 * - the payload from the drop event
3267 public static void transferFromDropTarget(List<Object> files,
3268 List<DataSourceType> protocols, DropTargetDropEvent evt,
3269 Transferable t) throws Exception
3272 DataFlavor uriListFlavor = new DataFlavor(
3273 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3276 urlFlavour = new DataFlavor(
3277 "application/x-java-url; class=java.net.URL");
3278 } catch (ClassNotFoundException cfe)
3280 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3284 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3289 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3290 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3291 // means url may be null.
3294 protocols.add(DataSourceType.URL);
3295 files.add(url.toString());
3296 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3297 + files.get(files.size() - 1));
3302 if (Platform.isAMacAndNotJS())
3305 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3308 } catch (Throwable ex)
3310 jalview.bin.Console.debug("URL drop handler failed.", ex);
3313 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3315 // Works on Windows and MacOSX
3316 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3317 for (Object file : (List) t
3318 .getTransferData(DataFlavor.javaFileListFlavor))
3321 protocols.add(DataSourceType.FILE);
3326 // Unix like behaviour
3327 boolean added = false;
3329 if (t.isDataFlavorSupported(uriListFlavor))
3331 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3332 // This is used by Unix drag system
3333 data = (String) t.getTransferData(uriListFlavor);
3337 // fallback to text: workaround - on OSX where there's a JVM bug
3339 .debug("standard URIListFlavor failed. Trying text");
3340 // try text fallback
3341 DataFlavor textDf = new DataFlavor(
3342 "text/plain;class=java.lang.String");
3343 if (t.isDataFlavorSupported(textDf))
3345 data = (String) t.getTransferData(textDf);
3348 jalview.bin.Console.debug("Plain text drop content returned "
3349 + (data == null ? "Null - failed" : data));
3354 while (protocols.size() < files.size())
3356 jalview.bin.Console.debug("Adding missing FILE protocol for "
3357 + files.get(protocols.size()));
3358 protocols.add(DataSourceType.FILE);
3360 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3361 data, "\r\n"); st.hasMoreTokens();)
3364 String s = st.nextToken();
3365 if (s.startsWith("#"))
3367 // the line is a comment (as per the RFC 2483)
3370 java.net.URI uri = new java.net.URI(s);
3371 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3373 protocols.add(DataSourceType.URL);
3374 files.add(uri.toString());
3378 // otherwise preserve old behaviour: catch all for file objects
3379 java.io.File file = new java.io.File(uri);
3380 protocols.add(DataSourceType.FILE);
3381 files.add(file.toString());
3386 if (jalview.bin.Console.isDebugEnabled())
3388 if (data == null || !added)
3391 if (t.getTransferDataFlavors() != null
3392 && t.getTransferDataFlavors().length > 0)
3394 jalview.bin.Console.debug(
3395 "Couldn't resolve drop data. Here are the supported flavors:");
3396 for (DataFlavor fl : t.getTransferDataFlavors())
3398 jalview.bin.Console.debug(
3399 "Supported transfer dataflavor: " + fl.toString());
3400 Object df = t.getTransferData(fl);
3403 jalview.bin.Console.debug("Retrieves: " + df);
3407 jalview.bin.Console.debug("Retrieved nothing");
3414 .debug("Couldn't resolve dataflavor for drop: "
3420 if (Platform.isWindowsAndNotJS())
3423 .debug("Scanning dropped content for Windows Link Files");
3425 // resolve any .lnk files in the file drop
3426 for (int f = 0; f < files.size(); f++)
3428 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3429 if (protocols.get(f).equals(DataSourceType.FILE)
3430 && (source.endsWith(".lnk") || source.endsWith(".url")
3431 || source.endsWith(".site")))
3435 Object obj = files.get(f);
3436 File lf = (obj instanceof File ? (File) obj
3437 : new File((String) obj));
3438 // process link file to get a URL
3439 jalview.bin.Console.debug("Found potential link file: " + lf);
3440 WindowsShortcut wscfile = new WindowsShortcut(lf);
3441 String fullname = wscfile.getRealFilename();
3442 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3443 files.set(f, fullname);
3444 jalview.bin.Console.debug("Parsed real filename " + fullname
3445 + " to extract protocol: " + protocols.get(f));
3446 } catch (Exception ex)
3448 jalview.bin.Console.error(
3449 "Couldn't parse " + files.get(f) + " as a link file.",
3458 * Sets the Preferences property for experimental features to True or False
3459 * depending on the state of the controlling menu item
3462 protected void showExperimental_actionPerformed(boolean selected)
3464 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3468 * Answers a (possibly empty) list of any structure viewer frames (currently
3469 * for either Jmol or Chimera) which are currently open. This may optionally
3470 * be restricted to viewers of a specified class, or viewers linked to a
3471 * specified alignment panel.
3474 * if not null, only return viewers linked to this panel
3475 * @param structureViewerClass
3476 * if not null, only return viewers of this class
3479 public List<StructureViewerBase> getStructureViewers(
3480 AlignmentPanel apanel,
3481 Class<? extends StructureViewerBase> structureViewerClass)
3483 List<StructureViewerBase> result = new ArrayList<>();
3484 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3486 for (JInternalFrame frame : frames)
3488 if (frame instanceof StructureViewerBase)
3490 if (structureViewerClass == null
3491 || structureViewerClass.isInstance(frame))
3494 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3496 result.add((StructureViewerBase) frame);
3504 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3506 private static boolean debugScaleMessageDone = false;
3508 public static void debugScaleMessage(Graphics g)
3510 if (debugScaleMessageDone)
3514 // output used by tests to check HiDPI scaling settings in action
3517 Graphics2D gg = (Graphics2D) g;
3520 AffineTransform t = gg.getTransform();
3521 double scaleX = t.getScaleX();
3522 double scaleY = t.getScaleY();
3523 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3524 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3525 debugScaleMessageDone = true;
3529 jalview.bin.Console.debug("Desktop graphics null");
3531 } catch (Exception e)
3533 jalview.bin.Console.debug(Cache.getStackTraceString(e));