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 System.out.println("Desktop exiting.");
1443 private void storeLastKnownDimensions(String string, Rectangle jc)
1445 jalview.bin.Console.debug("Storing last known dimensions for " + string
1446 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1447 + " height:" + jc.height);
1449 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1450 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1451 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1452 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1462 public void aboutMenuItem_actionPerformed(ActionEvent e)
1464 new Thread(new Runnable()
1469 new SplashScreen(false);
1475 * Returns the html text for the About screen, including any available version
1476 * number, build details, author details and citation reference, but without
1477 * the enclosing {@code html} tags
1481 public String getAboutMessage()
1483 StringBuilder message = new StringBuilder(1024);
1484 message.append("<div style=\"font-family: sans-serif;\">")
1485 .append("<h1><strong>Version: ")
1486 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1487 .append("<strong>Built: <em>")
1488 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1489 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1490 .append("</strong>");
1492 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1493 if (latestVersion.equals("Checking"))
1495 // JBP removed this message for 2.11: May be reinstated in future version
1496 // message.append("<br>...Checking latest version...</br>");
1498 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1500 boolean red = false;
1501 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1502 .indexOf("automated build") == -1)
1505 // Displayed when code version and jnlp version do not match and code
1506 // version is not a development build
1507 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1510 message.append("<br>!! Version ")
1511 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1512 .append(" is available for download from ")
1513 .append(Cache.getDefault("www.jalview.org",
1514 "https://www.jalview.org"))
1518 message.append("</div>");
1521 message.append("<br>Authors: ");
1522 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1523 message.append(CITATION);
1525 message.append("</div>");
1527 return message.toString();
1531 * Action on requesting Help documentation
1534 public void documentationMenuItem_actionPerformed()
1538 if (Platform.isJS())
1540 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1549 Help.showHelpWindow();
1551 } catch (Exception ex)
1553 System.err.println("Error opening help: " + ex.getMessage());
1558 public void closeAll_actionPerformed(ActionEvent e)
1560 // TODO show a progress bar while closing?
1561 JInternalFrame[] frames = desktop.getAllFrames();
1562 for (int i = 0; i < frames.length; i++)
1566 frames[i].setClosed(true);
1567 } catch (java.beans.PropertyVetoException ex)
1571 Jalview.setCurrentAlignFrame(null);
1572 System.out.println("ALL CLOSED");
1575 * reset state of singleton objects as appropriate (clear down session state
1576 * when all windows are closed)
1578 StructureSelectionManager ssm = StructureSelectionManager
1579 .getStructureSelectionManager(this);
1586 public int structureViewersStillRunningCount()
1589 JInternalFrame[] frames = desktop.getAllFrames();
1590 for (int i = 0; i < frames.length; i++)
1592 if (frames[i] != null
1593 && frames[i] instanceof JalviewStructureDisplayI)
1595 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1603 public void raiseRelated_actionPerformed(ActionEvent e)
1605 reorderAssociatedWindows(false, false);
1609 public void minimizeAssociated_actionPerformed(ActionEvent e)
1611 reorderAssociatedWindows(true, false);
1614 void closeAssociatedWindows()
1616 reorderAssociatedWindows(false, true);
1622 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1626 protected void garbageCollect_actionPerformed(ActionEvent e)
1628 // We simply collect the garbage
1629 jalview.bin.Console.debug("Collecting garbage...");
1631 jalview.bin.Console.debug("Finished garbage collection.");
1637 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1641 protected void showMemusage_actionPerformed(ActionEvent e)
1643 desktop.showMemoryUsage(showMemusage.isSelected());
1650 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1654 protected void showConsole_actionPerformed(ActionEvent e)
1656 showConsole(showConsole.isSelected());
1659 Console jconsole = null;
1662 * control whether the java console is visible or not
1666 void showConsole(boolean selected)
1668 // TODO: decide if we should update properties file
1669 if (jconsole != null) // BH 2018
1671 showConsole.setSelected(selected);
1672 Cache.setProperty("SHOW_JAVA_CONSOLE",
1673 Boolean.valueOf(selected).toString());
1674 jconsole.setVisible(selected);
1678 void reorderAssociatedWindows(boolean minimize, boolean close)
1680 JInternalFrame[] frames = desktop.getAllFrames();
1681 if (frames == null || frames.length < 1)
1686 AlignmentViewport source = null, target = null;
1687 if (frames[0] instanceof AlignFrame)
1689 source = ((AlignFrame) frames[0]).getCurrentView();
1691 else if (frames[0] instanceof TreePanel)
1693 source = ((TreePanel) frames[0]).getViewPort();
1695 else if (frames[0] instanceof PCAPanel)
1697 source = ((PCAPanel) frames[0]).av;
1699 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1701 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1706 for (int i = 0; i < frames.length; i++)
1709 if (frames[i] == null)
1713 if (frames[i] instanceof AlignFrame)
1715 target = ((AlignFrame) frames[i]).getCurrentView();
1717 else if (frames[i] instanceof TreePanel)
1719 target = ((TreePanel) frames[i]).getViewPort();
1721 else if (frames[i] instanceof PCAPanel)
1723 target = ((PCAPanel) frames[i]).av;
1725 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1727 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1730 if (source == target)
1736 frames[i].setClosed(true);
1740 frames[i].setIcon(minimize);
1743 frames[i].toFront();
1747 } catch (java.beans.PropertyVetoException ex)
1762 protected void preferences_actionPerformed(ActionEvent e)
1764 Preferences.openPreferences();
1768 * Prompts the user to choose a file and then saves the Jalview state as a
1769 * Jalview project file
1772 public void saveState_actionPerformed()
1774 saveState_actionPerformed(false);
1777 public void saveState_actionPerformed(boolean saveAs)
1779 java.io.File projectFile = getProjectFile();
1780 // autoSave indicates we already have a file and don't need to ask
1781 boolean autoSave = projectFile != null && !saveAs
1782 && BackupFiles.getEnabled();
1784 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1785 // saveAs="+saveAs+", Backups
1786 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1788 boolean approveSave = false;
1791 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1794 chooser.setFileView(new JalviewFileView());
1795 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1797 int value = chooser.showSaveDialog(this);
1799 if (value == JalviewFileChooser.APPROVE_OPTION)
1801 projectFile = chooser.getSelectedFile();
1802 setProjectFile(projectFile);
1807 if (approveSave || autoSave)
1809 final Desktop me = this;
1810 final java.io.File chosenFile = projectFile;
1811 new Thread(new Runnable()
1816 // TODO: refactor to Jalview desktop session controller action.
1817 setProgressBar(MessageManager.formatMessage(
1818 "label.saving_jalview_project", new Object[]
1819 { chosenFile.getName() }), chosenFile.hashCode());
1820 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1821 // TODO catch and handle errors for savestate
1822 // TODO prevent user from messing with the Desktop whilst we're saving
1825 boolean doBackup = BackupFiles.getEnabled();
1826 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1829 new Jalview2XML().saveState(
1830 doBackup ? backupfiles.getTempFile() : chosenFile);
1834 backupfiles.setWriteSuccess(true);
1835 backupfiles.rollBackupsAndRenameTempFile();
1837 } catch (OutOfMemoryError oom)
1839 new OOMWarning("Whilst saving current state to "
1840 + chosenFile.getName(), oom);
1841 } catch (Exception ex)
1843 jalview.bin.Console.error("Problems whilst trying to save to "
1844 + chosenFile.getName(), ex);
1845 JvOptionPane.showMessageDialog(me,
1846 MessageManager.formatMessage(
1847 "label.error_whilst_saving_current_state_to",
1849 { chosenFile.getName() }),
1850 MessageManager.getString("label.couldnt_save_project"),
1851 JvOptionPane.WARNING_MESSAGE);
1853 setProgressBar(null, chosenFile.hashCode());
1860 public void saveAsState_actionPerformed(ActionEvent e)
1862 saveState_actionPerformed(true);
1865 protected void setProjectFile(File choice)
1867 this.projectFile = choice;
1870 public File getProjectFile()
1872 return this.projectFile;
1876 * Shows a file chooser dialog and tries to read in the selected file as a
1880 public void loadState_actionPerformed()
1882 final String[] suffix = new String[] { "jvp", "jar" };
1883 final String[] desc = new String[] { "Jalview Project",
1884 "Jalview Project (old)" };
1885 JalviewFileChooser chooser = new JalviewFileChooser(
1886 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1887 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1891 chooser.setFileView(new JalviewFileView());
1892 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1893 chooser.setResponseHandler(0, () -> {
1894 File selectedFile = chooser.getSelectedFile();
1895 setProjectFile(selectedFile);
1896 String choice = selectedFile.getAbsolutePath();
1897 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1898 new Thread(new Runnable()
1905 new Jalview2XML().loadJalviewAlign(selectedFile);
1906 } catch (OutOfMemoryError oom)
1908 new OOMWarning("Whilst loading project from " + choice, oom);
1909 } catch (Exception ex)
1911 jalview.bin.Console.error(
1912 "Problems whilst loading project from " + choice, ex);
1913 JvOptionPane.showMessageDialog(Desktop.desktop,
1914 MessageManager.formatMessage(
1915 "label.error_whilst_loading_project_from",
1918 MessageManager.getString("label.couldnt_load_project"),
1919 JvOptionPane.WARNING_MESSAGE);
1922 }, "Project Loader").start();
1926 chooser.showOpenDialog(this);
1930 public void inputSequence_actionPerformed(ActionEvent e)
1932 new SequenceFetcher(this);
1935 JPanel progressPanel;
1937 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1939 public void startLoading(final Object fileName)
1941 if (fileLoadingCount == 0)
1943 fileLoadingPanels.add(addProgressPanel(MessageManager
1944 .formatMessage("label.loading_file", new Object[]
1950 private JPanel addProgressPanel(String string)
1952 if (progressPanel == null)
1954 progressPanel = new JPanel(new GridLayout(1, 1));
1955 totalProgressCount = 0;
1956 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1958 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1959 JProgressBar progressBar = new JProgressBar();
1960 progressBar.setIndeterminate(true);
1962 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1964 thisprogress.add(progressBar, BorderLayout.CENTER);
1965 progressPanel.add(thisprogress);
1966 ((GridLayout) progressPanel.getLayout()).setRows(
1967 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1968 ++totalProgressCount;
1969 instance.validate();
1970 return thisprogress;
1973 int totalProgressCount = 0;
1975 private void removeProgressPanel(JPanel progbar)
1977 if (progressPanel != null)
1979 synchronized (progressPanel)
1981 progressPanel.remove(progbar);
1982 GridLayout gl = (GridLayout) progressPanel.getLayout();
1983 gl.setRows(gl.getRows() - 1);
1984 if (--totalProgressCount < 1)
1986 this.getContentPane().remove(progressPanel);
1987 progressPanel = null;
1994 public void stopLoading()
1997 if (fileLoadingCount < 1)
1999 while (fileLoadingPanels.size() > 0)
2001 removeProgressPanel(fileLoadingPanels.remove(0));
2003 fileLoadingPanels.clear();
2004 fileLoadingCount = 0;
2009 public static int getViewCount(String alignmentId)
2011 AlignmentViewport[] aps = getViewports(alignmentId);
2012 return (aps == null) ? 0 : aps.length;
2017 * @param alignmentId
2018 * - if null, all sets are returned
2019 * @return all AlignmentPanels concerning the alignmentId sequence set
2021 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2023 if (Desktop.desktop == null)
2025 // no frames created and in headless mode
2026 // TODO: verify that frames are recoverable when in headless mode
2029 List<AlignmentPanel> aps = new ArrayList<>();
2030 AlignFrame[] frames = getAlignFrames();
2035 for (AlignFrame af : frames)
2037 for (AlignmentPanel ap : af.alignPanels)
2039 if (alignmentId == null
2040 || alignmentId.equals(ap.av.getSequenceSetId()))
2046 if (aps.size() == 0)
2050 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2055 * get all the viewports on an alignment.
2057 * @param sequenceSetId
2058 * unique alignment id (may be null - all viewports returned in that
2060 * @return all viewports on the alignment bound to sequenceSetId
2062 public static AlignmentViewport[] getViewports(String sequenceSetId)
2064 List<AlignmentViewport> viewp = new ArrayList<>();
2065 if (desktop != null)
2067 AlignFrame[] frames = Desktop.getAlignFrames();
2069 for (AlignFrame afr : frames)
2071 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2072 .equals(sequenceSetId))
2074 if (afr.alignPanels != null)
2076 for (AlignmentPanel ap : afr.alignPanels)
2078 if (sequenceSetId == null
2079 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2087 viewp.add(afr.getViewport());
2091 if (viewp.size() > 0)
2093 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2100 * Explode the views in the given frame into separate AlignFrame
2104 public static void explodeViews(AlignFrame af)
2106 int size = af.alignPanels.size();
2112 // FIXME: ideally should use UI interface API
2113 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2114 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2115 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2116 for (int i = 0; i < size; i++)
2118 AlignmentPanel ap = af.alignPanels.get(i);
2120 AlignFrame newaf = new AlignFrame(ap);
2122 // transfer reference for existing feature settings to new alignFrame
2123 if (ap == af.alignPanel)
2125 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2127 newaf.featureSettings = viewFeatureSettings;
2129 newaf.setFeatureSettingsGeometry(fsBounds);
2133 * Restore the view's last exploded frame geometry if known. Multiple views from
2134 * one exploded frame share and restore the same (frame) position and size.
2136 Rectangle geometry = ap.av.getExplodedGeometry();
2137 if (geometry != null)
2139 newaf.setBounds(geometry);
2142 ap.av.setGatherViewsHere(false);
2144 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2145 AlignFrame.DEFAULT_HEIGHT);
2146 // and materialise a new feature settings dialog instance for the new
2148 // (closes the old as if 'OK' was pressed)
2149 if (ap == af.alignPanel && newaf.featureSettings != null
2150 && newaf.featureSettings.isOpen()
2151 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2153 newaf.showFeatureSettingsUI();
2157 af.featureSettings = null;
2158 af.alignPanels.clear();
2159 af.closeMenuItem_actionPerformed(true);
2164 * Gather expanded views (separate AlignFrame's) with the same sequence set
2165 * identifier back in to this frame as additional views, and close the
2166 * expanded views. Note the expanded frames may themselves have multiple
2167 * views. We take the lot.
2171 public void gatherViews(AlignFrame source)
2173 source.viewport.setGatherViewsHere(true);
2174 source.viewport.setExplodedGeometry(source.getBounds());
2175 JInternalFrame[] frames = desktop.getAllFrames();
2176 String viewId = source.viewport.getSequenceSetId();
2177 for (int t = 0; t < frames.length; t++)
2179 if (frames[t] instanceof AlignFrame && frames[t] != source)
2181 AlignFrame af = (AlignFrame) frames[t];
2182 boolean gatherThis = false;
2183 for (int a = 0; a < af.alignPanels.size(); a++)
2185 AlignmentPanel ap = af.alignPanels.get(a);
2186 if (viewId.equals(ap.av.getSequenceSetId()))
2189 ap.av.setGatherViewsHere(false);
2190 ap.av.setExplodedGeometry(af.getBounds());
2191 source.addAlignmentPanel(ap, false);
2197 if (af.featureSettings != null && af.featureSettings.isOpen())
2199 if (source.featureSettings == null)
2201 // preserve the feature settings geometry for this frame
2202 source.featureSettings = af.featureSettings;
2203 source.setFeatureSettingsGeometry(
2204 af.getFeatureSettingsGeometry());
2208 // close it and forget
2209 af.featureSettings.close();
2212 af.alignPanels.clear();
2213 af.closeMenuItem_actionPerformed(true);
2218 // refresh the feature setting UI for the source frame if it exists
2219 if (source.featureSettings != null && source.featureSettings.isOpen())
2221 source.showFeatureSettingsUI();
2226 public JInternalFrame[] getAllFrames()
2228 return desktop.getAllFrames();
2232 * Checks the given url to see if it gives a response indicating that the user
2233 * should be informed of a new questionnaire.
2237 public void checkForQuestionnaire(String url)
2239 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2240 // javax.swing.SwingUtilities.invokeLater(jvq);
2241 new Thread(jvq).start();
2244 public void checkURLLinks()
2246 // Thread off the URL link checker
2247 addDialogThread(new Runnable()
2252 if (Cache.getDefault("CHECKURLLINKS", true))
2254 // check what the actual links are - if it's just the default don't
2255 // bother with the warning
2256 List<String> links = Preferences.sequenceUrlLinks
2259 // only need to check links if there is one with a
2260 // SEQUENCE_ID which is not the default EMBL_EBI link
2261 ListIterator<String> li = links.listIterator();
2262 boolean check = false;
2263 List<JLabel> urls = new ArrayList<>();
2264 while (li.hasNext())
2266 String link = li.next();
2267 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2268 && !UrlConstants.isDefaultString(link))
2271 int barPos = link.indexOf("|");
2272 String urlMsg = barPos == -1 ? link
2273 : link.substring(0, barPos) + ": "
2274 + link.substring(barPos + 1);
2275 urls.add(new JLabel(urlMsg));
2283 // ask user to check in case URL links use old style tokens
2284 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2285 JPanel msgPanel = new JPanel();
2286 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2287 msgPanel.add(Box.createVerticalGlue());
2288 JLabel msg = new JLabel(MessageManager
2289 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2290 JLabel msg2 = new JLabel(MessageManager
2291 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2293 for (JLabel url : urls)
2299 final JCheckBox jcb = new JCheckBox(
2300 MessageManager.getString("label.do_not_display_again"));
2301 jcb.addActionListener(new ActionListener()
2304 public void actionPerformed(ActionEvent e)
2306 // update Cache settings for "don't show this again"
2307 boolean showWarningAgain = !jcb.isSelected();
2308 Cache.setProperty("CHECKURLLINKS",
2309 Boolean.valueOf(showWarningAgain).toString());
2314 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2316 .getString("label.SEQUENCE_ID_no_longer_used"),
2317 JvOptionPane.WARNING_MESSAGE);
2324 * Proxy class for JDesktopPane which optionally displays the current memory
2325 * usage and highlights the desktop area with a red bar if free memory runs
2330 public class MyDesktopPane extends JDesktopPane implements Runnable
2332 private static final float ONE_MB = 1048576f;
2334 boolean showMemoryUsage = false;
2338 java.text.NumberFormat df;
2340 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2343 public MyDesktopPane(boolean showMemoryUsage)
2345 showMemoryUsage(showMemoryUsage);
2348 public void showMemoryUsage(boolean showMemory)
2350 this.showMemoryUsage = showMemory;
2353 Thread worker = new Thread(this);
2359 public boolean isShowMemoryUsage()
2361 return showMemoryUsage;
2367 df = java.text.NumberFormat.getNumberInstance();
2368 df.setMaximumFractionDigits(2);
2369 runtime = Runtime.getRuntime();
2371 while (showMemoryUsage)
2375 maxMemory = runtime.maxMemory() / ONE_MB;
2376 allocatedMemory = runtime.totalMemory() / ONE_MB;
2377 freeMemory = runtime.freeMemory() / ONE_MB;
2378 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2380 percentUsage = (totalFreeMemory / maxMemory) * 100;
2382 // if (percentUsage < 20)
2384 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2386 // instance.set.setBorder(border1);
2389 // sleep after showing usage
2391 } catch (Exception ex)
2393 ex.printStackTrace();
2399 public void paintComponent(Graphics g)
2401 if (showMemoryUsage && g != null && df != null)
2403 if (percentUsage < 20)
2405 g.setColor(Color.red);
2407 FontMetrics fm = g.getFontMetrics();
2410 g.drawString(MessageManager.formatMessage("label.memory_stats",
2412 { df.format(totalFreeMemory), df.format(maxMemory),
2413 df.format(percentUsage) }),
2414 10, getHeight() - fm.getHeight());
2418 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2419 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2424 * Accessor method to quickly get all the AlignmentFrames loaded.
2426 * @return an array of AlignFrame, or null if none found
2428 public static AlignFrame[] getAlignFrames()
2430 if (Jalview.isHeadlessMode())
2432 // Desktop.desktop is null in headless mode
2433 return new AlignFrame[] { Jalview.currentAlignFrame };
2436 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2442 List<AlignFrame> avp = new ArrayList<>();
2444 for (int i = frames.length - 1; i > -1; i--)
2446 if (frames[i] instanceof AlignFrame)
2448 avp.add((AlignFrame) frames[i]);
2450 else if (frames[i] instanceof SplitFrame)
2453 * Also check for a split frame containing an AlignFrame
2455 GSplitFrame sf = (GSplitFrame) frames[i];
2456 if (sf.getTopFrame() instanceof AlignFrame)
2458 avp.add((AlignFrame) sf.getTopFrame());
2460 if (sf.getBottomFrame() instanceof AlignFrame)
2462 avp.add((AlignFrame) sf.getBottomFrame());
2466 if (avp.size() == 0)
2470 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2475 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2479 public GStructureViewer[] getJmols()
2481 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2487 List<GStructureViewer> avp = new ArrayList<>();
2489 for (int i = frames.length - 1; i > -1; i--)
2491 if (frames[i] instanceof AppJmol)
2493 GStructureViewer af = (GStructureViewer) frames[i];
2497 if (avp.size() == 0)
2501 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2506 * Add Groovy Support to Jalview
2509 public void groovyShell_actionPerformed()
2513 openGroovyConsole();
2514 } catch (Exception ex)
2516 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2517 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2519 MessageManager.getString("label.couldnt_create_groovy_shell"),
2520 MessageManager.getString("label.groovy_support_failed"),
2521 JvOptionPane.ERROR_MESSAGE);
2526 * Open the Groovy console
2528 void openGroovyConsole()
2530 if (groovyConsole == null)
2532 groovyConsole = new groovy.ui.Console();
2533 groovyConsole.setVariable("Jalview", this);
2534 groovyConsole.run();
2537 * We allow only one console at a time, so that AlignFrame menu option
2538 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2539 * enable 'Run script', when the console is opened, and the reverse when it is
2542 Window window = (Window) groovyConsole.getFrame();
2543 window.addWindowListener(new WindowAdapter()
2546 public void windowClosed(WindowEvent e)
2549 * rebind CMD-Q from Groovy Console to Jalview Quit
2552 enableExecuteGroovy(false);
2558 * show Groovy console window (after close and reopen)
2560 ((Window) groovyConsole.getFrame()).setVisible(true);
2563 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2564 * opening a second console
2566 enableExecuteGroovy(true);
2570 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2571 * binding when opened
2573 protected void addQuitHandler()
2576 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2578 .getKeyStroke(KeyEvent.VK_Q,
2579 jalview.util.ShortcutKeyMaskExWrapper
2580 .getMenuShortcutKeyMaskEx()),
2582 getRootPane().getActionMap().put("Quit", new AbstractAction()
2585 public void actionPerformed(ActionEvent e)
2593 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2596 * true if Groovy console is open
2598 public void enableExecuteGroovy(boolean enabled)
2601 * disable opening a second Groovy console (or re-enable when the console is
2604 groovyShell.setEnabled(!enabled);
2606 AlignFrame[] alignFrames = getAlignFrames();
2607 if (alignFrames != null)
2609 for (AlignFrame af : alignFrames)
2611 af.setGroovyEnabled(enabled);
2617 * Progress bars managed by the IProgressIndicator method.
2619 private Hashtable<Long, JPanel> progressBars;
2621 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2626 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2629 public void setProgressBar(String message, long id)
2631 if (progressBars == null)
2633 progressBars = new Hashtable<>();
2634 progressBarHandlers = new Hashtable<>();
2637 if (progressBars.get(Long.valueOf(id)) != null)
2639 JPanel panel = progressBars.remove(Long.valueOf(id));
2640 if (progressBarHandlers.contains(Long.valueOf(id)))
2642 progressBarHandlers.remove(Long.valueOf(id));
2644 removeProgressPanel(panel);
2648 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2655 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2656 * jalview.gui.IProgressIndicatorHandler)
2659 public void registerHandler(final long id,
2660 final IProgressIndicatorHandler handler)
2662 if (progressBarHandlers == null
2663 || !progressBars.containsKey(Long.valueOf(id)))
2665 throw new Error(MessageManager.getString(
2666 "error.call_setprogressbar_before_registering_handler"));
2668 progressBarHandlers.put(Long.valueOf(id), handler);
2669 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2670 if (handler.canCancel())
2672 JButton cancel = new JButton(
2673 MessageManager.getString("action.cancel"));
2674 final IProgressIndicator us = this;
2675 cancel.addActionListener(new ActionListener()
2679 public void actionPerformed(ActionEvent e)
2681 handler.cancelActivity(id);
2682 us.setProgressBar(MessageManager
2683 .formatMessage("label.cancelled_params", new Object[]
2684 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2688 progressPanel.add(cancel, BorderLayout.EAST);
2694 * @return true if any progress bars are still active
2697 public boolean operationInProgress()
2699 if (progressBars != null && progressBars.size() > 0)
2707 * This will return the first AlignFrame holding the given viewport instance.
2708 * It will break if there are more than one AlignFrames viewing a particular
2712 * @return alignFrame for viewport
2714 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2716 if (desktop != null)
2718 AlignmentPanel[] aps = getAlignmentPanels(
2719 viewport.getSequenceSetId());
2720 for (int panel = 0; aps != null && panel < aps.length; panel++)
2722 if (aps[panel] != null && aps[panel].av == viewport)
2724 return aps[panel].alignFrame;
2731 public VamsasApplication getVamsasApplication()
2733 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2739 * flag set if jalview GUI is being operated programmatically
2741 private boolean inBatchMode = false;
2744 * check if jalview GUI is being operated programmatically
2746 * @return inBatchMode
2748 public boolean isInBatchMode()
2754 * set flag if jalview GUI is being operated programmatically
2756 * @param inBatchMode
2758 public void setInBatchMode(boolean inBatchMode)
2760 this.inBatchMode = inBatchMode;
2764 * start service discovery and wait till it is done
2766 public void startServiceDiscovery()
2768 startServiceDiscovery(false);
2772 * start service discovery threads - blocking or non-blocking
2776 public void startServiceDiscovery(boolean blocking)
2778 startServiceDiscovery(blocking, false);
2782 * start service discovery threads
2785 * - false means call returns immediately
2786 * @param ignore_SHOW_JWS2_SERVICES_preference
2787 * - when true JABA services are discovered regardless of user's JWS2
2788 * discovery preference setting
2790 public void startServiceDiscovery(boolean blocking,
2791 boolean ignore_SHOW_JWS2_SERVICES_preference)
2793 boolean alive = true;
2794 Thread t0 = null, t1 = null, t2 = null;
2795 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2798 // todo: changesupport handlers need to be transferred
2799 if (discoverer == null)
2801 discoverer = new jalview.ws.jws1.Discoverer();
2802 // register PCS handler for desktop.
2803 discoverer.addPropertyChangeListener(changeSupport);
2805 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2806 // until we phase out completely
2807 (t0 = new Thread(discoverer)).start();
2810 if (ignore_SHOW_JWS2_SERVICES_preference
2811 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2813 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2814 .startDiscoverer(changeSupport);
2818 // TODO: do rest service discovery
2827 } catch (Exception e)
2830 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2831 || (t3 != null && t3.isAlive())
2832 || (t0 != null && t0.isAlive());
2838 * called to check if the service discovery process completed successfully.
2842 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2844 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2846 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2847 .getErrorMessages();
2850 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2852 if (serviceChangedDialog == null)
2854 // only run if we aren't already displaying one of these.
2855 addDialogThread(serviceChangedDialog = new Runnable()
2862 * JalviewDialog jd =new JalviewDialog() {
2864 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2866 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2868 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2870 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2872 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2873 * + " or mis-configured HTTP proxy settings.<br/>" +
2874 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2875 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2876 * true, true, "Web Service Configuration Problem", 450, 400);
2878 * jd.waitForInput();
2880 JvOptionPane.showConfirmDialog(Desktop.desktop,
2881 new JLabel("<html><table width=\"450\"><tr><td>"
2882 + ermsg + "</td></tr></table>"
2883 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2884 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2885 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2886 + " Tools->Preferences dialog box to change them.</p></html>"),
2887 "Web Service Configuration Problem",
2888 JvOptionPane.DEFAULT_OPTION,
2889 JvOptionPane.ERROR_MESSAGE);
2890 serviceChangedDialog = null;
2898 jalview.bin.Console.error(
2899 "Errors reported by JABA discovery service. Check web services preferences.\n"
2906 private Runnable serviceChangedDialog = null;
2909 * start a thread to open a URL in the configured browser. Pops up a warning
2910 * dialog to the user if there is an exception when calling out to the browser
2915 public static void showUrl(final String url)
2917 showUrl(url, Desktop.instance);
2921 * Like showUrl but allows progress handler to be specified
2925 * (null) or object implementing IProgressIndicator
2927 public static void showUrl(final String url,
2928 final IProgressIndicator progress)
2930 new Thread(new Runnable()
2937 if (progress != null)
2939 progress.setProgressBar(MessageManager
2940 .formatMessage("status.opening_params", new Object[]
2941 { url }), this.hashCode());
2943 jalview.util.BrowserLauncher.openURL(url);
2944 } catch (Exception ex)
2946 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2948 .getString("label.web_browser_not_found_unix"),
2949 MessageManager.getString("label.web_browser_not_found"),
2950 JvOptionPane.WARNING_MESSAGE);
2952 ex.printStackTrace();
2954 if (progress != null)
2956 progress.setProgressBar(null, this.hashCode());
2962 public static WsParamSetManager wsparamManager = null;
2964 public static ParamManager getUserParameterStore()
2966 if (wsparamManager == null)
2968 wsparamManager = new WsParamSetManager();
2970 return wsparamManager;
2974 * static hyperlink handler proxy method for use by Jalview's internal windows
2978 public static void hyperlinkUpdate(HyperlinkEvent e)
2980 if (e.getEventType() == EventType.ACTIVATED)
2985 url = e.getURL().toString();
2986 Desktop.showUrl(url);
2987 } catch (Exception x)
2992 .error("Couldn't handle string " + url + " as a URL.");
2994 // ignore any exceptions due to dud links.
3001 * single thread that handles display of dialogs to user.
3003 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3006 * flag indicating if dialogExecutor should try to acquire a permit
3008 private volatile boolean dialogPause = true;
3013 private java.util.concurrent.Semaphore block = new Semaphore(0);
3015 private static groovy.ui.Console groovyConsole;
3018 * add another dialog thread to the queue
3022 public void addDialogThread(final Runnable prompter)
3024 dialogExecutor.submit(new Runnable()
3034 } catch (InterruptedException x)
3038 if (instance == null)
3044 SwingUtilities.invokeAndWait(prompter);
3045 } catch (Exception q)
3047 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3054 public void startDialogQueue()
3056 // set the flag so we don't pause waiting for another permit and semaphore
3057 // the current task to begin
3058 dialogPause = false;
3063 * Outputs an image of the desktop to file in EPS format, after prompting the
3064 * user for choice of Text or Lineart character rendering (unless a preference
3065 * has been set). The file name is generated as
3068 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3072 protected void snapShotWindow_actionPerformed(ActionEvent e)
3074 // currently the menu option to do this is not shown
3077 int width = getWidth();
3078 int height = getHeight();
3080 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3081 ImageWriterI writer = new ImageWriterI()
3084 public void exportImage(Graphics g) throws Exception
3087 jalview.bin.Console.info("Successfully written snapshot to file "
3088 + of.getAbsolutePath());
3091 String title = "View of desktop";
3092 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3094 exporter.doExport(of, this, width, height, title);
3098 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3099 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3100 * and location last time the view was expanded (if any). However it does not
3101 * remember the split pane divider location - this is set to match the
3102 * 'exploding' frame.
3106 public void explodeViews(SplitFrame sf)
3108 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3109 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3110 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3112 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3114 int viewCount = topPanels.size();
3121 * Processing in reverse order works, forwards order leaves the first panels not
3122 * visible. I don't know why!
3124 for (int i = viewCount - 1; i >= 0; i--)
3127 * Make new top and bottom frames. These take over the respective AlignmentPanel
3128 * objects, including their AlignmentViewports, so the cdna/protein
3129 * relationships between the viewports is carried over to the new split frames.
3131 * explodedGeometry holds the (x, y) position of the previously exploded
3132 * SplitFrame, and the (width, height) of the AlignFrame component
3134 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3135 AlignFrame newTopFrame = new AlignFrame(topPanel);
3136 newTopFrame.setSize(oldTopFrame.getSize());
3137 newTopFrame.setVisible(true);
3138 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3139 .getExplodedGeometry();
3140 if (geometry != null)
3142 newTopFrame.setSize(geometry.getSize());
3145 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3146 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3147 newBottomFrame.setSize(oldBottomFrame.getSize());
3148 newBottomFrame.setVisible(true);
3149 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3150 .getExplodedGeometry();
3151 if (geometry != null)
3153 newBottomFrame.setSize(geometry.getSize());
3156 topPanel.av.setGatherViewsHere(false);
3157 bottomPanel.av.setGatherViewsHere(false);
3158 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3160 if (geometry != null)
3162 splitFrame.setLocation(geometry.getLocation());
3164 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3168 * Clear references to the panels (now relocated in the new SplitFrames) before
3169 * closing the old SplitFrame.
3172 bottomPanels.clear();
3177 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3178 * back into the given SplitFrame as additional views. Note that the gathered
3179 * frames may themselves have multiple views.
3183 public void gatherViews(GSplitFrame source)
3186 * special handling of explodedGeometry for a view within a SplitFrame: - it
3187 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3188 * height) of the AlignFrame component
3190 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3191 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3192 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3193 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3194 myBottomFrame.viewport
3195 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3196 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3197 myTopFrame.viewport.setGatherViewsHere(true);
3198 myBottomFrame.viewport.setGatherViewsHere(true);
3199 String topViewId = myTopFrame.viewport.getSequenceSetId();
3200 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3202 JInternalFrame[] frames = desktop.getAllFrames();
3203 for (JInternalFrame frame : frames)
3205 if (frame instanceof SplitFrame && frame != source)
3207 SplitFrame sf = (SplitFrame) frame;
3208 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3209 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3210 boolean gatherThis = false;
3211 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3213 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3214 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3215 if (topViewId.equals(topPanel.av.getSequenceSetId())
3216 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3219 topPanel.av.setGatherViewsHere(false);
3220 bottomPanel.av.setGatherViewsHere(false);
3221 topPanel.av.setExplodedGeometry(
3222 new Rectangle(sf.getLocation(), topFrame.getSize()));
3223 bottomPanel.av.setExplodedGeometry(
3224 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3225 myTopFrame.addAlignmentPanel(topPanel, false);
3226 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3232 topFrame.getAlignPanels().clear();
3233 bottomFrame.getAlignPanels().clear();
3240 * The dust settles...give focus to the tab we did this from.
3242 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3245 public static groovy.ui.Console getGroovyConsole()
3247 return groovyConsole;
3251 * handles the payload of a drag and drop event.
3253 * TODO refactor to desktop utilities class
3256 * - Data source strings extracted from the drop event
3258 * - protocol for each data source extracted from the drop event
3262 * - the payload from the drop event
3265 public static void transferFromDropTarget(List<Object> files,
3266 List<DataSourceType> protocols, DropTargetDropEvent evt,
3267 Transferable t) throws Exception
3270 DataFlavor uriListFlavor = new DataFlavor(
3271 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3274 urlFlavour = new DataFlavor(
3275 "application/x-java-url; class=java.net.URL");
3276 } catch (ClassNotFoundException cfe)
3278 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3282 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3287 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3288 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3289 // means url may be null.
3292 protocols.add(DataSourceType.URL);
3293 files.add(url.toString());
3294 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3295 + files.get(files.size() - 1));
3300 if (Platform.isAMacAndNotJS())
3303 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3306 } catch (Throwable ex)
3308 jalview.bin.Console.debug("URL drop handler failed.", ex);
3311 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3313 // Works on Windows and MacOSX
3314 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3315 for (Object file : (List) t
3316 .getTransferData(DataFlavor.javaFileListFlavor))
3319 protocols.add(DataSourceType.FILE);
3324 // Unix like behaviour
3325 boolean added = false;
3327 if (t.isDataFlavorSupported(uriListFlavor))
3329 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3330 // This is used by Unix drag system
3331 data = (String) t.getTransferData(uriListFlavor);
3335 // fallback to text: workaround - on OSX where there's a JVM bug
3337 .debug("standard URIListFlavor failed. Trying text");
3338 // try text fallback
3339 DataFlavor textDf = new DataFlavor(
3340 "text/plain;class=java.lang.String");
3341 if (t.isDataFlavorSupported(textDf))
3343 data = (String) t.getTransferData(textDf);
3346 jalview.bin.Console.debug("Plain text drop content returned "
3347 + (data == null ? "Null - failed" : data));
3352 while (protocols.size() < files.size())
3354 jalview.bin.Console.debug("Adding missing FILE protocol for "
3355 + files.get(protocols.size()));
3356 protocols.add(DataSourceType.FILE);
3358 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3359 data, "\r\n"); st.hasMoreTokens();)
3362 String s = st.nextToken();
3363 if (s.startsWith("#"))
3365 // the line is a comment (as per the RFC 2483)
3368 java.net.URI uri = new java.net.URI(s);
3369 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3371 protocols.add(DataSourceType.URL);
3372 files.add(uri.toString());
3376 // otherwise preserve old behaviour: catch all for file objects
3377 java.io.File file = new java.io.File(uri);
3378 protocols.add(DataSourceType.FILE);
3379 files.add(file.toString());
3384 if (jalview.bin.Console.isDebugEnabled())
3386 if (data == null || !added)
3389 if (t.getTransferDataFlavors() != null
3390 && t.getTransferDataFlavors().length > 0)
3392 jalview.bin.Console.debug(
3393 "Couldn't resolve drop data. Here are the supported flavors:");
3394 for (DataFlavor fl : t.getTransferDataFlavors())
3396 jalview.bin.Console.debug(
3397 "Supported transfer dataflavor: " + fl.toString());
3398 Object df = t.getTransferData(fl);
3401 jalview.bin.Console.debug("Retrieves: " + df);
3405 jalview.bin.Console.debug("Retrieved nothing");
3412 .debug("Couldn't resolve dataflavor for drop: "
3418 if (Platform.isWindowsAndNotJS())
3421 .debug("Scanning dropped content for Windows Link Files");
3423 // resolve any .lnk files in the file drop
3424 for (int f = 0; f < files.size(); f++)
3426 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3427 if (protocols.get(f).equals(DataSourceType.FILE)
3428 && (source.endsWith(".lnk") || source.endsWith(".url")
3429 || source.endsWith(".site")))
3433 Object obj = files.get(f);
3434 File lf = (obj instanceof File ? (File) obj
3435 : new File((String) obj));
3436 // process link file to get a URL
3437 jalview.bin.Console.debug("Found potential link file: " + lf);
3438 WindowsShortcut wscfile = new WindowsShortcut(lf);
3439 String fullname = wscfile.getRealFilename();
3440 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3441 files.set(f, fullname);
3442 jalview.bin.Console.debug("Parsed real filename " + fullname
3443 + " to extract protocol: " + protocols.get(f));
3444 } catch (Exception ex)
3446 jalview.bin.Console.error(
3447 "Couldn't parse " + files.get(f) + " as a link file.",
3456 * Sets the Preferences property for experimental features to True or False
3457 * depending on the state of the controlling menu item
3460 protected void showExperimental_actionPerformed(boolean selected)
3462 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3466 * Answers a (possibly empty) list of any structure viewer frames (currently
3467 * for either Jmol or Chimera) which are currently open. This may optionally
3468 * be restricted to viewers of a specified class, or viewers linked to a
3469 * specified alignment panel.
3472 * if not null, only return viewers linked to this panel
3473 * @param structureViewerClass
3474 * if not null, only return viewers of this class
3477 public List<StructureViewerBase> getStructureViewers(
3478 AlignmentPanel apanel,
3479 Class<? extends StructureViewerBase> structureViewerClass)
3481 List<StructureViewerBase> result = new ArrayList<>();
3482 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3484 for (JInternalFrame frame : frames)
3486 if (frame instanceof StructureViewerBase)
3488 if (structureViewerClass == null
3489 || structureViewerClass.isInstance(frame))
3492 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3494 result.add((StructureViewerBase) frame);
3502 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3504 private static boolean debugScaleMessageDone = false;
3506 public static void debugScaleMessage(Graphics g)
3508 if (debugScaleMessageDone)
3512 // output used by tests to check HiDPI scaling settings in action
3515 Graphics2D gg = (Graphics2D) g;
3518 AffineTransform t = gg.getTransform();
3519 double scaleX = t.getScaleX();
3520 double scaleY = t.getScaleY();
3521 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3522 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3523 debugScaleMessageDone = true;
3527 jalview.bin.Console.debug("Desktop graphics null");
3529 } catch (Exception e)
3531 jalview.bin.Console.debug(Cache.getStackTraceString(e));