2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.Callable;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Semaphore;
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.Box;
76 import javax.swing.BoxLayout;
77 import javax.swing.DefaultDesktopManager;
78 import javax.swing.DesktopManager;
79 import javax.swing.InputMap;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JComboBox;
83 import javax.swing.JComponent;
84 import javax.swing.JDesktopPane;
85 import javax.swing.JFrame;
86 import javax.swing.JInternalFrame;
87 import javax.swing.JLabel;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JProgressBar;
92 import javax.swing.JTextField;
93 import javax.swing.KeyStroke;
94 import javax.swing.SwingUtilities;
95 import javax.swing.WindowConstants;
96 import javax.swing.event.HyperlinkEvent;
97 import javax.swing.event.HyperlinkEvent.EventType;
98 import javax.swing.event.InternalFrameAdapter;
99 import javax.swing.event.InternalFrameEvent;
101 import org.stackoverflowusers.file.WindowsShortcut;
103 import jalview.api.AlignViewportI;
104 import jalview.api.AlignmentViewPanel;
105 import jalview.api.structures.JalviewStructureDisplayI;
106 import jalview.bin.Cache;
107 import jalview.bin.Jalview;
108 import jalview.datamodel.Alignment;
109 import jalview.datamodel.HiddenColumns;
110 import jalview.datamodel.Sequence;
111 import jalview.datamodel.SequenceI;
112 import jalview.gui.ImageExporter.ImageWriterI;
113 import jalview.gui.QuitHandler.QResponse;
114 import jalview.io.BackupFiles;
115 import jalview.io.DataSourceType;
116 import jalview.io.FileFormat;
117 import jalview.io.FileFormatException;
118 import jalview.io.FileFormatI;
119 import jalview.io.FileFormats;
120 import jalview.io.FileLoader;
121 import jalview.io.FormatAdapter;
122 import jalview.io.IdentifyFile;
123 import jalview.io.JalviewFileChooser;
124 import jalview.io.JalviewFileView;
125 import jalview.jbgui.GSplitFrame;
126 import jalview.jbgui.GStructureViewer;
127 import jalview.project.Jalview2XML;
128 import jalview.structure.StructureSelectionManager;
129 import jalview.urls.IdOrgSettings;
130 import jalview.util.BrowserLauncher;
131 import jalview.util.ChannelProperties;
132 import jalview.util.ImageMaker.TYPE;
133 import jalview.util.LaunchUtils;
134 import jalview.util.MessageManager;
135 import jalview.util.Platform;
136 import jalview.util.ShortcutKeyMaskExWrapper;
137 import jalview.util.UrlConstants;
138 import jalview.viewmodel.AlignmentViewport;
139 import jalview.ws.params.ParamManager;
140 import jalview.ws.utils.UrlDownloadClient;
147 * @version $Revision: 1.155 $
149 public class Desktop extends jalview.jbgui.GDesktop
150 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
151 jalview.api.StructureSelectionManagerProvider
153 private static final String CITATION;
156 URL bg_logo_url = ChannelProperties.getImageURL(
157 "bg_logo." + String.valueOf(SplashScreen.logoSize));
158 URL uod_logo_url = ChannelProperties.getImageURL(
159 "uod_banner." + String.valueOf(SplashScreen.logoSize));
160 boolean logo = (bg_logo_url != null || uod_logo_url != null);
161 StringBuilder sb = new StringBuilder();
163 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
168 sb.append(bg_logo_url == null ? ""
169 : "<img alt=\"Barton Group logo\" src=\""
170 + bg_logo_url.toString() + "\">");
171 sb.append(uod_logo_url == null ? ""
172 : " <img alt=\"University of Dundee shield\" src=\""
173 + uod_logo_url.toString() + "\">");
175 "<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>");
176 sb.append("<br><br>If you use Jalview, please cite:"
177 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
178 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
179 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
180 CITATION = sb.toString();
183 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
185 private static int DEFAULT_MIN_WIDTH = 300;
187 private static int DEFAULT_MIN_HEIGHT = 250;
189 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
191 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
193 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
195 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
197 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
199 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
201 public static void setLiveDragMode(boolean b)
203 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
204 : JDesktopPane.OUTLINE_DRAG_MODE;
206 desktop.setDragMode(DRAG_MODE);
209 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
211 public static boolean nosplash = false;
214 * news reader - null if it was never started.
216 private BlogReader jvnews = null;
218 private File projectFile;
222 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
224 public void addJalviewPropertyChangeListener(
225 PropertyChangeListener listener)
227 changeSupport.addJalviewPropertyChangeListener(listener);
231 * @param propertyName
233 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
234 * java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(String propertyName,
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void removeJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.removeJalviewPropertyChangeListener(propertyName,
255 /** Singleton Desktop instance */
256 public static Desktop instance;
258 public static MyDesktopPane desktop;
260 public static MyDesktopPane getDesktop()
262 // BH 2018 could use currentThread() here as a reference to a
263 // Hashtable<Thread, MyDesktopPane> in JavaScript
267 static int openFrameCount = 0;
269 static final int xOffset = 30;
271 static final int yOffset = 30;
273 public static jalview.ws.jws1.Discoverer discoverer;
275 public static Object[] jalviewClipboard;
277 public static boolean internalCopy = false;
279 static int fileLoadingCount = 0;
281 class MyDesktopManager implements DesktopManager
284 private DesktopManager delegate;
286 public MyDesktopManager(DesktopManager delegate)
288 this.delegate = delegate;
292 public void activateFrame(JInternalFrame f)
296 delegate.activateFrame(f);
297 } catch (NullPointerException npe)
299 Point p = getMousePosition();
300 instance.showPasteMenu(p.x, p.y);
305 public void beginDraggingFrame(JComponent f)
307 delegate.beginDraggingFrame(f);
311 public void beginResizingFrame(JComponent f, int direction)
313 delegate.beginResizingFrame(f, direction);
317 public void closeFrame(JInternalFrame f)
319 delegate.closeFrame(f);
323 public void deactivateFrame(JInternalFrame f)
325 delegate.deactivateFrame(f);
329 public void deiconifyFrame(JInternalFrame f)
331 delegate.deiconifyFrame(f);
335 public void dragFrame(JComponent f, int newX, int newY)
341 delegate.dragFrame(f, newX, newY);
345 public void endDraggingFrame(JComponent f)
347 delegate.endDraggingFrame(f);
352 public void endResizingFrame(JComponent f)
354 delegate.endResizingFrame(f);
359 public void iconifyFrame(JInternalFrame f)
361 delegate.iconifyFrame(f);
365 public void maximizeFrame(JInternalFrame f)
367 delegate.maximizeFrame(f);
371 public void minimizeFrame(JInternalFrame f)
373 delegate.minimizeFrame(f);
377 public void openFrame(JInternalFrame f)
379 delegate.openFrame(f);
383 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
390 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
394 public void setBoundsForFrame(JComponent f, int newX, int newY,
395 int newWidth, int newHeight)
397 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
400 // All other methods, simply delegate
405 * Creates a new Desktop object.
411 * A note to implementors. It is ESSENTIAL that any activities that might
412 * block are spawned off as threads rather than waited for during this
417 doConfigureStructurePrefs();
418 setTitle(ChannelProperties.getProperty("app_name") + " "
419 + Cache.getProperty("VERSION"));
422 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
423 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
424 * officially documented or guaranteed to exist, so we access it via
425 * reflection. There appear to be unfathomable criteria about what this
426 * string can contain, and it if doesn't meet those criteria then "java"
427 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
428 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
429 * not. The reflection access may generate a warning: WARNING: An illegal
430 * reflective access operation has occurred WARNING: Illegal reflective
431 * access by jalview.gui.Desktop () to field
432 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
434 if (Platform.isLinux())
436 if (LaunchUtils.getJavaVersion() >= 11)
439 * Send this message to stderr as the warning that follows (due to
440 * reflection) also goes to stderr.
443 "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.");
447 Toolkit xToolkit = Toolkit.getDefaultToolkit();
448 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
449 Field awtAppClassNameField = null;
451 if (Arrays.stream(declaredFields)
452 .anyMatch(f -> f.getName().equals("awtAppClassName")))
454 awtAppClassNameField = xToolkit.getClass()
455 .getDeclaredField("awtAppClassName");
458 String title = ChannelProperties.getProperty("app_name");
459 if (awtAppClassNameField != null)
461 awtAppClassNameField.setAccessible(true);
462 awtAppClassNameField.set(xToolkit, title);
466 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
468 } catch (Exception e)
470 jalview.bin.Console.debug("Error setting awtAppClassName");
471 jalview.bin.Console.trace(Cache.getStackTraceString(e));
475 setIconImages(ChannelProperties.getIconList());
477 // override quit handling when GUI OS close [X] button pressed
478 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
479 addWindowListener(new WindowAdapter()
482 public void windowClosing(WindowEvent ev)
484 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
488 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
490 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
491 desktop = new MyDesktopPane(selmemusage);
493 showMemusage.setSelected(selmemusage);
494 desktop.setBackground(Color.white);
496 getContentPane().setLayout(new BorderLayout());
497 // alternate config - have scrollbars - see notes in JAL-153
498 // JScrollPane sp = new JScrollPane();
499 // sp.getViewport().setView(desktop);
500 // getContentPane().add(sp, BorderLayout.CENTER);
502 // BH 2018 - just an experiment to try unclipped JInternalFrames.
505 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
508 getContentPane().add(desktop, BorderLayout.CENTER);
509 desktop.setDragMode(DRAG_MODE);
511 // This line prevents Windows Look&Feel resizing all new windows to maximum
512 // if previous window was maximised
513 desktop.setDesktopManager(new MyDesktopManager(
514 Platform.isJS() ? desktop.getDesktopManager()
515 : new DefaultDesktopManager()));
517 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
518 : Platform.isAMacAndNotJS()
519 ? new AquaInternalFrameManager(
520 desktop.getDesktopManager())
521 : desktop.getDesktopManager())));
524 Rectangle dims = getLastKnownDimensions("");
531 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
532 int xPos = Math.max(5, (screenSize.width - 900) / 2);
533 int yPos = Math.max(5, (screenSize.height - 650) / 2);
534 setBounds(xPos, yPos, 900, 650);
537 if (!Platform.isJS())
544 jconsole = new Console(this, showjconsole);
545 jconsole.setHeader(Cache.getVersionDetailsForConsole());
546 showConsole(showjconsole);
548 showNews.setVisible(false);
550 experimentalFeatures.setSelected(showExperimental());
552 getIdentifiersOrgData();
556 // Spawn a thread that shows the splashscreen
559 SwingUtilities.invokeLater(new Runnable()
564 new SplashScreen(true);
569 // Thread off a new instance of the file chooser - this reduces the time
571 // takes to open it later on.
572 new Thread(new Runnable()
577 jalview.bin.Console.debug("Filechooser init thread started.");
578 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
579 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
581 jalview.bin.Console.debug("Filechooser init thread finished.");
584 // Add the service change listener
585 changeSupport.addJalviewPropertyChangeListener("services",
586 new PropertyChangeListener()
590 public void propertyChange(PropertyChangeEvent evt)
593 .debug("Firing service changed event for "
594 + evt.getNewValue());
595 JalviewServicesChanged(evt);
600 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
603 this.addMouseListener(ma = new MouseAdapter()
606 public void mousePressed(MouseEvent evt)
608 if (evt.isPopupTrigger()) // Mac
610 showPasteMenu(evt.getX(), evt.getY());
615 public void mouseReleased(MouseEvent evt)
617 if (evt.isPopupTrigger()) // Windows
619 showPasteMenu(evt.getX(), evt.getY());
623 desktop.addMouseListener(ma);
627 * Answers true if user preferences to enable experimental features is True
632 public boolean showExperimental()
634 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
635 Boolean.FALSE.toString());
636 return Boolean.valueOf(experimental).booleanValue();
639 public void doConfigureStructurePrefs()
641 // configure services
642 StructureSelectionManager ssm = StructureSelectionManager
643 .getStructureSelectionManager(this);
644 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
646 ssm.setAddTempFacAnnot(
647 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
648 ssm.setProcessSecondaryStructure(
649 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
650 // JAL-3915 - RNAView is no longer an option so this has no effect
651 ssm.setSecStructServices(
652 Cache.getDefault(Preferences.USE_RNAVIEW, false));
656 ssm.setAddTempFacAnnot(false);
657 ssm.setProcessSecondaryStructure(false);
658 ssm.setSecStructServices(false);
662 public void checkForNews()
664 final Desktop me = this;
665 // Thread off the news reader, in case there are connection problems.
666 new Thread(new Runnable()
671 jalview.bin.Console.debug("Starting news thread.");
672 jvnews = new BlogReader(me);
673 showNews.setVisible(true);
674 jalview.bin.Console.debug("Completed news thread.");
679 public void getIdentifiersOrgData()
681 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
682 {// Thread off the identifiers fetcher
683 new Thread(new Runnable()
689 .debug("Downloading data from identifiers.org");
692 UrlDownloadClient.download(IdOrgSettings.getUrl(),
693 IdOrgSettings.getDownloadLocation());
694 } catch (IOException e)
697 .debug("Exception downloading identifiers.org data"
707 protected void showNews_actionPerformed(ActionEvent e)
709 showNews(showNews.isSelected());
712 void showNews(boolean visible)
714 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
715 showNews.setSelected(visible);
716 if (visible && !jvnews.isVisible())
718 new Thread(new Runnable()
723 long now = System.currentTimeMillis();
724 Desktop.instance.setProgressBar(
725 MessageManager.getString("status.refreshing_news"), now);
726 jvnews.refreshNews();
727 Desktop.instance.setProgressBar(null, now);
735 * recover the last known dimensions for a jalview window
738 * - empty string is desktop, all other windows have unique prefix
739 * @return null or last known dimensions scaled to current geometry (if last
740 * window geom was known)
742 Rectangle getLastKnownDimensions(String windowName)
744 // TODO: lock aspect ratio for scaling desktop Bug #0058199
745 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
746 String x = Cache.getProperty(windowName + "SCREEN_X");
747 String y = Cache.getProperty(windowName + "SCREEN_Y");
748 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
749 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
750 if ((x != null) && (y != null) && (width != null) && (height != null))
752 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
753 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
754 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
756 // attempt #1 - try to cope with change in screen geometry - this
757 // version doesn't preserve original jv aspect ratio.
758 // take ratio of current screen size vs original screen size.
759 double sw = ((1f * screenSize.width) / (1f * Integer
760 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
761 double sh = ((1f * screenSize.height) / (1f * Integer
762 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
763 // rescale the bounds depending upon the current screen geometry.
764 ix = (int) (ix * sw);
765 iw = (int) (iw * sw);
766 iy = (int) (iy * sh);
767 ih = (int) (ih * sh);
768 while (ix >= screenSize.width)
770 jalview.bin.Console.debug(
771 "Window geometry location recall error: shifting horizontal to within screenbounds.");
772 ix -= screenSize.width;
774 while (iy >= screenSize.height)
776 jalview.bin.Console.debug(
777 "Window geometry location recall error: shifting vertical to within screenbounds.");
778 iy -= screenSize.height;
780 jalview.bin.Console.debug(
781 "Got last known dimensions for " + windowName + ": x:" + ix
782 + " y:" + iy + " width:" + iw + " height:" + ih);
784 // return dimensions for new instance
785 return new Rectangle(ix, iy, iw, ih);
790 void showPasteMenu(int x, int y)
792 JPopupMenu popup = new JPopupMenu();
793 JMenuItem item = new JMenuItem(
794 MessageManager.getString("label.paste_new_window"));
795 item.addActionListener(new ActionListener()
798 public void actionPerformed(ActionEvent evt)
805 popup.show(this, x, y);
810 // quick patch for JAL-4150 - needs some more work and test coverage
811 // TODO - unify below and AlignFrame.paste()
812 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
813 // clipboard has come from a different alignment window than the one where
814 // paste has been called! JAL-4151
816 if (Desktop.jalviewClipboard != null)
818 // The clipboard was filled from within Jalview, we must use the
820 // And dataset from the copied alignment
821 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
822 // be doubly sure that we create *new* sequence objects.
823 SequenceI[] sequences = new SequenceI[newseq.length];
824 for (int i = 0; i < newseq.length; i++)
826 sequences[i] = new Sequence(newseq[i]);
828 Alignment alignment = new Alignment(sequences);
829 // dataset is inherited
830 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
831 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
832 AlignFrame.DEFAULT_HEIGHT);
833 String newtitle = new String("Copied sequences");
835 if (Desktop.jalviewClipboard[2] != null)
837 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
838 af.viewport.setHiddenColumns(hc);
841 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
842 AlignFrame.DEFAULT_HEIGHT);
849 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
850 Transferable contents = c.getContents(this);
852 if (contents != null)
854 String file = (String) contents
855 .getTransferData(DataFlavor.stringFlavor);
857 FileFormatI format = new IdentifyFile().identify(file,
858 DataSourceType.PASTE);
860 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
863 } catch (Exception ex)
866 "Unable to paste alignment from system clipboard:\n" + ex);
872 * Adds and opens the given frame to the desktop
883 public static synchronized void addInternalFrame(
884 final JInternalFrame frame, String title, int w, int h)
886 addInternalFrame(frame, title, true, w, h, true, false);
890 * Add an internal frame to the Jalview desktop
897 * When true, display frame immediately, otherwise, caller must call
898 * setVisible themselves.
904 public static synchronized void addInternalFrame(
905 final JInternalFrame frame, String title, boolean makeVisible,
908 addInternalFrame(frame, title, makeVisible, w, h, true, false);
912 * Add an internal frame to the Jalview desktop and make it visible
925 public static synchronized void addInternalFrame(
926 final JInternalFrame frame, String title, int w, int h,
929 addInternalFrame(frame, title, true, w, h, resizable, false);
933 * Add an internal frame to the Jalview desktop
940 * When true, display frame immediately, otherwise, caller must call
941 * setVisible themselves.
948 * @param ignoreMinSize
949 * Do not set the default minimum size for frame
951 public static synchronized void addInternalFrame(
952 final JInternalFrame frame, String title, boolean makeVisible,
953 int w, int h, boolean resizable, boolean ignoreMinSize)
956 // TODO: allow callers to determine X and Y position of frame (eg. via
958 // TODO: consider fixing method to update entries in the window submenu with
959 // the current window title
961 frame.setTitle(title);
962 if (frame.getWidth() < 1 || frame.getHeight() < 1)
966 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
967 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
968 // IF JALVIEW IS RUNNING HEADLESS
969 // ///////////////////////////////////////////////
970 if (instance == null || (System.getProperty("java.awt.headless") != null
971 && System.getProperty("java.awt.headless").equals("true")))
980 frame.setMinimumSize(
981 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
983 // Set default dimension for Alignment Frame window.
984 // The Alignment Frame window could be added from a number of places,
986 // I did this here in order not to miss out on any Alignment frame.
987 if (frame instanceof AlignFrame)
989 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
990 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
994 frame.setVisible(makeVisible);
995 frame.setClosable(true);
996 frame.setResizable(resizable);
997 frame.setMaximizable(resizable);
998 frame.setIconifiable(resizable);
999 frame.setOpaque(Platform.isJS());
1001 if (frame.getX() < 1 && frame.getY() < 1)
1003 frame.setLocation(xOffset * openFrameCount,
1004 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1008 * add an entry for the new frame in the Window menu (and remove it when the
1011 final JMenuItem menuItem = new JMenuItem(title);
1012 frame.addInternalFrameListener(new InternalFrameAdapter()
1015 public void internalFrameActivated(InternalFrameEvent evt)
1017 JInternalFrame itf = desktop.getSelectedFrame();
1020 if (itf instanceof AlignFrame)
1022 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1029 public void internalFrameClosed(InternalFrameEvent evt)
1031 PaintRefresher.RemoveComponent(frame);
1034 * defensive check to prevent frames being added half off the window
1036 if (openFrameCount > 0)
1042 * ensure no reference to alignFrame retained by menu item listener
1044 if (menuItem.getActionListeners().length > 0)
1046 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1048 windowMenu.remove(menuItem);
1052 menuItem.addActionListener(new ActionListener()
1055 public void actionPerformed(ActionEvent e)
1059 frame.setSelected(true);
1060 frame.setIcon(false);
1061 } catch (java.beans.PropertyVetoException ex)
1068 setKeyBindings(frame);
1072 windowMenu.add(menuItem);
1077 frame.setSelected(true);
1078 frame.requestFocus();
1079 } catch (java.beans.PropertyVetoException ve)
1081 } catch (java.lang.ClassCastException cex)
1083 jalview.bin.Console.warn(
1084 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1090 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1095 private static void setKeyBindings(JInternalFrame frame)
1097 @SuppressWarnings("serial")
1098 final Action closeAction = new AbstractAction()
1101 public void actionPerformed(ActionEvent e)
1108 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1110 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1111 InputEvent.CTRL_DOWN_MASK);
1112 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1113 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1115 InputMap inputMap = frame
1116 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1117 String ctrlW = ctrlWKey.toString();
1118 inputMap.put(ctrlWKey, ctrlW);
1119 inputMap.put(cmdWKey, ctrlW);
1121 ActionMap actionMap = frame.getActionMap();
1122 actionMap.put(ctrlW, closeAction);
1126 public void lostOwnership(Clipboard clipboard, Transferable contents)
1130 Desktop.jalviewClipboard = null;
1133 internalCopy = false;
1137 public void dragEnter(DropTargetDragEvent evt)
1142 public void dragExit(DropTargetEvent evt)
1147 public void dragOver(DropTargetDragEvent evt)
1152 public void dropActionChanged(DropTargetDragEvent evt)
1163 public void drop(DropTargetDropEvent evt)
1165 boolean success = true;
1166 // JAL-1552 - acceptDrop required before getTransferable call for
1167 // Java's Transferable for native dnd
1168 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1169 Transferable t = evt.getTransferable();
1170 List<Object> files = new ArrayList<>();
1171 List<DataSourceType> protocols = new ArrayList<>();
1175 Desktop.transferFromDropTarget(files, protocols, evt, t);
1176 } catch (Exception e)
1178 e.printStackTrace();
1186 for (int i = 0; i < files.size(); i++)
1188 // BH 2018 File or String
1189 Object file = files.get(i);
1190 String fileName = file.toString();
1191 DataSourceType protocol = (protocols == null)
1192 ? DataSourceType.FILE
1194 FileFormatI format = null;
1196 if (fileName.endsWith(".jar"))
1198 format = FileFormat.Jalview;
1203 format = new IdentifyFile().identify(file, protocol);
1205 if (file instanceof File)
1207 Platform.cacheFileData((File) file);
1209 new FileLoader().LoadFile(null, file, protocol, format);
1212 } catch (Exception ex)
1217 evt.dropComplete(success); // need this to ensure input focus is properly
1218 // transfered to any new windows created
1228 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1230 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1231 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1232 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1233 BackupFiles.getEnabled());
1235 chooser.setFileView(new JalviewFileView());
1236 chooser.setDialogTitle(
1237 MessageManager.getString("label.open_local_file"));
1238 chooser.setToolTipText(MessageManager.getString("action.open"));
1240 chooser.setResponseHandler(0, () -> {
1241 File selectedFile = chooser.getSelectedFile();
1242 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1244 FileFormatI format = chooser.getSelectedFormat();
1247 * Call IdentifyFile to verify the file contains what its extension implies.
1248 * Skip this step for dynamically added file formats, because IdentifyFile does
1249 * not know how to recognise them.
1251 if (FileFormats.getInstance().isIdentifiable(format))
1255 format = new IdentifyFile().identify(selectedFile,
1256 DataSourceType.FILE);
1257 } catch (FileFormatException e)
1259 // format = null; //??
1263 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1267 chooser.showOpenDialog(this);
1271 * Shows a dialog for input of a URL at which to retrieve alignment data
1276 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1278 // This construct allows us to have a wider textfield
1280 JLabel label = new JLabel(
1281 MessageManager.getString("label.input_file_url"));
1283 JPanel panel = new JPanel(new GridLayout(2, 1));
1287 * the URL to fetch is input in Java: an editable combobox with history JS:
1288 * (pending JAL-3038) a plain text field
1291 String urlBase = "https://www.";
1292 if (Platform.isJS())
1294 history = new JTextField(urlBase, 35);
1303 JComboBox<String> asCombo = new JComboBox<>();
1304 asCombo.setPreferredSize(new Dimension(400, 20));
1305 asCombo.setEditable(true);
1306 asCombo.addItem(urlBase);
1307 String historyItems = Cache.getProperty("RECENT_URL");
1308 if (historyItems != null)
1310 for (String token : historyItems.split("\\t"))
1312 asCombo.addItem(token);
1319 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1320 MessageManager.getString("action.cancel") };
1321 Callable<Void> action = () -> {
1322 @SuppressWarnings("unchecked")
1323 String url = (history instanceof JTextField
1324 ? ((JTextField) history).getText()
1325 : ((JComboBox<String>) history).getEditor().getItem()
1326 .toString().trim());
1328 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1330 if (viewport != null)
1332 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1333 FileFormat.Jalview);
1337 new FileLoader().LoadFile(url, DataSourceType.URL,
1338 FileFormat.Jalview);
1343 FileFormatI format = null;
1346 format = new IdentifyFile().identify(url, DataSourceType.URL);
1347 } catch (FileFormatException e)
1349 // TODO revise error handling, distinguish between
1350 // URL not found and response not valid
1355 String msg = MessageManager.formatMessage("label.couldnt_locate",
1357 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1358 MessageManager.getString("label.url_not_found"),
1359 JvOptionPane.WARNING_MESSAGE);
1361 return null; // Void
1364 if (viewport != null)
1366 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1371 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1374 return null; // Void
1376 String dialogOption = MessageManager
1377 .getString("label.input_alignment_from_url");
1378 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1379 .showInternalDialog(panel, dialogOption,
1380 JvOptionPane.YES_NO_CANCEL_OPTION,
1381 JvOptionPane.PLAIN_MESSAGE, null, options,
1382 MessageManager.getString("action.ok"));
1386 * Opens the CutAndPaste window for the user to paste an alignment in to
1389 * - if not null, the pasted alignment is added to the current
1390 * alignment; if null, to a new alignment window
1393 public void inputTextboxMenuItem_actionPerformed(
1394 AlignmentViewPanel viewPanel)
1396 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1397 cap.setForInput(viewPanel);
1398 Desktop.addInternalFrame(cap,
1399 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1404 * Check with user and saving files before actually quitting
1406 public void desktopQuit()
1408 desktopQuit(true, false);
1411 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1413 final Callable<Void> doDesktopQuit = () -> {
1414 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1415 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1416 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1417 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1418 getBounds().y, getWidth(), getHeight()));
1420 if (jconsole != null)
1422 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1423 jconsole.stopConsole();
1428 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1431 // Frames should all close automatically. Keeping external
1432 // viewers open should already be decided by user.
1433 closeAll_actionPerformed(null);
1435 // check for aborted quit
1436 if (QuitHandler.quitCancelled())
1438 jalview.bin.Console.debug("Desktop aborting quit");
1442 if (dialogExecutor != null)
1444 dialogExecutor.shutdownNow();
1447 if (groovyConsole != null)
1449 // suppress a possible repeat prompt to save script
1450 groovyConsole.setDirty(false);
1451 groovyConsole.exit();
1454 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1456 // note that shutdown hook will not be run
1457 jalview.bin.Console.debug("Force Quit selected by user");
1458 Runtime.getRuntime().halt(0);
1461 jalview.bin.Console.debug("Quit selected by user");
1464 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1465 // instance.dispose();
1469 return null; // Void
1472 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1473 QuitHandler.defaultCancelQuit);
1477 * Don't call this directly, use desktopQuit() above. Exits the program.
1482 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1483 // not run a second time if gotQuitResponse flag has been set (i.e. user
1484 // confirmed quit of some kind).
1485 Jalview.exit("Desktop exiting.", 0);
1488 private void storeLastKnownDimensions(String string, Rectangle jc)
1490 jalview.bin.Console.debug("Storing last known dimensions for " + string
1491 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1492 + " height:" + jc.height);
1494 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1495 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1496 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1497 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1507 public void aboutMenuItem_actionPerformed(ActionEvent e)
1509 new Thread(new Runnable()
1514 new SplashScreen(false);
1520 * Returns the html text for the About screen, including any available version
1521 * number, build details, author details and citation reference, but without
1522 * the enclosing {@code html} tags
1526 public String getAboutMessage()
1528 StringBuilder message = new StringBuilder(1024);
1529 message.append("<div style=\"font-family: sans-serif;\">")
1530 .append("<h1><strong>Version: ")
1531 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1532 .append("<strong>Built: <em>")
1533 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1534 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1535 .append("</strong>");
1537 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1538 if (latestVersion.equals("Checking"))
1540 // JBP removed this message for 2.11: May be reinstated in future version
1541 // message.append("<br>...Checking latest version...</br>");
1543 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1545 boolean red = false;
1546 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1547 .indexOf("automated build") == -1)
1550 // Displayed when code version and jnlp version do not match and code
1551 // version is not a development build
1552 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1555 message.append("<br>!! Version ")
1556 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1557 .append(" is available for download from ")
1558 .append(Cache.getDefault("www.jalview.org",
1559 "https://www.jalview.org"))
1563 message.append("</div>");
1566 message.append("<br>Authors: ");
1567 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1568 message.append(CITATION);
1570 message.append("</div>");
1572 return message.toString();
1576 * Action on requesting Help documentation
1579 public void documentationMenuItem_actionPerformed()
1583 if (Platform.isJS())
1585 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1594 Help.showHelpWindow();
1596 } catch (Exception ex)
1598 System.err.println("Error opening help: " + ex.getMessage());
1603 public void closeAll_actionPerformed(ActionEvent e)
1605 // TODO show a progress bar while closing?
1606 JInternalFrame[] frames = desktop.getAllFrames();
1607 for (int i = 0; i < frames.length; i++)
1611 frames[i].setClosed(true);
1612 } catch (java.beans.PropertyVetoException ex)
1616 Jalview.setCurrentAlignFrame(null);
1617 System.out.println("ALL CLOSED");
1620 * reset state of singleton objects as appropriate (clear down session state
1621 * when all windows are closed)
1623 StructureSelectionManager ssm = StructureSelectionManager
1624 .getStructureSelectionManager(this);
1631 public int structureViewersStillRunningCount()
1634 JInternalFrame[] frames = desktop.getAllFrames();
1635 for (int i = 0; i < frames.length; i++)
1637 if (frames[i] != null
1638 && frames[i] instanceof JalviewStructureDisplayI)
1640 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1648 public void raiseRelated_actionPerformed(ActionEvent e)
1650 reorderAssociatedWindows(false, false);
1654 public void minimizeAssociated_actionPerformed(ActionEvent e)
1656 reorderAssociatedWindows(true, false);
1659 void closeAssociatedWindows()
1661 reorderAssociatedWindows(false, true);
1667 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1671 protected void garbageCollect_actionPerformed(ActionEvent e)
1673 // We simply collect the garbage
1674 jalview.bin.Console.debug("Collecting garbage...");
1676 jalview.bin.Console.debug("Finished garbage collection.");
1682 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1686 protected void showMemusage_actionPerformed(ActionEvent e)
1688 desktop.showMemoryUsage(showMemusage.isSelected());
1695 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1699 protected void showConsole_actionPerformed(ActionEvent e)
1701 showConsole(showConsole.isSelected());
1704 Console jconsole = null;
1707 * control whether the java console is visible or not
1711 void showConsole(boolean selected)
1713 // TODO: decide if we should update properties file
1714 if (jconsole != null) // BH 2018
1716 showConsole.setSelected(selected);
1717 Cache.setProperty("SHOW_JAVA_CONSOLE",
1718 Boolean.valueOf(selected).toString());
1719 jconsole.setVisible(selected);
1723 void reorderAssociatedWindows(boolean minimize, boolean close)
1725 JInternalFrame[] frames = desktop.getAllFrames();
1726 if (frames == null || frames.length < 1)
1731 AlignmentViewport source = null, target = null;
1732 if (frames[0] instanceof AlignFrame)
1734 source = ((AlignFrame) frames[0]).getCurrentView();
1736 else if (frames[0] instanceof TreePanel)
1738 source = ((TreePanel) frames[0]).getViewPort();
1740 else if (frames[0] instanceof PCAPanel)
1742 source = ((PCAPanel) frames[0]).av;
1744 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1746 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1751 for (int i = 0; i < frames.length; i++)
1754 if (frames[i] == null)
1758 if (frames[i] instanceof AlignFrame)
1760 target = ((AlignFrame) frames[i]).getCurrentView();
1762 else if (frames[i] instanceof TreePanel)
1764 target = ((TreePanel) frames[i]).getViewPort();
1766 else if (frames[i] instanceof PCAPanel)
1768 target = ((PCAPanel) frames[i]).av;
1770 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1772 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1775 if (source == target)
1781 frames[i].setClosed(true);
1785 frames[i].setIcon(minimize);
1788 frames[i].toFront();
1792 } catch (java.beans.PropertyVetoException ex)
1807 protected void preferences_actionPerformed(ActionEvent e)
1809 Preferences.openPreferences();
1813 * Prompts the user to choose a file and then saves the Jalview state as a
1814 * Jalview project file
1817 public void saveState_actionPerformed()
1819 saveState_actionPerformed(false);
1822 public void saveState_actionPerformed(boolean saveAs)
1824 java.io.File projectFile = getProjectFile();
1825 // autoSave indicates we already have a file and don't need to ask
1826 boolean autoSave = projectFile != null && !saveAs
1827 && BackupFiles.getEnabled();
1829 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1830 // saveAs="+saveAs+", Backups
1831 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1833 boolean approveSave = false;
1836 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1839 chooser.setFileView(new JalviewFileView());
1840 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1842 int value = chooser.showSaveDialog(this);
1844 if (value == JalviewFileChooser.APPROVE_OPTION)
1846 projectFile = chooser.getSelectedFile();
1847 setProjectFile(projectFile);
1852 if (approveSave || autoSave)
1854 final Desktop me = this;
1855 final java.io.File chosenFile = projectFile;
1856 new Thread(new Runnable()
1861 // TODO: refactor to Jalview desktop session controller action.
1862 setProgressBar(MessageManager.formatMessage(
1863 "label.saving_jalview_project", new Object[]
1864 { chosenFile.getName() }), chosenFile.hashCode());
1865 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1866 // TODO catch and handle errors for savestate
1867 // TODO prevent user from messing with the Desktop whilst we're saving
1870 boolean doBackup = BackupFiles.getEnabled();
1871 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1874 new Jalview2XML().saveState(
1875 doBackup ? backupfiles.getTempFile() : chosenFile);
1879 backupfiles.setWriteSuccess(true);
1880 backupfiles.rollBackupsAndRenameTempFile();
1882 } catch (OutOfMemoryError oom)
1884 new OOMWarning("Whilst saving current state to "
1885 + chosenFile.getName(), oom);
1886 } catch (Exception ex)
1888 jalview.bin.Console.error("Problems whilst trying to save to "
1889 + chosenFile.getName(), ex);
1890 JvOptionPane.showMessageDialog(me,
1891 MessageManager.formatMessage(
1892 "label.error_whilst_saving_current_state_to",
1894 { chosenFile.getName() }),
1895 MessageManager.getString("label.couldnt_save_project"),
1896 JvOptionPane.WARNING_MESSAGE);
1898 setProgressBar(null, chosenFile.hashCode());
1905 public void saveAsState_actionPerformed(ActionEvent e)
1907 saveState_actionPerformed(true);
1910 protected void setProjectFile(File choice)
1912 this.projectFile = choice;
1915 public File getProjectFile()
1917 return this.projectFile;
1921 * Shows a file chooser dialog and tries to read in the selected file as a
1925 public void loadState_actionPerformed()
1927 final String[] suffix = new String[] { "jvp", "jar" };
1928 final String[] desc = new String[] { "Jalview Project",
1929 "Jalview Project (old)" };
1930 JalviewFileChooser chooser = new JalviewFileChooser(
1931 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1932 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1936 chooser.setFileView(new JalviewFileView());
1937 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1938 chooser.setResponseHandler(0, () -> {
1939 File selectedFile = chooser.getSelectedFile();
1940 setProjectFile(selectedFile);
1941 String choice = selectedFile.getAbsolutePath();
1942 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1943 new Thread(new Runnable()
1950 new Jalview2XML().loadJalviewAlign(selectedFile);
1951 } catch (OutOfMemoryError oom)
1953 new OOMWarning("Whilst loading project from " + choice, oom);
1954 } catch (Exception ex)
1956 jalview.bin.Console.error(
1957 "Problems whilst loading project from " + choice, ex);
1958 JvOptionPane.showMessageDialog(Desktop.desktop,
1959 MessageManager.formatMessage(
1960 "label.error_whilst_loading_project_from",
1963 MessageManager.getString("label.couldnt_load_project"),
1964 JvOptionPane.WARNING_MESSAGE);
1967 }, "Project Loader").start();
1971 chooser.showOpenDialog(this);
1975 public void inputSequence_actionPerformed(ActionEvent e)
1977 new SequenceFetcher(this);
1980 JPanel progressPanel;
1982 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1984 public void startLoading(final Object fileName)
1986 if (fileLoadingCount == 0)
1988 fileLoadingPanels.add(addProgressPanel(MessageManager
1989 .formatMessage("label.loading_file", new Object[]
1995 private JPanel addProgressPanel(String string)
1997 if (progressPanel == null)
1999 progressPanel = new JPanel(new GridLayout(1, 1));
2000 totalProgressCount = 0;
2001 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2003 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2004 JProgressBar progressBar = new JProgressBar();
2005 progressBar.setIndeterminate(true);
2007 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2009 thisprogress.add(progressBar, BorderLayout.CENTER);
2010 progressPanel.add(thisprogress);
2011 ((GridLayout) progressPanel.getLayout()).setRows(
2012 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2013 ++totalProgressCount;
2014 instance.validate();
2015 return thisprogress;
2018 int totalProgressCount = 0;
2020 private void removeProgressPanel(JPanel progbar)
2022 if (progressPanel != null)
2024 synchronized (progressPanel)
2026 progressPanel.remove(progbar);
2027 GridLayout gl = (GridLayout) progressPanel.getLayout();
2028 gl.setRows(gl.getRows() - 1);
2029 if (--totalProgressCount < 1)
2031 this.getContentPane().remove(progressPanel);
2032 progressPanel = null;
2039 public void stopLoading()
2042 if (fileLoadingCount < 1)
2044 while (fileLoadingPanels.size() > 0)
2046 removeProgressPanel(fileLoadingPanels.remove(0));
2048 fileLoadingPanels.clear();
2049 fileLoadingCount = 0;
2054 public static int getViewCount(String alignmentId)
2056 AlignmentViewport[] aps = getViewports(alignmentId);
2057 return (aps == null) ? 0 : aps.length;
2062 * @param alignmentId
2063 * - if null, all sets are returned
2064 * @return all AlignmentPanels concerning the alignmentId sequence set
2066 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2068 if (Desktop.desktop == null)
2070 // no frames created and in headless mode
2071 // TODO: verify that frames are recoverable when in headless mode
2074 List<AlignmentPanel> aps = new ArrayList<>();
2075 AlignFrame[] frames = getAlignFrames();
2080 for (AlignFrame af : frames)
2082 for (AlignmentPanel ap : af.alignPanels)
2084 if (alignmentId == null
2085 || alignmentId.equals(ap.av.getSequenceSetId()))
2091 if (aps.size() == 0)
2095 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2100 * get all the viewports on an alignment.
2102 * @param sequenceSetId
2103 * unique alignment id (may be null - all viewports returned in that
2105 * @return all viewports on the alignment bound to sequenceSetId
2107 public static AlignmentViewport[] getViewports(String sequenceSetId)
2109 List<AlignmentViewport> viewp = new ArrayList<>();
2110 if (desktop != null)
2112 AlignFrame[] frames = Desktop.getAlignFrames();
2114 for (AlignFrame afr : frames)
2116 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2117 .equals(sequenceSetId))
2119 if (afr.alignPanels != null)
2121 for (AlignmentPanel ap : afr.alignPanels)
2123 if (sequenceSetId == null
2124 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2132 viewp.add(afr.getViewport());
2136 if (viewp.size() > 0)
2138 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2145 * Explode the views in the given frame into separate AlignFrame
2149 public static void explodeViews(AlignFrame af)
2151 int size = af.alignPanels.size();
2157 // FIXME: ideally should use UI interface API
2158 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2159 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2160 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2161 for (int i = 0; i < size; i++)
2163 AlignmentPanel ap = af.alignPanels.get(i);
2165 AlignFrame newaf = new AlignFrame(ap);
2167 // transfer reference for existing feature settings to new alignFrame
2168 if (ap == af.alignPanel)
2170 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2172 newaf.featureSettings = viewFeatureSettings;
2174 newaf.setFeatureSettingsGeometry(fsBounds);
2178 * Restore the view's last exploded frame geometry if known. Multiple views from
2179 * one exploded frame share and restore the same (frame) position and size.
2181 Rectangle geometry = ap.av.getExplodedGeometry();
2182 if (geometry != null)
2184 newaf.setBounds(geometry);
2187 ap.av.setGatherViewsHere(false);
2189 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2190 AlignFrame.DEFAULT_HEIGHT);
2191 // and materialise a new feature settings dialog instance for the new
2193 // (closes the old as if 'OK' was pressed)
2194 if (ap == af.alignPanel && newaf.featureSettings != null
2195 && newaf.featureSettings.isOpen()
2196 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2198 newaf.showFeatureSettingsUI();
2202 af.featureSettings = null;
2203 af.alignPanels.clear();
2204 af.closeMenuItem_actionPerformed(true);
2209 * Gather expanded views (separate AlignFrame's) with the same sequence set
2210 * identifier back in to this frame as additional views, and close the
2211 * expanded views. Note the expanded frames may themselves have multiple
2212 * views. We take the lot.
2216 public void gatherViews(AlignFrame source)
2218 source.viewport.setGatherViewsHere(true);
2219 source.viewport.setExplodedGeometry(source.getBounds());
2220 JInternalFrame[] frames = desktop.getAllFrames();
2221 String viewId = source.viewport.getSequenceSetId();
2222 for (int t = 0; t < frames.length; t++)
2224 if (frames[t] instanceof AlignFrame && frames[t] != source)
2226 AlignFrame af = (AlignFrame) frames[t];
2227 boolean gatherThis = false;
2228 for (int a = 0; a < af.alignPanels.size(); a++)
2230 AlignmentPanel ap = af.alignPanels.get(a);
2231 if (viewId.equals(ap.av.getSequenceSetId()))
2234 ap.av.setGatherViewsHere(false);
2235 ap.av.setExplodedGeometry(af.getBounds());
2236 source.addAlignmentPanel(ap, false);
2242 if (af.featureSettings != null && af.featureSettings.isOpen())
2244 if (source.featureSettings == null)
2246 // preserve the feature settings geometry for this frame
2247 source.featureSettings = af.featureSettings;
2248 source.setFeatureSettingsGeometry(
2249 af.getFeatureSettingsGeometry());
2253 // close it and forget
2254 af.featureSettings.close();
2257 af.alignPanels.clear();
2258 af.closeMenuItem_actionPerformed(true);
2263 // refresh the feature setting UI for the source frame if it exists
2264 if (source.featureSettings != null && source.featureSettings.isOpen())
2266 source.showFeatureSettingsUI();
2271 public JInternalFrame[] getAllFrames()
2273 return desktop.getAllFrames();
2277 * Checks the given url to see if it gives a response indicating that the user
2278 * should be informed of a new questionnaire.
2282 public void checkForQuestionnaire(String url)
2284 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2285 // javax.swing.SwingUtilities.invokeLater(jvq);
2286 new Thread(jvq).start();
2289 public void checkURLLinks()
2291 // Thread off the URL link checker
2292 addDialogThread(new Runnable()
2297 if (Cache.getDefault("CHECKURLLINKS", true))
2299 // check what the actual links are - if it's just the default don't
2300 // bother with the warning
2301 List<String> links = Preferences.sequenceUrlLinks
2304 // only need to check links if there is one with a
2305 // SEQUENCE_ID which is not the default EMBL_EBI link
2306 ListIterator<String> li = links.listIterator();
2307 boolean check = false;
2308 List<JLabel> urls = new ArrayList<>();
2309 while (li.hasNext())
2311 String link = li.next();
2312 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2313 && !UrlConstants.isDefaultString(link))
2316 int barPos = link.indexOf("|");
2317 String urlMsg = barPos == -1 ? link
2318 : link.substring(0, barPos) + ": "
2319 + link.substring(barPos + 1);
2320 urls.add(new JLabel(urlMsg));
2328 // ask user to check in case URL links use old style tokens
2329 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2330 JPanel msgPanel = new JPanel();
2331 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2332 msgPanel.add(Box.createVerticalGlue());
2333 JLabel msg = new JLabel(MessageManager
2334 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2335 JLabel msg2 = new JLabel(MessageManager
2336 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2338 for (JLabel url : urls)
2344 final JCheckBox jcb = new JCheckBox(
2345 MessageManager.getString("label.do_not_display_again"));
2346 jcb.addActionListener(new ActionListener()
2349 public void actionPerformed(ActionEvent e)
2351 // update Cache settings for "don't show this again"
2352 boolean showWarningAgain = !jcb.isSelected();
2353 Cache.setProperty("CHECKURLLINKS",
2354 Boolean.valueOf(showWarningAgain).toString());
2359 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2361 .getString("label.SEQUENCE_ID_no_longer_used"),
2362 JvOptionPane.WARNING_MESSAGE);
2369 * Proxy class for JDesktopPane which optionally displays the current memory
2370 * usage and highlights the desktop area with a red bar if free memory runs
2375 public class MyDesktopPane extends JDesktopPane implements Runnable
2377 private static final float ONE_MB = 1048576f;
2379 boolean showMemoryUsage = false;
2383 java.text.NumberFormat df;
2385 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2388 public MyDesktopPane(boolean showMemoryUsage)
2390 showMemoryUsage(showMemoryUsage);
2393 public void showMemoryUsage(boolean showMemory)
2395 this.showMemoryUsage = showMemory;
2398 Thread worker = new Thread(this);
2404 public boolean isShowMemoryUsage()
2406 return showMemoryUsage;
2412 df = java.text.NumberFormat.getNumberInstance();
2413 df.setMaximumFractionDigits(2);
2414 runtime = Runtime.getRuntime();
2416 while (showMemoryUsage)
2420 maxMemory = runtime.maxMemory() / ONE_MB;
2421 allocatedMemory = runtime.totalMemory() / ONE_MB;
2422 freeMemory = runtime.freeMemory() / ONE_MB;
2423 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2425 percentUsage = (totalFreeMemory / maxMemory) * 100;
2427 // if (percentUsage < 20)
2429 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2431 // instance.set.setBorder(border1);
2434 // sleep after showing usage
2436 } catch (Exception ex)
2438 ex.printStackTrace();
2444 public void paintComponent(Graphics g)
2446 if (showMemoryUsage && g != null && df != null)
2448 if (percentUsage < 20)
2450 g.setColor(Color.red);
2452 FontMetrics fm = g.getFontMetrics();
2455 g.drawString(MessageManager.formatMessage("label.memory_stats",
2457 { df.format(totalFreeMemory), df.format(maxMemory),
2458 df.format(percentUsage) }),
2459 10, getHeight() - fm.getHeight());
2463 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2464 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2469 * Accessor method to quickly get all the AlignmentFrames loaded.
2471 * @return an array of AlignFrame, or null if none found
2473 public static AlignFrame[] getAlignFrames()
2475 if (Jalview.isHeadlessMode())
2477 // Desktop.desktop is null in headless mode
2478 return new AlignFrame[] { Jalview.currentAlignFrame };
2481 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2487 List<AlignFrame> avp = new ArrayList<>();
2489 for (int i = frames.length - 1; i > -1; i--)
2491 if (frames[i] instanceof AlignFrame)
2493 avp.add((AlignFrame) frames[i]);
2495 else if (frames[i] instanceof SplitFrame)
2498 * Also check for a split frame containing an AlignFrame
2500 GSplitFrame sf = (GSplitFrame) frames[i];
2501 if (sf.getTopFrame() instanceof AlignFrame)
2503 avp.add((AlignFrame) sf.getTopFrame());
2505 if (sf.getBottomFrame() instanceof AlignFrame)
2507 avp.add((AlignFrame) sf.getBottomFrame());
2511 if (avp.size() == 0)
2515 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2520 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2524 public GStructureViewer[] getJmols()
2526 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2532 List<GStructureViewer> avp = new ArrayList<>();
2534 for (int i = frames.length - 1; i > -1; i--)
2536 if (frames[i] instanceof AppJmol)
2538 GStructureViewer af = (GStructureViewer) frames[i];
2542 if (avp.size() == 0)
2546 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2551 * Add Groovy Support to Jalview
2554 public void groovyShell_actionPerformed()
2558 openGroovyConsole();
2559 } catch (Exception ex)
2561 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2562 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2564 MessageManager.getString("label.couldnt_create_groovy_shell"),
2565 MessageManager.getString("label.groovy_support_failed"),
2566 JvOptionPane.ERROR_MESSAGE);
2571 * Open the Groovy console
2573 void openGroovyConsole()
2575 if (groovyConsole == null)
2577 groovyConsole = new groovy.ui.Console();
2578 groovyConsole.setVariable("Jalview", this);
2579 groovyConsole.run();
2582 * We allow only one console at a time, so that AlignFrame menu option
2583 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2584 * enable 'Run script', when the console is opened, and the reverse when it is
2587 Window window = (Window) groovyConsole.getFrame();
2588 window.addWindowListener(new WindowAdapter()
2591 public void windowClosed(WindowEvent e)
2594 * rebind CMD-Q from Groovy Console to Jalview Quit
2597 enableExecuteGroovy(false);
2603 * show Groovy console window (after close and reopen)
2605 ((Window) groovyConsole.getFrame()).setVisible(true);
2608 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2609 * opening a second console
2611 enableExecuteGroovy(true);
2615 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2616 * binding when opened
2618 protected void addQuitHandler()
2621 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2623 .getKeyStroke(KeyEvent.VK_Q,
2624 jalview.util.ShortcutKeyMaskExWrapper
2625 .getMenuShortcutKeyMaskEx()),
2627 getRootPane().getActionMap().put("Quit", new AbstractAction()
2630 public void actionPerformed(ActionEvent e)
2638 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2641 * true if Groovy console is open
2643 public void enableExecuteGroovy(boolean enabled)
2646 * disable opening a second Groovy console (or re-enable when the console is
2649 groovyShell.setEnabled(!enabled);
2651 AlignFrame[] alignFrames = getAlignFrames();
2652 if (alignFrames != null)
2654 for (AlignFrame af : alignFrames)
2656 af.setGroovyEnabled(enabled);
2662 * Progress bars managed by the IProgressIndicator method.
2664 private Hashtable<Long, JPanel> progressBars;
2666 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2671 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2674 public void setProgressBar(String message, long id)
2676 if (progressBars == null)
2678 progressBars = new Hashtable<>();
2679 progressBarHandlers = new Hashtable<>();
2682 if (progressBars.get(Long.valueOf(id)) != null)
2684 JPanel panel = progressBars.remove(Long.valueOf(id));
2685 if (progressBarHandlers.contains(Long.valueOf(id)))
2687 progressBarHandlers.remove(Long.valueOf(id));
2689 removeProgressPanel(panel);
2693 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2700 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2701 * jalview.gui.IProgressIndicatorHandler)
2704 public void registerHandler(final long id,
2705 final IProgressIndicatorHandler handler)
2707 if (progressBarHandlers == null
2708 || !progressBars.containsKey(Long.valueOf(id)))
2710 throw new Error(MessageManager.getString(
2711 "error.call_setprogressbar_before_registering_handler"));
2713 progressBarHandlers.put(Long.valueOf(id), handler);
2714 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2715 if (handler.canCancel())
2717 JButton cancel = new JButton(
2718 MessageManager.getString("action.cancel"));
2719 final IProgressIndicator us = this;
2720 cancel.addActionListener(new ActionListener()
2724 public void actionPerformed(ActionEvent e)
2726 handler.cancelActivity(id);
2727 us.setProgressBar(MessageManager
2728 .formatMessage("label.cancelled_params", new Object[]
2729 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2733 progressPanel.add(cancel, BorderLayout.EAST);
2739 * @return true if any progress bars are still active
2742 public boolean operationInProgress()
2744 if (progressBars != null && progressBars.size() > 0)
2752 * This will return the first AlignFrame holding the given viewport instance.
2753 * It will break if there are more than one AlignFrames viewing a particular
2757 * @return alignFrame for viewport
2759 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2761 if (desktop != null)
2763 AlignmentPanel[] aps = getAlignmentPanels(
2764 viewport.getSequenceSetId());
2765 for (int panel = 0; aps != null && panel < aps.length; panel++)
2767 if (aps[panel] != null && aps[panel].av == viewport)
2769 return aps[panel].alignFrame;
2776 public VamsasApplication getVamsasApplication()
2778 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2784 * flag set if jalview GUI is being operated programmatically
2786 private boolean inBatchMode = false;
2789 * check if jalview GUI is being operated programmatically
2791 * @return inBatchMode
2793 public boolean isInBatchMode()
2799 * set flag if jalview GUI is being operated programmatically
2801 * @param inBatchMode
2803 public void setInBatchMode(boolean inBatchMode)
2805 this.inBatchMode = inBatchMode;
2809 * start service discovery and wait till it is done
2811 public void startServiceDiscovery()
2813 startServiceDiscovery(false);
2817 * start service discovery threads - blocking or non-blocking
2821 public void startServiceDiscovery(boolean blocking)
2823 startServiceDiscovery(blocking, false);
2827 * start service discovery threads
2830 * - false means call returns immediately
2831 * @param ignore_SHOW_JWS2_SERVICES_preference
2832 * - when true JABA services are discovered regardless of user's JWS2
2833 * discovery preference setting
2835 public void startServiceDiscovery(boolean blocking,
2836 boolean ignore_SHOW_JWS2_SERVICES_preference)
2838 boolean alive = true;
2839 Thread t0 = null, t1 = null, t2 = null;
2840 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2843 // todo: changesupport handlers need to be transferred
2844 if (discoverer == null)
2846 discoverer = new jalview.ws.jws1.Discoverer();
2847 // register PCS handler for desktop.
2848 discoverer.addPropertyChangeListener(changeSupport);
2850 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2851 // until we phase out completely
2852 (t0 = new Thread(discoverer)).start();
2855 if (ignore_SHOW_JWS2_SERVICES_preference
2856 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2858 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2859 .startDiscoverer(changeSupport);
2863 // TODO: do rest service discovery
2872 } catch (Exception e)
2875 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2876 || (t3 != null && t3.isAlive())
2877 || (t0 != null && t0.isAlive());
2883 * called to check if the service discovery process completed successfully.
2887 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2889 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2891 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2892 .getErrorMessages();
2895 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2897 if (serviceChangedDialog == null)
2899 // only run if we aren't already displaying one of these.
2900 addDialogThread(serviceChangedDialog = new Runnable()
2907 * JalviewDialog jd =new JalviewDialog() {
2909 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2911 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2913 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2915 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2917 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2918 * + " or mis-configured HTTP proxy settings.<br/>" +
2919 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2920 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2921 * true, true, "Web Service Configuration Problem", 450, 400);
2923 * jd.waitForInput();
2925 JvOptionPane.showConfirmDialog(Desktop.desktop,
2926 new JLabel("<html><table width=\"450\"><tr><td>"
2927 + ermsg + "</td></tr></table>"
2928 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2929 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2930 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2931 + " Tools->Preferences dialog box to change them.</p></html>"),
2932 "Web Service Configuration Problem",
2933 JvOptionPane.DEFAULT_OPTION,
2934 JvOptionPane.ERROR_MESSAGE);
2935 serviceChangedDialog = null;
2943 jalview.bin.Console.error(
2944 "Errors reported by JABA discovery service. Check web services preferences.\n"
2951 private Runnable serviceChangedDialog = null;
2954 * start a thread to open a URL in the configured browser. Pops up a warning
2955 * dialog to the user if there is an exception when calling out to the browser
2960 public static void showUrl(final String url)
2962 showUrl(url, Desktop.instance);
2966 * Like showUrl but allows progress handler to be specified
2970 * (null) or object implementing IProgressIndicator
2972 public static void showUrl(final String url,
2973 final IProgressIndicator progress)
2975 new Thread(new Runnable()
2982 if (progress != null)
2984 progress.setProgressBar(MessageManager
2985 .formatMessage("status.opening_params", new Object[]
2986 { url }), this.hashCode());
2988 jalview.util.BrowserLauncher.openURL(url);
2989 } catch (Exception ex)
2991 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2993 .getString("label.web_browser_not_found_unix"),
2994 MessageManager.getString("label.web_browser_not_found"),
2995 JvOptionPane.WARNING_MESSAGE);
2997 ex.printStackTrace();
2999 if (progress != null)
3001 progress.setProgressBar(null, this.hashCode());
3007 public static WsParamSetManager wsparamManager = null;
3009 public static ParamManager getUserParameterStore()
3011 if (wsparamManager == null)
3013 wsparamManager = new WsParamSetManager();
3015 return wsparamManager;
3019 * static hyperlink handler proxy method for use by Jalview's internal windows
3023 public static void hyperlinkUpdate(HyperlinkEvent e)
3025 if (e.getEventType() == EventType.ACTIVATED)
3030 url = e.getURL().toString();
3031 Desktop.showUrl(url);
3032 } catch (Exception x)
3037 .error("Couldn't handle string " + url + " as a URL.");
3039 // ignore any exceptions due to dud links.
3046 * single thread that handles display of dialogs to user.
3048 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3051 * flag indicating if dialogExecutor should try to acquire a permit
3053 private volatile boolean dialogPause = true;
3058 private java.util.concurrent.Semaphore block = new Semaphore(0);
3060 private static groovy.ui.Console groovyConsole;
3063 * add another dialog thread to the queue
3067 public void addDialogThread(final Runnable prompter)
3069 dialogExecutor.submit(new Runnable()
3079 } catch (InterruptedException x)
3083 if (instance == null)
3089 SwingUtilities.invokeAndWait(prompter);
3090 } catch (Exception q)
3092 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3099 public void startDialogQueue()
3101 // set the flag so we don't pause waiting for another permit and semaphore
3102 // the current task to begin
3103 dialogPause = false;
3108 * Outputs an image of the desktop to file in EPS format, after prompting the
3109 * user for choice of Text or Lineart character rendering (unless a preference
3110 * has been set). The file name is generated as
3113 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3117 protected void snapShotWindow_actionPerformed(ActionEvent e)
3119 // currently the menu option to do this is not shown
3122 int width = getWidth();
3123 int height = getHeight();
3125 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3126 ImageWriterI writer = new ImageWriterI()
3129 public void exportImage(Graphics g) throws Exception
3132 jalview.bin.Console.info("Successfully written snapshot to file "
3133 + of.getAbsolutePath());
3136 String title = "View of desktop";
3137 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3139 exporter.doExport(of, this, width, height, title);
3143 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3144 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3145 * and location last time the view was expanded (if any). However it does not
3146 * remember the split pane divider location - this is set to match the
3147 * 'exploding' frame.
3151 public void explodeViews(SplitFrame sf)
3153 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3154 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3155 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3157 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3159 int viewCount = topPanels.size();
3166 * Processing in reverse order works, forwards order leaves the first panels not
3167 * visible. I don't know why!
3169 for (int i = viewCount - 1; i >= 0; i--)
3172 * Make new top and bottom frames. These take over the respective AlignmentPanel
3173 * objects, including their AlignmentViewports, so the cdna/protein
3174 * relationships between the viewports is carried over to the new split frames.
3176 * explodedGeometry holds the (x, y) position of the previously exploded
3177 * SplitFrame, and the (width, height) of the AlignFrame component
3179 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3180 AlignFrame newTopFrame = new AlignFrame(topPanel);
3181 newTopFrame.setSize(oldTopFrame.getSize());
3182 newTopFrame.setVisible(true);
3183 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3184 .getExplodedGeometry();
3185 if (geometry != null)
3187 newTopFrame.setSize(geometry.getSize());
3190 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3191 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3192 newBottomFrame.setSize(oldBottomFrame.getSize());
3193 newBottomFrame.setVisible(true);
3194 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3195 .getExplodedGeometry();
3196 if (geometry != null)
3198 newBottomFrame.setSize(geometry.getSize());
3201 topPanel.av.setGatherViewsHere(false);
3202 bottomPanel.av.setGatherViewsHere(false);
3203 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3205 if (geometry != null)
3207 splitFrame.setLocation(geometry.getLocation());
3209 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3213 * Clear references to the panels (now relocated in the new SplitFrames) before
3214 * closing the old SplitFrame.
3217 bottomPanels.clear();
3222 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3223 * back into the given SplitFrame as additional views. Note that the gathered
3224 * frames may themselves have multiple views.
3228 public void gatherViews(GSplitFrame source)
3231 * special handling of explodedGeometry for a view within a SplitFrame: - it
3232 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3233 * height) of the AlignFrame component
3235 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3236 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3237 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3238 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3239 myBottomFrame.viewport
3240 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3241 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3242 myTopFrame.viewport.setGatherViewsHere(true);
3243 myBottomFrame.viewport.setGatherViewsHere(true);
3244 String topViewId = myTopFrame.viewport.getSequenceSetId();
3245 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3247 JInternalFrame[] frames = desktop.getAllFrames();
3248 for (JInternalFrame frame : frames)
3250 if (frame instanceof SplitFrame && frame != source)
3252 SplitFrame sf = (SplitFrame) frame;
3253 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3254 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3255 boolean gatherThis = false;
3256 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3258 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3259 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3260 if (topViewId.equals(topPanel.av.getSequenceSetId())
3261 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3264 topPanel.av.setGatherViewsHere(false);
3265 bottomPanel.av.setGatherViewsHere(false);
3266 topPanel.av.setExplodedGeometry(
3267 new Rectangle(sf.getLocation(), topFrame.getSize()));
3268 bottomPanel.av.setExplodedGeometry(
3269 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3270 myTopFrame.addAlignmentPanel(topPanel, false);
3271 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3277 topFrame.getAlignPanels().clear();
3278 bottomFrame.getAlignPanels().clear();
3285 * The dust settles...give focus to the tab we did this from.
3287 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3290 public static groovy.ui.Console getGroovyConsole()
3292 return groovyConsole;
3296 * handles the payload of a drag and drop event.
3298 * TODO refactor to desktop utilities class
3301 * - Data source strings extracted from the drop event
3303 * - protocol for each data source extracted from the drop event
3307 * - the payload from the drop event
3310 public static void transferFromDropTarget(List<Object> files,
3311 List<DataSourceType> protocols, DropTargetDropEvent evt,
3312 Transferable t) throws Exception
3315 DataFlavor uriListFlavor = new DataFlavor(
3316 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3319 urlFlavour = new DataFlavor(
3320 "application/x-java-url; class=java.net.URL");
3321 } catch (ClassNotFoundException cfe)
3323 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3327 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3332 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3333 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3334 // means url may be null.
3337 protocols.add(DataSourceType.URL);
3338 files.add(url.toString());
3339 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3340 + files.get(files.size() - 1));
3345 if (Platform.isAMacAndNotJS())
3348 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3351 } catch (Throwable ex)
3353 jalview.bin.Console.debug("URL drop handler failed.", ex);
3356 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3358 // Works on Windows and MacOSX
3359 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3360 for (Object file : (List) t
3361 .getTransferData(DataFlavor.javaFileListFlavor))
3364 protocols.add(DataSourceType.FILE);
3369 // Unix like behaviour
3370 boolean added = false;
3372 if (t.isDataFlavorSupported(uriListFlavor))
3374 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3375 // This is used by Unix drag system
3376 data = (String) t.getTransferData(uriListFlavor);
3380 // fallback to text: workaround - on OSX where there's a JVM bug
3382 .debug("standard URIListFlavor failed. Trying text");
3383 // try text fallback
3384 DataFlavor textDf = new DataFlavor(
3385 "text/plain;class=java.lang.String");
3386 if (t.isDataFlavorSupported(textDf))
3388 data = (String) t.getTransferData(textDf);
3391 jalview.bin.Console.debug("Plain text drop content returned "
3392 + (data == null ? "Null - failed" : data));
3397 while (protocols.size() < files.size())
3399 jalview.bin.Console.debug("Adding missing FILE protocol for "
3400 + files.get(protocols.size()));
3401 protocols.add(DataSourceType.FILE);
3403 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3404 data, "\r\n"); st.hasMoreTokens();)
3407 String s = st.nextToken();
3408 if (s.startsWith("#"))
3410 // the line is a comment (as per the RFC 2483)
3413 java.net.URI uri = new java.net.URI(s);
3414 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3416 protocols.add(DataSourceType.URL);
3417 files.add(uri.toString());
3421 // otherwise preserve old behaviour: catch all for file objects
3422 java.io.File file = new java.io.File(uri);
3423 protocols.add(DataSourceType.FILE);
3424 files.add(file.toString());
3429 if (jalview.bin.Console.isDebugEnabled())
3431 if (data == null || !added)
3434 if (t.getTransferDataFlavors() != null
3435 && t.getTransferDataFlavors().length > 0)
3437 jalview.bin.Console.debug(
3438 "Couldn't resolve drop data. Here are the supported flavors:");
3439 for (DataFlavor fl : t.getTransferDataFlavors())
3441 jalview.bin.Console.debug(
3442 "Supported transfer dataflavor: " + fl.toString());
3443 Object df = t.getTransferData(fl);
3446 jalview.bin.Console.debug("Retrieves: " + df);
3450 jalview.bin.Console.debug("Retrieved nothing");
3457 .debug("Couldn't resolve dataflavor for drop: "
3463 if (Platform.isWindowsAndNotJS())
3466 .debug("Scanning dropped content for Windows Link Files");
3468 // resolve any .lnk files in the file drop
3469 for (int f = 0; f < files.size(); f++)
3471 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3472 if (protocols.get(f).equals(DataSourceType.FILE)
3473 && (source.endsWith(".lnk") || source.endsWith(".url")
3474 || source.endsWith(".site")))
3478 Object obj = files.get(f);
3479 File lf = (obj instanceof File ? (File) obj
3480 : new File((String) obj));
3481 // process link file to get a URL
3482 jalview.bin.Console.debug("Found potential link file: " + lf);
3483 WindowsShortcut wscfile = new WindowsShortcut(lf);
3484 String fullname = wscfile.getRealFilename();
3485 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3486 files.set(f, fullname);
3487 jalview.bin.Console.debug("Parsed real filename " + fullname
3488 + " to extract protocol: " + protocols.get(f));
3489 } catch (Exception ex)
3491 jalview.bin.Console.error(
3492 "Couldn't parse " + files.get(f) + " as a link file.",
3501 * Sets the Preferences property for experimental features to True or False
3502 * depending on the state of the controlling menu item
3505 protected void showExperimental_actionPerformed(boolean selected)
3507 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3511 * Answers a (possibly empty) list of any structure viewer frames (currently
3512 * for either Jmol or Chimera) which are currently open. This may optionally
3513 * be restricted to viewers of a specified class, or viewers linked to a
3514 * specified alignment panel.
3517 * if not null, only return viewers linked to this panel
3518 * @param structureViewerClass
3519 * if not null, only return viewers of this class
3522 public List<StructureViewerBase> getStructureViewers(
3523 AlignmentPanel apanel,
3524 Class<? extends StructureViewerBase> structureViewerClass)
3526 List<StructureViewerBase> result = new ArrayList<>();
3527 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3529 for (JInternalFrame frame : frames)
3531 if (frame instanceof StructureViewerBase)
3533 if (structureViewerClass == null
3534 || structureViewerClass.isInstance(frame))
3537 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3539 result.add((StructureViewerBase) frame);
3547 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3549 private static boolean debugScaleMessageDone = false;
3551 public static void debugScaleMessage(Graphics g)
3553 if (debugScaleMessageDone)
3557 // output used by tests to check HiDPI scaling settings in action
3560 Graphics2D gg = (Graphics2D) g;
3563 AffineTransform t = gg.getTransform();
3564 double scaleX = t.getScaleX();
3565 double scaleY = t.getScaleY();
3566 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3567 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3568 debugScaleMessageDone = true;
3572 jalview.bin.Console.debug("Desktop graphics null");
3574 } catch (Exception e)
3576 jalview.bin.Console.debug(Cache.getStackTraceString(e));