2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JInternalFrame;
85 import javax.swing.JLabel;
86 import javax.swing.JMenuItem;
87 import javax.swing.JPanel;
88 import javax.swing.JPopupMenu;
89 import javax.swing.JProgressBar;
90 import javax.swing.JTextField;
91 import javax.swing.KeyStroke;
92 import javax.swing.SwingUtilities;
93 import javax.swing.event.HyperlinkEvent;
94 import javax.swing.event.HyperlinkEvent.EventType;
95 import javax.swing.event.InternalFrameAdapter;
96 import javax.swing.event.InternalFrameEvent;
98 import org.stackoverflowusers.file.WindowsShortcut;
100 import jalview.api.AlignViewportI;
101 import jalview.api.AlignmentViewPanel;
102 import jalview.bin.Cache;
103 import jalview.bin.Jalview;
104 import jalview.gui.ImageExporter.ImageWriterI;
105 import jalview.io.BackupFiles;
106 import jalview.io.DataSourceType;
107 import jalview.io.FileFormat;
108 import jalview.io.FileFormatException;
109 import jalview.io.FileFormatI;
110 import jalview.io.FileFormats;
111 import jalview.io.FileLoader;
112 import jalview.io.FormatAdapter;
113 import jalview.io.IdentifyFile;
114 import jalview.io.JalviewFileChooser;
115 import jalview.io.JalviewFileView;
116 import jalview.jbgui.GSplitFrame;
117 import jalview.jbgui.GStructureViewer;
118 import jalview.project.Jalview2XML;
119 import jalview.structure.StructureSelectionManager;
120 import jalview.urls.IdOrgSettings;
121 import jalview.util.BrowserLauncher;
122 import jalview.util.ChannelProperties;
123 import jalview.util.ImageMaker.TYPE;
124 import jalview.util.LaunchUtils;
125 import jalview.util.MessageManager;
126 import jalview.util.Platform;
127 import jalview.util.ShortcutKeyMaskExWrapper;
128 import jalview.util.UrlConstants;
129 import jalview.viewmodel.AlignmentViewport;
130 import jalview.ws.params.ParamManager;
131 import jalview.ws.utils.UrlDownloadClient;
138 * @version $Revision: 1.155 $
140 public class Desktop extends jalview.jbgui.GDesktop
141 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
142 jalview.api.StructureSelectionManagerProvider
144 private static final String CITATION;
147 URL bg_logo_url = ChannelProperties.getImageURL(
148 "bg_logo." + String.valueOf(SplashScreen.logoSize));
149 URL uod_logo_url = ChannelProperties.getImageURL(
150 "uod_banner." + String.valueOf(SplashScreen.logoSize));
151 boolean logo = (bg_logo_url != null || uod_logo_url != null);
152 StringBuilder sb = new StringBuilder();
154 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
159 sb.append(bg_logo_url == null ? ""
160 : "<img alt=\"Barton Group logo\" src=\""
161 + bg_logo_url.toString() + "\">");
162 sb.append(uod_logo_url == null ? ""
163 : " <img alt=\"University of Dundee shield\" src=\""
164 + uod_logo_url.toString() + "\">");
166 "<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>");
167 sb.append("<br><br>If you use Jalview, please cite:"
168 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
169 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
170 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
171 CITATION = sb.toString();
174 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
176 private static int DEFAULT_MIN_WIDTH = 300;
178 private static int DEFAULT_MIN_HEIGHT = 250;
180 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
182 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
184 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
186 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
188 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
190 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
192 public static boolean nosplash = false;
195 * news reader - null if it was never started.
197 private BlogReader jvnews = null;
199 private File projectFile;
203 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
205 public void addJalviewPropertyChangeListener(
206 PropertyChangeListener listener)
208 changeSupport.addJalviewPropertyChangeListener(listener);
212 * @param propertyName
214 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
215 * java.beans.PropertyChangeListener)
217 public void addJalviewPropertyChangeListener(String propertyName,
218 PropertyChangeListener listener)
220 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
224 * @param propertyName
226 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
227 * java.beans.PropertyChangeListener)
229 public void removeJalviewPropertyChangeListener(String propertyName,
230 PropertyChangeListener listener)
232 changeSupport.removeJalviewPropertyChangeListener(propertyName,
236 /** Singleton Desktop instance */
237 public static Desktop instance;
239 public static MyDesktopPane desktop;
241 public static MyDesktopPane getDesktop()
243 // BH 2018 could use currentThread() here as a reference to a
244 // Hashtable<Thread, MyDesktopPane> in JavaScript
248 static int openFrameCount = 0;
250 static final int xOffset = 30;
252 static final int yOffset = 30;
254 public static jalview.ws.jws1.Discoverer discoverer;
256 public static Object[] jalviewClipboard;
258 public static boolean internalCopy = false;
260 static int fileLoadingCount = 0;
262 class MyDesktopManager implements DesktopManager
265 private DesktopManager delegate;
267 public MyDesktopManager(DesktopManager delegate)
269 this.delegate = delegate;
273 public void activateFrame(JInternalFrame f)
277 delegate.activateFrame(f);
278 } catch (NullPointerException npe)
280 Point p = getMousePosition();
281 instance.showPasteMenu(p.x, p.y);
286 public void beginDraggingFrame(JComponent f)
288 delegate.beginDraggingFrame(f);
292 public void beginResizingFrame(JComponent f, int direction)
294 delegate.beginResizingFrame(f, direction);
298 public void closeFrame(JInternalFrame f)
300 delegate.closeFrame(f);
304 public void deactivateFrame(JInternalFrame f)
306 delegate.deactivateFrame(f);
310 public void deiconifyFrame(JInternalFrame f)
312 delegate.deiconifyFrame(f);
316 public void dragFrame(JComponent f, int newX, int newY)
322 delegate.dragFrame(f, newX, newY);
326 public void endDraggingFrame(JComponent f)
328 delegate.endDraggingFrame(f);
333 public void endResizingFrame(JComponent f)
335 delegate.endResizingFrame(f);
340 public void iconifyFrame(JInternalFrame f)
342 delegate.iconifyFrame(f);
346 public void maximizeFrame(JInternalFrame f)
348 delegate.maximizeFrame(f);
352 public void minimizeFrame(JInternalFrame f)
354 delegate.minimizeFrame(f);
358 public void openFrame(JInternalFrame f)
360 delegate.openFrame(f);
364 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
371 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
375 public void setBoundsForFrame(JComponent f, int newX, int newY,
376 int newWidth, int newHeight)
378 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
381 // All other methods, simply delegate
386 * Creates a new Desktop object.
392 * A note to implementors. It is ESSENTIAL that any activities that might
393 * block are spawned off as threads rather than waited for during this
398 doConfigureStructurePrefs();
399 setTitle(ChannelProperties.getProperty("app_name") + " "
400 + Cache.getProperty("VERSION"));
403 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
404 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
405 * officially documented or guaranteed to exist, so we access it via
406 * reflection. There appear to be unfathomable criteria about what this
407 * string can contain, and it if doesn't meet those criteria then "java"
408 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
409 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
410 * not. The reflection access may generate a warning: WARNING: An illegal
411 * reflective access operation has occurred WARNING: Illegal reflective
412 * access by jalview.gui.Desktop () to field
413 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
415 if (Platform.isLinux())
417 if (LaunchUtils.getJavaVersion() >= 11)
419 jalview.bin.Console.info(
420 "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.");
424 Toolkit xToolkit = Toolkit.getDefaultToolkit();
425 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
426 Field awtAppClassNameField = null;
428 if (Arrays.stream(declaredFields)
429 .anyMatch(f -> f.getName().equals("awtAppClassName")))
431 awtAppClassNameField = xToolkit.getClass()
432 .getDeclaredField("awtAppClassName");
435 String title = ChannelProperties.getProperty("app_name");
436 if (awtAppClassNameField != null)
438 awtAppClassNameField.setAccessible(true);
439 awtAppClassNameField.set(xToolkit, title);
443 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
445 } catch (Exception e)
447 jalview.bin.Console.debug("Error setting awtAppClassName");
448 jalview.bin.Console.trace(Cache.getStackTraceString(e));
453 * APQHandlers sets handlers for About, Preferences and Quit actions
454 * peculiar to macOS's application menu. APQHandlers will check to see if a
455 * handler is supported before setting it.
459 APQHandlers.setAPQHandlers(this);
460 } catch (Exception e)
462 System.out.println("Cannot set APQHandlers");
463 // e.printStackTrace();
464 } catch (Throwable t)
467 .warn("Error setting APQHandlers: " + t.toString());
468 jalview.bin.Console.trace(Cache.getStackTraceString(t));
470 setIconImages(ChannelProperties.getIconList());
472 addWindowListener(new WindowAdapter()
476 public void windowClosing(WindowEvent ev)
482 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
484 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
485 desktop = new MyDesktopPane(selmemusage);
487 showMemusage.setSelected(selmemusage);
488 desktop.setBackground(Color.white);
490 getContentPane().setLayout(new BorderLayout());
491 // alternate config - have scrollbars - see notes in JAL-153
492 // JScrollPane sp = new JScrollPane();
493 // sp.getViewport().setView(desktop);
494 // getContentPane().add(sp, BorderLayout.CENTER);
496 // BH 2018 - just an experiment to try unclipped JInternalFrames.
499 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
502 getContentPane().add(desktop, BorderLayout.CENTER);
503 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
505 // This line prevents Windows Look&Feel resizing all new windows to maximum
506 // if previous window was maximised
507 desktop.setDesktopManager(new MyDesktopManager(
508 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
509 : Platform.isAMacAndNotJS()
510 ? new AquaInternalFrameManager(
511 desktop.getDesktopManager())
512 : desktop.getDesktopManager())));
514 Rectangle dims = getLastKnownDimensions("");
521 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
522 int xPos = Math.max(5, (screenSize.width - 900) / 2);
523 int yPos = Math.max(5, (screenSize.height - 650) / 2);
524 setBounds(xPos, yPos, 900, 650);
527 if (!Platform.isJS())
534 jconsole = new Console(this, showjconsole);
535 jconsole.setHeader(Cache.getVersionDetailsForConsole());
536 showConsole(showjconsole);
538 showNews.setVisible(false);
540 experimentalFeatures.setSelected(showExperimental());
542 getIdentifiersOrgData();
546 // Spawn a thread that shows the splashscreen
549 SwingUtilities.invokeLater(new Runnable()
554 new SplashScreen(true);
559 // Thread off a new instance of the file chooser - this reduces the time
561 // takes to open it later on.
562 new Thread(new Runnable()
567 jalview.bin.Console.debug("Filechooser init thread started.");
568 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
569 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
571 jalview.bin.Console.debug("Filechooser init thread finished.");
574 // Add the service change listener
575 changeSupport.addJalviewPropertyChangeListener("services",
576 new PropertyChangeListener()
580 public void propertyChange(PropertyChangeEvent evt)
583 .debug("Firing service changed event for "
584 + evt.getNewValue());
585 JalviewServicesChanged(evt);
590 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
592 this.addWindowListener(new WindowAdapter()
595 public void windowClosing(WindowEvent evt)
602 this.addMouseListener(ma = new MouseAdapter()
605 public void mousePressed(MouseEvent evt)
607 if (evt.isPopupTrigger()) // Mac
609 showPasteMenu(evt.getX(), evt.getY());
614 public void mouseReleased(MouseEvent evt)
616 if (evt.isPopupTrigger()) // Windows
618 showPasteMenu(evt.getX(), evt.getY());
622 desktop.addMouseListener(ma);
626 * Answers true if user preferences to enable experimental features is True
631 public boolean showExperimental()
633 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
634 Boolean.FALSE.toString());
635 return Boolean.valueOf(experimental).booleanValue();
638 public void doConfigureStructurePrefs()
640 // configure services
641 StructureSelectionManager ssm = StructureSelectionManager
642 .getStructureSelectionManager(this);
643 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
645 ssm.setAddTempFacAnnot(
646 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
647 ssm.setProcessSecondaryStructure(
648 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
649 // JAL-3915 - RNAView is no longer an option so this has no effect
650 ssm.setSecStructServices(
651 Cache.getDefault(Preferences.USE_RNAVIEW, false));
655 ssm.setAddTempFacAnnot(false);
656 ssm.setProcessSecondaryStructure(false);
657 ssm.setSecStructServices(false);
661 public void checkForNews()
663 final Desktop me = this;
664 // Thread off the news reader, in case there are connection problems.
665 new Thread(new Runnable()
670 jalview.bin.Console.debug("Starting news thread.");
671 jvnews = new BlogReader(me);
672 showNews.setVisible(true);
673 jalview.bin.Console.debug("Completed news thread.");
678 public void getIdentifiersOrgData()
680 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
681 {// Thread off the identifiers fetcher
682 new Thread(new Runnable()
688 .debug("Downloading data from identifiers.org");
691 UrlDownloadClient.download(IdOrgSettings.getUrl(),
692 IdOrgSettings.getDownloadLocation());
693 } catch (IOException e)
696 .debug("Exception downloading identifiers.org data"
706 protected void showNews_actionPerformed(ActionEvent e)
708 showNews(showNews.isSelected());
711 void showNews(boolean visible)
713 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
714 showNews.setSelected(visible);
715 if (visible && !jvnews.isVisible())
717 new Thread(new Runnable()
722 long now = System.currentTimeMillis();
723 Desktop.instance.setProgressBar(
724 MessageManager.getString("status.refreshing_news"), now);
725 jvnews.refreshNews();
726 Desktop.instance.setProgressBar(null, now);
734 * recover the last known dimensions for a jalview window
737 * - empty string is desktop, all other windows have unique prefix
738 * @return null or last known dimensions scaled to current geometry (if last
739 * window geom was known)
741 Rectangle getLastKnownDimensions(String windowName)
743 // TODO: lock aspect ratio for scaling desktop Bug #0058199
744 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
745 String x = Cache.getProperty(windowName + "SCREEN_X");
746 String y = Cache.getProperty(windowName + "SCREEN_Y");
747 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
748 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
749 if ((x != null) && (y != null) && (width != null) && (height != null))
751 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
752 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
753 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
755 // attempt #1 - try to cope with change in screen geometry - this
756 // version doesn't preserve original jv aspect ratio.
757 // take ratio of current screen size vs original screen size.
758 double sw = ((1f * screenSize.width) / (1f * Integer
759 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
760 double sh = ((1f * screenSize.height) / (1f * Integer
761 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
762 // rescale the bounds depending upon the current screen geometry.
763 ix = (int) (ix * sw);
764 iw = (int) (iw * sw);
765 iy = (int) (iy * sh);
766 ih = (int) (ih * sh);
767 while (ix >= screenSize.width)
769 jalview.bin.Console.debug(
770 "Window geometry location recall error: shifting horizontal to within screenbounds.");
771 ix -= screenSize.width;
773 while (iy >= screenSize.height)
775 jalview.bin.Console.debug(
776 "Window geometry location recall error: shifting vertical to within screenbounds.");
777 iy -= screenSize.height;
779 jalview.bin.Console.debug(
780 "Got last known dimensions for " + windowName + ": x:" + ix
781 + " y:" + iy + " width:" + iw + " height:" + ih);
783 // return dimensions for new instance
784 return new Rectangle(ix, iy, iw, ih);
789 void showPasteMenu(int x, int y)
791 JPopupMenu popup = new JPopupMenu();
792 JMenuItem item = new JMenuItem(
793 MessageManager.getString("label.paste_new_window"));
794 item.addActionListener(new ActionListener()
797 public void actionPerformed(ActionEvent evt)
804 popup.show(this, x, y);
811 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
812 Transferable contents = c.getContents(this);
814 if (contents != null)
816 String file = (String) contents
817 .getTransferData(DataFlavor.stringFlavor);
819 FileFormatI format = new IdentifyFile().identify(file,
820 DataSourceType.PASTE);
822 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
825 } catch (Exception ex)
828 "Unable to paste alignment from system clipboard:\n" + ex);
833 * Adds and opens the given frame to the desktop
844 public static synchronized void addInternalFrame(
845 final JInternalFrame frame, String title, int w, int h)
847 addInternalFrame(frame, title, true, w, h, true, false);
851 * Add an internal frame to the Jalview desktop
858 * When true, display frame immediately, otherwise, caller must call
859 * setVisible themselves.
865 public static synchronized void addInternalFrame(
866 final JInternalFrame frame, String title, boolean makeVisible,
869 addInternalFrame(frame, title, makeVisible, w, h, true, false);
873 * Add an internal frame to the Jalview desktop and make it visible
886 public static synchronized void addInternalFrame(
887 final JInternalFrame frame, String title, int w, int h,
890 addInternalFrame(frame, title, true, w, h, resizable, false);
894 * Add an internal frame to the Jalview desktop
901 * When true, display frame immediately, otherwise, caller must call
902 * setVisible themselves.
909 * @param ignoreMinSize
910 * Do not set the default minimum size for frame
912 public static synchronized void addInternalFrame(
913 final JInternalFrame frame, String title, boolean makeVisible,
914 int w, int h, boolean resizable, boolean ignoreMinSize)
917 // TODO: allow callers to determine X and Y position of frame (eg. via
919 // TODO: consider fixing method to update entries in the window submenu with
920 // the current window title
922 frame.setTitle(title);
923 if (frame.getWidth() < 1 || frame.getHeight() < 1)
927 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
928 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
929 // IF JALVIEW IS RUNNING HEADLESS
930 // ///////////////////////////////////////////////
931 if (instance == null || (System.getProperty("java.awt.headless") != null
932 && System.getProperty("java.awt.headless").equals("true")))
941 frame.setMinimumSize(
942 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
944 // Set default dimension for Alignment Frame window.
945 // The Alignment Frame window could be added from a number of places,
947 // I did this here in order not to miss out on any Alignment frame.
948 if (frame instanceof AlignFrame)
950 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
951 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
955 frame.setVisible(makeVisible);
956 frame.setClosable(true);
957 frame.setResizable(resizable);
958 frame.setMaximizable(resizable);
959 frame.setIconifiable(resizable);
960 frame.setOpaque(Platform.isJS());
962 if (frame.getX() < 1 && frame.getY() < 1)
964 frame.setLocation(xOffset * openFrameCount,
965 yOffset * ((openFrameCount - 1) % 10) + yOffset);
969 * add an entry for the new frame in the Window menu (and remove it when the
972 final JMenuItem menuItem = new JMenuItem(title);
973 frame.addInternalFrameListener(new InternalFrameAdapter()
976 public void internalFrameActivated(InternalFrameEvent evt)
978 JInternalFrame itf = desktop.getSelectedFrame();
981 if (itf instanceof AlignFrame)
983 Jalview.setCurrentAlignFrame((AlignFrame) itf);
990 public void internalFrameClosed(InternalFrameEvent evt)
992 PaintRefresher.RemoveComponent(frame);
995 * defensive check to prevent frames being added half off the window
997 if (openFrameCount > 0)
1003 * ensure no reference to alignFrame retained by menu item listener
1005 if (menuItem.getActionListeners().length > 0)
1007 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1009 windowMenu.remove(menuItem);
1013 menuItem.addActionListener(new ActionListener()
1016 public void actionPerformed(ActionEvent e)
1020 frame.setSelected(true);
1021 frame.setIcon(false);
1022 } catch (java.beans.PropertyVetoException ex)
1029 setKeyBindings(frame);
1033 windowMenu.add(menuItem);
1038 frame.setSelected(true);
1039 frame.requestFocus();
1040 } catch (java.beans.PropertyVetoException ve)
1042 } catch (java.lang.ClassCastException cex)
1044 jalview.bin.Console.warn(
1045 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1051 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1056 private static void setKeyBindings(JInternalFrame frame)
1058 @SuppressWarnings("serial")
1059 final Action closeAction = new AbstractAction()
1062 public void actionPerformed(ActionEvent e)
1069 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1071 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1072 InputEvent.CTRL_DOWN_MASK);
1073 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1074 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1076 InputMap inputMap = frame
1077 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1078 String ctrlW = ctrlWKey.toString();
1079 inputMap.put(ctrlWKey, ctrlW);
1080 inputMap.put(cmdWKey, ctrlW);
1082 ActionMap actionMap = frame.getActionMap();
1083 actionMap.put(ctrlW, closeAction);
1087 public void lostOwnership(Clipboard clipboard, Transferable contents)
1091 Desktop.jalviewClipboard = null;
1094 internalCopy = false;
1098 public void dragEnter(DropTargetDragEvent evt)
1103 public void dragExit(DropTargetEvent evt)
1108 public void dragOver(DropTargetDragEvent evt)
1113 public void dropActionChanged(DropTargetDragEvent evt)
1124 public void drop(DropTargetDropEvent evt)
1126 boolean success = true;
1127 // JAL-1552 - acceptDrop required before getTransferable call for
1128 // Java's Transferable for native dnd
1129 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1130 Transferable t = evt.getTransferable();
1131 List<Object> files = new ArrayList<>();
1132 List<DataSourceType> protocols = new ArrayList<>();
1136 Desktop.transferFromDropTarget(files, protocols, evt, t);
1137 } catch (Exception e)
1139 e.printStackTrace();
1147 for (int i = 0; i < files.size(); i++)
1149 // BH 2018 File or String
1150 Object file = files.get(i);
1151 String fileName = file.toString();
1152 DataSourceType protocol = (protocols == null)
1153 ? DataSourceType.FILE
1155 FileFormatI format = null;
1157 if (fileName.endsWith(".jar"))
1159 format = FileFormat.Jalview;
1164 format = new IdentifyFile().identify(file, protocol);
1166 if (file instanceof File)
1168 Platform.cacheFileData((File) file);
1170 new FileLoader().LoadFile(null, file, protocol, format);
1173 } catch (Exception ex)
1178 evt.dropComplete(success); // need this to ensure input focus is properly
1179 // transfered to any new windows created
1189 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1191 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1192 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1193 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1194 BackupFiles.getEnabled());
1196 chooser.setFileView(new JalviewFileView());
1197 chooser.setDialogTitle(
1198 MessageManager.getString("label.open_local_file"));
1199 chooser.setToolTipText(MessageManager.getString("action.open"));
1201 chooser.setResponseHandler(0, new Runnable()
1206 File selectedFile = chooser.getSelectedFile();
1207 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1209 FileFormatI format = chooser.getSelectedFormat();
1212 * Call IdentifyFile to verify the file contains what its extension implies.
1213 * Skip this step for dynamically added file formats, because IdentifyFile does
1214 * not know how to recognise them.
1216 if (FileFormats.getInstance().isIdentifiable(format))
1220 format = new IdentifyFile().identify(selectedFile,
1221 DataSourceType.FILE);
1222 } catch (FileFormatException e)
1224 // format = null; //??
1228 new FileLoader().LoadFile(viewport, selectedFile,
1229 DataSourceType.FILE, format);
1232 chooser.showOpenDialog(this);
1236 * Shows a dialog for input of a URL at which to retrieve alignment data
1241 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1243 // This construct allows us to have a wider textfield
1245 JLabel label = new JLabel(
1246 MessageManager.getString("label.input_file_url"));
1248 JPanel panel = new JPanel(new GridLayout(2, 1));
1252 * the URL to fetch is input in Java: an editable combobox with history JS:
1253 * (pending JAL-3038) a plain text field
1256 String urlBase = "https://www.";
1257 if (Platform.isJS())
1259 history = new JTextField(urlBase, 35);
1268 JComboBox<String> asCombo = new JComboBox<>();
1269 asCombo.setPreferredSize(new Dimension(400, 20));
1270 asCombo.setEditable(true);
1271 asCombo.addItem(urlBase);
1272 String historyItems = Cache.getProperty("RECENT_URL");
1273 if (historyItems != null)
1275 for (String token : historyItems.split("\\t"))
1277 asCombo.addItem(token);
1284 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1285 MessageManager.getString("action.cancel") };
1286 Runnable action = new Runnable()
1291 @SuppressWarnings("unchecked")
1292 String url = (history instanceof JTextField
1293 ? ((JTextField) history).getText()
1294 : ((JComboBox<String>) history).getEditor().getItem()
1295 .toString().trim());
1297 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1299 if (viewport != null)
1301 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1302 FileFormat.Jalview);
1306 new FileLoader().LoadFile(url, DataSourceType.URL,
1307 FileFormat.Jalview);
1312 FileFormatI format = null;
1315 format = new IdentifyFile().identify(url, DataSourceType.URL);
1316 } catch (FileFormatException e)
1318 // TODO revise error handling, distinguish between
1319 // URL not found and response not valid
1324 String msg = MessageManager
1325 .formatMessage("label.couldnt_locate", url);
1326 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1327 MessageManager.getString("label.url_not_found"),
1328 JvOptionPane.WARNING_MESSAGE);
1333 if (viewport != null)
1335 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1340 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1345 String dialogOption = MessageManager
1346 .getString("label.input_alignment_from_url");
1347 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1348 .showInternalDialog(panel, dialogOption,
1349 JvOptionPane.YES_NO_CANCEL_OPTION,
1350 JvOptionPane.PLAIN_MESSAGE, null, options,
1351 MessageManager.getString("action.ok"));
1355 * Opens the CutAndPaste window for the user to paste an alignment in to
1358 * - if not null, the pasted alignment is added to the current
1359 * alignment; if null, to a new alignment window
1362 public void inputTextboxMenuItem_actionPerformed(
1363 AlignmentViewPanel viewPanel)
1365 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1366 cap.setForInput(viewPanel);
1367 Desktop.addInternalFrame(cap,
1368 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1378 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1379 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1380 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1381 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1382 getWidth(), getHeight()));
1384 if (jconsole != null)
1386 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1387 jconsole.stopConsole();
1391 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1394 if (dialogExecutor != null)
1396 dialogExecutor.shutdownNow();
1398 closeAll_actionPerformed(null);
1400 if (groovyConsole != null)
1402 // suppress a possible repeat prompt to save script
1403 groovyConsole.setDirty(false);
1404 groovyConsole.exit();
1409 private void storeLastKnownDimensions(String string, Rectangle jc)
1411 jalview.bin.Console.debug("Storing last known dimensions for " + string
1412 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1413 + " height:" + jc.height);
1415 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1416 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1417 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1418 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1428 public void aboutMenuItem_actionPerformed(ActionEvent e)
1430 new Thread(new Runnable()
1435 new SplashScreen(false);
1441 * Returns the html text for the About screen, including any available version
1442 * number, build details, author details and citation reference, but without
1443 * the enclosing {@code html} tags
1447 public String getAboutMessage()
1449 StringBuilder message = new StringBuilder(1024);
1450 message.append("<div style=\"font-family: sans-serif;\">")
1451 .append("<h1><strong>Version: ")
1452 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1453 .append("<strong>Built: <em>")
1454 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1455 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1456 .append("</strong>");
1458 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1459 if (latestVersion.equals("Checking"))
1461 // JBP removed this message for 2.11: May be reinstated in future version
1462 // message.append("<br>...Checking latest version...</br>");
1464 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1466 boolean red = false;
1467 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1468 .indexOf("automated build") == -1)
1471 // Displayed when code version and jnlp version do not match and code
1472 // version is not a development build
1473 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1476 message.append("<br>!! Version ")
1477 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1478 .append(" is available for download from ")
1479 .append(Cache.getDefault("www.jalview.org",
1480 "https://www.jalview.org"))
1484 message.append("</div>");
1487 message.append("<br>Authors: ");
1488 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1489 message.append(CITATION);
1491 message.append("</div>");
1493 return message.toString();
1497 * Action on requesting Help documentation
1500 public void documentationMenuItem_actionPerformed()
1504 if (Platform.isJS())
1506 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1515 Help.showHelpWindow();
1517 } catch (Exception ex)
1519 System.err.println("Error opening help: " + ex.getMessage());
1524 public void closeAll_actionPerformed(ActionEvent e)
1526 // TODO show a progress bar while closing?
1527 JInternalFrame[] frames = desktop.getAllFrames();
1528 for (int i = 0; i < frames.length; i++)
1532 frames[i].setClosed(true);
1533 } catch (java.beans.PropertyVetoException ex)
1537 Jalview.setCurrentAlignFrame(null);
1538 System.out.println("ALL CLOSED");
1541 * reset state of singleton objects as appropriate (clear down session state
1542 * when all windows are closed)
1544 StructureSelectionManager ssm = StructureSelectionManager
1545 .getStructureSelectionManager(this);
1553 public void raiseRelated_actionPerformed(ActionEvent e)
1555 reorderAssociatedWindows(false, false);
1559 public void minimizeAssociated_actionPerformed(ActionEvent e)
1561 reorderAssociatedWindows(true, false);
1564 void closeAssociatedWindows()
1566 reorderAssociatedWindows(false, true);
1572 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1576 protected void garbageCollect_actionPerformed(ActionEvent e)
1578 // We simply collect the garbage
1579 jalview.bin.Console.debug("Collecting garbage...");
1581 jalview.bin.Console.debug("Finished garbage collection.");
1587 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1591 protected void showMemusage_actionPerformed(ActionEvent e)
1593 desktop.showMemoryUsage(showMemusage.isSelected());
1600 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1604 protected void showConsole_actionPerformed(ActionEvent e)
1606 showConsole(showConsole.isSelected());
1609 Console jconsole = null;
1612 * control whether the java console is visible or not
1616 void showConsole(boolean selected)
1618 // TODO: decide if we should update properties file
1619 if (jconsole != null) // BH 2018
1621 showConsole.setSelected(selected);
1622 Cache.setProperty("SHOW_JAVA_CONSOLE",
1623 Boolean.valueOf(selected).toString());
1624 jconsole.setVisible(selected);
1628 void reorderAssociatedWindows(boolean minimize, boolean close)
1630 JInternalFrame[] frames = desktop.getAllFrames();
1631 if (frames == null || frames.length < 1)
1636 AlignmentViewport source = null, target = null;
1637 if (frames[0] instanceof AlignFrame)
1639 source = ((AlignFrame) frames[0]).getCurrentView();
1641 else if (frames[0] instanceof TreePanel)
1643 source = ((TreePanel) frames[0]).getViewPort();
1645 else if (frames[0] instanceof PCAPanel)
1647 source = ((PCAPanel) frames[0]).av;
1649 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1651 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1656 for (int i = 0; i < frames.length; i++)
1659 if (frames[i] == null)
1663 if (frames[i] instanceof AlignFrame)
1665 target = ((AlignFrame) frames[i]).getCurrentView();
1667 else if (frames[i] instanceof TreePanel)
1669 target = ((TreePanel) frames[i]).getViewPort();
1671 else if (frames[i] instanceof PCAPanel)
1673 target = ((PCAPanel) frames[i]).av;
1675 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1677 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1680 if (source == target)
1686 frames[i].setClosed(true);
1690 frames[i].setIcon(minimize);
1693 frames[i].toFront();
1697 } catch (java.beans.PropertyVetoException ex)
1712 protected void preferences_actionPerformed(ActionEvent e)
1714 Preferences.openPreferences();
1718 * Prompts the user to choose a file and then saves the Jalview state as a
1719 * Jalview project file
1722 public void saveState_actionPerformed()
1724 saveState_actionPerformed(false);
1727 public void saveState_actionPerformed(boolean saveAs)
1729 java.io.File projectFile = getProjectFile();
1730 // autoSave indicates we already have a file and don't need to ask
1731 boolean autoSave = projectFile != null && !saveAs
1732 && BackupFiles.getEnabled();
1734 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1735 // saveAs="+saveAs+", Backups
1736 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1738 boolean approveSave = false;
1741 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1744 chooser.setFileView(new JalviewFileView());
1745 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1747 int value = chooser.showSaveDialog(this);
1749 if (value == JalviewFileChooser.APPROVE_OPTION)
1751 projectFile = chooser.getSelectedFile();
1752 setProjectFile(projectFile);
1757 if (approveSave || autoSave)
1759 final Desktop me = this;
1760 final java.io.File chosenFile = projectFile;
1761 new Thread(new Runnable()
1766 // TODO: refactor to Jalview desktop session controller action.
1767 setProgressBar(MessageManager.formatMessage(
1768 "label.saving_jalview_project", new Object[]
1769 { chosenFile.getName() }), chosenFile.hashCode());
1770 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1771 // TODO catch and handle errors for savestate
1772 // TODO prevent user from messing with the Desktop whilst we're saving
1775 boolean doBackup = BackupFiles.getEnabled();
1776 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1779 new Jalview2XML().saveState(
1780 doBackup ? backupfiles.getTempFile() : chosenFile);
1784 backupfiles.setWriteSuccess(true);
1785 backupfiles.rollBackupsAndRenameTempFile();
1787 } catch (OutOfMemoryError oom)
1789 new OOMWarning("Whilst saving current state to "
1790 + chosenFile.getName(), oom);
1791 } catch (Exception ex)
1793 jalview.bin.Console.error("Problems whilst trying to save to "
1794 + chosenFile.getName(), ex);
1795 JvOptionPane.showMessageDialog(me,
1796 MessageManager.formatMessage(
1797 "label.error_whilst_saving_current_state_to",
1799 { chosenFile.getName() }),
1800 MessageManager.getString("label.couldnt_save_project"),
1801 JvOptionPane.WARNING_MESSAGE);
1803 setProgressBar(null, chosenFile.hashCode());
1810 public void saveAsState_actionPerformed(ActionEvent e)
1812 saveState_actionPerformed(true);
1815 private void setProjectFile(File choice)
1817 this.projectFile = choice;
1820 public File getProjectFile()
1822 return this.projectFile;
1826 * Shows a file chooser dialog and tries to read in the selected file as a
1830 public void loadState_actionPerformed()
1832 final String[] suffix = new String[] { "jvp", "jar" };
1833 final String[] desc = new String[] { "Jalview Project",
1834 "Jalview Project (old)" };
1835 JalviewFileChooser chooser = new JalviewFileChooser(
1836 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1837 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1841 chooser.setFileView(new JalviewFileView());
1842 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1843 chooser.setResponseHandler(0, new Runnable()
1848 File selectedFile = chooser.getSelectedFile();
1849 setProjectFile(selectedFile);
1850 String choice = selectedFile.getAbsolutePath();
1851 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1852 new Thread(new Runnable()
1859 new Jalview2XML().loadJalviewAlign(selectedFile);
1860 } catch (OutOfMemoryError oom)
1862 new OOMWarning("Whilst loading project from " + choice, oom);
1863 } catch (Exception ex)
1865 jalview.bin.Console.error(
1866 "Problems whilst loading project from " + choice, ex);
1867 JvOptionPane.showMessageDialog(Desktop.desktop,
1868 MessageManager.formatMessage(
1869 "label.error_whilst_loading_project_from",
1873 .getString("label.couldnt_load_project"),
1874 JvOptionPane.WARNING_MESSAGE);
1877 }, "Project Loader").start();
1881 chooser.showOpenDialog(this);
1885 public void inputSequence_actionPerformed(ActionEvent e)
1887 new SequenceFetcher(this);
1890 JPanel progressPanel;
1892 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1894 public void startLoading(final Object fileName)
1896 if (fileLoadingCount == 0)
1898 fileLoadingPanels.add(addProgressPanel(MessageManager
1899 .formatMessage("label.loading_file", new Object[]
1905 private JPanel addProgressPanel(String string)
1907 if (progressPanel == null)
1909 progressPanel = new JPanel(new GridLayout(1, 1));
1910 totalProgressCount = 0;
1911 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1913 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1914 JProgressBar progressBar = new JProgressBar();
1915 progressBar.setIndeterminate(true);
1917 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1919 thisprogress.add(progressBar, BorderLayout.CENTER);
1920 progressPanel.add(thisprogress);
1921 ((GridLayout) progressPanel.getLayout()).setRows(
1922 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1923 ++totalProgressCount;
1924 instance.validate();
1925 return thisprogress;
1928 int totalProgressCount = 0;
1930 private void removeProgressPanel(JPanel progbar)
1932 if (progressPanel != null)
1934 synchronized (progressPanel)
1936 progressPanel.remove(progbar);
1937 GridLayout gl = (GridLayout) progressPanel.getLayout();
1938 gl.setRows(gl.getRows() - 1);
1939 if (--totalProgressCount < 1)
1941 this.getContentPane().remove(progressPanel);
1942 progressPanel = null;
1949 public void stopLoading()
1952 if (fileLoadingCount < 1)
1954 while (fileLoadingPanels.size() > 0)
1956 removeProgressPanel(fileLoadingPanels.remove(0));
1958 fileLoadingPanels.clear();
1959 fileLoadingCount = 0;
1964 public static int getViewCount(String alignmentId)
1966 AlignmentViewport[] aps = getViewports(alignmentId);
1967 return (aps == null) ? 0 : aps.length;
1972 * @param alignmentId
1973 * - if null, all sets are returned
1974 * @return all AlignmentPanels concerning the alignmentId sequence set
1976 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1978 if (Desktop.desktop == null)
1980 // no frames created and in headless mode
1981 // TODO: verify that frames are recoverable when in headless mode
1984 List<AlignmentPanel> aps = new ArrayList<>();
1985 AlignFrame[] frames = getAlignFrames();
1990 for (AlignFrame af : frames)
1992 for (AlignmentPanel ap : af.alignPanels)
1994 if (alignmentId == null
1995 || alignmentId.equals(ap.av.getSequenceSetId()))
2001 if (aps.size() == 0)
2005 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2010 * get all the viewports on an alignment.
2012 * @param sequenceSetId
2013 * unique alignment id (may be null - all viewports returned in that
2015 * @return all viewports on the alignment bound to sequenceSetId
2017 public static AlignmentViewport[] getViewports(String sequenceSetId)
2019 List<AlignmentViewport> viewp = new ArrayList<>();
2020 if (desktop != null)
2022 AlignFrame[] frames = Desktop.getAlignFrames();
2024 for (AlignFrame afr : frames)
2026 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2027 .equals(sequenceSetId))
2029 if (afr.alignPanels != null)
2031 for (AlignmentPanel ap : afr.alignPanels)
2033 if (sequenceSetId == null
2034 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2042 viewp.add(afr.getViewport());
2046 if (viewp.size() > 0)
2048 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2055 * Explode the views in the given frame into separate AlignFrame
2059 public static void explodeViews(AlignFrame af)
2061 int size = af.alignPanels.size();
2067 // FIXME: ideally should use UI interface API
2068 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2069 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2070 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2071 for (int i = 0; i < size; i++)
2073 AlignmentPanel ap = af.alignPanels.get(i);
2075 AlignFrame newaf = new AlignFrame(ap);
2077 // transfer reference for existing feature settings to new alignFrame
2078 if (ap == af.alignPanel)
2080 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2082 newaf.featureSettings = viewFeatureSettings;
2084 newaf.setFeatureSettingsGeometry(fsBounds);
2088 * Restore the view's last exploded frame geometry if known. Multiple views from
2089 * one exploded frame share and restore the same (frame) position and size.
2091 Rectangle geometry = ap.av.getExplodedGeometry();
2092 if (geometry != null)
2094 newaf.setBounds(geometry);
2097 ap.av.setGatherViewsHere(false);
2099 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2100 AlignFrame.DEFAULT_HEIGHT);
2101 // and materialise a new feature settings dialog instance for the new
2103 // (closes the old as if 'OK' was pressed)
2104 if (ap == af.alignPanel && newaf.featureSettings != null
2105 && newaf.featureSettings.isOpen()
2106 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2108 newaf.showFeatureSettingsUI();
2112 af.featureSettings = null;
2113 af.alignPanels.clear();
2114 af.closeMenuItem_actionPerformed(true);
2119 * Gather expanded views (separate AlignFrame's) with the same sequence set
2120 * identifier back in to this frame as additional views, and close the
2121 * expanded views. Note the expanded frames may themselves have multiple
2122 * views. We take the lot.
2126 public void gatherViews(AlignFrame source)
2128 source.viewport.setGatherViewsHere(true);
2129 source.viewport.setExplodedGeometry(source.getBounds());
2130 JInternalFrame[] frames = desktop.getAllFrames();
2131 String viewId = source.viewport.getSequenceSetId();
2132 for (int t = 0; t < frames.length; t++)
2134 if (frames[t] instanceof AlignFrame && frames[t] != source)
2136 AlignFrame af = (AlignFrame) frames[t];
2137 boolean gatherThis = false;
2138 for (int a = 0; a < af.alignPanels.size(); a++)
2140 AlignmentPanel ap = af.alignPanels.get(a);
2141 if (viewId.equals(ap.av.getSequenceSetId()))
2144 ap.av.setGatherViewsHere(false);
2145 ap.av.setExplodedGeometry(af.getBounds());
2146 source.addAlignmentPanel(ap, false);
2152 if (af.featureSettings != null && af.featureSettings.isOpen())
2154 if (source.featureSettings == null)
2156 // preserve the feature settings geometry for this frame
2157 source.featureSettings = af.featureSettings;
2158 source.setFeatureSettingsGeometry(
2159 af.getFeatureSettingsGeometry());
2163 // close it and forget
2164 af.featureSettings.close();
2167 af.alignPanels.clear();
2168 af.closeMenuItem_actionPerformed(true);
2173 // refresh the feature setting UI for the source frame if it exists
2174 if (source.featureSettings != null && source.featureSettings.isOpen())
2176 source.showFeatureSettingsUI();
2181 public JInternalFrame[] getAllFrames()
2183 return desktop.getAllFrames();
2187 * Checks the given url to see if it gives a response indicating that the user
2188 * should be informed of a new questionnaire.
2192 public void checkForQuestionnaire(String url)
2194 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2195 // javax.swing.SwingUtilities.invokeLater(jvq);
2196 new Thread(jvq).start();
2199 public void checkURLLinks()
2201 // Thread off the URL link checker
2202 addDialogThread(new Runnable()
2207 if (Cache.getDefault("CHECKURLLINKS", true))
2209 // check what the actual links are - if it's just the default don't
2210 // bother with the warning
2211 List<String> links = Preferences.sequenceUrlLinks
2214 // only need to check links if there is one with a
2215 // SEQUENCE_ID which is not the default EMBL_EBI link
2216 ListIterator<String> li = links.listIterator();
2217 boolean check = false;
2218 List<JLabel> urls = new ArrayList<>();
2219 while (li.hasNext())
2221 String link = li.next();
2222 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2223 && !UrlConstants.isDefaultString(link))
2226 int barPos = link.indexOf("|");
2227 String urlMsg = barPos == -1 ? link
2228 : link.substring(0, barPos) + ": "
2229 + link.substring(barPos + 1);
2230 urls.add(new JLabel(urlMsg));
2238 // ask user to check in case URL links use old style tokens
2239 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2240 JPanel msgPanel = new JPanel();
2241 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2242 msgPanel.add(Box.createVerticalGlue());
2243 JLabel msg = new JLabel(MessageManager
2244 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2245 JLabel msg2 = new JLabel(MessageManager
2246 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2248 for (JLabel url : urls)
2254 final JCheckBox jcb = new JCheckBox(
2255 MessageManager.getString("label.do_not_display_again"));
2256 jcb.addActionListener(new ActionListener()
2259 public void actionPerformed(ActionEvent e)
2261 // update Cache settings for "don't show this again"
2262 boolean showWarningAgain = !jcb.isSelected();
2263 Cache.setProperty("CHECKURLLINKS",
2264 Boolean.valueOf(showWarningAgain).toString());
2269 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2271 .getString("label.SEQUENCE_ID_no_longer_used"),
2272 JvOptionPane.WARNING_MESSAGE);
2279 * Proxy class for JDesktopPane which optionally displays the current memory
2280 * usage and highlights the desktop area with a red bar if free memory runs
2285 public class MyDesktopPane extends JDesktopPane implements Runnable
2287 private static final float ONE_MB = 1048576f;
2289 boolean showMemoryUsage = false;
2293 java.text.NumberFormat df;
2295 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2298 public MyDesktopPane(boolean showMemoryUsage)
2300 showMemoryUsage(showMemoryUsage);
2303 public void showMemoryUsage(boolean showMemory)
2305 this.showMemoryUsage = showMemory;
2308 Thread worker = new Thread(this);
2314 public boolean isShowMemoryUsage()
2316 return showMemoryUsage;
2322 df = java.text.NumberFormat.getNumberInstance();
2323 df.setMaximumFractionDigits(2);
2324 runtime = Runtime.getRuntime();
2326 while (showMemoryUsage)
2330 maxMemory = runtime.maxMemory() / ONE_MB;
2331 allocatedMemory = runtime.totalMemory() / ONE_MB;
2332 freeMemory = runtime.freeMemory() / ONE_MB;
2333 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2335 percentUsage = (totalFreeMemory / maxMemory) * 100;
2337 // if (percentUsage < 20)
2339 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2341 // instance.set.setBorder(border1);
2344 // sleep after showing usage
2346 } catch (Exception ex)
2348 ex.printStackTrace();
2354 public void paintComponent(Graphics g)
2356 if (showMemoryUsage && g != null && df != null)
2358 if (percentUsage < 20)
2360 g.setColor(Color.red);
2362 FontMetrics fm = g.getFontMetrics();
2365 g.drawString(MessageManager.formatMessage("label.memory_stats",
2367 { df.format(totalFreeMemory), df.format(maxMemory),
2368 df.format(percentUsage) }),
2369 10, getHeight() - fm.getHeight());
2373 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2374 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2379 * Accessor method to quickly get all the AlignmentFrames loaded.
2381 * @return an array of AlignFrame, or null if none found
2383 public static AlignFrame[] getAlignFrames()
2385 if (Jalview.isHeadlessMode())
2387 // Desktop.desktop is null in headless mode
2388 return new AlignFrame[] { Jalview.currentAlignFrame };
2391 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2397 List<AlignFrame> avp = new ArrayList<>();
2399 for (int i = frames.length - 1; i > -1; i--)
2401 if (frames[i] instanceof AlignFrame)
2403 avp.add((AlignFrame) frames[i]);
2405 else if (frames[i] instanceof SplitFrame)
2408 * Also check for a split frame containing an AlignFrame
2410 GSplitFrame sf = (GSplitFrame) frames[i];
2411 if (sf.getTopFrame() instanceof AlignFrame)
2413 avp.add((AlignFrame) sf.getTopFrame());
2415 if (sf.getBottomFrame() instanceof AlignFrame)
2417 avp.add((AlignFrame) sf.getBottomFrame());
2421 if (avp.size() == 0)
2425 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2430 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2434 public GStructureViewer[] getJmols()
2436 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2442 List<GStructureViewer> avp = new ArrayList<>();
2444 for (int i = frames.length - 1; i > -1; i--)
2446 if (frames[i] instanceof AppJmol)
2448 GStructureViewer af = (GStructureViewer) frames[i];
2452 if (avp.size() == 0)
2456 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2461 * Add Groovy Support to Jalview
2464 public void groovyShell_actionPerformed()
2468 openGroovyConsole();
2469 } catch (Exception ex)
2471 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2472 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2474 MessageManager.getString("label.couldnt_create_groovy_shell"),
2475 MessageManager.getString("label.groovy_support_failed"),
2476 JvOptionPane.ERROR_MESSAGE);
2481 * Open the Groovy console
2483 void openGroovyConsole()
2485 if (groovyConsole == null)
2487 groovyConsole = new groovy.ui.Console();
2488 groovyConsole.setVariable("Jalview", this);
2489 groovyConsole.run();
2492 * We allow only one console at a time, so that AlignFrame menu option
2493 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2494 * enable 'Run script', when the console is opened, and the reverse when it is
2497 Window window = (Window) groovyConsole.getFrame();
2498 window.addWindowListener(new WindowAdapter()
2501 public void windowClosed(WindowEvent e)
2504 * rebind CMD-Q from Groovy Console to Jalview Quit
2507 enableExecuteGroovy(false);
2513 * show Groovy console window (after close and reopen)
2515 ((Window) groovyConsole.getFrame()).setVisible(true);
2518 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2519 * opening a second console
2521 enableExecuteGroovy(true);
2525 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2526 * binding when opened
2528 protected void addQuitHandler()
2531 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2533 .getKeyStroke(KeyEvent.VK_Q,
2534 jalview.util.ShortcutKeyMaskExWrapper
2535 .getMenuShortcutKeyMaskEx()),
2537 getRootPane().getActionMap().put("Quit", new AbstractAction()
2540 public void actionPerformed(ActionEvent e)
2548 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2551 * true if Groovy console is open
2553 public void enableExecuteGroovy(boolean enabled)
2556 * disable opening a second Groovy console (or re-enable when the console is
2559 groovyShell.setEnabled(!enabled);
2561 AlignFrame[] alignFrames = getAlignFrames();
2562 if (alignFrames != null)
2564 for (AlignFrame af : alignFrames)
2566 af.setGroovyEnabled(enabled);
2572 * Progress bars managed by the IProgressIndicator method.
2574 private Hashtable<Long, JPanel> progressBars;
2576 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2581 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2584 public void setProgressBar(String message, long id)
2586 if (progressBars == null)
2588 progressBars = new Hashtable<>();
2589 progressBarHandlers = new Hashtable<>();
2592 if (progressBars.get(Long.valueOf(id)) != null)
2594 JPanel panel = progressBars.remove(Long.valueOf(id));
2595 if (progressBarHandlers.contains(Long.valueOf(id)))
2597 progressBarHandlers.remove(Long.valueOf(id));
2599 removeProgressPanel(panel);
2603 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2610 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2611 * jalview.gui.IProgressIndicatorHandler)
2614 public void registerHandler(final long id,
2615 final IProgressIndicatorHandler handler)
2617 if (progressBarHandlers == null
2618 || !progressBars.containsKey(Long.valueOf(id)))
2620 throw new Error(MessageManager.getString(
2621 "error.call_setprogressbar_before_registering_handler"));
2623 progressBarHandlers.put(Long.valueOf(id), handler);
2624 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2625 if (handler.canCancel())
2627 JButton cancel = new JButton(
2628 MessageManager.getString("action.cancel"));
2629 final IProgressIndicator us = this;
2630 cancel.addActionListener(new ActionListener()
2634 public void actionPerformed(ActionEvent e)
2636 handler.cancelActivity(id);
2637 us.setProgressBar(MessageManager
2638 .formatMessage("label.cancelled_params", new Object[]
2639 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2643 progressPanel.add(cancel, BorderLayout.EAST);
2649 * @return true if any progress bars are still active
2652 public boolean operationInProgress()
2654 if (progressBars != null && progressBars.size() > 0)
2662 * This will return the first AlignFrame holding the given viewport instance.
2663 * It will break if there are more than one AlignFrames viewing a particular
2667 * @return alignFrame for viewport
2669 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2671 if (desktop != null)
2673 AlignmentPanel[] aps = getAlignmentPanels(
2674 viewport.getSequenceSetId());
2675 for (int panel = 0; aps != null && panel < aps.length; panel++)
2677 if (aps[panel] != null && aps[panel].av == viewport)
2679 return aps[panel].alignFrame;
2686 public VamsasApplication getVamsasApplication()
2688 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2694 * flag set if jalview GUI is being operated programmatically
2696 private boolean inBatchMode = false;
2699 * check if jalview GUI is being operated programmatically
2701 * @return inBatchMode
2703 public boolean isInBatchMode()
2709 * set flag if jalview GUI is being operated programmatically
2711 * @param inBatchMode
2713 public void setInBatchMode(boolean inBatchMode)
2715 this.inBatchMode = inBatchMode;
2719 * start service discovery and wait till it is done
2721 public void startServiceDiscovery()
2723 startServiceDiscovery(false);
2727 * start service discovery threads - blocking or non-blocking
2731 public void startServiceDiscovery(boolean blocking)
2733 startServiceDiscovery(blocking, false);
2737 * start service discovery threads
2740 * - false means call returns immediately
2741 * @param ignore_SHOW_JWS2_SERVICES_preference
2742 * - when true JABA services are discovered regardless of user's JWS2
2743 * discovery preference setting
2745 public void startServiceDiscovery(boolean blocking,
2746 boolean ignore_SHOW_JWS2_SERVICES_preference)
2748 boolean alive = true;
2749 Thread t0 = null, t1 = null, t2 = null;
2750 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2753 // todo: changesupport handlers need to be transferred
2754 if (discoverer == null)
2756 discoverer = new jalview.ws.jws1.Discoverer();
2757 // register PCS handler for desktop.
2758 discoverer.addPropertyChangeListener(changeSupport);
2760 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2761 // until we phase out completely
2762 (t0 = new Thread(discoverer)).start();
2765 if (ignore_SHOW_JWS2_SERVICES_preference
2766 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2768 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2769 .startDiscoverer(changeSupport);
2773 // TODO: do rest service discovery
2782 } catch (Exception e)
2785 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2786 || (t3 != null && t3.isAlive())
2787 || (t0 != null && t0.isAlive());
2793 * called to check if the service discovery process completed successfully.
2797 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2799 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2801 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2802 .getErrorMessages();
2805 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2807 if (serviceChangedDialog == null)
2809 // only run if we aren't already displaying one of these.
2810 addDialogThread(serviceChangedDialog = new Runnable()
2817 * JalviewDialog jd =new JalviewDialog() {
2819 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2821 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2823 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2825 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2827 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2828 * + " or mis-configured HTTP proxy settings.<br/>" +
2829 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2830 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2831 * true, true, "Web Service Configuration Problem", 450, 400);
2833 * jd.waitForInput();
2835 JvOptionPane.showConfirmDialog(Desktop.desktop,
2836 new JLabel("<html><table width=\"450\"><tr><td>"
2837 + ermsg + "</td></tr></table>"
2838 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2839 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2840 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2841 + " Tools->Preferences dialog box to change them.</p></html>"),
2842 "Web Service Configuration Problem",
2843 JvOptionPane.DEFAULT_OPTION,
2844 JvOptionPane.ERROR_MESSAGE);
2845 serviceChangedDialog = null;
2853 jalview.bin.Console.error(
2854 "Errors reported by JABA discovery service. Check web services preferences.\n"
2861 private Runnable serviceChangedDialog = null;
2864 * start a thread to open a URL in the configured browser. Pops up a warning
2865 * dialog to the user if there is an exception when calling out to the browser
2870 public static void showUrl(final String url)
2872 showUrl(url, Desktop.instance);
2876 * Like showUrl but allows progress handler to be specified
2880 * (null) or object implementing IProgressIndicator
2882 public static void showUrl(final String url,
2883 final IProgressIndicator progress)
2885 new Thread(new Runnable()
2892 if (progress != null)
2894 progress.setProgressBar(MessageManager
2895 .formatMessage("status.opening_params", new Object[]
2896 { url }), this.hashCode());
2898 jalview.util.BrowserLauncher.openURL(url);
2899 } catch (Exception ex)
2901 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2903 .getString("label.web_browser_not_found_unix"),
2904 MessageManager.getString("label.web_browser_not_found"),
2905 JvOptionPane.WARNING_MESSAGE);
2907 ex.printStackTrace();
2909 if (progress != null)
2911 progress.setProgressBar(null, this.hashCode());
2917 public static WsParamSetManager wsparamManager = null;
2919 public static ParamManager getUserParameterStore()
2921 if (wsparamManager == null)
2923 wsparamManager = new WsParamSetManager();
2925 return wsparamManager;
2929 * static hyperlink handler proxy method for use by Jalview's internal windows
2933 public static void hyperlinkUpdate(HyperlinkEvent e)
2935 if (e.getEventType() == EventType.ACTIVATED)
2940 url = e.getURL().toString();
2941 Desktop.showUrl(url);
2942 } catch (Exception x)
2947 .error("Couldn't handle string " + url + " as a URL.");
2949 // ignore any exceptions due to dud links.
2956 * single thread that handles display of dialogs to user.
2958 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2961 * flag indicating if dialogExecutor should try to acquire a permit
2963 private volatile boolean dialogPause = true;
2968 private java.util.concurrent.Semaphore block = new Semaphore(0);
2970 private static groovy.ui.Console groovyConsole;
2973 * add another dialog thread to the queue
2977 public void addDialogThread(final Runnable prompter)
2979 dialogExecutor.submit(new Runnable()
2989 } catch (InterruptedException x)
2993 if (instance == null)
2999 SwingUtilities.invokeAndWait(prompter);
3000 } catch (Exception q)
3002 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3009 public void startDialogQueue()
3011 // set the flag so we don't pause waiting for another permit and semaphore
3012 // the current task to begin
3013 dialogPause = false;
3018 * Outputs an image of the desktop to file in EPS format, after prompting the
3019 * user for choice of Text or Lineart character rendering (unless a preference
3020 * has been set). The file name is generated as
3023 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3027 protected void snapShotWindow_actionPerformed(ActionEvent e)
3029 // currently the menu option to do this is not shown
3032 int width = getWidth();
3033 int height = getHeight();
3035 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3036 ImageWriterI writer = new ImageWriterI()
3039 public void exportImage(Graphics g) throws Exception
3042 jalview.bin.Console.info("Successfully written snapshot to file "
3043 + of.getAbsolutePath());
3046 String title = "View of desktop";
3047 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3049 exporter.doExport(of, this, width, height, title);
3053 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3054 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3055 * and location last time the view was expanded (if any). However it does not
3056 * remember the split pane divider location - this is set to match the
3057 * 'exploding' frame.
3061 public void explodeViews(SplitFrame sf)
3063 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3064 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3065 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3067 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3069 int viewCount = topPanels.size();
3076 * Processing in reverse order works, forwards order leaves the first panels not
3077 * visible. I don't know why!
3079 for (int i = viewCount - 1; i >= 0; i--)
3082 * Make new top and bottom frames. These take over the respective AlignmentPanel
3083 * objects, including their AlignmentViewports, so the cdna/protein
3084 * relationships between the viewports is carried over to the new split frames.
3086 * explodedGeometry holds the (x, y) position of the previously exploded
3087 * SplitFrame, and the (width, height) of the AlignFrame component
3089 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3090 AlignFrame newTopFrame = new AlignFrame(topPanel);
3091 newTopFrame.setSize(oldTopFrame.getSize());
3092 newTopFrame.setVisible(true);
3093 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3094 .getExplodedGeometry();
3095 if (geometry != null)
3097 newTopFrame.setSize(geometry.getSize());
3100 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3101 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3102 newBottomFrame.setSize(oldBottomFrame.getSize());
3103 newBottomFrame.setVisible(true);
3104 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3105 .getExplodedGeometry();
3106 if (geometry != null)
3108 newBottomFrame.setSize(geometry.getSize());
3111 topPanel.av.setGatherViewsHere(false);
3112 bottomPanel.av.setGatherViewsHere(false);
3113 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3115 if (geometry != null)
3117 splitFrame.setLocation(geometry.getLocation());
3119 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3123 * Clear references to the panels (now relocated in the new SplitFrames) before
3124 * closing the old SplitFrame.
3127 bottomPanels.clear();
3132 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3133 * back into the given SplitFrame as additional views. Note that the gathered
3134 * frames may themselves have multiple views.
3138 public void gatherViews(GSplitFrame source)
3141 * special handling of explodedGeometry for a view within a SplitFrame: - it
3142 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3143 * height) of the AlignFrame component
3145 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3146 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3147 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3148 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3149 myBottomFrame.viewport
3150 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3151 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3152 myTopFrame.viewport.setGatherViewsHere(true);
3153 myBottomFrame.viewport.setGatherViewsHere(true);
3154 String topViewId = myTopFrame.viewport.getSequenceSetId();
3155 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3157 JInternalFrame[] frames = desktop.getAllFrames();
3158 for (JInternalFrame frame : frames)
3160 if (frame instanceof SplitFrame && frame != source)
3162 SplitFrame sf = (SplitFrame) frame;
3163 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3164 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3165 boolean gatherThis = false;
3166 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3168 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3169 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3170 if (topViewId.equals(topPanel.av.getSequenceSetId())
3171 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3174 topPanel.av.setGatherViewsHere(false);
3175 bottomPanel.av.setGatherViewsHere(false);
3176 topPanel.av.setExplodedGeometry(
3177 new Rectangle(sf.getLocation(), topFrame.getSize()));
3178 bottomPanel.av.setExplodedGeometry(
3179 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3180 myTopFrame.addAlignmentPanel(topPanel, false);
3181 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3187 topFrame.getAlignPanels().clear();
3188 bottomFrame.getAlignPanels().clear();
3195 * The dust settles...give focus to the tab we did this from.
3197 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3200 public static groovy.ui.Console getGroovyConsole()
3202 return groovyConsole;
3206 * handles the payload of a drag and drop event.
3208 * TODO refactor to desktop utilities class
3211 * - Data source strings extracted from the drop event
3213 * - protocol for each data source extracted from the drop event
3217 * - the payload from the drop event
3220 public static void transferFromDropTarget(List<Object> files,
3221 List<DataSourceType> protocols, DropTargetDropEvent evt,
3222 Transferable t) throws Exception
3225 DataFlavor uriListFlavor = new DataFlavor(
3226 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3229 urlFlavour = new DataFlavor(
3230 "application/x-java-url; class=java.net.URL");
3231 } catch (ClassNotFoundException cfe)
3233 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3237 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3242 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3243 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3244 // means url may be null.
3247 protocols.add(DataSourceType.URL);
3248 files.add(url.toString());
3249 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3250 + files.get(files.size() - 1));
3255 if (Platform.isAMacAndNotJS())
3258 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3261 } catch (Throwable ex)
3263 jalview.bin.Console.debug("URL drop handler failed.", ex);
3266 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3268 // Works on Windows and MacOSX
3269 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3270 for (Object file : (List) t
3271 .getTransferData(DataFlavor.javaFileListFlavor))
3274 protocols.add(DataSourceType.FILE);
3279 // Unix like behaviour
3280 boolean added = false;
3282 if (t.isDataFlavorSupported(uriListFlavor))
3284 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3285 // This is used by Unix drag system
3286 data = (String) t.getTransferData(uriListFlavor);
3290 // fallback to text: workaround - on OSX where there's a JVM bug
3292 .debug("standard URIListFlavor failed. Trying text");
3293 // try text fallback
3294 DataFlavor textDf = new DataFlavor(
3295 "text/plain;class=java.lang.String");
3296 if (t.isDataFlavorSupported(textDf))
3298 data = (String) t.getTransferData(textDf);
3301 jalview.bin.Console.debug("Plain text drop content returned "
3302 + (data == null ? "Null - failed" : data));
3307 while (protocols.size() < files.size())
3309 jalview.bin.Console.debug("Adding missing FILE protocol for "
3310 + files.get(protocols.size()));
3311 protocols.add(DataSourceType.FILE);
3313 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3314 data, "\r\n"); st.hasMoreTokens();)
3317 String s = st.nextToken();
3318 if (s.startsWith("#"))
3320 // the line is a comment (as per the RFC 2483)
3323 java.net.URI uri = new java.net.URI(s);
3324 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3326 protocols.add(DataSourceType.URL);
3327 files.add(uri.toString());
3331 // otherwise preserve old behaviour: catch all for file objects
3332 java.io.File file = new java.io.File(uri);
3333 protocols.add(DataSourceType.FILE);
3334 files.add(file.toString());
3339 if (jalview.bin.Console.isDebugEnabled())
3341 if (data == null || !added)
3344 if (t.getTransferDataFlavors() != null
3345 && t.getTransferDataFlavors().length > 0)
3347 jalview.bin.Console.debug(
3348 "Couldn't resolve drop data. Here are the supported flavors:");
3349 for (DataFlavor fl : t.getTransferDataFlavors())
3351 jalview.bin.Console.debug(
3352 "Supported transfer dataflavor: " + fl.toString());
3353 Object df = t.getTransferData(fl);
3356 jalview.bin.Console.debug("Retrieves: " + df);
3360 jalview.bin.Console.debug("Retrieved nothing");
3367 .debug("Couldn't resolve dataflavor for drop: "
3373 if (Platform.isWindowsAndNotJS())
3376 .debug("Scanning dropped content for Windows Link Files");
3378 // resolve any .lnk files in the file drop
3379 for (int f = 0; f < files.size(); f++)
3381 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3382 if (protocols.get(f).equals(DataSourceType.FILE)
3383 && (source.endsWith(".lnk") || source.endsWith(".url")
3384 || source.endsWith(".site")))
3388 Object obj = files.get(f);
3389 File lf = (obj instanceof File ? (File) obj
3390 : new File((String) obj));
3391 // process link file to get a URL
3392 jalview.bin.Console.debug("Found potential link file: " + lf);
3393 WindowsShortcut wscfile = new WindowsShortcut(lf);
3394 String fullname = wscfile.getRealFilename();
3395 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3396 files.set(f, fullname);
3397 jalview.bin.Console.debug("Parsed real filename " + fullname
3398 + " to extract protocol: " + protocols.get(f));
3399 } catch (Exception ex)
3401 jalview.bin.Console.error(
3402 "Couldn't parse " + files.get(f) + " as a link file.",
3411 * Sets the Preferences property for experimental features to True or False
3412 * depending on the state of the controlling menu item
3415 protected void showExperimental_actionPerformed(boolean selected)
3417 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3421 * Answers a (possibly empty) list of any structure viewer frames (currently
3422 * for either Jmol or Chimera) which are currently open. This may optionally
3423 * be restricted to viewers of a specified class, or viewers linked to a
3424 * specified alignment panel.
3427 * if not null, only return viewers linked to this panel
3428 * @param structureViewerClass
3429 * if not null, only return viewers of this class
3432 public List<StructureViewerBase> getStructureViewers(
3433 AlignmentPanel apanel,
3434 Class<? extends StructureViewerBase> structureViewerClass)
3436 List<StructureViewerBase> result = new ArrayList<>();
3437 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3439 for (JInternalFrame frame : frames)
3441 if (frame instanceof StructureViewerBase)
3443 if (structureViewerClass == null
3444 || structureViewerClass.isInstance(frame))
3447 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3449 result.add((StructureViewerBase) frame);
3457 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3459 private static boolean debugScaleMessageDone = false;
3461 public static void debugScaleMessage(Graphics g)
3463 if (debugScaleMessageDone)
3467 // output used by tests to check HiDPI scaling settings in action
3470 Graphics2D gg = (Graphics2D) g;
3473 AffineTransform t = gg.getTransform();
3474 double scaleX = t.getScaleX();
3475 double scaleY = t.getScaleY();
3476 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3477 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3478 debugScaleMessageDone = true;
3482 jalview.bin.Console.debug("Desktop graphics null");
3484 } catch (Exception e)
3486 jalview.bin.Console.debug(Cache.getStackTraceString(e));