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.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JFrame;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.WindowConstants;
95 import javax.swing.event.HyperlinkEvent;
96 import javax.swing.event.HyperlinkEvent.EventType;
97 import javax.swing.event.InternalFrameAdapter;
98 import javax.swing.event.InternalFrameEvent;
100 import org.stackoverflowusers.file.WindowsShortcut;
102 import jalview.api.AlignViewportI;
103 import jalview.api.AlignmentViewPanel;
104 import jalview.api.structures.JalviewStructureDisplayI;
105 import jalview.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.datamodel.Alignment;
108 import jalview.datamodel.HiddenColumns;
109 import jalview.datamodel.Sequence;
110 import jalview.datamodel.SequenceI;
111 import jalview.gui.ImageExporter.ImageWriterI;
112 import jalview.gui.QuitHandler.QResponse;
113 import jalview.io.BackupFiles;
114 import jalview.io.DataSourceType;
115 import jalview.io.FileFormat;
116 import jalview.io.FileFormatException;
117 import jalview.io.FileFormatI;
118 import jalview.io.FileFormats;
119 import jalview.io.FileLoader;
120 import jalview.io.FormatAdapter;
121 import jalview.io.IdentifyFile;
122 import jalview.io.JalviewFileChooser;
123 import jalview.io.JalviewFileView;
124 import jalview.io.exceptions.ImageOutputException;
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.");
445 final String awtAppClassName = "awtAppClassName";
448 Toolkit xToolkit = Toolkit.getDefaultToolkit();
449 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
450 Field awtAppClassNameField = null;
452 if (Arrays.stream(declaredFields)
453 .anyMatch(f -> f.getName().equals(awtAppClassName)))
455 awtAppClassNameField = xToolkit.getClass()
456 .getDeclaredField(awtAppClassName);
459 String title = ChannelProperties.getProperty("app_name");
460 if (awtAppClassNameField != null)
462 awtAppClassNameField.setAccessible(true);
463 awtAppClassNameField.set(xToolkit, title);
468 .debug("XToolkit: " + awtAppClassName + " not found");
470 } catch (Exception e)
472 jalview.bin.Console.debug("Error setting " + awtAppClassName);
473 jalview.bin.Console.trace(Cache.getStackTraceString(e));
477 setIconImages(ChannelProperties.getIconList());
479 // override quit handling when GUI OS close [X] button pressed
480 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
481 addWindowListener(new WindowAdapter()
484 public void windowClosing(WindowEvent ev)
486 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
490 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
492 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
493 desktop = new MyDesktopPane(selmemusage);
495 showMemusage.setSelected(selmemusage);
496 desktop.setBackground(Color.white);
498 getContentPane().setLayout(new BorderLayout());
499 // alternate config - have scrollbars - see notes in JAL-153
500 // JScrollPane sp = new JScrollPane();
501 // sp.getViewport().setView(desktop);
502 // getContentPane().add(sp, BorderLayout.CENTER);
504 // BH 2018 - just an experiment to try unclipped JInternalFrames.
507 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
510 getContentPane().add(desktop, BorderLayout.CENTER);
511 desktop.setDragMode(DRAG_MODE);
513 // This line prevents Windows Look&Feel resizing all new windows to maximum
514 // if previous window was maximised
515 desktop.setDesktopManager(new MyDesktopManager(
516 Platform.isJS() ? desktop.getDesktopManager()
517 : new DefaultDesktopManager()));
519 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
520 : Platform.isAMacAndNotJS()
521 ? new AquaInternalFrameManager(
522 desktop.getDesktopManager())
523 : desktop.getDesktopManager())));
526 Rectangle dims = getLastKnownDimensions("");
533 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
534 int xPos = Math.max(5, (screenSize.width - 900) / 2);
535 int yPos = Math.max(5, (screenSize.height - 650) / 2);
536 setBounds(xPos, yPos, 900, 650);
539 // start dialogue queue for single dialogues
542 if (!Platform.isJS())
549 jconsole = new Console(this, showjconsole);
550 jconsole.setHeader(Cache.getVersionDetailsForConsole());
551 showConsole(showjconsole);
553 showNews.setVisible(false);
555 experimentalFeatures.setSelected(showExperimental());
557 getIdentifiersOrgData();
561 // Spawn a thread that shows the splashscreen
564 SwingUtilities.invokeLater(new Runnable()
569 new SplashScreen(true);
574 // Thread off a new instance of the file chooser - this reduces the time
576 // takes to open it later on.
577 new Thread(new Runnable()
582 jalview.bin.Console.debug("Filechooser init thread started.");
583 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
584 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
586 jalview.bin.Console.debug("Filechooser init thread finished.");
589 // Add the service change listener
590 changeSupport.addJalviewPropertyChangeListener("services",
591 new PropertyChangeListener()
595 public void propertyChange(PropertyChangeEvent evt)
598 .debug("Firing service changed event for "
599 + evt.getNewValue());
600 JalviewServicesChanged(evt);
605 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
608 this.addMouseListener(ma = new MouseAdapter()
611 public void mousePressed(MouseEvent evt)
613 if (evt.isPopupTrigger()) // Mac
615 showPasteMenu(evt.getX(), evt.getY());
620 public void mouseReleased(MouseEvent evt)
622 if (evt.isPopupTrigger()) // Windows
624 showPasteMenu(evt.getX(), evt.getY());
628 desktop.addMouseListener(ma);
632 // used for jalviewjsTest
633 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
638 * Answers true if user preferences to enable experimental features is True
643 public boolean showExperimental()
645 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
646 Boolean.FALSE.toString());
647 return Boolean.valueOf(experimental).booleanValue();
650 public void doConfigureStructurePrefs()
652 // configure services
653 StructureSelectionManager ssm = StructureSelectionManager
654 .getStructureSelectionManager(this);
655 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
657 ssm.setAddTempFacAnnot(
658 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
659 ssm.setProcessSecondaryStructure(
660 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
661 // JAL-3915 - RNAView is no longer an option so this has no effect
662 ssm.setSecStructServices(
663 Cache.getDefault(Preferences.USE_RNAVIEW, false));
667 ssm.setAddTempFacAnnot(false);
668 ssm.setProcessSecondaryStructure(false);
669 ssm.setSecStructServices(false);
673 public void checkForNews()
675 final Desktop me = this;
676 // Thread off the news reader, in case there are connection problems.
677 new Thread(new Runnable()
682 jalview.bin.Console.debug("Starting news thread.");
683 jvnews = new BlogReader(me);
684 showNews.setVisible(true);
685 jalview.bin.Console.debug("Completed news thread.");
690 public void getIdentifiersOrgData()
692 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
693 {// Thread off the identifiers fetcher
694 new Thread(new Runnable()
700 .debug("Downloading data from identifiers.org");
703 UrlDownloadClient.download(IdOrgSettings.getUrl(),
704 IdOrgSettings.getDownloadLocation());
705 } catch (IOException e)
708 .debug("Exception downloading identifiers.org data"
718 protected void showNews_actionPerformed(ActionEvent e)
720 showNews(showNews.isSelected());
723 void showNews(boolean visible)
725 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
726 showNews.setSelected(visible);
727 if (visible && !jvnews.isVisible())
729 new Thread(new Runnable()
734 long now = System.currentTimeMillis();
735 Desktop.instance.setProgressBar(
736 MessageManager.getString("status.refreshing_news"), now);
737 jvnews.refreshNews();
738 Desktop.instance.setProgressBar(null, now);
746 * recover the last known dimensions for a jalview window
749 * - empty string is desktop, all other windows have unique prefix
750 * @return null or last known dimensions scaled to current geometry (if last
751 * window geom was known)
753 Rectangle getLastKnownDimensions(String windowName)
755 // TODO: lock aspect ratio for scaling desktop Bug #0058199
756 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
757 String x = Cache.getProperty(windowName + "SCREEN_X");
758 String y = Cache.getProperty(windowName + "SCREEN_Y");
759 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
760 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
761 if ((x != null) && (y != null) && (width != null) && (height != null))
763 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
764 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
765 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
767 // attempt #1 - try to cope with change in screen geometry - this
768 // version doesn't preserve original jv aspect ratio.
769 // take ratio of current screen size vs original screen size.
770 double sw = ((1f * screenSize.width) / (1f * Integer
771 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
772 double sh = ((1f * screenSize.height) / (1f * Integer
773 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
774 // rescale the bounds depending upon the current screen geometry.
775 ix = (int) (ix * sw);
776 iw = (int) (iw * sw);
777 iy = (int) (iy * sh);
778 ih = (int) (ih * sh);
779 while (ix >= screenSize.width)
781 jalview.bin.Console.debug(
782 "Window geometry location recall error: shifting horizontal to within screenbounds.");
783 ix -= screenSize.width;
785 while (iy >= screenSize.height)
787 jalview.bin.Console.debug(
788 "Window geometry location recall error: shifting vertical to within screenbounds.");
789 iy -= screenSize.height;
791 jalview.bin.Console.debug(
792 "Got last known dimensions for " + windowName + ": x:" + ix
793 + " y:" + iy + " width:" + iw + " height:" + ih);
795 // return dimensions for new instance
796 return new Rectangle(ix, iy, iw, ih);
801 void showPasteMenu(int x, int y)
803 JPopupMenu popup = new JPopupMenu();
804 JMenuItem item = new JMenuItem(
805 MessageManager.getString("label.paste_new_window"));
806 item.addActionListener(new ActionListener()
809 public void actionPerformed(ActionEvent evt)
816 popup.show(this, x, y);
821 // quick patch for JAL-4150 - needs some more work and test coverage
822 // TODO - unify below and AlignFrame.paste()
823 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
824 // clipboard has come from a different alignment window than the one where
825 // paste has been called! JAL-4151
827 if (Desktop.jalviewClipboard != null)
829 // The clipboard was filled from within Jalview, we must use the
831 // And dataset from the copied alignment
832 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
833 // be doubly sure that we create *new* sequence objects.
834 SequenceI[] sequences = new SequenceI[newseq.length];
835 for (int i = 0; i < newseq.length; i++)
837 sequences[i] = new Sequence(newseq[i]);
839 Alignment alignment = new Alignment(sequences);
840 // dataset is inherited
841 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
842 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
843 AlignFrame.DEFAULT_HEIGHT);
844 String newtitle = new String("Copied sequences");
846 if (Desktop.jalviewClipboard[2] != null)
848 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
849 af.viewport.setHiddenColumns(hc);
852 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
853 AlignFrame.DEFAULT_HEIGHT);
860 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
861 Transferable contents = c.getContents(this);
863 if (contents != null)
865 String file = (String) contents
866 .getTransferData(DataFlavor.stringFlavor);
868 FileFormatI format = new IdentifyFile().identify(file,
869 DataSourceType.PASTE);
871 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
874 } catch (Exception ex)
877 "Unable to paste alignment from system clipboard:\n" + ex);
883 * Adds and opens the given frame to the desktop
894 public static synchronized void addInternalFrame(
895 final JInternalFrame frame, String title, int w, int h)
897 addInternalFrame(frame, title, true, w, h, true, false);
901 * Add an internal frame to the Jalview desktop
908 * When true, display frame immediately, otherwise, caller must call
909 * setVisible themselves.
915 public static synchronized void addInternalFrame(
916 final JInternalFrame frame, String title, boolean makeVisible,
919 addInternalFrame(frame, title, makeVisible, w, h, true, false);
923 * Add an internal frame to the Jalview desktop and make it visible
936 public static synchronized void addInternalFrame(
937 final JInternalFrame frame, String title, int w, int h,
940 addInternalFrame(frame, title, true, w, h, resizable, false);
944 * Add an internal frame to the Jalview desktop
951 * When true, display frame immediately, otherwise, caller must call
952 * setVisible themselves.
959 * @param ignoreMinSize
960 * Do not set the default minimum size for frame
962 public static synchronized void addInternalFrame(
963 final JInternalFrame frame, String title, boolean makeVisible,
964 int w, int h, boolean resizable, boolean ignoreMinSize)
967 // TODO: allow callers to determine X and Y position of frame (eg. via
969 // TODO: consider fixing method to update entries in the window submenu with
970 // the current window title
972 frame.setTitle(title);
973 if (frame.getWidth() < 1 || frame.getHeight() < 1)
977 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
978 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
979 // IF JALVIEW IS RUNNING HEADLESS
980 // ///////////////////////////////////////////////
981 if (instance == null || (System.getProperty("java.awt.headless") != null
982 && System.getProperty("java.awt.headless").equals("true")))
991 frame.setMinimumSize(
992 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
994 // Set default dimension for Alignment Frame window.
995 // The Alignment Frame window could be added from a number of places,
997 // I did this here in order not to miss out on any Alignment frame.
998 if (frame instanceof AlignFrame)
1000 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1001 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1005 frame.setVisible(makeVisible);
1006 frame.setClosable(true);
1007 frame.setResizable(resizable);
1008 frame.setMaximizable(resizable);
1009 frame.setIconifiable(resizable);
1010 frame.setOpaque(Platform.isJS());
1012 if (frame.getX() < 1 && frame.getY() < 1)
1014 frame.setLocation(xOffset * openFrameCount,
1015 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1019 * add an entry for the new frame in the Window menu (and remove it when the
1022 final JMenuItem menuItem = new JMenuItem(title);
1023 frame.addInternalFrameListener(new InternalFrameAdapter()
1026 public void internalFrameActivated(InternalFrameEvent evt)
1028 JInternalFrame itf = desktop.getSelectedFrame();
1031 if (itf instanceof AlignFrame)
1033 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1040 public void internalFrameClosed(InternalFrameEvent evt)
1042 PaintRefresher.RemoveComponent(frame);
1045 * defensive check to prevent frames being added half off the window
1047 if (openFrameCount > 0)
1053 * ensure no reference to alignFrame retained by menu item listener
1055 if (menuItem.getActionListeners().length > 0)
1057 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1059 windowMenu.remove(menuItem);
1063 menuItem.addActionListener(new ActionListener()
1066 public void actionPerformed(ActionEvent e)
1070 frame.setSelected(true);
1071 frame.setIcon(false);
1072 } catch (java.beans.PropertyVetoException ex)
1079 setKeyBindings(frame);
1083 windowMenu.add(menuItem);
1088 frame.setSelected(true);
1089 frame.requestFocus();
1090 } catch (java.beans.PropertyVetoException ve)
1092 } catch (java.lang.ClassCastException cex)
1094 jalview.bin.Console.warn(
1095 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1101 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1106 private static void setKeyBindings(JInternalFrame frame)
1108 @SuppressWarnings("serial")
1109 final Action closeAction = new AbstractAction()
1112 public void actionPerformed(ActionEvent e)
1119 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1121 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1122 InputEvent.CTRL_DOWN_MASK);
1123 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1124 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1126 InputMap inputMap = frame
1127 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1128 String ctrlW = ctrlWKey.toString();
1129 inputMap.put(ctrlWKey, ctrlW);
1130 inputMap.put(cmdWKey, ctrlW);
1132 ActionMap actionMap = frame.getActionMap();
1133 actionMap.put(ctrlW, closeAction);
1137 public void lostOwnership(Clipboard clipboard, Transferable contents)
1141 Desktop.jalviewClipboard = null;
1144 internalCopy = false;
1148 public void dragEnter(DropTargetDragEvent evt)
1153 public void dragExit(DropTargetEvent evt)
1158 public void dragOver(DropTargetDragEvent evt)
1163 public void dropActionChanged(DropTargetDragEvent evt)
1174 public void drop(DropTargetDropEvent evt)
1176 boolean success = true;
1177 // JAL-1552 - acceptDrop required before getTransferable call for
1178 // Java's Transferable for native dnd
1179 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1180 Transferable t = evt.getTransferable();
1181 List<Object> files = new ArrayList<>();
1182 List<DataSourceType> protocols = new ArrayList<>();
1186 Desktop.transferFromDropTarget(files, protocols, evt, t);
1187 } catch (Exception e)
1189 e.printStackTrace();
1197 for (int i = 0; i < files.size(); i++)
1199 // BH 2018 File or String
1200 Object file = files.get(i);
1201 String fileName = file.toString();
1202 DataSourceType protocol = (protocols == null)
1203 ? DataSourceType.FILE
1205 FileFormatI format = null;
1207 if (fileName.endsWith(".jar"))
1209 format = FileFormat.Jalview;
1214 format = new IdentifyFile().identify(file, protocol);
1216 if (file instanceof File)
1218 Platform.cacheFileData((File) file);
1220 new FileLoader().LoadFile(null, file, protocol, format);
1223 } catch (Exception ex)
1228 evt.dropComplete(success); // need this to ensure input focus is properly
1229 // transfered to any new windows created
1239 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1241 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1242 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1243 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1244 BackupFiles.getEnabled());
1246 chooser.setFileView(new JalviewFileView());
1247 chooser.setDialogTitle(
1248 MessageManager.getString("label.open_local_file"));
1249 chooser.setToolTipText(MessageManager.getString("action.open"));
1251 chooser.setResponseHandler(0, () -> {
1252 File selectedFile = chooser.getSelectedFile();
1253 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1255 FileFormatI format = chooser.getSelectedFormat();
1258 * Call IdentifyFile to verify the file contains what its extension implies.
1259 * Skip this step for dynamically added file formats, because IdentifyFile does
1260 * not know how to recognise them.
1262 if (FileFormats.getInstance().isIdentifiable(format))
1266 format = new IdentifyFile().identify(selectedFile,
1267 DataSourceType.FILE);
1268 } catch (FileFormatException e)
1270 // format = null; //??
1274 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1277 chooser.showOpenDialog(this);
1281 * Shows a dialog for input of a URL at which to retrieve alignment data
1286 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1288 // This construct allows us to have a wider textfield
1290 JLabel label = new JLabel(
1291 MessageManager.getString("label.input_file_url"));
1293 JPanel panel = new JPanel(new GridLayout(2, 1));
1297 * the URL to fetch is input in Java: an editable combobox with history JS:
1298 * (pending JAL-3038) a plain text field
1301 String urlBase = "https://www.";
1302 if (Platform.isJS())
1304 history = new JTextField(urlBase, 35);
1313 JComboBox<String> asCombo = new JComboBox<>();
1314 asCombo.setPreferredSize(new Dimension(400, 20));
1315 asCombo.setEditable(true);
1316 asCombo.addItem(urlBase);
1317 String historyItems = Cache.getProperty("RECENT_URL");
1318 if (historyItems != null)
1320 for (String token : historyItems.split("\\t"))
1322 asCombo.addItem(token);
1329 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1330 MessageManager.getString("action.cancel") };
1331 Runnable action = () -> {
1332 @SuppressWarnings("unchecked")
1333 String url = (history instanceof JTextField
1334 ? ((JTextField) history).getText()
1335 : ((JComboBox<String>) history).getEditor().getItem()
1336 .toString().trim());
1338 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1340 if (viewport != null)
1342 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1343 FileFormat.Jalview);
1347 new FileLoader().LoadFile(url, DataSourceType.URL,
1348 FileFormat.Jalview);
1353 FileFormatI format = null;
1356 format = new IdentifyFile().identify(url, DataSourceType.URL);
1357 } catch (FileFormatException e)
1359 // TODO revise error handling, distinguish between
1360 // URL not found and response not valid
1365 String msg = MessageManager.formatMessage("label.couldnt_locate",
1367 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1368 MessageManager.getString("label.url_not_found"),
1369 JvOptionPane.WARNING_MESSAGE);
1373 if (viewport != null)
1375 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1380 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1384 String dialogOption = MessageManager
1385 .getString("label.input_alignment_from_url");
1386 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1387 .showInternalDialog(panel, dialogOption,
1388 JvOptionPane.YES_NO_CANCEL_OPTION,
1389 JvOptionPane.PLAIN_MESSAGE, null, options,
1390 MessageManager.getString("action.ok"));
1394 * Opens the CutAndPaste window for the user to paste an alignment in to
1397 * - if not null, the pasted alignment is added to the current
1398 * alignment; if null, to a new alignment window
1401 public void inputTextboxMenuItem_actionPerformed(
1402 AlignmentViewPanel viewPanel)
1404 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1405 cap.setForInput(viewPanel);
1406 Desktop.addInternalFrame(cap,
1407 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1412 * Check with user and saving files before actually quitting
1414 public void desktopQuit()
1416 desktopQuit(true, false);
1419 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1421 final Runnable doDesktopQuit = () -> {
1422 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1423 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1424 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1425 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1426 getBounds().y, getWidth(), getHeight()));
1428 if (jconsole != null)
1430 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1431 jconsole.stopConsole();
1436 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1439 // Frames should all close automatically. Keeping external
1440 // viewers open should already be decided by user.
1441 closeAll_actionPerformed(null);
1443 // check for aborted quit
1444 if (QuitHandler.quitCancelled())
1446 jalview.bin.Console.debug("Desktop aborting quit");
1450 if (dialogExecutor != null)
1452 dialogExecutor.shutdownNow();
1455 if (groovyConsole != null)
1457 // suppress a possible repeat prompt to save script
1458 groovyConsole.setDirty(false);
1459 groovyConsole.exit();
1462 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1464 // note that shutdown hook will not be run
1465 jalview.bin.Console.debug("Force Quit selected by user");
1466 Runtime.getRuntime().halt(0);
1469 jalview.bin.Console.debug("Quit selected by user");
1472 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1473 // instance.dispose();
1478 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1479 QuitHandler.defaultCancelQuit);
1483 * Don't call this directly, use desktopQuit() above. Exits the program.
1488 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1489 // not run a second time if gotQuitResponse flag has been set (i.e. user
1490 // confirmed quit of some kind).
1491 Jalview.exit("Desktop exiting.", 0);
1494 private void storeLastKnownDimensions(String string, Rectangle jc)
1496 jalview.bin.Console.debug("Storing last known dimensions for " + string
1497 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1498 + " height:" + jc.height);
1500 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1501 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1502 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1503 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1513 public void aboutMenuItem_actionPerformed(ActionEvent e)
1515 new Thread(new Runnable()
1520 new SplashScreen(false);
1526 * Returns the html text for the About screen, including any available version
1527 * number, build details, author details and citation reference, but without
1528 * the enclosing {@code html} tags
1532 public String getAboutMessage()
1534 StringBuilder message = new StringBuilder(1024);
1535 message.append("<div style=\"font-family: sans-serif;\">")
1536 .append("<h1><strong>Version: ")
1537 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1538 .append("<strong>Built: <em>")
1539 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1540 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1541 .append("</strong>");
1543 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1544 if (latestVersion.equals("Checking"))
1546 // JBP removed this message for 2.11: May be reinstated in future version
1547 // message.append("<br>...Checking latest version...</br>");
1549 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1551 boolean red = false;
1552 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1553 .indexOf("automated build") == -1)
1556 // Displayed when code version and jnlp version do not match and code
1557 // version is not a development build
1558 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1561 message.append("<br>!! Version ")
1562 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1563 .append(" is available for download from ")
1564 .append(Cache.getDefault("www.jalview.org",
1565 "https://www.jalview.org"))
1569 message.append("</div>");
1572 message.append("<br>Authors: ");
1573 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1574 message.append(CITATION);
1576 message.append("</div>");
1578 return message.toString();
1582 * Action on requesting Help documentation
1585 public void documentationMenuItem_actionPerformed()
1589 if (Platform.isJS())
1591 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1600 Help.showHelpWindow();
1602 } catch (Exception ex)
1604 System.err.println("Error opening help: " + ex.getMessage());
1609 public void closeAll_actionPerformed(ActionEvent e)
1611 // TODO show a progress bar while closing?
1612 JInternalFrame[] frames = desktop.getAllFrames();
1613 for (int i = 0; i < frames.length; i++)
1617 frames[i].setClosed(true);
1618 } catch (java.beans.PropertyVetoException ex)
1622 Jalview.setCurrentAlignFrame(null);
1623 jalview.bin.Console.info("ALL CLOSED");
1626 * reset state of singleton objects as appropriate (clear down session state
1627 * when all windows are closed)
1629 StructureSelectionManager ssm = StructureSelectionManager
1630 .getStructureSelectionManager(this);
1637 public int structureViewersStillRunningCount()
1640 JInternalFrame[] frames = desktop.getAllFrames();
1641 for (int i = 0; i < frames.length; i++)
1643 if (frames[i] != null
1644 && frames[i] instanceof JalviewStructureDisplayI)
1646 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1654 public void raiseRelated_actionPerformed(ActionEvent e)
1656 reorderAssociatedWindows(false, false);
1660 public void minimizeAssociated_actionPerformed(ActionEvent e)
1662 reorderAssociatedWindows(true, false);
1665 void closeAssociatedWindows()
1667 reorderAssociatedWindows(false, true);
1673 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1677 protected void garbageCollect_actionPerformed(ActionEvent e)
1679 // We simply collect the garbage
1680 jalview.bin.Console.debug("Collecting garbage...");
1682 jalview.bin.Console.debug("Finished garbage collection.");
1688 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1692 protected void showMemusage_actionPerformed(ActionEvent e)
1694 desktop.showMemoryUsage(showMemusage.isSelected());
1701 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1705 protected void showConsole_actionPerformed(ActionEvent e)
1707 showConsole(showConsole.isSelected());
1710 Console jconsole = null;
1713 * control whether the java console is visible or not
1717 void showConsole(boolean selected)
1719 // TODO: decide if we should update properties file
1720 if (jconsole != null) // BH 2018
1722 showConsole.setSelected(selected);
1723 Cache.setProperty("SHOW_JAVA_CONSOLE",
1724 Boolean.valueOf(selected).toString());
1725 jconsole.setVisible(selected);
1729 void reorderAssociatedWindows(boolean minimize, boolean close)
1731 JInternalFrame[] frames = desktop.getAllFrames();
1732 if (frames == null || frames.length < 1)
1737 AlignmentViewport source = null, target = null;
1738 if (frames[0] instanceof AlignFrame)
1740 source = ((AlignFrame) frames[0]).getCurrentView();
1742 else if (frames[0] instanceof TreePanel)
1744 source = ((TreePanel) frames[0]).getViewPort();
1746 else if (frames[0] instanceof PCAPanel)
1748 source = ((PCAPanel) frames[0]).av;
1750 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1752 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1757 for (int i = 0; i < frames.length; i++)
1760 if (frames[i] == null)
1764 if (frames[i] instanceof AlignFrame)
1766 target = ((AlignFrame) frames[i]).getCurrentView();
1768 else if (frames[i] instanceof TreePanel)
1770 target = ((TreePanel) frames[i]).getViewPort();
1772 else if (frames[i] instanceof PCAPanel)
1774 target = ((PCAPanel) frames[i]).av;
1776 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1778 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1781 if (source == target)
1787 frames[i].setClosed(true);
1791 frames[i].setIcon(minimize);
1794 frames[i].toFront();
1798 } catch (java.beans.PropertyVetoException ex)
1813 protected void preferences_actionPerformed(ActionEvent e)
1815 Preferences.openPreferences();
1819 * Prompts the user to choose a file and then saves the Jalview state as a
1820 * Jalview project file
1823 public void saveState_actionPerformed()
1825 saveState_actionPerformed(false);
1828 public void saveState_actionPerformed(boolean saveAs)
1830 java.io.File projectFile = getProjectFile();
1831 // autoSave indicates we already have a file and don't need to ask
1832 boolean autoSave = projectFile != null && !saveAs
1833 && BackupFiles.getEnabled();
1835 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1836 // saveAs="+saveAs+", Backups
1837 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1839 boolean approveSave = false;
1842 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1845 chooser.setFileView(new JalviewFileView());
1846 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1848 int value = chooser.showSaveDialog(this);
1850 if (value == JalviewFileChooser.APPROVE_OPTION)
1852 projectFile = chooser.getSelectedFile();
1853 setProjectFile(projectFile);
1858 if (approveSave || autoSave)
1860 final Desktop me = this;
1861 final java.io.File chosenFile = projectFile;
1862 new Thread(new Runnable()
1867 // TODO: refactor to Jalview desktop session controller action.
1868 setProgressBar(MessageManager.formatMessage(
1869 "label.saving_jalview_project", new Object[]
1870 { chosenFile.getName() }), chosenFile.hashCode());
1871 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1872 // TODO catch and handle errors for savestate
1873 // TODO prevent user from messing with the Desktop whilst we're saving
1876 boolean doBackup = BackupFiles.getEnabled();
1877 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1880 new Jalview2XML().saveState(
1881 doBackup ? backupfiles.getTempFile() : chosenFile);
1885 backupfiles.setWriteSuccess(true);
1886 backupfiles.rollBackupsAndRenameTempFile();
1888 } catch (OutOfMemoryError oom)
1890 new OOMWarning("Whilst saving current state to "
1891 + chosenFile.getName(), oom);
1892 } catch (Exception ex)
1894 jalview.bin.Console.error("Problems whilst trying to save to "
1895 + chosenFile.getName(), ex);
1896 JvOptionPane.showMessageDialog(me,
1897 MessageManager.formatMessage(
1898 "label.error_whilst_saving_current_state_to",
1900 { chosenFile.getName() }),
1901 MessageManager.getString("label.couldnt_save_project"),
1902 JvOptionPane.WARNING_MESSAGE);
1904 setProgressBar(null, chosenFile.hashCode());
1911 public void saveAsState_actionPerformed(ActionEvent e)
1913 saveState_actionPerformed(true);
1916 protected void setProjectFile(File choice)
1918 this.projectFile = choice;
1921 public File getProjectFile()
1923 return this.projectFile;
1927 * Shows a file chooser dialog and tries to read in the selected file as a
1931 public void loadState_actionPerformed()
1933 final String[] suffix = new String[] { "jvp", "jar" };
1934 final String[] desc = new String[] { "Jalview Project",
1935 "Jalview Project (old)" };
1936 JalviewFileChooser chooser = new JalviewFileChooser(
1937 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1938 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1942 chooser.setFileView(new JalviewFileView());
1943 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1944 chooser.setResponseHandler(0, () -> {
1945 File selectedFile = chooser.getSelectedFile();
1946 setProjectFile(selectedFile);
1947 String choice = selectedFile.getAbsolutePath();
1948 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1949 new Thread(new Runnable()
1956 new Jalview2XML().loadJalviewAlign(selectedFile);
1957 } catch (OutOfMemoryError oom)
1959 new OOMWarning("Whilst loading project from " + choice, oom);
1960 } catch (Exception ex)
1962 jalview.bin.Console.error(
1963 "Problems whilst loading project from " + choice, ex);
1964 JvOptionPane.showMessageDialog(Desktop.desktop,
1965 MessageManager.formatMessage(
1966 "label.error_whilst_loading_project_from",
1969 MessageManager.getString("label.couldnt_load_project"),
1970 JvOptionPane.WARNING_MESSAGE);
1973 }, "Project Loader").start();
1976 chooser.showOpenDialog(this);
1980 public void inputSequence_actionPerformed(ActionEvent e)
1982 new SequenceFetcher(this);
1985 JPanel progressPanel;
1987 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1989 public void startLoading(final Object fileName)
1991 if (fileLoadingCount == 0)
1993 fileLoadingPanels.add(addProgressPanel(MessageManager
1994 .formatMessage("label.loading_file", new Object[]
2000 private JPanel addProgressPanel(String string)
2002 if (progressPanel == null)
2004 progressPanel = new JPanel(new GridLayout(1, 1));
2005 totalProgressCount = 0;
2006 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2008 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2009 JProgressBar progressBar = new JProgressBar();
2010 progressBar.setIndeterminate(true);
2012 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2014 thisprogress.add(progressBar, BorderLayout.CENTER);
2015 progressPanel.add(thisprogress);
2016 ((GridLayout) progressPanel.getLayout()).setRows(
2017 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2018 ++totalProgressCount;
2019 instance.validate();
2020 return thisprogress;
2023 int totalProgressCount = 0;
2025 private void removeProgressPanel(JPanel progbar)
2027 if (progressPanel != null)
2029 synchronized (progressPanel)
2031 progressPanel.remove(progbar);
2032 GridLayout gl = (GridLayout) progressPanel.getLayout();
2033 gl.setRows(gl.getRows() - 1);
2034 if (--totalProgressCount < 1)
2036 this.getContentPane().remove(progressPanel);
2037 progressPanel = null;
2044 public void stopLoading()
2047 if (fileLoadingCount < 1)
2049 while (fileLoadingPanels.size() > 0)
2051 removeProgressPanel(fileLoadingPanels.remove(0));
2053 fileLoadingPanels.clear();
2054 fileLoadingCount = 0;
2059 public static int getViewCount(String alignmentId)
2061 AlignmentViewport[] aps = getViewports(alignmentId);
2062 return (aps == null) ? 0 : aps.length;
2067 * @param alignmentId
2068 * - if null, all sets are returned
2069 * @return all AlignmentPanels concerning the alignmentId sequence set
2071 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2073 if (Desktop.desktop == null)
2075 // no frames created and in headless mode
2076 // TODO: verify that frames are recoverable when in headless mode
2079 List<AlignmentPanel> aps = new ArrayList<>();
2080 AlignFrame[] frames = getAlignFrames();
2085 for (AlignFrame af : frames)
2087 for (AlignmentPanel ap : af.alignPanels)
2089 if (alignmentId == null
2090 || alignmentId.equals(ap.av.getSequenceSetId()))
2096 if (aps.size() == 0)
2100 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2105 * get all the viewports on an alignment.
2107 * @param sequenceSetId
2108 * unique alignment id (may be null - all viewports returned in that
2110 * @return all viewports on the alignment bound to sequenceSetId
2112 public static AlignmentViewport[] getViewports(String sequenceSetId)
2114 List<AlignmentViewport> viewp = new ArrayList<>();
2115 if (desktop != null)
2117 AlignFrame[] frames = Desktop.getAlignFrames();
2119 for (AlignFrame afr : frames)
2121 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2122 .equals(sequenceSetId))
2124 if (afr.alignPanels != null)
2126 for (AlignmentPanel ap : afr.alignPanels)
2128 if (sequenceSetId == null
2129 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2137 viewp.add(afr.getViewport());
2141 if (viewp.size() > 0)
2143 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2150 * Explode the views in the given frame into separate AlignFrame
2154 public static void explodeViews(AlignFrame af)
2156 int size = af.alignPanels.size();
2162 // FIXME: ideally should use UI interface API
2163 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2164 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2165 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2166 for (int i = 0; i < size; i++)
2168 AlignmentPanel ap = af.alignPanels.get(i);
2170 AlignFrame newaf = new AlignFrame(ap);
2172 // transfer reference for existing feature settings to new alignFrame
2173 if (ap == af.alignPanel)
2175 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2177 newaf.featureSettings = viewFeatureSettings;
2179 newaf.setFeatureSettingsGeometry(fsBounds);
2183 * Restore the view's last exploded frame geometry if known. Multiple views from
2184 * one exploded frame share and restore the same (frame) position and size.
2186 Rectangle geometry = ap.av.getExplodedGeometry();
2187 if (geometry != null)
2189 newaf.setBounds(geometry);
2192 ap.av.setGatherViewsHere(false);
2194 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2195 AlignFrame.DEFAULT_HEIGHT);
2196 // and materialise a new feature settings dialog instance for the new
2198 // (closes the old as if 'OK' was pressed)
2199 if (ap == af.alignPanel && newaf.featureSettings != null
2200 && newaf.featureSettings.isOpen()
2201 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2203 newaf.showFeatureSettingsUI();
2207 af.featureSettings = null;
2208 af.alignPanels.clear();
2209 af.closeMenuItem_actionPerformed(true);
2214 * Gather expanded views (separate AlignFrame's) with the same sequence set
2215 * identifier back in to this frame as additional views, and close the
2216 * expanded views. Note the expanded frames may themselves have multiple
2217 * views. We take the lot.
2221 public void gatherViews(AlignFrame source)
2223 source.viewport.setGatherViewsHere(true);
2224 source.viewport.setExplodedGeometry(source.getBounds());
2225 JInternalFrame[] frames = desktop.getAllFrames();
2226 String viewId = source.viewport.getSequenceSetId();
2227 for (int t = 0; t < frames.length; t++)
2229 if (frames[t] instanceof AlignFrame && frames[t] != source)
2231 AlignFrame af = (AlignFrame) frames[t];
2232 boolean gatherThis = false;
2233 for (int a = 0; a < af.alignPanels.size(); a++)
2235 AlignmentPanel ap = af.alignPanels.get(a);
2236 if (viewId.equals(ap.av.getSequenceSetId()))
2239 ap.av.setGatherViewsHere(false);
2240 ap.av.setExplodedGeometry(af.getBounds());
2241 source.addAlignmentPanel(ap, false);
2247 if (af.featureSettings != null && af.featureSettings.isOpen())
2249 if (source.featureSettings == null)
2251 // preserve the feature settings geometry for this frame
2252 source.featureSettings = af.featureSettings;
2253 source.setFeatureSettingsGeometry(
2254 af.getFeatureSettingsGeometry());
2258 // close it and forget
2259 af.featureSettings.close();
2262 af.alignPanels.clear();
2263 af.closeMenuItem_actionPerformed(true);
2268 // refresh the feature setting UI for the source frame if it exists
2269 if (source.featureSettings != null && source.featureSettings.isOpen())
2271 source.showFeatureSettingsUI();
2276 public JInternalFrame[] getAllFrames()
2278 return desktop.getAllFrames();
2282 * Checks the given url to see if it gives a response indicating that the user
2283 * should be informed of a new questionnaire.
2287 public void checkForQuestionnaire(String url)
2289 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2290 // javax.swing.SwingUtilities.invokeLater(jvq);
2291 new Thread(jvq).start();
2294 public void checkURLLinks()
2296 // Thread off the URL link checker
2297 addDialogThread(new Runnable()
2302 if (Cache.getDefault("CHECKURLLINKS", true))
2304 // check what the actual links are - if it's just the default don't
2305 // bother with the warning
2306 List<String> links = Preferences.sequenceUrlLinks
2309 // only need to check links if there is one with a
2310 // SEQUENCE_ID which is not the default EMBL_EBI link
2311 ListIterator<String> li = links.listIterator();
2312 boolean check = false;
2313 List<JLabel> urls = new ArrayList<>();
2314 while (li.hasNext())
2316 String link = li.next();
2317 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2318 && !UrlConstants.isDefaultString(link))
2321 int barPos = link.indexOf("|");
2322 String urlMsg = barPos == -1 ? link
2323 : link.substring(0, barPos) + ": "
2324 + link.substring(barPos + 1);
2325 urls.add(new JLabel(urlMsg));
2333 // ask user to check in case URL links use old style tokens
2334 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2335 JPanel msgPanel = new JPanel();
2336 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2337 msgPanel.add(Box.createVerticalGlue());
2338 JLabel msg = new JLabel(MessageManager
2339 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2340 JLabel msg2 = new JLabel(MessageManager
2341 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2343 for (JLabel url : urls)
2349 final JCheckBox jcb = new JCheckBox(
2350 MessageManager.getString("label.do_not_display_again"));
2351 jcb.addActionListener(new ActionListener()
2354 public void actionPerformed(ActionEvent e)
2356 // update Cache settings for "don't show this again"
2357 boolean showWarningAgain = !jcb.isSelected();
2358 Cache.setProperty("CHECKURLLINKS",
2359 Boolean.valueOf(showWarningAgain).toString());
2364 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2366 .getString("label.SEQUENCE_ID_no_longer_used"),
2367 JvOptionPane.WARNING_MESSAGE);
2374 * Proxy class for JDesktopPane which optionally displays the current memory
2375 * usage and highlights the desktop area with a red bar if free memory runs
2380 public class MyDesktopPane extends JDesktopPane implements Runnable
2382 private static final float ONE_MB = 1048576f;
2384 boolean showMemoryUsage = false;
2388 java.text.NumberFormat df;
2390 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2393 public MyDesktopPane(boolean showMemoryUsage)
2395 showMemoryUsage(showMemoryUsage);
2398 public void showMemoryUsage(boolean showMemory)
2400 this.showMemoryUsage = showMemory;
2403 Thread worker = new Thread(this);
2409 public boolean isShowMemoryUsage()
2411 return showMemoryUsage;
2417 df = java.text.NumberFormat.getNumberInstance();
2418 df.setMaximumFractionDigits(2);
2419 runtime = Runtime.getRuntime();
2421 while (showMemoryUsage)
2425 maxMemory = runtime.maxMemory() / ONE_MB;
2426 allocatedMemory = runtime.totalMemory() / ONE_MB;
2427 freeMemory = runtime.freeMemory() / ONE_MB;
2428 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2430 percentUsage = (totalFreeMemory / maxMemory) * 100;
2432 // if (percentUsage < 20)
2434 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2436 // instance.set.setBorder(border1);
2439 // sleep after showing usage
2441 } catch (Exception ex)
2443 ex.printStackTrace();
2449 public void paintComponent(Graphics g)
2451 if (showMemoryUsage && g != null && df != null)
2453 if (percentUsage < 20)
2455 g.setColor(Color.red);
2457 FontMetrics fm = g.getFontMetrics();
2460 g.drawString(MessageManager.formatMessage("label.memory_stats",
2462 { df.format(totalFreeMemory), df.format(maxMemory),
2463 df.format(percentUsage) }),
2464 10, getHeight() - fm.getHeight());
2468 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2469 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2474 * Accessor method to quickly get all the AlignmentFrames loaded.
2476 * @return an array of AlignFrame, or null if none found
2478 public static AlignFrame[] getAlignFrames()
2480 if (Jalview.isHeadlessMode())
2482 // Desktop.desktop is null in headless mode
2483 return new AlignFrame[] { Jalview.currentAlignFrame };
2486 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2492 List<AlignFrame> avp = new ArrayList<>();
2494 for (int i = frames.length - 1; i > -1; i--)
2496 if (frames[i] instanceof AlignFrame)
2498 avp.add((AlignFrame) frames[i]);
2500 else if (frames[i] instanceof SplitFrame)
2503 * Also check for a split frame containing an AlignFrame
2505 GSplitFrame sf = (GSplitFrame) frames[i];
2506 if (sf.getTopFrame() instanceof AlignFrame)
2508 avp.add((AlignFrame) sf.getTopFrame());
2510 if (sf.getBottomFrame() instanceof AlignFrame)
2512 avp.add((AlignFrame) sf.getBottomFrame());
2516 if (avp.size() == 0)
2520 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2525 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2529 public GStructureViewer[] getJmols()
2531 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2537 List<GStructureViewer> avp = new ArrayList<>();
2539 for (int i = frames.length - 1; i > -1; i--)
2541 if (frames[i] instanceof AppJmol)
2543 GStructureViewer af = (GStructureViewer) frames[i];
2547 if (avp.size() == 0)
2551 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2556 * Add Groovy Support to Jalview
2559 public void groovyShell_actionPerformed()
2563 openGroovyConsole();
2564 } catch (Exception ex)
2566 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2567 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2569 MessageManager.getString("label.couldnt_create_groovy_shell"),
2570 MessageManager.getString("label.groovy_support_failed"),
2571 JvOptionPane.ERROR_MESSAGE);
2576 * Open the Groovy console
2578 void openGroovyConsole()
2580 if (groovyConsole == null)
2582 groovyConsole = new groovy.ui.Console();
2583 groovyConsole.setVariable("Jalview", this);
2584 groovyConsole.run();
2587 * We allow only one console at a time, so that AlignFrame menu option
2588 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2589 * enable 'Run script', when the console is opened, and the reverse when it is
2592 Window window = (Window) groovyConsole.getFrame();
2593 window.addWindowListener(new WindowAdapter()
2596 public void windowClosed(WindowEvent e)
2599 * rebind CMD-Q from Groovy Console to Jalview Quit
2602 enableExecuteGroovy(false);
2608 * show Groovy console window (after close and reopen)
2610 ((Window) groovyConsole.getFrame()).setVisible(true);
2613 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2614 * opening a second console
2616 enableExecuteGroovy(true);
2620 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2621 * binding when opened
2623 protected void addQuitHandler()
2626 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2628 .getKeyStroke(KeyEvent.VK_Q,
2629 jalview.util.ShortcutKeyMaskExWrapper
2630 .getMenuShortcutKeyMaskEx()),
2632 getRootPane().getActionMap().put("Quit", new AbstractAction()
2635 public void actionPerformed(ActionEvent e)
2643 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2646 * true if Groovy console is open
2648 public void enableExecuteGroovy(boolean enabled)
2651 * disable opening a second Groovy console (or re-enable when the console is
2654 groovyShell.setEnabled(!enabled);
2656 AlignFrame[] alignFrames = getAlignFrames();
2657 if (alignFrames != null)
2659 for (AlignFrame af : alignFrames)
2661 af.setGroovyEnabled(enabled);
2667 * Progress bars managed by the IProgressIndicator method.
2669 private Hashtable<Long, JPanel> progressBars;
2671 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2676 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2679 public void setProgressBar(String message, long id)
2681 if (progressBars == null)
2683 progressBars = new Hashtable<>();
2684 progressBarHandlers = new Hashtable<>();
2687 if (progressBars.get(Long.valueOf(id)) != null)
2689 JPanel panel = progressBars.remove(Long.valueOf(id));
2690 if (progressBarHandlers.contains(Long.valueOf(id)))
2692 progressBarHandlers.remove(Long.valueOf(id));
2694 removeProgressPanel(panel);
2698 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2705 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2706 * jalview.gui.IProgressIndicatorHandler)
2709 public void registerHandler(final long id,
2710 final IProgressIndicatorHandler handler)
2712 if (progressBarHandlers == null
2713 || !progressBars.containsKey(Long.valueOf(id)))
2715 throw new Error(MessageManager.getString(
2716 "error.call_setprogressbar_before_registering_handler"));
2718 progressBarHandlers.put(Long.valueOf(id), handler);
2719 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2720 if (handler.canCancel())
2722 JButton cancel = new JButton(
2723 MessageManager.getString("action.cancel"));
2724 final IProgressIndicator us = this;
2725 cancel.addActionListener(new ActionListener()
2729 public void actionPerformed(ActionEvent e)
2731 handler.cancelActivity(id);
2732 us.setProgressBar(MessageManager
2733 .formatMessage("label.cancelled_params", new Object[]
2734 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2738 progressPanel.add(cancel, BorderLayout.EAST);
2744 * @return true if any progress bars are still active
2747 public boolean operationInProgress()
2749 if (progressBars != null && progressBars.size() > 0)
2757 * This will return the first AlignFrame holding the given viewport instance.
2758 * It will break if there are more than one AlignFrames viewing a particular
2762 * @return alignFrame for viewport
2764 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2766 if (desktop != null)
2768 AlignmentPanel[] aps = getAlignmentPanels(
2769 viewport.getSequenceSetId());
2770 for (int panel = 0; aps != null && panel < aps.length; panel++)
2772 if (aps[panel] != null && aps[panel].av == viewport)
2774 return aps[panel].alignFrame;
2781 public VamsasApplication getVamsasApplication()
2783 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2789 * flag set if jalview GUI is being operated programmatically
2791 private boolean inBatchMode = false;
2794 * check if jalview GUI is being operated programmatically
2796 * @return inBatchMode
2798 public boolean isInBatchMode()
2804 * set flag if jalview GUI is being operated programmatically
2806 * @param inBatchMode
2808 public void setInBatchMode(boolean inBatchMode)
2810 this.inBatchMode = inBatchMode;
2814 * start service discovery and wait till it is done
2816 public void startServiceDiscovery()
2818 startServiceDiscovery(false);
2822 * start service discovery threads - blocking or non-blocking
2826 public void startServiceDiscovery(boolean blocking)
2828 startServiceDiscovery(blocking, false);
2832 * start service discovery threads
2835 * - false means call returns immediately
2836 * @param ignore_SHOW_JWS2_SERVICES_preference
2837 * - when true JABA services are discovered regardless of user's JWS2
2838 * discovery preference setting
2840 public void startServiceDiscovery(boolean blocking,
2841 boolean ignore_SHOW_JWS2_SERVICES_preference)
2843 boolean alive = true;
2844 Thread t0 = null, t1 = null, t2 = null;
2845 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2848 // todo: changesupport handlers need to be transferred
2849 if (discoverer == null)
2851 discoverer = new jalview.ws.jws1.Discoverer();
2852 // register PCS handler for desktop.
2853 discoverer.addPropertyChangeListener(changeSupport);
2855 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2856 // until we phase out completely
2857 (t0 = new Thread(discoverer)).start();
2860 if (ignore_SHOW_JWS2_SERVICES_preference
2861 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2863 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2864 .startDiscoverer(changeSupport);
2868 // TODO: do rest service discovery
2877 } catch (Exception e)
2880 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2881 || (t3 != null && t3.isAlive())
2882 || (t0 != null && t0.isAlive());
2888 * called to check if the service discovery process completed successfully.
2892 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2894 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2896 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2897 .getErrorMessages();
2900 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2902 if (serviceChangedDialog == null)
2904 // only run if we aren't already displaying one of these.
2905 addDialogThread(serviceChangedDialog = new Runnable()
2912 * JalviewDialog jd =new JalviewDialog() {
2914 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2916 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2918 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2920 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2922 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2923 * + " or mis-configured HTTP proxy settings.<br/>" +
2924 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2925 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2926 * true, true, "Web Service Configuration Problem", 450, 400);
2928 * jd.waitForInput();
2930 JvOptionPane.showConfirmDialog(Desktop.desktop,
2931 new JLabel("<html><table width=\"450\"><tr><td>"
2932 + ermsg + "</td></tr></table>"
2933 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2934 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2935 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2936 + " Tools->Preferences dialog box to change them.</p></html>"),
2937 "Web Service Configuration Problem",
2938 JvOptionPane.DEFAULT_OPTION,
2939 JvOptionPane.ERROR_MESSAGE);
2940 serviceChangedDialog = null;
2948 jalview.bin.Console.error(
2949 "Errors reported by JABA discovery service. Check web services preferences.\n"
2956 private Runnable serviceChangedDialog = null;
2959 * start a thread to open a URL in the configured browser. Pops up a warning
2960 * dialog to the user if there is an exception when calling out to the browser
2965 public static void showUrl(final String url)
2967 showUrl(url, Desktop.instance);
2971 * Like showUrl but allows progress handler to be specified
2975 * (null) or object implementing IProgressIndicator
2977 public static void showUrl(final String url,
2978 final IProgressIndicator progress)
2980 new Thread(new Runnable()
2987 if (progress != null)
2989 progress.setProgressBar(MessageManager
2990 .formatMessage("status.opening_params", new Object[]
2991 { url }), this.hashCode());
2993 jalview.util.BrowserLauncher.openURL(url);
2994 } catch (Exception ex)
2996 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2998 .getString("label.web_browser_not_found_unix"),
2999 MessageManager.getString("label.web_browser_not_found"),
3000 JvOptionPane.WARNING_MESSAGE);
3002 ex.printStackTrace();
3004 if (progress != null)
3006 progress.setProgressBar(null, this.hashCode());
3012 public static WsParamSetManager wsparamManager = null;
3014 public static ParamManager getUserParameterStore()
3016 if (wsparamManager == null)
3018 wsparamManager = new WsParamSetManager();
3020 return wsparamManager;
3024 * static hyperlink handler proxy method for use by Jalview's internal windows
3028 public static void hyperlinkUpdate(HyperlinkEvent e)
3030 if (e.getEventType() == EventType.ACTIVATED)
3035 url = e.getURL().toString();
3036 Desktop.showUrl(url);
3037 } catch (Exception x)
3042 .error("Couldn't handle string " + url + " as a URL.");
3044 // ignore any exceptions due to dud links.
3051 * single thread that handles display of dialogs to user.
3053 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3056 * flag indicating if dialogExecutor should try to acquire a permit
3058 private volatile boolean dialogPause = true;
3063 private Semaphore block = new Semaphore(0);
3065 private static groovy.ui.Console groovyConsole;
3068 * add another dialog thread to the queue
3072 public void addDialogThread(final Runnable prompter)
3074 dialogExecutor.submit(new Runnable()
3081 acquireDialogQueue();
3083 if (instance == null)
3089 SwingUtilities.invokeAndWait(prompter);
3090 } catch (Exception q)
3092 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3099 private boolean dialogQueueStarted = false;
3101 public void startDialogQueue()
3103 if (dialogQueueStarted)
3107 // set the flag so we don't pause waiting for another permit and semaphore
3108 // the current task to begin
3109 releaseDialogQueue();
3110 dialogQueueStarted = true;
3113 public void acquireDialogQueue()
3119 } catch (InterruptedException e)
3121 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3126 public void releaseDialogQueue()
3133 dialogPause = false;
3137 * Outputs an image of the desktop to file in EPS format, after prompting the
3138 * user for choice of Text or Lineart character rendering (unless a preference
3139 * has been set). The file name is generated as
3142 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3146 protected void snapShotWindow_actionPerformed(ActionEvent e)
3148 // currently the menu option to do this is not shown
3151 int width = getWidth();
3152 int height = getHeight();
3154 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3155 ImageWriterI writer = new ImageWriterI()
3158 public void exportImage(Graphics g) throws Exception
3161 jalview.bin.Console.info("Successfully written snapshot to file "
3162 + of.getAbsolutePath());
3165 String title = "View of desktop";
3166 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3170 exporter.doExport(of, this, width, height, title);
3171 } catch (ImageOutputException ioex)
3173 jalview.bin.Console.error(
3174 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3180 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3181 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3182 * and location last time the view was expanded (if any). However it does not
3183 * remember the split pane divider location - this is set to match the
3184 * 'exploding' frame.
3188 public void explodeViews(SplitFrame sf)
3190 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3191 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3192 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3194 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3196 int viewCount = topPanels.size();
3203 * Processing in reverse order works, forwards order leaves the first panels not
3204 * visible. I don't know why!
3206 for (int i = viewCount - 1; i >= 0; i--)
3209 * Make new top and bottom frames. These take over the respective AlignmentPanel
3210 * objects, including their AlignmentViewports, so the cdna/protein
3211 * relationships between the viewports is carried over to the new split frames.
3213 * explodedGeometry holds the (x, y) position of the previously exploded
3214 * SplitFrame, and the (width, height) of the AlignFrame component
3216 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3217 AlignFrame newTopFrame = new AlignFrame(topPanel);
3218 newTopFrame.setSize(oldTopFrame.getSize());
3219 newTopFrame.setVisible(true);
3220 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3221 .getExplodedGeometry();
3222 if (geometry != null)
3224 newTopFrame.setSize(geometry.getSize());
3227 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3228 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3229 newBottomFrame.setSize(oldBottomFrame.getSize());
3230 newBottomFrame.setVisible(true);
3231 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3232 .getExplodedGeometry();
3233 if (geometry != null)
3235 newBottomFrame.setSize(geometry.getSize());
3238 topPanel.av.setGatherViewsHere(false);
3239 bottomPanel.av.setGatherViewsHere(false);
3240 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3242 if (geometry != null)
3244 splitFrame.setLocation(geometry.getLocation());
3246 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3250 * Clear references to the panels (now relocated in the new SplitFrames) before
3251 * closing the old SplitFrame.
3254 bottomPanels.clear();
3259 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3260 * back into the given SplitFrame as additional views. Note that the gathered
3261 * frames may themselves have multiple views.
3265 public void gatherViews(GSplitFrame source)
3268 * special handling of explodedGeometry for a view within a SplitFrame: - it
3269 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3270 * height) of the AlignFrame component
3272 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3273 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3274 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3275 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3276 myBottomFrame.viewport
3277 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3278 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3279 myTopFrame.viewport.setGatherViewsHere(true);
3280 myBottomFrame.viewport.setGatherViewsHere(true);
3281 String topViewId = myTopFrame.viewport.getSequenceSetId();
3282 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3284 JInternalFrame[] frames = desktop.getAllFrames();
3285 for (JInternalFrame frame : frames)
3287 if (frame instanceof SplitFrame && frame != source)
3289 SplitFrame sf = (SplitFrame) frame;
3290 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3291 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3292 boolean gatherThis = false;
3293 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3295 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3296 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3297 if (topViewId.equals(topPanel.av.getSequenceSetId())
3298 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3301 topPanel.av.setGatherViewsHere(false);
3302 bottomPanel.av.setGatherViewsHere(false);
3303 topPanel.av.setExplodedGeometry(
3304 new Rectangle(sf.getLocation(), topFrame.getSize()));
3305 bottomPanel.av.setExplodedGeometry(
3306 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3307 myTopFrame.addAlignmentPanel(topPanel, false);
3308 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3314 topFrame.getAlignPanels().clear();
3315 bottomFrame.getAlignPanels().clear();
3322 * The dust settles...give focus to the tab we did this from.
3324 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3327 public static groovy.ui.Console getGroovyConsole()
3329 return groovyConsole;
3333 * handles the payload of a drag and drop event.
3335 * TODO refactor to desktop utilities class
3338 * - Data source strings extracted from the drop event
3340 * - protocol for each data source extracted from the drop event
3344 * - the payload from the drop event
3347 public static void transferFromDropTarget(List<Object> files,
3348 List<DataSourceType> protocols, DropTargetDropEvent evt,
3349 Transferable t) throws Exception
3352 DataFlavor uriListFlavor = new DataFlavor(
3353 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3356 urlFlavour = new DataFlavor(
3357 "application/x-java-url; class=java.net.URL");
3358 } catch (ClassNotFoundException cfe)
3360 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3364 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3369 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3370 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3371 // means url may be null.
3374 protocols.add(DataSourceType.URL);
3375 files.add(url.toString());
3376 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3377 + files.get(files.size() - 1));
3382 if (Platform.isAMacAndNotJS())
3385 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3388 } catch (Throwable ex)
3390 jalview.bin.Console.debug("URL drop handler failed.", ex);
3393 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3395 // Works on Windows and MacOSX
3396 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3397 for (Object file : (List) t
3398 .getTransferData(DataFlavor.javaFileListFlavor))
3401 protocols.add(DataSourceType.FILE);
3406 // Unix like behaviour
3407 boolean added = false;
3409 if (t.isDataFlavorSupported(uriListFlavor))
3411 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3412 // This is used by Unix drag system
3413 data = (String) t.getTransferData(uriListFlavor);
3417 // fallback to text: workaround - on OSX where there's a JVM bug
3419 .debug("standard URIListFlavor failed. Trying text");
3420 // try text fallback
3421 DataFlavor textDf = new DataFlavor(
3422 "text/plain;class=java.lang.String");
3423 if (t.isDataFlavorSupported(textDf))
3425 data = (String) t.getTransferData(textDf);
3428 jalview.bin.Console.debug("Plain text drop content returned "
3429 + (data == null ? "Null - failed" : data));
3434 while (protocols.size() < files.size())
3436 jalview.bin.Console.debug("Adding missing FILE protocol for "
3437 + files.get(protocols.size()));
3438 protocols.add(DataSourceType.FILE);
3440 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3441 data, "\r\n"); st.hasMoreTokens();)
3444 String s = st.nextToken();
3445 if (s.startsWith("#"))
3447 // the line is a comment (as per the RFC 2483)
3450 java.net.URI uri = new java.net.URI(s);
3451 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3453 protocols.add(DataSourceType.URL);
3454 files.add(uri.toString());
3458 // otherwise preserve old behaviour: catch all for file objects
3459 java.io.File file = new java.io.File(uri);
3460 protocols.add(DataSourceType.FILE);
3461 files.add(file.toString());
3466 if (jalview.bin.Console.isDebugEnabled())
3468 if (data == null || !added)
3471 if (t.getTransferDataFlavors() != null
3472 && t.getTransferDataFlavors().length > 0)
3474 jalview.bin.Console.debug(
3475 "Couldn't resolve drop data. Here are the supported flavors:");
3476 for (DataFlavor fl : t.getTransferDataFlavors())
3478 jalview.bin.Console.debug(
3479 "Supported transfer dataflavor: " + fl.toString());
3480 Object df = t.getTransferData(fl);
3483 jalview.bin.Console.debug("Retrieves: " + df);
3487 jalview.bin.Console.debug("Retrieved nothing");
3494 .debug("Couldn't resolve dataflavor for drop: "
3500 if (Platform.isWindowsAndNotJS())
3503 .debug("Scanning dropped content for Windows Link Files");
3505 // resolve any .lnk files in the file drop
3506 for (int f = 0; f < files.size(); f++)
3508 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3509 if (protocols.get(f).equals(DataSourceType.FILE)
3510 && (source.endsWith(".lnk") || source.endsWith(".url")
3511 || source.endsWith(".site")))
3515 Object obj = files.get(f);
3516 File lf = (obj instanceof File ? (File) obj
3517 : new File((String) obj));
3518 // process link file to get a URL
3519 jalview.bin.Console.debug("Found potential link file: " + lf);
3520 WindowsShortcut wscfile = new WindowsShortcut(lf);
3521 String fullname = wscfile.getRealFilename();
3522 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3523 files.set(f, fullname);
3524 jalview.bin.Console.debug("Parsed real filename " + fullname
3525 + " to extract protocol: " + protocols.get(f));
3526 } catch (Exception ex)
3528 jalview.bin.Console.error(
3529 "Couldn't parse " + files.get(f) + " as a link file.",
3538 * Sets the Preferences property for experimental features to True or False
3539 * depending on the state of the controlling menu item
3542 protected void showExperimental_actionPerformed(boolean selected)
3544 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3548 * Answers a (possibly empty) list of any structure viewer frames (currently
3549 * for either Jmol or Chimera) which are currently open. This may optionally
3550 * be restricted to viewers of a specified class, or viewers linked to a
3551 * specified alignment panel.
3554 * if not null, only return viewers linked to this panel
3555 * @param structureViewerClass
3556 * if not null, only return viewers of this class
3559 public List<StructureViewerBase> getStructureViewers(
3560 AlignmentPanel apanel,
3561 Class<? extends StructureViewerBase> structureViewerClass)
3563 List<StructureViewerBase> result = new ArrayList<>();
3564 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3566 for (JInternalFrame frame : frames)
3568 if (frame instanceof StructureViewerBase)
3570 if (structureViewerClass == null
3571 || structureViewerClass.isInstance(frame))
3574 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3576 result.add((StructureViewerBase) frame);
3584 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3586 private static boolean debugScaleMessageDone = false;
3588 public static void debugScaleMessage(Graphics g)
3590 if (debugScaleMessageDone)
3594 // output used by tests to check HiDPI scaling settings in action
3597 Graphics2D gg = (Graphics2D) g;
3600 AffineTransform t = gg.getTransform();
3601 double scaleX = t.getScaleX();
3602 double scaleY = t.getScaleY();
3603 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3604 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3605 debugScaleMessageDone = true;
3609 jalview.bin.Console.debug("Desktop graphics null");
3611 } catch (Exception e)
3613 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3618 * closes the current instance window, disposes and forgets about it.
3620 public static void closeDesktop()
3622 if (Desktop.instance != null)
3624 Desktop.instance.closeAll_actionPerformed(null);
3625 Desktop.instance.setVisible(false);
3626 Desktop us = Desktop.instance;
3627 Desktop.instance = null;
3628 // call dispose in a separate thread - try to avoid indirect deadlocks
3629 new Thread(new Runnable() {
3633 ExecutorService dex = us.dialogExecutor;
3636 us.dialogExecutor=null;
3637 us.block.drainPermits();
3646 * checks if any progress bars are being displayed in any of the windows managed by the desktop
3649 public boolean operationsAreInProgress()
3651 JInternalFrame[] frames = getAllFrames();
3652 for (JInternalFrame frame:frames)
3654 if (frame instanceof IProgressIndicator)
3656 if (((IProgressIndicator)frame).operationInProgress())
3662 return operationInProgress();