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.");
446 Toolkit xToolkit = Toolkit.getDefaultToolkit();
447 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
448 Field awtAppClassNameField = null;
450 if (Arrays.stream(declaredFields)
451 .anyMatch(f -> f.getName().equals("awtAppClassName")))
453 awtAppClassNameField = xToolkit.getClass()
454 .getDeclaredField("awtAppClassName");
457 String title = ChannelProperties.getProperty("app_name");
458 if (awtAppClassNameField != null)
460 awtAppClassNameField.setAccessible(true);
461 awtAppClassNameField.set(xToolkit, title);
465 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
467 } catch (Exception e)
469 jalview.bin.Console.debug("Error setting awtAppClassName");
470 jalview.bin.Console.trace(Cache.getStackTraceString(e));
474 setIconImages(ChannelProperties.getIconList());
476 // override quit handling when GUI OS close [X] button pressed
477 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
478 addWindowListener(new WindowAdapter()
481 public void windowClosing(WindowEvent ev)
483 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
487 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
489 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
490 desktop = new MyDesktopPane(selmemusage);
492 showMemusage.setSelected(selmemusage);
493 desktop.setBackground(Color.white);
495 getContentPane().setLayout(new BorderLayout());
496 // alternate config - have scrollbars - see notes in JAL-153
497 // JScrollPane sp = new JScrollPane();
498 // sp.getViewport().setView(desktop);
499 // getContentPane().add(sp, BorderLayout.CENTER);
501 // BH 2018 - just an experiment to try unclipped JInternalFrames.
504 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
507 getContentPane().add(desktop, BorderLayout.CENTER);
508 desktop.setDragMode(DRAG_MODE);
510 // This line prevents Windows Look&Feel resizing all new windows to maximum
511 // if previous window was maximised
512 desktop.setDesktopManager(new MyDesktopManager(
513 Platform.isJS() ? desktop.getDesktopManager()
514 : new DefaultDesktopManager()));
516 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
517 : Platform.isAMacAndNotJS()
518 ? new AquaInternalFrameManager(
519 desktop.getDesktopManager())
520 : desktop.getDesktopManager())));
523 Rectangle dims = getLastKnownDimensions("");
530 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
531 int xPos = Math.max(5, (screenSize.width - 900) / 2);
532 int yPos = Math.max(5, (screenSize.height - 650) / 2);
533 setBounds(xPos, yPos, 900, 650);
536 if (!Platform.isJS())
543 jconsole = new Console(this, showjconsole);
544 jconsole.setHeader(Cache.getVersionDetailsForConsole());
545 showConsole(showjconsole);
547 showNews.setVisible(false);
549 experimentalFeatures.setSelected(showExperimental());
551 getIdentifiersOrgData();
555 // Spawn a thread that shows the splashscreen
558 SwingUtilities.invokeLater(new Runnable()
563 new SplashScreen(true);
568 // Thread off a new instance of the file chooser - this reduces the time
570 // takes to open it later on.
571 new Thread(new Runnable()
576 jalview.bin.Console.debug("Filechooser init thread started.");
577 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
578 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
580 jalview.bin.Console.debug("Filechooser init thread finished.");
583 // Add the service change listener
584 changeSupport.addJalviewPropertyChangeListener("services",
585 new PropertyChangeListener()
589 public void propertyChange(PropertyChangeEvent evt)
592 .debug("Firing service changed event for "
593 + evt.getNewValue());
594 JalviewServicesChanged(evt);
599 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
602 this.addMouseListener(ma = new MouseAdapter()
605 public void mousePressed(MouseEvent evt)
607 if (evt.isPopupTrigger()) // Mac
609 showPasteMenu(evt.getX(), evt.getY());
614 public void mouseReleased(MouseEvent evt)
616 if (evt.isPopupTrigger()) // Windows
618 showPasteMenu(evt.getX(), evt.getY());
622 desktop.addMouseListener(ma);
626 * Answers true if user preferences to enable experimental features is True
631 public boolean showExperimental()
633 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
634 Boolean.FALSE.toString());
635 return Boolean.valueOf(experimental).booleanValue();
638 public void doConfigureStructurePrefs()
640 // configure services
641 StructureSelectionManager ssm = StructureSelectionManager
642 .getStructureSelectionManager(this);
643 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
645 ssm.setAddTempFacAnnot(
646 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
647 ssm.setProcessSecondaryStructure(
648 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
649 // JAL-3915 - RNAView is no longer an option so this has no effect
650 ssm.setSecStructServices(
651 Cache.getDefault(Preferences.USE_RNAVIEW, false));
655 ssm.setAddTempFacAnnot(false);
656 ssm.setProcessSecondaryStructure(false);
657 ssm.setSecStructServices(false);
661 public void checkForNews()
663 final Desktop me = this;
664 // Thread off the news reader, in case there are connection problems.
665 new Thread(new Runnable()
670 jalview.bin.Console.debug("Starting news thread.");
671 jvnews = new BlogReader(me);
672 showNews.setVisible(true);
673 jalview.bin.Console.debug("Completed news thread.");
678 public void getIdentifiersOrgData()
680 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
681 {// Thread off the identifiers fetcher
682 new Thread(new Runnable()
688 .debug("Downloading data from identifiers.org");
691 UrlDownloadClient.download(IdOrgSettings.getUrl(),
692 IdOrgSettings.getDownloadLocation());
693 } catch (IOException e)
696 .debug("Exception downloading identifiers.org data"
706 protected void showNews_actionPerformed(ActionEvent e)
708 showNews(showNews.isSelected());
711 void showNews(boolean visible)
713 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
714 showNews.setSelected(visible);
715 if (visible && !jvnews.isVisible())
717 new Thread(new Runnable()
722 long now = System.currentTimeMillis();
723 Desktop.instance.setProgressBar(
724 MessageManager.getString("status.refreshing_news"), now);
725 jvnews.refreshNews();
726 Desktop.instance.setProgressBar(null, now);
734 * recover the last known dimensions for a jalview window
737 * - empty string is desktop, all other windows have unique prefix
738 * @return null or last known dimensions scaled to current geometry (if last
739 * window geom was known)
741 Rectangle getLastKnownDimensions(String windowName)
743 // TODO: lock aspect ratio for scaling desktop Bug #0058199
744 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
745 String x = Cache.getProperty(windowName + "SCREEN_X");
746 String y = Cache.getProperty(windowName + "SCREEN_Y");
747 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
748 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
749 if ((x != null) && (y != null) && (width != null) && (height != null))
751 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
752 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
753 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
755 // attempt #1 - try to cope with change in screen geometry - this
756 // version doesn't preserve original jv aspect ratio.
757 // take ratio of current screen size vs original screen size.
758 double sw = ((1f * screenSize.width) / (1f * Integer
759 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
760 double sh = ((1f * screenSize.height) / (1f * Integer
761 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
762 // rescale the bounds depending upon the current screen geometry.
763 ix = (int) (ix * sw);
764 iw = (int) (iw * sw);
765 iy = (int) (iy * sh);
766 ih = (int) (ih * sh);
767 while (ix >= screenSize.width)
769 jalview.bin.Console.debug(
770 "Window geometry location recall error: shifting horizontal to within screenbounds.");
771 ix -= screenSize.width;
773 while (iy >= screenSize.height)
775 jalview.bin.Console.debug(
776 "Window geometry location recall error: shifting vertical to within screenbounds.");
777 iy -= screenSize.height;
779 jalview.bin.Console.debug(
780 "Got last known dimensions for " + windowName + ": x:" + ix
781 + " y:" + iy + " width:" + iw + " height:" + ih);
783 // return dimensions for new instance
784 return new Rectangle(ix, iy, iw, ih);
789 void showPasteMenu(int x, int y)
791 JPopupMenu popup = new JPopupMenu();
792 JMenuItem item = new JMenuItem(
793 MessageManager.getString("label.paste_new_window"));
794 item.addActionListener(new ActionListener()
797 public void actionPerformed(ActionEvent evt)
804 popup.show(this, x, y);
809 // quick patch for JAL-4150 - needs some more work and test coverage
810 // TODO - unify below and AlignFrame.paste()
811 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
812 // clipboard has come from a different alignment window than the one where
813 // paste has been called! JAL-4151
815 if (Desktop.jalviewClipboard != null)
817 // The clipboard was filled from within Jalview, we must use the
819 // And dataset from the copied alignment
820 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
821 // be doubly sure that we create *new* sequence objects.
822 SequenceI[] sequences = new SequenceI[newseq.length];
823 for (int i = 0; i < newseq.length; i++)
825 sequences[i] = new Sequence(newseq[i]);
827 Alignment alignment = new Alignment(sequences);
828 // dataset is inherited
829 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
830 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
831 AlignFrame.DEFAULT_HEIGHT);
832 String newtitle = new String("Copied sequences");
834 if (Desktop.jalviewClipboard[2] != null)
836 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
837 af.viewport.setHiddenColumns(hc);
840 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
841 AlignFrame.DEFAULT_HEIGHT);
848 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
849 Transferable contents = c.getContents(this);
851 if (contents != null)
853 String file = (String) contents
854 .getTransferData(DataFlavor.stringFlavor);
856 FileFormatI format = new IdentifyFile().identify(file,
857 DataSourceType.PASTE);
859 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
862 } catch (Exception ex)
865 "Unable to paste alignment from system clipboard:\n" + ex);
871 * Adds and opens the given frame to the desktop
882 public static synchronized void addInternalFrame(
883 final JInternalFrame frame, String title, int w, int h)
885 addInternalFrame(frame, title, true, w, h, true, false);
889 * Add an internal frame to the Jalview desktop
896 * When true, display frame immediately, otherwise, caller must call
897 * setVisible themselves.
903 public static synchronized void addInternalFrame(
904 final JInternalFrame frame, String title, boolean makeVisible,
907 addInternalFrame(frame, title, makeVisible, w, h, true, false);
911 * Add an internal frame to the Jalview desktop and make it visible
924 public static synchronized void addInternalFrame(
925 final JInternalFrame frame, String title, int w, int h,
928 addInternalFrame(frame, title, true, w, h, resizable, false);
932 * Add an internal frame to the Jalview desktop
939 * When true, display frame immediately, otherwise, caller must call
940 * setVisible themselves.
947 * @param ignoreMinSize
948 * Do not set the default minimum size for frame
950 public static synchronized void addInternalFrame(
951 final JInternalFrame frame, String title, boolean makeVisible,
952 int w, int h, boolean resizable, boolean ignoreMinSize)
955 // TODO: allow callers to determine X and Y position of frame (eg. via
957 // TODO: consider fixing method to update entries in the window submenu with
958 // the current window title
960 frame.setTitle(title);
961 if (frame.getWidth() < 1 || frame.getHeight() < 1)
965 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
966 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
967 // IF JALVIEW IS RUNNING HEADLESS
968 // ///////////////////////////////////////////////
969 if (instance == null || (System.getProperty("java.awt.headless") != null
970 && System.getProperty("java.awt.headless").equals("true")))
979 frame.setMinimumSize(
980 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
982 // Set default dimension for Alignment Frame window.
983 // The Alignment Frame window could be added from a number of places,
985 // I did this here in order not to miss out on any Alignment frame.
986 if (frame instanceof AlignFrame)
988 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
989 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
993 frame.setVisible(makeVisible);
994 frame.setClosable(true);
995 frame.setResizable(resizable);
996 frame.setMaximizable(resizable);
997 frame.setIconifiable(resizable);
998 frame.setOpaque(Platform.isJS());
1000 if (frame.getX() < 1 && frame.getY() < 1)
1002 frame.setLocation(xOffset * openFrameCount,
1003 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1007 * add an entry for the new frame in the Window menu (and remove it when the
1010 final JMenuItem menuItem = new JMenuItem(title);
1011 frame.addInternalFrameListener(new InternalFrameAdapter()
1014 public void internalFrameActivated(InternalFrameEvent evt)
1016 JInternalFrame itf = desktop.getSelectedFrame();
1019 if (itf instanceof AlignFrame)
1021 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1028 public void internalFrameClosed(InternalFrameEvent evt)
1030 PaintRefresher.RemoveComponent(frame);
1033 * defensive check to prevent frames being added half off the window
1035 if (openFrameCount > 0)
1041 * ensure no reference to alignFrame retained by menu item listener
1043 if (menuItem.getActionListeners().length > 0)
1045 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1047 windowMenu.remove(menuItem);
1051 menuItem.addActionListener(new ActionListener()
1054 public void actionPerformed(ActionEvent e)
1058 frame.setSelected(true);
1059 frame.setIcon(false);
1060 } catch (java.beans.PropertyVetoException ex)
1067 setKeyBindings(frame);
1071 windowMenu.add(menuItem);
1076 frame.setSelected(true);
1077 frame.requestFocus();
1078 } catch (java.beans.PropertyVetoException ve)
1080 } catch (java.lang.ClassCastException cex)
1082 jalview.bin.Console.warn(
1083 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1089 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1094 private static void setKeyBindings(JInternalFrame frame)
1096 @SuppressWarnings("serial")
1097 final Action closeAction = new AbstractAction()
1100 public void actionPerformed(ActionEvent e)
1107 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1109 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1110 InputEvent.CTRL_DOWN_MASK);
1111 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1112 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1114 InputMap inputMap = frame
1115 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1116 String ctrlW = ctrlWKey.toString();
1117 inputMap.put(ctrlWKey, ctrlW);
1118 inputMap.put(cmdWKey, ctrlW);
1120 ActionMap actionMap = frame.getActionMap();
1121 actionMap.put(ctrlW, closeAction);
1125 public void lostOwnership(Clipboard clipboard, Transferable contents)
1129 Desktop.jalviewClipboard = null;
1132 internalCopy = false;
1136 public void dragEnter(DropTargetDragEvent evt)
1141 public void dragExit(DropTargetEvent evt)
1146 public void dragOver(DropTargetDragEvent evt)
1151 public void dropActionChanged(DropTargetDragEvent evt)
1162 public void drop(DropTargetDropEvent evt)
1164 boolean success = true;
1165 // JAL-1552 - acceptDrop required before getTransferable call for
1166 // Java's Transferable for native dnd
1167 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1168 Transferable t = evt.getTransferable();
1169 List<Object> files = new ArrayList<>();
1170 List<DataSourceType> protocols = new ArrayList<>();
1174 Desktop.transferFromDropTarget(files, protocols, evt, t);
1175 } catch (Exception e)
1177 e.printStackTrace();
1185 for (int i = 0; i < files.size(); i++)
1187 // BH 2018 File or String
1188 Object file = files.get(i);
1189 String fileName = file.toString();
1190 DataSourceType protocol = (protocols == null)
1191 ? DataSourceType.FILE
1193 FileFormatI format = null;
1195 if (fileName.endsWith(".jar"))
1197 format = FileFormat.Jalview;
1202 format = new IdentifyFile().identify(file, protocol);
1204 if (file instanceof File)
1206 Platform.cacheFileData((File) file);
1208 new FileLoader().LoadFile(null, file, protocol, format);
1211 } catch (Exception ex)
1216 evt.dropComplete(success); // need this to ensure input focus is properly
1217 // transfered to any new windows created
1227 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1229 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1230 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1231 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1232 BackupFiles.getEnabled());
1234 chooser.setFileView(new JalviewFileView());
1235 chooser.setDialogTitle(
1236 MessageManager.getString("label.open_local_file"));
1237 chooser.setToolTipText(MessageManager.getString("action.open"));
1239 chooser.setResponseHandler(0, () -> {
1240 File selectedFile = chooser.getSelectedFile();
1241 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1243 FileFormatI format = chooser.getSelectedFormat();
1246 * Call IdentifyFile to verify the file contains what its extension implies.
1247 * Skip this step for dynamically added file formats, because IdentifyFile does
1248 * not know how to recognise them.
1250 if (FileFormats.getInstance().isIdentifiable(format))
1254 format = new IdentifyFile().identify(selectedFile,
1255 DataSourceType.FILE);
1256 } catch (FileFormatException e)
1258 // format = null; //??
1262 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1265 chooser.showOpenDialog(this);
1269 * Shows a dialog for input of a URL at which to retrieve alignment data
1274 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1276 // This construct allows us to have a wider textfield
1278 JLabel label = new JLabel(
1279 MessageManager.getString("label.input_file_url"));
1281 JPanel panel = new JPanel(new GridLayout(2, 1));
1285 * the URL to fetch is input in Java: an editable combobox with history JS:
1286 * (pending JAL-3038) a plain text field
1289 String urlBase = "https://www.";
1290 if (Platform.isJS())
1292 history = new JTextField(urlBase, 35);
1301 JComboBox<String> asCombo = new JComboBox<>();
1302 asCombo.setPreferredSize(new Dimension(400, 20));
1303 asCombo.setEditable(true);
1304 asCombo.addItem(urlBase);
1305 String historyItems = Cache.getProperty("RECENT_URL");
1306 if (historyItems != null)
1308 for (String token : historyItems.split("\\t"))
1310 asCombo.addItem(token);
1317 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1318 MessageManager.getString("action.cancel") };
1319 Runnable action = () -> {
1320 @SuppressWarnings("unchecked")
1321 String url = (history instanceof JTextField
1322 ? ((JTextField) history).getText()
1323 : ((JComboBox<String>) history).getEditor().getItem()
1324 .toString().trim());
1326 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1328 if (viewport != null)
1330 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1331 FileFormat.Jalview);
1335 new FileLoader().LoadFile(url, DataSourceType.URL,
1336 FileFormat.Jalview);
1341 FileFormatI format = null;
1344 format = new IdentifyFile().identify(url, DataSourceType.URL);
1345 } catch (FileFormatException e)
1347 // TODO revise error handling, distinguish between
1348 // URL not found and response not valid
1353 String msg = MessageManager.formatMessage("label.couldnt_locate",
1355 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1356 MessageManager.getString("label.url_not_found"),
1357 JvOptionPane.WARNING_MESSAGE);
1361 if (viewport != null)
1363 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1368 new FileLoader().LoadFile(url, DataSourceType.URL, format);
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 Runnable 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();
1466 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1467 QuitHandler.defaultCancelQuit);
1471 * Don't call this directly, use desktopQuit() above. Exits the program.
1476 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1477 // not run a second time if gotQuitResponse flag has been set (i.e. user
1478 // confirmed quit of some kind).
1479 Jalview.exit("Desktop exiting.", 0);
1482 private void storeLastKnownDimensions(String string, Rectangle jc)
1484 jalview.bin.Console.debug("Storing last known dimensions for " + string
1485 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1486 + " height:" + jc.height);
1488 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1489 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1490 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1491 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1501 public void aboutMenuItem_actionPerformed(ActionEvent e)
1503 new Thread(new Runnable()
1508 new SplashScreen(false);
1514 * Returns the html text for the About screen, including any available version
1515 * number, build details, author details and citation reference, but without
1516 * the enclosing {@code html} tags
1520 public String getAboutMessage()
1522 StringBuilder message = new StringBuilder(1024);
1523 message.append("<div style=\"font-family: sans-serif;\">")
1524 .append("<h1><strong>Version: ")
1525 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1526 .append("<strong>Built: <em>")
1527 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1528 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1529 .append("</strong>");
1531 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1532 if (latestVersion.equals("Checking"))
1534 // JBP removed this message for 2.11: May be reinstated in future version
1535 // message.append("<br>...Checking latest version...</br>");
1537 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1539 boolean red = false;
1540 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1541 .indexOf("automated build") == -1)
1544 // Displayed when code version and jnlp version do not match and code
1545 // version is not a development build
1546 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1549 message.append("<br>!! Version ")
1550 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1551 .append(" is available for download from ")
1552 .append(Cache.getDefault("www.jalview.org",
1553 "https://www.jalview.org"))
1557 message.append("</div>");
1560 message.append("<br>Authors: ");
1561 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1562 message.append(CITATION);
1564 message.append("</div>");
1566 return message.toString();
1570 * Action on requesting Help documentation
1573 public void documentationMenuItem_actionPerformed()
1577 if (Platform.isJS())
1579 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1588 Help.showHelpWindow();
1590 } catch (Exception ex)
1592 System.err.println("Error opening help: " + ex.getMessage());
1597 public void closeAll_actionPerformed(ActionEvent e)
1599 // TODO show a progress bar while closing?
1600 JInternalFrame[] frames = desktop.getAllFrames();
1601 for (int i = 0; i < frames.length; i++)
1605 frames[i].setClosed(true);
1606 } catch (java.beans.PropertyVetoException ex)
1610 Jalview.setCurrentAlignFrame(null);
1611 System.out.println("ALL CLOSED");
1614 * reset state of singleton objects as appropriate (clear down session state
1615 * when all windows are closed)
1617 StructureSelectionManager ssm = StructureSelectionManager
1618 .getStructureSelectionManager(this);
1625 public int structureViewersStillRunningCount()
1628 JInternalFrame[] frames = desktop.getAllFrames();
1629 for (int i = 0; i < frames.length; i++)
1631 if (frames[i] != null
1632 && frames[i] instanceof JalviewStructureDisplayI)
1634 if (((JalviewStructureDisplayI) frames[i]).stillRunning())
1642 public void raiseRelated_actionPerformed(ActionEvent e)
1644 reorderAssociatedWindows(false, false);
1648 public void minimizeAssociated_actionPerformed(ActionEvent e)
1650 reorderAssociatedWindows(true, false);
1653 void closeAssociatedWindows()
1655 reorderAssociatedWindows(false, true);
1661 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1665 protected void garbageCollect_actionPerformed(ActionEvent e)
1667 // We simply collect the garbage
1668 jalview.bin.Console.debug("Collecting garbage...");
1670 jalview.bin.Console.debug("Finished garbage collection.");
1676 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1680 protected void showMemusage_actionPerformed(ActionEvent e)
1682 desktop.showMemoryUsage(showMemusage.isSelected());
1689 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1693 protected void showConsole_actionPerformed(ActionEvent e)
1695 showConsole(showConsole.isSelected());
1698 Console jconsole = null;
1701 * control whether the java console is visible or not
1705 void showConsole(boolean selected)
1707 // TODO: decide if we should update properties file
1708 if (jconsole != null) // BH 2018
1710 showConsole.setSelected(selected);
1711 Cache.setProperty("SHOW_JAVA_CONSOLE",
1712 Boolean.valueOf(selected).toString());
1713 jconsole.setVisible(selected);
1717 void reorderAssociatedWindows(boolean minimize, boolean close)
1719 JInternalFrame[] frames = desktop.getAllFrames();
1720 if (frames == null || frames.length < 1)
1725 AlignmentViewport source = null, target = null;
1726 if (frames[0] instanceof AlignFrame)
1728 source = ((AlignFrame) frames[0]).getCurrentView();
1730 else if (frames[0] instanceof TreePanel)
1732 source = ((TreePanel) frames[0]).getViewPort();
1734 else if (frames[0] instanceof PCAPanel)
1736 source = ((PCAPanel) frames[0]).av;
1738 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1740 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1745 for (int i = 0; i < frames.length; i++)
1748 if (frames[i] == null)
1752 if (frames[i] instanceof AlignFrame)
1754 target = ((AlignFrame) frames[i]).getCurrentView();
1756 else if (frames[i] instanceof TreePanel)
1758 target = ((TreePanel) frames[i]).getViewPort();
1760 else if (frames[i] instanceof PCAPanel)
1762 target = ((PCAPanel) frames[i]).av;
1764 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1766 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1769 if (source == target)
1775 frames[i].setClosed(true);
1779 frames[i].setIcon(minimize);
1782 frames[i].toFront();
1786 } catch (java.beans.PropertyVetoException ex)
1801 protected void preferences_actionPerformed(ActionEvent e)
1803 Preferences.openPreferences();
1807 * Prompts the user to choose a file and then saves the Jalview state as a
1808 * Jalview project file
1811 public void saveState_actionPerformed()
1813 saveState_actionPerformed(false);
1816 public void saveState_actionPerformed(boolean saveAs)
1818 java.io.File projectFile = getProjectFile();
1819 // autoSave indicates we already have a file and don't need to ask
1820 boolean autoSave = projectFile != null && !saveAs
1821 && BackupFiles.getEnabled();
1823 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1824 // saveAs="+saveAs+", Backups
1825 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1827 boolean approveSave = false;
1830 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1833 chooser.setFileView(new JalviewFileView());
1834 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1836 int value = chooser.showSaveDialog(this);
1838 if (value == JalviewFileChooser.APPROVE_OPTION)
1840 projectFile = chooser.getSelectedFile();
1841 setProjectFile(projectFile);
1846 if (approveSave || autoSave)
1848 final Desktop me = this;
1849 final java.io.File chosenFile = projectFile;
1850 new Thread(new Runnable()
1855 // TODO: refactor to Jalview desktop session controller action.
1856 setProgressBar(MessageManager.formatMessage(
1857 "label.saving_jalview_project", new Object[]
1858 { chosenFile.getName() }), chosenFile.hashCode());
1859 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1860 // TODO catch and handle errors for savestate
1861 // TODO prevent user from messing with the Desktop whilst we're saving
1864 boolean doBackup = BackupFiles.getEnabled();
1865 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1868 new Jalview2XML().saveState(
1869 doBackup ? backupfiles.getTempFile() : chosenFile);
1873 backupfiles.setWriteSuccess(true);
1874 backupfiles.rollBackupsAndRenameTempFile();
1876 } catch (OutOfMemoryError oom)
1878 new OOMWarning("Whilst saving current state to "
1879 + chosenFile.getName(), oom);
1880 } catch (Exception ex)
1882 jalview.bin.Console.error("Problems whilst trying to save to "
1883 + chosenFile.getName(), ex);
1884 JvOptionPane.showMessageDialog(me,
1885 MessageManager.formatMessage(
1886 "label.error_whilst_saving_current_state_to",
1888 { chosenFile.getName() }),
1889 MessageManager.getString("label.couldnt_save_project"),
1890 JvOptionPane.WARNING_MESSAGE);
1892 setProgressBar(null, chosenFile.hashCode());
1899 public void saveAsState_actionPerformed(ActionEvent e)
1901 saveState_actionPerformed(true);
1904 protected void setProjectFile(File choice)
1906 this.projectFile = choice;
1909 public File getProjectFile()
1911 return this.projectFile;
1915 * Shows a file chooser dialog and tries to read in the selected file as a
1919 public void loadState_actionPerformed()
1921 final String[] suffix = new String[] { "jvp", "jar" };
1922 final String[] desc = new String[] { "Jalview Project",
1923 "Jalview Project (old)" };
1924 JalviewFileChooser chooser = new JalviewFileChooser(
1925 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1926 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1930 chooser.setFileView(new JalviewFileView());
1931 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1932 chooser.setResponseHandler(0, () -> {
1933 File selectedFile = chooser.getSelectedFile();
1934 setProjectFile(selectedFile);
1935 String choice = selectedFile.getAbsolutePath();
1936 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1937 new Thread(new Runnable()
1944 new Jalview2XML().loadJalviewAlign(selectedFile);
1945 } catch (OutOfMemoryError oom)
1947 new OOMWarning("Whilst loading project from " + choice, oom);
1948 } catch (Exception ex)
1950 jalview.bin.Console.error(
1951 "Problems whilst loading project from " + choice, ex);
1952 JvOptionPane.showMessageDialog(Desktop.desktop,
1953 MessageManager.formatMessage(
1954 "label.error_whilst_loading_project_from",
1957 MessageManager.getString("label.couldnt_load_project"),
1958 JvOptionPane.WARNING_MESSAGE);
1961 }, "Project Loader").start();
1964 chooser.showOpenDialog(this);
1968 public void inputSequence_actionPerformed(ActionEvent e)
1970 new SequenceFetcher(this);
1973 JPanel progressPanel;
1975 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1977 public void startLoading(final Object fileName)
1979 if (fileLoadingCount == 0)
1981 fileLoadingPanels.add(addProgressPanel(MessageManager
1982 .formatMessage("label.loading_file", new Object[]
1988 private JPanel addProgressPanel(String string)
1990 if (progressPanel == null)
1992 progressPanel = new JPanel(new GridLayout(1, 1));
1993 totalProgressCount = 0;
1994 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1996 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1997 JProgressBar progressBar = new JProgressBar();
1998 progressBar.setIndeterminate(true);
2000 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2002 thisprogress.add(progressBar, BorderLayout.CENTER);
2003 progressPanel.add(thisprogress);
2004 ((GridLayout) progressPanel.getLayout()).setRows(
2005 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2006 ++totalProgressCount;
2007 instance.validate();
2008 return thisprogress;
2011 int totalProgressCount = 0;
2013 private void removeProgressPanel(JPanel progbar)
2015 if (progressPanel != null)
2017 synchronized (progressPanel)
2019 progressPanel.remove(progbar);
2020 GridLayout gl = (GridLayout) progressPanel.getLayout();
2021 gl.setRows(gl.getRows() - 1);
2022 if (--totalProgressCount < 1)
2024 this.getContentPane().remove(progressPanel);
2025 progressPanel = null;
2032 public void stopLoading()
2035 if (fileLoadingCount < 1)
2037 while (fileLoadingPanels.size() > 0)
2039 removeProgressPanel(fileLoadingPanels.remove(0));
2041 fileLoadingPanels.clear();
2042 fileLoadingCount = 0;
2047 public static int getViewCount(String alignmentId)
2049 AlignmentViewport[] aps = getViewports(alignmentId);
2050 return (aps == null) ? 0 : aps.length;
2055 * @param alignmentId
2056 * - if null, all sets are returned
2057 * @return all AlignmentPanels concerning the alignmentId sequence set
2059 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2061 if (Desktop.desktop == null)
2063 // no frames created and in headless mode
2064 // TODO: verify that frames are recoverable when in headless mode
2067 List<AlignmentPanel> aps = new ArrayList<>();
2068 AlignFrame[] frames = getAlignFrames();
2073 for (AlignFrame af : frames)
2075 for (AlignmentPanel ap : af.alignPanels)
2077 if (alignmentId == null
2078 || alignmentId.equals(ap.av.getSequenceSetId()))
2084 if (aps.size() == 0)
2088 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2093 * get all the viewports on an alignment.
2095 * @param sequenceSetId
2096 * unique alignment id (may be null - all viewports returned in that
2098 * @return all viewports on the alignment bound to sequenceSetId
2100 public static AlignmentViewport[] getViewports(String sequenceSetId)
2102 List<AlignmentViewport> viewp = new ArrayList<>();
2103 if (desktop != null)
2105 AlignFrame[] frames = Desktop.getAlignFrames();
2107 for (AlignFrame afr : frames)
2109 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2110 .equals(sequenceSetId))
2112 if (afr.alignPanels != null)
2114 for (AlignmentPanel ap : afr.alignPanels)
2116 if (sequenceSetId == null
2117 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2125 viewp.add(afr.getViewport());
2129 if (viewp.size() > 0)
2131 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2138 * Explode the views in the given frame into separate AlignFrame
2142 public static void explodeViews(AlignFrame af)
2144 int size = af.alignPanels.size();
2150 // FIXME: ideally should use UI interface API
2151 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2152 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2153 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2154 for (int i = 0; i < size; i++)
2156 AlignmentPanel ap = af.alignPanels.get(i);
2158 AlignFrame newaf = new AlignFrame(ap);
2160 // transfer reference for existing feature settings to new alignFrame
2161 if (ap == af.alignPanel)
2163 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2165 newaf.featureSettings = viewFeatureSettings;
2167 newaf.setFeatureSettingsGeometry(fsBounds);
2171 * Restore the view's last exploded frame geometry if known. Multiple views from
2172 * one exploded frame share and restore the same (frame) position and size.
2174 Rectangle geometry = ap.av.getExplodedGeometry();
2175 if (geometry != null)
2177 newaf.setBounds(geometry);
2180 ap.av.setGatherViewsHere(false);
2182 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2183 AlignFrame.DEFAULT_HEIGHT);
2184 // and materialise a new feature settings dialog instance for the new
2186 // (closes the old as if 'OK' was pressed)
2187 if (ap == af.alignPanel && newaf.featureSettings != null
2188 && newaf.featureSettings.isOpen()
2189 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2191 newaf.showFeatureSettingsUI();
2195 af.featureSettings = null;
2196 af.alignPanels.clear();
2197 af.closeMenuItem_actionPerformed(true);
2202 * Gather expanded views (separate AlignFrame's) with the same sequence set
2203 * identifier back in to this frame as additional views, and close the
2204 * expanded views. Note the expanded frames may themselves have multiple
2205 * views. We take the lot.
2209 public void gatherViews(AlignFrame source)
2211 source.viewport.setGatherViewsHere(true);
2212 source.viewport.setExplodedGeometry(source.getBounds());
2213 JInternalFrame[] frames = desktop.getAllFrames();
2214 String viewId = source.viewport.getSequenceSetId();
2215 for (int t = 0; t < frames.length; t++)
2217 if (frames[t] instanceof AlignFrame && frames[t] != source)
2219 AlignFrame af = (AlignFrame) frames[t];
2220 boolean gatherThis = false;
2221 for (int a = 0; a < af.alignPanels.size(); a++)
2223 AlignmentPanel ap = af.alignPanels.get(a);
2224 if (viewId.equals(ap.av.getSequenceSetId()))
2227 ap.av.setGatherViewsHere(false);
2228 ap.av.setExplodedGeometry(af.getBounds());
2229 source.addAlignmentPanel(ap, false);
2235 if (af.featureSettings != null && af.featureSettings.isOpen())
2237 if (source.featureSettings == null)
2239 // preserve the feature settings geometry for this frame
2240 source.featureSettings = af.featureSettings;
2241 source.setFeatureSettingsGeometry(
2242 af.getFeatureSettingsGeometry());
2246 // close it and forget
2247 af.featureSettings.close();
2250 af.alignPanels.clear();
2251 af.closeMenuItem_actionPerformed(true);
2256 // refresh the feature setting UI for the source frame if it exists
2257 if (source.featureSettings != null && source.featureSettings.isOpen())
2259 source.showFeatureSettingsUI();
2264 public JInternalFrame[] getAllFrames()
2266 return desktop.getAllFrames();
2270 * Checks the given url to see if it gives a response indicating that the user
2271 * should be informed of a new questionnaire.
2275 public void checkForQuestionnaire(String url)
2277 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2278 // javax.swing.SwingUtilities.invokeLater(jvq);
2279 new Thread(jvq).start();
2282 public void checkURLLinks()
2284 // Thread off the URL link checker
2285 addDialogThread(new Runnable()
2290 if (Cache.getDefault("CHECKURLLINKS", true))
2292 // check what the actual links are - if it's just the default don't
2293 // bother with the warning
2294 List<String> links = Preferences.sequenceUrlLinks
2297 // only need to check links if there is one with a
2298 // SEQUENCE_ID which is not the default EMBL_EBI link
2299 ListIterator<String> li = links.listIterator();
2300 boolean check = false;
2301 List<JLabel> urls = new ArrayList<>();
2302 while (li.hasNext())
2304 String link = li.next();
2305 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2306 && !UrlConstants.isDefaultString(link))
2309 int barPos = link.indexOf("|");
2310 String urlMsg = barPos == -1 ? link
2311 : link.substring(0, barPos) + ": "
2312 + link.substring(barPos + 1);
2313 urls.add(new JLabel(urlMsg));
2321 // ask user to check in case URL links use old style tokens
2322 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2323 JPanel msgPanel = new JPanel();
2324 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2325 msgPanel.add(Box.createVerticalGlue());
2326 JLabel msg = new JLabel(MessageManager
2327 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2328 JLabel msg2 = new JLabel(MessageManager
2329 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2331 for (JLabel url : urls)
2337 final JCheckBox jcb = new JCheckBox(
2338 MessageManager.getString("label.do_not_display_again"));
2339 jcb.addActionListener(new ActionListener()
2342 public void actionPerformed(ActionEvent e)
2344 // update Cache settings for "don't show this again"
2345 boolean showWarningAgain = !jcb.isSelected();
2346 Cache.setProperty("CHECKURLLINKS",
2347 Boolean.valueOf(showWarningAgain).toString());
2352 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2354 .getString("label.SEQUENCE_ID_no_longer_used"),
2355 JvOptionPane.WARNING_MESSAGE);
2362 * Proxy class for JDesktopPane which optionally displays the current memory
2363 * usage and highlights the desktop area with a red bar if free memory runs
2368 public class MyDesktopPane extends JDesktopPane implements Runnable
2370 private static final float ONE_MB = 1048576f;
2372 boolean showMemoryUsage = false;
2376 java.text.NumberFormat df;
2378 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2381 public MyDesktopPane(boolean showMemoryUsage)
2383 showMemoryUsage(showMemoryUsage);
2386 public void showMemoryUsage(boolean showMemory)
2388 this.showMemoryUsage = showMemory;
2391 Thread worker = new Thread(this);
2397 public boolean isShowMemoryUsage()
2399 return showMemoryUsage;
2405 df = java.text.NumberFormat.getNumberInstance();
2406 df.setMaximumFractionDigits(2);
2407 runtime = Runtime.getRuntime();
2409 while (showMemoryUsage)
2413 maxMemory = runtime.maxMemory() / ONE_MB;
2414 allocatedMemory = runtime.totalMemory() / ONE_MB;
2415 freeMemory = runtime.freeMemory() / ONE_MB;
2416 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2418 percentUsage = (totalFreeMemory / maxMemory) * 100;
2420 // if (percentUsage < 20)
2422 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2424 // instance.set.setBorder(border1);
2427 // sleep after showing usage
2429 } catch (Exception ex)
2431 ex.printStackTrace();
2437 public void paintComponent(Graphics g)
2439 if (showMemoryUsage && g != null && df != null)
2441 if (percentUsage < 20)
2443 g.setColor(Color.red);
2445 FontMetrics fm = g.getFontMetrics();
2448 g.drawString(MessageManager.formatMessage("label.memory_stats",
2450 { df.format(totalFreeMemory), df.format(maxMemory),
2451 df.format(percentUsage) }),
2452 10, getHeight() - fm.getHeight());
2456 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2457 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2462 * Accessor method to quickly get all the AlignmentFrames loaded.
2464 * @return an array of AlignFrame, or null if none found
2466 public static AlignFrame[] getAlignFrames()
2468 if (Jalview.isHeadlessMode())
2470 // Desktop.desktop is null in headless mode
2471 return new AlignFrame[] { Jalview.currentAlignFrame };
2474 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2480 List<AlignFrame> avp = new ArrayList<>();
2482 for (int i = frames.length - 1; i > -1; i--)
2484 if (frames[i] instanceof AlignFrame)
2486 avp.add((AlignFrame) frames[i]);
2488 else if (frames[i] instanceof SplitFrame)
2491 * Also check for a split frame containing an AlignFrame
2493 GSplitFrame sf = (GSplitFrame) frames[i];
2494 if (sf.getTopFrame() instanceof AlignFrame)
2496 avp.add((AlignFrame) sf.getTopFrame());
2498 if (sf.getBottomFrame() instanceof AlignFrame)
2500 avp.add((AlignFrame) sf.getBottomFrame());
2504 if (avp.size() == 0)
2508 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2513 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2517 public GStructureViewer[] getJmols()
2519 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2525 List<GStructureViewer> avp = new ArrayList<>();
2527 for (int i = frames.length - 1; i > -1; i--)
2529 if (frames[i] instanceof AppJmol)
2531 GStructureViewer af = (GStructureViewer) frames[i];
2535 if (avp.size() == 0)
2539 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2544 * Add Groovy Support to Jalview
2547 public void groovyShell_actionPerformed()
2551 openGroovyConsole();
2552 } catch (Exception ex)
2554 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2555 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2557 MessageManager.getString("label.couldnt_create_groovy_shell"),
2558 MessageManager.getString("label.groovy_support_failed"),
2559 JvOptionPane.ERROR_MESSAGE);
2564 * Open the Groovy console
2566 void openGroovyConsole()
2568 if (groovyConsole == null)
2570 groovyConsole = new groovy.ui.Console();
2571 groovyConsole.setVariable("Jalview", this);
2572 groovyConsole.run();
2575 * We allow only one console at a time, so that AlignFrame menu option
2576 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2577 * enable 'Run script', when the console is opened, and the reverse when it is
2580 Window window = (Window) groovyConsole.getFrame();
2581 window.addWindowListener(new WindowAdapter()
2584 public void windowClosed(WindowEvent e)
2587 * rebind CMD-Q from Groovy Console to Jalview Quit
2590 enableExecuteGroovy(false);
2596 * show Groovy console window (after close and reopen)
2598 ((Window) groovyConsole.getFrame()).setVisible(true);
2601 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2602 * opening a second console
2604 enableExecuteGroovy(true);
2608 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2609 * binding when opened
2611 protected void addQuitHandler()
2614 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2616 .getKeyStroke(KeyEvent.VK_Q,
2617 jalview.util.ShortcutKeyMaskExWrapper
2618 .getMenuShortcutKeyMaskEx()),
2620 getRootPane().getActionMap().put("Quit", new AbstractAction()
2623 public void actionPerformed(ActionEvent e)
2631 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2634 * true if Groovy console is open
2636 public void enableExecuteGroovy(boolean enabled)
2639 * disable opening a second Groovy console (or re-enable when the console is
2642 groovyShell.setEnabled(!enabled);
2644 AlignFrame[] alignFrames = getAlignFrames();
2645 if (alignFrames != null)
2647 for (AlignFrame af : alignFrames)
2649 af.setGroovyEnabled(enabled);
2655 * Progress bars managed by the IProgressIndicator method.
2657 private Hashtable<Long, JPanel> progressBars;
2659 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2664 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2667 public void setProgressBar(String message, long id)
2669 if (progressBars == null)
2671 progressBars = new Hashtable<>();
2672 progressBarHandlers = new Hashtable<>();
2675 if (progressBars.get(Long.valueOf(id)) != null)
2677 JPanel panel = progressBars.remove(Long.valueOf(id));
2678 if (progressBarHandlers.contains(Long.valueOf(id)))
2680 progressBarHandlers.remove(Long.valueOf(id));
2682 removeProgressPanel(panel);
2686 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2693 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2694 * jalview.gui.IProgressIndicatorHandler)
2697 public void registerHandler(final long id,
2698 final IProgressIndicatorHandler handler)
2700 if (progressBarHandlers == null
2701 || !progressBars.containsKey(Long.valueOf(id)))
2703 throw new Error(MessageManager.getString(
2704 "error.call_setprogressbar_before_registering_handler"));
2706 progressBarHandlers.put(Long.valueOf(id), handler);
2707 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2708 if (handler.canCancel())
2710 JButton cancel = new JButton(
2711 MessageManager.getString("action.cancel"));
2712 final IProgressIndicator us = this;
2713 cancel.addActionListener(new ActionListener()
2717 public void actionPerformed(ActionEvent e)
2719 handler.cancelActivity(id);
2720 us.setProgressBar(MessageManager
2721 .formatMessage("label.cancelled_params", new Object[]
2722 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2726 progressPanel.add(cancel, BorderLayout.EAST);
2732 * @return true if any progress bars are still active
2735 public boolean operationInProgress()
2737 if (progressBars != null && progressBars.size() > 0)
2745 * This will return the first AlignFrame holding the given viewport instance.
2746 * It will break if there are more than one AlignFrames viewing a particular
2750 * @return alignFrame for viewport
2752 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2754 if (desktop != null)
2756 AlignmentPanel[] aps = getAlignmentPanels(
2757 viewport.getSequenceSetId());
2758 for (int panel = 0; aps != null && panel < aps.length; panel++)
2760 if (aps[panel] != null && aps[panel].av == viewport)
2762 return aps[panel].alignFrame;
2769 public VamsasApplication getVamsasApplication()
2771 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2777 * flag set if jalview GUI is being operated programmatically
2779 private boolean inBatchMode = false;
2782 * check if jalview GUI is being operated programmatically
2784 * @return inBatchMode
2786 public boolean isInBatchMode()
2792 * set flag if jalview GUI is being operated programmatically
2794 * @param inBatchMode
2796 public void setInBatchMode(boolean inBatchMode)
2798 this.inBatchMode = inBatchMode;
2802 * start service discovery and wait till it is done
2804 public void startServiceDiscovery()
2806 startServiceDiscovery(false);
2810 * start service discovery threads - blocking or non-blocking
2814 public void startServiceDiscovery(boolean blocking)
2816 startServiceDiscovery(blocking, false);
2820 * start service discovery threads
2823 * - false means call returns immediately
2824 * @param ignore_SHOW_JWS2_SERVICES_preference
2825 * - when true JABA services are discovered regardless of user's JWS2
2826 * discovery preference setting
2828 public void startServiceDiscovery(boolean blocking,
2829 boolean ignore_SHOW_JWS2_SERVICES_preference)
2831 boolean alive = true;
2832 Thread t0 = null, t1 = null, t2 = null;
2833 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2836 // todo: changesupport handlers need to be transferred
2837 if (discoverer == null)
2839 discoverer = new jalview.ws.jws1.Discoverer();
2840 // register PCS handler for desktop.
2841 discoverer.addPropertyChangeListener(changeSupport);
2843 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2844 // until we phase out completely
2845 (t0 = new Thread(discoverer)).start();
2848 if (ignore_SHOW_JWS2_SERVICES_preference
2849 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2851 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2852 .startDiscoverer(changeSupport);
2856 // TODO: do rest service discovery
2865 } catch (Exception e)
2868 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2869 || (t3 != null && t3.isAlive())
2870 || (t0 != null && t0.isAlive());
2876 * called to check if the service discovery process completed successfully.
2880 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2882 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2884 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2885 .getErrorMessages();
2888 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2890 if (serviceChangedDialog == null)
2892 // only run if we aren't already displaying one of these.
2893 addDialogThread(serviceChangedDialog = new Runnable()
2900 * JalviewDialog jd =new JalviewDialog() {
2902 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2904 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2906 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2908 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2910 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2911 * + " or mis-configured HTTP proxy settings.<br/>" +
2912 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2913 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2914 * true, true, "Web Service Configuration Problem", 450, 400);
2916 * jd.waitForInput();
2918 JvOptionPane.showConfirmDialog(Desktop.desktop,
2919 new JLabel("<html><table width=\"450\"><tr><td>"
2920 + ermsg + "</td></tr></table>"
2921 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2922 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2923 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2924 + " Tools->Preferences dialog box to change them.</p></html>"),
2925 "Web Service Configuration Problem",
2926 JvOptionPane.DEFAULT_OPTION,
2927 JvOptionPane.ERROR_MESSAGE);
2928 serviceChangedDialog = null;
2936 jalview.bin.Console.error(
2937 "Errors reported by JABA discovery service. Check web services preferences.\n"
2944 private Runnable serviceChangedDialog = null;
2947 * start a thread to open a URL in the configured browser. Pops up a warning
2948 * dialog to the user if there is an exception when calling out to the browser
2953 public static void showUrl(final String url)
2955 showUrl(url, Desktop.instance);
2959 * Like showUrl but allows progress handler to be specified
2963 * (null) or object implementing IProgressIndicator
2965 public static void showUrl(final String url,
2966 final IProgressIndicator progress)
2968 new Thread(new Runnable()
2975 if (progress != null)
2977 progress.setProgressBar(MessageManager
2978 .formatMessage("status.opening_params", new Object[]
2979 { url }), this.hashCode());
2981 jalview.util.BrowserLauncher.openURL(url);
2982 } catch (Exception ex)
2984 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2986 .getString("label.web_browser_not_found_unix"),
2987 MessageManager.getString("label.web_browser_not_found"),
2988 JvOptionPane.WARNING_MESSAGE);
2990 ex.printStackTrace();
2992 if (progress != null)
2994 progress.setProgressBar(null, this.hashCode());
3000 public static WsParamSetManager wsparamManager = null;
3002 public static ParamManager getUserParameterStore()
3004 if (wsparamManager == null)
3006 wsparamManager = new WsParamSetManager();
3008 return wsparamManager;
3012 * static hyperlink handler proxy method for use by Jalview's internal windows
3016 public static void hyperlinkUpdate(HyperlinkEvent e)
3018 if (e.getEventType() == EventType.ACTIVATED)
3023 url = e.getURL().toString();
3024 Desktop.showUrl(url);
3025 } catch (Exception x)
3030 .error("Couldn't handle string " + url + " as a URL.");
3032 // ignore any exceptions due to dud links.
3039 * single thread that handles display of dialogs to user.
3041 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3044 * flag indicating if dialogExecutor should try to acquire a permit
3046 private volatile boolean dialogPause = true;
3051 private java.util.concurrent.Semaphore block = new Semaphore(0);
3053 private static groovy.ui.Console groovyConsole;
3056 * add another dialog thread to the queue
3060 public void addDialogThread(final Runnable prompter)
3062 dialogExecutor.submit(new Runnable()
3072 } catch (InterruptedException x)
3076 if (instance == null)
3082 SwingUtilities.invokeAndWait(prompter);
3083 } catch (Exception q)
3085 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3092 public void startDialogQueue()
3094 // set the flag so we don't pause waiting for another permit and semaphore
3095 // the current task to begin
3096 dialogPause = false;
3101 * Outputs an image of the desktop to file in EPS format, after prompting the
3102 * user for choice of Text or Lineart character rendering (unless a preference
3103 * has been set). The file name is generated as
3106 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3110 protected void snapShotWindow_actionPerformed(ActionEvent e)
3112 // currently the menu option to do this is not shown
3115 int width = getWidth();
3116 int height = getHeight();
3118 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3119 ImageWriterI writer = new ImageWriterI()
3122 public void exportImage(Graphics g) throws Exception
3125 jalview.bin.Console.info("Successfully written snapshot to file "
3126 + of.getAbsolutePath());
3129 String title = "View of desktop";
3130 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3132 exporter.doExport(of, this, width, height, title);
3136 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3137 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3138 * and location last time the view was expanded (if any). However it does not
3139 * remember the split pane divider location - this is set to match the
3140 * 'exploding' frame.
3144 public void explodeViews(SplitFrame sf)
3146 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3147 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3148 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3150 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3152 int viewCount = topPanels.size();
3159 * Processing in reverse order works, forwards order leaves the first panels not
3160 * visible. I don't know why!
3162 for (int i = viewCount - 1; i >= 0; i--)
3165 * Make new top and bottom frames. These take over the respective AlignmentPanel
3166 * objects, including their AlignmentViewports, so the cdna/protein
3167 * relationships between the viewports is carried over to the new split frames.
3169 * explodedGeometry holds the (x, y) position of the previously exploded
3170 * SplitFrame, and the (width, height) of the AlignFrame component
3172 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3173 AlignFrame newTopFrame = new AlignFrame(topPanel);
3174 newTopFrame.setSize(oldTopFrame.getSize());
3175 newTopFrame.setVisible(true);
3176 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3177 .getExplodedGeometry();
3178 if (geometry != null)
3180 newTopFrame.setSize(geometry.getSize());
3183 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3184 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3185 newBottomFrame.setSize(oldBottomFrame.getSize());
3186 newBottomFrame.setVisible(true);
3187 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3188 .getExplodedGeometry();
3189 if (geometry != null)
3191 newBottomFrame.setSize(geometry.getSize());
3194 topPanel.av.setGatherViewsHere(false);
3195 bottomPanel.av.setGatherViewsHere(false);
3196 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3198 if (geometry != null)
3200 splitFrame.setLocation(geometry.getLocation());
3202 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3206 * Clear references to the panels (now relocated in the new SplitFrames) before
3207 * closing the old SplitFrame.
3210 bottomPanels.clear();
3215 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3216 * back into the given SplitFrame as additional views. Note that the gathered
3217 * frames may themselves have multiple views.
3221 public void gatherViews(GSplitFrame source)
3224 * special handling of explodedGeometry for a view within a SplitFrame: - it
3225 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3226 * height) of the AlignFrame component
3228 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3229 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3230 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3231 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3232 myBottomFrame.viewport
3233 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3234 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3235 myTopFrame.viewport.setGatherViewsHere(true);
3236 myBottomFrame.viewport.setGatherViewsHere(true);
3237 String topViewId = myTopFrame.viewport.getSequenceSetId();
3238 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3240 JInternalFrame[] frames = desktop.getAllFrames();
3241 for (JInternalFrame frame : frames)
3243 if (frame instanceof SplitFrame && frame != source)
3245 SplitFrame sf = (SplitFrame) frame;
3246 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3247 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3248 boolean gatherThis = false;
3249 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3251 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3252 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3253 if (topViewId.equals(topPanel.av.getSequenceSetId())
3254 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3257 topPanel.av.setGatherViewsHere(false);
3258 bottomPanel.av.setGatherViewsHere(false);
3259 topPanel.av.setExplodedGeometry(
3260 new Rectangle(sf.getLocation(), topFrame.getSize()));
3261 bottomPanel.av.setExplodedGeometry(
3262 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3263 myTopFrame.addAlignmentPanel(topPanel, false);
3264 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3270 topFrame.getAlignPanels().clear();
3271 bottomFrame.getAlignPanels().clear();
3278 * The dust settles...give focus to the tab we did this from.
3280 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3283 public static groovy.ui.Console getGroovyConsole()
3285 return groovyConsole;
3289 * handles the payload of a drag and drop event.
3291 * TODO refactor to desktop utilities class
3294 * - Data source strings extracted from the drop event
3296 * - protocol for each data source extracted from the drop event
3300 * - the payload from the drop event
3303 public static void transferFromDropTarget(List<Object> files,
3304 List<DataSourceType> protocols, DropTargetDropEvent evt,
3305 Transferable t) throws Exception
3308 DataFlavor uriListFlavor = new DataFlavor(
3309 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3312 urlFlavour = new DataFlavor(
3313 "application/x-java-url; class=java.net.URL");
3314 } catch (ClassNotFoundException cfe)
3316 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3320 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3325 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3326 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3327 // means url may be null.
3330 protocols.add(DataSourceType.URL);
3331 files.add(url.toString());
3332 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3333 + files.get(files.size() - 1));
3338 if (Platform.isAMacAndNotJS())
3341 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3344 } catch (Throwable ex)
3346 jalview.bin.Console.debug("URL drop handler failed.", ex);
3349 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3351 // Works on Windows and MacOSX
3352 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3353 for (Object file : (List) t
3354 .getTransferData(DataFlavor.javaFileListFlavor))
3357 protocols.add(DataSourceType.FILE);
3362 // Unix like behaviour
3363 boolean added = false;
3365 if (t.isDataFlavorSupported(uriListFlavor))
3367 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3368 // This is used by Unix drag system
3369 data = (String) t.getTransferData(uriListFlavor);
3373 // fallback to text: workaround - on OSX where there's a JVM bug
3375 .debug("standard URIListFlavor failed. Trying text");
3376 // try text fallback
3377 DataFlavor textDf = new DataFlavor(
3378 "text/plain;class=java.lang.String");
3379 if (t.isDataFlavorSupported(textDf))
3381 data = (String) t.getTransferData(textDf);
3384 jalview.bin.Console.debug("Plain text drop content returned "
3385 + (data == null ? "Null - failed" : data));
3390 while (protocols.size() < files.size())
3392 jalview.bin.Console.debug("Adding missing FILE protocol for "
3393 + files.get(protocols.size()));
3394 protocols.add(DataSourceType.FILE);
3396 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3397 data, "\r\n"); st.hasMoreTokens();)
3400 String s = st.nextToken();
3401 if (s.startsWith("#"))
3403 // the line is a comment (as per the RFC 2483)
3406 java.net.URI uri = new java.net.URI(s);
3407 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3409 protocols.add(DataSourceType.URL);
3410 files.add(uri.toString());
3414 // otherwise preserve old behaviour: catch all for file objects
3415 java.io.File file = new java.io.File(uri);
3416 protocols.add(DataSourceType.FILE);
3417 files.add(file.toString());
3422 if (jalview.bin.Console.isDebugEnabled())
3424 if (data == null || !added)
3427 if (t.getTransferDataFlavors() != null
3428 && t.getTransferDataFlavors().length > 0)
3430 jalview.bin.Console.debug(
3431 "Couldn't resolve drop data. Here are the supported flavors:");
3432 for (DataFlavor fl : t.getTransferDataFlavors())
3434 jalview.bin.Console.debug(
3435 "Supported transfer dataflavor: " + fl.toString());
3436 Object df = t.getTransferData(fl);
3439 jalview.bin.Console.debug("Retrieves: " + df);
3443 jalview.bin.Console.debug("Retrieved nothing");
3450 .debug("Couldn't resolve dataflavor for drop: "
3456 if (Platform.isWindowsAndNotJS())
3459 .debug("Scanning dropped content for Windows Link Files");
3461 // resolve any .lnk files in the file drop
3462 for (int f = 0; f < files.size(); f++)
3464 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3465 if (protocols.get(f).equals(DataSourceType.FILE)
3466 && (source.endsWith(".lnk") || source.endsWith(".url")
3467 || source.endsWith(".site")))
3471 Object obj = files.get(f);
3472 File lf = (obj instanceof File ? (File) obj
3473 : new File((String) obj));
3474 // process link file to get a URL
3475 jalview.bin.Console.debug("Found potential link file: " + lf);
3476 WindowsShortcut wscfile = new WindowsShortcut(lf);
3477 String fullname = wscfile.getRealFilename();
3478 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3479 files.set(f, fullname);
3480 jalview.bin.Console.debug("Parsed real filename " + fullname
3481 + " to extract protocol: " + protocols.get(f));
3482 } catch (Exception ex)
3484 jalview.bin.Console.error(
3485 "Couldn't parse " + files.get(f) + " as a link file.",
3494 * Sets the Preferences property for experimental features to True or False
3495 * depending on the state of the controlling menu item
3498 protected void showExperimental_actionPerformed(boolean selected)
3500 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3504 * Answers a (possibly empty) list of any structure viewer frames (currently
3505 * for either Jmol or Chimera) which are currently open. This may optionally
3506 * be restricted to viewers of a specified class, or viewers linked to a
3507 * specified alignment panel.
3510 * if not null, only return viewers linked to this panel
3511 * @param structureViewerClass
3512 * if not null, only return viewers of this class
3515 public List<StructureViewerBase> getStructureViewers(
3516 AlignmentPanel apanel,
3517 Class<? extends StructureViewerBase> structureViewerClass)
3519 List<StructureViewerBase> result = new ArrayList<>();
3520 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3522 for (JInternalFrame frame : frames)
3524 if (frame instanceof StructureViewerBase)
3526 if (structureViewerClass == null
3527 || structureViewerClass.isInstance(frame))
3530 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3532 result.add((StructureViewerBase) frame);
3540 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3542 private static boolean debugScaleMessageDone = false;
3544 public static void debugScaleMessage(Graphics g)
3546 if (debugScaleMessageDone)
3550 // output used by tests to check HiDPI scaling settings in action
3553 Graphics2D gg = (Graphics2D) g;
3556 AffineTransform t = gg.getTransform();
3557 double scaleX = t.getScaleX();
3558 double scaleY = t.getScaleY();
3559 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3560 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3561 debugScaleMessageDone = true;
3565 jalview.bin.Console.debug("Desktop graphics null");
3567 } catch (Exception e)
3569 jalview.bin.Console.debug(Cache.getStackTraceString(e));