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.gui.QuitHandler.QResponse;
109 import jalview.io.BackupFiles;
110 import jalview.io.DataSourceType;
111 import jalview.io.FileFormat;
112 import jalview.io.FileFormatException;
113 import jalview.io.FileFormatI;
114 import jalview.io.FileFormats;
115 import jalview.io.FileLoader;
116 import jalview.io.FormatAdapter;
117 import jalview.io.IdentifyFile;
118 import jalview.io.JalviewFileChooser;
119 import jalview.io.JalviewFileView;
120 import jalview.jbgui.GSplitFrame;
121 import jalview.jbgui.GStructureViewer;
122 import jalview.project.Jalview2XML;
123 import jalview.structure.StructureSelectionManager;
124 import jalview.urls.IdOrgSettings;
125 import jalview.util.BrowserLauncher;
126 import jalview.util.ChannelProperties;
127 import jalview.util.ImageMaker.TYPE;
128 import jalview.util.LaunchUtils;
129 import jalview.util.MessageManager;
130 import jalview.util.Platform;
131 import jalview.util.ShortcutKeyMaskExWrapper;
132 import jalview.util.UrlConstants;
133 import jalview.viewmodel.AlignmentViewport;
134 import jalview.ws.params.ParamManager;
135 import jalview.ws.utils.UrlDownloadClient;
142 * @version $Revision: 1.155 $
144 public class Desktop extends jalview.jbgui.GDesktop
145 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
146 jalview.api.StructureSelectionManagerProvider
148 private static final String CITATION;
151 URL bg_logo_url = ChannelProperties.getImageURL(
152 "bg_logo." + String.valueOf(SplashScreen.logoSize));
153 URL uod_logo_url = ChannelProperties.getImageURL(
154 "uod_banner." + String.valueOf(SplashScreen.logoSize));
155 boolean logo = (bg_logo_url != null || uod_logo_url != null);
156 StringBuilder sb = new StringBuilder();
158 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
163 sb.append(bg_logo_url == null ? ""
164 : "<img alt=\"Barton Group logo\" src=\""
165 + bg_logo_url.toString() + "\">");
166 sb.append(uod_logo_url == null ? ""
167 : " <img alt=\"University of Dundee shield\" src=\""
168 + 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 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
196 public static void setLiveDragMode(boolean b)
198 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
199 : JDesktopPane.OUTLINE_DRAG_MODE;
201 desktop.setDragMode(DRAG_MODE);
204 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
206 public static boolean nosplash = false;
209 * news reader - null if it was never started.
211 private BlogReader jvnews = null;
213 private File projectFile;
217 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
219 public void addJalviewPropertyChangeListener(
220 PropertyChangeListener listener)
222 changeSupport.addJalviewPropertyChangeListener(listener);
226 * @param propertyName
228 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
229 * java.beans.PropertyChangeListener)
231 public void addJalviewPropertyChangeListener(String propertyName,
232 PropertyChangeListener listener)
234 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
238 * @param propertyName
240 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
241 * java.beans.PropertyChangeListener)
243 public void removeJalviewPropertyChangeListener(String propertyName,
244 PropertyChangeListener listener)
246 changeSupport.removeJalviewPropertyChangeListener(propertyName,
250 /** Singleton Desktop instance */
251 public static Desktop instance;
253 public static MyDesktopPane desktop;
255 public static MyDesktopPane getDesktop()
257 // BH 2018 could use currentThread() here as a reference to a
258 // Hashtable<Thread, MyDesktopPane> in JavaScript
262 static int openFrameCount = 0;
264 static final int xOffset = 30;
266 static final int yOffset = 30;
268 public static jalview.ws.jws1.Discoverer discoverer;
270 public static Object[] jalviewClipboard;
272 public static boolean internalCopy = false;
274 static int fileLoadingCount = 0;
276 class MyDesktopManager implements DesktopManager
279 private DesktopManager delegate;
281 public MyDesktopManager(DesktopManager delegate)
283 this.delegate = delegate;
287 public void activateFrame(JInternalFrame f)
291 delegate.activateFrame(f);
292 } catch (NullPointerException npe)
294 Point p = getMousePosition();
295 instance.showPasteMenu(p.x, p.y);
300 public void beginDraggingFrame(JComponent f)
302 delegate.beginDraggingFrame(f);
306 public void beginResizingFrame(JComponent f, int direction)
308 delegate.beginResizingFrame(f, direction);
312 public void closeFrame(JInternalFrame f)
314 delegate.closeFrame(f);
318 public void deactivateFrame(JInternalFrame f)
320 delegate.deactivateFrame(f);
324 public void deiconifyFrame(JInternalFrame f)
326 delegate.deiconifyFrame(f);
330 public void dragFrame(JComponent f, int newX, int newY)
336 delegate.dragFrame(f, newX, newY);
340 public void endDraggingFrame(JComponent f)
342 delegate.endDraggingFrame(f);
347 public void endResizingFrame(JComponent f)
349 delegate.endResizingFrame(f);
354 public void iconifyFrame(JInternalFrame f)
356 delegate.iconifyFrame(f);
360 public void maximizeFrame(JInternalFrame f)
362 delegate.maximizeFrame(f);
366 public void minimizeFrame(JInternalFrame f)
368 delegate.minimizeFrame(f);
372 public void openFrame(JInternalFrame f)
374 delegate.openFrame(f);
378 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
385 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
389 public void setBoundsForFrame(JComponent f, int newX, int newY,
390 int newWidth, int newHeight)
392 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
395 // All other methods, simply delegate
400 * Creates a new Desktop object.
406 * A note to implementors. It is ESSENTIAL that any activities that might
407 * block are spawned off as threads rather than waited for during this
412 doConfigureStructurePrefs();
413 setTitle(ChannelProperties.getProperty("app_name") + " "
414 + Cache.getProperty("VERSION"));
417 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
418 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
419 * officially documented or guaranteed to exist, so we access it via
420 * reflection. There appear to be unfathomable criteria about what this
421 * string can contain, and it if doesn't meet those criteria then "java"
422 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
423 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
424 * not. The reflection access may generate a warning: WARNING: An illegal
425 * reflective access operation has occurred WARNING: Illegal reflective
426 * access by jalview.gui.Desktop () to field
427 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
429 if (Platform.isLinux())
431 if (LaunchUtils.getJavaVersion() >= 11)
433 jalview.bin.Console.info(
434 "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.");
438 Toolkit xToolkit = Toolkit.getDefaultToolkit();
439 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
440 Field awtAppClassNameField = null;
442 if (Arrays.stream(declaredFields)
443 .anyMatch(f -> f.getName().equals("awtAppClassName")))
445 awtAppClassNameField = xToolkit.getClass()
446 .getDeclaredField("awtAppClassName");
449 String title = ChannelProperties.getProperty("app_name");
450 if (awtAppClassNameField != null)
452 awtAppClassNameField.setAccessible(true);
453 awtAppClassNameField.set(xToolkit, title);
457 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
459 } catch (Exception e)
461 jalview.bin.Console.debug("Error setting awtAppClassName");
462 jalview.bin.Console.trace(Cache.getStackTraceString(e));
466 setIconImages(ChannelProperties.getIconList());
468 // override quit handling when GUI OS close [X] button pressed
469 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
470 addWindowListener(new WindowAdapter()
473 public void windowClosing(WindowEvent ev)
475 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
479 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
481 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
482 desktop = new MyDesktopPane(selmemusage);
484 showMemusage.setSelected(selmemusage);
485 desktop.setBackground(Color.white);
487 getContentPane().setLayout(new BorderLayout());
488 // alternate config - have scrollbars - see notes in JAL-153
489 // JScrollPane sp = new JScrollPane();
490 // sp.getViewport().setView(desktop);
491 // getContentPane().add(sp, BorderLayout.CENTER);
493 // BH 2018 - just an experiment to try unclipped JInternalFrames.
496 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
499 getContentPane().add(desktop, BorderLayout.CENTER);
500 desktop.setDragMode(DRAG_MODE);
502 // This line prevents Windows Look&Feel resizing all new windows to maximum
503 // if previous window was maximised
504 desktop.setDesktopManager(new MyDesktopManager(
505 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
506 : Platform.isAMacAndNotJS()
507 ? new AquaInternalFrameManager(
508 desktop.getDesktopManager())
509 : desktop.getDesktopManager())));
511 Rectangle dims = getLastKnownDimensions("");
518 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
519 int xPos = Math.max(5, (screenSize.width - 900) / 2);
520 int yPos = Math.max(5, (screenSize.height - 650) / 2);
521 setBounds(xPos, yPos, 900, 650);
524 if (!Platform.isJS())
531 jconsole = new Console(this, showjconsole);
532 jconsole.setHeader(Cache.getVersionDetailsForConsole());
533 showConsole(showjconsole);
535 showNews.setVisible(false);
537 experimentalFeatures.setSelected(showExperimental());
539 getIdentifiersOrgData();
543 // Spawn a thread that shows the splashscreen
546 SwingUtilities.invokeLater(new Runnable()
551 new SplashScreen(true);
556 // Thread off a new instance of the file chooser - this reduces the time
558 // takes to open it later on.
559 new Thread(new Runnable()
564 jalview.bin.Console.debug("Filechooser init thread started.");
565 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
566 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
568 jalview.bin.Console.debug("Filechooser init thread finished.");
571 // Add the service change listener
572 changeSupport.addJalviewPropertyChangeListener("services",
573 new PropertyChangeListener()
577 public void propertyChange(PropertyChangeEvent evt)
580 .debug("Firing service changed event for "
581 + evt.getNewValue());
582 JalviewServicesChanged(evt);
587 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
590 this.addMouseListener(ma = new MouseAdapter()
593 public void mousePressed(MouseEvent evt)
595 if (evt.isPopupTrigger()) // Mac
597 showPasteMenu(evt.getX(), evt.getY());
602 public void mouseReleased(MouseEvent evt)
604 if (evt.isPopupTrigger()) // Windows
606 showPasteMenu(evt.getX(), evt.getY());
610 desktop.addMouseListener(ma);
614 * Answers true if user preferences to enable experimental features is True
619 public boolean showExperimental()
621 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
622 Boolean.FALSE.toString());
623 return Boolean.valueOf(experimental).booleanValue();
626 public void doConfigureStructurePrefs()
628 // configure services
629 StructureSelectionManager ssm = StructureSelectionManager
630 .getStructureSelectionManager(this);
631 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
633 ssm.setAddTempFacAnnot(
634 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
635 ssm.setProcessSecondaryStructure(
636 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
637 // JAL-3915 - RNAView is no longer an option so this has no effect
638 ssm.setSecStructServices(
639 Cache.getDefault(Preferences.USE_RNAVIEW, false));
643 ssm.setAddTempFacAnnot(false);
644 ssm.setProcessSecondaryStructure(false);
645 ssm.setSecStructServices(false);
649 public void checkForNews()
651 final Desktop me = this;
652 // Thread off the news reader, in case there are connection problems.
653 new Thread(new Runnable()
658 jalview.bin.Console.debug("Starting news thread.");
659 jvnews = new BlogReader(me);
660 showNews.setVisible(true);
661 jalview.bin.Console.debug("Completed news thread.");
666 public void getIdentifiersOrgData()
668 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
669 {// Thread off the identifiers fetcher
670 new Thread(new Runnable()
676 .debug("Downloading data from identifiers.org");
679 UrlDownloadClient.download(IdOrgSettings.getUrl(),
680 IdOrgSettings.getDownloadLocation());
681 } catch (IOException e)
684 .debug("Exception downloading identifiers.org data"
694 protected void showNews_actionPerformed(ActionEvent e)
696 showNews(showNews.isSelected());
699 void showNews(boolean visible)
701 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
702 showNews.setSelected(visible);
703 if (visible && !jvnews.isVisible())
705 new Thread(new Runnable()
710 long now = System.currentTimeMillis();
711 Desktop.instance.setProgressBar(
712 MessageManager.getString("status.refreshing_news"), now);
713 jvnews.refreshNews();
714 Desktop.instance.setProgressBar(null, now);
722 * recover the last known dimensions for a jalview window
725 * - empty string is desktop, all other windows have unique prefix
726 * @return null or last known dimensions scaled to current geometry (if last
727 * window geom was known)
729 Rectangle getLastKnownDimensions(String windowName)
731 // TODO: lock aspect ratio for scaling desktop Bug #0058199
732 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
733 String x = Cache.getProperty(windowName + "SCREEN_X");
734 String y = Cache.getProperty(windowName + "SCREEN_Y");
735 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
736 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
737 if ((x != null) && (y != null) && (width != null) && (height != null))
739 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
740 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
741 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
743 // attempt #1 - try to cope with change in screen geometry - this
744 // version doesn't preserve original jv aspect ratio.
745 // take ratio of current screen size vs original screen size.
746 double sw = ((1f * screenSize.width) / (1f * Integer
747 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
748 double sh = ((1f * screenSize.height) / (1f * Integer
749 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
750 // rescale the bounds depending upon the current screen geometry.
751 ix = (int) (ix * sw);
752 iw = (int) (iw * sw);
753 iy = (int) (iy * sh);
754 ih = (int) (ih * sh);
755 while (ix >= screenSize.width)
757 jalview.bin.Console.debug(
758 "Window geometry location recall error: shifting horizontal to within screenbounds.");
759 ix -= screenSize.width;
761 while (iy >= screenSize.height)
763 jalview.bin.Console.debug(
764 "Window geometry location recall error: shifting vertical to within screenbounds.");
765 iy -= screenSize.height;
767 jalview.bin.Console.debug(
768 "Got last known dimensions for " + windowName + ": x:" + ix
769 + " y:" + iy + " width:" + iw + " height:" + ih);
771 // return dimensions for new instance
772 return new Rectangle(ix, iy, iw, ih);
777 void showPasteMenu(int x, int y)
779 JPopupMenu popup = new JPopupMenu();
780 JMenuItem item = new JMenuItem(
781 MessageManager.getString("label.paste_new_window"));
782 item.addActionListener(new ActionListener()
785 public void actionPerformed(ActionEvent evt)
792 popup.show(this, x, y);
799 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
800 Transferable contents = c.getContents(this);
802 if (contents != null)
804 String file = (String) contents
805 .getTransferData(DataFlavor.stringFlavor);
807 FileFormatI format = new IdentifyFile().identify(file,
808 DataSourceType.PASTE);
810 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
813 } catch (Exception ex)
816 "Unable to paste alignment from system clipboard:\n" + ex);
821 * Adds and opens the given frame to the desktop
832 public static synchronized void addInternalFrame(
833 final JInternalFrame frame, String title, int w, int h)
835 addInternalFrame(frame, title, true, w, h, true, false);
839 * Add an internal frame to the Jalview desktop
846 * When true, display frame immediately, otherwise, caller must call
847 * setVisible themselves.
853 public static synchronized void addInternalFrame(
854 final JInternalFrame frame, String title, boolean makeVisible,
857 addInternalFrame(frame, title, makeVisible, w, h, true, false);
861 * Add an internal frame to the Jalview desktop and make it visible
874 public static synchronized void addInternalFrame(
875 final JInternalFrame frame, String title, int w, int h,
878 addInternalFrame(frame, title, true, w, h, resizable, false);
882 * Add an internal frame to the Jalview desktop
889 * When true, display frame immediately, otherwise, caller must call
890 * setVisible themselves.
897 * @param ignoreMinSize
898 * Do not set the default minimum size for frame
900 public static synchronized void addInternalFrame(
901 final JInternalFrame frame, String title, boolean makeVisible,
902 int w, int h, boolean resizable, boolean ignoreMinSize)
905 // TODO: allow callers to determine X and Y position of frame (eg. via
907 // TODO: consider fixing method to update entries in the window submenu with
908 // the current window title
910 frame.setTitle(title);
911 if (frame.getWidth() < 1 || frame.getHeight() < 1)
915 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
916 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
917 // IF JALVIEW IS RUNNING HEADLESS
918 // ///////////////////////////////////////////////
919 if (instance == null || (System.getProperty("java.awt.headless") != null
920 && System.getProperty("java.awt.headless").equals("true")))
929 frame.setMinimumSize(
930 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
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));
943 frame.setVisible(makeVisible);
944 frame.setClosable(true);
945 frame.setResizable(resizable);
946 frame.setMaximizable(resizable);
947 frame.setIconifiable(resizable);
948 frame.setOpaque(Platform.isJS());
950 if (frame.getX() < 1 && frame.getY() < 1)
952 frame.setLocation(xOffset * openFrameCount,
953 yOffset * ((openFrameCount - 1) % 10) + yOffset);
957 * add an entry for the new frame in the Window menu (and remove it when the
960 final JMenuItem menuItem = new JMenuItem(title);
961 frame.addInternalFrameListener(new InternalFrameAdapter()
964 public void internalFrameActivated(InternalFrameEvent evt)
966 JInternalFrame itf = desktop.getSelectedFrame();
969 if (itf instanceof AlignFrame)
971 Jalview.setCurrentAlignFrame((AlignFrame) itf);
978 public void internalFrameClosed(InternalFrameEvent evt)
980 PaintRefresher.RemoveComponent(frame);
983 * defensive check to prevent frames being added half off the window
985 if (openFrameCount > 0)
991 * ensure no reference to alignFrame retained by menu item listener
993 if (menuItem.getActionListeners().length > 0)
995 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
997 windowMenu.remove(menuItem);
1001 menuItem.addActionListener(new ActionListener()
1004 public void actionPerformed(ActionEvent e)
1008 frame.setSelected(true);
1009 frame.setIcon(false);
1010 } catch (java.beans.PropertyVetoException ex)
1017 setKeyBindings(frame);
1021 windowMenu.add(menuItem);
1026 frame.setSelected(true);
1027 frame.requestFocus();
1028 } catch (java.beans.PropertyVetoException ve)
1030 } catch (java.lang.ClassCastException cex)
1032 jalview.bin.Console.warn(
1033 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1039 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1044 private static void setKeyBindings(JInternalFrame frame)
1046 @SuppressWarnings("serial")
1047 final Action closeAction = new AbstractAction()
1050 public void actionPerformed(ActionEvent e)
1057 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1059 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1060 InputEvent.CTRL_DOWN_MASK);
1061 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1062 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1064 InputMap inputMap = frame
1065 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1066 String ctrlW = ctrlWKey.toString();
1067 inputMap.put(ctrlWKey, ctrlW);
1068 inputMap.put(cmdWKey, ctrlW);
1070 ActionMap actionMap = frame.getActionMap();
1071 actionMap.put(ctrlW, closeAction);
1075 public void lostOwnership(Clipboard clipboard, Transferable contents)
1079 Desktop.jalviewClipboard = null;
1082 internalCopy = false;
1086 public void dragEnter(DropTargetDragEvent evt)
1091 public void dragExit(DropTargetEvent evt)
1096 public void dragOver(DropTargetDragEvent evt)
1101 public void dropActionChanged(DropTargetDragEvent evt)
1112 public void drop(DropTargetDropEvent evt)
1114 boolean success = true;
1115 // JAL-1552 - acceptDrop required before getTransferable call for
1116 // Java's Transferable for native dnd
1117 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1118 Transferable t = evt.getTransferable();
1119 List<Object> files = new ArrayList<>();
1120 List<DataSourceType> protocols = new ArrayList<>();
1124 Desktop.transferFromDropTarget(files, protocols, evt, t);
1125 } catch (Exception e)
1127 e.printStackTrace();
1135 for (int i = 0; i < files.size(); i++)
1137 // BH 2018 File or String
1138 Object file = files.get(i);
1139 String fileName = file.toString();
1140 DataSourceType protocol = (protocols == null)
1141 ? DataSourceType.FILE
1143 FileFormatI format = null;
1145 if (fileName.endsWith(".jar"))
1147 format = FileFormat.Jalview;
1152 format = new IdentifyFile().identify(file, protocol);
1154 if (file instanceof File)
1156 Platform.cacheFileData((File) file);
1158 new FileLoader().LoadFile(null, file, protocol, format);
1161 } catch (Exception ex)
1166 evt.dropComplete(success); // need this to ensure input focus is properly
1167 // transfered to any new windows created
1177 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1179 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1180 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1181 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1182 BackupFiles.getEnabled());
1184 chooser.setFileView(new JalviewFileView());
1185 chooser.setDialogTitle(
1186 MessageManager.getString("label.open_local_file"));
1187 chooser.setToolTipText(MessageManager.getString("action.open"));
1189 chooser.setResponseHandler(0, () -> {
1190 File selectedFile = chooser.getSelectedFile();
1191 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1193 FileFormatI format = chooser.getSelectedFormat();
1196 * Call IdentifyFile to verify the file contains what its extension implies.
1197 * Skip this step for dynamically added file formats, because IdentifyFile does
1198 * not know how to recognise them.
1200 if (FileFormats.getInstance().isIdentifiable(format))
1204 format = new IdentifyFile().identify(selectedFile,
1205 DataSourceType.FILE);
1206 } catch (FileFormatException e)
1208 // format = null; //??
1212 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1216 chooser.showOpenDialog(this);
1220 * Shows a dialog for input of a URL at which to retrieve alignment data
1225 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1227 // This construct allows us to have a wider textfield
1229 JLabel label = new JLabel(
1230 MessageManager.getString("label.input_file_url"));
1232 JPanel panel = new JPanel(new GridLayout(2, 1));
1236 * the URL to fetch is input in Java: an editable combobox with history JS:
1237 * (pending JAL-3038) a plain text field
1240 String urlBase = "https://www.";
1241 if (Platform.isJS())
1243 history = new JTextField(urlBase, 35);
1252 JComboBox<String> asCombo = new JComboBox<>();
1253 asCombo.setPreferredSize(new Dimension(400, 20));
1254 asCombo.setEditable(true);
1255 asCombo.addItem(urlBase);
1256 String historyItems = Cache.getProperty("RECENT_URL");
1257 if (historyItems != null)
1259 for (String token : historyItems.split("\\t"))
1261 asCombo.addItem(token);
1268 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1269 MessageManager.getString("action.cancel") };
1270 Callable<Void> action = () -> {
1271 @SuppressWarnings("unchecked")
1272 String url = (history instanceof JTextField
1273 ? ((JTextField) history).getText()
1274 : ((JComboBox<String>) history).getEditor().getItem()
1275 .toString().trim());
1277 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1279 if (viewport != null)
1281 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1282 FileFormat.Jalview);
1286 new FileLoader().LoadFile(url, DataSourceType.URL,
1287 FileFormat.Jalview);
1292 FileFormatI format = null;
1295 format = new IdentifyFile().identify(url, DataSourceType.URL);
1296 } catch (FileFormatException e)
1298 // TODO revise error handling, distinguish between
1299 // URL not found and response not valid
1304 String msg = MessageManager.formatMessage("label.couldnt_locate",
1306 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1307 MessageManager.getString("label.url_not_found"),
1308 JvOptionPane.WARNING_MESSAGE);
1310 return null; // Void
1313 if (viewport != null)
1315 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1320 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1323 return null; // Void
1325 String dialogOption = MessageManager
1326 .getString("label.input_alignment_from_url");
1327 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1328 .showInternalDialog(panel, dialogOption,
1329 JvOptionPane.YES_NO_CANCEL_OPTION,
1330 JvOptionPane.PLAIN_MESSAGE, null, options,
1331 MessageManager.getString("action.ok"));
1335 * Opens the CutAndPaste window for the user to paste an alignment in to
1338 * - if not null, the pasted alignment is added to the current
1339 * alignment; if null, to a new alignment window
1342 public void inputTextboxMenuItem_actionPerformed(
1343 AlignmentViewPanel viewPanel)
1345 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1346 cap.setForInput(viewPanel);
1347 Desktop.addInternalFrame(cap,
1348 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1353 * Check with user and saving files before actually quitting
1355 public void desktopQuit()
1357 desktopQuit(true, false);
1360 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1362 final Callable<Void> doDesktopQuit = () -> {
1363 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1364 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1365 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1366 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1367 getBounds().y, getWidth(), getHeight()));
1369 if (jconsole != null)
1371 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1372 jconsole.stopConsole();
1377 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1381 if (dialogExecutor != null)
1383 dialogExecutor.shutdownNow();
1386 closeAll_actionPerformed(null);
1388 if (groovyConsole != null)
1390 // suppress a possible repeat prompt to save script
1391 groovyConsole.setDirty(false);
1392 groovyConsole.exit();
1395 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1397 // note that shutdown hook will not be run
1398 jalview.bin.Console.debug("Force Quit selected by user");
1399 Runtime.getRuntime().halt(0);
1402 jalview.bin.Console.debug("Quit selected by user");
1405 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1406 // instance.dispose();
1410 return null; // Void
1413 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1414 QuitHandler.defaultCancelQuit);
1418 * Don't call this directly, use desktopQuit() above. Exits the program.
1423 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1424 // not run a second time if gotQuitResponse flag has been set (i.e. user
1425 // confirmed quit of some kind).
1429 private void storeLastKnownDimensions(String string, Rectangle jc)
1431 jalview.bin.Console.debug("Storing last known dimensions for " + string
1432 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1433 + " height:" + jc.height);
1435 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1436 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1437 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1438 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1448 public void aboutMenuItem_actionPerformed(ActionEvent e)
1450 new Thread(new Runnable()
1455 new SplashScreen(false);
1461 * Returns the html text for the About screen, including any available version
1462 * number, build details, author details and citation reference, but without
1463 * the enclosing {@code html} tags
1467 public String getAboutMessage()
1469 StringBuilder message = new StringBuilder(1024);
1470 message.append("<div style=\"font-family: sans-serif;\">")
1471 .append("<h1><strong>Version: ")
1472 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1473 .append("<strong>Built: <em>")
1474 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1475 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1476 .append("</strong>");
1478 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1479 if (latestVersion.equals("Checking"))
1481 // JBP removed this message for 2.11: May be reinstated in future version
1482 // message.append("<br>...Checking latest version...</br>");
1484 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1486 boolean red = false;
1487 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1488 .indexOf("automated build") == -1)
1491 // Displayed when code version and jnlp version do not match and code
1492 // version is not a development build
1493 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1496 message.append("<br>!! Version ")
1497 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1498 .append(" is available for download from ")
1499 .append(Cache.getDefault("www.jalview.org",
1500 "https://www.jalview.org"))
1504 message.append("</div>");
1507 message.append("<br>Authors: ");
1508 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1509 message.append(CITATION);
1511 message.append("</div>");
1513 return message.toString();
1517 * Action on requesting Help documentation
1520 public void documentationMenuItem_actionPerformed()
1524 if (Platform.isJS())
1526 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1535 Help.showHelpWindow();
1537 } catch (Exception ex)
1539 System.err.println("Error opening help: " + ex.getMessage());
1544 public void closeAll_actionPerformed(ActionEvent e)
1546 // TODO show a progress bar while closing?
1547 JInternalFrame[] frames = desktop.getAllFrames();
1548 for (int i = 0; i < frames.length; i++)
1552 frames[i].setClosed(true);
1553 } catch (java.beans.PropertyVetoException ex)
1557 Jalview.setCurrentAlignFrame(null);
1558 System.out.println("ALL CLOSED");
1561 * reset state of singleton objects as appropriate (clear down session state
1562 * when all windows are closed)
1564 StructureSelectionManager ssm = StructureSelectionManager
1565 .getStructureSelectionManager(this);
1573 public void raiseRelated_actionPerformed(ActionEvent e)
1575 reorderAssociatedWindows(false, false);
1579 public void minimizeAssociated_actionPerformed(ActionEvent e)
1581 reorderAssociatedWindows(true, false);
1584 void closeAssociatedWindows()
1586 reorderAssociatedWindows(false, true);
1592 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1596 protected void garbageCollect_actionPerformed(ActionEvent e)
1598 // We simply collect the garbage
1599 jalview.bin.Console.debug("Collecting garbage...");
1601 jalview.bin.Console.debug("Finished garbage collection.");
1607 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1611 protected void showMemusage_actionPerformed(ActionEvent e)
1613 desktop.showMemoryUsage(showMemusage.isSelected());
1620 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1624 protected void showConsole_actionPerformed(ActionEvent e)
1626 showConsole(showConsole.isSelected());
1629 Console jconsole = null;
1632 * control whether the java console is visible or not
1636 void showConsole(boolean selected)
1638 // TODO: decide if we should update properties file
1639 if (jconsole != null) // BH 2018
1641 showConsole.setSelected(selected);
1642 Cache.setProperty("SHOW_JAVA_CONSOLE",
1643 Boolean.valueOf(selected).toString());
1644 jconsole.setVisible(selected);
1648 void reorderAssociatedWindows(boolean minimize, boolean close)
1650 JInternalFrame[] frames = desktop.getAllFrames();
1651 if (frames == null || frames.length < 1)
1656 AlignmentViewport source = null, target = null;
1657 if (frames[0] instanceof AlignFrame)
1659 source = ((AlignFrame) frames[0]).getCurrentView();
1661 else if (frames[0] instanceof TreePanel)
1663 source = ((TreePanel) frames[0]).getViewPort();
1665 else if (frames[0] instanceof PCAPanel)
1667 source = ((PCAPanel) frames[0]).av;
1669 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1671 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1676 for (int i = 0; i < frames.length; i++)
1679 if (frames[i] == null)
1683 if (frames[i] instanceof AlignFrame)
1685 target = ((AlignFrame) frames[i]).getCurrentView();
1687 else if (frames[i] instanceof TreePanel)
1689 target = ((TreePanel) frames[i]).getViewPort();
1691 else if (frames[i] instanceof PCAPanel)
1693 target = ((PCAPanel) frames[i]).av;
1695 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1697 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1700 if (source == target)
1706 frames[i].setClosed(true);
1710 frames[i].setIcon(minimize);
1713 frames[i].toFront();
1717 } catch (java.beans.PropertyVetoException ex)
1732 protected void preferences_actionPerformed(ActionEvent e)
1734 Preferences.openPreferences();
1738 * Prompts the user to choose a file and then saves the Jalview state as a
1739 * Jalview project file
1742 public void saveState_actionPerformed()
1744 saveState_actionPerformed(false);
1747 public void saveState_actionPerformed(boolean saveAs)
1749 java.io.File projectFile = getProjectFile();
1750 // autoSave indicates we already have a file and don't need to ask
1751 boolean autoSave = projectFile != null && !saveAs
1752 && BackupFiles.getEnabled();
1754 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1755 // saveAs="+saveAs+", Backups
1756 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1758 boolean approveSave = false;
1761 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1764 chooser.setFileView(new JalviewFileView());
1765 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1767 int value = chooser.showSaveDialog(this);
1769 if (value == JalviewFileChooser.APPROVE_OPTION)
1771 projectFile = chooser.getSelectedFile();
1772 setProjectFile(projectFile);
1777 if (approveSave || autoSave)
1779 final Desktop me = this;
1780 final java.io.File chosenFile = projectFile;
1781 new Thread(new Runnable()
1786 // TODO: refactor to Jalview desktop session controller action.
1787 setProgressBar(MessageManager.formatMessage(
1788 "label.saving_jalview_project", new Object[]
1789 { chosenFile.getName() }), chosenFile.hashCode());
1790 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1791 // TODO catch and handle errors for savestate
1792 // TODO prevent user from messing with the Desktop whilst we're saving
1795 boolean doBackup = BackupFiles.getEnabled();
1796 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1799 new Jalview2XML().saveState(
1800 doBackup ? backupfiles.getTempFile() : chosenFile);
1804 backupfiles.setWriteSuccess(true);
1805 backupfiles.rollBackupsAndRenameTempFile();
1807 } catch (OutOfMemoryError oom)
1809 new OOMWarning("Whilst saving current state to "
1810 + chosenFile.getName(), oom);
1811 } catch (Exception ex)
1813 jalview.bin.Console.error("Problems whilst trying to save to "
1814 + chosenFile.getName(), ex);
1815 JvOptionPane.showMessageDialog(me,
1816 MessageManager.formatMessage(
1817 "label.error_whilst_saving_current_state_to",
1819 { chosenFile.getName() }),
1820 MessageManager.getString("label.couldnt_save_project"),
1821 JvOptionPane.WARNING_MESSAGE);
1823 setProgressBar(null, chosenFile.hashCode());
1830 public void saveAsState_actionPerformed(ActionEvent e)
1832 saveState_actionPerformed(true);
1835 protected void setProjectFile(File choice)
1837 this.projectFile = choice;
1840 public File getProjectFile()
1842 return this.projectFile;
1846 * Shows a file chooser dialog and tries to read in the selected file as a
1850 public void loadState_actionPerformed()
1852 final String[] suffix = new String[] { "jvp", "jar" };
1853 final String[] desc = new String[] { "Jalview Project",
1854 "Jalview Project (old)" };
1855 JalviewFileChooser chooser = new JalviewFileChooser(
1856 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1857 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1861 chooser.setFileView(new JalviewFileView());
1862 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1863 chooser.setResponseHandler(0, () -> {
1864 File selectedFile = chooser.getSelectedFile();
1865 setProjectFile(selectedFile);
1866 String choice = selectedFile.getAbsolutePath();
1867 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1868 new Thread(new Runnable()
1875 new Jalview2XML().loadJalviewAlign(selectedFile);
1876 } catch (OutOfMemoryError oom)
1878 new OOMWarning("Whilst loading project from " + choice, oom);
1879 } catch (Exception ex)
1881 jalview.bin.Console.error(
1882 "Problems whilst loading project from " + choice, ex);
1883 JvOptionPane.showMessageDialog(Desktop.desktop,
1884 MessageManager.formatMessage(
1885 "label.error_whilst_loading_project_from",
1888 MessageManager.getString("label.couldnt_load_project"),
1889 JvOptionPane.WARNING_MESSAGE);
1892 }, "Project Loader").start();
1896 chooser.showOpenDialog(this);
1900 public void inputSequence_actionPerformed(ActionEvent e)
1902 new SequenceFetcher(this);
1905 JPanel progressPanel;
1907 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1909 public void startLoading(final Object fileName)
1911 if (fileLoadingCount == 0)
1913 fileLoadingPanels.add(addProgressPanel(MessageManager
1914 .formatMessage("label.loading_file", new Object[]
1920 private JPanel addProgressPanel(String string)
1922 if (progressPanel == null)
1924 progressPanel = new JPanel(new GridLayout(1, 1));
1925 totalProgressCount = 0;
1926 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1928 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1929 JProgressBar progressBar = new JProgressBar();
1930 progressBar.setIndeterminate(true);
1932 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1934 thisprogress.add(progressBar, BorderLayout.CENTER);
1935 progressPanel.add(thisprogress);
1936 ((GridLayout) progressPanel.getLayout()).setRows(
1937 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1938 ++totalProgressCount;
1939 instance.validate();
1940 return thisprogress;
1943 int totalProgressCount = 0;
1945 private void removeProgressPanel(JPanel progbar)
1947 if (progressPanel != null)
1949 synchronized (progressPanel)
1951 progressPanel.remove(progbar);
1952 GridLayout gl = (GridLayout) progressPanel.getLayout();
1953 gl.setRows(gl.getRows() - 1);
1954 if (--totalProgressCount < 1)
1956 this.getContentPane().remove(progressPanel);
1957 progressPanel = null;
1964 public void stopLoading()
1967 if (fileLoadingCount < 1)
1969 while (fileLoadingPanels.size() > 0)
1971 removeProgressPanel(fileLoadingPanels.remove(0));
1973 fileLoadingPanels.clear();
1974 fileLoadingCount = 0;
1979 public static int getViewCount(String alignmentId)
1981 AlignmentViewport[] aps = getViewports(alignmentId);
1982 return (aps == null) ? 0 : aps.length;
1987 * @param alignmentId
1988 * - if null, all sets are returned
1989 * @return all AlignmentPanels concerning the alignmentId sequence set
1991 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1993 if (Desktop.desktop == null)
1995 // no frames created and in headless mode
1996 // TODO: verify that frames are recoverable when in headless mode
1999 List<AlignmentPanel> aps = new ArrayList<>();
2000 AlignFrame[] frames = getAlignFrames();
2005 for (AlignFrame af : frames)
2007 for (AlignmentPanel ap : af.alignPanels)
2009 if (alignmentId == null
2010 || alignmentId.equals(ap.av.getSequenceSetId()))
2016 if (aps.size() == 0)
2020 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2025 * get all the viewports on an alignment.
2027 * @param sequenceSetId
2028 * unique alignment id (may be null - all viewports returned in that
2030 * @return all viewports on the alignment bound to sequenceSetId
2032 public static AlignmentViewport[] getViewports(String sequenceSetId)
2034 List<AlignmentViewport> viewp = new ArrayList<>();
2035 if (desktop != null)
2037 AlignFrame[] frames = Desktop.getAlignFrames();
2039 for (AlignFrame afr : frames)
2041 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2042 .equals(sequenceSetId))
2044 if (afr.alignPanels != null)
2046 for (AlignmentPanel ap : afr.alignPanels)
2048 if (sequenceSetId == null
2049 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2057 viewp.add(afr.getViewport());
2061 if (viewp.size() > 0)
2063 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2070 * Explode the views in the given frame into separate AlignFrame
2074 public static void explodeViews(AlignFrame af)
2076 int size = af.alignPanels.size();
2082 // FIXME: ideally should use UI interface API
2083 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2084 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2085 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2086 for (int i = 0; i < size; i++)
2088 AlignmentPanel ap = af.alignPanels.get(i);
2090 AlignFrame newaf = new AlignFrame(ap);
2092 // transfer reference for existing feature settings to new alignFrame
2093 if (ap == af.alignPanel)
2095 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2097 newaf.featureSettings = viewFeatureSettings;
2099 newaf.setFeatureSettingsGeometry(fsBounds);
2103 * Restore the view's last exploded frame geometry if known. Multiple views from
2104 * one exploded frame share and restore the same (frame) position and size.
2106 Rectangle geometry = ap.av.getExplodedGeometry();
2107 if (geometry != null)
2109 newaf.setBounds(geometry);
2112 ap.av.setGatherViewsHere(false);
2114 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2115 AlignFrame.DEFAULT_HEIGHT);
2116 // and materialise a new feature settings dialog instance for the new
2118 // (closes the old as if 'OK' was pressed)
2119 if (ap == af.alignPanel && newaf.featureSettings != null
2120 && newaf.featureSettings.isOpen()
2121 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2123 newaf.showFeatureSettingsUI();
2127 af.featureSettings = null;
2128 af.alignPanels.clear();
2129 af.closeMenuItem_actionPerformed(true);
2134 * Gather expanded views (separate AlignFrame's) with the same sequence set
2135 * identifier back in to this frame as additional views, and close the
2136 * expanded views. Note the expanded frames may themselves have multiple
2137 * views. We take the lot.
2141 public void gatherViews(AlignFrame source)
2143 source.viewport.setGatherViewsHere(true);
2144 source.viewport.setExplodedGeometry(source.getBounds());
2145 JInternalFrame[] frames = desktop.getAllFrames();
2146 String viewId = source.viewport.getSequenceSetId();
2147 for (int t = 0; t < frames.length; t++)
2149 if (frames[t] instanceof AlignFrame && frames[t] != source)
2151 AlignFrame af = (AlignFrame) frames[t];
2152 boolean gatherThis = false;
2153 for (int a = 0; a < af.alignPanels.size(); a++)
2155 AlignmentPanel ap = af.alignPanels.get(a);
2156 if (viewId.equals(ap.av.getSequenceSetId()))
2159 ap.av.setGatherViewsHere(false);
2160 ap.av.setExplodedGeometry(af.getBounds());
2161 source.addAlignmentPanel(ap, false);
2167 if (af.featureSettings != null && af.featureSettings.isOpen())
2169 if (source.featureSettings == null)
2171 // preserve the feature settings geometry for this frame
2172 source.featureSettings = af.featureSettings;
2173 source.setFeatureSettingsGeometry(
2174 af.getFeatureSettingsGeometry());
2178 // close it and forget
2179 af.featureSettings.close();
2182 af.alignPanels.clear();
2183 af.closeMenuItem_actionPerformed(true);
2188 // refresh the feature setting UI for the source frame if it exists
2189 if (source.featureSettings != null && source.featureSettings.isOpen())
2191 source.showFeatureSettingsUI();
2196 public JInternalFrame[] getAllFrames()
2198 return desktop.getAllFrames();
2202 * Checks the given url to see if it gives a response indicating that the user
2203 * should be informed of a new questionnaire.
2207 public void checkForQuestionnaire(String url)
2209 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2210 // javax.swing.SwingUtilities.invokeLater(jvq);
2211 new Thread(jvq).start();
2214 public void checkURLLinks()
2216 // Thread off the URL link checker
2217 addDialogThread(new Runnable()
2222 if (Cache.getDefault("CHECKURLLINKS", true))
2224 // check what the actual links are - if it's just the default don't
2225 // bother with the warning
2226 List<String> links = Preferences.sequenceUrlLinks
2229 // only need to check links if there is one with a
2230 // SEQUENCE_ID which is not the default EMBL_EBI link
2231 ListIterator<String> li = links.listIterator();
2232 boolean check = false;
2233 List<JLabel> urls = new ArrayList<>();
2234 while (li.hasNext())
2236 String link = li.next();
2237 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2238 && !UrlConstants.isDefaultString(link))
2241 int barPos = link.indexOf("|");
2242 String urlMsg = barPos == -1 ? link
2243 : link.substring(0, barPos) + ": "
2244 + link.substring(barPos + 1);
2245 urls.add(new JLabel(urlMsg));
2253 // ask user to check in case URL links use old style tokens
2254 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2255 JPanel msgPanel = new JPanel();
2256 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2257 msgPanel.add(Box.createVerticalGlue());
2258 JLabel msg = new JLabel(MessageManager
2259 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2260 JLabel msg2 = new JLabel(MessageManager
2261 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2263 for (JLabel url : urls)
2269 final JCheckBox jcb = new JCheckBox(
2270 MessageManager.getString("label.do_not_display_again"));
2271 jcb.addActionListener(new ActionListener()
2274 public void actionPerformed(ActionEvent e)
2276 // update Cache settings for "don't show this again"
2277 boolean showWarningAgain = !jcb.isSelected();
2278 Cache.setProperty("CHECKURLLINKS",
2279 Boolean.valueOf(showWarningAgain).toString());
2284 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2286 .getString("label.SEQUENCE_ID_no_longer_used"),
2287 JvOptionPane.WARNING_MESSAGE);
2294 * Proxy class for JDesktopPane which optionally displays the current memory
2295 * usage and highlights the desktop area with a red bar if free memory runs
2300 public class MyDesktopPane extends JDesktopPane implements Runnable
2302 private static final float ONE_MB = 1048576f;
2304 boolean showMemoryUsage = false;
2308 java.text.NumberFormat df;
2310 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2313 public MyDesktopPane(boolean showMemoryUsage)
2315 showMemoryUsage(showMemoryUsage);
2318 public void showMemoryUsage(boolean showMemory)
2320 this.showMemoryUsage = showMemory;
2323 Thread worker = new Thread(this);
2329 public boolean isShowMemoryUsage()
2331 return showMemoryUsage;
2337 df = java.text.NumberFormat.getNumberInstance();
2338 df.setMaximumFractionDigits(2);
2339 runtime = Runtime.getRuntime();
2341 while (showMemoryUsage)
2345 maxMemory = runtime.maxMemory() / ONE_MB;
2346 allocatedMemory = runtime.totalMemory() / ONE_MB;
2347 freeMemory = runtime.freeMemory() / ONE_MB;
2348 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2350 percentUsage = (totalFreeMemory / maxMemory) * 100;
2352 // if (percentUsage < 20)
2354 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2356 // instance.set.setBorder(border1);
2359 // sleep after showing usage
2361 } catch (Exception ex)
2363 ex.printStackTrace();
2369 public void paintComponent(Graphics g)
2371 if (showMemoryUsage && g != null && df != null)
2373 if (percentUsage < 20)
2375 g.setColor(Color.red);
2377 FontMetrics fm = g.getFontMetrics();
2380 g.drawString(MessageManager.formatMessage("label.memory_stats",
2382 { df.format(totalFreeMemory), df.format(maxMemory),
2383 df.format(percentUsage) }),
2384 10, getHeight() - fm.getHeight());
2388 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2389 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2394 * Accessor method to quickly get all the AlignmentFrames loaded.
2396 * @return an array of AlignFrame, or null if none found
2398 public static AlignFrame[] getAlignFrames()
2400 if (Jalview.isHeadlessMode())
2402 // Desktop.desktop is null in headless mode
2403 return new AlignFrame[] { Jalview.currentAlignFrame };
2406 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2412 List<AlignFrame> avp = new ArrayList<>();
2414 for (int i = frames.length - 1; i > -1; i--)
2416 if (frames[i] instanceof AlignFrame)
2418 avp.add((AlignFrame) frames[i]);
2420 else if (frames[i] instanceof SplitFrame)
2423 * Also check for a split frame containing an AlignFrame
2425 GSplitFrame sf = (GSplitFrame) frames[i];
2426 if (sf.getTopFrame() instanceof AlignFrame)
2428 avp.add((AlignFrame) sf.getTopFrame());
2430 if (sf.getBottomFrame() instanceof AlignFrame)
2432 avp.add((AlignFrame) sf.getBottomFrame());
2436 if (avp.size() == 0)
2440 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2445 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2449 public GStructureViewer[] getJmols()
2451 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2457 List<GStructureViewer> avp = new ArrayList<>();
2459 for (int i = frames.length - 1; i > -1; i--)
2461 if (frames[i] instanceof AppJmol)
2463 GStructureViewer af = (GStructureViewer) frames[i];
2467 if (avp.size() == 0)
2471 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2476 * Add Groovy Support to Jalview
2479 public void groovyShell_actionPerformed()
2483 openGroovyConsole();
2484 } catch (Exception ex)
2486 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2487 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2489 MessageManager.getString("label.couldnt_create_groovy_shell"),
2490 MessageManager.getString("label.groovy_support_failed"),
2491 JvOptionPane.ERROR_MESSAGE);
2496 * Open the Groovy console
2498 void openGroovyConsole()
2500 if (groovyConsole == null)
2502 groovyConsole = new groovy.ui.Console();
2503 groovyConsole.setVariable("Jalview", this);
2504 groovyConsole.run();
2507 * We allow only one console at a time, so that AlignFrame menu option
2508 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2509 * enable 'Run script', when the console is opened, and the reverse when it is
2512 Window window = (Window) groovyConsole.getFrame();
2513 window.addWindowListener(new WindowAdapter()
2516 public void windowClosed(WindowEvent e)
2519 * rebind CMD-Q from Groovy Console to Jalview Quit
2522 enableExecuteGroovy(false);
2528 * show Groovy console window (after close and reopen)
2530 ((Window) groovyConsole.getFrame()).setVisible(true);
2533 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2534 * opening a second console
2536 enableExecuteGroovy(true);
2540 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2541 * binding when opened
2543 protected void addQuitHandler()
2546 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2548 .getKeyStroke(KeyEvent.VK_Q,
2549 jalview.util.ShortcutKeyMaskExWrapper
2550 .getMenuShortcutKeyMaskEx()),
2552 getRootPane().getActionMap().put("Quit", new AbstractAction()
2555 public void actionPerformed(ActionEvent e)
2563 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2566 * true if Groovy console is open
2568 public void enableExecuteGroovy(boolean enabled)
2571 * disable opening a second Groovy console (or re-enable when the console is
2574 groovyShell.setEnabled(!enabled);
2576 AlignFrame[] alignFrames = getAlignFrames();
2577 if (alignFrames != null)
2579 for (AlignFrame af : alignFrames)
2581 af.setGroovyEnabled(enabled);
2587 * Progress bars managed by the IProgressIndicator method.
2589 private Hashtable<Long, JPanel> progressBars;
2591 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2596 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2599 public void setProgressBar(String message, long id)
2601 if (progressBars == null)
2603 progressBars = new Hashtable<>();
2604 progressBarHandlers = new Hashtable<>();
2607 if (progressBars.get(Long.valueOf(id)) != null)
2609 JPanel panel = progressBars.remove(Long.valueOf(id));
2610 if (progressBarHandlers.contains(Long.valueOf(id)))
2612 progressBarHandlers.remove(Long.valueOf(id));
2614 removeProgressPanel(panel);
2618 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2625 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2626 * jalview.gui.IProgressIndicatorHandler)
2629 public void registerHandler(final long id,
2630 final IProgressIndicatorHandler handler)
2632 if (progressBarHandlers == null
2633 || !progressBars.containsKey(Long.valueOf(id)))
2635 throw new Error(MessageManager.getString(
2636 "error.call_setprogressbar_before_registering_handler"));
2638 progressBarHandlers.put(Long.valueOf(id), handler);
2639 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2640 if (handler.canCancel())
2642 JButton cancel = new JButton(
2643 MessageManager.getString("action.cancel"));
2644 final IProgressIndicator us = this;
2645 cancel.addActionListener(new ActionListener()
2649 public void actionPerformed(ActionEvent e)
2651 handler.cancelActivity(id);
2652 us.setProgressBar(MessageManager
2653 .formatMessage("label.cancelled_params", new Object[]
2654 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2658 progressPanel.add(cancel, BorderLayout.EAST);
2664 * @return true if any progress bars are still active
2667 public boolean operationInProgress()
2669 if (progressBars != null && progressBars.size() > 0)
2677 * This will return the first AlignFrame holding the given viewport instance.
2678 * It will break if there are more than one AlignFrames viewing a particular
2682 * @return alignFrame for viewport
2684 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2686 if (desktop != null)
2688 AlignmentPanel[] aps = getAlignmentPanels(
2689 viewport.getSequenceSetId());
2690 for (int panel = 0; aps != null && panel < aps.length; panel++)
2692 if (aps[panel] != null && aps[panel].av == viewport)
2694 return aps[panel].alignFrame;
2701 public VamsasApplication getVamsasApplication()
2703 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2709 * flag set if jalview GUI is being operated programmatically
2711 private boolean inBatchMode = false;
2714 * check if jalview GUI is being operated programmatically
2716 * @return inBatchMode
2718 public boolean isInBatchMode()
2724 * set flag if jalview GUI is being operated programmatically
2726 * @param inBatchMode
2728 public void setInBatchMode(boolean inBatchMode)
2730 this.inBatchMode = inBatchMode;
2734 * start service discovery and wait till it is done
2736 public void startServiceDiscovery()
2738 startServiceDiscovery(false);
2742 * start service discovery threads - blocking or non-blocking
2746 public void startServiceDiscovery(boolean blocking)
2748 startServiceDiscovery(blocking, false);
2752 * start service discovery threads
2755 * - false means call returns immediately
2756 * @param ignore_SHOW_JWS2_SERVICES_preference
2757 * - when true JABA services are discovered regardless of user's JWS2
2758 * discovery preference setting
2760 public void startServiceDiscovery(boolean blocking,
2761 boolean ignore_SHOW_JWS2_SERVICES_preference)
2763 boolean alive = true;
2764 Thread t0 = null, t1 = null, t2 = null;
2765 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2768 // todo: changesupport handlers need to be transferred
2769 if (discoverer == null)
2771 discoverer = new jalview.ws.jws1.Discoverer();
2772 // register PCS handler for desktop.
2773 discoverer.addPropertyChangeListener(changeSupport);
2775 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2776 // until we phase out completely
2777 (t0 = new Thread(discoverer)).start();
2780 if (ignore_SHOW_JWS2_SERVICES_preference
2781 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2783 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2784 .startDiscoverer(changeSupport);
2788 // TODO: do rest service discovery
2797 } catch (Exception e)
2800 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2801 || (t3 != null && t3.isAlive())
2802 || (t0 != null && t0.isAlive());
2808 * called to check if the service discovery process completed successfully.
2812 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2814 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2816 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2817 .getErrorMessages();
2820 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2822 if (serviceChangedDialog == null)
2824 // only run if we aren't already displaying one of these.
2825 addDialogThread(serviceChangedDialog = new Runnable()
2832 * JalviewDialog jd =new JalviewDialog() {
2834 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2836 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2838 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2840 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2842 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2843 * + " or mis-configured HTTP proxy settings.<br/>" +
2844 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2845 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2846 * true, true, "Web Service Configuration Problem", 450, 400);
2848 * jd.waitForInput();
2850 JvOptionPane.showConfirmDialog(Desktop.desktop,
2851 new JLabel("<html><table width=\"450\"><tr><td>"
2852 + ermsg + "</td></tr></table>"
2853 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2854 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2855 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2856 + " Tools->Preferences dialog box to change them.</p></html>"),
2857 "Web Service Configuration Problem",
2858 JvOptionPane.DEFAULT_OPTION,
2859 JvOptionPane.ERROR_MESSAGE);
2860 serviceChangedDialog = null;
2868 jalview.bin.Console.error(
2869 "Errors reported by JABA discovery service. Check web services preferences.\n"
2876 private Runnable serviceChangedDialog = null;
2879 * start a thread to open a URL in the configured browser. Pops up a warning
2880 * dialog to the user if there is an exception when calling out to the browser
2885 public static void showUrl(final String url)
2887 showUrl(url, Desktop.instance);
2891 * Like showUrl but allows progress handler to be specified
2895 * (null) or object implementing IProgressIndicator
2897 public static void showUrl(final String url,
2898 final IProgressIndicator progress)
2900 new Thread(new Runnable()
2907 if (progress != null)
2909 progress.setProgressBar(MessageManager
2910 .formatMessage("status.opening_params", new Object[]
2911 { url }), this.hashCode());
2913 jalview.util.BrowserLauncher.openURL(url);
2914 } catch (Exception ex)
2916 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2918 .getString("label.web_browser_not_found_unix"),
2919 MessageManager.getString("label.web_browser_not_found"),
2920 JvOptionPane.WARNING_MESSAGE);
2922 ex.printStackTrace();
2924 if (progress != null)
2926 progress.setProgressBar(null, this.hashCode());
2932 public static WsParamSetManager wsparamManager = null;
2934 public static ParamManager getUserParameterStore()
2936 if (wsparamManager == null)
2938 wsparamManager = new WsParamSetManager();
2940 return wsparamManager;
2944 * static hyperlink handler proxy method for use by Jalview's internal windows
2948 public static void hyperlinkUpdate(HyperlinkEvent e)
2950 if (e.getEventType() == EventType.ACTIVATED)
2955 url = e.getURL().toString();
2956 Desktop.showUrl(url);
2957 } catch (Exception x)
2962 .error("Couldn't handle string " + url + " as a URL.");
2964 // ignore any exceptions due to dud links.
2971 * single thread that handles display of dialogs to user.
2973 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2976 * flag indicating if dialogExecutor should try to acquire a permit
2978 private volatile boolean dialogPause = true;
2983 private java.util.concurrent.Semaphore block = new Semaphore(0);
2985 private static groovy.ui.Console groovyConsole;
2988 * add another dialog thread to the queue
2992 public void addDialogThread(final Runnable prompter)
2994 dialogExecutor.submit(new Runnable()
3004 } catch (InterruptedException x)
3008 if (instance == null)
3014 SwingUtilities.invokeAndWait(prompter);
3015 } catch (Exception q)
3017 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3024 public void startDialogQueue()
3026 // set the flag so we don't pause waiting for another permit and semaphore
3027 // the current task to begin
3028 dialogPause = false;
3033 * Outputs an image of the desktop to file in EPS format, after prompting the
3034 * user for choice of Text or Lineart character rendering (unless a preference
3035 * has been set). The file name is generated as
3038 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3042 protected void snapShotWindow_actionPerformed(ActionEvent e)
3044 // currently the menu option to do this is not shown
3047 int width = getWidth();
3048 int height = getHeight();
3050 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3051 ImageWriterI writer = new ImageWriterI()
3054 public void exportImage(Graphics g) throws Exception
3057 jalview.bin.Console.info("Successfully written snapshot to file "
3058 + of.getAbsolutePath());
3061 String title = "View of desktop";
3062 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3064 exporter.doExport(of, this, width, height, title);
3068 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3069 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3070 * and location last time the view was expanded (if any). However it does not
3071 * remember the split pane divider location - this is set to match the
3072 * 'exploding' frame.
3076 public void explodeViews(SplitFrame sf)
3078 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3079 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3080 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3082 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3084 int viewCount = topPanels.size();
3091 * Processing in reverse order works, forwards order leaves the first panels not
3092 * visible. I don't know why!
3094 for (int i = viewCount - 1; i >= 0; i--)
3097 * Make new top and bottom frames. These take over the respective AlignmentPanel
3098 * objects, including their AlignmentViewports, so the cdna/protein
3099 * relationships between the viewports is carried over to the new split frames.
3101 * explodedGeometry holds the (x, y) position of the previously exploded
3102 * SplitFrame, and the (width, height) of the AlignFrame component
3104 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3105 AlignFrame newTopFrame = new AlignFrame(topPanel);
3106 newTopFrame.setSize(oldTopFrame.getSize());
3107 newTopFrame.setVisible(true);
3108 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3109 .getExplodedGeometry();
3110 if (geometry != null)
3112 newTopFrame.setSize(geometry.getSize());
3115 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3116 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3117 newBottomFrame.setSize(oldBottomFrame.getSize());
3118 newBottomFrame.setVisible(true);
3119 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3120 .getExplodedGeometry();
3121 if (geometry != null)
3123 newBottomFrame.setSize(geometry.getSize());
3126 topPanel.av.setGatherViewsHere(false);
3127 bottomPanel.av.setGatherViewsHere(false);
3128 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3130 if (geometry != null)
3132 splitFrame.setLocation(geometry.getLocation());
3134 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3138 * Clear references to the panels (now relocated in the new SplitFrames) before
3139 * closing the old SplitFrame.
3142 bottomPanels.clear();
3147 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3148 * back into the given SplitFrame as additional views. Note that the gathered
3149 * frames may themselves have multiple views.
3153 public void gatherViews(GSplitFrame source)
3156 * special handling of explodedGeometry for a view within a SplitFrame: - it
3157 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3158 * height) of the AlignFrame component
3160 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3161 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3162 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3163 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3164 myBottomFrame.viewport
3165 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3166 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3167 myTopFrame.viewport.setGatherViewsHere(true);
3168 myBottomFrame.viewport.setGatherViewsHere(true);
3169 String topViewId = myTopFrame.viewport.getSequenceSetId();
3170 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3172 JInternalFrame[] frames = desktop.getAllFrames();
3173 for (JInternalFrame frame : frames)
3175 if (frame instanceof SplitFrame && frame != source)
3177 SplitFrame sf = (SplitFrame) frame;
3178 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3179 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3180 boolean gatherThis = false;
3181 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3183 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3184 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3185 if (topViewId.equals(topPanel.av.getSequenceSetId())
3186 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3189 topPanel.av.setGatherViewsHere(false);
3190 bottomPanel.av.setGatherViewsHere(false);
3191 topPanel.av.setExplodedGeometry(
3192 new Rectangle(sf.getLocation(), topFrame.getSize()));
3193 bottomPanel.av.setExplodedGeometry(
3194 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3195 myTopFrame.addAlignmentPanel(topPanel, false);
3196 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3202 topFrame.getAlignPanels().clear();
3203 bottomFrame.getAlignPanels().clear();
3210 * The dust settles...give focus to the tab we did this from.
3212 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3215 public static groovy.ui.Console getGroovyConsole()
3217 return groovyConsole;
3221 * handles the payload of a drag and drop event.
3223 * TODO refactor to desktop utilities class
3226 * - Data source strings extracted from the drop event
3228 * - protocol for each data source extracted from the drop event
3232 * - the payload from the drop event
3235 public static void transferFromDropTarget(List<Object> files,
3236 List<DataSourceType> protocols, DropTargetDropEvent evt,
3237 Transferable t) throws Exception
3240 DataFlavor uriListFlavor = new DataFlavor(
3241 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3244 urlFlavour = new DataFlavor(
3245 "application/x-java-url; class=java.net.URL");
3246 } catch (ClassNotFoundException cfe)
3248 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3252 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3257 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3258 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3259 // means url may be null.
3262 protocols.add(DataSourceType.URL);
3263 files.add(url.toString());
3264 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3265 + files.get(files.size() - 1));
3270 if (Platform.isAMacAndNotJS())
3273 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3276 } catch (Throwable ex)
3278 jalview.bin.Console.debug("URL drop handler failed.", ex);
3281 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3283 // Works on Windows and MacOSX
3284 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3285 for (Object file : (List) t
3286 .getTransferData(DataFlavor.javaFileListFlavor))
3289 protocols.add(DataSourceType.FILE);
3294 // Unix like behaviour
3295 boolean added = false;
3297 if (t.isDataFlavorSupported(uriListFlavor))
3299 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3300 // This is used by Unix drag system
3301 data = (String) t.getTransferData(uriListFlavor);
3305 // fallback to text: workaround - on OSX where there's a JVM bug
3307 .debug("standard URIListFlavor failed. Trying text");
3308 // try text fallback
3309 DataFlavor textDf = new DataFlavor(
3310 "text/plain;class=java.lang.String");
3311 if (t.isDataFlavorSupported(textDf))
3313 data = (String) t.getTransferData(textDf);
3316 jalview.bin.Console.debug("Plain text drop content returned "
3317 + (data == null ? "Null - failed" : data));
3322 while (protocols.size() < files.size())
3324 jalview.bin.Console.debug("Adding missing FILE protocol for "
3325 + files.get(protocols.size()));
3326 protocols.add(DataSourceType.FILE);
3328 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3329 data, "\r\n"); st.hasMoreTokens();)
3332 String s = st.nextToken();
3333 if (s.startsWith("#"))
3335 // the line is a comment (as per the RFC 2483)
3338 java.net.URI uri = new java.net.URI(s);
3339 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3341 protocols.add(DataSourceType.URL);
3342 files.add(uri.toString());
3346 // otherwise preserve old behaviour: catch all for file objects
3347 java.io.File file = new java.io.File(uri);
3348 protocols.add(DataSourceType.FILE);
3349 files.add(file.toString());
3354 if (jalview.bin.Console.isDebugEnabled())
3356 if (data == null || !added)
3359 if (t.getTransferDataFlavors() != null
3360 && t.getTransferDataFlavors().length > 0)
3362 jalview.bin.Console.debug(
3363 "Couldn't resolve drop data. Here are the supported flavors:");
3364 for (DataFlavor fl : t.getTransferDataFlavors())
3366 jalview.bin.Console.debug(
3367 "Supported transfer dataflavor: " + fl.toString());
3368 Object df = t.getTransferData(fl);
3371 jalview.bin.Console.debug("Retrieves: " + df);
3375 jalview.bin.Console.debug("Retrieved nothing");
3382 .debug("Couldn't resolve dataflavor for drop: "
3388 if (Platform.isWindowsAndNotJS())
3391 .debug("Scanning dropped content for Windows Link Files");
3393 // resolve any .lnk files in the file drop
3394 for (int f = 0; f < files.size(); f++)
3396 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3397 if (protocols.get(f).equals(DataSourceType.FILE)
3398 && (source.endsWith(".lnk") || source.endsWith(".url")
3399 || source.endsWith(".site")))
3403 Object obj = files.get(f);
3404 File lf = (obj instanceof File ? (File) obj
3405 : new File((String) obj));
3406 // process link file to get a URL
3407 jalview.bin.Console.debug("Found potential link file: " + lf);
3408 WindowsShortcut wscfile = new WindowsShortcut(lf);
3409 String fullname = wscfile.getRealFilename();
3410 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3411 files.set(f, fullname);
3412 jalview.bin.Console.debug("Parsed real filename " + fullname
3413 + " to extract protocol: " + protocols.get(f));
3414 } catch (Exception ex)
3416 jalview.bin.Console.error(
3417 "Couldn't parse " + files.get(f) + " as a link file.",
3426 * Sets the Preferences property for experimental features to True or False
3427 * depending on the state of the controlling menu item
3430 protected void showExperimental_actionPerformed(boolean selected)
3432 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3436 * Answers a (possibly empty) list of any structure viewer frames (currently
3437 * for either Jmol or Chimera) which are currently open. This may optionally
3438 * be restricted to viewers of a specified class, or viewers linked to a
3439 * specified alignment panel.
3442 * if not null, only return viewers linked to this panel
3443 * @param structureViewerClass
3444 * if not null, only return viewers of this class
3447 public List<StructureViewerBase> getStructureViewers(
3448 AlignmentPanel apanel,
3449 Class<? extends StructureViewerBase> structureViewerClass)
3451 List<StructureViewerBase> result = new ArrayList<>();
3452 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3454 for (JInternalFrame frame : frames)
3456 if (frame instanceof StructureViewerBase)
3458 if (structureViewerClass == null
3459 || structureViewerClass.isInstance(frame))
3462 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3464 result.add((StructureViewerBase) frame);
3472 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3474 private static boolean debugScaleMessageDone = false;
3476 public static void debugScaleMessage(Graphics g)
3478 if (debugScaleMessageDone)
3482 // output used by tests to check HiDPI scaling settings in action
3485 Graphics2D gg = (Graphics2D) g;
3488 AffineTransform t = gg.getTransform();
3489 double scaleX = t.getScaleX();
3490 double scaleY = t.getScaleY();
3491 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3492 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3493 debugScaleMessageDone = true;
3497 jalview.bin.Console.debug("Desktop graphics null");
3499 } catch (Exception e)
3501 jalview.bin.Console.debug(Cache.getStackTraceString(e));