2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.Callable;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Semaphore;
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.Box;
76 import javax.swing.BoxLayout;
77 import javax.swing.DefaultDesktopManager;
78 import javax.swing.DesktopManager;
79 import javax.swing.InputMap;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JComboBox;
83 import javax.swing.JComponent;
84 import javax.swing.JDesktopPane;
85 import javax.swing.JFrame;
86 import javax.swing.JInternalFrame;
87 import javax.swing.JLabel;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JProgressBar;
92 import javax.swing.JTextField;
93 import javax.swing.KeyStroke;
94 import javax.swing.SwingUtilities;
95 import javax.swing.WindowConstants;
96 import javax.swing.event.HyperlinkEvent;
97 import javax.swing.event.HyperlinkEvent.EventType;
98 import javax.swing.event.InternalFrameAdapter;
99 import javax.swing.event.InternalFrameEvent;
101 import org.stackoverflowusers.file.WindowsShortcut;
103 import jalview.api.AlignViewportI;
104 import jalview.api.AlignmentViewPanel;
105 import jalview.api.structures.JalviewStructureDisplayI;
106 import jalview.bin.Cache;
107 import jalview.bin.Jalview;
108 import jalview.datamodel.Alignment;
109 import jalview.datamodel.HiddenColumns;
110 import jalview.datamodel.Sequence;
111 import jalview.datamodel.SequenceI;
112 import jalview.gui.ImageExporter.ImageWriterI;
113 import jalview.gui.QuitHandler.QResponse;
114 import jalview.io.BackupFiles;
115 import jalview.io.DataSourceType;
116 import jalview.io.FileFormat;
117 import jalview.io.FileFormatException;
118 import jalview.io.FileFormatI;
119 import jalview.io.FileFormats;
120 import jalview.io.FileLoader;
121 import jalview.io.FormatAdapter;
122 import jalview.io.IdentifyFile;
123 import jalview.io.JalviewFileChooser;
124 import jalview.io.JalviewFileView;
125 import jalview.jbgui.GSplitFrame;
126 import jalview.jbgui.GStructureViewer;
127 import jalview.project.Jalview2XML;
128 import jalview.structure.StructureSelectionManager;
129 import jalview.urls.IdOrgSettings;
130 import jalview.util.BrowserLauncher;
131 import jalview.util.ChannelProperties;
132 import jalview.util.ImageMaker.TYPE;
133 import jalview.util.LaunchUtils;
134 import jalview.util.MessageManager;
135 import jalview.util.Platform;
136 import jalview.util.ShortcutKeyMaskExWrapper;
137 import jalview.util.UrlConstants;
138 import jalview.viewmodel.AlignmentViewport;
139 import jalview.ws.params.ParamManager;
140 import jalview.ws.utils.UrlDownloadClient;
147 * @version $Revision: 1.155 $
149 public class Desktop extends jalview.jbgui.GDesktop
150 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
151 jalview.api.StructureSelectionManagerProvider
153 private static final String CITATION;
156 URL bg_logo_url = ChannelProperties.getImageURL(
157 "bg_logo." + String.valueOf(SplashScreen.logoSize));
158 URL uod_logo_url = ChannelProperties.getImageURL(
159 "uod_banner." + String.valueOf(SplashScreen.logoSize));
160 boolean logo = (bg_logo_url != null || uod_logo_url != null);
161 StringBuilder sb = new StringBuilder();
163 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
168 sb.append(bg_logo_url == null ? ""
169 : "<img alt=\"Barton Group logo\" src=\""
170 + bg_logo_url.toString() + "\">");
171 sb.append(uod_logo_url == null ? ""
172 : " <img alt=\"University of Dundee shield\" src=\""
173 + uod_logo_url.toString() + "\">");
175 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
176 sb.append("<br><br>If you use Jalview, please cite:"
177 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
178 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
179 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
180 CITATION = sb.toString();
183 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
185 private static int DEFAULT_MIN_WIDTH = 300;
187 private static int DEFAULT_MIN_HEIGHT = 250;
189 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
191 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
193 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
195 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
197 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
199 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
201 public static void setLiveDragMode(boolean b)
203 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
204 : JDesktopPane.OUTLINE_DRAG_MODE;
206 desktop.setDragMode(DRAG_MODE);
209 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
211 public static boolean nosplash = false;
214 * news reader - null if it was never started.
216 private BlogReader jvnews = null;
218 private File projectFile;
222 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
224 public void addJalviewPropertyChangeListener(
225 PropertyChangeListener listener)
227 changeSupport.addJalviewPropertyChangeListener(listener);
231 * @param propertyName
233 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
234 * java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(String propertyName,
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void removeJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.removeJalviewPropertyChangeListener(propertyName,
255 /** Singleton Desktop instance */
256 public static Desktop instance;
258 public static MyDesktopPane desktop;
260 public static MyDesktopPane getDesktop()
262 // BH 2018 could use currentThread() here as a reference to a
263 // Hashtable<Thread, MyDesktopPane> in JavaScript
267 static int openFrameCount = 0;
269 static final int xOffset = 30;
271 static final int yOffset = 30;
273 public static jalview.ws.jws1.Discoverer discoverer;
275 public static Object[] jalviewClipboard;
277 public static boolean internalCopy = false;
279 static int fileLoadingCount = 0;
281 class MyDesktopManager implements DesktopManager
284 private DesktopManager delegate;
286 public MyDesktopManager(DesktopManager delegate)
288 this.delegate = delegate;
292 public void activateFrame(JInternalFrame f)
296 delegate.activateFrame(f);
297 } catch (NullPointerException npe)
299 Point p = getMousePosition();
300 instance.showPasteMenu(p.x, p.y);
305 public void beginDraggingFrame(JComponent f)
307 delegate.beginDraggingFrame(f);
311 public void beginResizingFrame(JComponent f, int direction)
313 delegate.beginResizingFrame(f, direction);
317 public void closeFrame(JInternalFrame f)
319 delegate.closeFrame(f);
323 public void deactivateFrame(JInternalFrame f)
325 delegate.deactivateFrame(f);
329 public void deiconifyFrame(JInternalFrame f)
331 delegate.deiconifyFrame(f);
335 public void dragFrame(JComponent f, int newX, int newY)
341 delegate.dragFrame(f, newX, newY);
345 public void endDraggingFrame(JComponent f)
347 delegate.endDraggingFrame(f);
352 public void endResizingFrame(JComponent f)
354 delegate.endResizingFrame(f);
359 public void iconifyFrame(JInternalFrame f)
361 delegate.iconifyFrame(f);
365 public void maximizeFrame(JInternalFrame f)
367 delegate.maximizeFrame(f);
371 public void minimizeFrame(JInternalFrame f)
373 delegate.minimizeFrame(f);
377 public void openFrame(JInternalFrame f)
379 delegate.openFrame(f);
383 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
390 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
394 public void setBoundsForFrame(JComponent f, int newX, int newY,
395 int newWidth, int newHeight)
397 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
400 // All other methods, simply delegate
405 * Creates a new Desktop object.
411 * A note to implementors. It is ESSENTIAL that any activities that might
412 * block are spawned off as threads rather than waited for during this
417 doConfigureStructurePrefs();
418 setTitle(ChannelProperties.getProperty("app_name") + " "
419 + Cache.getProperty("VERSION"));
422 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
423 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
424 * officially documented or guaranteed to exist, so we access it via
425 * reflection. There appear to be unfathomable criteria about what this
426 * string can contain, and it if doesn't meet those criteria then "java"
427 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
428 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
429 * not. The reflection access may generate a warning: WARNING: An illegal
430 * reflective access operation has occurred WARNING: Illegal reflective
431 * access by jalview.gui.Desktop () to field
432 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
434 if (Platform.isLinux())
436 if (LaunchUtils.getJavaVersion() >= 11)
438 jalview.bin.Console.info(
439 "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.");
443 Toolkit xToolkit = Toolkit.getDefaultToolkit();
444 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
445 Field awtAppClassNameField = null;
447 if (Arrays.stream(declaredFields)
448 .anyMatch(f -> f.getName().equals("awtAppClassName")))
450 awtAppClassNameField = xToolkit.getClass()
451 .getDeclaredField("awtAppClassName");
454 String title = ChannelProperties.getProperty("app_name");
455 if (awtAppClassNameField != null)
457 awtAppClassNameField.setAccessible(true);
458 awtAppClassNameField.set(xToolkit, title);
462 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
464 } catch (Exception e)
466 jalview.bin.Console.debug("Error setting awtAppClassName");
467 jalview.bin.Console.trace(Cache.getStackTraceString(e));
471 setIconImages(ChannelProperties.getIconList());
473 // override quit handling when GUI OS close [X] button pressed
474 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
475 addWindowListener(new WindowAdapter()
478 public void windowClosing(WindowEvent ev)
480 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
484 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
486 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
487 desktop = new MyDesktopPane(selmemusage);
489 showMemusage.setSelected(selmemusage);
490 desktop.setBackground(Color.white);
492 getContentPane().setLayout(new BorderLayout());
493 // alternate config - have scrollbars - see notes in JAL-153
494 // JScrollPane sp = new JScrollPane();
495 // sp.getViewport().setView(desktop);
496 // getContentPane().add(sp, BorderLayout.CENTER);
498 // BH 2018 - just an experiment to try unclipped JInternalFrames.
501 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
504 getContentPane().add(desktop, BorderLayout.CENTER);
505 desktop.setDragMode(DRAG_MODE);
507 // This line prevents Windows Look&Feel resizing all new windows to maximum
508 // if previous window was maximised
509 desktop.setDesktopManager(new MyDesktopManager(
510 Platform.isJS() ? desktop.getDesktopManager()
511 : new DefaultDesktopManager()));
513 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
514 : Platform.isAMacAndNotJS()
515 ? new AquaInternalFrameManager(
516 desktop.getDesktopManager())
517 : desktop.getDesktopManager())));
520 Rectangle dims = getLastKnownDimensions("");
527 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
528 int xPos = Math.max(5, (screenSize.width - 900) / 2);
529 int yPos = Math.max(5, (screenSize.height - 650) / 2);
530 setBounds(xPos, yPos, 900, 650);
533 if (!Platform.isJS())
540 jconsole = new Console(this, showjconsole);
541 jconsole.setHeader(Cache.getVersionDetailsForConsole());
542 showConsole(showjconsole);
544 showNews.setVisible(false);
546 experimentalFeatures.setSelected(showExperimental());
548 getIdentifiersOrgData();
552 // Spawn a thread that shows the splashscreen
555 SwingUtilities.invokeLater(new Runnable()
560 new SplashScreen(true);
565 // Thread off a new instance of the file chooser - this reduces the time
567 // takes to open it later on.
568 new Thread(new Runnable()
573 jalview.bin.Console.debug("Filechooser init thread started.");
574 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
575 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
577 jalview.bin.Console.debug("Filechooser init thread finished.");
580 // Add the service change listener
581 changeSupport.addJalviewPropertyChangeListener("services",
582 new PropertyChangeListener()
586 public void propertyChange(PropertyChangeEvent evt)
589 .debug("Firing service changed event for "
590 + evt.getNewValue());
591 JalviewServicesChanged(evt);
596 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
599 this.addMouseListener(ma = new MouseAdapter()
602 public void mousePressed(MouseEvent evt)
604 if (evt.isPopupTrigger()) // Mac
606 showPasteMenu(evt.getX(), evt.getY());
611 public void mouseReleased(MouseEvent evt)
613 if (evt.isPopupTrigger()) // Windows
615 showPasteMenu(evt.getX(), evt.getY());
619 desktop.addMouseListener(ma);
623 * Answers true if user preferences to enable experimental features is True
628 public boolean showExperimental()
630 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
631 Boolean.FALSE.toString());
632 return Boolean.valueOf(experimental).booleanValue();
635 public void doConfigureStructurePrefs()
637 // configure services
638 StructureSelectionManager ssm = StructureSelectionManager
639 .getStructureSelectionManager(this);
640 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
642 ssm.setAddTempFacAnnot(
643 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
644 ssm.setProcessSecondaryStructure(
645 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
646 // JAL-3915 - RNAView is no longer an option so this has no effect
647 ssm.setSecStructServices(
648 Cache.getDefault(Preferences.USE_RNAVIEW, false));
652 ssm.setAddTempFacAnnot(false);
653 ssm.setProcessSecondaryStructure(false);
654 ssm.setSecStructServices(false);
658 public void checkForNews()
660 final Desktop me = this;
661 // Thread off the news reader, in case there are connection problems.
662 new Thread(new Runnable()
667 jalview.bin.Console.debug("Starting news thread.");
668 jvnews = new BlogReader(me);
669 showNews.setVisible(true);
670 jalview.bin.Console.debug("Completed news thread.");
675 public void getIdentifiersOrgData()
677 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
678 {// Thread off the identifiers fetcher
679 new Thread(new Runnable()
685 .debug("Downloading data from identifiers.org");
688 UrlDownloadClient.download(IdOrgSettings.getUrl(),
689 IdOrgSettings.getDownloadLocation());
690 } catch (IOException e)
693 .debug("Exception downloading identifiers.org data"
703 protected void showNews_actionPerformed(ActionEvent e)
705 showNews(showNews.isSelected());
708 void showNews(boolean visible)
710 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
711 showNews.setSelected(visible);
712 if (visible && !jvnews.isVisible())
714 new Thread(new Runnable()
719 long now = System.currentTimeMillis();
720 Desktop.instance.setProgressBar(
721 MessageManager.getString("status.refreshing_news"), now);
722 jvnews.refreshNews();
723 Desktop.instance.setProgressBar(null, now);
731 * recover the last known dimensions for a jalview window
734 * - empty string is desktop, all other windows have unique prefix
735 * @return null or last known dimensions scaled to current geometry (if last
736 * window geom was known)
738 Rectangle getLastKnownDimensions(String windowName)
740 // TODO: lock aspect ratio for scaling desktop Bug #0058199
741 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
742 String x = Cache.getProperty(windowName + "SCREEN_X");
743 String y = Cache.getProperty(windowName + "SCREEN_Y");
744 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
745 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
746 if ((x != null) && (y != null) && (width != null) && (height != null))
748 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
749 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
750 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
752 // attempt #1 - try to cope with change in screen geometry - this
753 // version doesn't preserve original jv aspect ratio.
754 // take ratio of current screen size vs original screen size.
755 double sw = ((1f * screenSize.width) / (1f * Integer
756 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
757 double sh = ((1f * screenSize.height) / (1f * Integer
758 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
759 // rescale the bounds depending upon the current screen geometry.
760 ix = (int) (ix * sw);
761 iw = (int) (iw * sw);
762 iy = (int) (iy * sh);
763 ih = (int) (ih * sh);
764 while (ix >= screenSize.width)
766 jalview.bin.Console.debug(
767 "Window geometry location recall error: shifting horizontal to within screenbounds.");
768 ix -= screenSize.width;
770 while (iy >= screenSize.height)
772 jalview.bin.Console.debug(
773 "Window geometry location recall error: shifting vertical to within screenbounds.");
774 iy -= screenSize.height;
776 jalview.bin.Console.debug(
777 "Got last known dimensions for " + windowName + ": x:" + ix
778 + " y:" + iy + " width:" + iw + " height:" + ih);
780 // return dimensions for new instance
781 return new Rectangle(ix, iy, iw, ih);
786 void showPasteMenu(int x, int y)
788 JPopupMenu popup = new JPopupMenu();
789 JMenuItem item = new JMenuItem(
790 MessageManager.getString("label.paste_new_window"));
791 item.addActionListener(new ActionListener()
794 public void actionPerformed(ActionEvent evt)
801 popup.show(this, x, y);
806 // quick patch for JAL-4150 - needs some more work and test coverage
807 // TODO - unify below and AlignFrame.paste()
808 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
809 // clipboard has come from a different alignment window than the one where
810 // paste has been called! JAL-4151
812 if (Desktop.jalviewClipboard != null)
814 // The clipboard was filled from within Jalview, we must use the
816 // And dataset from the copied alignment
817 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
818 // be doubly sure that we create *new* sequence objects.
819 SequenceI[] sequences = new SequenceI[newseq.length];
820 for (int i = 0; i < newseq.length; i++)
822 sequences[i] = new Sequence(newseq[i]);
824 Alignment alignment = new Alignment(sequences);
825 // dataset is inherited
826 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
827 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
828 AlignFrame.DEFAULT_HEIGHT);
829 String newtitle = new String("Copied sequences");
831 if (Desktop.jalviewClipboard[2] != null)
833 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
834 af.viewport.setHiddenColumns(hc);
837 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
838 AlignFrame.DEFAULT_HEIGHT);
845 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
846 Transferable contents = c.getContents(this);
848 if (contents != null)
850 String file = (String) contents
851 .getTransferData(DataFlavor.stringFlavor);
853 FileFormatI format = new IdentifyFile().identify(file,
854 DataSourceType.PASTE);
856 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
859 } catch (Exception ex)
862 "Unable to paste alignment from system clipboard:\n" + ex);
868 * Adds and opens the given frame to the desktop
879 public static synchronized void addInternalFrame(
880 final JInternalFrame frame, String title, int w, int h)
882 addInternalFrame(frame, title, true, w, h, true, false);
886 * Add an internal frame to the Jalview desktop
893 * When true, display frame immediately, otherwise, caller must call
894 * setVisible themselves.
900 public static synchronized void addInternalFrame(
901 final JInternalFrame frame, String title, boolean makeVisible,
904 addInternalFrame(frame, title, makeVisible, w, h, true, false);
908 * Add an internal frame to the Jalview desktop and make it visible
921 public static synchronized void addInternalFrame(
922 final JInternalFrame frame, String title, int w, int h,
925 addInternalFrame(frame, title, true, w, h, resizable, false);
929 * Add an internal frame to the Jalview desktop
936 * When true, display frame immediately, otherwise, caller must call
937 * setVisible themselves.
944 * @param ignoreMinSize
945 * Do not set the default minimum size for frame
947 public static synchronized void addInternalFrame(
948 final JInternalFrame frame, String title, boolean makeVisible,
949 int w, int h, boolean resizable, boolean ignoreMinSize)
952 // TODO: allow callers to determine X and Y position of frame (eg. via
954 // TODO: consider fixing method to update entries in the window submenu with
955 // the current window title
957 frame.setTitle(title);
958 if (frame.getWidth() < 1 || frame.getHeight() < 1)
962 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
963 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
964 // IF JALVIEW IS RUNNING HEADLESS
965 // ///////////////////////////////////////////////
966 if (instance == null || (System.getProperty("java.awt.headless") != null
967 && System.getProperty("java.awt.headless").equals("true")))
976 frame.setMinimumSize(
977 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
979 // Set default dimension for Alignment Frame window.
980 // The Alignment Frame window could be added from a number of places,
982 // I did this here in order not to miss out on any Alignment frame.
983 if (frame instanceof AlignFrame)
985 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
986 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
990 frame.setVisible(makeVisible);
991 frame.setClosable(true);
992 frame.setResizable(resizable);
993 frame.setMaximizable(resizable);
994 frame.setIconifiable(resizable);
995 frame.setOpaque(Platform.isJS());
997 if (frame.getX() < 1 && frame.getY() < 1)
999 frame.setLocation(xOffset * openFrameCount,
1000 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1004 * add an entry for the new frame in the Window menu (and remove it when the
1007 final JMenuItem menuItem = new JMenuItem(title);
1008 frame.addInternalFrameListener(new InternalFrameAdapter()
1011 public void internalFrameActivated(InternalFrameEvent evt)
1013 JInternalFrame itf = desktop.getSelectedFrame();
1016 if (itf instanceof AlignFrame)
1018 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1025 public void internalFrameClosed(InternalFrameEvent evt)
1027 PaintRefresher.RemoveComponent(frame);
1030 * defensive check to prevent frames being added half off the window
1032 if (openFrameCount > 0)
1038 * ensure no reference to alignFrame retained by menu item listener
1040 if (menuItem.getActionListeners().length > 0)
1042 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1044 windowMenu.remove(menuItem);
1048 menuItem.addActionListener(new ActionListener()
1051 public void actionPerformed(ActionEvent e)
1055 frame.setSelected(true);
1056 frame.setIcon(false);
1057 } catch (java.beans.PropertyVetoException ex)
1064 setKeyBindings(frame);
1068 windowMenu.add(menuItem);
1073 frame.setSelected(true);
1074 frame.requestFocus();
1075 } catch (java.beans.PropertyVetoException ve)
1077 } catch (java.lang.ClassCastException cex)
1079 jalview.bin.Console.warn(
1080 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1086 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1091 private static void setKeyBindings(JInternalFrame frame)
1093 @SuppressWarnings("serial")
1094 final Action closeAction = new AbstractAction()
1097 public void actionPerformed(ActionEvent e)
1104 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1106 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1107 InputEvent.CTRL_DOWN_MASK);
1108 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1109 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1111 InputMap inputMap = frame
1112 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1113 String ctrlW = ctrlWKey.toString();
1114 inputMap.put(ctrlWKey, ctrlW);
1115 inputMap.put(cmdWKey, ctrlW);
1117 ActionMap actionMap = frame.getActionMap();
1118 actionMap.put(ctrlW, closeAction);
1122 public void lostOwnership(Clipboard clipboard, Transferable contents)
1126 Desktop.jalviewClipboard = null;
1129 internalCopy = false;
1133 public void dragEnter(DropTargetDragEvent evt)
1138 public void dragExit(DropTargetEvent evt)
1143 public void dragOver(DropTargetDragEvent evt)
1148 public void dropActionChanged(DropTargetDragEvent evt)
1159 public void drop(DropTargetDropEvent evt)
1161 boolean success = true;
1162 // JAL-1552 - acceptDrop required before getTransferable call for
1163 // Java's Transferable for native dnd
1164 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1165 Transferable t = evt.getTransferable();
1166 List<Object> files = new ArrayList<>();
1167 List<DataSourceType> protocols = new ArrayList<>();
1171 Desktop.transferFromDropTarget(files, protocols, evt, t);
1172 } catch (Exception e)
1174 e.printStackTrace();
1182 for (int i = 0; i < files.size(); i++)
1184 // BH 2018 File or String
1185 Object file = files.get(i);
1186 String fileName = file.toString();
1187 DataSourceType protocol = (protocols == null)
1188 ? DataSourceType.FILE
1190 FileFormatI format = null;
1192 if (fileName.endsWith(".jar"))
1194 format = FileFormat.Jalview;
1199 format = new IdentifyFile().identify(file, protocol);
1201 if (file instanceof File)
1203 Platform.cacheFileData((File) file);
1205 new FileLoader().LoadFile(null, file, protocol, format);
1208 } catch (Exception ex)
1213 evt.dropComplete(success); // need this to ensure input focus is properly
1214 // transfered to any new windows created
1224 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1226 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1227 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1228 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1229 BackupFiles.getEnabled());
1231 chooser.setFileView(new JalviewFileView());
1232 chooser.setDialogTitle(
1233 MessageManager.getString("label.open_local_file"));
1234 chooser.setToolTipText(MessageManager.getString("action.open"));
1236 chooser.setResponseHandler(0, () -> {
1237 File selectedFile = chooser.getSelectedFile();
1238 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1240 FileFormatI format = chooser.getSelectedFormat();
1243 * Call IdentifyFile to verify the file contains what its extension implies.
1244 * Skip this step for dynamically added file formats, because IdentifyFile does
1245 * not know how to recognise them.
1247 if (FileFormats.getInstance().isIdentifiable(format))
1251 format = new IdentifyFile().identify(selectedFile,
1252 DataSourceType.FILE);
1253 } catch (FileFormatException e)
1255 // format = null; //??
1259 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1263 chooser.showOpenDialog(this);
1267 * Shows a dialog for input of a URL at which to retrieve alignment data
1272 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1274 // This construct allows us to have a wider textfield
1276 JLabel label = new JLabel(
1277 MessageManager.getString("label.input_file_url"));
1279 JPanel panel = new JPanel(new GridLayout(2, 1));
1283 * the URL to fetch is input in Java: an editable combobox with history JS:
1284 * (pending JAL-3038) a plain text field
1287 String urlBase = "https://www.";
1288 if (Platform.isJS())
1290 history = new JTextField(urlBase, 35);
1299 JComboBox<String> asCombo = new JComboBox<>();
1300 asCombo.setPreferredSize(new Dimension(400, 20));
1301 asCombo.setEditable(true);
1302 asCombo.addItem(urlBase);
1303 String historyItems = Cache.getProperty("RECENT_URL");
1304 if (historyItems != null)
1306 for (String token : historyItems.split("\\t"))
1308 asCombo.addItem(token);
1315 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1316 MessageManager.getString("action.cancel") };
1317 Callable<Void> action = () -> {
1318 @SuppressWarnings("unchecked")
1319 String url = (history instanceof JTextField
1320 ? ((JTextField) history).getText()
1321 : ((JComboBox<String>) history).getEditor().getItem()
1322 .toString().trim());
1324 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1326 if (viewport != null)
1328 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1329 FileFormat.Jalview);
1333 new FileLoader().LoadFile(url, DataSourceType.URL,
1334 FileFormat.Jalview);
1339 FileFormatI format = null;
1342 format = new IdentifyFile().identify(url, DataSourceType.URL);
1343 } catch (FileFormatException e)
1345 // TODO revise error handling, distinguish between
1346 // URL not found and response not valid
1351 String msg = MessageManager.formatMessage("label.couldnt_locate",
1353 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1354 MessageManager.getString("label.url_not_found"),
1355 JvOptionPane.WARNING_MESSAGE);
1357 return null; // Void
1360 if (viewport != null)
1362 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1367 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1370 return null; // Void
1372 String dialogOption = MessageManager
1373 .getString("label.input_alignment_from_url");
1374 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1375 .showInternalDialog(panel, dialogOption,
1376 JvOptionPane.YES_NO_CANCEL_OPTION,
1377 JvOptionPane.PLAIN_MESSAGE, null, options,
1378 MessageManager.getString("action.ok"));
1382 * Opens the CutAndPaste window for the user to paste an alignment in to
1385 * - if not null, the pasted alignment is added to the current
1386 * alignment; if null, to a new alignment window
1389 public void inputTextboxMenuItem_actionPerformed(
1390 AlignmentViewPanel viewPanel)
1392 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1393 cap.setForInput(viewPanel);
1394 Desktop.addInternalFrame(cap,
1395 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1400 * Check with user and saving files before actually quitting
1402 public void desktopQuit()
1404 desktopQuit(true, false);
1407 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1409 final Callable<Void> doDesktopQuit = () -> {
1410 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1411 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1412 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1413 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1414 getBounds().y, getWidth(), getHeight()));
1416 if (jconsole != null)
1418 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1419 jconsole.stopConsole();
1424 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1427 // Frames should all close automatically. Keeping external
1428 // viewers open should already be decided by user.
1429 closeAll_actionPerformed(null);
1431 // check for aborted quit
1432 if (QuitHandler.quitCancelled())
1434 jalview.bin.Console.debug("Desktop aborting quit");
1438 if (dialogExecutor != null)
1440 dialogExecutor.shutdownNow();
1443 if (groovyConsole != null)
1445 // suppress a possible repeat prompt to save script
1446 groovyConsole.setDirty(false);
1447 groovyConsole.exit();
1450 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1452 // note that shutdown hook will not be run
1453 jalview.bin.Console.debug("Force Quit selected by user");
1454 Runtime.getRuntime().halt(0);
1457 jalview.bin.Console.debug("Quit selected by user");
1460 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1461 // instance.dispose();
1465 return null; // Void
1468 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1469 QuitHandler.defaultCancelQuit);
1473 * Don't call this directly, use desktopQuit() above. Exits the program.
1478 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1479 // not run a second time if gotQuitResponse flag has been set (i.e. user
1480 // confirmed quit of some kind).
1481 Jalview.exit("Desktop exiting.", 0);
1484 private void storeLastKnownDimensions(String string, Rectangle jc)
1486 jalview.bin.Console.debug("Storing last known dimensions for " + string
1487 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1488 + " height:" + jc.height);
1490 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1491 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1492 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1493 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1503 public void aboutMenuItem_actionPerformed(ActionEvent e)
1505 new Thread(new Runnable()
1510 new SplashScreen(false);
1516 * Returns the html text for the About screen, including any available version
1517 * number, build details, author details and citation reference, but without
1518 * the enclosing {@code html} tags
1522 public String getAboutMessage()
1524 StringBuilder message = new StringBuilder(1024);
1525 message.append("<div style=\"font-family: sans-serif;\">")
1526 .append("<h1><strong>Version: ")
1527 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1528 .append("<strong>Built: <em>")
1529 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1530 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1531 .append("</strong>");
1533 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1534 if (latestVersion.equals("Checking"))
1536 // JBP removed this message for 2.11: May be reinstated in future version
1537 // message.append("<br>...Checking latest version...</br>");
1539 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1541 boolean red = false;
1542 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1543 .indexOf("automated build") == -1)
1546 // Displayed when code version and jnlp version do not match and code
1547 // version is not a development build
1548 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1551 message.append("<br>!! Version ")
1552 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1553 .append(" is available for download from ")
1554 .append(Cache.getDefault("www.jalview.org",
1555 "https://www.jalview.org"))
1559 message.append("</div>");
1562 message.append("<br>Authors: ");
1563 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1564 message.append(CITATION);
1566 message.append("</div>");
1568 return message.toString();
1572 * Action on requesting Help documentation
1575 public void documentationMenuItem_actionPerformed()
1579 if (Platform.isJS())
1581 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1590 Help.showHelpWindow();
1592 } catch (Exception ex)
1594 System.err.println("Error opening help: " + ex.getMessage());
1599 public void closeAll_actionPerformed(ActionEvent e)
1601 // TODO show a progress bar while closing?
1602 JInternalFrame[] frames = desktop.getAllFrames();
1603 for (int i = 0; i < frames.length; i++)
1607 frames[i].setClosed(true);
1608 } catch (java.beans.PropertyVetoException ex)
1612 Jalview.setCurrentAlignFrame(null);
1613 System.out.println("ALL CLOSED");
1616 * reset state of singleton objects as appropriate (clear down session state
1617 * when all windows are closed)
1619 StructureSelectionManager ssm = StructureSelectionManager
1620 .getStructureSelectionManager(this);
1627 public int structureViewersStillRunningCount()
1630 JInternalFrame[] frames = desktop.getAllFrames();
1631 for (int i = 0; i < frames.length; i++)
1633 if (frames[i] != null
1634 && frames[i] instanceof JalviewStructureDisplayI)
1636 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1644 public void raiseRelated_actionPerformed(ActionEvent e)
1646 reorderAssociatedWindows(false, false);
1650 public void minimizeAssociated_actionPerformed(ActionEvent e)
1652 reorderAssociatedWindows(true, false);
1655 void closeAssociatedWindows()
1657 reorderAssociatedWindows(false, true);
1663 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1667 protected void garbageCollect_actionPerformed(ActionEvent e)
1669 // We simply collect the garbage
1670 jalview.bin.Console.debug("Collecting garbage...");
1672 jalview.bin.Console.debug("Finished garbage collection.");
1678 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1682 protected void showMemusage_actionPerformed(ActionEvent e)
1684 desktop.showMemoryUsage(showMemusage.isSelected());
1691 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1695 protected void showConsole_actionPerformed(ActionEvent e)
1697 showConsole(showConsole.isSelected());
1700 Console jconsole = null;
1703 * control whether the java console is visible or not
1707 void showConsole(boolean selected)
1709 // TODO: decide if we should update properties file
1710 if (jconsole != null) // BH 2018
1712 showConsole.setSelected(selected);
1713 Cache.setProperty("SHOW_JAVA_CONSOLE",
1714 Boolean.valueOf(selected).toString());
1715 jconsole.setVisible(selected);
1719 void reorderAssociatedWindows(boolean minimize, boolean close)
1721 JInternalFrame[] frames = desktop.getAllFrames();
1722 if (frames == null || frames.length < 1)
1727 AlignmentViewport source = null, target = null;
1728 if (frames[0] instanceof AlignFrame)
1730 source = ((AlignFrame) frames[0]).getCurrentView();
1732 else if (frames[0] instanceof TreePanel)
1734 source = ((TreePanel) frames[0]).getViewPort();
1736 else if (frames[0] instanceof PCAPanel)
1738 source = ((PCAPanel) frames[0]).av;
1740 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1742 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1747 for (int i = 0; i < frames.length; i++)
1750 if (frames[i] == null)
1754 if (frames[i] instanceof AlignFrame)
1756 target = ((AlignFrame) frames[i]).getCurrentView();
1758 else if (frames[i] instanceof TreePanel)
1760 target = ((TreePanel) frames[i]).getViewPort();
1762 else if (frames[i] instanceof PCAPanel)
1764 target = ((PCAPanel) frames[i]).av;
1766 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1768 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1771 if (source == target)
1777 frames[i].setClosed(true);
1781 frames[i].setIcon(minimize);
1784 frames[i].toFront();
1788 } catch (java.beans.PropertyVetoException ex)
1803 protected void preferences_actionPerformed(ActionEvent e)
1805 Preferences.openPreferences();
1809 * Prompts the user to choose a file and then saves the Jalview state as a
1810 * Jalview project file
1813 public void saveState_actionPerformed()
1815 saveState_actionPerformed(false);
1818 public void saveState_actionPerformed(boolean saveAs)
1820 java.io.File projectFile = getProjectFile();
1821 // autoSave indicates we already have a file and don't need to ask
1822 boolean autoSave = projectFile != null && !saveAs
1823 && BackupFiles.getEnabled();
1825 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1826 // saveAs="+saveAs+", Backups
1827 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1829 boolean approveSave = false;
1832 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1835 chooser.setFileView(new JalviewFileView());
1836 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1838 int value = chooser.showSaveDialog(this);
1840 if (value == JalviewFileChooser.APPROVE_OPTION)
1842 projectFile = chooser.getSelectedFile();
1843 setProjectFile(projectFile);
1848 if (approveSave || autoSave)
1850 final Desktop me = this;
1851 final java.io.File chosenFile = projectFile;
1852 new Thread(new Runnable()
1857 // TODO: refactor to Jalview desktop session controller action.
1858 setProgressBar(MessageManager.formatMessage(
1859 "label.saving_jalview_project", new Object[]
1860 { chosenFile.getName() }), chosenFile.hashCode());
1861 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1862 // TODO catch and handle errors for savestate
1863 // TODO prevent user from messing with the Desktop whilst we're saving
1866 boolean doBackup = BackupFiles.getEnabled();
1867 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1870 new Jalview2XML().saveState(
1871 doBackup ? backupfiles.getTempFile() : chosenFile);
1875 backupfiles.setWriteSuccess(true);
1876 backupfiles.rollBackupsAndRenameTempFile();
1878 } catch (OutOfMemoryError oom)
1880 new OOMWarning("Whilst saving current state to "
1881 + chosenFile.getName(), oom);
1882 } catch (Exception ex)
1884 jalview.bin.Console.error("Problems whilst trying to save to "
1885 + chosenFile.getName(), ex);
1886 JvOptionPane.showMessageDialog(me,
1887 MessageManager.formatMessage(
1888 "label.error_whilst_saving_current_state_to",
1890 { chosenFile.getName() }),
1891 MessageManager.getString("label.couldnt_save_project"),
1892 JvOptionPane.WARNING_MESSAGE);
1894 setProgressBar(null, chosenFile.hashCode());
1901 public void saveAsState_actionPerformed(ActionEvent e)
1903 saveState_actionPerformed(true);
1906 protected void setProjectFile(File choice)
1908 this.projectFile = choice;
1911 public File getProjectFile()
1913 return this.projectFile;
1917 * Shows a file chooser dialog and tries to read in the selected file as a
1921 public void loadState_actionPerformed()
1923 final String[] suffix = new String[] { "jvp", "jar" };
1924 final String[] desc = new String[] { "Jalview Project",
1925 "Jalview Project (old)" };
1926 JalviewFileChooser chooser = new JalviewFileChooser(
1927 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1928 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1932 chooser.setFileView(new JalviewFileView());
1933 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1934 chooser.setResponseHandler(0, () -> {
1935 File selectedFile = chooser.getSelectedFile();
1936 setProjectFile(selectedFile);
1937 String choice = selectedFile.getAbsolutePath();
1938 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1939 new Thread(new Runnable()
1946 new Jalview2XML().loadJalviewAlign(selectedFile);
1947 } catch (OutOfMemoryError oom)
1949 new OOMWarning("Whilst loading project from " + choice, oom);
1950 } catch (Exception ex)
1952 jalview.bin.Console.error(
1953 "Problems whilst loading project from " + choice, ex);
1954 JvOptionPane.showMessageDialog(Desktop.desktop,
1955 MessageManager.formatMessage(
1956 "label.error_whilst_loading_project_from",
1959 MessageManager.getString("label.couldnt_load_project"),
1960 JvOptionPane.WARNING_MESSAGE);
1963 }, "Project Loader").start();
1967 chooser.showOpenDialog(this);
1971 public void inputSequence_actionPerformed(ActionEvent e)
1973 new SequenceFetcher(this);
1976 JPanel progressPanel;
1978 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1980 public void startLoading(final Object fileName)
1982 if (fileLoadingCount == 0)
1984 fileLoadingPanels.add(addProgressPanel(MessageManager
1985 .formatMessage("label.loading_file", new Object[]
1991 private JPanel addProgressPanel(String string)
1993 if (progressPanel == null)
1995 progressPanel = new JPanel(new GridLayout(1, 1));
1996 totalProgressCount = 0;
1997 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1999 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
2000 JProgressBar progressBar = new JProgressBar();
2001 progressBar.setIndeterminate(true);
2003 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2005 thisprogress.add(progressBar, BorderLayout.CENTER);
2006 progressPanel.add(thisprogress);
2007 ((GridLayout) progressPanel.getLayout()).setRows(
2008 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2009 ++totalProgressCount;
2010 instance.validate();
2011 return thisprogress;
2014 int totalProgressCount = 0;
2016 private void removeProgressPanel(JPanel progbar)
2018 if (progressPanel != null)
2020 synchronized (progressPanel)
2022 progressPanel.remove(progbar);
2023 GridLayout gl = (GridLayout) progressPanel.getLayout();
2024 gl.setRows(gl.getRows() - 1);
2025 if (--totalProgressCount < 1)
2027 this.getContentPane().remove(progressPanel);
2028 progressPanel = null;
2035 public void stopLoading()
2038 if (fileLoadingCount < 1)
2040 while (fileLoadingPanels.size() > 0)
2042 removeProgressPanel(fileLoadingPanels.remove(0));
2044 fileLoadingPanels.clear();
2045 fileLoadingCount = 0;
2050 public static int getViewCount(String alignmentId)
2052 AlignmentViewport[] aps = getViewports(alignmentId);
2053 return (aps == null) ? 0 : aps.length;
2058 * @param alignmentId
2059 * - if null, all sets are returned
2060 * @return all AlignmentPanels concerning the alignmentId sequence set
2062 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2064 if (Desktop.desktop == null)
2066 // no frames created and in headless mode
2067 // TODO: verify that frames are recoverable when in headless mode
2070 List<AlignmentPanel> aps = new ArrayList<>();
2071 AlignFrame[] frames = getAlignFrames();
2076 for (AlignFrame af : frames)
2078 for (AlignmentPanel ap : af.alignPanels)
2080 if (alignmentId == null
2081 || alignmentId.equals(ap.av.getSequenceSetId()))
2087 if (aps.size() == 0)
2091 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2096 * get all the viewports on an alignment.
2098 * @param sequenceSetId
2099 * unique alignment id (may be null - all viewports returned in that
2101 * @return all viewports on the alignment bound to sequenceSetId
2103 public static AlignmentViewport[] getViewports(String sequenceSetId)
2105 List<AlignmentViewport> viewp = new ArrayList<>();
2106 if (desktop != null)
2108 AlignFrame[] frames = Desktop.getAlignFrames();
2110 for (AlignFrame afr : frames)
2112 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2113 .equals(sequenceSetId))
2115 if (afr.alignPanels != null)
2117 for (AlignmentPanel ap : afr.alignPanels)
2119 if (sequenceSetId == null
2120 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2128 viewp.add(afr.getViewport());
2132 if (viewp.size() > 0)
2134 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2141 * Explode the views in the given frame into separate AlignFrame
2145 public static void explodeViews(AlignFrame af)
2147 int size = af.alignPanels.size();
2153 // FIXME: ideally should use UI interface API
2154 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2155 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2156 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2157 for (int i = 0; i < size; i++)
2159 AlignmentPanel ap = af.alignPanels.get(i);
2161 AlignFrame newaf = new AlignFrame(ap);
2163 // transfer reference for existing feature settings to new alignFrame
2164 if (ap == af.alignPanel)
2166 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2168 newaf.featureSettings = viewFeatureSettings;
2170 newaf.setFeatureSettingsGeometry(fsBounds);
2174 * Restore the view's last exploded frame geometry if known. Multiple views from
2175 * one exploded frame share and restore the same (frame) position and size.
2177 Rectangle geometry = ap.av.getExplodedGeometry();
2178 if (geometry != null)
2180 newaf.setBounds(geometry);
2183 ap.av.setGatherViewsHere(false);
2185 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2186 AlignFrame.DEFAULT_HEIGHT);
2187 // and materialise a new feature settings dialog instance for the new
2189 // (closes the old as if 'OK' was pressed)
2190 if (ap == af.alignPanel && newaf.featureSettings != null
2191 && newaf.featureSettings.isOpen()
2192 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2194 newaf.showFeatureSettingsUI();
2198 af.featureSettings = null;
2199 af.alignPanels.clear();
2200 af.closeMenuItem_actionPerformed(true);
2205 * Gather expanded views (separate AlignFrame's) with the same sequence set
2206 * identifier back in to this frame as additional views, and close the
2207 * expanded views. Note the expanded frames may themselves have multiple
2208 * views. We take the lot.
2212 public void gatherViews(AlignFrame source)
2214 source.viewport.setGatherViewsHere(true);
2215 source.viewport.setExplodedGeometry(source.getBounds());
2216 JInternalFrame[] frames = desktop.getAllFrames();
2217 String viewId = source.viewport.getSequenceSetId();
2218 for (int t = 0; t < frames.length; t++)
2220 if (frames[t] instanceof AlignFrame && frames[t] != source)
2222 AlignFrame af = (AlignFrame) frames[t];
2223 boolean gatherThis = false;
2224 for (int a = 0; a < af.alignPanels.size(); a++)
2226 AlignmentPanel ap = af.alignPanels.get(a);
2227 if (viewId.equals(ap.av.getSequenceSetId()))
2230 ap.av.setGatherViewsHere(false);
2231 ap.av.setExplodedGeometry(af.getBounds());
2232 source.addAlignmentPanel(ap, false);
2238 if (af.featureSettings != null && af.featureSettings.isOpen())
2240 if (source.featureSettings == null)
2242 // preserve the feature settings geometry for this frame
2243 source.featureSettings = af.featureSettings;
2244 source.setFeatureSettingsGeometry(
2245 af.getFeatureSettingsGeometry());
2249 // close it and forget
2250 af.featureSettings.close();
2253 af.alignPanels.clear();
2254 af.closeMenuItem_actionPerformed(true);
2259 // refresh the feature setting UI for the source frame if it exists
2260 if (source.featureSettings != null && source.featureSettings.isOpen())
2262 source.showFeatureSettingsUI();
2267 public JInternalFrame[] getAllFrames()
2269 return desktop.getAllFrames();
2273 * Checks the given url to see if it gives a response indicating that the user
2274 * should be informed of a new questionnaire.
2278 public void checkForQuestionnaire(String url)
2280 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2281 // javax.swing.SwingUtilities.invokeLater(jvq);
2282 new Thread(jvq).start();
2285 public void checkURLLinks()
2287 // Thread off the URL link checker
2288 addDialogThread(new Runnable()
2293 if (Cache.getDefault("CHECKURLLINKS", true))
2295 // check what the actual links are - if it's just the default don't
2296 // bother with the warning
2297 List<String> links = Preferences.sequenceUrlLinks
2300 // only need to check links if there is one with a
2301 // SEQUENCE_ID which is not the default EMBL_EBI link
2302 ListIterator<String> li = links.listIterator();
2303 boolean check = false;
2304 List<JLabel> urls = new ArrayList<>();
2305 while (li.hasNext())
2307 String link = li.next();
2308 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2309 && !UrlConstants.isDefaultString(link))
2312 int barPos = link.indexOf("|");
2313 String urlMsg = barPos == -1 ? link
2314 : link.substring(0, barPos) + ": "
2315 + link.substring(barPos + 1);
2316 urls.add(new JLabel(urlMsg));
2324 // ask user to check in case URL links use old style tokens
2325 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2326 JPanel msgPanel = new JPanel();
2327 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2328 msgPanel.add(Box.createVerticalGlue());
2329 JLabel msg = new JLabel(MessageManager
2330 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2331 JLabel msg2 = new JLabel(MessageManager
2332 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2334 for (JLabel url : urls)
2340 final JCheckBox jcb = new JCheckBox(
2341 MessageManager.getString("label.do_not_display_again"));
2342 jcb.addActionListener(new ActionListener()
2345 public void actionPerformed(ActionEvent e)
2347 // update Cache settings for "don't show this again"
2348 boolean showWarningAgain = !jcb.isSelected();
2349 Cache.setProperty("CHECKURLLINKS",
2350 Boolean.valueOf(showWarningAgain).toString());
2355 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2357 .getString("label.SEQUENCE_ID_no_longer_used"),
2358 JvOptionPane.WARNING_MESSAGE);
2365 * Proxy class for JDesktopPane which optionally displays the current memory
2366 * usage and highlights the desktop area with a red bar if free memory runs
2371 public class MyDesktopPane extends JDesktopPane implements Runnable
2373 private static final float ONE_MB = 1048576f;
2375 boolean showMemoryUsage = false;
2379 java.text.NumberFormat df;
2381 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2384 public MyDesktopPane(boolean showMemoryUsage)
2386 showMemoryUsage(showMemoryUsage);
2389 public void showMemoryUsage(boolean showMemory)
2391 this.showMemoryUsage = showMemory;
2394 Thread worker = new Thread(this);
2400 public boolean isShowMemoryUsage()
2402 return showMemoryUsage;
2408 df = java.text.NumberFormat.getNumberInstance();
2409 df.setMaximumFractionDigits(2);
2410 runtime = Runtime.getRuntime();
2412 while (showMemoryUsage)
2416 maxMemory = runtime.maxMemory() / ONE_MB;
2417 allocatedMemory = runtime.totalMemory() / ONE_MB;
2418 freeMemory = runtime.freeMemory() / ONE_MB;
2419 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2421 percentUsage = (totalFreeMemory / maxMemory) * 100;
2423 // if (percentUsage < 20)
2425 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2427 // instance.set.setBorder(border1);
2430 // sleep after showing usage
2432 } catch (Exception ex)
2434 ex.printStackTrace();
2440 public void paintComponent(Graphics g)
2442 if (showMemoryUsage && g != null && df != null)
2444 if (percentUsage < 20)
2446 g.setColor(Color.red);
2448 FontMetrics fm = g.getFontMetrics();
2451 g.drawString(MessageManager.formatMessage("label.memory_stats",
2453 { df.format(totalFreeMemory), df.format(maxMemory),
2454 df.format(percentUsage) }),
2455 10, getHeight() - fm.getHeight());
2459 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2460 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2465 * Accessor method to quickly get all the AlignmentFrames loaded.
2467 * @return an array of AlignFrame, or null if none found
2469 public static AlignFrame[] getAlignFrames()
2471 if (Jalview.isHeadlessMode())
2473 // Desktop.desktop is null in headless mode
2474 return new AlignFrame[] { Jalview.currentAlignFrame };
2477 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2483 List<AlignFrame> avp = new ArrayList<>();
2485 for (int i = frames.length - 1; i > -1; i--)
2487 if (frames[i] instanceof AlignFrame)
2489 avp.add((AlignFrame) frames[i]);
2491 else if (frames[i] instanceof SplitFrame)
2494 * Also check for a split frame containing an AlignFrame
2496 GSplitFrame sf = (GSplitFrame) frames[i];
2497 if (sf.getTopFrame() instanceof AlignFrame)
2499 avp.add((AlignFrame) sf.getTopFrame());
2501 if (sf.getBottomFrame() instanceof AlignFrame)
2503 avp.add((AlignFrame) sf.getBottomFrame());
2507 if (avp.size() == 0)
2511 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2516 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2520 public GStructureViewer[] getJmols()
2522 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2528 List<GStructureViewer> avp = new ArrayList<>();
2530 for (int i = frames.length - 1; i > -1; i--)
2532 if (frames[i] instanceof AppJmol)
2534 GStructureViewer af = (GStructureViewer) frames[i];
2538 if (avp.size() == 0)
2542 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2547 * Add Groovy Support to Jalview
2550 public void groovyShell_actionPerformed()
2554 openGroovyConsole();
2555 } catch (Exception ex)
2557 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2558 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2560 MessageManager.getString("label.couldnt_create_groovy_shell"),
2561 MessageManager.getString("label.groovy_support_failed"),
2562 JvOptionPane.ERROR_MESSAGE);
2567 * Open the Groovy console
2569 void openGroovyConsole()
2571 if (groovyConsole == null)
2573 groovyConsole = new groovy.ui.Console();
2574 groovyConsole.setVariable("Jalview", this);
2575 groovyConsole.run();
2578 * We allow only one console at a time, so that AlignFrame menu option
2579 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2580 * enable 'Run script', when the console is opened, and the reverse when it is
2583 Window window = (Window) groovyConsole.getFrame();
2584 window.addWindowListener(new WindowAdapter()
2587 public void windowClosed(WindowEvent e)
2590 * rebind CMD-Q from Groovy Console to Jalview Quit
2593 enableExecuteGroovy(false);
2599 * show Groovy console window (after close and reopen)
2601 ((Window) groovyConsole.getFrame()).setVisible(true);
2604 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2605 * opening a second console
2607 enableExecuteGroovy(true);
2611 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2612 * binding when opened
2614 protected void addQuitHandler()
2617 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2619 .getKeyStroke(KeyEvent.VK_Q,
2620 jalview.util.ShortcutKeyMaskExWrapper
2621 .getMenuShortcutKeyMaskEx()),
2623 getRootPane().getActionMap().put("Quit", new AbstractAction()
2626 public void actionPerformed(ActionEvent e)
2634 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2637 * true if Groovy console is open
2639 public void enableExecuteGroovy(boolean enabled)
2642 * disable opening a second Groovy console (or re-enable when the console is
2645 groovyShell.setEnabled(!enabled);
2647 AlignFrame[] alignFrames = getAlignFrames();
2648 if (alignFrames != null)
2650 for (AlignFrame af : alignFrames)
2652 af.setGroovyEnabled(enabled);
2658 * Progress bars managed by the IProgressIndicator method.
2660 private Hashtable<Long, JPanel> progressBars;
2662 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2667 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2670 public void setProgressBar(String message, long id)
2672 if (progressBars == null)
2674 progressBars = new Hashtable<>();
2675 progressBarHandlers = new Hashtable<>();
2678 if (progressBars.get(Long.valueOf(id)) != null)
2680 JPanel panel = progressBars.remove(Long.valueOf(id));
2681 if (progressBarHandlers.contains(Long.valueOf(id)))
2683 progressBarHandlers.remove(Long.valueOf(id));
2685 removeProgressPanel(panel);
2689 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2696 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2697 * jalview.gui.IProgressIndicatorHandler)
2700 public void registerHandler(final long id,
2701 final IProgressIndicatorHandler handler)
2703 if (progressBarHandlers == null
2704 || !progressBars.containsKey(Long.valueOf(id)))
2706 throw new Error(MessageManager.getString(
2707 "error.call_setprogressbar_before_registering_handler"));
2709 progressBarHandlers.put(Long.valueOf(id), handler);
2710 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2711 if (handler.canCancel())
2713 JButton cancel = new JButton(
2714 MessageManager.getString("action.cancel"));
2715 final IProgressIndicator us = this;
2716 cancel.addActionListener(new ActionListener()
2720 public void actionPerformed(ActionEvent e)
2722 handler.cancelActivity(id);
2723 us.setProgressBar(MessageManager
2724 .formatMessage("label.cancelled_params", new Object[]
2725 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2729 progressPanel.add(cancel, BorderLayout.EAST);
2735 * @return true if any progress bars are still active
2738 public boolean operationInProgress()
2740 if (progressBars != null && progressBars.size() > 0)
2748 * This will return the first AlignFrame holding the given viewport instance.
2749 * It will break if there are more than one AlignFrames viewing a particular
2753 * @return alignFrame for viewport
2755 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2757 if (desktop != null)
2759 AlignmentPanel[] aps = getAlignmentPanels(
2760 viewport.getSequenceSetId());
2761 for (int panel = 0; aps != null && panel < aps.length; panel++)
2763 if (aps[panel] != null && aps[panel].av == viewport)
2765 return aps[panel].alignFrame;
2772 public VamsasApplication getVamsasApplication()
2774 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2780 * flag set if jalview GUI is being operated programmatically
2782 private boolean inBatchMode = false;
2785 * check if jalview GUI is being operated programmatically
2787 * @return inBatchMode
2789 public boolean isInBatchMode()
2795 * set flag if jalview GUI is being operated programmatically
2797 * @param inBatchMode
2799 public void setInBatchMode(boolean inBatchMode)
2801 this.inBatchMode = inBatchMode;
2805 * start service discovery and wait till it is done
2807 public void startServiceDiscovery()
2809 startServiceDiscovery(false);
2813 * start service discovery threads - blocking or non-blocking
2817 public void startServiceDiscovery(boolean blocking)
2819 startServiceDiscovery(blocking, false);
2823 * start service discovery threads
2826 * - false means call returns immediately
2827 * @param ignore_SHOW_JWS2_SERVICES_preference
2828 * - when true JABA services are discovered regardless of user's JWS2
2829 * discovery preference setting
2831 public void startServiceDiscovery(boolean blocking,
2832 boolean ignore_SHOW_JWS2_SERVICES_preference)
2834 boolean alive = true;
2835 Thread t0 = null, t1 = null, t2 = null;
2836 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2839 // todo: changesupport handlers need to be transferred
2840 if (discoverer == null)
2842 discoverer = new jalview.ws.jws1.Discoverer();
2843 // register PCS handler for desktop.
2844 discoverer.addPropertyChangeListener(changeSupport);
2846 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2847 // until we phase out completely
2848 (t0 = new Thread(discoverer)).start();
2851 if (ignore_SHOW_JWS2_SERVICES_preference
2852 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2854 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2855 .startDiscoverer(changeSupport);
2859 // TODO: do rest service discovery
2868 } catch (Exception e)
2871 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2872 || (t3 != null && t3.isAlive())
2873 || (t0 != null && t0.isAlive());
2879 * called to check if the service discovery process completed successfully.
2883 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2885 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2887 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2888 .getErrorMessages();
2891 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2893 if (serviceChangedDialog == null)
2895 // only run if we aren't already displaying one of these.
2896 addDialogThread(serviceChangedDialog = new Runnable()
2903 * JalviewDialog jd =new JalviewDialog() {
2905 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2907 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2909 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2911 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2913 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2914 * + " or mis-configured HTTP proxy settings.<br/>" +
2915 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2916 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2917 * true, true, "Web Service Configuration Problem", 450, 400);
2919 * jd.waitForInput();
2921 JvOptionPane.showConfirmDialog(Desktop.desktop,
2922 new JLabel("<html><table width=\"450\"><tr><td>"
2923 + ermsg + "</td></tr></table>"
2924 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2925 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2926 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2927 + " Tools->Preferences dialog box to change them.</p></html>"),
2928 "Web Service Configuration Problem",
2929 JvOptionPane.DEFAULT_OPTION,
2930 JvOptionPane.ERROR_MESSAGE);
2931 serviceChangedDialog = null;
2939 jalview.bin.Console.error(
2940 "Errors reported by JABA discovery service. Check web services preferences.\n"
2947 private Runnable serviceChangedDialog = null;
2950 * start a thread to open a URL in the configured browser. Pops up a warning
2951 * dialog to the user if there is an exception when calling out to the browser
2956 public static void showUrl(final String url)
2958 showUrl(url, Desktop.instance);
2962 * Like showUrl but allows progress handler to be specified
2966 * (null) or object implementing IProgressIndicator
2968 public static void showUrl(final String url,
2969 final IProgressIndicator progress)
2971 new Thread(new Runnable()
2978 if (progress != null)
2980 progress.setProgressBar(MessageManager
2981 .formatMessage("status.opening_params", new Object[]
2982 { url }), this.hashCode());
2984 jalview.util.BrowserLauncher.openURL(url);
2985 } catch (Exception ex)
2987 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2989 .getString("label.web_browser_not_found_unix"),
2990 MessageManager.getString("label.web_browser_not_found"),
2991 JvOptionPane.WARNING_MESSAGE);
2993 ex.printStackTrace();
2995 if (progress != null)
2997 progress.setProgressBar(null, this.hashCode());
3003 public static WsParamSetManager wsparamManager = null;
3005 public static ParamManager getUserParameterStore()
3007 if (wsparamManager == null)
3009 wsparamManager = new WsParamSetManager();
3011 return wsparamManager;
3015 * static hyperlink handler proxy method for use by Jalview's internal windows
3019 public static void hyperlinkUpdate(HyperlinkEvent e)
3021 if (e.getEventType() == EventType.ACTIVATED)
3026 url = e.getURL().toString();
3027 Desktop.showUrl(url);
3028 } catch (Exception x)
3033 .error("Couldn't handle string " + url + " as a URL.");
3035 // ignore any exceptions due to dud links.
3042 * single thread that handles display of dialogs to user.
3044 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3047 * flag indicating if dialogExecutor should try to acquire a permit
3049 private volatile boolean dialogPause = true;
3054 private java.util.concurrent.Semaphore block = new Semaphore(0);
3056 private static groovy.ui.Console groovyConsole;
3059 * add another dialog thread to the queue
3063 public void addDialogThread(final Runnable prompter)
3065 dialogExecutor.submit(new Runnable()
3075 } catch (InterruptedException x)
3079 if (instance == null)
3085 SwingUtilities.invokeAndWait(prompter);
3086 } catch (Exception q)
3088 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3095 public void startDialogQueue()
3097 // set the flag so we don't pause waiting for another permit and semaphore
3098 // the current task to begin
3099 dialogPause = false;
3104 * Outputs an image of the desktop to file in EPS format, after prompting the
3105 * user for choice of Text or Lineart character rendering (unless a preference
3106 * has been set). The file name is generated as
3109 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3113 protected void snapShotWindow_actionPerformed(ActionEvent e)
3115 // currently the menu option to do this is not shown
3118 int width = getWidth();
3119 int height = getHeight();
3121 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3122 ImageWriterI writer = new ImageWriterI()
3125 public void exportImage(Graphics g) throws Exception
3128 jalview.bin.Console.info("Successfully written snapshot to file "
3129 + of.getAbsolutePath());
3132 String title = "View of desktop";
3133 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3135 exporter.doExport(of, this, width, height, title);
3139 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3140 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3141 * and location last time the view was expanded (if any). However it does not
3142 * remember the split pane divider location - this is set to match the
3143 * 'exploding' frame.
3147 public void explodeViews(SplitFrame sf)
3149 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3150 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3151 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3153 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3155 int viewCount = topPanels.size();
3162 * Processing in reverse order works, forwards order leaves the first panels not
3163 * visible. I don't know why!
3165 for (int i = viewCount - 1; i >= 0; i--)
3168 * Make new top and bottom frames. These take over the respective AlignmentPanel
3169 * objects, including their AlignmentViewports, so the cdna/protein
3170 * relationships between the viewports is carried over to the new split frames.
3172 * explodedGeometry holds the (x, y) position of the previously exploded
3173 * SplitFrame, and the (width, height) of the AlignFrame component
3175 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3176 AlignFrame newTopFrame = new AlignFrame(topPanel);
3177 newTopFrame.setSize(oldTopFrame.getSize());
3178 newTopFrame.setVisible(true);
3179 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3180 .getExplodedGeometry();
3181 if (geometry != null)
3183 newTopFrame.setSize(geometry.getSize());
3186 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3187 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3188 newBottomFrame.setSize(oldBottomFrame.getSize());
3189 newBottomFrame.setVisible(true);
3190 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3191 .getExplodedGeometry();
3192 if (geometry != null)
3194 newBottomFrame.setSize(geometry.getSize());
3197 topPanel.av.setGatherViewsHere(false);
3198 bottomPanel.av.setGatherViewsHere(false);
3199 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3201 if (geometry != null)
3203 splitFrame.setLocation(geometry.getLocation());
3205 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3209 * Clear references to the panels (now relocated in the new SplitFrames) before
3210 * closing the old SplitFrame.
3213 bottomPanels.clear();
3218 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3219 * back into the given SplitFrame as additional views. Note that the gathered
3220 * frames may themselves have multiple views.
3224 public void gatherViews(GSplitFrame source)
3227 * special handling of explodedGeometry for a view within a SplitFrame: - it
3228 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3229 * height) of the AlignFrame component
3231 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3232 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3233 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3234 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3235 myBottomFrame.viewport
3236 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3237 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3238 myTopFrame.viewport.setGatherViewsHere(true);
3239 myBottomFrame.viewport.setGatherViewsHere(true);
3240 String topViewId = myTopFrame.viewport.getSequenceSetId();
3241 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3243 JInternalFrame[] frames = desktop.getAllFrames();
3244 for (JInternalFrame frame : frames)
3246 if (frame instanceof SplitFrame && frame != source)
3248 SplitFrame sf = (SplitFrame) frame;
3249 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3250 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3251 boolean gatherThis = false;
3252 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3254 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3255 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3256 if (topViewId.equals(topPanel.av.getSequenceSetId())
3257 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3260 topPanel.av.setGatherViewsHere(false);
3261 bottomPanel.av.setGatherViewsHere(false);
3262 topPanel.av.setExplodedGeometry(
3263 new Rectangle(sf.getLocation(), topFrame.getSize()));
3264 bottomPanel.av.setExplodedGeometry(
3265 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3266 myTopFrame.addAlignmentPanel(topPanel, false);
3267 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3273 topFrame.getAlignPanels().clear();
3274 bottomFrame.getAlignPanels().clear();
3281 * The dust settles...give focus to the tab we did this from.
3283 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3286 public static groovy.ui.Console getGroovyConsole()
3288 return groovyConsole;
3292 * handles the payload of a drag and drop event.
3294 * TODO refactor to desktop utilities class
3297 * - Data source strings extracted from the drop event
3299 * - protocol for each data source extracted from the drop event
3303 * - the payload from the drop event
3306 public static void transferFromDropTarget(List<Object> files,
3307 List<DataSourceType> protocols, DropTargetDropEvent evt,
3308 Transferable t) throws Exception
3311 DataFlavor uriListFlavor = new DataFlavor(
3312 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3315 urlFlavour = new DataFlavor(
3316 "application/x-java-url; class=java.net.URL");
3317 } catch (ClassNotFoundException cfe)
3319 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3323 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3328 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3329 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3330 // means url may be null.
3333 protocols.add(DataSourceType.URL);
3334 files.add(url.toString());
3335 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3336 + files.get(files.size() - 1));
3341 if (Platform.isAMacAndNotJS())
3344 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3347 } catch (Throwable ex)
3349 jalview.bin.Console.debug("URL drop handler failed.", ex);
3352 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3354 // Works on Windows and MacOSX
3355 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3356 for (Object file : (List) t
3357 .getTransferData(DataFlavor.javaFileListFlavor))
3360 protocols.add(DataSourceType.FILE);
3365 // Unix like behaviour
3366 boolean added = false;
3368 if (t.isDataFlavorSupported(uriListFlavor))
3370 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3371 // This is used by Unix drag system
3372 data = (String) t.getTransferData(uriListFlavor);
3376 // fallback to text: workaround - on OSX where there's a JVM bug
3378 .debug("standard URIListFlavor failed. Trying text");
3379 // try text fallback
3380 DataFlavor textDf = new DataFlavor(
3381 "text/plain;class=java.lang.String");
3382 if (t.isDataFlavorSupported(textDf))
3384 data = (String) t.getTransferData(textDf);
3387 jalview.bin.Console.debug("Plain text drop content returned "
3388 + (data == null ? "Null - failed" : data));
3393 while (protocols.size() < files.size())
3395 jalview.bin.Console.debug("Adding missing FILE protocol for "
3396 + files.get(protocols.size()));
3397 protocols.add(DataSourceType.FILE);
3399 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3400 data, "\r\n"); st.hasMoreTokens();)
3403 String s = st.nextToken();
3404 if (s.startsWith("#"))
3406 // the line is a comment (as per the RFC 2483)
3409 java.net.URI uri = new java.net.URI(s);
3410 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3412 protocols.add(DataSourceType.URL);
3413 files.add(uri.toString());
3417 // otherwise preserve old behaviour: catch all for file objects
3418 java.io.File file = new java.io.File(uri);
3419 protocols.add(DataSourceType.FILE);
3420 files.add(file.toString());
3425 if (jalview.bin.Console.isDebugEnabled())
3427 if (data == null || !added)
3430 if (t.getTransferDataFlavors() != null
3431 && t.getTransferDataFlavors().length > 0)
3433 jalview.bin.Console.debug(
3434 "Couldn't resolve drop data. Here are the supported flavors:");
3435 for (DataFlavor fl : t.getTransferDataFlavors())
3437 jalview.bin.Console.debug(
3438 "Supported transfer dataflavor: " + fl.toString());
3439 Object df = t.getTransferData(fl);
3442 jalview.bin.Console.debug("Retrieves: " + df);
3446 jalview.bin.Console.debug("Retrieved nothing");
3453 .debug("Couldn't resolve dataflavor for drop: "
3459 if (Platform.isWindowsAndNotJS())
3462 .debug("Scanning dropped content for Windows Link Files");
3464 // resolve any .lnk files in the file drop
3465 for (int f = 0; f < files.size(); f++)
3467 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3468 if (protocols.get(f).equals(DataSourceType.FILE)
3469 && (source.endsWith(".lnk") || source.endsWith(".url")
3470 || source.endsWith(".site")))
3474 Object obj = files.get(f);
3475 File lf = (obj instanceof File ? (File) obj
3476 : new File((String) obj));
3477 // process link file to get a URL
3478 jalview.bin.Console.debug("Found potential link file: " + lf);
3479 WindowsShortcut wscfile = new WindowsShortcut(lf);
3480 String fullname = wscfile.getRealFilename();
3481 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3482 files.set(f, fullname);
3483 jalview.bin.Console.debug("Parsed real filename " + fullname
3484 + " to extract protocol: " + protocols.get(f));
3485 } catch (Exception ex)
3487 jalview.bin.Console.error(
3488 "Couldn't parse " + files.get(f) + " as a link file.",
3497 * Sets the Preferences property for experimental features to True or False
3498 * depending on the state of the controlling menu item
3501 protected void showExperimental_actionPerformed(boolean selected)
3503 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3507 * Answers a (possibly empty) list of any structure viewer frames (currently
3508 * for either Jmol or Chimera) which are currently open. This may optionally
3509 * be restricted to viewers of a specified class, or viewers linked to a
3510 * specified alignment panel.
3513 * if not null, only return viewers linked to this panel
3514 * @param structureViewerClass
3515 * if not null, only return viewers of this class
3518 public List<StructureViewerBase> getStructureViewers(
3519 AlignmentPanel apanel,
3520 Class<? extends StructureViewerBase> structureViewerClass)
3522 List<StructureViewerBase> result = new ArrayList<>();
3523 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3525 for (JInternalFrame frame : frames)
3527 if (frame instanceof StructureViewerBase)
3529 if (structureViewerClass == null
3530 || structureViewerClass.isInstance(frame))
3533 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3535 result.add((StructureViewerBase) frame);
3543 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3545 private static boolean debugScaleMessageDone = false;
3547 public static void debugScaleMessage(Graphics g)
3549 if (debugScaleMessageDone)
3553 // output used by tests to check HiDPI scaling settings in action
3556 Graphics2D gg = (Graphics2D) g;
3559 AffineTransform t = gg.getTransform();
3560 double scaleX = t.getScaleX();
3561 double scaleY = t.getScaleY();
3562 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3563 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3564 debugScaleMessageDone = true;
3568 jalview.bin.Console.debug("Desktop graphics null");
3570 } catch (Exception e)
3572 jalview.bin.Console.debug(Cache.getStackTraceString(e));