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 if (!Platform.isJS())
546 jconsole = new Console(this, showjconsole);
547 jconsole.setHeader(Cache.getVersionDetailsForConsole());
548 showConsole(showjconsole);
550 showNews.setVisible(false);
552 experimentalFeatures.setSelected(showExperimental());
554 getIdentifiersOrgData();
558 // Spawn a thread that shows the splashscreen
561 SwingUtilities.invokeLater(new Runnable()
566 new SplashScreen(true);
571 // Thread off a new instance of the file chooser - this reduces the time
573 // takes to open it later on.
574 new Thread(new Runnable()
579 jalview.bin.Console.debug("Filechooser init thread started.");
580 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
581 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
583 jalview.bin.Console.debug("Filechooser init thread finished.");
586 // Add the service change listener
587 changeSupport.addJalviewPropertyChangeListener("services",
588 new PropertyChangeListener()
592 public void propertyChange(PropertyChangeEvent evt)
595 .debug("Firing service changed event for "
596 + evt.getNewValue());
597 JalviewServicesChanged(evt);
602 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
605 this.addMouseListener(ma = new MouseAdapter()
608 public void mousePressed(MouseEvent evt)
610 if (evt.isPopupTrigger()) // Mac
612 showPasteMenu(evt.getX(), evt.getY());
617 public void mouseReleased(MouseEvent evt)
619 if (evt.isPopupTrigger()) // Windows
621 showPasteMenu(evt.getX(), evt.getY());
625 desktop.addMouseListener(ma);
633 // used for jalviewjsTest
634 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
639 * Answers true if user preferences to enable experimental features is True
644 public boolean showExperimental()
646 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
647 Boolean.FALSE.toString());
648 return Boolean.valueOf(experimental).booleanValue();
651 public void doConfigureStructurePrefs()
653 // configure services
654 StructureSelectionManager ssm = StructureSelectionManager
655 .getStructureSelectionManager(this);
656 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
658 ssm.setAddTempFacAnnot(
659 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
660 ssm.setProcessSecondaryStructure(
661 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
662 // JAL-3915 - RNAView is no longer an option so this has no effect
663 ssm.setSecStructServices(
664 Cache.getDefault(Preferences.USE_RNAVIEW, false));
668 ssm.setAddTempFacAnnot(false);
669 ssm.setProcessSecondaryStructure(false);
670 ssm.setSecStructServices(false);
674 public void checkForNews()
676 final Desktop me = this;
677 // Thread off the news reader, in case there are connection problems.
678 new Thread(new Runnable()
683 jalview.bin.Console.debug("Starting news thread.");
684 jvnews = new BlogReader(me);
685 showNews.setVisible(true);
686 jalview.bin.Console.debug("Completed news thread.");
691 public void getIdentifiersOrgData()
693 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
694 {// Thread off the identifiers fetcher
695 new Thread(new Runnable()
701 .debug("Downloading data from identifiers.org");
704 UrlDownloadClient.download(IdOrgSettings.getUrl(),
705 IdOrgSettings.getDownloadLocation());
706 } catch (IOException e)
709 .debug("Exception downloading identifiers.org data"
719 protected void showNews_actionPerformed(ActionEvent e)
721 showNews(showNews.isSelected());
724 void showNews(boolean visible)
726 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
727 showNews.setSelected(visible);
728 if (visible && !jvnews.isVisible())
730 new Thread(new Runnable()
735 long now = System.currentTimeMillis();
736 Desktop.instance.setProgressBar(
737 MessageManager.getString("status.refreshing_news"), now);
738 jvnews.refreshNews();
739 Desktop.instance.setProgressBar(null, now);
747 * recover the last known dimensions for a jalview window
750 * - empty string is desktop, all other windows have unique prefix
751 * @return null or last known dimensions scaled to current geometry (if last
752 * window geom was known)
754 Rectangle getLastKnownDimensions(String windowName)
756 // TODO: lock aspect ratio for scaling desktop Bug #0058199
757 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
758 String x = Cache.getProperty(windowName + "SCREEN_X");
759 String y = Cache.getProperty(windowName + "SCREEN_Y");
760 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
761 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
762 if ((x != null) && (y != null) && (width != null) && (height != null))
764 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
765 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
766 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
768 // attempt #1 - try to cope with change in screen geometry - this
769 // version doesn't preserve original jv aspect ratio.
770 // take ratio of current screen size vs original screen size.
771 double sw = ((1f * screenSize.width) / (1f * Integer
772 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
773 double sh = ((1f * screenSize.height) / (1f * Integer
774 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
775 // rescale the bounds depending upon the current screen geometry.
776 ix = (int) (ix * sw);
777 iw = (int) (iw * sw);
778 iy = (int) (iy * sh);
779 ih = (int) (ih * sh);
780 while (ix >= screenSize.width)
782 jalview.bin.Console.debug(
783 "Window geometry location recall error: shifting horizontal to within screenbounds.");
784 ix -= screenSize.width;
786 while (iy >= screenSize.height)
788 jalview.bin.Console.debug(
789 "Window geometry location recall error: shifting vertical to within screenbounds.");
790 iy -= screenSize.height;
792 jalview.bin.Console.debug(
793 "Got last known dimensions for " + windowName + ": x:" + ix
794 + " y:" + iy + " width:" + iw + " height:" + ih);
796 // return dimensions for new instance
797 return new Rectangle(ix, iy, iw, ih);
802 void showPasteMenu(int x, int y)
804 JPopupMenu popup = new JPopupMenu();
805 JMenuItem item = new JMenuItem(
806 MessageManager.getString("label.paste_new_window"));
807 item.addActionListener(new ActionListener()
810 public void actionPerformed(ActionEvent evt)
817 popup.show(this, x, y);
822 // quick patch for JAL-4150 - needs some more work and test coverage
823 // TODO - unify below and AlignFrame.paste()
824 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
825 // clipboard has come from a different alignment window than the one where
826 // paste has been called! JAL-4151
828 if (Desktop.jalviewClipboard != null)
830 // The clipboard was filled from within Jalview, we must use the
832 // And dataset from the copied alignment
833 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
834 // be doubly sure that we create *new* sequence objects.
835 SequenceI[] sequences = new SequenceI[newseq.length];
836 for (int i = 0; i < newseq.length; i++)
838 sequences[i] = new Sequence(newseq[i]);
840 Alignment alignment = new Alignment(sequences);
841 // dataset is inherited
842 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
843 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
844 AlignFrame.DEFAULT_HEIGHT);
845 String newtitle = new String("Copied sequences");
847 if (Desktop.jalviewClipboard[2] != null)
849 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
850 af.viewport.setHiddenColumns(hc);
853 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
854 AlignFrame.DEFAULT_HEIGHT);
861 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
862 Transferable contents = c.getContents(this);
864 if (contents != null)
866 String file = (String) contents
867 .getTransferData(DataFlavor.stringFlavor);
869 FileFormatI format = new IdentifyFile().identify(file,
870 DataSourceType.PASTE);
872 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
875 } catch (Exception ex)
878 "Unable to paste alignment from system clipboard:\n" + ex);
884 * Adds and opens the given frame to the desktop
895 public static synchronized void addInternalFrame(
896 final JInternalFrame frame, String title, int w, int h)
898 addInternalFrame(frame, title, true, w, h, true, false);
902 * Add an internal frame to the Jalview desktop
909 * When true, display frame immediately, otherwise, caller must call
910 * setVisible themselves.
916 public static synchronized void addInternalFrame(
917 final JInternalFrame frame, String title, boolean makeVisible,
920 addInternalFrame(frame, title, makeVisible, w, h, true, false);
924 * Add an internal frame to the Jalview desktop and make it visible
937 public static synchronized void addInternalFrame(
938 final JInternalFrame frame, String title, int w, int h,
941 addInternalFrame(frame, title, true, w, h, resizable, false);
945 * Add an internal frame to the Jalview desktop
952 * When true, display frame immediately, otherwise, caller must call
953 * setVisible themselves.
960 * @param ignoreMinSize
961 * Do not set the default minimum size for frame
963 public static synchronized void addInternalFrame(
964 final JInternalFrame frame, String title, boolean makeVisible,
965 int w, int h, boolean resizable, boolean ignoreMinSize)
968 // TODO: allow callers to determine X and Y position of frame (eg. via
970 // TODO: consider fixing method to update entries in the window submenu with
971 // the current window title
973 frame.setTitle(title);
974 if (frame.getWidth() < 1 || frame.getHeight() < 1)
978 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
979 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
980 // IF JALVIEW IS RUNNING HEADLESS
981 // ///////////////////////////////////////////////
982 if (instance == null || (System.getProperty("java.awt.headless") != null
983 && System.getProperty("java.awt.headless").equals("true")))
992 frame.setMinimumSize(
993 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
995 // Set default dimension for Alignment Frame window.
996 // The Alignment Frame window could be added from a number of places,
998 // I did this here in order not to miss out on any Alignment frame.
999 if (frame instanceof AlignFrame)
1001 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1002 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1006 frame.setVisible(makeVisible);
1007 frame.setClosable(true);
1008 frame.setResizable(resizable);
1009 frame.setMaximizable(resizable);
1010 frame.setIconifiable(resizable);
1011 frame.setOpaque(Platform.isJS());
1013 if (frame.getX() < 1 && frame.getY() < 1)
1015 frame.setLocation(xOffset * openFrameCount,
1016 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1020 * add an entry for the new frame in the Window menu (and remove it when the
1023 final JMenuItem menuItem = new JMenuItem(title);
1024 frame.addInternalFrameListener(new InternalFrameAdapter()
1027 public void internalFrameActivated(InternalFrameEvent evt)
1029 JInternalFrame itf = desktop.getSelectedFrame();
1032 if (itf instanceof AlignFrame)
1034 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1041 public void internalFrameClosed(InternalFrameEvent evt)
1043 PaintRefresher.RemoveComponent(frame);
1046 * defensive check to prevent frames being added half off the window
1048 if (openFrameCount > 0)
1054 * ensure no reference to alignFrame retained by menu item listener
1056 if (menuItem.getActionListeners().length > 0)
1058 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1060 windowMenu.remove(menuItem);
1064 menuItem.addActionListener(new ActionListener()
1067 public void actionPerformed(ActionEvent e)
1071 frame.setSelected(true);
1072 frame.setIcon(false);
1073 } catch (java.beans.PropertyVetoException ex)
1080 setKeyBindings(frame);
1084 windowMenu.add(menuItem);
1089 frame.setSelected(true);
1090 frame.requestFocus();
1091 } catch (java.beans.PropertyVetoException ve)
1093 } catch (java.lang.ClassCastException cex)
1095 jalview.bin.Console.warn(
1096 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1102 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1107 private static void setKeyBindings(JInternalFrame frame)
1109 @SuppressWarnings("serial")
1110 final Action closeAction = new AbstractAction()
1113 public void actionPerformed(ActionEvent e)
1120 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1122 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1123 InputEvent.CTRL_DOWN_MASK);
1124 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1125 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1127 InputMap inputMap = frame
1128 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1129 String ctrlW = ctrlWKey.toString();
1130 inputMap.put(ctrlWKey, ctrlW);
1131 inputMap.put(cmdWKey, ctrlW);
1133 ActionMap actionMap = frame.getActionMap();
1134 actionMap.put(ctrlW, closeAction);
1138 public void lostOwnership(Clipboard clipboard, Transferable contents)
1142 Desktop.jalviewClipboard = null;
1145 internalCopy = false;
1149 public void dragEnter(DropTargetDragEvent evt)
1154 public void dragExit(DropTargetEvent evt)
1159 public void dragOver(DropTargetDragEvent evt)
1164 public void dropActionChanged(DropTargetDragEvent evt)
1175 public void drop(DropTargetDropEvent evt)
1177 boolean success = true;
1178 // JAL-1552 - acceptDrop required before getTransferable call for
1179 // Java's Transferable for native dnd
1180 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1181 Transferable t = evt.getTransferable();
1182 List<Object> files = new ArrayList<>();
1183 List<DataSourceType> protocols = new ArrayList<>();
1187 Desktop.transferFromDropTarget(files, protocols, evt, t);
1188 } catch (Exception e)
1190 e.printStackTrace();
1198 for (int i = 0; i < files.size(); i++)
1200 // BH 2018 File or String
1201 Object file = files.get(i);
1202 String fileName = file.toString();
1203 DataSourceType protocol = (protocols == null)
1204 ? DataSourceType.FILE
1206 FileFormatI format = null;
1208 if (fileName.endsWith(".jar"))
1210 format = FileFormat.Jalview;
1215 format = new IdentifyFile().identify(file, protocol);
1217 if (file instanceof File)
1219 Platform.cacheFileData((File) file);
1221 new FileLoader().LoadFile(null, file, protocol, format);
1224 } catch (Exception ex)
1229 evt.dropComplete(success); // need this to ensure input focus is properly
1230 // transfered to any new windows created
1240 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1242 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1243 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1244 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1245 BackupFiles.getEnabled());
1247 chooser.setFileView(new JalviewFileView());
1248 chooser.setDialogTitle(
1249 MessageManager.getString("label.open_local_file"));
1250 chooser.setToolTipText(MessageManager.getString("action.open"));
1252 chooser.setResponseHandler(0, () -> {
1253 File selectedFile = chooser.getSelectedFile();
1254 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1256 FileFormatI format = chooser.getSelectedFormat();
1259 * Call IdentifyFile to verify the file contains what its extension implies.
1260 * Skip this step for dynamically added file formats, because IdentifyFile does
1261 * not know how to recognise them.
1263 if (FileFormats.getInstance().isIdentifiable(format))
1267 format = new IdentifyFile().identify(selectedFile,
1268 DataSourceType.FILE);
1269 } catch (FileFormatException e)
1271 // format = null; //??
1275 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1278 chooser.showOpenDialog(this);
1282 * Shows a dialog for input of a URL at which to retrieve alignment data
1287 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1289 // This construct allows us to have a wider textfield
1291 JLabel label = new JLabel(
1292 MessageManager.getString("label.input_file_url"));
1294 JPanel panel = new JPanel(new GridLayout(2, 1));
1298 * the URL to fetch is input in Java: an editable combobox with history JS:
1299 * (pending JAL-3038) a plain text field
1302 String urlBase = "https://www.";
1303 if (Platform.isJS())
1305 history = new JTextField(urlBase, 35);
1314 JComboBox<String> asCombo = new JComboBox<>();
1315 asCombo.setPreferredSize(new Dimension(400, 20));
1316 asCombo.setEditable(true);
1317 asCombo.addItem(urlBase);
1318 String historyItems = Cache.getProperty("RECENT_URL");
1319 if (historyItems != null)
1321 for (String token : historyItems.split("\\t"))
1323 asCombo.addItem(token);
1330 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1331 MessageManager.getString("action.cancel") };
1332 Runnable action = () -> {
1333 @SuppressWarnings("unchecked")
1334 String url = (history instanceof JTextField
1335 ? ((JTextField) history).getText()
1336 : ((JComboBox<String>) history).getEditor().getItem()
1337 .toString().trim());
1339 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1341 if (viewport != null)
1343 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1344 FileFormat.Jalview);
1348 new FileLoader().LoadFile(url, DataSourceType.URL,
1349 FileFormat.Jalview);
1354 FileFormatI format = null;
1357 format = new IdentifyFile().identify(url, DataSourceType.URL);
1358 } catch (FileFormatException e)
1360 // TODO revise error handling, distinguish between
1361 // URL not found and response not valid
1366 String msg = MessageManager.formatMessage("label.couldnt_locate",
1368 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1369 MessageManager.getString("label.url_not_found"),
1370 JvOptionPane.WARNING_MESSAGE);
1374 if (viewport != null)
1376 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1381 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1385 String dialogOption = MessageManager
1386 .getString("label.input_alignment_from_url");
1387 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1388 .showInternalDialog(panel, dialogOption,
1389 JvOptionPane.YES_NO_CANCEL_OPTION,
1390 JvOptionPane.PLAIN_MESSAGE, null, options,
1391 MessageManager.getString("action.ok"));
1395 * Opens the CutAndPaste window for the user to paste an alignment in to
1398 * - if not null, the pasted alignment is added to the current
1399 * alignment; if null, to a new alignment window
1402 public void inputTextboxMenuItem_actionPerformed(
1403 AlignmentViewPanel viewPanel)
1405 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1406 cap.setForInput(viewPanel);
1407 Desktop.addInternalFrame(cap,
1408 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1413 * Check with user and saving files before actually quitting
1415 public void desktopQuit()
1417 desktopQuit(true, false);
1420 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1422 final Runnable doDesktopQuit = () -> {
1423 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1424 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1425 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1426 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1427 getBounds().y, getWidth(), getHeight()));
1429 if (jconsole != null)
1431 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1432 jconsole.stopConsole();
1437 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1440 // Frames should all close automatically. Keeping external
1441 // viewers open should already be decided by user.
1442 closeAll_actionPerformed(null);
1444 // check for aborted quit
1445 if (QuitHandler.quitCancelled())
1447 jalview.bin.Console.debug("Desktop aborting quit");
1451 if (dialogExecutor != null)
1453 dialogExecutor.shutdownNow();
1456 if (groovyConsole != null)
1458 // suppress a possible repeat prompt to save script
1459 groovyConsole.setDirty(false);
1460 groovyConsole.exit();
1463 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1465 // note that shutdown hook will not be run
1466 jalview.bin.Console.debug("Force Quit selected by user");
1467 Runtime.getRuntime().halt(0);
1470 jalview.bin.Console.debug("Quit selected by user");
1473 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1474 // instance.dispose();
1479 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1480 QuitHandler.defaultCancelQuit);
1484 * Don't call this directly, use desktopQuit() above. Exits the program.
1489 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1490 // not run a second time if gotQuitResponse flag has been set (i.e. user
1491 // confirmed quit of some kind).
1492 Jalview.exit("Desktop exiting.", 0);
1495 private void storeLastKnownDimensions(String string, Rectangle jc)
1497 jalview.bin.Console.debug("Storing last known dimensions for " + string
1498 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1499 + " height:" + jc.height);
1501 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1502 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1503 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1504 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1514 public void aboutMenuItem_actionPerformed(ActionEvent e)
1516 new Thread(new Runnable()
1521 new SplashScreen(false);
1527 * Returns the html text for the About screen, including any available version
1528 * number, build details, author details and citation reference, but without
1529 * the enclosing {@code html} tags
1533 public String getAboutMessage()
1535 StringBuilder message = new StringBuilder(1024);
1536 message.append("<div style=\"font-family: sans-serif;\">")
1537 .append("<h1><strong>Version: ")
1538 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1539 .append("<strong>Built: <em>")
1540 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1541 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1542 .append("</strong>");
1544 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1545 if (latestVersion.equals("Checking"))
1547 // JBP removed this message for 2.11: May be reinstated in future version
1548 // message.append("<br>...Checking latest version...</br>");
1550 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1552 boolean red = false;
1553 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1554 .indexOf("automated build") == -1)
1557 // Displayed when code version and jnlp version do not match and code
1558 // version is not a development build
1559 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1562 message.append("<br>!! Version ")
1563 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1564 .append(" is available for download from ")
1565 .append(Cache.getDefault("www.jalview.org",
1566 "https://www.jalview.org"))
1570 message.append("</div>");
1573 message.append("<br>Authors: ");
1574 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1575 message.append(CITATION);
1577 message.append("</div>");
1579 return message.toString();
1583 * Action on requesting Help documentation
1586 public void documentationMenuItem_actionPerformed()
1590 if (Platform.isJS())
1592 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1601 Help.showHelpWindow();
1603 } catch (Exception ex)
1605 System.err.println("Error opening help: " + ex.getMessage());
1610 public void closeAll_actionPerformed(ActionEvent e)
1612 // TODO show a progress bar while closing?
1613 JInternalFrame[] frames = desktop.getAllFrames();
1614 for (int i = 0; i < frames.length; i++)
1618 frames[i].setClosed(true);
1619 } catch (java.beans.PropertyVetoException ex)
1623 Jalview.setCurrentAlignFrame(null);
1624 jalview.bin.Console.info("ALL CLOSED");
1627 * reset state of singleton objects as appropriate (clear down session state
1628 * when all windows are closed)
1630 StructureSelectionManager ssm = StructureSelectionManager
1631 .getStructureSelectionManager(this);
1638 public int structureViewersStillRunningCount()
1641 JInternalFrame[] frames = desktop.getAllFrames();
1642 for (int i = 0; i < frames.length; i++)
1644 if (frames[i] != null
1645 && frames[i] instanceof JalviewStructureDisplayI)
1647 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1655 public void raiseRelated_actionPerformed(ActionEvent e)
1657 reorderAssociatedWindows(false, false);
1661 public void minimizeAssociated_actionPerformed(ActionEvent e)
1663 reorderAssociatedWindows(true, false);
1666 void closeAssociatedWindows()
1668 reorderAssociatedWindows(false, true);
1674 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1678 protected void garbageCollect_actionPerformed(ActionEvent e)
1680 // We simply collect the garbage
1681 jalview.bin.Console.debug("Collecting garbage...");
1683 jalview.bin.Console.debug("Finished garbage collection.");
1689 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1693 protected void showMemusage_actionPerformed(ActionEvent e)
1695 desktop.showMemoryUsage(showMemusage.isSelected());
1702 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1706 protected void showConsole_actionPerformed(ActionEvent e)
1708 showConsole(showConsole.isSelected());
1711 Console jconsole = null;
1714 * control whether the java console is visible or not
1718 void showConsole(boolean selected)
1720 // TODO: decide if we should update properties file
1721 if (jconsole != null) // BH 2018
1723 showConsole.setSelected(selected);
1724 Cache.setProperty("SHOW_JAVA_CONSOLE",
1725 Boolean.valueOf(selected).toString());
1726 jconsole.setVisible(selected);
1730 void reorderAssociatedWindows(boolean minimize, boolean close)
1732 JInternalFrame[] frames = desktop.getAllFrames();
1733 if (frames == null || frames.length < 1)
1738 AlignmentViewport source = null, target = null;
1739 if (frames[0] instanceof AlignFrame)
1741 source = ((AlignFrame) frames[0]).getCurrentView();
1743 else if (frames[0] instanceof TreePanel)
1745 source = ((TreePanel) frames[0]).getViewPort();
1747 else if (frames[0] instanceof PCAPanel)
1749 source = ((PCAPanel) frames[0]).av;
1751 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1753 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1758 for (int i = 0; i < frames.length; i++)
1761 if (frames[i] == null)
1765 if (frames[i] instanceof AlignFrame)
1767 target = ((AlignFrame) frames[i]).getCurrentView();
1769 else if (frames[i] instanceof TreePanel)
1771 target = ((TreePanel) frames[i]).getViewPort();
1773 else if (frames[i] instanceof PCAPanel)
1775 target = ((PCAPanel) frames[i]).av;
1777 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1779 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1782 if (source == target)
1788 frames[i].setClosed(true);
1792 frames[i].setIcon(minimize);
1795 frames[i].toFront();
1799 } catch (java.beans.PropertyVetoException ex)
1814 protected void preferences_actionPerformed(ActionEvent e)
1816 Preferences.openPreferences();
1820 * Prompts the user to choose a file and then saves the Jalview state as a
1821 * Jalview project file
1824 public void saveState_actionPerformed()
1826 saveState_actionPerformed(false);
1829 public void saveState_actionPerformed(boolean saveAs)
1831 java.io.File projectFile = getProjectFile();
1832 // autoSave indicates we already have a file and don't need to ask
1833 boolean autoSave = projectFile != null && !saveAs
1834 && BackupFiles.getEnabled();
1836 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1837 // saveAs="+saveAs+", Backups
1838 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1840 boolean approveSave = false;
1843 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1846 chooser.setFileView(new JalviewFileView());
1847 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1849 int value = chooser.showSaveDialog(this);
1851 if (value == JalviewFileChooser.APPROVE_OPTION)
1853 projectFile = chooser.getSelectedFile();
1854 setProjectFile(projectFile);
1859 if (approveSave || autoSave)
1861 final Desktop me = this;
1862 final java.io.File chosenFile = projectFile;
1863 new Thread(new Runnable()
1868 // TODO: refactor to Jalview desktop session controller action.
1869 setProgressBar(MessageManager.formatMessage(
1870 "label.saving_jalview_project", new Object[]
1871 { chosenFile.getName() }), chosenFile.hashCode());
1872 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1873 // TODO catch and handle errors for savestate
1874 // TODO prevent user from messing with the Desktop whilst we're saving
1877 boolean doBackup = BackupFiles.getEnabled();
1878 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1881 new Jalview2XML().saveState(
1882 doBackup ? backupfiles.getTempFile() : chosenFile);
1886 backupfiles.setWriteSuccess(true);
1887 backupfiles.rollBackupsAndRenameTempFile();
1889 } catch (OutOfMemoryError oom)
1891 new OOMWarning("Whilst saving current state to "
1892 + chosenFile.getName(), oom);
1893 } catch (Exception ex)
1895 jalview.bin.Console.error("Problems whilst trying to save to "
1896 + chosenFile.getName(), ex);
1897 JvOptionPane.showMessageDialog(me,
1898 MessageManager.formatMessage(
1899 "label.error_whilst_saving_current_state_to",
1901 { chosenFile.getName() }),
1902 MessageManager.getString("label.couldnt_save_project"),
1903 JvOptionPane.WARNING_MESSAGE);
1905 setProgressBar(null, chosenFile.hashCode());
1912 public void saveAsState_actionPerformed(ActionEvent e)
1914 saveState_actionPerformed(true);
1917 protected void setProjectFile(File choice)
1919 this.projectFile = choice;
1922 public File getProjectFile()
1924 return this.projectFile;
1928 * Shows a file chooser dialog and tries to read in the selected file as a
1932 public void loadState_actionPerformed()
1934 final String[] suffix = new String[] { "jvp", "jar" };
1935 final String[] desc = new String[] { "Jalview Project",
1936 "Jalview Project (old)" };
1937 JalviewFileChooser chooser = new JalviewFileChooser(
1938 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1939 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1943 chooser.setFileView(new JalviewFileView());
1944 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1945 chooser.setResponseHandler(0, () -> {
1946 File selectedFile = chooser.getSelectedFile();
1947 setProjectFile(selectedFile);
1948 String choice = selectedFile.getAbsolutePath();
1949 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1950 new Thread(new Runnable()
1957 new Jalview2XML().loadJalviewAlign(selectedFile);
1958 } catch (OutOfMemoryError oom)
1960 new OOMWarning("Whilst loading project from " + choice, oom);
1961 } catch (Exception ex)
1963 jalview.bin.Console.error(
1964 "Problems whilst loading project from " + choice, ex);
1965 JvOptionPane.showMessageDialog(Desktop.desktop,
1966 MessageManager.formatMessage(
1967 "label.error_whilst_loading_project_from",
1970 MessageManager.getString("label.couldnt_load_project"),
1971 JvOptionPane.WARNING_MESSAGE);
1974 }, "Project Loader").start();
1977 chooser.showOpenDialog(this);
1981 public void inputSequence_actionPerformed(ActionEvent e)
1983 new SequenceFetcher(this);
1986 JPanel progressPanel;
1988 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1990 public void startLoading(final Object fileName)
1992 if (fileLoadingCount == 0)
1994 fileLoadingPanels.add(addProgressPanel(MessageManager
1995 .formatMessage("label.loading_file", new Object[]
2001 private JPanel addProgressPanel(String string)
2003 if (progressPanel == null)
2005 progressPanel = new JPanel(new GridLayout(1, 1));
2006 totalProgressCount = 0;
2007 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2009 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2010 JProgressBar progressBar = new JProgressBar();
2011 progressBar.setIndeterminate(true);
2013 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2015 thisprogress.add(progressBar, BorderLayout.CENTER);
2016 progressPanel.add(thisprogress);
2017 ((GridLayout) progressPanel.getLayout()).setRows(
2018 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2019 ++totalProgressCount;
2020 instance.validate();
2021 return thisprogress;
2024 int totalProgressCount = 0;
2026 private void removeProgressPanel(JPanel progbar)
2028 if (progressPanel != null)
2030 synchronized (progressPanel)
2032 progressPanel.remove(progbar);
2033 GridLayout gl = (GridLayout) progressPanel.getLayout();
2034 gl.setRows(gl.getRows() - 1);
2035 if (--totalProgressCount < 1)
2037 this.getContentPane().remove(progressPanel);
2038 progressPanel = null;
2045 public void stopLoading()
2048 if (fileLoadingCount < 1)
2050 while (fileLoadingPanels.size() > 0)
2052 removeProgressPanel(fileLoadingPanels.remove(0));
2054 fileLoadingPanels.clear();
2055 fileLoadingCount = 0;
2060 public static int getViewCount(String alignmentId)
2062 AlignmentViewport[] aps = getViewports(alignmentId);
2063 return (aps == null) ? 0 : aps.length;
2068 * @param alignmentId
2069 * - if null, all sets are returned
2070 * @return all AlignmentPanels concerning the alignmentId sequence set
2072 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2074 if (Desktop.desktop == null)
2076 // no frames created and in headless mode
2077 // TODO: verify that frames are recoverable when in headless mode
2080 List<AlignmentPanel> aps = new ArrayList<>();
2081 AlignFrame[] frames = getAlignFrames();
2086 for (AlignFrame af : frames)
2088 for (AlignmentPanel ap : af.alignPanels)
2090 if (alignmentId == null
2091 || alignmentId.equals(ap.av.getSequenceSetId()))
2097 if (aps.size() == 0)
2101 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2106 * get all the viewports on an alignment.
2108 * @param sequenceSetId
2109 * unique alignment id (may be null - all viewports returned in that
2111 * @return all viewports on the alignment bound to sequenceSetId
2113 public static AlignmentViewport[] getViewports(String sequenceSetId)
2115 List<AlignmentViewport> viewp = new ArrayList<>();
2116 if (desktop != null)
2118 AlignFrame[] frames = Desktop.getAlignFrames();
2120 for (AlignFrame afr : frames)
2122 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2123 .equals(sequenceSetId))
2125 if (afr.alignPanels != null)
2127 for (AlignmentPanel ap : afr.alignPanels)
2129 if (sequenceSetId == null
2130 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2138 viewp.add(afr.getViewport());
2142 if (viewp.size() > 0)
2144 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2151 * Explode the views in the given frame into separate AlignFrame
2155 public static void explodeViews(AlignFrame af)
2157 int size = af.alignPanels.size();
2163 // FIXME: ideally should use UI interface API
2164 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2165 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2166 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2167 for (int i = 0; i < size; i++)
2169 AlignmentPanel ap = af.alignPanels.get(i);
2171 AlignFrame newaf = new AlignFrame(ap);
2173 // transfer reference for existing feature settings to new alignFrame
2174 if (ap == af.alignPanel)
2176 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2178 newaf.featureSettings = viewFeatureSettings;
2180 newaf.setFeatureSettingsGeometry(fsBounds);
2184 * Restore the view's last exploded frame geometry if known. Multiple views from
2185 * one exploded frame share and restore the same (frame) position and size.
2187 Rectangle geometry = ap.av.getExplodedGeometry();
2188 if (geometry != null)
2190 newaf.setBounds(geometry);
2193 ap.av.setGatherViewsHere(false);
2195 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2196 AlignFrame.DEFAULT_HEIGHT);
2197 // and materialise a new feature settings dialog instance for the new
2199 // (closes the old as if 'OK' was pressed)
2200 if (ap == af.alignPanel && newaf.featureSettings != null
2201 && newaf.featureSettings.isOpen()
2202 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2204 newaf.showFeatureSettingsUI();
2208 af.featureSettings = null;
2209 af.alignPanels.clear();
2210 af.closeMenuItem_actionPerformed(true);
2215 * Gather expanded views (separate AlignFrame's) with the same sequence set
2216 * identifier back in to this frame as additional views, and close the
2217 * expanded views. Note the expanded frames may themselves have multiple
2218 * views. We take the lot.
2222 public void gatherViews(AlignFrame source)
2224 source.viewport.setGatherViewsHere(true);
2225 source.viewport.setExplodedGeometry(source.getBounds());
2226 JInternalFrame[] frames = desktop.getAllFrames();
2227 String viewId = source.viewport.getSequenceSetId();
2228 for (int t = 0; t < frames.length; t++)
2230 if (frames[t] instanceof AlignFrame && frames[t] != source)
2232 AlignFrame af = (AlignFrame) frames[t];
2233 boolean gatherThis = false;
2234 for (int a = 0; a < af.alignPanels.size(); a++)
2236 AlignmentPanel ap = af.alignPanels.get(a);
2237 if (viewId.equals(ap.av.getSequenceSetId()))
2240 ap.av.setGatherViewsHere(false);
2241 ap.av.setExplodedGeometry(af.getBounds());
2242 source.addAlignmentPanel(ap, false);
2248 if (af.featureSettings != null && af.featureSettings.isOpen())
2250 if (source.featureSettings == null)
2252 // preserve the feature settings geometry for this frame
2253 source.featureSettings = af.featureSettings;
2254 source.setFeatureSettingsGeometry(
2255 af.getFeatureSettingsGeometry());
2259 // close it and forget
2260 af.featureSettings.close();
2263 af.alignPanels.clear();
2264 af.closeMenuItem_actionPerformed(true);
2269 // refresh the feature setting UI for the source frame if it exists
2270 if (source.featureSettings != null && source.featureSettings.isOpen())
2272 source.showFeatureSettingsUI();
2277 public JInternalFrame[] getAllFrames()
2279 return desktop.getAllFrames();
2283 * Checks the given url to see if it gives a response indicating that the user
2284 * should be informed of a new questionnaire.
2288 public void checkForQuestionnaire(String url)
2290 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2291 // javax.swing.SwingUtilities.invokeLater(jvq);
2292 new Thread(jvq).start();
2295 public void checkURLLinks()
2297 // Thread off the URL link checker
2298 addDialogThread(new Runnable()
2303 if (Cache.getDefault("CHECKURLLINKS", true))
2305 // check what the actual links are - if it's just the default don't
2306 // bother with the warning
2307 List<String> links = Preferences.sequenceUrlLinks
2310 // only need to check links if there is one with a
2311 // SEQUENCE_ID which is not the default EMBL_EBI link
2312 ListIterator<String> li = links.listIterator();
2313 boolean check = false;
2314 List<JLabel> urls = new ArrayList<>();
2315 while (li.hasNext())
2317 String link = li.next();
2318 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2319 && !UrlConstants.isDefaultString(link))
2322 int barPos = link.indexOf("|");
2323 String urlMsg = barPos == -1 ? link
2324 : link.substring(0, barPos) + ": "
2325 + link.substring(barPos + 1);
2326 urls.add(new JLabel(urlMsg));
2334 // ask user to check in case URL links use old style tokens
2335 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2336 JPanel msgPanel = new JPanel();
2337 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2338 msgPanel.add(Box.createVerticalGlue());
2339 JLabel msg = new JLabel(MessageManager
2340 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2341 JLabel msg2 = new JLabel(MessageManager
2342 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2344 for (JLabel url : urls)
2350 final JCheckBox jcb = new JCheckBox(
2351 MessageManager.getString("label.do_not_display_again"));
2352 jcb.addActionListener(new ActionListener()
2355 public void actionPerformed(ActionEvent e)
2357 // update Cache settings for "don't show this again"
2358 boolean showWarningAgain = !jcb.isSelected();
2359 Cache.setProperty("CHECKURLLINKS",
2360 Boolean.valueOf(showWarningAgain).toString());
2365 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2367 .getString("label.SEQUENCE_ID_no_longer_used"),
2368 JvOptionPane.WARNING_MESSAGE);
2375 * Proxy class for JDesktopPane which optionally displays the current memory
2376 * usage and highlights the desktop area with a red bar if free memory runs
2381 public class MyDesktopPane extends JDesktopPane implements Runnable
2383 private static final float ONE_MB = 1048576f;
2385 boolean showMemoryUsage = false;
2389 java.text.NumberFormat df;
2391 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2394 public MyDesktopPane(boolean showMemoryUsage)
2396 showMemoryUsage(showMemoryUsage);
2399 public void showMemoryUsage(boolean showMemory)
2401 this.showMemoryUsage = showMemory;
2404 Thread worker = new Thread(this);
2410 public boolean isShowMemoryUsage()
2412 return showMemoryUsage;
2418 df = java.text.NumberFormat.getNumberInstance();
2419 df.setMaximumFractionDigits(2);
2420 runtime = Runtime.getRuntime();
2422 while (showMemoryUsage)
2426 maxMemory = runtime.maxMemory() / ONE_MB;
2427 allocatedMemory = runtime.totalMemory() / ONE_MB;
2428 freeMemory = runtime.freeMemory() / ONE_MB;
2429 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2431 percentUsage = (totalFreeMemory / maxMemory) * 100;
2433 // if (percentUsage < 20)
2435 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2437 // instance.set.setBorder(border1);
2440 // sleep after showing usage
2442 } catch (Exception ex)
2444 ex.printStackTrace();
2450 public void paintComponent(Graphics g)
2452 if (showMemoryUsage && g != null && df != null)
2454 if (percentUsage < 20)
2456 g.setColor(Color.red);
2458 FontMetrics fm = g.getFontMetrics();
2461 g.drawString(MessageManager.formatMessage("label.memory_stats",
2463 { df.format(totalFreeMemory), df.format(maxMemory),
2464 df.format(percentUsage) }),
2465 10, getHeight() - fm.getHeight());
2469 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2470 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2475 * Accessor method to quickly get all the AlignmentFrames loaded.
2477 * @return an array of AlignFrame, or null if none found
2479 public static AlignFrame[] getAlignFrames()
2481 if (Jalview.isHeadlessMode())
2483 // Desktop.desktop is null in headless mode
2484 return new AlignFrame[] { Jalview.currentAlignFrame };
2487 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2493 List<AlignFrame> avp = new ArrayList<>();
2495 for (int i = frames.length - 1; i > -1; i--)
2497 if (frames[i] instanceof AlignFrame)
2499 avp.add((AlignFrame) frames[i]);
2501 else if (frames[i] instanceof SplitFrame)
2504 * Also check for a split frame containing an AlignFrame
2506 GSplitFrame sf = (GSplitFrame) frames[i];
2507 if (sf.getTopFrame() instanceof AlignFrame)
2509 avp.add((AlignFrame) sf.getTopFrame());
2511 if (sf.getBottomFrame() instanceof AlignFrame)
2513 avp.add((AlignFrame) sf.getBottomFrame());
2517 if (avp.size() == 0)
2521 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2526 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2530 public GStructureViewer[] getJmols()
2532 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2538 List<GStructureViewer> avp = new ArrayList<>();
2540 for (int i = frames.length - 1; i > -1; i--)
2542 if (frames[i] instanceof AppJmol)
2544 GStructureViewer af = (GStructureViewer) frames[i];
2548 if (avp.size() == 0)
2552 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2557 * Add Groovy Support to Jalview
2560 public void groovyShell_actionPerformed()
2564 openGroovyConsole();
2565 } catch (Exception ex)
2567 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2568 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2570 MessageManager.getString("label.couldnt_create_groovy_shell"),
2571 MessageManager.getString("label.groovy_support_failed"),
2572 JvOptionPane.ERROR_MESSAGE);
2577 * Open the Groovy console
2579 void openGroovyConsole()
2581 if (groovyConsole == null)
2583 groovyConsole = new groovy.ui.Console();
2584 groovyConsole.setVariable("Jalview", this);
2585 groovyConsole.run();
2588 * We allow only one console at a time, so that AlignFrame menu option
2589 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2590 * enable 'Run script', when the console is opened, and the reverse when it is
2593 Window window = (Window) groovyConsole.getFrame();
2594 window.addWindowListener(new WindowAdapter()
2597 public void windowClosed(WindowEvent e)
2600 * rebind CMD-Q from Groovy Console to Jalview Quit
2603 enableExecuteGroovy(false);
2609 * show Groovy console window (after close and reopen)
2611 ((Window) groovyConsole.getFrame()).setVisible(true);
2614 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2615 * opening a second console
2617 enableExecuteGroovy(true);
2621 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2622 * binding when opened
2624 protected void addQuitHandler()
2627 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2629 .getKeyStroke(KeyEvent.VK_Q,
2630 jalview.util.ShortcutKeyMaskExWrapper
2631 .getMenuShortcutKeyMaskEx()),
2633 getRootPane().getActionMap().put("Quit", new AbstractAction()
2636 public void actionPerformed(ActionEvent e)
2644 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2647 * true if Groovy console is open
2649 public void enableExecuteGroovy(boolean enabled)
2652 * disable opening a second Groovy console (or re-enable when the console is
2655 groovyShell.setEnabled(!enabled);
2657 AlignFrame[] alignFrames = getAlignFrames();
2658 if (alignFrames != null)
2660 for (AlignFrame af : alignFrames)
2662 af.setGroovyEnabled(enabled);
2668 * Progress bars managed by the IProgressIndicator method.
2670 private Hashtable<Long, JPanel> progressBars;
2672 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2677 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2680 public void setProgressBar(String message, long id)
2682 if (progressBars == null)
2684 progressBars = new Hashtable<>();
2685 progressBarHandlers = new Hashtable<>();
2688 if (progressBars.get(Long.valueOf(id)) != null)
2690 JPanel panel = progressBars.remove(Long.valueOf(id));
2691 if (progressBarHandlers.contains(Long.valueOf(id)))
2693 progressBarHandlers.remove(Long.valueOf(id));
2695 removeProgressPanel(panel);
2699 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2706 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2707 * jalview.gui.IProgressIndicatorHandler)
2710 public void registerHandler(final long id,
2711 final IProgressIndicatorHandler handler)
2713 if (progressBarHandlers == null
2714 || !progressBars.containsKey(Long.valueOf(id)))
2716 throw new Error(MessageManager.getString(
2717 "error.call_setprogressbar_before_registering_handler"));
2719 progressBarHandlers.put(Long.valueOf(id), handler);
2720 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2721 if (handler.canCancel())
2723 JButton cancel = new JButton(
2724 MessageManager.getString("action.cancel"));
2725 final IProgressIndicator us = this;
2726 cancel.addActionListener(new ActionListener()
2730 public void actionPerformed(ActionEvent e)
2732 handler.cancelActivity(id);
2733 us.setProgressBar(MessageManager
2734 .formatMessage("label.cancelled_params", new Object[]
2735 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2739 progressPanel.add(cancel, BorderLayout.EAST);
2745 * @return true if any progress bars are still active
2748 public boolean operationInProgress()
2750 if (progressBars != null && progressBars.size() > 0)
2758 * This will return the first AlignFrame holding the given viewport instance.
2759 * It will break if there are more than one AlignFrames viewing a particular
2763 * @return alignFrame for viewport
2765 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2767 if (desktop != null)
2769 AlignmentPanel[] aps = getAlignmentPanels(
2770 viewport.getSequenceSetId());
2771 for (int panel = 0; aps != null && panel < aps.length; panel++)
2773 if (aps[panel] != null && aps[panel].av == viewport)
2775 return aps[panel].alignFrame;
2782 public VamsasApplication getVamsasApplication()
2784 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2790 * flag set if jalview GUI is being operated programmatically
2792 private boolean inBatchMode = false;
2795 * check if jalview GUI is being operated programmatically
2797 * @return inBatchMode
2799 public boolean isInBatchMode()
2805 * set flag if jalview GUI is being operated programmatically
2807 * @param inBatchMode
2809 public void setInBatchMode(boolean inBatchMode)
2811 this.inBatchMode = inBatchMode;
2815 * start service discovery and wait till it is done
2817 public void startServiceDiscovery()
2819 startServiceDiscovery(false);
2823 * start service discovery threads - blocking or non-blocking
2827 public void startServiceDiscovery(boolean blocking)
2829 startServiceDiscovery(blocking, false);
2833 * start service discovery threads
2836 * - false means call returns immediately
2837 * @param ignore_SHOW_JWS2_SERVICES_preference
2838 * - when true JABA services are discovered regardless of user's JWS2
2839 * discovery preference setting
2841 public void startServiceDiscovery(boolean blocking,
2842 boolean ignore_SHOW_JWS2_SERVICES_preference)
2844 boolean alive = true;
2845 Thread t0 = null, t1 = null, t2 = null;
2846 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2849 // todo: changesupport handlers need to be transferred
2850 if (discoverer == null)
2852 discoverer = new jalview.ws.jws1.Discoverer();
2853 // register PCS handler for desktop.
2854 discoverer.addPropertyChangeListener(changeSupport);
2856 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2857 // until we phase out completely
2858 (t0 = new Thread(discoverer)).start();
2861 if (ignore_SHOW_JWS2_SERVICES_preference
2862 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2864 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2865 .startDiscoverer(changeSupport);
2869 // TODO: do rest service discovery
2878 } catch (Exception e)
2881 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2882 || (t3 != null && t3.isAlive())
2883 || (t0 != null && t0.isAlive());
2889 * called to check if the service discovery process completed successfully.
2893 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2895 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2897 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2898 .getErrorMessages();
2901 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2903 if (serviceChangedDialog == null)
2905 // only run if we aren't already displaying one of these.
2906 addDialogThread(serviceChangedDialog = new Runnable()
2913 * JalviewDialog jd =new JalviewDialog() {
2915 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2917 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2919 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2921 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2923 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2924 * + " or mis-configured HTTP proxy settings.<br/>" +
2925 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2926 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2927 * true, true, "Web Service Configuration Problem", 450, 400);
2929 * jd.waitForInput();
2931 JvOptionPane.showConfirmDialog(Desktop.desktop,
2932 new JLabel("<html><table width=\"450\"><tr><td>"
2933 + ermsg + "</td></tr></table>"
2934 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2935 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2936 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2937 + " Tools->Preferences dialog box to change them.</p></html>"),
2938 "Web Service Configuration Problem",
2939 JvOptionPane.DEFAULT_OPTION,
2940 JvOptionPane.ERROR_MESSAGE);
2941 serviceChangedDialog = null;
2949 jalview.bin.Console.error(
2950 "Errors reported by JABA discovery service. Check web services preferences.\n"
2957 private Runnable serviceChangedDialog = null;
2960 * start a thread to open a URL in the configured browser. Pops up a warning
2961 * dialog to the user if there is an exception when calling out to the browser
2966 public static void showUrl(final String url)
2968 showUrl(url, Desktop.instance);
2972 * Like showUrl but allows progress handler to be specified
2976 * (null) or object implementing IProgressIndicator
2978 public static void showUrl(final String url,
2979 final IProgressIndicator progress)
2981 new Thread(new Runnable()
2988 if (progress != null)
2990 progress.setProgressBar(MessageManager
2991 .formatMessage("status.opening_params", new Object[]
2992 { url }), this.hashCode());
2994 jalview.util.BrowserLauncher.openURL(url);
2995 } catch (Exception ex)
2997 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2999 .getString("label.web_browser_not_found_unix"),
3000 MessageManager.getString("label.web_browser_not_found"),
3001 JvOptionPane.WARNING_MESSAGE);
3003 ex.printStackTrace();
3005 if (progress != null)
3007 progress.setProgressBar(null, this.hashCode());
3013 public static WsParamSetManager wsparamManager = null;
3015 public static ParamManager getUserParameterStore()
3017 if (wsparamManager == null)
3019 wsparamManager = new WsParamSetManager();
3021 return wsparamManager;
3025 * static hyperlink handler proxy method for use by Jalview's internal windows
3029 public static void hyperlinkUpdate(HyperlinkEvent e)
3031 if (e.getEventType() == EventType.ACTIVATED)
3036 url = e.getURL().toString();
3037 Desktop.showUrl(url);
3038 } catch (Exception x)
3043 .error("Couldn't handle string " + url + " as a URL.");
3045 // ignore any exceptions due to dud links.
3052 * single thread that handles display of dialogs to user.
3054 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3057 * flag indicating if dialogExecutor should try to acquire a permit
3059 private volatile boolean dialogPause = true;
3064 private java.util.concurrent.Semaphore block = new Semaphore(0);
3066 private static groovy.ui.Console groovyConsole;
3069 * add another dialog thread to the queue
3073 public void addDialogThread(final Runnable prompter)
3075 dialogExecutor.submit(new Runnable()
3085 } catch (InterruptedException x)
3089 if (instance == null)
3095 SwingUtilities.invokeAndWait(prompter);
3096 } catch (Exception q)
3098 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3105 public void startDialogQueue()
3107 // set the flag so we don't pause waiting for another permit and semaphore
3108 // the current task to begin
3109 dialogPause = false;
3114 * Outputs an image of the desktop to file in EPS format, after prompting the
3115 * user for choice of Text or Lineart character rendering (unless a preference
3116 * has been set). The file name is generated as
3119 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3123 protected void snapShotWindow_actionPerformed(ActionEvent e)
3125 // currently the menu option to do this is not shown
3128 int width = getWidth();
3129 int height = getHeight();
3131 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3132 ImageWriterI writer = new ImageWriterI()
3135 public void exportImage(Graphics g) throws Exception
3138 jalview.bin.Console.info("Successfully written snapshot to file "
3139 + of.getAbsolutePath());
3142 String title = "View of desktop";
3143 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3147 exporter.doExport(of, this, width, height, title);
3148 } catch (ImageOutputException ioex)
3150 jalview.bin.Console.error(
3151 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3157 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3158 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3159 * and location last time the view was expanded (if any). However it does not
3160 * remember the split pane divider location - this is set to match the
3161 * 'exploding' frame.
3165 public void explodeViews(SplitFrame sf)
3167 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3168 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3169 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3171 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3173 int viewCount = topPanels.size();
3180 * Processing in reverse order works, forwards order leaves the first panels not
3181 * visible. I don't know why!
3183 for (int i = viewCount - 1; i >= 0; i--)
3186 * Make new top and bottom frames. These take over the respective AlignmentPanel
3187 * objects, including their AlignmentViewports, so the cdna/protein
3188 * relationships between the viewports is carried over to the new split frames.
3190 * explodedGeometry holds the (x, y) position of the previously exploded
3191 * SplitFrame, and the (width, height) of the AlignFrame component
3193 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3194 AlignFrame newTopFrame = new AlignFrame(topPanel);
3195 newTopFrame.setSize(oldTopFrame.getSize());
3196 newTopFrame.setVisible(true);
3197 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3198 .getExplodedGeometry();
3199 if (geometry != null)
3201 newTopFrame.setSize(geometry.getSize());
3204 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3205 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3206 newBottomFrame.setSize(oldBottomFrame.getSize());
3207 newBottomFrame.setVisible(true);
3208 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3209 .getExplodedGeometry();
3210 if (geometry != null)
3212 newBottomFrame.setSize(geometry.getSize());
3215 topPanel.av.setGatherViewsHere(false);
3216 bottomPanel.av.setGatherViewsHere(false);
3217 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3219 if (geometry != null)
3221 splitFrame.setLocation(geometry.getLocation());
3223 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3227 * Clear references to the panels (now relocated in the new SplitFrames) before
3228 * closing the old SplitFrame.
3231 bottomPanels.clear();
3236 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3237 * back into the given SplitFrame as additional views. Note that the gathered
3238 * frames may themselves have multiple views.
3242 public void gatherViews(GSplitFrame source)
3245 * special handling of explodedGeometry for a view within a SplitFrame: - it
3246 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3247 * height) of the AlignFrame component
3249 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3250 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3251 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3252 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3253 myBottomFrame.viewport
3254 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3255 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3256 myTopFrame.viewport.setGatherViewsHere(true);
3257 myBottomFrame.viewport.setGatherViewsHere(true);
3258 String topViewId = myTopFrame.viewport.getSequenceSetId();
3259 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3261 JInternalFrame[] frames = desktop.getAllFrames();
3262 for (JInternalFrame frame : frames)
3264 if (frame instanceof SplitFrame && frame != source)
3266 SplitFrame sf = (SplitFrame) frame;
3267 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3268 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3269 boolean gatherThis = false;
3270 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3272 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3273 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3274 if (topViewId.equals(topPanel.av.getSequenceSetId())
3275 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3278 topPanel.av.setGatherViewsHere(false);
3279 bottomPanel.av.setGatherViewsHere(false);
3280 topPanel.av.setExplodedGeometry(
3281 new Rectangle(sf.getLocation(), topFrame.getSize()));
3282 bottomPanel.av.setExplodedGeometry(
3283 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3284 myTopFrame.addAlignmentPanel(topPanel, false);
3285 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3291 topFrame.getAlignPanels().clear();
3292 bottomFrame.getAlignPanels().clear();
3299 * The dust settles...give focus to the tab we did this from.
3301 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3304 public static groovy.ui.Console getGroovyConsole()
3306 return groovyConsole;
3310 * handles the payload of a drag and drop event.
3312 * TODO refactor to desktop utilities class
3315 * - Data source strings extracted from the drop event
3317 * - protocol for each data source extracted from the drop event
3321 * - the payload from the drop event
3324 public static void transferFromDropTarget(List<Object> files,
3325 List<DataSourceType> protocols, DropTargetDropEvent evt,
3326 Transferable t) throws Exception
3329 DataFlavor uriListFlavor = new DataFlavor(
3330 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3333 urlFlavour = new DataFlavor(
3334 "application/x-java-url; class=java.net.URL");
3335 } catch (ClassNotFoundException cfe)
3337 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3341 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3346 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3347 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3348 // means url may be null.
3351 protocols.add(DataSourceType.URL);
3352 files.add(url.toString());
3353 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3354 + files.get(files.size() - 1));
3359 if (Platform.isAMacAndNotJS())
3362 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3365 } catch (Throwable ex)
3367 jalview.bin.Console.debug("URL drop handler failed.", ex);
3370 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3372 // Works on Windows and MacOSX
3373 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3374 for (Object file : (List) t
3375 .getTransferData(DataFlavor.javaFileListFlavor))
3378 protocols.add(DataSourceType.FILE);
3383 // Unix like behaviour
3384 boolean added = false;
3386 if (t.isDataFlavorSupported(uriListFlavor))
3388 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3389 // This is used by Unix drag system
3390 data = (String) t.getTransferData(uriListFlavor);
3394 // fallback to text: workaround - on OSX where there's a JVM bug
3396 .debug("standard URIListFlavor failed. Trying text");
3397 // try text fallback
3398 DataFlavor textDf = new DataFlavor(
3399 "text/plain;class=java.lang.String");
3400 if (t.isDataFlavorSupported(textDf))
3402 data = (String) t.getTransferData(textDf);
3405 jalview.bin.Console.debug("Plain text drop content returned "
3406 + (data == null ? "Null - failed" : data));
3411 while (protocols.size() < files.size())
3413 jalview.bin.Console.debug("Adding missing FILE protocol for "
3414 + files.get(protocols.size()));
3415 protocols.add(DataSourceType.FILE);
3417 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3418 data, "\r\n"); st.hasMoreTokens();)
3421 String s = st.nextToken();
3422 if (s.startsWith("#"))
3424 // the line is a comment (as per the RFC 2483)
3427 java.net.URI uri = new java.net.URI(s);
3428 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3430 protocols.add(DataSourceType.URL);
3431 files.add(uri.toString());
3435 // otherwise preserve old behaviour: catch all for file objects
3436 java.io.File file = new java.io.File(uri);
3437 protocols.add(DataSourceType.FILE);
3438 files.add(file.toString());
3443 if (jalview.bin.Console.isDebugEnabled())
3445 if (data == null || !added)
3448 if (t.getTransferDataFlavors() != null
3449 && t.getTransferDataFlavors().length > 0)
3451 jalview.bin.Console.debug(
3452 "Couldn't resolve drop data. Here are the supported flavors:");
3453 for (DataFlavor fl : t.getTransferDataFlavors())
3455 jalview.bin.Console.debug(
3456 "Supported transfer dataflavor: " + fl.toString());
3457 Object df = t.getTransferData(fl);
3460 jalview.bin.Console.debug("Retrieves: " + df);
3464 jalview.bin.Console.debug("Retrieved nothing");
3471 .debug("Couldn't resolve dataflavor for drop: "
3477 if (Platform.isWindowsAndNotJS())
3480 .debug("Scanning dropped content for Windows Link Files");
3482 // resolve any .lnk files in the file drop
3483 for (int f = 0; f < files.size(); f++)
3485 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3486 if (protocols.get(f).equals(DataSourceType.FILE)
3487 && (source.endsWith(".lnk") || source.endsWith(".url")
3488 || source.endsWith(".site")))
3492 Object obj = files.get(f);
3493 File lf = (obj instanceof File ? (File) obj
3494 : new File((String) obj));
3495 // process link file to get a URL
3496 jalview.bin.Console.debug("Found potential link file: " + lf);
3497 WindowsShortcut wscfile = new WindowsShortcut(lf);
3498 String fullname = wscfile.getRealFilename();
3499 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3500 files.set(f, fullname);
3501 jalview.bin.Console.debug("Parsed real filename " + fullname
3502 + " to extract protocol: " + protocols.get(f));
3503 } catch (Exception ex)
3505 jalview.bin.Console.error(
3506 "Couldn't parse " + files.get(f) + " as a link file.",
3515 * Sets the Preferences property for experimental features to True or False
3516 * depending on the state of the controlling menu item
3519 protected void showExperimental_actionPerformed(boolean selected)
3521 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3525 * Answers a (possibly empty) list of any structure viewer frames (currently
3526 * for either Jmol or Chimera) which are currently open. This may optionally
3527 * be restricted to viewers of a specified class, or viewers linked to a
3528 * specified alignment panel.
3531 * if not null, only return viewers linked to this panel
3532 * @param structureViewerClass
3533 * if not null, only return viewers of this class
3536 public List<StructureViewerBase> getStructureViewers(
3537 AlignmentPanel apanel,
3538 Class<? extends StructureViewerBase> structureViewerClass)
3540 List<StructureViewerBase> result = new ArrayList<>();
3541 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3543 for (JInternalFrame frame : frames)
3545 if (frame instanceof StructureViewerBase)
3547 if (structureViewerClass == null
3548 || structureViewerClass.isInstance(frame))
3551 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3553 result.add((StructureViewerBase) frame);
3561 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3563 private static boolean debugScaleMessageDone = false;
3565 public static void debugScaleMessage(Graphics g)
3567 if (debugScaleMessageDone)
3571 // output used by tests to check HiDPI scaling settings in action
3574 Graphics2D gg = (Graphics2D) g;
3577 AffineTransform t = gg.getTransform();
3578 double scaleX = t.getScaleX();
3579 double scaleY = t.getScaleY();
3580 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3581 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3582 debugScaleMessageDone = true;
3586 jalview.bin.Console.debug("Desktop graphics null");
3588 } catch (Exception e)
3590 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3595 * closes the current instance window, disposes and forgets about it.
3597 public static void closeDesktop()
3599 if (Desktop.instance != null)
3601 Desktop.instance.closeAll_actionPerformed(null);
3602 Desktop.instance.setVisible(false);
3603 Desktop.instance.dispose();
3604 Desktop.instance = null;