2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JFrame;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.event.HyperlinkEvent;
95 import javax.swing.event.HyperlinkEvent.EventType;
96 import javax.swing.event.InternalFrameAdapter;
97 import javax.swing.event.InternalFrameEvent;
99 import org.stackoverflowusers.file.WindowsShortcut;
101 import jalview.api.AlignViewportI;
102 import jalview.api.AlignmentViewPanel;
103 import jalview.bin.Cache;
104 import jalview.bin.Jalview;
105 import jalview.gui.ImageExporter.ImageWriterI;
106 import jalview.io.BackupFiles;
107 import jalview.io.DataSourceType;
108 import jalview.io.FileFormat;
109 import jalview.io.FileFormatException;
110 import jalview.io.FileFormatI;
111 import jalview.io.FileFormats;
112 import jalview.io.FileLoader;
113 import jalview.io.FormatAdapter;
114 import jalview.io.IdentifyFile;
115 import jalview.io.JalviewFileChooser;
116 import jalview.io.JalviewFileView;
117 import jalview.jbgui.GSplitFrame;
118 import jalview.jbgui.GStructureViewer;
119 import jalview.project.Jalview2XML;
120 import jalview.structure.StructureSelectionManager;
121 import jalview.urls.IdOrgSettings;
122 import jalview.util.BrowserLauncher;
123 import jalview.util.ChannelProperties;
124 import jalview.util.ImageMaker.TYPE;
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 public 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())
419 Toolkit xToolkit = Toolkit.getDefaultToolkit();
420 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
421 Field awtAppClassNameField = null;
423 if (Arrays.stream(declaredFields)
424 .anyMatch(f -> f.getName().equals("awtAppClassName")))
426 awtAppClassNameField = xToolkit.getClass()
427 .getDeclaredField("awtAppClassName");
430 String title = ChannelProperties.getProperty("app_name");
431 if (awtAppClassNameField != null)
433 awtAppClassNameField.setAccessible(true);
434 awtAppClassNameField.set(xToolkit, title);
438 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
440 } catch (Exception e)
442 jalview.bin.Console.debug("Error setting awtAppClassName");
443 jalview.bin.Console.trace(Cache.getStackTraceString(e));
447 setIconImages(ChannelProperties.getIconList());
449 addWindowListener(new WindowAdapter()
453 public void windowClosing(WindowEvent ev)
459 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
461 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
462 desktop = new MyDesktopPane(selmemusage);
464 showMemusage.setSelected(selmemusage);
465 desktop.setBackground(Color.white);
467 getContentPane().setLayout(new BorderLayout());
468 // alternate config - have scrollbars - see notes in JAL-153
469 // JScrollPane sp = new JScrollPane();
470 // sp.getViewport().setView(desktop);
471 // getContentPane().add(sp, BorderLayout.CENTER);
473 // BH 2018 - just an experiment to try unclipped JInternalFrames.
476 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
479 getContentPane().add(desktop, BorderLayout.CENTER);
480 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
482 // This line prevents Windows Look&Feel resizing all new windows to maximum
483 // if previous window was maximised
484 desktop.setDesktopManager(new MyDesktopManager(
485 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
486 : Platform.isAMacAndNotJS()
487 ? new AquaInternalFrameManager(
488 desktop.getDesktopManager())
489 : desktop.getDesktopManager())));
491 Rectangle dims = getLastKnownDimensions("");
498 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
499 int xPos = Math.max(5, (screenSize.width - 900) / 2);
500 int yPos = Math.max(5, (screenSize.height - 650) / 2);
501 setBounds(xPos, yPos, 900, 650);
504 if (!Platform.isJS())
511 jconsole = new Console(this, showjconsole);
512 jconsole.setHeader(Cache.getVersionDetailsForConsole());
513 showConsole(showjconsole);
515 showNews.setVisible(false);
517 experimentalFeatures.setSelected(showExperimental());
519 getIdentifiersOrgData();
523 // Spawn a thread that shows the splashscreen
526 SwingUtilities.invokeLater(new Runnable()
531 new SplashScreen(true);
536 // Thread off a new instance of the file chooser - this reduces the time
538 // takes to open it later on.
539 new Thread(new Runnable()
544 jalview.bin.Console.debug("Filechooser init thread started.");
545 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
546 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
548 jalview.bin.Console.debug("Filechooser init thread finished.");
551 // Add the service change listener
552 changeSupport.addJalviewPropertyChangeListener("services",
553 new PropertyChangeListener()
557 public void propertyChange(PropertyChangeEvent evt)
560 .debug("Firing service changed event for "
561 + evt.getNewValue());
562 JalviewServicesChanged(evt);
567 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
569 this.addWindowListener(new WindowAdapter()
572 public void windowClosing(WindowEvent evt)
579 this.addMouseListener(ma = new MouseAdapter()
582 public void mousePressed(MouseEvent evt)
584 if (evt.isPopupTrigger()) // Mac
586 showPasteMenu(evt.getX(), evt.getY());
591 public void mouseReleased(MouseEvent evt)
593 if (evt.isPopupTrigger()) // Windows
595 showPasteMenu(evt.getX(), evt.getY());
599 desktop.addMouseListener(ma);
603 * Answers true if user preferences to enable experimental features is True
608 public boolean showExperimental()
610 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
611 Boolean.FALSE.toString());
612 return Boolean.valueOf(experimental).booleanValue();
615 public void doConfigureStructurePrefs()
617 // configure services
618 StructureSelectionManager ssm = StructureSelectionManager
619 .getStructureSelectionManager(this);
620 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
622 ssm.setAddTempFacAnnot(
623 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
624 ssm.setProcessSecondaryStructure(
625 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
626 // JAL-3915 - RNAView is no longer an option so this has no effect
627 ssm.setSecStructServices(
628 Cache.getDefault(Preferences.USE_RNAVIEW, false));
632 ssm.setAddTempFacAnnot(false);
633 ssm.setProcessSecondaryStructure(false);
634 ssm.setSecStructServices(false);
638 public void checkForNews()
640 final Desktop me = this;
641 // Thread off the news reader, in case there are connection problems.
642 new Thread(new Runnable()
647 jalview.bin.Console.debug("Starting news thread.");
648 jvnews = new BlogReader(me);
649 showNews.setVisible(true);
650 jalview.bin.Console.debug("Completed news thread.");
655 public void getIdentifiersOrgData()
657 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
658 {// Thread off the identifiers fetcher
659 new Thread(new Runnable()
665 .debug("Downloading data from identifiers.org");
668 UrlDownloadClient.download(IdOrgSettings.getUrl(),
669 IdOrgSettings.getDownloadLocation());
670 } catch (IOException e)
673 .debug("Exception downloading identifiers.org data"
683 protected void showNews_actionPerformed(ActionEvent e)
685 showNews(showNews.isSelected());
688 void showNews(boolean visible)
690 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
691 showNews.setSelected(visible);
692 if (visible && !jvnews.isVisible())
694 new Thread(new Runnable()
699 long now = System.currentTimeMillis();
700 Desktop.instance.setProgressBar(
701 MessageManager.getString("status.refreshing_news"), now);
702 jvnews.refreshNews();
703 Desktop.instance.setProgressBar(null, now);
711 * recover the last known dimensions for a jalview window
714 * - empty string is desktop, all other windows have unique prefix
715 * @return null or last known dimensions scaled to current geometry (if last
716 * window geom was known)
718 Rectangle getLastKnownDimensions(String windowName)
720 // TODO: lock aspect ratio for scaling desktop Bug #0058199
721 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
722 String x = Cache.getProperty(windowName + "SCREEN_X");
723 String y = Cache.getProperty(windowName + "SCREEN_Y");
724 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
725 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
726 if ((x != null) && (y != null) && (width != null) && (height != null))
728 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
729 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
730 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
732 // attempt #1 - try to cope with change in screen geometry - this
733 // version doesn't preserve original jv aspect ratio.
734 // take ratio of current screen size vs original screen size.
735 double sw = ((1f * screenSize.width) / (1f * Integer
736 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
737 double sh = ((1f * screenSize.height) / (1f * Integer
738 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
739 // rescale the bounds depending upon the current screen geometry.
740 ix = (int) (ix * sw);
741 iw = (int) (iw * sw);
742 iy = (int) (iy * sh);
743 ih = (int) (ih * sh);
744 while (ix >= screenSize.width)
746 jalview.bin.Console.debug(
747 "Window geometry location recall error: shifting horizontal to within screenbounds.");
748 ix -= screenSize.width;
750 while (iy >= screenSize.height)
752 jalview.bin.Console.debug(
753 "Window geometry location recall error: shifting vertical to within screenbounds.");
754 iy -= screenSize.height;
756 jalview.bin.Console.debug(
757 "Got last known dimensions for " + windowName + ": x:" + ix
758 + " y:" + iy + " width:" + iw + " height:" + ih);
760 // return dimensions for new instance
761 return new Rectangle(ix, iy, iw, ih);
766 void showPasteMenu(int x, int y)
768 JPopupMenu popup = new JPopupMenu();
769 JMenuItem item = new JMenuItem(
770 MessageManager.getString("label.paste_new_window"));
771 item.addActionListener(new ActionListener()
774 public void actionPerformed(ActionEvent evt)
781 popup.show(this, x, y);
788 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
789 Transferable contents = c.getContents(this);
791 if (contents != null)
793 String file = (String) contents
794 .getTransferData(DataFlavor.stringFlavor);
796 FileFormatI format = new IdentifyFile().identify(file,
797 DataSourceType.PASTE);
799 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
802 } catch (Exception ex)
805 "Unable to paste alignment from system clipboard:\n" + ex);
810 * Adds and opens the given frame to the desktop
821 public static synchronized void addInternalFrame(
822 final JInternalFrame frame, String title, int w, int h)
824 addInternalFrame(frame, title, true, w, h, true, false);
828 * Add an internal frame to the Jalview desktop
835 * When true, display frame immediately, otherwise, caller must call
836 * setVisible themselves.
842 public static synchronized void addInternalFrame(
843 final JInternalFrame frame, String title, boolean makeVisible,
846 addInternalFrame(frame, title, makeVisible, w, h, true, false);
850 * Add an internal frame to the Jalview desktop and make it visible
863 public static synchronized void addInternalFrame(
864 final JInternalFrame frame, String title, int w, int h,
867 addInternalFrame(frame, title, true, w, h, resizable, false);
871 * Add an internal frame to the Jalview desktop
878 * When true, display frame immediately, otherwise, caller must call
879 * setVisible themselves.
886 * @param ignoreMinSize
887 * Do not set the default minimum size for frame
889 public static synchronized void addInternalFrame(
890 final JInternalFrame frame, String title, boolean makeVisible,
891 int w, int h, boolean resizable, boolean ignoreMinSize)
894 JFrame extFrame = new JFrame();
895 extFrame.setContentPane(frame);
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 extFrame.setTitle(title);
902 if (extFrame.getWidth() < 1 || extFrame.getHeight() < 1)
904 extFrame.setSize(w, h);
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 extFrame.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 extFrame.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);
1010 // desktop.add(frame);
1012 windowMenu.add(menuItem);
1015 extFrame.setVisible(true);
1019 extFrame.requestFocus();
1020 } catch (java.lang.ClassCastException cex)
1022 jalview.bin.Console.warn(
1023 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1029 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1034 private static void setKeyBindings(JInternalFrame frame)
1036 @SuppressWarnings("serial")
1037 final Action closeAction = new AbstractAction()
1040 public void actionPerformed(ActionEvent e)
1047 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1049 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1050 InputEvent.CTRL_DOWN_MASK);
1051 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1052 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1054 InputMap inputMap = frame
1055 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1056 String ctrlW = ctrlWKey.toString();
1057 inputMap.put(ctrlWKey, ctrlW);
1058 inputMap.put(cmdWKey, ctrlW);
1060 ActionMap actionMap = frame.getActionMap();
1061 actionMap.put(ctrlW, closeAction);
1065 public void lostOwnership(Clipboard clipboard, Transferable contents)
1069 Desktop.jalviewClipboard = null;
1072 internalCopy = false;
1076 public void dragEnter(DropTargetDragEvent evt)
1081 public void dragExit(DropTargetEvent evt)
1086 public void dragOver(DropTargetDragEvent evt)
1091 public void dropActionChanged(DropTargetDragEvent evt)
1102 public void drop(DropTargetDropEvent evt)
1104 boolean success = true;
1105 // JAL-1552 - acceptDrop required before getTransferable call for
1106 // Java's Transferable for native dnd
1107 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1108 Transferable t = evt.getTransferable();
1109 List<Object> files = new ArrayList<>();
1110 List<DataSourceType> protocols = new ArrayList<>();
1114 Desktop.transferFromDropTarget(files, protocols, evt, t);
1115 } catch (Exception e)
1117 e.printStackTrace();
1125 for (int i = 0; i < files.size(); i++)
1127 // BH 2018 File or String
1128 Object file = files.get(i);
1129 String fileName = file.toString();
1130 DataSourceType protocol = (protocols == null)
1131 ? DataSourceType.FILE
1133 FileFormatI format = null;
1135 if (fileName.endsWith(".jar"))
1137 format = FileFormat.Jalview;
1142 format = new IdentifyFile().identify(file, protocol);
1144 if (file instanceof File)
1146 Platform.cacheFileData((File) file);
1148 new FileLoader().LoadFile(null, file, protocol, format);
1151 } catch (Exception ex)
1156 evt.dropComplete(success); // need this to ensure input focus is properly
1157 // transfered to any new windows created
1167 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1169 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1170 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1171 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1172 BackupFiles.getEnabled());
1174 chooser.setFileView(new JalviewFileView());
1175 chooser.setDialogTitle(
1176 MessageManager.getString("label.open_local_file"));
1177 chooser.setToolTipText(MessageManager.getString("action.open"));
1179 chooser.setResponseHandler(0, new Runnable()
1184 File selectedFile = chooser.getSelectedFile();
1185 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1187 FileFormatI format = chooser.getSelectedFormat();
1190 * Call IdentifyFile to verify the file contains what its extension implies.
1191 * Skip this step for dynamically added file formats, because IdentifyFile does
1192 * not know how to recognise them.
1194 if (FileFormats.getInstance().isIdentifiable(format))
1198 format = new IdentifyFile().identify(selectedFile,
1199 DataSourceType.FILE);
1200 } catch (FileFormatException e)
1202 // format = null; //??
1206 new FileLoader().LoadFile(viewport, selectedFile,
1207 DataSourceType.FILE, format);
1210 chooser.showOpenDialog(this);
1214 * Shows a dialog for input of a URL at which to retrieve alignment data
1219 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1221 // This construct allows us to have a wider textfield
1223 JLabel label = new JLabel(
1224 MessageManager.getString("label.input_file_url"));
1226 JPanel panel = new JPanel(new GridLayout(2, 1));
1230 * the URL to fetch is input in Java: an editable combobox with history JS:
1231 * (pending JAL-3038) a plain text field
1234 String urlBase = "https://www.";
1235 if (Platform.isJS())
1237 history = new JTextField(urlBase, 35);
1246 JComboBox<String> asCombo = new JComboBox<>();
1247 asCombo.setPreferredSize(new Dimension(400, 20));
1248 asCombo.setEditable(true);
1249 asCombo.addItem(urlBase);
1250 String historyItems = Cache.getProperty("RECENT_URL");
1251 if (historyItems != null)
1253 for (String token : historyItems.split("\\t"))
1255 asCombo.addItem(token);
1262 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1263 MessageManager.getString("action.cancel") };
1264 Runnable action = new Runnable()
1269 @SuppressWarnings("unchecked")
1270 String url = (history instanceof JTextField
1271 ? ((JTextField) history).getText()
1272 : ((JComboBox<String>) history).getEditor().getItem()
1273 .toString().trim());
1275 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1277 if (viewport != null)
1279 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1280 FileFormat.Jalview);
1284 new FileLoader().LoadFile(url, DataSourceType.URL,
1285 FileFormat.Jalview);
1290 FileFormatI format = null;
1293 format = new IdentifyFile().identify(url, DataSourceType.URL);
1294 } catch (FileFormatException e)
1296 // TODO revise error handling, distinguish between
1297 // URL not found and response not valid
1302 String msg = MessageManager
1303 .formatMessage("label.couldnt_locate", url);
1304 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1305 MessageManager.getString("label.url_not_found"),
1306 JvOptionPane.WARNING_MESSAGE);
1311 if (viewport != null)
1313 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1318 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1323 String dialogOption = MessageManager
1324 .getString("label.input_alignment_from_url");
1325 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1326 .showInternalDialog(panel, dialogOption,
1327 JvOptionPane.YES_NO_CANCEL_OPTION,
1328 JvOptionPane.PLAIN_MESSAGE, null, options,
1329 MessageManager.getString("action.ok"));
1333 * Opens the CutAndPaste window for the user to paste an alignment in to
1336 * - if not null, the pasted alignment is added to the current
1337 * alignment; if null, to a new alignment window
1340 public void inputTextboxMenuItem_actionPerformed(
1341 AlignmentViewPanel viewPanel)
1343 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1344 cap.setForInput(viewPanel);
1345 Desktop.addInternalFrame(cap,
1346 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1356 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1357 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1358 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1359 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1360 getWidth(), getHeight()));
1362 if (jconsole != null)
1364 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1365 jconsole.stopConsole();
1369 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1372 if (dialogExecutor != null)
1374 dialogExecutor.shutdownNow();
1376 closeAll_actionPerformed(null);
1378 if (groovyConsole != null)
1380 // suppress a possible repeat prompt to save script
1381 groovyConsole.setDirty(false);
1382 groovyConsole.exit();
1387 private void storeLastKnownDimensions(String string, Rectangle jc)
1389 jalview.bin.Console.debug("Storing last known dimensions for " + string
1390 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1391 + " height:" + jc.height);
1393 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1394 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1395 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1396 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1406 public void aboutMenuItem_actionPerformed(ActionEvent e)
1408 new Thread(new Runnable()
1413 new SplashScreen(false);
1419 * Returns the html text for the About screen, including any available version
1420 * number, build details, author details and citation reference, but without
1421 * the enclosing {@code html} tags
1425 public String getAboutMessage()
1427 StringBuilder message = new StringBuilder(1024);
1428 message.append("<div style=\"font-family: sans-serif;\">")
1429 .append("<h1><strong>Version: ")
1430 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1431 .append("<strong>Built: <em>")
1432 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1433 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1434 .append("</strong>");
1436 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1437 if (latestVersion.equals("Checking"))
1439 // JBP removed this message for 2.11: May be reinstated in future version
1440 // message.append("<br>...Checking latest version...</br>");
1442 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1444 boolean red = false;
1445 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1446 .indexOf("automated build") == -1)
1449 // Displayed when code version and jnlp version do not match and code
1450 // version is not a development build
1451 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1454 message.append("<br>!! Version ")
1455 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1456 .append(" is available for download from ")
1457 .append(Cache.getDefault("www.jalview.org",
1458 "https://www.jalview.org"))
1462 message.append("</div>");
1465 message.append("<br>Authors: ");
1466 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1467 message.append(CITATION);
1469 message.append("</div>");
1471 return message.toString();
1475 * Action on requesting Help documentation
1478 public void documentationMenuItem_actionPerformed()
1482 if (Platform.isJS())
1484 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1493 Help.showHelpWindow();
1495 } catch (Exception ex)
1497 System.err.println("Error opening help: " + ex.getMessage());
1502 public void closeAll_actionPerformed(ActionEvent e)
1504 // TODO show a progress bar while closing?
1505 JInternalFrame[] frames = desktop.getAllFrames();
1506 for (int i = 0; i < frames.length; i++)
1510 frames[i].setClosed(true);
1511 } catch (java.beans.PropertyVetoException ex)
1515 Jalview.setCurrentAlignFrame(null);
1516 System.out.println("ALL CLOSED");
1519 * reset state of singleton objects as appropriate (clear down session state
1520 * when all windows are closed)
1522 StructureSelectionManager ssm = StructureSelectionManager
1523 .getStructureSelectionManager(this);
1531 public void raiseRelated_actionPerformed(ActionEvent e)
1533 reorderAssociatedWindows(false, false);
1537 public void minimizeAssociated_actionPerformed(ActionEvent e)
1539 reorderAssociatedWindows(true, false);
1542 void closeAssociatedWindows()
1544 reorderAssociatedWindows(false, true);
1550 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1554 protected void garbageCollect_actionPerformed(ActionEvent e)
1556 // We simply collect the garbage
1557 jalview.bin.Console.debug("Collecting garbage...");
1559 jalview.bin.Console.debug("Finished garbage collection.");
1565 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1569 protected void showMemusage_actionPerformed(ActionEvent e)
1571 desktop.showMemoryUsage(showMemusage.isSelected());
1578 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1582 protected void showConsole_actionPerformed(ActionEvent e)
1584 showConsole(showConsole.isSelected());
1587 Console jconsole = null;
1590 * control whether the java console is visible or not
1594 void showConsole(boolean selected)
1596 // TODO: decide if we should update properties file
1597 if (jconsole != null) // BH 2018
1599 showConsole.setSelected(selected);
1600 Cache.setProperty("SHOW_JAVA_CONSOLE",
1601 Boolean.valueOf(selected).toString());
1602 jconsole.setVisible(selected);
1606 void reorderAssociatedWindows(boolean minimize, boolean close)
1608 JInternalFrame[] frames = desktop.getAllFrames();
1609 if (frames == null || frames.length < 1)
1614 AlignmentViewport source = null, target = null;
1615 if (frames[0] instanceof AlignFrame)
1617 source = ((AlignFrame) frames[0]).getCurrentView();
1619 else if (frames[0] instanceof TreePanel)
1621 source = ((TreePanel) frames[0]).getViewPort();
1623 else if (frames[0] instanceof PCAPanel)
1625 source = ((PCAPanel) frames[0]).av;
1627 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1629 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1634 for (int i = 0; i < frames.length; i++)
1637 if (frames[i] == null)
1641 if (frames[i] instanceof AlignFrame)
1643 target = ((AlignFrame) frames[i]).getCurrentView();
1645 else if (frames[i] instanceof TreePanel)
1647 target = ((TreePanel) frames[i]).getViewPort();
1649 else if (frames[i] instanceof PCAPanel)
1651 target = ((PCAPanel) frames[i]).av;
1653 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1655 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1658 if (source == target)
1664 frames[i].setClosed(true);
1668 frames[i].setIcon(minimize);
1671 frames[i].toFront();
1675 } catch (java.beans.PropertyVetoException ex)
1690 protected void preferences_actionPerformed(ActionEvent e)
1692 Preferences.openPreferences();
1696 * Prompts the user to choose a file and then saves the Jalview state as a
1697 * Jalview project file
1700 public void saveState_actionPerformed()
1702 saveState_actionPerformed(false);
1705 public void saveState_actionPerformed(boolean saveAs)
1707 java.io.File projectFile = getProjectFile();
1708 // autoSave indicates we already have a file and don't need to ask
1709 boolean autoSave = projectFile != null && !saveAs
1710 && BackupFiles.getEnabled();
1712 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1713 // saveAs="+saveAs+", Backups
1714 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1716 boolean approveSave = false;
1719 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1722 chooser.setFileView(new JalviewFileView());
1723 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1725 int value = chooser.showSaveDialog(this);
1727 if (value == JalviewFileChooser.APPROVE_OPTION)
1729 projectFile = chooser.getSelectedFile();
1730 setProjectFile(projectFile);
1735 if (approveSave || autoSave)
1737 final Desktop me = this;
1738 final java.io.File chosenFile = projectFile;
1739 new Thread(new Runnable()
1744 // TODO: refactor to Jalview desktop session controller action.
1745 setProgressBar(MessageManager.formatMessage(
1746 "label.saving_jalview_project", new Object[]
1747 { chosenFile.getName() }), chosenFile.hashCode());
1748 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1749 // TODO catch and handle errors for savestate
1750 // TODO prevent user from messing with the Desktop whilst we're saving
1753 boolean doBackup = BackupFiles.getEnabled();
1754 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1757 new Jalview2XML().saveState(
1758 doBackup ? backupfiles.getTempFile() : chosenFile);
1762 backupfiles.setWriteSuccess(true);
1763 backupfiles.rollBackupsAndRenameTempFile();
1765 } catch (OutOfMemoryError oom)
1767 new OOMWarning("Whilst saving current state to "
1768 + chosenFile.getName(), oom);
1769 } catch (Exception ex)
1771 jalview.bin.Console.error("Problems whilst trying to save to "
1772 + chosenFile.getName(), ex);
1773 JvOptionPane.showMessageDialog(me,
1774 MessageManager.formatMessage(
1775 "label.error_whilst_saving_current_state_to",
1777 { chosenFile.getName() }),
1778 MessageManager.getString("label.couldnt_save_project"),
1779 JvOptionPane.WARNING_MESSAGE);
1781 setProgressBar(null, chosenFile.hashCode());
1788 public void saveAsState_actionPerformed(ActionEvent e)
1790 saveState_actionPerformed(true);
1793 private void setProjectFile(File choice)
1795 this.projectFile = choice;
1798 public File getProjectFile()
1800 return this.projectFile;
1804 * Shows a file chooser dialog and tries to read in the selected file as a
1808 public void loadState_actionPerformed()
1810 final String[] suffix = new String[] { "jvp", "jar" };
1811 final String[] desc = new String[] { "Jalview Project",
1812 "Jalview Project (old)" };
1813 JalviewFileChooser chooser = new JalviewFileChooser(
1814 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1815 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1819 chooser.setFileView(new JalviewFileView());
1820 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1821 chooser.setResponseHandler(0, new Runnable()
1826 File selectedFile = chooser.getSelectedFile();
1827 setProjectFile(selectedFile);
1828 String choice = selectedFile.getAbsolutePath();
1829 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1830 new Thread(new Runnable()
1837 new Jalview2XML().loadJalviewAlign(selectedFile);
1838 } catch (OutOfMemoryError oom)
1840 new OOMWarning("Whilst loading project from " + choice, oom);
1841 } catch (Exception ex)
1843 jalview.bin.Console.error(
1844 "Problems whilst loading project from " + choice, ex);
1845 JvOptionPane.showMessageDialog(Desktop.desktop,
1846 MessageManager.formatMessage(
1847 "label.error_whilst_loading_project_from",
1851 .getString("label.couldnt_load_project"),
1852 JvOptionPane.WARNING_MESSAGE);
1855 }, "Project Loader").start();
1859 chooser.showOpenDialog(this);
1863 public void inputSequence_actionPerformed(ActionEvent e)
1865 new SequenceFetcher(this);
1868 JPanel progressPanel;
1870 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1872 public void startLoading(final Object fileName)
1874 if (fileLoadingCount == 0)
1876 fileLoadingPanels.add(addProgressPanel(MessageManager
1877 .formatMessage("label.loading_file", new Object[]
1883 private JPanel addProgressPanel(String string)
1885 if (progressPanel == null)
1887 progressPanel = new JPanel(new GridLayout(1, 1));
1888 totalProgressCount = 0;
1889 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1891 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1892 JProgressBar progressBar = new JProgressBar();
1893 progressBar.setIndeterminate(true);
1895 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1897 thisprogress.add(progressBar, BorderLayout.CENTER);
1898 progressPanel.add(thisprogress);
1899 ((GridLayout) progressPanel.getLayout()).setRows(
1900 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1901 ++totalProgressCount;
1902 instance.validate();
1903 return thisprogress;
1906 int totalProgressCount = 0;
1908 private void removeProgressPanel(JPanel progbar)
1910 if (progressPanel != null)
1912 synchronized (progressPanel)
1914 progressPanel.remove(progbar);
1915 GridLayout gl = (GridLayout) progressPanel.getLayout();
1916 gl.setRows(gl.getRows() - 1);
1917 if (--totalProgressCount < 1)
1919 this.getContentPane().remove(progressPanel);
1920 progressPanel = null;
1927 public void stopLoading()
1930 if (fileLoadingCount < 1)
1932 while (fileLoadingPanels.size() > 0)
1934 removeProgressPanel(fileLoadingPanels.remove(0));
1936 fileLoadingPanels.clear();
1937 fileLoadingCount = 0;
1942 public static int getViewCount(String alignmentId)
1944 AlignmentViewport[] aps = getViewports(alignmentId);
1945 return (aps == null) ? 0 : aps.length;
1950 * @param alignmentId
1951 * - if null, all sets are returned
1952 * @return all AlignmentPanels concerning the alignmentId sequence set
1954 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1956 if (Desktop.desktop == null)
1958 // no frames created and in headless mode
1959 // TODO: verify that frames are recoverable when in headless mode
1962 List<AlignmentPanel> aps = new ArrayList<>();
1963 AlignFrame[] frames = getAlignFrames();
1968 for (AlignFrame af : frames)
1970 for (AlignmentPanel ap : af.alignPanels)
1972 if (alignmentId == null
1973 || alignmentId.equals(ap.av.getSequenceSetId()))
1979 if (aps.size() == 0)
1983 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1988 * get all the viewports on an alignment.
1990 * @param sequenceSetId
1991 * unique alignment id (may be null - all viewports returned in that
1993 * @return all viewports on the alignment bound to sequenceSetId
1995 public static AlignmentViewport[] getViewports(String sequenceSetId)
1997 List<AlignmentViewport> viewp = new ArrayList<>();
1998 if (desktop != null)
2000 AlignFrame[] frames = Desktop.getAlignFrames();
2002 for (AlignFrame afr : frames)
2004 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2005 .equals(sequenceSetId))
2007 if (afr.alignPanels != null)
2009 for (AlignmentPanel ap : afr.alignPanels)
2011 if (sequenceSetId == null
2012 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2020 viewp.add(afr.getViewport());
2024 if (viewp.size() > 0)
2026 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2033 * Explode the views in the given frame into separate AlignFrame
2037 public static void explodeViews(AlignFrame af)
2039 int size = af.alignPanels.size();
2045 // FIXME: ideally should use UI interface API
2046 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2047 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2048 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2049 for (int i = 0; i < size; i++)
2051 AlignmentPanel ap = af.alignPanels.get(i);
2053 AlignFrame newaf = new AlignFrame(ap);
2055 // transfer reference for existing feature settings to new alignFrame
2056 if (ap == af.alignPanel)
2058 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2060 newaf.featureSettings = viewFeatureSettings;
2062 newaf.setFeatureSettingsGeometry(fsBounds);
2066 * Restore the view's last exploded frame geometry if known. Multiple views from
2067 * one exploded frame share and restore the same (frame) position and size.
2069 Rectangle geometry = ap.av.getExplodedGeometry();
2070 if (geometry != null)
2072 newaf.setBounds(geometry);
2075 ap.av.setGatherViewsHere(false);
2077 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2078 AlignFrame.DEFAULT_HEIGHT);
2079 // and materialise a new feature settings dialog instance for the new
2081 // (closes the old as if 'OK' was pressed)
2082 if (ap == af.alignPanel && newaf.featureSettings != null
2083 && newaf.featureSettings.isOpen()
2084 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2086 newaf.showFeatureSettingsUI();
2090 af.featureSettings = null;
2091 af.alignPanels.clear();
2092 af.closeMenuItem_actionPerformed(true);
2097 * Gather expanded views (separate AlignFrame's) with the same sequence set
2098 * identifier back in to this frame as additional views, and close the
2099 * expanded views. Note the expanded frames may themselves have multiple
2100 * views. We take the lot.
2104 public void gatherViews(AlignFrame source)
2106 source.viewport.setGatherViewsHere(true);
2107 source.viewport.setExplodedGeometry(source.getBounds());
2108 JInternalFrame[] frames = desktop.getAllFrames();
2109 String viewId = source.viewport.getSequenceSetId();
2110 for (int t = 0; t < frames.length; t++)
2112 if (frames[t] instanceof AlignFrame && frames[t] != source)
2114 AlignFrame af = (AlignFrame) frames[t];
2115 boolean gatherThis = false;
2116 for (int a = 0; a < af.alignPanels.size(); a++)
2118 AlignmentPanel ap = af.alignPanels.get(a);
2119 if (viewId.equals(ap.av.getSequenceSetId()))
2122 ap.av.setGatherViewsHere(false);
2123 ap.av.setExplodedGeometry(af.getBounds());
2124 source.addAlignmentPanel(ap, false);
2130 if (af.featureSettings != null && af.featureSettings.isOpen())
2132 if (source.featureSettings == null)
2134 // preserve the feature settings geometry for this frame
2135 source.featureSettings = af.featureSettings;
2136 source.setFeatureSettingsGeometry(
2137 af.getFeatureSettingsGeometry());
2141 // close it and forget
2142 af.featureSettings.close();
2145 af.alignPanels.clear();
2146 af.closeMenuItem_actionPerformed(true);
2151 // refresh the feature setting UI for the source frame if it exists
2152 if (source.featureSettings != null && source.featureSettings.isOpen())
2154 source.showFeatureSettingsUI();
2159 public JInternalFrame[] getAllFrames()
2161 return desktop.getAllFrames();
2165 * Checks the given url to see if it gives a response indicating that the user
2166 * should be informed of a new questionnaire.
2170 public void checkForQuestionnaire(String url)
2172 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2173 // javax.swing.SwingUtilities.invokeLater(jvq);
2174 new Thread(jvq).start();
2177 public void checkURLLinks()
2179 // Thread off the URL link checker
2180 addDialogThread(new Runnable()
2185 if (Cache.getDefault("CHECKURLLINKS", true))
2187 // check what the actual links are - if it's just the default don't
2188 // bother with the warning
2189 List<String> links = Preferences.sequenceUrlLinks
2192 // only need to check links if there is one with a
2193 // SEQUENCE_ID which is not the default EMBL_EBI link
2194 ListIterator<String> li = links.listIterator();
2195 boolean check = false;
2196 List<JLabel> urls = new ArrayList<>();
2197 while (li.hasNext())
2199 String link = li.next();
2200 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2201 && !UrlConstants.isDefaultString(link))
2204 int barPos = link.indexOf("|");
2205 String urlMsg = barPos == -1 ? link
2206 : link.substring(0, barPos) + ": "
2207 + link.substring(barPos + 1);
2208 urls.add(new JLabel(urlMsg));
2216 // ask user to check in case URL links use old style tokens
2217 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2218 JPanel msgPanel = new JPanel();
2219 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2220 msgPanel.add(Box.createVerticalGlue());
2221 JLabel msg = new JLabel(MessageManager
2222 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2223 JLabel msg2 = new JLabel(MessageManager
2224 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2226 for (JLabel url : urls)
2232 final JCheckBox jcb = new JCheckBox(
2233 MessageManager.getString("label.do_not_display_again"));
2234 jcb.addActionListener(new ActionListener()
2237 public void actionPerformed(ActionEvent e)
2239 // update Cache settings for "don't show this again"
2240 boolean showWarningAgain = !jcb.isSelected();
2241 Cache.setProperty("CHECKURLLINKS",
2242 Boolean.valueOf(showWarningAgain).toString());
2247 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2249 .getString("label.SEQUENCE_ID_no_longer_used"),
2250 JvOptionPane.WARNING_MESSAGE);
2257 * Proxy class for JDesktopPane which optionally displays the current memory
2258 * usage and highlights the desktop area with a red bar if free memory runs
2263 public class MyDesktopPane extends JDesktopPane implements Runnable
2265 private static final float ONE_MB = 1048576f;
2267 boolean showMemoryUsage = false;
2271 java.text.NumberFormat df;
2273 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2276 public MyDesktopPane(boolean showMemoryUsage)
2278 showMemoryUsage(showMemoryUsage);
2281 public void showMemoryUsage(boolean showMemory)
2283 this.showMemoryUsage = showMemory;
2286 Thread worker = new Thread(this);
2292 public boolean isShowMemoryUsage()
2294 return showMemoryUsage;
2300 df = java.text.NumberFormat.getNumberInstance();
2301 df.setMaximumFractionDigits(2);
2302 runtime = Runtime.getRuntime();
2304 while (showMemoryUsage)
2308 maxMemory = runtime.maxMemory() / ONE_MB;
2309 allocatedMemory = runtime.totalMemory() / ONE_MB;
2310 freeMemory = runtime.freeMemory() / ONE_MB;
2311 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2313 percentUsage = (totalFreeMemory / maxMemory) * 100;
2315 // if (percentUsage < 20)
2317 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2319 // instance.set.setBorder(border1);
2322 // sleep after showing usage
2324 } catch (Exception ex)
2326 ex.printStackTrace();
2332 public void paintComponent(Graphics g)
2334 if (showMemoryUsage && g != null && df != null)
2336 if (percentUsage < 20)
2338 g.setColor(Color.red);
2340 FontMetrics fm = g.getFontMetrics();
2343 g.drawString(MessageManager.formatMessage("label.memory_stats",
2345 { df.format(totalFreeMemory), df.format(maxMemory),
2346 df.format(percentUsage) }),
2347 10, getHeight() - fm.getHeight());
2351 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2352 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2357 * Accessor method to quickly get all the AlignmentFrames loaded.
2359 * @return an array of AlignFrame, or null if none found
2361 public static AlignFrame[] getAlignFrames()
2363 if (Jalview.isHeadlessMode())
2365 // Desktop.desktop is null in headless mode
2366 return new AlignFrame[] { Jalview.currentAlignFrame };
2369 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2375 List<AlignFrame> avp = new ArrayList<>();
2377 for (int i = frames.length - 1; i > -1; i--)
2379 if (frames[i] instanceof AlignFrame)
2381 avp.add((AlignFrame) frames[i]);
2383 else if (frames[i] instanceof SplitFrame)
2386 * Also check for a split frame containing an AlignFrame
2388 GSplitFrame sf = (GSplitFrame) frames[i];
2389 if (sf.getTopFrame() instanceof AlignFrame)
2391 avp.add((AlignFrame) sf.getTopFrame());
2393 if (sf.getBottomFrame() instanceof AlignFrame)
2395 avp.add((AlignFrame) sf.getBottomFrame());
2399 if (avp.size() == 0)
2403 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2408 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2412 public GStructureViewer[] getJmols()
2414 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2420 List<GStructureViewer> avp = new ArrayList<>();
2422 for (int i = frames.length - 1; i > -1; i--)
2424 if (frames[i] instanceof AppJmol)
2426 GStructureViewer af = (GStructureViewer) frames[i];
2430 if (avp.size() == 0)
2434 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2439 * Add Groovy Support to Jalview
2442 public void groovyShell_actionPerformed()
2446 openGroovyConsole();
2447 } catch (Exception ex)
2449 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2450 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2452 MessageManager.getString("label.couldnt_create_groovy_shell"),
2453 MessageManager.getString("label.groovy_support_failed"),
2454 JvOptionPane.ERROR_MESSAGE);
2459 * Open the Groovy console
2461 void openGroovyConsole()
2463 if (groovyConsole == null)
2465 groovyConsole = new groovy.ui.Console();
2466 groovyConsole.setVariable("Jalview", this);
2467 groovyConsole.run();
2470 * We allow only one console at a time, so that AlignFrame menu option
2471 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2472 * enable 'Run script', when the console is opened, and the reverse when it is
2475 Window window = (Window) groovyConsole.getFrame();
2476 window.addWindowListener(new WindowAdapter()
2479 public void windowClosed(WindowEvent e)
2482 * rebind CMD-Q from Groovy Console to Jalview Quit
2485 enableExecuteGroovy(false);
2491 * show Groovy console window (after close and reopen)
2493 ((Window) groovyConsole.getFrame()).setVisible(true);
2496 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2497 * opening a second console
2499 enableExecuteGroovy(true);
2503 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2504 * binding when opened
2506 protected void addQuitHandler()
2509 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2511 .getKeyStroke(KeyEvent.VK_Q,
2512 jalview.util.ShortcutKeyMaskExWrapper
2513 .getMenuShortcutKeyMaskEx()),
2515 getRootPane().getActionMap().put("Quit", new AbstractAction()
2518 public void actionPerformed(ActionEvent e)
2526 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2529 * true if Groovy console is open
2531 public void enableExecuteGroovy(boolean enabled)
2534 * disable opening a second Groovy console (or re-enable when the console is
2537 groovyShell.setEnabled(!enabled);
2539 AlignFrame[] alignFrames = getAlignFrames();
2540 if (alignFrames != null)
2542 for (AlignFrame af : alignFrames)
2544 af.setGroovyEnabled(enabled);
2550 * Progress bars managed by the IProgressIndicator method.
2552 private Hashtable<Long, JPanel> progressBars;
2554 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2559 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2562 public void setProgressBar(String message, long id)
2564 if (progressBars == null)
2566 progressBars = new Hashtable<>();
2567 progressBarHandlers = new Hashtable<>();
2570 if (progressBars.get(Long.valueOf(id)) != null)
2572 JPanel panel = progressBars.remove(Long.valueOf(id));
2573 if (progressBarHandlers.contains(Long.valueOf(id)))
2575 progressBarHandlers.remove(Long.valueOf(id));
2577 removeProgressPanel(panel);
2581 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2588 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2589 * jalview.gui.IProgressIndicatorHandler)
2592 public void registerHandler(final long id,
2593 final IProgressIndicatorHandler handler)
2595 if (progressBarHandlers == null
2596 || !progressBars.containsKey(Long.valueOf(id)))
2598 throw new Error(MessageManager.getString(
2599 "error.call_setprogressbar_before_registering_handler"));
2601 progressBarHandlers.put(Long.valueOf(id), handler);
2602 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2603 if (handler.canCancel())
2605 JButton cancel = new JButton(
2606 MessageManager.getString("action.cancel"));
2607 final IProgressIndicator us = this;
2608 cancel.addActionListener(new ActionListener()
2612 public void actionPerformed(ActionEvent e)
2614 handler.cancelActivity(id);
2615 us.setProgressBar(MessageManager
2616 .formatMessage("label.cancelled_params", new Object[]
2617 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2621 progressPanel.add(cancel, BorderLayout.EAST);
2627 * @return true if any progress bars are still active
2630 public boolean operationInProgress()
2632 if (progressBars != null && progressBars.size() > 0)
2640 * This will return the first AlignFrame holding the given viewport instance.
2641 * It will break if there are more than one AlignFrames viewing a particular
2645 * @return alignFrame for viewport
2647 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2649 if (desktop != null)
2651 AlignmentPanel[] aps = getAlignmentPanels(
2652 viewport.getSequenceSetId());
2653 for (int panel = 0; aps != null && panel < aps.length; panel++)
2655 if (aps[panel] != null && aps[panel].av == viewport)
2657 return aps[panel].alignFrame;
2664 public VamsasApplication getVamsasApplication()
2666 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2672 * flag set if jalview GUI is being operated programmatically
2674 private boolean inBatchMode = false;
2677 * check if jalview GUI is being operated programmatically
2679 * @return inBatchMode
2681 public boolean isInBatchMode()
2687 * set flag if jalview GUI is being operated programmatically
2689 * @param inBatchMode
2691 public void setInBatchMode(boolean inBatchMode)
2693 this.inBatchMode = inBatchMode;
2697 * start service discovery and wait till it is done
2699 public void startServiceDiscovery()
2701 startServiceDiscovery(false);
2705 * start service discovery threads - blocking or non-blocking
2709 public void startServiceDiscovery(boolean blocking)
2711 startServiceDiscovery(blocking, false);
2715 * start service discovery threads
2718 * - false means call returns immediately
2719 * @param ignore_SHOW_JWS2_SERVICES_preference
2720 * - when true JABA services are discovered regardless of user's JWS2
2721 * discovery preference setting
2723 public void startServiceDiscovery(boolean blocking,
2724 boolean ignore_SHOW_JWS2_SERVICES_preference)
2726 boolean alive = true;
2727 Thread t0 = null, t1 = null, t2 = null;
2728 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2731 // todo: changesupport handlers need to be transferred
2732 if (discoverer == null)
2734 discoverer = new jalview.ws.jws1.Discoverer();
2735 // register PCS handler for desktop.
2736 discoverer.addPropertyChangeListener(changeSupport);
2738 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2739 // until we phase out completely
2740 (t0 = new Thread(discoverer)).start();
2743 if (ignore_SHOW_JWS2_SERVICES_preference
2744 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2746 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2747 .startDiscoverer(changeSupport);
2751 // TODO: do rest service discovery
2760 } catch (Exception e)
2763 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2764 || (t3 != null && t3.isAlive())
2765 || (t0 != null && t0.isAlive());
2771 * called to check if the service discovery process completed successfully.
2775 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2777 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2779 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2780 .getErrorMessages();
2783 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2785 if (serviceChangedDialog == null)
2787 // only run if we aren't already displaying one of these.
2788 addDialogThread(serviceChangedDialog = new Runnable()
2795 * JalviewDialog jd =new JalviewDialog() {
2797 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2799 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2801 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2803 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2805 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2806 * + " or mis-configured HTTP proxy settings.<br/>" +
2807 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2808 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2809 * true, true, "Web Service Configuration Problem", 450, 400);
2811 * jd.waitForInput();
2813 JvOptionPane.showConfirmDialog(Desktop.desktop,
2814 new JLabel("<html><table width=\"450\"><tr><td>"
2815 + ermsg + "</td></tr></table>"
2816 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2817 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2818 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2819 + " Tools->Preferences dialog box to change them.</p></html>"),
2820 "Web Service Configuration Problem",
2821 JvOptionPane.DEFAULT_OPTION,
2822 JvOptionPane.ERROR_MESSAGE);
2823 serviceChangedDialog = null;
2831 jalview.bin.Console.error(
2832 "Errors reported by JABA discovery service. Check web services preferences.\n"
2839 private Runnable serviceChangedDialog = null;
2842 * start a thread to open a URL in the configured browser. Pops up a warning
2843 * dialog to the user if there is an exception when calling out to the browser
2848 public static void showUrl(final String url)
2850 showUrl(url, Desktop.instance);
2854 * Like showUrl but allows progress handler to be specified
2858 * (null) or object implementing IProgressIndicator
2860 public static void showUrl(final String url,
2861 final IProgressIndicator progress)
2863 new Thread(new Runnable()
2870 if (progress != null)
2872 progress.setProgressBar(MessageManager
2873 .formatMessage("status.opening_params", new Object[]
2874 { url }), this.hashCode());
2876 jalview.util.BrowserLauncher.openURL(url);
2877 } catch (Exception ex)
2879 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2881 .getString("label.web_browser_not_found_unix"),
2882 MessageManager.getString("label.web_browser_not_found"),
2883 JvOptionPane.WARNING_MESSAGE);
2885 ex.printStackTrace();
2887 if (progress != null)
2889 progress.setProgressBar(null, this.hashCode());
2895 public static WsParamSetManager wsparamManager = null;
2897 public static ParamManager getUserParameterStore()
2899 if (wsparamManager == null)
2901 wsparamManager = new WsParamSetManager();
2903 return wsparamManager;
2907 * static hyperlink handler proxy method for use by Jalview's internal windows
2911 public static void hyperlinkUpdate(HyperlinkEvent e)
2913 if (e.getEventType() == EventType.ACTIVATED)
2918 url = e.getURL().toString();
2919 Desktop.showUrl(url);
2920 } catch (Exception x)
2925 .error("Couldn't handle string " + url + " as a URL.");
2927 // ignore any exceptions due to dud links.
2934 * single thread that handles display of dialogs to user.
2936 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2939 * flag indicating if dialogExecutor should try to acquire a permit
2941 private volatile boolean dialogPause = true;
2946 private java.util.concurrent.Semaphore block = new Semaphore(0);
2948 private static groovy.ui.Console groovyConsole;
2951 * add another dialog thread to the queue
2955 public void addDialogThread(final Runnable prompter)
2957 dialogExecutor.submit(new Runnable()
2967 } catch (InterruptedException x)
2971 if (instance == null)
2977 SwingUtilities.invokeAndWait(prompter);
2978 } catch (Exception q)
2980 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
2987 public void startDialogQueue()
2989 // set the flag so we don't pause waiting for another permit and semaphore
2990 // the current task to begin
2991 dialogPause = false;
2996 * Outputs an image of the desktop to file in EPS format, after prompting the
2997 * user for choice of Text or Lineart character rendering (unless a preference
2998 * has been set). The file name is generated as
3001 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3005 protected void snapShotWindow_actionPerformed(ActionEvent e)
3007 // currently the menu option to do this is not shown
3010 int width = getWidth();
3011 int height = getHeight();
3013 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3014 ImageWriterI writer = new ImageWriterI()
3017 public void exportImage(Graphics g) throws Exception
3020 jalview.bin.Console.info("Successfully written snapshot to file "
3021 + of.getAbsolutePath());
3024 String title = "View of desktop";
3025 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3027 exporter.doExport(of, this, width, height, title);
3031 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3032 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3033 * and location last time the view was expanded (if any). However it does not
3034 * remember the split pane divider location - this is set to match the
3035 * 'exploding' frame.
3039 public void explodeViews(SplitFrame sf)
3041 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3042 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3043 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3045 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3047 int viewCount = topPanels.size();
3054 * Processing in reverse order works, forwards order leaves the first panels not
3055 * visible. I don't know why!
3057 for (int i = viewCount - 1; i >= 0; i--)
3060 * Make new top and bottom frames. These take over the respective AlignmentPanel
3061 * objects, including their AlignmentViewports, so the cdna/protein
3062 * relationships between the viewports is carried over to the new split frames.
3064 * explodedGeometry holds the (x, y) position of the previously exploded
3065 * SplitFrame, and the (width, height) of the AlignFrame component
3067 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3068 AlignFrame newTopFrame = new AlignFrame(topPanel);
3069 newTopFrame.setSize(oldTopFrame.getSize());
3070 newTopFrame.setVisible(true);
3071 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3072 .getExplodedGeometry();
3073 if (geometry != null)
3075 newTopFrame.setSize(geometry.getSize());
3078 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3079 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3080 newBottomFrame.setSize(oldBottomFrame.getSize());
3081 newBottomFrame.setVisible(true);
3082 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3083 .getExplodedGeometry();
3084 if (geometry != null)
3086 newBottomFrame.setSize(geometry.getSize());
3089 topPanel.av.setGatherViewsHere(false);
3090 bottomPanel.av.setGatherViewsHere(false);
3091 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3093 if (geometry != null)
3095 splitFrame.setLocation(geometry.getLocation());
3097 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3101 * Clear references to the panels (now relocated in the new SplitFrames) before
3102 * closing the old SplitFrame.
3105 bottomPanels.clear();
3110 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3111 * back into the given SplitFrame as additional views. Note that the gathered
3112 * frames may themselves have multiple views.
3116 public void gatherViews(GSplitFrame source)
3119 * special handling of explodedGeometry for a view within a SplitFrame: - it
3120 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3121 * height) of the AlignFrame component
3123 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3124 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3125 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3126 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3127 myBottomFrame.viewport
3128 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3129 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3130 myTopFrame.viewport.setGatherViewsHere(true);
3131 myBottomFrame.viewport.setGatherViewsHere(true);
3132 String topViewId = myTopFrame.viewport.getSequenceSetId();
3133 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3135 JInternalFrame[] frames = desktop.getAllFrames();
3136 for (JInternalFrame frame : frames)
3138 if (frame instanceof SplitFrame && frame != source)
3140 SplitFrame sf = (SplitFrame) frame;
3141 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3142 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3143 boolean gatherThis = false;
3144 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3146 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3147 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3148 if (topViewId.equals(topPanel.av.getSequenceSetId())
3149 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3152 topPanel.av.setGatherViewsHere(false);
3153 bottomPanel.av.setGatherViewsHere(false);
3154 topPanel.av.setExplodedGeometry(
3155 new Rectangle(sf.getLocation(), topFrame.getSize()));
3156 bottomPanel.av.setExplodedGeometry(
3157 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3158 myTopFrame.addAlignmentPanel(topPanel, false);
3159 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3165 topFrame.getAlignPanels().clear();
3166 bottomFrame.getAlignPanels().clear();
3173 * The dust settles...give focus to the tab we did this from.
3175 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3178 public static groovy.ui.Console getGroovyConsole()
3180 return groovyConsole;
3184 * handles the payload of a drag and drop event.
3186 * TODO refactor to desktop utilities class
3189 * - Data source strings extracted from the drop event
3191 * - protocol for each data source extracted from the drop event
3195 * - the payload from the drop event
3198 public static void transferFromDropTarget(List<Object> files,
3199 List<DataSourceType> protocols, DropTargetDropEvent evt,
3200 Transferable t) throws Exception
3203 DataFlavor uriListFlavor = new DataFlavor(
3204 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3207 urlFlavour = new DataFlavor(
3208 "application/x-java-url; class=java.net.URL");
3209 } catch (ClassNotFoundException cfe)
3211 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3215 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3220 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3221 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3222 // means url may be null.
3225 protocols.add(DataSourceType.URL);
3226 files.add(url.toString());
3227 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3228 + files.get(files.size() - 1));
3233 if (Platform.isAMacAndNotJS())
3236 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3239 } catch (Throwable ex)
3241 jalview.bin.Console.debug("URL drop handler failed.", ex);
3244 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3246 // Works on Windows and MacOSX
3247 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3248 for (Object file : (List) t
3249 .getTransferData(DataFlavor.javaFileListFlavor))
3252 protocols.add(DataSourceType.FILE);
3257 // Unix like behaviour
3258 boolean added = false;
3260 if (t.isDataFlavorSupported(uriListFlavor))
3262 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3263 // This is used by Unix drag system
3264 data = (String) t.getTransferData(uriListFlavor);
3268 // fallback to text: workaround - on OSX where there's a JVM bug
3270 .debug("standard URIListFlavor failed. Trying text");
3271 // try text fallback
3272 DataFlavor textDf = new DataFlavor(
3273 "text/plain;class=java.lang.String");
3274 if (t.isDataFlavorSupported(textDf))
3276 data = (String) t.getTransferData(textDf);
3279 jalview.bin.Console.debug("Plain text drop content returned "
3280 + (data == null ? "Null - failed" : data));
3285 while (protocols.size() < files.size())
3287 jalview.bin.Console.debug("Adding missing FILE protocol for "
3288 + files.get(protocols.size()));
3289 protocols.add(DataSourceType.FILE);
3291 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3292 data, "\r\n"); st.hasMoreTokens();)
3295 String s = st.nextToken();
3296 if (s.startsWith("#"))
3298 // the line is a comment (as per the RFC 2483)
3301 java.net.URI uri = new java.net.URI(s);
3302 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3304 protocols.add(DataSourceType.URL);
3305 files.add(uri.toString());
3309 // otherwise preserve old behaviour: catch all for file objects
3310 java.io.File file = new java.io.File(uri);
3311 protocols.add(DataSourceType.FILE);
3312 files.add(file.toString());
3317 if (jalview.bin.Console.isDebugEnabled())
3319 if (data == null || !added)
3322 if (t.getTransferDataFlavors() != null
3323 && t.getTransferDataFlavors().length > 0)
3325 jalview.bin.Console.debug(
3326 "Couldn't resolve drop data. Here are the supported flavors:");
3327 for (DataFlavor fl : t.getTransferDataFlavors())
3329 jalview.bin.Console.debug(
3330 "Supported transfer dataflavor: " + fl.toString());
3331 Object df = t.getTransferData(fl);
3334 jalview.bin.Console.debug("Retrieves: " + df);
3338 jalview.bin.Console.debug("Retrieved nothing");
3345 .debug("Couldn't resolve dataflavor for drop: "
3351 if (Platform.isWindowsAndNotJS())
3354 .debug("Scanning dropped content for Windows Link Files");
3356 // resolve any .lnk files in the file drop
3357 for (int f = 0; f < files.size(); f++)
3359 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3360 if (protocols.get(f).equals(DataSourceType.FILE)
3361 && (source.endsWith(".lnk") || source.endsWith(".url")
3362 || source.endsWith(".site")))
3366 Object obj = files.get(f);
3367 File lf = (obj instanceof File ? (File) obj
3368 : new File((String) obj));
3369 // process link file to get a URL
3370 jalview.bin.Console.debug("Found potential link file: " + lf);
3371 WindowsShortcut wscfile = new WindowsShortcut(lf);
3372 String fullname = wscfile.getRealFilename();
3373 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3374 files.set(f, fullname);
3375 jalview.bin.Console.debug("Parsed real filename " + fullname
3376 + " to extract protocol: " + protocols.get(f));
3377 } catch (Exception ex)
3379 jalview.bin.Console.error(
3380 "Couldn't parse " + files.get(f) + " as a link file.",
3389 * Sets the Preferences property for experimental features to True or False
3390 * depending on the state of the controlling menu item
3393 protected void showExperimental_actionPerformed(boolean selected)
3395 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3399 * Answers a (possibly empty) list of any structure viewer frames (currently
3400 * for either Jmol or Chimera) which are currently open. This may optionally
3401 * be restricted to viewers of a specified class, or viewers linked to a
3402 * specified alignment panel.
3405 * if not null, only return viewers linked to this panel
3406 * @param structureViewerClass
3407 * if not null, only return viewers of this class
3410 public List<StructureViewerBase> getStructureViewers(
3411 AlignmentPanel apanel,
3412 Class<? extends StructureViewerBase> structureViewerClass)
3414 List<StructureViewerBase> result = new ArrayList<>();
3415 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3417 for (JInternalFrame frame : frames)
3419 if (frame instanceof StructureViewerBase)
3421 if (structureViewerClass == null
3422 || structureViewerClass.isInstance(frame))
3425 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3427 result.add((StructureViewerBase) frame);
3435 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3437 private static boolean debugScaleMessageDone = false;
3439 public static void debugScaleMessage(Graphics g)
3441 if (debugScaleMessageDone)
3445 // output used by tests to check HiDPI scaling settings in action
3448 Graphics2D gg = (Graphics2D) g;
3451 AffineTransform t = gg.getTransform();
3452 double scaleX = t.getScaleX();
3453 double scaleY = t.getScaleY();
3454 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3455 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3456 debugScaleMessageDone = true;
3460 jalview.bin.Console.debug("Desktop graphics null");
3462 } catch (Exception e)
3464 jalview.bin.Console.debug(Cache.getStackTraceString(e));