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).
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 for (AlignmentPanel ap : af.alignPanels)
2038 if (alignmentId == null
2039 || alignmentId.equals(ap.av.getSequenceSetId()))
2045 if (aps.size() == 0)
2049 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2054 * get all the viewports on an alignment.
2056 * @param sequenceSetId
2057 * unique alignment id (may be null - all viewports returned in that
2059 * @return all viewports on the alignment bound to sequenceSetId
2061 public static AlignmentViewport[] getViewports(String sequenceSetId)
2063 List<AlignmentViewport> viewp = new ArrayList<>();
2064 if (desktop != null)
2066 AlignFrame[] frames = Desktop.getAlignFrames();
2068 for (AlignFrame afr : frames)
2070 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2071 .equals(sequenceSetId))
2073 if (afr.alignPanels != null)
2075 for (AlignmentPanel ap : afr.alignPanels)
2077 if (sequenceSetId == null
2078 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2086 viewp.add(afr.getViewport());
2090 if (viewp.size() > 0)
2092 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2099 * Explode the views in the given frame into separate AlignFrame
2103 public static void explodeViews(AlignFrame af)
2105 int size = af.alignPanels.size();
2111 // FIXME: ideally should use UI interface API
2112 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2113 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2114 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2115 for (int i = 0; i < size; i++)
2117 AlignmentPanel ap = af.alignPanels.get(i);
2119 AlignFrame newaf = new AlignFrame(ap);
2121 // transfer reference for existing feature settings to new alignFrame
2122 if (ap == af.alignPanel)
2124 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2126 newaf.featureSettings = viewFeatureSettings;
2128 newaf.setFeatureSettingsGeometry(fsBounds);
2132 * Restore the view's last exploded frame geometry if known. Multiple views from
2133 * one exploded frame share and restore the same (frame) position and size.
2135 Rectangle geometry = ap.av.getExplodedGeometry();
2136 if (geometry != null)
2138 newaf.setBounds(geometry);
2141 ap.av.setGatherViewsHere(false);
2143 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2144 AlignFrame.DEFAULT_HEIGHT);
2145 // and materialise a new feature settings dialog instance for the new
2147 // (closes the old as if 'OK' was pressed)
2148 if (ap == af.alignPanel && newaf.featureSettings != null
2149 && newaf.featureSettings.isOpen()
2150 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2152 newaf.showFeatureSettingsUI();
2156 af.featureSettings = null;
2157 af.alignPanels.clear();
2158 af.closeMenuItem_actionPerformed(true);
2163 * Gather expanded views (separate AlignFrame's) with the same sequence set
2164 * identifier back in to this frame as additional views, and close the
2165 * expanded views. Note the expanded frames may themselves have multiple
2166 * views. We take the lot.
2170 public void gatherViews(AlignFrame source)
2172 source.viewport.setGatherViewsHere(true);
2173 source.viewport.setExplodedGeometry(source.getBounds());
2174 JInternalFrame[] frames = desktop.getAllFrames();
2175 String viewId = source.viewport.getSequenceSetId();
2176 for (int t = 0; t < frames.length; t++)
2178 if (frames[t] instanceof AlignFrame && frames[t] != source)
2180 AlignFrame af = (AlignFrame) frames[t];
2181 boolean gatherThis = false;
2182 for (int a = 0; a < af.alignPanels.size(); a++)
2184 AlignmentPanel ap = af.alignPanels.get(a);
2185 if (viewId.equals(ap.av.getSequenceSetId()))
2188 ap.av.setGatherViewsHere(false);
2189 ap.av.setExplodedGeometry(af.getBounds());
2190 source.addAlignmentPanel(ap, false);
2196 if (af.featureSettings != null && af.featureSettings.isOpen())
2198 if (source.featureSettings == null)
2200 // preserve the feature settings geometry for this frame
2201 source.featureSettings = af.featureSettings;
2202 source.setFeatureSettingsGeometry(
2203 af.getFeatureSettingsGeometry());
2207 // close it and forget
2208 af.featureSettings.close();
2211 af.alignPanels.clear();
2212 af.closeMenuItem_actionPerformed(true);
2217 // refresh the feature setting UI for the source frame if it exists
2218 if (source.featureSettings != null && source.featureSettings.isOpen())
2220 source.showFeatureSettingsUI();
2225 public JInternalFrame[] getAllFrames()
2227 return desktop.getAllFrames();
2231 * Checks the given url to see if it gives a response indicating that the user
2232 * should be informed of a new questionnaire.
2236 public void checkForQuestionnaire(String url)
2238 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2239 // javax.swing.SwingUtilities.invokeLater(jvq);
2240 new Thread(jvq).start();
2243 public void checkURLLinks()
2245 // Thread off the URL link checker
2246 addDialogThread(new Runnable()
2251 if (Cache.getDefault("CHECKURLLINKS", true))
2253 // check what the actual links are - if it's just the default don't
2254 // bother with the warning
2255 List<String> links = Preferences.sequenceUrlLinks
2258 // only need to check links if there is one with a
2259 // SEQUENCE_ID which is not the default EMBL_EBI link
2260 ListIterator<String> li = links.listIterator();
2261 boolean check = false;
2262 List<JLabel> urls = new ArrayList<>();
2263 while (li.hasNext())
2265 String link = li.next();
2266 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2267 && !UrlConstants.isDefaultString(link))
2270 int barPos = link.indexOf("|");
2271 String urlMsg = barPos == -1 ? link
2272 : link.substring(0, barPos) + ": "
2273 + link.substring(barPos + 1);
2274 urls.add(new JLabel(urlMsg));
2282 // ask user to check in case URL links use old style tokens
2283 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2284 JPanel msgPanel = new JPanel();
2285 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2286 msgPanel.add(Box.createVerticalGlue());
2287 JLabel msg = new JLabel(MessageManager
2288 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2289 JLabel msg2 = new JLabel(MessageManager
2290 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2292 for (JLabel url : urls)
2298 final JCheckBox jcb = new JCheckBox(
2299 MessageManager.getString("label.do_not_display_again"));
2300 jcb.addActionListener(new ActionListener()
2303 public void actionPerformed(ActionEvent e)
2305 // update Cache settings for "don't show this again"
2306 boolean showWarningAgain = !jcb.isSelected();
2307 Cache.setProperty("CHECKURLLINKS",
2308 Boolean.valueOf(showWarningAgain).toString());
2313 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2315 .getString("label.SEQUENCE_ID_no_longer_used"),
2316 JvOptionPane.WARNING_MESSAGE);
2323 * Proxy class for JDesktopPane which optionally displays the current memory
2324 * usage and highlights the desktop area with a red bar if free memory runs
2329 public class MyDesktopPane extends JDesktopPane implements Runnable
2331 private static final float ONE_MB = 1048576f;
2333 boolean showMemoryUsage = false;
2337 java.text.NumberFormat df;
2339 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2342 public MyDesktopPane(boolean showMemoryUsage)
2344 showMemoryUsage(showMemoryUsage);
2347 public void showMemoryUsage(boolean showMemory)
2349 this.showMemoryUsage = showMemory;
2352 Thread worker = new Thread(this);
2358 public boolean isShowMemoryUsage()
2360 return showMemoryUsage;
2366 df = java.text.NumberFormat.getNumberInstance();
2367 df.setMaximumFractionDigits(2);
2368 runtime = Runtime.getRuntime();
2370 while (showMemoryUsage)
2374 maxMemory = runtime.maxMemory() / ONE_MB;
2375 allocatedMemory = runtime.totalMemory() / ONE_MB;
2376 freeMemory = runtime.freeMemory() / ONE_MB;
2377 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2379 percentUsage = (totalFreeMemory / maxMemory) * 100;
2381 // if (percentUsage < 20)
2383 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2385 // instance.set.setBorder(border1);
2388 // sleep after showing usage
2390 } catch (Exception ex)
2392 ex.printStackTrace();
2398 public void paintComponent(Graphics g)
2400 if (showMemoryUsage && g != null && df != null)
2402 if (percentUsage < 20)
2404 g.setColor(Color.red);
2406 FontMetrics fm = g.getFontMetrics();
2409 g.drawString(MessageManager.formatMessage("label.memory_stats",
2411 { df.format(totalFreeMemory), df.format(maxMemory),
2412 df.format(percentUsage) }),
2413 10, getHeight() - fm.getHeight());
2417 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2418 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2423 * Accessor method to quickly get all the AlignmentFrames loaded.
2425 * @return an array of AlignFrame, or null if none found
2427 public static AlignFrame[] getAlignFrames()
2429 if (Jalview.isHeadlessMode())
2431 // Desktop.desktop is null in headless mode
2432 return new AlignFrame[] { Jalview.currentAlignFrame };
2435 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2441 List<AlignFrame> avp = new ArrayList<>();
2443 for (int i = frames.length - 1; i > -1; i--)
2445 if (frames[i] instanceof AlignFrame)
2447 avp.add((AlignFrame) frames[i]);
2449 else if (frames[i] instanceof SplitFrame)
2452 * Also check for a split frame containing an AlignFrame
2454 GSplitFrame sf = (GSplitFrame) frames[i];
2455 if (sf.getTopFrame() instanceof AlignFrame)
2457 avp.add((AlignFrame) sf.getTopFrame());
2459 if (sf.getBottomFrame() instanceof AlignFrame)
2461 avp.add((AlignFrame) sf.getBottomFrame());
2465 if (avp.size() == 0)
2469 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2474 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2478 public GStructureViewer[] getJmols()
2480 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2486 List<GStructureViewer> avp = new ArrayList<>();
2488 for (int i = frames.length - 1; i > -1; i--)
2490 if (frames[i] instanceof AppJmol)
2492 GStructureViewer af = (GStructureViewer) frames[i];
2496 if (avp.size() == 0)
2500 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2505 * Add Groovy Support to Jalview
2508 public void groovyShell_actionPerformed()
2512 openGroovyConsole();
2513 } catch (Exception ex)
2515 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2516 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2518 MessageManager.getString("label.couldnt_create_groovy_shell"),
2519 MessageManager.getString("label.groovy_support_failed"),
2520 JvOptionPane.ERROR_MESSAGE);
2525 * Open the Groovy console
2527 void openGroovyConsole()
2529 if (groovyConsole == null)
2531 groovyConsole = new groovy.ui.Console();
2532 groovyConsole.setVariable("Jalview", this);
2533 groovyConsole.run();
2536 * We allow only one console at a time, so that AlignFrame menu option
2537 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2538 * enable 'Run script', when the console is opened, and the reverse when it is
2541 Window window = (Window) groovyConsole.getFrame();
2542 window.addWindowListener(new WindowAdapter()
2545 public void windowClosed(WindowEvent e)
2548 * rebind CMD-Q from Groovy Console to Jalview Quit
2551 enableExecuteGroovy(false);
2557 * show Groovy console window (after close and reopen)
2559 ((Window) groovyConsole.getFrame()).setVisible(true);
2562 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2563 * opening a second console
2565 enableExecuteGroovy(true);
2569 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2570 * binding when opened
2572 protected void addQuitHandler()
2575 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2577 .getKeyStroke(KeyEvent.VK_Q,
2578 jalview.util.ShortcutKeyMaskExWrapper
2579 .getMenuShortcutKeyMaskEx()),
2581 getRootPane().getActionMap().put("Quit", new AbstractAction()
2584 public void actionPerformed(ActionEvent e)
2592 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2595 * true if Groovy console is open
2597 public void enableExecuteGroovy(boolean enabled)
2600 * disable opening a second Groovy console (or re-enable when the console is
2603 groovyShell.setEnabled(!enabled);
2605 AlignFrame[] alignFrames = getAlignFrames();
2606 if (alignFrames != null)
2608 for (AlignFrame af : alignFrames)
2610 af.setGroovyEnabled(enabled);
2616 * Progress bars managed by the IProgressIndicator method.
2618 private Hashtable<Long, JPanel> progressBars;
2620 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2625 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2628 public void setProgressBar(String message, long id)
2630 if (progressBars == null)
2632 progressBars = new Hashtable<>();
2633 progressBarHandlers = new Hashtable<>();
2636 if (progressBars.get(Long.valueOf(id)) != null)
2638 JPanel panel = progressBars.remove(Long.valueOf(id));
2639 if (progressBarHandlers.contains(Long.valueOf(id)))
2641 progressBarHandlers.remove(Long.valueOf(id));
2643 removeProgressPanel(panel);
2647 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2654 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2655 * jalview.gui.IProgressIndicatorHandler)
2658 public void registerHandler(final long id,
2659 final IProgressIndicatorHandler handler)
2661 if (progressBarHandlers == null
2662 || !progressBars.containsKey(Long.valueOf(id)))
2664 throw new Error(MessageManager.getString(
2665 "error.call_setprogressbar_before_registering_handler"));
2667 progressBarHandlers.put(Long.valueOf(id), handler);
2668 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2669 if (handler.canCancel())
2671 JButton cancel = new JButton(
2672 MessageManager.getString("action.cancel"));
2673 final IProgressIndicator us = this;
2674 cancel.addActionListener(new ActionListener()
2678 public void actionPerformed(ActionEvent e)
2680 handler.cancelActivity(id);
2681 us.setProgressBar(MessageManager
2682 .formatMessage("label.cancelled_params", new Object[]
2683 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2687 progressPanel.add(cancel, BorderLayout.EAST);
2693 * @return true if any progress bars are still active
2696 public boolean operationInProgress()
2698 if (progressBars != null && progressBars.size() > 0)
2706 * This will return the first AlignFrame holding the given viewport instance.
2707 * It will break if there are more than one AlignFrames viewing a particular
2711 * @return alignFrame for viewport
2713 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2715 if (desktop != null)
2717 AlignmentPanel[] aps = getAlignmentPanels(
2718 viewport.getSequenceSetId());
2719 for (int panel = 0; aps != null && panel < aps.length; panel++)
2721 if (aps[panel] != null && aps[panel].av == viewport)
2723 return aps[panel].alignFrame;
2730 public VamsasApplication getVamsasApplication()
2732 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2738 * flag set if jalview GUI is being operated programmatically
2740 private boolean inBatchMode = false;
2743 * check if jalview GUI is being operated programmatically
2745 * @return inBatchMode
2747 public boolean isInBatchMode()
2753 * set flag if jalview GUI is being operated programmatically
2755 * @param inBatchMode
2757 public void setInBatchMode(boolean inBatchMode)
2759 this.inBatchMode = inBatchMode;
2763 * start service discovery and wait till it is done
2765 public void startServiceDiscovery()
2767 startServiceDiscovery(false);
2771 * start service discovery threads - blocking or non-blocking
2775 public void startServiceDiscovery(boolean blocking)
2777 startServiceDiscovery(blocking, false);
2781 * start service discovery threads
2784 * - false means call returns immediately
2785 * @param ignore_SHOW_JWS2_SERVICES_preference
2786 * - when true JABA services are discovered regardless of user's JWS2
2787 * discovery preference setting
2789 public void startServiceDiscovery(boolean blocking,
2790 boolean ignore_SHOW_JWS2_SERVICES_preference)
2792 boolean alive = true;
2793 Thread t0 = null, t1 = null, t2 = null;
2794 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2797 // todo: changesupport handlers need to be transferred
2798 if (discoverer == null)
2800 discoverer = new jalview.ws.jws1.Discoverer();
2801 // register PCS handler for desktop.
2802 discoverer.addPropertyChangeListener(changeSupport);
2804 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2805 // until we phase out completely
2806 (t0 = new Thread(discoverer)).start();
2809 if (ignore_SHOW_JWS2_SERVICES_preference
2810 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2812 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2813 .startDiscoverer(changeSupport);
2817 // TODO: do rest service discovery
2826 } catch (Exception e)
2829 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2830 || (t3 != null && t3.isAlive())
2831 || (t0 != null && t0.isAlive());
2837 * called to check if the service discovery process completed successfully.
2841 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2843 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2845 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2846 .getErrorMessages();
2849 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2851 if (serviceChangedDialog == null)
2853 // only run if we aren't already displaying one of these.
2854 addDialogThread(serviceChangedDialog = new Runnable()
2861 * JalviewDialog jd =new JalviewDialog() {
2863 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2865 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2867 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2869 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2871 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2872 * + " or mis-configured HTTP proxy settings.<br/>" +
2873 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2874 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2875 * true, true, "Web Service Configuration Problem", 450, 400);
2877 * jd.waitForInput();
2879 JvOptionPane.showConfirmDialog(Desktop.desktop,
2880 new JLabel("<html><table width=\"450\"><tr><td>"
2881 + ermsg + "</td></tr></table>"
2882 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2883 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2884 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2885 + " Tools->Preferences dialog box to change them.</p></html>"),
2886 "Web Service Configuration Problem",
2887 JvOptionPane.DEFAULT_OPTION,
2888 JvOptionPane.ERROR_MESSAGE);
2889 serviceChangedDialog = null;
2897 jalview.bin.Console.error(
2898 "Errors reported by JABA discovery service. Check web services preferences.\n"
2905 private Runnable serviceChangedDialog = null;
2908 * start a thread to open a URL in the configured browser. Pops up a warning
2909 * dialog to the user if there is an exception when calling out to the browser
2914 public static void showUrl(final String url)
2916 showUrl(url, Desktop.instance);
2920 * Like showUrl but allows progress handler to be specified
2924 * (null) or object implementing IProgressIndicator
2926 public static void showUrl(final String url,
2927 final IProgressIndicator progress)
2929 new Thread(new Runnable()
2936 if (progress != null)
2938 progress.setProgressBar(MessageManager
2939 .formatMessage("status.opening_params", new Object[]
2940 { url }), this.hashCode());
2942 jalview.util.BrowserLauncher.openURL(url);
2943 } catch (Exception ex)
2945 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2947 .getString("label.web_browser_not_found_unix"),
2948 MessageManager.getString("label.web_browser_not_found"),
2949 JvOptionPane.WARNING_MESSAGE);
2951 ex.printStackTrace();
2953 if (progress != null)
2955 progress.setProgressBar(null, this.hashCode());
2961 public static WsParamSetManager wsparamManager = null;
2963 public static ParamManager getUserParameterStore()
2965 if (wsparamManager == null)
2967 wsparamManager = new WsParamSetManager();
2969 return wsparamManager;
2973 * static hyperlink handler proxy method for use by Jalview's internal windows
2977 public static void hyperlinkUpdate(HyperlinkEvent e)
2979 if (e.getEventType() == EventType.ACTIVATED)
2984 url = e.getURL().toString();
2985 Desktop.showUrl(url);
2986 } catch (Exception x)
2991 .error("Couldn't handle string " + url + " as a URL.");
2993 // ignore any exceptions due to dud links.
3000 * single thread that handles display of dialogs to user.
3002 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3005 * flag indicating if dialogExecutor should try to acquire a permit
3007 private volatile boolean dialogPause = true;
3012 private java.util.concurrent.Semaphore block = new Semaphore(0);
3014 private static groovy.ui.Console groovyConsole;
3017 * add another dialog thread to the queue
3021 public void addDialogThread(final Runnable prompter)
3023 dialogExecutor.submit(new Runnable()
3033 } catch (InterruptedException x)
3037 if (instance == null)
3043 SwingUtilities.invokeAndWait(prompter);
3044 } catch (Exception q)
3046 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3053 public void startDialogQueue()
3055 // set the flag so we don't pause waiting for another permit and semaphore
3056 // the current task to begin
3057 dialogPause = false;
3062 * Outputs an image of the desktop to file in EPS format, after prompting the
3063 * user for choice of Text or Lineart character rendering (unless a preference
3064 * has been set). The file name is generated as
3067 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3071 protected void snapShotWindow_actionPerformed(ActionEvent e)
3073 // currently the menu option to do this is not shown
3076 int width = getWidth();
3077 int height = getHeight();
3079 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3080 ImageWriterI writer = new ImageWriterI()
3083 public void exportImage(Graphics g) throws Exception
3086 jalview.bin.Console.info("Successfully written snapshot to file "
3087 + of.getAbsolutePath());
3090 String title = "View of desktop";
3091 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3093 exporter.doExport(of, this, width, height, title);
3097 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3098 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3099 * and location last time the view was expanded (if any). However it does not
3100 * remember the split pane divider location - this is set to match the
3101 * 'exploding' frame.
3105 public void explodeViews(SplitFrame sf)
3107 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3108 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3109 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3111 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3113 int viewCount = topPanels.size();
3120 * Processing in reverse order works, forwards order leaves the first panels not
3121 * visible. I don't know why!
3123 for (int i = viewCount - 1; i >= 0; i--)
3126 * Make new top and bottom frames. These take over the respective AlignmentPanel
3127 * objects, including their AlignmentViewports, so the cdna/protein
3128 * relationships between the viewports is carried over to the new split frames.
3130 * explodedGeometry holds the (x, y) position of the previously exploded
3131 * SplitFrame, and the (width, height) of the AlignFrame component
3133 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3134 AlignFrame newTopFrame = new AlignFrame(topPanel);
3135 newTopFrame.setSize(oldTopFrame.getSize());
3136 newTopFrame.setVisible(true);
3137 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3138 .getExplodedGeometry();
3139 if (geometry != null)
3141 newTopFrame.setSize(geometry.getSize());
3144 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3145 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3146 newBottomFrame.setSize(oldBottomFrame.getSize());
3147 newBottomFrame.setVisible(true);
3148 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3149 .getExplodedGeometry();
3150 if (geometry != null)
3152 newBottomFrame.setSize(geometry.getSize());
3155 topPanel.av.setGatherViewsHere(false);
3156 bottomPanel.av.setGatherViewsHere(false);
3157 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3159 if (geometry != null)
3161 splitFrame.setLocation(geometry.getLocation());
3163 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3167 * Clear references to the panels (now relocated in the new SplitFrames) before
3168 * closing the old SplitFrame.
3171 bottomPanels.clear();
3176 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3177 * back into the given SplitFrame as additional views. Note that the gathered
3178 * frames may themselves have multiple views.
3182 public void gatherViews(GSplitFrame source)
3185 * special handling of explodedGeometry for a view within a SplitFrame: - it
3186 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3187 * height) of the AlignFrame component
3189 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3190 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3191 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3192 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3193 myBottomFrame.viewport
3194 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3195 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3196 myTopFrame.viewport.setGatherViewsHere(true);
3197 myBottomFrame.viewport.setGatherViewsHere(true);
3198 String topViewId = myTopFrame.viewport.getSequenceSetId();
3199 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3201 JInternalFrame[] frames = desktop.getAllFrames();
3202 for (JInternalFrame frame : frames)
3204 if (frame instanceof SplitFrame && frame != source)
3206 SplitFrame sf = (SplitFrame) frame;
3207 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3208 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3209 boolean gatherThis = false;
3210 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3212 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3213 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3214 if (topViewId.equals(topPanel.av.getSequenceSetId())
3215 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3218 topPanel.av.setGatherViewsHere(false);
3219 bottomPanel.av.setGatherViewsHere(false);
3220 topPanel.av.setExplodedGeometry(
3221 new Rectangle(sf.getLocation(), topFrame.getSize()));
3222 bottomPanel.av.setExplodedGeometry(
3223 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3224 myTopFrame.addAlignmentPanel(topPanel, false);
3225 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3231 topFrame.getAlignPanels().clear();
3232 bottomFrame.getAlignPanels().clear();
3239 * The dust settles...give focus to the tab we did this from.
3241 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3244 public static groovy.ui.Console getGroovyConsole()
3246 return groovyConsole;
3250 * handles the payload of a drag and drop event.
3252 * TODO refactor to desktop utilities class
3255 * - Data source strings extracted from the drop event
3257 * - protocol for each data source extracted from the drop event
3261 * - the payload from the drop event
3264 public static void transferFromDropTarget(List<Object> files,
3265 List<DataSourceType> protocols, DropTargetDropEvent evt,
3266 Transferable t) throws Exception
3269 DataFlavor uriListFlavor = new DataFlavor(
3270 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3273 urlFlavour = new DataFlavor(
3274 "application/x-java-url; class=java.net.URL");
3275 } catch (ClassNotFoundException cfe)
3277 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3281 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3286 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3287 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3288 // means url may be null.
3291 protocols.add(DataSourceType.URL);
3292 files.add(url.toString());
3293 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3294 + files.get(files.size() - 1));
3299 if (Platform.isAMacAndNotJS())
3302 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3305 } catch (Throwable ex)
3307 jalview.bin.Console.debug("URL drop handler failed.", ex);
3310 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3312 // Works on Windows and MacOSX
3313 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3314 for (Object file : (List) t
3315 .getTransferData(DataFlavor.javaFileListFlavor))
3318 protocols.add(DataSourceType.FILE);
3323 // Unix like behaviour
3324 boolean added = false;
3326 if (t.isDataFlavorSupported(uriListFlavor))
3328 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3329 // This is used by Unix drag system
3330 data = (String) t.getTransferData(uriListFlavor);
3334 // fallback to text: workaround - on OSX where there's a JVM bug
3336 .debug("standard URIListFlavor failed. Trying text");
3337 // try text fallback
3338 DataFlavor textDf = new DataFlavor(
3339 "text/plain;class=java.lang.String");
3340 if (t.isDataFlavorSupported(textDf))
3342 data = (String) t.getTransferData(textDf);
3345 jalview.bin.Console.debug("Plain text drop content returned "
3346 + (data == null ? "Null - failed" : data));
3351 while (protocols.size() < files.size())
3353 jalview.bin.Console.debug("Adding missing FILE protocol for "
3354 + files.get(protocols.size()));
3355 protocols.add(DataSourceType.FILE);
3357 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3358 data, "\r\n"); st.hasMoreTokens();)
3361 String s = st.nextToken();
3362 if (s.startsWith("#"))
3364 // the line is a comment (as per the RFC 2483)
3367 java.net.URI uri = new java.net.URI(s);
3368 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3370 protocols.add(DataSourceType.URL);
3371 files.add(uri.toString());
3375 // otherwise preserve old behaviour: catch all for file objects
3376 java.io.File file = new java.io.File(uri);
3377 protocols.add(DataSourceType.FILE);
3378 files.add(file.toString());
3383 if (jalview.bin.Console.isDebugEnabled())
3385 if (data == null || !added)
3388 if (t.getTransferDataFlavors() != null
3389 && t.getTransferDataFlavors().length > 0)
3391 jalview.bin.Console.debug(
3392 "Couldn't resolve drop data. Here are the supported flavors:");
3393 for (DataFlavor fl : t.getTransferDataFlavors())
3395 jalview.bin.Console.debug(
3396 "Supported transfer dataflavor: " + fl.toString());
3397 Object df = t.getTransferData(fl);
3400 jalview.bin.Console.debug("Retrieves: " + df);
3404 jalview.bin.Console.debug("Retrieved nothing");
3411 .debug("Couldn't resolve dataflavor for drop: "
3417 if (Platform.isWindowsAndNotJS())
3420 .debug("Scanning dropped content for Windows Link Files");
3422 // resolve any .lnk files in the file drop
3423 for (int f = 0; f < files.size(); f++)
3425 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3426 if (protocols.get(f).equals(DataSourceType.FILE)
3427 && (source.endsWith(".lnk") || source.endsWith(".url")
3428 || source.endsWith(".site")))
3432 Object obj = files.get(f);
3433 File lf = (obj instanceof File ? (File) obj
3434 : new File((String) obj));
3435 // process link file to get a URL
3436 jalview.bin.Console.debug("Found potential link file: " + lf);
3437 WindowsShortcut wscfile = new WindowsShortcut(lf);
3438 String fullname = wscfile.getRealFilename();
3439 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3440 files.set(f, fullname);
3441 jalview.bin.Console.debug("Parsed real filename " + fullname
3442 + " to extract protocol: " + protocols.get(f));
3443 } catch (Exception ex)
3445 jalview.bin.Console.error(
3446 "Couldn't parse " + files.get(f) + " as a link file.",
3455 * Sets the Preferences property for experimental features to True or False
3456 * depending on the state of the controlling menu item
3459 protected void showExperimental_actionPerformed(boolean selected)
3461 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3465 * Answers a (possibly empty) list of any structure viewer frames (currently
3466 * for either Jmol or Chimera) which are currently open. This may optionally
3467 * be restricted to viewers of a specified class, or viewers linked to a
3468 * specified alignment panel.
3471 * if not null, only return viewers linked to this panel
3472 * @param structureViewerClass
3473 * if not null, only return viewers of this class
3476 public List<StructureViewerBase> getStructureViewers(
3477 AlignmentPanel apanel,
3478 Class<? extends StructureViewerBase> structureViewerClass)
3480 List<StructureViewerBase> result = new ArrayList<>();
3481 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3483 for (JInternalFrame frame : frames)
3485 if (frame instanceof StructureViewerBase)
3487 if (structureViewerClass == null
3488 || structureViewerClass.isInstance(frame))
3491 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3493 result.add((StructureViewerBase) frame);
3501 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3503 private static boolean debugScaleMessageDone = false;
3505 public static void debugScaleMessage(Graphics g)
3507 if (debugScaleMessageDone)
3511 // output used by tests to check HiDPI scaling settings in action
3514 Graphics2D gg = (Graphics2D) g;
3517 AffineTransform t = gg.getTransform();
3518 double scaleX = t.getScaleX();
3519 double scaleY = t.getScaleY();
3520 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3521 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3522 debugScaleMessageDone = true;
3526 jalview.bin.Console.debug("Desktop graphics null");
3528 } catch (Exception e)
3530 jalview.bin.Console.debug(Cache.getStackTraceString(e));