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;
202 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
204 public static boolean nosplash = false;
207 * news reader - null if it was never started.
209 private BlogReader jvnews = null;
211 private File projectFile;
215 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
217 public void addJalviewPropertyChangeListener(
218 PropertyChangeListener listener)
220 changeSupport.addJalviewPropertyChangeListener(listener);
224 * @param propertyName
226 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
227 * java.beans.PropertyChangeListener)
229 public void addJalviewPropertyChangeListener(String propertyName,
230 PropertyChangeListener listener)
232 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
236 * @param propertyName
238 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
239 * java.beans.PropertyChangeListener)
241 public void removeJalviewPropertyChangeListener(String propertyName,
242 PropertyChangeListener listener)
244 changeSupport.removeJalviewPropertyChangeListener(propertyName,
248 /** Singleton Desktop instance */
249 public static Desktop instance;
251 public static MyDesktopPane desktop;
253 public static MyDesktopPane getDesktop()
255 // BH 2018 could use currentThread() here as a reference to a
256 // Hashtable<Thread, MyDesktopPane> in JavaScript
260 static int openFrameCount = 0;
262 static final int xOffset = 30;
264 static final int yOffset = 30;
266 public static jalview.ws.jws1.Discoverer discoverer;
268 public static Object[] jalviewClipboard;
270 public static boolean internalCopy = false;
272 static int fileLoadingCount = 0;
274 class MyDesktopManager implements DesktopManager
277 private DesktopManager delegate;
279 public MyDesktopManager(DesktopManager delegate)
281 this.delegate = delegate;
285 public void activateFrame(JInternalFrame f)
289 delegate.activateFrame(f);
290 } catch (NullPointerException npe)
292 Point p = getMousePosition();
293 instance.showPasteMenu(p.x, p.y);
298 public void beginDraggingFrame(JComponent f)
300 delegate.beginDraggingFrame(f);
304 public void beginResizingFrame(JComponent f, int direction)
306 delegate.beginResizingFrame(f, direction);
310 public void closeFrame(JInternalFrame f)
312 delegate.closeFrame(f);
316 public void deactivateFrame(JInternalFrame f)
318 delegate.deactivateFrame(f);
322 public void deiconifyFrame(JInternalFrame f)
324 delegate.deiconifyFrame(f);
328 public void dragFrame(JComponent f, int newX, int newY)
334 delegate.dragFrame(f, newX, newY);
338 public void endDraggingFrame(JComponent f)
340 delegate.endDraggingFrame(f);
345 public void endResizingFrame(JComponent f)
347 delegate.endResizingFrame(f);
352 public void iconifyFrame(JInternalFrame f)
354 delegate.iconifyFrame(f);
358 public void maximizeFrame(JInternalFrame f)
360 delegate.maximizeFrame(f);
364 public void minimizeFrame(JInternalFrame f)
366 delegate.minimizeFrame(f);
370 public void openFrame(JInternalFrame f)
372 delegate.openFrame(f);
376 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
383 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
387 public void setBoundsForFrame(JComponent f, int newX, int newY,
388 int newWidth, int newHeight)
390 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
393 // All other methods, simply delegate
398 * Creates a new Desktop object.
404 * A note to implementors. It is ESSENTIAL that any activities that might
405 * block are spawned off as threads rather than waited for during this
410 doConfigureStructurePrefs();
411 setTitle(ChannelProperties.getProperty("app_name") + " "
412 + Cache.getProperty("VERSION"));
415 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
416 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
417 * officially documented or guaranteed to exist, so we access it via
418 * reflection. There appear to be unfathomable criteria about what this
419 * string can contain, and it if doesn't meet those criteria then "java"
420 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
421 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
422 * not. The reflection access may generate a warning: WARNING: An illegal
423 * reflective access operation has occurred WARNING: Illegal reflective
424 * access by jalview.gui.Desktop () to field
425 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
427 if (Platform.isLinux())
429 if (LaunchUtils.getJavaVersion() >= 11)
431 jalview.bin.Console.info(
432 "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.");
436 Toolkit xToolkit = Toolkit.getDefaultToolkit();
437 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
438 Field awtAppClassNameField = null;
440 if (Arrays.stream(declaredFields)
441 .anyMatch(f -> f.getName().equals("awtAppClassName")))
443 awtAppClassNameField = xToolkit.getClass()
444 .getDeclaredField("awtAppClassName");
447 String title = ChannelProperties.getProperty("app_name");
448 if (awtAppClassNameField != null)
450 awtAppClassNameField.setAccessible(true);
451 awtAppClassNameField.set(xToolkit, title);
455 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
457 } catch (Exception e)
459 jalview.bin.Console.debug("Error setting awtAppClassName");
460 jalview.bin.Console.trace(Cache.getStackTraceString(e));
464 setIconImages(ChannelProperties.getIconList());
466 // override quit handling when GUI OS close [X] button pressed
467 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
468 addWindowListener(new WindowAdapter()
471 public void windowClosing(WindowEvent ev)
473 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
477 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
479 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
480 desktop = new MyDesktopPane(selmemusage);
482 showMemusage.setSelected(selmemusage);
483 desktop.setBackground(Color.white);
485 getContentPane().setLayout(new BorderLayout());
486 // alternate config - have scrollbars - see notes in JAL-153
487 // JScrollPane sp = new JScrollPane();
488 // sp.getViewport().setView(desktop);
489 // getContentPane().add(sp, BorderLayout.CENTER);
491 // BH 2018 - just an experiment to try unclipped JInternalFrames.
494 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
497 getContentPane().add(desktop, BorderLayout.CENTER);
498 desktop.setDragMode(DRAG_MODE);
500 // This line prevents Windows Look&Feel resizing all new windows to maximum
501 // if previous window was maximised
502 desktop.setDesktopManager(new MyDesktopManager(
503 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
504 : Platform.isAMacAndNotJS()
505 ? new AquaInternalFrameManager(
506 desktop.getDesktopManager())
507 : desktop.getDesktopManager())));
509 Rectangle dims = getLastKnownDimensions("");
516 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
517 int xPos = Math.max(5, (screenSize.width - 900) / 2);
518 int yPos = Math.max(5, (screenSize.height - 650) / 2);
519 setBounds(xPos, yPos, 900, 650);
522 if (!Platform.isJS())
529 jconsole = new Console(this, showjconsole);
530 jconsole.setHeader(Cache.getVersionDetailsForConsole());
531 showConsole(showjconsole);
533 showNews.setVisible(false);
535 experimentalFeatures.setSelected(showExperimental());
537 getIdentifiersOrgData();
541 // Spawn a thread that shows the splashscreen
544 SwingUtilities.invokeLater(new Runnable()
549 new SplashScreen(true);
554 // Thread off a new instance of the file chooser - this reduces the time
556 // takes to open it later on.
557 new Thread(new Runnable()
562 jalview.bin.Console.debug("Filechooser init thread started.");
563 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
564 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
566 jalview.bin.Console.debug("Filechooser init thread finished.");
569 // Add the service change listener
570 changeSupport.addJalviewPropertyChangeListener("services",
571 new PropertyChangeListener()
575 public void propertyChange(PropertyChangeEvent evt)
578 .debug("Firing service changed event for "
579 + evt.getNewValue());
580 JalviewServicesChanged(evt);
585 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
588 this.addMouseListener(ma = new MouseAdapter()
591 public void mousePressed(MouseEvent evt)
593 if (evt.isPopupTrigger()) // Mac
595 showPasteMenu(evt.getX(), evt.getY());
600 public void mouseReleased(MouseEvent evt)
602 if (evt.isPopupTrigger()) // Windows
604 showPasteMenu(evt.getX(), evt.getY());
608 desktop.addMouseListener(ma);
612 * Answers true if user preferences to enable experimental features is True
617 public boolean showExperimental()
619 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
620 Boolean.FALSE.toString());
621 return Boolean.valueOf(experimental).booleanValue();
624 public void doConfigureStructurePrefs()
626 // configure services
627 StructureSelectionManager ssm = StructureSelectionManager
628 .getStructureSelectionManager(this);
629 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
631 ssm.setAddTempFacAnnot(
632 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
633 ssm.setProcessSecondaryStructure(
634 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
635 // JAL-3915 - RNAView is no longer an option so this has no effect
636 ssm.setSecStructServices(
637 Cache.getDefault(Preferences.USE_RNAVIEW, false));
641 ssm.setAddTempFacAnnot(false);
642 ssm.setProcessSecondaryStructure(false);
643 ssm.setSecStructServices(false);
647 public void checkForNews()
649 final Desktop me = this;
650 // Thread off the news reader, in case there are connection problems.
651 new Thread(new Runnable()
656 jalview.bin.Console.debug("Starting news thread.");
657 jvnews = new BlogReader(me);
658 showNews.setVisible(true);
659 jalview.bin.Console.debug("Completed news thread.");
664 public void getIdentifiersOrgData()
666 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
667 {// Thread off the identifiers fetcher
668 new Thread(new Runnable()
674 .debug("Downloading data from identifiers.org");
677 UrlDownloadClient.download(IdOrgSettings.getUrl(),
678 IdOrgSettings.getDownloadLocation());
679 } catch (IOException e)
682 .debug("Exception downloading identifiers.org data"
692 protected void showNews_actionPerformed(ActionEvent e)
694 showNews(showNews.isSelected());
697 void showNews(boolean visible)
699 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
700 showNews.setSelected(visible);
701 if (visible && !jvnews.isVisible())
703 new Thread(new Runnable()
708 long now = System.currentTimeMillis();
709 Desktop.instance.setProgressBar(
710 MessageManager.getString("status.refreshing_news"), now);
711 jvnews.refreshNews();
712 Desktop.instance.setProgressBar(null, now);
720 * recover the last known dimensions for a jalview window
723 * - empty string is desktop, all other windows have unique prefix
724 * @return null or last known dimensions scaled to current geometry (if last
725 * window geom was known)
727 Rectangle getLastKnownDimensions(String windowName)
729 // TODO: lock aspect ratio for scaling desktop Bug #0058199
730 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
731 String x = Cache.getProperty(windowName + "SCREEN_X");
732 String y = Cache.getProperty(windowName + "SCREEN_Y");
733 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
734 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
735 if ((x != null) && (y != null) && (width != null) && (height != null))
737 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
738 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
739 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
741 // attempt #1 - try to cope with change in screen geometry - this
742 // version doesn't preserve original jv aspect ratio.
743 // take ratio of current screen size vs original screen size.
744 double sw = ((1f * screenSize.width) / (1f * Integer
745 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
746 double sh = ((1f * screenSize.height) / (1f * Integer
747 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
748 // rescale the bounds depending upon the current screen geometry.
749 ix = (int) (ix * sw);
750 iw = (int) (iw * sw);
751 iy = (int) (iy * sh);
752 ih = (int) (ih * sh);
753 while (ix >= screenSize.width)
755 jalview.bin.Console.debug(
756 "Window geometry location recall error: shifting horizontal to within screenbounds.");
757 ix -= screenSize.width;
759 while (iy >= screenSize.height)
761 jalview.bin.Console.debug(
762 "Window geometry location recall error: shifting vertical to within screenbounds.");
763 iy -= screenSize.height;
765 jalview.bin.Console.debug(
766 "Got last known dimensions for " + windowName + ": x:" + ix
767 + " y:" + iy + " width:" + iw + " height:" + ih);
769 // return dimensions for new instance
770 return new Rectangle(ix, iy, iw, ih);
775 void showPasteMenu(int x, int y)
777 JPopupMenu popup = new JPopupMenu();
778 JMenuItem item = new JMenuItem(
779 MessageManager.getString("label.paste_new_window"));
780 item.addActionListener(new ActionListener()
783 public void actionPerformed(ActionEvent evt)
790 popup.show(this, x, y);
797 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
798 Transferable contents = c.getContents(this);
800 if (contents != null)
802 String file = (String) contents
803 .getTransferData(DataFlavor.stringFlavor);
805 FileFormatI format = new IdentifyFile().identify(file,
806 DataSourceType.PASTE);
808 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
811 } catch (Exception ex)
814 "Unable to paste alignment from system clipboard:\n" + ex);
819 * Adds and opens the given frame to the desktop
830 public static synchronized void addInternalFrame(
831 final JInternalFrame frame, String title, int w, int h)
833 addInternalFrame(frame, title, true, w, h, true, false);
837 * Add an internal frame to the Jalview desktop
844 * When true, display frame immediately, otherwise, caller must call
845 * setVisible themselves.
851 public static synchronized void addInternalFrame(
852 final JInternalFrame frame, String title, boolean makeVisible,
855 addInternalFrame(frame, title, makeVisible, w, h, true, false);
859 * Add an internal frame to the Jalview desktop and make it visible
872 public static synchronized void addInternalFrame(
873 final JInternalFrame frame, String title, int w, int h,
876 addInternalFrame(frame, title, true, w, h, resizable, false);
880 * Add an internal frame to the Jalview desktop
887 * When true, display frame immediately, otherwise, caller must call
888 * setVisible themselves.
895 * @param ignoreMinSize
896 * Do not set the default minimum size for frame
898 public static synchronized void addInternalFrame(
899 final JInternalFrame frame, String title, boolean makeVisible,
900 int w, int h, boolean resizable, boolean ignoreMinSize)
903 // TODO: allow callers to determine X and Y position of frame (eg. via
905 // TODO: consider fixing method to update entries in the window submenu with
906 // the current window title
908 frame.setTitle(title);
909 if (frame.getWidth() < 1 || frame.getHeight() < 1)
913 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
914 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
915 // IF JALVIEW IS RUNNING HEADLESS
916 // ///////////////////////////////////////////////
917 if (instance == null || (System.getProperty("java.awt.headless") != null
918 && System.getProperty("java.awt.headless").equals("true")))
927 frame.setMinimumSize(
928 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
930 // Set default dimension for Alignment Frame window.
931 // The Alignment Frame window could be added from a number of places,
933 // I did this here in order not to miss out on any Alignment frame.
934 if (frame instanceof AlignFrame)
936 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
937 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
941 frame.setVisible(makeVisible);
942 frame.setClosable(true);
943 frame.setResizable(resizable);
944 frame.setMaximizable(resizable);
945 frame.setIconifiable(resizable);
946 frame.setOpaque(Platform.isJS());
948 if (frame.getX() < 1 && frame.getY() < 1)
950 frame.setLocation(xOffset * openFrameCount,
951 yOffset * ((openFrameCount - 1) % 10) + yOffset);
955 * add an entry for the new frame in the Window menu (and remove it when the
958 final JMenuItem menuItem = new JMenuItem(title);
959 frame.addInternalFrameListener(new InternalFrameAdapter()
962 public void internalFrameActivated(InternalFrameEvent evt)
964 JInternalFrame itf = desktop.getSelectedFrame();
967 if (itf instanceof AlignFrame)
969 Jalview.setCurrentAlignFrame((AlignFrame) itf);
976 public void internalFrameClosed(InternalFrameEvent evt)
978 PaintRefresher.RemoveComponent(frame);
981 * defensive check to prevent frames being added half off the window
983 if (openFrameCount > 0)
989 * ensure no reference to alignFrame retained by menu item listener
991 if (menuItem.getActionListeners().length > 0)
993 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
995 windowMenu.remove(menuItem);
999 menuItem.addActionListener(new ActionListener()
1002 public void actionPerformed(ActionEvent e)
1006 frame.setSelected(true);
1007 frame.setIcon(false);
1008 } catch (java.beans.PropertyVetoException ex)
1015 setKeyBindings(frame);
1019 windowMenu.add(menuItem);
1024 frame.setSelected(true);
1025 frame.requestFocus();
1026 } catch (java.beans.PropertyVetoException ve)
1028 } catch (java.lang.ClassCastException cex)
1030 jalview.bin.Console.warn(
1031 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1037 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1042 private static void setKeyBindings(JInternalFrame frame)
1044 @SuppressWarnings("serial")
1045 final Action closeAction = new AbstractAction()
1048 public void actionPerformed(ActionEvent e)
1055 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1057 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1058 InputEvent.CTRL_DOWN_MASK);
1059 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1060 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1062 InputMap inputMap = frame
1063 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1064 String ctrlW = ctrlWKey.toString();
1065 inputMap.put(ctrlWKey, ctrlW);
1066 inputMap.put(cmdWKey, ctrlW);
1068 ActionMap actionMap = frame.getActionMap();
1069 actionMap.put(ctrlW, closeAction);
1073 public void lostOwnership(Clipboard clipboard, Transferable contents)
1077 Desktop.jalviewClipboard = null;
1080 internalCopy = false;
1084 public void dragEnter(DropTargetDragEvent evt)
1089 public void dragExit(DropTargetEvent evt)
1094 public void dragOver(DropTargetDragEvent evt)
1099 public void dropActionChanged(DropTargetDragEvent evt)
1110 public void drop(DropTargetDropEvent evt)
1112 boolean success = true;
1113 // JAL-1552 - acceptDrop required before getTransferable call for
1114 // Java's Transferable for native dnd
1115 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1116 Transferable t = evt.getTransferable();
1117 List<Object> files = new ArrayList<>();
1118 List<DataSourceType> protocols = new ArrayList<>();
1122 Desktop.transferFromDropTarget(files, protocols, evt, t);
1123 } catch (Exception e)
1125 e.printStackTrace();
1133 for (int i = 0; i < files.size(); i++)
1135 // BH 2018 File or String
1136 Object file = files.get(i);
1137 String fileName = file.toString();
1138 DataSourceType protocol = (protocols == null)
1139 ? DataSourceType.FILE
1141 FileFormatI format = null;
1143 if (fileName.endsWith(".jar"))
1145 format = FileFormat.Jalview;
1150 format = new IdentifyFile().identify(file, protocol);
1152 if (file instanceof File)
1154 Platform.cacheFileData((File) file);
1156 new FileLoader().LoadFile(null, file, protocol, format);
1159 } catch (Exception ex)
1164 evt.dropComplete(success); // need this to ensure input focus is properly
1165 // transfered to any new windows created
1175 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1177 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1178 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1179 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1180 BackupFiles.getEnabled());
1182 chooser.setFileView(new JalviewFileView());
1183 chooser.setDialogTitle(
1184 MessageManager.getString("label.open_local_file"));
1185 chooser.setToolTipText(MessageManager.getString("action.open"));
1187 chooser.setResponseHandler(0, () -> {
1188 File selectedFile = chooser.getSelectedFile();
1189 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1191 FileFormatI format = chooser.getSelectedFormat();
1194 * Call IdentifyFile to verify the file contains what its extension implies.
1195 * Skip this step for dynamically added file formats, because IdentifyFile does
1196 * not know how to recognise them.
1198 if (FileFormats.getInstance().isIdentifiable(format))
1202 format = new IdentifyFile().identify(selectedFile,
1203 DataSourceType.FILE);
1204 } catch (FileFormatException e)
1206 // format = null; //??
1210 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1214 chooser.showOpenDialog(this);
1218 * Shows a dialog for input of a URL at which to retrieve alignment data
1223 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1225 // This construct allows us to have a wider textfield
1227 JLabel label = new JLabel(
1228 MessageManager.getString("label.input_file_url"));
1230 JPanel panel = new JPanel(new GridLayout(2, 1));
1234 * the URL to fetch is input in Java: an editable combobox with history JS:
1235 * (pending JAL-3038) a plain text field
1238 String urlBase = "https://www.";
1239 if (Platform.isJS())
1241 history = new JTextField(urlBase, 35);
1250 JComboBox<String> asCombo = new JComboBox<>();
1251 asCombo.setPreferredSize(new Dimension(400, 20));
1252 asCombo.setEditable(true);
1253 asCombo.addItem(urlBase);
1254 String historyItems = Cache.getProperty("RECENT_URL");
1255 if (historyItems != null)
1257 for (String token : historyItems.split("\\t"))
1259 asCombo.addItem(token);
1266 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1267 MessageManager.getString("action.cancel") };
1268 Callable<Void> action = () -> {
1269 @SuppressWarnings("unchecked")
1270 String url = (history instanceof JTextField
1271 ? ((JTextField) history).getText()
1272 : ((JComboBox<String>) history).getEditor().getItem()
1273 .toString().trim());
1275 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1277 if (viewport != null)
1279 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1280 FileFormat.Jalview);
1284 new FileLoader().LoadFile(url, DataSourceType.URL,
1285 FileFormat.Jalview);
1290 FileFormatI format = null;
1293 format = new IdentifyFile().identify(url, DataSourceType.URL);
1294 } catch (FileFormatException e)
1296 // TODO revise error handling, distinguish between
1297 // URL not found and response not valid
1302 String msg = MessageManager.formatMessage("label.couldnt_locate",
1304 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1305 MessageManager.getString("label.url_not_found"),
1306 JvOptionPane.WARNING_MESSAGE);
1308 return null; // Void
1311 if (viewport != null)
1313 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1318 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1321 return null; // Void
1323 String dialogOption = MessageManager
1324 .getString("label.input_alignment_from_url");
1325 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1326 .showInternalDialog(panel, dialogOption,
1327 JvOptionPane.YES_NO_CANCEL_OPTION,
1328 JvOptionPane.PLAIN_MESSAGE, null, options,
1329 MessageManager.getString("action.ok"));
1333 * Opens the CutAndPaste window for the user to paste an alignment in to
1336 * - if not null, the pasted alignment is added to the current
1337 * alignment; if null, to a new alignment window
1340 public void inputTextboxMenuItem_actionPerformed(
1341 AlignmentViewPanel viewPanel)
1343 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1344 cap.setForInput(viewPanel);
1345 Desktop.addInternalFrame(cap,
1346 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1351 * Check with user and saving files before actually quitting
1353 public void desktopQuit()
1355 desktopQuit(true, false);
1358 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1360 final Callable<Void> doDesktopQuit = () -> {
1361 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1362 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1363 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1364 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1365 getBounds().y, getWidth(), getHeight()));
1367 if (jconsole != null)
1369 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1370 jconsole.stopConsole();
1375 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1379 if (dialogExecutor != null)
1381 dialogExecutor.shutdownNow();
1384 closeAll_actionPerformed(null);
1386 if (groovyConsole != null)
1388 // suppress a possible repeat prompt to save script
1389 groovyConsole.setDirty(false);
1390 groovyConsole.exit();
1393 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1395 // note that shutdown hook will not be run
1396 jalview.bin.Console.debug("Force Quit selected by user");
1397 Runtime.getRuntime().halt(0);
1400 jalview.bin.Console.debug("Quit selected by user");
1403 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1404 // instance.dispose();
1408 return null; // Void
1411 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1412 QuitHandler.defaultCancelQuit);
1416 * Don't call this directly, use desktopQuit() above. Exits the program.
1421 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1422 // not run a second time if gotQuitResponse flag has been set (i.e. user
1423 // confirmed quit of some kind).
1427 private void storeLastKnownDimensions(String string, Rectangle jc)
1429 jalview.bin.Console.debug("Storing last known dimensions for " + string
1430 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1431 + " height:" + jc.height);
1433 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1434 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1435 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1436 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1446 public void aboutMenuItem_actionPerformed(ActionEvent e)
1448 new Thread(new Runnable()
1453 new SplashScreen(false);
1459 * Returns the html text for the About screen, including any available version
1460 * number, build details, author details and citation reference, but without
1461 * the enclosing {@code html} tags
1465 public String getAboutMessage()
1467 StringBuilder message = new StringBuilder(1024);
1468 message.append("<div style=\"font-family: sans-serif;\">")
1469 .append("<h1><strong>Version: ")
1470 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1471 .append("<strong>Built: <em>")
1472 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1473 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1474 .append("</strong>");
1476 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1477 if (latestVersion.equals("Checking"))
1479 // JBP removed this message for 2.11: May be reinstated in future version
1480 // message.append("<br>...Checking latest version...</br>");
1482 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1484 boolean red = false;
1485 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1486 .indexOf("automated build") == -1)
1489 // Displayed when code version and jnlp version do not match and code
1490 // version is not a development build
1491 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1494 message.append("<br>!! Version ")
1495 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1496 .append(" is available for download from ")
1497 .append(Cache.getDefault("www.jalview.org",
1498 "https://www.jalview.org"))
1502 message.append("</div>");
1505 message.append("<br>Authors: ");
1506 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1507 message.append(CITATION);
1509 message.append("</div>");
1511 return message.toString();
1515 * Action on requesting Help documentation
1518 public void documentationMenuItem_actionPerformed()
1522 if (Platform.isJS())
1524 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1533 Help.showHelpWindow();
1535 } catch (Exception ex)
1537 System.err.println("Error opening help: " + ex.getMessage());
1542 public void closeAll_actionPerformed(ActionEvent e)
1544 // TODO show a progress bar while closing?
1545 JInternalFrame[] frames = desktop.getAllFrames();
1546 for (int i = 0; i < frames.length; i++)
1550 frames[i].setClosed(true);
1551 } catch (java.beans.PropertyVetoException ex)
1555 Jalview.setCurrentAlignFrame(null);
1556 System.out.println("ALL CLOSED");
1559 * reset state of singleton objects as appropriate (clear down session state
1560 * when all windows are closed)
1562 StructureSelectionManager ssm = StructureSelectionManager
1563 .getStructureSelectionManager(this);
1571 public void raiseRelated_actionPerformed(ActionEvent e)
1573 reorderAssociatedWindows(false, false);
1577 public void minimizeAssociated_actionPerformed(ActionEvent e)
1579 reorderAssociatedWindows(true, false);
1582 void closeAssociatedWindows()
1584 reorderAssociatedWindows(false, true);
1590 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1594 protected void garbageCollect_actionPerformed(ActionEvent e)
1596 // We simply collect the garbage
1597 jalview.bin.Console.debug("Collecting garbage...");
1599 jalview.bin.Console.debug("Finished garbage collection.");
1605 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1609 protected void showMemusage_actionPerformed(ActionEvent e)
1611 desktop.showMemoryUsage(showMemusage.isSelected());
1618 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1622 protected void showConsole_actionPerformed(ActionEvent e)
1624 showConsole(showConsole.isSelected());
1627 Console jconsole = null;
1630 * control whether the java console is visible or not
1634 void showConsole(boolean selected)
1636 // TODO: decide if we should update properties file
1637 if (jconsole != null) // BH 2018
1639 showConsole.setSelected(selected);
1640 Cache.setProperty("SHOW_JAVA_CONSOLE",
1641 Boolean.valueOf(selected).toString());
1642 jconsole.setVisible(selected);
1646 void reorderAssociatedWindows(boolean minimize, boolean close)
1648 JInternalFrame[] frames = desktop.getAllFrames();
1649 if (frames == null || frames.length < 1)
1654 AlignmentViewport source = null, target = null;
1655 if (frames[0] instanceof AlignFrame)
1657 source = ((AlignFrame) frames[0]).getCurrentView();
1659 else if (frames[0] instanceof TreePanel)
1661 source = ((TreePanel) frames[0]).getViewPort();
1663 else if (frames[0] instanceof PCAPanel)
1665 source = ((PCAPanel) frames[0]).av;
1667 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1669 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1674 for (int i = 0; i < frames.length; i++)
1677 if (frames[i] == null)
1681 if (frames[i] instanceof AlignFrame)
1683 target = ((AlignFrame) frames[i]).getCurrentView();
1685 else if (frames[i] instanceof TreePanel)
1687 target = ((TreePanel) frames[i]).getViewPort();
1689 else if (frames[i] instanceof PCAPanel)
1691 target = ((PCAPanel) frames[i]).av;
1693 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1695 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1698 if (source == target)
1704 frames[i].setClosed(true);
1708 frames[i].setIcon(minimize);
1711 frames[i].toFront();
1715 } catch (java.beans.PropertyVetoException ex)
1730 protected void preferences_actionPerformed(ActionEvent e)
1732 Preferences.openPreferences();
1736 * Prompts the user to choose a file and then saves the Jalview state as a
1737 * Jalview project file
1740 public void saveState_actionPerformed()
1742 saveState_actionPerformed(false);
1745 public void saveState_actionPerformed(boolean saveAs)
1747 java.io.File projectFile = getProjectFile();
1748 // autoSave indicates we already have a file and don't need to ask
1749 boolean autoSave = projectFile != null && !saveAs
1750 && BackupFiles.getEnabled();
1752 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1753 // saveAs="+saveAs+", Backups
1754 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1756 boolean approveSave = false;
1759 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1762 chooser.setFileView(new JalviewFileView());
1763 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1765 int value = chooser.showSaveDialog(this);
1767 if (value == JalviewFileChooser.APPROVE_OPTION)
1769 projectFile = chooser.getSelectedFile();
1770 setProjectFile(projectFile);
1775 if (approveSave || autoSave)
1777 final Desktop me = this;
1778 final java.io.File chosenFile = projectFile;
1779 new Thread(new Runnable()
1784 // TODO: refactor to Jalview desktop session controller action.
1785 setProgressBar(MessageManager.formatMessage(
1786 "label.saving_jalview_project", new Object[]
1787 { chosenFile.getName() }), chosenFile.hashCode());
1788 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1789 // TODO catch and handle errors for savestate
1790 // TODO prevent user from messing with the Desktop whilst we're saving
1793 boolean doBackup = BackupFiles.getEnabled();
1794 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1797 new Jalview2XML().saveState(
1798 doBackup ? backupfiles.getTempFile() : chosenFile);
1802 backupfiles.setWriteSuccess(true);
1803 backupfiles.rollBackupsAndRenameTempFile();
1805 } catch (OutOfMemoryError oom)
1807 new OOMWarning("Whilst saving current state to "
1808 + chosenFile.getName(), oom);
1809 } catch (Exception ex)
1811 jalview.bin.Console.error("Problems whilst trying to save to "
1812 + chosenFile.getName(), ex);
1813 JvOptionPane.showMessageDialog(me,
1814 MessageManager.formatMessage(
1815 "label.error_whilst_saving_current_state_to",
1817 { chosenFile.getName() }),
1818 MessageManager.getString("label.couldnt_save_project"),
1819 JvOptionPane.WARNING_MESSAGE);
1821 setProgressBar(null, chosenFile.hashCode());
1828 public void saveAsState_actionPerformed(ActionEvent e)
1830 saveState_actionPerformed(true);
1833 protected void setProjectFile(File choice)
1835 this.projectFile = choice;
1838 public File getProjectFile()
1840 return this.projectFile;
1844 * Shows a file chooser dialog and tries to read in the selected file as a
1848 public void loadState_actionPerformed()
1850 final String[] suffix = new String[] { "jvp", "jar" };
1851 final String[] desc = new String[] { "Jalview Project",
1852 "Jalview Project (old)" };
1853 JalviewFileChooser chooser = new JalviewFileChooser(
1854 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1855 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1859 chooser.setFileView(new JalviewFileView());
1860 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1861 chooser.setResponseHandler(0, () -> {
1862 File selectedFile = chooser.getSelectedFile();
1863 setProjectFile(selectedFile);
1864 String choice = selectedFile.getAbsolutePath();
1865 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1866 new Thread(new Runnable()
1873 new Jalview2XML().loadJalviewAlign(selectedFile);
1874 } catch (OutOfMemoryError oom)
1876 new OOMWarning("Whilst loading project from " + choice, oom);
1877 } catch (Exception ex)
1879 jalview.bin.Console.error(
1880 "Problems whilst loading project from " + choice, ex);
1881 JvOptionPane.showMessageDialog(Desktop.desktop,
1882 MessageManager.formatMessage(
1883 "label.error_whilst_loading_project_from",
1886 MessageManager.getString("label.couldnt_load_project"),
1887 JvOptionPane.WARNING_MESSAGE);
1890 }, "Project Loader").start();
1894 chooser.showOpenDialog(this);
1898 public void inputSequence_actionPerformed(ActionEvent e)
1900 new SequenceFetcher(this);
1903 JPanel progressPanel;
1905 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1907 public void startLoading(final Object fileName)
1909 if (fileLoadingCount == 0)
1911 fileLoadingPanels.add(addProgressPanel(MessageManager
1912 .formatMessage("label.loading_file", new Object[]
1918 private JPanel addProgressPanel(String string)
1920 if (progressPanel == null)
1922 progressPanel = new JPanel(new GridLayout(1, 1));
1923 totalProgressCount = 0;
1924 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1926 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1927 JProgressBar progressBar = new JProgressBar();
1928 progressBar.setIndeterminate(true);
1930 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1932 thisprogress.add(progressBar, BorderLayout.CENTER);
1933 progressPanel.add(thisprogress);
1934 ((GridLayout) progressPanel.getLayout()).setRows(
1935 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1936 ++totalProgressCount;
1937 instance.validate();
1938 return thisprogress;
1941 int totalProgressCount = 0;
1943 private void removeProgressPanel(JPanel progbar)
1945 if (progressPanel != null)
1947 synchronized (progressPanel)
1949 progressPanel.remove(progbar);
1950 GridLayout gl = (GridLayout) progressPanel.getLayout();
1951 gl.setRows(gl.getRows() - 1);
1952 if (--totalProgressCount < 1)
1954 this.getContentPane().remove(progressPanel);
1955 progressPanel = null;
1962 public void stopLoading()
1965 if (fileLoadingCount < 1)
1967 while (fileLoadingPanels.size() > 0)
1969 removeProgressPanel(fileLoadingPanels.remove(0));
1971 fileLoadingPanels.clear();
1972 fileLoadingCount = 0;
1977 public static int getViewCount(String alignmentId)
1979 AlignmentViewport[] aps = getViewports(alignmentId);
1980 return (aps == null) ? 0 : aps.length;
1985 * @param alignmentId
1986 * - if null, all sets are returned
1987 * @return all AlignmentPanels concerning the alignmentId sequence set
1989 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1991 if (Desktop.desktop == null)
1993 // no frames created and in headless mode
1994 // TODO: verify that frames are recoverable when in headless mode
1997 List<AlignmentPanel> aps = new ArrayList<>();
1998 AlignFrame[] frames = getAlignFrames();
2003 for (AlignFrame af : frames)
2005 for (AlignmentPanel ap : af.alignPanels)
2007 if (alignmentId == null
2008 || alignmentId.equals(ap.av.getSequenceSetId()))
2014 if (aps.size() == 0)
2018 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2023 * get all the viewports on an alignment.
2025 * @param sequenceSetId
2026 * unique alignment id (may be null - all viewports returned in that
2028 * @return all viewports on the alignment bound to sequenceSetId
2030 public static AlignmentViewport[] getViewports(String sequenceSetId)
2032 List<AlignmentViewport> viewp = new ArrayList<>();
2033 if (desktop != null)
2035 AlignFrame[] frames = Desktop.getAlignFrames();
2037 for (AlignFrame afr : frames)
2039 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2040 .equals(sequenceSetId))
2042 if (afr.alignPanels != null)
2044 for (AlignmentPanel ap : afr.alignPanels)
2046 if (sequenceSetId == null
2047 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2055 viewp.add(afr.getViewport());
2059 if (viewp.size() > 0)
2061 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2068 * Explode the views in the given frame into separate AlignFrame
2072 public static void explodeViews(AlignFrame af)
2074 int size = af.alignPanels.size();
2080 // FIXME: ideally should use UI interface API
2081 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2082 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2083 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2084 for (int i = 0; i < size; i++)
2086 AlignmentPanel ap = af.alignPanels.get(i);
2088 AlignFrame newaf = new AlignFrame(ap);
2090 // transfer reference for existing feature settings to new alignFrame
2091 if (ap == af.alignPanel)
2093 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2095 newaf.featureSettings = viewFeatureSettings;
2097 newaf.setFeatureSettingsGeometry(fsBounds);
2101 * Restore the view's last exploded frame geometry if known. Multiple views from
2102 * one exploded frame share and restore the same (frame) position and size.
2104 Rectangle geometry = ap.av.getExplodedGeometry();
2105 if (geometry != null)
2107 newaf.setBounds(geometry);
2110 ap.av.setGatherViewsHere(false);
2112 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2113 AlignFrame.DEFAULT_HEIGHT);
2114 // and materialise a new feature settings dialog instance for the new
2116 // (closes the old as if 'OK' was pressed)
2117 if (ap == af.alignPanel && newaf.featureSettings != null
2118 && newaf.featureSettings.isOpen()
2119 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2121 newaf.showFeatureSettingsUI();
2125 af.featureSettings = null;
2126 af.alignPanels.clear();
2127 af.closeMenuItem_actionPerformed(true);
2132 * Gather expanded views (separate AlignFrame's) with the same sequence set
2133 * identifier back in to this frame as additional views, and close the
2134 * expanded views. Note the expanded frames may themselves have multiple
2135 * views. We take the lot.
2139 public void gatherViews(AlignFrame source)
2141 source.viewport.setGatherViewsHere(true);
2142 source.viewport.setExplodedGeometry(source.getBounds());
2143 JInternalFrame[] frames = desktop.getAllFrames();
2144 String viewId = source.viewport.getSequenceSetId();
2145 for (int t = 0; t < frames.length; t++)
2147 if (frames[t] instanceof AlignFrame && frames[t] != source)
2149 AlignFrame af = (AlignFrame) frames[t];
2150 boolean gatherThis = false;
2151 for (int a = 0; a < af.alignPanels.size(); a++)
2153 AlignmentPanel ap = af.alignPanels.get(a);
2154 if (viewId.equals(ap.av.getSequenceSetId()))
2157 ap.av.setGatherViewsHere(false);
2158 ap.av.setExplodedGeometry(af.getBounds());
2159 source.addAlignmentPanel(ap, false);
2165 if (af.featureSettings != null && af.featureSettings.isOpen())
2167 if (source.featureSettings == null)
2169 // preserve the feature settings geometry for this frame
2170 source.featureSettings = af.featureSettings;
2171 source.setFeatureSettingsGeometry(
2172 af.getFeatureSettingsGeometry());
2176 // close it and forget
2177 af.featureSettings.close();
2180 af.alignPanels.clear();
2181 af.closeMenuItem_actionPerformed(true);
2186 // refresh the feature setting UI for the source frame if it exists
2187 if (source.featureSettings != null && source.featureSettings.isOpen())
2189 source.showFeatureSettingsUI();
2194 public JInternalFrame[] getAllFrames()
2196 return desktop.getAllFrames();
2200 * Checks the given url to see if it gives a response indicating that the user
2201 * should be informed of a new questionnaire.
2205 public void checkForQuestionnaire(String url)
2207 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2208 // javax.swing.SwingUtilities.invokeLater(jvq);
2209 new Thread(jvq).start();
2212 public void checkURLLinks()
2214 // Thread off the URL link checker
2215 addDialogThread(new Runnable()
2220 if (Cache.getDefault("CHECKURLLINKS", true))
2222 // check what the actual links are - if it's just the default don't
2223 // bother with the warning
2224 List<String> links = Preferences.sequenceUrlLinks
2227 // only need to check links if there is one with a
2228 // SEQUENCE_ID which is not the default EMBL_EBI link
2229 ListIterator<String> li = links.listIterator();
2230 boolean check = false;
2231 List<JLabel> urls = new ArrayList<>();
2232 while (li.hasNext())
2234 String link = li.next();
2235 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2236 && !UrlConstants.isDefaultString(link))
2239 int barPos = link.indexOf("|");
2240 String urlMsg = barPos == -1 ? link
2241 : link.substring(0, barPos) + ": "
2242 + link.substring(barPos + 1);
2243 urls.add(new JLabel(urlMsg));
2251 // ask user to check in case URL links use old style tokens
2252 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2253 JPanel msgPanel = new JPanel();
2254 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2255 msgPanel.add(Box.createVerticalGlue());
2256 JLabel msg = new JLabel(MessageManager
2257 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2258 JLabel msg2 = new JLabel(MessageManager
2259 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2261 for (JLabel url : urls)
2267 final JCheckBox jcb = new JCheckBox(
2268 MessageManager.getString("label.do_not_display_again"));
2269 jcb.addActionListener(new ActionListener()
2272 public void actionPerformed(ActionEvent e)
2274 // update Cache settings for "don't show this again"
2275 boolean showWarningAgain = !jcb.isSelected();
2276 Cache.setProperty("CHECKURLLINKS",
2277 Boolean.valueOf(showWarningAgain).toString());
2282 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2284 .getString("label.SEQUENCE_ID_no_longer_used"),
2285 JvOptionPane.WARNING_MESSAGE);
2292 * Proxy class for JDesktopPane which optionally displays the current memory
2293 * usage and highlights the desktop area with a red bar if free memory runs
2298 public class MyDesktopPane extends JDesktopPane implements Runnable
2300 private static final float ONE_MB = 1048576f;
2302 boolean showMemoryUsage = false;
2306 java.text.NumberFormat df;
2308 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2311 public MyDesktopPane(boolean showMemoryUsage)
2313 showMemoryUsage(showMemoryUsage);
2316 public void showMemoryUsage(boolean showMemory)
2318 this.showMemoryUsage = showMemory;
2321 Thread worker = new Thread(this);
2327 public boolean isShowMemoryUsage()
2329 return showMemoryUsage;
2335 df = java.text.NumberFormat.getNumberInstance();
2336 df.setMaximumFractionDigits(2);
2337 runtime = Runtime.getRuntime();
2339 while (showMemoryUsage)
2343 maxMemory = runtime.maxMemory() / ONE_MB;
2344 allocatedMemory = runtime.totalMemory() / ONE_MB;
2345 freeMemory = runtime.freeMemory() / ONE_MB;
2346 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2348 percentUsage = (totalFreeMemory / maxMemory) * 100;
2350 // if (percentUsage < 20)
2352 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2354 // instance.set.setBorder(border1);
2357 // sleep after showing usage
2359 } catch (Exception ex)
2361 ex.printStackTrace();
2367 public void paintComponent(Graphics g)
2369 if (showMemoryUsage && g != null && df != null)
2371 if (percentUsage < 20)
2373 g.setColor(Color.red);
2375 FontMetrics fm = g.getFontMetrics();
2378 g.drawString(MessageManager.formatMessage("label.memory_stats",
2380 { df.format(totalFreeMemory), df.format(maxMemory),
2381 df.format(percentUsage) }),
2382 10, getHeight() - fm.getHeight());
2386 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2387 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2392 * Accessor method to quickly get all the AlignmentFrames loaded.
2394 * @return an array of AlignFrame, or null if none found
2396 public static AlignFrame[] getAlignFrames()
2398 if (Jalview.isHeadlessMode())
2400 // Desktop.desktop is null in headless mode
2401 return new AlignFrame[] { Jalview.currentAlignFrame };
2404 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2410 List<AlignFrame> avp = new ArrayList<>();
2412 for (int i = frames.length - 1; i > -1; i--)
2414 if (frames[i] instanceof AlignFrame)
2416 avp.add((AlignFrame) frames[i]);
2418 else if (frames[i] instanceof SplitFrame)
2421 * Also check for a split frame containing an AlignFrame
2423 GSplitFrame sf = (GSplitFrame) frames[i];
2424 if (sf.getTopFrame() instanceof AlignFrame)
2426 avp.add((AlignFrame) sf.getTopFrame());
2428 if (sf.getBottomFrame() instanceof AlignFrame)
2430 avp.add((AlignFrame) sf.getBottomFrame());
2434 if (avp.size() == 0)
2438 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2443 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2447 public GStructureViewer[] getJmols()
2449 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2455 List<GStructureViewer> avp = new ArrayList<>();
2457 for (int i = frames.length - 1; i > -1; i--)
2459 if (frames[i] instanceof AppJmol)
2461 GStructureViewer af = (GStructureViewer) frames[i];
2465 if (avp.size() == 0)
2469 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2474 * Add Groovy Support to Jalview
2477 public void groovyShell_actionPerformed()
2481 openGroovyConsole();
2482 } catch (Exception ex)
2484 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2485 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2487 MessageManager.getString("label.couldnt_create_groovy_shell"),
2488 MessageManager.getString("label.groovy_support_failed"),
2489 JvOptionPane.ERROR_MESSAGE);
2494 * Open the Groovy console
2496 void openGroovyConsole()
2498 if (groovyConsole == null)
2500 groovyConsole = new groovy.ui.Console();
2501 groovyConsole.setVariable("Jalview", this);
2502 groovyConsole.run();
2505 * We allow only one console at a time, so that AlignFrame menu option
2506 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2507 * enable 'Run script', when the console is opened, and the reverse when it is
2510 Window window = (Window) groovyConsole.getFrame();
2511 window.addWindowListener(new WindowAdapter()
2514 public void windowClosed(WindowEvent e)
2517 * rebind CMD-Q from Groovy Console to Jalview Quit
2520 enableExecuteGroovy(false);
2526 * show Groovy console window (after close and reopen)
2528 ((Window) groovyConsole.getFrame()).setVisible(true);
2531 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2532 * opening a second console
2534 enableExecuteGroovy(true);
2538 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2539 * binding when opened
2541 protected void addQuitHandler()
2544 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2546 .getKeyStroke(KeyEvent.VK_Q,
2547 jalview.util.ShortcutKeyMaskExWrapper
2548 .getMenuShortcutKeyMaskEx()),
2550 getRootPane().getActionMap().put("Quit", new AbstractAction()
2553 public void actionPerformed(ActionEvent e)
2561 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2564 * true if Groovy console is open
2566 public void enableExecuteGroovy(boolean enabled)
2569 * disable opening a second Groovy console (or re-enable when the console is
2572 groovyShell.setEnabled(!enabled);
2574 AlignFrame[] alignFrames = getAlignFrames();
2575 if (alignFrames != null)
2577 for (AlignFrame af : alignFrames)
2579 af.setGroovyEnabled(enabled);
2585 * Progress bars managed by the IProgressIndicator method.
2587 private Hashtable<Long, JPanel> progressBars;
2589 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2594 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2597 public void setProgressBar(String message, long id)
2599 if (progressBars == null)
2601 progressBars = new Hashtable<>();
2602 progressBarHandlers = new Hashtable<>();
2605 if (progressBars.get(Long.valueOf(id)) != null)
2607 JPanel panel = progressBars.remove(Long.valueOf(id));
2608 if (progressBarHandlers.contains(Long.valueOf(id)))
2610 progressBarHandlers.remove(Long.valueOf(id));
2612 removeProgressPanel(panel);
2616 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2623 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2624 * jalview.gui.IProgressIndicatorHandler)
2627 public void registerHandler(final long id,
2628 final IProgressIndicatorHandler handler)
2630 if (progressBarHandlers == null
2631 || !progressBars.containsKey(Long.valueOf(id)))
2633 throw new Error(MessageManager.getString(
2634 "error.call_setprogressbar_before_registering_handler"));
2636 progressBarHandlers.put(Long.valueOf(id), handler);
2637 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2638 if (handler.canCancel())
2640 JButton cancel = new JButton(
2641 MessageManager.getString("action.cancel"));
2642 final IProgressIndicator us = this;
2643 cancel.addActionListener(new ActionListener()
2647 public void actionPerformed(ActionEvent e)
2649 handler.cancelActivity(id);
2650 us.setProgressBar(MessageManager
2651 .formatMessage("label.cancelled_params", new Object[]
2652 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2656 progressPanel.add(cancel, BorderLayout.EAST);
2662 * @return true if any progress bars are still active
2665 public boolean operationInProgress()
2667 if (progressBars != null && progressBars.size() > 0)
2675 * This will return the first AlignFrame holding the given viewport instance.
2676 * It will break if there are more than one AlignFrames viewing a particular
2680 * @return alignFrame for viewport
2682 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2684 if (desktop != null)
2686 AlignmentPanel[] aps = getAlignmentPanels(
2687 viewport.getSequenceSetId());
2688 for (int panel = 0; aps != null && panel < aps.length; panel++)
2690 if (aps[panel] != null && aps[panel].av == viewport)
2692 return aps[panel].alignFrame;
2699 public VamsasApplication getVamsasApplication()
2701 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2707 * flag set if jalview GUI is being operated programmatically
2709 private boolean inBatchMode = false;
2712 * check if jalview GUI is being operated programmatically
2714 * @return inBatchMode
2716 public boolean isInBatchMode()
2722 * set flag if jalview GUI is being operated programmatically
2724 * @param inBatchMode
2726 public void setInBatchMode(boolean inBatchMode)
2728 this.inBatchMode = inBatchMode;
2732 * start service discovery and wait till it is done
2734 public void startServiceDiscovery()
2736 startServiceDiscovery(false);
2740 * start service discovery threads - blocking or non-blocking
2744 public void startServiceDiscovery(boolean blocking)
2746 startServiceDiscovery(blocking, false);
2750 * start service discovery threads
2753 * - false means call returns immediately
2754 * @param ignore_SHOW_JWS2_SERVICES_preference
2755 * - when true JABA services are discovered regardless of user's JWS2
2756 * discovery preference setting
2758 public void startServiceDiscovery(boolean blocking,
2759 boolean ignore_SHOW_JWS2_SERVICES_preference)
2761 boolean alive = true;
2762 Thread t0 = null, t1 = null, t2 = null;
2763 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2766 // todo: changesupport handlers need to be transferred
2767 if (discoverer == null)
2769 discoverer = new jalview.ws.jws1.Discoverer();
2770 // register PCS handler for desktop.
2771 discoverer.addPropertyChangeListener(changeSupport);
2773 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2774 // until we phase out completely
2775 (t0 = new Thread(discoverer)).start();
2778 if (ignore_SHOW_JWS2_SERVICES_preference
2779 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2781 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2782 .startDiscoverer(changeSupport);
2786 // TODO: do rest service discovery
2795 } catch (Exception e)
2798 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2799 || (t3 != null && t3.isAlive())
2800 || (t0 != null && t0.isAlive());
2806 * called to check if the service discovery process completed successfully.
2810 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2812 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2814 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2815 .getErrorMessages();
2818 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2820 if (serviceChangedDialog == null)
2822 // only run if we aren't already displaying one of these.
2823 addDialogThread(serviceChangedDialog = new Runnable()
2830 * JalviewDialog jd =new JalviewDialog() {
2832 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2834 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2836 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2838 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2840 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2841 * + " or mis-configured HTTP proxy settings.<br/>" +
2842 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2843 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2844 * true, true, "Web Service Configuration Problem", 450, 400);
2846 * jd.waitForInput();
2848 JvOptionPane.showConfirmDialog(Desktop.desktop,
2849 new JLabel("<html><table width=\"450\"><tr><td>"
2850 + ermsg + "</td></tr></table>"
2851 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2852 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2853 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2854 + " Tools->Preferences dialog box to change them.</p></html>"),
2855 "Web Service Configuration Problem",
2856 JvOptionPane.DEFAULT_OPTION,
2857 JvOptionPane.ERROR_MESSAGE);
2858 serviceChangedDialog = null;
2866 jalview.bin.Console.error(
2867 "Errors reported by JABA discovery service. Check web services preferences.\n"
2874 private Runnable serviceChangedDialog = null;
2877 * start a thread to open a URL in the configured browser. Pops up a warning
2878 * dialog to the user if there is an exception when calling out to the browser
2883 public static void showUrl(final String url)
2885 showUrl(url, Desktop.instance);
2889 * Like showUrl but allows progress handler to be specified
2893 * (null) or object implementing IProgressIndicator
2895 public static void showUrl(final String url,
2896 final IProgressIndicator progress)
2898 new Thread(new Runnable()
2905 if (progress != null)
2907 progress.setProgressBar(MessageManager
2908 .formatMessage("status.opening_params", new Object[]
2909 { url }), this.hashCode());
2911 jalview.util.BrowserLauncher.openURL(url);
2912 } catch (Exception ex)
2914 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2916 .getString("label.web_browser_not_found_unix"),
2917 MessageManager.getString("label.web_browser_not_found"),
2918 JvOptionPane.WARNING_MESSAGE);
2920 ex.printStackTrace();
2922 if (progress != null)
2924 progress.setProgressBar(null, this.hashCode());
2930 public static WsParamSetManager wsparamManager = null;
2932 public static ParamManager getUserParameterStore()
2934 if (wsparamManager == null)
2936 wsparamManager = new WsParamSetManager();
2938 return wsparamManager;
2942 * static hyperlink handler proxy method for use by Jalview's internal windows
2946 public static void hyperlinkUpdate(HyperlinkEvent e)
2948 if (e.getEventType() == EventType.ACTIVATED)
2953 url = e.getURL().toString();
2954 Desktop.showUrl(url);
2955 } catch (Exception x)
2960 .error("Couldn't handle string " + url + " as a URL.");
2962 // ignore any exceptions due to dud links.
2969 * single thread that handles display of dialogs to user.
2971 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2974 * flag indicating if dialogExecutor should try to acquire a permit
2976 private volatile boolean dialogPause = true;
2981 private java.util.concurrent.Semaphore block = new Semaphore(0);
2983 private static groovy.ui.Console groovyConsole;
2986 * add another dialog thread to the queue
2990 public void addDialogThread(final Runnable prompter)
2992 dialogExecutor.submit(new Runnable()
3002 } catch (InterruptedException x)
3006 if (instance == null)
3012 SwingUtilities.invokeAndWait(prompter);
3013 } catch (Exception q)
3015 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3022 public void startDialogQueue()
3024 // set the flag so we don't pause waiting for another permit and semaphore
3025 // the current task to begin
3026 dialogPause = false;
3031 * Outputs an image of the desktop to file in EPS format, after prompting the
3032 * user for choice of Text or Lineart character rendering (unless a preference
3033 * has been set). The file name is generated as
3036 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3040 protected void snapShotWindow_actionPerformed(ActionEvent e)
3042 // currently the menu option to do this is not shown
3045 int width = getWidth();
3046 int height = getHeight();
3048 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3049 ImageWriterI writer = new ImageWriterI()
3052 public void exportImage(Graphics g) throws Exception
3055 jalview.bin.Console.info("Successfully written snapshot to file "
3056 + of.getAbsolutePath());
3059 String title = "View of desktop";
3060 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3062 exporter.doExport(of, this, width, height, title);
3066 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3067 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3068 * and location last time the view was expanded (if any). However it does not
3069 * remember the split pane divider location - this is set to match the
3070 * 'exploding' frame.
3074 public void explodeViews(SplitFrame sf)
3076 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3077 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3078 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3080 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3082 int viewCount = topPanels.size();
3089 * Processing in reverse order works, forwards order leaves the first panels not
3090 * visible. I don't know why!
3092 for (int i = viewCount - 1; i >= 0; i--)
3095 * Make new top and bottom frames. These take over the respective AlignmentPanel
3096 * objects, including their AlignmentViewports, so the cdna/protein
3097 * relationships between the viewports is carried over to the new split frames.
3099 * explodedGeometry holds the (x, y) position of the previously exploded
3100 * SplitFrame, and the (width, height) of the AlignFrame component
3102 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3103 AlignFrame newTopFrame = new AlignFrame(topPanel);
3104 newTopFrame.setSize(oldTopFrame.getSize());
3105 newTopFrame.setVisible(true);
3106 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3107 .getExplodedGeometry();
3108 if (geometry != null)
3110 newTopFrame.setSize(geometry.getSize());
3113 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3114 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3115 newBottomFrame.setSize(oldBottomFrame.getSize());
3116 newBottomFrame.setVisible(true);
3117 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3118 .getExplodedGeometry();
3119 if (geometry != null)
3121 newBottomFrame.setSize(geometry.getSize());
3124 topPanel.av.setGatherViewsHere(false);
3125 bottomPanel.av.setGatherViewsHere(false);
3126 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3128 if (geometry != null)
3130 splitFrame.setLocation(geometry.getLocation());
3132 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3136 * Clear references to the panels (now relocated in the new SplitFrames) before
3137 * closing the old SplitFrame.
3140 bottomPanels.clear();
3145 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3146 * back into the given SplitFrame as additional views. Note that the gathered
3147 * frames may themselves have multiple views.
3151 public void gatherViews(GSplitFrame source)
3154 * special handling of explodedGeometry for a view within a SplitFrame: - it
3155 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3156 * height) of the AlignFrame component
3158 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3159 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3160 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3161 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3162 myBottomFrame.viewport
3163 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3164 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3165 myTopFrame.viewport.setGatherViewsHere(true);
3166 myBottomFrame.viewport.setGatherViewsHere(true);
3167 String topViewId = myTopFrame.viewport.getSequenceSetId();
3168 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3170 JInternalFrame[] frames = desktop.getAllFrames();
3171 for (JInternalFrame frame : frames)
3173 if (frame instanceof SplitFrame && frame != source)
3175 SplitFrame sf = (SplitFrame) frame;
3176 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3177 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3178 boolean gatherThis = false;
3179 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3181 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3182 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3183 if (topViewId.equals(topPanel.av.getSequenceSetId())
3184 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3187 topPanel.av.setGatherViewsHere(false);
3188 bottomPanel.av.setGatherViewsHere(false);
3189 topPanel.av.setExplodedGeometry(
3190 new Rectangle(sf.getLocation(), topFrame.getSize()));
3191 bottomPanel.av.setExplodedGeometry(
3192 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3193 myTopFrame.addAlignmentPanel(topPanel, false);
3194 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3200 topFrame.getAlignPanels().clear();
3201 bottomFrame.getAlignPanels().clear();
3208 * The dust settles...give focus to the tab we did this from.
3210 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3213 public static groovy.ui.Console getGroovyConsole()
3215 return groovyConsole;
3219 * handles the payload of a drag and drop event.
3221 * TODO refactor to desktop utilities class
3224 * - Data source strings extracted from the drop event
3226 * - protocol for each data source extracted from the drop event
3230 * - the payload from the drop event
3233 public static void transferFromDropTarget(List<Object> files,
3234 List<DataSourceType> protocols, DropTargetDropEvent evt,
3235 Transferable t) throws Exception
3238 DataFlavor uriListFlavor = new DataFlavor(
3239 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3242 urlFlavour = new DataFlavor(
3243 "application/x-java-url; class=java.net.URL");
3244 } catch (ClassNotFoundException cfe)
3246 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3250 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3255 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3256 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3257 // means url may be null.
3260 protocols.add(DataSourceType.URL);
3261 files.add(url.toString());
3262 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3263 + files.get(files.size() - 1));
3268 if (Platform.isAMacAndNotJS())
3271 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3274 } catch (Throwable ex)
3276 jalview.bin.Console.debug("URL drop handler failed.", ex);
3279 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3281 // Works on Windows and MacOSX
3282 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3283 for (Object file : (List) t
3284 .getTransferData(DataFlavor.javaFileListFlavor))
3287 protocols.add(DataSourceType.FILE);
3292 // Unix like behaviour
3293 boolean added = false;
3295 if (t.isDataFlavorSupported(uriListFlavor))
3297 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3298 // This is used by Unix drag system
3299 data = (String) t.getTransferData(uriListFlavor);
3303 // fallback to text: workaround - on OSX where there's a JVM bug
3305 .debug("standard URIListFlavor failed. Trying text");
3306 // try text fallback
3307 DataFlavor textDf = new DataFlavor(
3308 "text/plain;class=java.lang.String");
3309 if (t.isDataFlavorSupported(textDf))
3311 data = (String) t.getTransferData(textDf);
3314 jalview.bin.Console.debug("Plain text drop content returned "
3315 + (data == null ? "Null - failed" : data));
3320 while (protocols.size() < files.size())
3322 jalview.bin.Console.debug("Adding missing FILE protocol for "
3323 + files.get(protocols.size()));
3324 protocols.add(DataSourceType.FILE);
3326 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3327 data, "\r\n"); st.hasMoreTokens();)
3330 String s = st.nextToken();
3331 if (s.startsWith("#"))
3333 // the line is a comment (as per the RFC 2483)
3336 java.net.URI uri = new java.net.URI(s);
3337 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3339 protocols.add(DataSourceType.URL);
3340 files.add(uri.toString());
3344 // otherwise preserve old behaviour: catch all for file objects
3345 java.io.File file = new java.io.File(uri);
3346 protocols.add(DataSourceType.FILE);
3347 files.add(file.toString());
3352 if (jalview.bin.Console.isDebugEnabled())
3354 if (data == null || !added)
3357 if (t.getTransferDataFlavors() != null
3358 && t.getTransferDataFlavors().length > 0)
3360 jalview.bin.Console.debug(
3361 "Couldn't resolve drop data. Here are the supported flavors:");
3362 for (DataFlavor fl : t.getTransferDataFlavors())
3364 jalview.bin.Console.debug(
3365 "Supported transfer dataflavor: " + fl.toString());
3366 Object df = t.getTransferData(fl);
3369 jalview.bin.Console.debug("Retrieves: " + df);
3373 jalview.bin.Console.debug("Retrieved nothing");
3380 .debug("Couldn't resolve dataflavor for drop: "
3386 if (Platform.isWindowsAndNotJS())
3389 .debug("Scanning dropped content for Windows Link Files");
3391 // resolve any .lnk files in the file drop
3392 for (int f = 0; f < files.size(); f++)
3394 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3395 if (protocols.get(f).equals(DataSourceType.FILE)
3396 && (source.endsWith(".lnk") || source.endsWith(".url")
3397 || source.endsWith(".site")))
3401 Object obj = files.get(f);
3402 File lf = (obj instanceof File ? (File) obj
3403 : new File((String) obj));
3404 // process link file to get a URL
3405 jalview.bin.Console.debug("Found potential link file: " + lf);
3406 WindowsShortcut wscfile = new WindowsShortcut(lf);
3407 String fullname = wscfile.getRealFilename();
3408 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3409 files.set(f, fullname);
3410 jalview.bin.Console.debug("Parsed real filename " + fullname
3411 + " to extract protocol: " + protocols.get(f));
3412 } catch (Exception ex)
3414 jalview.bin.Console.error(
3415 "Couldn't parse " + files.get(f) + " as a link file.",
3424 * Sets the Preferences property for experimental features to True or False
3425 * depending on the state of the controlling menu item
3428 protected void showExperimental_actionPerformed(boolean selected)
3430 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3434 * Answers a (possibly empty) list of any structure viewer frames (currently
3435 * for either Jmol or Chimera) which are currently open. This may optionally
3436 * be restricted to viewers of a specified class, or viewers linked to a
3437 * specified alignment panel.
3440 * if not null, only return viewers linked to this panel
3441 * @param structureViewerClass
3442 * if not null, only return viewers of this class
3445 public List<StructureViewerBase> getStructureViewers(
3446 AlignmentPanel apanel,
3447 Class<? extends StructureViewerBase> structureViewerClass)
3449 List<StructureViewerBase> result = new ArrayList<>();
3450 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3452 for (JInternalFrame frame : frames)
3454 if (frame instanceof StructureViewerBase)
3456 if (structureViewerClass == null
3457 || structureViewerClass.isInstance(frame))
3460 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3462 result.add((StructureViewerBase) frame);
3470 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3472 private static boolean debugScaleMessageDone = false;
3474 public static void debugScaleMessage(Graphics g)
3476 if (debugScaleMessageDone)
3480 // output used by tests to check HiDPI scaling settings in action
3483 Graphics2D gg = (Graphics2D) g;
3486 AffineTransform t = gg.getTransform();
3487 double scaleX = t.getScaleX();
3488 double scaleY = t.getScaleY();
3489 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3490 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3491 debugScaleMessageDone = true;
3495 jalview.bin.Console.debug("Desktop graphics null");
3497 } catch (Exception e)
3499 jalview.bin.Console.debug(Cache.getStackTraceString(e));