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 removeProgressBar(long id)
2590 throw new UnsupportedOperationException("not implemented");
2596 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2597 * jalview.gui.IProgressIndicatorHandler)
2600 public void registerHandler(final long id,
2601 final IProgressIndicatorHandler handler)
2603 if (progressBarHandlers == null
2604 || !progressBars.containsKey(Long.valueOf(id)))
2606 throw new Error(MessageManager.getString(
2607 "error.call_setprogressbar_before_registering_handler"));
2609 progressBarHandlers.put(Long.valueOf(id), handler);
2610 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2611 if (handler.canCancel())
2613 JButton cancel = new JButton(
2614 MessageManager.getString("action.cancel"));
2615 final IProgressIndicator us = this;
2616 cancel.addActionListener(new ActionListener()
2620 public void actionPerformed(ActionEvent e)
2622 handler.cancelActivity(id);
2623 us.setProgressBar(MessageManager
2624 .formatMessage("label.cancelled_params", new Object[]
2625 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2629 progressPanel.add(cancel, BorderLayout.EAST);
2635 * @return true if any progress bars are still active
2638 public boolean operationInProgress()
2640 if (progressBars != null && progressBars.size() > 0)
2648 * This will return the first AlignFrame holding the given viewport instance.
2649 * It will break if there are more than one AlignFrames viewing a particular
2653 * @return alignFrame for viewport
2655 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2657 if (getDesktopPane() != null)
2659 AlignmentPanel[] aps = getAlignmentPanels(
2660 viewport.getSequenceSetId());
2661 for (int panel = 0; aps != null && panel < aps.length; panel++)
2663 if (aps[panel] != null && aps[panel].av == viewport)
2665 return aps[panel].alignFrame;
2672 public VamsasApplication getVamsasApplication()
2674 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2680 * flag set if jalview GUI is being operated programmatically
2682 private boolean inBatchMode = false;
2685 * check if jalview GUI is being operated programmatically
2687 * @return inBatchMode
2689 public boolean isInBatchMode()
2695 * set flag if jalview GUI is being operated programmatically
2697 * @param inBatchMode
2699 public void setInBatchMode(boolean inBatchMode)
2701 this.inBatchMode = inBatchMode;
2704 public void startServiceDiscovery()
2706 startServiceDiscovery(false);
2709 public void startServiceDiscovery(boolean blocking)
2711 System.out.println("Starting service discovery");
2712 var tasks = new ArrayList<Future<?>>();
2713 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2715 System.out.println("loading services");
2719 // todo: changesupport handlers need to be transferred
2720 if (discoverer == null)
2722 discoverer = jalview.ws.jws1.Discoverer.getInstance();
2723 // register PCS handler for desktop.
2724 discoverer.addPropertyChangeListener(changeSupport);
2726 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2727 // until we phase out completely
2728 var f = new FutureTask<Void>(discoverer, null);
2729 new Thread(f).start();
2733 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2735 tasks.add(jalview.ws.jws2.Jws2Discoverer.getInstance().startDiscoverer());
2737 if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
2739 tasks.add(jalview.ws.slivkaws.SlivkaWSDiscoverer.getInstance().startDiscoverer());
2743 for (Future<?> task : tasks) {
2746 // block until all discovery tasks are done
2748 } catch (Exception e)
2750 e.printStackTrace();
2757 * called to check if the service discovery process completed successfully.
2761 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2763 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2765 final WSDiscovererI discoverer = jalview.ws.jws2.Jws2Discoverer
2767 final String ermsg = discoverer.getErrorMessages();
2768 // CONFLICT:ALT:? final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
2771 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2773 if (serviceChangedDialog == null)
2775 // only run if we aren't already displaying one of these.
2776 addDialogThread(serviceChangedDialog = new Runnable()
2783 * JalviewDialog jd =new JalviewDialog() {
2785 * @Override protected void cancelPressed() { // TODO
2786 * Auto-generated method stub
2788 * }@Override protected void okPressed() { // TODO
2789 * Auto-generated method stub
2791 * }@Override protected void raiseClosed() { // TODO
2792 * Auto-generated method stub
2794 * } }; jd.initDialogFrame(new
2795 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2796 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2797 * + " or mis-configured HTTP proxy settings.<br/>" +
2798 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
2800 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
2801 * ), true, true, "Web Service Configuration Problem", 450,
2804 * jd.waitForInput();
2806 JvOptionPane.showConfirmDialog(desktopPane,
2807 new JLabel("<html><table width=\"450\"><tr><td>"
2808 + ermsg + "</td></tr></table>"
2809 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2810 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2811 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2812 + " Tools->Preferences dialog box to change them.</p></html>"),
2813 "Web Service Configuration Problem",
2814 JvOptionPane.DEFAULT_OPTION,
2815 JvOptionPane.ERROR_MESSAGE);
2816 serviceChangedDialog = null;
2825 "Errors reported by JABA discovery service. Check web services preferences.\n"
2832 private Runnable serviceChangedDialog = null;
2835 * start a thread to open a URL in the configured browser. Pops up a warning
2836 * dialog to the user if there is an exception when calling out to the browser
2841 public static void showUrl(final String url)
2843 showUrl(url, getInstance());
2847 * Like showUrl but allows progress handler to be specified
2851 * (null) or object implementing IProgressIndicator
2853 public static void showUrl(final String url,
2854 final IProgressIndicator progress)
2856 new Thread(new Runnable()
2863 if (progress != null)
2865 progress.setProgressBar(MessageManager
2866 .formatMessage("status.opening_params", new Object[]
2867 { url }), this.hashCode());
2869 jalview.util.BrowserLauncher.openURL(url);
2870 } catch (Exception ex)
2872 JvOptionPane.showInternalMessageDialog(getDesktopPane(),
2874 .getString("label.web_browser_not_found_unix"),
2875 MessageManager.getString("label.web_browser_not_found"),
2876 JvOptionPane.WARNING_MESSAGE);
2878 ex.printStackTrace();
2880 if (progress != null)
2882 progress.setProgressBar(null, this.hashCode());
2888 public static WsParamSetManager wsparamManager = null;
2890 public static ParamManager getUserParameterStore()
2892 if (wsparamManager == null)
2894 wsparamManager = new WsParamSetManager();
2896 return wsparamManager;
2900 * static hyperlink handler proxy method for use by Jalview's internal windows
2904 public static void hyperlinkUpdate(HyperlinkEvent e)
2906 if (e.getEventType() == EventType.ACTIVATED)
2911 url = e.getURL().toString();
2913 } catch (Exception x)
2917 if (Cache.log != null)
2919 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2924 "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 (Jalview.isHeadlessMode())
2977 SwingUtilities.invokeAndWait(prompter);
2978 } catch (Exception q)
2980 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2986 public void startDialogQueue()
2988 // set the flag so we don't pause waiting for another permit and semaphore
2989 // the current task to begin
2990 dialogPause = false;
2995 * Outputs an image of the desktop to file in EPS format, after prompting the
2996 * user for choice of Text or Lineart character rendering (unless a preference
2997 * has been set). The file name is generated as
3000 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3004 protected void snapShotWindow_actionPerformed(ActionEvent e)
3006 // currently the menu option to do this is not shown
3009 int width = getWidth();
3010 int height = getHeight();
3012 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3013 ImageWriterI writer = new ImageWriterI()
3016 public void exportImage(Graphics g) throws Exception
3019 Cache.log.info("Successfully written snapshot to file "
3020 + of.getAbsolutePath());
3023 String title = "View of desktop";
3024 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3026 exporter.doExport(of, this, width, height, title);
3030 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3031 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3032 * and location last time the view was expanded (if any). However it does not
3033 * remember the split pane divider location - this is set to match the
3034 * 'exploding' frame.
3038 public void explodeViews(SplitFrame sf)
3040 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3041 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3042 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3044 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3046 int viewCount = topPanels.size();
3053 * Processing in reverse order works, forwards order leaves the first panels
3054 * not visible. I don't know why!
3056 for (int i = viewCount - 1; i >= 0; i--)
3059 * Make new top and bottom frames. These take over the respective
3060 * AlignmentPanel objects, including their AlignmentViewports, so the
3061 * cdna/protein relationships between the viewports is carried over to the
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 addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3101 * Clear references to the panels (now relocated in the new SplitFrames)
3102 * before 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 = desktopPane.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 @SuppressWarnings("unchecked")
3199 public static void transferFromDropTarget(List<Object> files,
3200 List<DataSourceType> protocols, DropTargetDropEvent evt,
3201 Transferable t) throws Exception
3204 // BH 2018 changed List<String> to List<Object> to allow for File from
3207 // DataFlavor[] flavors = t.getTransferDataFlavors();
3208 // for (int i = 0; i < flavors.length; i++) {
3209 // if (flavors[i].isFlavorJavaFileListType()) {
3210 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3211 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3212 // for (int j = 0; j < list.size(); j++) {
3213 // File file = (File) list.get(j);
3214 // byte[] data = getDroppedFileBytes(file);
3215 // fileName.setText(file.getName() + " - " + data.length + " " +
3216 // evt.getLocation());
3217 // JTextArea target = (JTextArea) ((DropTarget)
3218 // evt.getSource()).getComponent();
3219 // target.setText(new String(data));
3221 // dtde.dropComplete(true);
3226 DataFlavor uriListFlavor = new DataFlavor(
3227 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3230 urlFlavour = new DataFlavor(
3231 "application/x-java-url; class=java.net.URL");
3232 } catch (ClassNotFoundException cfe)
3234 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3237 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3242 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3243 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3244 // means url may be null.
3247 protocols.add(DataSourceType.URL);
3248 files.add(url.toString());
3249 Cache.log.debug("Drop handled as URL dataflavor "
3250 + files.get(files.size() - 1));
3255 if (Platform.isAMacAndNotJS())
3258 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3261 } catch (Throwable ex)
3263 Cache.log.debug("URL drop handler failed.", ex);
3266 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3268 // Works on Windows and MacOSX
3269 Cache.log.debug("Drop handled as javaFileListFlavor");
3270 for (File file : (List<File>) t
3271 .getTransferData(DataFlavor.javaFileListFlavor))
3274 protocols.add(DataSourceType.FILE);
3279 // Unix like behaviour
3280 boolean added = false;
3282 if (t.isDataFlavorSupported(uriListFlavor))
3284 Cache.log.debug("Drop handled as uriListFlavor");
3285 // This is used by Unix drag system
3286 data = (String) t.getTransferData(uriListFlavor);
3290 // fallback to text: workaround - on OSX where there's a JVM bug
3291 Cache.log.debug("standard URIListFlavor failed. Trying text");
3292 // try text fallback
3293 DataFlavor textDf = new DataFlavor(
3294 "text/plain;class=java.lang.String");
3295 if (t.isDataFlavorSupported(textDf))
3297 data = (String) t.getTransferData(textDf);
3300 Cache.log.debug("Plain text drop content returned "
3301 + (data == null ? "Null - failed" : data));
3306 while (protocols.size() < files.size())
3308 Cache.log.debug("Adding missing FILE protocol for "
3309 + files.get(protocols.size()));
3310 protocols.add(DataSourceType.FILE);
3312 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3313 data, "\r\n"); st.hasMoreTokens();)
3316 String s = st.nextToken();
3317 if (s.startsWith("#"))
3319 // the line is a comment (as per the RFC 2483)
3322 java.net.URI uri = new java.net.URI(s);
3323 if (uri.getScheme().toLowerCase().startsWith("http"))
3325 protocols.add(DataSourceType.URL);
3326 files.add(uri.toString());
3330 // otherwise preserve old behaviour: catch all for file objects
3331 java.io.File file = new java.io.File(uri);
3332 protocols.add(DataSourceType.FILE);
3333 files.add(file.toString());
3338 if (Cache.log.isDebugEnabled())
3340 if (data == null || !added)
3343 if (t.getTransferDataFlavors() != null
3344 && t.getTransferDataFlavors().length > 0)
3347 "Couldn't resolve drop data. Here are the supported flavors:");
3348 for (DataFlavor fl : t.getTransferDataFlavors())
3351 "Supported transfer dataflavor: " + fl.toString());
3352 Object df = t.getTransferData(fl);
3355 Cache.log.debug("Retrieves: " + df);
3359 Cache.log.debug("Retrieved nothing");
3365 Cache.log.debug("Couldn't resolve dataflavor for drop: "
3371 if (Platform.isWindowsAndNotJS())
3373 Cache.log.debug("Scanning dropped content for Windows Link Files");
3375 // resolve any .lnk files in the file drop
3376 for (int f = 0; f < files.size(); f++)
3378 String source = files.get(f).toString().toLowerCase();
3379 if (protocols.get(f).equals(DataSourceType.FILE)
3380 && (source.endsWith(".lnk") || source.endsWith(".url")
3381 || source.endsWith(".site")))
3385 Object obj = files.get(f);
3386 File lf = (obj instanceof File ? (File) obj
3387 : new File((String) obj));
3388 // process link file to get a URL
3389 Cache.log.debug("Found potential link file: " + lf);
3390 WindowsShortcut wscfile = new WindowsShortcut(lf);
3391 String fullname = wscfile.getRealFilename();
3392 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3393 files.set(f, fullname);
3394 Cache.log.debug("Parsed real filename " + fullname
3395 + " to extract protocol: " + protocols.get(f));
3396 } catch (Exception ex)
3399 "Couldn't parse " + files.get(f) + " as a link file.",
3408 * Sets the Preferences property for experimental features to True or False
3409 * depending on the state of the controlling menu item
3412 protected void showExperimental_actionPerformed(boolean selected)
3414 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3418 * Answers a (possibly empty) list of any structure viewer frames (currently
3419 * for either Jmol or Chimera) which are currently open. This may optionally
3420 * be restricted to viewers of a specified class, or viewers linked to a
3421 * specified alignment panel.
3424 * if not null, only return viewers linked to this panel
3425 * @param structureViewerClass
3426 * if not null, only return viewers of this class
3429 public List<StructureViewerBase> getStructureViewers(
3430 AlignmentPanel apanel,
3431 Class<? extends StructureViewerBase> structureViewerClass)
3433 List<StructureViewerBase> result = new ArrayList<>();
3434 JInternalFrame[] frames = getAllFrames();
3436 for (JInternalFrame frame : frames)
3438 if (frame instanceof StructureViewerBase)
3440 if (structureViewerClass == null
3441 || structureViewerClass.isInstance(frame))
3444 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3446 result.add((StructureViewerBase) frame);