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.Component;
26 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.datatransfer.Clipboard;
36 import java.awt.datatransfer.ClipboardOwner;
37 import java.awt.datatransfer.DataFlavor;
38 import java.awt.datatransfer.Transferable;
39 import java.awt.dnd.DnDConstants;
40 import java.awt.dnd.DropTargetDragEvent;
41 import java.awt.dnd.DropTargetDropEvent;
42 import java.awt.dnd.DropTargetEvent;
43 import java.awt.dnd.DropTargetListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.WindowAdapter;
51 import java.awt.event.WindowEvent;
52 import java.awt.geom.AffineTransform;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
55 import java.beans.PropertyVetoException;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Locale;
69 import java.util.Vector;
70 import java.util.concurrent.ExecutorService;
71 import java.util.concurrent.Executors;
72 import java.util.concurrent.Semaphore;
74 import javax.swing.AbstractAction;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.Box;
78 import javax.swing.BoxLayout;
79 import javax.swing.DefaultDesktopManager;
80 import javax.swing.DesktopManager;
81 import javax.swing.InputMap;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JComboBox;
85 import javax.swing.JComponent;
86 import javax.swing.JDesktopPane;
87 import javax.swing.JFrame;
88 import javax.swing.JInternalFrame;
89 import javax.swing.JLabel;
90 import javax.swing.JMenuItem;
91 import javax.swing.JOptionPane;
92 import javax.swing.JPanel;
93 import javax.swing.JPopupMenu;
94 import javax.swing.JProgressBar;
95 import javax.swing.JScrollPane;
96 import javax.swing.JTextArea;
97 import javax.swing.JTextField;
98 import javax.swing.JTextPane;
99 import javax.swing.KeyStroke;
100 import javax.swing.SwingUtilities;
101 import javax.swing.WindowConstants;
102 import javax.swing.event.HyperlinkEvent;
103 import javax.swing.event.HyperlinkEvent.EventType;
104 import javax.swing.event.InternalFrameAdapter;
105 import javax.swing.event.InternalFrameEvent;
106 import javax.swing.text.JTextComponent;
108 import org.stackoverflowusers.file.WindowsShortcut;
110 import jalview.api.AlignViewportI;
111 import jalview.api.AlignmentViewPanel;
112 import jalview.api.structures.JalviewStructureDisplayI;
113 import jalview.bin.Cache;
114 import jalview.bin.Jalview;
115 import jalview.bin.Jalview.ExitCode;
116 import jalview.bin.argparser.Arg;
117 import jalview.bin.groovy.JalviewObject;
118 import jalview.bin.groovy.JalviewObjectI;
119 import jalview.datamodel.Alignment;
120 import jalview.datamodel.HiddenColumns;
121 import jalview.datamodel.Sequence;
122 import jalview.datamodel.SequenceI;
123 import jalview.gui.ImageExporter.ImageWriterI;
124 import jalview.gui.QuitHandler.QResponse;
125 import jalview.io.BackupFiles;
126 import jalview.io.DataSourceType;
127 import jalview.io.FileFormat;
128 import jalview.io.FileFormatException;
129 import jalview.io.FileFormatI;
130 import jalview.io.FileFormats;
131 import jalview.io.FileLoader;
132 import jalview.io.FormatAdapter;
133 import jalview.io.IdentifyFile;
134 import jalview.io.JalviewFileChooser;
135 import jalview.io.JalviewFileView;
136 import jalview.io.exceptions.ImageOutputException;
137 import jalview.jbgui.GSplitFrame;
138 import jalview.jbgui.GStructureViewer;
139 import jalview.project.Jalview2XML;
140 import jalview.structure.StructureSelectionManager;
141 import jalview.urls.IdOrgSettings;
142 import jalview.util.BrowserLauncher;
143 import jalview.util.ChannelProperties;
144 import jalview.util.ImageMaker.TYPE;
145 import jalview.util.LaunchUtils;
146 import jalview.util.MessageManager;
147 import jalview.util.Platform;
148 import jalview.util.ShortcutKeyMaskExWrapper;
149 import jalview.util.UrlConstants;
150 import jalview.viewmodel.AlignmentViewport;
151 import jalview.ws.params.ParamManager;
152 import jalview.ws.utils.UrlDownloadClient;
159 * @version $Revision: 1.155 $
161 public class Desktop extends jalview.jbgui.GDesktop
162 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
163 jalview.api.StructureSelectionManagerProvider, JalviewObjectI
165 private static final String CITATION;
168 URL bg_logo_url = ChannelProperties.getImageURL(
169 "bg_logo." + String.valueOf(SplashScreen.logoSize));
170 URL uod_logo_url = ChannelProperties.getImageURL(
171 "uod_banner." + String.valueOf(SplashScreen.logoSize));
172 boolean logo = (bg_logo_url != null || uod_logo_url != null);
173 StringBuilder sb = new StringBuilder();
175 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
180 sb.append(bg_logo_url == null ? ""
181 : "<img alt=\"Barton Group logo\" src=\""
182 + bg_logo_url.toString() + "\">");
183 sb.append(uod_logo_url == null ? ""
184 : " <img alt=\"University of Dundee shield\" src=\""
185 + uod_logo_url.toString() + "\">");
187 "<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>");
188 sb.append("<br><br>If you use Jalview, please cite:"
189 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
190 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
191 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
192 CITATION = sb.toString();
195 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
197 private static int DEFAULT_MIN_WIDTH = 300;
199 private static int DEFAULT_MIN_HEIGHT = 250;
201 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
203 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
205 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
207 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
209 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
211 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
213 public static void setLiveDragMode(boolean b)
215 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
216 : JDesktopPane.OUTLINE_DRAG_MODE;
218 desktop.setDragMode(DRAG_MODE);
221 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
223 public static boolean nosplash = false;
226 * news reader - null if it was never started.
228 private BlogReader jvnews = null;
230 private File projectFile;
234 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void addJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
255 * @param propertyName
257 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
258 * java.beans.PropertyChangeListener)
260 public void removeJalviewPropertyChangeListener(String propertyName,
261 PropertyChangeListener listener)
263 changeSupport.removeJalviewPropertyChangeListener(propertyName,
267 /** Singleton Desktop instance */
268 public static Desktop instance;
270 public static MyDesktopPane desktop;
272 public static MyDesktopPane getDesktop()
274 // BH 2018 could use currentThread() here as a reference to a
275 // Hashtable<Thread, MyDesktopPane> in JavaScript
279 static int openFrameCount = 0;
281 static final int xOffset = 30;
283 static final int yOffset = 30;
285 public static jalview.ws.jws1.Discoverer discoverer;
287 public static Object[] jalviewClipboard;
289 public static boolean internalCopy = false;
291 static int fileLoadingCount = 0;
293 class MyDesktopManager implements DesktopManager
296 private DesktopManager delegate;
298 public MyDesktopManager(DesktopManager delegate)
300 this.delegate = delegate;
304 public void activateFrame(JInternalFrame f)
308 delegate.activateFrame(f);
309 } catch (NullPointerException npe)
311 Point p = getMousePosition();
312 instance.showPasteMenu(p.x, p.y);
317 public void beginDraggingFrame(JComponent f)
319 delegate.beginDraggingFrame(f);
323 public void beginResizingFrame(JComponent f, int direction)
325 delegate.beginResizingFrame(f, direction);
329 public void closeFrame(JInternalFrame f)
331 delegate.closeFrame(f);
335 public void deactivateFrame(JInternalFrame f)
337 delegate.deactivateFrame(f);
341 public void deiconifyFrame(JInternalFrame f)
343 delegate.deiconifyFrame(f);
347 public void dragFrame(JComponent f, int newX, int newY)
353 delegate.dragFrame(f, newX, newY);
357 public void endDraggingFrame(JComponent f)
359 delegate.endDraggingFrame(f);
364 public void endResizingFrame(JComponent f)
366 delegate.endResizingFrame(f);
371 public void iconifyFrame(JInternalFrame f)
373 delegate.iconifyFrame(f);
377 public void maximizeFrame(JInternalFrame f)
379 delegate.maximizeFrame(f);
383 public void minimizeFrame(JInternalFrame f)
385 delegate.minimizeFrame(f);
389 public void openFrame(JInternalFrame f)
391 delegate.openFrame(f);
395 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
402 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
406 public void setBoundsForFrame(JComponent f, int newX, int newY,
407 int newWidth, int newHeight)
409 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
412 // All other methods, simply delegate
417 * Creates a new Desktop object.
423 * A note to implementors. It is ESSENTIAL that any activities that might
424 * block are spawned off as threads rather than waited for during this
429 doConfigureStructurePrefs();
430 setTitle(ChannelProperties.getProperty("app_name") + " "
431 + Cache.getProperty("VERSION"));
434 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
435 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
436 * officially documented or guaranteed to exist, so we access it via
437 * reflection. There appear to be unfathomable criteria about what this
438 * string can contain, and it if doesn't meet those criteria then "java"
439 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
440 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
441 * not. The reflection access may generate a warning: WARNING: An illegal
442 * reflective access operation has occurred WARNING: Illegal reflective
443 * access by jalview.gui.Desktop () to field
444 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
446 if (Platform.isLinux())
448 if (LaunchUtils.getJavaVersion() >= 11)
451 * Send this message to stderr as the warning that follows (due to
452 * reflection) also goes to stderr.
454 jalview.bin.Console.errPrintln(
455 "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.");
457 final String awtAppClassName = "awtAppClassName";
460 Toolkit xToolkit = Toolkit.getDefaultToolkit();
461 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
462 Field awtAppClassNameField = null;
464 if (Arrays.stream(declaredFields)
465 .anyMatch(f -> f.getName().equals(awtAppClassName)))
467 awtAppClassNameField = xToolkit.getClass()
468 .getDeclaredField(awtAppClassName);
471 String title = ChannelProperties.getProperty("app_name");
472 if (awtAppClassNameField != null)
474 awtAppClassNameField.setAccessible(true);
475 awtAppClassNameField.set(xToolkit, title);
480 .debug("XToolkit: " + awtAppClassName + " not found");
482 } catch (Exception e)
484 jalview.bin.Console.debug("Error setting " + awtAppClassName);
485 jalview.bin.Console.trace(Cache.getStackTraceString(e));
489 setIconImages(ChannelProperties.getIconList());
491 // override quit handling when GUI OS close [X] button pressed
492 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
493 addWindowListener(new WindowAdapter()
496 public void windowClosing(WindowEvent ev)
498 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
502 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
504 boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE,
505 "SHOW_JAVA_CONSOLE", false);
507 // start dialogue queue for single dialogues
510 if (!Platform.isJS())
517 Desktop.instance.acquireDialogQueue();
519 jconsole = new Console(this);
520 jconsole.setHeader(Cache.getVersionDetailsForConsole());
521 showConsole(showjconsole);
523 Desktop.instance.releaseDialogQueue();
526 desktop = new MyDesktopPane(selmemusage);
528 showMemusage.setSelected(selmemusage);
529 desktop.setBackground(Color.white);
531 getContentPane().setLayout(new BorderLayout());
532 // alternate config - have scrollbars - see notes in JAL-153
533 // JScrollPane sp = new JScrollPane();
534 // sp.getViewport().setView(desktop);
535 // getContentPane().add(sp, BorderLayout.CENTER);
537 // BH 2018 - just an experiment to try unclipped JInternalFrames.
540 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
543 getContentPane().add(desktop, BorderLayout.CENTER);
544 desktop.setDragMode(DRAG_MODE);
546 // This line prevents Windows Look&Feel resizing all new windows to maximum
547 // if previous window was maximised
548 desktop.setDesktopManager(new MyDesktopManager(
549 Platform.isJS() ? desktop.getDesktopManager()
550 : new DefaultDesktopManager()));
552 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
553 : Platform.isAMacAndNotJS()
554 ? new AquaInternalFrameManager(
555 desktop.getDesktopManager())
556 : desktop.getDesktopManager())));
559 Rectangle dims = getLastKnownDimensions("");
566 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
567 int xPos = Math.max(5, (screenSize.width - 900) / 2);
568 int yPos = Math.max(5, (screenSize.height - 650) / 2);
569 setBounds(xPos, yPos, 900, 650);
572 if (!Platform.isJS())
579 showNews.setVisible(false);
581 experimentalFeatures.setSelected(showExperimental());
583 getIdentifiersOrgData();
587 // Spawn a thread that shows the splashscreen
590 SwingUtilities.invokeLater(new Runnable()
595 new SplashScreen(true);
600 // Thread off a new instance of the file chooser - this reduces the time
601 // it takes to open it later on.
602 new Thread(new Runnable()
607 jalview.bin.Console.debug("Filechooser init thread started.");
608 String fileFormat = FileLoader.getUseDefaultFileFormat()
609 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
611 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
613 jalview.bin.Console.debug("Filechooser init thread finished.");
616 // Add the service change listener
617 changeSupport.addJalviewPropertyChangeListener("services",
618 new PropertyChangeListener()
622 public void propertyChange(PropertyChangeEvent evt)
625 .debug("Firing service changed event for "
626 + evt.getNewValue());
627 JalviewServicesChanged(evt);
632 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
635 this.addMouseListener(ma = new MouseAdapter()
638 public void mousePressed(MouseEvent evt)
640 if (evt.isPopupTrigger()) // Mac
642 showPasteMenu(evt.getX(), evt.getY());
647 public void mouseReleased(MouseEvent evt)
649 if (evt.isPopupTrigger()) // Windows
651 showPasteMenu(evt.getX(), evt.getY());
655 desktop.addMouseListener(ma);
659 // used for jalviewjsTest
660 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
666 * Answers true if user preferences to enable experimental features is True
671 public boolean showExperimental()
673 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
674 Boolean.FALSE.toString());
675 return Boolean.valueOf(experimental).booleanValue();
678 public void doConfigureStructurePrefs()
680 // configure services
681 StructureSelectionManager ssm = StructureSelectionManager
682 .getStructureSelectionManager(this);
683 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
685 ssm.setAddTempFacAnnot(
686 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
687 ssm.setProcessSecondaryStructure(
688 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
689 // JAL-3915 - RNAView is no longer an option so this has no effect
690 ssm.setSecStructServices(
691 Cache.getDefault(Preferences.USE_RNAVIEW, false));
695 ssm.setAddTempFacAnnot(false);
696 ssm.setProcessSecondaryStructure(false);
697 ssm.setSecStructServices(false);
701 public void checkForNews()
703 final Desktop me = this;
704 // Thread off the news reader, in case there are connection problems.
705 new Thread(new Runnable()
710 jalview.bin.Console.debug("Starting news thread.");
711 jvnews = new BlogReader(me);
712 showNews.setVisible(true);
713 jalview.bin.Console.debug("Completed news thread.");
718 public void getIdentifiersOrgData()
720 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
721 {// Thread off the identifiers fetcher
722 new Thread(new Runnable()
728 .debug("Downloading data from identifiers.org");
731 UrlDownloadClient.download(IdOrgSettings.getUrl(),
732 IdOrgSettings.getDownloadLocation());
733 } catch (IOException e)
736 .debug("Exception downloading identifiers.org data"
746 protected void showNews_actionPerformed(ActionEvent e)
748 showNews(showNews.isSelected());
751 void showNews(boolean visible)
753 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
754 showNews.setSelected(visible);
755 if (visible && !jvnews.isVisible())
757 new Thread(new Runnable()
762 long now = System.currentTimeMillis();
763 Desktop.instance.setProgressBar(
764 MessageManager.getString("status.refreshing_news"), now);
765 jvnews.refreshNews();
766 Desktop.instance.setProgressBar(null, now);
774 * recover the last known dimensions for a jalview window
777 * - empty string is desktop, all other windows have unique prefix
778 * @return null or last known dimensions scaled to current geometry (if last
779 * window geom was known)
781 Rectangle getLastKnownDimensions(String windowName)
783 // TODO: lock aspect ratio for scaling desktop Bug #0058199
784 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
785 String x = Cache.getProperty(windowName + "SCREEN_X");
786 String y = Cache.getProperty(windowName + "SCREEN_Y");
787 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
788 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
789 if ((x != null) && (y != null) && (width != null) && (height != null))
791 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
792 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
793 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
795 // attempt #1 - try to cope with change in screen geometry - this
796 // version doesn't preserve original jv aspect ratio.
797 // take ratio of current screen size vs original screen size.
798 double sw = ((1f * screenSize.width) / (1f * Integer
799 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
800 double sh = ((1f * screenSize.height) / (1f * Integer
801 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
802 // rescale the bounds depending upon the current screen geometry.
803 ix = (int) (ix * sw);
804 iw = (int) (iw * sw);
805 iy = (int) (iy * sh);
806 ih = (int) (ih * sh);
807 if (ix >= screenSize.width)
809 jalview.bin.Console.debug(
810 "Window geometry location recall error: shifting horizontal to within screenbounds.");
811 ix = ix % screenSize.width;
813 if (iy >= screenSize.height)
815 jalview.bin.Console.debug(
816 "Window geometry location recall error: shifting vertical to within screenbounds.");
817 iy = iy % screenSize.height;
819 jalview.bin.Console.debug(
820 "Got last known dimensions for " + windowName + ": x:" + ix
821 + " y:" + iy + " width:" + iw + " height:" + ih);
823 // return dimensions for new instance
824 return new Rectangle(ix, iy, iw, ih);
829 void showPasteMenu(int x, int y)
831 JPopupMenu popup = new JPopupMenu();
832 JMenuItem item = new JMenuItem(
833 MessageManager.getString("label.paste_new_window"));
834 item.addActionListener(new ActionListener()
837 public void actionPerformed(ActionEvent evt)
844 popup.show(this, x, y);
849 // quick patch for JAL-4150 - needs some more work and test coverage
850 // TODO - unify below and AlignFrame.paste()
851 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
852 // clipboard has come from a different alignment window than the one where
853 // paste has been called! JAL-4151
855 if (Desktop.jalviewClipboard != null)
857 // The clipboard was filled from within Jalview, we must use the
859 // And dataset from the copied alignment
860 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
861 // be doubly sure that we create *new* sequence objects.
862 SequenceI[] sequences = new SequenceI[newseq.length];
863 for (int i = 0; i < newseq.length; i++)
865 sequences[i] = new Sequence(newseq[i]);
867 Alignment alignment = new Alignment(sequences);
868 // dataset is inherited
869 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
870 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
871 AlignFrame.DEFAULT_HEIGHT);
872 String newtitle = new String("Copied sequences");
874 if (Desktop.jalviewClipboard[2] != null)
876 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
877 af.viewport.setHiddenColumns(hc);
880 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
881 AlignFrame.DEFAULT_HEIGHT);
888 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
889 Transferable contents = c.getContents(this);
891 if (contents != null)
893 String file = (String) contents
894 .getTransferData(DataFlavor.stringFlavor);
896 FileFormatI format = new IdentifyFile().identify(file,
897 DataSourceType.PASTE);
899 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
902 } catch (Exception ex)
904 jalview.bin.Console.outPrintln(
905 "Unable to paste alignment from system clipboard:\n" + ex);
911 * Adds and opens the given frame to the desktop
922 public static synchronized void addInternalFrame(
923 final JInternalFrame frame, String title, int w, int h)
925 addInternalFrame(frame, title, true, w, h, true, false);
929 * Add an internal frame to the Jalview desktop
936 * When true, display frame immediately, otherwise, caller must call
937 * setVisible themselves.
943 public static synchronized void addInternalFrame(
944 final JInternalFrame frame, String title, boolean makeVisible,
947 addInternalFrame(frame, title, makeVisible, w, h, true, false);
951 * Add an internal frame to the Jalview desktop and make it visible
964 public static synchronized void addInternalFrame(
965 final JInternalFrame frame, String title, int w, int h,
968 addInternalFrame(frame, title, true, w, h, resizable, false);
972 * Add an internal frame to the Jalview desktop
979 * When true, display frame immediately, otherwise, caller must call
980 * setVisible themselves.
987 * @param ignoreMinSize
988 * Do not set the default minimum size for frame
990 public static synchronized void addInternalFrame(
991 final JInternalFrame frame, String title, boolean makeVisible,
992 int w, int h, boolean resizable, boolean ignoreMinSize)
995 // TODO: allow callers to determine X and Y position of frame (eg. via
997 // TODO: consider fixing method to update entries in the window submenu with
998 // the current window title
1000 frame.setTitle(title);
1001 if (frame.getWidth() < 1 || frame.getHeight() < 1)
1003 frame.setSize(w, h);
1005 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
1006 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
1007 // IF JALVIEW IS RUNNING HEADLESS
1008 // ///////////////////////////////////////////////
1009 if (instance == null || (System.getProperty("java.awt.headless") != null
1010 && System.getProperty("java.awt.headless").equals("true")))
1019 frame.setMinimumSize(
1020 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1022 // Set default dimension for Alignment Frame window.
1023 // The Alignment Frame window could be added from a number of places,
1025 // I did this here in order not to miss out on any Alignment frame.
1026 if (frame instanceof AlignFrame)
1028 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1029 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1033 frame.setVisible(makeVisible);
1034 frame.setClosable(true);
1035 frame.setResizable(resizable);
1036 frame.setMaximizable(resizable);
1037 frame.setIconifiable(resizable);
1038 frame.setOpaque(Platform.isJS());
1040 if (frame.getX() < 1 && frame.getY() < 1)
1042 frame.setLocation(xOffset * openFrameCount,
1043 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1047 * add an entry for the new frame in the Window menu (and remove it when the
1050 final JMenuItem menuItem = new JMenuItem(title);
1051 frame.addInternalFrameListener(new InternalFrameAdapter()
1054 public void internalFrameActivated(InternalFrameEvent evt)
1056 JInternalFrame itf = desktop.getSelectedFrame();
1059 if (itf instanceof AlignFrame)
1061 Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf);
1068 public void internalFrameClosed(InternalFrameEvent evt)
1070 PaintRefresher.RemoveComponent(frame);
1073 * defensive check to prevent frames being added half off the window
1075 if (openFrameCount > 0)
1081 * ensure no reference to alignFrame retained by menu item listener
1083 if (menuItem.getActionListeners().length > 0)
1085 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1087 windowMenu.remove(menuItem);
1091 menuItem.addActionListener(new ActionListener()
1094 public void actionPerformed(ActionEvent e)
1098 frame.setSelected(true);
1099 frame.setIcon(false);
1100 } catch (java.beans.PropertyVetoException ex)
1107 setKeyBindings(frame);
1109 // Since the latest FlatLaf patch, we occasionally have problems showing
1110 // structureViewer frames...
1112 boolean shown = false;
1113 Exception last = null;
1120 } catch (IllegalArgumentException iaex)
1124 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1125 + tries + " left) for " + frame.getTitle(), iaex);
1129 } catch (InterruptedException iex)
1134 } while (!shown && tries > 0);
1137 jalview.bin.Console.error(
1138 "Serious Problem whilst showing window " + frame.getTitle(),
1142 windowMenu.add(menuItem);
1147 frame.setSelected(true);
1148 frame.requestFocus();
1149 } catch (java.beans.PropertyVetoException ve)
1151 } catch (java.lang.ClassCastException cex)
1153 jalview.bin.Console.warn(
1154 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1160 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1165 private static void setKeyBindings(JInternalFrame frame)
1167 @SuppressWarnings("serial")
1168 final Action closeAction = new AbstractAction()
1171 public void actionPerformed(ActionEvent e)
1178 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1180 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1181 InputEvent.CTRL_DOWN_MASK);
1182 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1183 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1185 InputMap inputMap = frame
1186 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1187 String ctrlW = ctrlWKey.toString();
1188 inputMap.put(ctrlWKey, ctrlW);
1189 inputMap.put(cmdWKey, ctrlW);
1191 ActionMap actionMap = frame.getActionMap();
1192 actionMap.put(ctrlW, closeAction);
1196 public void lostOwnership(Clipboard clipboard, Transferable contents)
1200 Desktop.jalviewClipboard = null;
1203 internalCopy = false;
1207 public void dragEnter(DropTargetDragEvent evt)
1212 public void dragExit(DropTargetEvent evt)
1217 public void dragOver(DropTargetDragEvent evt)
1222 public void dropActionChanged(DropTargetDragEvent evt)
1233 public void drop(DropTargetDropEvent evt)
1235 boolean success = true;
1236 // JAL-1552 - acceptDrop required before getTransferable call for
1237 // Java's Transferable for native dnd
1238 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1239 Transferable t = evt.getTransferable();
1240 List<Object> files = new ArrayList<>();
1241 List<DataSourceType> protocols = new ArrayList<>();
1245 Desktop.transferFromDropTarget(files, protocols, evt, t);
1246 } catch (Exception e)
1248 e.printStackTrace();
1256 for (int i = 0; i < files.size(); i++)
1258 // BH 2018 File or String
1259 Object file = files.get(i);
1260 String fileName = file.toString();
1261 DataSourceType protocol = (protocols == null)
1262 ? DataSourceType.FILE
1264 FileFormatI format = null;
1266 if (fileName.endsWith(".jar"))
1268 format = FileFormat.Jalview;
1273 format = new IdentifyFile().identify(file, protocol);
1275 if (file instanceof File)
1277 Platform.cacheFileData((File) file);
1279 new FileLoader().LoadFile(null, file, protocol, format);
1282 } catch (Exception ex)
1287 evt.dropComplete(success); // need this to ensure input focus is properly
1288 // transfered to any new windows created
1298 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1300 String fileFormat = FileLoader.getUseDefaultFileFormat()
1301 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1303 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1304 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1305 BackupFiles.getEnabled());
1307 chooser.setFileView(new JalviewFileView());
1308 chooser.setDialogTitle(
1309 MessageManager.getString("label.open_local_file"));
1310 chooser.setToolTipText(MessageManager.getString("action.open"));
1312 chooser.setResponseHandler(0, () -> {
1313 File selectedFile = chooser.getSelectedFile();
1314 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1316 FileFormatI format = chooser.getSelectedFormat();
1319 * Call IdentifyFile to verify the file contains what its extension implies.
1320 * Skip this step for dynamically added file formats, because IdentifyFile does
1321 * not know how to recognise them.
1323 if (FileFormats.getInstance().isIdentifiable(format))
1327 format = new IdentifyFile().identify(selectedFile,
1328 DataSourceType.FILE);
1329 } catch (FileFormatException e)
1331 // format = null; //??
1335 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1338 chooser.showOpenDialog(this);
1342 * Shows a dialog for input of a URL at which to retrieve alignment data
1347 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1349 // This construct allows us to have a wider textfield
1351 JLabel label = new JLabel(
1352 MessageManager.getString("label.input_file_url"));
1354 JPanel panel = new JPanel(new GridLayout(2, 1));
1358 * the URL to fetch is input in Java: an editable combobox with history JS:
1359 * (pending JAL-3038) a plain text field
1362 String urlBase = "https://www.";
1363 if (Platform.isJS())
1365 history = new JTextField(urlBase, 35);
1374 JComboBox<String> asCombo = new JComboBox<>();
1375 asCombo.setPreferredSize(new Dimension(400, 20));
1376 asCombo.setEditable(true);
1377 asCombo.addItem(urlBase);
1378 String historyItems = Cache.getProperty("RECENT_URL");
1379 if (historyItems != null)
1381 for (String token : historyItems.split("\\t"))
1383 asCombo.addItem(token);
1390 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1391 MessageManager.getString("action.cancel") };
1392 Runnable action = () -> {
1393 @SuppressWarnings("unchecked")
1394 String url = (history instanceof JTextField
1395 ? ((JTextField) history).getText()
1396 : ((JComboBox<String>) history).getEditor().getItem()
1397 .toString().trim());
1399 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1401 if (viewport != null)
1403 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1404 FileFormat.Jalview);
1408 new FileLoader().LoadFile(url, DataSourceType.URL,
1409 FileFormat.Jalview);
1414 FileFormatI format = null;
1417 format = new IdentifyFile().identify(url, DataSourceType.URL);
1418 } catch (FileFormatException e)
1420 // TODO revise error handling, distinguish between
1421 // URL not found and response not valid
1426 String msg = MessageManager.formatMessage("label.couldnt_locate",
1428 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1429 MessageManager.getString("label.url_not_found"),
1430 JvOptionPane.WARNING_MESSAGE);
1434 if (viewport != null)
1436 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1441 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1445 String dialogOption = MessageManager
1446 .getString("label.input_alignment_from_url");
1447 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1448 .showInternalDialog(panel, dialogOption,
1449 JvOptionPane.YES_NO_CANCEL_OPTION,
1450 JvOptionPane.PLAIN_MESSAGE, null, options,
1451 MessageManager.getString("action.ok"));
1455 * Opens the CutAndPaste window for the user to paste an alignment in to
1458 * - if not null, the pasted alignment is added to the current
1459 * alignment; if null, to a new alignment window
1462 public void inputTextboxMenuItem_actionPerformed(
1463 AlignmentViewPanel viewPanel)
1465 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1466 cap.setForInput(viewPanel);
1467 Desktop.addInternalFrame(cap,
1468 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1473 * Check with user and saving files before actually quitting
1475 public void desktopQuit()
1477 desktopQuit(true, false);
1480 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1482 final Runnable doDesktopQuit = () -> {
1483 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1484 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1485 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1486 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1487 getBounds().y, getWidth(), getHeight()));
1489 if (jconsole != null)
1491 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1492 jconsole.stopConsole();
1497 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1500 // Frames should all close automatically. Keeping external
1501 // viewers open should already be decided by user.
1502 closeAll_actionPerformed(null);
1504 // check for aborted quit
1505 if (QuitHandler.quitCancelled())
1507 jalview.bin.Console.debug("Desktop aborting quit");
1511 if (dialogExecutor != null)
1513 dialogExecutor.shutdownNow();
1516 if (groovyConsole != null)
1518 // suppress a possible repeat prompt to save script
1519 groovyConsole.setDirty(false);
1520 groovyConsole.exit();
1523 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1525 // note that shutdown hook will not be run
1526 jalview.bin.Console.debug("Force Quit selected by user");
1527 Runtime.getRuntime().halt(0);
1530 jalview.bin.Console.debug("Quit selected by user");
1533 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1534 // instance.dispose();
1539 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1540 QuitHandler.defaultCancelQuit);
1544 * Don't call this directly, use desktopQuit() above. Exits the program.
1549 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1550 // not run a second time if gotQuitResponse flag has been set (i.e. user
1551 // confirmed quit of some kind).
1552 Jalview.exit("Desktop exiting.", ExitCode.OK);
1555 private void storeLastKnownDimensions(String string, Rectangle jc)
1557 jalview.bin.Console.debug("Storing last known dimensions for " + string
1558 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1559 + " height:" + jc.height);
1561 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1562 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1563 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1564 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1574 public void aboutMenuItem_actionPerformed(ActionEvent e)
1576 new Thread(new Runnable()
1581 new SplashScreen(false);
1587 * Returns the html text for the About screen, including any available version
1588 * number, build details, author details and citation reference, but without
1589 * the enclosing {@code html} tags
1593 public String getAboutMessage()
1595 StringBuilder message = new StringBuilder(1024);
1596 message.append("<div style=\"font-family: sans-serif;\">")
1597 .append("<h1><strong>Version: ")
1598 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1599 .append("<strong>Built: <em>")
1600 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1601 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1602 .append("</strong>");
1604 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1605 if (latestVersion.equals("Checking"))
1607 // JBP removed this message for 2.11: May be reinstated in future version
1608 // message.append("<br>...Checking latest version...</br>");
1610 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1612 boolean red = false;
1613 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1614 .indexOf("automated build") == -1)
1617 // Displayed when code version and jnlp version do not match and code
1618 // version is not a development build
1619 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1622 message.append("<br>!! Version ")
1623 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1624 .append(" is available for download from ")
1625 .append(Cache.getDefault("www.jalview.org",
1626 "https://www.jalview.org"))
1630 message.append("</div>");
1633 message.append("<br>Authors: ");
1634 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1635 message.append(CITATION);
1637 message.append("</div>");
1639 return message.toString();
1643 * Action on requesting Help documentation
1646 public void documentationMenuItem_actionPerformed()
1650 if (Platform.isJS())
1652 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1661 Help.showHelpWindow();
1663 } catch (Exception ex)
1666 .errPrintln("Error opening help: " + ex.getMessage());
1671 public void closeAll_actionPerformed(ActionEvent e)
1673 // TODO show a progress bar while closing?
1674 JInternalFrame[] frames = desktop.getAllFrames();
1675 for (int i = 0; i < frames.length; i++)
1679 frames[i].setClosed(true);
1680 } catch (java.beans.PropertyVetoException ex)
1684 Jalview.getInstance().setCurrentAlignFrame(null);
1685 jalview.bin.Console.info("ALL CLOSED");
1688 * reset state of singleton objects as appropriate (clear down session state
1689 * when all windows are closed)
1691 StructureSelectionManager ssm = StructureSelectionManager
1692 .getStructureSelectionManager(this);
1699 public int structureViewersStillRunningCount()
1702 JInternalFrame[] frames = desktop.getAllFrames();
1703 for (int i = 0; i < frames.length; i++)
1705 if (frames[i] != null
1706 && frames[i] instanceof JalviewStructureDisplayI)
1708 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1716 public void raiseRelated_actionPerformed(ActionEvent e)
1718 reorderAssociatedWindows(false, false);
1722 public void minimizeAssociated_actionPerformed(ActionEvent e)
1724 reorderAssociatedWindows(true, false);
1727 void closeAssociatedWindows()
1729 reorderAssociatedWindows(false, true);
1735 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1739 protected void garbageCollect_actionPerformed(ActionEvent e)
1741 // We simply collect the garbage
1742 jalview.bin.Console.debug("Collecting garbage...");
1744 jalview.bin.Console.debug("Finished garbage collection.");
1750 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1754 protected void showMemusage_actionPerformed(ActionEvent e)
1756 desktop.showMemoryUsage(showMemusage.isSelected());
1763 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1767 protected void showConsole_actionPerformed(ActionEvent e)
1769 showConsole(showConsole.isSelected());
1772 Console jconsole = null;
1775 * control whether the java console is visible or not
1779 void showConsole(boolean selected)
1781 // TODO: decide if we should update properties file
1782 if (jconsole != null) // BH 2018
1784 showConsole.setSelected(selected);
1785 Cache.setProperty("SHOW_JAVA_CONSOLE",
1786 Boolean.valueOf(selected).toString());
1787 jconsole.setVisible(selected);
1791 void reorderAssociatedWindows(boolean minimize, boolean close)
1793 JInternalFrame[] frames = desktop.getAllFrames();
1794 if (frames == null || frames.length < 1)
1799 AlignmentViewport source = null, target = null;
1800 if (frames[0] instanceof AlignFrame)
1802 source = ((AlignFrame) frames[0]).getCurrentView();
1804 else if (frames[0] instanceof TreePanel)
1806 source = ((TreePanel) frames[0]).getViewPort();
1808 else if (frames[0] instanceof PCAPanel)
1810 source = ((PCAPanel) frames[0]).av;
1812 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1814 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1819 for (int i = 0; i < frames.length; i++)
1822 if (frames[i] == null)
1826 if (frames[i] instanceof AlignFrame)
1828 target = ((AlignFrame) frames[i]).getCurrentView();
1830 else if (frames[i] instanceof TreePanel)
1832 target = ((TreePanel) frames[i]).getViewPort();
1834 else if (frames[i] instanceof PCAPanel)
1836 target = ((PCAPanel) frames[i]).av;
1838 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1840 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1843 if (source == target)
1849 frames[i].setClosed(true);
1853 frames[i].setIcon(minimize);
1856 frames[i].toFront();
1860 } catch (java.beans.PropertyVetoException ex)
1875 protected void preferences_actionPerformed(ActionEvent e)
1877 Preferences.openPreferences();
1881 * Prompts the user to choose a file and then saves the Jalview state as a
1882 * Jalview project file
1885 public void saveState_actionPerformed()
1887 saveState_actionPerformed(false);
1890 public void saveState_actionPerformed(boolean saveAs)
1892 java.io.File projectFile = getProjectFile();
1893 // autoSave indicates we already have a file and don't need to ask
1894 boolean autoSave = projectFile != null && !saveAs
1895 && BackupFiles.getEnabled();
1897 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1898 // projectFile='"+projectFile+"',
1899 // saveAs="+saveAs+", Backups
1900 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1902 boolean approveSave = false;
1905 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1908 chooser.setFileView(new JalviewFileView());
1909 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1911 int value = chooser.showSaveDialog(this);
1913 if (value == JalviewFileChooser.APPROVE_OPTION)
1915 projectFile = chooser.getSelectedFile();
1916 setProjectFile(projectFile);
1921 if (approveSave || autoSave)
1923 final Desktop me = this;
1924 final java.io.File chosenFile = projectFile;
1925 new Thread(new Runnable()
1930 // TODO: refactor to Jalview desktop session controller action.
1931 setProgressBar(MessageManager.formatMessage(
1932 "label.saving_jalview_project", new Object[]
1933 { chosenFile.getName() }), chosenFile.hashCode());
1934 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1935 // TODO catch and handle errors for savestate
1936 // TODO prevent user from messing with the Desktop whilst we're saving
1939 boolean doBackup = BackupFiles.getEnabled();
1940 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1943 new Jalview2XML().saveState(
1944 doBackup ? backupfiles.getTempFile() : chosenFile);
1948 backupfiles.setWriteSuccess(true);
1949 backupfiles.rollBackupsAndRenameTempFile();
1951 } catch (OutOfMemoryError oom)
1953 new OOMWarning("Whilst saving current state to "
1954 + chosenFile.getName(), oom);
1955 } catch (Exception ex)
1957 jalview.bin.Console.error("Problems whilst trying to save to "
1958 + chosenFile.getName(), ex);
1959 JvOptionPane.showMessageDialog(me,
1960 MessageManager.formatMessage(
1961 "label.error_whilst_saving_current_state_to",
1963 { chosenFile.getName() }),
1964 MessageManager.getString("label.couldnt_save_project"),
1965 JvOptionPane.WARNING_MESSAGE);
1967 setProgressBar(null, chosenFile.hashCode());
1974 public void saveAsState_actionPerformed(ActionEvent e)
1976 saveState_actionPerformed(true);
1979 protected void setProjectFile(File choice)
1981 this.projectFile = choice;
1984 public File getProjectFile()
1986 return this.projectFile;
1990 * Shows a file chooser dialog and tries to read in the selected file as a
1994 public void loadState_actionPerformed()
1996 final String[] suffix = new String[] { "jvp", "jar" };
1997 final String[] desc = new String[] { "Jalview Project",
1998 "Jalview Project (old)" };
1999 JalviewFileChooser chooser = new JalviewFileChooser(
2000 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2001 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2005 chooser.setFileView(new JalviewFileView());
2006 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2007 chooser.setResponseHandler(0, () -> {
2008 File selectedFile = chooser.getSelectedFile();
2009 setProjectFile(selectedFile);
2010 String choice = selectedFile.getAbsolutePath();
2011 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2012 new Thread(new Runnable()
2019 new Jalview2XML().loadJalviewAlign(selectedFile);
2020 } catch (OutOfMemoryError oom)
2022 new OOMWarning("Whilst loading project from " + choice, oom);
2023 } catch (Exception ex)
2025 jalview.bin.Console.error(
2026 "Problems whilst loading project from " + choice, ex);
2027 JvOptionPane.showMessageDialog(Desktop.desktop,
2028 MessageManager.formatMessage(
2029 "label.error_whilst_loading_project_from",
2032 MessageManager.getString("label.couldnt_load_project"),
2033 JvOptionPane.WARNING_MESSAGE);
2036 }, "Project Loader").start();
2039 chooser.showOpenDialog(this);
2043 public void inputSequence_actionPerformed(ActionEvent e)
2045 new SequenceFetcher(this);
2048 JPanel progressPanel;
2050 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2052 public void startLoading(final Object fileName)
2054 if (fileLoadingCount == 0)
2056 fileLoadingPanels.add(addProgressPanel(MessageManager
2057 .formatMessage("label.loading_file", new Object[]
2063 private JPanel addProgressPanel(String string)
2065 if (progressPanel == null)
2067 progressPanel = new JPanel(new GridLayout(1, 1));
2068 totalProgressCount = 0;
2069 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2071 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2072 JProgressBar progressBar = new JProgressBar();
2073 progressBar.setIndeterminate(true);
2075 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2077 thisprogress.add(progressBar, BorderLayout.CENTER);
2078 progressPanel.add(thisprogress);
2079 ((GridLayout) progressPanel.getLayout()).setRows(
2080 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2081 ++totalProgressCount;
2082 instance.validate();
2083 return thisprogress;
2086 int totalProgressCount = 0;
2088 private void removeProgressPanel(JPanel progbar)
2090 if (progressPanel != null)
2092 synchronized (progressPanel)
2094 progressPanel.remove(progbar);
2095 GridLayout gl = (GridLayout) progressPanel.getLayout();
2096 gl.setRows(gl.getRows() - 1);
2097 if (--totalProgressCount < 1)
2099 this.getContentPane().remove(progressPanel);
2100 progressPanel = null;
2107 public void stopLoading()
2110 if (fileLoadingCount < 1)
2112 while (fileLoadingPanels.size() > 0)
2114 removeProgressPanel(fileLoadingPanels.remove(0));
2116 fileLoadingPanels.clear();
2117 fileLoadingCount = 0;
2122 public static int getViewCount(String alignmentId)
2124 AlignmentViewport[] aps = getViewports(alignmentId);
2125 return (aps == null) ? 0 : aps.length;
2130 * @param alignmentId
2131 * - if null, all sets are returned
2132 * @return all AlignmentPanels concerning the alignmentId sequence set
2134 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2136 if (Desktop.desktop == null)
2138 // no frames created and in headless mode
2139 // TODO: verify that frames are recoverable when in headless mode
2142 List<AlignmentPanel> aps = new ArrayList<>();
2143 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2148 for (AlignFrame af : frames)
2150 for (AlignmentPanel ap : af.alignPanels)
2152 if (alignmentId == null
2153 || alignmentId.equals(ap.av.getSequenceSetId()))
2159 if (aps.size() == 0)
2163 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2168 * get all the viewports on an alignment.
2170 * @param sequenceSetId
2171 * unique alignment id (may be null - all viewports returned in that
2173 * @return all viewports on the alignment bound to sequenceSetId
2175 public static AlignmentViewport[] getViewports(String sequenceSetId)
2177 List<AlignmentViewport> viewp = new ArrayList<>();
2178 if (desktop != null)
2180 AlignFrame[] frames = Desktop.getDesktopAlignFrames();
2182 for (AlignFrame afr : frames)
2184 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2185 .equals(sequenceSetId))
2187 if (afr.alignPanels != null)
2189 for (AlignmentPanel ap : afr.alignPanels)
2191 if (sequenceSetId == null
2192 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2200 viewp.add(afr.getViewport());
2204 if (viewp.size() > 0)
2206 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2213 * Explode the views in the given frame into separate AlignFrame
2217 public static void explodeViews(AlignFrame af)
2219 int size = af.alignPanels.size();
2225 // FIXME: ideally should use UI interface API
2226 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2227 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2228 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2229 for (int i = 0; i < size; i++)
2231 AlignmentPanel ap = af.alignPanels.get(i);
2233 AlignFrame newaf = new AlignFrame(ap);
2235 // transfer reference for existing feature settings to new alignFrame
2236 if (ap == af.alignPanel)
2238 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2240 newaf.featureSettings = viewFeatureSettings;
2242 newaf.setFeatureSettingsGeometry(fsBounds);
2246 * Restore the view's last exploded frame geometry if known. Multiple views from
2247 * one exploded frame share and restore the same (frame) position and size.
2249 Rectangle geometry = ap.av.getExplodedGeometry();
2250 if (geometry != null)
2252 newaf.setBounds(geometry);
2255 ap.av.setGatherViewsHere(false);
2257 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2258 AlignFrame.DEFAULT_HEIGHT);
2259 // and materialise a new feature settings dialog instance for the new
2261 // (closes the old as if 'OK' was pressed)
2262 if (ap == af.alignPanel && newaf.featureSettings != null
2263 && newaf.featureSettings.isOpen()
2264 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2266 newaf.showFeatureSettingsUI();
2270 af.featureSettings = null;
2271 af.alignPanels.clear();
2272 af.closeMenuItem_actionPerformed(true);
2277 * Gather expanded views (separate AlignFrame's) with the same sequence set
2278 * identifier back in to this frame as additional views, and close the
2279 * expanded views. Note the expanded frames may themselves have multiple
2280 * views. We take the lot.
2284 public void gatherViews(AlignFrame source)
2286 source.viewport.setGatherViewsHere(true);
2287 source.viewport.setExplodedGeometry(source.getBounds());
2288 JInternalFrame[] frames = desktop.getAllFrames();
2289 String viewId = source.viewport.getSequenceSetId();
2290 for (int t = 0; t < frames.length; t++)
2292 if (frames[t] instanceof AlignFrame && frames[t] != source)
2294 AlignFrame af = (AlignFrame) frames[t];
2295 boolean gatherThis = false;
2296 for (int a = 0; a < af.alignPanels.size(); a++)
2298 AlignmentPanel ap = af.alignPanels.get(a);
2299 if (viewId.equals(ap.av.getSequenceSetId()))
2302 ap.av.setGatherViewsHere(false);
2303 ap.av.setExplodedGeometry(af.getBounds());
2304 source.addAlignmentPanel(ap, false);
2310 if (af.featureSettings != null && af.featureSettings.isOpen())
2312 if (source.featureSettings == null)
2314 // preserve the feature settings geometry for this frame
2315 source.featureSettings = af.featureSettings;
2316 source.setFeatureSettingsGeometry(
2317 af.getFeatureSettingsGeometry());
2321 // close it and forget
2322 af.featureSettings.close();
2325 af.alignPanels.clear();
2326 af.closeMenuItem_actionPerformed(true);
2331 // refresh the feature setting UI for the source frame if it exists
2332 if (source.featureSettings != null && source.featureSettings.isOpen())
2334 source.showFeatureSettingsUI();
2339 public JInternalFrame[] getAllFrames()
2341 return desktop.getAllFrames();
2345 * Checks the given url to see if it gives a response indicating that the user
2346 * should be informed of a new questionnaire.
2350 public void checkForQuestionnaire(String url)
2352 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2353 // javax.swing.SwingUtilities.invokeLater(jvq);
2354 new Thread(jvq).start();
2357 public void checkURLLinks()
2359 // Thread off the URL link checker
2360 addDialogThread(new Runnable()
2365 if (Cache.getDefault("CHECKURLLINKS", true))
2367 // check what the actual links are - if it's just the default don't
2368 // bother with the warning
2369 List<String> links = Preferences.sequenceUrlLinks
2372 // only need to check links if there is one with a
2373 // SEQUENCE_ID which is not the default EMBL_EBI link
2374 ListIterator<String> li = links.listIterator();
2375 boolean check = false;
2376 List<JLabel> urls = new ArrayList<>();
2377 while (li.hasNext())
2379 String link = li.next();
2380 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2381 && !UrlConstants.isDefaultString(link))
2384 int barPos = link.indexOf("|");
2385 String urlMsg = barPos == -1 ? link
2386 : link.substring(0, barPos) + ": "
2387 + link.substring(barPos + 1);
2388 urls.add(new JLabel(urlMsg));
2396 // ask user to check in case URL links use old style tokens
2397 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2398 JPanel msgPanel = new JPanel();
2399 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2400 msgPanel.add(Box.createVerticalGlue());
2401 JLabel msg = new JLabel(MessageManager
2402 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2403 JLabel msg2 = new JLabel(MessageManager
2404 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2406 for (JLabel url : urls)
2412 final JCheckBox jcb = new JCheckBox(
2413 MessageManager.getString("label.do_not_display_again"));
2414 jcb.addActionListener(new ActionListener()
2417 public void actionPerformed(ActionEvent e)
2419 // update Cache settings for "don't show this again"
2420 boolean showWarningAgain = !jcb.isSelected();
2421 Cache.setProperty("CHECKURLLINKS",
2422 Boolean.valueOf(showWarningAgain).toString());
2427 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2429 .getString("label.SEQUENCE_ID_no_longer_used"),
2430 JvOptionPane.WARNING_MESSAGE);
2437 * Proxy class for JDesktopPane which optionally displays the current memory
2438 * usage and highlights the desktop area with a red bar if free memory runs
2443 public class MyDesktopPane extends JDesktopPane implements Runnable
2445 private static final float ONE_MB = 1048576f;
2447 boolean showMemoryUsage = false;
2451 java.text.NumberFormat df;
2453 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2456 public MyDesktopPane(boolean showMemoryUsage)
2458 showMemoryUsage(showMemoryUsage);
2461 public void showMemoryUsage(boolean showMemory)
2463 this.showMemoryUsage = showMemory;
2466 Thread worker = new Thread(this);
2472 public boolean isShowMemoryUsage()
2474 return showMemoryUsage;
2480 df = java.text.NumberFormat.getNumberInstance();
2481 df.setMaximumFractionDigits(2);
2482 runtime = Runtime.getRuntime();
2484 while (showMemoryUsage)
2488 maxMemory = runtime.maxMemory() / ONE_MB;
2489 allocatedMemory = runtime.totalMemory() / ONE_MB;
2490 freeMemory = runtime.freeMemory() / ONE_MB;
2491 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2493 percentUsage = (totalFreeMemory / maxMemory) * 100;
2495 // if (percentUsage < 20)
2497 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2499 // instance.set.setBorder(border1);
2502 // sleep after showing usage
2504 } catch (Exception ex)
2506 ex.printStackTrace();
2512 public void paintComponent(Graphics g)
2514 if (showMemoryUsage && g != null && df != null)
2516 if (percentUsage < 20)
2518 g.setColor(Color.red);
2520 FontMetrics fm = g.getFontMetrics();
2523 g.drawString(MessageManager.formatMessage("label.memory_stats",
2525 { df.format(totalFreeMemory), df.format(maxMemory),
2526 df.format(percentUsage) }),
2527 10, getHeight() - fm.getHeight());
2531 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2532 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2537 * Accessor method to quickly get all the AlignmentFrames loaded.
2539 * @return an array of AlignFrame, or null if none found
2542 public AlignFrame[] getAlignFrames()
2544 if (desktop == null)
2549 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2555 List<AlignFrame> avp = new ArrayList<>();
2557 for (int i = frames.length - 1; i > -1; i--)
2559 if (frames[i] instanceof AlignFrame)
2561 avp.add((AlignFrame) frames[i]);
2563 else if (frames[i] instanceof SplitFrame)
2566 * Also check for a split frame containing an AlignFrame
2568 GSplitFrame sf = (GSplitFrame) frames[i];
2569 if (sf.getTopFrame() instanceof AlignFrame)
2571 avp.add((AlignFrame) sf.getTopFrame());
2573 if (sf.getBottomFrame() instanceof AlignFrame)
2575 avp.add((AlignFrame) sf.getBottomFrame());
2579 if (avp.size() == 0)
2583 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2590 public static AlignFrame[] getDesktopAlignFrames()
2592 if (Jalview.isHeadlessMode())
2594 // Desktop.desktop is null in headless mode
2595 return Jalview.getInstance().getAlignFrames();
2598 if (instance != null && desktop != null)
2600 return instance.getAlignFrames();
2607 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2611 public GStructureViewer[] getJmols()
2613 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2619 List<GStructureViewer> avp = new ArrayList<>();
2621 for (int i = frames.length - 1; i > -1; i--)
2623 if (frames[i] instanceof AppJmol)
2625 GStructureViewer af = (GStructureViewer) frames[i];
2629 if (avp.size() == 0)
2633 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2638 * Add Groovy Support to Jalview
2641 public void groovyShell_actionPerformed()
2645 openGroovyConsole();
2646 } catch (Exception ex)
2648 jalview.bin.Console.error("Groovy Console creation failed.", ex);
2649 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2651 MessageManager.getString("label.couldnt_create_groovy_shell"),
2652 MessageManager.getString("label.groovy_support_failed"),
2653 JvOptionPane.ERROR_MESSAGE);
2658 * Open the Groovy console
2660 void openGroovyConsole()
2662 if (groovyConsole == null)
2664 JalviewObjectI j = new JalviewObject(this);
2665 groovyConsole = new groovy.console.ui.Console();
2666 groovyConsole.setVariable("Jalview", j);
2667 groovyConsole.setVariable("currentAlFrame", getCurrentAlignFrame());
2668 groovyConsole.run();
2671 * We allow only one console at a time, so that AlignFrame menu option
2672 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2673 * enable 'Run script', when the console is opened, and the reverse when it is
2676 Window window = (Window) groovyConsole.getFrame();
2677 window.addWindowListener(new WindowAdapter()
2680 public void windowClosed(WindowEvent e)
2683 * rebind CMD-Q from Groovy Console to Jalview Quit
2686 enableExecuteGroovy(false);
2692 * show Groovy console window (after close and reopen)
2694 ((Window) groovyConsole.getFrame()).setVisible(true);
2697 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2698 * opening a second console
2700 enableExecuteGroovy(true);
2704 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2705 * binding when opened
2707 protected void addQuitHandler()
2710 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2712 .getKeyStroke(KeyEvent.VK_Q,
2713 jalview.util.ShortcutKeyMaskExWrapper
2714 .getMenuShortcutKeyMaskEx()),
2716 getRootPane().getActionMap().put("Quit", new AbstractAction()
2719 public void actionPerformed(ActionEvent e)
2727 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2730 * true if Groovy console is open
2732 public void enableExecuteGroovy(boolean enabled)
2735 * disable opening a second Groovy console (or re-enable when the console is
2738 groovyShell.setEnabled(!enabled);
2740 AlignFrame[] alignFrames = getDesktopAlignFrames();
2741 if (alignFrames != null)
2743 for (AlignFrame af : alignFrames)
2745 af.setGroovyEnabled(enabled);
2751 * Progress bars managed by the IProgressIndicator method.
2753 private Hashtable<Long, JPanel> progressBars;
2755 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2760 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2763 public void setProgressBar(String message, long id)
2765 if (progressBars == null)
2767 progressBars = new Hashtable<>();
2768 progressBarHandlers = new Hashtable<>();
2771 if (progressBars.get(Long.valueOf(id)) != null)
2773 JPanel panel = progressBars.remove(Long.valueOf(id));
2774 if (progressBarHandlers.contains(Long.valueOf(id)))
2776 progressBarHandlers.remove(Long.valueOf(id));
2778 removeProgressPanel(panel);
2782 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2789 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2790 * jalview.gui.IProgressIndicatorHandler)
2793 public void registerHandler(final long id,
2794 final IProgressIndicatorHandler handler)
2796 if (progressBarHandlers == null
2797 || !progressBars.containsKey(Long.valueOf(id)))
2799 throw new Error(MessageManager.getString(
2800 "error.call_setprogressbar_before_registering_handler"));
2802 progressBarHandlers.put(Long.valueOf(id), handler);
2803 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2804 if (handler.canCancel())
2806 JButton cancel = new JButton(
2807 MessageManager.getString("action.cancel"));
2808 final IProgressIndicator us = this;
2809 cancel.addActionListener(new ActionListener()
2813 public void actionPerformed(ActionEvent e)
2815 handler.cancelActivity(id);
2816 us.setProgressBar(MessageManager
2817 .formatMessage("label.cancelled_params", new Object[]
2818 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2822 progressPanel.add(cancel, BorderLayout.EAST);
2828 * @return true if any progress bars are still active
2831 public boolean operationInProgress()
2833 if (progressBars != null && progressBars.size() > 0)
2841 * This will return the first AlignFrame holding the given viewport instance.
2842 * It will break if there are more than one AlignFrames viewing a particular
2846 * @return alignFrame for viewport
2848 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2850 if (desktop != null)
2852 AlignmentPanel[] aps = getAlignmentPanels(
2853 viewport.getSequenceSetId());
2854 for (int panel = 0; aps != null && panel < aps.length; panel++)
2856 if (aps[panel] != null && aps[panel].av == viewport)
2858 return aps[panel].alignFrame;
2865 public VamsasApplication getVamsasApplication()
2867 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2873 * flag set if jalview GUI is being operated programmatically
2875 private boolean inBatchMode = false;
2878 * check if jalview GUI is being operated programmatically
2880 * @return inBatchMode
2882 public boolean isInBatchMode()
2888 * set flag if jalview GUI is being operated programmatically
2890 * @param inBatchMode
2892 public void setInBatchMode(boolean inBatchMode)
2894 this.inBatchMode = inBatchMode;
2898 * start service discovery and wait till it is done
2900 public void startServiceDiscovery()
2902 startServiceDiscovery(false);
2906 * start service discovery threads - blocking or non-blocking
2910 public void startServiceDiscovery(boolean blocking)
2912 startServiceDiscovery(blocking, false);
2916 * start service discovery threads
2919 * - false means call returns immediately
2920 * @param ignore_SHOW_JWS2_SERVICES_preference
2921 * - when true JABA services are discovered regardless of user's JWS2
2922 * discovery preference setting
2924 public void startServiceDiscovery(boolean blocking,
2925 boolean ignore_SHOW_JWS2_SERVICES_preference)
2927 boolean alive = true;
2928 Thread t0 = null, t1 = null, t2 = null;
2929 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2932 // todo: changesupport handlers need to be transferred
2933 if (discoverer == null)
2935 discoverer = new jalview.ws.jws1.Discoverer();
2936 // register PCS handler for desktop.
2937 discoverer.addPropertyChangeListener(changeSupport);
2939 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2940 // until we phase out completely
2941 (t0 = new Thread(discoverer)).start();
2944 if (ignore_SHOW_JWS2_SERVICES_preference
2945 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2947 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2948 .startDiscoverer(changeSupport);
2952 // TODO: do rest service discovery
2961 } catch (Exception e)
2964 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2965 || (t3 != null && t3.isAlive())
2966 || (t0 != null && t0.isAlive());
2972 * called to check if the service discovery process completed successfully.
2976 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2978 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2980 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2981 .getErrorMessages();
2984 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2986 if (serviceChangedDialog == null)
2988 // only run if we aren't already displaying one of these.
2989 addDialogThread(serviceChangedDialog = new Runnable()
2996 * JalviewDialog jd =new JalviewDialog() {
2998 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3000 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3002 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3004 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3006 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3007 * + " or mis-configured HTTP proxy settings.<br/>" +
3008 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3009 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3010 * true, true, "Web Service Configuration Problem", 450, 400);
3012 * jd.waitForInput();
3014 JvOptionPane.showConfirmDialog(Desktop.desktop,
3015 new JLabel("<html><table width=\"450\"><tr><td>"
3016 + ermsg + "</td></tr></table>"
3017 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3018 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3019 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3020 + " Tools->Preferences dialog box to change them.</p></html>"),
3021 "Web Service Configuration Problem",
3022 JvOptionPane.DEFAULT_OPTION,
3023 JvOptionPane.ERROR_MESSAGE);
3024 serviceChangedDialog = null;
3032 jalview.bin.Console.error(
3033 "Errors reported by JABA discovery service. Check web services preferences.\n"
3040 private Runnable serviceChangedDialog = null;
3043 * start a thread to open a URL in the configured browser. Pops up a warning
3044 * dialog to the user if there is an exception when calling out to the browser
3049 public static void showUrl(final String url)
3051 showUrl(url, Desktop.instance);
3055 * Like showUrl but allows progress handler to be specified
3059 * (null) or object implementing IProgressIndicator
3061 public static void showUrl(final String url,
3062 final IProgressIndicator progress)
3064 new Thread(new Runnable()
3071 if (progress != null)
3073 progress.setProgressBar(MessageManager
3074 .formatMessage("status.opening_params", new Object[]
3075 { url }), this.hashCode());
3077 jalview.util.BrowserLauncher.openURL(url);
3078 } catch (Exception ex)
3080 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3082 .getString("label.web_browser_not_found_unix"),
3083 MessageManager.getString("label.web_browser_not_found"),
3084 JvOptionPane.WARNING_MESSAGE);
3086 ex.printStackTrace();
3088 if (progress != null)
3090 progress.setProgressBar(null, this.hashCode());
3096 public static WsParamSetManager wsparamManager = null;
3098 public static ParamManager getUserParameterStore()
3100 if (wsparamManager == null)
3102 wsparamManager = new WsParamSetManager();
3104 return wsparamManager;
3108 * static hyperlink handler proxy method for use by Jalview's internal windows
3112 public static void hyperlinkUpdate(HyperlinkEvent e)
3114 if (e.getEventType() == EventType.ACTIVATED)
3119 url = e.getURL().toString();
3120 Desktop.showUrl(url);
3121 } catch (Exception x)
3126 .error("Couldn't handle string " + url + " as a URL.");
3128 // ignore any exceptions due to dud links.
3135 * single thread that handles display of dialogs to user.
3137 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3140 * flag indicating if dialogExecutor should try to acquire a permit
3142 private volatile boolean dialogPause = true;
3147 private Semaphore block = new Semaphore(0);
3149 private static groovy.console.ui.Console groovyConsole;
3152 * add another dialog thread to the queue
3156 public void addDialogThread(final Runnable prompter)
3158 dialogExecutor.submit(new Runnable()
3165 acquireDialogQueue();
3167 if (instance == null)
3173 SwingUtilities.invokeAndWait(prompter);
3174 } catch (Exception q)
3176 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3183 private boolean dialogQueueStarted = false;
3185 public void startDialogQueue()
3187 if (dialogQueueStarted)
3191 // set the flag so we don't pause waiting for another permit and semaphore
3192 // the current task to begin
3193 releaseDialogQueue();
3194 dialogQueueStarted = true;
3197 public void acquireDialogQueue()
3203 } catch (InterruptedException e)
3205 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3210 public void releaseDialogQueue()
3217 dialogPause = false;
3221 * Outputs an image of the desktop to file in EPS format, after prompting the
3222 * user for choice of Text or Lineart character rendering (unless a preference
3223 * has been set). The file name is generated as
3226 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3230 protected void snapShotWindow_actionPerformed(ActionEvent e)
3232 // currently the menu option to do this is not shown
3235 int width = getWidth();
3236 int height = getHeight();
3238 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3239 ImageWriterI writer = new ImageWriterI()
3242 public void exportImage(Graphics g) throws Exception
3245 jalview.bin.Console.info("Successfully written snapshot to file "
3246 + of.getAbsolutePath());
3249 String title = "View of desktop";
3250 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3254 exporter.doExport(of, this, width, height, title);
3255 } catch (ImageOutputException ioex)
3257 jalview.bin.Console.error(
3258 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3264 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3265 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3266 * and location last time the view was expanded (if any). However it does not
3267 * remember the split pane divider location - this is set to match the
3268 * 'exploding' frame.
3272 public void explodeViews(SplitFrame sf)
3274 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3275 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3276 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3278 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3280 int viewCount = topPanels.size();
3287 * Processing in reverse order works, forwards order leaves the first panels not
3288 * visible. I don't know why!
3290 for (int i = viewCount - 1; i >= 0; i--)
3293 * Make new top and bottom frames. These take over the respective AlignmentPanel
3294 * objects, including their AlignmentViewports, so the cdna/protein
3295 * relationships between the viewports is carried over to the new split frames.
3297 * explodedGeometry holds the (x, y) position of the previously exploded
3298 * SplitFrame, and the (width, height) of the AlignFrame component
3300 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3301 AlignFrame newTopFrame = new AlignFrame(topPanel);
3302 newTopFrame.setSize(oldTopFrame.getSize());
3303 newTopFrame.setVisible(true);
3304 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3305 .getExplodedGeometry();
3306 if (geometry != null)
3308 newTopFrame.setSize(geometry.getSize());
3311 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3312 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3313 newBottomFrame.setSize(oldBottomFrame.getSize());
3314 newBottomFrame.setVisible(true);
3315 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3316 .getExplodedGeometry();
3317 if (geometry != null)
3319 newBottomFrame.setSize(geometry.getSize());
3322 topPanel.av.setGatherViewsHere(false);
3323 bottomPanel.av.setGatherViewsHere(false);
3324 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3326 if (geometry != null)
3328 splitFrame.setLocation(geometry.getLocation());
3330 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3334 * Clear references to the panels (now relocated in the new SplitFrames) before
3335 * closing the old SplitFrame.
3338 bottomPanels.clear();
3343 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3344 * back into the given SplitFrame as additional views. Note that the gathered
3345 * frames may themselves have multiple views.
3349 public void gatherViews(GSplitFrame source)
3352 * special handling of explodedGeometry for a view within a SplitFrame: - it
3353 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3354 * height) of the AlignFrame component
3356 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3357 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3358 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3359 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3360 myBottomFrame.viewport
3361 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3362 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3363 myTopFrame.viewport.setGatherViewsHere(true);
3364 myBottomFrame.viewport.setGatherViewsHere(true);
3365 String topViewId = myTopFrame.viewport.getSequenceSetId();
3366 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3368 JInternalFrame[] frames = desktop.getAllFrames();
3369 for (JInternalFrame frame : frames)
3371 if (frame instanceof SplitFrame && frame != source)
3373 SplitFrame sf = (SplitFrame) frame;
3374 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3375 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3376 boolean gatherThis = false;
3377 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3379 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3380 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3381 if (topViewId.equals(topPanel.av.getSequenceSetId())
3382 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3385 topPanel.av.setGatherViewsHere(false);
3386 bottomPanel.av.setGatherViewsHere(false);
3387 topPanel.av.setExplodedGeometry(
3388 new Rectangle(sf.getLocation(), topFrame.getSize()));
3389 bottomPanel.av.setExplodedGeometry(
3390 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3391 myTopFrame.addAlignmentPanel(topPanel, false);
3392 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3398 topFrame.getAlignPanels().clear();
3399 bottomFrame.getAlignPanels().clear();
3406 * The dust settles...give focus to the tab we did this from.
3408 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3411 public static groovy.console.ui.Console getGroovyConsole()
3413 return groovyConsole;
3417 * handles the payload of a drag and drop event.
3419 * TODO refactor to desktop utilities class
3422 * - Data source strings extracted from the drop event
3424 * - protocol for each data source extracted from the drop event
3428 * - the payload from the drop event
3431 public static void transferFromDropTarget(List<Object> files,
3432 List<DataSourceType> protocols, DropTargetDropEvent evt,
3433 Transferable t) throws Exception
3436 DataFlavor uriListFlavor = new DataFlavor(
3437 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3440 urlFlavour = new DataFlavor(
3441 "application/x-java-url; class=java.net.URL");
3442 } catch (ClassNotFoundException cfe)
3444 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3448 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3453 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3454 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3455 // means url may be null.
3458 protocols.add(DataSourceType.URL);
3459 files.add(url.toString());
3460 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3461 + files.get(files.size() - 1));
3466 if (Platform.isAMacAndNotJS())
3468 jalview.bin.Console.errPrintln(
3469 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3472 } catch (Throwable ex)
3474 jalview.bin.Console.debug("URL drop handler failed.", ex);
3477 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3479 // Works on Windows and MacOSX
3480 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3481 for (Object file : (List) t
3482 .getTransferData(DataFlavor.javaFileListFlavor))
3485 protocols.add(DataSourceType.FILE);
3490 // Unix like behaviour
3491 boolean added = false;
3493 if (t.isDataFlavorSupported(uriListFlavor))
3495 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3496 // This is used by Unix drag system
3497 data = (String) t.getTransferData(uriListFlavor);
3501 // fallback to text: workaround - on OSX where there's a JVM bug
3503 .debug("standard URIListFlavor failed. Trying text");
3504 // try text fallback
3505 DataFlavor textDf = new DataFlavor(
3506 "text/plain;class=java.lang.String");
3507 if (t.isDataFlavorSupported(textDf))
3509 data = (String) t.getTransferData(textDf);
3512 jalview.bin.Console.debug("Plain text drop content returned "
3513 + (data == null ? "Null - failed" : data));
3518 while (protocols.size() < files.size())
3520 jalview.bin.Console.debug("Adding missing FILE protocol for "
3521 + files.get(protocols.size()));
3522 protocols.add(DataSourceType.FILE);
3524 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3525 data, "\r\n"); st.hasMoreTokens();)
3528 String s = st.nextToken();
3529 if (s.startsWith("#"))
3531 // the line is a comment (as per the RFC 2483)
3534 java.net.URI uri = new java.net.URI(s);
3535 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3537 protocols.add(DataSourceType.URL);
3538 files.add(uri.toString());
3542 // otherwise preserve old behaviour: catch all for file objects
3543 java.io.File file = new java.io.File(uri);
3544 protocols.add(DataSourceType.FILE);
3545 files.add(file.toString());
3550 if (jalview.bin.Console.isDebugEnabled())
3552 if (data == null || !added)
3555 if (t.getTransferDataFlavors() != null
3556 && t.getTransferDataFlavors().length > 0)
3558 jalview.bin.Console.debug(
3559 "Couldn't resolve drop data. Here are the supported flavors:");
3560 for (DataFlavor fl : t.getTransferDataFlavors())
3562 jalview.bin.Console.debug(
3563 "Supported transfer dataflavor: " + fl.toString());
3564 Object df = t.getTransferData(fl);
3567 jalview.bin.Console.debug("Retrieves: " + df);
3571 jalview.bin.Console.debug("Retrieved nothing");
3578 .debug("Couldn't resolve dataflavor for drop: "
3584 if (Platform.isWindowsAndNotJS())
3587 .debug("Scanning dropped content for Windows Link Files");
3589 // resolve any .lnk files in the file drop
3590 for (int f = 0; f < files.size(); f++)
3592 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3593 if (protocols.get(f).equals(DataSourceType.FILE)
3594 && (source.endsWith(".lnk") || source.endsWith(".url")
3595 || source.endsWith(".site")))
3599 Object obj = files.get(f);
3600 File lf = (obj instanceof File ? (File) obj
3601 : new File((String) obj));
3602 // process link file to get a URL
3603 jalview.bin.Console.debug("Found potential link file: " + lf);
3604 WindowsShortcut wscfile = new WindowsShortcut(lf);
3605 String fullname = wscfile.getRealFilename();
3606 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3607 files.set(f, fullname);
3608 jalview.bin.Console.debug("Parsed real filename " + fullname
3609 + " to extract protocol: " + protocols.get(f));
3610 } catch (Exception ex)
3612 jalview.bin.Console.error(
3613 "Couldn't parse " + files.get(f) + " as a link file.",
3622 * Sets the Preferences property for experimental features to True or False
3623 * depending on the state of the controlling menu item
3626 protected void showExperimental_actionPerformed(boolean selected)
3628 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3632 * Answers a (possibly empty) list of any structure viewer frames (currently
3633 * for either Jmol or Chimera) which are currently open. This may optionally
3634 * be restricted to viewers of a specified class, or viewers linked to a
3635 * specified alignment panel.
3638 * if not null, only return viewers linked to this panel
3639 * @param structureViewerClass
3640 * if not null, only return viewers of this class
3643 public List<StructureViewerBase> getStructureViewers(
3644 AlignmentPanel apanel,
3645 Class<? extends StructureViewerBase> structureViewerClass)
3647 List<StructureViewerBase> result = new ArrayList<>();
3648 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3650 for (JInternalFrame frame : frames)
3652 if (frame instanceof StructureViewerBase)
3654 if (structureViewerClass == null
3655 || structureViewerClass.isInstance(frame))
3658 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3660 result.add((StructureViewerBase) frame);
3668 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3670 private static boolean debugScaleMessageDone = false;
3672 public static void debugScaleMessage(Graphics g)
3674 if (debugScaleMessageDone)
3678 // output used by tests to check HiDPI scaling settings in action
3681 Graphics2D gg = (Graphics2D) g;
3684 AffineTransform t = gg.getTransform();
3685 double scaleX = t.getScaleX();
3686 double scaleY = t.getScaleY();
3687 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3688 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3689 debugScaleMessageDone = true;
3693 jalview.bin.Console.debug("Desktop graphics null");
3695 } catch (Exception e)
3697 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3702 * closes the current instance window, disposes and forgets about it.
3704 public static void closeDesktop()
3706 if (Desktop.instance != null)
3708 Desktop.instance.closeAll_actionPerformed(null);
3709 Desktop.instance.setVisible(false);
3710 Desktop us = Desktop.instance;
3711 Desktop.instance = null;
3712 // call dispose in a separate thread - try to avoid indirect deadlocks
3713 new Thread(new Runnable()
3718 ExecutorService dex = us.dialogExecutor;
3722 us.dialogExecutor = null;
3723 us.block.drainPermits();
3732 * checks if any progress bars are being displayed in any of the windows
3733 * managed by the desktop
3737 public boolean operationsAreInProgress()
3739 JInternalFrame[] frames = getAllFrames();
3740 for (JInternalFrame frame : frames)
3742 if (frame instanceof IProgressIndicator)
3744 if (((IProgressIndicator) frame).operationInProgress())
3750 return operationInProgress();
3754 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3755 * The way the modal JInternalFrame is made means it cannot be a child of an
3756 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3758 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3760 protected static void addModal(AlignFrame af, JInternalFrame jif)
3762 alignFrameModalMap.put(af, jif);
3765 protected static void closeModal(AlignFrame af)
3767 if (!alignFrameModalMap.containsKey(af))
3771 JInternalFrame jif = alignFrameModalMap.get(af);
3776 jif.setClosed(true);
3777 } catch (PropertyVetoException e)
3779 e.printStackTrace();
3782 alignFrameModalMap.remove(af);
3785 public void nonBlockingDialog(String title, String message, String button,
3786 int type, boolean scrollable, boolean modal)
3788 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3792 public void nonBlockingDialog(String title, String message,
3793 String boxtext, String button, int type, boolean scrollable,
3794 boolean html, boolean modal, int timeout)
3796 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3797 scrollable, html, modal, timeout);
3800 public void nonBlockingDialog(int width, int height, String title,
3801 String message, String boxtext, String button, int type,
3802 boolean scrollable, boolean html, boolean modal, int timeout)
3806 type = JvOptionPane.WARNING_MESSAGE;
3808 JLabel jl = new JLabel(message);
3810 JTextComponent jtc = null;
3813 JTextPane jtp = new JTextPane();
3814 jtp.setContentType("text/html");
3815 jtp.setEditable(false);
3816 jtp.setAutoscrolls(true);
3817 jtp.setText(boxtext);
3823 JTextArea jta = new JTextArea(height, width);
3824 // jta.setLineWrap(true);
3825 jta.setEditable(false);
3826 jta.setWrapStyleWord(true);
3827 jta.setAutoscrolls(true);
3828 jta.setText(boxtext);
3833 JScrollPane jsp = scrollable
3834 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3835 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3838 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3840 JPanel jp = new JPanel();
3841 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3843 if (message != null)
3845 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3848 if (boxtext != null)
3852 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3857 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3862 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3864 jvp.setTimeout(timeout);
3865 JButton jb = new JButton(button);
3866 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3868 { button }, button, modal, new JButton[] { jb }, false);
3872 public AlignFrame getCurrentAlignFrame()
3874 return Jalview.getInstance().getCurrentAlignFrame();