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.GridLayout;
29 import java.awt.Point;
30 import java.awt.Rectangle;
31 import java.awt.Toolkit;
32 import java.awt.Window;
33 import java.awt.datatransfer.Clipboard;
34 import java.awt.datatransfer.ClipboardOwner;
35 import java.awt.datatransfer.DataFlavor;
36 import java.awt.datatransfer.Transferable;
37 import java.awt.dnd.DnDConstants;
38 import java.awt.dnd.DropTargetDragEvent;
39 import java.awt.dnd.DropTargetDropEvent;
40 import java.awt.dnd.DropTargetEvent;
41 import java.awt.dnd.DropTargetListener;
42 import java.awt.event.ActionEvent;
43 import java.awt.event.ActionListener;
44 import java.awt.event.InputEvent;
45 import java.awt.event.KeyEvent;
46 import java.awt.event.MouseAdapter;
47 import java.awt.event.MouseEvent;
48 import java.awt.event.WindowAdapter;
49 import java.awt.event.WindowEvent;
50 import java.beans.PropertyChangeEvent;
51 import java.beans.PropertyChangeListener;
53 import java.io.FileWriter;
54 import java.io.IOException;
56 import java.util.ArrayList;
57 import java.util.HashMap;
58 import java.util.Hashtable;
59 import java.util.List;
60 import java.util.ListIterator;
61 import java.util.Vector;
62 import java.util.concurrent.ExecutionException;
63 import java.util.concurrent.ExecutorService;
64 import java.util.concurrent.Executors;
65 import java.util.concurrent.Future;
66 import java.util.concurrent.FutureTask;
67 import java.util.concurrent.Semaphore;
69 import javax.swing.AbstractAction;
70 import javax.swing.Action;
71 import javax.swing.ActionMap;
72 import javax.swing.Box;
73 import javax.swing.BoxLayout;
74 import javax.swing.DefaultDesktopManager;
75 import javax.swing.DesktopManager;
76 import javax.swing.InputMap;
77 import javax.swing.JButton;
78 import javax.swing.JCheckBox;
79 import javax.swing.JComboBox;
80 import javax.swing.JComponent;
81 import javax.swing.JDesktopPane;
82 import javax.swing.JInternalFrame;
83 import javax.swing.JLabel;
84 import javax.swing.JMenuItem;
85 import javax.swing.JPanel;
86 import javax.swing.JPopupMenu;
87 import javax.swing.JProgressBar;
88 import javax.swing.JTextField;
89 import javax.swing.KeyStroke;
90 import javax.swing.SwingUtilities;
91 import javax.swing.event.HyperlinkEvent;
92 import javax.swing.event.HyperlinkEvent.EventType;
93 import javax.swing.event.InternalFrameAdapter;
94 import javax.swing.event.InternalFrameEvent;
96 import org.stackoverflowusers.file.WindowsShortcut;
98 import jalview.api.AlignViewportI;
99 import jalview.api.AlignmentViewPanel;
100 import jalview.api.StructureSelectionManagerProvider;
101 import jalview.bin.ApplicationSingletonProvider;
102 import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
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.GDesktop;
118 import jalview.jbgui.GSplitFrame;
119 import jalview.jbgui.GStructureViewer;
120 import jalview.project.Jalview2XML;
121 import jalview.structure.StructureSelectionManager;
122 import jalview.urls.IdOrgSettings;
123 import jalview.util.BrowserLauncher;
124 import jalview.util.ImageMaker.TYPE;
125 import jalview.util.MessageManager;
126 import jalview.util.Platform;
127 import jalview.util.UrlConstants;
128 import jalview.viewmodel.AlignmentViewport;
129 import jalview.ws.WSDiscovererI;
130 import jalview.ws.params.ParamManager;
131 import jalview.ws.utils.UrlDownloadClient;
138 * @version $Revision: 1.155 $
140 @SuppressWarnings("serial")
141 public class Desktop extends GDesktop
142 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
143 StructureSelectionManagerProvider, ApplicationSingletonI
146 private static final String CITATION = "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
147 + "<br><br>For help, see the FAQ at <a href=\"http://www.jalview.org/faq\">www.jalview.org/faq</a> and/or join the jalview-discuss@jalview.org mailing list"
148 + "<br><br>If you use Jalview, please cite:"
149 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
150 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
151 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033";
153 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
155 private static int DEFAULT_MIN_WIDTH = 300;
157 private static int DEFAULT_MIN_HEIGHT = 250;
159 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
161 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
163 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
165 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
167 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
169 @SuppressWarnings("deprecation")
170 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
173 * news reader - null if it was never started.
175 private BlogReader jvnews = null;
177 private File projectFile;
181 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
184 public void addJalviewPropertyChangeListener(
185 PropertyChangeListener listener)
187 changeSupport.addJalviewPropertyChangeListener(listener);
191 * @param propertyName
193 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
194 * java.beans.PropertyChangeListener)
197 public void addJalviewPropertyChangeListener(String propertyName,
198 PropertyChangeListener listener)
200 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
204 * @param propertyName
206 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
207 * java.beans.PropertyChangeListener)
210 public void removeJalviewPropertyChangeListener(String propertyName,
211 PropertyChangeListener listener)
213 changeSupport.removeJalviewPropertyChangeListener(propertyName,
217 private MyDesktopPane desktopPane;
219 public static MyDesktopPane getDesktopPane()
221 Desktop desktop = getInstance();
222 return desktop == null ? null : desktop.desktopPane;
226 * Answers an 'application scope' singleton instance of this class. Separate
227 * SwingJS 'applets' running in the same browser page will each have a
228 * distinct instance of Desktop.
232 public static Desktop getInstance()
234 return Jalview.isHeadlessMode() ? null
235 : (Desktop) ApplicationSingletonProvider
236 .getInstance(Desktop.class);
239 public static StructureSelectionManager getStructureSelectionManager()
241 return StructureSelectionManager
242 .getStructureSelectionManager(getInstance());
245 int openFrameCount = 0;
247 final int xOffset = 30;
249 final int yOffset = 30;
251 public jalview.ws.jws1.Discoverer discoverer;
253 public Object[] jalviewClipboard;
255 public boolean internalCopy = false;
257 int fileLoadingCount = 0;
259 class MyDesktopManager implements DesktopManager
262 private DesktopManager delegate;
264 public MyDesktopManager(DesktopManager delegate)
266 this.delegate = delegate;
270 public void activateFrame(JInternalFrame f)
274 delegate.activateFrame(f);
275 } catch (NullPointerException npe)
277 Point p = getMousePosition();
278 showPasteMenu(p.x, p.y);
283 public void beginDraggingFrame(JComponent f)
285 delegate.beginDraggingFrame(f);
289 public void beginResizingFrame(JComponent f, int direction)
291 delegate.beginResizingFrame(f, direction);
295 public void closeFrame(JInternalFrame f)
297 delegate.closeFrame(f);
301 public void deactivateFrame(JInternalFrame f)
303 delegate.deactivateFrame(f);
307 public void deiconifyFrame(JInternalFrame f)
309 delegate.deiconifyFrame(f);
313 public void dragFrame(JComponent f, int newX, int newY)
319 delegate.dragFrame(f, newX, newY);
323 public void endDraggingFrame(JComponent f)
325 delegate.endDraggingFrame(f);
326 desktopPane.repaint();
330 public void endResizingFrame(JComponent f)
332 delegate.endResizingFrame(f);
333 desktopPane.repaint();
337 public void iconifyFrame(JInternalFrame f)
339 delegate.iconifyFrame(f);
343 public void maximizeFrame(JInternalFrame f)
345 delegate.maximizeFrame(f);
349 public void minimizeFrame(JInternalFrame f)
351 delegate.minimizeFrame(f);
355 public void openFrame(JInternalFrame f)
357 delegate.openFrame(f);
361 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
368 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
372 public void setBoundsForFrame(JComponent f, int newX, int newY,
373 int newWidth, int newHeight)
375 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
378 // All other methods, simply delegate
383 * Private constructor enforces singleton pattern. It is called by reflection
384 * from ApplicationSingletonProvider.getInstance().
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
397 doConfigureStructurePrefs();
398 setTitle("Jalview " + Cache.getProperty("VERSION"));
400 if (!Platform.isAMac())
402 // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
406 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
412 APQHandlers.setAPQHandlers(this);
413 } catch (Throwable t)
415 System.out.println("Error setting APQHandlers: " + t.toString());
416 // t.printStackTrace();
419 addWindowListener(new WindowAdapter()
423 public void windowClosing(WindowEvent ev)
429 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
431 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
432 desktopPane = new MyDesktopPane(selmemusage);
434 showMemusage.setSelected(selmemusage);
435 desktopPane.setBackground(Color.white);
437 getContentPane().setLayout(new BorderLayout());
438 // alternate config - have scrollbars - see notes in JAL-153
439 // JScrollPane sp = new JScrollPane();
440 // sp.getViewport().setView(desktop);
441 // getContentPane().add(sp, BorderLayout.CENTER);
443 // BH 2018 - just an experiment to try unclipped JInternalFrames.
446 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
449 getContentPane().add(desktopPane, BorderLayout.CENTER);
450 desktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
452 // This line prevents Windows Look&Feel resizing all new windows to
454 // if previous window was maximised
455 desktopPane.setDesktopManager(new MyDesktopManager(
456 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
457 : Platform.isAMacAndNotJS()
458 ? new AquaInternalFrameManager(
459 desktopPane.getDesktopManager())
460 : desktopPane.getDesktopManager())));
462 Rectangle dims = getLastKnownDimensions("");
469 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
470 int xPos = Math.max(5, (screenSize.width - 900) / 2);
471 int yPos = Math.max(5, (screenSize.height - 650) / 2);
472 setBounds(xPos, yPos, 900, 650);
475 getIdentifiersOrgData();
477 if (!Platform.isJS())
484 jconsole = new Console(this, showjconsole);
485 jconsole.setHeader(Cache.getVersionDetailsForConsole());
486 showConsole(showjconsole);
488 showNews.setVisible(false);
490 experimentalFeatures.setSelected(showExperimental());
492 if (Jalview.isInteractive())
494 // disabled for SeqCanvasTest
497 // Spawn a thread that shows the splashscreen
499 SwingUtilities.invokeLater(new Runnable()
504 new SplashScreen(true);
508 // Thread off a new instance of the file chooser - this reduces the
511 // takes to open it later on.
512 new Thread(new Runnable()
517 Cache.log.debug("Filechooser init thread started.");
518 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
519 JalviewFileChooser.forRead(
520 Cache.getProperty("LAST_DIRECTORY"), fileFormat);
521 Cache.log.debug("Filechooser init thread finished.");
524 // Add the service change listener
525 changeSupport.addJalviewPropertyChangeListener("services",
526 new PropertyChangeListener()
530 public void propertyChange(PropertyChangeEvent evt)
532 Cache.log.debug("Firing service changed event for "
533 + evt.getNewValue());
534 JalviewServicesChanged(evt);
539 this.setDropTarget(new java.awt.dnd.DropTarget(desktopPane, this));
541 this.addWindowListener(new WindowAdapter()
544 public void windowClosing(WindowEvent evt)
551 this.addMouseListener(ma = new MouseAdapter()
554 public void mousePressed(MouseEvent evt)
556 if (evt.isPopupTrigger()) // Mac
558 showPasteMenu(evt.getX(), evt.getY());
563 public void mouseReleased(MouseEvent evt)
565 if (evt.isPopupTrigger()) // Windows
567 showPasteMenu(evt.getX(), evt.getY());
571 desktopPane.addMouseListener(ma);
572 } catch (Throwable t)
580 * Answers true if user preferences to enable experimental features is True
585 public boolean showExperimental()
587 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
588 Boolean.FALSE.toString());
589 return Boolean.valueOf(experimental).booleanValue();
592 public void doConfigureStructurePrefs()
594 // configure services
595 StructureSelectionManager ssm = StructureSelectionManager
596 .getStructureSelectionManager(this);
597 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
599 ssm.setAddTempFacAnnot(
600 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
601 ssm.setProcessSecondaryStructure(
602 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
603 ssm.setSecStructServices(
604 Cache.getDefault(Preferences.USE_RNAVIEW, true));
608 ssm.setAddTempFacAnnot(false);
609 ssm.setProcessSecondaryStructure(false);
610 ssm.setSecStructServices(false);
614 public void checkForNews()
616 final Desktop me = this;
617 // Thread off the news reader, in case there are connection problems.
618 new Thread(new Runnable()
623 Cache.log.debug("Starting news thread.");
624 jvnews = new BlogReader(me);
625 showNews.setVisible(true);
626 Cache.log.debug("Completed news thread.");
631 public void getIdentifiersOrgData()
633 // Thread off the identifiers fetcher
634 new Thread(new Runnable()
639 Cache.log.debug("Downloading data from identifiers.org");
642 UrlDownloadClient.download(IdOrgSettings.getUrl(),
643 IdOrgSettings.getDownloadLocation());
644 } catch (IOException e)
646 Cache.log.debug("Exception downloading identifiers.org data"
655 protected void showNews_actionPerformed(ActionEvent e)
657 showNews(showNews.isSelected());
660 void showNews(boolean visible)
662 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
663 showNews.setSelected(visible);
664 if (visible && !jvnews.isVisible())
666 new Thread(new Runnable()
671 long now = System.currentTimeMillis();
672 setProgressBar(MessageManager.getString("status.refreshing_news"),
674 jvnews.refreshNews();
675 setProgressBar(null, now);
683 * recover the last known dimensions for a jalview window
686 * - empty string is desktop, all other windows have unique prefix
687 * @return null or last known dimensions scaled to current geometry (if last
688 * window geom was known)
690 Rectangle getLastKnownDimensions(String windowName)
692 // TODO: lock aspect ratio for scaling desktop Bug #0058199
693 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
694 String x = Cache.getProperty(windowName + "SCREEN_X");
695 String y = Cache.getProperty(windowName + "SCREEN_Y");
696 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
697 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
698 if ((x != null) && (y != null) && (width != null) && (height != null))
700 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
701 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
702 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
704 // attempt #1 - try to cope with change in screen geometry - this
705 // version doesn't preserve original jv aspect ratio.
706 // take ratio of current screen size vs original screen size.
707 double sw = ((1f * screenSize.width) / (1f * Integer
708 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
709 double sh = ((1f * screenSize.height) / (1f * Integer
710 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
711 // rescale the bounds depending upon the current screen geometry.
712 ix = (int) (ix * sw);
713 iw = (int) (iw * sw);
714 iy = (int) (iy * sh);
715 ih = (int) (ih * sh);
716 while (ix >= screenSize.width)
719 "Window geometry location recall error: shifting horizontal to within screenbounds.");
720 ix -= screenSize.width;
722 while (iy >= screenSize.height)
725 "Window geometry location recall error: shifting vertical to within screenbounds.");
726 iy -= screenSize.height;
729 "Got last known dimensions for " + windowName + ": x:" + ix
730 + " y:" + iy + " width:" + iw + " height:" + ih);
732 // return dimensions for new instance
733 return new Rectangle(ix, iy, iw, ih);
738 void showPasteMenu(int x, int y)
740 JPopupMenu popup = new JPopupMenu();
741 JMenuItem item = new JMenuItem(
742 MessageManager.getString("label.paste_new_window"));
743 item.addActionListener(new ActionListener()
746 public void actionPerformed(ActionEvent evt)
753 popup.show(this, x, y);
760 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
761 Transferable contents = c.getContents(this);
763 if (contents != null)
765 String file = (String) contents
766 .getTransferData(DataFlavor.stringFlavor);
768 FileFormatI format = new IdentifyFile().identify(file,
769 DataSourceType.PASTE);
771 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
774 } catch (Exception ex)
777 "Unable to paste alignment from system clipboard:\n" + ex);
782 // * Add an internal frame to the Jalview desktop that is allowed to be resized,
783 // * has a minimum size of 300px and might or might not be visible
789 // * @param makeVisible
790 // * When true, display frame immediately, otherwise, caller must call
791 // * setVisible themselves.
798 // public static synchronized void addInternalFrame(
799 // final JInternalFrame frame, String title, boolean makeVisible,
802 // // textbox, web services, sequenceFetcher, featureSettings
803 // getInstance().addFrame(frame, title, makeVisible, w, h,
804 // FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
808 // * Add an internal frame to the Jalview desktop that is visible, has a minimum
809 // * size of 300px, and may or may not be resizable
819 // * @param resizable
823 // public static synchronized void addInternalFrame(
824 // final JInternalFrame frame, String title, int w, int h,
825 // boolean resizable)
827 // // annotation, font, calculation, user-defined colors
828 // getInstance().addFrame(frame, title, FRAME_MAKE_VISIBLE, w, h,
829 // resizable, FRAME_SET_MIN_SIZE_300);
833 * Adds and opens the given frame to the desktop that is visible, allowed to
834 * resize, and has a 300px minimum width.
845 public static synchronized void addInternalFrame(
846 final JInternalFrame frame, String title, int w, int h)
850 addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, w, h,
851 FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
855 * Add an internal frame to the Jalview desktop that may optionally be
856 * visible, resizable, and allowed to be any size
863 * When true, display frame immediately, otherwise, caller must call
864 * setVisible themselves.
871 * @param ignoreMinSize
872 * Do not set the default minimum size for frame
874 public static synchronized void addInternalFrame(
875 final JInternalFrame frame, String title, boolean makeVisible,
876 int w, int h, boolean resizable, boolean ignoreMinSize)
878 // 15 classes call this method directly.
880 // TODO: allow callers to determine X and Y position of frame (eg. via
882 // TODO: consider fixing method to update entries in the window submenu with
883 // the current window title
885 frame.setTitle(title);
886 if (frame.getWidth() < 1 || frame.getHeight() < 1)
890 if (getInstance() != null)
891 getInstance().addFrame(frame, makeVisible, resizable,
895 // These can now by put into a single int flag, if desired:
897 public final static boolean FRAME_MAKE_VISIBLE = true;
899 public final static boolean FRAME_NOT_VISIBLE = false;
901 public final static boolean FRAME_ALLOW_RESIZE = true;
903 public final static boolean FRAME_NOT_RESIZABLE = false;
905 public final static boolean FRAME_ALLOW_ANY_SIZE = true;
907 public final static boolean FRAME_SET_MIN_SIZE_300 = false;
909 private void addFrame(JInternalFrame frame,
910 boolean makeVisible, boolean resizable,
911 boolean ignoreMinSize)
916 boolean isEmbedded = (Platform.getEmbeddedAttribute(frame, "id") != null);
917 boolean hasEmbeddedSize = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
918 // Web page embedding allows us to ignore minimum size
919 ignoreMinSize |= hasEmbeddedSize;
923 // Set default dimension for Alignment Frame window.
924 // The Alignment Frame window could be added from a number of places,
926 // I did this here in order not to miss out on any Alignment frame.
927 if (frame instanceof AlignFrame)
929 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
930 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
932 frame.setMinimumSize(
933 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
938 frame.setVisible(makeVisible);
939 frame.setClosable(true);
940 frame.setResizable(resizable);
941 frame.setMaximizable(resizable);
942 frame.setIconifiable(resizable);
943 frame.setOpaque(Platform.isJS());
944 if (!isEmbedded && frame.getX() < 1 && frame.getY() < 1)
946 frame.setLocation(xOffset * openFrameCount,
947 yOffset * ((openFrameCount - 1) % 10) + yOffset);
951 * add an entry for the new frame in the Window menu
952 * (and remove it when the frame is closed)
954 final JMenuItem menuItem = new JMenuItem(frame.getTitle());
955 frame.addInternalFrameListener(new InternalFrameAdapter()
958 public void internalFrameActivated(InternalFrameEvent evt)
960 JInternalFrame itf = getDesktopPane().getSelectedFrame();
963 if (itf instanceof AlignFrame)
965 Jalview.setCurrentAlignFrame((AlignFrame) itf);
972 public void internalFrameClosed(InternalFrameEvent evt)
974 PaintRefresher.RemoveComponent(frame);
977 * defensive check to prevent frames being
978 * added half off the window
980 if (openFrameCount > 0)
986 * ensure no reference to alignFrame retained by menu item listener
988 if (menuItem.getActionListeners().length > 0)
990 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
992 getInstance().windowMenu.remove(menuItem);
996 menuItem.addActionListener(new ActionListener()
999 public void actionPerformed(ActionEvent e)
1003 frame.setSelected(true);
1004 frame.setIcon(false);
1005 } catch (java.beans.PropertyVetoException ex)
1007 // System.err.println(ex.toString());
1012 setKeyBindings(frame);
1014 getDesktopPane().add(frame);
1016 getInstance().windowMenu.add(menuItem);
1021 frame.setSelected(true);
1022 frame.requestFocus();
1023 } catch (java.beans.PropertyVetoException ve)
1025 } catch (java.lang.ClassCastException cex)
1028 "Squashed a possible GUI implementation error. If you can recreate this, please look at http://issues.jalview.org/browse/JAL-869",
1034 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1039 private static void setKeyBindings(JInternalFrame frame)
1041 final Action closeAction = new AbstractAction()
1044 public void actionPerformed(ActionEvent e)
1051 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1053 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1054 InputEvent.CTRL_DOWN_MASK);
1055 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1056 Platform.SHORTCUT_KEY_MASK);
1058 InputMap inputMap = frame
1059 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1060 String ctrlW = ctrlWKey.toString();
1061 inputMap.put(ctrlWKey, ctrlW);
1062 inputMap.put(cmdWKey, ctrlW);
1064 ActionMap actionMap = frame.getActionMap();
1065 actionMap.put(ctrlW, closeAction);
1069 public void lostOwnership(Clipboard clipboard, Transferable contents)
1073 jalviewClipboard = null;
1076 internalCopy = false;
1080 public void dragEnter(DropTargetDragEvent evt)
1085 public void dragExit(DropTargetEvent evt)
1090 public void dragOver(DropTargetDragEvent evt)
1095 public void dropActionChanged(DropTargetDragEvent evt)
1106 public void drop(DropTargetDropEvent evt)
1108 boolean success = true;
1109 // JAL-1552 - acceptDrop required before getTransferable call for
1110 // Java's Transferable for native dnd
1111 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1112 Transferable t = evt.getTransferable();
1113 List<Object> files = new ArrayList<>();
1114 List<DataSourceType> protocols = new ArrayList<>();
1118 transferFromDropTarget(files, protocols, evt, t);
1119 } catch (Exception e)
1121 e.printStackTrace();
1129 for (int i = 0; i < files.size(); i++)
1131 // BH 2018 File or String
1132 Object file = files.get(i);
1133 String fileName = file.toString();
1134 DataSourceType protocol = (protocols == null)
1135 ? DataSourceType.FILE
1137 FileFormatI format = null;
1139 if (fileName.endsWith(".jar"))
1141 format = FileFormat.Jalview;
1146 format = new IdentifyFile().identify(file, protocol);
1148 if (file instanceof File)
1150 Platform.cacheFileData((File) file);
1152 new FileLoader().LoadFile(null, file, protocol, format);
1155 } catch (Exception ex)
1160 evt.dropComplete(success); // need this to ensure input focus is properly
1161 // transfered to any new windows created
1171 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1173 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1174 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1175 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1176 BackupFiles.getEnabled());
1178 chooser.setFileView(new JalviewFileView());
1179 chooser.setDialogTitle(
1180 MessageManager.getString("label.open_local_file"));
1181 chooser.setToolTipText(MessageManager.getString("action.open"));
1183 chooser.setResponseHandler(0, new Runnable()
1188 File selectedFile = chooser.getSelectedFile();
1189 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1191 FileFormatI format = chooser.getSelectedFormat();
1194 * Call IdentifyFile to verify the file contains what its extension implies.
1195 * Skip this step for dynamically added file formats, because
1196 * IdentifyFile does not know how to recognise them.
1198 if (FileFormats.getInstance().isIdentifiable(format))
1202 format = new IdentifyFile().identify(selectedFile,
1203 DataSourceType.FILE);
1204 } catch (FileFormatException e)
1206 // format = null; //??
1210 new FileLoader().LoadFile(viewport, selectedFile,
1211 DataSourceType.FILE, format);
1214 chooser.showOpenDialog(this);
1218 * Shows a dialog for input of a URL at which to retrieve alignment data
1223 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1225 // This construct allows us to have a wider textfield
1227 JLabel label = new JLabel(
1228 MessageManager.getString("label.input_file_url"));
1230 JPanel panel = new JPanel(new GridLayout(2, 1));
1234 * the URL to fetch is
1235 * Java: an editable combobox with history
1236 * JS: (pending JAL-3038) a plain text field
1239 String urlBase = "http://www.";
1240 if (Platform.isJS())
1242 history = new JTextField(urlBase, 35);
1251 JComboBox<String> asCombo = new JComboBox<>();
1252 asCombo.setPreferredSize(new Dimension(400, 20));
1253 asCombo.setEditable(true);
1254 asCombo.addItem(urlBase);
1255 String historyItems = Cache.getProperty("RECENT_URL");
1256 if (historyItems != null)
1258 for (String token : historyItems.split("\\t"))
1260 asCombo.addItem(token);
1267 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1268 MessageManager.getString("action.cancel") };
1269 Runnable action = new Runnable()
1274 @SuppressWarnings("unchecked")
1275 String url = (history instanceof JTextField
1276 ? ((JTextField) history).getText()
1277 : ((JComboBox<String>) history).getSelectedItem()
1280 if (url.toLowerCase().endsWith(".jar"))
1282 if (viewport != null)
1284 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1285 FileFormat.Jalview);
1289 new FileLoader().LoadFile(url, DataSourceType.URL,
1290 FileFormat.Jalview);
1295 FileFormatI format = null;
1298 format = new IdentifyFile().identify(url, DataSourceType.URL);
1299 } catch (FileFormatException e)
1301 // TODO revise error handling, distinguish between
1302 // URL not found and response not valid
1307 String msg = MessageManager
1308 .formatMessage("label.couldnt_locate", url);
1309 JvOptionPane.showInternalMessageDialog(getDesktopPane(), msg,
1310 MessageManager.getString("label.url_not_found"),
1311 JvOptionPane.WARNING_MESSAGE);
1316 if (viewport != null)
1318 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1323 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1328 String dialogOption = MessageManager
1329 .getString("label.input_alignment_from_url");
1330 JvOptionPane.newOptionDialog(desktopPane).setResponseHandler(0, action)
1331 .showInternalDialog(panel, dialogOption,
1332 JvOptionPane.YES_NO_CANCEL_OPTION,
1333 JvOptionPane.PLAIN_MESSAGE, null, options,
1334 MessageManager.getString("action.ok"));
1338 * Opens the CutAndPaste window for the user to paste an alignment in to
1341 * - if not null, the pasted alignment is added to the current
1342 * alignment; if null, to a new alignment window
1345 public void inputTextboxMenuItem_actionPerformed(
1346 AlignmentViewPanel viewPanel)
1348 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1349 cap.setForInput(viewPanel);
1350 addInternalFrame(cap,
1351 MessageManager.getString("label.cut_paste_alignmen_file"),
1352 FRAME_MAKE_VISIBLE, 600, 500, FRAME_ALLOW_RESIZE,
1353 FRAME_SET_MIN_SIZE_300);
1362 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1363 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1364 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1365 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1366 getWidth(), getHeight()));
1368 if (jconsole != null)
1370 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1371 jconsole.stopConsole();
1375 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1378 if (dialogExecutor != null)
1380 dialogExecutor.shutdownNow();
1382 closeAll_actionPerformed(null);
1384 if (groovyConsole != null)
1386 // suppress a possible repeat prompt to save script
1387 groovyConsole.setDirty(false);
1388 groovyConsole.exit();
1393 private void storeLastKnownDimensions(String string, Rectangle jc)
1395 Cache.log.debug("Storing last known dimensions for " + string + ": x:"
1396 + jc.x + " y:" + jc.y + " width:" + jc.width + " height:"
1399 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1400 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1401 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1402 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1412 public void aboutMenuItem_actionPerformed(ActionEvent e)
1414 new Thread(new Runnable()
1419 new SplashScreen(false);
1425 * Returns the html text for the About screen, including any available version
1426 * number, build details, author details and citation reference, but without
1427 * the enclosing {@code html} tags
1431 public String getAboutMessage()
1433 StringBuilder message = new StringBuilder(1024);
1434 message.append("<h1><strong>Version: ")
1435 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1436 .append("<strong>Built: <em>")
1437 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1438 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1439 .append("</strong>");
1441 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1442 if (latestVersion.equals("Checking"))
1444 // JBP removed this message for 2.11: May be reinstated in future version
1445 // message.append("<br>...Checking latest version...</br>");
1447 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1449 boolean red = false;
1450 if (Cache.getProperty("VERSION").toLowerCase()
1451 .indexOf("automated build") == -1)
1454 // Displayed when code version and jnlp version do not match and code
1455 // version is not a development build
1456 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1459 message.append("<br>!! Version ")
1460 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1461 .append(" is available for download from ")
1462 .append(Cache.getDefault("www.jalview.org",
1463 "http://www.jalview.org"))
1467 message.append("</div>");
1470 message.append("<br>Authors: ");
1471 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1472 message.append(CITATION);
1474 return message.toString();
1478 * Action on requesting Help documentation
1481 public void documentationMenuItem_actionPerformed()
1485 if (Platform.isJS())
1487 BrowserLauncher.openURL("http://www.jalview.org/help.html");
1496 Help.showHelpWindow();
1498 } catch (Exception ex)
1500 System.err.println("Error opening help: " + ex.getMessage());
1505 public void closeAll_actionPerformed(ActionEvent e)
1507 // TODO show a progress bar while closing?
1508 JInternalFrame[] frames = desktopPane.getAllFrames();
1509 for (int i = 0; i < frames.length; i++)
1513 frames[i].setClosed(true);
1514 } catch (java.beans.PropertyVetoException ex)
1518 Jalview.setCurrentAlignFrame(null);
1519 System.out.println("ALL CLOSED");
1522 * reset state of singleton objects as appropriate (clear down session state
1523 * when all windows are closed)
1525 StructureSelectionManager ssm = StructureSelectionManager
1526 .getStructureSelectionManager(this);
1534 public void raiseRelated_actionPerformed(ActionEvent e)
1536 reorderAssociatedWindows(false, false);
1540 public void minimizeAssociated_actionPerformed(ActionEvent e)
1542 reorderAssociatedWindows(true, false);
1545 void closeAssociatedWindows()
1547 reorderAssociatedWindows(false, true);
1553 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1557 protected void garbageCollect_actionPerformed(ActionEvent e)
1559 // We simply collect the garbage
1560 Cache.log.debug("Collecting garbage...");
1562 Cache.log.debug("Finished garbage collection.");
1569 * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1573 protected void showMemusage_actionPerformed(ActionEvent e)
1575 desktopPane.showMemoryUsage(showMemusage.isSelected());
1582 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1586 protected void showConsole_actionPerformed(ActionEvent e)
1588 showConsole(showConsole.isSelected());
1591 Console jconsole = null;
1594 * control whether the java console is visible or not
1598 void showConsole(boolean selected)
1600 // TODO: decide if we should update properties file
1601 if (jconsole != null) // BH 2018
1603 showConsole.setSelected(selected);
1604 Cache.setProperty("SHOW_JAVA_CONSOLE",
1605 Boolean.valueOf(selected).toString());
1606 jconsole.setVisible(selected);
1610 void reorderAssociatedWindows(boolean minimize, boolean close)
1612 JInternalFrame[] frames = desktopPane.getAllFrames();
1613 if (frames == null || frames.length < 1)
1618 AlignViewportI source = null;
1619 AlignViewportI target = null;
1620 if (frames[0] instanceof AlignFrame)
1622 source = ((AlignFrame) frames[0]).getCurrentView();
1624 else if (frames[0] instanceof TreePanel)
1626 source = ((TreePanel) frames[0]).getViewPort();
1628 else if (frames[0] instanceof PCAPanel)
1630 source = ((PCAPanel) frames[0]).av;
1632 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1634 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1639 for (int i = 0; i < frames.length; i++)
1642 if (frames[i] == null)
1646 if (frames[i] instanceof AlignFrame)
1648 target = ((AlignFrame) frames[i]).getCurrentView();
1650 else if (frames[i] instanceof TreePanel)
1652 target = ((TreePanel) frames[i]).getViewPort();
1654 else if (frames[i] instanceof PCAPanel)
1656 target = ((PCAPanel) frames[i]).av;
1658 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1660 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1663 if (source == target)
1669 frames[i].setClosed(true);
1673 frames[i].setIcon(minimize);
1676 frames[i].toFront();
1680 } catch (java.beans.PropertyVetoException ex)
1695 protected void preferences_actionPerformed(ActionEvent e)
1701 * Prompts the user to choose a file and then saves the Jalview state as a
1702 * Jalview project file
1705 public void saveState_actionPerformed()
1707 saveState_actionPerformed(false);
1710 public void saveState_actionPerformed(boolean saveAs)
1712 java.io.File projectFile = getProjectFile();
1713 // autoSave indicates we already have a file and don't need to ask
1714 boolean autoSave = projectFile != null && !saveAs
1715 && BackupFiles.getEnabled();
1717 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1718 // saveAs="+saveAs+", Backups
1719 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1721 boolean approveSave = false;
1724 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1727 chooser.setFileView(new JalviewFileView());
1728 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1730 int value = chooser.showSaveDialog(this);
1732 if (value == JalviewFileChooser.APPROVE_OPTION)
1734 projectFile = chooser.getSelectedFile();
1735 setProjectFile(projectFile);
1740 if (approveSave || autoSave)
1742 final Desktop me = this;
1743 final java.io.File chosenFile = projectFile;
1744 new Thread(new Runnable()
1749 // TODO: refactor to Jalview desktop session controller action.
1750 setProgressBar(MessageManager.formatMessage(
1751 "label.saving_jalview_project", new Object[]
1752 { chosenFile.getName() }), chosenFile.hashCode());
1753 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1754 // TODO catch and handle errors for savestate
1755 // TODO prevent user from messing with the Desktop whilst we're saving
1758 boolean doBackup = BackupFiles.getEnabled();
1759 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1762 new Jalview2XML().saveState(
1763 doBackup ? backupfiles.getTempFile() : chosenFile);
1767 backupfiles.setWriteSuccess(true);
1768 backupfiles.rollBackupsAndRenameTempFile();
1770 } catch (OutOfMemoryError oom)
1772 new OOMWarning("Whilst saving current state to "
1773 + chosenFile.getName(), oom);
1774 } catch (Exception ex)
1776 Cache.log.error("Problems whilst trying to save to "
1777 + chosenFile.getName(), ex);
1778 JvOptionPane.showMessageDialog(me,
1779 MessageManager.formatMessage(
1780 "label.error_whilst_saving_current_state_to",
1782 { chosenFile.getName() }),
1783 MessageManager.getString("label.couldnt_save_project"),
1784 JvOptionPane.WARNING_MESSAGE);
1786 setProgressBar(null, chosenFile.hashCode());
1793 public void saveAsState_actionPerformed(ActionEvent e)
1795 saveState_actionPerformed(true);
1798 private void setProjectFile(File choice)
1800 this.projectFile = choice;
1803 public File getProjectFile()
1805 return this.projectFile;
1809 * Shows a file chooser dialog and tries to read in the selected file as a
1813 public void loadState_actionPerformed()
1815 final String[] suffix = new String[] { "jvp", "jar" };
1816 final String[] desc = new String[] { "Jalview Project",
1817 "Jalview Project (old)" };
1818 JalviewFileChooser chooser = new JalviewFileChooser(
1819 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1820 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1824 chooser.setFileView(new JalviewFileView());
1825 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1826 chooser.setResponseHandler(0, new Runnable()
1831 File selectedFile = chooser.getSelectedFile();
1832 setProjectFile(selectedFile);
1833 String choice = selectedFile.getAbsolutePath();
1834 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1835 new Thread(new Runnable()
1842 new Jalview2XML().loadJalviewAlign(selectedFile);
1843 } catch (OutOfMemoryError oom)
1845 new OOMWarning("Whilst loading project from " + choice, oom);
1846 } catch (Exception ex)
1849 "Problems whilst loading project from " + choice, ex);
1850 JvOptionPane.showMessageDialog(getDesktopPane(),
1851 MessageManager.formatMessage(
1852 "label.error_whilst_loading_project_from",
1856 .getString("label.couldnt_load_project"),
1857 JvOptionPane.WARNING_MESSAGE);
1864 chooser.showOpenDialog(this);
1868 public void inputSequence_actionPerformed(ActionEvent e)
1870 new SequenceFetcher(this);
1873 JPanel progressPanel;
1875 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1877 public void startLoading(final Object fileName)
1879 if (fileLoadingCount == 0)
1881 fileLoadingPanels.add(addProgressPanel(MessageManager
1882 .formatMessage("label.loading_file", new Object[]
1888 private JPanel addProgressPanel(String string)
1890 if (progressPanel == null)
1892 progressPanel = new JPanel(new GridLayout(1, 1));
1893 totalProgressCount = 0;
1894 getContentPane().add(progressPanel, BorderLayout.SOUTH);
1896 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1897 JProgressBar progressBar = new JProgressBar();
1898 progressBar.setIndeterminate(true);
1900 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1902 thisprogress.add(progressBar, BorderLayout.CENTER);
1903 progressPanel.add(thisprogress);
1904 ((GridLayout) progressPanel.getLayout()).setRows(
1905 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1906 ++totalProgressCount;
1908 return thisprogress;
1911 int totalProgressCount = 0;
1913 private void removeProgressPanel(JPanel progbar)
1915 if (progressPanel != null)
1917 synchronized (progressPanel)
1919 progressPanel.remove(progbar);
1920 GridLayout gl = (GridLayout) progressPanel.getLayout();
1921 gl.setRows(gl.getRows() - 1);
1922 if (--totalProgressCount < 1)
1924 this.getContentPane().remove(progressPanel);
1925 progressPanel = null;
1932 public void stopLoading()
1935 if (fileLoadingCount < 1)
1937 while (fileLoadingPanels.size() > 0)
1939 removeProgressPanel(fileLoadingPanels.remove(0));
1941 fileLoadingPanels.clear();
1942 fileLoadingCount = 0;
1947 public static int getViewCount(String alignmentId)
1949 AlignmentViewport[] aps = getViewports(alignmentId);
1950 return (aps == null) ? 0 : aps.length;
1955 * @param alignmentId
1956 * - if null, all sets are returned
1957 * @return all AlignmentPanels concerning the alignmentId sequence set
1959 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1961 if (getDesktopPane() == null)
1963 // no frames created and in headless mode
1964 // TODO: verify that frames are recoverable when in headless mode
1967 List<AlignmentPanel> aps = new ArrayList<>();
1968 AlignFrame[] frames = getAlignFrames();
1973 for (AlignFrame af : frames)
1975 for (AlignmentPanel ap : af.alignPanels)
1977 if (alignmentId == null
1978 || alignmentId.equals(ap.av.getSequenceSetId()))
1984 if (aps.size() == 0)
1988 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1993 * get all the viewports on an alignment.
1995 * @param sequenceSetId
1996 * unique alignment id (may be null - all viewports returned in that
1998 * @return all viewports on the alignment bound to sequenceSetId
2000 public static AlignmentViewport[] getViewports(String sequenceSetId)
2002 List<AlignmentViewport> viewp = new ArrayList<>();
2003 if (getDesktopPane() != null)
2005 AlignFrame[] frames = getAlignFrames();
2007 for (AlignFrame afr : frames)
2009 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2010 .equals(sequenceSetId))
2012 if (afr.alignPanels != null)
2014 for (AlignmentPanel ap : afr.alignPanels)
2016 if (sequenceSetId == null
2017 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2025 viewp.add(afr.getViewport());
2029 if (viewp.size() > 0)
2031 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2038 * Explode the views in the given frame into separate AlignFrame
2042 public static void explodeViews(AlignFrame af)
2044 int size = af.alignPanels.size();
2050 // FIXME: ideally should use UI interface API
2051 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2052 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2053 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2054 for (int i = 0; i < size; i++)
2056 AlignmentPanel ap = af.alignPanels.get(i);
2058 AlignFrame newaf = new AlignFrame(ap);
2060 // transfer reference for existing feature settings to new alignFrame
2061 if (ap == af.alignPanel)
2063 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2065 newaf.featureSettings = viewFeatureSettings;
2067 newaf.setFeatureSettingsGeometry(fsBounds);
2071 * Restore the view's last exploded frame geometry if known. Multiple
2072 * views from one exploded frame share and restore the same (frame)
2073 * position and size.
2075 Rectangle geometry = ap.av.getExplodedGeometry();
2076 if (geometry != null)
2078 newaf.setBounds(geometry);
2081 ap.av.setGatherViewsHere(false);
2083 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2084 AlignFrame.DEFAULT_HEIGHT);
2085 // and materialise a new feature settings dialog instance for the new
2087 // (closes the old as if 'OK' was pressed)
2088 if (ap == af.alignPanel && newaf.featureSettings != null
2089 && newaf.featureSettings.isOpen()
2090 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2092 newaf.showFeatureSettingsUI();
2096 af.featureSettings = null;
2097 af.alignPanels.clear();
2098 af.closeMenuItem_actionPerformed(true);
2103 * Gather expanded views (separate AlignFrame's) with the same sequence set
2104 * identifier back in to this frame as additional views, and close the
2105 * expanded views. Note the expanded frames may themselves have multiple
2106 * views. We take the lot.
2110 public void gatherViews(AlignFrame source)
2112 source.viewport.setGatherViewsHere(true);
2113 source.viewport.setExplodedGeometry(source.getBounds());
2114 JInternalFrame[] frames = desktopPane.getAllFrames();
2115 String viewId = source.viewport.getSequenceSetId();
2116 for (int t = 0; t < frames.length; t++)
2118 if (frames[t] instanceof AlignFrame && frames[t] != source)
2120 AlignFrame af = (AlignFrame) frames[t];
2121 boolean gatherThis = false;
2122 for (int a = 0; a < af.alignPanels.size(); a++)
2124 AlignmentPanel ap = af.alignPanels.get(a);
2125 if (viewId.equals(ap.av.getSequenceSetId()))
2128 ap.av.setGatherViewsHere(false);
2129 ap.av.setExplodedGeometry(af.getBounds());
2130 source.addAlignmentPanel(ap, false);
2136 if (af.featureSettings != null && af.featureSettings.isOpen())
2138 if (source.featureSettings == null)
2140 // preserve the feature settings geometry for this frame
2141 source.featureSettings = af.featureSettings;
2142 source.setFeatureSettingsGeometry(
2143 af.getFeatureSettingsGeometry());
2147 // close it and forget
2148 af.featureSettings.close();
2151 af.alignPanels.clear();
2152 af.closeMenuItem_actionPerformed(true);
2157 // refresh the feature setting UI for the source frame if it exists
2158 if (source.featureSettings != null && source.featureSettings.isOpen())
2160 source.showFeatureSettingsUI();
2164 public JInternalFrame[] getAllFrames()
2166 return desktopPane.getAllFrames();
2170 * Checks the given url to see if it gives a response indicating that the user
2171 * should be informed of a new questionnaire.
2175 public void checkForQuestionnaire(String url)
2177 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2178 // javax.swing.SwingUtilities.invokeLater(jvq);
2179 new Thread(jvq).start();
2182 public void checkURLLinks()
2184 // Thread off the URL link checker
2185 addDialogThread(new Runnable()
2190 if (Cache.getDefault("CHECKURLLINKS", true))
2192 // check what the actual links are - if it's just the default don't
2193 // bother with the warning
2194 List<String> links = Preferences.sequenceUrlLinks
2197 // only need to check links if there is one with a
2198 // SEQUENCE_ID which is not the default EMBL_EBI link
2199 ListIterator<String> li = links.listIterator();
2200 boolean check = false;
2201 List<JLabel> urls = new ArrayList<>();
2202 while (li.hasNext())
2204 String link = li.next();
2205 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2206 && !UrlConstants.isDefaultString(link))
2209 int barPos = link.indexOf("|");
2210 String urlMsg = barPos == -1 ? link
2211 : link.substring(0, barPos) + ": "
2212 + link.substring(barPos + 1);
2213 urls.add(new JLabel(urlMsg));
2221 // ask user to check in case URL links use old style tokens
2222 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2223 JPanel msgPanel = new JPanel();
2224 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2225 msgPanel.add(Box.createVerticalGlue());
2226 JLabel msg = new JLabel(MessageManager
2227 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2228 JLabel msg2 = new JLabel(MessageManager
2229 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2231 for (JLabel url : urls)
2237 final JCheckBox jcb = new JCheckBox(
2238 MessageManager.getString("label.do_not_display_again"));
2239 jcb.addActionListener(new ActionListener()
2242 public void actionPerformed(ActionEvent e)
2244 // update Cache settings for "don't show this again"
2245 boolean showWarningAgain = !jcb.isSelected();
2246 Cache.setProperty("CHECKURLLINKS",
2247 Boolean.valueOf(showWarningAgain).toString());
2252 JvOptionPane.showMessageDialog(desktopPane, msgPanel,
2254 .getString("label.SEQUENCE_ID_no_longer_used"),
2255 JvOptionPane.WARNING_MESSAGE);
2262 * Proxy class for JDesktopPane which optionally displays the current memory
2263 * usage and highlights the desktop area with a red bar if free memory runs
2268 public class MyDesktopPane extends JDesktopPane implements Runnable
2270 private static final float ONE_MB = 1048576f;
2272 boolean showMemoryUsage = false;
2276 java.text.NumberFormat df;
2278 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2281 public MyDesktopPane(boolean showMemoryUsage)
2283 showMemoryUsage(showMemoryUsage);
2286 public void showMemoryUsage(boolean showMemory)
2288 this.showMemoryUsage = showMemory;
2291 Thread worker = new Thread(this);
2297 public boolean isShowMemoryUsage()
2299 return showMemoryUsage;
2305 df = java.text.NumberFormat.getNumberInstance();
2306 df.setMaximumFractionDigits(2);
2307 runtime = Runtime.getRuntime();
2309 while (showMemoryUsage)
2313 maxMemory = runtime.maxMemory() / ONE_MB;
2314 allocatedMemory = runtime.totalMemory() / ONE_MB;
2315 freeMemory = runtime.freeMemory() / ONE_MB;
2316 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2318 percentUsage = (totalFreeMemory / maxMemory) * 100;
2320 // if (percentUsage < 20)
2322 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2324 // instance.set.setBorder(border1);
2327 // sleep after showing usage
2329 } catch (Exception ex)
2331 ex.printStackTrace();
2337 public void paintComponent(Graphics g)
2339 if (showMemoryUsage && g != null && df != null)
2341 if (percentUsage < 20)
2343 g.setColor(Color.red);
2345 FontMetrics fm = g.getFontMetrics();
2348 g.drawString(MessageManager.formatMessage("label.memory_stats",
2350 { df.format(totalFreeMemory), df.format(maxMemory),
2351 df.format(percentUsage) }),
2352 10, getHeight() - fm.getHeight());
2359 * Accessor method to quickly get all the AlignmentFrames loaded.
2361 * @return an array of AlignFrame, or null if none found
2363 public static AlignFrame[] getAlignFrames()
2365 if (Jalview.isHeadlessMode())
2367 return new AlignFrame[] { Jalview.getInstance().currentAlignFrame };
2370 JInternalFrame[] frames = getDesktopPane().getAllFrames();
2376 List<AlignFrame> avp = new ArrayList<>();
2378 for (int i = frames.length - 1; i > -1; i--)
2380 if (frames[i] instanceof AlignFrame)
2382 avp.add((AlignFrame) frames[i]);
2384 else if (frames[i] instanceof SplitFrame)
2387 * Also check for a split frame containing an AlignFrame
2389 GSplitFrame sf = (GSplitFrame) frames[i];
2390 if (sf.getTopFrame() instanceof AlignFrame)
2392 avp.add((AlignFrame) sf.getTopFrame());
2394 if (sf.getBottomFrame() instanceof AlignFrame)
2396 avp.add((AlignFrame) sf.getBottomFrame());
2400 if (avp.size() == 0)
2404 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2409 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2413 public GStructureViewer[] getJmols()
2415 JInternalFrame[] frames = desktopPane.getAllFrames();
2421 List<GStructureViewer> avp = new ArrayList<>();
2423 for (int i = frames.length - 1; i > -1; i--)
2425 if (frames[i] instanceof AppJmol)
2427 GStructureViewer af = (GStructureViewer) frames[i];
2431 if (avp.size() == 0)
2435 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2440 * Add Groovy Support to Jalview
2443 public void groovyShell_actionPerformed()
2447 openGroovyConsole();
2448 } catch (Exception ex)
2450 Cache.log.error("Groovy Shell Creation failed.", ex);
2451 JvOptionPane.showInternalMessageDialog(desktopPane,
2453 MessageManager.getString("label.couldnt_create_groovy_shell"),
2454 MessageManager.getString("label.groovy_support_failed"),
2455 JvOptionPane.ERROR_MESSAGE);
2460 * Open the Groovy console
2462 void openGroovyConsole()
2464 if (groovyConsole == null)
2466 groovyConsole = new groovy.ui.Console();
2467 groovyConsole.setVariable("Jalview", this);
2468 groovyConsole.run();
2471 * We allow only one console at a time, so that AlignFrame menu option
2472 * 'Calculate | Run Groovy script' is unambiguous.
2473 * Disable 'Groovy Console', and enable 'Run script', when the console is
2474 * opened, and the reverse when it is closed
2476 Window window = (Window) groovyConsole.getFrame();
2477 window.addWindowListener(new WindowAdapter()
2480 public void windowClosed(WindowEvent e)
2483 * rebind CMD-Q from Groovy Console to Jalview Quit
2486 enableExecuteGroovy(false);
2492 * show Groovy console window (after close and reopen)
2494 ((Window) groovyConsole.getFrame()).setVisible(true);
2497 * if we got this far, enable 'Run Groovy' in AlignFrame menus
2498 * and disable opening a second console
2500 enableExecuteGroovy(true);
2504 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2505 * binding when opened
2507 protected void addQuitHandler()
2510 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
2511 .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
2512 Platform.SHORTCUT_KEY_MASK),
2514 getRootPane().getActionMap().put("Quit", new AbstractAction()
2517 public void actionPerformed(ActionEvent e)
2525 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2528 * true if Groovy console is open
2530 public void enableExecuteGroovy(boolean enabled)
2533 * disable opening a second Groovy console
2534 * (or re-enable when the console is closed)
2536 groovyShell.setEnabled(!enabled);
2538 AlignFrame[] alignFrames = getAlignFrames();
2539 if (alignFrames != null)
2541 for (AlignFrame af : alignFrames)
2543 af.setGroovyEnabled(enabled);
2549 * Progress bars managed by the IProgressIndicator method.
2551 private Hashtable<Long, JPanel> progressBars;
2553 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2558 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2561 public void setProgressBar(String message, long id)
2563 // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
2565 if (progressBars == null)
2567 progressBars = new Hashtable<>();
2568 progressBarHandlers = new Hashtable<>();
2571 if (progressBars.get(Long.valueOf(id)) != null)
2573 JPanel panel = progressBars.remove(Long.valueOf(id));
2574 if (progressBarHandlers.contains(Long.valueOf(id)))
2576 progressBarHandlers.remove(Long.valueOf(id));
2578 removeProgressPanel(panel);
2582 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2587 public void addProgressBar(long id, String message)
2590 throw new UnsupportedOperationException("not implemented");
2594 public void removeProgressBar(long id)
2597 throw new UnsupportedOperationException("not implemented");
2603 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2604 * jalview.gui.IProgressIndicatorHandler)
2607 public void registerHandler(final long id,
2608 final IProgressIndicatorHandler handler)
2610 if (progressBarHandlers == null
2611 || !progressBars.containsKey(Long.valueOf(id)))
2613 throw new Error(MessageManager.getString(
2614 "error.call_setprogressbar_before_registering_handler"));
2616 progressBarHandlers.put(Long.valueOf(id), handler);
2617 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2618 if (handler.canCancel())
2620 JButton cancel = new JButton(
2621 MessageManager.getString("action.cancel"));
2622 final IProgressIndicator us = this;
2623 cancel.addActionListener(new ActionListener()
2627 public void actionPerformed(ActionEvent e)
2629 handler.cancelActivity(id);
2630 us.setProgressBar(MessageManager
2631 .formatMessage("label.cancelled_params", new Object[]
2632 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2636 progressPanel.add(cancel, BorderLayout.EAST);
2642 * @return true if any progress bars are still active
2645 public boolean operationInProgress()
2647 if (progressBars != null && progressBars.size() > 0)
2655 * This will return the first AlignFrame holding the given viewport instance.
2656 * It will break if there are more than one AlignFrames viewing a particular
2660 * @return alignFrame for viewport
2662 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2664 if (getDesktopPane() != null)
2666 AlignmentPanel[] aps = getAlignmentPanels(
2667 viewport.getSequenceSetId());
2668 for (int panel = 0; aps != null && panel < aps.length; panel++)
2670 if (aps[panel] != null && aps[panel].av == viewport)
2672 return aps[panel].alignFrame;
2679 public VamsasApplication getVamsasApplication()
2681 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2687 * flag set if jalview GUI is being operated programmatically
2689 private boolean inBatchMode = false;
2692 * check if jalview GUI is being operated programmatically
2694 * @return inBatchMode
2696 public boolean isInBatchMode()
2702 * set flag if jalview GUI is being operated programmatically
2704 * @param inBatchMode
2706 public void setInBatchMode(boolean inBatchMode)
2708 this.inBatchMode = inBatchMode;
2711 public void startServiceDiscovery()
2713 startServiceDiscovery(false);
2716 public void startServiceDiscovery(boolean blocking)
2718 System.out.println("Starting service discovery");
2719 var tasks = new ArrayList<Future<?>>();
2720 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2722 System.out.println("loading services");
2726 // todo: changesupport handlers need to be transferred
2727 if (discoverer == null)
2729 discoverer = jalview.ws.jws1.Discoverer.getInstance();
2730 // register PCS handler for desktop.
2731 discoverer.addPropertyChangeListener(changeSupport);
2733 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2734 // until we phase out completely
2735 var f = new FutureTask<Void>(discoverer, null);
2736 new Thread(f).start();
2740 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2742 tasks.add(jalview.ws.jws2.Jws2Discoverer.getInstance().startDiscoverer());
2744 if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
2746 tasks.add(jalview.ws2.client.slivka.SlivkaWSDiscoverer
2747 .getInstance().startDiscoverer());
2751 for (Future<?> task : tasks) {
2754 // block until all discovery tasks are done
2756 } catch (Exception e)
2758 e.printStackTrace();
2765 * called to check if the service discovery process completed successfully.
2769 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2771 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2773 final WSDiscovererI discoverer = jalview.ws.jws2.Jws2Discoverer
2775 final String ermsg = discoverer.getErrorMessages();
2776 // CONFLICT:ALT:? final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
2779 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2781 if (serviceChangedDialog == null)
2783 // only run if we aren't already displaying one of these.
2784 addDialogThread(serviceChangedDialog = new Runnable()
2791 * JalviewDialog jd =new JalviewDialog() {
2793 * @Override protected void cancelPressed() { // TODO
2794 * Auto-generated method stub
2796 * }@Override protected void okPressed() { // TODO
2797 * Auto-generated method stub
2799 * }@Override protected void raiseClosed() { // TODO
2800 * Auto-generated method stub
2802 * } }; jd.initDialogFrame(new
2803 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2804 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2805 * + " or mis-configured HTTP proxy settings.<br/>" +
2806 * "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,
2812 * jd.waitForInput();
2814 JvOptionPane.showConfirmDialog(desktopPane,
2815 new JLabel("<html><table width=\"450\"><tr><td>"
2816 + ermsg + "</td></tr></table>"
2817 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2818 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2819 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2820 + " Tools->Preferences dialog box to change them.</p></html>"),
2821 "Web Service Configuration Problem",
2822 JvOptionPane.DEFAULT_OPTION,
2823 JvOptionPane.ERROR_MESSAGE);
2824 serviceChangedDialog = null;
2833 "Errors reported by JABA discovery service. Check web services preferences.\n"
2840 private Runnable serviceChangedDialog = null;
2843 * start a thread to open a URL in the configured browser. Pops up a warning
2844 * dialog to the user if there is an exception when calling out to the browser
2849 public static void showUrl(final String url)
2851 showUrl(url, getInstance());
2855 * Like showUrl but allows progress handler to be specified
2859 * (null) or object implementing IProgressIndicator
2861 public static void showUrl(final String url,
2862 final IProgressIndicator progress)
2864 new Thread(new Runnable()
2871 if (progress != null)
2873 progress.setProgressBar(MessageManager
2874 .formatMessage("status.opening_params", new Object[]
2875 { url }), this.hashCode());
2877 jalview.util.BrowserLauncher.openURL(url);
2878 } catch (Exception ex)
2880 JvOptionPane.showInternalMessageDialog(getDesktopPane(),
2882 .getString("label.web_browser_not_found_unix"),
2883 MessageManager.getString("label.web_browser_not_found"),
2884 JvOptionPane.WARNING_MESSAGE);
2886 ex.printStackTrace();
2888 if (progress != null)
2890 progress.setProgressBar(null, this.hashCode());
2896 public static WsParamSetManager wsparamManager = null;
2898 public static ParamManager getUserParameterStore()
2900 if (wsparamManager == null)
2902 wsparamManager = new WsParamSetManager();
2904 return wsparamManager;
2908 * static hyperlink handler proxy method for use by Jalview's internal windows
2912 public static void hyperlinkUpdate(HyperlinkEvent e)
2914 if (e.getEventType() == EventType.ACTIVATED)
2919 url = e.getURL().toString();
2921 } catch (Exception x)
2925 if (Cache.log != null)
2927 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2932 "Couldn't handle string " + url + " as a URL.");
2935 // ignore any exceptions due to dud links.
2942 * single thread that handles display of dialogs to user.
2944 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2947 * flag indicating if dialogExecutor should try to acquire a permit
2949 private volatile boolean dialogPause = true;
2954 private java.util.concurrent.Semaphore block = new Semaphore(0);
2956 private static groovy.ui.Console groovyConsole;
2959 * add another dialog thread to the queue
2963 public void addDialogThread(final Runnable prompter)
2965 dialogExecutor.submit(new Runnable()
2975 } catch (InterruptedException x)
2979 if (Jalview.isHeadlessMode())
2985 SwingUtilities.invokeAndWait(prompter);
2986 } catch (Exception q)
2988 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2994 public void startDialogQueue()
2996 // set the flag so we don't pause waiting for another permit and semaphore
2997 // the current task to begin
2998 dialogPause = false;
3003 * Outputs an image of the desktop to file in EPS format, after prompting the
3004 * user for choice of Text or Lineart character rendering (unless a preference
3005 * has been set). The file name is generated as
3008 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3012 protected void snapShotWindow_actionPerformed(ActionEvent e)
3014 // currently the menu option to do this is not shown
3017 int width = getWidth();
3018 int height = getHeight();
3020 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3021 ImageWriterI writer = new ImageWriterI()
3024 public void exportImage(Graphics g) throws Exception
3027 Cache.log.info("Successfully written snapshot to file "
3028 + of.getAbsolutePath());
3031 String title = "View of desktop";
3032 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3034 exporter.doExport(of, this, width, height, title);
3038 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3039 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3040 * and location last time the view was expanded (if any). However it does not
3041 * remember the split pane divider location - this is set to match the
3042 * 'exploding' frame.
3046 public void explodeViews(SplitFrame sf)
3048 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3049 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3050 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3052 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3054 int viewCount = topPanels.size();
3061 * Processing in reverse order works, forwards order leaves the first panels
3062 * not visible. I don't know why!
3064 for (int i = viewCount - 1; i >= 0; i--)
3067 * Make new top and bottom frames. These take over the respective
3068 * AlignmentPanel objects, including their AlignmentViewports, so the
3069 * cdna/protein relationships between the viewports is carried over to the
3072 * explodedGeometry holds the (x, y) position of the previously exploded
3073 * SplitFrame, and the (width, height) of the AlignFrame component
3075 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3076 AlignFrame newTopFrame = new AlignFrame(topPanel);
3077 newTopFrame.setSize(oldTopFrame.getSize());
3078 newTopFrame.setVisible(true);
3079 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3080 .getExplodedGeometry();
3081 if (geometry != null)
3083 newTopFrame.setSize(geometry.getSize());
3086 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3087 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3088 newBottomFrame.setSize(oldBottomFrame.getSize());
3089 newBottomFrame.setVisible(true);
3090 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3091 .getExplodedGeometry();
3092 if (geometry != null)
3094 newBottomFrame.setSize(geometry.getSize());
3097 topPanel.av.setGatherViewsHere(false);
3098 bottomPanel.av.setGatherViewsHere(false);
3099 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3101 if (geometry != null)
3103 splitFrame.setLocation(geometry.getLocation());
3105 addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3109 * Clear references to the panels (now relocated in the new SplitFrames)
3110 * before closing the old SplitFrame.
3113 bottomPanels.clear();
3118 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3119 * back into the given SplitFrame as additional views. Note that the gathered
3120 * frames may themselves have multiple views.
3124 public void gatherViews(GSplitFrame source)
3127 * special handling of explodedGeometry for a view within a SplitFrame: - it
3128 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3129 * height) of the AlignFrame component
3131 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3132 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3133 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3134 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3135 myBottomFrame.viewport
3136 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3137 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3138 myTopFrame.viewport.setGatherViewsHere(true);
3139 myBottomFrame.viewport.setGatherViewsHere(true);
3140 String topViewId = myTopFrame.viewport.getSequenceSetId();
3141 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3143 JInternalFrame[] frames = desktopPane.getAllFrames();
3144 for (JInternalFrame frame : frames)
3146 if (frame instanceof SplitFrame && frame != source)
3148 SplitFrame sf = (SplitFrame) frame;
3149 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3150 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3151 boolean gatherThis = false;
3152 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3154 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3155 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3156 if (topViewId.equals(topPanel.av.getSequenceSetId())
3157 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3160 topPanel.av.setGatherViewsHere(false);
3161 bottomPanel.av.setGatherViewsHere(false);
3162 topPanel.av.setExplodedGeometry(
3163 new Rectangle(sf.getLocation(), topFrame.getSize()));
3164 bottomPanel.av.setExplodedGeometry(
3165 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3166 myTopFrame.addAlignmentPanel(topPanel, false);
3167 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3173 topFrame.getAlignPanels().clear();
3174 bottomFrame.getAlignPanels().clear();
3181 * The dust settles...give focus to the tab we did this from.
3183 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3186 public static groovy.ui.Console getGroovyConsole()
3188 return groovyConsole;
3192 * handles the payload of a drag and drop event.
3194 * TODO refactor to desktop utilities class
3197 * - Data source strings extracted from the drop event
3199 * - protocol for each data source extracted from the drop event
3203 * - the payload from the drop event
3206 @SuppressWarnings("unchecked")
3207 public static void transferFromDropTarget(List<Object> files,
3208 List<DataSourceType> protocols, DropTargetDropEvent evt,
3209 Transferable t) throws Exception
3212 // BH 2018 changed List<String> to List<Object> to allow for File from
3215 // DataFlavor[] flavors = t.getTransferDataFlavors();
3216 // for (int i = 0; i < flavors.length; i++) {
3217 // if (flavors[i].isFlavorJavaFileListType()) {
3218 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3219 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3220 // for (int j = 0; j < list.size(); j++) {
3221 // File file = (File) list.get(j);
3222 // byte[] data = getDroppedFileBytes(file);
3223 // fileName.setText(file.getName() + " - " + data.length + " " +
3224 // evt.getLocation());
3225 // JTextArea target = (JTextArea) ((DropTarget)
3226 // evt.getSource()).getComponent();
3227 // target.setText(new String(data));
3229 // dtde.dropComplete(true);
3234 DataFlavor uriListFlavor = new DataFlavor(
3235 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3238 urlFlavour = new DataFlavor(
3239 "application/x-java-url; class=java.net.URL");
3240 } catch (ClassNotFoundException cfe)
3242 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3245 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3250 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3251 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3252 // means url may be null.
3255 protocols.add(DataSourceType.URL);
3256 files.add(url.toString());
3257 Cache.log.debug("Drop handled as URL dataflavor "
3258 + files.get(files.size() - 1));
3263 if (Platform.isAMacAndNotJS())
3266 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3269 } catch (Throwable ex)
3271 Cache.log.debug("URL drop handler failed.", ex);
3274 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3276 // Works on Windows and MacOSX
3277 Cache.log.debug("Drop handled as javaFileListFlavor");
3278 for (File file : (List<File>) t
3279 .getTransferData(DataFlavor.javaFileListFlavor))
3282 protocols.add(DataSourceType.FILE);
3287 // Unix like behaviour
3288 boolean added = false;
3290 if (t.isDataFlavorSupported(uriListFlavor))
3292 Cache.log.debug("Drop handled as uriListFlavor");
3293 // This is used by Unix drag system
3294 data = (String) t.getTransferData(uriListFlavor);
3298 // fallback to text: workaround - on OSX where there's a JVM bug
3299 Cache.log.debug("standard URIListFlavor failed. Trying text");
3300 // try text fallback
3301 DataFlavor textDf = new DataFlavor(
3302 "text/plain;class=java.lang.String");
3303 if (t.isDataFlavorSupported(textDf))
3305 data = (String) t.getTransferData(textDf);
3308 Cache.log.debug("Plain text drop content returned "
3309 + (data == null ? "Null - failed" : data));
3314 while (protocols.size() < files.size())
3316 Cache.log.debug("Adding missing FILE protocol for "
3317 + files.get(protocols.size()));
3318 protocols.add(DataSourceType.FILE);
3320 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3321 data, "\r\n"); st.hasMoreTokens();)
3324 String s = st.nextToken();
3325 if (s.startsWith("#"))
3327 // the line is a comment (as per the RFC 2483)
3330 java.net.URI uri = new java.net.URI(s);
3331 if (uri.getScheme().toLowerCase().startsWith("http"))
3333 protocols.add(DataSourceType.URL);
3334 files.add(uri.toString());
3338 // otherwise preserve old behaviour: catch all for file objects
3339 java.io.File file = new java.io.File(uri);
3340 protocols.add(DataSourceType.FILE);
3341 files.add(file.toString());
3346 if (Cache.log.isDebugEnabled())
3348 if (data == null || !added)
3351 if (t.getTransferDataFlavors() != null
3352 && t.getTransferDataFlavors().length > 0)
3355 "Couldn't resolve drop data. Here are the supported flavors:");
3356 for (DataFlavor fl : t.getTransferDataFlavors())
3359 "Supported transfer dataflavor: " + fl.toString());
3360 Object df = t.getTransferData(fl);
3363 Cache.log.debug("Retrieves: " + df);
3367 Cache.log.debug("Retrieved nothing");
3373 Cache.log.debug("Couldn't resolve dataflavor for drop: "
3379 if (Platform.isWindowsAndNotJS())
3381 Cache.log.debug("Scanning dropped content for Windows Link Files");
3383 // resolve any .lnk files in the file drop
3384 for (int f = 0; f < files.size(); f++)
3386 String source = files.get(f).toString().toLowerCase();
3387 if (protocols.get(f).equals(DataSourceType.FILE)
3388 && (source.endsWith(".lnk") || source.endsWith(".url")
3389 || source.endsWith(".site")))
3393 Object obj = files.get(f);
3394 File lf = (obj instanceof File ? (File) obj
3395 : new File((String) obj));
3396 // process link file to get a URL
3397 Cache.log.debug("Found potential link file: " + lf);
3398 WindowsShortcut wscfile = new WindowsShortcut(lf);
3399 String fullname = wscfile.getRealFilename();
3400 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3401 files.set(f, fullname);
3402 Cache.log.debug("Parsed real filename " + fullname
3403 + " to extract protocol: " + protocols.get(f));
3404 } catch (Exception ex)
3407 "Couldn't parse " + files.get(f) + " as a link file.",
3416 * Sets the Preferences property for experimental features to True or False
3417 * depending on the state of the controlling menu item
3420 protected void showExperimental_actionPerformed(boolean selected)
3422 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3426 * Answers a (possibly empty) list of any structure viewer frames (currently
3427 * for either Jmol or Chimera) which are currently open. This may optionally
3428 * be restricted to viewers of a specified class, or viewers linked to a
3429 * specified alignment panel.
3432 * if not null, only return viewers linked to this panel
3433 * @param structureViewerClass
3434 * if not null, only return viewers of this class
3437 public List<StructureViewerBase> getStructureViewers(
3438 AlignmentPanel apanel,
3439 Class<? extends StructureViewerBase> structureViewerClass)
3441 List<StructureViewerBase> result = new ArrayList<>();
3442 JInternalFrame[] frames = getAllFrames();
3444 for (JInternalFrame frame : frames)
3446 if (frame instanceof StructureViewerBase)
3448 if (structureViewerClass == null
3449 || structureViewerClass.isInstance(frame))
3452 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3454 result.add((StructureViewerBase) frame);