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.isWindowsAndNotJS() ? new DefaultDesktopManager()
507 : Platform.isAMacAndNotJS()
508 ? new AquaInternalFrameManager(
509 desktop.getDesktopManager())
510 : desktop.getDesktopManager())));
512 Rectangle dims = getLastKnownDimensions("");
519 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
520 int xPos = Math.max(5, (screenSize.width - 900) / 2);
521 int yPos = Math.max(5, (screenSize.height - 650) / 2);
522 setBounds(xPos, yPos, 900, 650);
525 if (!Platform.isJS())
532 jconsole = new Console(this, showjconsole);
533 jconsole.setHeader(Cache.getVersionDetailsForConsole());
534 showConsole(showjconsole);
536 showNews.setVisible(false);
538 experimentalFeatures.setSelected(showExperimental());
540 getIdentifiersOrgData();
544 // Spawn a thread that shows the splashscreen
547 SwingUtilities.invokeLater(new Runnable()
552 new SplashScreen(true);
557 // Thread off a new instance of the file chooser - this reduces the time
559 // takes to open it later on.
560 new Thread(new Runnable()
565 jalview.bin.Console.debug("Filechooser init thread started.");
566 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
567 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
569 jalview.bin.Console.debug("Filechooser init thread finished.");
572 // Add the service change listener
573 changeSupport.addJalviewPropertyChangeListener("services",
574 new PropertyChangeListener()
578 public void propertyChange(PropertyChangeEvent evt)
581 .debug("Firing service changed event for "
582 + evt.getNewValue());
583 JalviewServicesChanged(evt);
588 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
591 this.addMouseListener(ma = new MouseAdapter()
594 public void mousePressed(MouseEvent evt)
596 if (evt.isPopupTrigger()) // Mac
598 showPasteMenu(evt.getX(), evt.getY());
603 public void mouseReleased(MouseEvent evt)
605 if (evt.isPopupTrigger()) // Windows
607 showPasteMenu(evt.getX(), evt.getY());
611 desktop.addMouseListener(ma);
615 * Answers true if user preferences to enable experimental features is True
620 public boolean showExperimental()
622 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
623 Boolean.FALSE.toString());
624 return Boolean.valueOf(experimental).booleanValue();
627 public void doConfigureStructurePrefs()
629 // configure services
630 StructureSelectionManager ssm = StructureSelectionManager
631 .getStructureSelectionManager(this);
632 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
634 ssm.setAddTempFacAnnot(
635 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
636 ssm.setProcessSecondaryStructure(
637 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
638 // JAL-3915 - RNAView is no longer an option so this has no effect
639 ssm.setSecStructServices(
640 Cache.getDefault(Preferences.USE_RNAVIEW, false));
644 ssm.setAddTempFacAnnot(false);
645 ssm.setProcessSecondaryStructure(false);
646 ssm.setSecStructServices(false);
650 public void checkForNews()
652 final Desktop me = this;
653 // Thread off the news reader, in case there are connection problems.
654 new Thread(new Runnable()
659 jalview.bin.Console.debug("Starting news thread.");
660 jvnews = new BlogReader(me);
661 showNews.setVisible(true);
662 jalview.bin.Console.debug("Completed news thread.");
667 public void getIdentifiersOrgData()
669 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
670 {// Thread off the identifiers fetcher
671 new Thread(new Runnable()
677 .debug("Downloading data from identifiers.org");
680 UrlDownloadClient.download(IdOrgSettings.getUrl(),
681 IdOrgSettings.getDownloadLocation());
682 } catch (IOException e)
685 .debug("Exception downloading identifiers.org data"
695 protected void showNews_actionPerformed(ActionEvent e)
697 showNews(showNews.isSelected());
700 void showNews(boolean visible)
702 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
703 showNews.setSelected(visible);
704 if (visible && !jvnews.isVisible())
706 new Thread(new Runnable()
711 long now = System.currentTimeMillis();
712 Desktop.instance.setProgressBar(
713 MessageManager.getString("status.refreshing_news"), now);
714 jvnews.refreshNews();
715 Desktop.instance.setProgressBar(null, now);
723 * recover the last known dimensions for a jalview window
726 * - empty string is desktop, all other windows have unique prefix
727 * @return null or last known dimensions scaled to current geometry (if last
728 * window geom was known)
730 Rectangle getLastKnownDimensions(String windowName)
732 // TODO: lock aspect ratio for scaling desktop Bug #0058199
733 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
734 String x = Cache.getProperty(windowName + "SCREEN_X");
735 String y = Cache.getProperty(windowName + "SCREEN_Y");
736 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
737 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
738 if ((x != null) && (y != null) && (width != null) && (height != null))
740 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
741 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
742 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
744 // attempt #1 - try to cope with change in screen geometry - this
745 // version doesn't preserve original jv aspect ratio.
746 // take ratio of current screen size vs original screen size.
747 double sw = ((1f * screenSize.width) / (1f * Integer
748 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
749 double sh = ((1f * screenSize.height) / (1f * Integer
750 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
751 // rescale the bounds depending upon the current screen geometry.
752 ix = (int) (ix * sw);
753 iw = (int) (iw * sw);
754 iy = (int) (iy * sh);
755 ih = (int) (ih * sh);
756 while (ix >= screenSize.width)
758 jalview.bin.Console.debug(
759 "Window geometry location recall error: shifting horizontal to within screenbounds.");
760 ix -= screenSize.width;
762 while (iy >= screenSize.height)
764 jalview.bin.Console.debug(
765 "Window geometry location recall error: shifting vertical to within screenbounds.");
766 iy -= screenSize.height;
768 jalview.bin.Console.debug(
769 "Got last known dimensions for " + windowName + ": x:" + ix
770 + " y:" + iy + " width:" + iw + " height:" + ih);
772 // return dimensions for new instance
773 return new Rectangle(ix, iy, iw, ih);
778 void showPasteMenu(int x, int y)
780 JPopupMenu popup = new JPopupMenu();
781 JMenuItem item = new JMenuItem(
782 MessageManager.getString("label.paste_new_window"));
783 item.addActionListener(new ActionListener()
786 public void actionPerformed(ActionEvent evt)
793 popup.show(this, x, y);
800 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
801 Transferable contents = c.getContents(this);
803 if (contents != null)
805 String file = (String) contents
806 .getTransferData(DataFlavor.stringFlavor);
808 FileFormatI format = new IdentifyFile().identify(file,
809 DataSourceType.PASTE);
811 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
814 } catch (Exception ex)
817 "Unable to paste alignment from system clipboard:\n" + ex);
822 * Adds and opens the given frame to the desktop
833 public static synchronized void addInternalFrame(
834 final JInternalFrame frame, String title, int w, int h)
836 addInternalFrame(frame, title, true, w, h, true, false);
840 * Add an internal frame to the Jalview desktop
847 * When true, display frame immediately, otherwise, caller must call
848 * setVisible themselves.
854 public static synchronized void addInternalFrame(
855 final JInternalFrame frame, String title, boolean makeVisible,
858 addInternalFrame(frame, title, makeVisible, w, h, true, false);
862 * Add an internal frame to the Jalview desktop and make it visible
875 public static synchronized void addInternalFrame(
876 final JInternalFrame frame, String title, int w, int h,
879 addInternalFrame(frame, title, true, w, h, resizable, false);
883 * Add an internal frame to the Jalview desktop
890 * When true, display frame immediately, otherwise, caller must call
891 * setVisible themselves.
898 * @param ignoreMinSize
899 * Do not set the default minimum size for frame
901 public static synchronized void addInternalFrame(
902 final JInternalFrame frame, String title, boolean makeVisible,
903 int w, int h, boolean resizable, boolean ignoreMinSize)
906 // TODO: allow callers to determine X and Y position of frame (eg. via
908 // TODO: consider fixing method to update entries in the window submenu with
909 // the current window title
911 frame.setTitle(title);
912 if (frame.getWidth() < 1 || frame.getHeight() < 1)
916 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
917 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
918 // IF JALVIEW IS RUNNING HEADLESS
919 // ///////////////////////////////////////////////
920 if (instance == null || (System.getProperty("java.awt.headless") != null
921 && System.getProperty("java.awt.headless").equals("true")))
930 frame.setMinimumSize(
931 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
933 // Set default dimension for Alignment Frame window.
934 // The Alignment Frame window could be added from a number of places,
936 // I did this here in order not to miss out on any Alignment frame.
937 if (frame instanceof AlignFrame)
939 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
940 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
944 frame.setVisible(makeVisible);
945 frame.setClosable(true);
946 frame.setResizable(resizable);
947 frame.setMaximizable(resizable);
948 frame.setIconifiable(resizable);
949 frame.setOpaque(Platform.isJS());
951 if (frame.getX() < 1 && frame.getY() < 1)
953 frame.setLocation(xOffset * openFrameCount,
954 yOffset * ((openFrameCount - 1) % 10) + yOffset);
958 * add an entry for the new frame in the Window menu (and remove it when the
961 final JMenuItem menuItem = new JMenuItem(title);
962 frame.addInternalFrameListener(new InternalFrameAdapter()
965 public void internalFrameActivated(InternalFrameEvent evt)
967 JInternalFrame itf = desktop.getSelectedFrame();
970 if (itf instanceof AlignFrame)
972 Jalview.setCurrentAlignFrame((AlignFrame) itf);
979 public void internalFrameClosed(InternalFrameEvent evt)
981 PaintRefresher.RemoveComponent(frame);
984 * defensive check to prevent frames being added half off the window
986 if (openFrameCount > 0)
992 * ensure no reference to alignFrame retained by menu item listener
994 if (menuItem.getActionListeners().length > 0)
996 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
998 windowMenu.remove(menuItem);
1002 menuItem.addActionListener(new ActionListener()
1005 public void actionPerformed(ActionEvent e)
1009 frame.setSelected(true);
1010 frame.setIcon(false);
1011 } catch (java.beans.PropertyVetoException ex)
1018 setKeyBindings(frame);
1022 windowMenu.add(menuItem);
1027 frame.setSelected(true);
1028 frame.requestFocus();
1029 } catch (java.beans.PropertyVetoException ve)
1031 } catch (java.lang.ClassCastException cex)
1033 jalview.bin.Console.warn(
1034 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1040 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1045 private static void setKeyBindings(JInternalFrame frame)
1047 @SuppressWarnings("serial")
1048 final Action closeAction = new AbstractAction()
1051 public void actionPerformed(ActionEvent e)
1058 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1060 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1061 InputEvent.CTRL_DOWN_MASK);
1062 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1063 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1065 InputMap inputMap = frame
1066 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1067 String ctrlW = ctrlWKey.toString();
1068 inputMap.put(ctrlWKey, ctrlW);
1069 inputMap.put(cmdWKey, ctrlW);
1071 ActionMap actionMap = frame.getActionMap();
1072 actionMap.put(ctrlW, closeAction);
1076 public void lostOwnership(Clipboard clipboard, Transferable contents)
1080 Desktop.jalviewClipboard = null;
1083 internalCopy = false;
1087 public void dragEnter(DropTargetDragEvent evt)
1092 public void dragExit(DropTargetEvent evt)
1097 public void dragOver(DropTargetDragEvent evt)
1102 public void dropActionChanged(DropTargetDragEvent evt)
1113 public void drop(DropTargetDropEvent evt)
1115 boolean success = true;
1116 // JAL-1552 - acceptDrop required before getTransferable call for
1117 // Java's Transferable for native dnd
1118 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1119 Transferable t = evt.getTransferable();
1120 List<Object> files = new ArrayList<>();
1121 List<DataSourceType> protocols = new ArrayList<>();
1125 Desktop.transferFromDropTarget(files, protocols, evt, t);
1126 } catch (Exception e)
1128 e.printStackTrace();
1136 for (int i = 0; i < files.size(); i++)
1138 // BH 2018 File or String
1139 Object file = files.get(i);
1140 String fileName = file.toString();
1141 DataSourceType protocol = (protocols == null)
1142 ? DataSourceType.FILE
1144 FileFormatI format = null;
1146 if (fileName.endsWith(".jar"))
1148 format = FileFormat.Jalview;
1153 format = new IdentifyFile().identify(file, protocol);
1155 if (file instanceof File)
1157 Platform.cacheFileData((File) file);
1159 new FileLoader().LoadFile(null, file, protocol, format);
1162 } catch (Exception ex)
1167 evt.dropComplete(success); // need this to ensure input focus is properly
1168 // transfered to any new windows created
1178 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1180 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1181 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1182 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1183 BackupFiles.getEnabled());
1185 chooser.setFileView(new JalviewFileView());
1186 chooser.setDialogTitle(
1187 MessageManager.getString("label.open_local_file"));
1188 chooser.setToolTipText(MessageManager.getString("action.open"));
1190 chooser.setResponseHandler(0, () -> {
1191 File selectedFile = chooser.getSelectedFile();
1192 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1194 FileFormatI format = chooser.getSelectedFormat();
1197 * Call IdentifyFile to verify the file contains what its extension implies.
1198 * Skip this step for dynamically added file formats, because IdentifyFile does
1199 * not know how to recognise them.
1201 if (FileFormats.getInstance().isIdentifiable(format))
1205 format = new IdentifyFile().identify(selectedFile,
1206 DataSourceType.FILE);
1207 } catch (FileFormatException e)
1209 // format = null; //??
1213 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1217 chooser.showOpenDialog(this);
1221 * Shows a dialog for input of a URL at which to retrieve alignment data
1226 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1228 // This construct allows us to have a wider textfield
1230 JLabel label = new JLabel(
1231 MessageManager.getString("label.input_file_url"));
1233 JPanel panel = new JPanel(new GridLayout(2, 1));
1237 * the URL to fetch is input in Java: an editable combobox with history JS:
1238 * (pending JAL-3038) a plain text field
1241 String urlBase = "https://www.";
1242 if (Platform.isJS())
1244 history = new JTextField(urlBase, 35);
1253 JComboBox<String> asCombo = new JComboBox<>();
1254 asCombo.setPreferredSize(new Dimension(400, 20));
1255 asCombo.setEditable(true);
1256 asCombo.addItem(urlBase);
1257 String historyItems = Cache.getProperty("RECENT_URL");
1258 if (historyItems != null)
1260 for (String token : historyItems.split("\\t"))
1262 asCombo.addItem(token);
1269 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1270 MessageManager.getString("action.cancel") };
1271 Callable<Void> action = () -> {
1272 @SuppressWarnings("unchecked")
1273 String url = (history instanceof JTextField
1274 ? ((JTextField) history).getText()
1275 : ((JComboBox<String>) history).getEditor().getItem()
1276 .toString().trim());
1278 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1280 if (viewport != null)
1282 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1283 FileFormat.Jalview);
1287 new FileLoader().LoadFile(url, DataSourceType.URL,
1288 FileFormat.Jalview);
1293 FileFormatI format = null;
1296 format = new IdentifyFile().identify(url, DataSourceType.URL);
1297 } catch (FileFormatException e)
1299 // TODO revise error handling, distinguish between
1300 // URL not found and response not valid
1305 String msg = MessageManager.formatMessage("label.couldnt_locate",
1307 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1308 MessageManager.getString("label.url_not_found"),
1309 JvOptionPane.WARNING_MESSAGE);
1311 return null; // Void
1314 if (viewport != null)
1316 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1321 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1324 return null; // Void
1326 String dialogOption = MessageManager
1327 .getString("label.input_alignment_from_url");
1328 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1329 .showInternalDialog(panel, dialogOption,
1330 JvOptionPane.YES_NO_CANCEL_OPTION,
1331 JvOptionPane.PLAIN_MESSAGE, null, options,
1332 MessageManager.getString("action.ok"));
1336 * Opens the CutAndPaste window for the user to paste an alignment in to
1339 * - if not null, the pasted alignment is added to the current
1340 * alignment; if null, to a new alignment window
1343 public void inputTextboxMenuItem_actionPerformed(
1344 AlignmentViewPanel viewPanel)
1346 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1347 cap.setForInput(viewPanel);
1348 Desktop.addInternalFrame(cap,
1349 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1354 * Check with user and saving files before actually quitting
1356 public void desktopQuit()
1358 desktopQuit(true, false);
1361 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1363 final Callable<Void> doDesktopQuit = () -> {
1364 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1365 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1366 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1367 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1368 getBounds().y, getWidth(), getHeight()));
1370 if (jconsole != null)
1372 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1373 jconsole.stopConsole();
1378 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1381 // Frames should all close automatically. Keeping external
1382 // viewers open should already be decided by user.
1383 closeAll_actionPerformed(null);
1385 // check for aborted quit
1386 if (QuitHandler.quitCancelled())
1388 jalview.bin.Console.debug("Desktop aborting quit");
1392 if (dialogExecutor != null)
1394 dialogExecutor.shutdownNow();
1397 if (groovyConsole != null)
1399 // suppress a possible repeat prompt to save script
1400 groovyConsole.setDirty(false);
1401 groovyConsole.exit();
1404 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1406 // note that shutdown hook will not be run
1407 jalview.bin.Console.debug("Force Quit selected by user");
1408 Runtime.getRuntime().halt(0);
1411 jalview.bin.Console.debug("Quit selected by user");
1414 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1415 // instance.dispose();
1419 return null; // Void
1422 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1423 QuitHandler.defaultCancelQuit);
1427 * Don't call this directly, use desktopQuit() above. Exits the program.
1432 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1433 // not run a second time if gotQuitResponse flag has been set (i.e. user
1434 // confirmed quit of some kind).
1438 private void storeLastKnownDimensions(String string, Rectangle jc)
1440 jalview.bin.Console.debug("Storing last known dimensions for " + string
1441 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1442 + " height:" + jc.height);
1444 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1445 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1446 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1447 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1457 public void aboutMenuItem_actionPerformed(ActionEvent e)
1459 new Thread(new Runnable()
1464 new SplashScreen(false);
1470 * Returns the html text for the About screen, including any available version
1471 * number, build details, author details and citation reference, but without
1472 * the enclosing {@code html} tags
1476 public String getAboutMessage()
1478 StringBuilder message = new StringBuilder(1024);
1479 message.append("<div style=\"font-family: sans-serif;\">")
1480 .append("<h1><strong>Version: ")
1481 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1482 .append("<strong>Built: <em>")
1483 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1484 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1485 .append("</strong>");
1487 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1488 if (latestVersion.equals("Checking"))
1490 // JBP removed this message for 2.11: May be reinstated in future version
1491 // message.append("<br>...Checking latest version...</br>");
1493 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1495 boolean red = false;
1496 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1497 .indexOf("automated build") == -1)
1500 // Displayed when code version and jnlp version do not match and code
1501 // version is not a development build
1502 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1505 message.append("<br>!! Version ")
1506 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1507 .append(" is available for download from ")
1508 .append(Cache.getDefault("www.jalview.org",
1509 "https://www.jalview.org"))
1513 message.append("</div>");
1516 message.append("<br>Authors: ");
1517 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1518 message.append(CITATION);
1520 message.append("</div>");
1522 return message.toString();
1526 * Action on requesting Help documentation
1529 public void documentationMenuItem_actionPerformed()
1533 if (Platform.isJS())
1535 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1544 Help.showHelpWindow();
1546 } catch (Exception ex)
1548 System.err.println("Error opening help: " + ex.getMessage());
1553 public void closeAll_actionPerformed(ActionEvent e)
1555 // TODO show a progress bar while closing?
1556 JInternalFrame[] frames = desktop.getAllFrames();
1557 for (int i = 0; i < frames.length; i++)
1561 frames[i].setClosed(true);
1562 } catch (java.beans.PropertyVetoException ex)
1566 Jalview.setCurrentAlignFrame(null);
1567 System.out.println("ALL CLOSED");
1570 * reset state of singleton objects as appropriate (clear down session state
1571 * when all windows are closed)
1573 StructureSelectionManager ssm = StructureSelectionManager
1574 .getStructureSelectionManager(this);
1581 public int structureViewersStillRunningCount()
1584 JInternalFrame[] frames = desktop.getAllFrames();
1585 for (int i = 0; i < frames.length; i++)
1587 if (frames[i] != null
1588 && frames[i] instanceof JalviewStructureDisplayI)
1590 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1598 public void raiseRelated_actionPerformed(ActionEvent e)
1600 reorderAssociatedWindows(false, false);
1604 public void minimizeAssociated_actionPerformed(ActionEvent e)
1606 reorderAssociatedWindows(true, false);
1609 void closeAssociatedWindows()
1611 reorderAssociatedWindows(false, true);
1617 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1621 protected void garbageCollect_actionPerformed(ActionEvent e)
1623 // We simply collect the garbage
1624 jalview.bin.Console.debug("Collecting garbage...");
1626 jalview.bin.Console.debug("Finished garbage collection.");
1632 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1636 protected void showMemusage_actionPerformed(ActionEvent e)
1638 desktop.showMemoryUsage(showMemusage.isSelected());
1645 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1649 protected void showConsole_actionPerformed(ActionEvent e)
1651 showConsole(showConsole.isSelected());
1654 Console jconsole = null;
1657 * control whether the java console is visible or not
1661 void showConsole(boolean selected)
1663 // TODO: decide if we should update properties file
1664 if (jconsole != null) // BH 2018
1666 showConsole.setSelected(selected);
1667 Cache.setProperty("SHOW_JAVA_CONSOLE",
1668 Boolean.valueOf(selected).toString());
1669 jconsole.setVisible(selected);
1673 void reorderAssociatedWindows(boolean minimize, boolean close)
1675 JInternalFrame[] frames = desktop.getAllFrames();
1676 if (frames == null || frames.length < 1)
1681 AlignmentViewport source = null, target = null;
1682 if (frames[0] instanceof AlignFrame)
1684 source = ((AlignFrame) frames[0]).getCurrentView();
1686 else if (frames[0] instanceof TreePanel)
1688 source = ((TreePanel) frames[0]).getViewPort();
1690 else if (frames[0] instanceof PCAPanel)
1692 source = ((PCAPanel) frames[0]).av;
1694 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1696 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1701 for (int i = 0; i < frames.length; i++)
1704 if (frames[i] == null)
1708 if (frames[i] instanceof AlignFrame)
1710 target = ((AlignFrame) frames[i]).getCurrentView();
1712 else if (frames[i] instanceof TreePanel)
1714 target = ((TreePanel) frames[i]).getViewPort();
1716 else if (frames[i] instanceof PCAPanel)
1718 target = ((PCAPanel) frames[i]).av;
1720 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1722 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1725 if (source == target)
1731 frames[i].setClosed(true);
1735 frames[i].setIcon(minimize);
1738 frames[i].toFront();
1742 } catch (java.beans.PropertyVetoException ex)
1757 protected void preferences_actionPerformed(ActionEvent e)
1759 Preferences.openPreferences();
1763 * Prompts the user to choose a file and then saves the Jalview state as a
1764 * Jalview project file
1767 public void saveState_actionPerformed()
1769 saveState_actionPerformed(false);
1772 public void saveState_actionPerformed(boolean saveAs)
1774 java.io.File projectFile = getProjectFile();
1775 // autoSave indicates we already have a file and don't need to ask
1776 boolean autoSave = projectFile != null && !saveAs
1777 && BackupFiles.getEnabled();
1779 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1780 // saveAs="+saveAs+", Backups
1781 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1783 boolean approveSave = false;
1786 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1789 chooser.setFileView(new JalviewFileView());
1790 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1792 int value = chooser.showSaveDialog(this);
1794 if (value == JalviewFileChooser.APPROVE_OPTION)
1796 projectFile = chooser.getSelectedFile();
1797 setProjectFile(projectFile);
1802 if (approveSave || autoSave)
1804 final Desktop me = this;
1805 final java.io.File chosenFile = projectFile;
1806 new Thread(new Runnable()
1811 // TODO: refactor to Jalview desktop session controller action.
1812 setProgressBar(MessageManager.formatMessage(
1813 "label.saving_jalview_project", new Object[]
1814 { chosenFile.getName() }), chosenFile.hashCode());
1815 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1816 // TODO catch and handle errors for savestate
1817 // TODO prevent user from messing with the Desktop whilst we're saving
1820 boolean doBackup = BackupFiles.getEnabled();
1821 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1824 new Jalview2XML().saveState(
1825 doBackup ? backupfiles.getTempFile() : chosenFile);
1829 backupfiles.setWriteSuccess(true);
1830 backupfiles.rollBackupsAndRenameTempFile();
1832 } catch (OutOfMemoryError oom)
1834 new OOMWarning("Whilst saving current state to "
1835 + chosenFile.getName(), oom);
1836 } catch (Exception ex)
1838 jalview.bin.Console.error("Problems whilst trying to save to "
1839 + chosenFile.getName(), ex);
1840 JvOptionPane.showMessageDialog(me,
1841 MessageManager.formatMessage(
1842 "label.error_whilst_saving_current_state_to",
1844 { chosenFile.getName() }),
1845 MessageManager.getString("label.couldnt_save_project"),
1846 JvOptionPane.WARNING_MESSAGE);
1848 setProgressBar(null, chosenFile.hashCode());
1855 public void saveAsState_actionPerformed(ActionEvent e)
1857 saveState_actionPerformed(true);
1860 protected void setProjectFile(File choice)
1862 this.projectFile = choice;
1865 public File getProjectFile()
1867 return this.projectFile;
1871 * Shows a file chooser dialog and tries to read in the selected file as a
1875 public void loadState_actionPerformed()
1877 final String[] suffix = new String[] { "jvp", "jar" };
1878 final String[] desc = new String[] { "Jalview Project",
1879 "Jalview Project (old)" };
1880 JalviewFileChooser chooser = new JalviewFileChooser(
1881 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1882 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1886 chooser.setFileView(new JalviewFileView());
1887 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1888 chooser.setResponseHandler(0, () -> {
1889 File selectedFile = chooser.getSelectedFile();
1890 setProjectFile(selectedFile);
1891 String choice = selectedFile.getAbsolutePath();
1892 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1893 new Thread(new Runnable()
1900 new Jalview2XML().loadJalviewAlign(selectedFile);
1901 } catch (OutOfMemoryError oom)
1903 new OOMWarning("Whilst loading project from " + choice, oom);
1904 } catch (Exception ex)
1906 jalview.bin.Console.error(
1907 "Problems whilst loading project from " + choice, ex);
1908 JvOptionPane.showMessageDialog(Desktop.desktop,
1909 MessageManager.formatMessage(
1910 "label.error_whilst_loading_project_from",
1913 MessageManager.getString("label.couldnt_load_project"),
1914 JvOptionPane.WARNING_MESSAGE);
1917 }, "Project Loader").start();
1921 chooser.showOpenDialog(this);
1925 public void inputSequence_actionPerformed(ActionEvent e)
1927 new SequenceFetcher(this);
1930 JPanel progressPanel;
1932 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1934 public void startLoading(final Object fileName)
1936 if (fileLoadingCount == 0)
1938 fileLoadingPanels.add(addProgressPanel(MessageManager
1939 .formatMessage("label.loading_file", new Object[]
1945 private JPanel addProgressPanel(String string)
1947 if (progressPanel == null)
1949 progressPanel = new JPanel(new GridLayout(1, 1));
1950 totalProgressCount = 0;
1951 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1953 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1954 JProgressBar progressBar = new JProgressBar();
1955 progressBar.setIndeterminate(true);
1957 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1959 thisprogress.add(progressBar, BorderLayout.CENTER);
1960 progressPanel.add(thisprogress);
1961 ((GridLayout) progressPanel.getLayout()).setRows(
1962 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1963 ++totalProgressCount;
1964 instance.validate();
1965 return thisprogress;
1968 int totalProgressCount = 0;
1970 private void removeProgressPanel(JPanel progbar)
1972 if (progressPanel != null)
1974 synchronized (progressPanel)
1976 progressPanel.remove(progbar);
1977 GridLayout gl = (GridLayout) progressPanel.getLayout();
1978 gl.setRows(gl.getRows() - 1);
1979 if (--totalProgressCount < 1)
1981 this.getContentPane().remove(progressPanel);
1982 progressPanel = null;
1989 public void stopLoading()
1992 if (fileLoadingCount < 1)
1994 while (fileLoadingPanels.size() > 0)
1996 removeProgressPanel(fileLoadingPanels.remove(0));
1998 fileLoadingPanels.clear();
1999 fileLoadingCount = 0;
2004 public static int getViewCount(String alignmentId)
2006 AlignmentViewport[] aps = getViewports(alignmentId);
2007 return (aps == null) ? 0 : aps.length;
2012 * @param alignmentId
2013 * - if null, all sets are returned
2014 * @return all AlignmentPanels concerning the alignmentId sequence set
2016 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2018 if (Desktop.desktop == null)
2020 // no frames created and in headless mode
2021 // TODO: verify that frames are recoverable when in headless mode
2024 List<AlignmentPanel> aps = new ArrayList<>();
2025 AlignFrame[] frames = getAlignFrames();
2030 for (AlignFrame af : frames)
2032 for (AlignmentPanel ap : af.alignPanels)
2034 if (alignmentId == null
2035 || alignmentId.equals(ap.av.getSequenceSetId()))
2041 if (aps.size() == 0)
2045 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2050 * get all the viewports on an alignment.
2052 * @param sequenceSetId
2053 * unique alignment id (may be null - all viewports returned in that
2055 * @return all viewports on the alignment bound to sequenceSetId
2057 public static AlignmentViewport[] getViewports(String sequenceSetId)
2059 List<AlignmentViewport> viewp = new ArrayList<>();
2060 if (desktop != null)
2062 AlignFrame[] frames = Desktop.getAlignFrames();
2064 for (AlignFrame afr : frames)
2066 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2067 .equals(sequenceSetId))
2069 if (afr.alignPanels != null)
2071 for (AlignmentPanel ap : afr.alignPanels)
2073 if (sequenceSetId == null
2074 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2082 viewp.add(afr.getViewport());
2086 if (viewp.size() > 0)
2088 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2095 * Explode the views in the given frame into separate AlignFrame
2099 public static void explodeViews(AlignFrame af)
2101 int size = af.alignPanels.size();
2107 // FIXME: ideally should use UI interface API
2108 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2109 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2110 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2111 for (int i = 0; i < size; i++)
2113 AlignmentPanel ap = af.alignPanels.get(i);
2115 AlignFrame newaf = new AlignFrame(ap);
2117 // transfer reference for existing feature settings to new alignFrame
2118 if (ap == af.alignPanel)
2120 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2122 newaf.featureSettings = viewFeatureSettings;
2124 newaf.setFeatureSettingsGeometry(fsBounds);
2128 * Restore the view's last exploded frame geometry if known. Multiple views from
2129 * one exploded frame share and restore the same (frame) position and size.
2131 Rectangle geometry = ap.av.getExplodedGeometry();
2132 if (geometry != null)
2134 newaf.setBounds(geometry);
2137 ap.av.setGatherViewsHere(false);
2139 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2140 AlignFrame.DEFAULT_HEIGHT);
2141 // and materialise a new feature settings dialog instance for the new
2143 // (closes the old as if 'OK' was pressed)
2144 if (ap == af.alignPanel && newaf.featureSettings != null
2145 && newaf.featureSettings.isOpen()
2146 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2148 newaf.showFeatureSettingsUI();
2152 af.featureSettings = null;
2153 af.alignPanels.clear();
2154 af.closeMenuItem_actionPerformed(true);
2159 * Gather expanded views (separate AlignFrame's) with the same sequence set
2160 * identifier back in to this frame as additional views, and close the
2161 * expanded views. Note the expanded frames may themselves have multiple
2162 * views. We take the lot.
2166 public void gatherViews(AlignFrame source)
2168 source.viewport.setGatherViewsHere(true);
2169 source.viewport.setExplodedGeometry(source.getBounds());
2170 JInternalFrame[] frames = desktop.getAllFrames();
2171 String viewId = source.viewport.getSequenceSetId();
2172 for (int t = 0; t < frames.length; t++)
2174 if (frames[t] instanceof AlignFrame && frames[t] != source)
2176 AlignFrame af = (AlignFrame) frames[t];
2177 boolean gatherThis = false;
2178 for (int a = 0; a < af.alignPanels.size(); a++)
2180 AlignmentPanel ap = af.alignPanels.get(a);
2181 if (viewId.equals(ap.av.getSequenceSetId()))
2184 ap.av.setGatherViewsHere(false);
2185 ap.av.setExplodedGeometry(af.getBounds());
2186 source.addAlignmentPanel(ap, false);
2192 if (af.featureSettings != null && af.featureSettings.isOpen())
2194 if (source.featureSettings == null)
2196 // preserve the feature settings geometry for this frame
2197 source.featureSettings = af.featureSettings;
2198 source.setFeatureSettingsGeometry(
2199 af.getFeatureSettingsGeometry());
2203 // close it and forget
2204 af.featureSettings.close();
2207 af.alignPanels.clear();
2208 af.closeMenuItem_actionPerformed(true);
2213 // refresh the feature setting UI for the source frame if it exists
2214 if (source.featureSettings != null && source.featureSettings.isOpen())
2216 source.showFeatureSettingsUI();
2221 public JInternalFrame[] getAllFrames()
2223 return desktop.getAllFrames();
2227 * Checks the given url to see if it gives a response indicating that the user
2228 * should be informed of a new questionnaire.
2232 public void checkForQuestionnaire(String url)
2234 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2235 // javax.swing.SwingUtilities.invokeLater(jvq);
2236 new Thread(jvq).start();
2239 public void checkURLLinks()
2241 // Thread off the URL link checker
2242 addDialogThread(new Runnable()
2247 if (Cache.getDefault("CHECKURLLINKS", true))
2249 // check what the actual links are - if it's just the default don't
2250 // bother with the warning
2251 List<String> links = Preferences.sequenceUrlLinks
2254 // only need to check links if there is one with a
2255 // SEQUENCE_ID which is not the default EMBL_EBI link
2256 ListIterator<String> li = links.listIterator();
2257 boolean check = false;
2258 List<JLabel> urls = new ArrayList<>();
2259 while (li.hasNext())
2261 String link = li.next();
2262 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2263 && !UrlConstants.isDefaultString(link))
2266 int barPos = link.indexOf("|");
2267 String urlMsg = barPos == -1 ? link
2268 : link.substring(0, barPos) + ": "
2269 + link.substring(barPos + 1);
2270 urls.add(new JLabel(urlMsg));
2278 // ask user to check in case URL links use old style tokens
2279 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2280 JPanel msgPanel = new JPanel();
2281 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2282 msgPanel.add(Box.createVerticalGlue());
2283 JLabel msg = new JLabel(MessageManager
2284 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2285 JLabel msg2 = new JLabel(MessageManager
2286 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2288 for (JLabel url : urls)
2294 final JCheckBox jcb = new JCheckBox(
2295 MessageManager.getString("label.do_not_display_again"));
2296 jcb.addActionListener(new ActionListener()
2299 public void actionPerformed(ActionEvent e)
2301 // update Cache settings for "don't show this again"
2302 boolean showWarningAgain = !jcb.isSelected();
2303 Cache.setProperty("CHECKURLLINKS",
2304 Boolean.valueOf(showWarningAgain).toString());
2309 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2311 .getString("label.SEQUENCE_ID_no_longer_used"),
2312 JvOptionPane.WARNING_MESSAGE);
2319 * Proxy class for JDesktopPane which optionally displays the current memory
2320 * usage and highlights the desktop area with a red bar if free memory runs
2325 public class MyDesktopPane extends JDesktopPane implements Runnable
2327 private static final float ONE_MB = 1048576f;
2329 boolean showMemoryUsage = false;
2333 java.text.NumberFormat df;
2335 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2338 public MyDesktopPane(boolean showMemoryUsage)
2340 showMemoryUsage(showMemoryUsage);
2343 public void showMemoryUsage(boolean showMemory)
2345 this.showMemoryUsage = showMemory;
2348 Thread worker = new Thread(this);
2354 public boolean isShowMemoryUsage()
2356 return showMemoryUsage;
2362 df = java.text.NumberFormat.getNumberInstance();
2363 df.setMaximumFractionDigits(2);
2364 runtime = Runtime.getRuntime();
2366 while (showMemoryUsage)
2370 maxMemory = runtime.maxMemory() / ONE_MB;
2371 allocatedMemory = runtime.totalMemory() / ONE_MB;
2372 freeMemory = runtime.freeMemory() / ONE_MB;
2373 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2375 percentUsage = (totalFreeMemory / maxMemory) * 100;
2377 // if (percentUsage < 20)
2379 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2381 // instance.set.setBorder(border1);
2384 // sleep after showing usage
2386 } catch (Exception ex)
2388 ex.printStackTrace();
2394 public void paintComponent(Graphics g)
2396 if (showMemoryUsage && g != null && df != null)
2398 if (percentUsage < 20)
2400 g.setColor(Color.red);
2402 FontMetrics fm = g.getFontMetrics();
2405 g.drawString(MessageManager.formatMessage("label.memory_stats",
2407 { df.format(totalFreeMemory), df.format(maxMemory),
2408 df.format(percentUsage) }),
2409 10, getHeight() - fm.getHeight());
2413 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2414 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2419 * Accessor method to quickly get all the AlignmentFrames loaded.
2421 * @return an array of AlignFrame, or null if none found
2423 public static AlignFrame[] getAlignFrames()
2425 if (Jalview.isHeadlessMode())
2427 // Desktop.desktop is null in headless mode
2428 return new AlignFrame[] { Jalview.currentAlignFrame };
2431 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2437 List<AlignFrame> avp = new ArrayList<>();
2439 for (int i = frames.length - 1; i > -1; i--)
2441 if (frames[i] instanceof AlignFrame)
2443 avp.add((AlignFrame) frames[i]);
2445 else if (frames[i] instanceof SplitFrame)
2448 * Also check for a split frame containing an AlignFrame
2450 GSplitFrame sf = (GSplitFrame) frames[i];
2451 if (sf.getTopFrame() instanceof AlignFrame)
2453 avp.add((AlignFrame) sf.getTopFrame());
2455 if (sf.getBottomFrame() instanceof AlignFrame)
2457 avp.add((AlignFrame) sf.getBottomFrame());
2461 if (avp.size() == 0)
2465 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2470 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2474 public GStructureViewer[] getJmols()
2476 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2482 List<GStructureViewer> avp = new ArrayList<>();
2484 for (int i = frames.length - 1; i > -1; i--)
2486 if (frames[i] instanceof AppJmol)
2488 GStructureViewer af = (GStructureViewer) frames[i];
2492 if (avp.size() == 0)
2496 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2501 * Add Groovy Support to Jalview
2504 public void groovyShell_actionPerformed()
2508 openGroovyConsole();
2509 } catch (Exception ex)
2511 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2512 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2514 MessageManager.getString("label.couldnt_create_groovy_shell"),
2515 MessageManager.getString("label.groovy_support_failed"),
2516 JvOptionPane.ERROR_MESSAGE);
2521 * Open the Groovy console
2523 void openGroovyConsole()
2525 if (groovyConsole == null)
2527 groovyConsole = new groovy.ui.Console();
2528 groovyConsole.setVariable("Jalview", this);
2529 groovyConsole.run();
2532 * We allow only one console at a time, so that AlignFrame menu option
2533 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2534 * enable 'Run script', when the console is opened, and the reverse when it is
2537 Window window = (Window) groovyConsole.getFrame();
2538 window.addWindowListener(new WindowAdapter()
2541 public void windowClosed(WindowEvent e)
2544 * rebind CMD-Q from Groovy Console to Jalview Quit
2547 enableExecuteGroovy(false);
2553 * show Groovy console window (after close and reopen)
2555 ((Window) groovyConsole.getFrame()).setVisible(true);
2558 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2559 * opening a second console
2561 enableExecuteGroovy(true);
2565 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2566 * binding when opened
2568 protected void addQuitHandler()
2571 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2573 .getKeyStroke(KeyEvent.VK_Q,
2574 jalview.util.ShortcutKeyMaskExWrapper
2575 .getMenuShortcutKeyMaskEx()),
2577 getRootPane().getActionMap().put("Quit", new AbstractAction()
2580 public void actionPerformed(ActionEvent e)
2588 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2591 * true if Groovy console is open
2593 public void enableExecuteGroovy(boolean enabled)
2596 * disable opening a second Groovy console (or re-enable when the console is
2599 groovyShell.setEnabled(!enabled);
2601 AlignFrame[] alignFrames = getAlignFrames();
2602 if (alignFrames != null)
2604 for (AlignFrame af : alignFrames)
2606 af.setGroovyEnabled(enabled);
2612 * Progress bars managed by the IProgressIndicator method.
2614 private Hashtable<Long, JPanel> progressBars;
2616 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2621 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2624 public void setProgressBar(String message, long id)
2626 if (progressBars == null)
2628 progressBars = new Hashtable<>();
2629 progressBarHandlers = new Hashtable<>();
2632 if (progressBars.get(Long.valueOf(id)) != null)
2634 JPanel panel = progressBars.remove(Long.valueOf(id));
2635 if (progressBarHandlers.contains(Long.valueOf(id)))
2637 progressBarHandlers.remove(Long.valueOf(id));
2639 removeProgressPanel(panel);
2643 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2650 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2651 * jalview.gui.IProgressIndicatorHandler)
2654 public void registerHandler(final long id,
2655 final IProgressIndicatorHandler handler)
2657 if (progressBarHandlers == null
2658 || !progressBars.containsKey(Long.valueOf(id)))
2660 throw new Error(MessageManager.getString(
2661 "error.call_setprogressbar_before_registering_handler"));
2663 progressBarHandlers.put(Long.valueOf(id), handler);
2664 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2665 if (handler.canCancel())
2667 JButton cancel = new JButton(
2668 MessageManager.getString("action.cancel"));
2669 final IProgressIndicator us = this;
2670 cancel.addActionListener(new ActionListener()
2674 public void actionPerformed(ActionEvent e)
2676 handler.cancelActivity(id);
2677 us.setProgressBar(MessageManager
2678 .formatMessage("label.cancelled_params", new Object[]
2679 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2683 progressPanel.add(cancel, BorderLayout.EAST);
2689 * @return true if any progress bars are still active
2692 public boolean operationInProgress()
2694 if (progressBars != null && progressBars.size() > 0)
2702 * This will return the first AlignFrame holding the given viewport instance.
2703 * It will break if there are more than one AlignFrames viewing a particular
2707 * @return alignFrame for viewport
2709 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2711 if (desktop != null)
2713 AlignmentPanel[] aps = getAlignmentPanels(
2714 viewport.getSequenceSetId());
2715 for (int panel = 0; aps != null && panel < aps.length; panel++)
2717 if (aps[panel] != null && aps[panel].av == viewport)
2719 return aps[panel].alignFrame;
2726 public VamsasApplication getVamsasApplication()
2728 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2734 * flag set if jalview GUI is being operated programmatically
2736 private boolean inBatchMode = false;
2739 * check if jalview GUI is being operated programmatically
2741 * @return inBatchMode
2743 public boolean isInBatchMode()
2749 * set flag if jalview GUI is being operated programmatically
2751 * @param inBatchMode
2753 public void setInBatchMode(boolean inBatchMode)
2755 this.inBatchMode = inBatchMode;
2759 * start service discovery and wait till it is done
2761 public void startServiceDiscovery()
2763 startServiceDiscovery(false);
2767 * start service discovery threads - blocking or non-blocking
2771 public void startServiceDiscovery(boolean blocking)
2773 startServiceDiscovery(blocking, false);
2777 * start service discovery threads
2780 * - false means call returns immediately
2781 * @param ignore_SHOW_JWS2_SERVICES_preference
2782 * - when true JABA services are discovered regardless of user's JWS2
2783 * discovery preference setting
2785 public void startServiceDiscovery(boolean blocking,
2786 boolean ignore_SHOW_JWS2_SERVICES_preference)
2788 boolean alive = true;
2789 Thread t0 = null, t1 = null, t2 = null;
2790 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2793 // todo: changesupport handlers need to be transferred
2794 if (discoverer == null)
2796 discoverer = new jalview.ws.jws1.Discoverer();
2797 // register PCS handler for desktop.
2798 discoverer.addPropertyChangeListener(changeSupport);
2800 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2801 // until we phase out completely
2802 (t0 = new Thread(discoverer)).start();
2805 if (ignore_SHOW_JWS2_SERVICES_preference
2806 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2808 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2809 .startDiscoverer(changeSupport);
2813 // TODO: do rest service discovery
2822 } catch (Exception e)
2825 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2826 || (t3 != null && t3.isAlive())
2827 || (t0 != null && t0.isAlive());
2833 * called to check if the service discovery process completed successfully.
2837 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2839 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2841 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2842 .getErrorMessages();
2845 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2847 if (serviceChangedDialog == null)
2849 // only run if we aren't already displaying one of these.
2850 addDialogThread(serviceChangedDialog = new Runnable()
2857 * JalviewDialog jd =new JalviewDialog() {
2859 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2861 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2863 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2865 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2867 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2868 * + " or mis-configured HTTP proxy settings.<br/>" +
2869 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2870 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2871 * true, true, "Web Service Configuration Problem", 450, 400);
2873 * jd.waitForInput();
2875 JvOptionPane.showConfirmDialog(Desktop.desktop,
2876 new JLabel("<html><table width=\"450\"><tr><td>"
2877 + ermsg + "</td></tr></table>"
2878 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2879 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2880 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2881 + " Tools->Preferences dialog box to change them.</p></html>"),
2882 "Web Service Configuration Problem",
2883 JvOptionPane.DEFAULT_OPTION,
2884 JvOptionPane.ERROR_MESSAGE);
2885 serviceChangedDialog = null;
2893 jalview.bin.Console.error(
2894 "Errors reported by JABA discovery service. Check web services preferences.\n"
2901 private Runnable serviceChangedDialog = null;
2904 * start a thread to open a URL in the configured browser. Pops up a warning
2905 * dialog to the user if there is an exception when calling out to the browser
2910 public static void showUrl(final String url)
2912 showUrl(url, Desktop.instance);
2916 * Like showUrl but allows progress handler to be specified
2920 * (null) or object implementing IProgressIndicator
2922 public static void showUrl(final String url,
2923 final IProgressIndicator progress)
2925 new Thread(new Runnable()
2932 if (progress != null)
2934 progress.setProgressBar(MessageManager
2935 .formatMessage("status.opening_params", new Object[]
2936 { url }), this.hashCode());
2938 jalview.util.BrowserLauncher.openURL(url);
2939 } catch (Exception ex)
2941 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2943 .getString("label.web_browser_not_found_unix"),
2944 MessageManager.getString("label.web_browser_not_found"),
2945 JvOptionPane.WARNING_MESSAGE);
2947 ex.printStackTrace();
2949 if (progress != null)
2951 progress.setProgressBar(null, this.hashCode());
2957 public static WsParamSetManager wsparamManager = null;
2959 public static ParamManager getUserParameterStore()
2961 if (wsparamManager == null)
2963 wsparamManager = new WsParamSetManager();
2965 return wsparamManager;
2969 * static hyperlink handler proxy method for use by Jalview's internal windows
2973 public static void hyperlinkUpdate(HyperlinkEvent e)
2975 if (e.getEventType() == EventType.ACTIVATED)
2980 url = e.getURL().toString();
2981 Desktop.showUrl(url);
2982 } catch (Exception x)
2987 .error("Couldn't handle string " + url + " as a URL.");
2989 // ignore any exceptions due to dud links.
2996 * single thread that handles display of dialogs to user.
2998 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3001 * flag indicating if dialogExecutor should try to acquire a permit
3003 private volatile boolean dialogPause = true;
3008 private java.util.concurrent.Semaphore block = new Semaphore(0);
3010 private static groovy.ui.Console groovyConsole;
3013 * add another dialog thread to the queue
3017 public void addDialogThread(final Runnable prompter)
3019 dialogExecutor.submit(new Runnable()
3029 } catch (InterruptedException x)
3033 if (instance == null)
3039 SwingUtilities.invokeAndWait(prompter);
3040 } catch (Exception q)
3042 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3049 public void startDialogQueue()
3051 // set the flag so we don't pause waiting for another permit and semaphore
3052 // the current task to begin
3053 dialogPause = false;
3058 * Outputs an image of the desktop to file in EPS format, after prompting the
3059 * user for choice of Text or Lineart character rendering (unless a preference
3060 * has been set). The file name is generated as
3063 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3067 protected void snapShotWindow_actionPerformed(ActionEvent e)
3069 // currently the menu option to do this is not shown
3072 int width = getWidth();
3073 int height = getHeight();
3075 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3076 ImageWriterI writer = new ImageWriterI()
3079 public void exportImage(Graphics g) throws Exception
3082 jalview.bin.Console.info("Successfully written snapshot to file "
3083 + of.getAbsolutePath());
3086 String title = "View of desktop";
3087 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3089 exporter.doExport(of, this, width, height, title);
3093 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3094 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3095 * and location last time the view was expanded (if any). However it does not
3096 * remember the split pane divider location - this is set to match the
3097 * 'exploding' frame.
3101 public void explodeViews(SplitFrame sf)
3103 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3104 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3105 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3107 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3109 int viewCount = topPanels.size();
3116 * Processing in reverse order works, forwards order leaves the first panels not
3117 * visible. I don't know why!
3119 for (int i = viewCount - 1; i >= 0; i--)
3122 * Make new top and bottom frames. These take over the respective AlignmentPanel
3123 * objects, including their AlignmentViewports, so the cdna/protein
3124 * relationships between the viewports is carried over to the new split frames.
3126 * explodedGeometry holds the (x, y) position of the previously exploded
3127 * SplitFrame, and the (width, height) of the AlignFrame component
3129 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3130 AlignFrame newTopFrame = new AlignFrame(topPanel);
3131 newTopFrame.setSize(oldTopFrame.getSize());
3132 newTopFrame.setVisible(true);
3133 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3134 .getExplodedGeometry();
3135 if (geometry != null)
3137 newTopFrame.setSize(geometry.getSize());
3140 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3141 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3142 newBottomFrame.setSize(oldBottomFrame.getSize());
3143 newBottomFrame.setVisible(true);
3144 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3145 .getExplodedGeometry();
3146 if (geometry != null)
3148 newBottomFrame.setSize(geometry.getSize());
3151 topPanel.av.setGatherViewsHere(false);
3152 bottomPanel.av.setGatherViewsHere(false);
3153 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3155 if (geometry != null)
3157 splitFrame.setLocation(geometry.getLocation());
3159 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3163 * Clear references to the panels (now relocated in the new SplitFrames) before
3164 * closing the old SplitFrame.
3167 bottomPanels.clear();
3172 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3173 * back into the given SplitFrame as additional views. Note that the gathered
3174 * frames may themselves have multiple views.
3178 public void gatherViews(GSplitFrame source)
3181 * special handling of explodedGeometry for a view within a SplitFrame: - it
3182 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3183 * height) of the AlignFrame component
3185 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3186 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3187 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3188 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3189 myBottomFrame.viewport
3190 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3191 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3192 myTopFrame.viewport.setGatherViewsHere(true);
3193 myBottomFrame.viewport.setGatherViewsHere(true);
3194 String topViewId = myTopFrame.viewport.getSequenceSetId();
3195 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3197 JInternalFrame[] frames = desktop.getAllFrames();
3198 for (JInternalFrame frame : frames)
3200 if (frame instanceof SplitFrame && frame != source)
3202 SplitFrame sf = (SplitFrame) frame;
3203 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3204 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3205 boolean gatherThis = false;
3206 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3208 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3209 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3210 if (topViewId.equals(topPanel.av.getSequenceSetId())
3211 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3214 topPanel.av.setGatherViewsHere(false);
3215 bottomPanel.av.setGatherViewsHere(false);
3216 topPanel.av.setExplodedGeometry(
3217 new Rectangle(sf.getLocation(), topFrame.getSize()));
3218 bottomPanel.av.setExplodedGeometry(
3219 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3220 myTopFrame.addAlignmentPanel(topPanel, false);
3221 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3227 topFrame.getAlignPanels().clear();
3228 bottomFrame.getAlignPanels().clear();
3235 * The dust settles...give focus to the tab we did this from.
3237 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3240 public static groovy.ui.Console getGroovyConsole()
3242 return groovyConsole;
3246 * handles the payload of a drag and drop event.
3248 * TODO refactor to desktop utilities class
3251 * - Data source strings extracted from the drop event
3253 * - protocol for each data source extracted from the drop event
3257 * - the payload from the drop event
3260 public static void transferFromDropTarget(List<Object> files,
3261 List<DataSourceType> protocols, DropTargetDropEvent evt,
3262 Transferable t) throws Exception
3265 DataFlavor uriListFlavor = new DataFlavor(
3266 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3269 urlFlavour = new DataFlavor(
3270 "application/x-java-url; class=java.net.URL");
3271 } catch (ClassNotFoundException cfe)
3273 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3277 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3282 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3283 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3284 // means url may be null.
3287 protocols.add(DataSourceType.URL);
3288 files.add(url.toString());
3289 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3290 + files.get(files.size() - 1));
3295 if (Platform.isAMacAndNotJS())
3298 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3301 } catch (Throwable ex)
3303 jalview.bin.Console.debug("URL drop handler failed.", ex);
3306 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3308 // Works on Windows and MacOSX
3309 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3310 for (Object file : (List) t
3311 .getTransferData(DataFlavor.javaFileListFlavor))
3314 protocols.add(DataSourceType.FILE);
3319 // Unix like behaviour
3320 boolean added = false;
3322 if (t.isDataFlavorSupported(uriListFlavor))
3324 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3325 // This is used by Unix drag system
3326 data = (String) t.getTransferData(uriListFlavor);
3330 // fallback to text: workaround - on OSX where there's a JVM bug
3332 .debug("standard URIListFlavor failed. Trying text");
3333 // try text fallback
3334 DataFlavor textDf = new DataFlavor(
3335 "text/plain;class=java.lang.String");
3336 if (t.isDataFlavorSupported(textDf))
3338 data = (String) t.getTransferData(textDf);
3341 jalview.bin.Console.debug("Plain text drop content returned "
3342 + (data == null ? "Null - failed" : data));
3347 while (protocols.size() < files.size())
3349 jalview.bin.Console.debug("Adding missing FILE protocol for "
3350 + files.get(protocols.size()));
3351 protocols.add(DataSourceType.FILE);
3353 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3354 data, "\r\n"); st.hasMoreTokens();)
3357 String s = st.nextToken();
3358 if (s.startsWith("#"))
3360 // the line is a comment (as per the RFC 2483)
3363 java.net.URI uri = new java.net.URI(s);
3364 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3366 protocols.add(DataSourceType.URL);
3367 files.add(uri.toString());
3371 // otherwise preserve old behaviour: catch all for file objects
3372 java.io.File file = new java.io.File(uri);
3373 protocols.add(DataSourceType.FILE);
3374 files.add(file.toString());
3379 if (jalview.bin.Console.isDebugEnabled())
3381 if (data == null || !added)
3384 if (t.getTransferDataFlavors() != null
3385 && t.getTransferDataFlavors().length > 0)
3387 jalview.bin.Console.debug(
3388 "Couldn't resolve drop data. Here are the supported flavors:");
3389 for (DataFlavor fl : t.getTransferDataFlavors())
3391 jalview.bin.Console.debug(
3392 "Supported transfer dataflavor: " + fl.toString());
3393 Object df = t.getTransferData(fl);
3396 jalview.bin.Console.debug("Retrieves: " + df);
3400 jalview.bin.Console.debug("Retrieved nothing");
3407 .debug("Couldn't resolve dataflavor for drop: "
3413 if (Platform.isWindowsAndNotJS())
3416 .debug("Scanning dropped content for Windows Link Files");
3418 // resolve any .lnk files in the file drop
3419 for (int f = 0; f < files.size(); f++)
3421 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3422 if (protocols.get(f).equals(DataSourceType.FILE)
3423 && (source.endsWith(".lnk") || source.endsWith(".url")
3424 || source.endsWith(".site")))
3428 Object obj = files.get(f);
3429 File lf = (obj instanceof File ? (File) obj
3430 : new File((String) obj));
3431 // process link file to get a URL
3432 jalview.bin.Console.debug("Found potential link file: " + lf);
3433 WindowsShortcut wscfile = new WindowsShortcut(lf);
3434 String fullname = wscfile.getRealFilename();
3435 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3436 files.set(f, fullname);
3437 jalview.bin.Console.debug("Parsed real filename " + fullname
3438 + " to extract protocol: " + protocols.get(f));
3439 } catch (Exception ex)
3441 jalview.bin.Console.error(
3442 "Couldn't parse " + files.get(f) + " as a link file.",
3451 * Sets the Preferences property for experimental features to True or False
3452 * depending on the state of the controlling menu item
3455 protected void showExperimental_actionPerformed(boolean selected)
3457 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3461 * Answers a (possibly empty) list of any structure viewer frames (currently
3462 * for either Jmol or Chimera) which are currently open. This may optionally
3463 * be restricted to viewers of a specified class, or viewers linked to a
3464 * specified alignment panel.
3467 * if not null, only return viewers linked to this panel
3468 * @param structureViewerClass
3469 * if not null, only return viewers of this class
3472 public List<StructureViewerBase> getStructureViewers(
3473 AlignmentPanel apanel,
3474 Class<? extends StructureViewerBase> structureViewerClass)
3476 List<StructureViewerBase> result = new ArrayList<>();
3477 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3479 for (JInternalFrame frame : frames)
3481 if (frame instanceof StructureViewerBase)
3483 if (structureViewerClass == null
3484 || structureViewerClass.isInstance(frame))
3487 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3489 result.add((StructureViewerBase) frame);
3497 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3499 private static boolean debugScaleMessageDone = false;
3501 public static void debugScaleMessage(Graphics g)
3503 if (debugScaleMessageDone)
3507 // output used by tests to check HiDPI scaling settings in action
3510 Graphics2D gg = (Graphics2D) g;
3513 AffineTransform t = gg.getTransform();
3514 double scaleX = t.getScaleX();
3515 double scaleY = t.getScaleY();
3516 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3517 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3518 debugScaleMessageDone = true;
3522 jalview.bin.Console.debug("Desktop graphics null");
3524 } catch (Exception e)
3526 jalview.bin.Console.debug(Cache.getStackTraceString(e));