2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.util.Locale;
24 import java.awt.BorderLayout;
25 import java.awt.Color;
26 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.datatransfer.Clipboard;
36 import java.awt.datatransfer.ClipboardOwner;
37 import java.awt.datatransfer.DataFlavor;
38 import java.awt.datatransfer.Transferable;
39 import java.awt.dnd.DnDConstants;
40 import java.awt.dnd.DropTargetDragEvent;
41 import java.awt.dnd.DropTargetDropEvent;
42 import java.awt.dnd.DropTargetEvent;
43 import java.awt.dnd.DropTargetListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.WindowAdapter;
51 import java.awt.event.WindowEvent;
52 import java.awt.geom.AffineTransform;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
56 import java.io.FileWriter;
57 import java.io.IOException;
58 import java.lang.reflect.Field;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashMap;
63 import java.util.Hashtable;
64 import java.util.List;
65 import java.util.ListIterator;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutionException;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Future;
71 import java.util.concurrent.FutureTask;
72 import java.util.concurrent.Semaphore;
74 import javax.swing.AbstractAction;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.Box;
78 import javax.swing.BoxLayout;
79 import javax.swing.DefaultDesktopManager;
80 import javax.swing.DesktopManager;
81 import javax.swing.InputMap;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JComboBox;
85 import javax.swing.JComponent;
86 import javax.swing.JDesktopPane;
87 import javax.swing.JInternalFrame;
88 import javax.swing.JLabel;
89 import javax.swing.JMenuItem;
90 import javax.swing.JPanel;
91 import javax.swing.JPopupMenu;
92 import javax.swing.JProgressBar;
93 import javax.swing.JTextField;
94 import javax.swing.KeyStroke;
95 import javax.swing.SwingUtilities;
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.StructureSelectionManagerProvider;
106 import jalview.bin.ApplicationSingletonProvider;
107 import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
108 import jalview.bin.Cache;
109 import jalview.bin.Jalview;
110 import jalview.gui.ImageExporter.ImageWriterI;
111 import jalview.io.BackupFiles;
112 import jalview.io.DataSourceType;
113 import jalview.io.FileFormat;
114 import jalview.io.FileFormatException;
115 import jalview.io.FileFormatI;
116 import jalview.io.FileFormats;
117 import jalview.io.FileLoader;
118 import jalview.io.FormatAdapter;
119 import jalview.io.IdentifyFile;
120 import jalview.io.JalviewFileChooser;
121 import jalview.io.JalviewFileView;
122 import jalview.jbgui.APQHandlers;
123 import jalview.jbgui.GDesktop;
124 import jalview.jbgui.GSplitFrame;
125 import jalview.jbgui.GStructureViewer;
126 import jalview.project.Jalview2XML;
127 import jalview.structure.StructureSelectionManager;
128 import jalview.urls.IdOrgSettings;
129 import jalview.util.BrowserLauncher;
130 import jalview.util.ChannelProperties;
131 import jalview.util.ImageMaker.TYPE;
132 import jalview.util.LaunchUtils;
133 import jalview.util.MessageManager;
134 import jalview.util.Platform;
135 import jalview.util.UrlConstants;
136 import jalview.viewmodel.AlignmentViewport;
137 import jalview.ws.WSDiscovererI;
138 import jalview.ws.params.ParamManager;
139 import jalview.ws.utils.UrlDownloadClient;
146 * @version $Revision: 1.155 $
148 @SuppressWarnings("serial")
149 public class Desktop extends GDesktop
150 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
151 StructureSelectionManagerProvider, ApplicationSingletonI
154 private static final String CITATION;
156 URL bg_logo_url = ChannelProperties.getImageURL("bg_logo." + String.valueOf(SplashScreen.logoSize));
157 URL uod_logo_url = ChannelProperties.getImageURL("uod_banner." + String.valueOf(SplashScreen.logoSize));
158 boolean logo = (bg_logo_url != null || uod_logo_url != null);
159 StringBuilder sb = new StringBuilder();
161 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
166 sb.append(bg_logo_url == null ? "" : "<img alt=\"Barton Group logo\" src=\"" + bg_logo_url.toString() + "\">");
167 sb.append(uod_logo_url == null ? ""
168 : " <img alt=\"University of Dundee shield\" src=\"" + uod_logo_url.toString() + "\">");
170 "<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>");
171 sb.append("<br><br>If you use Jalview, please cite:"
172 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
173 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
174 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
175 CITATION = sb.toString();
178 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
180 private static int DEFAULT_MIN_WIDTH = 300;
182 private static int DEFAULT_MIN_HEIGHT = 250;
184 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
186 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
188 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
190 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
192 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
194 @SuppressWarnings("deprecation")
195 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
197 public static boolean nosplash = false;
200 * news reader - null if it was never started.
202 private BlogReader jvnews = null;
204 private File projectFile;
208 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
211 public void addJalviewPropertyChangeListener(
212 PropertyChangeListener listener)
214 changeSupport.addJalviewPropertyChangeListener(listener);
218 * @param propertyName
220 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
221 * java.beans.PropertyChangeListener)
224 public void addJalviewPropertyChangeListener(String propertyName,
225 PropertyChangeListener listener)
227 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
231 * @param propertyName
233 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
234 * java.beans.PropertyChangeListener)
237 public void removeJalviewPropertyChangeListener(String propertyName,
238 PropertyChangeListener listener)
240 changeSupport.removeJalviewPropertyChangeListener(propertyName,
244 private MyDesktopPane desktopPane;
246 public static MyDesktopPane getDesktopPane()
248 Desktop desktop = getInstance();
249 return desktop == null ? null : desktop.desktopPane;
253 * Answers an 'application scope' singleton instance of this class. Separate
254 * SwingJS 'applets' running in the same browser page will each have a
255 * distinct instance of Desktop.
259 public static Desktop getInstance()
261 return Jalview.isHeadlessMode() ? null
262 : (Desktop) ApplicationSingletonProvider
263 .getInstance(Desktop.class);
266 public static StructureSelectionManager getStructureSelectionManager()
268 return StructureSelectionManager
269 .getStructureSelectionManager(getInstance());
272 int openFrameCount = 0;
274 final int xOffset = 30;
276 final int yOffset = 30;
278 public jalview.ws.jws1.Discoverer discoverer;
280 public Object[] jalviewClipboard;
282 public boolean internalCopy = false;
284 int fileLoadingCount = 0;
286 class MyDesktopManager implements DesktopManager
289 private DesktopManager delegate;
291 public MyDesktopManager(DesktopManager delegate)
293 this.delegate = delegate;
297 public void activateFrame(JInternalFrame f)
301 delegate.activateFrame(f);
302 } catch (NullPointerException npe)
304 Point p = getMousePosition();
305 showPasteMenu(p.x, p.y);
310 public void beginDraggingFrame(JComponent f)
312 delegate.beginDraggingFrame(f);
316 public void beginResizingFrame(JComponent f, int direction)
318 delegate.beginResizingFrame(f, direction);
322 public void closeFrame(JInternalFrame f)
324 delegate.closeFrame(f);
328 public void deactivateFrame(JInternalFrame f)
330 delegate.deactivateFrame(f);
334 public void deiconifyFrame(JInternalFrame f)
336 delegate.deiconifyFrame(f);
340 public void dragFrame(JComponent f, int newX, int newY)
346 delegate.dragFrame(f, newX, newY);
350 public void endDraggingFrame(JComponent f)
352 delegate.endDraggingFrame(f);
353 desktopPane.repaint();
357 public void endResizingFrame(JComponent f)
359 delegate.endResizingFrame(f);
360 desktopPane.repaint();
364 public void iconifyFrame(JInternalFrame f)
366 delegate.iconifyFrame(f);
370 public void maximizeFrame(JInternalFrame f)
372 delegate.maximizeFrame(f);
376 public void minimizeFrame(JInternalFrame f)
378 delegate.minimizeFrame(f);
382 public void openFrame(JInternalFrame f)
384 delegate.openFrame(f);
388 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
395 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
399 public void setBoundsForFrame(JComponent f, int newX, int newY,
400 int newWidth, int newHeight)
402 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
405 // All other methods, simply delegate
410 * Private constructor enforces singleton pattern. It is called by reflection
411 * from ApplicationSingletonProvider.getInstance().
419 * A note to implementors. It is ESSENTIAL that any activities that might
420 * block are spawned off as threads rather than waited for during this
424 doConfigureStructurePrefs();
425 setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
428 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
429 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
430 * documented or guaranteed to exist, so we access it via reflection. There
431 * appear to be unfathomable criteria about what this string can contain, and it
432 * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
433 * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
434 * but "Jalview non-release" does not. The reflection access may generate a
435 * warning: WARNING: An illegal reflective access operation has occurred
436 * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
437 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
439 if (Platform.isLinux())
441 if (LaunchUtils.getJavaVersion() >= 11)
443 jalview.bin.Console.info(
444 "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.");
448 Toolkit xToolkit = Toolkit.getDefaultToolkit();
449 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
450 Field awtAppClassNameField = null;
452 if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
453 awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
456 String title = ChannelProperties.getProperty("app_name");
457 if (awtAppClassNameField != null) {
458 awtAppClassNameField.setAccessible(true);
459 awtAppClassNameField.set(xToolkit, title);
463 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
465 } catch (Exception e)
467 jalview.bin.Console.debug("Error setting awtAppClassName");
468 jalview.bin.Console.trace(Cache.getStackTraceString(e));
473 * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
474 * macOS's application menu. APQHandlers will check to see if a handler is
475 * supported before setting it.
478 APQHandlers.setAPQHandlers(this);
479 } catch (Exception e) {
480 System.out.println("Cannot set APQHandlers");
481 // e.printStackTrace();
482 } catch (Throwable t) {
483 jalview.bin.Console.warn("Error setting APQHandlers: " + t.toString());
484 jalview.bin.Console.trace(Cache.getStackTraceString(t));
487 setIconImages(ChannelProperties.getIconList());
489 addWindowListener(new WindowAdapter() {
492 public void windowClosing(WindowEvent ev) {
497 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
499 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
500 desktopPane = new MyDesktopPane(selmemusage);
502 showMemusage.setSelected(selmemusage);
503 desktopPane.setBackground(Color.white);
505 getContentPane().setLayout(new BorderLayout());
506 // alternate config - have scrollbars - see notes in JAL-153
507 // JScrollPane sp = new JScrollPane();
508 // sp.getViewport().setView(desktop);
509 // getContentPane().add(sp, BorderLayout.CENTER);
511 // BH 2018 - just an experiment to try unclipped JInternalFrames.
514 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
517 getContentPane().add(desktopPane, BorderLayout.CENTER);
518 desktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
521 // This line prevents Windows Look&Feel resizing all new windows to
523 // if previous window was maximised
524 desktopPane.setDesktopManager(new MyDesktopManager(
525 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
526 : Platform.isAMacAndNotJS()
527 ? new AquaInternalFrameManager(
528 desktopPane.getDesktopManager())
529 : desktopPane.getDesktopManager())));
531 Rectangle dims = getLastKnownDimensions("");
538 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
539 int xPos = Math.max(5, (screenSize.width - 900) / 2);
540 int yPos = Math.max(5, (screenSize.height - 650) / 2);
541 setBounds(xPos, yPos, 900, 650);
544 if (!Platform.isJS())
551 jconsole = new Console(this, showjconsole);
552 jconsole.setHeader(Cache.getVersionDetailsForConsole());
553 showConsole(showjconsole);
555 showNews.setVisible(false); // not sure if we should only do this for interactive session?
557 experimentalFeatures.setSelected(showExperimental());
559 getIdentifiersOrgData();
561 if (Jalview.isInteractive())
563 // disabled for SeqCanvasTest
566 // Spawn a thread that shows the splashscreen
568 SwingUtilities.invokeLater(new Runnable()
573 new SplashScreen(true);
578 // Thread off a new instance of the file chooser - this reduces the
581 // takes to open it later on.
582 new Thread(new Runnable()
587 jalview.bin.Console.debug("Filechooser init thread started.");
588 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
589 JalviewFileChooser.forRead(
590 Cache.getProperty("LAST_DIRECTORY"), fileFormat);
591 jalview.bin.Console.debug("Filechooser init thread finished.");
594 // Add the service change listener
595 changeSupport.addJalviewPropertyChangeListener("services",
596 new PropertyChangeListener()
600 public void propertyChange(PropertyChangeEvent evt)
602 jalview.bin.Console.debug("Firing service changed event for "
603 + evt.getNewValue());
604 JalviewServicesChanged(evt);
610 this.setDropTarget(new java.awt.dnd.DropTarget(desktopPane, this));
612 this.addWindowListener(new WindowAdapter()
615 public void windowClosing(WindowEvent evt)
622 this.addMouseListener(ma = new MouseAdapter()
625 public void mousePressed(MouseEvent evt)
627 if (evt.isPopupTrigger()) // Mac
629 showPasteMenu(evt.getX(), evt.getY());
633 public void mouseReleased(MouseEvent evt)
635 if (evt.isPopupTrigger()) // Windows
637 showPasteMenu(evt.getX(), evt.getY());
641 desktopPane.addMouseListener(ma);
642 } catch (Throwable t)
649 * Answers true if user preferences to enable experimental features is True
654 public boolean showExperimental()
656 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
657 Boolean.FALSE.toString());
658 return Boolean.valueOf(experimental).booleanValue();
661 public void doConfigureStructurePrefs()
663 // configure services
664 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
665 if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) {
666 ssm.setAddTempFacAnnot(Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
667 ssm.setProcessSecondaryStructure(Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
668 // JAL-3915 - RNAView is no longer an option so this has no effect
669 ssm.setSecStructServices(Cache.getDefault(Preferences.USE_RNAVIEW, false));
671 ssm.setAddTempFacAnnot(false);
672 ssm.setProcessSecondaryStructure(false);
673 ssm.setSecStructServices(false);
677 public void checkForNews() {
678 final Desktop me = this;
679 // Thread off the news reader, in case there are connection problems.
680 new Thread(new Runnable() {
684 jalview.bin.Console.debug("Starting news thread.");
685 jvnews = new BlogReader(me);
686 showNews.setVisible(true);
687 jalview.bin.Console.debug("Completed news thread.");
692 public void getIdentifiersOrgData() {
693 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
694 // Thread off the identifiers fetcher
695 new Thread(new Runnable() {
699 jalview.bin.Console.debug("Downloading data from identifiers.org");
702 UrlDownloadClient.download(IdOrgSettings.getUrl(),
703 IdOrgSettings.getDownloadLocation());
704 } catch (IOException e)
706 jalview.bin.Console.debug("Exception downloading identifiers.org data"
715 protected void showNews_actionPerformed(ActionEvent e) {
716 showNews(showNews.isSelected());
719 void showNews(boolean visible)
721 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
722 showNews.setSelected(visible);
723 if (visible && !jvnews.isVisible())
725 new Thread(new Runnable()
730 long now = System.currentTimeMillis();
731 setProgressBar(MessageManager.getString("status.refreshing_news"),
733 jvnews.refreshNews();
734 setProgressBar(null, now);
742 * recover the last known dimensions for a jalview window
745 * - empty string is desktop, all other windows have unique prefix
746 * @return null or last known dimensions scaled to current geometry (if last
747 * window geom was known)
749 Rectangle getLastKnownDimensions(String windowName)
751 // TODO: lock aspect ratio for scaling desktop Bug #0058199
752 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
753 String x = Cache.getProperty(windowName + "SCREEN_X");
754 String y = Cache.getProperty(windowName + "SCREEN_Y");
755 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
756 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
757 if ((x != null) && (y != null) && (width != null) && (height != null)) {
758 int ix = Integer.parseInt(x), iy = Integer.parseInt(y), iw = Integer.parseInt(width),
759 ih = Integer.parseInt(height);
760 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) {
761 // attempt #1 - try to cope with change in screen geometry - this
762 // version doesn't preserve original jv aspect ratio.
763 // take ratio of current screen size vs original screen size.
764 double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
765 double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
766 // rescale the bounds depending upon the current screen geometry.
767 ix = (int) (ix * sw);
768 iw = (int) (iw * sw);
769 iy = (int) (iy * sh);
770 ih = (int) (ih * sh);
771 while (ix >= screenSize.width)
773 jalview.bin.Console.debug(
774 "Window geometry location recall error: shifting horizontal to within screenbounds.");
775 ix -= screenSize.width;
777 while (iy >= screenSize.height)
779 jalview.bin.Console.debug(
780 "Window geometry location recall error: shifting vertical to within screenbounds.");
781 iy -= screenSize.height;
783 jalview.bin.Console.debug(
784 "Got last known dimensions for " + windowName + ": x:" + ix
785 + " y:" + iy + " width:" + iw + " height:" + ih);
787 // return dimensions for new instance
788 return new Rectangle(ix, iy, iw, ih);
793 void showPasteMenu(int x, int y)
795 JPopupMenu popup = new JPopupMenu();
796 JMenuItem item = new JMenuItem(
797 MessageManager.getString("label.paste_new_window"));
798 item.addActionListener(new ActionListener()
801 public void actionPerformed(ActionEvent evt)
808 popup.show(this, x, y);
815 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
816 Transferable contents = c.getContents(this);
818 if (contents != null)
820 String file = (String) contents
821 .getTransferData(DataFlavor.stringFlavor);
823 FileFormatI format = new IdentifyFile().identify(file,
824 DataSourceType.PASTE);
826 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
829 } catch (Exception ex)
832 "Unable to paste alignment from system clipboard:\n" + ex);
839 * Adds and opens the given frame to the desktop that is visible, allowed to
840 * resize, and has a 300px minimum width.
851 public static synchronized void addInternalFrame(
852 final JInternalFrame frame, String title, int w, int h)
856 addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, w, h,
857 FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
861 * Add an internal frame to the Jalview desktop that may optionally be
862 * visible, resizable, and allowed to be any size
869 * When true, display frame immediately, otherwise, caller must call
870 * setVisible themselves.
877 * @param ignoreMinSize
878 * Do not set the default minimum size for frame
880 public static synchronized void addInternalFrame(
881 final JInternalFrame frame, String title, boolean makeVisible,
882 int w, int h, boolean resizable, boolean ignoreMinSize)
884 // 15 classes call this method directly.
887 // TODO: allow callers to determine X and Y position of frame (eg. via
889 // TODO: consider fixing method to update entries in the window submenu with
890 // the current window title
892 frame.setTitle(title);
893 if (frame.getWidth() < 1 || frame.getHeight() < 1)
897 if (getInstance() != null)
898 getInstance().addFrame(frame, makeVisible, resizable,
902 // These can now by put into a single int flag, if desired:
904 public final static boolean FRAME_MAKE_VISIBLE = true;
906 public final static boolean FRAME_NOT_VISIBLE = false;
908 public final static boolean FRAME_ALLOW_RESIZE = true;
910 public final static boolean FRAME_NOT_RESIZABLE = false;
912 public final static boolean FRAME_ALLOW_ANY_SIZE = true;
914 public final static boolean FRAME_SET_MIN_SIZE_300 = false;
916 private void addFrame(JInternalFrame frame,
917 boolean makeVisible, boolean resizable,
918 boolean ignoreMinSize)
924 boolean isEmbedded = (Platform.getEmbeddedAttribute(frame, "id") != null);
925 boolean hasEmbeddedSize = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
926 // Web page embedding allows us to ignore minimum size
927 ignoreMinSize |= hasEmbeddedSize;
932 // Set default dimension for Alignment Frame window.
933 // The Alignment Frame window could be added from a number of places,
935 // I did this here in order not to miss out on any Alignment frame.
936 if (frame instanceof AlignFrame)
938 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
939 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
941 frame.setMinimumSize(
942 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
947 frame.setVisible(makeVisible);
948 frame.setClosable(true);
949 frame.setResizable(resizable);
950 frame.setMaximizable(resizable);
951 frame.setIconifiable(resizable);
952 frame.setOpaque(Platform.isJS());
954 if (!isEmbedded && frame.getX() < 1 && frame.getY() < 1)
956 frame.setLocation(xOffset * openFrameCount,
957 yOffset * ((openFrameCount - 1) % 10) + yOffset);
961 * add an entry for the new frame in the Window menu
962 * (and remove it when the frame is closed)
964 final JMenuItem menuItem = new JMenuItem(frame.getTitle());
965 frame.addInternalFrameListener(new InternalFrameAdapter()
968 public void internalFrameActivated(InternalFrameEvent evt)
970 JInternalFrame itf = getDesktopPane().getSelectedFrame();
973 if (itf instanceof AlignFrame)
975 Jalview.setCurrentAlignFrame((AlignFrame) itf);
982 public void internalFrameClosed(InternalFrameEvent evt)
984 PaintRefresher.RemoveComponent(frame);
987 * defensive check to prevent frames being
988 * 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 getInstance().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)
1017 // System.err.println(ex.toString());
1023 setKeyBindings(frame);
1025 getDesktopPane().add(frame);
1027 getInstance().windowMenu.add(menuItem);
1032 frame.setSelected(true);
1033 frame.requestFocus();
1034 } catch (java.beans.PropertyVetoException ve)
1036 } catch (java.lang.ClassCastException cex)
1038 jalview.bin.Console.warn(
1039 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1045 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1050 private static void setKeyBindings(JInternalFrame frame)
1052 @SuppressWarnings("serial")
1053 final Action closeAction = new AbstractAction()
1056 public void actionPerformed(ActionEvent e)
1063 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1065 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
1066 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, Platform.SHORTCUT_KEY_MASK);
1068 InputMap inputMap = frame
1069 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1070 String ctrlW = ctrlWKey.toString();
1071 inputMap.put(ctrlWKey, ctrlW);
1072 inputMap.put(cmdWKey, ctrlW);
1074 ActionMap actionMap = frame.getActionMap();
1075 actionMap.put(ctrlW, closeAction);
1079 public void lostOwnership(Clipboard clipboard, Transferable contents)
1083 jalviewClipboard = null;
1086 internalCopy = false;
1090 public void dragEnter(DropTargetDragEvent evt)
1095 public void dragExit(DropTargetEvent evt)
1100 public void dragOver(DropTargetDragEvent evt)
1105 public void dropActionChanged(DropTargetDragEvent evt)
1116 public void drop(DropTargetDropEvent evt)
1118 boolean success = true;
1119 // JAL-1552 - acceptDrop required before getTransferable call for
1120 // Java's Transferable for native dnd
1121 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1122 Transferable t = evt.getTransferable();
1123 List<Object> files = new ArrayList<>();
1124 List<DataSourceType> protocols = new ArrayList<>();
1128 transferFromDropTarget(files, protocols, evt, t);
1129 } catch (Exception e)
1131 e.printStackTrace();
1139 for (int i = 0; i < files.size(); i++)
1141 // BH 2018 File or String
1142 Object file = files.get(i);
1143 String fileName = file.toString();
1144 DataSourceType protocol = (protocols == null)
1145 ? DataSourceType.FILE
1147 FileFormatI format = null;
1149 if (fileName.endsWith(".jar"))
1151 format = FileFormat.Jalview;
1156 format = new IdentifyFile().identify(file, protocol);
1158 if (file instanceof File)
1160 Platform.cacheFileData((File) file);
1162 new FileLoader().LoadFile(null, file, protocol, format);
1165 } catch (Exception ex)
1170 evt.dropComplete(success); // need this to ensure input focus is properly
1171 // transfered to any new windows created
1181 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1183 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1184 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1185 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1186 BackupFiles.getEnabled());
1188 chooser.setFileView(new JalviewFileView());
1189 chooser.setDialogTitle(
1190 MessageManager.getString("label.open_local_file"));
1191 chooser.setToolTipText(MessageManager.getString("action.open"));
1193 chooser.setResponseHandler(0, new Runnable()
1198 File selectedFile = chooser.getSelectedFile();
1199 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1201 FileFormatI format = chooser.getSelectedFormat();
1204 * Call IdentifyFile to verify the file contains what its extension implies.
1205 * Skip this step for dynamically added file formats, because
1206 * IdentifyFile does not know how to recognise them.
1208 if (FileFormats.getInstance().isIdentifiable(format))
1212 format = new IdentifyFile().identify(selectedFile,
1213 DataSourceType.FILE);
1214 } catch (FileFormatException e)
1216 // format = null; //??
1220 new FileLoader().LoadFile(viewport, selectedFile,
1221 DataSourceType.FILE, format);
1224 chooser.showOpenDialog(this);
1228 * Shows a dialog for input of a URL at which to retrieve alignment data
1233 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1235 // This construct allows us to have a wider textfield
1237 JLabel label = new JLabel(
1238 MessageManager.getString("label.input_file_url"));
1240 JPanel panel = new JPanel(new GridLayout(2, 1));
1244 * the URL to fetch is
1245 * Java: an editable combobox with history
1246 * JS: (pending JAL-3038) a plain text field
1249 String urlBase = "https://www.";
1250 if (Platform.isJS())
1252 history = new JTextField(urlBase, 35);
1261 JComboBox<String> asCombo = new JComboBox<>();
1262 asCombo.setPreferredSize(new Dimension(400, 20));
1263 asCombo.setEditable(true);
1264 asCombo.addItem(urlBase);
1265 String historyItems = Cache.getProperty("RECENT_URL");
1266 if (historyItems != null)
1268 for (String token : historyItems.split("\\t"))
1270 asCombo.addItem(token);
1277 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1278 MessageManager.getString("action.cancel") };
1279 Runnable action = new Runnable()
1284 @SuppressWarnings("unchecked")
1285 String url = (history instanceof JTextField ? ((JTextField) history).getText()
1286 : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
1288 if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) {
1289 if (viewport != null) {
1290 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
1292 new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
1295 FileFormatI format = null;
1298 format = new IdentifyFile().identify(url, DataSourceType.URL);
1299 } catch (FileFormatException e)
1301 // TODO revise error handling, distinguish between
1302 // URL not found and response not valid
1307 String msg = MessageManager
1308 .formatMessage("label.couldnt_locate", url);
1309 JvOptionPane.showInternalMessageDialog(getDesktopPane(), msg,
1310 MessageManager.getString("label.url_not_found"),
1311 JvOptionPane.WARNING_MESSAGE);
1316 if (viewport != null)
1318 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1323 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1328 String dialogOption = MessageManager
1329 .getString("label.input_alignment_from_url");
1330 JvOptionPane.newOptionDialog(desktopPane).setResponseHandler(0, action)
1331 .showInternalDialog(panel, dialogOption,
1332 JvOptionPane.YES_NO_CANCEL_OPTION,
1333 JvOptionPane.PLAIN_MESSAGE, null, options,
1334 MessageManager.getString("action.ok"));
1338 * Opens the CutAndPaste window for the user to paste an alignment in to
1341 * - if not null, the pasted alignment is added to the current
1342 * alignment; if null, to a new alignment window
1345 public void inputTextboxMenuItem_actionPerformed(
1346 AlignmentViewPanel viewPanel)
1348 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1349 cap.setForInput(viewPanel);
1350 addInternalFrame(cap,
1351 MessageManager.getString("label.cut_paste_alignmen_file"),
1352 FRAME_MAKE_VISIBLE, 600, 500, FRAME_ALLOW_RESIZE,
1353 FRAME_SET_MIN_SIZE_300);
1362 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1363 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1364 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1365 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1366 getWidth(), getHeight()));
1368 if (jconsole != null)
1370 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1371 jconsole.stopConsole();
1375 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1378 if (dialogExecutor != null)
1380 dialogExecutor.shutdownNow();
1382 closeAll_actionPerformed(null);
1384 if (groovyConsole != null)
1386 // suppress a possible repeat prompt to save script
1387 groovyConsole.setDirty(false);
1388 groovyConsole.exit();
1393 private void storeLastKnownDimensions(String string, Rectangle jc)
1395 jalview.bin.Console.debug("Storing last known dimensions for " + string
1396 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1397 + " height:" + jc.height);
1399 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1400 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1401 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1402 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1412 public void aboutMenuItem_actionPerformed(ActionEvent e)
1414 new Thread(new Runnable()
1419 new SplashScreen(false);
1425 * Returns the html text for the About screen, including any available version
1426 * number, build details, author details and citation reference, but without
1427 * the enclosing {@code html} tags
1431 public String getAboutMessage()
1433 StringBuilder message = new StringBuilder(1024);
1434 message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
1435 .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
1436 .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
1437 .append(Cache.getBuildDetailsForSplash()).append("</strong>");
1439 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1440 if (latestVersion.equals("Checking")) {
1441 // JBP removed this message for 2.11: May be reinstated in future version
1442 // message.append("<br>...Checking latest version...</br>");
1443 } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
1444 boolean red = false;
1445 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT).indexOf("automated build") == -1) {
1447 // Displayed when code version and jnlp version do not match and code
1448 // version is not a development build
1449 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1452 message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1453 .append(" is available for download from ")
1454 .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
1456 message.append("</div>");
1459 message.append("<br>Authors: ");
1460 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1461 message.append(CITATION);
1463 message.append("</div>");
1465 return message.toString();
1469 * Action on requesting Help documentation
1472 public void documentationMenuItem_actionPerformed() {
1474 if (Platform.isJS()) {
1475 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1483 Help.showHelpWindow();
1485 } catch (Exception ex) {
1486 System.err.println("Error opening help: " + ex.getMessage());
1491 public void closeAll_actionPerformed(ActionEvent e)
1493 // TODO show a progress bar while closing?
1494 JInternalFrame[] frames = desktopPane.getAllFrames();
1495 for (int i = 0; i < frames.length; i++)
1499 frames[i].setClosed(true);
1500 } catch (java.beans.PropertyVetoException ex)
1504 Jalview.setCurrentAlignFrame(null);
1505 System.out.println("ALL CLOSED");
1508 * reset state of singleton objects as appropriate (clear down session state
1509 * when all windows are closed)
1511 StructureSelectionManager ssm = StructureSelectionManager
1512 .getStructureSelectionManager(this);
1520 public void raiseRelated_actionPerformed(ActionEvent e)
1522 reorderAssociatedWindows(false, false);
1526 public void minimizeAssociated_actionPerformed(ActionEvent e)
1528 reorderAssociatedWindows(true, false);
1531 void closeAssociatedWindows()
1533 reorderAssociatedWindows(false, true);
1539 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1543 protected void garbageCollect_actionPerformed(ActionEvent e)
1545 // We simply collect the garbage
1546 jalview.bin.Console.debug("Collecting garbage...");
1548 jalview.bin.Console.debug("Finished garbage collection.");
1555 * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1559 protected void showMemusage_actionPerformed(ActionEvent e)
1561 desktopPane.showMemoryUsage(showMemusage.isSelected());
1568 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1572 protected void showConsole_actionPerformed(ActionEvent e)
1574 showConsole(showConsole.isSelected());
1577 Console jconsole = null;
1580 * control whether the java console is visible or not
1584 void showConsole(boolean selected)
1586 // TODO: decide if we should update properties file
1587 if (jconsole != null) // BH 2018
1589 showConsole.setSelected(selected);
1590 Cache.setProperty("SHOW_JAVA_CONSOLE",
1591 Boolean.valueOf(selected).toString());
1592 jconsole.setVisible(selected);
1596 void reorderAssociatedWindows(boolean minimize, boolean close)
1598 JInternalFrame[] frames = desktopPane.getAllFrames();
1599 if (frames == null || frames.length < 1)
1604 AlignViewportI source = null;
1605 AlignViewportI target = null;
1606 if (frames[0] instanceof AlignFrame)
1608 source = ((AlignFrame) frames[0]).getCurrentView();
1610 else if (frames[0] instanceof TreePanel)
1612 source = ((TreePanel) frames[0]).getViewPort();
1614 else if (frames[0] instanceof PCAPanel)
1616 source = ((PCAPanel) frames[0]).av;
1618 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1620 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1625 for (int i = 0; i < frames.length; i++)
1628 if (frames[i] == null)
1632 if (frames[i] instanceof AlignFrame)
1634 target = ((AlignFrame) frames[i]).getCurrentView();
1636 else if (frames[i] instanceof TreePanel)
1638 target = ((TreePanel) frames[i]).getViewPort();
1640 else if (frames[i] instanceof PCAPanel)
1642 target = ((PCAPanel) frames[i]).av;
1644 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1646 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1649 if (source == target)
1655 frames[i].setClosed(true);
1659 frames[i].setIcon(minimize);
1662 frames[i].toFront();
1666 } catch (java.beans.PropertyVetoException ex)
1681 protected void preferences_actionPerformed(ActionEvent e) {
1682 Preferences.openPreferences();
1686 * Prompts the user to choose a file and then saves the Jalview state as a
1687 * Jalview project file
1690 public void saveState_actionPerformed()
1692 saveState_actionPerformed(false);
1695 public void saveState_actionPerformed(boolean saveAs)
1697 java.io.File projectFile = getProjectFile();
1698 // autoSave indicates we already have a file and don't need to ask
1699 boolean autoSave = projectFile != null && !saveAs
1700 && BackupFiles.getEnabled();
1702 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1703 // saveAs="+saveAs+", Backups
1704 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1706 boolean approveSave = false;
1709 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1712 chooser.setFileView(new JalviewFileView());
1713 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1715 int value = chooser.showSaveDialog(this);
1717 if (value == JalviewFileChooser.APPROVE_OPTION)
1719 projectFile = chooser.getSelectedFile();
1720 setProjectFile(projectFile);
1725 if (approveSave || autoSave) {
1726 final Desktop me = this;
1727 final java.io.File chosenFile = projectFile;
1728 new Thread(new Runnable()
1733 // TODO: refactor to Jalview desktop session controller action.
1734 setProgressBar(MessageManager.formatMessage(
1735 "label.saving_jalview_project", new Object[]
1736 { chosenFile.getName() }), chosenFile.hashCode());
1737 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1738 // TODO catch and handle errors for savestate
1739 // TODO prevent user from messing with the Desktop whilst we're saving
1742 boolean doBackup = BackupFiles.getEnabled();
1743 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1746 new Jalview2XML().saveState(
1747 doBackup ? backupfiles.getTempFile() : chosenFile);
1751 backupfiles.setWriteSuccess(true);
1752 backupfiles.rollBackupsAndRenameTempFile();
1754 } catch (OutOfMemoryError oom)
1756 new OOMWarning("Whilst saving current state to "
1757 + chosenFile.getName(), oom);
1758 } catch (Exception ex)
1760 jalview.bin.Console.error("Problems whilst trying to save to "
1761 + chosenFile.getName(), ex);
1762 JvOptionPane.showMessageDialog(me,
1763 MessageManager.formatMessage(
1764 "label.error_whilst_saving_current_state_to",
1766 { chosenFile.getName() }),
1767 MessageManager.getString("label.couldnt_save_project"),
1768 JvOptionPane.WARNING_MESSAGE);
1770 setProgressBar(null, chosenFile.hashCode());
1777 public void saveAsState_actionPerformed(ActionEvent e)
1779 saveState_actionPerformed(true);
1782 private void setProjectFile(File choice)
1784 this.projectFile = choice;
1787 public File getProjectFile()
1789 return this.projectFile;
1793 * Shows a file chooser dialog and tries to read in the selected file as a
1797 public void loadState_actionPerformed()
1799 final String[] suffix = new String[] { "jvp", "jar" };
1800 final String[] desc = new String[] { "Jalview Project",
1801 "Jalview Project (old)" };
1802 JalviewFileChooser chooser = new JalviewFileChooser(
1803 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1804 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1808 chooser.setFileView(new JalviewFileView());
1809 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1810 chooser.setResponseHandler(0, new Runnable()
1815 File selectedFile = chooser.getSelectedFile();
1816 setProjectFile(selectedFile);
1817 String choice = selectedFile.getAbsolutePath();
1818 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1819 new Thread(new Runnable()
1826 new Jalview2XML().loadJalviewAlign(selectedFile);
1827 } catch (OutOfMemoryError oom)
1829 new OOMWarning("Whilst loading project from " + choice, oom);
1830 } catch (Exception ex)
1832 jalview.bin.Console.error(
1833 "Problems whilst loading project from " + choice, ex);
1834 JvOptionPane.showMessageDialog(getDesktopPane(),
1835 MessageManager.formatMessage(
1836 "label.error_whilst_loading_project_from",
1840 .getString("label.couldnt_load_project"),
1841 JvOptionPane.WARNING_MESSAGE);
1844 }, "Project Loader").start();
1848 chooser.showOpenDialog(this);
1852 public void inputSequence_actionPerformed(ActionEvent e)
1854 new SequenceFetcher(this);
1857 JPanel progressPanel;
1859 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1861 public void startLoading(final Object fileName)
1863 if (fileLoadingCount == 0)
1865 fileLoadingPanels.add(addProgressPanel(MessageManager
1866 .formatMessage("label.loading_file", new Object[]
1872 private JPanel addProgressPanel(String string)
1874 if (progressPanel == null)
1876 progressPanel = new JPanel(new GridLayout(1, 1));
1877 totalProgressCount = 0;
1878 getContentPane().add(progressPanel, BorderLayout.SOUTH);
1880 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1881 JProgressBar progressBar = new JProgressBar();
1882 progressBar.setIndeterminate(true);
1884 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1886 thisprogress.add(progressBar, BorderLayout.CENTER);
1887 progressPanel.add(thisprogress);
1888 ((GridLayout) progressPanel.getLayout()).setRows(
1889 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1890 ++totalProgressCount;
1892 return thisprogress;
1895 int totalProgressCount = 0;
1897 private void removeProgressPanel(JPanel progbar)
1899 if (progressPanel != null)
1901 synchronized (progressPanel)
1903 progressPanel.remove(progbar);
1904 GridLayout gl = (GridLayout) progressPanel.getLayout();
1905 gl.setRows(gl.getRows() - 1);
1906 if (--totalProgressCount < 1)
1908 this.getContentPane().remove(progressPanel);
1909 progressPanel = null;
1916 public void stopLoading()
1919 if (fileLoadingCount < 1)
1921 while (fileLoadingPanels.size() > 0)
1923 removeProgressPanel(fileLoadingPanels.remove(0));
1925 fileLoadingPanels.clear();
1926 fileLoadingCount = 0;
1931 public static int getViewCount(String alignmentId)
1933 AlignmentViewport[] aps = getViewports(alignmentId);
1934 return (aps == null) ? 0 : aps.length;
1939 * @param alignmentId
1940 * - if null, all sets are returned
1941 * @return all AlignmentPanels concerning the alignmentId sequence set
1943 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1945 if (getDesktopPane() == null)
1947 // no frames created and in headless mode
1948 // TODO: verify that frames are recoverable when in headless mode
1951 List<AlignmentPanel> aps = new ArrayList<>();
1952 AlignFrame[] frames = getAlignFrames();
1957 for (AlignFrame af : frames)
1959 for (AlignmentPanel ap : af.alignPanels)
1961 if (alignmentId == null
1962 || alignmentId.equals(ap.av.getSequenceSetId()))
1968 if (aps.size() == 0)
1972 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1977 * get all the viewports on an alignment.
1979 * @param sequenceSetId
1980 * unique alignment id (may be null - all viewports returned in that
1982 * @return all viewports on the alignment bound to sequenceSetId
1984 public static AlignmentViewport[] getViewports(String sequenceSetId)
1986 List<AlignmentViewport> viewp = new ArrayList<>();
1987 if (getDesktopPane() != null)
1989 AlignFrame[] frames = getAlignFrames();
1991 for (AlignFrame afr : frames)
1993 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
1994 .equals(sequenceSetId))
1996 if (afr.alignPanels != null)
1998 for (AlignmentPanel ap : afr.alignPanels)
2000 if (sequenceSetId == null
2001 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2009 viewp.add(afr.getViewport());
2013 if (viewp.size() > 0)
2015 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2022 * Explode the views in the given frame into separate AlignFrame
2026 public static void explodeViews(AlignFrame af)
2028 int size = af.alignPanels.size();
2034 // FIXME: ideally should use UI interface API
2035 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2036 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2037 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2038 for (int i = 0; i < size; i++)
2040 AlignmentPanel ap = af.alignPanels.get(i);
2042 AlignFrame newaf = new AlignFrame(ap);
2044 // transfer reference for existing feature settings to new alignFrame
2045 if (ap == af.alignPanel)
2047 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2049 newaf.featureSettings = viewFeatureSettings;
2051 newaf.setFeatureSettingsGeometry(fsBounds);
2055 * Restore the view's last exploded frame geometry if known. Multiple
2056 * views from one exploded frame share and restore the same (frame)
2057 * position and size.
2059 Rectangle geometry = ap.av.getExplodedGeometry();
2060 if (geometry != null)
2062 newaf.setBounds(geometry);
2065 ap.av.setGatherViewsHere(false);
2067 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2068 AlignFrame.DEFAULT_HEIGHT);
2069 // and materialise a new feature settings dialog instance for the new
2071 // (closes the old as if 'OK' was pressed)
2072 if (ap == af.alignPanel && newaf.featureSettings != null
2073 && newaf.featureSettings.isOpen()
2074 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2076 newaf.showFeatureSettingsUI();
2080 af.featureSettings = null;
2081 af.alignPanels.clear();
2082 af.closeMenuItem_actionPerformed(true);
2087 * Gather expanded views (separate AlignFrame's) with the same sequence set
2088 * identifier back in to this frame as additional views, and close the
2089 * expanded views. Note the expanded frames may themselves have multiple
2090 * views. We take the lot.
2094 public void gatherViews(AlignFrame source)
2096 source.viewport.setGatherViewsHere(true);
2097 source.viewport.setExplodedGeometry(source.getBounds());
2098 JInternalFrame[] frames = desktopPane.getAllFrames();
2099 String viewId = source.viewport.getSequenceSetId();
2100 for (int t = 0; t < frames.length; t++)
2102 if (frames[t] instanceof AlignFrame && frames[t] != source)
2104 AlignFrame af = (AlignFrame) frames[t];
2105 boolean gatherThis = false;
2106 for (int a = 0; a < af.alignPanels.size(); a++)
2108 AlignmentPanel ap = af.alignPanels.get(a);
2109 if (viewId.equals(ap.av.getSequenceSetId()))
2112 ap.av.setGatherViewsHere(false);
2113 ap.av.setExplodedGeometry(af.getBounds());
2114 source.addAlignmentPanel(ap, false);
2120 if (af.featureSettings != null && af.featureSettings.isOpen())
2122 if (source.featureSettings == null)
2124 // preserve the feature settings geometry for this frame
2125 source.featureSettings = af.featureSettings;
2126 source.setFeatureSettingsGeometry(
2127 af.getFeatureSettingsGeometry());
2131 // close it and forget
2132 af.featureSettings.close();
2135 af.alignPanels.clear();
2136 af.closeMenuItem_actionPerformed(true);
2141 // refresh the feature setting UI for the source frame if it exists
2142 if (source.featureSettings != null && source.featureSettings.isOpen())
2144 source.showFeatureSettingsUI();
2149 public JInternalFrame[] getAllFrames()
2151 return desktopPane.getAllFrames();
2155 * Checks the given url to see if it gives a response indicating that the user
2156 * should be informed of a new questionnaire.
2160 public void checkForQuestionnaire(String url)
2162 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2163 // javax.swing.SwingUtilities.invokeLater(jvq);
2164 new Thread(jvq).start();
2167 public void checkURLLinks()
2169 // Thread off the URL link checker
2170 addDialogThread(new Runnable()
2175 if (Cache.getDefault("CHECKURLLINKS", true))
2177 // check what the actual links are - if it's just the default don't
2178 // bother with the warning
2179 List<String> links = Preferences.sequenceUrlLinks
2182 // only need to check links if there is one with a
2183 // SEQUENCE_ID which is not the default EMBL_EBI link
2184 ListIterator<String> li = links.listIterator();
2185 boolean check = false;
2186 List<JLabel> urls = new ArrayList<>();
2187 while (li.hasNext())
2189 String link = li.next();
2190 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2191 && !UrlConstants.isDefaultString(link))
2194 int barPos = link.indexOf("|");
2195 String urlMsg = barPos == -1 ? link
2196 : link.substring(0, barPos) + ": "
2197 + link.substring(barPos + 1);
2198 urls.add(new JLabel(urlMsg));
2206 // ask user to check in case URL links use old style tokens
2207 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2208 JPanel msgPanel = new JPanel();
2209 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2210 msgPanel.add(Box.createVerticalGlue());
2211 JLabel msg = new JLabel(MessageManager
2212 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2213 JLabel msg2 = new JLabel(MessageManager
2214 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2216 for (JLabel url : urls)
2222 final JCheckBox jcb = new JCheckBox(
2223 MessageManager.getString("label.do_not_display_again"));
2224 jcb.addActionListener(new ActionListener()
2227 public void actionPerformed(ActionEvent e)
2229 // update Cache settings for "don't show this again"
2230 boolean showWarningAgain = !jcb.isSelected();
2231 Cache.setProperty("CHECKURLLINKS",
2232 Boolean.valueOf(showWarningAgain).toString());
2237 JvOptionPane.showMessageDialog(desktopPane, msgPanel,
2239 .getString("label.SEQUENCE_ID_no_longer_used"),
2240 JvOptionPane.WARNING_MESSAGE);
2247 * Proxy class for JDesktopPane which optionally displays the current memory
2248 * usage and highlights the desktop area with a red bar if free memory runs
2253 public class MyDesktopPane extends JDesktopPane implements Runnable
2255 private static final float ONE_MB = 1048576f;
2257 boolean showMemoryUsage = false;
2261 java.text.NumberFormat df;
2263 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2266 public MyDesktopPane(boolean showMemoryUsage)
2268 showMemoryUsage(showMemoryUsage);
2271 public void showMemoryUsage(boolean showMemory)
2273 this.showMemoryUsage = showMemory;
2276 Thread worker = new Thread(this);
2282 public boolean isShowMemoryUsage()
2284 return showMemoryUsage;
2290 df = java.text.NumberFormat.getNumberInstance();
2291 df.setMaximumFractionDigits(2);
2292 runtime = Runtime.getRuntime();
2294 while (showMemoryUsage)
2298 maxMemory = runtime.maxMemory() / ONE_MB;
2299 allocatedMemory = runtime.totalMemory() / ONE_MB;
2300 freeMemory = runtime.freeMemory() / ONE_MB;
2301 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2303 percentUsage = (totalFreeMemory / maxMemory) * 100;
2305 // if (percentUsage < 20)
2307 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2309 // instance.set.setBorder(border1);
2312 // sleep after showing usage
2314 } catch (Exception ex)
2316 ex.printStackTrace();
2322 public void paintComponent(Graphics g)
2324 if (showMemoryUsage && g != null && df != null)
2326 if (percentUsage < 20)
2328 g.setColor(Color.red);
2330 FontMetrics fm = g.getFontMetrics();
2333 g.drawString(MessageManager.formatMessage("label.memory_stats",
2335 { df.format(totalFreeMemory), df.format(maxMemory),
2336 df.format(percentUsage) }),
2337 10, getHeight() - fm.getHeight());
2341 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2342 Desktop.debugScaleMessage(Desktop.getDesktopPane().getGraphics());
2347 * Accessor method to quickly get all the AlignmentFrames loaded.
2349 * @return an array of AlignFrame, or null if none found
2351 public static AlignFrame[] getAlignFrames()
2353 if (Jalview.isHeadlessMode())
2355 return new AlignFrame[] { Jalview.getInstance().currentAlignFrame };
2358 JInternalFrame[] frames = getDesktopPane().getAllFrames();
2364 List<AlignFrame> avp = new ArrayList<>();
2366 for (int i = frames.length - 1; i > -1; i--)
2368 if (frames[i] instanceof AlignFrame)
2370 avp.add((AlignFrame) frames[i]);
2372 else if (frames[i] instanceof SplitFrame)
2375 * Also check for a split frame containing an AlignFrame
2377 GSplitFrame sf = (GSplitFrame) frames[i];
2378 if (sf.getTopFrame() instanceof AlignFrame)
2380 avp.add((AlignFrame) sf.getTopFrame());
2382 if (sf.getBottomFrame() instanceof AlignFrame)
2384 avp.add((AlignFrame) sf.getBottomFrame());
2388 if (avp.size() == 0)
2392 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2397 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2401 public GStructureViewer[] getJmols()
2403 JInternalFrame[] frames = desktopPane.getAllFrames();
2409 List<GStructureViewer> avp = new ArrayList<>();
2411 for (int i = frames.length - 1; i > -1; i--)
2413 if (frames[i] instanceof AppJmol)
2415 GStructureViewer af = (GStructureViewer) frames[i];
2419 if (avp.size() == 0)
2423 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2428 * Add Groovy Support to Jalview
2431 public void groovyShell_actionPerformed()
2435 openGroovyConsole();
2436 } catch (Exception ex)
2438 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2439 JvOptionPane.showInternalMessageDialog(desktopPane,
2441 MessageManager.getString("label.couldnt_create_groovy_shell"),
2442 MessageManager.getString("label.groovy_support_failed"),
2443 JvOptionPane.ERROR_MESSAGE);
2448 * Open the Groovy console
2450 void openGroovyConsole()
2452 if (groovyConsole == null)
2454 groovyConsole = new groovy.ui.Console();
2455 groovyConsole.setVariable("Jalview", this);
2456 groovyConsole.run();
2459 * We allow only one console at a time, so that AlignFrame menu option
2460 * 'Calculate | Run Groovy script' is unambiguous.
2461 * Disable 'Groovy Console', and enable 'Run script', when the console is
2462 * opened, and the reverse when it is closed
2464 Window window = (Window) groovyConsole.getFrame();
2465 window.addWindowListener(new WindowAdapter()
2468 public void windowClosed(WindowEvent e)
2471 * rebind CMD-Q from Groovy Console to Jalview Quit
2474 enableExecuteGroovy(false);
2480 * show Groovy console window (after close and reopen)
2482 ((Window) groovyConsole.getFrame()).setVisible(true);
2485 * if we got this far, enable 'Run Groovy' in AlignFrame menus
2486 * and disable opening a second console
2488 enableExecuteGroovy(true);
2492 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2493 * binding when opened
2495 protected void addQuitHandler()
2498 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
2499 .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
2500 Platform.SHORTCUT_KEY_MASK),
2502 getRootPane().getActionMap().put("Quit", new AbstractAction()
2505 public void actionPerformed(ActionEvent e)
2513 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2516 * true if Groovy console is open
2518 public void enableExecuteGroovy(boolean enabled)
2521 * disable opening a second Groovy console
2522 * (or re-enable when the console is closed)
2524 groovyShell.setEnabled(!enabled);
2526 AlignFrame[] alignFrames = getAlignFrames();
2527 if (alignFrames != null)
2529 for (AlignFrame af : alignFrames)
2531 af.setGroovyEnabled(enabled);
2537 * Progress bars managed by the IProgressIndicator method.
2539 private Hashtable<Long, JPanel> progressBars;
2541 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2546 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2549 public void setProgressBar(String message, long id)
2551 // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
2553 if (progressBars == null)
2555 progressBars = new Hashtable<>();
2556 progressBarHandlers = new Hashtable<>();
2559 if (progressBars.get(Long.valueOf(id)) != null)
2561 JPanel panel = progressBars.remove(Long.valueOf(id));
2562 if (progressBarHandlers.contains(Long.valueOf(id)))
2564 progressBarHandlers.remove(Long.valueOf(id));
2566 removeProgressPanel(panel);
2570 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2575 public void addProgressBar(long id, String message)
2578 throw new UnsupportedOperationException("not implemented");
2582 public void removeProgressBar(long id)
2585 throw new UnsupportedOperationException("not implemented");
2591 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2592 * jalview.gui.IProgressIndicatorHandler)
2595 public void registerHandler(final long id,
2596 final IProgressIndicatorHandler handler)
2598 if (progressBarHandlers == null
2599 || !progressBars.containsKey(Long.valueOf(id)))
2601 throw new Error(MessageManager.getString(
2602 "error.call_setprogressbar_before_registering_handler"));
2604 progressBarHandlers.put(Long.valueOf(id), handler);
2605 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2606 if (handler.canCancel())
2608 JButton cancel = new JButton(
2609 MessageManager.getString("action.cancel"));
2610 final IProgressIndicator us = this;
2611 cancel.addActionListener(new ActionListener()
2615 public void actionPerformed(ActionEvent e)
2617 handler.cancelActivity(id);
2618 us.setProgressBar(MessageManager
2619 .formatMessage("label.cancelled_params", new Object[]
2620 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2624 progressPanel.add(cancel, BorderLayout.EAST);
2630 * @return true if any progress bars are still active
2633 public boolean operationInProgress()
2635 if (progressBars != null && progressBars.size() > 0)
2643 * This will return the first AlignFrame holding the given viewport instance.
2644 * It will break if there are more than one AlignFrames viewing a particular
2648 * @return alignFrame for viewport
2650 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2652 if (getDesktopPane() != null)
2654 AlignmentPanel[] aps = getAlignmentPanels(
2655 viewport.getSequenceSetId());
2656 for (int panel = 0; aps != null && panel < aps.length; panel++)
2658 if (aps[panel] != null && aps[panel].av == viewport)
2660 return aps[panel].alignFrame;
2667 public VamsasApplication getVamsasApplication()
2669 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2675 * flag set if jalview GUI is being operated programmatically
2677 private boolean inBatchMode = false;
2680 * check if jalview GUI is being operated programmatically
2682 * @return inBatchMode
2684 public boolean isInBatchMode()
2690 * set flag if jalview GUI is being operated programmatically
2692 * @param inBatchMode
2694 public void setInBatchMode(boolean inBatchMode)
2696 this.inBatchMode = inBatchMode;
2700 * start service discovery and wait till it is done
2702 public void startServiceDiscovery()
2704 startServiceDiscovery(false);
2708 * start service discovery threads - blocking or non-blocking
2712 public void startServiceDiscovery(boolean blocking)
2714 jalview.bin.Console.debug("Starting service discovery");
2716 var tasks = new ArrayList<Future<?>>();
2717 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2719 System.out.println("loading services");
2723 // todo: changesupport handlers need to be transferred
2724 if (discoverer == null)
2726 discoverer = jalview.ws.jws1.Discoverer.getInstance();
2727 // register PCS handler for desktop.
2728 discoverer.addPropertyChangeListener(changeSupport);
2730 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2731 // until we phase out completely
2732 var f = new FutureTask<Void>(discoverer, null);
2733 new Thread(f).start();
2737 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2739 tasks.add(jalview.ws.jws2.Jws2Discoverer.getInstance().startDiscoverer());
2741 if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
2743 tasks.add(jalview.ws2.client.slivka.SlivkaWSDiscoverer
2744 .getInstance().startDiscoverer());
2748 for (Future<?> task : tasks) {
2751 // block until all discovery tasks are done
2753 } catch (Exception e)
2755 e.printStackTrace();
2762 * called to check if the service discovery process completed successfully.
2766 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2768 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2770 final WSDiscovererI discoverer = jalview.ws.jws2.Jws2Discoverer
2772 final String ermsg = discoverer.getErrorMessages();
2773 // CONFLICT:ALT:? final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
2776 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2778 if (serviceChangedDialog == null)
2780 // only run if we aren't already displaying one of these.
2781 addDialogThread(serviceChangedDialog = new Runnable()
2788 * JalviewDialog jd =new JalviewDialog() {
2790 * @Override protected void cancelPressed() { // TODO
2791 * Auto-generated method stub
2793 * }@Override protected void okPressed() { // TODO
2794 * Auto-generated method stub
2796 * }@Override protected void raiseClosed() { // TODO
2797 * Auto-generated method stub
2799 * } }; jd.initDialogFrame(new
2800 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2801 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2802 * + " or mis-configured HTTP proxy settings.<br/>" +
2803 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
2805 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
2806 * ), true, true, "Web Service Configuration Problem", 450,
2809 * jd.waitForInput();
2811 JvOptionPane.showConfirmDialog(desktopPane,
2812 new JLabel("<html><table width=\"450\"><tr><td>"
2813 + ermsg + "</td></tr></table>"
2814 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2815 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2816 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2817 + " Tools->Preferences dialog box to change them.</p></html>"),
2818 "Web Service Configuration Problem",
2819 JvOptionPane.DEFAULT_OPTION,
2820 JvOptionPane.ERROR_MESSAGE);
2821 serviceChangedDialog = null;
2829 jalview.bin.Console.error(
2830 "Errors reported by JABA discovery service. Check web services preferences.\n"
2837 private Runnable serviceChangedDialog = null;
2840 * start a thread to open a URL in the configured browser. Pops up a warning
2841 * dialog to the user if there is an exception when calling out to the browser
2846 public static void showUrl(final String url)
2848 showUrl(url, getInstance());
2852 * Like showUrl but allows progress handler to be specified
2856 * (null) or object implementing IProgressIndicator
2858 public static void showUrl(final String url,
2859 final IProgressIndicator progress)
2861 new Thread(new Runnable()
2868 if (progress != null)
2870 progress.setProgressBar(MessageManager
2871 .formatMessage("status.opening_params", new Object[]
2872 { url }), this.hashCode());
2874 jalview.util.BrowserLauncher.openURL(url);
2875 } catch (Exception ex)
2877 JvOptionPane.showInternalMessageDialog(getDesktopPane(),
2879 .getString("label.web_browser_not_found_unix"),
2880 MessageManager.getString("label.web_browser_not_found"),
2881 JvOptionPane.WARNING_MESSAGE);
2883 ex.printStackTrace();
2885 if (progress != null)
2887 progress.setProgressBar(null, this.hashCode());
2893 public static WsParamSetManager wsparamManager = null;
2895 public static ParamManager getUserParameterStore()
2897 if (wsparamManager == null)
2899 wsparamManager = new WsParamSetManager();
2901 return wsparamManager;
2905 * static hyperlink handler proxy method for use by Jalview's internal windows
2909 public static void hyperlinkUpdate(HyperlinkEvent e)
2911 if (e.getEventType() == EventType.ACTIVATED)
2916 url = e.getURL().toString();
2918 } catch (Exception x)
2922 // TODO does error send to stderr if no log exists ?
2923 jalview.bin.Console.error("Couldn't handle string " + url + " as a URL.");
2925 // ignore any exceptions due to dud links.
2932 * single thread that handles display of dialogs to user.
2934 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2937 * flag indicating if dialogExecutor should try to acquire a permit
2939 private volatile boolean dialogPause = true;
2944 private java.util.concurrent.Semaphore block = new Semaphore(0);
2946 private static groovy.ui.Console groovyConsole;
2949 * add another dialog thread to the queue
2953 public void addDialogThread(final Runnable prompter)
2955 dialogExecutor.submit(new Runnable()
2965 } catch (InterruptedException x)
2969 if (Jalview.isHeadlessMode())
2975 SwingUtilities.invokeAndWait(prompter);
2976 } catch (Exception q)
2978 jalview.bin.Console.warn("Unexpected Exception in dialog thread.", q);
2984 public void startDialogQueue()
2986 // set the flag so we don't pause waiting for another permit and semaphore
2987 // the current task to begin
2988 dialogPause = false;
2993 * Outputs an image of the desktop to file in EPS format, after prompting the
2994 * user for choice of Text or Lineart character rendering (unless a preference
2995 * has been set). The file name is generated as
2998 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3002 protected void snapShotWindow_actionPerformed(ActionEvent e)
3004 // currently the menu option to do this is not shown
3007 int width = getWidth();
3008 int height = getHeight();
3010 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3011 ImageWriterI writer = new ImageWriterI()
3014 public void exportImage(Graphics g) throws Exception
3017 jalview.bin.Console.info("Successfully written snapshot to file "
3018 + of.getAbsolutePath());
3021 String title = "View of desktop";
3022 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3024 exporter.doExport(of, this, width, height, title);
3028 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3029 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3030 * and location last time the view was expanded (if any). However it does not
3031 * remember the split pane divider location - this is set to match the
3032 * 'exploding' frame.
3036 public void explodeViews(SplitFrame sf)
3038 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3039 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3040 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3042 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3044 int viewCount = topPanels.size();
3051 * Processing in reverse order works, forwards order leaves the first panels
3052 * not visible. I don't know why!
3054 for (int i = viewCount - 1; i >= 0; i--)
3057 * Make new top and bottom frames. These take over the respective
3058 * AlignmentPanel objects, including their AlignmentViewports, so the
3059 * cdna/protein relationships between the viewports is carried over to the
3062 * explodedGeometry holds the (x, y) position of the previously exploded
3063 * SplitFrame, and the (width, height) of the AlignFrame component
3065 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3066 AlignFrame newTopFrame = new AlignFrame(topPanel);
3067 newTopFrame.setSize(oldTopFrame.getSize());
3068 newTopFrame.setVisible(true);
3069 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3070 .getExplodedGeometry();
3071 if (geometry != null)
3073 newTopFrame.setSize(geometry.getSize());
3076 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3077 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3078 newBottomFrame.setSize(oldBottomFrame.getSize());
3079 newBottomFrame.setVisible(true);
3080 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3081 .getExplodedGeometry();
3082 if (geometry != null)
3084 newBottomFrame.setSize(geometry.getSize());
3087 topPanel.av.setGatherViewsHere(false);
3088 bottomPanel.av.setGatherViewsHere(false);
3089 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3091 if (geometry != null)
3093 splitFrame.setLocation(geometry.getLocation());
3095 addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3099 * Clear references to the panels (now relocated in the new SplitFrames)
3100 * before closing the old SplitFrame.
3103 bottomPanels.clear();
3108 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3109 * back into the given SplitFrame as additional views. Note that the gathered
3110 * frames may themselves have multiple views.
3114 public void gatherViews(GSplitFrame source)
3117 * special handling of explodedGeometry for a view within a SplitFrame: - it
3118 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3119 * height) of the AlignFrame component
3121 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3122 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3123 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3124 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3125 myBottomFrame.viewport
3126 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3127 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3128 myTopFrame.viewport.setGatherViewsHere(true);
3129 myBottomFrame.viewport.setGatherViewsHere(true);
3130 String topViewId = myTopFrame.viewport.getSequenceSetId();
3131 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3133 JInternalFrame[] frames = desktopPane.getAllFrames();
3134 for (JInternalFrame frame : frames)
3136 if (frame instanceof SplitFrame && frame != source)
3138 SplitFrame sf = (SplitFrame) frame;
3139 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3140 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3141 boolean gatherThis = false;
3142 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3144 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3145 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3146 if (topViewId.equals(topPanel.av.getSequenceSetId())
3147 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3150 topPanel.av.setGatherViewsHere(false);
3151 bottomPanel.av.setGatherViewsHere(false);
3152 topPanel.av.setExplodedGeometry(
3153 new Rectangle(sf.getLocation(), topFrame.getSize()));
3154 bottomPanel.av.setExplodedGeometry(
3155 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3156 myTopFrame.addAlignmentPanel(topPanel, false);
3157 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3163 topFrame.getAlignPanels().clear();
3164 bottomFrame.getAlignPanels().clear();
3171 * The dust settles...give focus to the tab we did this from.
3173 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3176 public static groovy.ui.Console getGroovyConsole()
3178 return groovyConsole;
3182 * handles the payload of a drag and drop event.
3184 * TODO refactor to desktop utilities class
3187 * - Data source strings extracted from the drop event
3189 * - protocol for each data source extracted from the drop event
3193 * - the payload from the drop event
3196 @SuppressWarnings("unchecked")
3197 public static void transferFromDropTarget(List<Object> files,
3198 List<DataSourceType> protocols, DropTargetDropEvent evt,
3199 Transferable t) throws Exception
3202 // BH 2018 changed List<String> to List<Object> to allow for File from
3205 // DataFlavor[] flavors = t.getTransferDataFlavors();
3206 // for (int i = 0; i < flavors.length; i++) {
3207 // if (flavors[i].isFlavorJavaFileListType()) {
3208 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3209 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3210 // for (int j = 0; j < list.size(); j++) {
3211 // File file = (File) list.get(j);
3212 // byte[] data = getDroppedFileBytes(file);
3213 // fileName.setText(file.getName() + " - " + data.length + " " +
3214 // evt.getLocation());
3215 // JTextArea target = (JTextArea) ((DropTarget)
3216 // evt.getSource()).getComponent();
3217 // target.setText(new String(data));
3219 // dtde.dropComplete(true);
3224 DataFlavor uriListFlavor = new DataFlavor(
3225 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3228 urlFlavour = new DataFlavor(
3229 "application/x-java-url; class=java.net.URL");
3230 } catch (ClassNotFoundException cfe)
3232 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3236 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3241 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3242 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3243 // means url may be null.
3246 protocols.add(DataSourceType.URL);
3247 files.add(url.toString());
3248 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3249 + files.get(files.size() - 1));
3254 if (Platform.isAMacAndNotJS())
3257 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3260 } catch (Throwable ex)
3262 jalview.bin.Console.debug("URL drop handler failed.", ex);
3265 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3267 // Works on Windows and MacOSX
3268 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3269 for (File file : (List<File>) t
3270 .getTransferData(DataFlavor.javaFileListFlavor))
3273 protocols.add(DataSourceType.FILE);
3278 // Unix like behaviour
3279 boolean added = false;
3281 if (t.isDataFlavorSupported(uriListFlavor))
3283 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3284 // This is used by Unix drag system
3285 data = (String) t.getTransferData(uriListFlavor);
3289 // fallback to text: workaround - on OSX where there's a JVM bug
3291 .debug("standard URIListFlavor failed. Trying text");
3292 // try text fallback
3293 DataFlavor textDf = new DataFlavor(
3294 "text/plain;class=java.lang.String");
3295 if (t.isDataFlavorSupported(textDf))
3297 data = (String) t.getTransferData(textDf);
3300 jalview.bin.Console.debug("Plain text drop content returned "
3301 + (data == null ? "Null - failed" : data));
3306 while (protocols.size() < files.size())
3308 jalview.bin.Console.debug("Adding missing FILE protocol for "
3309 + files.get(protocols.size()));
3310 protocols.add(DataSourceType.FILE);
3312 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3313 data, "\r\n"); st.hasMoreTokens();)
3316 String s = st.nextToken();
3317 if (s.startsWith("#"))
3319 // the line is a comment (as per the RFC 2483)
3322 java.net.URI uri = new java.net.URI(s);
3323 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3325 protocols.add(DataSourceType.URL);
3326 files.add(uri.toString());
3330 // otherwise preserve old behaviour: catch all for file objects
3331 java.io.File file = new java.io.File(uri);
3332 protocols.add(DataSourceType.FILE);
3333 files.add(file.toString());
3338 if (jalview.bin.Console.isDebugEnabled())
3340 if (data == null || !added)
3343 if (t.getTransferDataFlavors() != null
3344 && t.getTransferDataFlavors().length > 0)
3346 jalview.bin.Console.debug(
3347 "Couldn't resolve drop data. Here are the supported flavors:");
3348 for (DataFlavor fl : t.getTransferDataFlavors())
3350 jalview.bin.Console.debug(
3351 "Supported transfer dataflavor: " + fl.toString());
3352 Object df = t.getTransferData(fl);
3355 jalview.bin.Console.debug("Retrieves: " + df);
3359 jalview.bin.Console.debug("Retrieved nothing");
3365 jalview.bin.Console.debug("Couldn't resolve dataflavor for drop: "
3371 if (Platform.isWindowsAndNotJS())
3373 jalview.bin.Console.debug("Scanning dropped content for Windows Link Files");
3375 // resolve any .lnk files in the file drop
3376 for (int f = 0; f < files.size(); f++)
3378 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3379 if (protocols.get(f).equals(DataSourceType.FILE)
3380 && (source.endsWith(".lnk") || source.endsWith(".url")
3381 || source.endsWith(".site")))
3385 Object obj = files.get(f);
3386 File lf = (obj instanceof File ? (File) obj
3387 : new File((String) obj));
3388 // process link file to get a URL
3389 jalview.bin.Console.debug("Found potential link file: " + lf);
3390 WindowsShortcut wscfile = new WindowsShortcut(lf);
3391 String fullname = wscfile.getRealFilename();
3392 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3393 files.set(f, fullname);
3394 jalview.bin.Console.debug("Parsed real filename " + fullname
3395 + " to extract protocol: " + protocols.get(f));
3396 } catch (Exception ex)
3398 jalview.bin.Console.error(
3399 "Couldn't parse " + files.get(f) + " as a link file.",
3408 * Sets the Preferences property for experimental features to True or False
3409 * depending on the state of the controlling menu item
3412 protected void showExperimental_actionPerformed(boolean selected)
3414 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3418 * Answers a (possibly empty) list of any structure viewer frames (currently
3419 * for either Jmol or Chimera) which are currently open. This may optionally
3420 * be restricted to viewers of a specified class, or viewers linked to a
3421 * specified alignment panel.
3424 * if not null, only return viewers linked to this panel
3425 * @param structureViewerClass
3426 * if not null, only return viewers of this class
3429 public List<StructureViewerBase> getStructureViewers(
3430 AlignmentPanel apanel,
3431 Class<? extends StructureViewerBase> structureViewerClass)
3433 List<StructureViewerBase> result = new ArrayList<>();
3434 JInternalFrame[] frames = getAllFrames();
3436 for (JInternalFrame frame : frames)
3438 if (frame instanceof StructureViewerBase)
3440 if (structureViewerClass == null
3441 || structureViewerClass.isInstance(frame))
3444 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3446 result.add((StructureViewerBase) frame);
3454 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3456 private static boolean debugScaleMessageDone = false;
3458 public static void debugScaleMessage(Graphics g) {
3459 if (debugScaleMessageDone) {
3462 // output used by tests to check HiDPI scaling settings in action
3464 Graphics2D gg = (Graphics2D) g;
3466 AffineTransform t = gg.getTransform();
3467 double scaleX = t.getScaleX();
3468 double scaleY = t.getScaleY();
3469 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3470 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3471 debugScaleMessageDone = true;
3475 jalview.bin.Console.debug("Desktop graphics null");
3477 } catch (Exception e)
3479 jalview.bin.Console.debug(Cache.getStackTraceString(e));