2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
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 org.stackoverflowusers.file.WindowsShortcut;
97 import jalview.api.AlignViewportI;
98 import jalview.api.AlignmentViewPanel;
99 import jalview.bin.Cache;
100 import jalview.bin.Jalview;
101 import jalview.gui.ImageExporter.ImageWriterI;
102 import jalview.io.BackupFiles;
103 import jalview.io.DataSourceType;
104 import jalview.io.FileFormat;
105 import jalview.io.FileFormatException;
106 import jalview.io.FileFormatI;
107 import jalview.io.FileFormats;
108 import jalview.io.FileLoader;
109 import jalview.io.FormatAdapter;
110 import jalview.io.IdentifyFile;
111 import jalview.io.JalviewFileChooser;
112 import jalview.io.JalviewFileView;
113 import jalview.jbgui.GSplitFrame;
114 import jalview.jbgui.GStructureViewer;
115 import jalview.project.Jalview2XML;
116 import jalview.structure.StructureSelectionManager;
117 import jalview.urls.IdOrgSettings;
118 import jalview.util.BrowserLauncher;
119 import jalview.util.ImageMaker.TYPE;
120 import jalview.util.MessageManager;
121 import jalview.util.Platform;
122 import jalview.util.ShortcutKeyMaskExWrapper;
123 import jalview.util.UrlConstants;
124 import jalview.viewmodel.AlignmentViewport;
125 import jalview.ws.params.ParamManager;
126 import jalview.ws.utils.UrlDownloadClient;
133 * @version $Revision: 1.155 $
135 public class Desktop extends jalview.jbgui.GDesktop
136 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
137 jalview.api.StructureSelectionManagerProvider
139 private static final String CITATION = "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
140 + "<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"
141 + "<br><br>If you use Jalview, please cite:"
142 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
143 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
144 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033";
146 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
148 private static int DEFAULT_MIN_WIDTH = 300;
150 private static int DEFAULT_MIN_HEIGHT = 250;
152 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
154 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
156 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
158 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
160 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
162 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
164 public static boolean nosplash = false;
167 * news reader - null if it was never started.
169 private BlogReader jvnews = null;
171 private File projectFile;
175 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
177 public void addJalviewPropertyChangeListener(
178 PropertyChangeListener listener)
180 changeSupport.addJalviewPropertyChangeListener(listener);
184 * @param propertyName
186 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
187 * java.beans.PropertyChangeListener)
189 public void addJalviewPropertyChangeListener(String propertyName,
190 PropertyChangeListener listener)
192 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
196 * @param propertyName
198 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
199 * java.beans.PropertyChangeListener)
201 public void removeJalviewPropertyChangeListener(String propertyName,
202 PropertyChangeListener listener)
204 changeSupport.removeJalviewPropertyChangeListener(propertyName,
208 /** Singleton Desktop instance */
209 public static Desktop instance;
211 public static MyDesktopPane desktop;
213 public static MyDesktopPane getDesktop()
215 // BH 2018 could use currentThread() here as a reference to a
216 // Hashtable<Thread, MyDesktopPane> in JavaScript
220 static int openFrameCount = 0;
222 static final int xOffset = 30;
224 static final int yOffset = 30;
226 public static jalview.ws.jws1.Discoverer discoverer;
228 public static Object[] jalviewClipboard;
230 public static boolean internalCopy = false;
232 static int fileLoadingCount = 0;
234 class MyDesktopManager implements DesktopManager
237 private DesktopManager delegate;
239 public MyDesktopManager(DesktopManager delegate)
241 this.delegate = delegate;
245 public void activateFrame(JInternalFrame f)
249 delegate.activateFrame(f);
250 } catch (NullPointerException npe)
252 Point p = getMousePosition();
253 instance.showPasteMenu(p.x, p.y);
258 public void beginDraggingFrame(JComponent f)
260 delegate.beginDraggingFrame(f);
264 public void beginResizingFrame(JComponent f, int direction)
266 delegate.beginResizingFrame(f, direction);
270 public void closeFrame(JInternalFrame f)
272 delegate.closeFrame(f);
276 public void deactivateFrame(JInternalFrame f)
278 delegate.deactivateFrame(f);
282 public void deiconifyFrame(JInternalFrame f)
284 delegate.deiconifyFrame(f);
288 public void dragFrame(JComponent f, int newX, int newY)
294 delegate.dragFrame(f, newX, newY);
298 public void endDraggingFrame(JComponent f)
300 delegate.endDraggingFrame(f);
305 public void endResizingFrame(JComponent f)
307 delegate.endResizingFrame(f);
312 public void iconifyFrame(JInternalFrame f)
314 delegate.iconifyFrame(f);
318 public void maximizeFrame(JInternalFrame f)
320 delegate.maximizeFrame(f);
324 public void minimizeFrame(JInternalFrame f)
326 delegate.minimizeFrame(f);
330 public void openFrame(JInternalFrame f)
332 delegate.openFrame(f);
336 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
343 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
347 public void setBoundsForFrame(JComponent f, int newX, int newY,
348 int newWidth, int newHeight)
350 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
353 // All other methods, simply delegate
358 * Creates a new Desktop object.
364 * A note to implementors. It is ESSENTIAL that any activities that might
365 * block are spawned off as threads rather than waited for during this
370 doConfigureStructurePrefs();
371 setTitle("Jalview " + Cache.getProperty("VERSION"));
373 if (!Platform.isAMac())
375 // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
379 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
385 APQHandlers.setAPQHandlers(this);
386 } catch (Throwable t)
388 System.out.println("Error setting APQHandlers: " + t.toString());
389 // t.printStackTrace();
392 addWindowListener(new WindowAdapter()
396 public void windowClosing(WindowEvent ev)
402 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
404 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
405 desktop = new MyDesktopPane(selmemusage);
407 showMemusage.setSelected(selmemusage);
408 desktop.setBackground(Color.white);
410 getContentPane().setLayout(new BorderLayout());
411 // alternate config - have scrollbars - see notes in JAL-153
412 // JScrollPane sp = new JScrollPane();
413 // sp.getViewport().setView(desktop);
414 // getContentPane().add(sp, BorderLayout.CENTER);
416 // BH 2018 - just an experiment to try unclipped JInternalFrames.
419 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
422 getContentPane().add(desktop, BorderLayout.CENTER);
423 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
425 // This line prevents Windows Look&Feel resizing all new windows to maximum
426 // if previous window was maximised
427 desktop.setDesktopManager(new MyDesktopManager(
428 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
429 : Platform.isAMacAndNotJS()
430 ? new AquaInternalFrameManager(
431 desktop.getDesktopManager())
432 : desktop.getDesktopManager())));
434 Rectangle dims = getLastKnownDimensions("");
441 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
442 int xPos = Math.max(5, (screenSize.width - 900) / 2);
443 int yPos = Math.max(5, (screenSize.height - 650) / 2);
444 setBounds(xPos, yPos, 900, 650);
447 if (!Platform.isJS())
454 jconsole = new Console(this, showjconsole);
455 jconsole.setHeader(Cache.getVersionDetailsForConsole());
456 showConsole(showjconsole);
458 showNews.setVisible(false);
460 experimentalFeatures.setSelected(showExperimental());
462 getIdentifiersOrgData();
466 // Spawn a thread that shows the splashscreen
469 SwingUtilities.invokeLater(new Runnable()
474 new SplashScreen(true);
479 // Thread off a new instance of the file chooser - this reduces the time
481 // takes to open it later on.
482 new Thread(new Runnable()
487 Cache.log.debug("Filechooser init thread started.");
488 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
489 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
491 Cache.log.debug("Filechooser init thread finished.");
494 // Add the service change listener
495 changeSupport.addJalviewPropertyChangeListener("services",
496 new PropertyChangeListener()
500 public void propertyChange(PropertyChangeEvent evt)
502 Cache.log.debug("Firing service changed event for "
503 + evt.getNewValue());
504 JalviewServicesChanged(evt);
509 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
511 this.addWindowListener(new WindowAdapter()
514 public void windowClosing(WindowEvent evt)
521 this.addMouseListener(ma = new MouseAdapter()
524 public void mousePressed(MouseEvent evt)
526 if (evt.isPopupTrigger()) // Mac
528 showPasteMenu(evt.getX(), evt.getY());
533 public void mouseReleased(MouseEvent evt)
535 if (evt.isPopupTrigger()) // Windows
537 showPasteMenu(evt.getX(), evt.getY());
541 desktop.addMouseListener(ma);
545 * Answers true if user preferences to enable experimental features is True
550 public boolean showExperimental()
552 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
553 Boolean.FALSE.toString());
554 return Boolean.valueOf(experimental).booleanValue();
557 public void doConfigureStructurePrefs()
559 // configure services
560 StructureSelectionManager ssm = StructureSelectionManager
561 .getStructureSelectionManager(this);
562 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
564 ssm.setAddTempFacAnnot(
565 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
566 ssm.setProcessSecondaryStructure(
567 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
568 ssm.setSecStructServices(
569 Cache.getDefault(Preferences.USE_RNAVIEW, true));
573 ssm.setAddTempFacAnnot(false);
574 ssm.setProcessSecondaryStructure(false);
575 ssm.setSecStructServices(false);
579 public void checkForNews()
581 final Desktop me = this;
582 // Thread off the news reader, in case there are connection problems.
583 new Thread(new Runnable()
588 Cache.log.debug("Starting news thread.");
589 jvnews = new BlogReader(me);
590 showNews.setVisible(true);
591 Cache.log.debug("Completed news thread.");
596 public void getIdentifiersOrgData()
598 // Thread off the identifiers fetcher
599 new Thread(new Runnable()
604 Cache.log.debug("Downloading data from identifiers.org");
607 UrlDownloadClient.download(IdOrgSettings.getUrl(),
608 IdOrgSettings.getDownloadLocation());
609 } catch (IOException e)
611 Cache.log.debug("Exception downloading identifiers.org data"
620 protected void showNews_actionPerformed(ActionEvent e)
622 showNews(showNews.isSelected());
625 void showNews(boolean visible)
627 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
628 showNews.setSelected(visible);
629 if (visible && !jvnews.isVisible())
631 new Thread(new Runnable()
636 long now = System.currentTimeMillis();
637 Desktop.instance.setProgressBar(
638 MessageManager.getString("status.refreshing_news"), now);
639 jvnews.refreshNews();
640 Desktop.instance.setProgressBar(null, now);
648 * recover the last known dimensions for a jalview window
651 * - empty string is desktop, all other windows have unique prefix
652 * @return null or last known dimensions scaled to current geometry (if last
653 * window geom was known)
655 Rectangle getLastKnownDimensions(String windowName)
657 // TODO: lock aspect ratio for scaling desktop Bug #0058199
658 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
659 String x = Cache.getProperty(windowName + "SCREEN_X");
660 String y = Cache.getProperty(windowName + "SCREEN_Y");
661 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
662 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
663 if ((x != null) && (y != null) && (width != null) && (height != null))
665 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
666 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
667 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
669 // attempt #1 - try to cope with change in screen geometry - this
670 // version doesn't preserve original jv aspect ratio.
671 // take ratio of current screen size vs original screen size.
672 double sw = ((1f * screenSize.width) / (1f * Integer
673 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
674 double sh = ((1f * screenSize.height) / (1f * Integer
675 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
676 // rescale the bounds depending upon the current screen geometry.
677 ix = (int) (ix * sw);
678 iw = (int) (iw * sw);
679 iy = (int) (iy * sh);
680 ih = (int) (ih * sh);
681 while (ix >= screenSize.width)
684 "Window geometry location recall error: shifting horizontal to within screenbounds.");
685 ix -= screenSize.width;
687 while (iy >= screenSize.height)
690 "Window geometry location recall error: shifting vertical to within screenbounds.");
691 iy -= screenSize.height;
694 "Got last known dimensions for " + windowName + ": x:" + ix
695 + " y:" + iy + " width:" + iw + " height:" + ih);
697 // return dimensions for new instance
698 return new Rectangle(ix, iy, iw, ih);
703 void showPasteMenu(int x, int y)
705 JPopupMenu popup = new JPopupMenu();
706 JMenuItem item = new JMenuItem(
707 MessageManager.getString("label.paste_new_window"));
708 item.addActionListener(new ActionListener()
711 public void actionPerformed(ActionEvent evt)
718 popup.show(this, x, y);
725 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
726 Transferable contents = c.getContents(this);
728 if (contents != null)
730 String file = (String) contents
731 .getTransferData(DataFlavor.stringFlavor);
733 FileFormatI format = new IdentifyFile().identify(file,
734 DataSourceType.PASTE);
736 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
739 } catch (Exception ex)
742 "Unable to paste alignment from system clipboard:\n" + ex);
747 * Adds and opens the given frame to the desktop
758 public static synchronized void addInternalFrame(
759 final JInternalFrame frame, String title, int w, int h)
761 addInternalFrame(frame, title, true, w, h, true, false);
765 * Add an internal frame to the Jalview desktop
772 * When true, display frame immediately, otherwise, caller must call
773 * setVisible themselves.
779 public static synchronized void addInternalFrame(
780 final JInternalFrame frame, String title, boolean makeVisible,
783 addInternalFrame(frame, title, makeVisible, w, h, true, false);
787 * Add an internal frame to the Jalview desktop and make it visible
800 public static synchronized void addInternalFrame(
801 final JInternalFrame frame, String title, int w, int h,
804 addInternalFrame(frame, title, true, w, h, resizable, false);
808 * Add an internal frame to the Jalview desktop
815 * When true, display frame immediately, otherwise, caller must call
816 * setVisible themselves.
823 * @param ignoreMinSize
824 * Do not set the default minimum size for frame
826 public static synchronized void addInternalFrame(
827 final JInternalFrame frame, String title, boolean makeVisible,
828 int w, int h, boolean resizable, boolean ignoreMinSize)
831 // TODO: allow callers to determine X and Y position of frame (eg. via
833 // TODO: consider fixing method to update entries in the window submenu with
834 // the current window title
836 frame.setTitle(title);
837 if (frame.getWidth() < 1 || frame.getHeight() < 1)
841 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
842 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
843 // IF JALVIEW IS RUNNING HEADLESS
844 // ///////////////////////////////////////////////
845 if (instance == null || (System.getProperty("java.awt.headless") != null
846 && System.getProperty("java.awt.headless").equals("true")))
855 frame.setMinimumSize(
856 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
858 // Set default dimension for Alignment Frame window.
859 // The Alignment Frame window could be added from a number of places,
861 // I did this here in order not to miss out on any Alignment frame.
862 if (frame instanceof AlignFrame)
864 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
865 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
869 frame.setVisible(makeVisible);
870 frame.setClosable(true);
871 frame.setResizable(resizable);
872 frame.setMaximizable(resizable);
873 frame.setIconifiable(resizable);
874 frame.setOpaque(Platform.isJS());
876 if (frame.getX() < 1 && frame.getY() < 1)
878 frame.setLocation(xOffset * openFrameCount,
879 yOffset * ((openFrameCount - 1) % 10) + yOffset);
883 * add an entry for the new frame in the Window menu
884 * (and remove it when the frame is closed)
886 final JMenuItem menuItem = new JMenuItem(title);
887 frame.addInternalFrameListener(new InternalFrameAdapter()
890 public void internalFrameActivated(InternalFrameEvent evt)
892 JInternalFrame itf = desktop.getSelectedFrame();
895 if (itf instanceof AlignFrame)
897 Jalview.setCurrentAlignFrame((AlignFrame) itf);
904 public void internalFrameClosed(InternalFrameEvent evt)
906 PaintRefresher.RemoveComponent(frame);
909 * defensive check to prevent frames being
910 * added half off the window
912 if (openFrameCount > 0)
918 * ensure no reference to alignFrame retained by menu item listener
920 if (menuItem.getActionListeners().length > 0)
922 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
924 windowMenu.remove(menuItem);
928 menuItem.addActionListener(new ActionListener()
931 public void actionPerformed(ActionEvent e)
935 frame.setSelected(true);
936 frame.setIcon(false);
937 } catch (java.beans.PropertyVetoException ex)
939 // System.err.println(ex.toString());
944 setKeyBindings(frame);
948 windowMenu.add(menuItem);
953 frame.setSelected(true);
954 frame.requestFocus();
955 } catch (java.beans.PropertyVetoException ve)
957 } catch (java.lang.ClassCastException cex)
960 "Squashed a possible GUI implementation error. If you can recreate this, please look at http://issues.jalview.org/browse/JAL-869",
966 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
971 private static void setKeyBindings(JInternalFrame frame)
973 @SuppressWarnings("serial")
974 final Action closeAction = new AbstractAction()
977 public void actionPerformed(ActionEvent e)
984 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
986 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
987 InputEvent.CTRL_DOWN_MASK);
988 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
989 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
991 InputMap inputMap = frame
992 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
993 String ctrlW = ctrlWKey.toString();
994 inputMap.put(ctrlWKey, ctrlW);
995 inputMap.put(cmdWKey, ctrlW);
997 ActionMap actionMap = frame.getActionMap();
998 actionMap.put(ctrlW, closeAction);
1002 public void lostOwnership(Clipboard clipboard, Transferable contents)
1006 Desktop.jalviewClipboard = null;
1009 internalCopy = false;
1013 public void dragEnter(DropTargetDragEvent evt)
1018 public void dragExit(DropTargetEvent evt)
1023 public void dragOver(DropTargetDragEvent evt)
1028 public void dropActionChanged(DropTargetDragEvent evt)
1039 public void drop(DropTargetDropEvent evt)
1041 boolean success = true;
1042 // JAL-1552 - acceptDrop required before getTransferable call for
1043 // Java's Transferable for native dnd
1044 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1045 Transferable t = evt.getTransferable();
1046 List<Object> files = new ArrayList<>();
1047 List<DataSourceType> protocols = new ArrayList<>();
1051 Desktop.transferFromDropTarget(files, protocols, evt, t);
1052 } catch (Exception e)
1054 e.printStackTrace();
1062 for (int i = 0; i < files.size(); i++)
1064 // BH 2018 File or String
1065 Object file = files.get(i);
1066 String fileName = file.toString();
1067 DataSourceType protocol = (protocols == null)
1068 ? DataSourceType.FILE
1070 FileFormatI format = null;
1072 if (fileName.endsWith(".jar"))
1074 format = FileFormat.Jalview;
1079 format = new IdentifyFile().identify(file, protocol);
1081 if (file instanceof File)
1083 Platform.cacheFileData((File) file);
1085 new FileLoader().LoadFile(null, file, protocol, format);
1088 } catch (Exception ex)
1093 evt.dropComplete(success); // need this to ensure input focus is properly
1094 // transfered to any new windows created
1104 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1106 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1107 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1108 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1109 BackupFiles.getEnabled());
1111 chooser.setFileView(new JalviewFileView());
1112 chooser.setDialogTitle(
1113 MessageManager.getString("label.open_local_file"));
1114 chooser.setToolTipText(MessageManager.getString("action.open"));
1116 chooser.setResponseHandler(0, new Runnable()
1121 File selectedFile = chooser.getSelectedFile();
1122 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1124 FileFormatI format = chooser.getSelectedFormat();
1127 * Call IdentifyFile to verify the file contains what its extension implies.
1128 * Skip this step for dynamically added file formats, because
1129 * IdentifyFile does not know how to recognise them.
1131 if (FileFormats.getInstance().isIdentifiable(format))
1135 format = new IdentifyFile().identify(selectedFile,
1136 DataSourceType.FILE);
1137 } catch (FileFormatException e)
1139 // format = null; //??
1143 new FileLoader().LoadFile(viewport, selectedFile,
1144 DataSourceType.FILE, format);
1147 chooser.showOpenDialog(this);
1151 * Shows a dialog for input of a URL at which to retrieve alignment data
1156 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1158 // This construct allows us to have a wider textfield
1160 JLabel label = new JLabel(
1161 MessageManager.getString("label.input_file_url"));
1163 JPanel panel = new JPanel(new GridLayout(2, 1));
1167 * the URL to fetch is
1168 * Java: an editable combobox with history
1169 * JS: (pending JAL-3038) a plain text field
1172 String urlBase = "http://www.";
1173 if (Platform.isJS())
1175 history = new JTextField(urlBase, 35);
1184 JComboBox<String> asCombo = new JComboBox<>();
1185 asCombo.setPreferredSize(new Dimension(400, 20));
1186 asCombo.setEditable(true);
1187 asCombo.addItem(urlBase);
1188 String historyItems = Cache.getProperty("RECENT_URL");
1189 if (historyItems != null)
1191 for (String token : historyItems.split("\\t"))
1193 asCombo.addItem(token);
1200 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1201 MessageManager.getString("action.cancel") };
1202 Runnable action = new Runnable()
1207 @SuppressWarnings("unchecked")
1208 String url = (history instanceof JTextField
1209 ? ((JTextField) history).getText()
1210 : ((JComboBox<String>) history).getSelectedItem()
1213 if (url.toLowerCase().endsWith(".jar"))
1215 if (viewport != null)
1217 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1218 FileFormat.Jalview);
1222 new FileLoader().LoadFile(url, DataSourceType.URL,
1223 FileFormat.Jalview);
1228 FileFormatI format = null;
1231 format = new IdentifyFile().identify(url, DataSourceType.URL);
1232 } catch (FileFormatException e)
1234 // TODO revise error handling, distinguish between
1235 // URL not found and response not valid
1240 String msg = MessageManager
1241 .formatMessage("label.couldnt_locate", url);
1242 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1243 MessageManager.getString("label.url_not_found"),
1244 JvOptionPane.WARNING_MESSAGE);
1249 if (viewport != null)
1251 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1256 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1261 String dialogOption = MessageManager
1262 .getString("label.input_alignment_from_url");
1263 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1264 .showInternalDialog(panel, dialogOption,
1265 JvOptionPane.YES_NO_CANCEL_OPTION,
1266 JvOptionPane.PLAIN_MESSAGE, null, options,
1267 MessageManager.getString("action.ok"));
1271 * Opens the CutAndPaste window for the user to paste an alignment in to
1274 * - if not null, the pasted alignment is added to the current
1275 * alignment; if null, to a new alignment window
1278 public void inputTextboxMenuItem_actionPerformed(
1279 AlignmentViewPanel viewPanel)
1281 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1282 cap.setForInput(viewPanel);
1283 Desktop.addInternalFrame(cap,
1284 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1294 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1295 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1296 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1297 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1298 getWidth(), getHeight()));
1300 if (jconsole != null)
1302 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1303 jconsole.stopConsole();
1307 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1310 if (dialogExecutor != null)
1312 dialogExecutor.shutdownNow();
1314 closeAll_actionPerformed(null);
1316 if (groovyConsole != null)
1318 // suppress a possible repeat prompt to save script
1319 groovyConsole.setDirty(false);
1320 groovyConsole.exit();
1325 private void storeLastKnownDimensions(String string, Rectangle jc)
1327 Cache.log.debug("Storing last known dimensions for " + string + ": x:"
1328 + jc.x + " y:" + jc.y + " width:" + jc.width + " height:"
1331 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1332 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1333 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1334 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1344 public void aboutMenuItem_actionPerformed(ActionEvent e)
1346 new Thread(new Runnable()
1351 new SplashScreen(false);
1357 * Returns the html text for the About screen, including any available version
1358 * number, build details, author details and citation reference, but without
1359 * the enclosing {@code html} tags
1363 public String getAboutMessage()
1365 StringBuilder message = new StringBuilder(1024);
1366 message.append("<h1><strong>Version: ")
1367 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1368 .append("<strong>Built: <em>")
1369 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1370 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1371 .append("</strong>");
1373 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1374 if (latestVersion.equals("Checking"))
1376 // JBP removed this message for 2.11: May be reinstated in future version
1377 // message.append("<br>...Checking latest version...</br>");
1379 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1381 boolean red = false;
1382 if (Cache.getProperty("VERSION").toLowerCase()
1383 .indexOf("automated build") == -1)
1386 // Displayed when code version and jnlp version do not match and code
1387 // version is not a development build
1388 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1391 message.append("<br>!! Version ")
1392 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1393 .append(" is available for download from ")
1394 .append(Cache.getDefault("www.jalview.org",
1395 "http://www.jalview.org"))
1399 message.append("</div>");
1402 message.append("<br>Authors: ");
1403 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1404 message.append(CITATION);
1406 return message.toString();
1410 * Action on requesting Help documentation
1413 public void documentationMenuItem_actionPerformed()
1417 if (Platform.isJS())
1419 BrowserLauncher.openURL("http://www.jalview.org/help.html");
1428 Help.showHelpWindow();
1430 } catch (Exception ex)
1432 System.err.println("Error opening help: " + ex.getMessage());
1437 public void closeAll_actionPerformed(ActionEvent e)
1439 // TODO show a progress bar while closing?
1440 JInternalFrame[] frames = desktop.getAllFrames();
1441 for (int i = 0; i < frames.length; i++)
1445 frames[i].setClosed(true);
1446 } catch (java.beans.PropertyVetoException ex)
1450 Jalview.setCurrentAlignFrame(null);
1451 System.out.println("ALL CLOSED");
1454 * reset state of singleton objects as appropriate (clear down session state
1455 * when all windows are closed)
1457 StructureSelectionManager ssm = StructureSelectionManager
1458 .getStructureSelectionManager(this);
1466 public void raiseRelated_actionPerformed(ActionEvent e)
1468 reorderAssociatedWindows(false, false);
1472 public void minimizeAssociated_actionPerformed(ActionEvent e)
1474 reorderAssociatedWindows(true, false);
1477 void closeAssociatedWindows()
1479 reorderAssociatedWindows(false, true);
1485 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1489 protected void garbageCollect_actionPerformed(ActionEvent e)
1491 // We simply collect the garbage
1492 Cache.log.debug("Collecting garbage...");
1494 Cache.log.debug("Finished garbage collection.");
1501 * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1505 protected void showMemusage_actionPerformed(ActionEvent e)
1507 desktop.showMemoryUsage(showMemusage.isSelected());
1514 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1518 protected void showConsole_actionPerformed(ActionEvent e)
1520 showConsole(showConsole.isSelected());
1523 Console jconsole = null;
1526 * control whether the java console is visible or not
1530 void showConsole(boolean selected)
1532 // TODO: decide if we should update properties file
1533 if (jconsole != null) // BH 2018
1535 showConsole.setSelected(selected);
1536 Cache.setProperty("SHOW_JAVA_CONSOLE",
1537 Boolean.valueOf(selected).toString());
1538 jconsole.setVisible(selected);
1542 void reorderAssociatedWindows(boolean minimize, boolean close)
1544 JInternalFrame[] frames = desktop.getAllFrames();
1545 if (frames == null || frames.length < 1)
1550 AlignmentViewport source = null, target = null;
1551 if (frames[0] instanceof AlignFrame)
1553 source = ((AlignFrame) frames[0]).getCurrentView();
1555 else if (frames[0] instanceof TreePanel)
1557 source = ((TreePanel) frames[0]).getViewPort();
1559 else if (frames[0] instanceof PCAPanel)
1561 source = ((PCAPanel) frames[0]).av;
1563 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1565 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1570 for (int i = 0; i < frames.length; i++)
1573 if (frames[i] == null)
1577 if (frames[i] instanceof AlignFrame)
1579 target = ((AlignFrame) frames[i]).getCurrentView();
1581 else if (frames[i] instanceof TreePanel)
1583 target = ((TreePanel) frames[i]).getViewPort();
1585 else if (frames[i] instanceof PCAPanel)
1587 target = ((PCAPanel) frames[i]).av;
1589 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1591 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1594 if (source == target)
1600 frames[i].setClosed(true);
1604 frames[i].setIcon(minimize);
1607 frames[i].toFront();
1611 } catch (java.beans.PropertyVetoException ex)
1626 protected void preferences_actionPerformed(ActionEvent e)
1628 Preferences.openPreferences();
1632 * Prompts the user to choose a file and then saves the Jalview state as a
1633 * Jalview project file
1636 public void saveState_actionPerformed()
1638 saveState_actionPerformed(false);
1641 public void saveState_actionPerformed(boolean saveAs)
1643 java.io.File projectFile = getProjectFile();
1644 // autoSave indicates we already have a file and don't need to ask
1645 boolean autoSave = projectFile != null && !saveAs
1646 && BackupFiles.getEnabled();
1648 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1649 // saveAs="+saveAs+", Backups
1650 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1652 boolean approveSave = false;
1655 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1658 chooser.setFileView(new JalviewFileView());
1659 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1661 int value = chooser.showSaveDialog(this);
1663 if (value == JalviewFileChooser.APPROVE_OPTION)
1665 projectFile = chooser.getSelectedFile();
1666 setProjectFile(projectFile);
1671 if (approveSave || autoSave)
1673 final Desktop me = this;
1674 final java.io.File chosenFile = projectFile;
1675 new Thread(new Runnable()
1680 // TODO: refactor to Jalview desktop session controller action.
1681 setProgressBar(MessageManager.formatMessage(
1682 "label.saving_jalview_project", new Object[]
1683 { chosenFile.getName() }), chosenFile.hashCode());
1684 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1685 // TODO catch and handle errors for savestate
1686 // TODO prevent user from messing with the Desktop whilst we're saving
1689 boolean doBackup = BackupFiles.getEnabled();
1690 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1693 new Jalview2XML().saveState(
1694 doBackup ? backupfiles.getTempFile() : chosenFile);
1698 backupfiles.setWriteSuccess(true);
1699 backupfiles.rollBackupsAndRenameTempFile();
1701 } catch (OutOfMemoryError oom)
1703 new OOMWarning("Whilst saving current state to "
1704 + chosenFile.getName(), oom);
1705 } catch (Exception ex)
1707 Cache.log.error("Problems whilst trying to save to "
1708 + chosenFile.getName(), ex);
1709 JvOptionPane.showMessageDialog(me,
1710 MessageManager.formatMessage(
1711 "label.error_whilst_saving_current_state_to",
1713 { chosenFile.getName() }),
1714 MessageManager.getString("label.couldnt_save_project"),
1715 JvOptionPane.WARNING_MESSAGE);
1717 setProgressBar(null, chosenFile.hashCode());
1724 public void saveAsState_actionPerformed(ActionEvent e)
1726 saveState_actionPerformed(true);
1729 private void setProjectFile(File choice)
1731 this.projectFile = choice;
1734 public File getProjectFile()
1736 return this.projectFile;
1740 * Shows a file chooser dialog and tries to read in the selected file as a
1744 public void loadState_actionPerformed()
1746 final String[] suffix = new String[] { "jvp", "jar" };
1747 final String[] desc = new String[] { "Jalview Project",
1748 "Jalview Project (old)" };
1749 JalviewFileChooser chooser = new JalviewFileChooser(
1750 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1751 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1755 chooser.setFileView(new JalviewFileView());
1756 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1757 chooser.setResponseHandler(0, new Runnable()
1762 File selectedFile = chooser.getSelectedFile();
1763 setProjectFile(selectedFile);
1764 String choice = selectedFile.getAbsolutePath();
1765 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1766 new Thread(new Runnable()
1773 new Jalview2XML().loadJalviewAlign(selectedFile);
1774 } catch (OutOfMemoryError oom)
1776 new OOMWarning("Whilst loading project from " + choice, oom);
1777 } catch (Exception ex)
1780 "Problems whilst loading project from " + choice, ex);
1781 JvOptionPane.showMessageDialog(Desktop.desktop,
1782 MessageManager.formatMessage(
1783 "label.error_whilst_loading_project_from",
1787 .getString("label.couldnt_load_project"),
1788 JvOptionPane.WARNING_MESSAGE);
1791 }, "Project Loader").start();
1795 chooser.showOpenDialog(this);
1799 public void inputSequence_actionPerformed(ActionEvent e)
1801 new SequenceFetcher(this);
1804 JPanel progressPanel;
1806 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1808 public void startLoading(final Object fileName)
1810 if (fileLoadingCount == 0)
1812 fileLoadingPanels.add(addProgressPanel(MessageManager
1813 .formatMessage("label.loading_file", new Object[]
1819 private JPanel addProgressPanel(String string)
1821 if (progressPanel == null)
1823 progressPanel = new JPanel(new GridLayout(1, 1));
1824 totalProgressCount = 0;
1825 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1827 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1828 JProgressBar progressBar = new JProgressBar();
1829 progressBar.setIndeterminate(true);
1831 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1833 thisprogress.add(progressBar, BorderLayout.CENTER);
1834 progressPanel.add(thisprogress);
1835 ((GridLayout) progressPanel.getLayout()).setRows(
1836 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1837 ++totalProgressCount;
1838 instance.validate();
1839 return thisprogress;
1842 int totalProgressCount = 0;
1844 private void removeProgressPanel(JPanel progbar)
1846 if (progressPanel != null)
1848 synchronized (progressPanel)
1850 progressPanel.remove(progbar);
1851 GridLayout gl = (GridLayout) progressPanel.getLayout();
1852 gl.setRows(gl.getRows() - 1);
1853 if (--totalProgressCount < 1)
1855 this.getContentPane().remove(progressPanel);
1856 progressPanel = null;
1863 public void stopLoading()
1866 if (fileLoadingCount < 1)
1868 while (fileLoadingPanels.size() > 0)
1870 removeProgressPanel(fileLoadingPanels.remove(0));
1872 fileLoadingPanels.clear();
1873 fileLoadingCount = 0;
1878 public static int getViewCount(String alignmentId)
1880 AlignmentViewport[] aps = getViewports(alignmentId);
1881 return (aps == null) ? 0 : aps.length;
1886 * @param alignmentId
1887 * - if null, all sets are returned
1888 * @return all AlignmentPanels concerning the alignmentId sequence set
1890 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1892 if (Desktop.desktop == null)
1894 // no frames created and in headless mode
1895 // TODO: verify that frames are recoverable when in headless mode
1898 List<AlignmentPanel> aps = new ArrayList<>();
1899 AlignFrame[] frames = getAlignFrames();
1904 for (AlignFrame af : frames)
1906 for (AlignmentPanel ap : af.alignPanels)
1908 if (alignmentId == null
1909 || alignmentId.equals(ap.av.getSequenceSetId()))
1915 if (aps.size() == 0)
1919 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1924 * get all the viewports on an alignment.
1926 * @param sequenceSetId
1927 * unique alignment id (may be null - all viewports returned in that
1929 * @return all viewports on the alignment bound to sequenceSetId
1931 public static AlignmentViewport[] getViewports(String sequenceSetId)
1933 List<AlignmentViewport> viewp = new ArrayList<>();
1934 if (desktop != null)
1936 AlignFrame[] frames = Desktop.getAlignFrames();
1938 for (AlignFrame afr : frames)
1940 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
1941 .equals(sequenceSetId))
1943 if (afr.alignPanels != null)
1945 for (AlignmentPanel ap : afr.alignPanels)
1947 if (sequenceSetId == null
1948 || sequenceSetId.equals(ap.av.getSequenceSetId()))
1956 viewp.add(afr.getViewport());
1960 if (viewp.size() > 0)
1962 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1969 * Explode the views in the given frame into separate AlignFrame
1973 public static void explodeViews(AlignFrame af)
1975 int size = af.alignPanels.size();
1981 // FIXME: ideally should use UI interface API
1982 FeatureSettings viewFeatureSettings = (af.featureSettings != null
1983 && af.featureSettings.isOpen()) ? af.featureSettings : null;
1984 Rectangle fsBounds = af.getFeatureSettingsGeometry();
1985 for (int i = 0; i < size; i++)
1987 AlignmentPanel ap = af.alignPanels.get(i);
1989 AlignFrame newaf = new AlignFrame(ap);
1991 // transfer reference for existing feature settings to new alignFrame
1992 if (ap == af.alignPanel)
1994 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
1996 newaf.featureSettings = viewFeatureSettings;
1998 newaf.setFeatureSettingsGeometry(fsBounds);
2002 * Restore the view's last exploded frame geometry if known. Multiple
2003 * views from one exploded frame share and restore the same (frame)
2004 * position and size.
2006 Rectangle geometry = ap.av.getExplodedGeometry();
2007 if (geometry != null)
2009 newaf.setBounds(geometry);
2012 ap.av.setGatherViewsHere(false);
2014 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2015 AlignFrame.DEFAULT_HEIGHT);
2016 // and materialise a new feature settings dialog instance for the new
2018 // (closes the old as if 'OK' was pressed)
2019 if (ap == af.alignPanel && newaf.featureSettings != null
2020 && newaf.featureSettings.isOpen()
2021 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2023 newaf.showFeatureSettingsUI();
2027 af.featureSettings = null;
2028 af.alignPanels.clear();
2029 af.closeMenuItem_actionPerformed(true);
2034 * Gather expanded views (separate AlignFrame's) with the same sequence set
2035 * identifier back in to this frame as additional views, and close the
2036 * expanded views. Note the expanded frames may themselves have multiple
2037 * views. We take the lot.
2041 public void gatherViews(AlignFrame source)
2043 source.viewport.setGatherViewsHere(true);
2044 source.viewport.setExplodedGeometry(source.getBounds());
2045 JInternalFrame[] frames = desktop.getAllFrames();
2046 String viewId = source.viewport.getSequenceSetId();
2047 for (int t = 0; t < frames.length; t++)
2049 if (frames[t] instanceof AlignFrame && frames[t] != source)
2051 AlignFrame af = (AlignFrame) frames[t];
2052 boolean gatherThis = false;
2053 for (int a = 0; a < af.alignPanels.size(); a++)
2055 AlignmentPanel ap = af.alignPanels.get(a);
2056 if (viewId.equals(ap.av.getSequenceSetId()))
2059 ap.av.setGatherViewsHere(false);
2060 ap.av.setExplodedGeometry(af.getBounds());
2061 source.addAlignmentPanel(ap, false);
2067 if (af.featureSettings != null && af.featureSettings.isOpen())
2069 if (source.featureSettings == null)
2071 // preserve the feature settings geometry for this frame
2072 source.featureSettings = af.featureSettings;
2073 source.setFeatureSettingsGeometry(
2074 af.getFeatureSettingsGeometry());
2078 // close it and forget
2079 af.featureSettings.close();
2082 af.alignPanels.clear();
2083 af.closeMenuItem_actionPerformed(true);
2088 // refresh the feature setting UI for the source frame if it exists
2089 if (source.featureSettings != null && source.featureSettings.isOpen())
2091 source.showFeatureSettingsUI();
2095 public JInternalFrame[] getAllFrames()
2097 return desktop.getAllFrames();
2101 * Checks the given url to see if it gives a response indicating that the user
2102 * should be informed of a new questionnaire.
2106 public void checkForQuestionnaire(String url)
2108 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2109 // javax.swing.SwingUtilities.invokeLater(jvq);
2110 new Thread(jvq).start();
2113 public void checkURLLinks()
2115 // Thread off the URL link checker
2116 addDialogThread(new Runnable()
2121 if (Cache.getDefault("CHECKURLLINKS", true))
2123 // check what the actual links are - if it's just the default don't
2124 // bother with the warning
2125 List<String> links = Preferences.sequenceUrlLinks
2128 // only need to check links if there is one with a
2129 // SEQUENCE_ID which is not the default EMBL_EBI link
2130 ListIterator<String> li = links.listIterator();
2131 boolean check = false;
2132 List<JLabel> urls = new ArrayList<>();
2133 while (li.hasNext())
2135 String link = li.next();
2136 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2137 && !UrlConstants.isDefaultString(link))
2140 int barPos = link.indexOf("|");
2141 String urlMsg = barPos == -1 ? link
2142 : link.substring(0, barPos) + ": "
2143 + link.substring(barPos + 1);
2144 urls.add(new JLabel(urlMsg));
2152 // ask user to check in case URL links use old style tokens
2153 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2154 JPanel msgPanel = new JPanel();
2155 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2156 msgPanel.add(Box.createVerticalGlue());
2157 JLabel msg = new JLabel(MessageManager
2158 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2159 JLabel msg2 = new JLabel(MessageManager
2160 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2162 for (JLabel url : urls)
2168 final JCheckBox jcb = new JCheckBox(
2169 MessageManager.getString("label.do_not_display_again"));
2170 jcb.addActionListener(new ActionListener()
2173 public void actionPerformed(ActionEvent e)
2175 // update Cache settings for "don't show this again"
2176 boolean showWarningAgain = !jcb.isSelected();
2177 Cache.setProperty("CHECKURLLINKS",
2178 Boolean.valueOf(showWarningAgain).toString());
2183 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2185 .getString("label.SEQUENCE_ID_no_longer_used"),
2186 JvOptionPane.WARNING_MESSAGE);
2193 * Proxy class for JDesktopPane which optionally displays the current memory
2194 * usage and highlights the desktop area with a red bar if free memory runs
2199 public class MyDesktopPane extends JDesktopPane implements Runnable
2201 private static final float ONE_MB = 1048576f;
2203 boolean showMemoryUsage = false;
2207 java.text.NumberFormat df;
2209 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2212 public MyDesktopPane(boolean showMemoryUsage)
2214 showMemoryUsage(showMemoryUsage);
2217 public void showMemoryUsage(boolean showMemory)
2219 this.showMemoryUsage = showMemory;
2222 Thread worker = new Thread(this);
2228 public boolean isShowMemoryUsage()
2230 return showMemoryUsage;
2236 df = java.text.NumberFormat.getNumberInstance();
2237 df.setMaximumFractionDigits(2);
2238 runtime = Runtime.getRuntime();
2240 while (showMemoryUsage)
2244 maxMemory = runtime.maxMemory() / ONE_MB;
2245 allocatedMemory = runtime.totalMemory() / ONE_MB;
2246 freeMemory = runtime.freeMemory() / ONE_MB;
2247 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2249 percentUsage = (totalFreeMemory / maxMemory) * 100;
2251 // if (percentUsage < 20)
2253 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2255 // instance.set.setBorder(border1);
2258 // sleep after showing usage
2260 } catch (Exception ex)
2262 ex.printStackTrace();
2268 public void paintComponent(Graphics g)
2270 if (showMemoryUsage && g != null && df != null)
2272 if (percentUsage < 20)
2274 g.setColor(Color.red);
2276 FontMetrics fm = g.getFontMetrics();
2279 g.drawString(MessageManager.formatMessage("label.memory_stats",
2281 { df.format(totalFreeMemory), df.format(maxMemory),
2282 df.format(percentUsage) }),
2283 10, getHeight() - fm.getHeight());
2287 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2288 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2293 * Accessor method to quickly get all the AlignmentFrames loaded.
2295 * @return an array of AlignFrame, or null if none found
2297 public static AlignFrame[] getAlignFrames()
2299 if (Jalview.isHeadlessMode())
2301 // Desktop.desktop is null in headless mode
2302 return new AlignFrame[] { Jalview.currentAlignFrame };
2305 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2311 List<AlignFrame> avp = new ArrayList<>();
2313 for (int i = frames.length - 1; i > -1; i--)
2315 if (frames[i] instanceof AlignFrame)
2317 avp.add((AlignFrame) frames[i]);
2319 else if (frames[i] instanceof SplitFrame)
2322 * Also check for a split frame containing an AlignFrame
2324 GSplitFrame sf = (GSplitFrame) frames[i];
2325 if (sf.getTopFrame() instanceof AlignFrame)
2327 avp.add((AlignFrame) sf.getTopFrame());
2329 if (sf.getBottomFrame() instanceof AlignFrame)
2331 avp.add((AlignFrame) sf.getBottomFrame());
2335 if (avp.size() == 0)
2339 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2344 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2348 public GStructureViewer[] getJmols()
2350 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2356 List<GStructureViewer> avp = new ArrayList<>();
2358 for (int i = frames.length - 1; i > -1; i--)
2360 if (frames[i] instanceof AppJmol)
2362 GStructureViewer af = (GStructureViewer) frames[i];
2366 if (avp.size() == 0)
2370 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2375 * Add Groovy Support to Jalview
2378 public void groovyShell_actionPerformed()
2382 openGroovyConsole();
2383 } catch (Exception ex)
2385 Cache.log.error("Groovy Shell Creation failed.", ex);
2386 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2388 MessageManager.getString("label.couldnt_create_groovy_shell"),
2389 MessageManager.getString("label.groovy_support_failed"),
2390 JvOptionPane.ERROR_MESSAGE);
2395 * Open the Groovy console
2397 void openGroovyConsole()
2399 if (groovyConsole == null)
2401 groovyConsole = new groovy.ui.Console();
2402 groovyConsole.setVariable("Jalview", this);
2403 groovyConsole.run();
2406 * We allow only one console at a time, so that AlignFrame menu option
2407 * 'Calculate | Run Groovy script' is unambiguous.
2408 * Disable 'Groovy Console', and enable 'Run script', when the console is
2409 * opened, and the reverse when it is closed
2411 Window window = (Window) groovyConsole.getFrame();
2412 window.addWindowListener(new WindowAdapter()
2415 public void windowClosed(WindowEvent e)
2418 * rebind CMD-Q from Groovy Console to Jalview Quit
2421 enableExecuteGroovy(false);
2427 * show Groovy console window (after close and reopen)
2429 ((Window) groovyConsole.getFrame()).setVisible(true);
2432 * if we got this far, enable 'Run Groovy' in AlignFrame menus
2433 * and disable opening a second console
2435 enableExecuteGroovy(true);
2439 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2440 * binding when opened
2442 protected void addQuitHandler()
2445 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2447 .getKeyStroke(KeyEvent.VK_Q,
2448 jalview.util.ShortcutKeyMaskExWrapper
2449 .getMenuShortcutKeyMaskEx()),
2451 getRootPane().getActionMap().put("Quit", new AbstractAction()
2454 public void actionPerformed(ActionEvent e)
2462 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2465 * true if Groovy console is open
2467 public void enableExecuteGroovy(boolean enabled)
2470 * disable opening a second Groovy console
2471 * (or re-enable when the console is closed)
2473 groovyShell.setEnabled(!enabled);
2475 AlignFrame[] alignFrames = getAlignFrames();
2476 if (alignFrames != null)
2478 for (AlignFrame af : alignFrames)
2480 af.setGroovyEnabled(enabled);
2486 * Progress bars managed by the IProgressIndicator method.
2488 private Hashtable<Long, JPanel> progressBars;
2490 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2495 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2498 public void setProgressBar(String message, long id)
2500 // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
2502 if (progressBars == null)
2504 progressBars = new Hashtable<>();
2505 progressBarHandlers = new Hashtable<>();
2508 if (progressBars.get(Long.valueOf(id)) != null)
2510 JPanel panel = progressBars.remove(Long.valueOf(id));
2511 if (progressBarHandlers.contains(Long.valueOf(id)))
2513 progressBarHandlers.remove(Long.valueOf(id));
2515 removeProgressPanel(panel);
2519 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2526 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2527 * jalview.gui.IProgressIndicatorHandler)
2530 public void registerHandler(final long id,
2531 final IProgressIndicatorHandler handler)
2533 if (progressBarHandlers == null
2534 || !progressBars.containsKey(Long.valueOf(id)))
2536 throw new Error(MessageManager.getString(
2537 "error.call_setprogressbar_before_registering_handler"));
2539 progressBarHandlers.put(Long.valueOf(id), handler);
2540 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2541 if (handler.canCancel())
2543 JButton cancel = new JButton(
2544 MessageManager.getString("action.cancel"));
2545 final IProgressIndicator us = this;
2546 cancel.addActionListener(new ActionListener()
2550 public void actionPerformed(ActionEvent e)
2552 handler.cancelActivity(id);
2553 us.setProgressBar(MessageManager
2554 .formatMessage("label.cancelled_params", new Object[]
2555 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2559 progressPanel.add(cancel, BorderLayout.EAST);
2565 * @return true if any progress bars are still active
2568 public boolean operationInProgress()
2570 if (progressBars != null && progressBars.size() > 0)
2578 * This will return the first AlignFrame holding the given viewport instance.
2579 * It will break if there are more than one AlignFrames viewing a particular
2583 * @return alignFrame for viewport
2585 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2587 if (desktop != null)
2589 AlignmentPanel[] aps = getAlignmentPanels(
2590 viewport.getSequenceSetId());
2591 for (int panel = 0; aps != null && panel < aps.length; panel++)
2593 if (aps[panel] != null && aps[panel].av == viewport)
2595 return aps[panel].alignFrame;
2602 public VamsasApplication getVamsasApplication()
2604 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2610 * flag set if jalview GUI is being operated programmatically
2612 private boolean inBatchMode = false;
2615 * check if jalview GUI is being operated programmatically
2617 * @return inBatchMode
2619 public boolean isInBatchMode()
2625 * set flag if jalview GUI is being operated programmatically
2627 * @param inBatchMode
2629 public void setInBatchMode(boolean inBatchMode)
2631 this.inBatchMode = inBatchMode;
2634 public void startServiceDiscovery()
2636 startServiceDiscovery(false);
2639 public void startServiceDiscovery(boolean blocking)
2641 boolean alive = true;
2642 Thread t0 = null, t1 = null, t2 = null;
2643 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2646 // todo: changesupport handlers need to be transferred
2647 if (discoverer == null)
2649 discoverer = new jalview.ws.jws1.Discoverer();
2650 // register PCS handler for desktop.
2651 discoverer.addPropertyChangeListener(changeSupport);
2653 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2654 // until we phase out completely
2655 (t0 = new Thread(discoverer)).start();
2658 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2660 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2661 .startDiscoverer(changeSupport);
2665 // TODO: do rest service discovery
2674 } catch (Exception e)
2677 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2678 || (t3 != null && t3.isAlive())
2679 || (t0 != null && t0.isAlive());
2685 * called to check if the service discovery process completed successfully.
2689 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2691 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2693 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2694 .getErrorMessages();
2697 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2699 if (serviceChangedDialog == null)
2701 // only run if we aren't already displaying one of these.
2702 addDialogThread(serviceChangedDialog = new Runnable()
2709 * JalviewDialog jd =new JalviewDialog() {
2711 * @Override protected void cancelPressed() { // TODO
2712 * Auto-generated method stub
2714 * }@Override protected void okPressed() { // TODO
2715 * Auto-generated method stub
2717 * }@Override protected void raiseClosed() { // TODO
2718 * Auto-generated method stub
2720 * } }; jd.initDialogFrame(new
2721 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2722 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2723 * + " or mis-configured HTTP proxy settings.<br/>" +
2724 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
2726 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
2727 * ), true, true, "Web Service Configuration Problem", 450,
2730 * jd.waitForInput();
2732 JvOptionPane.showConfirmDialog(Desktop.desktop,
2733 new JLabel("<html><table width=\"450\"><tr><td>"
2734 + ermsg + "</td></tr></table>"
2735 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2736 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2737 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2738 + " Tools->Preferences dialog box to change them.</p></html>"),
2739 "Web Service Configuration Problem",
2740 JvOptionPane.DEFAULT_OPTION,
2741 JvOptionPane.ERROR_MESSAGE);
2742 serviceChangedDialog = null;
2751 "Errors reported by JABA discovery service. Check web services preferences.\n"
2758 private Runnable serviceChangedDialog = null;
2761 * start a thread to open a URL in the configured browser. Pops up a warning
2762 * dialog to the user if there is an exception when calling out to the browser
2767 public static void showUrl(final String url)
2769 showUrl(url, Desktop.instance);
2773 * Like showUrl but allows progress handler to be specified
2777 * (null) or object implementing IProgressIndicator
2779 public static void showUrl(final String url,
2780 final IProgressIndicator progress)
2782 new Thread(new Runnable()
2789 if (progress != null)
2791 progress.setProgressBar(MessageManager
2792 .formatMessage("status.opening_params", new Object[]
2793 { url }), this.hashCode());
2795 jalview.util.BrowserLauncher.openURL(url);
2796 } catch (Exception ex)
2798 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2800 .getString("label.web_browser_not_found_unix"),
2801 MessageManager.getString("label.web_browser_not_found"),
2802 JvOptionPane.WARNING_MESSAGE);
2804 ex.printStackTrace();
2806 if (progress != null)
2808 progress.setProgressBar(null, this.hashCode());
2814 public static WsParamSetManager wsparamManager = null;
2816 public static ParamManager getUserParameterStore()
2818 if (wsparamManager == null)
2820 wsparamManager = new WsParamSetManager();
2822 return wsparamManager;
2826 * static hyperlink handler proxy method for use by Jalview's internal windows
2830 public static void hyperlinkUpdate(HyperlinkEvent e)
2832 if (e.getEventType() == EventType.ACTIVATED)
2837 url = e.getURL().toString();
2838 Desktop.showUrl(url);
2839 } catch (Exception x)
2843 if (Cache.log != null)
2845 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2850 "Couldn't handle string " + url + " as a URL.");
2853 // ignore any exceptions due to dud links.
2860 * single thread that handles display of dialogs to user.
2862 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2865 * flag indicating if dialogExecutor should try to acquire a permit
2867 private volatile boolean dialogPause = true;
2872 private java.util.concurrent.Semaphore block = new Semaphore(0);
2874 private static groovy.ui.Console groovyConsole;
2877 * add another dialog thread to the queue
2881 public void addDialogThread(final Runnable prompter)
2883 dialogExecutor.submit(new Runnable()
2893 } catch (InterruptedException x)
2897 if (instance == null)
2903 SwingUtilities.invokeAndWait(prompter);
2904 } catch (Exception q)
2906 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2912 public void startDialogQueue()
2914 // set the flag so we don't pause waiting for another permit and semaphore
2915 // the current task to begin
2916 dialogPause = false;
2921 * Outputs an image of the desktop to file in EPS format, after prompting the
2922 * user for choice of Text or Lineart character rendering (unless a preference
2923 * has been set). The file name is generated as
2926 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2930 protected void snapShotWindow_actionPerformed(ActionEvent e)
2932 // currently the menu option to do this is not shown
2935 int width = getWidth();
2936 int height = getHeight();
2938 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2939 ImageWriterI writer = new ImageWriterI()
2942 public void exportImage(Graphics g) throws Exception
2945 Cache.log.info("Successfully written snapshot to file "
2946 + of.getAbsolutePath());
2949 String title = "View of desktop";
2950 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
2952 exporter.doExport(of, this, width, height, title);
2956 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2957 * This respects (remembers) any previous 'exploded geometry' i.e. the size
2958 * and location last time the view was expanded (if any). However it does not
2959 * remember the split pane divider location - this is set to match the
2960 * 'exploding' frame.
2964 public void explodeViews(SplitFrame sf)
2966 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2967 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2968 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
2970 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
2972 int viewCount = topPanels.size();
2979 * Processing in reverse order works, forwards order leaves the first panels
2980 * not visible. I don't know why!
2982 for (int i = viewCount - 1; i >= 0; i--)
2985 * Make new top and bottom frames. These take over the respective
2986 * AlignmentPanel objects, including their AlignmentViewports, so the
2987 * cdna/protein relationships between the viewports is carried over to the
2990 * explodedGeometry holds the (x, y) position of the previously exploded
2991 * SplitFrame, and the (width, height) of the AlignFrame component
2993 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
2994 AlignFrame newTopFrame = new AlignFrame(topPanel);
2995 newTopFrame.setSize(oldTopFrame.getSize());
2996 newTopFrame.setVisible(true);
2997 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
2998 .getExplodedGeometry();
2999 if (geometry != null)
3001 newTopFrame.setSize(geometry.getSize());
3004 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3005 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3006 newBottomFrame.setSize(oldBottomFrame.getSize());
3007 newBottomFrame.setVisible(true);
3008 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3009 .getExplodedGeometry();
3010 if (geometry != null)
3012 newBottomFrame.setSize(geometry.getSize());
3015 topPanel.av.setGatherViewsHere(false);
3016 bottomPanel.av.setGatherViewsHere(false);
3017 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3019 if (geometry != null)
3021 splitFrame.setLocation(geometry.getLocation());
3023 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3027 * Clear references to the panels (now relocated in the new SplitFrames)
3028 * before closing the old SplitFrame.
3031 bottomPanels.clear();
3036 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3037 * back into the given SplitFrame as additional views. Note that the gathered
3038 * frames may themselves have multiple views.
3042 public void gatherViews(GSplitFrame source)
3045 * special handling of explodedGeometry for a view within a SplitFrame: - it
3046 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3047 * height) of the AlignFrame component
3049 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3050 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3051 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3052 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3053 myBottomFrame.viewport
3054 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3055 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3056 myTopFrame.viewport.setGatherViewsHere(true);
3057 myBottomFrame.viewport.setGatherViewsHere(true);
3058 String topViewId = myTopFrame.viewport.getSequenceSetId();
3059 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3061 JInternalFrame[] frames = desktop.getAllFrames();
3062 for (JInternalFrame frame : frames)
3064 if (frame instanceof SplitFrame && frame != source)
3066 SplitFrame sf = (SplitFrame) frame;
3067 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3068 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3069 boolean gatherThis = false;
3070 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3072 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3073 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3074 if (topViewId.equals(topPanel.av.getSequenceSetId())
3075 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3078 topPanel.av.setGatherViewsHere(false);
3079 bottomPanel.av.setGatherViewsHere(false);
3080 topPanel.av.setExplodedGeometry(
3081 new Rectangle(sf.getLocation(), topFrame.getSize()));
3082 bottomPanel.av.setExplodedGeometry(
3083 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3084 myTopFrame.addAlignmentPanel(topPanel, false);
3085 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3091 topFrame.getAlignPanels().clear();
3092 bottomFrame.getAlignPanels().clear();
3099 * The dust settles...give focus to the tab we did this from.
3101 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3104 public static groovy.ui.Console getGroovyConsole()
3106 return groovyConsole;
3110 * handles the payload of a drag and drop event.
3112 * TODO refactor to desktop utilities class
3115 * - Data source strings extracted from the drop event
3117 * - protocol for each data source extracted from the drop event
3121 * - the payload from the drop event
3124 public static void transferFromDropTarget(List<Object> files,
3125 List<DataSourceType> protocols, DropTargetDropEvent evt,
3126 Transferable t) throws Exception
3129 // BH 2018 changed List<String> to List<Object> to allow for File from
3132 // DataFlavor[] flavors = t.getTransferDataFlavors();
3133 // for (int i = 0; i < flavors.length; i++) {
3134 // if (flavors[i].isFlavorJavaFileListType()) {
3135 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3136 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3137 // for (int j = 0; j < list.size(); j++) {
3138 // File file = (File) list.get(j);
3139 // byte[] data = getDroppedFileBytes(file);
3140 // fileName.setText(file.getName() + " - " + data.length + " " +
3141 // evt.getLocation());
3142 // JTextArea target = (JTextArea) ((DropTarget)
3143 // evt.getSource()).getComponent();
3144 // target.setText(new String(data));
3146 // dtde.dropComplete(true);
3151 DataFlavor uriListFlavor = new DataFlavor(
3152 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3155 urlFlavour = new DataFlavor(
3156 "application/x-java-url; class=java.net.URL");
3157 } catch (ClassNotFoundException cfe)
3159 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3162 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3167 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3168 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3169 // means url may be null.
3172 protocols.add(DataSourceType.URL);
3173 files.add(url.toString());
3174 Cache.log.debug("Drop handled as URL dataflavor "
3175 + files.get(files.size() - 1));
3180 if (Platform.isAMacAndNotJS())
3183 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3186 } catch (Throwable ex)
3188 Cache.log.debug("URL drop handler failed.", ex);
3191 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3193 // Works on Windows and MacOSX
3194 Cache.log.debug("Drop handled as javaFileListFlavor");
3195 for (Object file : (List) t
3196 .getTransferData(DataFlavor.javaFileListFlavor))
3199 protocols.add(DataSourceType.FILE);
3204 // Unix like behaviour
3205 boolean added = false;
3207 if (t.isDataFlavorSupported(uriListFlavor))
3209 Cache.log.debug("Drop handled as uriListFlavor");
3210 // This is used by Unix drag system
3211 data = (String) t.getTransferData(uriListFlavor);
3215 // fallback to text: workaround - on OSX where there's a JVM bug
3216 Cache.log.debug("standard URIListFlavor failed. Trying text");
3217 // try text fallback
3218 DataFlavor textDf = new DataFlavor(
3219 "text/plain;class=java.lang.String");
3220 if (t.isDataFlavorSupported(textDf))
3222 data = (String) t.getTransferData(textDf);
3225 Cache.log.debug("Plain text drop content returned "
3226 + (data == null ? "Null - failed" : data));
3231 while (protocols.size() < files.size())
3233 Cache.log.debug("Adding missing FILE protocol for "
3234 + files.get(protocols.size()));
3235 protocols.add(DataSourceType.FILE);
3237 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3238 data, "\r\n"); st.hasMoreTokens();)
3241 String s = st.nextToken();
3242 if (s.startsWith("#"))
3244 // the line is a comment (as per the RFC 2483)
3247 java.net.URI uri = new java.net.URI(s);
3248 if (uri.getScheme().toLowerCase().startsWith("http"))
3250 protocols.add(DataSourceType.URL);
3251 files.add(uri.toString());
3255 // otherwise preserve old behaviour: catch all for file objects
3256 java.io.File file = new java.io.File(uri);
3257 protocols.add(DataSourceType.FILE);
3258 files.add(file.toString());
3263 if (Cache.log.isDebugEnabled())
3265 if (data == null || !added)
3268 if (t.getTransferDataFlavors() != null
3269 && t.getTransferDataFlavors().length > 0)
3272 "Couldn't resolve drop data. Here are the supported flavors:");
3273 for (DataFlavor fl : t.getTransferDataFlavors())
3276 "Supported transfer dataflavor: " + fl.toString());
3277 Object df = t.getTransferData(fl);
3280 Cache.log.debug("Retrieves: " + df);
3284 Cache.log.debug("Retrieved nothing");
3290 Cache.log.debug("Couldn't resolve dataflavor for drop: "
3296 if (Platform.isWindowsAndNotJS())
3298 Cache.log.debug("Scanning dropped content for Windows Link Files");
3300 // resolve any .lnk files in the file drop
3301 for (int f = 0; f < files.size(); f++)
3303 String source = files.get(f).toString().toLowerCase();
3304 if (protocols.get(f).equals(DataSourceType.FILE)
3305 && (source.endsWith(".lnk") || source.endsWith(".url")
3306 || source.endsWith(".site")))
3310 Object obj = files.get(f);
3311 File lf = (obj instanceof File ? (File) obj
3312 : new File((String) obj));
3313 // process link file to get a URL
3314 Cache.log.debug("Found potential link file: " + lf);
3315 WindowsShortcut wscfile = new WindowsShortcut(lf);
3316 String fullname = wscfile.getRealFilename();
3317 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3318 files.set(f, fullname);
3319 Cache.log.debug("Parsed real filename " + fullname
3320 + " to extract protocol: " + protocols.get(f));
3321 } catch (Exception ex)
3324 "Couldn't parse " + files.get(f) + " as a link file.",
3333 * Sets the Preferences property for experimental features to True or False
3334 * depending on the state of the controlling menu item
3337 protected void showExperimental_actionPerformed(boolean selected)
3339 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3343 * Answers a (possibly empty) list of any structure viewer frames (currently
3344 * for either Jmol or Chimera) which are currently open. This may optionally
3345 * be restricted to viewers of a specified class, or viewers linked to a
3346 * specified alignment panel.
3349 * if not null, only return viewers linked to this panel
3350 * @param structureViewerClass
3351 * if not null, only return viewers of this class
3354 public List<StructureViewerBase> getStructureViewers(
3355 AlignmentPanel apanel,
3356 Class<? extends StructureViewerBase> structureViewerClass)
3358 List<StructureViewerBase> result = new ArrayList<>();
3359 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3361 for (JInternalFrame frame : frames)
3363 if (frame instanceof StructureViewerBase)
3365 if (structureViewerClass == null
3366 || structureViewerClass.isInstance(frame))
3369 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3371 result.add((StructureViewerBase) frame);
3379 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3381 private static boolean debugScaleMessageDone = false;
3383 public static void debugScaleMessage(Graphics g)
3385 if (debugScaleMessageDone)
3389 // output used by tests to check HiDPI scaling settings in action
3392 Graphics2D gg = (Graphics2D) g;
3395 AffineTransform t = gg.getTransform();
3396 double scaleX = t.getScaleX();
3397 double scaleY = t.getScaleY();
3398 Cache.debug(debugScaleMessage + scaleX + " (X)");
3399 Cache.debug(debugScaleMessage + scaleY + " (Y)");
3400 debugScaleMessageDone = true;
3404 Cache.debug("Desktop graphics null");
3406 } catch (Exception e)
3408 Cache.debug(Cache.getStackTraceString(e));