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.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.gui.ImageExporter.ImageWriterI;
108 import jalview.io.BackupFiles;
109 import jalview.io.DataSourceType;
110 import jalview.io.FileFormat;
111 import jalview.io.FileFormatException;
112 import jalview.io.FileFormatI;
113 import jalview.io.FileFormats;
114 import jalview.io.FileLoader;
115 import jalview.io.FormatAdapter;
116 import jalview.io.IdentifyFile;
117 import jalview.io.JalviewFileChooser;
118 import jalview.io.JalviewFileView;
119 import jalview.jbgui.GSplitFrame;
120 import jalview.jbgui.GStructureViewer;
121 import jalview.jbgui.QuitHandler;
122 import jalview.jbgui.QuitHandler.QResponse;
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 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)
210 public void addJalviewPropertyChangeListener(
211 PropertyChangeListener listener)
213 changeSupport.addJalviewPropertyChangeListener(listener);
217 * @param propertyName
219 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
220 * java.beans.PropertyChangeListener)
222 public void addJalviewPropertyChangeListener(String propertyName,
223 PropertyChangeListener listener)
225 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
229 * @param propertyName
231 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
232 * java.beans.PropertyChangeListener)
234 public void removeJalviewPropertyChangeListener(String propertyName,
235 PropertyChangeListener listener)
237 changeSupport.removeJalviewPropertyChangeListener(propertyName,
241 /** Singleton Desktop instance */
242 public static Desktop instance;
244 public static MyDesktopPane desktop;
246 public static MyDesktopPane getDesktop()
248 // BH 2018 could use currentThread() here as a reference to a
249 // Hashtable<Thread, MyDesktopPane> in JavaScript
253 static int openFrameCount = 0;
255 static final int xOffset = 30;
257 static final int yOffset = 30;
259 public static jalview.ws.jws1.Discoverer discoverer;
261 public static Object[] jalviewClipboard;
263 public static boolean internalCopy = false;
265 static int fileLoadingCount = 0;
267 class MyDesktopManager implements DesktopManager
270 private DesktopManager delegate;
272 public MyDesktopManager(DesktopManager delegate)
274 this.delegate = delegate;
278 public void activateFrame(JInternalFrame f)
282 delegate.activateFrame(f);
283 } catch (NullPointerException npe)
285 Point p = getMousePosition();
286 instance.showPasteMenu(p.x, p.y);
291 public void beginDraggingFrame(JComponent f)
293 delegate.beginDraggingFrame(f);
297 public void beginResizingFrame(JComponent f, int direction)
299 delegate.beginResizingFrame(f, direction);
303 public void closeFrame(JInternalFrame f)
305 delegate.closeFrame(f);
309 public void deactivateFrame(JInternalFrame f)
311 delegate.deactivateFrame(f);
315 public void deiconifyFrame(JInternalFrame f)
317 delegate.deiconifyFrame(f);
321 public void dragFrame(JComponent f, int newX, int newY)
327 delegate.dragFrame(f, newX, newY);
331 public void endDraggingFrame(JComponent f)
333 delegate.endDraggingFrame(f);
338 public void endResizingFrame(JComponent f)
340 delegate.endResizingFrame(f);
345 public void iconifyFrame(JInternalFrame f)
347 delegate.iconifyFrame(f);
351 public void maximizeFrame(JInternalFrame f)
353 delegate.maximizeFrame(f);
357 public void minimizeFrame(JInternalFrame f)
359 delegate.minimizeFrame(f);
363 public void openFrame(JInternalFrame f)
365 delegate.openFrame(f);
369 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
376 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
380 public void setBoundsForFrame(JComponent f, int newX, int newY,
381 int newWidth, int newHeight)
383 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
386 // All other methods, simply delegate
391 * Creates a new Desktop object.
397 * A note to implementors. It is ESSENTIAL that any activities that might
398 * block are spawned off as threads rather than waited for during this
403 doConfigureStructurePrefs();
404 setTitle(ChannelProperties.getProperty("app_name") + " "
405 + Cache.getProperty("VERSION"));
408 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
409 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
410 * officially documented or guaranteed to exist, so we access it via
411 * reflection. There appear to be unfathomable criteria about what this
412 * string can contain, and it if doesn't meet those criteria then "java"
413 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
414 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
415 * not. The reflection access may generate a warning: WARNING: An illegal
416 * reflective access operation has occurred WARNING: Illegal reflective
417 * access by jalview.gui.Desktop () to field
418 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
420 if (Platform.isLinux())
422 if (LaunchUtils.getJavaVersion() >= 11)
424 jalview.bin.Console.info(
425 "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.");
429 Toolkit xToolkit = Toolkit.getDefaultToolkit();
430 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
431 Field awtAppClassNameField = null;
433 if (Arrays.stream(declaredFields)
434 .anyMatch(f -> f.getName().equals("awtAppClassName")))
436 awtAppClassNameField = xToolkit.getClass()
437 .getDeclaredField("awtAppClassName");
440 String title = ChannelProperties.getProperty("app_name");
441 if (awtAppClassNameField != null)
443 awtAppClassNameField.setAccessible(true);
444 awtAppClassNameField.set(xToolkit, title);
448 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
450 } catch (Exception e)
452 jalview.bin.Console.debug("Error setting awtAppClassName");
453 jalview.bin.Console.trace(Cache.getStackTraceString(e));
457 setIconImages(ChannelProperties.getIconList());
459 // override quit handling when GUI OS close [X] button pressed
460 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
461 addWindowListener(new WindowAdapter()
464 public void windowClosing(WindowEvent ev)
466 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
470 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
472 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
473 desktop = new MyDesktopPane(selmemusage);
475 showMemusage.setSelected(selmemusage);
476 desktop.setBackground(Color.white);
478 getContentPane().setLayout(new BorderLayout());
479 // alternate config - have scrollbars - see notes in JAL-153
480 // JScrollPane sp = new JScrollPane();
481 // sp.getViewport().setView(desktop);
482 // getContentPane().add(sp, BorderLayout.CENTER);
484 // BH 2018 - just an experiment to try unclipped JInternalFrames.
487 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
490 getContentPane().add(desktop, BorderLayout.CENTER);
491 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
493 // This line prevents Windows Look&Feel resizing all new windows to maximum
494 // if previous window was maximised
495 desktop.setDesktopManager(new MyDesktopManager(
496 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
497 : Platform.isAMacAndNotJS()
498 ? new AquaInternalFrameManager(
499 desktop.getDesktopManager())
500 : desktop.getDesktopManager())));
502 Rectangle dims = getLastKnownDimensions("");
509 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
510 int xPos = Math.max(5, (screenSize.width - 900) / 2);
511 int yPos = Math.max(5, (screenSize.height - 650) / 2);
512 setBounds(xPos, yPos, 900, 650);
515 if (!Platform.isJS())
522 jconsole = new Console(this, showjconsole);
523 jconsole.setHeader(Cache.getVersionDetailsForConsole());
524 showConsole(showjconsole);
526 showNews.setVisible(false);
528 experimentalFeatures.setSelected(showExperimental());
530 getIdentifiersOrgData();
534 // Spawn a thread that shows the splashscreen
537 SwingUtilities.invokeLater(new Runnable()
542 new SplashScreen(true);
547 // Thread off a new instance of the file chooser - this reduces the time
549 // takes to open it later on.
550 new Thread(new Runnable()
555 jalview.bin.Console.debug("Filechooser init thread started.");
556 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
557 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
559 jalview.bin.Console.debug("Filechooser init thread finished.");
562 // Add the service change listener
563 changeSupport.addJalviewPropertyChangeListener("services",
564 new PropertyChangeListener()
568 public void propertyChange(PropertyChangeEvent evt)
571 .debug("Firing service changed event for "
572 + evt.getNewValue());
573 JalviewServicesChanged(evt);
578 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
581 this.addMouseListener(ma = new MouseAdapter()
584 public void mousePressed(MouseEvent evt)
586 if (evt.isPopupTrigger()) // Mac
588 showPasteMenu(evt.getX(), evt.getY());
593 public void mouseReleased(MouseEvent evt)
595 if (evt.isPopupTrigger()) // Windows
597 showPasteMenu(evt.getX(), evt.getY());
601 desktop.addMouseListener(ma);
605 * Answers true if user preferences to enable experimental features is True
610 public boolean showExperimental()
612 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
613 Boolean.FALSE.toString());
614 return Boolean.valueOf(experimental).booleanValue();
617 public void doConfigureStructurePrefs()
619 // configure services
620 StructureSelectionManager ssm = StructureSelectionManager
621 .getStructureSelectionManager(this);
622 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
624 ssm.setAddTempFacAnnot(
625 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
626 ssm.setProcessSecondaryStructure(
627 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
628 // JAL-3915 - RNAView is no longer an option so this has no effect
629 ssm.setSecStructServices(
630 Cache.getDefault(Preferences.USE_RNAVIEW, false));
634 ssm.setAddTempFacAnnot(false);
635 ssm.setProcessSecondaryStructure(false);
636 ssm.setSecStructServices(false);
640 public void checkForNews()
642 final Desktop me = this;
643 // Thread off the news reader, in case there are connection problems.
644 new Thread(new Runnable()
649 jalview.bin.Console.debug("Starting news thread.");
650 jvnews = new BlogReader(me);
651 showNews.setVisible(true);
652 jalview.bin.Console.debug("Completed news thread.");
657 public void getIdentifiersOrgData()
659 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
660 {// Thread off the identifiers fetcher
661 new Thread(new Runnable()
667 .debug("Downloading data from identifiers.org");
670 UrlDownloadClient.download(IdOrgSettings.getUrl(),
671 IdOrgSettings.getDownloadLocation());
672 } catch (IOException e)
675 .debug("Exception downloading identifiers.org data"
685 protected void showNews_actionPerformed(ActionEvent e)
687 showNews(showNews.isSelected());
690 void showNews(boolean visible)
692 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
693 showNews.setSelected(visible);
694 if (visible && !jvnews.isVisible())
696 new Thread(new Runnable()
701 long now = System.currentTimeMillis();
702 Desktop.instance.setProgressBar(
703 MessageManager.getString("status.refreshing_news"), now);
704 jvnews.refreshNews();
705 Desktop.instance.setProgressBar(null, now);
713 * recover the last known dimensions for a jalview window
716 * - empty string is desktop, all other windows have unique prefix
717 * @return null or last known dimensions scaled to current geometry (if last
718 * window geom was known)
720 Rectangle getLastKnownDimensions(String windowName)
722 // TODO: lock aspect ratio for scaling desktop Bug #0058199
723 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
724 String x = Cache.getProperty(windowName + "SCREEN_X");
725 String y = Cache.getProperty(windowName + "SCREEN_Y");
726 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
727 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
728 if ((x != null) && (y != null) && (width != null) && (height != null))
730 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
731 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
732 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
734 // attempt #1 - try to cope with change in screen geometry - this
735 // version doesn't preserve original jv aspect ratio.
736 // take ratio of current screen size vs original screen size.
737 double sw = ((1f * screenSize.width) / (1f * Integer
738 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
739 double sh = ((1f * screenSize.height) / (1f * Integer
740 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
741 // rescale the bounds depending upon the current screen geometry.
742 ix = (int) (ix * sw);
743 iw = (int) (iw * sw);
744 iy = (int) (iy * sh);
745 ih = (int) (ih * sh);
746 while (ix >= screenSize.width)
748 jalview.bin.Console.debug(
749 "Window geometry location recall error: shifting horizontal to within screenbounds.");
750 ix -= screenSize.width;
752 while (iy >= screenSize.height)
754 jalview.bin.Console.debug(
755 "Window geometry location recall error: shifting vertical to within screenbounds.");
756 iy -= screenSize.height;
758 jalview.bin.Console.debug(
759 "Got last known dimensions for " + windowName + ": x:" + ix
760 + " y:" + iy + " width:" + iw + " height:" + ih);
762 // return dimensions for new instance
763 return new Rectangle(ix, iy, iw, ih);
768 void showPasteMenu(int x, int y)
770 JPopupMenu popup = new JPopupMenu();
771 JMenuItem item = new JMenuItem(
772 MessageManager.getString("label.paste_new_window"));
773 item.addActionListener(new ActionListener()
776 public void actionPerformed(ActionEvent evt)
783 popup.show(this, x, y);
790 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
791 Transferable contents = c.getContents(this);
793 if (contents != null)
795 String file = (String) contents
796 .getTransferData(DataFlavor.stringFlavor);
798 FileFormatI format = new IdentifyFile().identify(file,
799 DataSourceType.PASTE);
801 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
804 } catch (Exception ex)
807 "Unable to paste alignment from system clipboard:\n" + ex);
812 * Adds and opens the given frame to the desktop
823 public static synchronized void addInternalFrame(
824 final JInternalFrame frame, String title, int w, int h)
826 addInternalFrame(frame, title, true, w, h, true, false);
830 * Add an internal frame to the Jalview desktop
837 * When true, display frame immediately, otherwise, caller must call
838 * setVisible themselves.
844 public static synchronized void addInternalFrame(
845 final JInternalFrame frame, String title, boolean makeVisible,
848 addInternalFrame(frame, title, makeVisible, w, h, true, false);
852 * Add an internal frame to the Jalview desktop and make it visible
865 public static synchronized void addInternalFrame(
866 final JInternalFrame frame, String title, int w, int h,
869 addInternalFrame(frame, title, true, w, h, resizable, false);
873 * Add an internal frame to the Jalview desktop
880 * When true, display frame immediately, otherwise, caller must call
881 * setVisible themselves.
888 * @param ignoreMinSize
889 * Do not set the default minimum size for frame
891 public static synchronized void addInternalFrame(
892 final JInternalFrame frame, String title, boolean makeVisible,
893 int w, int h, boolean resizable, boolean ignoreMinSize)
896 // TODO: allow callers to determine X and Y position of frame (eg. via
898 // TODO: consider fixing method to update entries in the window submenu with
899 // the current window title
901 frame.setTitle(title);
902 if (frame.getWidth() < 1 || frame.getHeight() < 1)
906 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
907 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
908 // IF JALVIEW IS RUNNING HEADLESS
909 // ///////////////////////////////////////////////
910 if (instance == null || (System.getProperty("java.awt.headless") != null
911 && System.getProperty("java.awt.headless").equals("true")))
920 frame.setMinimumSize(
921 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
923 // Set default dimension for Alignment Frame window.
924 // The Alignment Frame window could be added from a number of places,
926 // I did this here in order not to miss out on any Alignment frame.
927 if (frame instanceof AlignFrame)
929 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
930 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
934 frame.setVisible(makeVisible);
935 frame.setClosable(true);
936 frame.setResizable(resizable);
937 frame.setMaximizable(resizable);
938 frame.setIconifiable(resizable);
939 frame.setOpaque(Platform.isJS());
941 if (frame.getX() < 1 && frame.getY() < 1)
943 frame.setLocation(xOffset * openFrameCount,
944 yOffset * ((openFrameCount - 1) % 10) + yOffset);
948 * add an entry for the new frame in the Window menu (and remove it when the
951 final JMenuItem menuItem = new JMenuItem(title);
952 frame.addInternalFrameListener(new InternalFrameAdapter()
955 public void internalFrameActivated(InternalFrameEvent evt)
957 JInternalFrame itf = desktop.getSelectedFrame();
960 if (itf instanceof AlignFrame)
962 Jalview.setCurrentAlignFrame((AlignFrame) itf);
969 public void internalFrameClosed(InternalFrameEvent evt)
971 PaintRefresher.RemoveComponent(frame);
974 * defensive check to prevent frames being added half off the window
976 if (openFrameCount > 0)
982 * ensure no reference to alignFrame retained by menu item listener
984 if (menuItem.getActionListeners().length > 0)
986 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
988 windowMenu.remove(menuItem);
992 menuItem.addActionListener(new ActionListener()
995 public void actionPerformed(ActionEvent e)
999 frame.setSelected(true);
1000 frame.setIcon(false);
1001 } catch (java.beans.PropertyVetoException ex)
1008 setKeyBindings(frame);
1012 windowMenu.add(menuItem);
1017 frame.setSelected(true);
1018 frame.requestFocus();
1019 } catch (java.beans.PropertyVetoException ve)
1021 } catch (java.lang.ClassCastException cex)
1023 jalview.bin.Console.warn(
1024 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1030 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1035 private static void setKeyBindings(JInternalFrame frame)
1037 @SuppressWarnings("serial")
1038 final Action closeAction = new AbstractAction()
1041 public void actionPerformed(ActionEvent e)
1048 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1050 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1051 InputEvent.CTRL_DOWN_MASK);
1052 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1053 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1055 InputMap inputMap = frame
1056 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1057 String ctrlW = ctrlWKey.toString();
1058 inputMap.put(ctrlWKey, ctrlW);
1059 inputMap.put(cmdWKey, ctrlW);
1061 ActionMap actionMap = frame.getActionMap();
1062 actionMap.put(ctrlW, closeAction);
1066 public void lostOwnership(Clipboard clipboard, Transferable contents)
1070 Desktop.jalviewClipboard = null;
1073 internalCopy = false;
1077 public void dragEnter(DropTargetDragEvent evt)
1082 public void dragExit(DropTargetEvent evt)
1087 public void dragOver(DropTargetDragEvent evt)
1092 public void dropActionChanged(DropTargetDragEvent evt)
1103 public void drop(DropTargetDropEvent evt)
1105 boolean success = true;
1106 // JAL-1552 - acceptDrop required before getTransferable call for
1107 // Java's Transferable for native dnd
1108 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1109 Transferable t = evt.getTransferable();
1110 List<Object> files = new ArrayList<>();
1111 List<DataSourceType> protocols = new ArrayList<>();
1115 Desktop.transferFromDropTarget(files, protocols, evt, t);
1116 } catch (Exception e)
1118 e.printStackTrace();
1126 for (int i = 0; i < files.size(); i++)
1128 // BH 2018 File or String
1129 Object file = files.get(i);
1130 String fileName = file.toString();
1131 DataSourceType protocol = (protocols == null)
1132 ? DataSourceType.FILE
1134 FileFormatI format = null;
1136 if (fileName.endsWith(".jar"))
1138 format = FileFormat.Jalview;
1143 format = new IdentifyFile().identify(file, protocol);
1145 if (file instanceof File)
1147 Platform.cacheFileData((File) file);
1149 new FileLoader().LoadFile(null, file, protocol, format);
1152 } catch (Exception ex)
1157 evt.dropComplete(success); // need this to ensure input focus is properly
1158 // transfered to any new windows created
1168 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1170 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1171 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1172 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1173 BackupFiles.getEnabled());
1175 chooser.setFileView(new JalviewFileView());
1176 chooser.setDialogTitle(
1177 MessageManager.getString("label.open_local_file"));
1178 chooser.setToolTipText(MessageManager.getString("action.open"));
1180 chooser.setResponseHandler(0, () -> {
1181 File selectedFile = chooser.getSelectedFile();
1182 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1184 FileFormatI format = chooser.getSelectedFormat();
1187 * Call IdentifyFile to verify the file contains what its extension implies.
1188 * Skip this step for dynamically added file formats, because IdentifyFile does
1189 * not know how to recognise them.
1191 if (FileFormats.getInstance().isIdentifiable(format))
1195 format = new IdentifyFile().identify(selectedFile,
1196 DataSourceType.FILE);
1197 } catch (FileFormatException e)
1199 // format = null; //??
1203 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1207 chooser.showOpenDialog(this);
1211 * Shows a dialog for input of a URL at which to retrieve alignment data
1216 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1218 // This construct allows us to have a wider textfield
1220 JLabel label = new JLabel(
1221 MessageManager.getString("label.input_file_url"));
1223 JPanel panel = new JPanel(new GridLayout(2, 1));
1227 * the URL to fetch is input in Java: an editable combobox with history JS:
1228 * (pending JAL-3038) a plain text field
1231 String urlBase = "https://www.";
1232 if (Platform.isJS())
1234 history = new JTextField(urlBase, 35);
1243 JComboBox<String> asCombo = new JComboBox<>();
1244 asCombo.setPreferredSize(new Dimension(400, 20));
1245 asCombo.setEditable(true);
1246 asCombo.addItem(urlBase);
1247 String historyItems = Cache.getProperty("RECENT_URL");
1248 if (historyItems != null)
1250 for (String token : historyItems.split("\\t"))
1252 asCombo.addItem(token);
1259 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1260 MessageManager.getString("action.cancel") };
1261 Callable<Void> action = () -> {
1262 @SuppressWarnings("unchecked")
1263 String url = (history instanceof JTextField
1264 ? ((JTextField) history).getText()
1265 : ((JComboBox<String>) history).getEditor().getItem()
1266 .toString().trim());
1268 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1270 if (viewport != null)
1272 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1273 FileFormat.Jalview);
1277 new FileLoader().LoadFile(url, DataSourceType.URL,
1278 FileFormat.Jalview);
1283 FileFormatI format = null;
1286 format = new IdentifyFile().identify(url, DataSourceType.URL);
1287 } catch (FileFormatException e)
1289 // TODO revise error handling, distinguish between
1290 // URL not found and response not valid
1295 String msg = MessageManager.formatMessage("label.couldnt_locate",
1297 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1298 MessageManager.getString("label.url_not_found"),
1299 JvOptionPane.WARNING_MESSAGE);
1301 return null; // Void
1304 if (viewport != null)
1306 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1311 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1314 return null; // Void
1316 String dialogOption = MessageManager
1317 .getString("label.input_alignment_from_url");
1318 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1319 .showInternalDialog(panel, dialogOption,
1320 JvOptionPane.YES_NO_CANCEL_OPTION,
1321 JvOptionPane.PLAIN_MESSAGE, null, options,
1322 MessageManager.getString("action.ok"));
1326 * Opens the CutAndPaste window for the user to paste an alignment in to
1329 * - if not null, the pasted alignment is added to the current
1330 * alignment; if null, to a new alignment window
1333 public void inputTextboxMenuItem_actionPerformed(
1334 AlignmentViewPanel viewPanel)
1336 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1337 cap.setForInput(viewPanel);
1338 Desktop.addInternalFrame(cap,
1339 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1344 * Check with user and saving files before actually quitting
1346 public void desktopQuit()
1348 desktopQuit(true, false);
1351 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1353 final Callable<QuitHandler.QResponse> doDesktopQuit = () -> {
1354 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1355 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1356 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1357 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1358 getBounds().y, getWidth(), getHeight()));
1360 if (jconsole != null)
1362 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1363 jconsole.stopConsole();
1368 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1372 if (dialogExecutor != null)
1374 dialogExecutor.shutdownNow();
1377 closeAll_actionPerformed(null);
1379 if (groovyConsole != null)
1381 // suppress a possible repeat prompt to save script
1382 groovyConsole.setDirty(false);
1383 groovyConsole.exit();
1386 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1388 // note that shutdown hook will not be run
1389 jalview.bin.Console.debug("Force Quit selected by user");
1390 Runtime.getRuntime().halt(0);
1393 jalview.bin.Console.debug("Quit selected by user");
1396 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1397 // instance.dispose();
1399 jalview.bin.Console.debug("**** BEFORE quit");
1400 jalview.bin.Console.debug("**** QuitHandler.gotQuitResponse="
1401 + QuitHandler.gotQuitResponse());
1403 jalview.bin.Console.debug("**** AFTER quit");
1405 return QuitHandler.gotQuitResponse();
1408 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1409 QuitHandler.defaultCancelQuit);
1413 * Don't call this directly, use desktopQuit() above. Exits the program.
1418 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1419 // not run a second time if gotQuitResponse flag has been set (i.e. user
1420 // confirmed quit of some kind).
1424 private void storeLastKnownDimensions(String string, Rectangle jc)
1426 jalview.bin.Console.debug("Storing last known dimensions for " + string
1427 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1428 + " height:" + jc.height);
1430 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1431 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1432 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1433 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1443 public void aboutMenuItem_actionPerformed(ActionEvent e)
1445 new Thread(new Runnable()
1450 new SplashScreen(false);
1456 * Returns the html text for the About screen, including any available version
1457 * number, build details, author details and citation reference, but without
1458 * the enclosing {@code html} tags
1462 public String getAboutMessage()
1464 StringBuilder message = new StringBuilder(1024);
1465 message.append("<div style=\"font-family: sans-serif;\">")
1466 .append("<h1><strong>Version: ")
1467 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1468 .append("<strong>Built: <em>")
1469 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1470 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1471 .append("</strong>");
1473 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1474 if (latestVersion.equals("Checking"))
1476 // JBP removed this message for 2.11: May be reinstated in future version
1477 // message.append("<br>...Checking latest version...</br>");
1479 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1481 boolean red = false;
1482 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1483 .indexOf("automated build") == -1)
1486 // Displayed when code version and jnlp version do not match and code
1487 // version is not a development build
1488 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1491 message.append("<br>!! Version ")
1492 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1493 .append(" is available for download from ")
1494 .append(Cache.getDefault("www.jalview.org",
1495 "https://www.jalview.org"))
1499 message.append("</div>");
1502 message.append("<br>Authors: ");
1503 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1504 message.append(CITATION);
1506 message.append("</div>");
1508 return message.toString();
1512 * Action on requesting Help documentation
1515 public void documentationMenuItem_actionPerformed()
1519 if (Platform.isJS())
1521 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1530 Help.showHelpWindow();
1532 } catch (Exception ex)
1534 System.err.println("Error opening help: " + ex.getMessage());
1539 public void closeAll_actionPerformed(ActionEvent e)
1541 // TODO show a progress bar while closing?
1542 JInternalFrame[] frames = desktop.getAllFrames();
1543 for (int i = 0; i < frames.length; i++)
1547 frames[i].setClosed(true);
1548 } catch (java.beans.PropertyVetoException ex)
1552 Jalview.setCurrentAlignFrame(null);
1553 System.out.println("ALL CLOSED");
1556 * reset state of singleton objects as appropriate (clear down session state
1557 * when all windows are closed)
1559 StructureSelectionManager ssm = StructureSelectionManager
1560 .getStructureSelectionManager(this);
1568 public void raiseRelated_actionPerformed(ActionEvent e)
1570 reorderAssociatedWindows(false, false);
1574 public void minimizeAssociated_actionPerformed(ActionEvent e)
1576 reorderAssociatedWindows(true, false);
1579 void closeAssociatedWindows()
1581 reorderAssociatedWindows(false, true);
1587 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1591 protected void garbageCollect_actionPerformed(ActionEvent e)
1593 // We simply collect the garbage
1594 jalview.bin.Console.debug("Collecting garbage...");
1596 jalview.bin.Console.debug("Finished garbage collection.");
1602 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1606 protected void showMemusage_actionPerformed(ActionEvent e)
1608 desktop.showMemoryUsage(showMemusage.isSelected());
1615 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1619 protected void showConsole_actionPerformed(ActionEvent e)
1621 showConsole(showConsole.isSelected());
1624 Console jconsole = null;
1627 * control whether the java console is visible or not
1631 void showConsole(boolean selected)
1633 // TODO: decide if we should update properties file
1634 if (jconsole != null) // BH 2018
1636 showConsole.setSelected(selected);
1637 Cache.setProperty("SHOW_JAVA_CONSOLE",
1638 Boolean.valueOf(selected).toString());
1639 jconsole.setVisible(selected);
1643 void reorderAssociatedWindows(boolean minimize, boolean close)
1645 JInternalFrame[] frames = desktop.getAllFrames();
1646 if (frames == null || frames.length < 1)
1651 AlignmentViewport source = null, target = null;
1652 if (frames[0] instanceof AlignFrame)
1654 source = ((AlignFrame) frames[0]).getCurrentView();
1656 else if (frames[0] instanceof TreePanel)
1658 source = ((TreePanel) frames[0]).getViewPort();
1660 else if (frames[0] instanceof PCAPanel)
1662 source = ((PCAPanel) frames[0]).av;
1664 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1666 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1671 for (int i = 0; i < frames.length; i++)
1674 if (frames[i] == null)
1678 if (frames[i] instanceof AlignFrame)
1680 target = ((AlignFrame) frames[i]).getCurrentView();
1682 else if (frames[i] instanceof TreePanel)
1684 target = ((TreePanel) frames[i]).getViewPort();
1686 else if (frames[i] instanceof PCAPanel)
1688 target = ((PCAPanel) frames[i]).av;
1690 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1692 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1695 if (source == target)
1701 frames[i].setClosed(true);
1705 frames[i].setIcon(minimize);
1708 frames[i].toFront();
1712 } catch (java.beans.PropertyVetoException ex)
1727 protected void preferences_actionPerformed(ActionEvent e)
1729 Preferences.openPreferences();
1733 * Prompts the user to choose a file and then saves the Jalview state as a
1734 * Jalview project file
1737 public void saveState_actionPerformed()
1739 saveState_actionPerformed(false);
1742 public void saveState_actionPerformed(boolean saveAs)
1744 java.io.File projectFile = getProjectFile();
1745 // autoSave indicates we already have a file and don't need to ask
1746 boolean autoSave = projectFile != null && !saveAs
1747 && BackupFiles.getEnabled();
1749 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1750 // saveAs="+saveAs+", Backups
1751 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1753 boolean approveSave = false;
1756 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1759 chooser.setFileView(new JalviewFileView());
1760 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1762 int value = chooser.showSaveDialog(this);
1764 if (value == JalviewFileChooser.APPROVE_OPTION)
1766 projectFile = chooser.getSelectedFile();
1767 setProjectFile(projectFile);
1772 if (approveSave || autoSave)
1774 final Desktop me = this;
1775 final java.io.File chosenFile = projectFile;
1776 new Thread(new Runnable()
1781 // TODO: refactor to Jalview desktop session controller action.
1782 setProgressBar(MessageManager.formatMessage(
1783 "label.saving_jalview_project", new Object[]
1784 { chosenFile.getName() }), chosenFile.hashCode());
1785 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1786 // TODO catch and handle errors for savestate
1787 // TODO prevent user from messing with the Desktop whilst we're saving
1790 boolean doBackup = BackupFiles.getEnabled();
1791 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1794 new Jalview2XML().saveState(
1795 doBackup ? backupfiles.getTempFile() : chosenFile);
1799 backupfiles.setWriteSuccess(true);
1800 backupfiles.rollBackupsAndRenameTempFile();
1802 } catch (OutOfMemoryError oom)
1804 new OOMWarning("Whilst saving current state to "
1805 + chosenFile.getName(), oom);
1806 } catch (Exception ex)
1808 jalview.bin.Console.error("Problems whilst trying to save to "
1809 + chosenFile.getName(), ex);
1810 JvOptionPane.showMessageDialog(me,
1811 MessageManager.formatMessage(
1812 "label.error_whilst_saving_current_state_to",
1814 { chosenFile.getName() }),
1815 MessageManager.getString("label.couldnt_save_project"),
1816 JvOptionPane.WARNING_MESSAGE);
1818 setProgressBar(null, chosenFile.hashCode());
1825 public void saveAsState_actionPerformed(ActionEvent e)
1827 saveState_actionPerformed(true);
1830 private void setProjectFile(File choice)
1832 this.projectFile = choice;
1835 public File getProjectFile()
1837 return this.projectFile;
1841 * Shows a file chooser dialog and tries to read in the selected file as a
1845 public void loadState_actionPerformed()
1847 final String[] suffix = new String[] { "jvp", "jar" };
1848 final String[] desc = new String[] { "Jalview Project",
1849 "Jalview Project (old)" };
1850 JalviewFileChooser chooser = new JalviewFileChooser(
1851 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1852 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1856 chooser.setFileView(new JalviewFileView());
1857 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1858 chooser.setResponseHandler(0, () -> {
1859 File selectedFile = chooser.getSelectedFile();
1860 setProjectFile(selectedFile);
1861 String choice = selectedFile.getAbsolutePath();
1862 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1863 new Thread(new Runnable()
1870 new Jalview2XML().loadJalviewAlign(selectedFile);
1871 } catch (OutOfMemoryError oom)
1873 new OOMWarning("Whilst loading project from " + choice, oom);
1874 } catch (Exception ex)
1876 jalview.bin.Console.error(
1877 "Problems whilst loading project from " + choice, ex);
1878 JvOptionPane.showMessageDialog(Desktop.desktop,
1879 MessageManager.formatMessage(
1880 "label.error_whilst_loading_project_from",
1883 MessageManager.getString("label.couldnt_load_project"),
1884 JvOptionPane.WARNING_MESSAGE);
1887 }, "Project Loader").start();
1891 chooser.showOpenDialog(this);
1895 public void inputSequence_actionPerformed(ActionEvent e)
1897 new SequenceFetcher(this);
1900 JPanel progressPanel;
1902 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1904 public void startLoading(final Object fileName)
1906 if (fileLoadingCount == 0)
1908 fileLoadingPanels.add(addProgressPanel(MessageManager
1909 .formatMessage("label.loading_file", new Object[]
1915 private JPanel addProgressPanel(String string)
1917 if (progressPanel == null)
1919 progressPanel = new JPanel(new GridLayout(1, 1));
1920 totalProgressCount = 0;
1921 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1923 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1924 JProgressBar progressBar = new JProgressBar();
1925 progressBar.setIndeterminate(true);
1927 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1929 thisprogress.add(progressBar, BorderLayout.CENTER);
1930 progressPanel.add(thisprogress);
1931 ((GridLayout) progressPanel.getLayout()).setRows(
1932 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1933 ++totalProgressCount;
1934 instance.validate();
1935 return thisprogress;
1938 int totalProgressCount = 0;
1940 private void removeProgressPanel(JPanel progbar)
1942 if (progressPanel != null)
1944 synchronized (progressPanel)
1946 progressPanel.remove(progbar);
1947 GridLayout gl = (GridLayout) progressPanel.getLayout();
1948 gl.setRows(gl.getRows() - 1);
1949 if (--totalProgressCount < 1)
1951 this.getContentPane().remove(progressPanel);
1952 progressPanel = null;
1959 public void stopLoading()
1962 if (fileLoadingCount < 1)
1964 while (fileLoadingPanels.size() > 0)
1966 removeProgressPanel(fileLoadingPanels.remove(0));
1968 fileLoadingPanels.clear();
1969 fileLoadingCount = 0;
1974 public static int getViewCount(String alignmentId)
1976 AlignmentViewport[] aps = getViewports(alignmentId);
1977 return (aps == null) ? 0 : aps.length;
1982 * @param alignmentId
1983 * - if null, all sets are returned
1984 * @return all AlignmentPanels concerning the alignmentId sequence set
1986 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1988 if (Desktop.desktop == null)
1990 // no frames created and in headless mode
1991 // TODO: verify that frames are recoverable when in headless mode
1994 List<AlignmentPanel> aps = new ArrayList<>();
1995 AlignFrame[] frames = getAlignFrames();
2000 for (AlignFrame af : frames)
2002 for (AlignmentPanel ap : af.alignPanels)
2004 if (alignmentId == null
2005 || alignmentId.equals(ap.av.getSequenceSetId()))
2011 if (aps.size() == 0)
2015 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2020 * get all the viewports on an alignment.
2022 * @param sequenceSetId
2023 * unique alignment id (may be null - all viewports returned in that
2025 * @return all viewports on the alignment bound to sequenceSetId
2027 public static AlignmentViewport[] getViewports(String sequenceSetId)
2029 List<AlignmentViewport> viewp = new ArrayList<>();
2030 if (desktop != null)
2032 AlignFrame[] frames = Desktop.getAlignFrames();
2034 for (AlignFrame afr : frames)
2036 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2037 .equals(sequenceSetId))
2039 if (afr.alignPanels != null)
2041 for (AlignmentPanel ap : afr.alignPanels)
2043 if (sequenceSetId == null
2044 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2052 viewp.add(afr.getViewport());
2056 if (viewp.size() > 0)
2058 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2065 * Explode the views in the given frame into separate AlignFrame
2069 public static void explodeViews(AlignFrame af)
2071 int size = af.alignPanels.size();
2077 // FIXME: ideally should use UI interface API
2078 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2079 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2080 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2081 for (int i = 0; i < size; i++)
2083 AlignmentPanel ap = af.alignPanels.get(i);
2085 AlignFrame newaf = new AlignFrame(ap);
2087 // transfer reference for existing feature settings to new alignFrame
2088 if (ap == af.alignPanel)
2090 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2092 newaf.featureSettings = viewFeatureSettings;
2094 newaf.setFeatureSettingsGeometry(fsBounds);
2098 * Restore the view's last exploded frame geometry if known. Multiple views from
2099 * one exploded frame share and restore the same (frame) position and size.
2101 Rectangle geometry = ap.av.getExplodedGeometry();
2102 if (geometry != null)
2104 newaf.setBounds(geometry);
2107 ap.av.setGatherViewsHere(false);
2109 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2110 AlignFrame.DEFAULT_HEIGHT);
2111 // and materialise a new feature settings dialog instance for the new
2113 // (closes the old as if 'OK' was pressed)
2114 if (ap == af.alignPanel && newaf.featureSettings != null
2115 && newaf.featureSettings.isOpen()
2116 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2118 newaf.showFeatureSettingsUI();
2122 af.featureSettings = null;
2123 af.alignPanels.clear();
2124 af.closeMenuItem_actionPerformed(true);
2129 * Gather expanded views (separate AlignFrame's) with the same sequence set
2130 * identifier back in to this frame as additional views, and close the
2131 * expanded views. Note the expanded frames may themselves have multiple
2132 * views. We take the lot.
2136 public void gatherViews(AlignFrame source)
2138 source.viewport.setGatherViewsHere(true);
2139 source.viewport.setExplodedGeometry(source.getBounds());
2140 JInternalFrame[] frames = desktop.getAllFrames();
2141 String viewId = source.viewport.getSequenceSetId();
2142 for (int t = 0; t < frames.length; t++)
2144 if (frames[t] instanceof AlignFrame && frames[t] != source)
2146 AlignFrame af = (AlignFrame) frames[t];
2147 boolean gatherThis = false;
2148 for (int a = 0; a < af.alignPanels.size(); a++)
2150 AlignmentPanel ap = af.alignPanels.get(a);
2151 if (viewId.equals(ap.av.getSequenceSetId()))
2154 ap.av.setGatherViewsHere(false);
2155 ap.av.setExplodedGeometry(af.getBounds());
2156 source.addAlignmentPanel(ap, false);
2162 if (af.featureSettings != null && af.featureSettings.isOpen())
2164 if (source.featureSettings == null)
2166 // preserve the feature settings geometry for this frame
2167 source.featureSettings = af.featureSettings;
2168 source.setFeatureSettingsGeometry(
2169 af.getFeatureSettingsGeometry());
2173 // close it and forget
2174 af.featureSettings.close();
2177 af.alignPanels.clear();
2178 af.closeMenuItem_actionPerformed(true);
2183 // refresh the feature setting UI for the source frame if it exists
2184 if (source.featureSettings != null && source.featureSettings.isOpen())
2186 source.showFeatureSettingsUI();
2191 public JInternalFrame[] getAllFrames()
2193 return desktop.getAllFrames();
2197 * Checks the given url to see if it gives a response indicating that the user
2198 * should be informed of a new questionnaire.
2202 public void checkForQuestionnaire(String url)
2204 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2205 // javax.swing.SwingUtilities.invokeLater(jvq);
2206 new Thread(jvq).start();
2209 public void checkURLLinks()
2211 // Thread off the URL link checker
2212 addDialogThread(new Runnable()
2217 if (Cache.getDefault("CHECKURLLINKS", true))
2219 // check what the actual links are - if it's just the default don't
2220 // bother with the warning
2221 List<String> links = Preferences.sequenceUrlLinks
2224 // only need to check links if there is one with a
2225 // SEQUENCE_ID which is not the default EMBL_EBI link
2226 ListIterator<String> li = links.listIterator();
2227 boolean check = false;
2228 List<JLabel> urls = new ArrayList<>();
2229 while (li.hasNext())
2231 String link = li.next();
2232 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2233 && !UrlConstants.isDefaultString(link))
2236 int barPos = link.indexOf("|");
2237 String urlMsg = barPos == -1 ? link
2238 : link.substring(0, barPos) + ": "
2239 + link.substring(barPos + 1);
2240 urls.add(new JLabel(urlMsg));
2248 // ask user to check in case URL links use old style tokens
2249 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2250 JPanel msgPanel = new JPanel();
2251 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2252 msgPanel.add(Box.createVerticalGlue());
2253 JLabel msg = new JLabel(MessageManager
2254 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2255 JLabel msg2 = new JLabel(MessageManager
2256 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2258 for (JLabel url : urls)
2264 final JCheckBox jcb = new JCheckBox(
2265 MessageManager.getString("label.do_not_display_again"));
2266 jcb.addActionListener(new ActionListener()
2269 public void actionPerformed(ActionEvent e)
2271 // update Cache settings for "don't show this again"
2272 boolean showWarningAgain = !jcb.isSelected();
2273 Cache.setProperty("CHECKURLLINKS",
2274 Boolean.valueOf(showWarningAgain).toString());
2279 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2281 .getString("label.SEQUENCE_ID_no_longer_used"),
2282 JvOptionPane.WARNING_MESSAGE);
2289 * Proxy class for JDesktopPane which optionally displays the current memory
2290 * usage and highlights the desktop area with a red bar if free memory runs
2295 public class MyDesktopPane extends JDesktopPane implements Runnable
2297 private static final float ONE_MB = 1048576f;
2299 boolean showMemoryUsage = false;
2303 java.text.NumberFormat df;
2305 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2308 public MyDesktopPane(boolean showMemoryUsage)
2310 showMemoryUsage(showMemoryUsage);
2313 public void showMemoryUsage(boolean showMemory)
2315 this.showMemoryUsage = showMemory;
2318 Thread worker = new Thread(this);
2324 public boolean isShowMemoryUsage()
2326 return showMemoryUsage;
2332 df = java.text.NumberFormat.getNumberInstance();
2333 df.setMaximumFractionDigits(2);
2334 runtime = Runtime.getRuntime();
2336 while (showMemoryUsage)
2340 maxMemory = runtime.maxMemory() / ONE_MB;
2341 allocatedMemory = runtime.totalMemory() / ONE_MB;
2342 freeMemory = runtime.freeMemory() / ONE_MB;
2343 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2345 percentUsage = (totalFreeMemory / maxMemory) * 100;
2347 // if (percentUsage < 20)
2349 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2351 // instance.set.setBorder(border1);
2354 // sleep after showing usage
2356 } catch (Exception ex)
2358 ex.printStackTrace();
2364 public void paintComponent(Graphics g)
2366 if (showMemoryUsage && g != null && df != null)
2368 if (percentUsage < 20)
2370 g.setColor(Color.red);
2372 FontMetrics fm = g.getFontMetrics();
2375 g.drawString(MessageManager.formatMessage("label.memory_stats",
2377 { df.format(totalFreeMemory), df.format(maxMemory),
2378 df.format(percentUsage) }),
2379 10, getHeight() - fm.getHeight());
2383 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2384 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2389 * Accessor method to quickly get all the AlignmentFrames loaded.
2391 * @return an array of AlignFrame, or null if none found
2393 public static AlignFrame[] getAlignFrames()
2395 if (Jalview.isHeadlessMode())
2397 // Desktop.desktop is null in headless mode
2398 return new AlignFrame[] { Jalview.currentAlignFrame };
2401 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2407 List<AlignFrame> avp = new ArrayList<>();
2409 for (int i = frames.length - 1; i > -1; i--)
2411 if (frames[i] instanceof AlignFrame)
2413 avp.add((AlignFrame) frames[i]);
2415 else if (frames[i] instanceof SplitFrame)
2418 * Also check for a split frame containing an AlignFrame
2420 GSplitFrame sf = (GSplitFrame) frames[i];
2421 if (sf.getTopFrame() instanceof AlignFrame)
2423 avp.add((AlignFrame) sf.getTopFrame());
2425 if (sf.getBottomFrame() instanceof AlignFrame)
2427 avp.add((AlignFrame) sf.getBottomFrame());
2431 if (avp.size() == 0)
2435 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2440 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2444 public GStructureViewer[] getJmols()
2446 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2452 List<GStructureViewer> avp = new ArrayList<>();
2454 for (int i = frames.length - 1; i > -1; i--)
2456 if (frames[i] instanceof AppJmol)
2458 GStructureViewer af = (GStructureViewer) frames[i];
2462 if (avp.size() == 0)
2466 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2471 * Add Groovy Support to Jalview
2474 public void groovyShell_actionPerformed()
2478 openGroovyConsole();
2479 } catch (Exception ex)
2481 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2482 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2484 MessageManager.getString("label.couldnt_create_groovy_shell"),
2485 MessageManager.getString("label.groovy_support_failed"),
2486 JvOptionPane.ERROR_MESSAGE);
2491 * Open the Groovy console
2493 void openGroovyConsole()
2495 if (groovyConsole == null)
2497 groovyConsole = new groovy.ui.Console();
2498 groovyConsole.setVariable("Jalview", this);
2499 groovyConsole.run();
2502 * We allow only one console at a time, so that AlignFrame menu option
2503 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2504 * enable 'Run script', when the console is opened, and the reverse when it is
2507 Window window = (Window) groovyConsole.getFrame();
2508 window.addWindowListener(new WindowAdapter()
2511 public void windowClosed(WindowEvent e)
2514 * rebind CMD-Q from Groovy Console to Jalview Quit
2517 enableExecuteGroovy(false);
2523 * show Groovy console window (after close and reopen)
2525 ((Window) groovyConsole.getFrame()).setVisible(true);
2528 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2529 * opening a second console
2531 enableExecuteGroovy(true);
2535 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2536 * binding when opened
2538 protected void addQuitHandler()
2541 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2543 .getKeyStroke(KeyEvent.VK_Q,
2544 jalview.util.ShortcutKeyMaskExWrapper
2545 .getMenuShortcutKeyMaskEx()),
2547 getRootPane().getActionMap().put("Quit", new AbstractAction()
2550 public void actionPerformed(ActionEvent e)
2558 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2561 * true if Groovy console is open
2563 public void enableExecuteGroovy(boolean enabled)
2566 * disable opening a second Groovy console (or re-enable when the console is
2569 groovyShell.setEnabled(!enabled);
2571 AlignFrame[] alignFrames = getAlignFrames();
2572 if (alignFrames != null)
2574 for (AlignFrame af : alignFrames)
2576 af.setGroovyEnabled(enabled);
2582 * Progress bars managed by the IProgressIndicator method.
2584 private Hashtable<Long, JPanel> progressBars;
2586 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2591 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2594 public void setProgressBar(String message, long id)
2596 if (progressBars == null)
2598 progressBars = new Hashtable<>();
2599 progressBarHandlers = new Hashtable<>();
2602 if (progressBars.get(Long.valueOf(id)) != null)
2604 JPanel panel = progressBars.remove(Long.valueOf(id));
2605 if (progressBarHandlers.contains(Long.valueOf(id)))
2607 progressBarHandlers.remove(Long.valueOf(id));
2609 removeProgressPanel(panel);
2613 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2620 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2621 * jalview.gui.IProgressIndicatorHandler)
2624 public void registerHandler(final long id,
2625 final IProgressIndicatorHandler handler)
2627 if (progressBarHandlers == null
2628 || !progressBars.containsKey(Long.valueOf(id)))
2630 throw new Error(MessageManager.getString(
2631 "error.call_setprogressbar_before_registering_handler"));
2633 progressBarHandlers.put(Long.valueOf(id), handler);
2634 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2635 if (handler.canCancel())
2637 JButton cancel = new JButton(
2638 MessageManager.getString("action.cancel"));
2639 final IProgressIndicator us = this;
2640 cancel.addActionListener(new ActionListener()
2644 public void actionPerformed(ActionEvent e)
2646 handler.cancelActivity(id);
2647 us.setProgressBar(MessageManager
2648 .formatMessage("label.cancelled_params", new Object[]
2649 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2653 progressPanel.add(cancel, BorderLayout.EAST);
2659 * @return true if any progress bars are still active
2662 public boolean operationInProgress()
2664 if (progressBars != null && progressBars.size() > 0)
2672 * This will return the first AlignFrame holding the given viewport instance.
2673 * It will break if there are more than one AlignFrames viewing a particular
2677 * @return alignFrame for viewport
2679 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2681 if (desktop != null)
2683 AlignmentPanel[] aps = getAlignmentPanels(
2684 viewport.getSequenceSetId());
2685 for (int panel = 0; aps != null && panel < aps.length; panel++)
2687 if (aps[panel] != null && aps[panel].av == viewport)
2689 return aps[panel].alignFrame;
2696 public VamsasApplication getVamsasApplication()
2698 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2704 * flag set if jalview GUI is being operated programmatically
2706 private boolean inBatchMode = false;
2709 * check if jalview GUI is being operated programmatically
2711 * @return inBatchMode
2713 public boolean isInBatchMode()
2719 * set flag if jalview GUI is being operated programmatically
2721 * @param inBatchMode
2723 public void setInBatchMode(boolean inBatchMode)
2725 this.inBatchMode = inBatchMode;
2729 * start service discovery and wait till it is done
2731 public void startServiceDiscovery()
2733 startServiceDiscovery(false);
2737 * start service discovery threads - blocking or non-blocking
2741 public void startServiceDiscovery(boolean blocking)
2743 startServiceDiscovery(blocking, false);
2747 * start service discovery threads
2750 * - false means call returns immediately
2751 * @param ignore_SHOW_JWS2_SERVICES_preference
2752 * - when true JABA services are discovered regardless of user's JWS2
2753 * discovery preference setting
2755 public void startServiceDiscovery(boolean blocking,
2756 boolean ignore_SHOW_JWS2_SERVICES_preference)
2758 boolean alive = true;
2759 Thread t0 = null, t1 = null, t2 = null;
2760 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2763 // todo: changesupport handlers need to be transferred
2764 if (discoverer == null)
2766 discoverer = new jalview.ws.jws1.Discoverer();
2767 // register PCS handler for desktop.
2768 discoverer.addPropertyChangeListener(changeSupport);
2770 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2771 // until we phase out completely
2772 (t0 = new Thread(discoverer)).start();
2775 if (ignore_SHOW_JWS2_SERVICES_preference
2776 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2778 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2779 .startDiscoverer(changeSupport);
2783 // TODO: do rest service discovery
2792 } catch (Exception e)
2795 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2796 || (t3 != null && t3.isAlive())
2797 || (t0 != null && t0.isAlive());
2803 * called to check if the service discovery process completed successfully.
2807 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2809 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2811 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2812 .getErrorMessages();
2815 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2817 if (serviceChangedDialog == null)
2819 // only run if we aren't already displaying one of these.
2820 addDialogThread(serviceChangedDialog = new Runnable()
2827 * JalviewDialog jd =new JalviewDialog() {
2829 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2831 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2833 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2835 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2837 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2838 * + " or mis-configured HTTP proxy settings.<br/>" +
2839 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2840 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2841 * true, true, "Web Service Configuration Problem", 450, 400);
2843 * jd.waitForInput();
2845 JvOptionPane.showConfirmDialog(Desktop.desktop,
2846 new JLabel("<html><table width=\"450\"><tr><td>"
2847 + ermsg + "</td></tr></table>"
2848 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2849 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2850 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2851 + " Tools->Preferences dialog box to change them.</p></html>"),
2852 "Web Service Configuration Problem",
2853 JvOptionPane.DEFAULT_OPTION,
2854 JvOptionPane.ERROR_MESSAGE);
2855 serviceChangedDialog = null;
2863 jalview.bin.Console.error(
2864 "Errors reported by JABA discovery service. Check web services preferences.\n"
2871 private Runnable serviceChangedDialog = null;
2874 * start a thread to open a URL in the configured browser. Pops up a warning
2875 * dialog to the user if there is an exception when calling out to the browser
2880 public static void showUrl(final String url)
2882 showUrl(url, Desktop.instance);
2886 * Like showUrl but allows progress handler to be specified
2890 * (null) or object implementing IProgressIndicator
2892 public static void showUrl(final String url,
2893 final IProgressIndicator progress)
2895 new Thread(new Runnable()
2902 if (progress != null)
2904 progress.setProgressBar(MessageManager
2905 .formatMessage("status.opening_params", new Object[]
2906 { url }), this.hashCode());
2908 jalview.util.BrowserLauncher.openURL(url);
2909 } catch (Exception ex)
2911 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2913 .getString("label.web_browser_not_found_unix"),
2914 MessageManager.getString("label.web_browser_not_found"),
2915 JvOptionPane.WARNING_MESSAGE);
2917 ex.printStackTrace();
2919 if (progress != null)
2921 progress.setProgressBar(null, this.hashCode());
2927 public static WsParamSetManager wsparamManager = null;
2929 public static ParamManager getUserParameterStore()
2931 if (wsparamManager == null)
2933 wsparamManager = new WsParamSetManager();
2935 return wsparamManager;
2939 * static hyperlink handler proxy method for use by Jalview's internal windows
2943 public static void hyperlinkUpdate(HyperlinkEvent e)
2945 if (e.getEventType() == EventType.ACTIVATED)
2950 url = e.getURL().toString();
2951 Desktop.showUrl(url);
2952 } catch (Exception x)
2957 .error("Couldn't handle string " + url + " as a URL.");
2959 // ignore any exceptions due to dud links.
2966 * single thread that handles display of dialogs to user.
2968 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2971 * flag indicating if dialogExecutor should try to acquire a permit
2973 private volatile boolean dialogPause = true;
2978 private java.util.concurrent.Semaphore block = new Semaphore(0);
2980 private static groovy.ui.Console groovyConsole;
2983 * add another dialog thread to the queue
2987 public void addDialogThread(final Runnable prompter)
2989 dialogExecutor.submit(new Runnable()
2999 } catch (InterruptedException x)
3003 if (instance == null)
3009 SwingUtilities.invokeAndWait(prompter);
3010 } catch (Exception q)
3012 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3019 public void startDialogQueue()
3021 // set the flag so we don't pause waiting for another permit and semaphore
3022 // the current task to begin
3023 dialogPause = false;
3028 * Outputs an image of the desktop to file in EPS format, after prompting the
3029 * user for choice of Text or Lineart character rendering (unless a preference
3030 * has been set). The file name is generated as
3033 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3037 protected void snapShotWindow_actionPerformed(ActionEvent e)
3039 // currently the menu option to do this is not shown
3042 int width = getWidth();
3043 int height = getHeight();
3045 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3046 ImageWriterI writer = new ImageWriterI()
3049 public void exportImage(Graphics g) throws Exception
3052 jalview.bin.Console.info("Successfully written snapshot to file "
3053 + of.getAbsolutePath());
3056 String title = "View of desktop";
3057 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3059 exporter.doExport(of, this, width, height, title);
3063 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3064 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3065 * and location last time the view was expanded (if any). However it does not
3066 * remember the split pane divider location - this is set to match the
3067 * 'exploding' frame.
3071 public void explodeViews(SplitFrame sf)
3073 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3074 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3075 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3077 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3079 int viewCount = topPanels.size();
3086 * Processing in reverse order works, forwards order leaves the first panels not
3087 * visible. I don't know why!
3089 for (int i = viewCount - 1; i >= 0; i--)
3092 * Make new top and bottom frames. These take over the respective AlignmentPanel
3093 * objects, including their AlignmentViewports, so the cdna/protein
3094 * relationships between the viewports is carried over to the new split frames.
3096 * explodedGeometry holds the (x, y) position of the previously exploded
3097 * SplitFrame, and the (width, height) of the AlignFrame component
3099 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3100 AlignFrame newTopFrame = new AlignFrame(topPanel);
3101 newTopFrame.setSize(oldTopFrame.getSize());
3102 newTopFrame.setVisible(true);
3103 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3104 .getExplodedGeometry();
3105 if (geometry != null)
3107 newTopFrame.setSize(geometry.getSize());
3110 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3111 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3112 newBottomFrame.setSize(oldBottomFrame.getSize());
3113 newBottomFrame.setVisible(true);
3114 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3115 .getExplodedGeometry();
3116 if (geometry != null)
3118 newBottomFrame.setSize(geometry.getSize());
3121 topPanel.av.setGatherViewsHere(false);
3122 bottomPanel.av.setGatherViewsHere(false);
3123 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3125 if (geometry != null)
3127 splitFrame.setLocation(geometry.getLocation());
3129 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3133 * Clear references to the panels (now relocated in the new SplitFrames) before
3134 * closing the old SplitFrame.
3137 bottomPanels.clear();
3142 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3143 * back into the given SplitFrame as additional views. Note that the gathered
3144 * frames may themselves have multiple views.
3148 public void gatherViews(GSplitFrame source)
3151 * special handling of explodedGeometry for a view within a SplitFrame: - it
3152 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3153 * height) of the AlignFrame component
3155 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3156 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3157 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3158 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3159 myBottomFrame.viewport
3160 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3161 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3162 myTopFrame.viewport.setGatherViewsHere(true);
3163 myBottomFrame.viewport.setGatherViewsHere(true);
3164 String topViewId = myTopFrame.viewport.getSequenceSetId();
3165 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3167 JInternalFrame[] frames = desktop.getAllFrames();
3168 for (JInternalFrame frame : frames)
3170 if (frame instanceof SplitFrame && frame != source)
3172 SplitFrame sf = (SplitFrame) frame;
3173 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3174 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3175 boolean gatherThis = false;
3176 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3178 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3179 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3180 if (topViewId.equals(topPanel.av.getSequenceSetId())
3181 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3184 topPanel.av.setGatherViewsHere(false);
3185 bottomPanel.av.setGatherViewsHere(false);
3186 topPanel.av.setExplodedGeometry(
3187 new Rectangle(sf.getLocation(), topFrame.getSize()));
3188 bottomPanel.av.setExplodedGeometry(
3189 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3190 myTopFrame.addAlignmentPanel(topPanel, false);
3191 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3197 topFrame.getAlignPanels().clear();
3198 bottomFrame.getAlignPanels().clear();
3205 * The dust settles...give focus to the tab we did this from.
3207 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3210 public static groovy.ui.Console getGroovyConsole()
3212 return groovyConsole;
3216 * handles the payload of a drag and drop event.
3218 * TODO refactor to desktop utilities class
3221 * - Data source strings extracted from the drop event
3223 * - protocol for each data source extracted from the drop event
3227 * - the payload from the drop event
3230 public static void transferFromDropTarget(List<Object> files,
3231 List<DataSourceType> protocols, DropTargetDropEvent evt,
3232 Transferable t) throws Exception
3235 DataFlavor uriListFlavor = new DataFlavor(
3236 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3239 urlFlavour = new DataFlavor(
3240 "application/x-java-url; class=java.net.URL");
3241 } catch (ClassNotFoundException cfe)
3243 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3247 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3252 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3253 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3254 // means url may be null.
3257 protocols.add(DataSourceType.URL);
3258 files.add(url.toString());
3259 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3260 + files.get(files.size() - 1));
3265 if (Platform.isAMacAndNotJS())
3268 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3271 } catch (Throwable ex)
3273 jalview.bin.Console.debug("URL drop handler failed.", ex);
3276 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3278 // Works on Windows and MacOSX
3279 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3280 for (Object file : (List) t
3281 .getTransferData(DataFlavor.javaFileListFlavor))
3284 protocols.add(DataSourceType.FILE);
3289 // Unix like behaviour
3290 boolean added = false;
3292 if (t.isDataFlavorSupported(uriListFlavor))
3294 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3295 // This is used by Unix drag system
3296 data = (String) t.getTransferData(uriListFlavor);
3300 // fallback to text: workaround - on OSX where there's a JVM bug
3302 .debug("standard URIListFlavor failed. Trying text");
3303 // try text fallback
3304 DataFlavor textDf = new DataFlavor(
3305 "text/plain;class=java.lang.String");
3306 if (t.isDataFlavorSupported(textDf))
3308 data = (String) t.getTransferData(textDf);
3311 jalview.bin.Console.debug("Plain text drop content returned "
3312 + (data == null ? "Null - failed" : data));
3317 while (protocols.size() < files.size())
3319 jalview.bin.Console.debug("Adding missing FILE protocol for "
3320 + files.get(protocols.size()));
3321 protocols.add(DataSourceType.FILE);
3323 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3324 data, "\r\n"); st.hasMoreTokens();)
3327 String s = st.nextToken();
3328 if (s.startsWith("#"))
3330 // the line is a comment (as per the RFC 2483)
3333 java.net.URI uri = new java.net.URI(s);
3334 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3336 protocols.add(DataSourceType.URL);
3337 files.add(uri.toString());
3341 // otherwise preserve old behaviour: catch all for file objects
3342 java.io.File file = new java.io.File(uri);
3343 protocols.add(DataSourceType.FILE);
3344 files.add(file.toString());
3349 if (jalview.bin.Console.isDebugEnabled())
3351 if (data == null || !added)
3354 if (t.getTransferDataFlavors() != null
3355 && t.getTransferDataFlavors().length > 0)
3357 jalview.bin.Console.debug(
3358 "Couldn't resolve drop data. Here are the supported flavors:");
3359 for (DataFlavor fl : t.getTransferDataFlavors())
3361 jalview.bin.Console.debug(
3362 "Supported transfer dataflavor: " + fl.toString());
3363 Object df = t.getTransferData(fl);
3366 jalview.bin.Console.debug("Retrieves: " + df);
3370 jalview.bin.Console.debug("Retrieved nothing");
3377 .debug("Couldn't resolve dataflavor for drop: "
3383 if (Platform.isWindowsAndNotJS())
3386 .debug("Scanning dropped content for Windows Link Files");
3388 // resolve any .lnk files in the file drop
3389 for (int f = 0; f < files.size(); f++)
3391 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3392 if (protocols.get(f).equals(DataSourceType.FILE)
3393 && (source.endsWith(".lnk") || source.endsWith(".url")
3394 || source.endsWith(".site")))
3398 Object obj = files.get(f);
3399 File lf = (obj instanceof File ? (File) obj
3400 : new File((String) obj));
3401 // process link file to get a URL
3402 jalview.bin.Console.debug("Found potential link file: " + lf);
3403 WindowsShortcut wscfile = new WindowsShortcut(lf);
3404 String fullname = wscfile.getRealFilename();
3405 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3406 files.set(f, fullname);
3407 jalview.bin.Console.debug("Parsed real filename " + fullname
3408 + " to extract protocol: " + protocols.get(f));
3409 } catch (Exception ex)
3411 jalview.bin.Console.error(
3412 "Couldn't parse " + files.get(f) + " as a link file.",
3421 * Sets the Preferences property for experimental features to True or False
3422 * depending on the state of the controlling menu item
3425 protected void showExperimental_actionPerformed(boolean selected)
3427 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3431 * Answers a (possibly empty) list of any structure viewer frames (currently
3432 * for either Jmol or Chimera) which are currently open. This may optionally
3433 * be restricted to viewers of a specified class, or viewers linked to a
3434 * specified alignment panel.
3437 * if not null, only return viewers linked to this panel
3438 * @param structureViewerClass
3439 * if not null, only return viewers of this class
3442 public List<StructureViewerBase> getStructureViewers(
3443 AlignmentPanel apanel,
3444 Class<? extends StructureViewerBase> structureViewerClass)
3446 List<StructureViewerBase> result = new ArrayList<>();
3447 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3449 for (JInternalFrame frame : frames)
3451 if (frame instanceof StructureViewerBase)
3453 if (structureViewerClass == null
3454 || structureViewerClass.isInstance(frame))
3457 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3459 result.add((StructureViewerBase) frame);
3467 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3469 private static boolean debugScaleMessageDone = false;
3471 public static void debugScaleMessage(Graphics g)
3473 if (debugScaleMessageDone)
3477 // output used by tests to check HiDPI scaling settings in action
3480 Graphics2D gg = (Graphics2D) g;
3483 AffineTransform t = gg.getTransform();
3484 double scaleX = t.getScaleX();
3485 double scaleY = t.getScaleY();
3486 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3487 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3488 debugScaleMessageDone = true;
3492 jalview.bin.Console.debug("Desktop graphics null");
3494 } catch (Exception e)
3496 jalview.bin.Console.debug(Cache.getStackTraceString(e));