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(JalviewObjectI.jalviewObjectName, j);
2667 groovyConsole.setVariable(JalviewObjectI.currentAlFrameName,
2668 getCurrentAlignFrame());
2669 groovyConsole.run();
2672 * We allow only one console at a time, so that AlignFrame menu option
2673 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2674 * enable 'Run script', when the console is opened, and the reverse when it is
2677 Window window = (Window) groovyConsole.getFrame();
2678 window.addWindowListener(new WindowAdapter()
2681 public void windowClosed(WindowEvent e)
2684 * rebind CMD-Q from Groovy Console to Jalview Quit
2687 enableExecuteGroovy(false);
2693 * show Groovy console window (after close and reopen)
2695 ((Window) groovyConsole.getFrame()).setVisible(true);
2698 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2699 * opening a second console
2701 enableExecuteGroovy(true);
2705 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2706 * binding when opened
2708 protected void addQuitHandler()
2711 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2713 .getKeyStroke(KeyEvent.VK_Q,
2714 jalview.util.ShortcutKeyMaskExWrapper
2715 .getMenuShortcutKeyMaskEx()),
2717 getRootPane().getActionMap().put("Quit", new AbstractAction()
2720 public void actionPerformed(ActionEvent e)
2728 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2731 * true if Groovy console is open
2733 public void enableExecuteGroovy(boolean enabled)
2736 * disable opening a second Groovy console (or re-enable when the console is
2739 groovyShell.setEnabled(!enabled);
2741 AlignFrame[] alignFrames = getDesktopAlignFrames();
2742 if (alignFrames != null)
2744 for (AlignFrame af : alignFrames)
2746 af.setGroovyEnabled(enabled);
2752 * Progress bars managed by the IProgressIndicator method.
2754 private Hashtable<Long, JPanel> progressBars;
2756 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2761 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2764 public void setProgressBar(String message, long id)
2766 if (progressBars == null)
2768 progressBars = new Hashtable<>();
2769 progressBarHandlers = new Hashtable<>();
2772 if (progressBars.get(Long.valueOf(id)) != null)
2774 JPanel panel = progressBars.remove(Long.valueOf(id));
2775 if (progressBarHandlers.contains(Long.valueOf(id)))
2777 progressBarHandlers.remove(Long.valueOf(id));
2779 removeProgressPanel(panel);
2783 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2790 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2791 * jalview.gui.IProgressIndicatorHandler)
2794 public void registerHandler(final long id,
2795 final IProgressIndicatorHandler handler)
2797 if (progressBarHandlers == null
2798 || !progressBars.containsKey(Long.valueOf(id)))
2800 throw new Error(MessageManager.getString(
2801 "error.call_setprogressbar_before_registering_handler"));
2803 progressBarHandlers.put(Long.valueOf(id), handler);
2804 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2805 if (handler.canCancel())
2807 JButton cancel = new JButton(
2808 MessageManager.getString("action.cancel"));
2809 final IProgressIndicator us = this;
2810 cancel.addActionListener(new ActionListener()
2814 public void actionPerformed(ActionEvent e)
2816 handler.cancelActivity(id);
2817 us.setProgressBar(MessageManager
2818 .formatMessage("label.cancelled_params", new Object[]
2819 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2823 progressPanel.add(cancel, BorderLayout.EAST);
2829 * @return true if any progress bars are still active
2832 public boolean operationInProgress()
2834 if (progressBars != null && progressBars.size() > 0)
2842 * This will return the first AlignFrame holding the given viewport instance.
2843 * It will break if there are more than one AlignFrames viewing a particular
2847 * @return alignFrame for viewport
2849 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2851 if (desktop != null)
2853 AlignmentPanel[] aps = getAlignmentPanels(
2854 viewport.getSequenceSetId());
2855 for (int panel = 0; aps != null && panel < aps.length; panel++)
2857 if (aps[panel] != null && aps[panel].av == viewport)
2859 return aps[panel].alignFrame;
2866 public VamsasApplication getVamsasApplication()
2868 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2874 * flag set if jalview GUI is being operated programmatically
2876 private boolean inBatchMode = false;
2879 * check if jalview GUI is being operated programmatically
2881 * @return inBatchMode
2883 public boolean isInBatchMode()
2889 * set flag if jalview GUI is being operated programmatically
2891 * @param inBatchMode
2893 public void setInBatchMode(boolean inBatchMode)
2895 this.inBatchMode = inBatchMode;
2899 * start service discovery and wait till it is done
2901 public void startServiceDiscovery()
2903 startServiceDiscovery(false);
2907 * start service discovery threads - blocking or non-blocking
2911 public void startServiceDiscovery(boolean blocking)
2913 startServiceDiscovery(blocking, false);
2917 * start service discovery threads
2920 * - false means call returns immediately
2921 * @param ignore_SHOW_JWS2_SERVICES_preference
2922 * - when true JABA services are discovered regardless of user's JWS2
2923 * discovery preference setting
2925 public void startServiceDiscovery(boolean blocking,
2926 boolean ignore_SHOW_JWS2_SERVICES_preference)
2928 boolean alive = true;
2929 Thread t0 = null, t1 = null, t2 = null;
2930 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2933 // todo: changesupport handlers need to be transferred
2934 if (discoverer == null)
2936 discoverer = new jalview.ws.jws1.Discoverer();
2937 // register PCS handler for desktop.
2938 discoverer.addPropertyChangeListener(changeSupport);
2940 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2941 // until we phase out completely
2942 (t0 = new Thread(discoverer)).start();
2945 if (ignore_SHOW_JWS2_SERVICES_preference
2946 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2948 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2949 .startDiscoverer(changeSupport);
2953 // TODO: do rest service discovery
2962 } catch (Exception e)
2965 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2966 || (t3 != null && t3.isAlive())
2967 || (t0 != null && t0.isAlive());
2973 * called to check if the service discovery process completed successfully.
2977 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2979 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2981 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2982 .getErrorMessages();
2985 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2987 if (serviceChangedDialog == null)
2989 // only run if we aren't already displaying one of these.
2990 addDialogThread(serviceChangedDialog = new Runnable()
2997 * JalviewDialog jd =new JalviewDialog() {
2999 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3001 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3003 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3005 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3007 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3008 * + " or mis-configured HTTP proxy settings.<br/>" +
3009 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3010 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3011 * true, true, "Web Service Configuration Problem", 450, 400);
3013 * jd.waitForInput();
3015 JvOptionPane.showConfirmDialog(Desktop.desktop,
3016 new JLabel("<html><table width=\"450\"><tr><td>"
3017 + ermsg + "</td></tr></table>"
3018 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3019 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3020 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3021 + " Tools->Preferences dialog box to change them.</p></html>"),
3022 "Web Service Configuration Problem",
3023 JvOptionPane.DEFAULT_OPTION,
3024 JvOptionPane.ERROR_MESSAGE);
3025 serviceChangedDialog = null;
3033 jalview.bin.Console.error(
3034 "Errors reported by JABA discovery service. Check web services preferences.\n"
3041 private Runnable serviceChangedDialog = null;
3044 * start a thread to open a URL in the configured browser. Pops up a warning
3045 * dialog to the user if there is an exception when calling out to the browser
3050 public static void showUrl(final String url)
3052 showUrl(url, Desktop.instance);
3056 * Like showUrl but allows progress handler to be specified
3060 * (null) or object implementing IProgressIndicator
3062 public static void showUrl(final String url,
3063 final IProgressIndicator progress)
3065 new Thread(new Runnable()
3072 if (progress != null)
3074 progress.setProgressBar(MessageManager
3075 .formatMessage("status.opening_params", new Object[]
3076 { url }), this.hashCode());
3078 jalview.util.BrowserLauncher.openURL(url);
3079 } catch (Exception ex)
3081 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3083 .getString("label.web_browser_not_found_unix"),
3084 MessageManager.getString("label.web_browser_not_found"),
3085 JvOptionPane.WARNING_MESSAGE);
3087 ex.printStackTrace();
3089 if (progress != null)
3091 progress.setProgressBar(null, this.hashCode());
3097 public static WsParamSetManager wsparamManager = null;
3099 public static ParamManager getUserParameterStore()
3101 if (wsparamManager == null)
3103 wsparamManager = new WsParamSetManager();
3105 return wsparamManager;
3109 * static hyperlink handler proxy method for use by Jalview's internal windows
3113 public static void hyperlinkUpdate(HyperlinkEvent e)
3115 if (e.getEventType() == EventType.ACTIVATED)
3120 url = e.getURL().toString();
3121 Desktop.showUrl(url);
3122 } catch (Exception x)
3127 .error("Couldn't handle string " + url + " as a URL.");
3129 // ignore any exceptions due to dud links.
3136 * single thread that handles display of dialogs to user.
3138 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3141 * flag indicating if dialogExecutor should try to acquire a permit
3143 private volatile boolean dialogPause = true;
3148 private Semaphore block = new Semaphore(0);
3150 private static groovy.console.ui.Console groovyConsole;
3153 * add another dialog thread to the queue
3157 public void addDialogThread(final Runnable prompter)
3159 dialogExecutor.submit(new Runnable()
3166 acquireDialogQueue();
3168 if (instance == null)
3174 SwingUtilities.invokeAndWait(prompter);
3175 } catch (Exception q)
3177 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3184 private boolean dialogQueueStarted = false;
3186 public void startDialogQueue()
3188 if (dialogQueueStarted)
3192 // set the flag so we don't pause waiting for another permit and semaphore
3193 // the current task to begin
3194 releaseDialogQueue();
3195 dialogQueueStarted = true;
3198 public void acquireDialogQueue()
3204 } catch (InterruptedException e)
3206 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3211 public void releaseDialogQueue()
3218 dialogPause = false;
3222 * Outputs an image of the desktop to file in EPS format, after prompting the
3223 * user for choice of Text or Lineart character rendering (unless a preference
3224 * has been set). The file name is generated as
3227 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3231 protected void snapShotWindow_actionPerformed(ActionEvent e)
3233 // currently the menu option to do this is not shown
3236 int width = getWidth();
3237 int height = getHeight();
3239 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3240 ImageWriterI writer = new ImageWriterI()
3243 public void exportImage(Graphics g) throws Exception
3246 jalview.bin.Console.info("Successfully written snapshot to file "
3247 + of.getAbsolutePath());
3250 String title = "View of desktop";
3251 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3255 exporter.doExport(of, this, width, height, title);
3256 } catch (ImageOutputException ioex)
3258 jalview.bin.Console.error(
3259 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3265 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3266 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3267 * and location last time the view was expanded (if any). However it does not
3268 * remember the split pane divider location - this is set to match the
3269 * 'exploding' frame.
3273 public void explodeViews(SplitFrame sf)
3275 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3276 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3277 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3279 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3281 int viewCount = topPanels.size();
3288 * Processing in reverse order works, forwards order leaves the first panels not
3289 * visible. I don't know why!
3291 for (int i = viewCount - 1; i >= 0; i--)
3294 * Make new top and bottom frames. These take over the respective AlignmentPanel
3295 * objects, including their AlignmentViewports, so the cdna/protein
3296 * relationships between the viewports is carried over to the new split frames.
3298 * explodedGeometry holds the (x, y) position of the previously exploded
3299 * SplitFrame, and the (width, height) of the AlignFrame component
3301 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3302 AlignFrame newTopFrame = new AlignFrame(topPanel);
3303 newTopFrame.setSize(oldTopFrame.getSize());
3304 newTopFrame.setVisible(true);
3305 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3306 .getExplodedGeometry();
3307 if (geometry != null)
3309 newTopFrame.setSize(geometry.getSize());
3312 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3313 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3314 newBottomFrame.setSize(oldBottomFrame.getSize());
3315 newBottomFrame.setVisible(true);
3316 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3317 .getExplodedGeometry();
3318 if (geometry != null)
3320 newBottomFrame.setSize(geometry.getSize());
3323 topPanel.av.setGatherViewsHere(false);
3324 bottomPanel.av.setGatherViewsHere(false);
3325 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3327 if (geometry != null)
3329 splitFrame.setLocation(geometry.getLocation());
3331 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3335 * Clear references to the panels (now relocated in the new SplitFrames) before
3336 * closing the old SplitFrame.
3339 bottomPanels.clear();
3344 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3345 * back into the given SplitFrame as additional views. Note that the gathered
3346 * frames may themselves have multiple views.
3350 public void gatherViews(GSplitFrame source)
3353 * special handling of explodedGeometry for a view within a SplitFrame: - it
3354 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3355 * height) of the AlignFrame component
3357 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3358 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3359 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3360 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3361 myBottomFrame.viewport
3362 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3363 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3364 myTopFrame.viewport.setGatherViewsHere(true);
3365 myBottomFrame.viewport.setGatherViewsHere(true);
3366 String topViewId = myTopFrame.viewport.getSequenceSetId();
3367 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3369 JInternalFrame[] frames = desktop.getAllFrames();
3370 for (JInternalFrame frame : frames)
3372 if (frame instanceof SplitFrame && frame != source)
3374 SplitFrame sf = (SplitFrame) frame;
3375 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3376 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3377 boolean gatherThis = false;
3378 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3380 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3381 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3382 if (topViewId.equals(topPanel.av.getSequenceSetId())
3383 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3386 topPanel.av.setGatherViewsHere(false);
3387 bottomPanel.av.setGatherViewsHere(false);
3388 topPanel.av.setExplodedGeometry(
3389 new Rectangle(sf.getLocation(), topFrame.getSize()));
3390 bottomPanel.av.setExplodedGeometry(
3391 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3392 myTopFrame.addAlignmentPanel(topPanel, false);
3393 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3399 topFrame.getAlignPanels().clear();
3400 bottomFrame.getAlignPanels().clear();
3407 * The dust settles...give focus to the tab we did this from.
3409 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3412 public static groovy.console.ui.Console getGroovyConsole()
3414 return groovyConsole;
3418 * handles the payload of a drag and drop event.
3420 * TODO refactor to desktop utilities class
3423 * - Data source strings extracted from the drop event
3425 * - protocol for each data source extracted from the drop event
3429 * - the payload from the drop event
3432 public static void transferFromDropTarget(List<Object> files,
3433 List<DataSourceType> protocols, DropTargetDropEvent evt,
3434 Transferable t) throws Exception
3437 DataFlavor uriListFlavor = new DataFlavor(
3438 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3441 urlFlavour = new DataFlavor(
3442 "application/x-java-url; class=java.net.URL");
3443 } catch (ClassNotFoundException cfe)
3445 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3449 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3454 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3455 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3456 // means url may be null.
3459 protocols.add(DataSourceType.URL);
3460 files.add(url.toString());
3461 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3462 + files.get(files.size() - 1));
3467 if (Platform.isAMacAndNotJS())
3469 jalview.bin.Console.errPrintln(
3470 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3473 } catch (Throwable ex)
3475 jalview.bin.Console.debug("URL drop handler failed.", ex);
3478 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3480 // Works on Windows and MacOSX
3481 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3482 for (Object file : (List) t
3483 .getTransferData(DataFlavor.javaFileListFlavor))
3486 protocols.add(DataSourceType.FILE);
3491 // Unix like behaviour
3492 boolean added = false;
3494 if (t.isDataFlavorSupported(uriListFlavor))
3496 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3497 // This is used by Unix drag system
3498 data = (String) t.getTransferData(uriListFlavor);
3502 // fallback to text: workaround - on OSX where there's a JVM bug
3504 .debug("standard URIListFlavor failed. Trying text");
3505 // try text fallback
3506 DataFlavor textDf = new DataFlavor(
3507 "text/plain;class=java.lang.String");
3508 if (t.isDataFlavorSupported(textDf))
3510 data = (String) t.getTransferData(textDf);
3513 jalview.bin.Console.debug("Plain text drop content returned "
3514 + (data == null ? "Null - failed" : data));
3519 while (protocols.size() < files.size())
3521 jalview.bin.Console.debug("Adding missing FILE protocol for "
3522 + files.get(protocols.size()));
3523 protocols.add(DataSourceType.FILE);
3525 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3526 data, "\r\n"); st.hasMoreTokens();)
3529 String s = st.nextToken();
3530 if (s.startsWith("#"))
3532 // the line is a comment (as per the RFC 2483)
3535 java.net.URI uri = new java.net.URI(s);
3536 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3538 protocols.add(DataSourceType.URL);
3539 files.add(uri.toString());
3543 // otherwise preserve old behaviour: catch all for file objects
3544 java.io.File file = new java.io.File(uri);
3545 protocols.add(DataSourceType.FILE);
3546 files.add(file.toString());
3551 if (jalview.bin.Console.isDebugEnabled())
3553 if (data == null || !added)
3556 if (t.getTransferDataFlavors() != null
3557 && t.getTransferDataFlavors().length > 0)
3559 jalview.bin.Console.debug(
3560 "Couldn't resolve drop data. Here are the supported flavors:");
3561 for (DataFlavor fl : t.getTransferDataFlavors())
3563 jalview.bin.Console.debug(
3564 "Supported transfer dataflavor: " + fl.toString());
3565 Object df = t.getTransferData(fl);
3568 jalview.bin.Console.debug("Retrieves: " + df);
3572 jalview.bin.Console.debug("Retrieved nothing");
3579 .debug("Couldn't resolve dataflavor for drop: "
3585 if (Platform.isWindowsAndNotJS())
3588 .debug("Scanning dropped content for Windows Link Files");
3590 // resolve any .lnk files in the file drop
3591 for (int f = 0; f < files.size(); f++)
3593 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3594 if (protocols.get(f).equals(DataSourceType.FILE)
3595 && (source.endsWith(".lnk") || source.endsWith(".url")
3596 || source.endsWith(".site")))
3600 Object obj = files.get(f);
3601 File lf = (obj instanceof File ? (File) obj
3602 : new File((String) obj));
3603 // process link file to get a URL
3604 jalview.bin.Console.debug("Found potential link file: " + lf);
3605 WindowsShortcut wscfile = new WindowsShortcut(lf);
3606 String fullname = wscfile.getRealFilename();
3607 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3608 files.set(f, fullname);
3609 jalview.bin.Console.debug("Parsed real filename " + fullname
3610 + " to extract protocol: " + protocols.get(f));
3611 } catch (Exception ex)
3613 jalview.bin.Console.error(
3614 "Couldn't parse " + files.get(f) + " as a link file.",
3623 * Sets the Preferences property for experimental features to True or False
3624 * depending on the state of the controlling menu item
3627 protected void showExperimental_actionPerformed(boolean selected)
3629 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3633 * Answers a (possibly empty) list of any structure viewer frames (currently
3634 * for either Jmol or Chimera) which are currently open. This may optionally
3635 * be restricted to viewers of a specified class, or viewers linked to a
3636 * specified alignment panel.
3639 * if not null, only return viewers linked to this panel
3640 * @param structureViewerClass
3641 * if not null, only return viewers of this class
3644 public List<StructureViewerBase> getStructureViewers(
3645 AlignmentPanel apanel,
3646 Class<? extends StructureViewerBase> structureViewerClass)
3648 List<StructureViewerBase> result = new ArrayList<>();
3649 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3651 for (JInternalFrame frame : frames)
3653 if (frame instanceof StructureViewerBase)
3655 if (structureViewerClass == null
3656 || structureViewerClass.isInstance(frame))
3659 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3661 result.add((StructureViewerBase) frame);
3669 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3671 private static boolean debugScaleMessageDone = false;
3673 public static void debugScaleMessage(Graphics g)
3675 if (debugScaleMessageDone)
3679 // output used by tests to check HiDPI scaling settings in action
3682 Graphics2D gg = (Graphics2D) g;
3685 AffineTransform t = gg.getTransform();
3686 double scaleX = t.getScaleX();
3687 double scaleY = t.getScaleY();
3688 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3689 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3690 debugScaleMessageDone = true;
3694 jalview.bin.Console.debug("Desktop graphics null");
3696 } catch (Exception e)
3698 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3703 * closes the current instance window, disposes and forgets about it.
3705 public static void closeDesktop()
3707 if (Desktop.instance != null)
3709 Desktop.instance.closeAll_actionPerformed(null);
3710 Desktop.instance.setVisible(false);
3711 Desktop us = Desktop.instance;
3712 Desktop.instance = null;
3713 // call dispose in a separate thread - try to avoid indirect deadlocks
3714 new Thread(new Runnable()
3719 ExecutorService dex = us.dialogExecutor;
3723 us.dialogExecutor = null;
3724 us.block.drainPermits();
3733 * checks if any progress bars are being displayed in any of the windows
3734 * managed by the desktop
3738 public boolean operationsAreInProgress()
3740 JInternalFrame[] frames = getAllFrames();
3741 for (JInternalFrame frame : frames)
3743 if (frame instanceof IProgressIndicator)
3745 if (((IProgressIndicator) frame).operationInProgress())
3751 return operationInProgress();
3755 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3756 * The way the modal JInternalFrame is made means it cannot be a child of an
3757 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3759 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3761 protected static void addModal(AlignFrame af, JInternalFrame jif)
3763 alignFrameModalMap.put(af, jif);
3766 protected static void closeModal(AlignFrame af)
3768 if (!alignFrameModalMap.containsKey(af))
3772 JInternalFrame jif = alignFrameModalMap.get(af);
3777 jif.setClosed(true);
3778 } catch (PropertyVetoException e)
3780 e.printStackTrace();
3783 alignFrameModalMap.remove(af);
3786 public void nonBlockingDialog(String title, String message, String button,
3787 int type, boolean scrollable, boolean modal)
3789 nonBlockingDialog(title, message, null, button, type, scrollable, false,
3793 public void nonBlockingDialog(String title, String message,
3794 String boxtext, String button, int type, boolean scrollable,
3795 boolean html, boolean modal, int timeout)
3797 nonBlockingDialog(32, 2, title, message, boxtext, button, type,
3798 scrollable, html, modal, timeout);
3801 public void nonBlockingDialog(int width, int height, String title,
3802 String message, String boxtext, String button, int type,
3803 boolean scrollable, boolean html, boolean modal, int timeout)
3807 type = JvOptionPane.WARNING_MESSAGE;
3809 JLabel jl = new JLabel(message);
3811 JTextComponent jtc = null;
3814 JTextPane jtp = new JTextPane();
3815 jtp.setContentType("text/html");
3816 jtp.setEditable(false);
3817 jtp.setAutoscrolls(true);
3818 jtp.setText(boxtext);
3824 JTextArea jta = new JTextArea(height, width);
3825 // jta.setLineWrap(true);
3826 jta.setEditable(false);
3827 jta.setWrapStyleWord(true);
3828 jta.setAutoscrolls(true);
3829 jta.setText(boxtext);
3834 JScrollPane jsp = scrollable
3835 ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3836 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
3839 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
3841 JPanel jp = new JPanel();
3842 jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
3844 if (message != null)
3846 jl.setAlignmentX(Component.LEFT_ALIGNMENT);
3849 if (boxtext != null)
3853 jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
3858 jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
3863 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
3865 jvp.setTimeout(timeout);
3866 JButton jb = new JButton(button);
3867 jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
3869 { button }, button, modal, new JButton[] { jb }, false);
3873 public AlignFrame getCurrentAlignFrame()
3875 return Jalview.getInstance().getCurrentAlignFrame();