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.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.gui.ImageExporter.ImageWriterI;
108 import jalview.io.BackupFiles;
109 import jalview.io.DataSourceType;
110 import jalview.io.FileFormat;
111 import jalview.io.FileFormatException;
112 import jalview.io.FileFormatI;
113 import jalview.io.FileFormats;
114 import jalview.io.FileLoader;
115 import jalview.io.FormatAdapter;
116 import jalview.io.IdentifyFile;
117 import jalview.io.JalviewFileChooser;
118 import jalview.io.JalviewFileView;
119 import jalview.jbgui.GSplitFrame;
120 import jalview.jbgui.GStructureViewer;
121 import jalview.jbgui.QuitHandler;
122 import jalview.jbgui.QuitHandler.QResponse;
123 import jalview.project.Jalview2XML;
124 import jalview.structure.StructureSelectionManager;
125 import jalview.urls.IdOrgSettings;
126 import jalview.util.BrowserLauncher;
127 import jalview.util.ChannelProperties;
128 import jalview.util.ImageMaker.TYPE;
129 import jalview.util.LaunchUtils;
130 import jalview.util.MessageManager;
131 import jalview.util.Platform;
132 import jalview.util.ShortcutKeyMaskExWrapper;
133 import jalview.util.UrlConstants;
134 import jalview.viewmodel.AlignmentViewport;
135 import jalview.ws.params.ParamManager;
136 import jalview.ws.utils.UrlDownloadClient;
143 * @version $Revision: 1.155 $
145 public class Desktop extends jalview.jbgui.GDesktop
146 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
147 jalview.api.StructureSelectionManagerProvider
149 private static final String CITATION;
152 URL bg_logo_url = ChannelProperties.getImageURL(
153 "bg_logo." + String.valueOf(SplashScreen.logoSize));
154 URL uod_logo_url = ChannelProperties.getImageURL(
155 "uod_banner." + String.valueOf(SplashScreen.logoSize));
156 boolean logo = (bg_logo_url != null || uod_logo_url != null);
157 StringBuilder sb = new StringBuilder();
159 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
164 sb.append(bg_logo_url == null ? ""
165 : "<img alt=\"Barton Group logo\" src=\""
166 + bg_logo_url.toString() + "\">");
167 sb.append(uod_logo_url == null ? ""
168 : " <img alt=\"University of Dundee shield\" src=\""
169 + uod_logo_url.toString() + "\">");
171 "<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>");
172 sb.append("<br><br>If you use Jalview, please cite:"
173 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
174 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
175 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
176 CITATION = sb.toString();
179 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
181 private static int DEFAULT_MIN_WIDTH = 300;
183 private static int DEFAULT_MIN_HEIGHT = 250;
185 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
187 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
189 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
191 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
193 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
195 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
197 public static boolean nosplash = false;
200 * news reader - null if it was never started.
202 private BlogReader jvnews = null;
204 private File projectFile;
208 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
210 public void addJalviewPropertyChangeListener(
211 PropertyChangeListener listener)
213 changeSupport.addJalviewPropertyChangeListener(listener);
217 * @param propertyName
219 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
220 * java.beans.PropertyChangeListener)
222 public void addJalviewPropertyChangeListener(String propertyName,
223 PropertyChangeListener listener)
225 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
229 * @param propertyName
231 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
232 * java.beans.PropertyChangeListener)
234 public void removeJalviewPropertyChangeListener(String propertyName,
235 PropertyChangeListener listener)
237 changeSupport.removeJalviewPropertyChangeListener(propertyName,
241 /** Singleton Desktop instance */
242 public static Desktop instance;
244 public static MyDesktopPane desktop;
246 public static MyDesktopPane getDesktop()
248 // BH 2018 could use currentThread() here as a reference to a
249 // Hashtable<Thread, MyDesktopPane> in JavaScript
253 static int openFrameCount = 0;
255 static final int xOffset = 30;
257 static final int yOffset = 30;
259 public static jalview.ws.jws1.Discoverer discoverer;
261 public static Object[] jalviewClipboard;
263 public static boolean internalCopy = false;
265 static int fileLoadingCount = 0;
267 class MyDesktopManager implements DesktopManager
270 private DesktopManager delegate;
272 public MyDesktopManager(DesktopManager delegate)
274 this.delegate = delegate;
278 public void activateFrame(JInternalFrame f)
282 delegate.activateFrame(f);
283 } catch (NullPointerException npe)
285 Point p = getMousePosition();
286 instance.showPasteMenu(p.x, p.y);
291 public void beginDraggingFrame(JComponent f)
293 delegate.beginDraggingFrame(f);
297 public void beginResizingFrame(JComponent f, int direction)
299 delegate.beginResizingFrame(f, direction);
303 public void closeFrame(JInternalFrame f)
305 delegate.closeFrame(f);
309 public void deactivateFrame(JInternalFrame f)
311 delegate.deactivateFrame(f);
315 public void deiconifyFrame(JInternalFrame f)
317 delegate.deiconifyFrame(f);
321 public void dragFrame(JComponent f, int newX, int newY)
327 delegate.dragFrame(f, newX, newY);
331 public void endDraggingFrame(JComponent f)
333 delegate.endDraggingFrame(f);
338 public void endResizingFrame(JComponent f)
340 delegate.endResizingFrame(f);
345 public void iconifyFrame(JInternalFrame f)
347 delegate.iconifyFrame(f);
351 public void maximizeFrame(JInternalFrame f)
353 delegate.maximizeFrame(f);
357 public void minimizeFrame(JInternalFrame f)
359 delegate.minimizeFrame(f);
363 public void openFrame(JInternalFrame f)
365 delegate.openFrame(f);
369 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
376 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
380 public void setBoundsForFrame(JComponent f, int newX, int newY,
381 int newWidth, int newHeight)
383 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
386 // All other methods, simply delegate
391 * Creates a new Desktop object.
397 * A note to implementors. It is ESSENTIAL that any activities that might
398 * block are spawned off as threads rather than waited for during this
403 doConfigureStructurePrefs();
404 setTitle(ChannelProperties.getProperty("app_name") + " "
405 + Cache.getProperty("VERSION"));
408 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
409 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
410 * officially documented or guaranteed to exist, so we access it via
411 * reflection. There appear to be unfathomable criteria about what this
412 * string can contain, and it if doesn't meet those criteria then "java"
413 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
414 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
415 * not. The reflection access may generate a warning: WARNING: An illegal
416 * reflective access operation has occurred WARNING: Illegal reflective
417 * access by jalview.gui.Desktop () to field
418 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
420 if (Platform.isLinux())
422 if (LaunchUtils.getJavaVersion() >= 11)
424 jalview.bin.Console.info(
425 "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.");
429 Toolkit xToolkit = Toolkit.getDefaultToolkit();
430 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
431 Field awtAppClassNameField = null;
433 if (Arrays.stream(declaredFields)
434 .anyMatch(f -> f.getName().equals("awtAppClassName")))
436 awtAppClassNameField = xToolkit.getClass()
437 .getDeclaredField("awtAppClassName");
440 String title = ChannelProperties.getProperty("app_name");
441 if (awtAppClassNameField != null)
443 awtAppClassNameField.setAccessible(true);
444 awtAppClassNameField.set(xToolkit, title);
448 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
450 } catch (Exception e)
452 jalview.bin.Console.debug("Error setting awtAppClassName");
453 jalview.bin.Console.trace(Cache.getStackTraceString(e));
457 setIconImages(ChannelProperties.getIconList());
459 // override quit handling when GUI OS close [X] button pressed
460 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
461 addWindowListener(new WindowAdapter()
464 public void windowClosing(WindowEvent ev)
466 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
470 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
472 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
473 desktop = new MyDesktopPane(selmemusage);
475 showMemusage.setSelected(selmemusage);
476 desktop.setBackground(Color.white);
478 getContentPane().setLayout(new BorderLayout());
479 // alternate config - have scrollbars - see notes in JAL-153
480 // JScrollPane sp = new JScrollPane();
481 // sp.getViewport().setView(desktop);
482 // getContentPane().add(sp, BorderLayout.CENTER);
484 // BH 2018 - just an experiment to try unclipped JInternalFrames.
487 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
490 getContentPane().add(desktop, BorderLayout.CENTER);
491 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
493 // This line prevents Windows Look&Feel resizing all new windows to maximum
494 // if previous window was maximised
495 desktop.setDesktopManager(new MyDesktopManager(
496 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
497 : Platform.isAMacAndNotJS()
498 ? new AquaInternalFrameManager(
499 desktop.getDesktopManager())
500 : desktop.getDesktopManager())));
502 Rectangle dims = getLastKnownDimensions("");
509 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
510 int xPos = Math.max(5, (screenSize.width - 900) / 2);
511 int yPos = Math.max(5, (screenSize.height - 650) / 2);
512 setBounds(xPos, yPos, 900, 650);
515 if (!Platform.isJS())
522 jconsole = new Console(this, showjconsole);
523 jconsole.setHeader(Cache.getVersionDetailsForConsole());
524 showConsole(showjconsole);
526 showNews.setVisible(false);
528 experimentalFeatures.setSelected(showExperimental());
530 getIdentifiersOrgData();
534 // Spawn a thread that shows the splashscreen
537 SwingUtilities.invokeLater(new Runnable()
542 new SplashScreen(true);
547 // Thread off a new instance of the file chooser - this reduces the time
549 // takes to open it later on.
550 new Thread(new Runnable()
555 jalview.bin.Console.debug("Filechooser init thread started.");
556 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
557 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
559 jalview.bin.Console.debug("Filechooser init thread finished.");
562 // Add the service change listener
563 changeSupport.addJalviewPropertyChangeListener("services",
564 new PropertyChangeListener()
568 public void propertyChange(PropertyChangeEvent evt)
571 .debug("Firing service changed event for "
572 + evt.getNewValue());
573 JalviewServicesChanged(evt);
578 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
581 this.addMouseListener(ma = new MouseAdapter()
584 public void mousePressed(MouseEvent evt)
586 if (evt.isPopupTrigger()) // Mac
588 showPasteMenu(evt.getX(), evt.getY());
593 public void mouseReleased(MouseEvent evt)
595 if (evt.isPopupTrigger()) // Windows
597 showPasteMenu(evt.getX(), evt.getY());
601 desktop.addMouseListener(ma);
605 * Answers true if user preferences to enable experimental features is True
610 public boolean showExperimental()
612 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
613 Boolean.FALSE.toString());
614 return Boolean.valueOf(experimental).booleanValue();
617 public void doConfigureStructurePrefs()
619 // configure services
620 StructureSelectionManager ssm = StructureSelectionManager
621 .getStructureSelectionManager(this);
622 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
624 ssm.setAddTempFacAnnot(
625 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
626 ssm.setProcessSecondaryStructure(
627 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
628 // JAL-3915 - RNAView is no longer an option so this has no effect
629 ssm.setSecStructServices(
630 Cache.getDefault(Preferences.USE_RNAVIEW, false));
634 ssm.setAddTempFacAnnot(false);
635 ssm.setProcessSecondaryStructure(false);
636 ssm.setSecStructServices(false);
640 public void checkForNews()
642 final Desktop me = this;
643 // Thread off the news reader, in case there are connection problems.
644 new Thread(new Runnable()
649 jalview.bin.Console.debug("Starting news thread.");
650 jvnews = new BlogReader(me);
651 showNews.setVisible(true);
652 jalview.bin.Console.debug("Completed news thread.");
657 public void getIdentifiersOrgData()
659 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
660 {// Thread off the identifiers fetcher
661 new Thread(new Runnable()
667 .debug("Downloading data from identifiers.org");
670 UrlDownloadClient.download(IdOrgSettings.getUrl(),
671 IdOrgSettings.getDownloadLocation());
672 } catch (IOException e)
675 .debug("Exception downloading identifiers.org data"
685 protected void showNews_actionPerformed(ActionEvent e)
687 showNews(showNews.isSelected());
690 void showNews(boolean visible)
692 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
693 showNews.setSelected(visible);
694 if (visible && !jvnews.isVisible())
696 new Thread(new Runnable()
701 long now = System.currentTimeMillis();
702 Desktop.instance.setProgressBar(
703 MessageManager.getString("status.refreshing_news"), now);
704 jvnews.refreshNews();
705 Desktop.instance.setProgressBar(null, now);
713 * recover the last known dimensions for a jalview window
716 * - empty string is desktop, all other windows have unique prefix
717 * @return null or last known dimensions scaled to current geometry (if last
718 * window geom was known)
720 Rectangle getLastKnownDimensions(String windowName)
722 // TODO: lock aspect ratio for scaling desktop Bug #0058199
723 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
724 String x = Cache.getProperty(windowName + "SCREEN_X");
725 String y = Cache.getProperty(windowName + "SCREEN_Y");
726 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
727 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
728 if ((x != null) && (y != null) && (width != null) && (height != null))
730 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
731 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
732 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
734 // attempt #1 - try to cope with change in screen geometry - this
735 // version doesn't preserve original jv aspect ratio.
736 // take ratio of current screen size vs original screen size.
737 double sw = ((1f * screenSize.width) / (1f * Integer
738 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
739 double sh = ((1f * screenSize.height) / (1f * Integer
740 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
741 // rescale the bounds depending upon the current screen geometry.
742 ix = (int) (ix * sw);
743 iw = (int) (iw * sw);
744 iy = (int) (iy * sh);
745 ih = (int) (ih * sh);
746 while (ix >= screenSize.width)
748 jalview.bin.Console.debug(
749 "Window geometry location recall error: shifting horizontal to within screenbounds.");
750 ix -= screenSize.width;
752 while (iy >= screenSize.height)
754 jalview.bin.Console.debug(
755 "Window geometry location recall error: shifting vertical to within screenbounds.");
756 iy -= screenSize.height;
758 jalview.bin.Console.debug(
759 "Got last known dimensions for " + windowName + ": x:" + ix
760 + " y:" + iy + " width:" + iw + " height:" + ih);
762 // return dimensions for new instance
763 return new Rectangle(ix, iy, iw, ih);
768 void showPasteMenu(int x, int y)
770 JPopupMenu popup = new JPopupMenu();
771 JMenuItem item = new JMenuItem(
772 MessageManager.getString("label.paste_new_window"));
773 item.addActionListener(new ActionListener()
776 public void actionPerformed(ActionEvent evt)
783 popup.show(this, x, y);
790 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
791 Transferable contents = c.getContents(this);
793 if (contents != null)
795 String file = (String) contents
796 .getTransferData(DataFlavor.stringFlavor);
798 FileFormatI format = new IdentifyFile().identify(file,
799 DataSourceType.PASTE);
801 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
804 } catch (Exception ex)
807 "Unable to paste alignment from system clipboard:\n" + ex);
812 * Adds and opens the given frame to the desktop
823 public static synchronized void addInternalFrame(
824 final JInternalFrame frame, String title, int w, int h)
826 addInternalFrame(frame, title, true, w, h, true, false);
830 * Add an internal frame to the Jalview desktop
837 * When true, display frame immediately, otherwise, caller must call
838 * setVisible themselves.
844 public static synchronized void addInternalFrame(
845 final JInternalFrame frame, String title, boolean makeVisible,
848 addInternalFrame(frame, title, makeVisible, w, h, true, false);
852 * Add an internal frame to the Jalview desktop and make it visible
865 public static synchronized void addInternalFrame(
866 final JInternalFrame frame, String title, int w, int h,
869 addInternalFrame(frame, title, true, w, h, resizable, false);
873 * Add an internal frame to the Jalview desktop
880 * When true, display frame immediately, otherwise, caller must call
881 * setVisible themselves.
888 * @param ignoreMinSize
889 * Do not set the default minimum size for frame
891 public static synchronized void addInternalFrame(
892 final JInternalFrame frame, String title, boolean makeVisible,
893 int w, int h, boolean resizable, boolean ignoreMinSize)
896 // TODO: allow callers to determine X and Y position of frame (eg. via
898 // TODO: consider fixing method to update entries in the window submenu with
899 // the current window title
901 frame.setTitle(title);
902 if (frame.getWidth() < 1 || frame.getHeight() < 1)
906 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
907 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
908 // IF JALVIEW IS RUNNING HEADLESS
909 // ///////////////////////////////////////////////
910 if (instance == null || (System.getProperty("java.awt.headless") != null
911 && System.getProperty("java.awt.headless").equals("true")))
920 frame.setMinimumSize(
921 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
923 // Set default dimension for Alignment Frame window.
924 // The Alignment Frame window could be added from a number of places,
926 // I did this here in order not to miss out on any Alignment frame.
927 if (frame instanceof AlignFrame)
929 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
930 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
934 frame.setVisible(makeVisible);
935 frame.setClosable(true);
936 frame.setResizable(resizable);
937 frame.setMaximizable(resizable);
938 frame.setIconifiable(resizable);
939 frame.setOpaque(Platform.isJS());
941 if (frame.getX() < 1 && frame.getY() < 1)
943 frame.setLocation(xOffset * openFrameCount,
944 yOffset * ((openFrameCount - 1) % 10) + yOffset);
948 * add an entry for the new frame in the Window menu (and remove it when the
951 final JMenuItem menuItem = new JMenuItem(title);
952 frame.addInternalFrameListener(new InternalFrameAdapter()
955 public void internalFrameActivated(InternalFrameEvent evt)
957 JInternalFrame itf = desktop.getSelectedFrame();
960 if (itf instanceof AlignFrame)
962 Jalview.setCurrentAlignFrame((AlignFrame) itf);
969 public void internalFrameClosed(InternalFrameEvent evt)
971 PaintRefresher.RemoveComponent(frame);
974 * defensive check to prevent frames being added half off the window
976 if (openFrameCount > 0)
982 * ensure no reference to alignFrame retained by menu item listener
984 if (menuItem.getActionListeners().length > 0)
986 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
988 windowMenu.remove(menuItem);
992 menuItem.addActionListener(new ActionListener()
995 public void actionPerformed(ActionEvent e)
999 frame.setSelected(true);
1000 frame.setIcon(false);
1001 } catch (java.beans.PropertyVetoException ex)
1008 setKeyBindings(frame);
1012 windowMenu.add(menuItem);
1017 frame.setSelected(true);
1018 frame.requestFocus();
1019 } catch (java.beans.PropertyVetoException ve)
1021 } catch (java.lang.ClassCastException cex)
1023 jalview.bin.Console.warn(
1024 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1030 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1035 private static void setKeyBindings(JInternalFrame frame)
1037 @SuppressWarnings("serial")
1038 final Action closeAction = new AbstractAction()
1041 public void actionPerformed(ActionEvent e)
1048 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1050 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1051 InputEvent.CTRL_DOWN_MASK);
1052 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1053 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1055 InputMap inputMap = frame
1056 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1057 String ctrlW = ctrlWKey.toString();
1058 inputMap.put(ctrlWKey, ctrlW);
1059 inputMap.put(cmdWKey, ctrlW);
1061 ActionMap actionMap = frame.getActionMap();
1062 actionMap.put(ctrlW, closeAction);
1066 public void lostOwnership(Clipboard clipboard, Transferable contents)
1070 Desktop.jalviewClipboard = null;
1073 internalCopy = false;
1077 public void dragEnter(DropTargetDragEvent evt)
1082 public void dragExit(DropTargetEvent evt)
1087 public void dragOver(DropTargetDragEvent evt)
1092 public void dropActionChanged(DropTargetDragEvent evt)
1103 public void drop(DropTargetDropEvent evt)
1105 boolean success = true;
1106 // JAL-1552 - acceptDrop required before getTransferable call for
1107 // Java's Transferable for native dnd
1108 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1109 Transferable t = evt.getTransferable();
1110 List<Object> files = new ArrayList<>();
1111 List<DataSourceType> protocols = new ArrayList<>();
1115 Desktop.transferFromDropTarget(files, protocols, evt, t);
1116 } catch (Exception e)
1118 e.printStackTrace();
1126 for (int i = 0; i < files.size(); i++)
1128 // BH 2018 File or String
1129 Object file = files.get(i);
1130 String fileName = file.toString();
1131 DataSourceType protocol = (protocols == null)
1132 ? DataSourceType.FILE
1134 FileFormatI format = null;
1136 if (fileName.endsWith(".jar"))
1138 format = FileFormat.Jalview;
1143 format = new IdentifyFile().identify(file, protocol);
1145 if (file instanceof File)
1147 Platform.cacheFileData((File) file);
1149 new FileLoader().LoadFile(null, file, protocol, format);
1152 } catch (Exception ex)
1157 evt.dropComplete(success); // need this to ensure input focus is properly
1158 // transfered to any new windows created
1168 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1170 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1171 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1172 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1173 BackupFiles.getEnabled());
1175 chooser.setFileView(new JalviewFileView());
1176 chooser.setDialogTitle(
1177 MessageManager.getString("label.open_local_file"));
1178 chooser.setToolTipText(MessageManager.getString("action.open"));
1180 chooser.setResponseHandler(0, () -> {
1181 File selectedFile = chooser.getSelectedFile();
1182 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1184 FileFormatI format = chooser.getSelectedFormat();
1187 * Call IdentifyFile to verify the file contains what its extension implies.
1188 * Skip this step for dynamically added file formats, because IdentifyFile does
1189 * not know how to recognise them.
1191 if (FileFormats.getInstance().isIdentifiable(format))
1195 format = new IdentifyFile().identify(selectedFile,
1196 DataSourceType.FILE);
1197 } catch (FileFormatException e)
1199 // format = null; //??
1203 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1207 chooser.showOpenDialog(this);
1211 * Shows a dialog for input of a URL at which to retrieve alignment data
1216 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1218 // This construct allows us to have a wider textfield
1220 JLabel label = new JLabel(
1221 MessageManager.getString("label.input_file_url"));
1223 JPanel panel = new JPanel(new GridLayout(2, 1));
1227 * the URL to fetch is input in Java: an editable combobox with history JS:
1228 * (pending JAL-3038) a plain text field
1231 String urlBase = "https://www.";
1232 if (Platform.isJS())
1234 history = new JTextField(urlBase, 35);
1243 JComboBox<String> asCombo = new JComboBox<>();
1244 asCombo.setPreferredSize(new Dimension(400, 20));
1245 asCombo.setEditable(true);
1246 asCombo.addItem(urlBase);
1247 String historyItems = Cache.getProperty("RECENT_URL");
1248 if (historyItems != null)
1250 for (String token : historyItems.split("\\t"))
1252 asCombo.addItem(token);
1259 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1260 MessageManager.getString("action.cancel") };
1261 Callable<Void> action = () -> {
1262 @SuppressWarnings("unchecked")
1263 String url = (history instanceof JTextField
1264 ? ((JTextField) history).getText()
1265 : ((JComboBox<String>) history).getEditor().getItem()
1266 .toString().trim());
1268 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1270 if (viewport != null)
1272 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1273 FileFormat.Jalview);
1277 new FileLoader().LoadFile(url, DataSourceType.URL,
1278 FileFormat.Jalview);
1283 FileFormatI format = null;
1286 format = new IdentifyFile().identify(url, DataSourceType.URL);
1287 } catch (FileFormatException e)
1289 // TODO revise error handling, distinguish between
1290 // URL not found and response not valid
1295 String msg = MessageManager.formatMessage("label.couldnt_locate",
1297 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1298 MessageManager.getString("label.url_not_found"),
1299 JvOptionPane.WARNING_MESSAGE);
1301 return null; // Void
1304 if (viewport != null)
1306 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1311 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1314 return null; // Void
1316 String dialogOption = MessageManager
1317 .getString("label.input_alignment_from_url");
1318 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1319 .showInternalDialog(panel, dialogOption,
1320 JvOptionPane.YES_NO_CANCEL_OPTION,
1321 JvOptionPane.PLAIN_MESSAGE, null, options,
1322 MessageManager.getString("action.ok"));
1326 * Opens the CutAndPaste window for the user to paste an alignment in to
1329 * - if not null, the pasted alignment is added to the current
1330 * alignment; if null, to a new alignment window
1333 public void inputTextboxMenuItem_actionPerformed(
1334 AlignmentViewPanel viewPanel)
1336 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1337 cap.setForInput(viewPanel);
1338 Desktop.addInternalFrame(cap,
1339 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1344 * Check with user and saving files before actually quitting
1346 public void desktopQuit()
1348 desktopQuit(true, false);
1351 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1353 final Callable<QuitHandler.QResponse> doDesktopQuit = () -> {
1354 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1355 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1356 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1357 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1358 getBounds().y, getWidth(), getHeight()));
1360 if (jconsole != null)
1362 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1363 jconsole.stopConsole();
1368 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1372 if (dialogExecutor != null)
1374 dialogExecutor.shutdownNow();
1377 closeAll_actionPerformed(null);
1379 if (groovyConsole != null)
1381 // suppress a possible repeat prompt to save script
1382 groovyConsole.setDirty(false);
1383 groovyConsole.exit();
1386 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1388 // note that shutdown hook will not be run
1389 jalview.bin.Console.debug("Force Quit selected by user");
1390 Runtime.getRuntime().halt(0);
1393 jalview.bin.Console.debug("Quit selected by user");
1396 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1397 // instance.dispose();
1401 return QuitHandler.gotQuitResponse();
1404 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1405 QuitHandler.defaultCancelQuit);
1409 * Don't call this directly, use desktopQuit() above. Exits the program.
1414 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1415 // not run a second time if gotQuitResponse flag has been set (i.e. user
1416 // confirmed quit of some kind).
1420 private void storeLastKnownDimensions(String string, Rectangle jc)
1422 jalview.bin.Console.debug("Storing last known dimensions for " + string
1423 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1424 + " height:" + jc.height);
1426 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1427 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1428 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1429 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1439 public void aboutMenuItem_actionPerformed(ActionEvent e)
1441 new Thread(new Runnable()
1446 new SplashScreen(false);
1452 * Returns the html text for the About screen, including any available version
1453 * number, build details, author details and citation reference, but without
1454 * the enclosing {@code html} tags
1458 public String getAboutMessage()
1460 StringBuilder message = new StringBuilder(1024);
1461 message.append("<div style=\"font-family: sans-serif;\">")
1462 .append("<h1><strong>Version: ")
1463 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1464 .append("<strong>Built: <em>")
1465 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1466 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1467 .append("</strong>");
1469 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1470 if (latestVersion.equals("Checking"))
1472 // JBP removed this message for 2.11: May be reinstated in future version
1473 // message.append("<br>...Checking latest version...</br>");
1475 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1477 boolean red = false;
1478 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1479 .indexOf("automated build") == -1)
1482 // Displayed when code version and jnlp version do not match and code
1483 // version is not a development build
1484 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1487 message.append("<br>!! Version ")
1488 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1489 .append(" is available for download from ")
1490 .append(Cache.getDefault("www.jalview.org",
1491 "https://www.jalview.org"))
1495 message.append("</div>");
1498 message.append("<br>Authors: ");
1499 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1500 message.append(CITATION);
1502 message.append("</div>");
1504 return message.toString();
1508 * Action on requesting Help documentation
1511 public void documentationMenuItem_actionPerformed()
1515 if (Platform.isJS())
1517 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1526 Help.showHelpWindow();
1528 } catch (Exception ex)
1530 System.err.println("Error opening help: " + ex.getMessage());
1535 public void closeAll_actionPerformed(ActionEvent e)
1537 // TODO show a progress bar while closing?
1538 JInternalFrame[] frames = desktop.getAllFrames();
1539 for (int i = 0; i < frames.length; i++)
1543 frames[i].setClosed(true);
1544 } catch (java.beans.PropertyVetoException ex)
1548 Jalview.setCurrentAlignFrame(null);
1549 System.out.println("ALL CLOSED");
1552 * reset state of singleton objects as appropriate (clear down session state
1553 * when all windows are closed)
1555 StructureSelectionManager ssm = StructureSelectionManager
1556 .getStructureSelectionManager(this);
1564 public void raiseRelated_actionPerformed(ActionEvent e)
1566 reorderAssociatedWindows(false, false);
1570 public void minimizeAssociated_actionPerformed(ActionEvent e)
1572 reorderAssociatedWindows(true, false);
1575 void closeAssociatedWindows()
1577 reorderAssociatedWindows(false, true);
1583 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1587 protected void garbageCollect_actionPerformed(ActionEvent e)
1589 // We simply collect the garbage
1590 jalview.bin.Console.debug("Collecting garbage...");
1592 jalview.bin.Console.debug("Finished garbage collection.");
1598 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1602 protected void showMemusage_actionPerformed(ActionEvent e)
1604 desktop.showMemoryUsage(showMemusage.isSelected());
1611 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1615 protected void showConsole_actionPerformed(ActionEvent e)
1617 showConsole(showConsole.isSelected());
1620 Console jconsole = null;
1623 * control whether the java console is visible or not
1627 void showConsole(boolean selected)
1629 // TODO: decide if we should update properties file
1630 if (jconsole != null) // BH 2018
1632 showConsole.setSelected(selected);
1633 Cache.setProperty("SHOW_JAVA_CONSOLE",
1634 Boolean.valueOf(selected).toString());
1635 jconsole.setVisible(selected);
1639 void reorderAssociatedWindows(boolean minimize, boolean close)
1641 JInternalFrame[] frames = desktop.getAllFrames();
1642 if (frames == null || frames.length < 1)
1647 AlignmentViewport source = null, target = null;
1648 if (frames[0] instanceof AlignFrame)
1650 source = ((AlignFrame) frames[0]).getCurrentView();
1652 else if (frames[0] instanceof TreePanel)
1654 source = ((TreePanel) frames[0]).getViewPort();
1656 else if (frames[0] instanceof PCAPanel)
1658 source = ((PCAPanel) frames[0]).av;
1660 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1662 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1667 for (int i = 0; i < frames.length; i++)
1670 if (frames[i] == null)
1674 if (frames[i] instanceof AlignFrame)
1676 target = ((AlignFrame) frames[i]).getCurrentView();
1678 else if (frames[i] instanceof TreePanel)
1680 target = ((TreePanel) frames[i]).getViewPort();
1682 else if (frames[i] instanceof PCAPanel)
1684 target = ((PCAPanel) frames[i]).av;
1686 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1688 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1691 if (source == target)
1697 frames[i].setClosed(true);
1701 frames[i].setIcon(minimize);
1704 frames[i].toFront();
1708 } catch (java.beans.PropertyVetoException ex)
1723 protected void preferences_actionPerformed(ActionEvent e)
1725 Preferences.openPreferences();
1729 * Prompts the user to choose a file and then saves the Jalview state as a
1730 * Jalview project file
1733 public void saveState_actionPerformed()
1735 saveState_actionPerformed(false);
1738 public void saveState_actionPerformed(boolean saveAs)
1740 java.io.File projectFile = getProjectFile();
1741 // autoSave indicates we already have a file and don't need to ask
1742 boolean autoSave = projectFile != null && !saveAs
1743 && BackupFiles.getEnabled();
1745 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1746 // saveAs="+saveAs+", Backups
1747 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1749 boolean approveSave = false;
1752 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1755 chooser.setFileView(new JalviewFileView());
1756 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1758 int value = chooser.showSaveDialog(this);
1760 if (value == JalviewFileChooser.APPROVE_OPTION)
1762 projectFile = chooser.getSelectedFile();
1763 setProjectFile(projectFile);
1768 if (approveSave || autoSave)
1770 final Desktop me = this;
1771 final java.io.File chosenFile = projectFile;
1772 new Thread(new Runnable()
1777 // TODO: refactor to Jalview desktop session controller action.
1778 setProgressBar(MessageManager.formatMessage(
1779 "label.saving_jalview_project", new Object[]
1780 { chosenFile.getName() }), chosenFile.hashCode());
1781 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1782 // TODO catch and handle errors for savestate
1783 // TODO prevent user from messing with the Desktop whilst we're saving
1786 boolean doBackup = BackupFiles.getEnabled();
1787 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1790 new Jalview2XML().saveState(
1791 doBackup ? backupfiles.getTempFile() : chosenFile);
1795 backupfiles.setWriteSuccess(true);
1796 backupfiles.rollBackupsAndRenameTempFile();
1798 } catch (OutOfMemoryError oom)
1800 new OOMWarning("Whilst saving current state to "
1801 + chosenFile.getName(), oom);
1802 } catch (Exception ex)
1804 jalview.bin.Console.error("Problems whilst trying to save to "
1805 + chosenFile.getName(), ex);
1806 JvOptionPane.showMessageDialog(me,
1807 MessageManager.formatMessage(
1808 "label.error_whilst_saving_current_state_to",
1810 { chosenFile.getName() }),
1811 MessageManager.getString("label.couldnt_save_project"),
1812 JvOptionPane.WARNING_MESSAGE);
1814 setProgressBar(null, chosenFile.hashCode());
1821 public void saveAsState_actionPerformed(ActionEvent e)
1823 saveState_actionPerformed(true);
1826 private void setProjectFile(File choice)
1828 this.projectFile = choice;
1831 public File getProjectFile()
1833 return this.projectFile;
1837 * Shows a file chooser dialog and tries to read in the selected file as a
1841 public void loadState_actionPerformed()
1843 final String[] suffix = new String[] { "jvp", "jar" };
1844 final String[] desc = new String[] { "Jalview Project",
1845 "Jalview Project (old)" };
1846 JalviewFileChooser chooser = new JalviewFileChooser(
1847 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1848 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1852 chooser.setFileView(new JalviewFileView());
1853 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1854 chooser.setResponseHandler(0, () -> {
1855 File selectedFile = chooser.getSelectedFile();
1856 setProjectFile(selectedFile);
1857 String choice = selectedFile.getAbsolutePath();
1858 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1859 new Thread(new Runnable()
1866 new Jalview2XML().loadJalviewAlign(selectedFile);
1867 } catch (OutOfMemoryError oom)
1869 new OOMWarning("Whilst loading project from " + choice, oom);
1870 } catch (Exception ex)
1872 jalview.bin.Console.error(
1873 "Problems whilst loading project from " + choice, ex);
1874 JvOptionPane.showMessageDialog(Desktop.desktop,
1875 MessageManager.formatMessage(
1876 "label.error_whilst_loading_project_from",
1879 MessageManager.getString("label.couldnt_load_project"),
1880 JvOptionPane.WARNING_MESSAGE);
1883 }, "Project Loader").start();
1887 chooser.showOpenDialog(this);
1891 public void inputSequence_actionPerformed(ActionEvent e)
1893 new SequenceFetcher(this);
1896 JPanel progressPanel;
1898 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1900 public void startLoading(final Object fileName)
1902 if (fileLoadingCount == 0)
1904 fileLoadingPanels.add(addProgressPanel(MessageManager
1905 .formatMessage("label.loading_file", new Object[]
1911 private JPanel addProgressPanel(String string)
1913 if (progressPanel == null)
1915 progressPanel = new JPanel(new GridLayout(1, 1));
1916 totalProgressCount = 0;
1917 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1919 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1920 JProgressBar progressBar = new JProgressBar();
1921 progressBar.setIndeterminate(true);
1923 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1925 thisprogress.add(progressBar, BorderLayout.CENTER);
1926 progressPanel.add(thisprogress);
1927 ((GridLayout) progressPanel.getLayout()).setRows(
1928 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1929 ++totalProgressCount;
1930 instance.validate();
1931 return thisprogress;
1934 int totalProgressCount = 0;
1936 private void removeProgressPanel(JPanel progbar)
1938 if (progressPanel != null)
1940 synchronized (progressPanel)
1942 progressPanel.remove(progbar);
1943 GridLayout gl = (GridLayout) progressPanel.getLayout();
1944 gl.setRows(gl.getRows() - 1);
1945 if (--totalProgressCount < 1)
1947 this.getContentPane().remove(progressPanel);
1948 progressPanel = null;
1955 public void stopLoading()
1958 if (fileLoadingCount < 1)
1960 while (fileLoadingPanels.size() > 0)
1962 removeProgressPanel(fileLoadingPanels.remove(0));
1964 fileLoadingPanels.clear();
1965 fileLoadingCount = 0;
1970 public static int getViewCount(String alignmentId)
1972 AlignmentViewport[] aps = getViewports(alignmentId);
1973 return (aps == null) ? 0 : aps.length;
1978 * @param alignmentId
1979 * - if null, all sets are returned
1980 * @return all AlignmentPanels concerning the alignmentId sequence set
1982 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1984 if (Desktop.desktop == null)
1986 // no frames created and in headless mode
1987 // TODO: verify that frames are recoverable when in headless mode
1990 List<AlignmentPanel> aps = new ArrayList<>();
1991 AlignFrame[] frames = getAlignFrames();
1996 for (AlignFrame af : frames)
1998 for (AlignmentPanel ap : af.alignPanels)
2000 if (alignmentId == null
2001 || alignmentId.equals(ap.av.getSequenceSetId()))
2007 if (aps.size() == 0)
2011 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2016 * get all the viewports on an alignment.
2018 * @param sequenceSetId
2019 * unique alignment id (may be null - all viewports returned in that
2021 * @return all viewports on the alignment bound to sequenceSetId
2023 public static AlignmentViewport[] getViewports(String sequenceSetId)
2025 List<AlignmentViewport> viewp = new ArrayList<>();
2026 if (desktop != null)
2028 AlignFrame[] frames = Desktop.getAlignFrames();
2030 for (AlignFrame afr : frames)
2032 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2033 .equals(sequenceSetId))
2035 if (afr.alignPanels != null)
2037 for (AlignmentPanel ap : afr.alignPanels)
2039 if (sequenceSetId == null
2040 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2048 viewp.add(afr.getViewport());
2052 if (viewp.size() > 0)
2054 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2061 * Explode the views in the given frame into separate AlignFrame
2065 public static void explodeViews(AlignFrame af)
2067 int size = af.alignPanels.size();
2073 // FIXME: ideally should use UI interface API
2074 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2075 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2076 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2077 for (int i = 0; i < size; i++)
2079 AlignmentPanel ap = af.alignPanels.get(i);
2081 AlignFrame newaf = new AlignFrame(ap);
2083 // transfer reference for existing feature settings to new alignFrame
2084 if (ap == af.alignPanel)
2086 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2088 newaf.featureSettings = viewFeatureSettings;
2090 newaf.setFeatureSettingsGeometry(fsBounds);
2094 * Restore the view's last exploded frame geometry if known. Multiple views from
2095 * one exploded frame share and restore the same (frame) position and size.
2097 Rectangle geometry = ap.av.getExplodedGeometry();
2098 if (geometry != null)
2100 newaf.setBounds(geometry);
2103 ap.av.setGatherViewsHere(false);
2105 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2106 AlignFrame.DEFAULT_HEIGHT);
2107 // and materialise a new feature settings dialog instance for the new
2109 // (closes the old as if 'OK' was pressed)
2110 if (ap == af.alignPanel && newaf.featureSettings != null
2111 && newaf.featureSettings.isOpen()
2112 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2114 newaf.showFeatureSettingsUI();
2118 af.featureSettings = null;
2119 af.alignPanels.clear();
2120 af.closeMenuItem_actionPerformed(true);
2125 * Gather expanded views (separate AlignFrame's) with the same sequence set
2126 * identifier back in to this frame as additional views, and close the
2127 * expanded views. Note the expanded frames may themselves have multiple
2128 * views. We take the lot.
2132 public void gatherViews(AlignFrame source)
2134 source.viewport.setGatherViewsHere(true);
2135 source.viewport.setExplodedGeometry(source.getBounds());
2136 JInternalFrame[] frames = desktop.getAllFrames();
2137 String viewId = source.viewport.getSequenceSetId();
2138 for (int t = 0; t < frames.length; t++)
2140 if (frames[t] instanceof AlignFrame && frames[t] != source)
2142 AlignFrame af = (AlignFrame) frames[t];
2143 boolean gatherThis = false;
2144 for (int a = 0; a < af.alignPanels.size(); a++)
2146 AlignmentPanel ap = af.alignPanels.get(a);
2147 if (viewId.equals(ap.av.getSequenceSetId()))
2150 ap.av.setGatherViewsHere(false);
2151 ap.av.setExplodedGeometry(af.getBounds());
2152 source.addAlignmentPanel(ap, false);
2158 if (af.featureSettings != null && af.featureSettings.isOpen())
2160 if (source.featureSettings == null)
2162 // preserve the feature settings geometry for this frame
2163 source.featureSettings = af.featureSettings;
2164 source.setFeatureSettingsGeometry(
2165 af.getFeatureSettingsGeometry());
2169 // close it and forget
2170 af.featureSettings.close();
2173 af.alignPanels.clear();
2174 af.closeMenuItem_actionPerformed(true);
2179 // refresh the feature setting UI for the source frame if it exists
2180 if (source.featureSettings != null && source.featureSettings.isOpen())
2182 source.showFeatureSettingsUI();
2187 public JInternalFrame[] getAllFrames()
2189 return desktop.getAllFrames();
2193 * Checks the given url to see if it gives a response indicating that the user
2194 * should be informed of a new questionnaire.
2198 public void checkForQuestionnaire(String url)
2200 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2201 // javax.swing.SwingUtilities.invokeLater(jvq);
2202 new Thread(jvq).start();
2205 public void checkURLLinks()
2207 // Thread off the URL link checker
2208 addDialogThread(new Runnable()
2213 if (Cache.getDefault("CHECKURLLINKS", true))
2215 // check what the actual links are - if it's just the default don't
2216 // bother with the warning
2217 List<String> links = Preferences.sequenceUrlLinks
2220 // only need to check links if there is one with a
2221 // SEQUENCE_ID which is not the default EMBL_EBI link
2222 ListIterator<String> li = links.listIterator();
2223 boolean check = false;
2224 List<JLabel> urls = new ArrayList<>();
2225 while (li.hasNext())
2227 String link = li.next();
2228 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2229 && !UrlConstants.isDefaultString(link))
2232 int barPos = link.indexOf("|");
2233 String urlMsg = barPos == -1 ? link
2234 : link.substring(0, barPos) + ": "
2235 + link.substring(barPos + 1);
2236 urls.add(new JLabel(urlMsg));
2244 // ask user to check in case URL links use old style tokens
2245 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2246 JPanel msgPanel = new JPanel();
2247 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2248 msgPanel.add(Box.createVerticalGlue());
2249 JLabel msg = new JLabel(MessageManager
2250 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2251 JLabel msg2 = new JLabel(MessageManager
2252 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2254 for (JLabel url : urls)
2260 final JCheckBox jcb = new JCheckBox(
2261 MessageManager.getString("label.do_not_display_again"));
2262 jcb.addActionListener(new ActionListener()
2265 public void actionPerformed(ActionEvent e)
2267 // update Cache settings for "don't show this again"
2268 boolean showWarningAgain = !jcb.isSelected();
2269 Cache.setProperty("CHECKURLLINKS",
2270 Boolean.valueOf(showWarningAgain).toString());
2275 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2277 .getString("label.SEQUENCE_ID_no_longer_used"),
2278 JvOptionPane.WARNING_MESSAGE);
2285 * Proxy class for JDesktopPane which optionally displays the current memory
2286 * usage and highlights the desktop area with a red bar if free memory runs
2291 public class MyDesktopPane extends JDesktopPane implements Runnable
2293 private static final float ONE_MB = 1048576f;
2295 boolean showMemoryUsage = false;
2299 java.text.NumberFormat df;
2301 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2304 public MyDesktopPane(boolean showMemoryUsage)
2306 showMemoryUsage(showMemoryUsage);
2309 public void showMemoryUsage(boolean showMemory)
2311 this.showMemoryUsage = showMemory;
2314 Thread worker = new Thread(this);
2320 public boolean isShowMemoryUsage()
2322 return showMemoryUsage;
2328 df = java.text.NumberFormat.getNumberInstance();
2329 df.setMaximumFractionDigits(2);
2330 runtime = Runtime.getRuntime();
2332 while (showMemoryUsage)
2336 maxMemory = runtime.maxMemory() / ONE_MB;
2337 allocatedMemory = runtime.totalMemory() / ONE_MB;
2338 freeMemory = runtime.freeMemory() / ONE_MB;
2339 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2341 percentUsage = (totalFreeMemory / maxMemory) * 100;
2343 // if (percentUsage < 20)
2345 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2347 // instance.set.setBorder(border1);
2350 // sleep after showing usage
2352 } catch (Exception ex)
2354 ex.printStackTrace();
2360 public void paintComponent(Graphics g)
2362 if (showMemoryUsage && g != null && df != null)
2364 if (percentUsage < 20)
2366 g.setColor(Color.red);
2368 FontMetrics fm = g.getFontMetrics();
2371 g.drawString(MessageManager.formatMessage("label.memory_stats",
2373 { df.format(totalFreeMemory), df.format(maxMemory),
2374 df.format(percentUsage) }),
2375 10, getHeight() - fm.getHeight());
2379 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2380 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2385 * Accessor method to quickly get all the AlignmentFrames loaded.
2387 * @return an array of AlignFrame, or null if none found
2389 public static AlignFrame[] getAlignFrames()
2391 if (Jalview.isHeadlessMode())
2393 // Desktop.desktop is null in headless mode
2394 return new AlignFrame[] { Jalview.currentAlignFrame };
2397 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2403 List<AlignFrame> avp = new ArrayList<>();
2405 for (int i = frames.length - 1; i > -1; i--)
2407 if (frames[i] instanceof AlignFrame)
2409 avp.add((AlignFrame) frames[i]);
2411 else if (frames[i] instanceof SplitFrame)
2414 * Also check for a split frame containing an AlignFrame
2416 GSplitFrame sf = (GSplitFrame) frames[i];
2417 if (sf.getTopFrame() instanceof AlignFrame)
2419 avp.add((AlignFrame) sf.getTopFrame());
2421 if (sf.getBottomFrame() instanceof AlignFrame)
2423 avp.add((AlignFrame) sf.getBottomFrame());
2427 if (avp.size() == 0)
2431 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2436 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2440 public GStructureViewer[] getJmols()
2442 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2448 List<GStructureViewer> avp = new ArrayList<>();
2450 for (int i = frames.length - 1; i > -1; i--)
2452 if (frames[i] instanceof AppJmol)
2454 GStructureViewer af = (GStructureViewer) frames[i];
2458 if (avp.size() == 0)
2462 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2467 * Add Groovy Support to Jalview
2470 public void groovyShell_actionPerformed()
2474 openGroovyConsole();
2475 } catch (Exception ex)
2477 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2478 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2480 MessageManager.getString("label.couldnt_create_groovy_shell"),
2481 MessageManager.getString("label.groovy_support_failed"),
2482 JvOptionPane.ERROR_MESSAGE);
2487 * Open the Groovy console
2489 void openGroovyConsole()
2491 if (groovyConsole == null)
2493 groovyConsole = new groovy.ui.Console();
2494 groovyConsole.setVariable("Jalview", this);
2495 groovyConsole.run();
2498 * We allow only one console at a time, so that AlignFrame menu option
2499 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2500 * enable 'Run script', when the console is opened, and the reverse when it is
2503 Window window = (Window) groovyConsole.getFrame();
2504 window.addWindowListener(new WindowAdapter()
2507 public void windowClosed(WindowEvent e)
2510 * rebind CMD-Q from Groovy Console to Jalview Quit
2513 enableExecuteGroovy(false);
2519 * show Groovy console window (after close and reopen)
2521 ((Window) groovyConsole.getFrame()).setVisible(true);
2524 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2525 * opening a second console
2527 enableExecuteGroovy(true);
2531 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2532 * binding when opened
2534 protected void addQuitHandler()
2537 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2539 .getKeyStroke(KeyEvent.VK_Q,
2540 jalview.util.ShortcutKeyMaskExWrapper
2541 .getMenuShortcutKeyMaskEx()),
2543 getRootPane().getActionMap().put("Quit", new AbstractAction()
2546 public void actionPerformed(ActionEvent e)
2554 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2557 * true if Groovy console is open
2559 public void enableExecuteGroovy(boolean enabled)
2562 * disable opening a second Groovy console (or re-enable when the console is
2565 groovyShell.setEnabled(!enabled);
2567 AlignFrame[] alignFrames = getAlignFrames();
2568 if (alignFrames != null)
2570 for (AlignFrame af : alignFrames)
2572 af.setGroovyEnabled(enabled);
2578 * Progress bars managed by the IProgressIndicator method.
2580 private Hashtable<Long, JPanel> progressBars;
2582 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2587 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2590 public void setProgressBar(String message, long id)
2592 if (progressBars == null)
2594 progressBars = new Hashtable<>();
2595 progressBarHandlers = new Hashtable<>();
2598 if (progressBars.get(Long.valueOf(id)) != null)
2600 JPanel panel = progressBars.remove(Long.valueOf(id));
2601 if (progressBarHandlers.contains(Long.valueOf(id)))
2603 progressBarHandlers.remove(Long.valueOf(id));
2605 removeProgressPanel(panel);
2609 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2616 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2617 * jalview.gui.IProgressIndicatorHandler)
2620 public void registerHandler(final long id,
2621 final IProgressIndicatorHandler handler)
2623 if (progressBarHandlers == null
2624 || !progressBars.containsKey(Long.valueOf(id)))
2626 throw new Error(MessageManager.getString(
2627 "error.call_setprogressbar_before_registering_handler"));
2629 progressBarHandlers.put(Long.valueOf(id), handler);
2630 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2631 if (handler.canCancel())
2633 JButton cancel = new JButton(
2634 MessageManager.getString("action.cancel"));
2635 final IProgressIndicator us = this;
2636 cancel.addActionListener(new ActionListener()
2640 public void actionPerformed(ActionEvent e)
2642 handler.cancelActivity(id);
2643 us.setProgressBar(MessageManager
2644 .formatMessage("label.cancelled_params", new Object[]
2645 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2649 progressPanel.add(cancel, BorderLayout.EAST);
2655 * @return true if any progress bars are still active
2658 public boolean operationInProgress()
2660 if (progressBars != null && progressBars.size() > 0)
2668 * This will return the first AlignFrame holding the given viewport instance.
2669 * It will break if there are more than one AlignFrames viewing a particular
2673 * @return alignFrame for viewport
2675 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2677 if (desktop != null)
2679 AlignmentPanel[] aps = getAlignmentPanels(
2680 viewport.getSequenceSetId());
2681 for (int panel = 0; aps != null && panel < aps.length; panel++)
2683 if (aps[panel] != null && aps[panel].av == viewport)
2685 return aps[panel].alignFrame;
2692 public VamsasApplication getVamsasApplication()
2694 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2700 * flag set if jalview GUI is being operated programmatically
2702 private boolean inBatchMode = false;
2705 * check if jalview GUI is being operated programmatically
2707 * @return inBatchMode
2709 public boolean isInBatchMode()
2715 * set flag if jalview GUI is being operated programmatically
2717 * @param inBatchMode
2719 public void setInBatchMode(boolean inBatchMode)
2721 this.inBatchMode = inBatchMode;
2725 * start service discovery and wait till it is done
2727 public void startServiceDiscovery()
2729 startServiceDiscovery(false);
2733 * start service discovery threads - blocking or non-blocking
2737 public void startServiceDiscovery(boolean blocking)
2739 startServiceDiscovery(blocking, false);
2743 * start service discovery threads
2746 * - false means call returns immediately
2747 * @param ignore_SHOW_JWS2_SERVICES_preference
2748 * - when true JABA services are discovered regardless of user's JWS2
2749 * discovery preference setting
2751 public void startServiceDiscovery(boolean blocking,
2752 boolean ignore_SHOW_JWS2_SERVICES_preference)
2754 boolean alive = true;
2755 Thread t0 = null, t1 = null, t2 = null;
2756 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2759 // todo: changesupport handlers need to be transferred
2760 if (discoverer == null)
2762 discoverer = new jalview.ws.jws1.Discoverer();
2763 // register PCS handler for desktop.
2764 discoverer.addPropertyChangeListener(changeSupport);
2766 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2767 // until we phase out completely
2768 (t0 = new Thread(discoverer)).start();
2771 if (ignore_SHOW_JWS2_SERVICES_preference
2772 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2774 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2775 .startDiscoverer(changeSupport);
2779 // TODO: do rest service discovery
2788 } catch (Exception e)
2791 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2792 || (t3 != null && t3.isAlive())
2793 || (t0 != null && t0.isAlive());
2799 * called to check if the service discovery process completed successfully.
2803 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2805 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2807 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2808 .getErrorMessages();
2811 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2813 if (serviceChangedDialog == null)
2815 // only run if we aren't already displaying one of these.
2816 addDialogThread(serviceChangedDialog = new Runnable()
2823 * JalviewDialog jd =new JalviewDialog() {
2825 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2827 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2829 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2831 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2833 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2834 * + " or mis-configured HTTP proxy settings.<br/>" +
2835 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2836 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2837 * true, true, "Web Service Configuration Problem", 450, 400);
2839 * jd.waitForInput();
2841 JvOptionPane.showConfirmDialog(Desktop.desktop,
2842 new JLabel("<html><table width=\"450\"><tr><td>"
2843 + ermsg + "</td></tr></table>"
2844 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2845 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2846 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2847 + " Tools->Preferences dialog box to change them.</p></html>"),
2848 "Web Service Configuration Problem",
2849 JvOptionPane.DEFAULT_OPTION,
2850 JvOptionPane.ERROR_MESSAGE);
2851 serviceChangedDialog = null;
2859 jalview.bin.Console.error(
2860 "Errors reported by JABA discovery service. Check web services preferences.\n"
2867 private Runnable serviceChangedDialog = null;
2870 * start a thread to open a URL in the configured browser. Pops up a warning
2871 * dialog to the user if there is an exception when calling out to the browser
2876 public static void showUrl(final String url)
2878 showUrl(url, Desktop.instance);
2882 * Like showUrl but allows progress handler to be specified
2886 * (null) or object implementing IProgressIndicator
2888 public static void showUrl(final String url,
2889 final IProgressIndicator progress)
2891 new Thread(new Runnable()
2898 if (progress != null)
2900 progress.setProgressBar(MessageManager
2901 .formatMessage("status.opening_params", new Object[]
2902 { url }), this.hashCode());
2904 jalview.util.BrowserLauncher.openURL(url);
2905 } catch (Exception ex)
2907 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2909 .getString("label.web_browser_not_found_unix"),
2910 MessageManager.getString("label.web_browser_not_found"),
2911 JvOptionPane.WARNING_MESSAGE);
2913 ex.printStackTrace();
2915 if (progress != null)
2917 progress.setProgressBar(null, this.hashCode());
2923 public static WsParamSetManager wsparamManager = null;
2925 public static ParamManager getUserParameterStore()
2927 if (wsparamManager == null)
2929 wsparamManager = new WsParamSetManager();
2931 return wsparamManager;
2935 * static hyperlink handler proxy method for use by Jalview's internal windows
2939 public static void hyperlinkUpdate(HyperlinkEvent e)
2941 if (e.getEventType() == EventType.ACTIVATED)
2946 url = e.getURL().toString();
2947 Desktop.showUrl(url);
2948 } catch (Exception x)
2953 .error("Couldn't handle string " + url + " as a URL.");
2955 // ignore any exceptions due to dud links.
2962 * single thread that handles display of dialogs to user.
2964 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2967 * flag indicating if dialogExecutor should try to acquire a permit
2969 private volatile boolean dialogPause = true;
2974 private java.util.concurrent.Semaphore block = new Semaphore(0);
2976 private static groovy.ui.Console groovyConsole;
2979 * add another dialog thread to the queue
2983 public void addDialogThread(final Runnable prompter)
2985 dialogExecutor.submit(new Runnable()
2995 } catch (InterruptedException x)
2999 if (instance == null)
3005 SwingUtilities.invokeAndWait(prompter);
3006 } catch (Exception q)
3008 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3015 public void startDialogQueue()
3017 // set the flag so we don't pause waiting for another permit and semaphore
3018 // the current task to begin
3019 dialogPause = false;
3024 * Outputs an image of the desktop to file in EPS format, after prompting the
3025 * user for choice of Text or Lineart character rendering (unless a preference
3026 * has been set). The file name is generated as
3029 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3033 protected void snapShotWindow_actionPerformed(ActionEvent e)
3035 // currently the menu option to do this is not shown
3038 int width = getWidth();
3039 int height = getHeight();
3041 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3042 ImageWriterI writer = new ImageWriterI()
3045 public void exportImage(Graphics g) throws Exception
3048 jalview.bin.Console.info("Successfully written snapshot to file "
3049 + of.getAbsolutePath());
3052 String title = "View of desktop";
3053 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3055 exporter.doExport(of, this, width, height, title);
3059 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3060 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3061 * and location last time the view was expanded (if any). However it does not
3062 * remember the split pane divider location - this is set to match the
3063 * 'exploding' frame.
3067 public void explodeViews(SplitFrame sf)
3069 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3070 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3071 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3073 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3075 int viewCount = topPanels.size();
3082 * Processing in reverse order works, forwards order leaves the first panels not
3083 * visible. I don't know why!
3085 for (int i = viewCount - 1; i >= 0; i--)
3088 * Make new top and bottom frames. These take over the respective AlignmentPanel
3089 * objects, including their AlignmentViewports, so the cdna/protein
3090 * relationships between the viewports is carried over to the new split frames.
3092 * explodedGeometry holds the (x, y) position of the previously exploded
3093 * SplitFrame, and the (width, height) of the AlignFrame component
3095 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3096 AlignFrame newTopFrame = new AlignFrame(topPanel);
3097 newTopFrame.setSize(oldTopFrame.getSize());
3098 newTopFrame.setVisible(true);
3099 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3100 .getExplodedGeometry();
3101 if (geometry != null)
3103 newTopFrame.setSize(geometry.getSize());
3106 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3107 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3108 newBottomFrame.setSize(oldBottomFrame.getSize());
3109 newBottomFrame.setVisible(true);
3110 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3111 .getExplodedGeometry();
3112 if (geometry != null)
3114 newBottomFrame.setSize(geometry.getSize());
3117 topPanel.av.setGatherViewsHere(false);
3118 bottomPanel.av.setGatherViewsHere(false);
3119 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3121 if (geometry != null)
3123 splitFrame.setLocation(geometry.getLocation());
3125 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3129 * Clear references to the panels (now relocated in the new SplitFrames) before
3130 * closing the old SplitFrame.
3133 bottomPanels.clear();
3138 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3139 * back into the given SplitFrame as additional views. Note that the gathered
3140 * frames may themselves have multiple views.
3144 public void gatherViews(GSplitFrame source)
3147 * special handling of explodedGeometry for a view within a SplitFrame: - it
3148 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3149 * height) of the AlignFrame component
3151 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3152 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3153 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3154 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3155 myBottomFrame.viewport
3156 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3157 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3158 myTopFrame.viewport.setGatherViewsHere(true);
3159 myBottomFrame.viewport.setGatherViewsHere(true);
3160 String topViewId = myTopFrame.viewport.getSequenceSetId();
3161 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3163 JInternalFrame[] frames = desktop.getAllFrames();
3164 for (JInternalFrame frame : frames)
3166 if (frame instanceof SplitFrame && frame != source)
3168 SplitFrame sf = (SplitFrame) frame;
3169 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3170 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3171 boolean gatherThis = false;
3172 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3174 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3175 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3176 if (topViewId.equals(topPanel.av.getSequenceSetId())
3177 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3180 topPanel.av.setGatherViewsHere(false);
3181 bottomPanel.av.setGatherViewsHere(false);
3182 topPanel.av.setExplodedGeometry(
3183 new Rectangle(sf.getLocation(), topFrame.getSize()));
3184 bottomPanel.av.setExplodedGeometry(
3185 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3186 myTopFrame.addAlignmentPanel(topPanel, false);
3187 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3193 topFrame.getAlignPanels().clear();
3194 bottomFrame.getAlignPanels().clear();
3201 * The dust settles...give focus to the tab we did this from.
3203 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3206 public static groovy.ui.Console getGroovyConsole()
3208 return groovyConsole;
3212 * handles the payload of a drag and drop event.
3214 * TODO refactor to desktop utilities class
3217 * - Data source strings extracted from the drop event
3219 * - protocol for each data source extracted from the drop event
3223 * - the payload from the drop event
3226 public static void transferFromDropTarget(List<Object> files,
3227 List<DataSourceType> protocols, DropTargetDropEvent evt,
3228 Transferable t) throws Exception
3231 DataFlavor uriListFlavor = new DataFlavor(
3232 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3235 urlFlavour = new DataFlavor(
3236 "application/x-java-url; class=java.net.URL");
3237 } catch (ClassNotFoundException cfe)
3239 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3243 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3248 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3249 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3250 // means url may be null.
3253 protocols.add(DataSourceType.URL);
3254 files.add(url.toString());
3255 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3256 + files.get(files.size() - 1));
3261 if (Platform.isAMacAndNotJS())
3264 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3267 } catch (Throwable ex)
3269 jalview.bin.Console.debug("URL drop handler failed.", ex);
3272 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3274 // Works on Windows and MacOSX
3275 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3276 for (Object file : (List) t
3277 .getTransferData(DataFlavor.javaFileListFlavor))
3280 protocols.add(DataSourceType.FILE);
3285 // Unix like behaviour
3286 boolean added = false;
3288 if (t.isDataFlavorSupported(uriListFlavor))
3290 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3291 // This is used by Unix drag system
3292 data = (String) t.getTransferData(uriListFlavor);
3296 // fallback to text: workaround - on OSX where there's a JVM bug
3298 .debug("standard URIListFlavor failed. Trying text");
3299 // try text fallback
3300 DataFlavor textDf = new DataFlavor(
3301 "text/plain;class=java.lang.String");
3302 if (t.isDataFlavorSupported(textDf))
3304 data = (String) t.getTransferData(textDf);
3307 jalview.bin.Console.debug("Plain text drop content returned "
3308 + (data == null ? "Null - failed" : data));
3313 while (protocols.size() < files.size())
3315 jalview.bin.Console.debug("Adding missing FILE protocol for "
3316 + files.get(protocols.size()));
3317 protocols.add(DataSourceType.FILE);
3319 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3320 data, "\r\n"); st.hasMoreTokens();)
3323 String s = st.nextToken();
3324 if (s.startsWith("#"))
3326 // the line is a comment (as per the RFC 2483)
3329 java.net.URI uri = new java.net.URI(s);
3330 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3332 protocols.add(DataSourceType.URL);
3333 files.add(uri.toString());
3337 // otherwise preserve old behaviour: catch all for file objects
3338 java.io.File file = new java.io.File(uri);
3339 protocols.add(DataSourceType.FILE);
3340 files.add(file.toString());
3345 if (jalview.bin.Console.isDebugEnabled())
3347 if (data == null || !added)
3350 if (t.getTransferDataFlavors() != null
3351 && t.getTransferDataFlavors().length > 0)
3353 jalview.bin.Console.debug(
3354 "Couldn't resolve drop data. Here are the supported flavors:");
3355 for (DataFlavor fl : t.getTransferDataFlavors())
3357 jalview.bin.Console.debug(
3358 "Supported transfer dataflavor: " + fl.toString());
3359 Object df = t.getTransferData(fl);
3362 jalview.bin.Console.debug("Retrieves: " + df);
3366 jalview.bin.Console.debug("Retrieved nothing");
3373 .debug("Couldn't resolve dataflavor for drop: "
3379 if (Platform.isWindowsAndNotJS())
3382 .debug("Scanning dropped content for Windows Link Files");
3384 // resolve any .lnk files in the file drop
3385 for (int f = 0; f < files.size(); f++)
3387 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3388 if (protocols.get(f).equals(DataSourceType.FILE)
3389 && (source.endsWith(".lnk") || source.endsWith(".url")
3390 || source.endsWith(".site")))
3394 Object obj = files.get(f);
3395 File lf = (obj instanceof File ? (File) obj
3396 : new File((String) obj));
3397 // process link file to get a URL
3398 jalview.bin.Console.debug("Found potential link file: " + lf);
3399 WindowsShortcut wscfile = new WindowsShortcut(lf);
3400 String fullname = wscfile.getRealFilename();
3401 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3402 files.set(f, fullname);
3403 jalview.bin.Console.debug("Parsed real filename " + fullname
3404 + " to extract protocol: " + protocols.get(f));
3405 } catch (Exception ex)
3407 jalview.bin.Console.error(
3408 "Couldn't parse " + files.get(f) + " as a link file.",
3417 * Sets the Preferences property for experimental features to True or False
3418 * depending on the state of the controlling menu item
3421 protected void showExperimental_actionPerformed(boolean selected)
3423 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3427 * Answers a (possibly empty) list of any structure viewer frames (currently
3428 * for either Jmol or Chimera) which are currently open. This may optionally
3429 * be restricted to viewers of a specified class, or viewers linked to a
3430 * specified alignment panel.
3433 * if not null, only return viewers linked to this panel
3434 * @param structureViewerClass
3435 * if not null, only return viewers of this class
3438 public List<StructureViewerBase> getStructureViewers(
3439 AlignmentPanel apanel,
3440 Class<? extends StructureViewerBase> structureViewerClass)
3442 List<StructureViewerBase> result = new ArrayList<>();
3443 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3445 for (JInternalFrame frame : frames)
3447 if (frame instanceof StructureViewerBase)
3449 if (structureViewerClass == null
3450 || structureViewerClass.isInstance(frame))
3453 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3455 result.add((StructureViewerBase) frame);
3463 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3465 private static boolean debugScaleMessageDone = false;
3467 public static void debugScaleMessage(Graphics g)
3469 if (debugScaleMessageDone)
3473 // output used by tests to check HiDPI scaling settings in action
3476 Graphics2D gg = (Graphics2D) g;
3479 AffineTransform t = gg.getTransform();
3480 double scaleX = t.getScaleX();
3481 double scaleY = t.getScaleY();
3482 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3483 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3484 debugScaleMessageDone = true;
3488 jalview.bin.Console.debug("Desktop graphics null");
3490 } catch (Exception e)
3492 jalview.bin.Console.debug(Cache.getStackTraceString(e));