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;
143 URL bg_logo_url = ChannelProperties.getImageURL(
144 "bg_logo." + String.valueOf(SplashScreen.logoSize));
145 URL uod_logo_url = ChannelProperties.getImageURL(
146 "uod_banner." + String.valueOf(SplashScreen.logoSize));
147 boolean logo = (bg_logo_url != null || uod_logo_url != null);
148 StringBuilder sb = new StringBuilder();
150 "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.");
155 sb.append(bg_logo_url == null ? ""
156 : "<img alt=\"Barton Group logo\" src=\""
157 + bg_logo_url.toString() + "\">");
158 sb.append(uod_logo_url == null ? ""
159 : " <img alt=\"University of Dundee shield\" src=\""
160 + uod_logo_url.toString() + "\">");
162 "<br><br>For help, see the FAQ at <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and/or join the jalview-discuss@jalview.org mailing list");
163 sb.append("<br><br>If you use Jalview, please cite:"
164 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
165 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
166 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033");
167 CITATION = sb.toString();
170 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
172 private static int DEFAULT_MIN_WIDTH = 300;
174 private static int DEFAULT_MIN_HEIGHT = 250;
176 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
178 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
180 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
182 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
184 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
186 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
188 public static boolean nosplash = false;
191 * news reader - null if it was never started.
193 private BlogReader jvnews = null;
195 private File projectFile;
199 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
201 public void addJalviewPropertyChangeListener(
202 PropertyChangeListener listener)
204 changeSupport.addJalviewPropertyChangeListener(listener);
208 * @param propertyName
210 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
211 * java.beans.PropertyChangeListener)
213 public void addJalviewPropertyChangeListener(String propertyName,
214 PropertyChangeListener listener)
216 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
220 * @param propertyName
222 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
223 * java.beans.PropertyChangeListener)
225 public void removeJalviewPropertyChangeListener(String propertyName,
226 PropertyChangeListener listener)
228 changeSupport.removeJalviewPropertyChangeListener(propertyName,
232 /** Singleton Desktop instance */
233 public static Desktop instance;
235 public static MyDesktopPane desktop;
237 public static MyDesktopPane getDesktop()
239 // BH 2018 could use currentThread() here as a reference to a
240 // Hashtable<Thread, MyDesktopPane> in JavaScript
244 static int openFrameCount = 0;
246 static final int xOffset = 30;
248 static final int yOffset = 30;
250 public static jalview.ws.jws1.Discoverer discoverer;
252 public static Object[] jalviewClipboard;
254 public static boolean internalCopy = false;
256 static int fileLoadingCount = 0;
258 class MyDesktopManager implements DesktopManager
261 private DesktopManager delegate;
263 public MyDesktopManager(DesktopManager delegate)
265 this.delegate = delegate;
269 public void activateFrame(JInternalFrame f)
273 delegate.activateFrame(f);
274 } catch (NullPointerException npe)
276 Point p = getMousePosition();
277 instance.showPasteMenu(p.x, p.y);
282 public void beginDraggingFrame(JComponent f)
284 delegate.beginDraggingFrame(f);
288 public void beginResizingFrame(JComponent f, int direction)
290 delegate.beginResizingFrame(f, direction);
294 public void closeFrame(JInternalFrame f)
296 delegate.closeFrame(f);
300 public void deactivateFrame(JInternalFrame f)
302 delegate.deactivateFrame(f);
306 public void deiconifyFrame(JInternalFrame f)
308 delegate.deiconifyFrame(f);
312 public void dragFrame(JComponent f, int newX, int newY)
318 delegate.dragFrame(f, newX, newY);
322 public void endDraggingFrame(JComponent f)
324 delegate.endDraggingFrame(f);
329 public void endResizingFrame(JComponent f)
331 delegate.endResizingFrame(f);
336 public void iconifyFrame(JInternalFrame f)
338 delegate.iconifyFrame(f);
342 public void maximizeFrame(JInternalFrame f)
344 delegate.maximizeFrame(f);
348 public void minimizeFrame(JInternalFrame f)
350 delegate.minimizeFrame(f);
354 public void openFrame(JInternalFrame f)
356 delegate.openFrame(f);
360 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
367 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
371 public void setBoundsForFrame(JComponent f, int newX, int newY,
372 int newWidth, int newHeight)
374 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
377 // All other methods, simply delegate
382 * Creates a new Desktop object.
388 * A note to implementors. It is ESSENTIAL that any activities that might
389 * block are spawned off as threads rather than waited for during this
394 doConfigureStructurePrefs();
395 setTitle(ChannelProperties.getProperty("app_name") + " "
396 + Cache.getProperty("VERSION"));
398 if (!Platform.isAMac())
400 // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
404 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
410 APQHandlers.setAPQHandlers(this);
411 } catch (Throwable t)
413 System.out.println("Error setting APQHandlers: " + t.toString());
414 // t.printStackTrace();
416 setIconImages(ChannelProperties.getIconList());
418 addWindowListener(new WindowAdapter()
422 public void windowClosing(WindowEvent ev)
428 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
430 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
431 desktop = new MyDesktopPane(selmemusage);
433 showMemusage.setSelected(selmemusage);
434 desktop.setBackground(Color.white);
436 this.setIconImages(ChannelProperties.getIconList());
438 getContentPane().setLayout(new BorderLayout());
439 // alternate config - have scrollbars - see notes in JAL-153
440 // JScrollPane sp = new JScrollPane();
441 // sp.getViewport().setView(desktop);
442 // getContentPane().add(sp, BorderLayout.CENTER);
444 // BH 2018 - just an experiment to try unclipped JInternalFrames.
447 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
450 getContentPane().add(desktop, BorderLayout.CENTER);
451 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
453 // This line prevents Windows Look&Feel resizing all new windows to maximum
454 // if previous window was maximised
455 desktop.setDesktopManager(new MyDesktopManager(
456 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
457 : Platform.isAMacAndNotJS()
458 ? new AquaInternalFrameManager(
459 desktop.getDesktopManager())
460 : desktop.getDesktopManager())));
462 Rectangle dims = getLastKnownDimensions("");
469 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
470 int xPos = Math.max(5, (screenSize.width - 900) / 2);
471 int yPos = Math.max(5, (screenSize.height - 650) / 2);
472 setBounds(xPos, yPos, 900, 650);
475 if (!Platform.isJS())
482 jconsole = new Console(this, showjconsole);
483 jconsole.setHeader(Cache.getVersionDetailsForConsole());
484 showConsole(showjconsole);
486 showNews.setVisible(false);
488 experimentalFeatures.setSelected(showExperimental());
490 getIdentifiersOrgData();
494 // Spawn a thread that shows the splashscreen
497 SwingUtilities.invokeLater(new Runnable()
502 new SplashScreen(true);
507 // Thread off a new instance of the file chooser - this reduces the time
509 // takes to open it later on.
510 new Thread(new Runnable()
515 Cache.log.debug("Filechooser init thread started.");
516 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
517 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
519 Cache.log.debug("Filechooser init thread finished.");
522 // Add the service change listener
523 changeSupport.addJalviewPropertyChangeListener("services",
524 new PropertyChangeListener()
528 public void propertyChange(PropertyChangeEvent evt)
530 Cache.log.debug("Firing service changed event for "
531 + evt.getNewValue());
532 JalviewServicesChanged(evt);
537 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
539 this.addWindowListener(new WindowAdapter()
542 public void windowClosing(WindowEvent evt)
549 this.addMouseListener(ma = new MouseAdapter()
552 public void mousePressed(MouseEvent evt)
554 if (evt.isPopupTrigger()) // Mac
556 showPasteMenu(evt.getX(), evt.getY());
561 public void mouseReleased(MouseEvent evt)
563 if (evt.isPopupTrigger()) // Windows
565 showPasteMenu(evt.getX(), evt.getY());
569 desktop.addMouseListener(ma);
573 * Answers true if user preferences to enable experimental features is True
578 public boolean showExperimental()
580 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
581 Boolean.FALSE.toString());
582 return Boolean.valueOf(experimental).booleanValue();
585 public void doConfigureStructurePrefs()
587 // configure services
588 StructureSelectionManager ssm = StructureSelectionManager
589 .getStructureSelectionManager(this);
590 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
592 ssm.setAddTempFacAnnot(
593 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
594 ssm.setProcessSecondaryStructure(
595 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
596 ssm.setSecStructServices(
597 Cache.getDefault(Preferences.USE_RNAVIEW, true));
601 ssm.setAddTempFacAnnot(false);
602 ssm.setProcessSecondaryStructure(false);
603 ssm.setSecStructServices(false);
607 public void checkForNews()
609 final Desktop me = this;
610 // Thread off the news reader, in case there are connection problems.
611 new Thread(new Runnable()
616 Cache.log.debug("Starting news thread.");
617 jvnews = new BlogReader(me);
618 showNews.setVisible(true);
619 Cache.log.debug("Completed news thread.");
624 public void getIdentifiersOrgData()
626 // Thread off the identifiers fetcher
627 new Thread(new Runnable()
632 Cache.log.debug("Downloading data from identifiers.org");
635 UrlDownloadClient.download(IdOrgSettings.getUrl(),
636 IdOrgSettings.getDownloadLocation());
637 } catch (IOException e)
639 Cache.log.debug("Exception downloading identifiers.org data"
648 protected void showNews_actionPerformed(ActionEvent e)
650 showNews(showNews.isSelected());
653 void showNews(boolean visible)
655 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
656 showNews.setSelected(visible);
657 if (visible && !jvnews.isVisible())
659 new Thread(new Runnable()
664 long now = System.currentTimeMillis();
665 Desktop.instance.setProgressBar(
666 MessageManager.getString("status.refreshing_news"), now);
667 jvnews.refreshNews();
668 Desktop.instance.setProgressBar(null, now);
676 * recover the last known dimensions for a jalview window
679 * - empty string is desktop, all other windows have unique prefix
680 * @return null or last known dimensions scaled to current geometry (if last
681 * window geom was known)
683 Rectangle getLastKnownDimensions(String windowName)
685 // TODO: lock aspect ratio for scaling desktop Bug #0058199
686 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
687 String x = Cache.getProperty(windowName + "SCREEN_X");
688 String y = Cache.getProperty(windowName + "SCREEN_Y");
689 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
690 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
691 if ((x != null) && (y != null) && (width != null) && (height != null))
693 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
694 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
695 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
697 // attempt #1 - try to cope with change in screen geometry - this
698 // version doesn't preserve original jv aspect ratio.
699 // take ratio of current screen size vs original screen size.
700 double sw = ((1f * screenSize.width) / (1f * Integer
701 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
702 double sh = ((1f * screenSize.height) / (1f * Integer
703 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
704 // rescale the bounds depending upon the current screen geometry.
705 ix = (int) (ix * sw);
706 iw = (int) (iw * sw);
707 iy = (int) (iy * sh);
708 ih = (int) (ih * sh);
709 while (ix >= screenSize.width)
712 "Window geometry location recall error: shifting horizontal to within screenbounds.");
713 ix -= screenSize.width;
715 while (iy >= screenSize.height)
718 "Window geometry location recall error: shifting vertical to within screenbounds.");
719 iy -= screenSize.height;
722 "Got last known dimensions for " + windowName + ": x:" + ix
723 + " y:" + iy + " width:" + iw + " height:" + ih);
725 // return dimensions for new instance
726 return new Rectangle(ix, iy, iw, ih);
731 void showPasteMenu(int x, int y)
733 JPopupMenu popup = new JPopupMenu();
734 JMenuItem item = new JMenuItem(
735 MessageManager.getString("label.paste_new_window"));
736 item.addActionListener(new ActionListener()
739 public void actionPerformed(ActionEvent evt)
746 popup.show(this, x, y);
753 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
754 Transferable contents = c.getContents(this);
756 if (contents != null)
758 String file = (String) contents
759 .getTransferData(DataFlavor.stringFlavor);
761 FileFormatI format = new IdentifyFile().identify(file,
762 DataSourceType.PASTE);
764 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
767 } catch (Exception ex)
770 "Unable to paste alignment from system clipboard:\n" + ex);
775 * Adds and opens the given frame to the desktop
786 public static synchronized void addInternalFrame(
787 final JInternalFrame frame, String title, int w, int h)
789 addInternalFrame(frame, title, true, w, h, true, false);
793 * Add an internal frame to the Jalview desktop
800 * When true, display frame immediately, otherwise, caller must call
801 * setVisible themselves.
807 public static synchronized void addInternalFrame(
808 final JInternalFrame frame, String title, boolean makeVisible,
811 addInternalFrame(frame, title, makeVisible, w, h, true, false);
815 * Add an internal frame to the Jalview desktop and make it visible
828 public static synchronized void addInternalFrame(
829 final JInternalFrame frame, String title, int w, int h,
832 addInternalFrame(frame, title, true, w, h, resizable, false);
836 * Add an internal frame to the Jalview desktop
843 * When true, display frame immediately, otherwise, caller must call
844 * setVisible themselves.
851 * @param ignoreMinSize
852 * Do not set the default minimum size for frame
854 public static synchronized void addInternalFrame(
855 final JInternalFrame frame, String title, boolean makeVisible,
856 int w, int h, boolean resizable, boolean ignoreMinSize)
859 // TODO: allow callers to determine X and Y position of frame (eg. via
861 // TODO: consider fixing method to update entries in the window submenu with
862 // the current window title
864 frame.setTitle(title);
865 if (frame.getWidth() < 1 || frame.getHeight() < 1)
869 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
870 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
871 // IF JALVIEW IS RUNNING HEADLESS
872 // ///////////////////////////////////////////////
873 if (instance == null || (System.getProperty("java.awt.headless") != null
874 && System.getProperty("java.awt.headless").equals("true")))
883 frame.setMinimumSize(
884 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
886 // Set default dimension for Alignment Frame window.
887 // The Alignment Frame window could be added from a number of places,
889 // I did this here in order not to miss out on any Alignment frame.
890 if (frame instanceof AlignFrame)
892 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
893 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
897 frame.setVisible(makeVisible);
898 frame.setClosable(true);
899 frame.setResizable(resizable);
900 frame.setMaximizable(resizable);
901 frame.setIconifiable(resizable);
902 frame.setOpaque(Platform.isJS());
904 if (frame.getX() < 1 && frame.getY() < 1)
906 frame.setLocation(xOffset * openFrameCount,
907 yOffset * ((openFrameCount - 1) % 10) + yOffset);
911 * add an entry for the new frame in the Window menu
912 * (and remove it when the frame is closed)
914 final JMenuItem menuItem = new JMenuItem(title);
915 frame.addInternalFrameListener(new InternalFrameAdapter()
918 public void internalFrameActivated(InternalFrameEvent evt)
920 JInternalFrame itf = desktop.getSelectedFrame();
923 if (itf instanceof AlignFrame)
925 Jalview.setCurrentAlignFrame((AlignFrame) itf);
932 public void internalFrameClosed(InternalFrameEvent evt)
934 PaintRefresher.RemoveComponent(frame);
937 * defensive check to prevent frames being
938 * added half off the window
940 if (openFrameCount > 0)
946 * ensure no reference to alignFrame retained by menu item listener
948 if (menuItem.getActionListeners().length > 0)
950 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
952 windowMenu.remove(menuItem);
956 menuItem.addActionListener(new ActionListener()
959 public void actionPerformed(ActionEvent e)
963 frame.setSelected(true);
964 frame.setIcon(false);
965 } catch (java.beans.PropertyVetoException ex)
967 // System.err.println(ex.toString());
972 setKeyBindings(frame);
976 windowMenu.add(menuItem);
981 frame.setSelected(true);
982 frame.requestFocus();
983 } catch (java.beans.PropertyVetoException ve)
985 } catch (java.lang.ClassCastException cex)
988 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
994 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
999 private static void setKeyBindings(JInternalFrame frame)
1001 @SuppressWarnings("serial")
1002 final Action closeAction = new AbstractAction()
1005 public void actionPerformed(ActionEvent e)
1012 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1014 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1015 InputEvent.CTRL_DOWN_MASK);
1016 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1017 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1019 InputMap inputMap = frame
1020 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1021 String ctrlW = ctrlWKey.toString();
1022 inputMap.put(ctrlWKey, ctrlW);
1023 inputMap.put(cmdWKey, ctrlW);
1025 ActionMap actionMap = frame.getActionMap();
1026 actionMap.put(ctrlW, closeAction);
1030 public void lostOwnership(Clipboard clipboard, Transferable contents)
1034 Desktop.jalviewClipboard = null;
1037 internalCopy = false;
1041 public void dragEnter(DropTargetDragEvent evt)
1046 public void dragExit(DropTargetEvent evt)
1051 public void dragOver(DropTargetDragEvent evt)
1056 public void dropActionChanged(DropTargetDragEvent evt)
1067 public void drop(DropTargetDropEvent evt)
1069 boolean success = true;
1070 // JAL-1552 - acceptDrop required before getTransferable call for
1071 // Java's Transferable for native dnd
1072 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1073 Transferable t = evt.getTransferable();
1074 List<Object> files = new ArrayList<>();
1075 List<DataSourceType> protocols = new ArrayList<>();
1079 Desktop.transferFromDropTarget(files, protocols, evt, t);
1080 } catch (Exception e)
1082 e.printStackTrace();
1090 for (int i = 0; i < files.size(); i++)
1092 // BH 2018 File or String
1093 Object file = files.get(i);
1094 String fileName = file.toString();
1095 DataSourceType protocol = (protocols == null)
1096 ? DataSourceType.FILE
1098 FileFormatI format = null;
1100 if (fileName.endsWith(".jar"))
1102 format = FileFormat.Jalview;
1107 format = new IdentifyFile().identify(file, protocol);
1109 if (file instanceof File)
1111 Platform.cacheFileData((File) file);
1113 new FileLoader().LoadFile(null, file, protocol, format);
1116 } catch (Exception ex)
1121 evt.dropComplete(success); // need this to ensure input focus is properly
1122 // transfered to any new windows created
1132 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1134 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1135 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1136 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1137 BackupFiles.getEnabled());
1139 chooser.setFileView(new JalviewFileView());
1140 chooser.setDialogTitle(
1141 MessageManager.getString("label.open_local_file"));
1142 chooser.setToolTipText(MessageManager.getString("action.open"));
1144 chooser.setResponseHandler(0, new Runnable()
1149 File selectedFile = chooser.getSelectedFile();
1150 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1152 FileFormatI format = chooser.getSelectedFormat();
1155 * Call IdentifyFile to verify the file contains what its extension implies.
1156 * Skip this step for dynamically added file formats, because
1157 * IdentifyFile does not know how to recognise them.
1159 if (FileFormats.getInstance().isIdentifiable(format))
1163 format = new IdentifyFile().identify(selectedFile,
1164 DataSourceType.FILE);
1165 } catch (FileFormatException e)
1167 // format = null; //??
1171 new FileLoader().LoadFile(viewport, selectedFile,
1172 DataSourceType.FILE, format);
1175 chooser.showOpenDialog(this);
1179 * Shows a dialog for input of a URL at which to retrieve alignment data
1184 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1186 // This construct allows us to have a wider textfield
1188 JLabel label = new JLabel(
1189 MessageManager.getString("label.input_file_url"));
1191 JPanel panel = new JPanel(new GridLayout(2, 1));
1195 * the URL to fetch is input in
1196 * Java: an editable combobox with history
1197 * JS: (pending JAL-3038) a plain text field
1200 String urlBase = "https://www.";
1201 if (Platform.isJS())
1203 history = new JTextField(urlBase, 35);
1212 JComboBox<String> asCombo = new JComboBox<>();
1213 asCombo.setPreferredSize(new Dimension(400, 20));
1214 asCombo.setEditable(true);
1215 asCombo.addItem(urlBase);
1216 String historyItems = Cache.getProperty("RECENT_URL");
1217 if (historyItems != null)
1219 for (String token : historyItems.split("\\t"))
1221 asCombo.addItem(token);
1228 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1229 MessageManager.getString("action.cancel") };
1230 Runnable action = new Runnable()
1235 @SuppressWarnings("unchecked")
1236 String url = (history instanceof JTextField
1237 ? ((JTextField) history).getText()
1238 : ((JComboBox<String>) history).getEditor().getItem()
1239 .toString().trim());
1241 if (url.toLowerCase().endsWith(".jar"))
1243 if (viewport != null)
1245 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1246 FileFormat.Jalview);
1250 new FileLoader().LoadFile(url, DataSourceType.URL,
1251 FileFormat.Jalview);
1256 FileFormatI format = null;
1259 format = new IdentifyFile().identify(url, DataSourceType.URL);
1260 } catch (FileFormatException e)
1262 // TODO revise error handling, distinguish between
1263 // URL not found and response not valid
1268 String msg = MessageManager
1269 .formatMessage("label.couldnt_locate", url);
1270 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1271 MessageManager.getString("label.url_not_found"),
1272 JvOptionPane.WARNING_MESSAGE);
1277 if (viewport != null)
1279 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1284 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1289 String dialogOption = MessageManager
1290 .getString("label.input_alignment_from_url");
1291 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1292 .showInternalDialog(panel, dialogOption,
1293 JvOptionPane.YES_NO_CANCEL_OPTION,
1294 JvOptionPane.PLAIN_MESSAGE, null, options,
1295 MessageManager.getString("action.ok"));
1299 * Opens the CutAndPaste window for the user to paste an alignment in to
1302 * - if not null, the pasted alignment is added to the current
1303 * alignment; if null, to a new alignment window
1306 public void inputTextboxMenuItem_actionPerformed(
1307 AlignmentViewPanel viewPanel)
1309 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1310 cap.setForInput(viewPanel);
1311 Desktop.addInternalFrame(cap,
1312 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1322 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1323 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1324 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1325 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1326 getWidth(), getHeight()));
1328 if (jconsole != null)
1330 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1331 jconsole.stopConsole();
1335 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1338 if (dialogExecutor != null)
1340 dialogExecutor.shutdownNow();
1342 closeAll_actionPerformed(null);
1344 if (groovyConsole != null)
1346 // suppress a possible repeat prompt to save script
1347 groovyConsole.setDirty(false);
1348 groovyConsole.exit();
1353 private void storeLastKnownDimensions(String string, Rectangle jc)
1355 Cache.log.debug("Storing last known dimensions for " + string + ": x:"
1356 + jc.x + " y:" + jc.y + " width:" + jc.width + " height:"
1359 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1360 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1361 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1362 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1372 public void aboutMenuItem_actionPerformed(ActionEvent e)
1374 new Thread(new Runnable()
1379 new SplashScreen(false);
1385 * Returns the html text for the About screen, including any available version
1386 * number, build details, author details and citation reference, but without
1387 * the enclosing {@code html} tags
1391 public String getAboutMessage()
1393 StringBuilder message = new StringBuilder(1024);
1394 message.append("<div style=\"font-family: sans-serif;\">")
1395 .append("<h1><strong>Version: ")
1396 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1397 .append("<strong>Built: <em>")
1398 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1399 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1400 .append("</strong>");
1402 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1403 if (latestVersion.equals("Checking"))
1405 // JBP removed this message for 2.11: May be reinstated in future version
1406 // message.append("<br>...Checking latest version...</br>");
1408 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1410 boolean red = false;
1411 if (Cache.getProperty("VERSION").toLowerCase()
1412 .indexOf("automated build") == -1)
1415 // Displayed when code version and jnlp version do not match and code
1416 // version is not a development build
1417 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1420 message.append("<br>!! Version ")
1421 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1422 .append(" is available for download from ")
1423 .append(Cache.getDefault("www.jalview.org",
1424 "https://www.jalview.org"))
1428 message.append("</div>");
1431 message.append("<br>Authors: ");
1432 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1433 message.append(CITATION);
1435 message.append("</div>");
1437 return message.toString();
1441 * Action on requesting Help documentation
1444 public void documentationMenuItem_actionPerformed()
1448 if (Platform.isJS())
1450 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1459 Help.showHelpWindow();
1461 } catch (Exception ex)
1463 System.err.println("Error opening help: " + ex.getMessage());
1468 public void closeAll_actionPerformed(ActionEvent e)
1470 // TODO show a progress bar while closing?
1471 JInternalFrame[] frames = desktop.getAllFrames();
1472 for (int i = 0; i < frames.length; i++)
1476 frames[i].setClosed(true);
1477 } catch (java.beans.PropertyVetoException ex)
1481 Jalview.setCurrentAlignFrame(null);
1482 System.out.println("ALL CLOSED");
1485 * reset state of singleton objects as appropriate (clear down session state
1486 * when all windows are closed)
1488 StructureSelectionManager ssm = StructureSelectionManager
1489 .getStructureSelectionManager(this);
1497 public void raiseRelated_actionPerformed(ActionEvent e)
1499 reorderAssociatedWindows(false, false);
1503 public void minimizeAssociated_actionPerformed(ActionEvent e)
1505 reorderAssociatedWindows(true, false);
1508 void closeAssociatedWindows()
1510 reorderAssociatedWindows(false, true);
1516 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1520 protected void garbageCollect_actionPerformed(ActionEvent e)
1522 // We simply collect the garbage
1523 Cache.log.debug("Collecting garbage...");
1525 Cache.log.debug("Finished garbage collection.");
1532 * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1536 protected void showMemusage_actionPerformed(ActionEvent e)
1538 desktop.showMemoryUsage(showMemusage.isSelected());
1545 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1549 protected void showConsole_actionPerformed(ActionEvent e)
1551 showConsole(showConsole.isSelected());
1554 Console jconsole = null;
1557 * control whether the java console is visible or not
1561 void showConsole(boolean selected)
1563 // TODO: decide if we should update properties file
1564 if (jconsole != null) // BH 2018
1566 showConsole.setSelected(selected);
1567 Cache.setProperty("SHOW_JAVA_CONSOLE",
1568 Boolean.valueOf(selected).toString());
1569 jconsole.setVisible(selected);
1573 void reorderAssociatedWindows(boolean minimize, boolean close)
1575 JInternalFrame[] frames = desktop.getAllFrames();
1576 if (frames == null || frames.length < 1)
1581 AlignmentViewport source = null, target = null;
1582 if (frames[0] instanceof AlignFrame)
1584 source = ((AlignFrame) frames[0]).getCurrentView();
1586 else if (frames[0] instanceof TreePanel)
1588 source = ((TreePanel) frames[0]).getViewPort();
1590 else if (frames[0] instanceof PCAPanel)
1592 source = ((PCAPanel) frames[0]).av;
1594 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1596 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1601 for (int i = 0; i < frames.length; i++)
1604 if (frames[i] == null)
1608 if (frames[i] instanceof AlignFrame)
1610 target = ((AlignFrame) frames[i]).getCurrentView();
1612 else if (frames[i] instanceof TreePanel)
1614 target = ((TreePanel) frames[i]).getViewPort();
1616 else if (frames[i] instanceof PCAPanel)
1618 target = ((PCAPanel) frames[i]).av;
1620 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1622 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1625 if (source == target)
1631 frames[i].setClosed(true);
1635 frames[i].setIcon(minimize);
1638 frames[i].toFront();
1642 } catch (java.beans.PropertyVetoException ex)
1657 protected void preferences_actionPerformed(ActionEvent e)
1659 Preferences.openPreferences();
1663 * Prompts the user to choose a file and then saves the Jalview state as a
1664 * Jalview project file
1667 public void saveState_actionPerformed()
1669 saveState_actionPerformed(false);
1672 public void saveState_actionPerformed(boolean saveAs)
1674 java.io.File projectFile = getProjectFile();
1675 // autoSave indicates we already have a file and don't need to ask
1676 boolean autoSave = projectFile != null && !saveAs
1677 && BackupFiles.getEnabled();
1679 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1680 // saveAs="+saveAs+", Backups
1681 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1683 boolean approveSave = false;
1686 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1689 chooser.setFileView(new JalviewFileView());
1690 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1692 int value = chooser.showSaveDialog(this);
1694 if (value == JalviewFileChooser.APPROVE_OPTION)
1696 projectFile = chooser.getSelectedFile();
1697 setProjectFile(projectFile);
1702 if (approveSave || autoSave)
1704 final Desktop me = this;
1705 final java.io.File chosenFile = projectFile;
1706 new Thread(new Runnable()
1711 // TODO: refactor to Jalview desktop session controller action.
1712 setProgressBar(MessageManager.formatMessage(
1713 "label.saving_jalview_project", new Object[]
1714 { chosenFile.getName() }), chosenFile.hashCode());
1715 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1716 // TODO catch and handle errors for savestate
1717 // TODO prevent user from messing with the Desktop whilst we're saving
1720 boolean doBackup = BackupFiles.getEnabled();
1721 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1724 new Jalview2XML().saveState(
1725 doBackup ? backupfiles.getTempFile() : chosenFile);
1729 backupfiles.setWriteSuccess(true);
1730 backupfiles.rollBackupsAndRenameTempFile();
1732 } catch (OutOfMemoryError oom)
1734 new OOMWarning("Whilst saving current state to "
1735 + chosenFile.getName(), oom);
1736 } catch (Exception ex)
1738 Cache.log.error("Problems whilst trying to save to "
1739 + chosenFile.getName(), ex);
1740 JvOptionPane.showMessageDialog(me,
1741 MessageManager.formatMessage(
1742 "label.error_whilst_saving_current_state_to",
1744 { chosenFile.getName() }),
1745 MessageManager.getString("label.couldnt_save_project"),
1746 JvOptionPane.WARNING_MESSAGE);
1748 setProgressBar(null, chosenFile.hashCode());
1755 public void saveAsState_actionPerformed(ActionEvent e)
1757 saveState_actionPerformed(true);
1760 private void setProjectFile(File choice)
1762 this.projectFile = choice;
1765 public File getProjectFile()
1767 return this.projectFile;
1771 * Shows a file chooser dialog and tries to read in the selected file as a
1775 public void loadState_actionPerformed()
1777 final String[] suffix = new String[] { "jvp", "jar" };
1778 final String[] desc = new String[] { "Jalview Project",
1779 "Jalview Project (old)" };
1780 JalviewFileChooser chooser = new JalviewFileChooser(
1781 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1782 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1786 chooser.setFileView(new JalviewFileView());
1787 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1788 chooser.setResponseHandler(0, new Runnable()
1793 File selectedFile = chooser.getSelectedFile();
1794 setProjectFile(selectedFile);
1795 String choice = selectedFile.getAbsolutePath();
1796 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1797 new Thread(new Runnable()
1804 new Jalview2XML().loadJalviewAlign(selectedFile);
1805 } catch (OutOfMemoryError oom)
1807 new OOMWarning("Whilst loading project from " + choice, oom);
1808 } catch (Exception ex)
1811 "Problems whilst loading project from " + choice, ex);
1812 JvOptionPane.showMessageDialog(Desktop.desktop,
1813 MessageManager.formatMessage(
1814 "label.error_whilst_loading_project_from",
1818 .getString("label.couldnt_load_project"),
1819 JvOptionPane.WARNING_MESSAGE);
1822 }, "Project Loader").start();
1826 chooser.showOpenDialog(this);
1830 public void inputSequence_actionPerformed(ActionEvent e)
1832 new SequenceFetcher(this);
1835 JPanel progressPanel;
1837 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1839 public void startLoading(final Object fileName)
1841 if (fileLoadingCount == 0)
1843 fileLoadingPanels.add(addProgressPanel(MessageManager
1844 .formatMessage("label.loading_file", new Object[]
1850 private JPanel addProgressPanel(String string)
1852 if (progressPanel == null)
1854 progressPanel = new JPanel(new GridLayout(1, 1));
1855 totalProgressCount = 0;
1856 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1858 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1859 JProgressBar progressBar = new JProgressBar();
1860 progressBar.setIndeterminate(true);
1862 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1864 thisprogress.add(progressBar, BorderLayout.CENTER);
1865 progressPanel.add(thisprogress);
1866 ((GridLayout) progressPanel.getLayout()).setRows(
1867 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1868 ++totalProgressCount;
1869 instance.validate();
1870 return thisprogress;
1873 int totalProgressCount = 0;
1875 private void removeProgressPanel(JPanel progbar)
1877 if (progressPanel != null)
1879 synchronized (progressPanel)
1881 progressPanel.remove(progbar);
1882 GridLayout gl = (GridLayout) progressPanel.getLayout();
1883 gl.setRows(gl.getRows() - 1);
1884 if (--totalProgressCount < 1)
1886 this.getContentPane().remove(progressPanel);
1887 progressPanel = null;
1894 public void stopLoading()
1897 if (fileLoadingCount < 1)
1899 while (fileLoadingPanels.size() > 0)
1901 removeProgressPanel(fileLoadingPanels.remove(0));
1903 fileLoadingPanels.clear();
1904 fileLoadingCount = 0;
1909 public static int getViewCount(String alignmentId)
1911 AlignmentViewport[] aps = getViewports(alignmentId);
1912 return (aps == null) ? 0 : aps.length;
1917 * @param alignmentId
1918 * - if null, all sets are returned
1919 * @return all AlignmentPanels concerning the alignmentId sequence set
1921 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1923 if (Desktop.desktop == null)
1925 // no frames created and in headless mode
1926 // TODO: verify that frames are recoverable when in headless mode
1929 List<AlignmentPanel> aps = new ArrayList<>();
1930 AlignFrame[] frames = getAlignFrames();
1935 for (AlignFrame af : frames)
1937 for (AlignmentPanel ap : af.alignPanels)
1939 if (alignmentId == null
1940 || alignmentId.equals(ap.av.getSequenceSetId()))
1946 if (aps.size() == 0)
1950 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1955 * get all the viewports on an alignment.
1957 * @param sequenceSetId
1958 * unique alignment id (may be null - all viewports returned in that
1960 * @return all viewports on the alignment bound to sequenceSetId
1962 public static AlignmentViewport[] getViewports(String sequenceSetId)
1964 List<AlignmentViewport> viewp = new ArrayList<>();
1965 if (desktop != null)
1967 AlignFrame[] frames = Desktop.getAlignFrames();
1969 for (AlignFrame afr : frames)
1971 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
1972 .equals(sequenceSetId))
1974 if (afr.alignPanels != null)
1976 for (AlignmentPanel ap : afr.alignPanels)
1978 if (sequenceSetId == null
1979 || sequenceSetId.equals(ap.av.getSequenceSetId()))
1987 viewp.add(afr.getViewport());
1991 if (viewp.size() > 0)
1993 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2000 * Explode the views in the given frame into separate AlignFrame
2004 public static void explodeViews(AlignFrame af)
2006 int size = af.alignPanels.size();
2012 // FIXME: ideally should use UI interface API
2013 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2014 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2015 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2016 for (int i = 0; i < size; i++)
2018 AlignmentPanel ap = af.alignPanels.get(i);
2020 AlignFrame newaf = new AlignFrame(ap);
2022 // transfer reference for existing feature settings to new alignFrame
2023 if (ap == af.alignPanel)
2025 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2027 newaf.featureSettings = viewFeatureSettings;
2029 newaf.setFeatureSettingsGeometry(fsBounds);
2033 * Restore the view's last exploded frame geometry if known. Multiple
2034 * views from one exploded frame share and restore the same (frame)
2035 * position and size.
2037 Rectangle geometry = ap.av.getExplodedGeometry();
2038 if (geometry != null)
2040 newaf.setBounds(geometry);
2043 ap.av.setGatherViewsHere(false);
2045 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2046 AlignFrame.DEFAULT_HEIGHT);
2047 // and materialise a new feature settings dialog instance for the new
2049 // (closes the old as if 'OK' was pressed)
2050 if (ap == af.alignPanel && newaf.featureSettings != null
2051 && newaf.featureSettings.isOpen()
2052 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2054 newaf.showFeatureSettingsUI();
2058 af.featureSettings = null;
2059 af.alignPanels.clear();
2060 af.closeMenuItem_actionPerformed(true);
2065 * Gather expanded views (separate AlignFrame's) with the same sequence set
2066 * identifier back in to this frame as additional views, and close the
2067 * expanded views. Note the expanded frames may themselves have multiple
2068 * views. We take the lot.
2072 public void gatherViews(AlignFrame source)
2074 source.viewport.setGatherViewsHere(true);
2075 source.viewport.setExplodedGeometry(source.getBounds());
2076 JInternalFrame[] frames = desktop.getAllFrames();
2077 String viewId = source.viewport.getSequenceSetId();
2078 for (int t = 0; t < frames.length; t++)
2080 if (frames[t] instanceof AlignFrame && frames[t] != source)
2082 AlignFrame af = (AlignFrame) frames[t];
2083 boolean gatherThis = false;
2084 for (int a = 0; a < af.alignPanels.size(); a++)
2086 AlignmentPanel ap = af.alignPanels.get(a);
2087 if (viewId.equals(ap.av.getSequenceSetId()))
2090 ap.av.setGatherViewsHere(false);
2091 ap.av.setExplodedGeometry(af.getBounds());
2092 source.addAlignmentPanel(ap, false);
2098 if (af.featureSettings != null && af.featureSettings.isOpen())
2100 if (source.featureSettings == null)
2102 // preserve the feature settings geometry for this frame
2103 source.featureSettings = af.featureSettings;
2104 source.setFeatureSettingsGeometry(
2105 af.getFeatureSettingsGeometry());
2109 // close it and forget
2110 af.featureSettings.close();
2113 af.alignPanels.clear();
2114 af.closeMenuItem_actionPerformed(true);
2119 // refresh the feature setting UI for the source frame if it exists
2120 if (source.featureSettings != null && source.featureSettings.isOpen())
2122 source.showFeatureSettingsUI();
2126 public JInternalFrame[] getAllFrames()
2128 return desktop.getAllFrames();
2132 * Checks the given url to see if it gives a response indicating that the user
2133 * should be informed of a new questionnaire.
2137 public void checkForQuestionnaire(String url)
2139 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2140 // javax.swing.SwingUtilities.invokeLater(jvq);
2141 new Thread(jvq).start();
2144 public void checkURLLinks()
2146 // Thread off the URL link checker
2147 addDialogThread(new Runnable()
2152 if (Cache.getDefault("CHECKURLLINKS", true))
2154 // check what the actual links are - if it's just the default don't
2155 // bother with the warning
2156 List<String> links = Preferences.sequenceUrlLinks
2159 // only need to check links if there is one with a
2160 // SEQUENCE_ID which is not the default EMBL_EBI link
2161 ListIterator<String> li = links.listIterator();
2162 boolean check = false;
2163 List<JLabel> urls = new ArrayList<>();
2164 while (li.hasNext())
2166 String link = li.next();
2167 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2168 && !UrlConstants.isDefaultString(link))
2171 int barPos = link.indexOf("|");
2172 String urlMsg = barPos == -1 ? link
2173 : link.substring(0, barPos) + ": "
2174 + link.substring(barPos + 1);
2175 urls.add(new JLabel(urlMsg));
2183 // ask user to check in case URL links use old style tokens
2184 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2185 JPanel msgPanel = new JPanel();
2186 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2187 msgPanel.add(Box.createVerticalGlue());
2188 JLabel msg = new JLabel(MessageManager
2189 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2190 JLabel msg2 = new JLabel(MessageManager
2191 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2193 for (JLabel url : urls)
2199 final JCheckBox jcb = new JCheckBox(
2200 MessageManager.getString("label.do_not_display_again"));
2201 jcb.addActionListener(new ActionListener()
2204 public void actionPerformed(ActionEvent e)
2206 // update Cache settings for "don't show this again"
2207 boolean showWarningAgain = !jcb.isSelected();
2208 Cache.setProperty("CHECKURLLINKS",
2209 Boolean.valueOf(showWarningAgain).toString());
2214 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2216 .getString("label.SEQUENCE_ID_no_longer_used"),
2217 JvOptionPane.WARNING_MESSAGE);
2224 * Proxy class for JDesktopPane which optionally displays the current memory
2225 * usage and highlights the desktop area with a red bar if free memory runs
2230 public class MyDesktopPane extends JDesktopPane implements Runnable
2232 private static final float ONE_MB = 1048576f;
2234 boolean showMemoryUsage = false;
2238 java.text.NumberFormat df;
2240 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2243 public MyDesktopPane(boolean showMemoryUsage)
2245 showMemoryUsage(showMemoryUsage);
2248 public void showMemoryUsage(boolean showMemory)
2250 this.showMemoryUsage = showMemory;
2253 Thread worker = new Thread(this);
2259 public boolean isShowMemoryUsage()
2261 return showMemoryUsage;
2267 df = java.text.NumberFormat.getNumberInstance();
2268 df.setMaximumFractionDigits(2);
2269 runtime = Runtime.getRuntime();
2271 while (showMemoryUsage)
2275 maxMemory = runtime.maxMemory() / ONE_MB;
2276 allocatedMemory = runtime.totalMemory() / ONE_MB;
2277 freeMemory = runtime.freeMemory() / ONE_MB;
2278 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2280 percentUsage = (totalFreeMemory / maxMemory) * 100;
2282 // if (percentUsage < 20)
2284 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2286 // instance.set.setBorder(border1);
2289 // sleep after showing usage
2291 } catch (Exception ex)
2293 ex.printStackTrace();
2299 public void paintComponent(Graphics g)
2301 if (showMemoryUsage && g != null && df != null)
2303 if (percentUsage < 20)
2305 g.setColor(Color.red);
2307 FontMetrics fm = g.getFontMetrics();
2310 g.drawString(MessageManager.formatMessage("label.memory_stats",
2312 { df.format(totalFreeMemory), df.format(maxMemory),
2313 df.format(percentUsage) }),
2314 10, getHeight() - fm.getHeight());
2318 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2319 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2324 * Accessor method to quickly get all the AlignmentFrames loaded.
2326 * @return an array of AlignFrame, or null if none found
2328 public static AlignFrame[] getAlignFrames()
2330 if (Jalview.isHeadlessMode())
2332 // Desktop.desktop is null in headless mode
2333 return new AlignFrame[] { Jalview.currentAlignFrame };
2336 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2342 List<AlignFrame> avp = new ArrayList<>();
2344 for (int i = frames.length - 1; i > -1; i--)
2346 if (frames[i] instanceof AlignFrame)
2348 avp.add((AlignFrame) frames[i]);
2350 else if (frames[i] instanceof SplitFrame)
2353 * Also check for a split frame containing an AlignFrame
2355 GSplitFrame sf = (GSplitFrame) frames[i];
2356 if (sf.getTopFrame() instanceof AlignFrame)
2358 avp.add((AlignFrame) sf.getTopFrame());
2360 if (sf.getBottomFrame() instanceof AlignFrame)
2362 avp.add((AlignFrame) sf.getBottomFrame());
2366 if (avp.size() == 0)
2370 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2375 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2379 public GStructureViewer[] getJmols()
2381 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2387 List<GStructureViewer> avp = new ArrayList<>();
2389 for (int i = frames.length - 1; i > -1; i--)
2391 if (frames[i] instanceof AppJmol)
2393 GStructureViewer af = (GStructureViewer) frames[i];
2397 if (avp.size() == 0)
2401 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2406 * Add Groovy Support to Jalview
2409 public void groovyShell_actionPerformed()
2413 openGroovyConsole();
2414 } catch (Exception ex)
2416 Cache.log.error("Groovy Shell Creation failed.", ex);
2417 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2419 MessageManager.getString("label.couldnt_create_groovy_shell"),
2420 MessageManager.getString("label.groovy_support_failed"),
2421 JvOptionPane.ERROR_MESSAGE);
2426 * Open the Groovy console
2428 void openGroovyConsole()
2430 if (groovyConsole == null)
2432 groovyConsole = new groovy.ui.Console();
2433 groovyConsole.setVariable("Jalview", this);
2434 groovyConsole.run();
2437 * We allow only one console at a time, so that AlignFrame menu option
2438 * 'Calculate | Run Groovy script' is unambiguous.
2439 * Disable 'Groovy Console', and enable 'Run script', when the console is
2440 * opened, and the reverse when it is closed
2442 Window window = (Window) groovyConsole.getFrame();
2443 window.addWindowListener(new WindowAdapter()
2446 public void windowClosed(WindowEvent e)
2449 * rebind CMD-Q from Groovy Console to Jalview Quit
2452 enableExecuteGroovy(false);
2458 * show Groovy console window (after close and reopen)
2460 ((Window) groovyConsole.getFrame()).setVisible(true);
2463 * if we got this far, enable 'Run Groovy' in AlignFrame menus
2464 * and disable opening a second console
2466 enableExecuteGroovy(true);
2470 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2471 * binding when opened
2473 protected void addQuitHandler()
2476 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2478 .getKeyStroke(KeyEvent.VK_Q,
2479 jalview.util.ShortcutKeyMaskExWrapper
2480 .getMenuShortcutKeyMaskEx()),
2482 getRootPane().getActionMap().put("Quit", new AbstractAction()
2485 public void actionPerformed(ActionEvent e)
2493 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2496 * true if Groovy console is open
2498 public void enableExecuteGroovy(boolean enabled)
2501 * disable opening a second Groovy console
2502 * (or re-enable when the console is closed)
2504 groovyShell.setEnabled(!enabled);
2506 AlignFrame[] alignFrames = getAlignFrames();
2507 if (alignFrames != null)
2509 for (AlignFrame af : alignFrames)
2511 af.setGroovyEnabled(enabled);
2517 * Progress bars managed by the IProgressIndicator method.
2519 private Hashtable<Long, JPanel> progressBars;
2521 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2526 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2529 public void setProgressBar(String message, long id)
2531 // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
2533 if (progressBars == null)
2535 progressBars = new Hashtable<>();
2536 progressBarHandlers = new Hashtable<>();
2539 if (progressBars.get(Long.valueOf(id)) != null)
2541 JPanel panel = progressBars.remove(Long.valueOf(id));
2542 if (progressBarHandlers.contains(Long.valueOf(id)))
2544 progressBarHandlers.remove(Long.valueOf(id));
2546 removeProgressPanel(panel);
2550 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2557 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2558 * jalview.gui.IProgressIndicatorHandler)
2561 public void registerHandler(final long id,
2562 final IProgressIndicatorHandler handler)
2564 if (progressBarHandlers == null
2565 || !progressBars.containsKey(Long.valueOf(id)))
2567 throw new Error(MessageManager.getString(
2568 "error.call_setprogressbar_before_registering_handler"));
2570 progressBarHandlers.put(Long.valueOf(id), handler);
2571 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2572 if (handler.canCancel())
2574 JButton cancel = new JButton(
2575 MessageManager.getString("action.cancel"));
2576 final IProgressIndicator us = this;
2577 cancel.addActionListener(new ActionListener()
2581 public void actionPerformed(ActionEvent e)
2583 handler.cancelActivity(id);
2584 us.setProgressBar(MessageManager
2585 .formatMessage("label.cancelled_params", new Object[]
2586 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2590 progressPanel.add(cancel, BorderLayout.EAST);
2596 * @return true if any progress bars are still active
2599 public boolean operationInProgress()
2601 if (progressBars != null && progressBars.size() > 0)
2609 * This will return the first AlignFrame holding the given viewport instance.
2610 * It will break if there are more than one AlignFrames viewing a particular
2614 * @return alignFrame for viewport
2616 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2618 if (desktop != null)
2620 AlignmentPanel[] aps = getAlignmentPanels(
2621 viewport.getSequenceSetId());
2622 for (int panel = 0; aps != null && panel < aps.length; panel++)
2624 if (aps[panel] != null && aps[panel].av == viewport)
2626 return aps[panel].alignFrame;
2633 public VamsasApplication getVamsasApplication()
2635 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2641 * flag set if jalview GUI is being operated programmatically
2643 private boolean inBatchMode = false;
2646 * check if jalview GUI is being operated programmatically
2648 * @return inBatchMode
2650 public boolean isInBatchMode()
2656 * set flag if jalview GUI is being operated programmatically
2658 * @param inBatchMode
2660 public void setInBatchMode(boolean inBatchMode)
2662 this.inBatchMode = inBatchMode;
2665 public void startServiceDiscovery()
2667 startServiceDiscovery(false);
2670 public void startServiceDiscovery(boolean blocking)
2672 boolean alive = true;
2673 Thread t0 = null, t1 = null, t2 = null;
2674 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2677 // todo: changesupport handlers need to be transferred
2678 if (discoverer == null)
2680 discoverer = new jalview.ws.jws1.Discoverer();
2681 // register PCS handler for desktop.
2682 discoverer.addPropertyChangeListener(changeSupport);
2684 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2685 // until we phase out completely
2686 (t0 = new Thread(discoverer)).start();
2689 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2691 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2692 .startDiscoverer(changeSupport);
2696 // TODO: do rest service discovery
2705 } catch (Exception e)
2708 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2709 || (t3 != null && t3.isAlive())
2710 || (t0 != null && t0.isAlive());
2716 * called to check if the service discovery process completed successfully.
2720 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2722 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2724 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2725 .getErrorMessages();
2728 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2730 if (serviceChangedDialog == null)
2732 // only run if we aren't already displaying one of these.
2733 addDialogThread(serviceChangedDialog = new Runnable()
2740 * JalviewDialog jd =new JalviewDialog() {
2742 * @Override protected void cancelPressed() { // TODO
2743 * Auto-generated method stub
2745 * }@Override protected void okPressed() { // TODO
2746 * Auto-generated method stub
2748 * }@Override protected void raiseClosed() { // TODO
2749 * Auto-generated method stub
2751 * } }; jd.initDialogFrame(new
2752 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2753 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2754 * + " or mis-configured HTTP proxy settings.<br/>" +
2755 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
2757 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
2758 * ), true, true, "Web Service Configuration Problem", 450,
2761 * jd.waitForInput();
2763 JvOptionPane.showConfirmDialog(Desktop.desktop,
2764 new JLabel("<html><table width=\"450\"><tr><td>"
2765 + ermsg + "</td></tr></table>"
2766 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2767 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2768 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2769 + " Tools->Preferences dialog box to change them.</p></html>"),
2770 "Web Service Configuration Problem",
2771 JvOptionPane.DEFAULT_OPTION,
2772 JvOptionPane.ERROR_MESSAGE);
2773 serviceChangedDialog = null;
2782 "Errors reported by JABA discovery service. Check web services preferences.\n"
2789 private Runnable serviceChangedDialog = null;
2792 * start a thread to open a URL in the configured browser. Pops up a warning
2793 * dialog to the user if there is an exception when calling out to the browser
2798 public static void showUrl(final String url)
2800 showUrl(url, Desktop.instance);
2804 * Like showUrl but allows progress handler to be specified
2808 * (null) or object implementing IProgressIndicator
2810 public static void showUrl(final String url,
2811 final IProgressIndicator progress)
2813 new Thread(new Runnable()
2820 if (progress != null)
2822 progress.setProgressBar(MessageManager
2823 .formatMessage("status.opening_params", new Object[]
2824 { url }), this.hashCode());
2826 jalview.util.BrowserLauncher.openURL(url);
2827 } catch (Exception ex)
2829 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2831 .getString("label.web_browser_not_found_unix"),
2832 MessageManager.getString("label.web_browser_not_found"),
2833 JvOptionPane.WARNING_MESSAGE);
2835 ex.printStackTrace();
2837 if (progress != null)
2839 progress.setProgressBar(null, this.hashCode());
2845 public static WsParamSetManager wsparamManager = null;
2847 public static ParamManager getUserParameterStore()
2849 if (wsparamManager == null)
2851 wsparamManager = new WsParamSetManager();
2853 return wsparamManager;
2857 * static hyperlink handler proxy method for use by Jalview's internal windows
2861 public static void hyperlinkUpdate(HyperlinkEvent e)
2863 if (e.getEventType() == EventType.ACTIVATED)
2868 url = e.getURL().toString();
2869 Desktop.showUrl(url);
2870 } catch (Exception x)
2874 if (Cache.log != null)
2876 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2881 "Couldn't handle string " + url + " as a URL.");
2884 // ignore any exceptions due to dud links.
2891 * single thread that handles display of dialogs to user.
2893 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2896 * flag indicating if dialogExecutor should try to acquire a permit
2898 private volatile boolean dialogPause = true;
2903 private java.util.concurrent.Semaphore block = new Semaphore(0);
2905 private static groovy.ui.Console groovyConsole;
2908 * add another dialog thread to the queue
2912 public void addDialogThread(final Runnable prompter)
2914 dialogExecutor.submit(new Runnable()
2924 } catch (InterruptedException x)
2928 if (instance == null)
2934 SwingUtilities.invokeAndWait(prompter);
2935 } catch (Exception q)
2937 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2943 public void startDialogQueue()
2945 // set the flag so we don't pause waiting for another permit and semaphore
2946 // the current task to begin
2947 dialogPause = false;
2952 * Outputs an image of the desktop to file in EPS format, after prompting the
2953 * user for choice of Text or Lineart character rendering (unless a preference
2954 * has been set). The file name is generated as
2957 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2961 protected void snapShotWindow_actionPerformed(ActionEvent e)
2963 // currently the menu option to do this is not shown
2966 int width = getWidth();
2967 int height = getHeight();
2969 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2970 ImageWriterI writer = new ImageWriterI()
2973 public void exportImage(Graphics g) throws Exception
2976 Cache.log.info("Successfully written snapshot to file "
2977 + of.getAbsolutePath());
2980 String title = "View of desktop";
2981 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
2983 exporter.doExport(of, this, width, height, title);
2987 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2988 * This respects (remembers) any previous 'exploded geometry' i.e. the size
2989 * and location last time the view was expanded (if any). However it does not
2990 * remember the split pane divider location - this is set to match the
2991 * 'exploding' frame.
2995 public void explodeViews(SplitFrame sf)
2997 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2998 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2999 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3001 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3003 int viewCount = topPanels.size();
3010 * Processing in reverse order works, forwards order leaves the first panels
3011 * not visible. I don't know why!
3013 for (int i = viewCount - 1; i >= 0; i--)
3016 * Make new top and bottom frames. These take over the respective
3017 * AlignmentPanel objects, including their AlignmentViewports, so the
3018 * cdna/protein relationships between the viewports is carried over to the
3021 * explodedGeometry holds the (x, y) position of the previously exploded
3022 * SplitFrame, and the (width, height) of the AlignFrame component
3024 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3025 AlignFrame newTopFrame = new AlignFrame(topPanel);
3026 newTopFrame.setSize(oldTopFrame.getSize());
3027 newTopFrame.setVisible(true);
3028 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3029 .getExplodedGeometry();
3030 if (geometry != null)
3032 newTopFrame.setSize(geometry.getSize());
3035 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3036 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3037 newBottomFrame.setSize(oldBottomFrame.getSize());
3038 newBottomFrame.setVisible(true);
3039 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3040 .getExplodedGeometry();
3041 if (geometry != null)
3043 newBottomFrame.setSize(geometry.getSize());
3046 topPanel.av.setGatherViewsHere(false);
3047 bottomPanel.av.setGatherViewsHere(false);
3048 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3050 if (geometry != null)
3052 splitFrame.setLocation(geometry.getLocation());
3054 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3058 * Clear references to the panels (now relocated in the new SplitFrames)
3059 * before closing the old SplitFrame.
3062 bottomPanels.clear();
3067 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3068 * back into the given SplitFrame as additional views. Note that the gathered
3069 * frames may themselves have multiple views.
3073 public void gatherViews(GSplitFrame source)
3076 * special handling of explodedGeometry for a view within a SplitFrame: - it
3077 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3078 * height) of the AlignFrame component
3080 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3081 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3082 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3083 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3084 myBottomFrame.viewport
3085 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3086 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3087 myTopFrame.viewport.setGatherViewsHere(true);
3088 myBottomFrame.viewport.setGatherViewsHere(true);
3089 String topViewId = myTopFrame.viewport.getSequenceSetId();
3090 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3092 JInternalFrame[] frames = desktop.getAllFrames();
3093 for (JInternalFrame frame : frames)
3095 if (frame instanceof SplitFrame && frame != source)
3097 SplitFrame sf = (SplitFrame) frame;
3098 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3099 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3100 boolean gatherThis = false;
3101 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3103 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3104 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3105 if (topViewId.equals(topPanel.av.getSequenceSetId())
3106 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3109 topPanel.av.setGatherViewsHere(false);
3110 bottomPanel.av.setGatherViewsHere(false);
3111 topPanel.av.setExplodedGeometry(
3112 new Rectangle(sf.getLocation(), topFrame.getSize()));
3113 bottomPanel.av.setExplodedGeometry(
3114 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3115 myTopFrame.addAlignmentPanel(topPanel, false);
3116 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3122 topFrame.getAlignPanels().clear();
3123 bottomFrame.getAlignPanels().clear();
3130 * The dust settles...give focus to the tab we did this from.
3132 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3135 public static groovy.ui.Console getGroovyConsole()
3137 return groovyConsole;
3141 * handles the payload of a drag and drop event.
3143 * TODO refactor to desktop utilities class
3146 * - Data source strings extracted from the drop event
3148 * - protocol for each data source extracted from the drop event
3152 * - the payload from the drop event
3155 public static void transferFromDropTarget(List<Object> files,
3156 List<DataSourceType> protocols, DropTargetDropEvent evt,
3157 Transferable t) throws Exception
3160 // BH 2018 changed List<String> to List<Object> to allow for File from
3163 // DataFlavor[] flavors = t.getTransferDataFlavors();
3164 // for (int i = 0; i < flavors.length; i++) {
3165 // if (flavors[i].isFlavorJavaFileListType()) {
3166 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3167 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3168 // for (int j = 0; j < list.size(); j++) {
3169 // File file = (File) list.get(j);
3170 // byte[] data = getDroppedFileBytes(file);
3171 // fileName.setText(file.getName() + " - " + data.length + " " +
3172 // evt.getLocation());
3173 // JTextArea target = (JTextArea) ((DropTarget)
3174 // evt.getSource()).getComponent();
3175 // target.setText(new String(data));
3177 // dtde.dropComplete(true);
3182 DataFlavor uriListFlavor = new DataFlavor(
3183 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3186 urlFlavour = new DataFlavor(
3187 "application/x-java-url; class=java.net.URL");
3188 } catch (ClassNotFoundException cfe)
3190 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3193 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3198 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3199 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3200 // means url may be null.
3203 protocols.add(DataSourceType.URL);
3204 files.add(url.toString());
3205 Cache.log.debug("Drop handled as URL dataflavor "
3206 + files.get(files.size() - 1));
3211 if (Platform.isAMacAndNotJS())
3214 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3217 } catch (Throwable ex)
3219 Cache.log.debug("URL drop handler failed.", ex);
3222 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3224 // Works on Windows and MacOSX
3225 Cache.log.debug("Drop handled as javaFileListFlavor");
3226 for (Object file : (List) t
3227 .getTransferData(DataFlavor.javaFileListFlavor))
3230 protocols.add(DataSourceType.FILE);
3235 // Unix like behaviour
3236 boolean added = false;
3238 if (t.isDataFlavorSupported(uriListFlavor))
3240 Cache.log.debug("Drop handled as uriListFlavor");
3241 // This is used by Unix drag system
3242 data = (String) t.getTransferData(uriListFlavor);
3246 // fallback to text: workaround - on OSX where there's a JVM bug
3247 Cache.log.debug("standard URIListFlavor failed. Trying text");
3248 // try text fallback
3249 DataFlavor textDf = new DataFlavor(
3250 "text/plain;class=java.lang.String");
3251 if (t.isDataFlavorSupported(textDf))
3253 data = (String) t.getTransferData(textDf);
3256 Cache.log.debug("Plain text drop content returned "
3257 + (data == null ? "Null - failed" : data));
3262 while (protocols.size() < files.size())
3264 Cache.log.debug("Adding missing FILE protocol for "
3265 + files.get(protocols.size()));
3266 protocols.add(DataSourceType.FILE);
3268 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3269 data, "\r\n"); st.hasMoreTokens();)
3272 String s = st.nextToken();
3273 if (s.startsWith("#"))
3275 // the line is a comment (as per the RFC 2483)
3278 java.net.URI uri = new java.net.URI(s);
3279 if (uri.getScheme().toLowerCase().startsWith("http"))
3281 protocols.add(DataSourceType.URL);
3282 files.add(uri.toString());
3286 // otherwise preserve old behaviour: catch all for file objects
3287 java.io.File file = new java.io.File(uri);
3288 protocols.add(DataSourceType.FILE);
3289 files.add(file.toString());
3294 if (Cache.log.isDebugEnabled())
3296 if (data == null || !added)
3299 if (t.getTransferDataFlavors() != null
3300 && t.getTransferDataFlavors().length > 0)
3303 "Couldn't resolve drop data. Here are the supported flavors:");
3304 for (DataFlavor fl : t.getTransferDataFlavors())
3307 "Supported transfer dataflavor: " + fl.toString());
3308 Object df = t.getTransferData(fl);
3311 Cache.log.debug("Retrieves: " + df);
3315 Cache.log.debug("Retrieved nothing");
3321 Cache.log.debug("Couldn't resolve dataflavor for drop: "
3327 if (Platform.isWindowsAndNotJS())
3329 Cache.log.debug("Scanning dropped content for Windows Link Files");
3331 // resolve any .lnk files in the file drop
3332 for (int f = 0; f < files.size(); f++)
3334 String source = files.get(f).toString().toLowerCase();
3335 if (protocols.get(f).equals(DataSourceType.FILE)
3336 && (source.endsWith(".lnk") || source.endsWith(".url")
3337 || source.endsWith(".site")))
3341 Object obj = files.get(f);
3342 File lf = (obj instanceof File ? (File) obj
3343 : new File((String) obj));
3344 // process link file to get a URL
3345 Cache.log.debug("Found potential link file: " + lf);
3346 WindowsShortcut wscfile = new WindowsShortcut(lf);
3347 String fullname = wscfile.getRealFilename();
3348 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3349 files.set(f, fullname);
3350 Cache.log.debug("Parsed real filename " + fullname
3351 + " to extract protocol: " + protocols.get(f));
3352 } catch (Exception ex)
3355 "Couldn't parse " + files.get(f) + " as a link file.",
3364 * Sets the Preferences property for experimental features to True or False
3365 * depending on the state of the controlling menu item
3368 protected void showExperimental_actionPerformed(boolean selected)
3370 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3374 * Answers a (possibly empty) list of any structure viewer frames (currently
3375 * for either Jmol or Chimera) which are currently open. This may optionally
3376 * be restricted to viewers of a specified class, or viewers linked to a
3377 * specified alignment panel.
3380 * if not null, only return viewers linked to this panel
3381 * @param structureViewerClass
3382 * if not null, only return viewers of this class
3385 public List<StructureViewerBase> getStructureViewers(
3386 AlignmentPanel apanel,
3387 Class<? extends StructureViewerBase> structureViewerClass)
3389 List<StructureViewerBase> result = new ArrayList<>();
3390 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3392 for (JInternalFrame frame : frames)
3394 if (frame instanceof StructureViewerBase)
3396 if (structureViewerClass == null
3397 || structureViewerClass.isInstance(frame))
3400 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3402 result.add((StructureViewerBase) frame);
3410 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3412 private static boolean debugScaleMessageDone = false;
3414 public static void debugScaleMessage(Graphics g)
3416 if (debugScaleMessageDone)
3420 // output used by tests to check HiDPI scaling settings in action
3423 Graphics2D gg = (Graphics2D) g;
3426 AffineTransform t = gg.getTransform();
3427 double scaleX = t.getScaleX();
3428 double scaleY = t.getScaleY();
3429 Cache.debug(debugScaleMessage + scaleX + " (X)");
3430 Cache.debug(debugScaleMessage + scaleY + " (Y)");
3431 debugScaleMessageDone = true;
3435 Cache.debug("Desktop graphics null");
3437 } catch (Exception e)
3439 Cache.debug(Cache.getStackTraceString(e));