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.ChannelProperties;
120 import jalview.util.ImageMaker.TYPE;
121 import jalview.util.MessageManager;
122 import jalview.util.Platform;
123 import jalview.util.ShortcutKeyMaskExWrapper;
124 import jalview.util.UrlConstants;
125 import jalview.viewmodel.AlignmentViewport;
126 import jalview.ws.params.ParamManager;
127 import jalview.ws.utils.UrlDownloadClient;
134 * @version $Revision: 1.155 $
136 public class Desktop extends jalview.jbgui.GDesktop
137 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
138 jalview.api.StructureSelectionManagerProvider
140 private static final String CITATION = "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
141 + "<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"
142 + "<br><br>If you use Jalview, please cite:"
143 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
144 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
145 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033";
147 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
149 private static int DEFAULT_MIN_WIDTH = 300;
151 private static int DEFAULT_MIN_HEIGHT = 250;
153 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
155 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
157 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
159 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
161 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
163 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
165 public static boolean nosplash = false;
168 * news reader - null if it was never started.
170 private BlogReader jvnews = null;
172 private File projectFile;
176 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
178 public void addJalviewPropertyChangeListener(
179 PropertyChangeListener listener)
181 changeSupport.addJalviewPropertyChangeListener(listener);
185 * @param propertyName
187 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
188 * java.beans.PropertyChangeListener)
190 public void addJalviewPropertyChangeListener(String propertyName,
191 PropertyChangeListener listener)
193 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
197 * @param propertyName
199 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
200 * java.beans.PropertyChangeListener)
202 public void removeJalviewPropertyChangeListener(String propertyName,
203 PropertyChangeListener listener)
205 changeSupport.removeJalviewPropertyChangeListener(propertyName,
209 /** Singleton Desktop instance */
210 public static Desktop instance;
212 public static MyDesktopPane desktop;
214 public static MyDesktopPane getDesktop()
216 // BH 2018 could use currentThread() here as a reference to a
217 // Hashtable<Thread, MyDesktopPane> in JavaScript
221 static int openFrameCount = 0;
223 static final int xOffset = 30;
225 static final int yOffset = 30;
227 public static jalview.ws.jws1.Discoverer discoverer;
229 public static Object[] jalviewClipboard;
231 public static boolean internalCopy = false;
233 static int fileLoadingCount = 0;
235 class MyDesktopManager implements DesktopManager
238 private DesktopManager delegate;
240 public MyDesktopManager(DesktopManager delegate)
242 this.delegate = delegate;
246 public void activateFrame(JInternalFrame f)
250 delegate.activateFrame(f);
251 } catch (NullPointerException npe)
253 Point p = getMousePosition();
254 instance.showPasteMenu(p.x, p.y);
259 public void beginDraggingFrame(JComponent f)
261 delegate.beginDraggingFrame(f);
265 public void beginResizingFrame(JComponent f, int direction)
267 delegate.beginResizingFrame(f, direction);
271 public void closeFrame(JInternalFrame f)
273 delegate.closeFrame(f);
277 public void deactivateFrame(JInternalFrame f)
279 delegate.deactivateFrame(f);
283 public void deiconifyFrame(JInternalFrame f)
285 delegate.deiconifyFrame(f);
289 public void dragFrame(JComponent f, int newX, int newY)
295 delegate.dragFrame(f, newX, newY);
299 public void endDraggingFrame(JComponent f)
301 delegate.endDraggingFrame(f);
306 public void endResizingFrame(JComponent f)
308 delegate.endResizingFrame(f);
313 public void iconifyFrame(JInternalFrame f)
315 delegate.iconifyFrame(f);
319 public void maximizeFrame(JInternalFrame f)
321 delegate.maximizeFrame(f);
325 public void minimizeFrame(JInternalFrame f)
327 delegate.minimizeFrame(f);
331 public void openFrame(JInternalFrame f)
333 delegate.openFrame(f);
337 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
344 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
348 public void setBoundsForFrame(JComponent f, int newX, int newY,
349 int newWidth, int newHeight)
351 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
354 // All other methods, simply delegate
359 * Creates a new Desktop object.
365 * A note to implementors. It is ESSENTIAL that any activities that might
366 * block are spawned off as threads rather than waited for during this
371 doConfigureStructurePrefs();
372 setTitle(ChannelProperties.getProperty("app_name") + " "
373 + Cache.getProperty("VERSION"));
375 if (!Platform.isAMac())
377 // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
381 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
387 APQHandlers.setAPQHandlers(this);
388 } catch (Throwable t)
390 System.out.println("Error setting APQHandlers: " + t.toString());
391 // t.printStackTrace();
393 setIconImages(ChannelProperties.getIconList());
395 addWindowListener(new WindowAdapter()
399 public void windowClosing(WindowEvent ev)
405 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
407 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
408 desktop = new MyDesktopPane(selmemusage);
410 showMemusage.setSelected(selmemusage);
411 desktop.setBackground(Color.white);
413 this.setIconImages(ChannelProperties.getIconList());
415 getContentPane().setLayout(new BorderLayout());
416 // alternate config - have scrollbars - see notes in JAL-153
417 // JScrollPane sp = new JScrollPane();
418 // sp.getViewport().setView(desktop);
419 // getContentPane().add(sp, BorderLayout.CENTER);
421 // BH 2018 - just an experiment to try unclipped JInternalFrames.
424 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
427 getContentPane().add(desktop, BorderLayout.CENTER);
428 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
430 // This line prevents Windows Look&Feel resizing all new windows to maximum
431 // if previous window was maximised
432 desktop.setDesktopManager(new MyDesktopManager(
433 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
434 : Platform.isAMacAndNotJS()
435 ? new AquaInternalFrameManager(
436 desktop.getDesktopManager())
437 : desktop.getDesktopManager())));
439 Rectangle dims = getLastKnownDimensions("");
446 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
447 int xPos = Math.max(5, (screenSize.width - 900) / 2);
448 int yPos = Math.max(5, (screenSize.height - 650) / 2);
449 setBounds(xPos, yPos, 900, 650);
452 if (!Platform.isJS())
459 jconsole = new Console(this, showjconsole);
460 jconsole.setHeader(Cache.getVersionDetailsForConsole());
461 showConsole(showjconsole);
463 showNews.setVisible(false);
465 experimentalFeatures.setSelected(showExperimental());
467 getIdentifiersOrgData();
471 // Spawn a thread that shows the splashscreen
474 SwingUtilities.invokeLater(new Runnable()
479 new SplashScreen(true);
484 // Thread off a new instance of the file chooser - this reduces the time
486 // takes to open it later on.
487 new Thread(new Runnable()
492 Cache.log.debug("Filechooser init thread started.");
493 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
494 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
496 Cache.log.debug("Filechooser init thread finished.");
499 // Add the service change listener
500 changeSupport.addJalviewPropertyChangeListener("services",
501 new PropertyChangeListener()
505 public void propertyChange(PropertyChangeEvent evt)
507 Cache.log.debug("Firing service changed event for "
508 + evt.getNewValue());
509 JalviewServicesChanged(evt);
514 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
516 this.addWindowListener(new WindowAdapter()
519 public void windowClosing(WindowEvent evt)
526 this.addMouseListener(ma = new MouseAdapter()
529 public void mousePressed(MouseEvent evt)
531 if (evt.isPopupTrigger()) // Mac
533 showPasteMenu(evt.getX(), evt.getY());
538 public void mouseReleased(MouseEvent evt)
540 if (evt.isPopupTrigger()) // Windows
542 showPasteMenu(evt.getX(), evt.getY());
546 desktop.addMouseListener(ma);
550 * Answers true if user preferences to enable experimental features is True
555 public boolean showExperimental()
557 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
558 Boolean.FALSE.toString());
559 return Boolean.valueOf(experimental).booleanValue();
562 public void doConfigureStructurePrefs()
564 // configure services
565 StructureSelectionManager ssm = StructureSelectionManager
566 .getStructureSelectionManager(this);
567 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
569 ssm.setAddTempFacAnnot(
570 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
571 ssm.setProcessSecondaryStructure(
572 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
573 ssm.setSecStructServices(
574 Cache.getDefault(Preferences.USE_RNAVIEW, true));
578 ssm.setAddTempFacAnnot(false);
579 ssm.setProcessSecondaryStructure(false);
580 ssm.setSecStructServices(false);
584 public void checkForNews()
586 final Desktop me = this;
587 // Thread off the news reader, in case there are connection problems.
588 new Thread(new Runnable()
593 Cache.log.debug("Starting news thread.");
594 jvnews = new BlogReader(me);
595 showNews.setVisible(true);
596 Cache.log.debug("Completed news thread.");
601 public void getIdentifiersOrgData()
603 // Thread off the identifiers fetcher
604 new Thread(new Runnable()
609 Cache.log.debug("Downloading data from identifiers.org");
612 UrlDownloadClient.download(IdOrgSettings.getUrl(),
613 IdOrgSettings.getDownloadLocation());
614 } catch (IOException e)
616 Cache.log.debug("Exception downloading identifiers.org data"
625 protected void showNews_actionPerformed(ActionEvent e)
627 showNews(showNews.isSelected());
630 void showNews(boolean visible)
632 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
633 showNews.setSelected(visible);
634 if (visible && !jvnews.isVisible())
636 new Thread(new Runnable()
641 long now = System.currentTimeMillis();
642 Desktop.instance.setProgressBar(
643 MessageManager.getString("status.refreshing_news"), now);
644 jvnews.refreshNews();
645 Desktop.instance.setProgressBar(null, now);
653 * recover the last known dimensions for a jalview window
656 * - empty string is desktop, all other windows have unique prefix
657 * @return null or last known dimensions scaled to current geometry (if last
658 * window geom was known)
660 Rectangle getLastKnownDimensions(String windowName)
662 // TODO: lock aspect ratio for scaling desktop Bug #0058199
663 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
664 String x = Cache.getProperty(windowName + "SCREEN_X");
665 String y = Cache.getProperty(windowName + "SCREEN_Y");
666 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
667 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
668 if ((x != null) && (y != null) && (width != null) && (height != null))
670 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
671 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
672 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
674 // attempt #1 - try to cope with change in screen geometry - this
675 // version doesn't preserve original jv aspect ratio.
676 // take ratio of current screen size vs original screen size.
677 double sw = ((1f * screenSize.width) / (1f * Integer
678 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
679 double sh = ((1f * screenSize.height) / (1f * Integer
680 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
681 // rescale the bounds depending upon the current screen geometry.
682 ix = (int) (ix * sw);
683 iw = (int) (iw * sw);
684 iy = (int) (iy * sh);
685 ih = (int) (ih * sh);
686 while (ix >= screenSize.width)
689 "Window geometry location recall error: shifting horizontal to within screenbounds.");
690 ix -= screenSize.width;
692 while (iy >= screenSize.height)
695 "Window geometry location recall error: shifting vertical to within screenbounds.");
696 iy -= screenSize.height;
699 "Got last known dimensions for " + windowName + ": x:" + ix
700 + " y:" + iy + " width:" + iw + " height:" + ih);
702 // return dimensions for new instance
703 return new Rectangle(ix, iy, iw, ih);
708 void showPasteMenu(int x, int y)
710 JPopupMenu popup = new JPopupMenu();
711 JMenuItem item = new JMenuItem(
712 MessageManager.getString("label.paste_new_window"));
713 item.addActionListener(new ActionListener()
716 public void actionPerformed(ActionEvent evt)
723 popup.show(this, x, y);
730 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
731 Transferable contents = c.getContents(this);
733 if (contents != null)
735 String file = (String) contents
736 .getTransferData(DataFlavor.stringFlavor);
738 FileFormatI format = new IdentifyFile().identify(file,
739 DataSourceType.PASTE);
741 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
744 } catch (Exception ex)
747 "Unable to paste alignment from system clipboard:\n" + ex);
752 * Adds and opens the given frame to the desktop
763 public static synchronized void addInternalFrame(
764 final JInternalFrame frame, String title, int w, int h)
766 addInternalFrame(frame, title, true, w, h, true, false);
770 * Add an internal frame to the Jalview desktop
777 * When true, display frame immediately, otherwise, caller must call
778 * setVisible themselves.
784 public static synchronized void addInternalFrame(
785 final JInternalFrame frame, String title, boolean makeVisible,
788 addInternalFrame(frame, title, makeVisible, w, h, true, false);
792 * Add an internal frame to the Jalview desktop and make it visible
805 public static synchronized void addInternalFrame(
806 final JInternalFrame frame, String title, int w, int h,
809 addInternalFrame(frame, title, true, w, h, resizable, false);
813 * Add an internal frame to the Jalview desktop
820 * When true, display frame immediately, otherwise, caller must call
821 * setVisible themselves.
828 * @param ignoreMinSize
829 * Do not set the default minimum size for frame
831 public static synchronized void addInternalFrame(
832 final JInternalFrame frame, String title, boolean makeVisible,
833 int w, int h, boolean resizable, boolean ignoreMinSize)
836 // TODO: allow callers to determine X and Y position of frame (eg. via
838 // TODO: consider fixing method to update entries in the window submenu with
839 // the current window title
841 frame.setTitle(title);
842 if (frame.getWidth() < 1 || frame.getHeight() < 1)
846 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
847 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
848 // IF JALVIEW IS RUNNING HEADLESS
849 // ///////////////////////////////////////////////
850 if (instance == null || (System.getProperty("java.awt.headless") != null
851 && System.getProperty("java.awt.headless").equals("true")))
860 frame.setMinimumSize(
861 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
863 // Set default dimension for Alignment Frame window.
864 // The Alignment Frame window could be added from a number of places,
866 // I did this here in order not to miss out on any Alignment frame.
867 if (frame instanceof AlignFrame)
869 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
870 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
874 frame.setVisible(makeVisible);
875 frame.setClosable(true);
876 frame.setResizable(resizable);
877 frame.setMaximizable(resizable);
878 frame.setIconifiable(resizable);
879 frame.setOpaque(Platform.isJS());
881 if (frame.getX() < 1 && frame.getY() < 1)
883 frame.setLocation(xOffset * openFrameCount,
884 yOffset * ((openFrameCount - 1) % 10) + yOffset);
888 * add an entry for the new frame in the Window menu
889 * (and remove it when the frame is closed)
891 final JMenuItem menuItem = new JMenuItem(title);
892 frame.addInternalFrameListener(new InternalFrameAdapter()
895 public void internalFrameActivated(InternalFrameEvent evt)
897 JInternalFrame itf = desktop.getSelectedFrame();
900 if (itf instanceof AlignFrame)
902 Jalview.setCurrentAlignFrame((AlignFrame) itf);
909 public void internalFrameClosed(InternalFrameEvent evt)
911 PaintRefresher.RemoveComponent(frame);
914 * defensive check to prevent frames being
915 * added half off the window
917 if (openFrameCount > 0)
923 * ensure no reference to alignFrame retained by menu item listener
925 if (menuItem.getActionListeners().length > 0)
927 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
929 windowMenu.remove(menuItem);
933 menuItem.addActionListener(new ActionListener()
936 public void actionPerformed(ActionEvent e)
940 frame.setSelected(true);
941 frame.setIcon(false);
942 } catch (java.beans.PropertyVetoException ex)
944 // System.err.println(ex.toString());
949 setKeyBindings(frame);
953 windowMenu.add(menuItem);
958 frame.setSelected(true);
959 frame.requestFocus();
960 } catch (java.beans.PropertyVetoException ve)
962 } catch (java.lang.ClassCastException cex)
965 "Squashed a possible GUI implementation error. If you can recreate this, please look at http://issues.jalview.org/browse/JAL-869",
971 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
976 private static void setKeyBindings(JInternalFrame frame)
978 @SuppressWarnings("serial")
979 final Action closeAction = new AbstractAction()
982 public void actionPerformed(ActionEvent e)
989 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
991 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
992 InputEvent.CTRL_DOWN_MASK);
993 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
994 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
996 InputMap inputMap = frame
997 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
998 String ctrlW = ctrlWKey.toString();
999 inputMap.put(ctrlWKey, ctrlW);
1000 inputMap.put(cmdWKey, ctrlW);
1002 ActionMap actionMap = frame.getActionMap();
1003 actionMap.put(ctrlW, closeAction);
1007 public void lostOwnership(Clipboard clipboard, Transferable contents)
1011 Desktop.jalviewClipboard = null;
1014 internalCopy = false;
1018 public void dragEnter(DropTargetDragEvent evt)
1023 public void dragExit(DropTargetEvent evt)
1028 public void dragOver(DropTargetDragEvent evt)
1033 public void dropActionChanged(DropTargetDragEvent evt)
1044 public void drop(DropTargetDropEvent evt)
1046 boolean success = true;
1047 // JAL-1552 - acceptDrop required before getTransferable call for
1048 // Java's Transferable for native dnd
1049 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1050 Transferable t = evt.getTransferable();
1051 List<Object> files = new ArrayList<>();
1052 List<DataSourceType> protocols = new ArrayList<>();
1056 Desktop.transferFromDropTarget(files, protocols, evt, t);
1057 } catch (Exception e)
1059 e.printStackTrace();
1067 for (int i = 0; i < files.size(); i++)
1069 // BH 2018 File or String
1070 Object file = files.get(i);
1071 String fileName = file.toString();
1072 DataSourceType protocol = (protocols == null)
1073 ? DataSourceType.FILE
1075 FileFormatI format = null;
1077 if (fileName.endsWith(".jar"))
1079 format = FileFormat.Jalview;
1084 format = new IdentifyFile().identify(file, protocol);
1086 if (file instanceof File)
1088 Platform.cacheFileData((File) file);
1090 new FileLoader().LoadFile(null, file, protocol, format);
1093 } catch (Exception ex)
1098 evt.dropComplete(success); // need this to ensure input focus is properly
1099 // transfered to any new windows created
1109 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1111 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1112 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1113 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1114 BackupFiles.getEnabled());
1116 chooser.setFileView(new JalviewFileView());
1117 chooser.setDialogTitle(
1118 MessageManager.getString("label.open_local_file"));
1119 chooser.setToolTipText(MessageManager.getString("action.open"));
1121 chooser.setResponseHandler(0, new Runnable()
1126 File selectedFile = chooser.getSelectedFile();
1127 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1129 FileFormatI format = chooser.getSelectedFormat();
1132 * Call IdentifyFile to verify the file contains what its extension implies.
1133 * Skip this step for dynamically added file formats, because
1134 * IdentifyFile does not know how to recognise them.
1136 if (FileFormats.getInstance().isIdentifiable(format))
1140 format = new IdentifyFile().identify(selectedFile,
1141 DataSourceType.FILE);
1142 } catch (FileFormatException e)
1144 // format = null; //??
1148 new FileLoader().LoadFile(viewport, selectedFile,
1149 DataSourceType.FILE, format);
1152 chooser.showOpenDialog(this);
1156 * Shows a dialog for input of a URL at which to retrieve alignment data
1161 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1163 // This construct allows us to have a wider textfield
1165 JLabel label = new JLabel(
1166 MessageManager.getString("label.input_file_url"));
1168 JPanel panel = new JPanel(new GridLayout(2, 1));
1172 * the URL to fetch is
1173 * Java: an editable combobox with history
1174 * JS: (pending JAL-3038) a plain text field
1177 String urlBase = "http://www.";
1178 if (Platform.isJS())
1180 history = new JTextField(urlBase, 35);
1189 JComboBox<String> asCombo = new JComboBox<>();
1190 asCombo.setPreferredSize(new Dimension(400, 20));
1191 asCombo.setEditable(true);
1192 asCombo.addItem(urlBase);
1193 String historyItems = Cache.getProperty("RECENT_URL");
1194 if (historyItems != null)
1196 for (String token : historyItems.split("\\t"))
1198 asCombo.addItem(token);
1205 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1206 MessageManager.getString("action.cancel") };
1207 Runnable action = new Runnable()
1212 @SuppressWarnings("unchecked")
1213 String url = (history instanceof JTextField
1214 ? ((JTextField) history).getText()
1215 : ((JComboBox<String>) history).getSelectedItem()
1218 if (url.toLowerCase().endsWith(".jar"))
1220 if (viewport != null)
1222 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1223 FileFormat.Jalview);
1227 new FileLoader().LoadFile(url, DataSourceType.URL,
1228 FileFormat.Jalview);
1233 FileFormatI format = null;
1236 format = new IdentifyFile().identify(url, DataSourceType.URL);
1237 } catch (FileFormatException e)
1239 // TODO revise error handling, distinguish between
1240 // URL not found and response not valid
1245 String msg = MessageManager
1246 .formatMessage("label.couldnt_locate", url);
1247 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1248 MessageManager.getString("label.url_not_found"),
1249 JvOptionPane.WARNING_MESSAGE);
1254 if (viewport != null)
1256 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1261 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1266 String dialogOption = MessageManager
1267 .getString("label.input_alignment_from_url");
1268 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1269 .showInternalDialog(panel, dialogOption,
1270 JvOptionPane.YES_NO_CANCEL_OPTION,
1271 JvOptionPane.PLAIN_MESSAGE, null, options,
1272 MessageManager.getString("action.ok"));
1276 * Opens the CutAndPaste window for the user to paste an alignment in to
1279 * - if not null, the pasted alignment is added to the current
1280 * alignment; if null, to a new alignment window
1283 public void inputTextboxMenuItem_actionPerformed(
1284 AlignmentViewPanel viewPanel)
1286 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1287 cap.setForInput(viewPanel);
1288 Desktop.addInternalFrame(cap,
1289 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1299 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1300 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1301 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1302 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1303 getWidth(), getHeight()));
1305 if (jconsole != null)
1307 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1308 jconsole.stopConsole();
1312 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1315 if (dialogExecutor != null)
1317 dialogExecutor.shutdownNow();
1319 closeAll_actionPerformed(null);
1321 if (groovyConsole != null)
1323 // suppress a possible repeat prompt to save script
1324 groovyConsole.setDirty(false);
1325 groovyConsole.exit();
1330 private void storeLastKnownDimensions(String string, Rectangle jc)
1332 Cache.log.debug("Storing last known dimensions for " + string + ": x:"
1333 + jc.x + " y:" + jc.y + " width:" + jc.width + " height:"
1336 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1337 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1338 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1339 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1349 public void aboutMenuItem_actionPerformed(ActionEvent e)
1351 new Thread(new Runnable()
1356 new SplashScreen(false);
1362 * Returns the html text for the About screen, including any available version
1363 * number, build details, author details and citation reference, but without
1364 * the enclosing {@code html} tags
1368 public String getAboutMessage()
1370 StringBuilder message = new StringBuilder(1024);
1371 message.append("<h1><strong>Version: ")
1372 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1373 .append("<strong>Built: <em>")
1374 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1375 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1376 .append("</strong>");
1378 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1379 if (latestVersion.equals("Checking"))
1381 // JBP removed this message for 2.11: May be reinstated in future version
1382 // message.append("<br>...Checking latest version...</br>");
1384 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1386 boolean red = false;
1387 if (Cache.getProperty("VERSION").toLowerCase()
1388 .indexOf("automated build") == -1)
1391 // Displayed when code version and jnlp version do not match and code
1392 // version is not a development build
1393 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1396 message.append("<br>!! Version ")
1397 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1398 .append(" is available for download from ")
1399 .append(Cache.getDefault("www.jalview.org",
1400 "http://www.jalview.org"))
1404 message.append("</div>");
1407 message.append("<br>Authors: ");
1408 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1409 message.append(CITATION);
1411 return message.toString();
1415 * Action on requesting Help documentation
1418 public void documentationMenuItem_actionPerformed()
1422 if (Platform.isJS())
1424 BrowserLauncher.openURL("http://www.jalview.org/help.html");
1433 Help.showHelpWindow();
1435 } catch (Exception ex)
1437 System.err.println("Error opening help: " + ex.getMessage());
1442 public void closeAll_actionPerformed(ActionEvent e)
1444 // TODO show a progress bar while closing?
1445 JInternalFrame[] frames = desktop.getAllFrames();
1446 for (int i = 0; i < frames.length; i++)
1450 frames[i].setClosed(true);
1451 } catch (java.beans.PropertyVetoException ex)
1455 Jalview.setCurrentAlignFrame(null);
1456 System.out.println("ALL CLOSED");
1459 * reset state of singleton objects as appropriate (clear down session state
1460 * when all windows are closed)
1462 StructureSelectionManager ssm = StructureSelectionManager
1463 .getStructureSelectionManager(this);
1471 public void raiseRelated_actionPerformed(ActionEvent e)
1473 reorderAssociatedWindows(false, false);
1477 public void minimizeAssociated_actionPerformed(ActionEvent e)
1479 reorderAssociatedWindows(true, false);
1482 void closeAssociatedWindows()
1484 reorderAssociatedWindows(false, true);
1490 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1494 protected void garbageCollect_actionPerformed(ActionEvent e)
1496 // We simply collect the garbage
1497 Cache.log.debug("Collecting garbage...");
1499 Cache.log.debug("Finished garbage collection.");
1506 * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1510 protected void showMemusage_actionPerformed(ActionEvent e)
1512 desktop.showMemoryUsage(showMemusage.isSelected());
1519 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1523 protected void showConsole_actionPerformed(ActionEvent e)
1525 showConsole(showConsole.isSelected());
1528 Console jconsole = null;
1531 * control whether the java console is visible or not
1535 void showConsole(boolean selected)
1537 // TODO: decide if we should update properties file
1538 if (jconsole != null) // BH 2018
1540 showConsole.setSelected(selected);
1541 Cache.setProperty("SHOW_JAVA_CONSOLE",
1542 Boolean.valueOf(selected).toString());
1543 jconsole.setVisible(selected);
1547 void reorderAssociatedWindows(boolean minimize, boolean close)
1549 JInternalFrame[] frames = desktop.getAllFrames();
1550 if (frames == null || frames.length < 1)
1555 AlignmentViewport source = null, target = null;
1556 if (frames[0] instanceof AlignFrame)
1558 source = ((AlignFrame) frames[0]).getCurrentView();
1560 else if (frames[0] instanceof TreePanel)
1562 source = ((TreePanel) frames[0]).getViewPort();
1564 else if (frames[0] instanceof PCAPanel)
1566 source = ((PCAPanel) frames[0]).av;
1568 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1570 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1575 for (int i = 0; i < frames.length; i++)
1578 if (frames[i] == null)
1582 if (frames[i] instanceof AlignFrame)
1584 target = ((AlignFrame) frames[i]).getCurrentView();
1586 else if (frames[i] instanceof TreePanel)
1588 target = ((TreePanel) frames[i]).getViewPort();
1590 else if (frames[i] instanceof PCAPanel)
1592 target = ((PCAPanel) frames[i]).av;
1594 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1596 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1599 if (source == target)
1605 frames[i].setClosed(true);
1609 frames[i].setIcon(minimize);
1612 frames[i].toFront();
1616 } catch (java.beans.PropertyVetoException ex)
1631 protected void preferences_actionPerformed(ActionEvent e)
1633 Preferences.openPreferences();
1637 * Prompts the user to choose a file and then saves the Jalview state as a
1638 * Jalview project file
1641 public void saveState_actionPerformed()
1643 saveState_actionPerformed(false);
1646 public void saveState_actionPerformed(boolean saveAs)
1648 java.io.File projectFile = getProjectFile();
1649 // autoSave indicates we already have a file and don't need to ask
1650 boolean autoSave = projectFile != null && !saveAs
1651 && BackupFiles.getEnabled();
1653 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1654 // saveAs="+saveAs+", Backups
1655 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1657 boolean approveSave = false;
1660 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1663 chooser.setFileView(new JalviewFileView());
1664 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1666 int value = chooser.showSaveDialog(this);
1668 if (value == JalviewFileChooser.APPROVE_OPTION)
1670 projectFile = chooser.getSelectedFile();
1671 setProjectFile(projectFile);
1676 if (approveSave || autoSave)
1678 final Desktop me = this;
1679 final java.io.File chosenFile = projectFile;
1680 new Thread(new Runnable()
1685 // TODO: refactor to Jalview desktop session controller action.
1686 setProgressBar(MessageManager.formatMessage(
1687 "label.saving_jalview_project", new Object[]
1688 { chosenFile.getName() }), chosenFile.hashCode());
1689 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1690 // TODO catch and handle errors for savestate
1691 // TODO prevent user from messing with the Desktop whilst we're saving
1694 boolean doBackup = BackupFiles.getEnabled();
1695 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1698 new Jalview2XML().saveState(
1699 doBackup ? backupfiles.getTempFile() : chosenFile);
1703 backupfiles.setWriteSuccess(true);
1704 backupfiles.rollBackupsAndRenameTempFile();
1706 } catch (OutOfMemoryError oom)
1708 new OOMWarning("Whilst saving current state to "
1709 + chosenFile.getName(), oom);
1710 } catch (Exception ex)
1712 Cache.log.error("Problems whilst trying to save to "
1713 + chosenFile.getName(), ex);
1714 JvOptionPane.showMessageDialog(me,
1715 MessageManager.formatMessage(
1716 "label.error_whilst_saving_current_state_to",
1718 { chosenFile.getName() }),
1719 MessageManager.getString("label.couldnt_save_project"),
1720 JvOptionPane.WARNING_MESSAGE);
1722 setProgressBar(null, chosenFile.hashCode());
1729 public void saveAsState_actionPerformed(ActionEvent e)
1731 saveState_actionPerformed(true);
1734 private void setProjectFile(File choice)
1736 this.projectFile = choice;
1739 public File getProjectFile()
1741 return this.projectFile;
1745 * Shows a file chooser dialog and tries to read in the selected file as a
1749 public void loadState_actionPerformed()
1751 final String[] suffix = new String[] { "jvp", "jar" };
1752 final String[] desc = new String[] { "Jalview Project",
1753 "Jalview Project (old)" };
1754 JalviewFileChooser chooser = new JalviewFileChooser(
1755 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1756 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1760 chooser.setFileView(new JalviewFileView());
1761 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1762 chooser.setResponseHandler(0, new Runnable()
1767 File selectedFile = chooser.getSelectedFile();
1768 setProjectFile(selectedFile);
1769 String choice = selectedFile.getAbsolutePath();
1770 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1771 new Thread(new Runnable()
1778 new Jalview2XML().loadJalviewAlign(selectedFile);
1779 } catch (OutOfMemoryError oom)
1781 new OOMWarning("Whilst loading project from " + choice, oom);
1782 } catch (Exception ex)
1785 "Problems whilst loading project from " + choice, ex);
1786 JvOptionPane.showMessageDialog(Desktop.desktop,
1787 MessageManager.formatMessage(
1788 "label.error_whilst_loading_project_from",
1792 .getString("label.couldnt_load_project"),
1793 JvOptionPane.WARNING_MESSAGE);
1796 }, "Project Loader").start();
1800 chooser.showOpenDialog(this);
1804 public void inputSequence_actionPerformed(ActionEvent e)
1806 new SequenceFetcher(this);
1809 JPanel progressPanel;
1811 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1813 public void startLoading(final Object fileName)
1815 if (fileLoadingCount == 0)
1817 fileLoadingPanels.add(addProgressPanel(MessageManager
1818 .formatMessage("label.loading_file", new Object[]
1824 private JPanel addProgressPanel(String string)
1826 if (progressPanel == null)
1828 progressPanel = new JPanel(new GridLayout(1, 1));
1829 totalProgressCount = 0;
1830 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1832 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1833 JProgressBar progressBar = new JProgressBar();
1834 progressBar.setIndeterminate(true);
1836 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1838 thisprogress.add(progressBar, BorderLayout.CENTER);
1839 progressPanel.add(thisprogress);
1840 ((GridLayout) progressPanel.getLayout()).setRows(
1841 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1842 ++totalProgressCount;
1843 instance.validate();
1844 return thisprogress;
1847 int totalProgressCount = 0;
1849 private void removeProgressPanel(JPanel progbar)
1851 if (progressPanel != null)
1853 synchronized (progressPanel)
1855 progressPanel.remove(progbar);
1856 GridLayout gl = (GridLayout) progressPanel.getLayout();
1857 gl.setRows(gl.getRows() - 1);
1858 if (--totalProgressCount < 1)
1860 this.getContentPane().remove(progressPanel);
1861 progressPanel = null;
1868 public void stopLoading()
1871 if (fileLoadingCount < 1)
1873 while (fileLoadingPanels.size() > 0)
1875 removeProgressPanel(fileLoadingPanels.remove(0));
1877 fileLoadingPanels.clear();
1878 fileLoadingCount = 0;
1883 public static int getViewCount(String alignmentId)
1885 AlignmentViewport[] aps = getViewports(alignmentId);
1886 return (aps == null) ? 0 : aps.length;
1891 * @param alignmentId
1892 * - if null, all sets are returned
1893 * @return all AlignmentPanels concerning the alignmentId sequence set
1895 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1897 if (Desktop.desktop == null)
1899 // no frames created and in headless mode
1900 // TODO: verify that frames are recoverable when in headless mode
1903 List<AlignmentPanel> aps = new ArrayList<>();
1904 AlignFrame[] frames = getAlignFrames();
1909 for (AlignFrame af : frames)
1911 for (AlignmentPanel ap : af.alignPanels)
1913 if (alignmentId == null
1914 || alignmentId.equals(ap.av.getSequenceSetId()))
1920 if (aps.size() == 0)
1924 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1929 * get all the viewports on an alignment.
1931 * @param sequenceSetId
1932 * unique alignment id (may be null - all viewports returned in that
1934 * @return all viewports on the alignment bound to sequenceSetId
1936 public static AlignmentViewport[] getViewports(String sequenceSetId)
1938 List<AlignmentViewport> viewp = new ArrayList<>();
1939 if (desktop != null)
1941 AlignFrame[] frames = Desktop.getAlignFrames();
1943 for (AlignFrame afr : frames)
1945 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
1946 .equals(sequenceSetId))
1948 if (afr.alignPanels != null)
1950 for (AlignmentPanel ap : afr.alignPanels)
1952 if (sequenceSetId == null
1953 || sequenceSetId.equals(ap.av.getSequenceSetId()))
1961 viewp.add(afr.getViewport());
1965 if (viewp.size() > 0)
1967 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1974 * Explode the views in the given frame into separate AlignFrame
1978 public static void explodeViews(AlignFrame af)
1980 int size = af.alignPanels.size();
1986 // FIXME: ideally should use UI interface API
1987 FeatureSettings viewFeatureSettings = (af.featureSettings != null
1988 && af.featureSettings.isOpen()) ? af.featureSettings : null;
1989 Rectangle fsBounds = af.getFeatureSettingsGeometry();
1990 for (int i = 0; i < size; i++)
1992 AlignmentPanel ap = af.alignPanels.get(i);
1994 AlignFrame newaf = new AlignFrame(ap);
1996 // transfer reference for existing feature settings to new alignFrame
1997 if (ap == af.alignPanel)
1999 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2001 newaf.featureSettings = viewFeatureSettings;
2003 newaf.setFeatureSettingsGeometry(fsBounds);
2007 * Restore the view's last exploded frame geometry if known. Multiple
2008 * views from one exploded frame share and restore the same (frame)
2009 * position and size.
2011 Rectangle geometry = ap.av.getExplodedGeometry();
2012 if (geometry != null)
2014 newaf.setBounds(geometry);
2017 ap.av.setGatherViewsHere(false);
2019 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2020 AlignFrame.DEFAULT_HEIGHT);
2021 // and materialise a new feature settings dialog instance for the new
2023 // (closes the old as if 'OK' was pressed)
2024 if (ap == af.alignPanel && newaf.featureSettings != null
2025 && newaf.featureSettings.isOpen()
2026 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2028 newaf.showFeatureSettingsUI();
2032 af.featureSettings = null;
2033 af.alignPanels.clear();
2034 af.closeMenuItem_actionPerformed(true);
2039 * Gather expanded views (separate AlignFrame's) with the same sequence set
2040 * identifier back in to this frame as additional views, and close the
2041 * expanded views. Note the expanded frames may themselves have multiple
2042 * views. We take the lot.
2046 public void gatherViews(AlignFrame source)
2048 source.viewport.setGatherViewsHere(true);
2049 source.viewport.setExplodedGeometry(source.getBounds());
2050 JInternalFrame[] frames = desktop.getAllFrames();
2051 String viewId = source.viewport.getSequenceSetId();
2052 for (int t = 0; t < frames.length; t++)
2054 if (frames[t] instanceof AlignFrame && frames[t] != source)
2056 AlignFrame af = (AlignFrame) frames[t];
2057 boolean gatherThis = false;
2058 for (int a = 0; a < af.alignPanels.size(); a++)
2060 AlignmentPanel ap = af.alignPanels.get(a);
2061 if (viewId.equals(ap.av.getSequenceSetId()))
2064 ap.av.setGatherViewsHere(false);
2065 ap.av.setExplodedGeometry(af.getBounds());
2066 source.addAlignmentPanel(ap, false);
2072 if (af.featureSettings != null && af.featureSettings.isOpen())
2074 if (source.featureSettings == null)
2076 // preserve the feature settings geometry for this frame
2077 source.featureSettings = af.featureSettings;
2078 source.setFeatureSettingsGeometry(
2079 af.getFeatureSettingsGeometry());
2083 // close it and forget
2084 af.featureSettings.close();
2087 af.alignPanels.clear();
2088 af.closeMenuItem_actionPerformed(true);
2093 // refresh the feature setting UI for the source frame if it exists
2094 if (source.featureSettings != null && source.featureSettings.isOpen())
2096 source.showFeatureSettingsUI();
2100 public JInternalFrame[] getAllFrames()
2102 return desktop.getAllFrames();
2106 * Checks the given url to see if it gives a response indicating that the user
2107 * should be informed of a new questionnaire.
2111 public void checkForQuestionnaire(String url)
2113 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2114 // javax.swing.SwingUtilities.invokeLater(jvq);
2115 new Thread(jvq).start();
2118 public void checkURLLinks()
2120 // Thread off the URL link checker
2121 addDialogThread(new Runnable()
2126 if (Cache.getDefault("CHECKURLLINKS", true))
2128 // check what the actual links are - if it's just the default don't
2129 // bother with the warning
2130 List<String> links = Preferences.sequenceUrlLinks
2133 // only need to check links if there is one with a
2134 // SEQUENCE_ID which is not the default EMBL_EBI link
2135 ListIterator<String> li = links.listIterator();
2136 boolean check = false;
2137 List<JLabel> urls = new ArrayList<>();
2138 while (li.hasNext())
2140 String link = li.next();
2141 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2142 && !UrlConstants.isDefaultString(link))
2145 int barPos = link.indexOf("|");
2146 String urlMsg = barPos == -1 ? link
2147 : link.substring(0, barPos) + ": "
2148 + link.substring(barPos + 1);
2149 urls.add(new JLabel(urlMsg));
2157 // ask user to check in case URL links use old style tokens
2158 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2159 JPanel msgPanel = new JPanel();
2160 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2161 msgPanel.add(Box.createVerticalGlue());
2162 JLabel msg = new JLabel(MessageManager
2163 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2164 JLabel msg2 = new JLabel(MessageManager
2165 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2167 for (JLabel url : urls)
2173 final JCheckBox jcb = new JCheckBox(
2174 MessageManager.getString("label.do_not_display_again"));
2175 jcb.addActionListener(new ActionListener()
2178 public void actionPerformed(ActionEvent e)
2180 // update Cache settings for "don't show this again"
2181 boolean showWarningAgain = !jcb.isSelected();
2182 Cache.setProperty("CHECKURLLINKS",
2183 Boolean.valueOf(showWarningAgain).toString());
2188 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2190 .getString("label.SEQUENCE_ID_no_longer_used"),
2191 JvOptionPane.WARNING_MESSAGE);
2198 * Proxy class for JDesktopPane which optionally displays the current memory
2199 * usage and highlights the desktop area with a red bar if free memory runs
2204 public class MyDesktopPane extends JDesktopPane implements Runnable
2206 private static final float ONE_MB = 1048576f;
2208 boolean showMemoryUsage = false;
2212 java.text.NumberFormat df;
2214 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2217 public MyDesktopPane(boolean showMemoryUsage)
2219 showMemoryUsage(showMemoryUsage);
2222 public void showMemoryUsage(boolean showMemory)
2224 this.showMemoryUsage = showMemory;
2227 Thread worker = new Thread(this);
2233 public boolean isShowMemoryUsage()
2235 return showMemoryUsage;
2241 df = java.text.NumberFormat.getNumberInstance();
2242 df.setMaximumFractionDigits(2);
2243 runtime = Runtime.getRuntime();
2245 while (showMemoryUsage)
2249 maxMemory = runtime.maxMemory() / ONE_MB;
2250 allocatedMemory = runtime.totalMemory() / ONE_MB;
2251 freeMemory = runtime.freeMemory() / ONE_MB;
2252 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2254 percentUsage = (totalFreeMemory / maxMemory) * 100;
2256 // if (percentUsage < 20)
2258 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2260 // instance.set.setBorder(border1);
2263 // sleep after showing usage
2265 } catch (Exception ex)
2267 ex.printStackTrace();
2273 public void paintComponent(Graphics g)
2275 if (showMemoryUsage && g != null && df != null)
2277 if (percentUsage < 20)
2279 g.setColor(Color.red);
2281 FontMetrics fm = g.getFontMetrics();
2284 g.drawString(MessageManager.formatMessage("label.memory_stats",
2286 { df.format(totalFreeMemory), df.format(maxMemory),
2287 df.format(percentUsage) }),
2288 10, getHeight() - fm.getHeight());
2292 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2293 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2298 * Accessor method to quickly get all the AlignmentFrames loaded.
2300 * @return an array of AlignFrame, or null if none found
2302 public static AlignFrame[] getAlignFrames()
2304 if (Jalview.isHeadlessMode())
2306 // Desktop.desktop is null in headless mode
2307 return new AlignFrame[] { Jalview.currentAlignFrame };
2310 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2316 List<AlignFrame> avp = new ArrayList<>();
2318 for (int i = frames.length - 1; i > -1; i--)
2320 if (frames[i] instanceof AlignFrame)
2322 avp.add((AlignFrame) frames[i]);
2324 else if (frames[i] instanceof SplitFrame)
2327 * Also check for a split frame containing an AlignFrame
2329 GSplitFrame sf = (GSplitFrame) frames[i];
2330 if (sf.getTopFrame() instanceof AlignFrame)
2332 avp.add((AlignFrame) sf.getTopFrame());
2334 if (sf.getBottomFrame() instanceof AlignFrame)
2336 avp.add((AlignFrame) sf.getBottomFrame());
2340 if (avp.size() == 0)
2344 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2349 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2353 public GStructureViewer[] getJmols()
2355 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2361 List<GStructureViewer> avp = new ArrayList<>();
2363 for (int i = frames.length - 1; i > -1; i--)
2365 if (frames[i] instanceof AppJmol)
2367 GStructureViewer af = (GStructureViewer) frames[i];
2371 if (avp.size() == 0)
2375 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2380 * Add Groovy Support to Jalview
2383 public void groovyShell_actionPerformed()
2387 openGroovyConsole();
2388 } catch (Exception ex)
2390 Cache.log.error("Groovy Shell Creation failed.", ex);
2391 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2393 MessageManager.getString("label.couldnt_create_groovy_shell"),
2394 MessageManager.getString("label.groovy_support_failed"),
2395 JvOptionPane.ERROR_MESSAGE);
2400 * Open the Groovy console
2402 void openGroovyConsole()
2404 if (groovyConsole == null)
2406 groovyConsole = new groovy.ui.Console();
2407 groovyConsole.setVariable("Jalview", this);
2408 groovyConsole.run();
2411 * We allow only one console at a time, so that AlignFrame menu option
2412 * 'Calculate | Run Groovy script' is unambiguous.
2413 * Disable 'Groovy Console', and enable 'Run script', when the console is
2414 * opened, and the reverse when it is closed
2416 Window window = (Window) groovyConsole.getFrame();
2417 window.addWindowListener(new WindowAdapter()
2420 public void windowClosed(WindowEvent e)
2423 * rebind CMD-Q from Groovy Console to Jalview Quit
2426 enableExecuteGroovy(false);
2432 * show Groovy console window (after close and reopen)
2434 ((Window) groovyConsole.getFrame()).setVisible(true);
2437 * if we got this far, enable 'Run Groovy' in AlignFrame menus
2438 * and disable opening a second console
2440 enableExecuteGroovy(true);
2444 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2445 * binding when opened
2447 protected void addQuitHandler()
2450 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2452 .getKeyStroke(KeyEvent.VK_Q,
2453 jalview.util.ShortcutKeyMaskExWrapper
2454 .getMenuShortcutKeyMaskEx()),
2456 getRootPane().getActionMap().put("Quit", new AbstractAction()
2459 public void actionPerformed(ActionEvent e)
2467 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2470 * true if Groovy console is open
2472 public void enableExecuteGroovy(boolean enabled)
2475 * disable opening a second Groovy console
2476 * (or re-enable when the console is closed)
2478 groovyShell.setEnabled(!enabled);
2480 AlignFrame[] alignFrames = getAlignFrames();
2481 if (alignFrames != null)
2483 for (AlignFrame af : alignFrames)
2485 af.setGroovyEnabled(enabled);
2491 * Progress bars managed by the IProgressIndicator method.
2493 private Hashtable<Long, JPanel> progressBars;
2495 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2500 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2503 public void setProgressBar(String message, long id)
2505 // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
2507 if (progressBars == null)
2509 progressBars = new Hashtable<>();
2510 progressBarHandlers = new Hashtable<>();
2513 if (progressBars.get(Long.valueOf(id)) != null)
2515 JPanel panel = progressBars.remove(Long.valueOf(id));
2516 if (progressBarHandlers.contains(Long.valueOf(id)))
2518 progressBarHandlers.remove(Long.valueOf(id));
2520 removeProgressPanel(panel);
2524 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2531 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2532 * jalview.gui.IProgressIndicatorHandler)
2535 public void registerHandler(final long id,
2536 final IProgressIndicatorHandler handler)
2538 if (progressBarHandlers == null
2539 || !progressBars.containsKey(Long.valueOf(id)))
2541 throw new Error(MessageManager.getString(
2542 "error.call_setprogressbar_before_registering_handler"));
2544 progressBarHandlers.put(Long.valueOf(id), handler);
2545 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2546 if (handler.canCancel())
2548 JButton cancel = new JButton(
2549 MessageManager.getString("action.cancel"));
2550 final IProgressIndicator us = this;
2551 cancel.addActionListener(new ActionListener()
2555 public void actionPerformed(ActionEvent e)
2557 handler.cancelActivity(id);
2558 us.setProgressBar(MessageManager
2559 .formatMessage("label.cancelled_params", new Object[]
2560 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2564 progressPanel.add(cancel, BorderLayout.EAST);
2570 * @return true if any progress bars are still active
2573 public boolean operationInProgress()
2575 if (progressBars != null && progressBars.size() > 0)
2583 * This will return the first AlignFrame holding the given viewport instance.
2584 * It will break if there are more than one AlignFrames viewing a particular
2588 * @return alignFrame for viewport
2590 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2592 if (desktop != null)
2594 AlignmentPanel[] aps = getAlignmentPanels(
2595 viewport.getSequenceSetId());
2596 for (int panel = 0; aps != null && panel < aps.length; panel++)
2598 if (aps[panel] != null && aps[panel].av == viewport)
2600 return aps[panel].alignFrame;
2607 public VamsasApplication getVamsasApplication()
2609 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2615 * flag set if jalview GUI is being operated programmatically
2617 private boolean inBatchMode = false;
2620 * check if jalview GUI is being operated programmatically
2622 * @return inBatchMode
2624 public boolean isInBatchMode()
2630 * set flag if jalview GUI is being operated programmatically
2632 * @param inBatchMode
2634 public void setInBatchMode(boolean inBatchMode)
2636 this.inBatchMode = inBatchMode;
2639 public void startServiceDiscovery()
2641 startServiceDiscovery(false);
2644 public void startServiceDiscovery(boolean blocking)
2646 boolean alive = true;
2647 Thread t0 = null, t1 = null, t2 = null;
2648 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2651 // todo: changesupport handlers need to be transferred
2652 if (discoverer == null)
2654 discoverer = new jalview.ws.jws1.Discoverer();
2655 // register PCS handler for desktop.
2656 discoverer.addPropertyChangeListener(changeSupport);
2658 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2659 // until we phase out completely
2660 (t0 = new Thread(discoverer)).start();
2663 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2665 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2666 .startDiscoverer(changeSupport);
2670 // TODO: do rest service discovery
2679 } catch (Exception e)
2682 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2683 || (t3 != null && t3.isAlive())
2684 || (t0 != null && t0.isAlive());
2690 * called to check if the service discovery process completed successfully.
2694 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2696 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2698 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2699 .getErrorMessages();
2702 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2704 if (serviceChangedDialog == null)
2706 // only run if we aren't already displaying one of these.
2707 addDialogThread(serviceChangedDialog = new Runnable()
2714 * JalviewDialog jd =new JalviewDialog() {
2716 * @Override protected void cancelPressed() { // TODO
2717 * Auto-generated method stub
2719 * }@Override protected void okPressed() { // TODO
2720 * Auto-generated method stub
2722 * }@Override protected void raiseClosed() { // TODO
2723 * Auto-generated method stub
2725 * } }; jd.initDialogFrame(new
2726 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2727 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2728 * + " or mis-configured HTTP proxy settings.<br/>" +
2729 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
2731 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
2732 * ), true, true, "Web Service Configuration Problem", 450,
2735 * jd.waitForInput();
2737 JvOptionPane.showConfirmDialog(Desktop.desktop,
2738 new JLabel("<html><table width=\"450\"><tr><td>"
2739 + ermsg + "</td></tr></table>"
2740 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2741 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2742 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2743 + " Tools->Preferences dialog box to change them.</p></html>"),
2744 "Web Service Configuration Problem",
2745 JvOptionPane.DEFAULT_OPTION,
2746 JvOptionPane.ERROR_MESSAGE);
2747 serviceChangedDialog = null;
2756 "Errors reported by JABA discovery service. Check web services preferences.\n"
2763 private Runnable serviceChangedDialog = null;
2766 * start a thread to open a URL in the configured browser. Pops up a warning
2767 * dialog to the user if there is an exception when calling out to the browser
2772 public static void showUrl(final String url)
2774 showUrl(url, Desktop.instance);
2778 * Like showUrl but allows progress handler to be specified
2782 * (null) or object implementing IProgressIndicator
2784 public static void showUrl(final String url,
2785 final IProgressIndicator progress)
2787 new Thread(new Runnable()
2794 if (progress != null)
2796 progress.setProgressBar(MessageManager
2797 .formatMessage("status.opening_params", new Object[]
2798 { url }), this.hashCode());
2800 jalview.util.BrowserLauncher.openURL(url);
2801 } catch (Exception ex)
2803 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2805 .getString("label.web_browser_not_found_unix"),
2806 MessageManager.getString("label.web_browser_not_found"),
2807 JvOptionPane.WARNING_MESSAGE);
2809 ex.printStackTrace();
2811 if (progress != null)
2813 progress.setProgressBar(null, this.hashCode());
2819 public static WsParamSetManager wsparamManager = null;
2821 public static ParamManager getUserParameterStore()
2823 if (wsparamManager == null)
2825 wsparamManager = new WsParamSetManager();
2827 return wsparamManager;
2831 * static hyperlink handler proxy method for use by Jalview's internal windows
2835 public static void hyperlinkUpdate(HyperlinkEvent e)
2837 if (e.getEventType() == EventType.ACTIVATED)
2842 url = e.getURL().toString();
2843 Desktop.showUrl(url);
2844 } catch (Exception x)
2848 if (Cache.log != null)
2850 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2855 "Couldn't handle string " + url + " as a URL.");
2858 // ignore any exceptions due to dud links.
2865 * single thread that handles display of dialogs to user.
2867 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2870 * flag indicating if dialogExecutor should try to acquire a permit
2872 private volatile boolean dialogPause = true;
2877 private java.util.concurrent.Semaphore block = new Semaphore(0);
2879 private static groovy.ui.Console groovyConsole;
2882 * add another dialog thread to the queue
2886 public void addDialogThread(final Runnable prompter)
2888 dialogExecutor.submit(new Runnable()
2898 } catch (InterruptedException x)
2902 if (instance == null)
2908 SwingUtilities.invokeAndWait(prompter);
2909 } catch (Exception q)
2911 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2917 public void startDialogQueue()
2919 // set the flag so we don't pause waiting for another permit and semaphore
2920 // the current task to begin
2921 dialogPause = false;
2926 * Outputs an image of the desktop to file in EPS format, after prompting the
2927 * user for choice of Text or Lineart character rendering (unless a preference
2928 * has been set). The file name is generated as
2931 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2935 protected void snapShotWindow_actionPerformed(ActionEvent e)
2937 // currently the menu option to do this is not shown
2940 int width = getWidth();
2941 int height = getHeight();
2943 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2944 ImageWriterI writer = new ImageWriterI()
2947 public void exportImage(Graphics g) throws Exception
2950 Cache.log.info("Successfully written snapshot to file "
2951 + of.getAbsolutePath());
2954 String title = "View of desktop";
2955 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
2957 exporter.doExport(of, this, width, height, title);
2961 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2962 * This respects (remembers) any previous 'exploded geometry' i.e. the size
2963 * and location last time the view was expanded (if any). However it does not
2964 * remember the split pane divider location - this is set to match the
2965 * 'exploding' frame.
2969 public void explodeViews(SplitFrame sf)
2971 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2972 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2973 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
2975 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
2977 int viewCount = topPanels.size();
2984 * Processing in reverse order works, forwards order leaves the first panels
2985 * not visible. I don't know why!
2987 for (int i = viewCount - 1; i >= 0; i--)
2990 * Make new top and bottom frames. These take over the respective
2991 * AlignmentPanel objects, including their AlignmentViewports, so the
2992 * cdna/protein relationships between the viewports is carried over to the
2995 * explodedGeometry holds the (x, y) position of the previously exploded
2996 * SplitFrame, and the (width, height) of the AlignFrame component
2998 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
2999 AlignFrame newTopFrame = new AlignFrame(topPanel);
3000 newTopFrame.setSize(oldTopFrame.getSize());
3001 newTopFrame.setVisible(true);
3002 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3003 .getExplodedGeometry();
3004 if (geometry != null)
3006 newTopFrame.setSize(geometry.getSize());
3009 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3010 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3011 newBottomFrame.setSize(oldBottomFrame.getSize());
3012 newBottomFrame.setVisible(true);
3013 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3014 .getExplodedGeometry();
3015 if (geometry != null)
3017 newBottomFrame.setSize(geometry.getSize());
3020 topPanel.av.setGatherViewsHere(false);
3021 bottomPanel.av.setGatherViewsHere(false);
3022 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3024 if (geometry != null)
3026 splitFrame.setLocation(geometry.getLocation());
3028 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3032 * Clear references to the panels (now relocated in the new SplitFrames)
3033 * before closing the old SplitFrame.
3036 bottomPanels.clear();
3041 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3042 * back into the given SplitFrame as additional views. Note that the gathered
3043 * frames may themselves have multiple views.
3047 public void gatherViews(GSplitFrame source)
3050 * special handling of explodedGeometry for a view within a SplitFrame: - it
3051 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3052 * height) of the AlignFrame component
3054 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3055 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3056 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3057 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3058 myBottomFrame.viewport
3059 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3060 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3061 myTopFrame.viewport.setGatherViewsHere(true);
3062 myBottomFrame.viewport.setGatherViewsHere(true);
3063 String topViewId = myTopFrame.viewport.getSequenceSetId();
3064 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3066 JInternalFrame[] frames = desktop.getAllFrames();
3067 for (JInternalFrame frame : frames)
3069 if (frame instanceof SplitFrame && frame != source)
3071 SplitFrame sf = (SplitFrame) frame;
3072 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3073 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3074 boolean gatherThis = false;
3075 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3077 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3078 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3079 if (topViewId.equals(topPanel.av.getSequenceSetId())
3080 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3083 topPanel.av.setGatherViewsHere(false);
3084 bottomPanel.av.setGatherViewsHere(false);
3085 topPanel.av.setExplodedGeometry(
3086 new Rectangle(sf.getLocation(), topFrame.getSize()));
3087 bottomPanel.av.setExplodedGeometry(
3088 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3089 myTopFrame.addAlignmentPanel(topPanel, false);
3090 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3096 topFrame.getAlignPanels().clear();
3097 bottomFrame.getAlignPanels().clear();
3104 * The dust settles...give focus to the tab we did this from.
3106 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3109 public static groovy.ui.Console getGroovyConsole()
3111 return groovyConsole;
3115 * handles the payload of a drag and drop event.
3117 * TODO refactor to desktop utilities class
3120 * - Data source strings extracted from the drop event
3122 * - protocol for each data source extracted from the drop event
3126 * - the payload from the drop event
3129 public static void transferFromDropTarget(List<Object> files,
3130 List<DataSourceType> protocols, DropTargetDropEvent evt,
3131 Transferable t) throws Exception
3134 // BH 2018 changed List<String> to List<Object> to allow for File from
3137 // DataFlavor[] flavors = t.getTransferDataFlavors();
3138 // for (int i = 0; i < flavors.length; i++) {
3139 // if (flavors[i].isFlavorJavaFileListType()) {
3140 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3141 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3142 // for (int j = 0; j < list.size(); j++) {
3143 // File file = (File) list.get(j);
3144 // byte[] data = getDroppedFileBytes(file);
3145 // fileName.setText(file.getName() + " - " + data.length + " " +
3146 // evt.getLocation());
3147 // JTextArea target = (JTextArea) ((DropTarget)
3148 // evt.getSource()).getComponent();
3149 // target.setText(new String(data));
3151 // dtde.dropComplete(true);
3156 DataFlavor uriListFlavor = new DataFlavor(
3157 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3160 urlFlavour = new DataFlavor(
3161 "application/x-java-url; class=java.net.URL");
3162 } catch (ClassNotFoundException cfe)
3164 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3167 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3172 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3173 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3174 // means url may be null.
3177 protocols.add(DataSourceType.URL);
3178 files.add(url.toString());
3179 Cache.log.debug("Drop handled as URL dataflavor "
3180 + files.get(files.size() - 1));
3185 if (Platform.isAMacAndNotJS())
3188 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3191 } catch (Throwable ex)
3193 Cache.log.debug("URL drop handler failed.", ex);
3196 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3198 // Works on Windows and MacOSX
3199 Cache.log.debug("Drop handled as javaFileListFlavor");
3200 for (Object file : (List) t
3201 .getTransferData(DataFlavor.javaFileListFlavor))
3204 protocols.add(DataSourceType.FILE);
3209 // Unix like behaviour
3210 boolean added = false;
3212 if (t.isDataFlavorSupported(uriListFlavor))
3214 Cache.log.debug("Drop handled as uriListFlavor");
3215 // This is used by Unix drag system
3216 data = (String) t.getTransferData(uriListFlavor);
3220 // fallback to text: workaround - on OSX where there's a JVM bug
3221 Cache.log.debug("standard URIListFlavor failed. Trying text");
3222 // try text fallback
3223 DataFlavor textDf = new DataFlavor(
3224 "text/plain;class=java.lang.String");
3225 if (t.isDataFlavorSupported(textDf))
3227 data = (String) t.getTransferData(textDf);
3230 Cache.log.debug("Plain text drop content returned "
3231 + (data == null ? "Null - failed" : data));
3236 while (protocols.size() < files.size())
3238 Cache.log.debug("Adding missing FILE protocol for "
3239 + files.get(protocols.size()));
3240 protocols.add(DataSourceType.FILE);
3242 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3243 data, "\r\n"); st.hasMoreTokens();)
3246 String s = st.nextToken();
3247 if (s.startsWith("#"))
3249 // the line is a comment (as per the RFC 2483)
3252 java.net.URI uri = new java.net.URI(s);
3253 if (uri.getScheme().toLowerCase().startsWith("http"))
3255 protocols.add(DataSourceType.URL);
3256 files.add(uri.toString());
3260 // otherwise preserve old behaviour: catch all for file objects
3261 java.io.File file = new java.io.File(uri);
3262 protocols.add(DataSourceType.FILE);
3263 files.add(file.toString());
3268 if (Cache.log.isDebugEnabled())
3270 if (data == null || !added)
3273 if (t.getTransferDataFlavors() != null
3274 && t.getTransferDataFlavors().length > 0)
3277 "Couldn't resolve drop data. Here are the supported flavors:");
3278 for (DataFlavor fl : t.getTransferDataFlavors())
3281 "Supported transfer dataflavor: " + fl.toString());
3282 Object df = t.getTransferData(fl);
3285 Cache.log.debug("Retrieves: " + df);
3289 Cache.log.debug("Retrieved nothing");
3295 Cache.log.debug("Couldn't resolve dataflavor for drop: "
3301 if (Platform.isWindowsAndNotJS())
3303 Cache.log.debug("Scanning dropped content for Windows Link Files");
3305 // resolve any .lnk files in the file drop
3306 for (int f = 0; f < files.size(); f++)
3308 String source = files.get(f).toString().toLowerCase();
3309 if (protocols.get(f).equals(DataSourceType.FILE)
3310 && (source.endsWith(".lnk") || source.endsWith(".url")
3311 || source.endsWith(".site")))
3315 Object obj = files.get(f);
3316 File lf = (obj instanceof File ? (File) obj
3317 : new File((String) obj));
3318 // process link file to get a URL
3319 Cache.log.debug("Found potential link file: " + lf);
3320 WindowsShortcut wscfile = new WindowsShortcut(lf);
3321 String fullname = wscfile.getRealFilename();
3322 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3323 files.set(f, fullname);
3324 Cache.log.debug("Parsed real filename " + fullname
3325 + " to extract protocol: " + protocols.get(f));
3326 } catch (Exception ex)
3329 "Couldn't parse " + files.get(f) + " as a link file.",
3338 * Sets the Preferences property for experimental features to True or False
3339 * depending on the state of the controlling menu item
3342 protected void showExperimental_actionPerformed(boolean selected)
3344 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3348 * Answers a (possibly empty) list of any structure viewer frames (currently
3349 * for either Jmol or Chimera) which are currently open. This may optionally
3350 * be restricted to viewers of a specified class, or viewers linked to a
3351 * specified alignment panel.
3354 * if not null, only return viewers linked to this panel
3355 * @param structureViewerClass
3356 * if not null, only return viewers of this class
3359 public List<StructureViewerBase> getStructureViewers(
3360 AlignmentPanel apanel,
3361 Class<? extends StructureViewerBase> structureViewerClass)
3363 List<StructureViewerBase> result = new ArrayList<>();
3364 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3366 for (JInternalFrame frame : frames)
3368 if (frame instanceof StructureViewerBase)
3370 if (structureViewerClass == null
3371 || structureViewerClass.isInstance(frame))
3374 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3376 result.add((StructureViewerBase) frame);
3384 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3386 private static boolean debugScaleMessageDone = false;
3388 public static void debugScaleMessage(Graphics g)
3390 if (debugScaleMessageDone)
3394 // output used by tests to check HiDPI scaling settings in action
3397 Graphics2D gg = (Graphics2D) g;
3400 AffineTransform t = gg.getTransform();
3401 double scaleX = t.getScaleX();
3402 double scaleY = t.getScaleY();
3403 Cache.debug(debugScaleMessage + scaleX + " (X)");
3404 Cache.debug(debugScaleMessage + scaleY + " (Y)");
3405 debugScaleMessageDone = true;
3409 Cache.debug("Desktop graphics null");
3411 } catch (Exception e)
3413 Cache.debug(Cache.getStackTraceString(e));