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;
54 import java.beans.PropertyVetoException;
56 import java.io.FileWriter;
57 import java.io.IOException;
58 import java.lang.reflect.Field;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashMap;
63 import java.util.Hashtable;
64 import java.util.List;
65 import java.util.ListIterator;
66 import java.util.Locale;
68 import java.util.Vector;
69 import java.util.concurrent.ExecutorService;
70 import java.util.concurrent.Executors;
71 import java.util.concurrent.Semaphore;
73 import javax.swing.AbstractAction;
74 import javax.swing.Action;
75 import javax.swing.ActionMap;
76 import javax.swing.Box;
77 import javax.swing.BoxLayout;
78 import javax.swing.DefaultDesktopManager;
79 import javax.swing.DesktopManager;
80 import javax.swing.InputMap;
81 import javax.swing.JButton;
82 import javax.swing.JCheckBox;
83 import javax.swing.JComboBox;
84 import javax.swing.JComponent;
85 import javax.swing.JDesktopPane;
86 import javax.swing.JFrame;
87 import javax.swing.JInternalFrame;
88 import javax.swing.JLabel;
89 import javax.swing.JMenuItem;
90 import javax.swing.JPanel;
91 import javax.swing.JPopupMenu;
92 import javax.swing.JProgressBar;
93 import javax.swing.JTextField;
94 import javax.swing.KeyStroke;
95 import javax.swing.SwingUtilities;
96 import javax.swing.WindowConstants;
97 import javax.swing.event.HyperlinkEvent;
98 import javax.swing.event.HyperlinkEvent.EventType;
99 import javax.swing.event.InternalFrameAdapter;
100 import javax.swing.event.InternalFrameEvent;
102 import org.stackoverflowusers.file.WindowsShortcut;
104 import jalview.api.AlignViewportI;
105 import jalview.api.AlignmentViewPanel;
106 import jalview.api.structures.JalviewStructureDisplayI;
107 import jalview.bin.Cache;
108 import jalview.bin.Jalview;
109 import jalview.datamodel.Alignment;
110 import jalview.datamodel.HiddenColumns;
111 import jalview.datamodel.Sequence;
112 import jalview.datamodel.SequenceI;
113 import jalview.gui.ImageExporter.ImageWriterI;
114 import jalview.gui.QuitHandler.QResponse;
115 import jalview.io.BackupFiles;
116 import jalview.io.DataSourceType;
117 import jalview.io.FileFormat;
118 import jalview.io.FileFormatException;
119 import jalview.io.FileFormatI;
120 import jalview.io.FileFormats;
121 import jalview.io.FileLoader;
122 import jalview.io.FormatAdapter;
123 import jalview.io.IdentifyFile;
124 import jalview.io.JalviewFileChooser;
125 import jalview.io.JalviewFileView;
126 import jalview.io.exceptions.ImageOutputException;
127 import jalview.jbgui.GSplitFrame;
128 import jalview.jbgui.GStructureViewer;
129 import jalview.project.Jalview2XML;
130 import jalview.structure.StructureSelectionManager;
131 import jalview.urls.IdOrgSettings;
132 import jalview.util.BrowserLauncher;
133 import jalview.util.ChannelProperties;
134 import jalview.util.ImageMaker.TYPE;
135 import jalview.util.LaunchUtils;
136 import jalview.util.MessageManager;
137 import jalview.util.Platform;
138 import jalview.util.ShortcutKeyMaskExWrapper;
139 import jalview.util.UrlConstants;
140 import jalview.viewmodel.AlignmentViewport;
141 import jalview.ws.params.ParamManager;
142 import jalview.ws.utils.UrlDownloadClient;
149 * @version $Revision: 1.155 $
151 public class Desktop extends jalview.jbgui.GDesktop
152 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
153 jalview.api.StructureSelectionManagerProvider
155 private static final String CITATION;
158 URL bg_logo_url = ChannelProperties.getImageURL(
159 "bg_logo." + String.valueOf(SplashScreen.logoSize));
160 URL uod_logo_url = ChannelProperties.getImageURL(
161 "uod_banner." + String.valueOf(SplashScreen.logoSize));
162 boolean logo = (bg_logo_url != null || uod_logo_url != null);
163 StringBuilder sb = new StringBuilder();
165 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
170 sb.append(bg_logo_url == null ? ""
171 : "<img alt=\"Barton Group logo\" src=\""
172 + bg_logo_url.toString() + "\">");
173 sb.append(uod_logo_url == null ? ""
174 : " <img alt=\"University of Dundee shield\" src=\""
175 + uod_logo_url.toString() + "\">");
177 "<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>");
178 sb.append("<br><br>If you use Jalview, please cite:"
179 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
180 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
181 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
182 CITATION = sb.toString();
185 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
187 private static int DEFAULT_MIN_WIDTH = 300;
189 private static int DEFAULT_MIN_HEIGHT = 250;
191 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
193 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
195 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
197 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
199 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
201 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
203 public static void setLiveDragMode(boolean b)
205 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
206 : JDesktopPane.OUTLINE_DRAG_MODE;
208 desktop.setDragMode(DRAG_MODE);
211 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
213 public static boolean nosplash = false;
216 * news reader - null if it was never started.
218 private BlogReader jvnews = null;
220 private File projectFile;
224 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
226 public void addJalviewPropertyChangeListener(
227 PropertyChangeListener listener)
229 changeSupport.addJalviewPropertyChangeListener(listener);
233 * @param propertyName
235 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
236 * java.beans.PropertyChangeListener)
238 public void addJalviewPropertyChangeListener(String propertyName,
239 PropertyChangeListener listener)
241 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
245 * @param propertyName
247 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
248 * java.beans.PropertyChangeListener)
250 public void removeJalviewPropertyChangeListener(String propertyName,
251 PropertyChangeListener listener)
253 changeSupport.removeJalviewPropertyChangeListener(propertyName,
257 /** Singleton Desktop instance */
258 public static Desktop instance;
260 public static MyDesktopPane desktop;
262 public static MyDesktopPane getDesktop()
264 // BH 2018 could use currentThread() here as a reference to a
265 // Hashtable<Thread, MyDesktopPane> in JavaScript
269 static int openFrameCount = 0;
271 static final int xOffset = 30;
273 static final int yOffset = 30;
275 public static jalview.ws.jws1.Discoverer discoverer;
277 public static Object[] jalviewClipboard;
279 public static boolean internalCopy = false;
281 static int fileLoadingCount = 0;
283 class MyDesktopManager implements DesktopManager
286 private DesktopManager delegate;
288 public MyDesktopManager(DesktopManager delegate)
290 this.delegate = delegate;
294 public void activateFrame(JInternalFrame f)
298 delegate.activateFrame(f);
299 } catch (NullPointerException npe)
301 Point p = getMousePosition();
302 instance.showPasteMenu(p.x, p.y);
307 public void beginDraggingFrame(JComponent f)
309 delegate.beginDraggingFrame(f);
313 public void beginResizingFrame(JComponent f, int direction)
315 delegate.beginResizingFrame(f, direction);
319 public void closeFrame(JInternalFrame f)
321 delegate.closeFrame(f);
325 public void deactivateFrame(JInternalFrame f)
327 delegate.deactivateFrame(f);
331 public void deiconifyFrame(JInternalFrame f)
333 delegate.deiconifyFrame(f);
337 public void dragFrame(JComponent f, int newX, int newY)
343 delegate.dragFrame(f, newX, newY);
347 public void endDraggingFrame(JComponent f)
349 delegate.endDraggingFrame(f);
354 public void endResizingFrame(JComponent f)
356 delegate.endResizingFrame(f);
361 public void iconifyFrame(JInternalFrame f)
363 delegate.iconifyFrame(f);
367 public void maximizeFrame(JInternalFrame f)
369 delegate.maximizeFrame(f);
373 public void minimizeFrame(JInternalFrame f)
375 delegate.minimizeFrame(f);
379 public void openFrame(JInternalFrame f)
381 delegate.openFrame(f);
385 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
392 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
396 public void setBoundsForFrame(JComponent f, int newX, int newY,
397 int newWidth, int newHeight)
399 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
402 // All other methods, simply delegate
407 * Creates a new Desktop object.
413 * A note to implementors. It is ESSENTIAL that any activities that might
414 * block are spawned off as threads rather than waited for during this
419 doConfigureStructurePrefs();
420 setTitle(ChannelProperties.getProperty("app_name") + " "
421 + Cache.getProperty("VERSION"));
424 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
425 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
426 * officially documented or guaranteed to exist, so we access it via
427 * reflection. There appear to be unfathomable criteria about what this
428 * string can contain, and it if doesn't meet those criteria then "java"
429 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
430 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
431 * not. The reflection access may generate a warning: WARNING: An illegal
432 * reflective access operation has occurred WARNING: Illegal reflective
433 * access by jalview.gui.Desktop () to field
434 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
436 if (Platform.isLinux())
438 if (LaunchUtils.getJavaVersion() >= 11)
441 * Send this message to stderr as the warning that follows (due to
442 * reflection) also goes to stderr.
444 jalview.bin.Console.errPrintln(
445 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
447 final String awtAppClassName = "awtAppClassName";
450 Toolkit xToolkit = Toolkit.getDefaultToolkit();
451 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
452 Field awtAppClassNameField = null;
454 if (Arrays.stream(declaredFields)
455 .anyMatch(f -> f.getName().equals(awtAppClassName)))
457 awtAppClassNameField = xToolkit.getClass()
458 .getDeclaredField(awtAppClassName);
461 String title = ChannelProperties.getProperty("app_name");
462 if (awtAppClassNameField != null)
464 awtAppClassNameField.setAccessible(true);
465 awtAppClassNameField.set(xToolkit, title);
470 .debug("XToolkit: " + awtAppClassName + " not found");
472 } catch (Exception e)
474 jalview.bin.Console.debug("Error setting " + awtAppClassName);
475 jalview.bin.Console.trace(Cache.getStackTraceString(e));
479 setIconImages(ChannelProperties.getIconList());
481 // override quit handling when GUI OS close [X] button pressed
482 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
483 addWindowListener(new WindowAdapter()
486 public void windowClosing(WindowEvent ev)
488 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
492 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
494 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
495 desktop = new MyDesktopPane(selmemusage);
497 showMemusage.setSelected(selmemusage);
498 desktop.setBackground(Color.white);
500 getContentPane().setLayout(new BorderLayout());
501 // alternate config - have scrollbars - see notes in JAL-153
502 // JScrollPane sp = new JScrollPane();
503 // sp.getViewport().setView(desktop);
504 // getContentPane().add(sp, BorderLayout.CENTER);
506 // BH 2018 - just an experiment to try unclipped JInternalFrames.
509 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
512 getContentPane().add(desktop, BorderLayout.CENTER);
513 desktop.setDragMode(DRAG_MODE);
515 // This line prevents Windows Look&Feel resizing all new windows to maximum
516 // if previous window was maximised
517 desktop.setDesktopManager(new MyDesktopManager(
518 Platform.isJS() ? desktop.getDesktopManager()
519 : new DefaultDesktopManager()));
521 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
522 : Platform.isAMacAndNotJS()
523 ? new AquaInternalFrameManager(
524 desktop.getDesktopManager())
525 : desktop.getDesktopManager())));
528 Rectangle dims = getLastKnownDimensions("");
535 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
536 int xPos = Math.max(5, (screenSize.width - 900) / 2);
537 int yPos = Math.max(5, (screenSize.height - 650) / 2);
538 setBounds(xPos, yPos, 900, 650);
541 // start dialogue queue for single dialogues
544 if (!Platform.isJS())
551 jconsole = new Console(this, showjconsole);
552 jconsole.setHeader(Cache.getVersionDetailsForConsole());
553 showConsole(showjconsole);
555 showNews.setVisible(false);
557 experimentalFeatures.setSelected(showExperimental());
559 getIdentifiersOrgData();
563 // Spawn a thread that shows the splashscreen
566 SwingUtilities.invokeLater(new Runnable()
571 new SplashScreen(true);
576 // Thread off a new instance of the file chooser - this reduces the time
577 // it takes to open it later on.
578 new Thread(new Runnable()
583 jalview.bin.Console.debug("Filechooser init thread started.");
584 String fileFormat = FileLoader.getUseDefaultFileFormat()
585 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
587 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
589 jalview.bin.Console.debug("Filechooser init thread finished.");
592 // Add the service change listener
593 changeSupport.addJalviewPropertyChangeListener("services",
594 new PropertyChangeListener()
598 public void propertyChange(PropertyChangeEvent evt)
601 .debug("Firing service changed event for "
602 + evt.getNewValue());
603 JalviewServicesChanged(evt);
608 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
611 this.addMouseListener(ma = new MouseAdapter()
614 public void mousePressed(MouseEvent evt)
616 if (evt.isPopupTrigger()) // Mac
618 showPasteMenu(evt.getX(), evt.getY());
623 public void mouseReleased(MouseEvent evt)
625 if (evt.isPopupTrigger()) // Windows
627 showPasteMenu(evt.getX(), evt.getY());
631 desktop.addMouseListener(ma);
635 // used for jalviewjsTest
636 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
641 * Answers true if user preferences to enable experimental features is True
646 public boolean showExperimental()
648 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
649 Boolean.FALSE.toString());
650 return Boolean.valueOf(experimental).booleanValue();
653 public void doConfigureStructurePrefs()
655 // configure services
656 StructureSelectionManager ssm = StructureSelectionManager
657 .getStructureSelectionManager(this);
658 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
660 ssm.setAddTempFacAnnot(
661 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
662 ssm.setProcessSecondaryStructure(
663 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
664 // JAL-3915 - RNAView is no longer an option so this has no effect
665 ssm.setSecStructServices(
666 Cache.getDefault(Preferences.USE_RNAVIEW, false));
670 ssm.setAddTempFacAnnot(false);
671 ssm.setProcessSecondaryStructure(false);
672 ssm.setSecStructServices(false);
676 public void checkForNews()
678 final Desktop me = this;
679 // Thread off the news reader, in case there are connection problems.
680 new Thread(new Runnable()
685 jalview.bin.Console.debug("Starting news thread.");
686 jvnews = new BlogReader(me);
687 showNews.setVisible(true);
688 jalview.bin.Console.debug("Completed news thread.");
693 public void getIdentifiersOrgData()
695 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
696 {// Thread off the identifiers fetcher
697 new Thread(new Runnable()
703 .debug("Downloading data from identifiers.org");
706 UrlDownloadClient.download(IdOrgSettings.getUrl(),
707 IdOrgSettings.getDownloadLocation());
708 } catch (IOException e)
711 .debug("Exception downloading identifiers.org data"
721 protected void showNews_actionPerformed(ActionEvent e)
723 showNews(showNews.isSelected());
726 void showNews(boolean visible)
728 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
729 showNews.setSelected(visible);
730 if (visible && !jvnews.isVisible())
732 new Thread(new Runnable()
737 long now = System.currentTimeMillis();
738 Desktop.instance.setProgressBar(
739 MessageManager.getString("status.refreshing_news"), now);
740 jvnews.refreshNews();
741 Desktop.instance.setProgressBar(null, now);
749 * recover the last known dimensions for a jalview window
752 * - empty string is desktop, all other windows have unique prefix
753 * @return null or last known dimensions scaled to current geometry (if last
754 * window geom was known)
756 Rectangle getLastKnownDimensions(String windowName)
758 // TODO: lock aspect ratio for scaling desktop Bug #0058199
759 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
760 String x = Cache.getProperty(windowName + "SCREEN_X");
761 String y = Cache.getProperty(windowName + "SCREEN_Y");
762 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
763 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
764 if ((x != null) && (y != null) && (width != null) && (height != null))
766 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
767 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
768 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
770 // attempt #1 - try to cope with change in screen geometry - this
771 // version doesn't preserve original jv aspect ratio.
772 // take ratio of current screen size vs original screen size.
773 double sw = ((1f * screenSize.width) / (1f * Integer
774 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
775 double sh = ((1f * screenSize.height) / (1f * Integer
776 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
777 // rescale the bounds depending upon the current screen geometry.
778 ix = (int) (ix * sw);
779 iw = (int) (iw * sw);
780 iy = (int) (iy * sh);
781 ih = (int) (ih * sh);
782 while (ix >= screenSize.width)
784 jalview.bin.Console.debug(
785 "Window geometry location recall error: shifting horizontal to within screenbounds.");
786 ix -= screenSize.width;
788 while (iy >= screenSize.height)
790 jalview.bin.Console.debug(
791 "Window geometry location recall error: shifting vertical to within screenbounds.");
792 iy -= screenSize.height;
794 jalview.bin.Console.debug(
795 "Got last known dimensions for " + windowName + ": x:" + ix
796 + " y:" + iy + " width:" + iw + " height:" + ih);
798 // return dimensions for new instance
799 return new Rectangle(ix, iy, iw, ih);
804 void showPasteMenu(int x, int y)
806 JPopupMenu popup = new JPopupMenu();
807 JMenuItem item = new JMenuItem(
808 MessageManager.getString("label.paste_new_window"));
809 item.addActionListener(new ActionListener()
812 public void actionPerformed(ActionEvent evt)
819 popup.show(this, x, y);
824 // quick patch for JAL-4150 - needs some more work and test coverage
825 // TODO - unify below and AlignFrame.paste()
826 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
827 // clipboard has come from a different alignment window than the one where
828 // paste has been called! JAL-4151
830 if (Desktop.jalviewClipboard != null)
832 // The clipboard was filled from within Jalview, we must use the
834 // And dataset from the copied alignment
835 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
836 // be doubly sure that we create *new* sequence objects.
837 SequenceI[] sequences = new SequenceI[newseq.length];
838 for (int i = 0; i < newseq.length; i++)
840 sequences[i] = new Sequence(newseq[i]);
842 Alignment alignment = new Alignment(sequences);
843 // dataset is inherited
844 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
845 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
846 AlignFrame.DEFAULT_HEIGHT);
847 String newtitle = new String("Copied sequences");
849 if (Desktop.jalviewClipboard[2] != null)
851 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
852 af.viewport.setHiddenColumns(hc);
855 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
856 AlignFrame.DEFAULT_HEIGHT);
863 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
864 Transferable contents = c.getContents(this);
866 if (contents != null)
868 String file = (String) contents
869 .getTransferData(DataFlavor.stringFlavor);
871 FileFormatI format = new IdentifyFile().identify(file,
872 DataSourceType.PASTE);
874 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
877 } catch (Exception ex)
879 jalview.bin.Console.outPrintln(
880 "Unable to paste alignment from system clipboard:\n" + ex);
886 * Adds and opens the given frame to the desktop
897 public static synchronized void addInternalFrame(
898 final JInternalFrame frame, String title, int w, int h)
900 addInternalFrame(frame, title, true, w, h, true, false);
904 * Add an internal frame to the Jalview desktop
911 * When true, display frame immediately, otherwise, caller must call
912 * setVisible themselves.
918 public static synchronized void addInternalFrame(
919 final JInternalFrame frame, String title, boolean makeVisible,
922 addInternalFrame(frame, title, makeVisible, w, h, true, false);
926 * Add an internal frame to the Jalview desktop and make it visible
939 public static synchronized void addInternalFrame(
940 final JInternalFrame frame, String title, int w, int h,
943 addInternalFrame(frame, title, true, w, h, resizable, false);
947 * Add an internal frame to the Jalview desktop
954 * When true, display frame immediately, otherwise, caller must call
955 * setVisible themselves.
962 * @param ignoreMinSize
963 * Do not set the default minimum size for frame
965 public static synchronized void addInternalFrame(
966 final JInternalFrame frame, String title, boolean makeVisible,
967 int w, int h, boolean resizable, boolean ignoreMinSize)
970 // TODO: allow callers to determine X and Y position of frame (eg. via
972 // TODO: consider fixing method to update entries in the window submenu with
973 // the current window title
975 frame.setTitle(title);
976 if (frame.getWidth() < 1 || frame.getHeight() < 1)
980 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
981 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
982 // IF JALVIEW IS RUNNING HEADLESS
983 // ///////////////////////////////////////////////
984 if (instance == null || (System.getProperty("java.awt.headless") != null
985 && System.getProperty("java.awt.headless").equals("true")))
994 frame.setMinimumSize(
995 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
997 // Set default dimension for Alignment Frame window.
998 // The Alignment Frame window could be added from a number of places,
1000 // I did this here in order not to miss out on any Alignment frame.
1001 if (frame instanceof AlignFrame)
1003 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1004 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1008 frame.setVisible(makeVisible);
1009 frame.setClosable(true);
1010 frame.setResizable(resizable);
1011 frame.setMaximizable(resizable);
1012 frame.setIconifiable(resizable);
1013 frame.setOpaque(Platform.isJS());
1015 if (frame.getX() < 1 && frame.getY() < 1)
1017 frame.setLocation(xOffset * openFrameCount,
1018 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1022 * add an entry for the new frame in the Window menu (and remove it when the
1025 final JMenuItem menuItem = new JMenuItem(title);
1026 frame.addInternalFrameListener(new InternalFrameAdapter()
1029 public void internalFrameActivated(InternalFrameEvent evt)
1031 JInternalFrame itf = desktop.getSelectedFrame();
1034 if (itf instanceof AlignFrame)
1036 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1043 public void internalFrameClosed(InternalFrameEvent evt)
1045 PaintRefresher.RemoveComponent(frame);
1048 * defensive check to prevent frames being added half off the window
1050 if (openFrameCount > 0)
1056 * ensure no reference to alignFrame retained by menu item listener
1058 if (menuItem.getActionListeners().length > 0)
1060 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1062 windowMenu.remove(menuItem);
1066 menuItem.addActionListener(new ActionListener()
1069 public void actionPerformed(ActionEvent e)
1073 frame.setSelected(true);
1074 frame.setIcon(false);
1075 } catch (java.beans.PropertyVetoException ex)
1082 setKeyBindings(frame);
1084 // Since the latest FlatLaf patch, we occasionally have problems showing structureViewer frames...
1086 boolean shown=false;
1087 Exception last=null;
1094 } catch (IllegalArgumentException iaex)
1098 jalview.bin.Console.info(
1099 "Squashed IllegalArgument Exception (" + tries + " left) for "+frame.getTitle(),
1104 } catch (InterruptedException iex)
1109 } while (!shown && tries > 0);
1112 jalview.bin.Console.error("Serious Problem whilst showing window "+frame.getTitle(),last);
1115 windowMenu.add(menuItem);
1120 frame.setSelected(true);
1121 frame.requestFocus();
1122 } catch (java.beans.PropertyVetoException ve)
1124 } catch (java.lang.ClassCastException cex)
1126 jalview.bin.Console.warn(
1127 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1133 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1138 private static void setKeyBindings(JInternalFrame frame)
1140 @SuppressWarnings("serial")
1141 final Action closeAction = new AbstractAction()
1144 public void actionPerformed(ActionEvent e)
1151 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1153 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1154 InputEvent.CTRL_DOWN_MASK);
1155 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1156 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1158 InputMap inputMap = frame
1159 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1160 String ctrlW = ctrlWKey.toString();
1161 inputMap.put(ctrlWKey, ctrlW);
1162 inputMap.put(cmdWKey, ctrlW);
1164 ActionMap actionMap = frame.getActionMap();
1165 actionMap.put(ctrlW, closeAction);
1169 public void lostOwnership(Clipboard clipboard, Transferable contents)
1173 Desktop.jalviewClipboard = null;
1176 internalCopy = false;
1180 public void dragEnter(DropTargetDragEvent evt)
1185 public void dragExit(DropTargetEvent evt)
1190 public void dragOver(DropTargetDragEvent evt)
1195 public void dropActionChanged(DropTargetDragEvent evt)
1206 public void drop(DropTargetDropEvent evt)
1208 boolean success = true;
1209 // JAL-1552 - acceptDrop required before getTransferable call for
1210 // Java's Transferable for native dnd
1211 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1212 Transferable t = evt.getTransferable();
1213 List<Object> files = new ArrayList<>();
1214 List<DataSourceType> protocols = new ArrayList<>();
1218 Desktop.transferFromDropTarget(files, protocols, evt, t);
1219 } catch (Exception e)
1221 e.printStackTrace();
1229 for (int i = 0; i < files.size(); i++)
1231 // BH 2018 File or String
1232 Object file = files.get(i);
1233 String fileName = file.toString();
1234 DataSourceType protocol = (protocols == null)
1235 ? DataSourceType.FILE
1237 FileFormatI format = null;
1239 if (fileName.endsWith(".jar"))
1241 format = FileFormat.Jalview;
1246 format = new IdentifyFile().identify(file, protocol);
1248 if (file instanceof File)
1250 Platform.cacheFileData((File) file);
1252 new FileLoader().LoadFile(null, file, protocol, format);
1255 } catch (Exception ex)
1260 evt.dropComplete(success); // need this to ensure input focus is properly
1261 // transfered to any new windows created
1271 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1273 String fileFormat = FileLoader.getUseDefaultFileFormat()
1274 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1276 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1277 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1278 BackupFiles.getEnabled());
1280 chooser.setFileView(new JalviewFileView());
1281 chooser.setDialogTitle(
1282 MessageManager.getString("label.open_local_file"));
1283 chooser.setToolTipText(MessageManager.getString("action.open"));
1285 chooser.setResponseHandler(0, () -> {
1286 File selectedFile = chooser.getSelectedFile();
1287 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1289 FileFormatI format = chooser.getSelectedFormat();
1292 * Call IdentifyFile to verify the file contains what its extension implies.
1293 * Skip this step for dynamically added file formats, because IdentifyFile does
1294 * not know how to recognise them.
1296 if (FileFormats.getInstance().isIdentifiable(format))
1300 format = new IdentifyFile().identify(selectedFile,
1301 DataSourceType.FILE);
1302 } catch (FileFormatException e)
1304 // format = null; //??
1308 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1311 chooser.showOpenDialog(this);
1315 * Shows a dialog for input of a URL at which to retrieve alignment data
1320 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1322 // This construct allows us to have a wider textfield
1324 JLabel label = new JLabel(
1325 MessageManager.getString("label.input_file_url"));
1327 JPanel panel = new JPanel(new GridLayout(2, 1));
1331 * the URL to fetch is input in Java: an editable combobox with history JS:
1332 * (pending JAL-3038) a plain text field
1335 String urlBase = "https://www.";
1336 if (Platform.isJS())
1338 history = new JTextField(urlBase, 35);
1347 JComboBox<String> asCombo = new JComboBox<>();
1348 asCombo.setPreferredSize(new Dimension(400, 20));
1349 asCombo.setEditable(true);
1350 asCombo.addItem(urlBase);
1351 String historyItems = Cache.getProperty("RECENT_URL");
1352 if (historyItems != null)
1354 for (String token : historyItems.split("\\t"))
1356 asCombo.addItem(token);
1363 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1364 MessageManager.getString("action.cancel") };
1365 Runnable action = () -> {
1366 @SuppressWarnings("unchecked")
1367 String url = (history instanceof JTextField
1368 ? ((JTextField) history).getText()
1369 : ((JComboBox<String>) history).getEditor().getItem()
1370 .toString().trim());
1372 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1374 if (viewport != null)
1376 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1377 FileFormat.Jalview);
1381 new FileLoader().LoadFile(url, DataSourceType.URL,
1382 FileFormat.Jalview);
1387 FileFormatI format = null;
1390 format = new IdentifyFile().identify(url, DataSourceType.URL);
1391 } catch (FileFormatException e)
1393 // TODO revise error handling, distinguish between
1394 // URL not found and response not valid
1399 String msg = MessageManager.formatMessage("label.couldnt_locate",
1401 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1402 MessageManager.getString("label.url_not_found"),
1403 JvOptionPane.WARNING_MESSAGE);
1407 if (viewport != null)
1409 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1414 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1418 String dialogOption = MessageManager
1419 .getString("label.input_alignment_from_url");
1420 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1421 .showInternalDialog(panel, dialogOption,
1422 JvOptionPane.YES_NO_CANCEL_OPTION,
1423 JvOptionPane.PLAIN_MESSAGE, null, options,
1424 MessageManager.getString("action.ok"));
1428 * Opens the CutAndPaste window for the user to paste an alignment in to
1431 * - if not null, the pasted alignment is added to the current
1432 * alignment; if null, to a new alignment window
1435 public void inputTextboxMenuItem_actionPerformed(
1436 AlignmentViewPanel viewPanel)
1438 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1439 cap.setForInput(viewPanel);
1440 Desktop.addInternalFrame(cap,
1441 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1446 * Check with user and saving files before actually quitting
1448 public void desktopQuit()
1450 desktopQuit(true, false);
1453 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1455 final Runnable doDesktopQuit = () -> {
1456 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1457 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1458 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1459 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1460 getBounds().y, getWidth(), getHeight()));
1462 if (jconsole != null)
1464 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1465 jconsole.stopConsole();
1470 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1473 // Frames should all close automatically. Keeping external
1474 // viewers open should already be decided by user.
1475 closeAll_actionPerformed(null);
1477 // check for aborted quit
1478 if (QuitHandler.quitCancelled())
1480 jalview.bin.Console.debug("Desktop aborting quit");
1484 if (dialogExecutor != null)
1486 dialogExecutor.shutdownNow();
1489 if (groovyConsole != null)
1491 // suppress a possible repeat prompt to save script
1492 groovyConsole.setDirty(false);
1493 groovyConsole.exit();
1496 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1498 // note that shutdown hook will not be run
1499 jalview.bin.Console.debug("Force Quit selected by user");
1500 Runtime.getRuntime().halt(0);
1503 jalview.bin.Console.debug("Quit selected by user");
1506 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1507 // instance.dispose();
1512 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1513 QuitHandler.defaultCancelQuit);
1517 * Don't call this directly, use desktopQuit() above. Exits the program.
1522 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1523 // not run a second time if gotQuitResponse flag has been set (i.e. user
1524 // confirmed quit of some kind).
1525 Jalview.exit("Desktop exiting.", 0);
1528 private void storeLastKnownDimensions(String string, Rectangle jc)
1530 jalview.bin.Console.debug("Storing last known dimensions for " + string
1531 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1532 + " height:" + jc.height);
1534 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1535 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1536 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1537 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1547 public void aboutMenuItem_actionPerformed(ActionEvent e)
1549 new Thread(new Runnable()
1554 new SplashScreen(false);
1560 * Returns the html text for the About screen, including any available version
1561 * number, build details, author details and citation reference, but without
1562 * the enclosing {@code html} tags
1566 public String getAboutMessage()
1568 StringBuilder message = new StringBuilder(1024);
1569 message.append("<div style=\"font-family: sans-serif;\">")
1570 .append("<h1><strong>Version: ")
1571 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1572 .append("<strong>Built: <em>")
1573 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1574 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1575 .append("</strong>");
1577 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1578 if (latestVersion.equals("Checking"))
1580 // JBP removed this message for 2.11: May be reinstated in future version
1581 // message.append("<br>...Checking latest version...</br>");
1583 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1585 boolean red = false;
1586 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1587 .indexOf("automated build") == -1)
1590 // Displayed when code version and jnlp version do not match and code
1591 // version is not a development build
1592 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1595 message.append("<br>!! Version ")
1596 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1597 .append(" is available for download from ")
1598 .append(Cache.getDefault("www.jalview.org",
1599 "https://www.jalview.org"))
1603 message.append("</div>");
1606 message.append("<br>Authors: ");
1607 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1608 message.append(CITATION);
1610 message.append("</div>");
1612 return message.toString();
1616 * Action on requesting Help documentation
1619 public void documentationMenuItem_actionPerformed()
1623 if (Platform.isJS())
1625 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1634 Help.showHelpWindow();
1636 } catch (Exception ex)
1638 jalview.bin.Console.errPrintln("Error opening help: " + ex.getMessage());
1643 public void closeAll_actionPerformed(ActionEvent e)
1645 // TODO show a progress bar while closing?
1646 JInternalFrame[] frames = desktop.getAllFrames();
1647 for (int i = 0; i < frames.length; i++)
1651 frames[i].setClosed(true);
1652 } catch (java.beans.PropertyVetoException ex)
1656 Jalview.setCurrentAlignFrame(null);
1657 jalview.bin.Console.info("ALL CLOSED");
1660 * reset state of singleton objects as appropriate (clear down session state
1661 * when all windows are closed)
1663 StructureSelectionManager ssm = StructureSelectionManager
1664 .getStructureSelectionManager(this);
1671 public int structureViewersStillRunningCount()
1674 JInternalFrame[] frames = desktop.getAllFrames();
1675 for (int i = 0; i < frames.length; i++)
1677 if (frames[i] != null
1678 && frames[i] instanceof JalviewStructureDisplayI)
1680 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1688 public void raiseRelated_actionPerformed(ActionEvent e)
1690 reorderAssociatedWindows(false, false);
1694 public void minimizeAssociated_actionPerformed(ActionEvent e)
1696 reorderAssociatedWindows(true, false);
1699 void closeAssociatedWindows()
1701 reorderAssociatedWindows(false, true);
1707 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1711 protected void garbageCollect_actionPerformed(ActionEvent e)
1713 // We simply collect the garbage
1714 jalview.bin.Console.debug("Collecting garbage...");
1716 jalview.bin.Console.debug("Finished garbage collection.");
1722 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1726 protected void showMemusage_actionPerformed(ActionEvent e)
1728 desktop.showMemoryUsage(showMemusage.isSelected());
1735 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1739 protected void showConsole_actionPerformed(ActionEvent e)
1741 showConsole(showConsole.isSelected());
1744 Console jconsole = null;
1747 * control whether the java console is visible or not
1751 void showConsole(boolean selected)
1753 // TODO: decide if we should update properties file
1754 if (jconsole != null) // BH 2018
1756 showConsole.setSelected(selected);
1757 Cache.setProperty("SHOW_JAVA_CONSOLE",
1758 Boolean.valueOf(selected).toString());
1759 jconsole.setVisible(selected);
1763 void reorderAssociatedWindows(boolean minimize, boolean close)
1765 JInternalFrame[] frames = desktop.getAllFrames();
1766 if (frames == null || frames.length < 1)
1771 AlignmentViewport source = null, target = null;
1772 if (frames[0] instanceof AlignFrame)
1774 source = ((AlignFrame) frames[0]).getCurrentView();
1776 else if (frames[0] instanceof TreePanel)
1778 source = ((TreePanel) frames[0]).getViewPort();
1780 else if (frames[0] instanceof PCAPanel)
1782 source = ((PCAPanel) frames[0]).av;
1784 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1786 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1791 for (int i = 0; i < frames.length; i++)
1794 if (frames[i] == null)
1798 if (frames[i] instanceof AlignFrame)
1800 target = ((AlignFrame) frames[i]).getCurrentView();
1802 else if (frames[i] instanceof TreePanel)
1804 target = ((TreePanel) frames[i]).getViewPort();
1806 else if (frames[i] instanceof PCAPanel)
1808 target = ((PCAPanel) frames[i]).av;
1810 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1812 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1815 if (source == target)
1821 frames[i].setClosed(true);
1825 frames[i].setIcon(minimize);
1828 frames[i].toFront();
1832 } catch (java.beans.PropertyVetoException ex)
1847 protected void preferences_actionPerformed(ActionEvent e)
1849 Preferences.openPreferences();
1853 * Prompts the user to choose a file and then saves the Jalview state as a
1854 * Jalview project file
1857 public void saveState_actionPerformed()
1859 saveState_actionPerformed(false);
1862 public void saveState_actionPerformed(boolean saveAs)
1864 java.io.File projectFile = getProjectFile();
1865 // autoSave indicates we already have a file and don't need to ask
1866 boolean autoSave = projectFile != null && !saveAs
1867 && BackupFiles.getEnabled();
1869 // jalview.bin.Console.outPrintln("autoSave="+autoSave+", projectFile='"+projectFile+"',
1870 // saveAs="+saveAs+", Backups
1871 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1873 boolean approveSave = false;
1876 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1879 chooser.setFileView(new JalviewFileView());
1880 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1882 int value = chooser.showSaveDialog(this);
1884 if (value == JalviewFileChooser.APPROVE_OPTION)
1886 projectFile = chooser.getSelectedFile();
1887 setProjectFile(projectFile);
1892 if (approveSave || autoSave)
1894 final Desktop me = this;
1895 final java.io.File chosenFile = projectFile;
1896 new Thread(new Runnable()
1901 // TODO: refactor to Jalview desktop session controller action.
1902 setProgressBar(MessageManager.formatMessage(
1903 "label.saving_jalview_project", new Object[]
1904 { chosenFile.getName() }), chosenFile.hashCode());
1905 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1906 // TODO catch and handle errors for savestate
1907 // TODO prevent user from messing with the Desktop whilst we're saving
1910 boolean doBackup = BackupFiles.getEnabled();
1911 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1914 new Jalview2XML().saveState(
1915 doBackup ? backupfiles.getTempFile() : chosenFile);
1919 backupfiles.setWriteSuccess(true);
1920 backupfiles.rollBackupsAndRenameTempFile();
1922 } catch (OutOfMemoryError oom)
1924 new OOMWarning("Whilst saving current state to "
1925 + chosenFile.getName(), oom);
1926 } catch (Exception ex)
1928 jalview.bin.Console.error("Problems whilst trying to save to "
1929 + chosenFile.getName(), ex);
1930 JvOptionPane.showMessageDialog(me,
1931 MessageManager.formatMessage(
1932 "label.error_whilst_saving_current_state_to",
1934 { chosenFile.getName() }),
1935 MessageManager.getString("label.couldnt_save_project"),
1936 JvOptionPane.WARNING_MESSAGE);
1938 setProgressBar(null, chosenFile.hashCode());
1945 public void saveAsState_actionPerformed(ActionEvent e)
1947 saveState_actionPerformed(true);
1950 protected void setProjectFile(File choice)
1952 this.projectFile = choice;
1955 public File getProjectFile()
1957 return this.projectFile;
1961 * Shows a file chooser dialog and tries to read in the selected file as a
1965 public void loadState_actionPerformed()
1967 final String[] suffix = new String[] { "jvp", "jar" };
1968 final String[] desc = new String[] { "Jalview Project",
1969 "Jalview Project (old)" };
1970 JalviewFileChooser chooser = new JalviewFileChooser(
1971 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1972 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1976 chooser.setFileView(new JalviewFileView());
1977 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1978 chooser.setResponseHandler(0, () -> {
1979 File selectedFile = chooser.getSelectedFile();
1980 setProjectFile(selectedFile);
1981 String choice = selectedFile.getAbsolutePath();
1982 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1983 new Thread(new Runnable()
1990 new Jalview2XML().loadJalviewAlign(selectedFile);
1991 } catch (OutOfMemoryError oom)
1993 new OOMWarning("Whilst loading project from " + choice, oom);
1994 } catch (Exception ex)
1996 jalview.bin.Console.error(
1997 "Problems whilst loading project from " + choice, ex);
1998 JvOptionPane.showMessageDialog(Desktop.desktop,
1999 MessageManager.formatMessage(
2000 "label.error_whilst_loading_project_from",
2003 MessageManager.getString("label.couldnt_load_project"),
2004 JvOptionPane.WARNING_MESSAGE);
2007 }, "Project Loader").start();
2010 chooser.showOpenDialog(this);
2014 public void inputSequence_actionPerformed(ActionEvent e)
2016 new SequenceFetcher(this);
2019 JPanel progressPanel;
2021 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2023 public void startLoading(final Object fileName)
2025 if (fileLoadingCount == 0)
2027 fileLoadingPanels.add(addProgressPanel(MessageManager
2028 .formatMessage("label.loading_file", new Object[]
2034 private JPanel addProgressPanel(String string)
2036 if (progressPanel == null)
2038 progressPanel = new JPanel(new GridLayout(1, 1));
2039 totalProgressCount = 0;
2040 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2042 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2043 JProgressBar progressBar = new JProgressBar();
2044 progressBar.setIndeterminate(true);
2046 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2048 thisprogress.add(progressBar, BorderLayout.CENTER);
2049 progressPanel.add(thisprogress);
2050 ((GridLayout) progressPanel.getLayout()).setRows(
2051 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2052 ++totalProgressCount;
2053 instance.validate();
2054 return thisprogress;
2057 int totalProgressCount = 0;
2059 private void removeProgressPanel(JPanel progbar)
2061 if (progressPanel != null)
2063 synchronized (progressPanel)
2065 progressPanel.remove(progbar);
2066 GridLayout gl = (GridLayout) progressPanel.getLayout();
2067 gl.setRows(gl.getRows() - 1);
2068 if (--totalProgressCount < 1)
2070 this.getContentPane().remove(progressPanel);
2071 progressPanel = null;
2078 public void stopLoading()
2081 if (fileLoadingCount < 1)
2083 while (fileLoadingPanels.size() > 0)
2085 removeProgressPanel(fileLoadingPanels.remove(0));
2087 fileLoadingPanels.clear();
2088 fileLoadingCount = 0;
2093 public static int getViewCount(String alignmentId)
2095 AlignmentViewport[] aps = getViewports(alignmentId);
2096 return (aps == null) ? 0 : aps.length;
2101 * @param alignmentId
2102 * - if null, all sets are returned
2103 * @return all AlignmentPanels concerning the alignmentId sequence set
2105 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2107 if (Desktop.desktop == null)
2109 // no frames created and in headless mode
2110 // TODO: verify that frames are recoverable when in headless mode
2113 List<AlignmentPanel> aps = new ArrayList<>();
2114 AlignFrame[] frames = getAlignFrames();
2119 for (AlignFrame af : frames)
2121 for (AlignmentPanel ap : af.alignPanels)
2123 if (alignmentId == null
2124 || alignmentId.equals(ap.av.getSequenceSetId()))
2130 if (aps.size() == 0)
2134 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2139 * get all the viewports on an alignment.
2141 * @param sequenceSetId
2142 * unique alignment id (may be null - all viewports returned in that
2144 * @return all viewports on the alignment bound to sequenceSetId
2146 public static AlignmentViewport[] getViewports(String sequenceSetId)
2148 List<AlignmentViewport> viewp = new ArrayList<>();
2149 if (desktop != null)
2151 AlignFrame[] frames = Desktop.getAlignFrames();
2153 for (AlignFrame afr : frames)
2155 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2156 .equals(sequenceSetId))
2158 if (afr.alignPanels != null)
2160 for (AlignmentPanel ap : afr.alignPanels)
2162 if (sequenceSetId == null
2163 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2171 viewp.add(afr.getViewport());
2175 if (viewp.size() > 0)
2177 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2184 * Explode the views in the given frame into separate AlignFrame
2188 public static void explodeViews(AlignFrame af)
2190 int size = af.alignPanels.size();
2196 // FIXME: ideally should use UI interface API
2197 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2198 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2199 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2200 for (int i = 0; i < size; i++)
2202 AlignmentPanel ap = af.alignPanels.get(i);
2204 AlignFrame newaf = new AlignFrame(ap);
2206 // transfer reference for existing feature settings to new alignFrame
2207 if (ap == af.alignPanel)
2209 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2211 newaf.featureSettings = viewFeatureSettings;
2213 newaf.setFeatureSettingsGeometry(fsBounds);
2217 * Restore the view's last exploded frame geometry if known. Multiple views from
2218 * one exploded frame share and restore the same (frame) position and size.
2220 Rectangle geometry = ap.av.getExplodedGeometry();
2221 if (geometry != null)
2223 newaf.setBounds(geometry);
2226 ap.av.setGatherViewsHere(false);
2228 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2229 AlignFrame.DEFAULT_HEIGHT);
2230 // and materialise a new feature settings dialog instance for the new
2232 // (closes the old as if 'OK' was pressed)
2233 if (ap == af.alignPanel && newaf.featureSettings != null
2234 && newaf.featureSettings.isOpen()
2235 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2237 newaf.showFeatureSettingsUI();
2241 af.featureSettings = null;
2242 af.alignPanels.clear();
2243 af.closeMenuItem_actionPerformed(true);
2248 * Gather expanded views (separate AlignFrame's) with the same sequence set
2249 * identifier back in to this frame as additional views, and close the
2250 * expanded views. Note the expanded frames may themselves have multiple
2251 * views. We take the lot.
2255 public void gatherViews(AlignFrame source)
2257 source.viewport.setGatherViewsHere(true);
2258 source.viewport.setExplodedGeometry(source.getBounds());
2259 JInternalFrame[] frames = desktop.getAllFrames();
2260 String viewId = source.viewport.getSequenceSetId();
2261 for (int t = 0; t < frames.length; t++)
2263 if (frames[t] instanceof AlignFrame && frames[t] != source)
2265 AlignFrame af = (AlignFrame) frames[t];
2266 boolean gatherThis = false;
2267 for (int a = 0; a < af.alignPanels.size(); a++)
2269 AlignmentPanel ap = af.alignPanels.get(a);
2270 if (viewId.equals(ap.av.getSequenceSetId()))
2273 ap.av.setGatherViewsHere(false);
2274 ap.av.setExplodedGeometry(af.getBounds());
2275 source.addAlignmentPanel(ap, false);
2281 if (af.featureSettings != null && af.featureSettings.isOpen())
2283 if (source.featureSettings == null)
2285 // preserve the feature settings geometry for this frame
2286 source.featureSettings = af.featureSettings;
2287 source.setFeatureSettingsGeometry(
2288 af.getFeatureSettingsGeometry());
2292 // close it and forget
2293 af.featureSettings.close();
2296 af.alignPanels.clear();
2297 af.closeMenuItem_actionPerformed(true);
2302 // refresh the feature setting UI for the source frame if it exists
2303 if (source.featureSettings != null && source.featureSettings.isOpen())
2305 source.showFeatureSettingsUI();
2310 public JInternalFrame[] getAllFrames()
2312 return desktop.getAllFrames();
2316 * Checks the given url to see if it gives a response indicating that the user
2317 * should be informed of a new questionnaire.
2321 public void checkForQuestionnaire(String url)
2323 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2324 // javax.swing.SwingUtilities.invokeLater(jvq);
2325 new Thread(jvq).start();
2328 public void checkURLLinks()
2330 // Thread off the URL link checker
2331 addDialogThread(new Runnable()
2336 if (Cache.getDefault("CHECKURLLINKS", true))
2338 // check what the actual links are - if it's just the default don't
2339 // bother with the warning
2340 List<String> links = Preferences.sequenceUrlLinks
2343 // only need to check links if there is one with a
2344 // SEQUENCE_ID which is not the default EMBL_EBI link
2345 ListIterator<String> li = links.listIterator();
2346 boolean check = false;
2347 List<JLabel> urls = new ArrayList<>();
2348 while (li.hasNext())
2350 String link = li.next();
2351 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2352 && !UrlConstants.isDefaultString(link))
2355 int barPos = link.indexOf("|");
2356 String urlMsg = barPos == -1 ? link
2357 : link.substring(0, barPos) + ": "
2358 + link.substring(barPos + 1);
2359 urls.add(new JLabel(urlMsg));
2367 // ask user to check in case URL links use old style tokens
2368 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2369 JPanel msgPanel = new JPanel();
2370 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2371 msgPanel.add(Box.createVerticalGlue());
2372 JLabel msg = new JLabel(MessageManager
2373 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2374 JLabel msg2 = new JLabel(MessageManager
2375 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2377 for (JLabel url : urls)
2383 final JCheckBox jcb = new JCheckBox(
2384 MessageManager.getString("label.do_not_display_again"));
2385 jcb.addActionListener(new ActionListener()
2388 public void actionPerformed(ActionEvent e)
2390 // update Cache settings for "don't show this again"
2391 boolean showWarningAgain = !jcb.isSelected();
2392 Cache.setProperty("CHECKURLLINKS",
2393 Boolean.valueOf(showWarningAgain).toString());
2398 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2400 .getString("label.SEQUENCE_ID_no_longer_used"),
2401 JvOptionPane.WARNING_MESSAGE);
2408 * Proxy class for JDesktopPane which optionally displays the current memory
2409 * usage and highlights the desktop area with a red bar if free memory runs
2414 public class MyDesktopPane extends JDesktopPane implements Runnable
2416 private static final float ONE_MB = 1048576f;
2418 boolean showMemoryUsage = false;
2422 java.text.NumberFormat df;
2424 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2427 public MyDesktopPane(boolean showMemoryUsage)
2429 showMemoryUsage(showMemoryUsage);
2432 public void showMemoryUsage(boolean showMemory)
2434 this.showMemoryUsage = showMemory;
2437 Thread worker = new Thread(this);
2443 public boolean isShowMemoryUsage()
2445 return showMemoryUsage;
2451 df = java.text.NumberFormat.getNumberInstance();
2452 df.setMaximumFractionDigits(2);
2453 runtime = Runtime.getRuntime();
2455 while (showMemoryUsage)
2459 maxMemory = runtime.maxMemory() / ONE_MB;
2460 allocatedMemory = runtime.totalMemory() / ONE_MB;
2461 freeMemory = runtime.freeMemory() / ONE_MB;
2462 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2464 percentUsage = (totalFreeMemory / maxMemory) * 100;
2466 // if (percentUsage < 20)
2468 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2470 // instance.set.setBorder(border1);
2473 // sleep after showing usage
2475 } catch (Exception ex)
2477 ex.printStackTrace();
2483 public void paintComponent(Graphics g)
2485 if (showMemoryUsage && g != null && df != null)
2487 if (percentUsage < 20)
2489 g.setColor(Color.red);
2491 FontMetrics fm = g.getFontMetrics();
2494 g.drawString(MessageManager.formatMessage("label.memory_stats",
2496 { df.format(totalFreeMemory), df.format(maxMemory),
2497 df.format(percentUsage) }),
2498 10, getHeight() - fm.getHeight());
2502 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2503 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2508 * Accessor method to quickly get all the AlignmentFrames loaded.
2510 * @return an array of AlignFrame, or null if none found
2512 public static AlignFrame[] getAlignFrames()
2514 if (Jalview.isHeadlessMode())
2516 // Desktop.desktop is null in headless mode
2517 return new AlignFrame[] { Jalview.currentAlignFrame };
2520 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2526 List<AlignFrame> avp = new ArrayList<>();
2528 for (int i = frames.length - 1; i > -1; i--)
2530 if (frames[i] instanceof AlignFrame)
2532 avp.add((AlignFrame) frames[i]);
2534 else if (frames[i] instanceof SplitFrame)
2537 * Also check for a split frame containing an AlignFrame
2539 GSplitFrame sf = (GSplitFrame) frames[i];
2540 if (sf.getTopFrame() instanceof AlignFrame)
2542 avp.add((AlignFrame) sf.getTopFrame());
2544 if (sf.getBottomFrame() instanceof AlignFrame)
2546 avp.add((AlignFrame) sf.getBottomFrame());
2550 if (avp.size() == 0)
2554 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2559 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2563 public GStructureViewer[] getJmols()
2565 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2571 List<GStructureViewer> avp = new ArrayList<>();
2573 for (int i = frames.length - 1; i > -1; i--)
2575 if (frames[i] instanceof AppJmol)
2577 GStructureViewer af = (GStructureViewer) frames[i];
2581 if (avp.size() == 0)
2585 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2590 * Add Groovy Support to Jalview
2593 public void groovyShell_actionPerformed()
2597 openGroovyConsole();
2598 } catch (Exception ex)
2600 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2601 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2603 MessageManager.getString("label.couldnt_create_groovy_shell"),
2604 MessageManager.getString("label.groovy_support_failed"),
2605 JvOptionPane.ERROR_MESSAGE);
2610 * Open the Groovy console
2612 void openGroovyConsole()
2614 if (groovyConsole == null)
2616 groovyConsole = new groovy.ui.Console();
2617 groovyConsole.setVariable("Jalview", this);
2618 groovyConsole.run();
2621 * We allow only one console at a time, so that AlignFrame menu option
2622 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2623 * enable 'Run script', when the console is opened, and the reverse when it is
2626 Window window = (Window) groovyConsole.getFrame();
2627 window.addWindowListener(new WindowAdapter()
2630 public void windowClosed(WindowEvent e)
2633 * rebind CMD-Q from Groovy Console to Jalview Quit
2636 enableExecuteGroovy(false);
2642 * show Groovy console window (after close and reopen)
2644 ((Window) groovyConsole.getFrame()).setVisible(true);
2647 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2648 * opening a second console
2650 enableExecuteGroovy(true);
2654 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2655 * binding when opened
2657 protected void addQuitHandler()
2660 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2662 .getKeyStroke(KeyEvent.VK_Q,
2663 jalview.util.ShortcutKeyMaskExWrapper
2664 .getMenuShortcutKeyMaskEx()),
2666 getRootPane().getActionMap().put("Quit", new AbstractAction()
2669 public void actionPerformed(ActionEvent e)
2677 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2680 * true if Groovy console is open
2682 public void enableExecuteGroovy(boolean enabled)
2685 * disable opening a second Groovy console (or re-enable when the console is
2688 groovyShell.setEnabled(!enabled);
2690 AlignFrame[] alignFrames = getAlignFrames();
2691 if (alignFrames != null)
2693 for (AlignFrame af : alignFrames)
2695 af.setGroovyEnabled(enabled);
2701 * Progress bars managed by the IProgressIndicator method.
2703 private Hashtable<Long, JPanel> progressBars;
2705 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2710 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2713 public void setProgressBar(String message, long id)
2715 if (progressBars == null)
2717 progressBars = new Hashtable<>();
2718 progressBarHandlers = new Hashtable<>();
2721 if (progressBars.get(Long.valueOf(id)) != null)
2723 JPanel panel = progressBars.remove(Long.valueOf(id));
2724 if (progressBarHandlers.contains(Long.valueOf(id)))
2726 progressBarHandlers.remove(Long.valueOf(id));
2728 removeProgressPanel(panel);
2732 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2739 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2740 * jalview.gui.IProgressIndicatorHandler)
2743 public void registerHandler(final long id,
2744 final IProgressIndicatorHandler handler)
2746 if (progressBarHandlers == null
2747 || !progressBars.containsKey(Long.valueOf(id)))
2749 throw new Error(MessageManager.getString(
2750 "error.call_setprogressbar_before_registering_handler"));
2752 progressBarHandlers.put(Long.valueOf(id), handler);
2753 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2754 if (handler.canCancel())
2756 JButton cancel = new JButton(
2757 MessageManager.getString("action.cancel"));
2758 final IProgressIndicator us = this;
2759 cancel.addActionListener(new ActionListener()
2763 public void actionPerformed(ActionEvent e)
2765 handler.cancelActivity(id);
2766 us.setProgressBar(MessageManager
2767 .formatMessage("label.cancelled_params", new Object[]
2768 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2772 progressPanel.add(cancel, BorderLayout.EAST);
2778 * @return true if any progress bars are still active
2781 public boolean operationInProgress()
2783 if (progressBars != null && progressBars.size() > 0)
2791 * This will return the first AlignFrame holding the given viewport instance.
2792 * It will break if there are more than one AlignFrames viewing a particular
2796 * @return alignFrame for viewport
2798 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2800 if (desktop != null)
2802 AlignmentPanel[] aps = getAlignmentPanels(
2803 viewport.getSequenceSetId());
2804 for (int panel = 0; aps != null && panel < aps.length; panel++)
2806 if (aps[panel] != null && aps[panel].av == viewport)
2808 return aps[panel].alignFrame;
2815 public VamsasApplication getVamsasApplication()
2817 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2823 * flag set if jalview GUI is being operated programmatically
2825 private boolean inBatchMode = false;
2828 * check if jalview GUI is being operated programmatically
2830 * @return inBatchMode
2832 public boolean isInBatchMode()
2838 * set flag if jalview GUI is being operated programmatically
2840 * @param inBatchMode
2842 public void setInBatchMode(boolean inBatchMode)
2844 this.inBatchMode = inBatchMode;
2848 * start service discovery and wait till it is done
2850 public void startServiceDiscovery()
2852 startServiceDiscovery(false);
2856 * start service discovery threads - blocking or non-blocking
2860 public void startServiceDiscovery(boolean blocking)
2862 startServiceDiscovery(blocking, false);
2866 * start service discovery threads
2869 * - false means call returns immediately
2870 * @param ignore_SHOW_JWS2_SERVICES_preference
2871 * - when true JABA services are discovered regardless of user's JWS2
2872 * discovery preference setting
2874 public void startServiceDiscovery(boolean blocking,
2875 boolean ignore_SHOW_JWS2_SERVICES_preference)
2877 boolean alive = true;
2878 Thread t0 = null, t1 = null, t2 = null;
2879 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2882 // todo: changesupport handlers need to be transferred
2883 if (discoverer == null)
2885 discoverer = new jalview.ws.jws1.Discoverer();
2886 // register PCS handler for desktop.
2887 discoverer.addPropertyChangeListener(changeSupport);
2889 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2890 // until we phase out completely
2891 (t0 = new Thread(discoverer)).start();
2894 if (ignore_SHOW_JWS2_SERVICES_preference
2895 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2897 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2898 .startDiscoverer(changeSupport);
2902 // TODO: do rest service discovery
2911 } catch (Exception e)
2914 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2915 || (t3 != null && t3.isAlive())
2916 || (t0 != null && t0.isAlive());
2922 * called to check if the service discovery process completed successfully.
2926 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2928 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2930 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2931 .getErrorMessages();
2934 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2936 if (serviceChangedDialog == null)
2938 // only run if we aren't already displaying one of these.
2939 addDialogThread(serviceChangedDialog = new Runnable()
2946 * JalviewDialog jd =new JalviewDialog() {
2948 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2950 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2952 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2954 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2956 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2957 * + " or mis-configured HTTP proxy settings.<br/>" +
2958 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2959 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2960 * true, true, "Web Service Configuration Problem", 450, 400);
2962 * jd.waitForInput();
2964 JvOptionPane.showConfirmDialog(Desktop.desktop,
2965 new JLabel("<html><table width=\"450\"><tr><td>"
2966 + ermsg + "</td></tr></table>"
2967 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2968 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2969 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2970 + " Tools->Preferences dialog box to change them.</p></html>"),
2971 "Web Service Configuration Problem",
2972 JvOptionPane.DEFAULT_OPTION,
2973 JvOptionPane.ERROR_MESSAGE);
2974 serviceChangedDialog = null;
2982 jalview.bin.Console.error(
2983 "Errors reported by JABA discovery service. Check web services preferences.\n"
2990 private Runnable serviceChangedDialog = null;
2993 * start a thread to open a URL in the configured browser. Pops up a warning
2994 * dialog to the user if there is an exception when calling out to the browser
2999 public static void showUrl(final String url)
3001 showUrl(url, Desktop.instance);
3005 * Like showUrl but allows progress handler to be specified
3009 * (null) or object implementing IProgressIndicator
3011 public static void showUrl(final String url,
3012 final IProgressIndicator progress)
3014 new Thread(new Runnable()
3021 if (progress != null)
3023 progress.setProgressBar(MessageManager
3024 .formatMessage("status.opening_params", new Object[]
3025 { url }), this.hashCode());
3027 jalview.util.BrowserLauncher.openURL(url);
3028 } catch (Exception ex)
3030 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3032 .getString("label.web_browser_not_found_unix"),
3033 MessageManager.getString("label.web_browser_not_found"),
3034 JvOptionPane.WARNING_MESSAGE);
3036 ex.printStackTrace();
3038 if (progress != null)
3040 progress.setProgressBar(null, this.hashCode());
3046 public static WsParamSetManager wsparamManager = null;
3048 public static ParamManager getUserParameterStore()
3050 if (wsparamManager == null)
3052 wsparamManager = new WsParamSetManager();
3054 return wsparamManager;
3058 * static hyperlink handler proxy method for use by Jalview's internal windows
3062 public static void hyperlinkUpdate(HyperlinkEvent e)
3064 if (e.getEventType() == EventType.ACTIVATED)
3069 url = e.getURL().toString();
3070 Desktop.showUrl(url);
3071 } catch (Exception x)
3076 .error("Couldn't handle string " + url + " as a URL.");
3078 // ignore any exceptions due to dud links.
3085 * single thread that handles display of dialogs to user.
3087 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3090 * flag indicating if dialogExecutor should try to acquire a permit
3092 private volatile boolean dialogPause = true;
3097 private Semaphore block = new Semaphore(0);
3099 private static groovy.ui.Console groovyConsole;
3102 * add another dialog thread to the queue
3106 public void addDialogThread(final Runnable prompter)
3108 dialogExecutor.submit(new Runnable()
3115 acquireDialogQueue();
3117 if (instance == null)
3123 SwingUtilities.invokeAndWait(prompter);
3124 } catch (Exception q)
3126 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3133 private boolean dialogQueueStarted = false;
3135 public void startDialogQueue()
3137 if (dialogQueueStarted)
3141 // set the flag so we don't pause waiting for another permit and semaphore
3142 // the current task to begin
3143 releaseDialogQueue();
3144 dialogQueueStarted = true;
3147 public void acquireDialogQueue()
3153 } catch (InterruptedException e)
3155 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3160 public void releaseDialogQueue()
3167 dialogPause = false;
3171 * Outputs an image of the desktop to file in EPS format, after prompting the
3172 * user for choice of Text or Lineart character rendering (unless a preference
3173 * has been set). The file name is generated as
3176 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3180 protected void snapShotWindow_actionPerformed(ActionEvent e)
3182 // currently the menu option to do this is not shown
3185 int width = getWidth();
3186 int height = getHeight();
3188 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3189 ImageWriterI writer = new ImageWriterI()
3192 public void exportImage(Graphics g) throws Exception
3195 jalview.bin.Console.info("Successfully written snapshot to file "
3196 + of.getAbsolutePath());
3199 String title = "View of desktop";
3200 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3204 exporter.doExport(of, this, width, height, title);
3205 } catch (ImageOutputException ioex)
3207 jalview.bin.Console.error(
3208 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3214 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3215 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3216 * and location last time the view was expanded (if any). However it does not
3217 * remember the split pane divider location - this is set to match the
3218 * 'exploding' frame.
3222 public void explodeViews(SplitFrame sf)
3224 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3225 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3226 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3228 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3230 int viewCount = topPanels.size();
3237 * Processing in reverse order works, forwards order leaves the first panels not
3238 * visible. I don't know why!
3240 for (int i = viewCount - 1; i >= 0; i--)
3243 * Make new top and bottom frames. These take over the respective AlignmentPanel
3244 * objects, including their AlignmentViewports, so the cdna/protein
3245 * relationships between the viewports is carried over to the new split frames.
3247 * explodedGeometry holds the (x, y) position of the previously exploded
3248 * SplitFrame, and the (width, height) of the AlignFrame component
3250 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3251 AlignFrame newTopFrame = new AlignFrame(topPanel);
3252 newTopFrame.setSize(oldTopFrame.getSize());
3253 newTopFrame.setVisible(true);
3254 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3255 .getExplodedGeometry();
3256 if (geometry != null)
3258 newTopFrame.setSize(geometry.getSize());
3261 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3262 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3263 newBottomFrame.setSize(oldBottomFrame.getSize());
3264 newBottomFrame.setVisible(true);
3265 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3266 .getExplodedGeometry();
3267 if (geometry != null)
3269 newBottomFrame.setSize(geometry.getSize());
3272 topPanel.av.setGatherViewsHere(false);
3273 bottomPanel.av.setGatherViewsHere(false);
3274 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3276 if (geometry != null)
3278 splitFrame.setLocation(geometry.getLocation());
3280 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3284 * Clear references to the panels (now relocated in the new SplitFrames) before
3285 * closing the old SplitFrame.
3288 bottomPanels.clear();
3293 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3294 * back into the given SplitFrame as additional views. Note that the gathered
3295 * frames may themselves have multiple views.
3299 public void gatherViews(GSplitFrame source)
3302 * special handling of explodedGeometry for a view within a SplitFrame: - it
3303 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3304 * height) of the AlignFrame component
3306 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3307 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3308 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3309 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3310 myBottomFrame.viewport
3311 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3312 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3313 myTopFrame.viewport.setGatherViewsHere(true);
3314 myBottomFrame.viewport.setGatherViewsHere(true);
3315 String topViewId = myTopFrame.viewport.getSequenceSetId();
3316 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3318 JInternalFrame[] frames = desktop.getAllFrames();
3319 for (JInternalFrame frame : frames)
3321 if (frame instanceof SplitFrame && frame != source)
3323 SplitFrame sf = (SplitFrame) frame;
3324 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3325 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3326 boolean gatherThis = false;
3327 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3329 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3330 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3331 if (topViewId.equals(topPanel.av.getSequenceSetId())
3332 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3335 topPanel.av.setGatherViewsHere(false);
3336 bottomPanel.av.setGatherViewsHere(false);
3337 topPanel.av.setExplodedGeometry(
3338 new Rectangle(sf.getLocation(), topFrame.getSize()));
3339 bottomPanel.av.setExplodedGeometry(
3340 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3341 myTopFrame.addAlignmentPanel(topPanel, false);
3342 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3348 topFrame.getAlignPanels().clear();
3349 bottomFrame.getAlignPanels().clear();
3356 * The dust settles...give focus to the tab we did this from.
3358 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3361 public static groovy.ui.Console getGroovyConsole()
3363 return groovyConsole;
3367 * handles the payload of a drag and drop event.
3369 * TODO refactor to desktop utilities class
3372 * - Data source strings extracted from the drop event
3374 * - protocol for each data source extracted from the drop event
3378 * - the payload from the drop event
3381 public static void transferFromDropTarget(List<Object> files,
3382 List<DataSourceType> protocols, DropTargetDropEvent evt,
3383 Transferable t) throws Exception
3386 DataFlavor uriListFlavor = new DataFlavor(
3387 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3390 urlFlavour = new DataFlavor(
3391 "application/x-java-url; class=java.net.URL");
3392 } catch (ClassNotFoundException cfe)
3394 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3398 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3403 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3404 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3405 // means url may be null.
3408 protocols.add(DataSourceType.URL);
3409 files.add(url.toString());
3410 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3411 + files.get(files.size() - 1));
3416 if (Platform.isAMacAndNotJS())
3418 jalview.bin.Console.errPrintln(
3419 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3422 } catch (Throwable ex)
3424 jalview.bin.Console.debug("URL drop handler failed.", ex);
3427 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3429 // Works on Windows and MacOSX
3430 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3431 for (Object file : (List) t
3432 .getTransferData(DataFlavor.javaFileListFlavor))
3435 protocols.add(DataSourceType.FILE);
3440 // Unix like behaviour
3441 boolean added = false;
3443 if (t.isDataFlavorSupported(uriListFlavor))
3445 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3446 // This is used by Unix drag system
3447 data = (String) t.getTransferData(uriListFlavor);
3451 // fallback to text: workaround - on OSX where there's a JVM bug
3453 .debug("standard URIListFlavor failed. Trying text");
3454 // try text fallback
3455 DataFlavor textDf = new DataFlavor(
3456 "text/plain;class=java.lang.String");
3457 if (t.isDataFlavorSupported(textDf))
3459 data = (String) t.getTransferData(textDf);
3462 jalview.bin.Console.debug("Plain text drop content returned "
3463 + (data == null ? "Null - failed" : data));
3468 while (protocols.size() < files.size())
3470 jalview.bin.Console.debug("Adding missing FILE protocol for "
3471 + files.get(protocols.size()));
3472 protocols.add(DataSourceType.FILE);
3474 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3475 data, "\r\n"); st.hasMoreTokens();)
3478 String s = st.nextToken();
3479 if (s.startsWith("#"))
3481 // the line is a comment (as per the RFC 2483)
3484 java.net.URI uri = new java.net.URI(s);
3485 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3487 protocols.add(DataSourceType.URL);
3488 files.add(uri.toString());
3492 // otherwise preserve old behaviour: catch all for file objects
3493 java.io.File file = new java.io.File(uri);
3494 protocols.add(DataSourceType.FILE);
3495 files.add(file.toString());
3500 if (jalview.bin.Console.isDebugEnabled())
3502 if (data == null || !added)
3505 if (t.getTransferDataFlavors() != null
3506 && t.getTransferDataFlavors().length > 0)
3508 jalview.bin.Console.debug(
3509 "Couldn't resolve drop data. Here are the supported flavors:");
3510 for (DataFlavor fl : t.getTransferDataFlavors())
3512 jalview.bin.Console.debug(
3513 "Supported transfer dataflavor: " + fl.toString());
3514 Object df = t.getTransferData(fl);
3517 jalview.bin.Console.debug("Retrieves: " + df);
3521 jalview.bin.Console.debug("Retrieved nothing");
3528 .debug("Couldn't resolve dataflavor for drop: "
3534 if (Platform.isWindowsAndNotJS())
3537 .debug("Scanning dropped content for Windows Link Files");
3539 // resolve any .lnk files in the file drop
3540 for (int f = 0; f < files.size(); f++)
3542 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3543 if (protocols.get(f).equals(DataSourceType.FILE)
3544 && (source.endsWith(".lnk") || source.endsWith(".url")
3545 || source.endsWith(".site")))
3549 Object obj = files.get(f);
3550 File lf = (obj instanceof File ? (File) obj
3551 : new File((String) obj));
3552 // process link file to get a URL
3553 jalview.bin.Console.debug("Found potential link file: " + lf);
3554 WindowsShortcut wscfile = new WindowsShortcut(lf);
3555 String fullname = wscfile.getRealFilename();
3556 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3557 files.set(f, fullname);
3558 jalview.bin.Console.debug("Parsed real filename " + fullname
3559 + " to extract protocol: " + protocols.get(f));
3560 } catch (Exception ex)
3562 jalview.bin.Console.error(
3563 "Couldn't parse " + files.get(f) + " as a link file.",
3572 * Sets the Preferences property for experimental features to True or False
3573 * depending on the state of the controlling menu item
3576 protected void showExperimental_actionPerformed(boolean selected)
3578 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3582 * Answers a (possibly empty) list of any structure viewer frames (currently
3583 * for either Jmol or Chimera) which are currently open. This may optionally
3584 * be restricted to viewers of a specified class, or viewers linked to a
3585 * specified alignment panel.
3588 * if not null, only return viewers linked to this panel
3589 * @param structureViewerClass
3590 * if not null, only return viewers of this class
3593 public List<StructureViewerBase> getStructureViewers(
3594 AlignmentPanel apanel,
3595 Class<? extends StructureViewerBase> structureViewerClass)
3597 List<StructureViewerBase> result = new ArrayList<>();
3598 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3600 for (JInternalFrame frame : frames)
3602 if (frame instanceof StructureViewerBase)
3604 if (structureViewerClass == null
3605 || structureViewerClass.isInstance(frame))
3608 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3610 result.add((StructureViewerBase) frame);
3618 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3620 private static boolean debugScaleMessageDone = false;
3622 public static void debugScaleMessage(Graphics g)
3624 if (debugScaleMessageDone)
3628 // output used by tests to check HiDPI scaling settings in action
3631 Graphics2D gg = (Graphics2D) g;
3634 AffineTransform t = gg.getTransform();
3635 double scaleX = t.getScaleX();
3636 double scaleY = t.getScaleY();
3637 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3638 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3639 debugScaleMessageDone = true;
3643 jalview.bin.Console.debug("Desktop graphics null");
3645 } catch (Exception e)
3647 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3652 * closes the current instance window, disposes and forgets about it.
3654 public static void closeDesktop()
3656 if (Desktop.instance != null)
3658 Desktop.instance.closeAll_actionPerformed(null);
3659 Desktop.instance.setVisible(false);
3660 Desktop us = Desktop.instance;
3661 Desktop.instance = null;
3662 // call dispose in a separate thread - try to avoid indirect deadlocks
3663 new Thread(new Runnable() {
3667 ExecutorService dex = us.dialogExecutor;
3670 us.dialogExecutor=null;
3671 us.block.drainPermits();
3680 * checks if any progress bars are being displayed in any of the windows
3681 * managed by the desktop
3685 public boolean operationsAreInProgress()
3687 JInternalFrame[] frames = getAllFrames();
3688 for (JInternalFrame frame : frames)
3690 if (frame instanceof IProgressIndicator)
3692 if (((IProgressIndicator) frame).operationInProgress())
3698 return operationInProgress();
3702 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3703 * The way the modal JInternalFrame is made means it cannot be a child of an
3704 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3706 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3708 protected static void addModal(AlignFrame af, JInternalFrame jif)
3710 alignFrameModalMap.put(af, jif);
3713 protected static void closeModal(AlignFrame af)
3715 if (!alignFrameModalMap.containsKey(af))
3719 JInternalFrame jif = alignFrameModalMap.get(af);
3724 jif.setClosed(true);
3725 } catch (PropertyVetoException e)
3727 e.printStackTrace();
3730 alignFrameModalMap.remove(af);