2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
54 import java.beans.PropertyVetoException;
56 import java.io.FileWriter;
57 import java.io.IOException;
58 import java.lang.reflect.Field;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashMap;
63 import java.util.Hashtable;
64 import java.util.List;
65 import java.util.ListIterator;
66 import java.util.Locale;
68 import java.util.Vector;
69 import java.util.concurrent.ExecutorService;
70 import java.util.concurrent.Executors;
71 import java.util.concurrent.Semaphore;
73 import javax.swing.AbstractAction;
74 import javax.swing.Action;
75 import javax.swing.ActionMap;
76 import javax.swing.Box;
77 import javax.swing.BoxLayout;
78 import javax.swing.DefaultDesktopManager;
79 import javax.swing.DesktopManager;
80 import javax.swing.InputMap;
81 import javax.swing.JButton;
82 import javax.swing.JCheckBox;
83 import javax.swing.JComboBox;
84 import javax.swing.JComponent;
85 import javax.swing.JDesktopPane;
86 import javax.swing.JFrame;
87 import javax.swing.JInternalFrame;
88 import javax.swing.JLabel;
89 import javax.swing.JMenuItem;
90 import javax.swing.JOptionPane;
91 import javax.swing.JPanel;
92 import javax.swing.JPopupMenu;
93 import javax.swing.JProgressBar;
94 import javax.swing.JTextArea;
95 import javax.swing.JTextField;
96 import javax.swing.KeyStroke;
97 import javax.swing.SwingUtilities;
98 import javax.swing.WindowConstants;
99 import javax.swing.event.HyperlinkEvent;
100 import javax.swing.event.HyperlinkEvent.EventType;
101 import javax.swing.event.InternalFrameAdapter;
102 import javax.swing.event.InternalFrameEvent;
104 import org.stackoverflowusers.file.WindowsShortcut;
106 import jalview.api.AlignViewportI;
107 import jalview.api.AlignmentViewPanel;
108 import jalview.api.structures.JalviewStructureDisplayI;
109 import jalview.bin.Cache;
110 import jalview.bin.Jalview;
111 import jalview.bin.Jalview.ExitCode;
112 import jalview.datamodel.Alignment;
113 import jalview.datamodel.HiddenColumns;
114 import jalview.datamodel.Sequence;
115 import jalview.datamodel.SequenceI;
116 import jalview.gui.ImageExporter.ImageWriterI;
117 import jalview.gui.QuitHandler.QResponse;
118 import jalview.io.BackupFiles;
119 import jalview.io.DataSourceType;
120 import jalview.io.FileFormat;
121 import jalview.io.FileFormatException;
122 import jalview.io.FileFormatI;
123 import jalview.io.FileFormats;
124 import jalview.io.FileLoader;
125 import jalview.io.FormatAdapter;
126 import jalview.io.IdentifyFile;
127 import jalview.io.JalviewFileChooser;
128 import jalview.io.JalviewFileView;
129 import jalview.io.exceptions.ImageOutputException;
130 import jalview.jbgui.GSplitFrame;
131 import jalview.jbgui.GStructureViewer;
132 import jalview.project.Jalview2XML;
133 import jalview.structure.StructureSelectionManager;
134 import jalview.urls.IdOrgSettings;
135 import jalview.util.BrowserLauncher;
136 import jalview.util.ChannelProperties;
137 import jalview.util.ImageMaker.TYPE;
138 import jalview.util.LaunchUtils;
139 import jalview.util.MessageManager;
140 import jalview.util.Platform;
141 import jalview.util.ShortcutKeyMaskExWrapper;
142 import jalview.util.UrlConstants;
143 import jalview.viewmodel.AlignmentViewport;
144 import jalview.ws.params.ParamManager;
145 import jalview.ws.utils.UrlDownloadClient;
152 * @version $Revision: 1.155 $
154 public class Desktop extends jalview.jbgui.GDesktop
155 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
156 jalview.api.StructureSelectionManagerProvider
158 private static final String CITATION;
161 URL bg_logo_url = ChannelProperties.getImageURL(
162 "bg_logo." + String.valueOf(SplashScreen.logoSize));
163 URL uod_logo_url = ChannelProperties.getImageURL(
164 "uod_banner." + String.valueOf(SplashScreen.logoSize));
165 boolean logo = (bg_logo_url != null || uod_logo_url != null);
166 StringBuilder sb = new StringBuilder();
168 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
173 sb.append(bg_logo_url == null ? ""
174 : "<img alt=\"Barton Group logo\" src=\""
175 + bg_logo_url.toString() + "\">");
176 sb.append(uod_logo_url == null ? ""
177 : " <img alt=\"University of Dundee shield\" src=\""
178 + uod_logo_url.toString() + "\">");
180 "<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>");
181 sb.append("<br><br>If you use Jalview, please cite:"
182 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
183 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
184 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
185 CITATION = sb.toString();
188 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
190 private static int DEFAULT_MIN_WIDTH = 300;
192 private static int DEFAULT_MIN_HEIGHT = 250;
194 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
196 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
198 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
200 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
202 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
204 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
206 public static void setLiveDragMode(boolean b)
208 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
209 : JDesktopPane.OUTLINE_DRAG_MODE;
211 desktop.setDragMode(DRAG_MODE);
214 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
216 public static boolean nosplash = false;
219 * news reader - null if it was never started.
221 private BlogReader jvnews = null;
223 private File projectFile;
227 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
229 public void addJalviewPropertyChangeListener(
230 PropertyChangeListener listener)
232 changeSupport.addJalviewPropertyChangeListener(listener);
236 * @param propertyName
238 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
239 * java.beans.PropertyChangeListener)
241 public void addJalviewPropertyChangeListener(String propertyName,
242 PropertyChangeListener listener)
244 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
248 * @param propertyName
250 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
251 * java.beans.PropertyChangeListener)
253 public void removeJalviewPropertyChangeListener(String propertyName,
254 PropertyChangeListener listener)
256 changeSupport.removeJalviewPropertyChangeListener(propertyName,
260 /** Singleton Desktop instance */
261 public static Desktop instance;
263 public static MyDesktopPane desktop;
265 public static MyDesktopPane getDesktop()
267 // BH 2018 could use currentThread() here as a reference to a
268 // Hashtable<Thread, MyDesktopPane> in JavaScript
272 static int openFrameCount = 0;
274 static final int xOffset = 30;
276 static final int yOffset = 30;
278 public static jalview.ws.jws1.Discoverer discoverer;
280 public static Object[] jalviewClipboard;
282 public static boolean internalCopy = false;
284 static int fileLoadingCount = 0;
286 class MyDesktopManager implements DesktopManager
289 private DesktopManager delegate;
291 public MyDesktopManager(DesktopManager delegate)
293 this.delegate = delegate;
297 public void activateFrame(JInternalFrame f)
301 delegate.activateFrame(f);
302 } catch (NullPointerException npe)
304 Point p = getMousePosition();
305 instance.showPasteMenu(p.x, p.y);
310 public void beginDraggingFrame(JComponent f)
312 delegate.beginDraggingFrame(f);
316 public void beginResizingFrame(JComponent f, int direction)
318 delegate.beginResizingFrame(f, direction);
322 public void closeFrame(JInternalFrame f)
324 delegate.closeFrame(f);
328 public void deactivateFrame(JInternalFrame f)
330 delegate.deactivateFrame(f);
334 public void deiconifyFrame(JInternalFrame f)
336 delegate.deiconifyFrame(f);
340 public void dragFrame(JComponent f, int newX, int newY)
346 delegate.dragFrame(f, newX, newY);
350 public void endDraggingFrame(JComponent f)
352 delegate.endDraggingFrame(f);
357 public void endResizingFrame(JComponent f)
359 delegate.endResizingFrame(f);
364 public void iconifyFrame(JInternalFrame f)
366 delegate.iconifyFrame(f);
370 public void maximizeFrame(JInternalFrame f)
372 delegate.maximizeFrame(f);
376 public void minimizeFrame(JInternalFrame f)
378 delegate.minimizeFrame(f);
382 public void openFrame(JInternalFrame f)
384 delegate.openFrame(f);
388 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
395 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
399 public void setBoundsForFrame(JComponent f, int newX, int newY,
400 int newWidth, int newHeight)
402 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
405 // All other methods, simply delegate
410 * Creates a new Desktop object.
416 * A note to implementors. It is ESSENTIAL that any activities that might
417 * block are spawned off as threads rather than waited for during this
422 doConfigureStructurePrefs();
423 setTitle(ChannelProperties.getProperty("app_name") + " "
424 + Cache.getProperty("VERSION"));
427 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
428 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
429 * officially documented or guaranteed to exist, so we access it via
430 * reflection. There appear to be unfathomable criteria about what this
431 * string can contain, and it if doesn't meet those criteria then "java"
432 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
433 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
434 * not. The reflection access may generate a warning: WARNING: An illegal
435 * reflective access operation has occurred WARNING: Illegal reflective
436 * access by jalview.gui.Desktop () to field
437 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
439 if (Platform.isLinux())
441 if (LaunchUtils.getJavaVersion() >= 11)
444 * Send this message to stderr as the warning that follows (due to
445 * reflection) also goes to stderr.
447 jalview.bin.Console.errPrintln(
448 "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.");
450 final String awtAppClassName = "awtAppClassName";
453 Toolkit xToolkit = Toolkit.getDefaultToolkit();
454 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
455 Field awtAppClassNameField = null;
457 if (Arrays.stream(declaredFields)
458 .anyMatch(f -> f.getName().equals(awtAppClassName)))
460 awtAppClassNameField = xToolkit.getClass()
461 .getDeclaredField(awtAppClassName);
464 String title = ChannelProperties.getProperty("app_name");
465 if (awtAppClassNameField != null)
467 awtAppClassNameField.setAccessible(true);
468 awtAppClassNameField.set(xToolkit, title);
473 .debug("XToolkit: " + awtAppClassName + " not found");
475 } catch (Exception e)
477 jalview.bin.Console.debug("Error setting " + awtAppClassName);
478 jalview.bin.Console.trace(Cache.getStackTraceString(e));
482 setIconImages(ChannelProperties.getIconList());
484 // override quit handling when GUI OS close [X] button pressed
485 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
486 addWindowListener(new WindowAdapter()
489 public void windowClosing(WindowEvent ev)
491 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
495 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
497 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
498 desktop = new MyDesktopPane(selmemusage);
500 showMemusage.setSelected(selmemusage);
501 desktop.setBackground(Color.white);
503 getContentPane().setLayout(new BorderLayout());
504 // alternate config - have scrollbars - see notes in JAL-153
505 // JScrollPane sp = new JScrollPane();
506 // sp.getViewport().setView(desktop);
507 // getContentPane().add(sp, BorderLayout.CENTER);
509 // BH 2018 - just an experiment to try unclipped JInternalFrames.
512 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
515 getContentPane().add(desktop, BorderLayout.CENTER);
516 desktop.setDragMode(DRAG_MODE);
518 // This line prevents Windows Look&Feel resizing all new windows to maximum
519 // if previous window was maximised
520 desktop.setDesktopManager(new MyDesktopManager(
521 Platform.isJS() ? desktop.getDesktopManager()
522 : new DefaultDesktopManager()));
524 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
525 : Platform.isAMacAndNotJS()
526 ? new AquaInternalFrameManager(
527 desktop.getDesktopManager())
528 : desktop.getDesktopManager())));
531 Rectangle dims = getLastKnownDimensions("");
538 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
539 int xPos = Math.max(5, (screenSize.width - 900) / 2);
540 int yPos = Math.max(5, (screenSize.height - 650) / 2);
541 setBounds(xPos, yPos, 900, 650);
544 // start dialogue queue for single dialogues
547 if (!Platform.isJS())
554 jconsole = new Console(this, showjconsole);
555 jconsole.setHeader(Cache.getVersionDetailsForConsole());
556 showConsole(showjconsole);
558 showNews.setVisible(false);
560 experimentalFeatures.setSelected(showExperimental());
562 getIdentifiersOrgData();
566 // Spawn a thread that shows the splashscreen
569 SwingUtilities.invokeLater(new Runnable()
574 new SplashScreen(true);
579 // Thread off a new instance of the file chooser - this reduces the time
580 // it takes to open it later on.
581 new Thread(new Runnable()
586 jalview.bin.Console.debug("Filechooser init thread started.");
587 String fileFormat = FileLoader.getUseDefaultFileFormat()
588 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
590 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
592 jalview.bin.Console.debug("Filechooser init thread finished.");
595 // Add the service change listener
596 changeSupport.addJalviewPropertyChangeListener("services",
597 new PropertyChangeListener()
601 public void propertyChange(PropertyChangeEvent evt)
604 .debug("Firing service changed event for "
605 + evt.getNewValue());
606 JalviewServicesChanged(evt);
611 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
614 this.addMouseListener(ma = new MouseAdapter()
617 public void mousePressed(MouseEvent evt)
619 if (evt.isPopupTrigger()) // Mac
621 showPasteMenu(evt.getX(), evt.getY());
626 public void mouseReleased(MouseEvent evt)
628 if (evt.isPopupTrigger()) // Windows
630 showPasteMenu(evt.getX(), evt.getY());
634 desktop.addMouseListener(ma);
638 // used for jalviewjsTest
639 jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
646 * Warning about old or mixed command line arguments
648 private void cliWarning()
650 Jalview j = Jalview.getInstance();
651 boolean oldStyle = j.getArgParser() != null
652 && j.getArgParser().isOldStyle();
653 boolean mixedStyle = j.getArgParser() != null
654 && j.getArgParser().isMixedStyle();
656 String title = MessageManager.getString("label.command_line_arguments");
659 String warning = MessageManager.formatMessage(
660 "warning.using_mixed_command_line_arguments",
661 j.getArgParser().getMixedExamples());
662 String quit = MessageManager.getString("action.quit");
663 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
664 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
666 jvp.showDialogOnTopAsync(this, warning, title, JOptionPane.YES_OPTION,
667 JOptionPane.WARNING_MESSAGE, null, new Object[]
668 { quit }, quit, true, null, false);
670 Jalview.getInstance().exit(
671 "Exiting due to mixed old and new command line arguments.",
672 ExitCode.MIXED_CLI_ARGUMENTS);
677 String warning = MessageManager
678 .getString("warning.using_old_command_line_arguments")
679 + "https://www.jalview.org/help/html/features/commandline.html";
681 JTextArea jta = new JTextArea(2, 32);
682 // jta.setLineWrap(true);
683 jta.setEditable(false);
684 jta.setWrapStyleWord(true);
685 jta.setAutoscrolls(true);
686 jta.setText(warning);
688 String ok = MessageManager.getString("label.continue");
689 JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
690 jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
692 jvp.showDialogOnTopAsync(this, jta, title, JOptionPane.YES_OPTION,
693 JOptionPane.WARNING_MESSAGE, null, new Object[]
694 { ok }, ok, false, null, false);
700 * Answers true if user preferences to enable experimental features is True
705 public boolean showExperimental()
707 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
708 Boolean.FALSE.toString());
709 return Boolean.valueOf(experimental).booleanValue();
712 public void doConfigureStructurePrefs()
714 // configure services
715 StructureSelectionManager ssm = StructureSelectionManager
716 .getStructureSelectionManager(this);
717 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
719 ssm.setAddTempFacAnnot(
720 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
721 ssm.setProcessSecondaryStructure(
722 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
723 // JAL-3915 - RNAView is no longer an option so this has no effect
724 ssm.setSecStructServices(
725 Cache.getDefault(Preferences.USE_RNAVIEW, false));
729 ssm.setAddTempFacAnnot(false);
730 ssm.setProcessSecondaryStructure(false);
731 ssm.setSecStructServices(false);
735 public void checkForNews()
737 final Desktop me = this;
738 // Thread off the news reader, in case there are connection problems.
739 new Thread(new Runnable()
744 jalview.bin.Console.debug("Starting news thread.");
745 jvnews = new BlogReader(me);
746 showNews.setVisible(true);
747 jalview.bin.Console.debug("Completed news thread.");
752 public void getIdentifiersOrgData()
754 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
755 {// Thread off the identifiers fetcher
756 new Thread(new Runnable()
762 .debug("Downloading data from identifiers.org");
765 UrlDownloadClient.download(IdOrgSettings.getUrl(),
766 IdOrgSettings.getDownloadLocation());
767 } catch (IOException e)
770 .debug("Exception downloading identifiers.org data"
780 protected void showNews_actionPerformed(ActionEvent e)
782 showNews(showNews.isSelected());
785 void showNews(boolean visible)
787 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
788 showNews.setSelected(visible);
789 if (visible && !jvnews.isVisible())
791 new Thread(new Runnable()
796 long now = System.currentTimeMillis();
797 Desktop.instance.setProgressBar(
798 MessageManager.getString("status.refreshing_news"), now);
799 jvnews.refreshNews();
800 Desktop.instance.setProgressBar(null, now);
808 * recover the last known dimensions for a jalview window
811 * - empty string is desktop, all other windows have unique prefix
812 * @return null or last known dimensions scaled to current geometry (if last
813 * window geom was known)
815 Rectangle getLastKnownDimensions(String windowName)
817 // TODO: lock aspect ratio for scaling desktop Bug #0058199
818 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
819 String x = Cache.getProperty(windowName + "SCREEN_X");
820 String y = Cache.getProperty(windowName + "SCREEN_Y");
821 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
822 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
823 if ((x != null) && (y != null) && (width != null) && (height != null))
825 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
826 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
827 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
829 // attempt #1 - try to cope with change in screen geometry - this
830 // version doesn't preserve original jv aspect ratio.
831 // take ratio of current screen size vs original screen size.
832 double sw = ((1f * screenSize.width) / (1f * Integer
833 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
834 double sh = ((1f * screenSize.height) / (1f * Integer
835 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
836 // rescale the bounds depending upon the current screen geometry.
837 ix = (int) (ix * sw);
838 iw = (int) (iw * sw);
839 iy = (int) (iy * sh);
840 ih = (int) (ih * sh);
841 while (ix >= screenSize.width)
843 jalview.bin.Console.debug(
844 "Window geometry location recall error: shifting horizontal to within screenbounds.");
845 ix -= screenSize.width;
847 while (iy >= screenSize.height)
849 jalview.bin.Console.debug(
850 "Window geometry location recall error: shifting vertical to within screenbounds.");
851 iy -= screenSize.height;
853 jalview.bin.Console.debug(
854 "Got last known dimensions for " + windowName + ": x:" + ix
855 + " y:" + iy + " width:" + iw + " height:" + ih);
857 // return dimensions for new instance
858 return new Rectangle(ix, iy, iw, ih);
863 void showPasteMenu(int x, int y)
865 JPopupMenu popup = new JPopupMenu();
866 JMenuItem item = new JMenuItem(
867 MessageManager.getString("label.paste_new_window"));
868 item.addActionListener(new ActionListener()
871 public void actionPerformed(ActionEvent evt)
878 popup.show(this, x, y);
883 // quick patch for JAL-4150 - needs some more work and test coverage
884 // TODO - unify below and AlignFrame.paste()
885 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
886 // clipboard has come from a different alignment window than the one where
887 // paste has been called! JAL-4151
889 if (Desktop.jalviewClipboard != null)
891 // The clipboard was filled from within Jalview, we must use the
893 // And dataset from the copied alignment
894 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
895 // be doubly sure that we create *new* sequence objects.
896 SequenceI[] sequences = new SequenceI[newseq.length];
897 for (int i = 0; i < newseq.length; i++)
899 sequences[i] = new Sequence(newseq[i]);
901 Alignment alignment = new Alignment(sequences);
902 // dataset is inherited
903 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
904 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
905 AlignFrame.DEFAULT_HEIGHT);
906 String newtitle = new String("Copied sequences");
908 if (Desktop.jalviewClipboard[2] != null)
910 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
911 af.viewport.setHiddenColumns(hc);
914 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
915 AlignFrame.DEFAULT_HEIGHT);
922 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
923 Transferable contents = c.getContents(this);
925 if (contents != null)
927 String file = (String) contents
928 .getTransferData(DataFlavor.stringFlavor);
930 FileFormatI format = new IdentifyFile().identify(file,
931 DataSourceType.PASTE);
933 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
936 } catch (Exception ex)
938 jalview.bin.Console.outPrintln(
939 "Unable to paste alignment from system clipboard:\n" + ex);
945 * Adds and opens the given frame to the desktop
956 public static synchronized void addInternalFrame(
957 final JInternalFrame frame, String title, int w, int h)
959 addInternalFrame(frame, title, true, w, h, true, false);
963 * Add an internal frame to the Jalview desktop
970 * When true, display frame immediately, otherwise, caller must call
971 * setVisible themselves.
977 public static synchronized void addInternalFrame(
978 final JInternalFrame frame, String title, boolean makeVisible,
981 addInternalFrame(frame, title, makeVisible, w, h, true, false);
985 * Add an internal frame to the Jalview desktop and make it visible
998 public static synchronized void addInternalFrame(
999 final JInternalFrame frame, String title, int w, int h,
1002 addInternalFrame(frame, title, true, w, h, resizable, false);
1006 * Add an internal frame to the Jalview desktop
1012 * @param makeVisible
1013 * When true, display frame immediately, otherwise, caller must call
1014 * setVisible themselves.
1021 * @param ignoreMinSize
1022 * Do not set the default minimum size for frame
1024 public static synchronized void addInternalFrame(
1025 final JInternalFrame frame, String title, boolean makeVisible,
1026 int w, int h, boolean resizable, boolean ignoreMinSize)
1029 // TODO: allow callers to determine X and Y position of frame (eg. via
1031 // TODO: consider fixing method to update entries in the window submenu with
1032 // the current window title
1034 frame.setTitle(title);
1035 if (frame.getWidth() < 1 || frame.getHeight() < 1)
1037 frame.setSize(w, h);
1039 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
1040 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
1041 // IF JALVIEW IS RUNNING HEADLESS
1042 // ///////////////////////////////////////////////
1043 if (instance == null || (System.getProperty("java.awt.headless") != null
1044 && System.getProperty("java.awt.headless").equals("true")))
1053 frame.setMinimumSize(
1054 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
1056 // Set default dimension for Alignment Frame window.
1057 // The Alignment Frame window could be added from a number of places,
1059 // I did this here in order not to miss out on any Alignment frame.
1060 if (frame instanceof AlignFrame)
1062 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
1063 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
1067 frame.setVisible(makeVisible);
1068 frame.setClosable(true);
1069 frame.setResizable(resizable);
1070 frame.setMaximizable(resizable);
1071 frame.setIconifiable(resizable);
1072 frame.setOpaque(Platform.isJS());
1074 if (frame.getX() < 1 && frame.getY() < 1)
1076 frame.setLocation(xOffset * openFrameCount,
1077 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1081 * add an entry for the new frame in the Window menu (and remove it when the
1084 final JMenuItem menuItem = new JMenuItem(title);
1085 frame.addInternalFrameListener(new InternalFrameAdapter()
1088 public void internalFrameActivated(InternalFrameEvent evt)
1090 JInternalFrame itf = desktop.getSelectedFrame();
1093 if (itf instanceof AlignFrame)
1095 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1102 public void internalFrameClosed(InternalFrameEvent evt)
1104 PaintRefresher.RemoveComponent(frame);
1107 * defensive check to prevent frames being added half off the window
1109 if (openFrameCount > 0)
1115 * ensure no reference to alignFrame retained by menu item listener
1117 if (menuItem.getActionListeners().length > 0)
1119 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1121 windowMenu.remove(menuItem);
1125 menuItem.addActionListener(new ActionListener()
1128 public void actionPerformed(ActionEvent e)
1132 frame.setSelected(true);
1133 frame.setIcon(false);
1134 } catch (java.beans.PropertyVetoException ex)
1141 setKeyBindings(frame);
1143 // Since the latest FlatLaf patch, we occasionally have problems showing
1144 // structureViewer frames...
1146 boolean shown = false;
1147 Exception last = null;
1154 } catch (IllegalArgumentException iaex)
1158 jalview.bin.Console.info("Squashed IllegalArgument Exception ("
1159 + tries + " left) for " + frame.getTitle(), iaex);
1163 } catch (InterruptedException iex)
1168 } while (!shown && tries > 0);
1171 jalview.bin.Console.error(
1172 "Serious Problem whilst showing window " + frame.getTitle(),
1176 windowMenu.add(menuItem);
1181 frame.setSelected(true);
1182 frame.requestFocus();
1183 } catch (java.beans.PropertyVetoException ve)
1185 } catch (java.lang.ClassCastException cex)
1187 jalview.bin.Console.warn(
1188 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1194 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1199 private static void setKeyBindings(JInternalFrame frame)
1201 @SuppressWarnings("serial")
1202 final Action closeAction = new AbstractAction()
1205 public void actionPerformed(ActionEvent e)
1212 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1214 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1215 InputEvent.CTRL_DOWN_MASK);
1216 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1217 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1219 InputMap inputMap = frame
1220 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1221 String ctrlW = ctrlWKey.toString();
1222 inputMap.put(ctrlWKey, ctrlW);
1223 inputMap.put(cmdWKey, ctrlW);
1225 ActionMap actionMap = frame.getActionMap();
1226 actionMap.put(ctrlW, closeAction);
1230 public void lostOwnership(Clipboard clipboard, Transferable contents)
1234 Desktop.jalviewClipboard = null;
1237 internalCopy = false;
1241 public void dragEnter(DropTargetDragEvent evt)
1246 public void dragExit(DropTargetEvent evt)
1251 public void dragOver(DropTargetDragEvent evt)
1256 public void dropActionChanged(DropTargetDragEvent evt)
1267 public void drop(DropTargetDropEvent evt)
1269 boolean success = true;
1270 // JAL-1552 - acceptDrop required before getTransferable call for
1271 // Java's Transferable for native dnd
1272 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1273 Transferable t = evt.getTransferable();
1274 List<Object> files = new ArrayList<>();
1275 List<DataSourceType> protocols = new ArrayList<>();
1279 Desktop.transferFromDropTarget(files, protocols, evt, t);
1280 } catch (Exception e)
1282 e.printStackTrace();
1290 for (int i = 0; i < files.size(); i++)
1292 // BH 2018 File or String
1293 Object file = files.get(i);
1294 String fileName = file.toString();
1295 DataSourceType protocol = (protocols == null)
1296 ? DataSourceType.FILE
1298 FileFormatI format = null;
1300 if (fileName.endsWith(".jar"))
1302 format = FileFormat.Jalview;
1307 format = new IdentifyFile().identify(file, protocol);
1309 if (file instanceof File)
1311 Platform.cacheFileData((File) file);
1313 new FileLoader().LoadFile(null, file, protocol, format);
1316 } catch (Exception ex)
1321 evt.dropComplete(success); // need this to ensure input focus is properly
1322 // transfered to any new windows created
1332 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1334 String fileFormat = FileLoader.getUseDefaultFileFormat()
1335 ? Cache.getProperty("DEFAULT_FILE_FORMAT")
1337 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1338 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1339 BackupFiles.getEnabled());
1341 chooser.setFileView(new JalviewFileView());
1342 chooser.setDialogTitle(
1343 MessageManager.getString("label.open_local_file"));
1344 chooser.setToolTipText(MessageManager.getString("action.open"));
1346 chooser.setResponseHandler(0, () -> {
1347 File selectedFile = chooser.getSelectedFile();
1348 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1350 FileFormatI format = chooser.getSelectedFormat();
1353 * Call IdentifyFile to verify the file contains what its extension implies.
1354 * Skip this step for dynamically added file formats, because IdentifyFile does
1355 * not know how to recognise them.
1357 if (FileFormats.getInstance().isIdentifiable(format))
1361 format = new IdentifyFile().identify(selectedFile,
1362 DataSourceType.FILE);
1363 } catch (FileFormatException e)
1365 // format = null; //??
1369 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1372 chooser.showOpenDialog(this);
1376 * Shows a dialog for input of a URL at which to retrieve alignment data
1381 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1383 // This construct allows us to have a wider textfield
1385 JLabel label = new JLabel(
1386 MessageManager.getString("label.input_file_url"));
1388 JPanel panel = new JPanel(new GridLayout(2, 1));
1392 * the URL to fetch is input in Java: an editable combobox with history JS:
1393 * (pending JAL-3038) a plain text field
1396 String urlBase = "https://www.";
1397 if (Platform.isJS())
1399 history = new JTextField(urlBase, 35);
1408 JComboBox<String> asCombo = new JComboBox<>();
1409 asCombo.setPreferredSize(new Dimension(400, 20));
1410 asCombo.setEditable(true);
1411 asCombo.addItem(urlBase);
1412 String historyItems = Cache.getProperty("RECENT_URL");
1413 if (historyItems != null)
1415 for (String token : historyItems.split("\\t"))
1417 asCombo.addItem(token);
1424 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1425 MessageManager.getString("action.cancel") };
1426 Runnable action = () -> {
1427 @SuppressWarnings("unchecked")
1428 String url = (history instanceof JTextField
1429 ? ((JTextField) history).getText()
1430 : ((JComboBox<String>) history).getEditor().getItem()
1431 .toString().trim());
1433 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1435 if (viewport != null)
1437 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1438 FileFormat.Jalview);
1442 new FileLoader().LoadFile(url, DataSourceType.URL,
1443 FileFormat.Jalview);
1448 FileFormatI format = null;
1451 format = new IdentifyFile().identify(url, DataSourceType.URL);
1452 } catch (FileFormatException e)
1454 // TODO revise error handling, distinguish between
1455 // URL not found and response not valid
1460 String msg = MessageManager.formatMessage("label.couldnt_locate",
1462 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1463 MessageManager.getString("label.url_not_found"),
1464 JvOptionPane.WARNING_MESSAGE);
1468 if (viewport != null)
1470 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1475 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1479 String dialogOption = MessageManager
1480 .getString("label.input_alignment_from_url");
1481 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1482 .showInternalDialog(panel, dialogOption,
1483 JvOptionPane.YES_NO_CANCEL_OPTION,
1484 JvOptionPane.PLAIN_MESSAGE, null, options,
1485 MessageManager.getString("action.ok"));
1489 * Opens the CutAndPaste window for the user to paste an alignment in to
1492 * - if not null, the pasted alignment is added to the current
1493 * alignment; if null, to a new alignment window
1496 public void inputTextboxMenuItem_actionPerformed(
1497 AlignmentViewPanel viewPanel)
1499 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1500 cap.setForInput(viewPanel);
1501 Desktop.addInternalFrame(cap,
1502 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1507 * Check with user and saving files before actually quitting
1509 public void desktopQuit()
1511 desktopQuit(true, false);
1514 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1516 final Runnable doDesktopQuit = () -> {
1517 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1518 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1519 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1520 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1521 getBounds().y, getWidth(), getHeight()));
1523 if (jconsole != null)
1525 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1526 jconsole.stopConsole();
1531 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1534 // Frames should all close automatically. Keeping external
1535 // viewers open should already be decided by user.
1536 closeAll_actionPerformed(null);
1538 // check for aborted quit
1539 if (QuitHandler.quitCancelled())
1541 jalview.bin.Console.debug("Desktop aborting quit");
1545 if (dialogExecutor != null)
1547 dialogExecutor.shutdownNow();
1550 if (groovyConsole != null)
1552 // suppress a possible repeat prompt to save script
1553 groovyConsole.setDirty(false);
1554 groovyConsole.exit();
1557 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1559 // note that shutdown hook will not be run
1560 jalview.bin.Console.debug("Force Quit selected by user");
1561 Runtime.getRuntime().halt(0);
1564 jalview.bin.Console.debug("Quit selected by user");
1567 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1568 // instance.dispose();
1573 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1574 QuitHandler.defaultCancelQuit);
1578 * Don't call this directly, use desktopQuit() above. Exits the program.
1583 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1584 // not run a second time if gotQuitResponse flag has been set (i.e. user
1585 // confirmed quit of some kind).
1586 Jalview.exit("Desktop exiting.", ExitCode.OK);
1589 private void storeLastKnownDimensions(String string, Rectangle jc)
1591 jalview.bin.Console.debug("Storing last known dimensions for " + string
1592 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1593 + " height:" + jc.height);
1595 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1596 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1597 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1598 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1608 public void aboutMenuItem_actionPerformed(ActionEvent e)
1610 new Thread(new Runnable()
1615 new SplashScreen(false);
1621 * Returns the html text for the About screen, including any available version
1622 * number, build details, author details and citation reference, but without
1623 * the enclosing {@code html} tags
1627 public String getAboutMessage()
1629 StringBuilder message = new StringBuilder(1024);
1630 message.append("<div style=\"font-family: sans-serif;\">")
1631 .append("<h1><strong>Version: ")
1632 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1633 .append("<strong>Built: <em>")
1634 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1635 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1636 .append("</strong>");
1638 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1639 if (latestVersion.equals("Checking"))
1641 // JBP removed this message for 2.11: May be reinstated in future version
1642 // message.append("<br>...Checking latest version...</br>");
1644 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1646 boolean red = false;
1647 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1648 .indexOf("automated build") == -1)
1651 // Displayed when code version and jnlp version do not match and code
1652 // version is not a development build
1653 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1656 message.append("<br>!! Version ")
1657 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1658 .append(" is available for download from ")
1659 .append(Cache.getDefault("www.jalview.org",
1660 "https://www.jalview.org"))
1664 message.append("</div>");
1667 message.append("<br>Authors: ");
1668 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1669 message.append(CITATION);
1671 message.append("</div>");
1673 return message.toString();
1677 * Action on requesting Help documentation
1680 public void documentationMenuItem_actionPerformed()
1684 if (Platform.isJS())
1686 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1695 Help.showHelpWindow();
1697 } catch (Exception ex)
1700 .errPrintln("Error opening help: " + ex.getMessage());
1705 public void closeAll_actionPerformed(ActionEvent e)
1707 // TODO show a progress bar while closing?
1708 JInternalFrame[] frames = desktop.getAllFrames();
1709 for (int i = 0; i < frames.length; i++)
1713 frames[i].setClosed(true);
1714 } catch (java.beans.PropertyVetoException ex)
1718 Jalview.setCurrentAlignFrame(null);
1719 jalview.bin.Console.info("ALL CLOSED");
1722 * reset state of singleton objects as appropriate (clear down session state
1723 * when all windows are closed)
1725 StructureSelectionManager ssm = StructureSelectionManager
1726 .getStructureSelectionManager(this);
1733 public int structureViewersStillRunningCount()
1736 JInternalFrame[] frames = desktop.getAllFrames();
1737 for (int i = 0; i < frames.length; i++)
1739 if (frames[i] != null
1740 && frames[i] instanceof JalviewStructureDisplayI)
1742 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1750 public void raiseRelated_actionPerformed(ActionEvent e)
1752 reorderAssociatedWindows(false, false);
1756 public void minimizeAssociated_actionPerformed(ActionEvent e)
1758 reorderAssociatedWindows(true, false);
1761 void closeAssociatedWindows()
1763 reorderAssociatedWindows(false, true);
1769 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1773 protected void garbageCollect_actionPerformed(ActionEvent e)
1775 // We simply collect the garbage
1776 jalview.bin.Console.debug("Collecting garbage...");
1778 jalview.bin.Console.debug("Finished garbage collection.");
1784 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1788 protected void showMemusage_actionPerformed(ActionEvent e)
1790 desktop.showMemoryUsage(showMemusage.isSelected());
1797 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1801 protected void showConsole_actionPerformed(ActionEvent e)
1803 showConsole(showConsole.isSelected());
1806 Console jconsole = null;
1809 * control whether the java console is visible or not
1813 void showConsole(boolean selected)
1815 // TODO: decide if we should update properties file
1816 if (jconsole != null) // BH 2018
1818 showConsole.setSelected(selected);
1819 Cache.setProperty("SHOW_JAVA_CONSOLE",
1820 Boolean.valueOf(selected).toString());
1821 jconsole.setVisible(selected);
1825 void reorderAssociatedWindows(boolean minimize, boolean close)
1827 JInternalFrame[] frames = desktop.getAllFrames();
1828 if (frames == null || frames.length < 1)
1833 AlignmentViewport source = null, target = null;
1834 if (frames[0] instanceof AlignFrame)
1836 source = ((AlignFrame) frames[0]).getCurrentView();
1838 else if (frames[0] instanceof TreePanel)
1840 source = ((TreePanel) frames[0]).getViewPort();
1842 else if (frames[0] instanceof PCAPanel)
1844 source = ((PCAPanel) frames[0]).av;
1846 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1848 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1853 for (int i = 0; i < frames.length; i++)
1856 if (frames[i] == null)
1860 if (frames[i] instanceof AlignFrame)
1862 target = ((AlignFrame) frames[i]).getCurrentView();
1864 else if (frames[i] instanceof TreePanel)
1866 target = ((TreePanel) frames[i]).getViewPort();
1868 else if (frames[i] instanceof PCAPanel)
1870 target = ((PCAPanel) frames[i]).av;
1872 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1874 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1877 if (source == target)
1883 frames[i].setClosed(true);
1887 frames[i].setIcon(minimize);
1890 frames[i].toFront();
1894 } catch (java.beans.PropertyVetoException ex)
1909 protected void preferences_actionPerformed(ActionEvent e)
1911 Preferences.openPreferences();
1915 * Prompts the user to choose a file and then saves the Jalview state as a
1916 * Jalview project file
1919 public void saveState_actionPerformed()
1921 saveState_actionPerformed(false);
1924 public void saveState_actionPerformed(boolean saveAs)
1926 java.io.File projectFile = getProjectFile();
1927 // autoSave indicates we already have a file and don't need to ask
1928 boolean autoSave = projectFile != null && !saveAs
1929 && BackupFiles.getEnabled();
1931 // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
1932 // projectFile='"+projectFile+"',
1933 // saveAs="+saveAs+", Backups
1934 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1936 boolean approveSave = false;
1939 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1942 chooser.setFileView(new JalviewFileView());
1943 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1945 int value = chooser.showSaveDialog(this);
1947 if (value == JalviewFileChooser.APPROVE_OPTION)
1949 projectFile = chooser.getSelectedFile();
1950 setProjectFile(projectFile);
1955 if (approveSave || autoSave)
1957 final Desktop me = this;
1958 final java.io.File chosenFile = projectFile;
1959 new Thread(new Runnable()
1964 // TODO: refactor to Jalview desktop session controller action.
1965 setProgressBar(MessageManager.formatMessage(
1966 "label.saving_jalview_project", new Object[]
1967 { chosenFile.getName() }), chosenFile.hashCode());
1968 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1969 // TODO catch and handle errors for savestate
1970 // TODO prevent user from messing with the Desktop whilst we're saving
1973 boolean doBackup = BackupFiles.getEnabled();
1974 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1977 new Jalview2XML().saveState(
1978 doBackup ? backupfiles.getTempFile() : chosenFile);
1982 backupfiles.setWriteSuccess(true);
1983 backupfiles.rollBackupsAndRenameTempFile();
1985 } catch (OutOfMemoryError oom)
1987 new OOMWarning("Whilst saving current state to "
1988 + chosenFile.getName(), oom);
1989 } catch (Exception ex)
1991 jalview.bin.Console.error("Problems whilst trying to save to "
1992 + chosenFile.getName(), ex);
1993 JvOptionPane.showMessageDialog(me,
1994 MessageManager.formatMessage(
1995 "label.error_whilst_saving_current_state_to",
1997 { chosenFile.getName() }),
1998 MessageManager.getString("label.couldnt_save_project"),
1999 JvOptionPane.WARNING_MESSAGE);
2001 setProgressBar(null, chosenFile.hashCode());
2008 public void saveAsState_actionPerformed(ActionEvent e)
2010 saveState_actionPerformed(true);
2013 protected void setProjectFile(File choice)
2015 this.projectFile = choice;
2018 public File getProjectFile()
2020 return this.projectFile;
2024 * Shows a file chooser dialog and tries to read in the selected file as a
2028 public void loadState_actionPerformed()
2030 final String[] suffix = new String[] { "jvp", "jar" };
2031 final String[] desc = new String[] { "Jalview Project",
2032 "Jalview Project (old)" };
2033 JalviewFileChooser chooser = new JalviewFileChooser(
2034 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
2035 "Jalview Project", true, BackupFiles.getEnabled()); // last two
2039 chooser.setFileView(new JalviewFileView());
2040 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
2041 chooser.setResponseHandler(0, () -> {
2042 File selectedFile = chooser.getSelectedFile();
2043 setProjectFile(selectedFile);
2044 String choice = selectedFile.getAbsolutePath();
2045 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
2046 new Thread(new Runnable()
2053 new Jalview2XML().loadJalviewAlign(selectedFile);
2054 } catch (OutOfMemoryError oom)
2056 new OOMWarning("Whilst loading project from " + choice, oom);
2057 } catch (Exception ex)
2059 jalview.bin.Console.error(
2060 "Problems whilst loading project from " + choice, ex);
2061 JvOptionPane.showMessageDialog(Desktop.desktop,
2062 MessageManager.formatMessage(
2063 "label.error_whilst_loading_project_from",
2066 MessageManager.getString("label.couldnt_load_project"),
2067 JvOptionPane.WARNING_MESSAGE);
2070 }, "Project Loader").start();
2073 chooser.showOpenDialog(this);
2077 public void inputSequence_actionPerformed(ActionEvent e)
2079 new SequenceFetcher(this);
2082 JPanel progressPanel;
2084 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
2086 public void startLoading(final Object fileName)
2088 if (fileLoadingCount == 0)
2090 fileLoadingPanels.add(addProgressPanel(MessageManager
2091 .formatMessage("label.loading_file", new Object[]
2097 private JPanel addProgressPanel(String string)
2099 if (progressPanel == null)
2101 progressPanel = new JPanel(new GridLayout(1, 1));
2102 totalProgressCount = 0;
2103 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
2105 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2106 JProgressBar progressBar = new JProgressBar();
2107 progressBar.setIndeterminate(true);
2109 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2111 thisprogress.add(progressBar, BorderLayout.CENTER);
2112 progressPanel.add(thisprogress);
2113 ((GridLayout) progressPanel.getLayout()).setRows(
2114 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2115 ++totalProgressCount;
2116 instance.validate();
2117 return thisprogress;
2120 int totalProgressCount = 0;
2122 private void removeProgressPanel(JPanel progbar)
2124 if (progressPanel != null)
2126 synchronized (progressPanel)
2128 progressPanel.remove(progbar);
2129 GridLayout gl = (GridLayout) progressPanel.getLayout();
2130 gl.setRows(gl.getRows() - 1);
2131 if (--totalProgressCount < 1)
2133 this.getContentPane().remove(progressPanel);
2134 progressPanel = null;
2141 public void stopLoading()
2144 if (fileLoadingCount < 1)
2146 while (fileLoadingPanels.size() > 0)
2148 removeProgressPanel(fileLoadingPanels.remove(0));
2150 fileLoadingPanels.clear();
2151 fileLoadingCount = 0;
2156 public static int getViewCount(String alignmentId)
2158 AlignmentViewport[] aps = getViewports(alignmentId);
2159 return (aps == null) ? 0 : aps.length;
2164 * @param alignmentId
2165 * - if null, all sets are returned
2166 * @return all AlignmentPanels concerning the alignmentId sequence set
2168 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2170 if (Desktop.desktop == null)
2172 // no frames created and in headless mode
2173 // TODO: verify that frames are recoverable when in headless mode
2176 List<AlignmentPanel> aps = new ArrayList<>();
2177 AlignFrame[] frames = getAlignFrames();
2182 for (AlignFrame af : frames)
2184 for (AlignmentPanel ap : af.alignPanels)
2186 if (alignmentId == null
2187 || alignmentId.equals(ap.av.getSequenceSetId()))
2193 if (aps.size() == 0)
2197 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2202 * get all the viewports on an alignment.
2204 * @param sequenceSetId
2205 * unique alignment id (may be null - all viewports returned in that
2207 * @return all viewports on the alignment bound to sequenceSetId
2209 public static AlignmentViewport[] getViewports(String sequenceSetId)
2211 List<AlignmentViewport> viewp = new ArrayList<>();
2212 if (desktop != null)
2214 AlignFrame[] frames = Desktop.getAlignFrames();
2216 for (AlignFrame afr : frames)
2218 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2219 .equals(sequenceSetId))
2221 if (afr.alignPanels != null)
2223 for (AlignmentPanel ap : afr.alignPanels)
2225 if (sequenceSetId == null
2226 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2234 viewp.add(afr.getViewport());
2238 if (viewp.size() > 0)
2240 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2247 * Explode the views in the given frame into separate AlignFrame
2251 public static void explodeViews(AlignFrame af)
2253 int size = af.alignPanels.size();
2259 // FIXME: ideally should use UI interface API
2260 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2261 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2262 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2263 for (int i = 0; i < size; i++)
2265 AlignmentPanel ap = af.alignPanels.get(i);
2267 AlignFrame newaf = new AlignFrame(ap);
2269 // transfer reference for existing feature settings to new alignFrame
2270 if (ap == af.alignPanel)
2272 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2274 newaf.featureSettings = viewFeatureSettings;
2276 newaf.setFeatureSettingsGeometry(fsBounds);
2280 * Restore the view's last exploded frame geometry if known. Multiple views from
2281 * one exploded frame share and restore the same (frame) position and size.
2283 Rectangle geometry = ap.av.getExplodedGeometry();
2284 if (geometry != null)
2286 newaf.setBounds(geometry);
2289 ap.av.setGatherViewsHere(false);
2291 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2292 AlignFrame.DEFAULT_HEIGHT);
2293 // and materialise a new feature settings dialog instance for the new
2295 // (closes the old as if 'OK' was pressed)
2296 if (ap == af.alignPanel && newaf.featureSettings != null
2297 && newaf.featureSettings.isOpen()
2298 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2300 newaf.showFeatureSettingsUI();
2304 af.featureSettings = null;
2305 af.alignPanels.clear();
2306 af.closeMenuItem_actionPerformed(true);
2311 * Gather expanded views (separate AlignFrame's) with the same sequence set
2312 * identifier back in to this frame as additional views, and close the
2313 * expanded views. Note the expanded frames may themselves have multiple
2314 * views. We take the lot.
2318 public void gatherViews(AlignFrame source)
2320 source.viewport.setGatherViewsHere(true);
2321 source.viewport.setExplodedGeometry(source.getBounds());
2322 JInternalFrame[] frames = desktop.getAllFrames();
2323 String viewId = source.viewport.getSequenceSetId();
2324 for (int t = 0; t < frames.length; t++)
2326 if (frames[t] instanceof AlignFrame && frames[t] != source)
2328 AlignFrame af = (AlignFrame) frames[t];
2329 boolean gatherThis = false;
2330 for (int a = 0; a < af.alignPanels.size(); a++)
2332 AlignmentPanel ap = af.alignPanels.get(a);
2333 if (viewId.equals(ap.av.getSequenceSetId()))
2336 ap.av.setGatherViewsHere(false);
2337 ap.av.setExplodedGeometry(af.getBounds());
2338 source.addAlignmentPanel(ap, false);
2344 if (af.featureSettings != null && af.featureSettings.isOpen())
2346 if (source.featureSettings == null)
2348 // preserve the feature settings geometry for this frame
2349 source.featureSettings = af.featureSettings;
2350 source.setFeatureSettingsGeometry(
2351 af.getFeatureSettingsGeometry());
2355 // close it and forget
2356 af.featureSettings.close();
2359 af.alignPanels.clear();
2360 af.closeMenuItem_actionPerformed(true);
2365 // refresh the feature setting UI for the source frame if it exists
2366 if (source.featureSettings != null && source.featureSettings.isOpen())
2368 source.showFeatureSettingsUI();
2373 public JInternalFrame[] getAllFrames()
2375 return desktop.getAllFrames();
2379 * Checks the given url to see if it gives a response indicating that the user
2380 * should be informed of a new questionnaire.
2384 public void checkForQuestionnaire(String url)
2386 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2387 // javax.swing.SwingUtilities.invokeLater(jvq);
2388 new Thread(jvq).start();
2391 public void checkURLLinks()
2393 // Thread off the URL link checker
2394 addDialogThread(new Runnable()
2399 if (Cache.getDefault("CHECKURLLINKS", true))
2401 // check what the actual links are - if it's just the default don't
2402 // bother with the warning
2403 List<String> links = Preferences.sequenceUrlLinks
2406 // only need to check links if there is one with a
2407 // SEQUENCE_ID which is not the default EMBL_EBI link
2408 ListIterator<String> li = links.listIterator();
2409 boolean check = false;
2410 List<JLabel> urls = new ArrayList<>();
2411 while (li.hasNext())
2413 String link = li.next();
2414 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2415 && !UrlConstants.isDefaultString(link))
2418 int barPos = link.indexOf("|");
2419 String urlMsg = barPos == -1 ? link
2420 : link.substring(0, barPos) + ": "
2421 + link.substring(barPos + 1);
2422 urls.add(new JLabel(urlMsg));
2430 // ask user to check in case URL links use old style tokens
2431 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2432 JPanel msgPanel = new JPanel();
2433 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2434 msgPanel.add(Box.createVerticalGlue());
2435 JLabel msg = new JLabel(MessageManager
2436 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2437 JLabel msg2 = new JLabel(MessageManager
2438 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2440 for (JLabel url : urls)
2446 final JCheckBox jcb = new JCheckBox(
2447 MessageManager.getString("label.do_not_display_again"));
2448 jcb.addActionListener(new ActionListener()
2451 public void actionPerformed(ActionEvent e)
2453 // update Cache settings for "don't show this again"
2454 boolean showWarningAgain = !jcb.isSelected();
2455 Cache.setProperty("CHECKURLLINKS",
2456 Boolean.valueOf(showWarningAgain).toString());
2461 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2463 .getString("label.SEQUENCE_ID_no_longer_used"),
2464 JvOptionPane.WARNING_MESSAGE);
2471 * Proxy class for JDesktopPane which optionally displays the current memory
2472 * usage and highlights the desktop area with a red bar if free memory runs
2477 public class MyDesktopPane extends JDesktopPane implements Runnable
2479 private static final float ONE_MB = 1048576f;
2481 boolean showMemoryUsage = false;
2485 java.text.NumberFormat df;
2487 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2490 public MyDesktopPane(boolean showMemoryUsage)
2492 showMemoryUsage(showMemoryUsage);
2495 public void showMemoryUsage(boolean showMemory)
2497 this.showMemoryUsage = showMemory;
2500 Thread worker = new Thread(this);
2506 public boolean isShowMemoryUsage()
2508 return showMemoryUsage;
2514 df = java.text.NumberFormat.getNumberInstance();
2515 df.setMaximumFractionDigits(2);
2516 runtime = Runtime.getRuntime();
2518 while (showMemoryUsage)
2522 maxMemory = runtime.maxMemory() / ONE_MB;
2523 allocatedMemory = runtime.totalMemory() / ONE_MB;
2524 freeMemory = runtime.freeMemory() / ONE_MB;
2525 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2527 percentUsage = (totalFreeMemory / maxMemory) * 100;
2529 // if (percentUsage < 20)
2531 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2533 // instance.set.setBorder(border1);
2536 // sleep after showing usage
2538 } catch (Exception ex)
2540 ex.printStackTrace();
2546 public void paintComponent(Graphics g)
2548 if (showMemoryUsage && g != null && df != null)
2550 if (percentUsage < 20)
2552 g.setColor(Color.red);
2554 FontMetrics fm = g.getFontMetrics();
2557 g.drawString(MessageManager.formatMessage("label.memory_stats",
2559 { df.format(totalFreeMemory), df.format(maxMemory),
2560 df.format(percentUsage) }),
2561 10, getHeight() - fm.getHeight());
2565 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2566 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2571 * Accessor method to quickly get all the AlignmentFrames loaded.
2573 * @return an array of AlignFrame, or null if none found
2575 public static AlignFrame[] getAlignFrames()
2577 if (Jalview.isHeadlessMode())
2579 // Desktop.desktop is null in headless mode
2580 return new AlignFrame[] { Jalview.currentAlignFrame };
2583 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2589 List<AlignFrame> avp = new ArrayList<>();
2591 for (int i = frames.length - 1; i > -1; i--)
2593 if (frames[i] instanceof AlignFrame)
2595 avp.add((AlignFrame) frames[i]);
2597 else if (frames[i] instanceof SplitFrame)
2600 * Also check for a split frame containing an AlignFrame
2602 GSplitFrame sf = (GSplitFrame) frames[i];
2603 if (sf.getTopFrame() instanceof AlignFrame)
2605 avp.add((AlignFrame) sf.getTopFrame());
2607 if (sf.getBottomFrame() instanceof AlignFrame)
2609 avp.add((AlignFrame) sf.getBottomFrame());
2613 if (avp.size() == 0)
2617 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2622 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2626 public GStructureViewer[] getJmols()
2628 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2634 List<GStructureViewer> avp = new ArrayList<>();
2636 for (int i = frames.length - 1; i > -1; i--)
2638 if (frames[i] instanceof AppJmol)
2640 GStructureViewer af = (GStructureViewer) frames[i];
2644 if (avp.size() == 0)
2648 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2653 * Add Groovy Support to Jalview
2656 public void groovyShell_actionPerformed()
2660 openGroovyConsole();
2661 } catch (Exception ex)
2663 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2664 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2666 MessageManager.getString("label.couldnt_create_groovy_shell"),
2667 MessageManager.getString("label.groovy_support_failed"),
2668 JvOptionPane.ERROR_MESSAGE);
2673 * Open the Groovy console
2675 void openGroovyConsole()
2677 if (groovyConsole == null)
2679 groovyConsole = new groovy.ui.Console();
2680 groovyConsole.setVariable("Jalview", this);
2681 groovyConsole.run();
2684 * We allow only one console at a time, so that AlignFrame menu option
2685 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2686 * enable 'Run script', when the console is opened, and the reverse when it is
2689 Window window = (Window) groovyConsole.getFrame();
2690 window.addWindowListener(new WindowAdapter()
2693 public void windowClosed(WindowEvent e)
2696 * rebind CMD-Q from Groovy Console to Jalview Quit
2699 enableExecuteGroovy(false);
2705 * show Groovy console window (after close and reopen)
2707 ((Window) groovyConsole.getFrame()).setVisible(true);
2710 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2711 * opening a second console
2713 enableExecuteGroovy(true);
2717 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2718 * binding when opened
2720 protected void addQuitHandler()
2723 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2725 .getKeyStroke(KeyEvent.VK_Q,
2726 jalview.util.ShortcutKeyMaskExWrapper
2727 .getMenuShortcutKeyMaskEx()),
2729 getRootPane().getActionMap().put("Quit", new AbstractAction()
2732 public void actionPerformed(ActionEvent e)
2740 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2743 * true if Groovy console is open
2745 public void enableExecuteGroovy(boolean enabled)
2748 * disable opening a second Groovy console (or re-enable when the console is
2751 groovyShell.setEnabled(!enabled);
2753 AlignFrame[] alignFrames = getAlignFrames();
2754 if (alignFrames != null)
2756 for (AlignFrame af : alignFrames)
2758 af.setGroovyEnabled(enabled);
2764 * Progress bars managed by the IProgressIndicator method.
2766 private Hashtable<Long, JPanel> progressBars;
2768 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2773 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2776 public void setProgressBar(String message, long id)
2778 if (progressBars == null)
2780 progressBars = new Hashtable<>();
2781 progressBarHandlers = new Hashtable<>();
2784 if (progressBars.get(Long.valueOf(id)) != null)
2786 JPanel panel = progressBars.remove(Long.valueOf(id));
2787 if (progressBarHandlers.contains(Long.valueOf(id)))
2789 progressBarHandlers.remove(Long.valueOf(id));
2791 removeProgressPanel(panel);
2795 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2802 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2803 * jalview.gui.IProgressIndicatorHandler)
2806 public void registerHandler(final long id,
2807 final IProgressIndicatorHandler handler)
2809 if (progressBarHandlers == null
2810 || !progressBars.containsKey(Long.valueOf(id)))
2812 throw new Error(MessageManager.getString(
2813 "error.call_setprogressbar_before_registering_handler"));
2815 progressBarHandlers.put(Long.valueOf(id), handler);
2816 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2817 if (handler.canCancel())
2819 JButton cancel = new JButton(
2820 MessageManager.getString("action.cancel"));
2821 final IProgressIndicator us = this;
2822 cancel.addActionListener(new ActionListener()
2826 public void actionPerformed(ActionEvent e)
2828 handler.cancelActivity(id);
2829 us.setProgressBar(MessageManager
2830 .formatMessage("label.cancelled_params", new Object[]
2831 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2835 progressPanel.add(cancel, BorderLayout.EAST);
2841 * @return true if any progress bars are still active
2844 public boolean operationInProgress()
2846 if (progressBars != null && progressBars.size() > 0)
2854 * This will return the first AlignFrame holding the given viewport instance.
2855 * It will break if there are more than one AlignFrames viewing a particular
2859 * @return alignFrame for viewport
2861 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2863 if (desktop != null)
2865 AlignmentPanel[] aps = getAlignmentPanels(
2866 viewport.getSequenceSetId());
2867 for (int panel = 0; aps != null && panel < aps.length; panel++)
2869 if (aps[panel] != null && aps[panel].av == viewport)
2871 return aps[panel].alignFrame;
2878 public VamsasApplication getVamsasApplication()
2880 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2886 * flag set if jalview GUI is being operated programmatically
2888 private boolean inBatchMode = false;
2891 * check if jalview GUI is being operated programmatically
2893 * @return inBatchMode
2895 public boolean isInBatchMode()
2901 * set flag if jalview GUI is being operated programmatically
2903 * @param inBatchMode
2905 public void setInBatchMode(boolean inBatchMode)
2907 this.inBatchMode = inBatchMode;
2911 * start service discovery and wait till it is done
2913 public void startServiceDiscovery()
2915 startServiceDiscovery(false);
2919 * start service discovery threads - blocking or non-blocking
2923 public void startServiceDiscovery(boolean blocking)
2925 startServiceDiscovery(blocking, false);
2929 * start service discovery threads
2932 * - false means call returns immediately
2933 * @param ignore_SHOW_JWS2_SERVICES_preference
2934 * - when true JABA services are discovered regardless of user's JWS2
2935 * discovery preference setting
2937 public void startServiceDiscovery(boolean blocking,
2938 boolean ignore_SHOW_JWS2_SERVICES_preference)
2940 boolean alive = true;
2941 Thread t0 = null, t1 = null, t2 = null;
2942 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2945 // todo: changesupport handlers need to be transferred
2946 if (discoverer == null)
2948 discoverer = new jalview.ws.jws1.Discoverer();
2949 // register PCS handler for desktop.
2950 discoverer.addPropertyChangeListener(changeSupport);
2952 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2953 // until we phase out completely
2954 (t0 = new Thread(discoverer)).start();
2957 if (ignore_SHOW_JWS2_SERVICES_preference
2958 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2960 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2961 .startDiscoverer(changeSupport);
2965 // TODO: do rest service discovery
2974 } catch (Exception e)
2977 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2978 || (t3 != null && t3.isAlive())
2979 || (t0 != null && t0.isAlive());
2985 * called to check if the service discovery process completed successfully.
2989 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2991 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2993 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2994 .getErrorMessages();
2997 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2999 if (serviceChangedDialog == null)
3001 // only run if we aren't already displaying one of these.
3002 addDialogThread(serviceChangedDialog = new Runnable()
3009 * JalviewDialog jd =new JalviewDialog() {
3011 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
3013 * }@Override protected void okPressed() { // TODO Auto-generated method stub
3015 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
3017 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
3019 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
3020 * + " or mis-configured HTTP proxy settings.<br/>" +
3021 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
3022 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
3023 * true, true, "Web Service Configuration Problem", 450, 400);
3025 * jd.waitForInput();
3027 JvOptionPane.showConfirmDialog(Desktop.desktop,
3028 new JLabel("<html><table width=\"450\"><tr><td>"
3029 + ermsg + "</td></tr></table>"
3030 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
3031 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
3032 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
3033 + " Tools->Preferences dialog box to change them.</p></html>"),
3034 "Web Service Configuration Problem",
3035 JvOptionPane.DEFAULT_OPTION,
3036 JvOptionPane.ERROR_MESSAGE);
3037 serviceChangedDialog = null;
3045 jalview.bin.Console.error(
3046 "Errors reported by JABA discovery service. Check web services preferences.\n"
3053 private Runnable serviceChangedDialog = null;
3056 * start a thread to open a URL in the configured browser. Pops up a warning
3057 * dialog to the user if there is an exception when calling out to the browser
3062 public static void showUrl(final String url)
3064 showUrl(url, Desktop.instance);
3068 * Like showUrl but allows progress handler to be specified
3072 * (null) or object implementing IProgressIndicator
3074 public static void showUrl(final String url,
3075 final IProgressIndicator progress)
3077 new Thread(new Runnable()
3084 if (progress != null)
3086 progress.setProgressBar(MessageManager
3087 .formatMessage("status.opening_params", new Object[]
3088 { url }), this.hashCode());
3090 jalview.util.BrowserLauncher.openURL(url);
3091 } catch (Exception ex)
3093 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3095 .getString("label.web_browser_not_found_unix"),
3096 MessageManager.getString("label.web_browser_not_found"),
3097 JvOptionPane.WARNING_MESSAGE);
3099 ex.printStackTrace();
3101 if (progress != null)
3103 progress.setProgressBar(null, this.hashCode());
3109 public static WsParamSetManager wsparamManager = null;
3111 public static ParamManager getUserParameterStore()
3113 if (wsparamManager == null)
3115 wsparamManager = new WsParamSetManager();
3117 return wsparamManager;
3121 * static hyperlink handler proxy method for use by Jalview's internal windows
3125 public static void hyperlinkUpdate(HyperlinkEvent e)
3127 if (e.getEventType() == EventType.ACTIVATED)
3132 url = e.getURL().toString();
3133 Desktop.showUrl(url);
3134 } catch (Exception x)
3139 .error("Couldn't handle string " + url + " as a URL.");
3141 // ignore any exceptions due to dud links.
3148 * single thread that handles display of dialogs to user.
3150 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3153 * flag indicating if dialogExecutor should try to acquire a permit
3155 private volatile boolean dialogPause = true;
3160 private Semaphore block = new Semaphore(0);
3162 private static groovy.ui.Console groovyConsole;
3165 * add another dialog thread to the queue
3169 public void addDialogThread(final Runnable prompter)
3171 dialogExecutor.submit(new Runnable()
3178 acquireDialogQueue();
3180 if (instance == null)
3186 SwingUtilities.invokeAndWait(prompter);
3187 } catch (Exception q)
3189 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3196 private boolean dialogQueueStarted = false;
3198 public void startDialogQueue()
3200 if (dialogQueueStarted)
3204 // set the flag so we don't pause waiting for another permit and semaphore
3205 // the current task to begin
3206 releaseDialogQueue();
3207 dialogQueueStarted = true;
3210 public void acquireDialogQueue()
3216 } catch (InterruptedException e)
3218 jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
3223 public void releaseDialogQueue()
3230 dialogPause = false;
3234 * Outputs an image of the desktop to file in EPS format, after prompting the
3235 * user for choice of Text or Lineart character rendering (unless a preference
3236 * has been set). The file name is generated as
3239 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3243 protected void snapShotWindow_actionPerformed(ActionEvent e)
3245 // currently the menu option to do this is not shown
3248 int width = getWidth();
3249 int height = getHeight();
3251 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3252 ImageWriterI writer = new ImageWriterI()
3255 public void exportImage(Graphics g) throws Exception
3258 jalview.bin.Console.info("Successfully written snapshot to file "
3259 + of.getAbsolutePath());
3262 String title = "View of desktop";
3263 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3267 exporter.doExport(of, this, width, height, title);
3268 } catch (ImageOutputException ioex)
3270 jalview.bin.Console.error(
3271 "Unexpected error whilst writing Jalview desktop snapshot as EPS",
3277 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3278 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3279 * and location last time the view was expanded (if any). However it does not
3280 * remember the split pane divider location - this is set to match the
3281 * 'exploding' frame.
3285 public void explodeViews(SplitFrame sf)
3287 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3288 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3289 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3291 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3293 int viewCount = topPanels.size();
3300 * Processing in reverse order works, forwards order leaves the first panels not
3301 * visible. I don't know why!
3303 for (int i = viewCount - 1; i >= 0; i--)
3306 * Make new top and bottom frames. These take over the respective AlignmentPanel
3307 * objects, including their AlignmentViewports, so the cdna/protein
3308 * relationships between the viewports is carried over to the new split frames.
3310 * explodedGeometry holds the (x, y) position of the previously exploded
3311 * SplitFrame, and the (width, height) of the AlignFrame component
3313 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3314 AlignFrame newTopFrame = new AlignFrame(topPanel);
3315 newTopFrame.setSize(oldTopFrame.getSize());
3316 newTopFrame.setVisible(true);
3317 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3318 .getExplodedGeometry();
3319 if (geometry != null)
3321 newTopFrame.setSize(geometry.getSize());
3324 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3325 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3326 newBottomFrame.setSize(oldBottomFrame.getSize());
3327 newBottomFrame.setVisible(true);
3328 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3329 .getExplodedGeometry();
3330 if (geometry != null)
3332 newBottomFrame.setSize(geometry.getSize());
3335 topPanel.av.setGatherViewsHere(false);
3336 bottomPanel.av.setGatherViewsHere(false);
3337 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3339 if (geometry != null)
3341 splitFrame.setLocation(geometry.getLocation());
3343 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3347 * Clear references to the panels (now relocated in the new SplitFrames) before
3348 * closing the old SplitFrame.
3351 bottomPanels.clear();
3356 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3357 * back into the given SplitFrame as additional views. Note that the gathered
3358 * frames may themselves have multiple views.
3362 public void gatherViews(GSplitFrame source)
3365 * special handling of explodedGeometry for a view within a SplitFrame: - it
3366 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3367 * height) of the AlignFrame component
3369 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3370 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3371 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3372 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3373 myBottomFrame.viewport
3374 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3375 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3376 myTopFrame.viewport.setGatherViewsHere(true);
3377 myBottomFrame.viewport.setGatherViewsHere(true);
3378 String topViewId = myTopFrame.viewport.getSequenceSetId();
3379 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3381 JInternalFrame[] frames = desktop.getAllFrames();
3382 for (JInternalFrame frame : frames)
3384 if (frame instanceof SplitFrame && frame != source)
3386 SplitFrame sf = (SplitFrame) frame;
3387 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3388 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3389 boolean gatherThis = false;
3390 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3392 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3393 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3394 if (topViewId.equals(topPanel.av.getSequenceSetId())
3395 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3398 topPanel.av.setGatherViewsHere(false);
3399 bottomPanel.av.setGatherViewsHere(false);
3400 topPanel.av.setExplodedGeometry(
3401 new Rectangle(sf.getLocation(), topFrame.getSize()));
3402 bottomPanel.av.setExplodedGeometry(
3403 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3404 myTopFrame.addAlignmentPanel(topPanel, false);
3405 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3411 topFrame.getAlignPanels().clear();
3412 bottomFrame.getAlignPanels().clear();
3419 * The dust settles...give focus to the tab we did this from.
3421 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3424 public static groovy.ui.Console getGroovyConsole()
3426 return groovyConsole;
3430 * handles the payload of a drag and drop event.
3432 * TODO refactor to desktop utilities class
3435 * - Data source strings extracted from the drop event
3437 * - protocol for each data source extracted from the drop event
3441 * - the payload from the drop event
3444 public static void transferFromDropTarget(List<Object> files,
3445 List<DataSourceType> protocols, DropTargetDropEvent evt,
3446 Transferable t) throws Exception
3449 DataFlavor uriListFlavor = new DataFlavor(
3450 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3453 urlFlavour = new DataFlavor(
3454 "application/x-java-url; class=java.net.URL");
3455 } catch (ClassNotFoundException cfe)
3457 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3461 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3466 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3467 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3468 // means url may be null.
3471 protocols.add(DataSourceType.URL);
3472 files.add(url.toString());
3473 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3474 + files.get(files.size() - 1));
3479 if (Platform.isAMacAndNotJS())
3481 jalview.bin.Console.errPrintln(
3482 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3485 } catch (Throwable ex)
3487 jalview.bin.Console.debug("URL drop handler failed.", ex);
3490 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3492 // Works on Windows and MacOSX
3493 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3494 for (Object file : (List) t
3495 .getTransferData(DataFlavor.javaFileListFlavor))
3498 protocols.add(DataSourceType.FILE);
3503 // Unix like behaviour
3504 boolean added = false;
3506 if (t.isDataFlavorSupported(uriListFlavor))
3508 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3509 // This is used by Unix drag system
3510 data = (String) t.getTransferData(uriListFlavor);
3514 // fallback to text: workaround - on OSX where there's a JVM bug
3516 .debug("standard URIListFlavor failed. Trying text");
3517 // try text fallback
3518 DataFlavor textDf = new DataFlavor(
3519 "text/plain;class=java.lang.String");
3520 if (t.isDataFlavorSupported(textDf))
3522 data = (String) t.getTransferData(textDf);
3525 jalview.bin.Console.debug("Plain text drop content returned "
3526 + (data == null ? "Null - failed" : data));
3531 while (protocols.size() < files.size())
3533 jalview.bin.Console.debug("Adding missing FILE protocol for "
3534 + files.get(protocols.size()));
3535 protocols.add(DataSourceType.FILE);
3537 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3538 data, "\r\n"); st.hasMoreTokens();)
3541 String s = st.nextToken();
3542 if (s.startsWith("#"))
3544 // the line is a comment (as per the RFC 2483)
3547 java.net.URI uri = new java.net.URI(s);
3548 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3550 protocols.add(DataSourceType.URL);
3551 files.add(uri.toString());
3555 // otherwise preserve old behaviour: catch all for file objects
3556 java.io.File file = new java.io.File(uri);
3557 protocols.add(DataSourceType.FILE);
3558 files.add(file.toString());
3563 if (jalview.bin.Console.isDebugEnabled())
3565 if (data == null || !added)
3568 if (t.getTransferDataFlavors() != null
3569 && t.getTransferDataFlavors().length > 0)
3571 jalview.bin.Console.debug(
3572 "Couldn't resolve drop data. Here are the supported flavors:");
3573 for (DataFlavor fl : t.getTransferDataFlavors())
3575 jalview.bin.Console.debug(
3576 "Supported transfer dataflavor: " + fl.toString());
3577 Object df = t.getTransferData(fl);
3580 jalview.bin.Console.debug("Retrieves: " + df);
3584 jalview.bin.Console.debug("Retrieved nothing");
3591 .debug("Couldn't resolve dataflavor for drop: "
3597 if (Platform.isWindowsAndNotJS())
3600 .debug("Scanning dropped content for Windows Link Files");
3602 // resolve any .lnk files in the file drop
3603 for (int f = 0; f < files.size(); f++)
3605 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3606 if (protocols.get(f).equals(DataSourceType.FILE)
3607 && (source.endsWith(".lnk") || source.endsWith(".url")
3608 || source.endsWith(".site")))
3612 Object obj = files.get(f);
3613 File lf = (obj instanceof File ? (File) obj
3614 : new File((String) obj));
3615 // process link file to get a URL
3616 jalview.bin.Console.debug("Found potential link file: " + lf);
3617 WindowsShortcut wscfile = new WindowsShortcut(lf);
3618 String fullname = wscfile.getRealFilename();
3619 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3620 files.set(f, fullname);
3621 jalview.bin.Console.debug("Parsed real filename " + fullname
3622 + " to extract protocol: " + protocols.get(f));
3623 } catch (Exception ex)
3625 jalview.bin.Console.error(
3626 "Couldn't parse " + files.get(f) + " as a link file.",
3635 * Sets the Preferences property for experimental features to True or False
3636 * depending on the state of the controlling menu item
3639 protected void showExperimental_actionPerformed(boolean selected)
3641 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3645 * Answers a (possibly empty) list of any structure viewer frames (currently
3646 * for either Jmol or Chimera) which are currently open. This may optionally
3647 * be restricted to viewers of a specified class, or viewers linked to a
3648 * specified alignment panel.
3651 * if not null, only return viewers linked to this panel
3652 * @param structureViewerClass
3653 * if not null, only return viewers of this class
3656 public List<StructureViewerBase> getStructureViewers(
3657 AlignmentPanel apanel,
3658 Class<? extends StructureViewerBase> structureViewerClass)
3660 List<StructureViewerBase> result = new ArrayList<>();
3661 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3663 for (JInternalFrame frame : frames)
3665 if (frame instanceof StructureViewerBase)
3667 if (structureViewerClass == null
3668 || structureViewerClass.isInstance(frame))
3671 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3673 result.add((StructureViewerBase) frame);
3681 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3683 private static boolean debugScaleMessageDone = false;
3685 public static void debugScaleMessage(Graphics g)
3687 if (debugScaleMessageDone)
3691 // output used by tests to check HiDPI scaling settings in action
3694 Graphics2D gg = (Graphics2D) g;
3697 AffineTransform t = gg.getTransform();
3698 double scaleX = t.getScaleX();
3699 double scaleY = t.getScaleY();
3700 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3701 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3702 debugScaleMessageDone = true;
3706 jalview.bin.Console.debug("Desktop graphics null");
3708 } catch (Exception e)
3710 jalview.bin.Console.debug(Cache.getStackTraceString(e));
3715 * closes the current instance window, disposes and forgets about it.
3717 public static void closeDesktop()
3719 if (Desktop.instance != null)
3721 Desktop.instance.closeAll_actionPerformed(null);
3722 Desktop.instance.setVisible(false);
3723 Desktop us = Desktop.instance;
3724 Desktop.instance = null;
3725 // call dispose in a separate thread - try to avoid indirect deadlocks
3726 new Thread(new Runnable()
3731 ExecutorService dex = us.dialogExecutor;
3735 us.dialogExecutor = null;
3736 us.block.drainPermits();
3745 * checks if any progress bars are being displayed in any of the windows
3746 * managed by the desktop
3750 public boolean operationsAreInProgress()
3752 JInternalFrame[] frames = getAllFrames();
3753 for (JInternalFrame frame : frames)
3755 if (frame instanceof IProgressIndicator)
3757 if (((IProgressIndicator) frame).operationInProgress())
3763 return operationInProgress();
3767 * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
3768 * The way the modal JInternalFrame is made means it cannot be a child of an
3769 * AlignFrame, so closing the AlignFrame might leave the modal open :(
3771 private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
3773 protected static void addModal(AlignFrame af, JInternalFrame jif)
3775 alignFrameModalMap.put(af, jif);
3778 protected static void closeModal(AlignFrame af)
3780 if (!alignFrameModalMap.containsKey(af))
3784 JInternalFrame jif = alignFrameModalMap.get(af);
3789 jif.setClosed(true);
3790 } catch (PropertyVetoException e)
3792 e.printStackTrace();
3795 alignFrameModalMap.remove(af);