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;
55 import java.io.IOException;
56 import java.lang.reflect.Method;
58 import java.util.ArrayList;
59 import java.util.HashMap;
60 import java.util.Hashtable;
61 import java.util.List;
62 import java.util.ListIterator;
63 import java.util.Vector;
64 import java.util.concurrent.ExecutorService;
65 import java.util.concurrent.Executors;
66 import java.util.concurrent.Semaphore;
68 import javax.swing.AbstractAction;
69 import javax.swing.Action;
70 import javax.swing.ActionMap;
71 import javax.swing.Box;
72 import javax.swing.BoxLayout;
73 import javax.swing.DefaultDesktopManager;
74 import javax.swing.DesktopManager;
75 import javax.swing.InputMap;
76 import javax.swing.JButton;
77 import javax.swing.JCheckBox;
78 import javax.swing.JComboBox;
79 import javax.swing.JComponent;
80 import javax.swing.JDesktopPane;
81 import javax.swing.JInternalFrame;
82 import javax.swing.JLabel;
83 import javax.swing.JMenuItem;
84 import javax.swing.JPanel;
85 import javax.swing.JPopupMenu;
86 import javax.swing.JProgressBar;
87 import javax.swing.JTextField;
88 import javax.swing.KeyStroke;
89 import javax.swing.SwingUtilities;
90 import javax.swing.event.HyperlinkEvent;
91 import javax.swing.event.HyperlinkEvent.EventType;
92 import javax.swing.event.InternalFrameAdapter;
93 import javax.swing.event.InternalFrameEvent;
95 import jalview.bin.ApplicationSingletonProvider;
96 import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
97 import javax.swing.event.MenuEvent;
98 import javax.swing.event.MenuListener;
99 import java.io.BufferedInputStream;
100 import java.io.FileOutputStream;
101 import javax.swing.JFrame;
102 import jalview.api.StructureSelectionManagerProvider;
104 import org.stackoverflowusers.file.WindowsShortcut;
106 import jalview.api.AlignViewportI;
107 import jalview.api.AlignmentViewPanel;
108 import jalview.bin.Cache;
109 import jalview.bin.Jalview;
110 import jalview.gui.ImageExporter.ImageWriterI;
111 import jalview.io.BackupFiles;
112 import jalview.io.DataSourceType;
113 import jalview.io.FileFormat;
114 import jalview.io.FileFormatException;
115 import jalview.io.FileFormatI;
116 import jalview.io.FileFormats;
117 import jalview.io.FileLoader;
118 import jalview.io.FormatAdapter;
119 import jalview.io.IdentifyFile;
120 import jalview.io.JalviewFileChooser;
121 import jalview.io.JalviewFileView;
122 import jalview.jbgui.GSplitFrame;
123 import jalview.jbgui.GStructureViewer;
124 import jalview.project.Jalview2XML;
125 import jalview.structure.StructureSelectionManager;
126 import jalview.urls.IdOrgSettings;
127 import jalview.util.BrowserLauncher;
128 import jalview.util.ImageMaker.TYPE;
129 import jalview.util.MessageManager;
130 import jalview.util.Platform;
131 import jalview.util.ShortcutKeyMaskExWrapper;
132 import jalview.util.UrlConstants;
133 import jalview.viewmodel.AlignmentViewport;
134 import jalview.ws.params.ParamManager;
135 import jalview.ws.utils.UrlDownloadClient;
142 * @version $Revision: 1.155 $
144 public class Desktop extends jalview.jbgui.GDesktop
145 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
146 StructureSelectionManagerProvider, ApplicationSingletonI
148 private static final String CITATION = "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
149 + "<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"
150 + "<br><br>If you use Jalview, please cite:"
151 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
152 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
153 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033";
155 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
157 private final static int DEFAULT_MIN_WIDTH = 300;
159 private final static int DEFAULT_MIN_HEIGHT = 250;
161 private final static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
163 private final static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
165 private final static String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
167 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
169 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
171 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
174 * news reader - null if it was never started.
176 private BlogReader jvnews = null;
178 private File projectFile;
182 * @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)
196 public void addJalviewPropertyChangeListener(String propertyName,
197 PropertyChangeListener listener)
199 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
203 * @param propertyName
205 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
206 * java.beans.PropertyChangeListener)
208 public void removeJalviewPropertyChangeListener(String propertyName,
209 PropertyChangeListener listener)
211 changeSupport.removeJalviewPropertyChangeListener(propertyName,
215 public static StructureSelectionManager getStructureSelectionManager()
217 return StructureSelectionManager
218 .getStructureSelectionManager(getInstance());
221 static int openFrameCount = 0;
223 static final int xOffset = 30;
225 static final int yOffset = 30;
228 public jalview.ws.jws1.Discoverer discoverer;
231 public Object[] jalviewClipboard;
234 public boolean internalCopy = false;
236 private static int fileLoadingCount = 0;
238 public JInternalFrame conservationSlider;
240 public JInternalFrame PIDSlider;
242 class MyDesktopManager implements DesktopManager
245 private DesktopManager delegate;
247 public MyDesktopManager(DesktopManager delegate)
249 this.delegate = delegate;
253 public void activateFrame(JInternalFrame f)
257 delegate.activateFrame(f);
258 } catch (NullPointerException npe)
260 Point p = getMousePosition();
261 showPasteMenu(p.x, p.y);
266 public void beginDraggingFrame(JComponent f)
268 delegate.beginDraggingFrame(f);
272 public void beginResizingFrame(JComponent f, int direction)
274 delegate.beginResizingFrame(f, direction);
278 public void closeFrame(JInternalFrame f)
280 delegate.closeFrame(f);
284 public void deactivateFrame(JInternalFrame f)
286 delegate.deactivateFrame(f);
290 public void deiconifyFrame(JInternalFrame f)
292 delegate.deiconifyFrame(f);
296 public void dragFrame(JComponent f, int newX, int newY)
302 delegate.dragFrame(f, newX, newY);
306 public void endDraggingFrame(JComponent f)
308 delegate.endDraggingFrame(f);
309 desktopPane.repaint();
313 public void endResizingFrame(JComponent f)
315 delegate.endResizingFrame(f);
316 desktopPane.repaint();
320 public void iconifyFrame(JInternalFrame f)
322 delegate.iconifyFrame(f);
326 public void maximizeFrame(JInternalFrame f)
328 delegate.maximizeFrame(f);
332 public void minimizeFrame(JInternalFrame f)
334 delegate.minimizeFrame(f);
338 public void openFrame(JInternalFrame f)
340 delegate.openFrame(f);
344 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
351 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
355 public void setBoundsForFrame(JComponent f, int newX, int newY,
356 int newWidth, int newHeight)
358 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
361 // All other methods, simply delegate
365 * Private constructor enforces singleton pattern. It is called by reflection
366 * from ApplicationSingletonProvider.getInstance().
374 * A note to implementors. It is ESSENTIAL that any activities that might
375 * block are spawned off as threads rather than waited for during this
379 doConfigureStructurePrefs();
380 setTitle("Jalview " + Cache.getProperty("VERSION"));
384 if (Platform.getJavaVersion() >= 11)
386 // BH use reflection so that this code can be in both the Java8 and Java11 versions
387 Class<?> j11APQHandlers = Class.forName("jalview.gui.APQHandlers");
388 Method meth = j11APQHandlers.getMethod("setAPQHandlers", new Class<?>[] {Desktop.class});
389 meth.invoke(j11APQHandlers.newInstance(), this);
391 } catch (Throwable t)
393 System.out.println("Desktop Error setting APQHandlers: " + t.toString());
396 addWindowListener(new WindowAdapter()
400 public void windowClosing(WindowEvent ev)
406 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE",
409 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE",
411 desktopPane = new MyDesktopPane(selmemusage);
413 showMemusage.setSelected(selmemusage);
414 desktopPane.setBackground(Color.white);
416 getContentPane().setLayout(new BorderLayout());
417 // alternate config - have scrollbars - see notes in JAL-153
418 // JScrollPane sp = new JScrollPane();
419 // sp.getViewport().setView(desktop);
420 // getContentPane().add(sp, BorderLayout.CENTER);
422 // BH 2018 - just an experiment to try unclipped JInternalFrames.
425 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
428 getContentPane().add(desktopPane, BorderLayout.CENTER);
429 desktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
431 // This line prevents Windows Look&Feel resizing all new windows to
433 // if previous window was maximised
434 desktopPane.setDesktopManager(new MyDesktopManager(
435 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
436 : Platform.isAMacAndNotJS()
437 ? new AquaInternalFrameManager(
438 desktopPane.getDesktopManager())
439 : desktopPane.getDesktopManager())));
441 Rectangle dims = getLastKnownDimensions("");
448 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
449 int xPos = Math.max(5, (screenSize.width - 900) / 2);
450 int yPos = Math.max(5, (screenSize.height - 650) / 2);
451 setBounds(xPos, yPos, 900, 650);
454 // Note that this next syntax, checking for Platform.isJS and also
455 // escaping the code using @j2sIgnore, serves two purposes. It gives
456 // us an easily findable tag, Platform.isJS(), to places in the code where
457 // there is something different about the SwingJS implementation. Second,
458 // it deletes the unneeded Java-only code form the JavaScript version
459 // completely (@j2sIgnore), since it will never be used there.
461 if (!Platform.isJS() && !Jalview.isSynchronous())
469 jconsole = new Console(this, showjconsole);
470 jconsole.setHeader(Cache.getVersionDetailsForConsole());
471 showConsole(showjconsole);
473 showNews.setVisible(false);
475 experimentalFeatures.setSelected(showExperimental());
477 getIdentifiersOrgData();
481 // Spawn a thread that shows the splashscreen
483 SwingUtilities.invokeLater(new Runnable()
488 new SplashScreen(true);
492 // Thread off a new instance of the file chooser - this reduces the time
494 // takes to open it later on.
495 new Thread(new Runnable()
500 Cache.log.debug("Filechooser init thread started.");
501 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
502 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
504 Cache.log.debug("Filechooser init thread finished.");
507 // Add the service change listener
508 changeSupport.addJalviewPropertyChangeListener("services",
509 new PropertyChangeListener()
513 public void propertyChange(PropertyChangeEvent evt)
515 Cache.log.debug("Firing service changed event for "
516 + evt.getNewValue());
517 JalviewServicesChanged(evt);
524 if (!Jalview.isSynchronous())
526 this.setDropTarget(new java.awt.dnd.DropTarget(desktopPane, this));
528 this.addWindowListener(new WindowAdapter()
531 public void windowClosing(WindowEvent evt)
538 this.addMouseListener(ma = new MouseAdapter()
541 public void mousePressed(MouseEvent evt)
543 if (evt.isPopupTrigger()) // Mac
545 showPasteMenu(evt.getX(), evt.getY());
550 public void mouseReleased(MouseEvent evt)
552 if (evt.isPopupTrigger()) // Windows
554 showPasteMenu(evt.getX(), evt.getY());
558 desktopPane.addMouseListener(ma);
560 } catch (Throwable t)
567 * Answers true if user preferences to enable experimental features is True
572 public boolean showExperimental()
574 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
575 Boolean.FALSE.toString());
576 return Boolean.valueOf(experimental).booleanValue();
579 public void doConfigureStructurePrefs()
581 // configure services
582 StructureSelectionManager ssm = StructureSelectionManager
583 .getStructureSelectionManager(this);
584 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
586 ssm.setAddTempFacAnnot(Cache
587 .getDefault(Preferences.ADD_TEMPFACT_ANN, true));
588 ssm.setProcessSecondaryStructure(Cache
589 .getDefault(Preferences.STRUCT_FROM_PDB, true));
590 ssm.setSecStructServices(
591 Cache.getDefault(Preferences.USE_RNAVIEW, true));
595 ssm.setAddTempFacAnnot(false);
596 ssm.setProcessSecondaryStructure(false);
597 ssm.setSecStructServices(false);
601 public void checkForNews()
603 final Desktop me = this;
604 // Thread off the news reader, in case there are connection problems.
605 new Thread(new Runnable()
610 Cache.log.debug("Starting news thread.");
611 jvnews = new BlogReader(me);
612 showNews.setVisible(true);
613 Cache.log.debug("Completed news thread.");
618 public void getIdentifiersOrgData()
620 // Thread off the identifiers fetcher
621 new Thread(new Runnable()
626 Cache.log.debug("Downloading data from identifiers.org");
629 UrlDownloadClient.download(IdOrgSettings.getUrl(),
630 IdOrgSettings.getDownloadLocation());
631 } catch (IOException e)
633 Cache.log.debug("Exception downloading identifiers.org data"
642 protected void showNews_actionPerformed(ActionEvent e)
644 showNews(showNews.isSelected());
647 protected void showNews(boolean visible)
649 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
650 showNews.setSelected(visible);
651 if (visible && !jvnews.isVisible())
653 new Thread(new Runnable()
658 long now = System.currentTimeMillis();
660 MessageManager.getString("status.refreshing_news"), now);
661 jvnews.refreshNews();
662 setProgressBar(null, now);
670 * recover the last known dimensions for a jalview window
673 * - empty string is desktop, all other windows have unique prefix
674 * @return null or last known dimensions scaled to current geometry (if last
675 * window geom was known)
677 Rectangle getLastKnownDimensions(String windowName)
679 // TODO: lock aspect ratio for scaling desktop Bug #0058199
680 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
681 String x = Cache.getProperty(windowName + "SCREEN_X");
682 String y = Cache.getProperty(windowName + "SCREEN_Y");
684 .getProperty(windowName + "SCREEN_WIDTH");
685 String height = Cache
686 .getProperty(windowName + "SCREEN_HEIGHT");
687 if ((x != null) && (y != null) && (width != null) && (height != null))
689 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
690 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
691 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
693 // attempt #1 - try to cope with change in screen geometry - this
694 // version doesn't preserve original jv aspect ratio.
695 // take ratio of current screen size vs original screen size.
696 double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(
697 Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
698 double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(
699 Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
700 // rescale the bounds depending upon the current screen geometry.
701 ix = (int) (ix * sw);
702 iw = (int) (iw * sw);
703 iy = (int) (iy * sh);
704 ih = (int) (ih * sh);
705 while (ix >= screenSize.width)
708 "Window geometry location recall error: shifting horizontal to within screenbounds.");
709 ix -= screenSize.width;
711 while (iy >= screenSize.height)
714 "Window geometry location recall error: shifting vertical to within screenbounds.");
715 iy -= screenSize.height;
718 "Got last known dimensions for " + windowName + ": x:" + ix
719 + " y:" + iy + " width:" + iw + " height:" + ih);
721 // return dimensions for new instance
722 return new Rectangle(ix, iy, iw, ih);
727 protected void showPasteMenu(int x, int y)
729 JPopupMenu popup = new JPopupMenu();
730 JMenuItem item = new JMenuItem(
731 MessageManager.getString("label.paste_new_window"));
732 item.addActionListener(new ActionListener()
735 public void actionPerformed(ActionEvent evt)
742 popup.show(this, x, y);
749 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
750 Transferable contents = c.getContents(this);
752 if (contents != null)
754 String file = (String) contents
755 .getTransferData(DataFlavor.stringFlavor);
757 FileFormatI format = new IdentifyFile().identify(file,
758 DataSourceType.PASTE);
760 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
763 } catch (Exception ex)
766 "Unable to paste alignment from system clipboard:\n" + ex);
771 * Adds and opens the given frame to the desktop
782 public static synchronized void addInternalFrame(
783 final JInternalFrame frame, String title, int w, int h)
785 addInternalFrame(frame, title, true, w, h, true, false);
789 * Add an internal frame to the Jalview desktop
796 * When true, display frame immediately, otherwise, caller must call
797 * setVisible themselves.
803 public static synchronized void addInternalFrame(
804 final JInternalFrame frame, String title, boolean makeVisible,
807 addInternalFrame(frame, title, makeVisible, w, h, true, false);
811 * Add an internal frame to the Jalview desktop and make it visible
824 public static synchronized void addInternalFrame(
825 final JInternalFrame frame, String title, int w, int h,
828 addInternalFrame(frame, title, true, w, h, resizable, false);
832 * Add an internal frame to the Jalview desktop
839 * When true, display frame immediately, otherwise, caller must call
840 * setVisible themselves.
847 * @param ignoreMinSize
848 * Do not set the default minimum size for frame
850 public static synchronized void addInternalFrame(
851 final JInternalFrame frame, String title, boolean makeVisible,
852 int w, int h, boolean resizable, boolean ignoreMinSize)
856 // TODO: allow callers to determine X and Y position of frame (eg. via
858 // TODO: consider fixing method to update entries in the window submenu with
859 // the current window title
861 frame.setTitle(title);
863 if (w > 0 && (frame.getWidth() < 1 || frame.getHeight() < 1))
867 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
868 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
869 // IF JALVIEW IS RUNNING HEADLESS
870 // ///////////////////////////////////////////////
871 if (Jalview.isHeadlessMode())
880 frame.setMinimumSize(
881 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
883 // Set default dimension for Alignment Frame window.
884 // The Alignment Frame window could be added from a number of places,
886 // I did this here in order not to miss out on any Alignment frame.
887 if (frame instanceof AlignFrame)
889 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
890 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
894 frame.setVisible(makeVisible);
895 frame.setClosable(true);
896 frame.setResizable(resizable);
897 frame.setMaximizable(resizable);
898 frame.setIconifiable(resizable);
899 frame.setOpaque(Platform.isJS());
901 boolean isEmbedded = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
902 if (!isEmbedded && frame.getX() < 1 && frame.getY() < 1)
904 frame.setLocation(xOffset * openFrameCount,
905 yOffset * ((openFrameCount - 1) % 10) + yOffset);
909 * add an entry for the new frame in the Window menu
910 * (and remove it when the frame is closed)
912 JMenuItem menuItem = new JMenuItem(title);
913 frame.addInternalFrameListener(new InternalFrameAdapter()
916 public void internalFrameActivated(InternalFrameEvent evt)
918 JInternalFrame itf = getDesktopPane().getSelectedFrame();
921 if (itf instanceof AlignFrame)
923 Jalview.setCurrentAlignFrame((AlignFrame) itf);
930 public void internalFrameClosed(InternalFrameEvent evt)
932 PaintRefresher.RemoveComponent(frame);
935 * defensive check to prevent frames being
936 * added half off the window
938 if (openFrameCount > 0)
944 * ensure no reference to alignFrame retained by menu item listener
946 if (menuItem.getActionListeners().length > 0)
948 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
950 Desktop.getInstance().windowMenu.remove(menuItem);
954 menuItem.addActionListener(new ActionListener()
957 public void actionPerformed(ActionEvent e)
961 frame.setSelected(true);
962 frame.setIcon(false);
963 } catch (java.beans.PropertyVetoException ex)
965 // System.err.println(ex.toString());
970 setKeyBindings(frame);
972 getDesktopPane().add(frame);
974 Desktop.getInstance().windowMenu.add(menuItem);
979 frame.setSelected(true);
980 frame.requestFocus();
981 } catch (java.beans.PropertyVetoException ve)
983 } catch (java.lang.ClassCastException cex)
986 "Squashed a possible GUI implementation error. If you can recreate this, please look at http://issues.jalview.org/browse/JAL-869",
992 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
997 private static void setKeyBindings(JInternalFrame frame)
999 @SuppressWarnings("serial")
1000 final Action closeAction = new AbstractAction()
1003 public void actionPerformed(ActionEvent e)
1010 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1012 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1013 InputEvent.CTRL_DOWN_MASK);
1014 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1015 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1017 InputMap inputMap = frame
1018 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1019 String ctrlW = ctrlWKey.toString();
1020 inputMap.put(ctrlWKey, ctrlW);
1021 inputMap.put(cmdWKey, ctrlW);
1023 ActionMap actionMap = frame.getActionMap();
1024 actionMap.put(ctrlW, closeAction);
1028 public void lostOwnership(Clipboard clipboard, Transferable contents)
1032 Desktop.getInstance().jalviewClipboard = null;
1035 internalCopy = false;
1039 public void dragEnter(DropTargetDragEvent evt)
1044 public void dragExit(DropTargetEvent evt)
1049 public void dragOver(DropTargetDragEvent evt)
1054 public void dropActionChanged(DropTargetDragEvent evt)
1065 public void drop(DropTargetDropEvent evt)
1067 boolean success = true;
1068 // JAL-1552 - acceptDrop required before getTransferable call for
1069 // Java's Transferable for native dnd
1070 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1071 Transferable t = evt.getTransferable();
1072 List<Object> files = new ArrayList<>();
1073 List<DataSourceType> protocols = new ArrayList<>();
1077 Desktop.transferFromDropTarget(files, protocols, evt, t);
1078 } catch (Exception e)
1080 e.printStackTrace();
1088 for (int i = 0; i < files.size(); i++)
1090 // BH 2018 File or String
1091 Object file = files.get(i);
1092 String fileName = file.toString();
1093 DataSourceType protocol = (protocols == null)
1094 ? DataSourceType.FILE
1096 FileFormatI format = null;
1098 if (fileName.endsWith(".jar"))
1100 format = FileFormat.Jalview;
1105 format = new IdentifyFile().identify(file, protocol);
1107 if (file instanceof File)
1109 Platform.cacheFileData((File) file);
1111 new FileLoader().LoadFile(null, file, protocol, format);
1114 } catch (Exception ex)
1119 evt.dropComplete(success); // need this to ensure input focus is properly
1120 // transfered to any new windows created
1130 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1132 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1133 JalviewFileChooser chooser = JalviewFileChooser
1134 .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat, BackupFiles.getEnabled());
1136 chooser.setFileView(new JalviewFileView());
1137 chooser.setDialogTitle(
1138 MessageManager.getString("label.open_local_file"));
1139 chooser.setToolTipText(MessageManager.getString("action.open"));
1141 chooser.setResponseHandler(0, new Runnable()
1146 File selectedFile = chooser.getSelectedFile();
1147 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1149 FileFormatI format = chooser.getSelectedFormat();
1152 * Call IdentifyFile to verify the file contains what its extension implies.
1153 * Skip this step for dynamically added file formats, because
1154 * IdentifyFile does not know how to recognise them.
1156 if (FileFormats.getInstance().isIdentifiable(format))
1160 format = new IdentifyFile().identify(selectedFile,
1161 DataSourceType.FILE);
1162 } catch (FileFormatException e)
1164 // format = null; //??
1168 new FileLoader().LoadFile(viewport, selectedFile,
1169 DataSourceType.FILE, format);
1172 chooser.showOpenDialog(this);
1176 * Shows a dialog for input of a URL at which to retrieve alignment data
1181 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1183 // This construct allows us to have a wider textfield
1185 JLabel label = new JLabel(
1186 MessageManager.getString("label.input_file_url"));
1188 JPanel panel = new JPanel(new GridLayout(2, 1));
1192 * the URL to fetch is
1193 * Java: an editable combobox with history
1194 * JS: (pending JAL-3038) a plain text field
1197 String urlBase = "http://www.";
1198 if (Platform.isJS())
1200 history = new JTextField(urlBase, 35);
1209 JComboBox<String> asCombo = new JComboBox<>();
1210 asCombo.setPreferredSize(new Dimension(400, 20));
1211 asCombo.setEditable(true);
1212 asCombo.addItem(urlBase);
1213 String historyItems = Cache.getProperty("RECENT_URL");
1214 if (historyItems != null)
1216 for (String token : historyItems.split("\\t"))
1218 asCombo.addItem(token);
1225 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1226 MessageManager.getString("action.cancel") };
1227 Runnable action = new Runnable()
1232 @SuppressWarnings("unchecked")
1233 String url = (history instanceof JTextField
1234 ? ((JTextField) history).getText()
1235 : ((JComboBox<String>) history).getSelectedItem()
1238 if (url.toLowerCase().endsWith(".jar"))
1240 if (viewport != null)
1242 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1243 FileFormat.Jalview);
1247 new FileLoader().LoadFile(url, DataSourceType.URL,
1248 FileFormat.Jalview);
1253 FileFormatI format = null;
1256 format = new IdentifyFile().identify(url, DataSourceType.URL);
1257 } catch (FileFormatException e)
1259 // TODO revise error handling, distinguish between
1260 // URL not found and response not valid
1265 String msg = MessageManager
1266 .formatMessage("label.couldnt_locate", url);
1267 JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
1269 MessageManager.getString("label.url_not_found"),
1270 JvOptionPane.WARNING_MESSAGE);
1275 if (viewport != null)
1277 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1282 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1287 String dialogOption = MessageManager
1288 .getString("label.input_alignment_from_url");
1289 JvOptionPane.newOptionDialog(getDesktopPane())
1290 .setResponseHandler(0, action)
1291 .showInternalDialog(panel, dialogOption,
1292 JvOptionPane.YES_NO_CANCEL_OPTION,
1293 JvOptionPane.PLAIN_MESSAGE, null, options,
1294 MessageManager.getString("action.ok"));
1298 * Opens the CutAndPaste window for the user to paste an alignment in to
1301 * - if not null, the pasted alignment is added to the current
1302 * alignment; if null, to a new alignment window
1305 public void inputTextboxMenuItem_actionPerformed(
1306 AlignmentViewPanel viewPanel)
1308 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1309 cap.setForInput(viewPanel);
1310 Desktop.addInternalFrame(cap,
1311 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1321 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1322 Cache.setProperty("SCREENGEOMETRY_WIDTH",
1324 Cache.setProperty("SCREENGEOMETRY_HEIGHT",
1325 screen.height + "");
1326 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1327 getWidth(), getHeight()));
1329 if (jconsole != null)
1331 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1332 jconsole.stopConsole();
1336 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1339 if (dialogExecutor != null)
1341 dialogExecutor.shutdownNow();
1343 closeAll_actionPerformed(null);
1345 if (groovyConsole != null)
1347 // suppress a possible repeat prompt to save script
1348 groovyConsole.setDirty(false);
1349 groovyConsole.exit();
1354 private void storeLastKnownDimensions(String string, Rectangle jc)
1356 Cache.log.debug("Storing last known dimensions for "
1357 + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1358 + " height:" + jc.height);
1360 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1361 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1362 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1363 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1373 public void aboutMenuItem_actionPerformed(ActionEvent e)
1375 new Thread(new Runnable()
1380 // BH! true meaning "interactive" here (applet branch); was false in develop version??
1381 new SplashScreen(true);
1387 * Returns the html text for the About screen, including any available version
1388 * number, build details, author details and citation reference, but without
1389 * the enclosing {@code html} tags
1393 public String getAboutMessage()
1395 StringBuilder message = new StringBuilder(1024);
1396 message.append("<h1><strong>Version: ")
1397 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1398 .append("<strong>Built: <em>")
1399 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1400 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1401 .append("</strong>");
1403 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1404 if (latestVersion.equals("Checking"))
1406 // JBP removed this message for 2.11: May be reinstated in future version
1407 // message.append("<br>...Checking latest version...</br>");
1409 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1411 boolean red = false;
1412 if (Cache.getProperty("VERSION").toLowerCase()
1413 .indexOf("automated build") == -1)
1416 // Displayed when code version and jnlp version do not match and code
1417 // version is not a development build
1418 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1421 message.append("<br>!! Version ")
1422 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1423 .append(" is available for download from ")
1424 .append(Cache.getDefault("www.jalview.org",
1425 "http://www.jalview.org"))
1429 message.append("</div>");
1432 message.append("<br>Authors: ");
1433 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1434 message.append(CITATION);
1436 return message.toString();
1440 * Action on requesting Help documentation
1443 public void documentationMenuItem_actionPerformed()
1447 if (Platform.isJS())
1449 BrowserLauncher.openURL("http://www.jalview.org/help.html");
1458 Help.showHelpWindow();
1460 } catch (Exception ex)
1462 System.err.println("Error opening help: " + ex.getMessage());
1467 public void closeAll_actionPerformed(ActionEvent e)
1469 if (desktopPane == null)
1473 // TODO show a progress bar while closing?
1474 JInternalFrame[] frames = desktopPane.getAllFrames();
1475 for (int i = 0; i < frames.length; i++)
1479 frames[i].setClosed(true);
1480 } catch (java.beans.PropertyVetoException ex)
1484 Jalview.setCurrentAlignFrame(null);
1485 System.out.println("ALL CLOSED");
1488 * reset state of singleton objects as appropriate (clear down session state
1489 * when all windows are closed)
1491 getStructureSelectionManager().resetAll();
1495 public void raiseRelated_actionPerformed(ActionEvent e)
1497 reorderAssociatedWindows(false, false);
1501 public void minimizeAssociated_actionPerformed(ActionEvent e)
1503 reorderAssociatedWindows(true, false);
1506 void closeAssociatedWindows()
1508 reorderAssociatedWindows(false, true);
1514 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1518 protected void garbageCollect_actionPerformed(ActionEvent e)
1520 // We simply collect the garbage
1521 Cache.log.debug("Collecting garbage...");
1523 Cache.log.debug("Finished garbage collection.");
1530 * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1534 protected void showMemusage_actionPerformed(ActionEvent e)
1536 getDesktopPane().showMemoryUsage(showMemusage.isSelected());
1543 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1547 protected void showConsole_actionPerformed(ActionEvent e)
1549 showConsole(showConsole.isSelected());
1552 Console jconsole = null;
1555 * control whether the java console is visible or not
1559 void showConsole(boolean selected)
1561 // TODO: decide if we should update properties file
1562 if (jconsole != null) // BH 2018
1564 showConsole.setSelected(selected);
1565 Cache.setProperty("SHOW_JAVA_CONSOLE",
1566 Boolean.valueOf(selected).toString());
1567 jconsole.setVisible(selected);
1571 void reorderAssociatedWindows(boolean minimize, boolean close)
1573 JInternalFrame[] frames = getDesktopPane().getAllFrames();
1574 if (frames == null || frames.length < 1)
1579 AlignmentViewport source = null, target = null;
1580 if (frames[0] instanceof AlignFrame)
1582 source = ((AlignFrame) frames[0]).getCurrentView();
1584 else if (frames[0] instanceof TreePanel)
1586 source = ((TreePanel) frames[0]).getViewPort();
1588 else if (frames[0] instanceof PCAPanel)
1590 source = ((PCAPanel) frames[0]).av;
1592 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1594 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1599 for (int i = 0; i < frames.length; i++)
1602 if (frames[i] == null)
1606 if (frames[i] instanceof AlignFrame)
1608 target = ((AlignFrame) frames[i]).getCurrentView();
1610 else if (frames[i] instanceof TreePanel)
1612 target = ((TreePanel) frames[i]).getViewPort();
1614 else if (frames[i] instanceof PCAPanel)
1616 target = ((PCAPanel) frames[i]).av;
1618 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1620 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1623 if (source == target)
1629 frames[i].setClosed(true);
1633 frames[i].setIcon(minimize);
1636 frames[i].toFront();
1640 } catch (java.beans.PropertyVetoException ex)
1655 protected void preferences_actionPerformed(ActionEvent e)
1661 * Prompts the user to choose a file and then saves the Jalview state as a
1662 * Jalview project file
1665 public void saveState_actionPerformed()
1667 saveState_actionPerformed(false);
1670 public void saveState_actionPerformed(boolean saveAs)
1672 java.io.File projectFile = getProjectFile();
1673 // autoSave indicates we already have a file and don't need to ask
1674 boolean autoSave = projectFile != null && !saveAs
1675 && BackupFiles.getEnabled();
1677 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1678 // saveAs="+saveAs+", Backups
1679 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1681 boolean approveSave = false;
1684 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1687 chooser.setFileView(new JalviewFileView());
1688 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1690 int value = chooser.showSaveDialog(this);
1692 if (value == JalviewFileChooser.APPROVE_OPTION)
1694 projectFile = chooser.getSelectedFile();
1695 setProjectFile(projectFile);
1700 if (approveSave || autoSave)
1702 final Desktop me = this;
1703 final java.io.File chosenFile = projectFile;
1704 new Thread(new Runnable()
1709 // TODO: refactor to Jalview desktop session controller action.
1710 setProgressBar(MessageManager.formatMessage(
1711 "label.saving_jalview_project", new Object[]
1712 { chosenFile.getName() }), chosenFile.hashCode());
1713 Cache.setProperty("LAST_DIRECTORY",
1714 chosenFile.getParent());
1715 // TODO catch and handle errors for savestate
1716 // TODO prevent user from messing with the Desktop whilst we're saving
1719 boolean doBackup = BackupFiles.getEnabled();
1720 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
1722 new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
1726 backupfiles.setWriteSuccess(true);
1727 backupfiles.rollBackupsAndRenameTempFile();
1729 } catch (OutOfMemoryError oom)
1731 new OOMWarning("Whilst saving current state to "
1732 + chosenFile.getName(), oom);
1733 } catch (Exception ex)
1735 Cache.log.error("Problems whilst trying to save to "
1736 + chosenFile.getName(), ex);
1737 JvOptionPane.showMessageDialog(me,
1738 MessageManager.formatMessage(
1739 "label.error_whilst_saving_current_state_to",
1741 { chosenFile.getName() }),
1742 MessageManager.getString("label.couldnt_save_project"),
1743 JvOptionPane.WARNING_MESSAGE);
1745 setProgressBar(null, chosenFile.hashCode());
1752 public void saveAsState_actionPerformed(ActionEvent e)
1754 saveState_actionPerformed(true);
1757 protected void setProjectFile(File choice)
1759 this.projectFile = choice;
1762 public File getProjectFile()
1764 return this.projectFile;
1768 * Shows a file chooser dialog and tries to read in the selected file as a
1772 public void loadState_actionPerformed()
1774 final String[] suffix = new String[] { "jvp", "jar" };
1775 final String[] desc = new String[] { "Jalview Project",
1776 "Jalview Project (old)" };
1777 JalviewFileChooser chooser = new JalviewFileChooser(
1778 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1779 "Jalview Project", true, BackupFiles.getEnabled()); // last two booleans: allFiles,
1781 chooser.setFileView(new JalviewFileView());
1782 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1783 chooser.setResponseHandler(0, new Runnable()
1788 File selectedFile = chooser.getSelectedFile();
1789 setProjectFile(selectedFile);
1790 String choice = selectedFile.getAbsolutePath();
1791 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1792 new Thread(new Runnable()
1799 // BH was String "choice" here but needs to be File object
1800 new Jalview2XML().loadJalviewAlign(selectedFile);
1801 } catch (OutOfMemoryError oom)
1803 new OOMWarning("Whilst loading project from " + choice, oom);
1804 } catch (Exception ex)
1807 "Problems whilst loading project from " + choice, ex);
1808 JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
1809 MessageManager.formatMessage(
1810 "label.error_whilst_loading_project_from",
1813 MessageManager.getString("label.couldnt_load_project"),
1814 JvOptionPane.WARNING_MESSAGE);
1821 chooser.showOpenDialog(this);
1825 public void inputSequence_actionPerformed(ActionEvent e)
1827 new SequenceFetcher(this);
1830 JPanel progressPanel;
1832 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1834 public void startLoading(final Object fileName)
1836 if (fileLoadingCount == 0)
1838 fileLoadingPanels.add(addProgressPanel(MessageManager
1839 .formatMessage("label.loading_file", new Object[]
1845 private JPanel addProgressPanel(String string)
1847 if (progressPanel == null)
1849 progressPanel = new JPanel(new GridLayout(1, 1));
1850 totalProgressCount = 0;
1851 getContentPane().add(progressPanel, BorderLayout.SOUTH);
1853 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1854 JProgressBar progressBar = new JProgressBar();
1855 progressBar.setIndeterminate(true);
1857 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1859 thisprogress.add(progressBar, BorderLayout.CENTER);
1860 progressPanel.add(thisprogress);
1861 ((GridLayout) progressPanel.getLayout()).setRows(
1862 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1863 ++totalProgressCount;
1865 return thisprogress;
1868 int totalProgressCount = 0;
1870 private void removeProgressPanel(JPanel progbar)
1872 if (progressPanel != null)
1874 synchronized (progressPanel)
1876 progressPanel.remove(progbar);
1877 GridLayout gl = (GridLayout) progressPanel.getLayout();
1878 gl.setRows(gl.getRows() - 1);
1879 if (--totalProgressCount < 1)
1881 this.getContentPane().remove(progressPanel);
1882 progressPanel = null;
1889 public void stopLoading()
1892 if (fileLoadingCount < 1)
1894 while (fileLoadingPanels.size() > 0)
1896 removeProgressPanel(fileLoadingPanels.remove(0));
1898 fileLoadingPanels.clear();
1899 fileLoadingCount = 0;
1904 public static int getViewCount(String alignmentId)
1906 AlignmentViewport[] aps = getViewports(alignmentId);
1907 return (aps == null) ? 0 : aps.length;
1912 * @param alignmentId
1913 * - if null, all sets are returned
1914 * @return all AlignmentPanels concerning the alignmentId sequence set
1916 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1918 if (Desktop.getDesktopPane() == null)
1920 // no frames created and in headless mode
1921 // TODO: verify that frames are recoverable when in headless mode
1924 List<AlignmentPanel> aps = new ArrayList<>();
1925 AlignFrame[] frames = getAlignFrames();
1930 for (AlignFrame af : frames)
1932 for (AlignmentPanel ap : af.alignPanels)
1934 if (alignmentId == null
1935 || alignmentId.equals(ap.av.getSequenceSetId()))
1941 if (aps.size() == 0)
1945 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1950 * get all the viewports on an alignment.
1952 * @param sequenceSetId
1953 * unique alignment id (may be null - all viewports returned in that
1955 * @return all viewports on the alignment bound to sequenceSetId
1957 public static AlignmentViewport[] getViewports(String sequenceSetId)
1959 List<AlignmentViewport> viewp = new ArrayList<>();
1960 if (getDesktopPane() != null)
1962 AlignFrame[] frames = Desktop.getAlignFrames();
1964 for (AlignFrame afr : frames)
1966 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
1967 .equals(sequenceSetId))
1969 if (afr.alignPanels != null)
1971 for (AlignmentPanel ap : afr.alignPanels)
1973 if (sequenceSetId == null
1974 || sequenceSetId.equals(ap.av.getSequenceSetId()))
1982 viewp.add(afr.getViewport());
1986 if (viewp.size() > 0)
1988 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1995 * Explode the views in the given frame into separate AlignFrame
1999 public static void explodeViews(AlignFrame af)
2001 int size = af.alignPanels.size();
2007 // BH! not in applet branch
2008 // FIXME: ideally should use UI interface API
2009 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2010 && af.featureSettings.isOpen())
2011 ? af.featureSettings
2013 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2014 for (int i = 0; i < size; i++)
2016 AlignmentPanel ap = af.alignPanels.get(i);
2017 AlignFrame newaf = new AlignFrame(ap);
2019 // BH! not in applet branch
2020 // transfer reference for existing feature settings to new alignFrame
2021 if (ap == af.alignPanel)
2023 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2025 newaf.featureSettings = viewFeatureSettings;
2027 newaf.setFeatureSettingsGeometry(fsBounds);
2031 * Restore the view's last exploded frame geometry if known. Multiple
2032 * views from one exploded frame share and restore the same (frame)
2033 * position and size.
2035 Rectangle geometry = ap.av.getExplodedGeometry();
2036 if (geometry != null)
2038 newaf.setBounds(geometry);
2041 ap.av.setGatherViewsHere(false);
2043 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2044 AlignFrame.DEFAULT_HEIGHT);
2045 // BH! not in applet branch
2046 // and materialise a new feature settings dialog instance for the new alignframe
2047 // (closes the old as if 'OK' was pressed)
2048 if (ap == af.alignPanel && newaf.featureSettings != null
2049 && newaf.featureSettings.isOpen()
2050 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2052 newaf.showFeatureSettingsUI();
2056 // BH! not in applet branch
2057 af.featureSettings = null;
2058 af.alignPanels.clear();
2059 af.closeMenuItem_actionPerformed(true);
2064 * Gather expanded views (separate AlignFrame's) with the same sequence set
2065 * identifier back in to this frame as additional views, and close the expanded
2066 * views. Note the expanded frames may themselves have multiple views. We take
2071 public void gatherViews(AlignFrame source)
2073 source.viewport.setGatherViewsHere(true);
2074 source.viewport.setExplodedGeometry(source.getBounds());
2075 JInternalFrame[] frames = getAllFrames();
2076 String viewId = source.viewport.getSequenceSetId();
2078 for (int t = 0; t < frames.length; t++)
2080 if (frames[t] instanceof AlignFrame && frames[t] != source)
2082 AlignFrame af = (AlignFrame) frames[t];
2083 boolean gatherThis = false;
2084 for (int a = 0; a < af.alignPanels.size(); a++)
2086 AlignmentPanel ap = af.alignPanels.get(a);
2087 if (viewId.equals(ap.av.getSequenceSetId()))
2090 ap.av.setGatherViewsHere(false);
2091 ap.av.setExplodedGeometry(af.getBounds());
2092 source.addAlignmentPanel(ap, false);
2098 if (af.featureSettings != null && af.featureSettings.isOpen())
2100 if (source.featureSettings == null)
2102 // preserve the feature settings geometry for this frame
2103 source.featureSettings = af.featureSettings;
2104 source.setFeatureSettingsGeometry(
2105 af.getFeatureSettingsGeometry());
2109 // close it and forget
2110 af.featureSettings.close();
2113 af.alignPanels.clear();
2114 af.closeMenuItem_actionPerformed(true);
2118 // refresh the feature setting UI for the source frame if it exists
2119 if (source.featureSettings != null
2120 && source.featureSettings.isOpen())
2122 source.showFeatureSettingsUI();
2126 public JInternalFrame[] getAllFrames()
2128 return desktopPane.getAllFrames();
2132 * Checks the given url to see if it gives a response indicating that the user
2133 * should be informed of a new questionnaire.
2137 public void checkForQuestionnaire(String url)
2139 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2140 // javax.swing.SwingUtilities.invokeLater(jvq);
2141 new Thread(jvq).start();
2144 public void checkURLLinks()
2146 // Thread off the URL link checker
2147 addDialogThread(new Runnable()
2152 if (Cache.getDefault("CHECKURLLINKS", true))
2154 // check what the actual links are - if it's just the default don't
2155 // bother with the warning
2156 List<String> links = Preferences.sequenceUrlLinks
2159 // only need to check links if there is one with a
2160 // SEQUENCE_ID which is not the default EMBL_EBI link
2161 ListIterator<String> li = links.listIterator();
2162 boolean check = false;
2163 List<JLabel> urls = new ArrayList<>();
2164 while (li.hasNext())
2166 String link = li.next();
2167 if (link.contains(UrlConstants.SEQUENCE_ID)
2168 && !UrlConstants.isDefaultString(link))
2171 int barPos = link.indexOf("|");
2172 String urlMsg = barPos == -1 ? link
2173 : link.substring(0, barPos) + ": "
2174 + link.substring(barPos + 1);
2175 urls.add(new JLabel(urlMsg));
2183 // ask user to check in case URL links use old style tokens
2184 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2185 JPanel msgPanel = new JPanel();
2186 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2187 msgPanel.add(Box.createVerticalGlue());
2188 JLabel msg = new JLabel(MessageManager
2189 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2190 JLabel msg2 = new JLabel(MessageManager
2191 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2193 for (JLabel url : urls)
2199 final JCheckBox jcb = new JCheckBox(
2200 MessageManager.getString("label.do_not_display_again"));
2201 jcb.addActionListener(new ActionListener()
2204 public void actionPerformed(ActionEvent e)
2206 // update Cache settings for "don't show this again"
2207 boolean showWarningAgain = !jcb.isSelected();
2208 Cache.setProperty("CHECKURLLINKS",
2209 Boolean.valueOf(showWarningAgain).toString());
2214 JvOptionPane.showMessageDialog(desktopPane, msgPanel,
2216 .getString("label.SEQUENCE_ID_no_longer_used"),
2217 JvOptionPane.WARNING_MESSAGE);
2224 * Proxy class for JDesktopPane which optionally displays the current memory
2225 * usage and highlights the desktop area with a red bar if free memory runs low.
2229 public class MyDesktopPane extends JDesktopPane
2232 private static final float ONE_MB = 1048576f;
2234 boolean showMemoryUsage = false;
2238 java.text.NumberFormat df;
2240 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2243 public MyDesktopPane(boolean showMemoryUsage)
2245 showMemoryUsage(showMemoryUsage);
2248 public void showMemoryUsage(boolean showMemory)
2250 this.showMemoryUsage = showMemory;
2253 Thread worker = new Thread(this);
2259 public boolean isShowMemoryUsage()
2261 return showMemoryUsage;
2267 df = java.text.NumberFormat.getNumberInstance();
2268 df.setMaximumFractionDigits(2);
2269 runtime = Runtime.getRuntime();
2271 while (showMemoryUsage)
2275 maxMemory = runtime.maxMemory() / ONE_MB;
2276 allocatedMemory = runtime.totalMemory() / ONE_MB;
2277 freeMemory = runtime.freeMemory() / ONE_MB;
2278 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2280 percentUsage = (totalFreeMemory / maxMemory) * 100;
2282 // if (percentUsage < 20)
2284 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2286 // instance.set.setBorder(border1);
2289 // sleep after showing usage
2291 } catch (Exception ex)
2293 ex.printStackTrace();
2299 public void paintComponent(Graphics g)
2301 if (showMemoryUsage && g != null && df != null)
2303 if (percentUsage < 20)
2305 g.setColor(Color.red);
2307 FontMetrics fm = g.getFontMetrics();
2310 g.drawString(MessageManager.formatMessage("label.memory_stats",
2312 { df.format(totalFreeMemory), df.format(maxMemory),
2313 df.format(percentUsage) }),
2314 10, getHeight() - fm.getHeight());
2321 * Accessor method to quickly get all the AlignmentFrames loaded.
2323 * @return an array of AlignFrame, or null if none found
2325 public static AlignFrame[] getAlignFrames()
2327 if (Jalview.isHeadlessMode())
2329 // Desktop.getDesktopPane() is null in headless mode
2330 return new AlignFrame[] { Jalview.getCurrentAlignFrame() };
2333 JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
2339 List<AlignFrame> avp = new ArrayList<>();
2341 for (int i = frames.length - 1; i > -1; i--)
2343 if (frames[i] instanceof AlignFrame)
2345 avp.add((AlignFrame) frames[i]);
2347 else if (frames[i] instanceof SplitFrame)
2350 * Also check for a split frame containing an AlignFrame
2352 GSplitFrame sf = (GSplitFrame) frames[i];
2353 if (sf.getTopFrame() instanceof AlignFrame)
2355 avp.add((AlignFrame) sf.getTopFrame());
2357 if (sf.getBottomFrame() instanceof AlignFrame)
2359 avp.add((AlignFrame) sf.getBottomFrame());
2363 if (avp.size() == 0)
2367 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2372 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2376 public GStructureViewer[] getJmols()
2378 JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
2384 List<GStructureViewer> avp = new ArrayList<>();
2386 for (int i = frames.length - 1; i > -1; i--)
2388 if (frames[i] instanceof AppJmol)
2390 GStructureViewer af = (GStructureViewer) frames[i];
2394 if (avp.size() == 0)
2398 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2403 * Add Groovy Support to Jalview
2406 public void groovyShell_actionPerformed()
2410 openGroovyConsole();
2411 } catch (Exception ex)
2413 Cache.log.error("Groovy Shell Creation failed.", ex);
2414 JvOptionPane.showInternalMessageDialog(desktopPane,
2416 MessageManager.getString("label.couldnt_create_groovy_shell"),
2417 MessageManager.getString("label.groovy_support_failed"),
2418 JvOptionPane.ERROR_MESSAGE);
2423 * Open the Groovy console
2425 private void openGroovyConsole()
2427 if (groovyConsole == null)
2429 groovyConsole = new groovy.ui.Console();
2430 groovyConsole.setVariable("Jalview", this);
2431 groovyConsole.run();
2434 * We allow only one console at a time, so that AlignFrame menu option
2435 * 'Calculate | Run Groovy script' is unambiguous.
2436 * Disable 'Groovy Console', and enable 'Run script', when the console is
2437 * opened, and the reverse when it is closed
2439 Window window = (Window) groovyConsole.getFrame();
2440 window.addWindowListener(new WindowAdapter()
2443 public void windowClosed(WindowEvent e)
2446 * rebind CMD-Q from Groovy Console to Jalview Quit
2449 enableExecuteGroovy(false);
2455 * show Groovy console window (after close and reopen)
2457 ((Window) groovyConsole.getFrame()).setVisible(true);
2460 * if we got this far, enable 'Run Groovy' in AlignFrame menus
2461 * and disable opening a second console
2463 enableExecuteGroovy(true);
2467 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
2470 protected void addQuitHandler()
2472 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
2473 .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
2474 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
2476 getRootPane().getActionMap().put("Quit", new AbstractAction()
2479 public void actionPerformed(ActionEvent e)
2487 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2490 * true if Groovy console is open
2492 public void enableExecuteGroovy(boolean enabled)
2495 * disable opening a second Groovy console
2496 * (or re-enable when the console is closed)
2498 groovyShell.setEnabled(!enabled);
2500 AlignFrame[] alignFrames = getAlignFrames();
2501 if (alignFrames != null)
2503 for (AlignFrame af : alignFrames)
2505 af.setGroovyEnabled(enabled);
2511 * Progress bars managed by the IProgressIndicator method.
2513 private Hashtable<Long, JPanel> progressBars;
2515 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2520 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2523 public void setProgressBar(String message, long id)
2525 // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
2527 if (progressBars == null)
2529 progressBars = new Hashtable<>();
2530 progressBarHandlers = new Hashtable<>();
2533 if (progressBars.get(Long.valueOf(id)) != null)
2535 JPanel panel = progressBars.remove(Long.valueOf(id));
2536 if (progressBarHandlers.contains(Long.valueOf(id)))
2538 progressBarHandlers.remove(Long.valueOf(id));
2540 removeProgressPanel(panel);
2544 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2551 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2552 * jalview.gui.IProgressIndicatorHandler)
2555 public void registerHandler(final long id,
2556 final IProgressIndicatorHandler handler)
2558 if (progressBarHandlers == null
2559 || !progressBars.containsKey(Long.valueOf(id)))
2561 throw new Error(MessageManager.getString(
2562 "error.call_setprogressbar_before_registering_handler"));
2564 progressBarHandlers.put(Long.valueOf(id), handler);
2565 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2566 if (handler.canCancel())
2568 JButton cancel = new JButton(
2569 MessageManager.getString("action.cancel"));
2570 final IProgressIndicator us = this;
2571 cancel.addActionListener(new ActionListener()
2575 public void actionPerformed(ActionEvent e)
2577 handler.cancelActivity(id);
2578 us.setProgressBar(MessageManager
2579 .formatMessage("label.cancelled_params", new Object[]
2580 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2584 progressPanel.add(cancel, BorderLayout.EAST);
2590 * @return true if any progress bars are still active
2593 public boolean operationInProgress()
2595 if (progressBars != null && progressBars.size() > 0)
2603 * This will return the first AlignFrame holding the given viewport instance. It
2604 * will break if there are more than one AlignFrames viewing a particular av.
2607 * @return alignFrame for viewport
2609 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2611 if (getDesktopPane() != null)
2613 AlignmentPanel[] aps = getAlignmentPanels(
2614 viewport.getSequenceSetId());
2615 for (int panel = 0; aps != null && panel < aps.length; panel++)
2617 if (aps[panel] != null && aps[panel].av == viewport)
2619 return aps[panel].alignFrame;
2627 * flag set if jalview GUI is being operated programmatically
2629 private boolean inBatchMode = false;
2632 * check if jalview GUI is being operated programmatically
2634 * @return inBatchMode
2636 public boolean isInBatchMode()
2642 * set flag if jalview GUI is being operated programmatically
2644 * @param inBatchMode
2646 public void setInBatchMode(boolean inBatchMode)
2648 this.inBatchMode = inBatchMode;
2651 public void startServiceDiscovery()
2653 startServiceDiscovery(false);
2656 public void startServiceDiscovery(boolean blocking)
2658 boolean alive = true;
2659 Thread t0 = null, t1 = null, t2 = null;
2660 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2663 // todo: changesupport handlers need to be transferred
2664 if (discoverer == null)
2666 discoverer = jalview.ws.jws1.Discoverer.getInstance();
2667 // register PCS handler for getDesktopPane().
2668 discoverer.addPropertyChangeListener(changeSupport);
2670 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2671 // until we phase out completely
2672 (t0 = new Thread(discoverer)).start();
2675 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2677 t2 = jalview.ws.jws2.Jws2Discoverer.getInstance()
2678 .startDiscoverer(changeSupport);
2682 // TODO: do rest service discovery
2691 } catch (Exception e)
2694 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2695 || (t3 != null && t3.isAlive())
2696 || (t0 != null && t0.isAlive());
2702 * called to check if the service discovery process completed successfully.
2706 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2708 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2710 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
2711 .getErrorMessages();
2714 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2716 if (serviceChangedDialog == null)
2718 // only run if we aren't already displaying one of these.
2719 addDialogThread(serviceChangedDialog = new Runnable()
2726 * JalviewDialog jd =new JalviewDialog() {
2728 * @Override protected void cancelPressed() { // TODO
2729 * Auto-generated method stub
2731 * }@Override protected void okPressed() { // TODO
2732 * Auto-generated method stub
2734 * }@Override protected void raiseClosed() { // TODO
2735 * Auto-generated method stub
2737 * } }; jd.initDialogFrame(new
2738 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2739 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2740 * + " or mis-configured HTTP proxy settings.<br/>" +
2741 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
2743 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
2744 * ), true, true, "Web Service Configuration Problem", 450,
2747 * jd.waitForInput();
2749 JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
2750 new JLabel("<html><table width=\"450\"><tr><td>"
2751 + ermsg + "</td></tr></table>"
2752 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2753 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2754 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2755 + " Tools->Preferences dialog box to change them.</p></html>"),
2756 "Web Service Configuration Problem",
2757 JvOptionPane.DEFAULT_OPTION,
2758 JvOptionPane.ERROR_MESSAGE);
2759 serviceChangedDialog = null;
2768 "Errors reported by JABA discovery service. Check web services preferences.\n"
2775 Runnable serviceChangedDialog = null;
2778 * start a thread to open a URL in the configured browser. Pops up a warning
2779 * dialog to the user if there is an exception when calling out to the browser
2784 public static void showUrl(final String url)
2786 showUrl(url, Desktop.getInstance());
2790 * Like showUrl but allows progress handler to be specified
2794 * (null) or object implementing IProgressIndicator
2796 public static void showUrl(final String url,
2797 final IProgressIndicator progress)
2799 new Thread(new Runnable()
2806 if (progress != null && !Platform.isJS())
2808 progress.setProgressBar(MessageManager
2809 .formatMessage("status.opening_params", new Object[]
2810 { url }), this.hashCode());
2812 Platform.openURL(url);
2813 } catch (Exception ex)
2815 JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
2817 .getString("label.web_browser_not_found_unix"),
2818 MessageManager.getString("label.web_browser_not_found"),
2819 JvOptionPane.WARNING_MESSAGE);
2821 ex.printStackTrace();
2823 if (progress != null && !Platform.isJS())
2825 progress.setProgressBar(null, this.hashCode());
2831 private WsParamSetManager wsparamManager = null;
2833 public static ParamManager getUserParameterStore()
2835 Desktop d = Desktop.getInstance();
2836 if (d.wsparamManager == null)
2838 d.wsparamManager = new WsParamSetManager();
2840 return d.wsparamManager;
2844 * static hyperlink handler proxy method for use by Jalview's internal windows
2848 public static void hyperlinkUpdate(HyperlinkEvent e)
2850 if (e.getEventType() == EventType.ACTIVATED)
2855 url = e.getURL().toString();
2856 Desktop.showUrl(url);
2857 } catch (Exception x)
2861 if (Cache.log != null)
2863 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2868 "Couldn't handle string " + url + " as a URL.");
2871 // ignore any exceptions due to dud links.
2878 * single thread that handles display of dialogs to user.
2880 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2883 * flag indicating if dialogExecutor should try to acquire a permit
2885 private volatile boolean dialogPause = true;
2890 private java.util.concurrent.Semaphore block = new Semaphore(0);
2893 private groovy.ui.Console groovyConsole;
2895 public StructureViewer lastTargetedView;
2898 * add another dialog thread to the queue
2902 public void addDialogThread(final Runnable prompter)
2904 dialogExecutor.submit(new Runnable()
2914 } catch (InterruptedException x)
2918 // BH! Q: do we mean System.headless ? or "nogui/nodisplay" headless?
2919 if (Jalview.isHeadlessMode())
2925 SwingUtilities.invokeAndWait(prompter);
2926 } catch (Exception q)
2928 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2934 public void startDialogQueue()
2936 // set the flag so we don't pause waiting for another permit and semaphore
2937 // the current task to begin
2938 dialogPause = false;
2943 * Outputs an image of the desktop to file in EPS format, after prompting the
2944 * user for choice of Text or Lineart character rendering (unless a preference
2945 * has been set). The file name is generated as
2948 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2952 protected void snapShotWindow_actionPerformed(ActionEvent e)
2954 // currently the menu option to do this is not shown
2957 int width = getWidth();
2958 int height = getHeight();
2960 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2961 ImageWriterI writer = new ImageWriterI()
2964 public void exportImage(Graphics g) throws Exception
2967 Cache.log.info("Successfully written snapshot to file "
2968 + of.getAbsolutePath());
2971 String title = "View of desktop";
2972 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
2974 exporter.doExport(of, this, width, height, title);
2978 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2979 * This respects (remembers) any previous 'exploded geometry' i.e. the size and
2980 * location last time the view was expanded (if any). However it does not
2981 * remember the split pane divider location - this is set to match the
2982 * 'exploding' frame.
2986 public void explodeViews(SplitFrame sf)
2988 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2989 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2990 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
2992 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
2994 int viewCount = topPanels.size();
3001 * Processing in reverse order works, forwards order leaves the first panels
3002 * not visible. I don't know why!
3004 for (int i = viewCount - 1; i >= 0; i--)
3007 * Make new top and bottom frames. These take over the respective
3008 * AlignmentPanel objects, including their AlignmentViewports, so the
3009 * cdna/protein relationships between the viewports is carried over to the
3012 * explodedGeometry holds the (x, y) position of the previously exploded
3013 * SplitFrame, and the (width, height) of the AlignFrame component
3015 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3016 AlignFrame newTopFrame = new AlignFrame(topPanel);
3017 newTopFrame.setSize(oldTopFrame.getSize());
3018 newTopFrame.setVisible(true);
3019 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3020 .getExplodedGeometry();
3021 if (geometry != null)
3023 newTopFrame.setSize(geometry.getSize());
3026 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3027 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3028 newBottomFrame.setSize(oldBottomFrame.getSize());
3029 newBottomFrame.setVisible(true);
3030 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3031 .getExplodedGeometry();
3032 if (geometry != null)
3034 newBottomFrame.setSize(geometry.getSize());
3037 topPanel.av.setGatherViewsHere(false);
3038 bottomPanel.av.setGatherViewsHere(false);
3039 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3041 if (geometry != null)
3043 splitFrame.setLocation(geometry.getLocation());
3045 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3049 * Clear references to the panels (now relocated in the new SplitFrames)
3050 * before closing the old SplitFrame.
3053 bottomPanels.clear();
3058 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3059 * back into the given SplitFrame as additional views. Note that the gathered
3060 * frames may themselves have multiple views.
3064 public void gatherViews(GSplitFrame source)
3067 * special handling of explodedGeometry for a view within a SplitFrame: - it
3068 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3069 * height) of the AlignFrame component
3071 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3072 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3073 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3074 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3075 myBottomFrame.viewport
3076 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3077 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3078 myTopFrame.viewport.setGatherViewsHere(true);
3079 myBottomFrame.viewport.setGatherViewsHere(true);
3080 String topViewId = myTopFrame.viewport.getSequenceSetId();
3081 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3083 JInternalFrame[] frames = desktopPane.getAllFrames();
3084 for (JInternalFrame frame : frames)
3086 if (frame instanceof SplitFrame && frame != source)
3088 SplitFrame sf = (SplitFrame) frame;
3089 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3090 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3091 boolean gatherThis = false;
3092 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3094 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3095 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3096 if (topViewId.equals(topPanel.av.getSequenceSetId())
3097 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3100 topPanel.av.setGatherViewsHere(false);
3101 bottomPanel.av.setGatherViewsHere(false);
3102 topPanel.av.setExplodedGeometry(
3103 new Rectangle(sf.getLocation(), topFrame.getSize()));
3104 bottomPanel.av.setExplodedGeometry(
3105 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3106 myTopFrame.addAlignmentPanel(topPanel, false);
3107 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3113 topFrame.getAlignPanels().clear();
3114 bottomFrame.getAlignPanels().clear();
3121 * The dust settles...give focus to the tab we did this from.
3123 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3126 public static groovy.ui.Console getGroovyConsole()
3128 Desktop desktop = Desktop.getInstance();
3129 return desktop == null ? null : desktop.groovyConsole;
3133 * handles the payload of a drag and drop event.
3135 * TODO refactor to desktop utilities class
3138 * - Data source strings extracted from the drop event
3140 * - protocol for each data source extracted from the drop event
3144 * - the payload from the drop event
3147 @SuppressWarnings("unchecked")
3148 public static void transferFromDropTarget(List<Object> files,
3149 List<DataSourceType> protocols, DropTargetDropEvent evt,
3150 Transferable t) throws Exception
3153 // BH 2018 changed List<String> to List<Object> to allow for File from SwingJS
3155 // DataFlavor[] flavors = t.getTransferDataFlavors();
3156 // for (int i = 0; i < flavors.length; i++) {
3157 // if (flavors[i].isFlavorJavaFileListType()) {
3158 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3159 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3160 // for (int j = 0; j < list.size(); j++) {
3161 // File file = (File) list.get(j);
3162 // byte[] data = getDroppedFileBytes(file);
3163 // fileName.setText(file.getName() + " - " + data.length + " " +
3164 // evt.getLocation());
3165 // JTextArea target = (JTextArea) ((DropTarget) evt.getSource()).getComponent();
3166 // target.setText(new String(data));
3168 // dtde.dropComplete(true);
3173 DataFlavor uriListFlavor = new DataFlavor(
3174 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3177 urlFlavour = new DataFlavor(
3178 "application/x-java-url; class=java.net.URL");
3179 } catch (ClassNotFoundException cfe)
3181 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3184 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3189 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3190 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3191 // means url may be null.
3194 protocols.add(DataSourceType.URL);
3195 files.add(url.toString());
3196 Cache.log.debug("Drop handled as URL dataflavor "
3197 + files.get(files.size() - 1));
3202 if (Platform.isAMacAndNotJS())
3205 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3208 } catch (Throwable ex)
3210 Cache.log.debug("URL drop handler failed.", ex);
3213 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3215 // Works on Windows and MacOSX
3216 Cache.log.debug("Drop handled as javaFileListFlavor");
3217 for (Object file : (List<Object>) t
3218 .getTransferData(DataFlavor.javaFileListFlavor))
3221 protocols.add(DataSourceType.FILE);
3226 // Unix like behaviour
3227 boolean added = false;
3229 if (t.isDataFlavorSupported(uriListFlavor))
3231 Cache.log.debug("Drop handled as uriListFlavor");
3232 // This is used by Unix drag system
3233 data = (String) t.getTransferData(uriListFlavor);
3237 // fallback to text: workaround - on OSX where there's a JVM bug
3238 Cache.log.debug("standard URIListFlavor failed. Trying text");
3239 // try text fallback
3240 DataFlavor textDf = new DataFlavor(
3241 "text/plain;class=java.lang.String");
3242 if (t.isDataFlavorSupported(textDf))
3244 data = (String) t.getTransferData(textDf);
3247 Cache.log.debug("Plain text drop content returned "
3248 + (data == null ? "Null - failed" : data));
3253 while (protocols.size() < files.size())
3255 Cache.log.debug("Adding missing FILE protocol for "
3256 + files.get(protocols.size()));
3257 protocols.add(DataSourceType.FILE);
3259 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3260 data, "\r\n"); st.hasMoreTokens();)
3263 String s = st.nextToken();
3264 if (s.startsWith("#"))
3266 // the line is a comment (as per the RFC 2483)
3269 java.net.URI uri = new java.net.URI(s);
3270 if (uri.getScheme().toLowerCase().startsWith("http"))
3272 protocols.add(DataSourceType.URL);
3273 files.add(uri.toString());
3277 // otherwise preserve old behaviour: catch all for file objects
3278 java.io.File file = new java.io.File(uri);
3279 protocols.add(DataSourceType.FILE);
3280 files.add(file.toString());
3285 if (Cache.log.isDebugEnabled())
3287 if (data == null || !added)
3290 if (t.getTransferDataFlavors() != null
3291 && t.getTransferDataFlavors().length > 0)
3294 "Couldn't resolve drop data. Here are the supported flavors:");
3295 for (DataFlavor fl : t.getTransferDataFlavors())
3298 "Supported transfer dataflavor: " + fl.toString());
3299 Object df = t.getTransferData(fl);
3302 Cache.log.debug("Retrieves: " + df);
3306 Cache.log.debug("Retrieved nothing");
3312 Cache.log.debug("Couldn't resolve dataflavor for drop: "
3318 if (Platform.isWindowsAndNotJS())
3320 Cache.log.debug("Scanning dropped content for Windows Link Files");
3322 // resolve any .lnk files in the file drop
3323 for (int f = 0; f < files.size(); f++)
3325 String source = files.get(f).toString().toLowerCase();
3326 if (protocols.get(f).equals(DataSourceType.FILE)
3327 && (source.endsWith(".lnk") || source.endsWith(".url")
3328 || source.endsWith(".site")))
3332 Object obj = files.get(f);
3333 File lf = (obj instanceof File ? (File) obj
3334 : new File((String) obj));
3335 // process link file to get a URL
3336 Cache.log.debug("Found potential link file: " + lf);
3337 WindowsShortcut wscfile = new WindowsShortcut(lf);
3338 String fullname = wscfile.getRealFilename();
3339 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3340 files.set(f, fullname);
3341 Cache.log.debug("Parsed real filename " + fullname
3342 + " to extract protocol: " + protocols.get(f));
3343 } catch (Exception ex)
3346 "Couldn't parse " + files.get(f) + " as a link file.",
3355 * Sets the Preferences property for experimental features to True or False
3356 * depending on the state of the controlling menu item
3359 protected void showExperimental_actionPerformed(boolean selected)
3361 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3365 * Answers a (possibly empty) list of any structure viewer frames (currently for
3366 * either Jmol or Chimera) which are currently open. This may optionally be
3367 * restricted to viewers of a specified class, or viewers linked to a specified
3371 * if not null, only return viewers linked to this panel
3372 * @param structureViewerClass
3373 * if not null, only return viewers of this class
3376 public List<StructureViewerBase> getStructureViewers(
3377 AlignmentPanel apanel,
3378 Class<? extends StructureViewerBase> structureViewerClass)
3380 List<StructureViewerBase> result = new ArrayList<>();
3381 JInternalFrame[] frames = getAllFrames();
3383 for (JInternalFrame frame : frames)
3385 if (frame instanceof StructureViewerBase)
3387 if (structureViewerClass == null
3388 || structureViewerClass.isInstance(frame))
3391 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3393 result.add((StructureViewerBase) frame);
3403 public MyDesktopPane desktopPane;
3406 * Get the instance of the JDesktopPane from the application-local Desktop
3409 * The key here is that the Java application can have multiple static
3410 * instances of the desktop JFrame because those instances are sandboxed, but
3411 * the SwingJS JFrames will be in the same VM-like space. So we need
3412 * application singletons, at least for JavaScript.
3416 public static MyDesktopPane getDesktopPane()
3418 Desktop desktop = Desktop.getInstance();
3419 return desktop == null ? null : desktop.desktopPane;
3423 * Answers an 'application scope' singleton instance of this class. Separate
3424 * SwingJS 'applets' running in the same browser page will each have a
3425 * distinct instance of Desktop.
3429 public static Desktop getInstance()
3431 return Jalview.isHeadlessMode() ? null
3432 : (Desktop) ApplicationSingletonProvider
3433 .getInstance(Desktop.class);