2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.Callable;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Semaphore;
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.Box;
76 import javax.swing.BoxLayout;
77 import javax.swing.DefaultDesktopManager;
78 import javax.swing.DesktopManager;
79 import javax.swing.InputMap;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JComboBox;
83 import javax.swing.JComponent;
84 import javax.swing.JDesktopPane;
85 import javax.swing.JFrame;
86 import javax.swing.JInternalFrame;
87 import javax.swing.JLabel;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JProgressBar;
92 import javax.swing.JTextField;
93 import javax.swing.KeyStroke;
94 import javax.swing.SwingUtilities;
95 import javax.swing.WindowConstants;
96 import javax.swing.event.HyperlinkEvent;
97 import javax.swing.event.HyperlinkEvent.EventType;
98 import javax.swing.event.InternalFrameAdapter;
99 import javax.swing.event.InternalFrameEvent;
101 import org.stackoverflowusers.file.WindowsShortcut;
103 import jalview.api.AlignViewportI;
104 import jalview.api.AlignmentViewPanel;
105 import jalview.api.structures.JalviewStructureDisplayI;
106 import jalview.bin.Cache;
107 import jalview.bin.Jalview;
108 import jalview.datamodel.Alignment;
109 import jalview.datamodel.HiddenColumns;
110 import jalview.datamodel.Sequence;
111 import jalview.datamodel.SequenceI;
112 import jalview.gui.ImageExporter.ImageWriterI;
113 import jalview.gui.QuitHandler.QResponse;
114 import jalview.io.BackupFiles;
115 import jalview.io.DataSourceType;
116 import jalview.io.FileFormat;
117 import jalview.io.FileFormatException;
118 import jalview.io.FileFormatI;
119 import jalview.io.FileFormats;
120 import jalview.io.FileLoader;
121 import jalview.io.FormatAdapter;
122 import jalview.io.IdentifyFile;
123 import jalview.io.JalviewFileChooser;
124 import jalview.io.JalviewFileView;
125 import jalview.jbgui.GSplitFrame;
126 import jalview.jbgui.GStructureViewer;
127 import jalview.project.Jalview2XML;
128 import jalview.structure.StructureSelectionManager;
129 import jalview.urls.IdOrgSettings;
130 import jalview.util.BrowserLauncher;
131 import jalview.util.ChannelProperties;
132 import jalview.util.ImageMaker.TYPE;
133 import jalview.util.LaunchUtils;
134 import jalview.util.MessageManager;
135 import jalview.util.Platform;
136 import jalview.util.ShortcutKeyMaskExWrapper;
137 import jalview.util.UrlConstants;
138 import jalview.viewmodel.AlignmentViewport;
139 import jalview.ws.params.ParamManager;
140 import jalview.ws.utils.UrlDownloadClient;
147 * @version $Revision: 1.155 $
149 public class Desktop extends jalview.jbgui.GDesktop
150 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
151 jalview.api.StructureSelectionManagerProvider
153 private static final String CITATION;
156 URL bg_logo_url = ChannelProperties.getImageURL(
157 "bg_logo." + String.valueOf(SplashScreen.logoSize));
158 URL uod_logo_url = ChannelProperties.getImageURL(
159 "uod_banner." + String.valueOf(SplashScreen.logoSize));
160 boolean logo = (bg_logo_url != null || uod_logo_url != null);
161 StringBuilder sb = new StringBuilder();
163 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
168 sb.append(bg_logo_url == null ? ""
169 : "<img alt=\"Barton Group logo\" src=\""
170 + bg_logo_url.toString() + "\">");
171 sb.append(uod_logo_url == null ? ""
172 : " <img alt=\"University of Dundee shield\" src=\""
173 + uod_logo_url.toString() + "\">");
175 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
176 sb.append("<br><br>If you use Jalview, please cite:"
177 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
178 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
179 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
180 CITATION = sb.toString();
183 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
185 private static int DEFAULT_MIN_WIDTH = 300;
187 private static int DEFAULT_MIN_HEIGHT = 250;
189 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
191 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
193 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
195 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
197 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
199 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
201 public static void setLiveDragMode(boolean b)
203 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
204 : JDesktopPane.OUTLINE_DRAG_MODE;
206 desktop.setDragMode(DRAG_MODE);
209 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
211 public static boolean nosplash = false;
214 * news reader - null if it was never started.
216 private BlogReader jvnews = null;
218 private File projectFile;
222 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
224 public void addJalviewPropertyChangeListener(
225 PropertyChangeListener listener)
227 changeSupport.addJalviewPropertyChangeListener(listener);
231 * @param propertyName
233 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
234 * java.beans.PropertyChangeListener)
236 public void addJalviewPropertyChangeListener(String propertyName,
237 PropertyChangeListener listener)
239 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
243 * @param propertyName
245 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
246 * java.beans.PropertyChangeListener)
248 public void removeJalviewPropertyChangeListener(String propertyName,
249 PropertyChangeListener listener)
251 changeSupport.removeJalviewPropertyChangeListener(propertyName,
255 /** Singleton Desktop instance */
256 public static Desktop instance;
258 public static MyDesktopPane desktop;
260 public static MyDesktopPane getDesktop()
262 // BH 2018 could use currentThread() here as a reference to a
263 // Hashtable<Thread, MyDesktopPane> in JavaScript
267 static int openFrameCount = 0;
269 static final int xOffset = 30;
271 static final int yOffset = 30;
273 public static jalview.ws.jws1.Discoverer discoverer;
275 public static Object[] jalviewClipboard;
277 public static boolean internalCopy = false;
279 static int fileLoadingCount = 0;
281 class MyDesktopManager implements DesktopManager
284 private DesktopManager delegate;
286 public MyDesktopManager(DesktopManager delegate)
288 this.delegate = delegate;
292 public void activateFrame(JInternalFrame f)
296 delegate.activateFrame(f);
297 } catch (NullPointerException npe)
299 Point p = getMousePosition();
300 instance.showPasteMenu(p.x, p.y);
305 public void beginDraggingFrame(JComponent f)
307 delegate.beginDraggingFrame(f);
311 public void beginResizingFrame(JComponent f, int direction)
313 delegate.beginResizingFrame(f, direction);
317 public void closeFrame(JInternalFrame f)
319 delegate.closeFrame(f);
323 public void deactivateFrame(JInternalFrame f)
325 delegate.deactivateFrame(f);
329 public void deiconifyFrame(JInternalFrame f)
331 delegate.deiconifyFrame(f);
335 public void dragFrame(JComponent f, int newX, int newY)
341 delegate.dragFrame(f, newX, newY);
345 public void endDraggingFrame(JComponent f)
347 delegate.endDraggingFrame(f);
352 public void endResizingFrame(JComponent f)
354 delegate.endResizingFrame(f);
359 public void iconifyFrame(JInternalFrame f)
361 delegate.iconifyFrame(f);
365 public void maximizeFrame(JInternalFrame f)
367 delegate.maximizeFrame(f);
371 public void minimizeFrame(JInternalFrame f)
373 delegate.minimizeFrame(f);
377 public void openFrame(JInternalFrame f)
379 delegate.openFrame(f);
383 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
390 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
394 public void setBoundsForFrame(JComponent f, int newX, int newY,
395 int newWidth, int newHeight)
397 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
400 // All other methods, simply delegate
405 * Creates a new Desktop object.
411 * A note to implementors. It is ESSENTIAL that any activities that might
412 * block are spawned off as threads rather than waited for during this
417 doConfigureStructurePrefs();
418 setTitle(ChannelProperties.getProperty("app_name") + " "
419 + Cache.getProperty("VERSION"));
422 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
423 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
424 * officially documented or guaranteed to exist, so we access it via
425 * reflection. There appear to be unfathomable criteria about what this
426 * string can contain, and it if doesn't meet those criteria then "java"
427 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
428 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
429 * not. The reflection access may generate a warning: WARNING: An illegal
430 * reflective access operation has occurred WARNING: Illegal reflective
431 * access by jalview.gui.Desktop () to field
432 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
434 if (Platform.isLinux())
436 if (LaunchUtils.getJavaVersion() >= 11)
438 jalview.bin.Console.info(
439 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
443 Toolkit xToolkit = Toolkit.getDefaultToolkit();
444 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
445 Field awtAppClassNameField = null;
447 if (Arrays.stream(declaredFields)
448 .anyMatch(f -> f.getName().equals("awtAppClassName")))
450 awtAppClassNameField = xToolkit.getClass()
451 .getDeclaredField("awtAppClassName");
454 String title = ChannelProperties.getProperty("app_name");
455 if (awtAppClassNameField != null)
457 awtAppClassNameField.setAccessible(true);
458 awtAppClassNameField.set(xToolkit, title);
462 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
464 } catch (Exception e)
466 jalview.bin.Console.debug("Error setting awtAppClassName");
467 jalview.bin.Console.trace(Cache.getStackTraceString(e));
471 setIconImages(ChannelProperties.getIconList());
473 // override quit handling when GUI OS close [X] button pressed
474 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
475 addWindowListener(new WindowAdapter()
478 public void windowClosing(WindowEvent ev)
480 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
484 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
486 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
487 desktop = new MyDesktopPane(selmemusage);
489 showMemusage.setSelected(selmemusage);
490 desktop.setBackground(Color.white);
492 getContentPane().setLayout(new BorderLayout());
493 // alternate config - have scrollbars - see notes in JAL-153
494 // JScrollPane sp = new JScrollPane();
495 // sp.getViewport().setView(desktop);
496 // getContentPane().add(sp, BorderLayout.CENTER);
498 // BH 2018 - just an experiment to try unclipped JInternalFrames.
501 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
504 getContentPane().add(desktop, BorderLayout.CENTER);
505 desktop.setDragMode(DRAG_MODE);
507 // This line prevents Windows Look&Feel resizing all new windows to maximum
508 // if previous window was maximised
509 desktop.setDesktopManager(new MyDesktopManager(
510 Platform.isJS() ? desktop.getDesktopManager()
511 : new DefaultDesktopManager()));
513 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
514 : Platform.isAMacAndNotJS()
515 ? new AquaInternalFrameManager(
516 desktop.getDesktopManager())
517 : desktop.getDesktopManager())));
520 Rectangle dims = getLastKnownDimensions("");
527 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
528 int xPos = Math.max(5, (screenSize.width - 900) / 2);
529 int yPos = Math.max(5, (screenSize.height - 650) / 2);
530 setBounds(xPos, yPos, 900, 650);
533 if (!Platform.isJS())
540 jconsole = new Console(this, showjconsole);
541 jconsole.setHeader(Cache.getVersionDetailsForConsole());
542 showConsole(showjconsole);
544 showNews.setVisible(false);
546 experimentalFeatures.setSelected(showExperimental());
548 getIdentifiersOrgData();
552 // Spawn a thread that shows the splashscreen
555 SwingUtilities.invokeLater(new Runnable()
560 new SplashScreen(true);
565 // Thread off a new instance of the file chooser - this reduces the time
567 // takes to open it later on.
568 new Thread(new Runnable()
573 jalview.bin.Console.debug("Filechooser init thread started.");
574 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
575 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
577 jalview.bin.Console.debug("Filechooser init thread finished.");
580 // Add the service change listener
581 changeSupport.addJalviewPropertyChangeListener("services",
582 new PropertyChangeListener()
586 public void propertyChange(PropertyChangeEvent evt)
589 .debug("Firing service changed event for "
590 + evt.getNewValue());
591 JalviewServicesChanged(evt);
596 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
599 this.addMouseListener(ma = new MouseAdapter()
602 public void mousePressed(MouseEvent evt)
604 if (evt.isPopupTrigger()) // Mac
606 showPasteMenu(evt.getX(), evt.getY());
611 public void mouseReleased(MouseEvent evt)
613 if (evt.isPopupTrigger()) // Windows
615 showPasteMenu(evt.getX(), evt.getY());
619 desktop.addMouseListener(ma);
623 * Answers true if user preferences to enable experimental features is True
628 public boolean showExperimental()
630 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
631 Boolean.FALSE.toString());
632 return Boolean.valueOf(experimental).booleanValue();
635 public void doConfigureStructurePrefs()
637 // configure services
638 StructureSelectionManager ssm = StructureSelectionManager
639 .getStructureSelectionManager(this);
640 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
642 ssm.setAddTempFacAnnot(
643 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
644 ssm.setProcessSecondaryStructure(
645 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
646 // JAL-3915 - RNAView is no longer an option so this has no effect
647 ssm.setSecStructServices(
648 Cache.getDefault(Preferences.USE_RNAVIEW, false));
652 ssm.setAddTempFacAnnot(false);
653 ssm.setProcessSecondaryStructure(false);
654 ssm.setSecStructServices(false);
658 public void checkForNews()
660 final Desktop me = this;
661 // Thread off the news reader, in case there are connection problems.
662 new Thread(new Runnable()
667 jalview.bin.Console.debug("Starting news thread.");
668 jvnews = new BlogReader(me);
669 showNews.setVisible(true);
670 jalview.bin.Console.debug("Completed news thread.");
675 public void getIdentifiersOrgData()
677 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
678 {// Thread off the identifiers fetcher
679 new Thread(new Runnable()
685 .debug("Downloading data from identifiers.org");
688 UrlDownloadClient.download(IdOrgSettings.getUrl(),
689 IdOrgSettings.getDownloadLocation());
690 } catch (IOException e)
693 .debug("Exception downloading identifiers.org data"
703 protected void showNews_actionPerformed(ActionEvent e)
705 showNews(showNews.isSelected());
708 void showNews(boolean visible)
710 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
711 showNews.setSelected(visible);
712 if (visible && !jvnews.isVisible())
714 new Thread(new Runnable()
719 long now = System.currentTimeMillis();
720 Desktop.instance.setProgressBar(
721 MessageManager.getString("status.refreshing_news"), now);
722 jvnews.refreshNews();
723 Desktop.instance.setProgressBar(null, now);
731 * recover the last known dimensions for a jalview window
734 * - empty string is desktop, all other windows have unique prefix
735 * @return null or last known dimensions scaled to current geometry (if last
736 * window geom was known)
738 Rectangle getLastKnownDimensions(String windowName)
740 // TODO: lock aspect ratio for scaling desktop Bug #0058199
741 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
742 String x = Cache.getProperty(windowName + "SCREEN_X");
743 String y = Cache.getProperty(windowName + "SCREEN_Y");
744 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
745 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
746 if ((x != null) && (y != null) && (width != null) && (height != null))
748 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
749 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
750 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
752 // attempt #1 - try to cope with change in screen geometry - this
753 // version doesn't preserve original jv aspect ratio.
754 // take ratio of current screen size vs original screen size.
755 double sw = ((1f * screenSize.width) / (1f * Integer
756 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
757 double sh = ((1f * screenSize.height) / (1f * Integer
758 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
759 // rescale the bounds depending upon the current screen geometry.
760 ix = (int) (ix * sw);
761 iw = (int) (iw * sw);
762 iy = (int) (iy * sh);
763 ih = (int) (ih * sh);
764 while (ix >= screenSize.width)
766 jalview.bin.Console.debug(
767 "Window geometry location recall error: shifting horizontal to within screenbounds.");
768 ix -= screenSize.width;
770 while (iy >= screenSize.height)
772 jalview.bin.Console.debug(
773 "Window geometry location recall error: shifting vertical to within screenbounds.");
774 iy -= screenSize.height;
776 jalview.bin.Console.debug(
777 "Got last known dimensions for " + windowName + ": x:" + ix
778 + " y:" + iy + " width:" + iw + " height:" + ih);
780 // return dimensions for new instance
781 return new Rectangle(ix, iy, iw, ih);
786 void showPasteMenu(int x, int y)
788 JPopupMenu popup = new JPopupMenu();
789 JMenuItem item = new JMenuItem(
790 MessageManager.getString("label.paste_new_window"));
791 item.addActionListener(new ActionListener()
794 public void actionPerformed(ActionEvent evt)
801 popup.show(this, x, y);
806 // quick patch for JAL-4150 - needs some more work and test coverage
807 // TODO - unify below and AlignFrame.paste()
808 // TODO - write tests and fix AlignFrame.paste() which doesn't track if
809 // clipboard has come from a different alignment window than the one where
810 // paste has been called! JAL-4151
812 if (Desktop.jalviewClipboard != null)
814 // The clipboard was filled from within Jalview, we must use the
816 // And dataset from the copied alignment
817 SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
818 // be doubly sure that we create *new* sequence objects.
819 SequenceI[] sequences = new SequenceI[newseq.length];
820 for (int i = 0; i < newseq.length; i++)
822 sequences[i] = new Sequence(newseq[i]);
824 Alignment alignment = new Alignment(sequences);
825 // dataset is inherited
826 alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
827 AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH,
828 AlignFrame.DEFAULT_HEIGHT);
829 String newtitle = new String("Copied sequences");
831 if (Desktop.jalviewClipboard[2] != null)
833 HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
834 af.viewport.setHiddenColumns(hc);
837 Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
838 AlignFrame.DEFAULT_HEIGHT);
843 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
844 Transferable contents = c.getContents(this);
846 if (contents != null)
848 String file = (String) contents
849 .getTransferData(DataFlavor.stringFlavor);
851 FileFormatI format = new IdentifyFile().identify(file,
852 DataSourceType.PASTE);
854 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
857 } catch (Exception ex)
860 "Unable to paste alignment from system clipboard:\n" + ex);
866 * Adds and opens the given frame to the desktop
877 public static synchronized void addInternalFrame(
878 final JInternalFrame frame, String title, int w, int h)
880 addInternalFrame(frame, title, true, w, h, true, false);
884 * Add an internal frame to the Jalview desktop
891 * When true, display frame immediately, otherwise, caller must call
892 * setVisible themselves.
898 public static synchronized void addInternalFrame(
899 final JInternalFrame frame, String title, boolean makeVisible,
902 addInternalFrame(frame, title, makeVisible, w, h, true, false);
906 * Add an internal frame to the Jalview desktop and make it visible
919 public static synchronized void addInternalFrame(
920 final JInternalFrame frame, String title, int w, int h,
923 addInternalFrame(frame, title, true, w, h, resizable, false);
927 * Add an internal frame to the Jalview desktop
934 * When true, display frame immediately, otherwise, caller must call
935 * setVisible themselves.
942 * @param ignoreMinSize
943 * Do not set the default minimum size for frame
945 public static synchronized void addInternalFrame(
946 final JInternalFrame frame, String title, boolean makeVisible,
947 int w, int h, boolean resizable, boolean ignoreMinSize)
950 // TODO: allow callers to determine X and Y position of frame (eg. via
952 // TODO: consider fixing method to update entries in the window submenu with
953 // the current window title
955 frame.setTitle(title);
956 if (frame.getWidth() < 1 || frame.getHeight() < 1)
960 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
961 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
962 // IF JALVIEW IS RUNNING HEADLESS
963 // ///////////////////////////////////////////////
964 if (instance == null || (System.getProperty("java.awt.headless") != null
965 && System.getProperty("java.awt.headless").equals("true")))
974 frame.setMinimumSize(
975 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
977 // Set default dimension for Alignment Frame window.
978 // The Alignment Frame window could be added from a number of places,
980 // I did this here in order not to miss out on any Alignment frame.
981 if (frame instanceof AlignFrame)
983 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
984 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
988 frame.setVisible(makeVisible);
989 frame.setClosable(true);
990 frame.setResizable(resizable);
991 frame.setMaximizable(resizable);
992 frame.setIconifiable(resizable);
993 frame.setOpaque(Platform.isJS());
995 if (frame.getX() < 1 && frame.getY() < 1)
997 frame.setLocation(xOffset * openFrameCount,
998 yOffset * ((openFrameCount - 1) % 10) + yOffset);
1002 * add an entry for the new frame in the Window menu (and remove it when the
1005 final JMenuItem menuItem = new JMenuItem(title);
1006 frame.addInternalFrameListener(new InternalFrameAdapter()
1009 public void internalFrameActivated(InternalFrameEvent evt)
1011 JInternalFrame itf = desktop.getSelectedFrame();
1014 if (itf instanceof AlignFrame)
1016 Jalview.setCurrentAlignFrame((AlignFrame) itf);
1023 public void internalFrameClosed(InternalFrameEvent evt)
1025 PaintRefresher.RemoveComponent(frame);
1028 * defensive check to prevent frames being added half off the window
1030 if (openFrameCount > 0)
1036 * ensure no reference to alignFrame retained by menu item listener
1038 if (menuItem.getActionListeners().length > 0)
1040 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1042 windowMenu.remove(menuItem);
1046 menuItem.addActionListener(new ActionListener()
1049 public void actionPerformed(ActionEvent e)
1053 frame.setSelected(true);
1054 frame.setIcon(false);
1055 } catch (java.beans.PropertyVetoException ex)
1062 setKeyBindings(frame);
1066 windowMenu.add(menuItem);
1071 frame.setSelected(true);
1072 frame.requestFocus();
1073 } catch (java.beans.PropertyVetoException ve)
1075 } catch (java.lang.ClassCastException cex)
1077 jalview.bin.Console.warn(
1078 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1084 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1089 private static void setKeyBindings(JInternalFrame frame)
1091 @SuppressWarnings("serial")
1092 final Action closeAction = new AbstractAction()
1095 public void actionPerformed(ActionEvent e)
1102 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1104 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1105 InputEvent.CTRL_DOWN_MASK);
1106 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1107 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1109 InputMap inputMap = frame
1110 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1111 String ctrlW = ctrlWKey.toString();
1112 inputMap.put(ctrlWKey, ctrlW);
1113 inputMap.put(cmdWKey, ctrlW);
1115 ActionMap actionMap = frame.getActionMap();
1116 actionMap.put(ctrlW, closeAction);
1120 public void lostOwnership(Clipboard clipboard, Transferable contents)
1124 Desktop.jalviewClipboard = null;
1127 internalCopy = false;
1131 public void dragEnter(DropTargetDragEvent evt)
1136 public void dragExit(DropTargetEvent evt)
1141 public void dragOver(DropTargetDragEvent evt)
1146 public void dropActionChanged(DropTargetDragEvent evt)
1157 public void drop(DropTargetDropEvent evt)
1159 boolean success = true;
1160 // JAL-1552 - acceptDrop required before getTransferable call for
1161 // Java's Transferable for native dnd
1162 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1163 Transferable t = evt.getTransferable();
1164 List<Object> files = new ArrayList<>();
1165 List<DataSourceType> protocols = new ArrayList<>();
1169 Desktop.transferFromDropTarget(files, protocols, evt, t);
1170 } catch (Exception e)
1172 e.printStackTrace();
1180 for (int i = 0; i < files.size(); i++)
1182 // BH 2018 File or String
1183 Object file = files.get(i);
1184 String fileName = file.toString();
1185 DataSourceType protocol = (protocols == null)
1186 ? DataSourceType.FILE
1188 FileFormatI format = null;
1190 if (fileName.endsWith(".jar"))
1192 format = FileFormat.Jalview;
1197 format = new IdentifyFile().identify(file, protocol);
1199 if (file instanceof File)
1201 Platform.cacheFileData((File) file);
1203 new FileLoader().LoadFile(null, file, protocol, format);
1206 } catch (Exception ex)
1211 evt.dropComplete(success); // need this to ensure input focus is properly
1212 // transfered to any new windows created
1222 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1224 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1225 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1226 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1227 BackupFiles.getEnabled());
1229 chooser.setFileView(new JalviewFileView());
1230 chooser.setDialogTitle(
1231 MessageManager.getString("label.open_local_file"));
1232 chooser.setToolTipText(MessageManager.getString("action.open"));
1234 chooser.setResponseHandler(0, () -> {
1235 File selectedFile = chooser.getSelectedFile();
1236 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1238 FileFormatI format = chooser.getSelectedFormat();
1241 * Call IdentifyFile to verify the file contains what its extension implies.
1242 * Skip this step for dynamically added file formats, because IdentifyFile does
1243 * not know how to recognise them.
1245 if (FileFormats.getInstance().isIdentifiable(format))
1249 format = new IdentifyFile().identify(selectedFile,
1250 DataSourceType.FILE);
1251 } catch (FileFormatException e)
1253 // format = null; //??
1257 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1261 chooser.showOpenDialog(this);
1265 * Shows a dialog for input of a URL at which to retrieve alignment data
1270 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1272 // This construct allows us to have a wider textfield
1274 JLabel label = new JLabel(
1275 MessageManager.getString("label.input_file_url"));
1277 JPanel panel = new JPanel(new GridLayout(2, 1));
1281 * the URL to fetch is input in Java: an editable combobox with history JS:
1282 * (pending JAL-3038) a plain text field
1285 String urlBase = "https://www.";
1286 if (Platform.isJS())
1288 history = new JTextField(urlBase, 35);
1297 JComboBox<String> asCombo = new JComboBox<>();
1298 asCombo.setPreferredSize(new Dimension(400, 20));
1299 asCombo.setEditable(true);
1300 asCombo.addItem(urlBase);
1301 String historyItems = Cache.getProperty("RECENT_URL");
1302 if (historyItems != null)
1304 for (String token : historyItems.split("\\t"))
1306 asCombo.addItem(token);
1313 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1314 MessageManager.getString("action.cancel") };
1315 Callable<Void> action = () -> {
1316 @SuppressWarnings("unchecked")
1317 String url = (history instanceof JTextField
1318 ? ((JTextField) history).getText()
1319 : ((JComboBox<String>) history).getEditor().getItem()
1320 .toString().trim());
1322 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1324 if (viewport != null)
1326 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1327 FileFormat.Jalview);
1331 new FileLoader().LoadFile(url, DataSourceType.URL,
1332 FileFormat.Jalview);
1337 FileFormatI format = null;
1340 format = new IdentifyFile().identify(url, DataSourceType.URL);
1341 } catch (FileFormatException e)
1343 // TODO revise error handling, distinguish between
1344 // URL not found and response not valid
1349 String msg = MessageManager.formatMessage("label.couldnt_locate",
1351 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1352 MessageManager.getString("label.url_not_found"),
1353 JvOptionPane.WARNING_MESSAGE);
1355 return null; // Void
1358 if (viewport != null)
1360 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1365 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1368 return null; // Void
1370 String dialogOption = MessageManager
1371 .getString("label.input_alignment_from_url");
1372 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1373 .showInternalDialog(panel, dialogOption,
1374 JvOptionPane.YES_NO_CANCEL_OPTION,
1375 JvOptionPane.PLAIN_MESSAGE, null, options,
1376 MessageManager.getString("action.ok"));
1380 * Opens the CutAndPaste window for the user to paste an alignment in to
1383 * - if not null, the pasted alignment is added to the current
1384 * alignment; if null, to a new alignment window
1387 public void inputTextboxMenuItem_actionPerformed(
1388 AlignmentViewPanel viewPanel)
1390 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1391 cap.setForInput(viewPanel);
1392 Desktop.addInternalFrame(cap,
1393 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1398 * Check with user and saving files before actually quitting
1400 public void desktopQuit()
1402 desktopQuit(true, false);
1405 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1407 final Callable<Void> doDesktopQuit = () -> {
1408 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1409 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1410 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1411 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1412 getBounds().y, getWidth(), getHeight()));
1414 if (jconsole != null)
1416 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1417 jconsole.stopConsole();
1422 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1425 // Frames should all close automatically. Keeping external
1426 // viewers open should already be decided by user.
1427 closeAll_actionPerformed(null);
1429 // check for aborted quit
1430 if (QuitHandler.quitCancelled())
1432 jalview.bin.Console.debug("Desktop aborting quit");
1436 if (dialogExecutor != null)
1438 dialogExecutor.shutdownNow();
1441 if (groovyConsole != null)
1443 // suppress a possible repeat prompt to save script
1444 groovyConsole.setDirty(false);
1445 groovyConsole.exit();
1448 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1450 // note that shutdown hook will not be run
1451 jalview.bin.Console.debug("Force Quit selected by user");
1452 Runtime.getRuntime().halt(0);
1455 jalview.bin.Console.debug("Quit selected by user");
1458 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1459 // instance.dispose();
1463 return null; // Void
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).
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();
1965 chooser.showOpenDialog(this);
1969 public void inputSequence_actionPerformed(ActionEvent e)
1971 new SequenceFetcher(this);
1974 JPanel progressPanel;
1976 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1978 public void startLoading(final Object fileName)
1980 if (fileLoadingCount == 0)
1982 fileLoadingPanels.add(addProgressPanel(MessageManager
1983 .formatMessage("label.loading_file", new Object[]
1989 private JPanel addProgressPanel(String string)
1991 if (progressPanel == null)
1993 progressPanel = new JPanel(new GridLayout(1, 1));
1994 totalProgressCount = 0;
1995 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1997 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1998 JProgressBar progressBar = new JProgressBar();
1999 progressBar.setIndeterminate(true);
2001 thisprogress.add(new JLabel(string), BorderLayout.WEST);
2003 thisprogress.add(progressBar, BorderLayout.CENTER);
2004 progressPanel.add(thisprogress);
2005 ((GridLayout) progressPanel.getLayout()).setRows(
2006 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
2007 ++totalProgressCount;
2008 instance.validate();
2009 return thisprogress;
2012 int totalProgressCount = 0;
2014 private void removeProgressPanel(JPanel progbar)
2016 if (progressPanel != null)
2018 synchronized (progressPanel)
2020 progressPanel.remove(progbar);
2021 GridLayout gl = (GridLayout) progressPanel.getLayout();
2022 gl.setRows(gl.getRows() - 1);
2023 if (--totalProgressCount < 1)
2025 this.getContentPane().remove(progressPanel);
2026 progressPanel = null;
2033 public void stopLoading()
2036 if (fileLoadingCount < 1)
2038 while (fileLoadingPanels.size() > 0)
2040 removeProgressPanel(fileLoadingPanels.remove(0));
2042 fileLoadingPanels.clear();
2043 fileLoadingCount = 0;
2048 public static int getViewCount(String alignmentId)
2050 AlignmentViewport[] aps = getViewports(alignmentId);
2051 return (aps == null) ? 0 : aps.length;
2056 * @param alignmentId
2057 * - if null, all sets are returned
2058 * @return all AlignmentPanels concerning the alignmentId sequence set
2060 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2062 if (Desktop.desktop == null)
2064 // no frames created and in headless mode
2065 // TODO: verify that frames are recoverable when in headless mode
2068 List<AlignmentPanel> aps = new ArrayList<>();
2069 AlignFrame[] frames = getAlignFrames();
2074 for (AlignFrame af : frames)
2076 for (AlignmentPanel ap : af.alignPanels)
2078 if (alignmentId == null
2079 || alignmentId.equals(ap.av.getSequenceSetId()))
2085 if (aps.size() == 0)
2089 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2094 * get all the viewports on an alignment.
2096 * @param sequenceSetId
2097 * unique alignment id (may be null - all viewports returned in that
2099 * @return all viewports on the alignment bound to sequenceSetId
2101 public static AlignmentViewport[] getViewports(String sequenceSetId)
2103 List<AlignmentViewport> viewp = new ArrayList<>();
2104 if (desktop != null)
2106 AlignFrame[] frames = Desktop.getAlignFrames();
2108 for (AlignFrame afr : frames)
2110 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2111 .equals(sequenceSetId))
2113 if (afr.alignPanels != null)
2115 for (AlignmentPanel ap : afr.alignPanels)
2117 if (sequenceSetId == null
2118 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2126 viewp.add(afr.getViewport());
2130 if (viewp.size() > 0)
2132 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2139 * Explode the views in the given frame into separate AlignFrame
2143 public static void explodeViews(AlignFrame af)
2145 int size = af.alignPanels.size();
2151 // FIXME: ideally should use UI interface API
2152 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2153 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2154 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2155 for (int i = 0; i < size; i++)
2157 AlignmentPanel ap = af.alignPanels.get(i);
2159 AlignFrame newaf = new AlignFrame(ap);
2161 // transfer reference for existing feature settings to new alignFrame
2162 if (ap == af.alignPanel)
2164 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2166 newaf.featureSettings = viewFeatureSettings;
2168 newaf.setFeatureSettingsGeometry(fsBounds);
2172 * Restore the view's last exploded frame geometry if known. Multiple views from
2173 * one exploded frame share and restore the same (frame) position and size.
2175 Rectangle geometry = ap.av.getExplodedGeometry();
2176 if (geometry != null)
2178 newaf.setBounds(geometry);
2181 ap.av.setGatherViewsHere(false);
2183 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2184 AlignFrame.DEFAULT_HEIGHT);
2185 // and materialise a new feature settings dialog instance for the new
2187 // (closes the old as if 'OK' was pressed)
2188 if (ap == af.alignPanel && newaf.featureSettings != null
2189 && newaf.featureSettings.isOpen()
2190 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2192 newaf.showFeatureSettingsUI();
2196 af.featureSettings = null;
2197 af.alignPanels.clear();
2198 af.closeMenuItem_actionPerformed(true);
2203 * Gather expanded views (separate AlignFrame's) with the same sequence set
2204 * identifier back in to this frame as additional views, and close the
2205 * expanded views. Note the expanded frames may themselves have multiple
2206 * views. We take the lot.
2210 public void gatherViews(AlignFrame source)
2212 source.viewport.setGatherViewsHere(true);
2213 source.viewport.setExplodedGeometry(source.getBounds());
2214 JInternalFrame[] frames = desktop.getAllFrames();
2215 String viewId = source.viewport.getSequenceSetId();
2216 for (int t = 0; t < frames.length; t++)
2218 if (frames[t] instanceof AlignFrame && frames[t] != source)
2220 AlignFrame af = (AlignFrame) frames[t];
2221 boolean gatherThis = false;
2222 for (int a = 0; a < af.alignPanels.size(); a++)
2224 AlignmentPanel ap = af.alignPanels.get(a);
2225 if (viewId.equals(ap.av.getSequenceSetId()))
2228 ap.av.setGatherViewsHere(false);
2229 ap.av.setExplodedGeometry(af.getBounds());
2230 source.addAlignmentPanel(ap, false);
2236 if (af.featureSettings != null && af.featureSettings.isOpen())
2238 if (source.featureSettings == null)
2240 // preserve the feature settings geometry for this frame
2241 source.featureSettings = af.featureSettings;
2242 source.setFeatureSettingsGeometry(
2243 af.getFeatureSettingsGeometry());
2247 // close it and forget
2248 af.featureSettings.close();
2251 af.alignPanels.clear();
2252 af.closeMenuItem_actionPerformed(true);
2257 // refresh the feature setting UI for the source frame if it exists
2258 if (source.featureSettings != null && source.featureSettings.isOpen())
2260 source.showFeatureSettingsUI();
2265 public JInternalFrame[] getAllFrames()
2267 return desktop.getAllFrames();
2271 * Checks the given url to see if it gives a response indicating that the user
2272 * should be informed of a new questionnaire.
2276 public void checkForQuestionnaire(String url)
2278 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2279 // javax.swing.SwingUtilities.invokeLater(jvq);
2280 new Thread(jvq).start();
2283 public void checkURLLinks()
2285 // Thread off the URL link checker
2286 addDialogThread(new Runnable()
2291 if (Cache.getDefault("CHECKURLLINKS", true))
2293 // check what the actual links are - if it's just the default don't
2294 // bother with the warning
2295 List<String> links = Preferences.sequenceUrlLinks
2298 // only need to check links if there is one with a
2299 // SEQUENCE_ID which is not the default EMBL_EBI link
2300 ListIterator<String> li = links.listIterator();
2301 boolean check = false;
2302 List<JLabel> urls = new ArrayList<>();
2303 while (li.hasNext())
2305 String link = li.next();
2306 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2307 && !UrlConstants.isDefaultString(link))
2310 int barPos = link.indexOf("|");
2311 String urlMsg = barPos == -1 ? link
2312 : link.substring(0, barPos) + ": "
2313 + link.substring(barPos + 1);
2314 urls.add(new JLabel(urlMsg));
2322 // ask user to check in case URL links use old style tokens
2323 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2324 JPanel msgPanel = new JPanel();
2325 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2326 msgPanel.add(Box.createVerticalGlue());
2327 JLabel msg = new JLabel(MessageManager
2328 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2329 JLabel msg2 = new JLabel(MessageManager
2330 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2332 for (JLabel url : urls)
2338 final JCheckBox jcb = new JCheckBox(
2339 MessageManager.getString("label.do_not_display_again"));
2340 jcb.addActionListener(new ActionListener()
2343 public void actionPerformed(ActionEvent e)
2345 // update Cache settings for "don't show this again"
2346 boolean showWarningAgain = !jcb.isSelected();
2347 Cache.setProperty("CHECKURLLINKS",
2348 Boolean.valueOf(showWarningAgain).toString());
2353 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2355 .getString("label.SEQUENCE_ID_no_longer_used"),
2356 JvOptionPane.WARNING_MESSAGE);
2363 * Proxy class for JDesktopPane which optionally displays the current memory
2364 * usage and highlights the desktop area with a red bar if free memory runs
2369 public class MyDesktopPane extends JDesktopPane implements Runnable
2371 private static final float ONE_MB = 1048576f;
2373 boolean showMemoryUsage = false;
2377 java.text.NumberFormat df;
2379 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2382 public MyDesktopPane(boolean showMemoryUsage)
2384 showMemoryUsage(showMemoryUsage);
2387 public void showMemoryUsage(boolean showMemory)
2389 this.showMemoryUsage = showMemory;
2392 Thread worker = new Thread(this);
2398 public boolean isShowMemoryUsage()
2400 return showMemoryUsage;
2406 df = java.text.NumberFormat.getNumberInstance();
2407 df.setMaximumFractionDigits(2);
2408 runtime = Runtime.getRuntime();
2410 while (showMemoryUsage)
2414 maxMemory = runtime.maxMemory() / ONE_MB;
2415 allocatedMemory = runtime.totalMemory() / ONE_MB;
2416 freeMemory = runtime.freeMemory() / ONE_MB;
2417 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2419 percentUsage = (totalFreeMemory / maxMemory) * 100;
2421 // if (percentUsage < 20)
2423 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2425 // instance.set.setBorder(border1);
2428 // sleep after showing usage
2430 } catch (Exception ex)
2432 ex.printStackTrace();
2438 public void paintComponent(Graphics g)
2440 if (showMemoryUsage && g != null && df != null)
2442 if (percentUsage < 20)
2444 g.setColor(Color.red);
2446 FontMetrics fm = g.getFontMetrics();
2449 g.drawString(MessageManager.formatMessage("label.memory_stats",
2451 { df.format(totalFreeMemory), df.format(maxMemory),
2452 df.format(percentUsage) }),
2453 10, getHeight() - fm.getHeight());
2457 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2458 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2463 * Accessor method to quickly get all the AlignmentFrames loaded.
2465 * @return an array of AlignFrame, or null if none found
2467 public static AlignFrame[] getAlignFrames()
2469 if (Jalview.isHeadlessMode())
2471 // Desktop.desktop is null in headless mode
2472 return new AlignFrame[] { Jalview.currentAlignFrame };
2475 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2481 List<AlignFrame> avp = new ArrayList<>();
2483 for (int i = frames.length - 1; i > -1; i--)
2485 if (frames[i] instanceof AlignFrame)
2487 avp.add((AlignFrame) frames[i]);
2489 else if (frames[i] instanceof SplitFrame)
2492 * Also check for a split frame containing an AlignFrame
2494 GSplitFrame sf = (GSplitFrame) frames[i];
2495 if (sf.getTopFrame() instanceof AlignFrame)
2497 avp.add((AlignFrame) sf.getTopFrame());
2499 if (sf.getBottomFrame() instanceof AlignFrame)
2501 avp.add((AlignFrame) sf.getBottomFrame());
2505 if (avp.size() == 0)
2509 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2514 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2518 public GStructureViewer[] getJmols()
2520 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2526 List<GStructureViewer> avp = new ArrayList<>();
2528 for (int i = frames.length - 1; i > -1; i--)
2530 if (frames[i] instanceof AppJmol)
2532 GStructureViewer af = (GStructureViewer) frames[i];
2536 if (avp.size() == 0)
2540 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2545 * Add Groovy Support to Jalview
2548 public void groovyShell_actionPerformed()
2552 openGroovyConsole();
2553 } catch (Exception ex)
2555 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2556 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2558 MessageManager.getString("label.couldnt_create_groovy_shell"),
2559 MessageManager.getString("label.groovy_support_failed"),
2560 JvOptionPane.ERROR_MESSAGE);
2565 * Open the Groovy console
2567 void openGroovyConsole()
2569 if (groovyConsole == null)
2571 groovyConsole = new groovy.ui.Console();
2572 groovyConsole.setVariable("Jalview", this);
2573 groovyConsole.run();
2576 * We allow only one console at a time, so that AlignFrame menu option
2577 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2578 * enable 'Run script', when the console is opened, and the reverse when it is
2581 Window window = (Window) groovyConsole.getFrame();
2582 window.addWindowListener(new WindowAdapter()
2585 public void windowClosed(WindowEvent e)
2588 * rebind CMD-Q from Groovy Console to Jalview Quit
2591 enableExecuteGroovy(false);
2597 * show Groovy console window (after close and reopen)
2599 ((Window) groovyConsole.getFrame()).setVisible(true);
2602 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2603 * opening a second console
2605 enableExecuteGroovy(true);
2609 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2610 * binding when opened
2612 protected void addQuitHandler()
2615 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2617 .getKeyStroke(KeyEvent.VK_Q,
2618 jalview.util.ShortcutKeyMaskExWrapper
2619 .getMenuShortcutKeyMaskEx()),
2621 getRootPane().getActionMap().put("Quit", new AbstractAction()
2624 public void actionPerformed(ActionEvent e)
2632 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2635 * true if Groovy console is open
2637 public void enableExecuteGroovy(boolean enabled)
2640 * disable opening a second Groovy console (or re-enable when the console is
2643 groovyShell.setEnabled(!enabled);
2645 AlignFrame[] alignFrames = getAlignFrames();
2646 if (alignFrames != null)
2648 for (AlignFrame af : alignFrames)
2650 af.setGroovyEnabled(enabled);
2656 * Progress bars managed by the IProgressIndicator method.
2658 private Hashtable<Long, JPanel> progressBars;
2660 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2665 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2668 public void setProgressBar(String message, long id)
2670 if (progressBars == null)
2672 progressBars = new Hashtable<>();
2673 progressBarHandlers = new Hashtable<>();
2676 if (progressBars.get(Long.valueOf(id)) != null)
2678 JPanel panel = progressBars.remove(Long.valueOf(id));
2679 if (progressBarHandlers.contains(Long.valueOf(id)))
2681 progressBarHandlers.remove(Long.valueOf(id));
2683 removeProgressPanel(panel);
2687 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2694 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2695 * jalview.gui.IProgressIndicatorHandler)
2698 public void registerHandler(final long id,
2699 final IProgressIndicatorHandler handler)
2701 if (progressBarHandlers == null
2702 || !progressBars.containsKey(Long.valueOf(id)))
2704 throw new Error(MessageManager.getString(
2705 "error.call_setprogressbar_before_registering_handler"));
2707 progressBarHandlers.put(Long.valueOf(id), handler);
2708 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2709 if (handler.canCancel())
2711 JButton cancel = new JButton(
2712 MessageManager.getString("action.cancel"));
2713 final IProgressIndicator us = this;
2714 cancel.addActionListener(new ActionListener()
2718 public void actionPerformed(ActionEvent e)
2720 handler.cancelActivity(id);
2721 us.setProgressBar(MessageManager
2722 .formatMessage("label.cancelled_params", new Object[]
2723 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2727 progressPanel.add(cancel, BorderLayout.EAST);
2733 * @return true if any progress bars are still active
2736 public boolean operationInProgress()
2738 if (progressBars != null && progressBars.size() > 0)
2746 * This will return the first AlignFrame holding the given viewport instance.
2747 * It will break if there are more than one AlignFrames viewing a particular
2751 * @return alignFrame for viewport
2753 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2755 if (desktop != null)
2757 AlignmentPanel[] aps = getAlignmentPanels(
2758 viewport.getSequenceSetId());
2759 for (int panel = 0; aps != null && panel < aps.length; panel++)
2761 if (aps[panel] != null && aps[panel].av == viewport)
2763 return aps[panel].alignFrame;
2770 public VamsasApplication getVamsasApplication()
2772 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2778 * flag set if jalview GUI is being operated programmatically
2780 private boolean inBatchMode = false;
2783 * check if jalview GUI is being operated programmatically
2785 * @return inBatchMode
2787 public boolean isInBatchMode()
2793 * set flag if jalview GUI is being operated programmatically
2795 * @param inBatchMode
2797 public void setInBatchMode(boolean inBatchMode)
2799 this.inBatchMode = inBatchMode;
2803 * start service discovery and wait till it is done
2805 public void startServiceDiscovery()
2807 startServiceDiscovery(false);
2811 * start service discovery threads - blocking or non-blocking
2815 public void startServiceDiscovery(boolean blocking)
2817 startServiceDiscovery(blocking, false);
2821 * start service discovery threads
2824 * - false means call returns immediately
2825 * @param ignore_SHOW_JWS2_SERVICES_preference
2826 * - when true JABA services are discovered regardless of user's JWS2
2827 * discovery preference setting
2829 public void startServiceDiscovery(boolean blocking,
2830 boolean ignore_SHOW_JWS2_SERVICES_preference)
2832 boolean alive = true;
2833 Thread t0 = null, t1 = null, t2 = null;
2834 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2837 // todo: changesupport handlers need to be transferred
2838 if (discoverer == null)
2840 discoverer = new jalview.ws.jws1.Discoverer();
2841 // register PCS handler for desktop.
2842 discoverer.addPropertyChangeListener(changeSupport);
2844 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2845 // until we phase out completely
2846 (t0 = new Thread(discoverer)).start();
2849 if (ignore_SHOW_JWS2_SERVICES_preference
2850 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2852 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2853 .startDiscoverer(changeSupport);
2857 // TODO: do rest service discovery
2866 } catch (Exception e)
2869 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2870 || (t3 != null && t3.isAlive())
2871 || (t0 != null && t0.isAlive());
2877 * called to check if the service discovery process completed successfully.
2881 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2883 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2885 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2886 .getErrorMessages();
2889 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2891 if (serviceChangedDialog == null)
2893 // only run if we aren't already displaying one of these.
2894 addDialogThread(serviceChangedDialog = new Runnable()
2901 * JalviewDialog jd =new JalviewDialog() {
2903 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2905 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2907 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2909 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2911 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2912 * + " or mis-configured HTTP proxy settings.<br/>" +
2913 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2914 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2915 * true, true, "Web Service Configuration Problem", 450, 400);
2917 * jd.waitForInput();
2919 JvOptionPane.showConfirmDialog(Desktop.desktop,
2920 new JLabel("<html><table width=\"450\"><tr><td>"
2921 + ermsg + "</td></tr></table>"
2922 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2923 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2924 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2925 + " Tools->Preferences dialog box to change them.</p></html>"),
2926 "Web Service Configuration Problem",
2927 JvOptionPane.DEFAULT_OPTION,
2928 JvOptionPane.ERROR_MESSAGE);
2929 serviceChangedDialog = null;
2937 jalview.bin.Console.error(
2938 "Errors reported by JABA discovery service. Check web services preferences.\n"
2945 private Runnable serviceChangedDialog = null;
2948 * start a thread to open a URL in the configured browser. Pops up a warning
2949 * dialog to the user if there is an exception when calling out to the browser
2954 public static void showUrl(final String url)
2956 showUrl(url, Desktop.instance);
2960 * Like showUrl but allows progress handler to be specified
2964 * (null) or object implementing IProgressIndicator
2966 public static void showUrl(final String url,
2967 final IProgressIndicator progress)
2969 new Thread(new Runnable()
2976 if (progress != null)
2978 progress.setProgressBar(MessageManager
2979 .formatMessage("status.opening_params", new Object[]
2980 { url }), this.hashCode());
2982 jalview.util.BrowserLauncher.openURL(url);
2983 } catch (Exception ex)
2985 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2987 .getString("label.web_browser_not_found_unix"),
2988 MessageManager.getString("label.web_browser_not_found"),
2989 JvOptionPane.WARNING_MESSAGE);
2991 ex.printStackTrace();
2993 if (progress != null)
2995 progress.setProgressBar(null, this.hashCode());
3001 public static WsParamSetManager wsparamManager = null;
3003 public static ParamManager getUserParameterStore()
3005 if (wsparamManager == null)
3007 wsparamManager = new WsParamSetManager();
3009 return wsparamManager;
3013 * static hyperlink handler proxy method for use by Jalview's internal windows
3017 public static void hyperlinkUpdate(HyperlinkEvent e)
3019 if (e.getEventType() == EventType.ACTIVATED)
3024 url = e.getURL().toString();
3025 Desktop.showUrl(url);
3026 } catch (Exception x)
3031 .error("Couldn't handle string " + url + " as a URL.");
3033 // ignore any exceptions due to dud links.
3040 * single thread that handles display of dialogs to user.
3042 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
3045 * flag indicating if dialogExecutor should try to acquire a permit
3047 private volatile boolean dialogPause = true;
3052 private java.util.concurrent.Semaphore block = new Semaphore(0);
3054 private static groovy.ui.Console groovyConsole;
3057 * add another dialog thread to the queue
3061 public void addDialogThread(final Runnable prompter)
3063 dialogExecutor.submit(new Runnable()
3073 } catch (InterruptedException x)
3077 if (instance == null)
3083 SwingUtilities.invokeAndWait(prompter);
3084 } catch (Exception q)
3086 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3093 public void startDialogQueue()
3095 // set the flag so we don't pause waiting for another permit and semaphore
3096 // the current task to begin
3097 dialogPause = false;
3102 * Outputs an image of the desktop to file in EPS format, after prompting the
3103 * user for choice of Text or Lineart character rendering (unless a preference
3104 * has been set). The file name is generated as
3107 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3111 protected void snapShotWindow_actionPerformed(ActionEvent e)
3113 // currently the menu option to do this is not shown
3116 int width = getWidth();
3117 int height = getHeight();
3119 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3120 ImageWriterI writer = new ImageWriterI()
3123 public void exportImage(Graphics g) throws Exception
3126 jalview.bin.Console.info("Successfully written snapshot to file "
3127 + of.getAbsolutePath());
3130 String title = "View of desktop";
3131 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3133 exporter.doExport(of, this, width, height, title);
3137 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3138 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3139 * and location last time the view was expanded (if any). However it does not
3140 * remember the split pane divider location - this is set to match the
3141 * 'exploding' frame.
3145 public void explodeViews(SplitFrame sf)
3147 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3148 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3149 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3151 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3153 int viewCount = topPanels.size();
3160 * Processing in reverse order works, forwards order leaves the first panels not
3161 * visible. I don't know why!
3163 for (int i = viewCount - 1; i >= 0; i--)
3166 * Make new top and bottom frames. These take over the respective AlignmentPanel
3167 * objects, including their AlignmentViewports, so the cdna/protein
3168 * relationships between the viewports is carried over to the new split frames.
3170 * explodedGeometry holds the (x, y) position of the previously exploded
3171 * SplitFrame, and the (width, height) of the AlignFrame component
3173 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3174 AlignFrame newTopFrame = new AlignFrame(topPanel);
3175 newTopFrame.setSize(oldTopFrame.getSize());
3176 newTopFrame.setVisible(true);
3177 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3178 .getExplodedGeometry();
3179 if (geometry != null)
3181 newTopFrame.setSize(geometry.getSize());
3184 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3185 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3186 newBottomFrame.setSize(oldBottomFrame.getSize());
3187 newBottomFrame.setVisible(true);
3188 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3189 .getExplodedGeometry();
3190 if (geometry != null)
3192 newBottomFrame.setSize(geometry.getSize());
3195 topPanel.av.setGatherViewsHere(false);
3196 bottomPanel.av.setGatherViewsHere(false);
3197 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3199 if (geometry != null)
3201 splitFrame.setLocation(geometry.getLocation());
3203 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3207 * Clear references to the panels (now relocated in the new SplitFrames) before
3208 * closing the old SplitFrame.
3211 bottomPanels.clear();
3216 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3217 * back into the given SplitFrame as additional views. Note that the gathered
3218 * frames may themselves have multiple views.
3222 public void gatherViews(GSplitFrame source)
3225 * special handling of explodedGeometry for a view within a SplitFrame: - it
3226 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3227 * height) of the AlignFrame component
3229 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3230 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3231 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3232 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3233 myBottomFrame.viewport
3234 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3235 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3236 myTopFrame.viewport.setGatherViewsHere(true);
3237 myBottomFrame.viewport.setGatherViewsHere(true);
3238 String topViewId = myTopFrame.viewport.getSequenceSetId();
3239 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3241 JInternalFrame[] frames = desktop.getAllFrames();
3242 for (JInternalFrame frame : frames)
3244 if (frame instanceof SplitFrame && frame != source)
3246 SplitFrame sf = (SplitFrame) frame;
3247 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3248 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3249 boolean gatherThis = false;
3250 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3252 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3253 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3254 if (topViewId.equals(topPanel.av.getSequenceSetId())
3255 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3258 topPanel.av.setGatherViewsHere(false);
3259 bottomPanel.av.setGatherViewsHere(false);
3260 topPanel.av.setExplodedGeometry(
3261 new Rectangle(sf.getLocation(), topFrame.getSize()));
3262 bottomPanel.av.setExplodedGeometry(
3263 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3264 myTopFrame.addAlignmentPanel(topPanel, false);
3265 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3271 topFrame.getAlignPanels().clear();
3272 bottomFrame.getAlignPanels().clear();
3279 * The dust settles...give focus to the tab we did this from.
3281 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3284 public static groovy.ui.Console getGroovyConsole()
3286 return groovyConsole;
3290 * handles the payload of a drag and drop event.
3292 * TODO refactor to desktop utilities class
3295 * - Data source strings extracted from the drop event
3297 * - protocol for each data source extracted from the drop event
3301 * - the payload from the drop event
3304 public static void transferFromDropTarget(List<Object> files,
3305 List<DataSourceType> protocols, DropTargetDropEvent evt,
3306 Transferable t) throws Exception
3309 DataFlavor uriListFlavor = new DataFlavor(
3310 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3313 urlFlavour = new DataFlavor(
3314 "application/x-java-url; class=java.net.URL");
3315 } catch (ClassNotFoundException cfe)
3317 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3321 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3326 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3327 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3328 // means url may be null.
3331 protocols.add(DataSourceType.URL);
3332 files.add(url.toString());
3333 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3334 + files.get(files.size() - 1));
3339 if (Platform.isAMacAndNotJS())
3342 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3345 } catch (Throwable ex)
3347 jalview.bin.Console.debug("URL drop handler failed.", ex);
3350 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3352 // Works on Windows and MacOSX
3353 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3354 for (Object file : (List) t
3355 .getTransferData(DataFlavor.javaFileListFlavor))
3358 protocols.add(DataSourceType.FILE);
3363 // Unix like behaviour
3364 boolean added = false;
3366 if (t.isDataFlavorSupported(uriListFlavor))
3368 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3369 // This is used by Unix drag system
3370 data = (String) t.getTransferData(uriListFlavor);
3374 // fallback to text: workaround - on OSX where there's a JVM bug
3376 .debug("standard URIListFlavor failed. Trying text");
3377 // try text fallback
3378 DataFlavor textDf = new DataFlavor(
3379 "text/plain;class=java.lang.String");
3380 if (t.isDataFlavorSupported(textDf))
3382 data = (String) t.getTransferData(textDf);
3385 jalview.bin.Console.debug("Plain text drop content returned "
3386 + (data == null ? "Null - failed" : data));
3391 while (protocols.size() < files.size())
3393 jalview.bin.Console.debug("Adding missing FILE protocol for "
3394 + files.get(protocols.size()));
3395 protocols.add(DataSourceType.FILE);
3397 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3398 data, "\r\n"); st.hasMoreTokens();)
3401 String s = st.nextToken();
3402 if (s.startsWith("#"))
3404 // the line is a comment (as per the RFC 2483)
3407 java.net.URI uri = new java.net.URI(s);
3408 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3410 protocols.add(DataSourceType.URL);
3411 files.add(uri.toString());
3415 // otherwise preserve old behaviour: catch all for file objects
3416 java.io.File file = new java.io.File(uri);
3417 protocols.add(DataSourceType.FILE);
3418 files.add(file.toString());
3423 if (jalview.bin.Console.isDebugEnabled())
3425 if (data == null || !added)
3428 if (t.getTransferDataFlavors() != null
3429 && t.getTransferDataFlavors().length > 0)
3431 jalview.bin.Console.debug(
3432 "Couldn't resolve drop data. Here are the supported flavors:");
3433 for (DataFlavor fl : t.getTransferDataFlavors())
3435 jalview.bin.Console.debug(
3436 "Supported transfer dataflavor: " + fl.toString());
3437 Object df = t.getTransferData(fl);
3440 jalview.bin.Console.debug("Retrieves: " + df);
3444 jalview.bin.Console.debug("Retrieved nothing");
3451 .debug("Couldn't resolve dataflavor for drop: "
3457 if (Platform.isWindowsAndNotJS())
3460 .debug("Scanning dropped content for Windows Link Files");
3462 // resolve any .lnk files in the file drop
3463 for (int f = 0; f < files.size(); f++)
3465 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3466 if (protocols.get(f).equals(DataSourceType.FILE)
3467 && (source.endsWith(".lnk") || source.endsWith(".url")
3468 || source.endsWith(".site")))
3472 Object obj = files.get(f);
3473 File lf = (obj instanceof File ? (File) obj
3474 : new File((String) obj));
3475 // process link file to get a URL
3476 jalview.bin.Console.debug("Found potential link file: " + lf);
3477 WindowsShortcut wscfile = new WindowsShortcut(lf);
3478 String fullname = wscfile.getRealFilename();
3479 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3480 files.set(f, fullname);
3481 jalview.bin.Console.debug("Parsed real filename " + fullname
3482 + " to extract protocol: " + protocols.get(f));
3483 } catch (Exception ex)
3485 jalview.bin.Console.error(
3486 "Couldn't parse " + files.get(f) + " as a link file.",
3495 * Sets the Preferences property for experimental features to True or False
3496 * depending on the state of the controlling menu item
3499 protected void showExperimental_actionPerformed(boolean selected)
3501 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3505 * Answers a (possibly empty) list of any structure viewer frames (currently
3506 * for either Jmol or Chimera) which are currently open. This may optionally
3507 * be restricted to viewers of a specified class, or viewers linked to a
3508 * specified alignment panel.
3511 * if not null, only return viewers linked to this panel
3512 * @param structureViewerClass
3513 * if not null, only return viewers of this class
3516 public List<StructureViewerBase> getStructureViewers(
3517 AlignmentPanel apanel,
3518 Class<? extends StructureViewerBase> structureViewerClass)
3520 List<StructureViewerBase> result = new ArrayList<>();
3521 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3523 for (JInternalFrame frame : frames)
3525 if (frame instanceof StructureViewerBase)
3527 if (structureViewerClass == null
3528 || structureViewerClass.isInstance(frame))
3531 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3533 result.add((StructureViewerBase) frame);
3541 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3543 private static boolean debugScaleMessageDone = false;
3545 public static void debugScaleMessage(Graphics g)
3547 if (debugScaleMessageDone)
3551 // output used by tests to check HiDPI scaling settings in action
3554 Graphics2D gg = (Graphics2D) g;
3557 AffineTransform t = gg.getTransform();
3558 double scaleX = t.getScaleX();
3559 double scaleY = t.getScaleY();
3560 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3561 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3562 debugScaleMessageDone = true;
3566 jalview.bin.Console.debug("Desktop graphics null");
3568 } catch (Exception e)
3570 jalview.bin.Console.debug(Cache.getStackTraceString(e));