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.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JFrame;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.WindowConstants;
95 import javax.swing.event.HyperlinkEvent;
96 import javax.swing.event.HyperlinkEvent.EventType;
97 import javax.swing.event.InternalFrameAdapter;
98 import javax.swing.event.InternalFrameEvent;
100 import org.stackoverflowusers.file.WindowsShortcut;
102 import jalview.api.AlignViewportI;
103 import jalview.api.AlignmentViewPanel;
104 import jalview.api.structures.JalviewStructureDisplayI;
105 import jalview.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.datamodel.Alignment;
108 import jalview.datamodel.HiddenColumns;
109 import jalview.datamodel.Sequence;
110 import jalview.datamodel.SequenceI;
111 import jalview.gui.ImageExporter.ImageWriterI;
112 import jalview.gui.QuitHandler.QResponse;
113 import jalview.io.BackupFiles;
114 import jalview.io.DataSourceType;
115 import jalview.io.FileFormat;
116 import jalview.io.FileFormatException;
117 import jalview.io.FileFormatI;
118 import jalview.io.FileFormats;
119 import jalview.io.FileLoader;
120 import jalview.io.FormatAdapter;
121 import jalview.io.IdentifyFile;
122 import jalview.io.JalviewFileChooser;
123 import jalview.io.JalviewFileView;
124 import jalview.jbgui.GSplitFrame;
125 import jalview.jbgui.GStructureViewer;
126 import jalview.project.Jalview2XML;
127 import jalview.structure.StructureSelectionManager;
128 import jalview.urls.IdOrgSettings;
129 import jalview.util.BrowserLauncher;
130 import jalview.util.ChannelProperties;
131 import jalview.util.ImageMaker.TYPE;
132 import jalview.util.LaunchUtils;
133 import jalview.util.MessageManager;
134 import jalview.util.Platform;
135 import jalview.util.ShortcutKeyMaskExWrapper;
136 import jalview.util.UrlConstants;
137 import jalview.viewmodel.AlignmentViewport;
138 import jalview.ws.params.ParamManager;
139 import jalview.ws.utils.UrlDownloadClient;
146 * @version $Revision: 1.155 $
148 public class Desktop extends jalview.jbgui.GDesktop
149 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
150 jalview.api.StructureSelectionManagerProvider
152 private static final String CITATION;
155 URL bg_logo_url = ChannelProperties.getImageURL(
156 "bg_logo." + String.valueOf(SplashScreen.logoSize));
157 URL uod_logo_url = ChannelProperties.getImageURL(
158 "uod_banner." + String.valueOf(SplashScreen.logoSize));
159 boolean logo = (bg_logo_url != null || uod_logo_url != null);
160 StringBuilder sb = new StringBuilder();
162 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
167 sb.append(bg_logo_url == null ? ""
168 : "<img alt=\"Barton Group logo\" src=\""
169 + bg_logo_url.toString() + "\">");
170 sb.append(uod_logo_url == null ? ""
171 : " <img alt=\"University of Dundee shield\" src=\""
172 + uod_logo_url.toString() + "\">");
174 "<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>");
175 sb.append("<br><br>If you use Jalview, please cite:"
176 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
177 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
178 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
179 CITATION = sb.toString();
182 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
184 private static int DEFAULT_MIN_WIDTH = 300;
186 private static int DEFAULT_MIN_HEIGHT = 250;
188 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
190 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
192 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
194 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
196 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
198 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
200 public static void setLiveDragMode(boolean b)
202 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
203 : JDesktopPane.OUTLINE_DRAG_MODE;
205 desktop.setDragMode(DRAG_MODE);
208 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
210 public static boolean nosplash = false;
213 * news reader - null if it was never started.
215 private BlogReader jvnews = null;
217 private File projectFile;
221 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
223 public void addJalviewPropertyChangeListener(
224 PropertyChangeListener listener)
226 changeSupport.addJalviewPropertyChangeListener(listener);
230 * @param propertyName
232 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
233 * java.beans.PropertyChangeListener)
235 public void addJalviewPropertyChangeListener(String propertyName,
236 PropertyChangeListener listener)
238 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
242 * @param propertyName
244 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
245 * java.beans.PropertyChangeListener)
247 public void removeJalviewPropertyChangeListener(String propertyName,
248 PropertyChangeListener listener)
250 changeSupport.removeJalviewPropertyChangeListener(propertyName,
254 /** Singleton Desktop instance */
255 public static Desktop instance;
257 public static MyDesktopPane desktop;
259 public static MyDesktopPane getDesktop()
261 // BH 2018 could use currentThread() here as a reference to a
262 // Hashtable<Thread, MyDesktopPane> in JavaScript
266 static int openFrameCount = 0;
268 static final int xOffset = 30;
270 static final int yOffset = 30;
272 public static jalview.ws.jws1.Discoverer discoverer;
274 public static Object[] jalviewClipboard;
276 public static boolean internalCopy = false;
278 static int fileLoadingCount = 0;
280 class MyDesktopManager implements DesktopManager
283 private DesktopManager delegate;
285 public MyDesktopManager(DesktopManager delegate)
287 this.delegate = delegate;
291 public void activateFrame(JInternalFrame f)
295 delegate.activateFrame(f);
296 } catch (NullPointerException npe)
298 Point p = getMousePosition();
299 instance.showPasteMenu(p.x, p.y);
304 public void beginDraggingFrame(JComponent f)
306 delegate.beginDraggingFrame(f);
310 public void beginResizingFrame(JComponent f, int direction)
312 delegate.beginResizingFrame(f, direction);
316 public void closeFrame(JInternalFrame f)
318 delegate.closeFrame(f);
322 public void deactivateFrame(JInternalFrame f)
324 delegate.deactivateFrame(f);
328 public void deiconifyFrame(JInternalFrame f)
330 delegate.deiconifyFrame(f);
334 public void dragFrame(JComponent f, int newX, int newY)
340 delegate.dragFrame(f, newX, newY);
344 public void endDraggingFrame(JComponent f)
346 delegate.endDraggingFrame(f);
351 public void endResizingFrame(JComponent f)
353 delegate.endResizingFrame(f);
358 public void iconifyFrame(JInternalFrame f)
360 delegate.iconifyFrame(f);
364 public void maximizeFrame(JInternalFrame f)
366 delegate.maximizeFrame(f);
370 public void minimizeFrame(JInternalFrame f)
372 delegate.minimizeFrame(f);
376 public void openFrame(JInternalFrame f)
378 delegate.openFrame(f);
382 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
389 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
393 public void setBoundsForFrame(JComponent f, int newX, int newY,
394 int newWidth, int newHeight)
396 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
399 // All other methods, simply delegate
404 * Creates a new Desktop object.
410 * A note to implementors. It is ESSENTIAL that any activities that might
411 * block are spawned off as threads rather than waited for during this
416 doConfigureStructurePrefs();
417 setTitle(ChannelProperties.getProperty("app_name") + " "
418 + Cache.getProperty("VERSION"));
421 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
422 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
423 * officially documented or guaranteed to exist, so we access it via
424 * reflection. There appear to be unfathomable criteria about what this
425 * string can contain, and it if doesn't meet those criteria then "java"
426 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
427 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
428 * not. The reflection access may generate a warning: WARNING: An illegal
429 * reflective access operation has occurred WARNING: Illegal reflective
430 * access by jalview.gui.Desktop () to field
431 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
433 if (Platform.isLinux())
435 if (LaunchUtils.getJavaVersion() >= 11)
438 * Send this message to stderr as the warning that follows (due to
439 * reflection) also goes to stderr.
442 "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.");
444 final String awtAppClassName = "awtAppClassName";
447 Toolkit xToolkit = Toolkit.getDefaultToolkit();
448 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
449 Field awtAppClassNameField = null;
451 if (Arrays.stream(declaredFields)
452 .anyMatch(f -> f.getName().equals(awtAppClassName)))
454 awtAppClassNameField = xToolkit.getClass()
455 .getDeclaredField(awtAppClassName);
458 String title = ChannelProperties.getProperty("app_name");
459 if (awtAppClassNameField != null)
461 awtAppClassNameField.setAccessible(true);
462 awtAppClassNameField.set(xToolkit, title);
467 .debug("XToolkit: " + awtAppClassName + " not found");
469 } catch (Exception e)
471 jalview.bin.Console.debug("Error setting " + awtAppClassName);
472 jalview.bin.Console.trace(Cache.getStackTraceString(e));
476 setIconImages(ChannelProperties.getIconList());
478 // override quit handling when GUI OS close [X] button pressed
479 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
480 addWindowListener(new WindowAdapter()
483 public void windowClosing(WindowEvent ev)
485 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
489 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
491 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
492 desktop = new MyDesktopPane(selmemusage);
494 showMemusage.setSelected(selmemusage);
495 desktop.setBackground(Color.white);
497 getContentPane().setLayout(new BorderLayout());
498 // alternate config - have scrollbars - see notes in JAL-153
499 // JScrollPane sp = new JScrollPane();
500 // sp.getViewport().setView(desktop);
501 // getContentPane().add(sp, BorderLayout.CENTER);
503 // BH 2018 - just an experiment to try unclipped JInternalFrames.
506 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
509 getContentPane().add(desktop, BorderLayout.CENTER);
510 desktop.setDragMode(DRAG_MODE);
512 // This line prevents Windows Look&Feel resizing all new windows to maximum
513 // if previous window was maximised
514 desktop.setDesktopManager(new MyDesktopManager(
515 Platform.isJS() ? desktop.getDesktopManager()
516 : new DefaultDesktopManager()));
518 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
519 : Platform.isAMacAndNotJS()
520 ? new AquaInternalFrameManager(
521 desktop.getDesktopManager())
522 : desktop.getDesktopManager())));
525 Rectangle dims = getLastKnownDimensions("");
532 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
533 int xPos = Math.max(5, (screenSize.width - 900) / 2);
534 int yPos = Math.max(5, (screenSize.height - 650) / 2);
535 setBounds(xPos, yPos, 900, 650);
538 if (!Platform.isJS())
545 jconsole = new Console(this, showjconsole);
546 jconsole.setHeader(Cache.getVersionDetailsForConsole());
547 showConsole(showjconsole);
549 showNews.setVisible(false);
551 experimentalFeatures.setSelected(showExperimental());
553 getIdentifiersOrgData();
557 // Spawn a thread that shows the splashscreen
560 SwingUtilities.invokeLater(new Runnable()
565 new SplashScreen(true);
570 // Thread off a new instance of the file chooser - this reduces the time
572 // takes to open it later on.
573 new Thread(new Runnable()
578 jalview.bin.Console.debug("Filechooser init thread started.");
579 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
580 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
582 jalview.bin.Console.debug("Filechooser init thread finished.");
585 // Add the service change listener
586 changeSupport.addJalviewPropertyChangeListener("services",
587 new PropertyChangeListener()
591 public void propertyChange(PropertyChangeEvent evt)
594 .debug("Firing service changed event for "
595 + evt.getNewValue());
596 JalviewServicesChanged(evt);
601 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
604 this.addMouseListener(ma = new MouseAdapter()
607 public void mousePressed(MouseEvent evt)
609 if (evt.isPopupTrigger()) // Mac
611 showPasteMenu(evt.getX(), evt.getY());
616 public void mouseReleased(MouseEvent evt)
618 if (evt.isPopupTrigger()) // Windows
620 showPasteMenu(evt.getX(), evt.getY());
624 desktop.addMouseListener(ma);
628 * Answers true if user preferences to enable experimental features is True
633 public boolean showExperimental()
635 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
636 Boolean.FALSE.toString());
637 return Boolean.valueOf(experimental).booleanValue();
640 public void doConfigureStructurePrefs()
642 // configure services
643 StructureSelectionManager ssm = StructureSelectionManager
644 .getStructureSelectionManager(this);
645 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
647 ssm.setAddTempFacAnnot(
648 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
649 ssm.setProcessSecondaryStructure(
650 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
651 // JAL-3915 - RNAView is no longer an option so this has no effect
652 ssm.setSecStructServices(
653 Cache.getDefault(Preferences.USE_RNAVIEW, false));
657 ssm.setAddTempFacAnnot(false);
658 ssm.setProcessSecondaryStructure(false);
659 ssm.setSecStructServices(false);
663 public void checkForNews()
665 final Desktop me = this;
666 // Thread off the news reader, in case there are connection problems.
667 new Thread(new Runnable()
672 jalview.bin.Console.debug("Starting news thread.");
673 jvnews = new BlogReader(me);
674 showNews.setVisible(true);
675 jalview.bin.Console.debug("Completed news thread.");
680 public void getIdentifiersOrgData()
682 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
683 {// Thread off the identifiers fetcher
684 new Thread(new Runnable()
690 .debug("Downloading data from identifiers.org");
693 UrlDownloadClient.download(IdOrgSettings.getUrl(),
694 IdOrgSettings.getDownloadLocation());
695 } catch (IOException e)
698 .debug("Exception downloading identifiers.org data"
708 protected void showNews_actionPerformed(ActionEvent e)
710 showNews(showNews.isSelected());
713 void showNews(boolean visible)
715 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
716 showNews.setSelected(visible);
717 if (visible && !jvnews.isVisible())
719 new Thread(new Runnable()
724 long now = System.currentTimeMillis();
725 Desktop.instance.setProgressBar(
726 MessageManager.getString("status.refreshing_news"), now);
727 jvnews.refreshNews();
728 Desktop.instance.setProgressBar(null, now);
736 * recover the last known dimensions for a jalview window
739 * - empty string is desktop, all other windows have unique prefix
740 * @return null or last known dimensions scaled to current geometry (if last
741 * window geom was known)
743 Rectangle getLastKnownDimensions(String windowName)
745 // TODO: lock aspect ratio for scaling desktop Bug #0058199
746 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
747 String x = Cache.getProperty(windowName + "SCREEN_X");
748 String y = Cache.getProperty(windowName + "SCREEN_Y");
749 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
750 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
751 if ((x != null) && (y != null) && (width != null) && (height != null))
753 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
754 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
755 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
757 // attempt #1 - try to cope with change in screen geometry - this
758 // version doesn't preserve original jv aspect ratio.
759 // take ratio of current screen size vs original screen size.
760 double sw = ((1f * screenSize.width) / (1f * Integer
761 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
762 double sh = ((1f * screenSize.height) / (1f * Integer
763 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
764 // rescale the bounds depending upon the current screen geometry.
765 ix = (int) (ix * sw);
766 iw = (int) (iw * sw);
767 iy = (int) (iy * sh);
768 ih = (int) (ih * sh);
769 while (ix >= screenSize.width)
771 jalview.bin.Console.debug(
772 "Window geometry location recall error: shifting horizontal to within screenbounds.");
773 ix -= screenSize.width;
775 while (iy >= screenSize.height)
777 jalview.bin.Console.debug(
778 "Window geometry location recall error: shifting vertical to within screenbounds.");
779 iy -= screenSize.height;
781 jalview.bin.Console.debug(
782 "Got last known dimensions for " + windowName + ": x:" + ix
783 + " y:" + iy + " width:" + iw + " height:" + ih);
785 // return dimensions for new instance
786 return new Rectangle(ix, iy, iw, ih);
791 void showPasteMenu(int x, int y)
793 JPopupMenu popup = new JPopupMenu();
794 JMenuItem item = new JMenuItem(
795 MessageManager.getString("label.paste_new_window"));
796 item.addActionListener(new ActionListener()
799 public void actionPerformed(ActionEvent evt)
806 popup.show(this, x, y);
811 // quick patch for JAL-4150 - needs some more work and test coverage
812 // TODO - unify below and AlignFrame.paste()
813 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
814 // clipboard has come from a different alignment window than the one where
815 // paste has been called! JAL-4151
817 if (Desktop.jalviewClipboard != null)
819 // The clipboard was filled from within Jalview, we must use the
821 // And dataset from the copied alignment
822 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
823 // be doubly sure that we create *new* sequence objects.
824 SequenceI[] sequences = new SequenceI[newseq.length];
825 for (int i = 0; i < newseq.length; i++)
827 sequences[i] = new Sequence(newseq[i]);
829 Alignment alignment = new Alignment(sequences);
830 // dataset is inherited
831 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
832 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
833 AlignFrame.DEFAULT_HEIGHT);
834 String newtitle = new String("Copied sequences");
836 if (Desktop.jalviewClipboard[2] != null)
838 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
839 af.viewport.setHiddenColumns(hc);
842 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
843 AlignFrame.DEFAULT_HEIGHT);
850 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
851 Transferable contents = c.getContents(this);
853 if (contents != null)
855 String file = (String) contents
856 .getTransferData(DataFlavor.stringFlavor);
858 FileFormatI format = new IdentifyFile().identify(file,
859 DataSourceType.PASTE);
861 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
864 } catch (Exception ex)
867 "Unable to paste alignment from system clipboard:\n" + ex);
873 * Adds and opens the given frame to the desktop
884 public static synchronized void addInternalFrame(
885 final JInternalFrame frame, String title, int w, int h)
887 addInternalFrame(frame, title, true, w, h, true, false);
891 * Add an internal frame to the Jalview desktop
898 * When true, display frame immediately, otherwise, caller must call
899 * setVisible themselves.
905 public static synchronized void addInternalFrame(
906 final JInternalFrame frame, String title, boolean makeVisible,
909 addInternalFrame(frame, title, makeVisible, w, h, true, false);
913 * Add an internal frame to the Jalview desktop and make it visible
926 public static synchronized void addInternalFrame(
927 final JInternalFrame frame, String title, int w, int h,
930 addInternalFrame(frame, title, true, w, h, resizable, false);
934 * Add an internal frame to the Jalview desktop
941 * When true, display frame immediately, otherwise, caller must call
942 * setVisible themselves.
949 * @param ignoreMinSize
950 * Do not set the default minimum size for frame
952 public static synchronized void addInternalFrame(
953 final JInternalFrame frame, String title, boolean makeVisible,
954 int w, int h, boolean resizable, boolean ignoreMinSize)
957 // TODO: allow callers to determine X and Y position of frame (eg. via
959 // TODO: consider fixing method to update entries in the window submenu with
960 // the current window title
962 frame.setTitle(title);
963 if (frame.getWidth() < 1 || frame.getHeight() < 1)
967 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
968 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
969 // IF JALVIEW IS RUNNING HEADLESS
970 // ///////////////////////////////////////////////
971 if (instance == null || (System.getProperty("java.awt.headless") != null
972 && System.getProperty("java.awt.headless").equals("true")))
981 frame.setMinimumSize(
982 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
984 // Set default dimension for Alignment Frame window.
985 // The Alignment Frame window could be added from a number of places,
987 // I did this here in order not to miss out on any Alignment frame.
988 if (frame instanceof AlignFrame)
990 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
991 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
995 frame.setVisible(makeVisible);
996 frame.setClosable(true);
997 frame.setResizable(resizable);
998 frame.setMaximizable(resizable);
999 frame.setIconifiable(resizable);
1000 frame.setOpaque(Platform.isJS());
1002 if (frame.getX() < 1 && frame.getY() < 1)
1004 frame.setLocation(xOffset * openFrameCount,
1005 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1009 * add an entry for the new frame in the Window menu (and remove it when the
1012 final JMenuItem menuItem = new JMenuItem(title);
1013 frame.addInternalFrameListener(new InternalFrameAdapter()
1016 public void internalFrameActivated(InternalFrameEvent evt)
1018 JInternalFrame itf = desktop.getSelectedFrame();
1021 if (itf instanceof AlignFrame)
1023 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1030 public void internalFrameClosed(InternalFrameEvent evt)
1032 PaintRefresher.RemoveComponent(frame);
1035 * defensive check to prevent frames being added half off the window
1037 if (openFrameCount > 0)
1043 * ensure no reference to alignFrame retained by menu item listener
1045 if (menuItem.getActionListeners().length > 0)
1047 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1049 windowMenu.remove(menuItem);
1053 menuItem.addActionListener(new ActionListener()
1056 public void actionPerformed(ActionEvent e)
1060 frame.setSelected(true);
1061 frame.setIcon(false);
1062 } catch (java.beans.PropertyVetoException ex)
1069 setKeyBindings(frame);
1073 windowMenu.add(menuItem);
1078 frame.setSelected(true);
1079 frame.requestFocus();
1080 } catch (java.beans.PropertyVetoException ve)
1082 } catch (java.lang.ClassCastException cex)
1084 jalview.bin.Console.warn(
1085 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1091 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1096 private static void setKeyBindings(JInternalFrame frame)
1098 @SuppressWarnings("serial")
1099 final Action closeAction = new AbstractAction()
1102 public void actionPerformed(ActionEvent e)
1109 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1111 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1112 InputEvent.CTRL_DOWN_MASK);
1113 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1114 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1116 InputMap inputMap = frame
1117 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1118 String ctrlW = ctrlWKey.toString();
1119 inputMap.put(ctrlWKey, ctrlW);
1120 inputMap.put(cmdWKey, ctrlW);
1122 ActionMap actionMap = frame.getActionMap();
1123 actionMap.put(ctrlW, closeAction);
1127 public void lostOwnership(Clipboard clipboard, Transferable contents)
1131 Desktop.jalviewClipboard = null;
1134 internalCopy = false;
1138 public void dragEnter(DropTargetDragEvent evt)
1143 public void dragExit(DropTargetEvent evt)
1148 public void dragOver(DropTargetDragEvent evt)
1153 public void dropActionChanged(DropTargetDragEvent evt)
1164 public void drop(DropTargetDropEvent evt)
1166 boolean success = true;
1167 // JAL-1552 - acceptDrop required before getTransferable call for
1168 // Java's Transferable for native dnd
1169 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1170 Transferable t = evt.getTransferable();
1171 List<Object> files = new ArrayList<>();
1172 List<DataSourceType> protocols = new ArrayList<>();
1176 Desktop.transferFromDropTarget(files, protocols, evt, t);
1177 } catch (Exception e)
1179 e.printStackTrace();
1187 for (int i = 0; i < files.size(); i++)
1189 // BH 2018 File or String
1190 Object file = files.get(i);
1191 String fileName = file.toString();
1192 DataSourceType protocol = (protocols == null)
1193 ? DataSourceType.FILE
1195 FileFormatI format = null;
1197 if (fileName.endsWith(".jar"))
1199 format = FileFormat.Jalview;
1204 format = new IdentifyFile().identify(file, protocol);
1206 if (file instanceof File)
1208 Platform.cacheFileData((File) file);
1210 new FileLoader().LoadFile(null, file, protocol, format);
1213 } catch (Exception ex)
1218 evt.dropComplete(success); // need this to ensure input focus is properly
1219 // transfered to any new windows created
1229 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1231 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1232 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1233 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1234 BackupFiles.getEnabled());
1236 chooser.setFileView(new JalviewFileView());
1237 chooser.setDialogTitle(
1238 MessageManager.getString("label.open_local_file"));
1239 chooser.setToolTipText(MessageManager.getString("action.open"));
1241 chooser.setResponseHandler(0, () -> {
1242 File selectedFile = chooser.getSelectedFile();
1243 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1245 FileFormatI format = chooser.getSelectedFormat();
1248 * Call IdentifyFile to verify the file contains what its extension implies.
1249 * Skip this step for dynamically added file formats, because IdentifyFile does
1250 * not know how to recognise them.
1252 if (FileFormats.getInstance().isIdentifiable(format))
1256 format = new IdentifyFile().identify(selectedFile,
1257 DataSourceType.FILE);
1258 } catch (FileFormatException e)
1260 // format = null; //??
1264 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1267 chooser.showOpenDialog(this);
1271 * Shows a dialog for input of a URL at which to retrieve alignment data
1276 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1278 // This construct allows us to have a wider textfield
1280 JLabel label = new JLabel(
1281 MessageManager.getString("label.input_file_url"));
1283 JPanel panel = new JPanel(new GridLayout(2, 1));
1287 * the URL to fetch is input in Java: an editable combobox with history JS:
1288 * (pending JAL-3038) a plain text field
1291 String urlBase = "https://www.";
1292 if (Platform.isJS())
1294 history = new JTextField(urlBase, 35);
1303 JComboBox<String> asCombo = new JComboBox<>();
1304 asCombo.setPreferredSize(new Dimension(400, 20));
1305 asCombo.setEditable(true);
1306 asCombo.addItem(urlBase);
1307 String historyItems = Cache.getProperty("RECENT_URL");
1308 if (historyItems != null)
1310 for (String token : historyItems.split("\\t"))
1312 asCombo.addItem(token);
1319 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1320 MessageManager.getString("action.cancel") };
1321 Runnable action = () -> {
1322 @SuppressWarnings("unchecked")
1323 String url = (history instanceof JTextField
1324 ? ((JTextField) history).getText()
1325 : ((JComboBox<String>) history).getEditor().getItem()
1326 .toString().trim());
1328 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1330 if (viewport != null)
1332 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1333 FileFormat.Jalview);
1337 new FileLoader().LoadFile(url, DataSourceType.URL,
1338 FileFormat.Jalview);
1343 FileFormatI format = null;
1346 format = new IdentifyFile().identify(url, DataSourceType.URL);
1347 } catch (FileFormatException e)
1349 // TODO revise error handling, distinguish between
1350 // URL not found and response not valid
1355 String msg = MessageManager.formatMessage("label.couldnt_locate",
1357 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1358 MessageManager.getString("label.url_not_found"),
1359 JvOptionPane.WARNING_MESSAGE);
1363 if (viewport != null)
1365 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1370 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1374 String dialogOption = MessageManager
1375 .getString("label.input_alignment_from_url");
1376 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1377 .showInternalDialog(panel, dialogOption,
1378 JvOptionPane.YES_NO_CANCEL_OPTION,
1379 JvOptionPane.PLAIN_MESSAGE, null, options,
1380 MessageManager.getString("action.ok"));
1384 * Opens the CutAndPaste window for the user to paste an alignment in to
1387 * - if not null, the pasted alignment is added to the current
1388 * alignment; if null, to a new alignment window
1391 public void inputTextboxMenuItem_actionPerformed(
1392 AlignmentViewPanel viewPanel)
1394 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1395 cap.setForInput(viewPanel);
1396 Desktop.addInternalFrame(cap,
1397 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1402 * Check with user and saving files before actually quitting
1404 public void desktopQuit()
1406 desktopQuit(true, false);
1409 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1411 final Runnable doDesktopQuit = () -> {
1412 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1413 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1414 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1415 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1416 getBounds().y, getWidth(), getHeight()));
1418 if (jconsole != null)
1420 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1421 jconsole.stopConsole();
1426 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1429 // Frames should all close automatically. Keeping external
1430 // viewers open should already be decided by user.
1431 closeAll_actionPerformed(null);
1433 // check for aborted quit
1434 if (QuitHandler.quitCancelled())
1436 jalview.bin.Console.debug("Desktop aborting quit");
1440 if (dialogExecutor != null)
1442 dialogExecutor.shutdownNow();
1445 if (groovyConsole != null)
1447 // suppress a possible repeat prompt to save script
1448 groovyConsole.setDirty(false);
1449 groovyConsole.exit();
1452 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1454 // note that shutdown hook will not be run
1455 jalview.bin.Console.debug("Force Quit selected by user");
1456 Runtime.getRuntime().halt(0);
1459 jalview.bin.Console.debug("Quit selected by user");
1462 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1463 // instance.dispose();
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 jalview.bin.Console.info("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();
1966 chooser.showOpenDialog(this);
1970 public void inputSequence_actionPerformed(ActionEvent e)
1972 new SequenceFetcher(this);
1975 JPanel progressPanel;
1977 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1979 public void startLoading(final Object fileName)
1981 if (fileLoadingCount == 0)
1983 fileLoadingPanels.add(addProgressPanel(MessageManager
1984 .formatMessage("label.loading_file", new Object[]
1990 private JPanel addProgressPanel(String string)
1992 if (progressPanel == null)
1994 progressPanel = new JPanel(new GridLayout(1, 1));
1995 totalProgressCount = 0;
1996 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1998 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1999 JProgressBar progressBar = new JProgressBar();
2000 progressBar.setIndeterminate(true);
2002 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2004 thisprogress.add(progressBar, BorderLayout.CENTER);
2005 progressPanel.add(thisprogress);
2006 ((GridLayout) progressPanel.getLayout()).setRows(
2007 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2008 ++totalProgressCount;
2009 instance.validate();
2010 return thisprogress;
2013 int totalProgressCount = 0;
2015 private void removeProgressPanel(JPanel progbar)
2017 if (progressPanel != null)
2019 synchronized (progressPanel)
2021 progressPanel.remove(progbar);
2022 GridLayout gl = (GridLayout) progressPanel.getLayout();
2023 gl.setRows(gl.getRows() - 1);
2024 if (--totalProgressCount < 1)
2026 this.getContentPane().remove(progressPanel);
2027 progressPanel = null;
2034 public void stopLoading()
2037 if (fileLoadingCount < 1)
2039 while (fileLoadingPanels.size() > 0)
2041 removeProgressPanel(fileLoadingPanels.remove(0));
2043 fileLoadingPanels.clear();
2044 fileLoadingCount = 0;
2049 public static int getViewCount(String alignmentId)
2051 AlignmentViewport[] aps = getViewports(alignmentId);
2052 return (aps == null) ? 0 : aps.length;
2057 * @param alignmentId
2058 * - if null, all sets are returned
2059 * @return all AlignmentPanels concerning the alignmentId sequence set
2061 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2063 if (Desktop.desktop == null)
2065 // no frames created and in headless mode
2066 // TODO: verify that frames are recoverable when in headless mode
2069 List<AlignmentPanel> aps = new ArrayList<>();
2070 AlignFrame[] frames = getAlignFrames();
2075 for (AlignFrame af : frames)
2077 for (AlignmentPanel ap : af.alignPanels)
2079 if (alignmentId == null
2080 || alignmentId.equals(ap.av.getSequenceSetId()))
2086 if (aps.size() == 0)
2090 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2095 * get all the viewports on an alignment.
2097 * @param sequenceSetId
2098 * unique alignment id (may be null - all viewports returned in that
2100 * @return all viewports on the alignment bound to sequenceSetId
2102 public static AlignmentViewport[] getViewports(String sequenceSetId)
2104 List<AlignmentViewport> viewp = new ArrayList<>();
2105 if (desktop != null)
2107 AlignFrame[] frames = Desktop.getAlignFrames();
2109 for (AlignFrame afr : frames)
2111 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2112 .equals(sequenceSetId))
2114 if (afr.alignPanels != null)
2116 for (AlignmentPanel ap : afr.alignPanels)
2118 if (sequenceSetId == null
2119 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2127 viewp.add(afr.getViewport());
2131 if (viewp.size() > 0)
2133 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2140 * Explode the views in the given frame into separate AlignFrame
2144 public static void explodeViews(AlignFrame af)
2146 int size = af.alignPanels.size();
2152 // FIXME: ideally should use UI interface API
2153 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2154 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2155 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2156 for (int i = 0; i < size; i++)
2158 AlignmentPanel ap = af.alignPanels.get(i);
2160 AlignFrame newaf = new AlignFrame(ap);
2162 // transfer reference for existing feature settings to new alignFrame
2163 if (ap == af.alignPanel)
2165 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2167 newaf.featureSettings = viewFeatureSettings;
2169 newaf.setFeatureSettingsGeometry(fsBounds);
2173 * Restore the view's last exploded frame geometry if known. Multiple views from
2174 * one exploded frame share and restore the same (frame) position and size.
2176 Rectangle geometry = ap.av.getExplodedGeometry();
2177 if (geometry != null)
2179 newaf.setBounds(geometry);
2182 ap.av.setGatherViewsHere(false);
2184 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2185 AlignFrame.DEFAULT_HEIGHT);
2186 // and materialise a new feature settings dialog instance for the new
2188 // (closes the old as if 'OK' was pressed)
2189 if (ap == af.alignPanel && newaf.featureSettings != null
2190 && newaf.featureSettings.isOpen()
2191 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2193 newaf.showFeatureSettingsUI();
2197 af.featureSettings = null;
2198 af.alignPanels.clear();
2199 af.closeMenuItem_actionPerformed(true);
2204 * Gather expanded views (separate AlignFrame's) with the same sequence set
2205 * identifier back in to this frame as additional views, and close the
2206 * expanded views. Note the expanded frames may themselves have multiple
2207 * views. We take the lot.
2211 public void gatherViews(AlignFrame source)
2213 source.viewport.setGatherViewsHere(true);
2214 source.viewport.setExplodedGeometry(source.getBounds());
2215 JInternalFrame[] frames = desktop.getAllFrames();
2216 String viewId = source.viewport.getSequenceSetId();
2217 for (int t = 0; t < frames.length; t++)
2219 if (frames[t] instanceof AlignFrame && frames[t] != source)
2221 AlignFrame af = (AlignFrame) frames[t];
2222 boolean gatherThis = false;
2223 for (int a = 0; a < af.alignPanels.size(); a++)
2225 AlignmentPanel ap = af.alignPanels.get(a);
2226 if (viewId.equals(ap.av.getSequenceSetId()))
2229 ap.av.setGatherViewsHere(false);
2230 ap.av.setExplodedGeometry(af.getBounds());
2231 source.addAlignmentPanel(ap, false);
2237 if (af.featureSettings != null && af.featureSettings.isOpen())
2239 if (source.featureSettings == null)
2241 // preserve the feature settings geometry for this frame
2242 source.featureSettings = af.featureSettings;
2243 source.setFeatureSettingsGeometry(
2244 af.getFeatureSettingsGeometry());
2248 // close it and forget
2249 af.featureSettings.close();
2252 af.alignPanels.clear();
2253 af.closeMenuItem_actionPerformed(true);
2258 // refresh the feature setting UI for the source frame if it exists
2259 if (source.featureSettings != null && source.featureSettings.isOpen())
2261 source.showFeatureSettingsUI();
2266 public JInternalFrame[] getAllFrames()
2268 return desktop.getAllFrames();
2272 * Checks the given url to see if it gives a response indicating that the user
2273 * should be informed of a new questionnaire.
2277 public void checkForQuestionnaire(String url)
2279 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2280 // javax.swing.SwingUtilities.invokeLater(jvq);
2281 new Thread(jvq).start();
2284 public void checkURLLinks()
2286 // Thread off the URL link checker
2287 addDialogThread(new Runnable()
2292 if (Cache.getDefault("CHECKURLLINKS", true))
2294 // check what the actual links are - if it's just the default don't
2295 // bother with the warning
2296 List<String> links = Preferences.sequenceUrlLinks
2299 // only need to check links if there is one with a
2300 // SEQUENCE_ID which is not the default EMBL_EBI link
2301 ListIterator<String> li = links.listIterator();
2302 boolean check = false;
2303 List<JLabel> urls = new ArrayList<>();
2304 while (li.hasNext())
2306 String link = li.next();
2307 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2308 && !UrlConstants.isDefaultString(link))
2311 int barPos = link.indexOf("|");
2312 String urlMsg = barPos == -1 ? link
2313 : link.substring(0, barPos) + ": "
2314 + link.substring(barPos + 1);
2315 urls.add(new JLabel(urlMsg));
2323 // ask user to check in case URL links use old style tokens
2324 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2325 JPanel msgPanel = new JPanel();
2326 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2327 msgPanel.add(Box.createVerticalGlue());
2328 JLabel msg = new JLabel(MessageManager
2329 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2330 JLabel msg2 = new JLabel(MessageManager
2331 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2333 for (JLabel url : urls)
2339 final JCheckBox jcb = new JCheckBox(
2340 MessageManager.getString("label.do_not_display_again"));
2341 jcb.addActionListener(new ActionListener()
2344 public void actionPerformed(ActionEvent e)
2346 // update Cache settings for "don't show this again"
2347 boolean showWarningAgain = !jcb.isSelected();
2348 Cache.setProperty("CHECKURLLINKS",
2349 Boolean.valueOf(showWarningAgain).toString());
2354 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2356 .getString("label.SEQUENCE_ID_no_longer_used"),
2357 JvOptionPane.WARNING_MESSAGE);
2364 * Proxy class for JDesktopPane which optionally displays the current memory
2365 * usage and highlights the desktop area with a red bar if free memory runs
2370 public class MyDesktopPane extends JDesktopPane implements Runnable
2372 private static final float ONE_MB = 1048576f;
2374 boolean showMemoryUsage = false;
2378 java.text.NumberFormat df;
2380 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2383 public MyDesktopPane(boolean showMemoryUsage)
2385 showMemoryUsage(showMemoryUsage);
2388 public void showMemoryUsage(boolean showMemory)
2390 this.showMemoryUsage = showMemory;
2393 Thread worker = new Thread(this);
2399 public boolean isShowMemoryUsage()
2401 return showMemoryUsage;
2407 df = java.text.NumberFormat.getNumberInstance();
2408 df.setMaximumFractionDigits(2);
2409 runtime = Runtime.getRuntime();
2411 while (showMemoryUsage)
2415 maxMemory = runtime.maxMemory() / ONE_MB;
2416 allocatedMemory = runtime.totalMemory() / ONE_MB;
2417 freeMemory = runtime.freeMemory() / ONE_MB;
2418 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2420 percentUsage = (totalFreeMemory / maxMemory) * 100;
2422 // if (percentUsage < 20)
2424 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2426 // instance.set.setBorder(border1);
2429 // sleep after showing usage
2431 } catch (Exception ex)
2433 ex.printStackTrace();
2439 public void paintComponent(Graphics g)
2441 if (showMemoryUsage && g != null && df != null)
2443 if (percentUsage < 20)
2445 g.setColor(Color.red);
2447 FontMetrics fm = g.getFontMetrics();
2450 g.drawString(MessageManager.formatMessage("label.memory_stats",
2452 { df.format(totalFreeMemory), df.format(maxMemory),
2453 df.format(percentUsage) }),
2454 10, getHeight() - fm.getHeight());
2458 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2459 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2464 * Accessor method to quickly get all the AlignmentFrames loaded.
2466 * @return an array of AlignFrame, or null if none found
2468 public static AlignFrame[] getAlignFrames()
2470 if (Jalview.isHeadlessMode())
2472 // Desktop.desktop is null in headless mode
2473 return new AlignFrame[] { Jalview.currentAlignFrame };
2476 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2482 List<AlignFrame> avp = new ArrayList<>();
2484 for (int i = frames.length - 1; i > -1; i--)
2486 if (frames[i] instanceof AlignFrame)
2488 avp.add((AlignFrame) frames[i]);
2490 else if (frames[i] instanceof SplitFrame)
2493 * Also check for a split frame containing an AlignFrame
2495 GSplitFrame sf = (GSplitFrame) frames[i];
2496 if (sf.getTopFrame() instanceof AlignFrame)
2498 avp.add((AlignFrame) sf.getTopFrame());
2500 if (sf.getBottomFrame() instanceof AlignFrame)
2502 avp.add((AlignFrame) sf.getBottomFrame());
2506 if (avp.size() == 0)
2510 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2515 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2519 public GStructureViewer[] getJmols()
2521 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2527 List<GStructureViewer> avp = new ArrayList<>();
2529 for (int i = frames.length - 1; i > -1; i--)
2531 if (frames[i] instanceof AppJmol)
2533 GStructureViewer af = (GStructureViewer) frames[i];
2537 if (avp.size() == 0)
2541 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2546 * Add Groovy Support to Jalview
2549 public void groovyShell_actionPerformed()
2553 openGroovyConsole();
2554 } catch (Exception ex)
2556 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2557 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2559 MessageManager.getString("label.couldnt_create_groovy_shell"),
2560 MessageManager.getString("label.groovy_support_failed"),
2561 JvOptionPane.ERROR_MESSAGE);
2566 * Open the Groovy console
2568 void openGroovyConsole()
2570 if (groovyConsole == null)
2572 groovyConsole = new groovy.ui.Console();
2573 groovyConsole.setVariable("Jalview", this);
2574 groovyConsole.run();
2577 * We allow only one console at a time, so that AlignFrame menu option
2578 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2579 * enable 'Run script', when the console is opened, and the reverse when it is
2582 Window window = (Window) groovyConsole.getFrame();
2583 window.addWindowListener(new WindowAdapter()
2586 public void windowClosed(WindowEvent e)
2589 * rebind CMD-Q from Groovy Console to Jalview Quit
2592 enableExecuteGroovy(false);
2598 * show Groovy console window (after close and reopen)
2600 ((Window) groovyConsole.getFrame()).setVisible(true);
2603 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2604 * opening a second console
2606 enableExecuteGroovy(true);
2610 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2611 * binding when opened
2613 protected void addQuitHandler()
2616 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2618 .getKeyStroke(KeyEvent.VK_Q,
2619 jalview.util.ShortcutKeyMaskExWrapper
2620 .getMenuShortcutKeyMaskEx()),
2622 getRootPane().getActionMap().put("Quit", new AbstractAction()
2625 public void actionPerformed(ActionEvent e)
2633 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2636 * true if Groovy console is open
2638 public void enableExecuteGroovy(boolean enabled)
2641 * disable opening a second Groovy console (or re-enable when the console is
2644 groovyShell.setEnabled(!enabled);
2646 AlignFrame[] alignFrames = getAlignFrames();
2647 if (alignFrames != null)
2649 for (AlignFrame af : alignFrames)
2651 af.setGroovyEnabled(enabled);
2657 * Progress bars managed by the IProgressIndicator method.
2659 private Hashtable<Long, JPanel> progressBars;
2661 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2666 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2669 public void setProgressBar(String message, long id)
2671 if (progressBars == null)
2673 progressBars = new Hashtable<>();
2674 progressBarHandlers = new Hashtable<>();
2677 if (progressBars.get(Long.valueOf(id)) != null)
2679 JPanel panel = progressBars.remove(Long.valueOf(id));
2680 if (progressBarHandlers.contains(Long.valueOf(id)))
2682 progressBarHandlers.remove(Long.valueOf(id));
2684 removeProgressPanel(panel);
2688 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2695 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2696 * jalview.gui.IProgressIndicatorHandler)
2699 public void registerHandler(final long id,
2700 final IProgressIndicatorHandler handler)
2702 if (progressBarHandlers == null
2703 || !progressBars.containsKey(Long.valueOf(id)))
2705 throw new Error(MessageManager.getString(
2706 "error.call_setprogressbar_before_registering_handler"));
2708 progressBarHandlers.put(Long.valueOf(id), handler);
2709 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2710 if (handler.canCancel())
2712 JButton cancel = new JButton(
2713 MessageManager.getString("action.cancel"));
2714 final IProgressIndicator us = this;
2715 cancel.addActionListener(new ActionListener()
2719 public void actionPerformed(ActionEvent e)
2721 handler.cancelActivity(id);
2722 us.setProgressBar(MessageManager
2723 .formatMessage("label.cancelled_params", new Object[]
2724 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2728 progressPanel.add(cancel, BorderLayout.EAST);
2734 * @return true if any progress bars are still active
2737 public boolean operationInProgress()
2739 if (progressBars != null && progressBars.size() > 0)
2747 * This will return the first AlignFrame holding the given viewport instance.
2748 * It will break if there are more than one AlignFrames viewing a particular
2752 * @return alignFrame for viewport
2754 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2756 if (desktop != null)
2758 AlignmentPanel[] aps = getAlignmentPanels(
2759 viewport.getSequenceSetId());
2760 for (int panel = 0; aps != null && panel < aps.length; panel++)
2762 if (aps[panel] != null && aps[panel].av == viewport)
2764 return aps[panel].alignFrame;
2771 public VamsasApplication getVamsasApplication()
2773 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2779 * flag set if jalview GUI is being operated programmatically
2781 private boolean inBatchMode = false;
2784 * check if jalview GUI is being operated programmatically
2786 * @return inBatchMode
2788 public boolean isInBatchMode()
2794 * set flag if jalview GUI is being operated programmatically
2796 * @param inBatchMode
2798 public void setInBatchMode(boolean inBatchMode)
2800 this.inBatchMode = inBatchMode;
2804 * start service discovery and wait till it is done
2806 public void startServiceDiscovery()
2808 startServiceDiscovery(false);
2812 * start service discovery threads - blocking or non-blocking
2816 public void startServiceDiscovery(boolean blocking)
2818 startServiceDiscovery(blocking, false);
2822 * start service discovery threads
2825 * - false means call returns immediately
2826 * @param ignore_SHOW_JWS2_SERVICES_preference
2827 * - when true JABA services are discovered regardless of user's JWS2
2828 * discovery preference setting
2830 public void startServiceDiscovery(boolean blocking,
2831 boolean ignore_SHOW_JWS2_SERVICES_preference)
2833 boolean alive = true;
2834 Thread t0 = null, t1 = null, t2 = null;
2835 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2838 // todo: changesupport handlers need to be transferred
2839 if (discoverer == null)
2841 discoverer = new jalview.ws.jws1.Discoverer();
2842 // register PCS handler for desktop.
2843 discoverer.addPropertyChangeListener(changeSupport);
2845 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2846 // until we phase out completely
2847 (t0 = new Thread(discoverer)).start();
2850 if (ignore_SHOW_JWS2_SERVICES_preference
2851 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2853 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2854 .startDiscoverer(changeSupport);
2858 // TODO: do rest service discovery
2867 } catch (Exception e)
2870 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2871 || (t3 != null && t3.isAlive())
2872 || (t0 != null && t0.isAlive());
2878 * called to check if the service discovery process completed successfully.
2882 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2884 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2886 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2887 .getErrorMessages();
2890 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2892 if (serviceChangedDialog == null)
2894 // only run if we aren't already displaying one of these.
2895 addDialogThread(serviceChangedDialog = new Runnable()
2902 * JalviewDialog jd =new JalviewDialog() {
2904 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2906 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2908 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2910 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2912 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2913 * + " or mis-configured HTTP proxy settings.<br/>" +
2914 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2915 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2916 * true, true, "Web Service Configuration Problem", 450, 400);
2918 * jd.waitForInput();
2920 JvOptionPane.showConfirmDialog(Desktop.desktop,
2921 new JLabel("<html><table width=\"450\"><tr><td>"
2922 + ermsg + "</td></tr></table>"
2923 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2924 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2925 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2926 + " Tools->Preferences dialog box to change them.</p></html>"),
2927 "Web Service Configuration Problem",
2928 JvOptionPane.DEFAULT_OPTION,
2929 JvOptionPane.ERROR_MESSAGE);
2930 serviceChangedDialog = null;
2938 jalview.bin.Console.error(
2939 "Errors reported by JABA discovery service. Check web services preferences.\n"
2946 private Runnable serviceChangedDialog = null;
2949 * start a thread to open a URL in the configured browser. Pops up a warning
2950 * dialog to the user if there is an exception when calling out to the browser
2955 public static void showUrl(final String url)
2957 showUrl(url, Desktop.instance);
2961 * Like showUrl but allows progress handler to be specified
2965 * (null) or object implementing IProgressIndicator
2967 public static void showUrl(final String url,
2968 final IProgressIndicator progress)
2970 new Thread(new Runnable()
2977 if (progress != null)
2979 progress.setProgressBar(MessageManager
2980 .formatMessage("status.opening_params", new Object[]
2981 { url }), this.hashCode());
2983 jalview.util.BrowserLauncher.openURL(url);
2984 } catch (Exception ex)
2986 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2988 .getString("label.web_browser_not_found_unix"),
2989 MessageManager.getString("label.web_browser_not_found"),
2990 JvOptionPane.WARNING_MESSAGE);
2992 ex.printStackTrace();
2994 if (progress != null)
2996 progress.setProgressBar(null, this.hashCode());
3002 public static WsParamSetManager wsparamManager = null;
3004 public static ParamManager getUserParameterStore()
3006 if (wsparamManager == null)
3008 wsparamManager = new WsParamSetManager();
3010 return wsparamManager;
3014 * static hyperlink handler proxy method for use by Jalview's internal windows
3018 public static void hyperlinkUpdate(HyperlinkEvent e)
3020 if (e.getEventType() == EventType.ACTIVATED)
3025 url = e.getURL().toString();
3026 Desktop.showUrl(url);
3027 } catch (Exception x)
3032 .error("Couldn't handle string " + url + " as a URL.");
3034 // ignore any exceptions due to dud links.
3041 * single thread that handles display of dialogs to user.
3043 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3046 * flag indicating if dialogExecutor should try to acquire a permit
3048 private volatile boolean dialogPause = true;
3053 private java.util.concurrent.Semaphore block = new Semaphore(0);
3055 private static groovy.ui.Console groovyConsole;
3058 * add another dialog thread to the queue
3062 public void addDialogThread(final Runnable prompter)
3064 dialogExecutor.submit(new Runnable()
3074 } catch (InterruptedException x)
3078 if (instance == null)
3084 SwingUtilities.invokeAndWait(prompter);
3085 } catch (Exception q)
3087 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3094 public void startDialogQueue()
3096 // set the flag so we don't pause waiting for another permit and semaphore
3097 // the current task to begin
3098 dialogPause = false;
3103 * Outputs an image of the desktop to file in EPS format, after prompting the
3104 * user for choice of Text or Lineart character rendering (unless a preference
3105 * has been set). The file name is generated as
3108 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3112 protected void snapShotWindow_actionPerformed(ActionEvent e)
3114 // currently the menu option to do this is not shown
3117 int width = getWidth();
3118 int height = getHeight();
3120 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3121 ImageWriterI writer = new ImageWriterI()
3124 public void exportImage(Graphics g) throws Exception
3127 jalview.bin.Console.info("Successfully written snapshot to file "
3128 + of.getAbsolutePath());
3131 String title = "View of desktop";
3132 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3134 exporter.doExport(of, this, width, height, title);
3138 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3139 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3140 * and location last time the view was expanded (if any). However it does not
3141 * remember the split pane divider location - this is set to match the
3142 * 'exploding' frame.
3146 public void explodeViews(SplitFrame sf)
3148 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3149 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3150 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3152 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3154 int viewCount = topPanels.size();
3161 * Processing in reverse order works, forwards order leaves the first panels not
3162 * visible. I don't know why!
3164 for (int i = viewCount - 1; i >= 0; i--)
3167 * Make new top and bottom frames. These take over the respective AlignmentPanel
3168 * objects, including their AlignmentViewports, so the cdna/protein
3169 * relationships between the viewports is carried over to the new split frames.
3171 * explodedGeometry holds the (x, y) position of the previously exploded
3172 * SplitFrame, and the (width, height) of the AlignFrame component
3174 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3175 AlignFrame newTopFrame = new AlignFrame(topPanel);
3176 newTopFrame.setSize(oldTopFrame.getSize());
3177 newTopFrame.setVisible(true);
3178 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3179 .getExplodedGeometry();
3180 if (geometry != null)
3182 newTopFrame.setSize(geometry.getSize());
3185 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3186 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3187 newBottomFrame.setSize(oldBottomFrame.getSize());
3188 newBottomFrame.setVisible(true);
3189 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3190 .getExplodedGeometry();
3191 if (geometry != null)
3193 newBottomFrame.setSize(geometry.getSize());
3196 topPanel.av.setGatherViewsHere(false);
3197 bottomPanel.av.setGatherViewsHere(false);
3198 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3200 if (geometry != null)
3202 splitFrame.setLocation(geometry.getLocation());
3204 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3208 * Clear references to the panels (now relocated in the new SplitFrames) before
3209 * closing the old SplitFrame.
3212 bottomPanels.clear();
3217 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3218 * back into the given SplitFrame as additional views. Note that the gathered
3219 * frames may themselves have multiple views.
3223 public void gatherViews(GSplitFrame source)
3226 * special handling of explodedGeometry for a view within a SplitFrame: - it
3227 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3228 * height) of the AlignFrame component
3230 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3231 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3232 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3233 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3234 myBottomFrame.viewport
3235 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3236 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3237 myTopFrame.viewport.setGatherViewsHere(true);
3238 myBottomFrame.viewport.setGatherViewsHere(true);
3239 String topViewId = myTopFrame.viewport.getSequenceSetId();
3240 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3242 JInternalFrame[] frames = desktop.getAllFrames();
3243 for (JInternalFrame frame : frames)
3245 if (frame instanceof SplitFrame && frame != source)
3247 SplitFrame sf = (SplitFrame) frame;
3248 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3249 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3250 boolean gatherThis = false;
3251 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3253 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3254 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3255 if (topViewId.equals(topPanel.av.getSequenceSetId())
3256 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3259 topPanel.av.setGatherViewsHere(false);
3260 bottomPanel.av.setGatherViewsHere(false);
3261 topPanel.av.setExplodedGeometry(
3262 new Rectangle(sf.getLocation(), topFrame.getSize()));
3263 bottomPanel.av.setExplodedGeometry(
3264 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3265 myTopFrame.addAlignmentPanel(topPanel, false);
3266 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3272 topFrame.getAlignPanels().clear();
3273 bottomFrame.getAlignPanels().clear();
3280 * The dust settles...give focus to the tab we did this from.
3282 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3285 public static groovy.ui.Console getGroovyConsole()
3287 return groovyConsole;
3291 * handles the payload of a drag and drop event.
3293 * TODO refactor to desktop utilities class
3296 * - Data source strings extracted from the drop event
3298 * - protocol for each data source extracted from the drop event
3302 * - the payload from the drop event
3305 public static void transferFromDropTarget(List<Object> files,
3306 List<DataSourceType> protocols, DropTargetDropEvent evt,
3307 Transferable t) throws Exception
3310 DataFlavor uriListFlavor = new DataFlavor(
3311 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3314 urlFlavour = new DataFlavor(
3315 "application/x-java-url; class=java.net.URL");
3316 } catch (ClassNotFoundException cfe)
3318 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3322 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3327 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3328 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3329 // means url may be null.
3332 protocols.add(DataSourceType.URL);
3333 files.add(url.toString());
3334 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3335 + files.get(files.size() - 1));
3340 if (Platform.isAMacAndNotJS())
3343 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3346 } catch (Throwable ex)
3348 jalview.bin.Console.debug("URL drop handler failed.", ex);
3351 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3353 // Works on Windows and MacOSX
3354 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3355 for (Object file : (List) t
3356 .getTransferData(DataFlavor.javaFileListFlavor))
3359 protocols.add(DataSourceType.FILE);
3364 // Unix like behaviour
3365 boolean added = false;
3367 if (t.isDataFlavorSupported(uriListFlavor))
3369 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3370 // This is used by Unix drag system
3371 data = (String) t.getTransferData(uriListFlavor);
3375 // fallback to text: workaround - on OSX where there's a JVM bug
3377 .debug("standard URIListFlavor failed. Trying text");
3378 // try text fallback
3379 DataFlavor textDf = new DataFlavor(
3380 "text/plain;class=java.lang.String");
3381 if (t.isDataFlavorSupported(textDf))
3383 data = (String) t.getTransferData(textDf);
3386 jalview.bin.Console.debug("Plain text drop content returned "
3387 + (data == null ? "Null - failed" : data));
3392 while (protocols.size() < files.size())
3394 jalview.bin.Console.debug("Adding missing FILE protocol for "
3395 + files.get(protocols.size()));
3396 protocols.add(DataSourceType.FILE);
3398 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3399 data, "\r\n"); st.hasMoreTokens();)
3402 String s = st.nextToken();
3403 if (s.startsWith("#"))
3405 // the line is a comment (as per the RFC 2483)
3408 java.net.URI uri = new java.net.URI(s);
3409 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3411 protocols.add(DataSourceType.URL);
3412 files.add(uri.toString());
3416 // otherwise preserve old behaviour: catch all for file objects
3417 java.io.File file = new java.io.File(uri);
3418 protocols.add(DataSourceType.FILE);
3419 files.add(file.toString());
3424 if (jalview.bin.Console.isDebugEnabled())
3426 if (data == null || !added)
3429 if (t.getTransferDataFlavors() != null
3430 && t.getTransferDataFlavors().length > 0)
3432 jalview.bin.Console.debug(
3433 "Couldn't resolve drop data. Here are the supported flavors:");
3434 for (DataFlavor fl : t.getTransferDataFlavors())
3436 jalview.bin.Console.debug(
3437 "Supported transfer dataflavor: " + fl.toString());
3438 Object df = t.getTransferData(fl);
3441 jalview.bin.Console.debug("Retrieves: " + df);
3445 jalview.bin.Console.debug("Retrieved nothing");
3452 .debug("Couldn't resolve dataflavor for drop: "
3458 if (Platform.isWindowsAndNotJS())
3461 .debug("Scanning dropped content for Windows Link Files");
3463 // resolve any .lnk files in the file drop
3464 for (int f = 0; f < files.size(); f++)
3466 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3467 if (protocols.get(f).equals(DataSourceType.FILE)
3468 && (source.endsWith(".lnk") || source.endsWith(".url")
3469 || source.endsWith(".site")))
3473 Object obj = files.get(f);
3474 File lf = (obj instanceof File ? (File) obj
3475 : new File((String) obj));
3476 // process link file to get a URL
3477 jalview.bin.Console.debug("Found potential link file: " + lf);
3478 WindowsShortcut wscfile = new WindowsShortcut(lf);
3479 String fullname = wscfile.getRealFilename();
3480 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3481 files.set(f, fullname);
3482 jalview.bin.Console.debug("Parsed real filename " + fullname
3483 + " to extract protocol: " + protocols.get(f));
3484 } catch (Exception ex)
3486 jalview.bin.Console.error(
3487 "Couldn't parse " + files.get(f) + " as a link file.",
3496 * Sets the Preferences property for experimental features to True or False
3497 * depending on the state of the controlling menu item
3500 protected void showExperimental_actionPerformed(boolean selected)
3502 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3506 * Answers a (possibly empty) list of any structure viewer frames (currently
3507 * for either Jmol or Chimera) which are currently open. This may optionally
3508 * be restricted to viewers of a specified class, or viewers linked to a
3509 * specified alignment panel.
3512 * if not null, only return viewers linked to this panel
3513 * @param structureViewerClass
3514 * if not null, only return viewers of this class
3517 public List<StructureViewerBase> getStructureViewers(
3518 AlignmentPanel apanel,
3519 Class<? extends StructureViewerBase> structureViewerClass)
3521 List<StructureViewerBase> result = new ArrayList<>();
3522 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3524 for (JInternalFrame frame : frames)
3526 if (frame instanceof StructureViewerBase)
3528 if (structureViewerClass == null
3529 || structureViewerClass.isInstance(frame))
3532 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3534 result.add((StructureViewerBase) frame);
3542 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3544 private static boolean debugScaleMessageDone = false;
3546 public static void debugScaleMessage(Graphics g)
3548 if (debugScaleMessageDone)
3552 // output used by tests to check HiDPI scaling settings in action
3555 Graphics2D gg = (Graphics2D) g;
3558 AffineTransform t = gg.getTransform();
3559 double scaleX = t.getScaleX();
3560 double scaleY = t.getScaleY();
3561 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3562 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3563 debugScaleMessageDone = true;
3567 jalview.bin.Console.debug("Desktop graphics null");
3569 } catch (Exception e)
3571 jalview.bin.Console.debug(Cache.getStackTraceString(e));