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;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Vector;
66 import java.util.concurrent.ExecutorService;
67 import java.util.concurrent.Executors;
68 import java.util.concurrent.Semaphore;
70 import javax.swing.AbstractAction;
71 import javax.swing.Action;
72 import javax.swing.ActionMap;
73 import javax.swing.Box;
74 import javax.swing.BoxLayout;
75 import javax.swing.DefaultDesktopManager;
76 import javax.swing.DesktopManager;
77 import javax.swing.InputMap;
78 import javax.swing.JButton;
79 import javax.swing.JCheckBox;
80 import javax.swing.JComboBox;
81 import javax.swing.JComponent;
82 import javax.swing.JDesktopPane;
83 import javax.swing.JInternalFrame;
84 import javax.swing.JLabel;
85 import javax.swing.JMenuItem;
86 import javax.swing.JPanel;
87 import javax.swing.JPopupMenu;
88 import javax.swing.JProgressBar;
89 import javax.swing.JTextField;
90 import javax.swing.KeyStroke;
91 import javax.swing.SwingUtilities;
92 import javax.swing.event.HyperlinkEvent;
93 import javax.swing.event.HyperlinkEvent.EventType;
94 import javax.swing.event.InternalFrameAdapter;
95 import javax.swing.event.InternalFrameEvent;
97 import org.stackoverflowusers.file.WindowsShortcut;
99 import jalview.api.AlignViewportI;
100 import jalview.api.AlignmentViewPanel;
101 import jalview.bin.Cache;
102 import jalview.bin.Jalview;
103 import jalview.gui.ImageExporter.ImageWriterI;
104 import jalview.io.BackupFiles;
105 import jalview.io.DataSourceType;
106 import jalview.io.FileFormat;
107 import jalview.io.FileFormatException;
108 import jalview.io.FileFormatI;
109 import jalview.io.FileFormats;
110 import jalview.io.FileLoader;
111 import jalview.io.FormatAdapter;
112 import jalview.io.IdentifyFile;
113 import jalview.io.JalviewFileChooser;
114 import jalview.io.JalviewFileView;
115 import jalview.jbgui.GSplitFrame;
116 import jalview.jbgui.GStructureViewer;
117 import jalview.project.Jalview2XML;
118 import jalview.structure.StructureSelectionManager;
119 import jalview.urls.IdOrgSettings;
120 import jalview.util.BrowserLauncher;
121 import jalview.util.ChannelProperties;
122 import jalview.util.ImageMaker.TYPE;
123 import jalview.util.MessageManager;
124 import jalview.util.Platform;
125 import jalview.util.ShortcutKeyMaskExWrapper;
126 import jalview.util.UrlConstants;
127 import jalview.viewmodel.AlignmentViewport;
128 import jalview.ws.params.ParamManager;
129 import jalview.ws.utils.UrlDownloadClient;
136 * @version $Revision: 1.155 $
138 public class Desktop extends jalview.jbgui.GDesktop
139 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
140 jalview.api.StructureSelectionManagerProvider
142 private static final String CITATION;
145 URL bg_logo_url = ChannelProperties.getImageURL(
146 "bg_logo." + String.valueOf(SplashScreen.logoSize));
147 URL uod_logo_url = ChannelProperties.getImageURL(
148 "uod_banner." + String.valueOf(SplashScreen.logoSize));
149 boolean logo = (bg_logo_url != null || uod_logo_url != null);
150 StringBuilder sb = new StringBuilder();
152 "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.");
157 sb.append(bg_logo_url == null ? ""
158 : "<img alt=\"Barton Group logo\" src=\""
159 + bg_logo_url.toString() + "\">");
160 sb.append(uod_logo_url == null ? ""
161 : " <img alt=\"University of Dundee shield\" src=\""
162 + uod_logo_url.toString() + "\">");
164 "<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");
165 sb.append("<br><br>If you use Jalview, please cite:"
166 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
167 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
168 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033");
169 CITATION = sb.toString();
172 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
174 private static int DEFAULT_MIN_WIDTH = 300;
176 private static int DEFAULT_MIN_HEIGHT = 250;
178 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
180 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
182 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
184 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
186 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
188 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
190 public static boolean nosplash = false;
193 * news reader - null if it was never started.
195 private BlogReader jvnews = null;
197 private File projectFile;
201 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
203 public void addJalviewPropertyChangeListener(
204 PropertyChangeListener listener)
206 changeSupport.addJalviewPropertyChangeListener(listener);
210 * @param propertyName
212 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
213 * java.beans.PropertyChangeListener)
215 public void addJalviewPropertyChangeListener(String propertyName,
216 PropertyChangeListener listener)
218 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
222 * @param propertyName
224 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
225 * java.beans.PropertyChangeListener)
227 public void removeJalviewPropertyChangeListener(String propertyName,
228 PropertyChangeListener listener)
230 changeSupport.removeJalviewPropertyChangeListener(propertyName,
234 /** Singleton Desktop instance */
235 public static Desktop instance;
237 public static MyDesktopPane desktop;
239 public static MyDesktopPane getDesktop()
241 // BH 2018 could use currentThread() here as a reference to a
242 // Hashtable<Thread, MyDesktopPane> in JavaScript
246 static int openFrameCount = 0;
248 static final int xOffset = 30;
250 static final int yOffset = 30;
252 public static jalview.ws.jws1.Discoverer discoverer;
254 public static Object[] jalviewClipboard;
256 public static boolean internalCopy = false;
258 static int fileLoadingCount = 0;
260 class MyDesktopManager implements DesktopManager
263 private DesktopManager delegate;
265 public MyDesktopManager(DesktopManager delegate)
267 this.delegate = delegate;
271 public void activateFrame(JInternalFrame f)
275 delegate.activateFrame(f);
276 } catch (NullPointerException npe)
278 Point p = getMousePosition();
279 instance.showPasteMenu(p.x, p.y);
284 public void beginDraggingFrame(JComponent f)
286 delegate.beginDraggingFrame(f);
290 public void beginResizingFrame(JComponent f, int direction)
292 delegate.beginResizingFrame(f, direction);
296 public void closeFrame(JInternalFrame f)
298 delegate.closeFrame(f);
302 public void deactivateFrame(JInternalFrame f)
304 delegate.deactivateFrame(f);
308 public void deiconifyFrame(JInternalFrame f)
310 delegate.deiconifyFrame(f);
314 public void dragFrame(JComponent f, int newX, int newY)
320 delegate.dragFrame(f, newX, newY);
324 public void endDraggingFrame(JComponent f)
326 delegate.endDraggingFrame(f);
331 public void endResizingFrame(JComponent f)
333 delegate.endResizingFrame(f);
338 public void iconifyFrame(JInternalFrame f)
340 delegate.iconifyFrame(f);
344 public void maximizeFrame(JInternalFrame f)
346 delegate.maximizeFrame(f);
350 public void minimizeFrame(JInternalFrame f)
352 delegate.minimizeFrame(f);
356 public void openFrame(JInternalFrame f)
358 delegate.openFrame(f);
362 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
369 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
373 public void setBoundsForFrame(JComponent f, int newX, int newY,
374 int newWidth, int newHeight)
376 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
379 // All other methods, simply delegate
384 * Creates a new Desktop object.
390 * A note to implementors. It is ESSENTIAL that any activities that might
391 * block are spawned off as threads rather than waited for during this
396 doConfigureStructurePrefs();
397 setTitle(ChannelProperties.getProperty("app_name") + " "
398 + Cache.getProperty("VERSION"));
401 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
402 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
403 * officially documented or guaranteed to exist, so we access it via
404 * reflection. There appear to be unfathomable criteria about what this
405 * string can contain, and it if doesn't meet those criteria then "java"
406 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
407 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
408 * not. The reflection access may generate a warning: WARNING: An illegal
409 * reflective access operation has occurred WARNING: Illegal reflective
410 * access by jalview.gui.Desktop () to field
411 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
413 if (Platform.isLinux())
417 Toolkit xToolkit = Toolkit.getDefaultToolkit();
418 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
419 Field awtAppClassNameField = null;
421 if (Arrays.stream(declaredFields)
422 .anyMatch(f -> f.getName().equals("awtAppClassName")))
424 awtAppClassNameField = xToolkit.getClass()
425 .getDeclaredField("awtAppClassName");
428 String title = ChannelProperties.getProperty("app_name");
429 if (awtAppClassNameField != null)
431 awtAppClassNameField.setAccessible(true);
432 awtAppClassNameField.set(xToolkit, title);
436 Cache.log.debug("XToolkit: awtAppClassName not found");
438 } catch (Exception e)
440 Cache.debug("Error setting awtAppClassName");
441 Cache.trace(Cache.getStackTraceString(e));
446 * APQHandlers sets handlers for About, Preferences and Quit actions
447 * peculiar to macOS's application menu. APQHandlers will check to see if a
448 * handler is supported before setting it.
452 APQHandlers.setAPQHandlers(this);
453 } catch (Throwable t)
455 Cache.warn("Error setting APQHandlers: " + t.toString());
456 Cache.trace(Cache.getStackTraceString(t));
458 setIconImages(ChannelProperties.getIconList());
460 addWindowListener(new WindowAdapter()
464 public void windowClosing(WindowEvent ev)
470 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
472 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
473 desktop = new MyDesktopPane(selmemusage);
475 showMemusage.setSelected(selmemusage);
476 desktop.setBackground(Color.white);
478 this.setIconImages(ChannelProperties.getIconList());
480 getContentPane().setLayout(new BorderLayout());
481 // alternate config - have scrollbars - see notes in JAL-153
482 // JScrollPane sp = new JScrollPane();
483 // sp.getViewport().setView(desktop);
484 // getContentPane().add(sp, BorderLayout.CENTER);
486 // BH 2018 - just an experiment to try unclipped JInternalFrames.
489 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
492 getContentPane().add(desktop, BorderLayout.CENTER);
493 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
495 // This line prevents Windows Look&Feel resizing all new windows to maximum
496 // if previous window was maximised
497 desktop.setDesktopManager(new MyDesktopManager(
498 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
499 : Platform.isAMacAndNotJS()
500 ? new AquaInternalFrameManager(
501 desktop.getDesktopManager())
502 : desktop.getDesktopManager())));
504 Rectangle dims = getLastKnownDimensions("");
511 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
512 int xPos = Math.max(5, (screenSize.width - 900) / 2);
513 int yPos = Math.max(5, (screenSize.height - 650) / 2);
514 setBounds(xPos, yPos, 900, 650);
517 if (!Platform.isJS())
524 jconsole = new Console(this, showjconsole);
525 jconsole.setHeader(Cache.getVersionDetailsForConsole());
526 showConsole(showjconsole);
528 showNews.setVisible(false);
530 experimentalFeatures.setSelected(showExperimental());
532 getIdentifiersOrgData();
536 // Spawn a thread that shows the splashscreen
539 SwingUtilities.invokeLater(new Runnable()
544 new SplashScreen(true);
549 // Thread off a new instance of the file chooser - this reduces the time
551 // takes to open it later on.
552 new Thread(new Runnable()
557 Cache.log.debug("Filechooser init thread started.");
558 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
559 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
561 Cache.log.debug("Filechooser init thread finished.");
564 // Add the service change listener
565 changeSupport.addJalviewPropertyChangeListener("services",
566 new PropertyChangeListener()
570 public void propertyChange(PropertyChangeEvent evt)
572 Cache.log.debug("Firing service changed event for "
573 + evt.getNewValue());
574 JalviewServicesChanged(evt);
579 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
581 this.addWindowListener(new WindowAdapter()
584 public void windowClosing(WindowEvent evt)
591 this.addMouseListener(ma = new MouseAdapter()
594 public void mousePressed(MouseEvent evt)
596 if (evt.isPopupTrigger()) // Mac
598 showPasteMenu(evt.getX(), evt.getY());
603 public void mouseReleased(MouseEvent evt)
605 if (evt.isPopupTrigger()) // Windows
607 showPasteMenu(evt.getX(), evt.getY());
611 desktop.addMouseListener(ma);
615 * Answers true if user preferences to enable experimental features is True
620 public boolean showExperimental()
622 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
623 Boolean.FALSE.toString());
624 return Boolean.valueOf(experimental).booleanValue();
627 public void doConfigureStructurePrefs()
629 // configure services
630 StructureSelectionManager ssm = StructureSelectionManager
631 .getStructureSelectionManager(this);
632 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
634 ssm.setAddTempFacAnnot(
635 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
636 ssm.setProcessSecondaryStructure(
637 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
638 ssm.setSecStructServices(
639 Cache.getDefault(Preferences.USE_RNAVIEW, true));
643 ssm.setAddTempFacAnnot(false);
644 ssm.setProcessSecondaryStructure(false);
645 ssm.setSecStructServices(false);
649 public void checkForNews()
651 final Desktop me = this;
652 // Thread off the news reader, in case there are connection problems.
653 new Thread(new Runnable()
658 Cache.log.debug("Starting news thread.");
659 jvnews = new BlogReader(me);
660 showNews.setVisible(true);
661 Cache.log.debug("Completed news thread.");
666 public void getIdentifiersOrgData()
668 // Thread off the identifiers fetcher
669 new Thread(new Runnable()
674 Cache.log.debug("Downloading data from identifiers.org");
677 UrlDownloadClient.download(IdOrgSettings.getUrl(),
678 IdOrgSettings.getDownloadLocation());
679 } catch (IOException e)
681 Cache.log.debug("Exception downloading identifiers.org data"
690 protected void showNews_actionPerformed(ActionEvent e)
692 showNews(showNews.isSelected());
695 void showNews(boolean visible)
697 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
698 showNews.setSelected(visible);
699 if (visible && !jvnews.isVisible())
701 new Thread(new Runnable()
706 long now = System.currentTimeMillis();
707 Desktop.instance.setProgressBar(
708 MessageManager.getString("status.refreshing_news"), now);
709 jvnews.refreshNews();
710 Desktop.instance.setProgressBar(null, now);
718 * recover the last known dimensions for a jalview window
721 * - empty string is desktop, all other windows have unique prefix
722 * @return null or last known dimensions scaled to current geometry (if last
723 * window geom was known)
725 Rectangle getLastKnownDimensions(String windowName)
727 // TODO: lock aspect ratio for scaling desktop Bug #0058199
728 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
729 String x = Cache.getProperty(windowName + "SCREEN_X");
730 String y = Cache.getProperty(windowName + "SCREEN_Y");
731 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
732 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
733 if ((x != null) && (y != null) && (width != null) && (height != null))
735 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
736 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
737 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
739 // attempt #1 - try to cope with change in screen geometry - this
740 // version doesn't preserve original jv aspect ratio.
741 // take ratio of current screen size vs original screen size.
742 double sw = ((1f * screenSize.width) / (1f * Integer
743 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
744 double sh = ((1f * screenSize.height) / (1f * Integer
745 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
746 // rescale the bounds depending upon the current screen geometry.
747 ix = (int) (ix * sw);
748 iw = (int) (iw * sw);
749 iy = (int) (iy * sh);
750 ih = (int) (ih * sh);
751 while (ix >= screenSize.width)
754 "Window geometry location recall error: shifting horizontal to within screenbounds.");
755 ix -= screenSize.width;
757 while (iy >= screenSize.height)
760 "Window geometry location recall error: shifting vertical to within screenbounds.");
761 iy -= screenSize.height;
764 "Got last known dimensions for " + windowName + ": x:" + ix
765 + " y:" + iy + " width:" + iw + " height:" + ih);
767 // return dimensions for new instance
768 return new Rectangle(ix, iy, iw, ih);
773 void showPasteMenu(int x, int y)
775 JPopupMenu popup = new JPopupMenu();
776 JMenuItem item = new JMenuItem(
777 MessageManager.getString("label.paste_new_window"));
778 item.addActionListener(new ActionListener()
781 public void actionPerformed(ActionEvent evt)
788 popup.show(this, x, y);
795 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
796 Transferable contents = c.getContents(this);
798 if (contents != null)
800 String file = (String) contents
801 .getTransferData(DataFlavor.stringFlavor);
803 FileFormatI format = new IdentifyFile().identify(file,
804 DataSourceType.PASTE);
806 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
809 } catch (Exception ex)
812 "Unable to paste alignment from system clipboard:\n" + ex);
817 * Adds and opens the given frame to the desktop
828 public static synchronized void addInternalFrame(
829 final JInternalFrame frame, String title, int w, int h)
831 addInternalFrame(frame, title, true, w, h, true, false);
835 * Add an internal frame to the Jalview desktop
842 * When true, display frame immediately, otherwise, caller must call
843 * setVisible themselves.
849 public static synchronized void addInternalFrame(
850 final JInternalFrame frame, String title, boolean makeVisible,
853 addInternalFrame(frame, title, makeVisible, w, h, true, false);
857 * Add an internal frame to the Jalview desktop and make it visible
870 public static synchronized void addInternalFrame(
871 final JInternalFrame frame, String title, int w, int h,
874 addInternalFrame(frame, title, true, w, h, resizable, false);
878 * Add an internal frame to the Jalview desktop
885 * When true, display frame immediately, otherwise, caller must call
886 * setVisible themselves.
893 * @param ignoreMinSize
894 * Do not set the default minimum size for frame
896 public static synchronized void addInternalFrame(
897 final JInternalFrame frame, String title, boolean makeVisible,
898 int w, int h, boolean resizable, boolean ignoreMinSize)
901 // TODO: allow callers to determine X and Y position of frame (eg. via
903 // TODO: consider fixing method to update entries in the window submenu with
904 // the current window title
906 frame.setTitle(title);
907 if (frame.getWidth() < 1 || frame.getHeight() < 1)
911 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
912 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
913 // IF JALVIEW IS RUNNING HEADLESS
914 // ///////////////////////////////////////////////
915 if (instance == null || (System.getProperty("java.awt.headless") != null
916 && System.getProperty("java.awt.headless").equals("true")))
925 frame.setMinimumSize(
926 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
928 // Set default dimension for Alignment Frame window.
929 // The Alignment Frame window could be added from a number of places,
931 // I did this here in order not to miss out on any Alignment frame.
932 if (frame instanceof AlignFrame)
934 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
935 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
939 frame.setVisible(makeVisible);
940 frame.setClosable(true);
941 frame.setResizable(resizable);
942 frame.setMaximizable(resizable);
943 frame.setIconifiable(resizable);
944 frame.setOpaque(Platform.isJS());
946 if (frame.getX() < 1 && frame.getY() < 1)
948 frame.setLocation(xOffset * openFrameCount,
949 yOffset * ((openFrameCount - 1) % 10) + yOffset);
953 * add an entry for the new frame in the Window menu
954 * (and remove it when the frame is closed)
956 final JMenuItem menuItem = new JMenuItem(title);
957 frame.addInternalFrameListener(new InternalFrameAdapter()
960 public void internalFrameActivated(InternalFrameEvent evt)
962 JInternalFrame itf = desktop.getSelectedFrame();
965 if (itf instanceof AlignFrame)
967 Jalview.setCurrentAlignFrame((AlignFrame) itf);
974 public void internalFrameClosed(InternalFrameEvent evt)
976 PaintRefresher.RemoveComponent(frame);
979 * defensive check to prevent frames being
980 * added half off the window
982 if (openFrameCount > 0)
988 * ensure no reference to alignFrame retained by menu item listener
990 if (menuItem.getActionListeners().length > 0)
992 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
994 windowMenu.remove(menuItem);
998 menuItem.addActionListener(new ActionListener()
1001 public void actionPerformed(ActionEvent e)
1005 frame.setSelected(true);
1006 frame.setIcon(false);
1007 } catch (java.beans.PropertyVetoException ex)
1009 // System.err.println(ex.toString());
1014 setKeyBindings(frame);
1018 windowMenu.add(menuItem);
1023 frame.setSelected(true);
1024 frame.requestFocus();
1025 } catch (java.beans.PropertyVetoException ve)
1027 } catch (java.lang.ClassCastException cex)
1030 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1036 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1041 private static void setKeyBindings(JInternalFrame frame)
1043 @SuppressWarnings("serial")
1044 final Action closeAction = new AbstractAction()
1047 public void actionPerformed(ActionEvent e)
1054 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1056 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1057 InputEvent.CTRL_DOWN_MASK);
1058 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1059 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1061 InputMap inputMap = frame
1062 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1063 String ctrlW = ctrlWKey.toString();
1064 inputMap.put(ctrlWKey, ctrlW);
1065 inputMap.put(cmdWKey, ctrlW);
1067 ActionMap actionMap = frame.getActionMap();
1068 actionMap.put(ctrlW, closeAction);
1072 public void lostOwnership(Clipboard clipboard, Transferable contents)
1076 Desktop.jalviewClipboard = null;
1079 internalCopy = false;
1083 public void dragEnter(DropTargetDragEvent evt)
1088 public void dragExit(DropTargetEvent evt)
1093 public void dragOver(DropTargetDragEvent evt)
1098 public void dropActionChanged(DropTargetDragEvent evt)
1109 public void drop(DropTargetDropEvent evt)
1111 boolean success = true;
1112 // JAL-1552 - acceptDrop required before getTransferable call for
1113 // Java's Transferable for native dnd
1114 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1115 Transferable t = evt.getTransferable();
1116 List<Object> files = new ArrayList<>();
1117 List<DataSourceType> protocols = new ArrayList<>();
1121 Desktop.transferFromDropTarget(files, protocols, evt, t);
1122 } catch (Exception e)
1124 e.printStackTrace();
1132 for (int i = 0; i < files.size(); i++)
1134 // BH 2018 File or String
1135 Object file = files.get(i);
1136 String fileName = file.toString();
1137 DataSourceType protocol = (protocols == null)
1138 ? DataSourceType.FILE
1140 FileFormatI format = null;
1142 if (fileName.endsWith(".jar"))
1144 format = FileFormat.Jalview;
1149 format = new IdentifyFile().identify(file, protocol);
1151 if (file instanceof File)
1153 Platform.cacheFileData((File) file);
1155 new FileLoader().LoadFile(null, file, protocol, format);
1158 } catch (Exception ex)
1163 evt.dropComplete(success); // need this to ensure input focus is properly
1164 // transfered to any new windows created
1174 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1176 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1177 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1178 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1179 BackupFiles.getEnabled());
1181 chooser.setFileView(new JalviewFileView());
1182 chooser.setDialogTitle(
1183 MessageManager.getString("label.open_local_file"));
1184 chooser.setToolTipText(MessageManager.getString("action.open"));
1186 chooser.setResponseHandler(0, new Runnable()
1191 File selectedFile = chooser.getSelectedFile();
1192 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1193 FileFormatI format = chooser.getSelectedFormat();
1194 openFile(selectedFile, format, viewport);
1197 chooser.showOpenDialog(this);
1200 public void openFile(File selectedFile, FileFormatI format,
1201 AlignViewport viewport)
1205 * Call IdentifyFile to verify the file contains what its extension implies.
1206 * Skip this step for dynamically added file formats, because
1207 * IdentifyFile does not know how to recognise them.
1209 if (FileFormats.getInstance().isIdentifiable(format))
1213 format = new IdentifyFile().identify(selectedFile,
1214 DataSourceType.FILE);
1215 } catch (FileFormatException e)
1217 // format = null; //??
1221 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1226 * Shows a dialog for input of a URL at which to retrieve alignment data
1231 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1233 // This construct allows us to have a wider textfield
1235 JLabel label = new JLabel(
1236 MessageManager.getString("label.input_file_url"));
1238 JPanel panel = new JPanel(new GridLayout(2, 1));
1242 * the URL to fetch is input in
1243 * Java: an editable combobox with history
1244 * JS: (pending JAL-3038) a plain text field
1247 String urlBase = "https://www.";
1248 if (Platform.isJS())
1250 history = new JTextField(urlBase, 35);
1259 JComboBox<String> asCombo = new JComboBox<>();
1260 asCombo.setPreferredSize(new Dimension(400, 20));
1261 asCombo.setEditable(true);
1262 asCombo.addItem(urlBase);
1263 String historyItems = Cache.getProperty("RECENT_URL");
1264 if (historyItems != null)
1266 for (String token : historyItems.split("\\t"))
1268 asCombo.addItem(token);
1275 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1276 MessageManager.getString("action.cancel") };
1277 Runnable action = new Runnable()
1282 @SuppressWarnings("unchecked")
1283 String url = (history instanceof JTextField
1284 ? ((JTextField) history).getText()
1285 : ((JComboBox<String>) history).getEditor().getItem()
1286 .toString().trim());
1288 if (!loadUrl(url, viewport))
1291 String msg = MessageManager.formatMessage("label.couldnt_locate",
1293 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1294 MessageManager.getString("label.url_not_found"),
1295 JvOptionPane.WARNING_MESSAGE);
1299 String dialogOption = MessageManager
1300 .getString("label.input_alignment_from_url");
1301 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1302 .showInternalDialog(panel, dialogOption,
1303 JvOptionPane.YES_NO_CANCEL_OPTION,
1304 JvOptionPane.PLAIN_MESSAGE, null, options,
1305 MessageManager.getString("action.ok"));
1308 public boolean loadUrl(String url, AlignViewport viewport)
1310 FileFormatI format = null;
1313 format = new IdentifyFile().identify(url, DataSourceType.URL);
1314 } catch (FileFormatException e)
1316 // TODO revise error handling, distinguish between
1317 // URL not found and response not valid
1325 if (viewport != null)
1327 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, format);
1331 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1337 * Opens the CutAndPaste window for the user to paste an alignment in to
1340 * - if not null, the pasted alignment is added to the current
1341 * alignment; if null, to a new alignment window
1344 public void inputTextboxMenuItem_actionPerformed(
1345 AlignmentViewPanel viewPanel)
1347 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1348 cap.setForInput(viewPanel);
1349 Desktop.addInternalFrame(cap,
1350 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1360 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1361 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1362 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1363 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1364 getWidth(), getHeight()));
1366 if (jconsole != null)
1368 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1369 jconsole.stopConsole();
1373 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1376 if (dialogExecutor != null)
1378 dialogExecutor.shutdownNow();
1380 closeAll_actionPerformed(null);
1382 if (groovyConsole != null)
1384 // suppress a possible repeat prompt to save script
1385 groovyConsole.setDirty(false);
1386 groovyConsole.exit();
1391 private void storeLastKnownDimensions(String string, Rectangle jc)
1393 Cache.log.debug("Storing last known dimensions for " + string + ": x:"
1394 + jc.x + " y:" + jc.y + " width:" + jc.width + " height:"
1397 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1398 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1399 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1400 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1410 public void aboutMenuItem_actionPerformed(ActionEvent e)
1412 new Thread(new Runnable()
1417 new SplashScreen(false);
1423 * Returns the html text for the About screen, including any available version
1424 * number, build details, author details and citation reference, but without
1425 * the enclosing {@code html} tags
1429 public String getAboutMessage()
1431 StringBuilder message = new StringBuilder(1024);
1432 message.append("<div style=\"font-family: sans-serif;\">")
1433 .append("<h1><strong>Version: ")
1434 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1435 .append("<strong>Built: <em>")
1436 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1437 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1438 .append("</strong>");
1440 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1441 if (latestVersion.equals("Checking"))
1443 // JBP removed this message for 2.11: May be reinstated in future version
1444 // message.append("<br>...Checking latest version...</br>");
1446 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1448 boolean red = false;
1449 if (Cache.getProperty("VERSION").toLowerCase()
1450 .indexOf("automated build") == -1)
1453 // Displayed when code version and jnlp version do not match and code
1454 // version is not a development build
1455 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1458 message.append("<br>!! Version ")
1459 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1460 .append(" is available for download from ")
1461 .append(Cache.getDefault("www.jalview.org",
1462 "https://www.jalview.org"))
1466 message.append("</div>");
1469 message.append("<br>Authors: ");
1470 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1471 message.append(CITATION);
1473 message.append("</div>");
1475 return message.toString();
1479 * Action on requesting Help documentation
1482 public void documentationMenuItem_actionPerformed()
1486 if (Platform.isJS())
1488 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1497 Help.showHelpWindow();
1499 } catch (Exception ex)
1501 System.err.println("Error opening help: " + ex.getMessage());
1506 public void closeAll_actionPerformed(ActionEvent e)
1508 // TODO show a progress bar while closing?
1509 JInternalFrame[] frames = desktop.getAllFrames();
1510 for (int i = 0; i < frames.length; i++)
1514 frames[i].setClosed(true);
1515 } catch (java.beans.PropertyVetoException ex)
1519 Jalview.setCurrentAlignFrame(null);
1520 System.out.println("ALL CLOSED");
1523 * reset state of singleton objects as appropriate (clear down session state
1524 * when all windows are closed)
1526 StructureSelectionManager ssm = StructureSelectionManager
1527 .getStructureSelectionManager(this);
1535 public void raiseRelated_actionPerformed(ActionEvent e)
1537 reorderAssociatedWindows(false, false);
1541 public void minimizeAssociated_actionPerformed(ActionEvent e)
1543 reorderAssociatedWindows(true, false);
1546 void closeAssociatedWindows()
1548 reorderAssociatedWindows(false, true);
1554 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1558 protected void garbageCollect_actionPerformed(ActionEvent e)
1560 // We simply collect the garbage
1561 Cache.log.debug("Collecting garbage...");
1563 Cache.log.debug("Finished garbage collection.");
1570 * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1574 protected void showMemusage_actionPerformed(ActionEvent e)
1576 desktop.showMemoryUsage(showMemusage.isSelected());
1583 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1587 protected void showConsole_actionPerformed(ActionEvent e)
1589 showConsole(showConsole.isSelected());
1592 Console jconsole = null;
1595 * control whether the java console is visible or not
1599 void showConsole(boolean selected)
1601 // TODO: decide if we should update properties file
1602 if (jconsole != null) // BH 2018
1604 showConsole.setSelected(selected);
1605 Cache.setProperty("SHOW_JAVA_CONSOLE",
1606 Boolean.valueOf(selected).toString());
1607 jconsole.setVisible(selected);
1611 void reorderAssociatedWindows(boolean minimize, boolean close)
1613 JInternalFrame[] frames = desktop.getAllFrames();
1614 if (frames == null || frames.length < 1)
1619 AlignmentViewport source = null, target = null;
1620 if (frames[0] instanceof AlignFrame)
1622 source = ((AlignFrame) frames[0]).getCurrentView();
1624 else if (frames[0] instanceof TreePanel)
1626 source = ((TreePanel) frames[0]).getViewPort();
1628 else if (frames[0] instanceof PCAPanel)
1630 source = ((PCAPanel) frames[0]).av;
1632 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1634 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1639 for (int i = 0; i < frames.length; i++)
1642 if (frames[i] == null)
1646 if (frames[i] instanceof AlignFrame)
1648 target = ((AlignFrame) frames[i]).getCurrentView();
1650 else if (frames[i] instanceof TreePanel)
1652 target = ((TreePanel) frames[i]).getViewPort();
1654 else if (frames[i] instanceof PCAPanel)
1656 target = ((PCAPanel) frames[i]).av;
1658 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1660 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1663 if (source == target)
1669 frames[i].setClosed(true);
1673 frames[i].setIcon(minimize);
1676 frames[i].toFront();
1680 } catch (java.beans.PropertyVetoException ex)
1695 protected void preferences_actionPerformed(ActionEvent e)
1697 Preferences.openPreferences();
1701 * Prompts the user to choose a file and then saves the Jalview state as a
1702 * Jalview project file
1705 public void saveState_actionPerformed()
1707 saveState_actionPerformed(false);
1710 public void saveState_actionPerformed(boolean saveAs)
1712 java.io.File projectFile = getProjectFile();
1713 // autoSave indicates we already have a file and don't need to ask
1714 boolean autoSave = projectFile != null && !saveAs
1715 && BackupFiles.getEnabled();
1717 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1718 // saveAs="+saveAs+", Backups
1719 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1721 boolean approveSave = false;
1724 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1727 chooser.setFileView(new JalviewFileView());
1728 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1730 int value = chooser.showSaveDialog(this);
1732 if (value == JalviewFileChooser.APPROVE_OPTION)
1734 projectFile = chooser.getSelectedFile();
1735 setProjectFile(projectFile);
1740 if (approveSave || autoSave)
1742 final Desktop me = this;
1743 final java.io.File chosenFile = projectFile;
1744 new Thread(new Runnable()
1749 // TODO: refactor to Jalview desktop session controller action.
1750 setProgressBar(MessageManager.formatMessage(
1751 "label.saving_jalview_project", new Object[]
1752 { chosenFile.getName() }), chosenFile.hashCode());
1753 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1754 // TODO catch and handle errors for savestate
1755 // TODO prevent user from messing with the Desktop whilst we're saving
1758 boolean doBackup = BackupFiles.getEnabled();
1759 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1762 new Jalview2XML().saveState(
1763 doBackup ? backupfiles.getTempFile() : chosenFile);
1767 backupfiles.setWriteSuccess(true);
1768 backupfiles.rollBackupsAndRenameTempFile();
1770 } catch (OutOfMemoryError oom)
1772 new OOMWarning("Whilst saving current state to "
1773 + chosenFile.getName(), oom);
1774 } catch (Exception ex)
1776 Cache.log.error("Problems whilst trying to save to "
1777 + chosenFile.getName(), ex);
1778 JvOptionPane.showMessageDialog(me,
1779 MessageManager.formatMessage(
1780 "label.error_whilst_saving_current_state_to",
1782 { chosenFile.getName() }),
1783 MessageManager.getString("label.couldnt_save_project"),
1784 JvOptionPane.WARNING_MESSAGE);
1786 setProgressBar(null, chosenFile.hashCode());
1793 public void saveAsState_actionPerformed(ActionEvent e)
1795 saveState_actionPerformed(true);
1798 private void setProjectFile(File choice)
1800 this.projectFile = choice;
1803 public File getProjectFile()
1805 return this.projectFile;
1809 * Shows a file chooser dialog and tries to read in the selected file as a
1813 public void loadState_actionPerformed()
1815 final String[] suffix = new String[] { "jvp", "jar" };
1816 final String[] desc = new String[] { "Jalview Project",
1817 "Jalview Project (old)" };
1818 JalviewFileChooser chooser = new JalviewFileChooser(
1819 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1820 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1824 chooser.setFileView(new JalviewFileView());
1825 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1826 chooser.setResponseHandler(0, new Runnable()
1831 File selectedFile = chooser.getSelectedFile();
1832 setProjectFile(selectedFile);
1833 String choice = selectedFile.getAbsolutePath();
1834 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1835 new Thread(new Runnable()
1842 new Jalview2XML().loadJalviewAlign(selectedFile);
1843 } catch (OutOfMemoryError oom)
1845 new OOMWarning("Whilst loading project from " + choice, oom);
1846 } catch (Exception ex)
1849 "Problems whilst loading project from " + choice, ex);
1850 JvOptionPane.showMessageDialog(Desktop.desktop,
1851 MessageManager.formatMessage(
1852 "label.error_whilst_loading_project_from",
1856 .getString("label.couldnt_load_project"),
1857 JvOptionPane.WARNING_MESSAGE);
1860 }, "Project Loader").start();
1864 chooser.showOpenDialog(this);
1868 public void inputSequence_actionPerformed(ActionEvent e)
1870 new SequenceFetcher(this);
1873 JPanel progressPanel;
1875 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1877 public void startLoading(final Object fileName)
1879 if (fileLoadingCount == 0)
1881 fileLoadingPanels.add(addProgressPanel(MessageManager
1882 .formatMessage("label.loading_file", new Object[]
1888 private JPanel addProgressPanel(String string)
1890 if (progressPanel == null)
1892 progressPanel = new JPanel(new GridLayout(1, 1));
1893 totalProgressCount = 0;
1894 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1896 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1897 JProgressBar progressBar = new JProgressBar();
1898 progressBar.setIndeterminate(true);
1900 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1902 thisprogress.add(progressBar, BorderLayout.CENTER);
1903 progressPanel.add(thisprogress);
1904 ((GridLayout) progressPanel.getLayout()).setRows(
1905 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1906 ++totalProgressCount;
1907 instance.validate();
1908 return thisprogress;
1911 int totalProgressCount = 0;
1913 private void removeProgressPanel(JPanel progbar)
1915 if (progressPanel != null)
1917 synchronized (progressPanel)
1919 progressPanel.remove(progbar);
1920 GridLayout gl = (GridLayout) progressPanel.getLayout();
1921 gl.setRows(gl.getRows() - 1);
1922 if (--totalProgressCount < 1)
1924 this.getContentPane().remove(progressPanel);
1925 progressPanel = null;
1932 public void stopLoading()
1935 if (fileLoadingCount < 1)
1937 while (fileLoadingPanels.size() > 0)
1939 removeProgressPanel(fileLoadingPanels.remove(0));
1941 fileLoadingPanels.clear();
1942 fileLoadingCount = 0;
1947 public static int getViewCount(String alignmentId)
1949 AlignmentViewport[] aps = getViewports(alignmentId);
1950 return (aps == null) ? 0 : aps.length;
1955 * @param alignmentId
1956 * - if null, all sets are returned
1957 * @return all AlignmentPanels concerning the alignmentId sequence set
1959 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1961 if (Desktop.desktop == null)
1963 // no frames created and in headless mode
1964 // TODO: verify that frames are recoverable when in headless mode
1967 List<AlignmentPanel> aps = new ArrayList<>();
1968 AlignFrame[] frames = getAlignFrames();
1973 for (AlignFrame af : frames)
1975 for (AlignmentPanel ap : af.alignPanels)
1977 if (alignmentId == null
1978 || alignmentId.equals(ap.av.getSequenceSetId()))
1984 if (aps.size() == 0)
1988 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1993 * get all the viewports on an alignment.
1995 * @param sequenceSetId
1996 * unique alignment id (may be null - all viewports returned in that
1998 * @return all viewports on the alignment bound to sequenceSetId
2000 public static AlignmentViewport[] getViewports(String sequenceSetId)
2002 List<AlignmentViewport> viewp = new ArrayList<>();
2003 if (desktop != null)
2005 AlignFrame[] frames = Desktop.getAlignFrames();
2007 for (AlignFrame afr : frames)
2009 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2010 .equals(sequenceSetId))
2012 if (afr.alignPanels != null)
2014 for (AlignmentPanel ap : afr.alignPanels)
2016 if (sequenceSetId == null
2017 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2025 viewp.add(afr.getViewport());
2029 if (viewp.size() > 0)
2031 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2038 * Explode the views in the given frame into separate AlignFrame
2042 public static void explodeViews(AlignFrame af)
2044 int size = af.alignPanels.size();
2050 // FIXME: ideally should use UI interface API
2051 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2052 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2053 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2054 for (int i = 0; i < size; i++)
2056 AlignmentPanel ap = af.alignPanels.get(i);
2058 AlignFrame newaf = new AlignFrame(ap);
2060 // transfer reference for existing feature settings to new alignFrame
2061 if (ap == af.alignPanel)
2063 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2065 newaf.featureSettings = viewFeatureSettings;
2067 newaf.setFeatureSettingsGeometry(fsBounds);
2071 * Restore the view's last exploded frame geometry if known. Multiple
2072 * views from one exploded frame share and restore the same (frame)
2073 * position and size.
2075 Rectangle geometry = ap.av.getExplodedGeometry();
2076 if (geometry != null)
2078 newaf.setBounds(geometry);
2081 ap.av.setGatherViewsHere(false);
2083 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2084 AlignFrame.DEFAULT_HEIGHT);
2085 // and materialise a new feature settings dialog instance for the new
2087 // (closes the old as if 'OK' was pressed)
2088 if (ap == af.alignPanel && newaf.featureSettings != null
2089 && newaf.featureSettings.isOpen()
2090 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2092 newaf.showFeatureSettingsUI();
2096 af.featureSettings = null;
2097 af.alignPanels.clear();
2098 af.closeMenuItem_actionPerformed(true);
2103 * Gather expanded views (separate AlignFrame's) with the same sequence set
2104 * identifier back in to this frame as additional views, and close the
2105 * expanded views. Note the expanded frames may themselves have multiple
2106 * views. We take the lot.
2110 public void gatherViews(AlignFrame source)
2112 source.viewport.setGatherViewsHere(true);
2113 source.viewport.setExplodedGeometry(source.getBounds());
2114 JInternalFrame[] frames = desktop.getAllFrames();
2115 String viewId = source.viewport.getSequenceSetId();
2116 for (int t = 0; t < frames.length; t++)
2118 if (frames[t] instanceof AlignFrame && frames[t] != source)
2120 AlignFrame af = (AlignFrame) frames[t];
2121 boolean gatherThis = false;
2122 for (int a = 0; a < af.alignPanels.size(); a++)
2124 AlignmentPanel ap = af.alignPanels.get(a);
2125 if (viewId.equals(ap.av.getSequenceSetId()))
2128 ap.av.setGatherViewsHere(false);
2129 ap.av.setExplodedGeometry(af.getBounds());
2130 source.addAlignmentPanel(ap, false);
2136 if (af.featureSettings != null && af.featureSettings.isOpen())
2138 if (source.featureSettings == null)
2140 // preserve the feature settings geometry for this frame
2141 source.featureSettings = af.featureSettings;
2142 source.setFeatureSettingsGeometry(
2143 af.getFeatureSettingsGeometry());
2147 // close it and forget
2148 af.featureSettings.close();
2151 af.alignPanels.clear();
2152 af.closeMenuItem_actionPerformed(true);
2157 // refresh the feature setting UI for the source frame if it exists
2158 if (source.featureSettings != null && source.featureSettings.isOpen())
2160 source.showFeatureSettingsUI();
2164 public JInternalFrame[] getAllFrames()
2166 return desktop.getAllFrames();
2170 * Checks the given url to see if it gives a response indicating that the user
2171 * should be informed of a new questionnaire.
2175 public void checkForQuestionnaire(String url)
2177 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2178 // javax.swing.SwingUtilities.invokeLater(jvq);
2179 new Thread(jvq).start();
2182 public void checkURLLinks()
2184 // Thread off the URL link checker
2185 addDialogThread(new Runnable()
2190 if (Cache.getDefault("CHECKURLLINKS", true))
2192 // check what the actual links are - if it's just the default don't
2193 // bother with the warning
2194 List<String> links = Preferences.sequenceUrlLinks
2197 // only need to check links if there is one with a
2198 // SEQUENCE_ID which is not the default EMBL_EBI link
2199 ListIterator<String> li = links.listIterator();
2200 boolean check = false;
2201 List<JLabel> urls = new ArrayList<>();
2202 while (li.hasNext())
2204 String link = li.next();
2205 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2206 && !UrlConstants.isDefaultString(link))
2209 int barPos = link.indexOf("|");
2210 String urlMsg = barPos == -1 ? link
2211 : link.substring(0, barPos) + ": "
2212 + link.substring(barPos + 1);
2213 urls.add(new JLabel(urlMsg));
2221 // ask user to check in case URL links use old style tokens
2222 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2223 JPanel msgPanel = new JPanel();
2224 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2225 msgPanel.add(Box.createVerticalGlue());
2226 JLabel msg = new JLabel(MessageManager
2227 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2228 JLabel msg2 = new JLabel(MessageManager
2229 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2231 for (JLabel url : urls)
2237 final JCheckBox jcb = new JCheckBox(
2238 MessageManager.getString("label.do_not_display_again"));
2239 jcb.addActionListener(new ActionListener()
2242 public void actionPerformed(ActionEvent e)
2244 // update Cache settings for "don't show this again"
2245 boolean showWarningAgain = !jcb.isSelected();
2246 Cache.setProperty("CHECKURLLINKS",
2247 Boolean.valueOf(showWarningAgain).toString());
2252 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2254 .getString("label.SEQUENCE_ID_no_longer_used"),
2255 JvOptionPane.WARNING_MESSAGE);
2262 * Proxy class for JDesktopPane which optionally displays the current memory
2263 * usage and highlights the desktop area with a red bar if free memory runs
2268 public class MyDesktopPane extends JDesktopPane implements Runnable
2270 private static final float ONE_MB = 1048576f;
2272 boolean showMemoryUsage = false;
2276 java.text.NumberFormat df;
2278 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2281 public MyDesktopPane(boolean showMemoryUsage)
2283 showMemoryUsage(showMemoryUsage);
2286 public void showMemoryUsage(boolean showMemory)
2288 this.showMemoryUsage = showMemory;
2291 Thread worker = new Thread(this);
2297 public boolean isShowMemoryUsage()
2299 return showMemoryUsage;
2305 df = java.text.NumberFormat.getNumberInstance();
2306 df.setMaximumFractionDigits(2);
2307 runtime = Runtime.getRuntime();
2309 while (showMemoryUsage)
2313 maxMemory = runtime.maxMemory() / ONE_MB;
2314 allocatedMemory = runtime.totalMemory() / ONE_MB;
2315 freeMemory = runtime.freeMemory() / ONE_MB;
2316 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2318 percentUsage = (totalFreeMemory / maxMemory) * 100;
2320 // if (percentUsage < 20)
2322 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2324 // instance.set.setBorder(border1);
2327 // sleep after showing usage
2329 } catch (Exception ex)
2331 ex.printStackTrace();
2337 public void paintComponent(Graphics g)
2339 if (showMemoryUsage && g != null && df != null)
2341 if (percentUsage < 20)
2343 g.setColor(Color.red);
2345 FontMetrics fm = g.getFontMetrics();
2348 g.drawString(MessageManager.formatMessage("label.memory_stats",
2350 { df.format(totalFreeMemory), df.format(maxMemory),
2351 df.format(percentUsage) }),
2352 10, getHeight() - fm.getHeight());
2356 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2357 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2362 * Accessor method to quickly get all the AlignmentFrames loaded.
2364 * @return an array of AlignFrame, or null if none found
2366 public static AlignFrame[] getAlignFrames()
2368 if (Jalview.isHeadlessMode())
2370 // Desktop.desktop is null in headless mode
2371 return new AlignFrame[] { Jalview.currentAlignFrame };
2374 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2380 List<AlignFrame> avp = new ArrayList<>();
2382 for (int i = frames.length - 1; i > -1; i--)
2384 if (frames[i] instanceof AlignFrame)
2386 avp.add((AlignFrame) frames[i]);
2388 else if (frames[i] instanceof SplitFrame)
2391 * Also check for a split frame containing an AlignFrame
2393 GSplitFrame sf = (GSplitFrame) frames[i];
2394 if (sf.getTopFrame() instanceof AlignFrame)
2396 avp.add((AlignFrame) sf.getTopFrame());
2398 if (sf.getBottomFrame() instanceof AlignFrame)
2400 avp.add((AlignFrame) sf.getBottomFrame());
2404 if (avp.size() == 0)
2408 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2413 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2417 public GStructureViewer[] getJmols()
2419 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2425 List<GStructureViewer> avp = new ArrayList<>();
2427 for (int i = frames.length - 1; i > -1; i--)
2429 if (frames[i] instanceof AppJmol)
2431 GStructureViewer af = (GStructureViewer) frames[i];
2435 if (avp.size() == 0)
2439 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2444 * Add Groovy Support to Jalview
2447 public void groovyShell_actionPerformed()
2451 openGroovyConsole();
2452 } catch (Exception ex)
2454 Cache.log.error("Groovy Shell Creation failed.", ex);
2455 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2457 MessageManager.getString("label.couldnt_create_groovy_shell"),
2458 MessageManager.getString("label.groovy_support_failed"),
2459 JvOptionPane.ERROR_MESSAGE);
2464 * Open the Groovy console
2466 void openGroovyConsole()
2468 if (groovyConsole == null)
2470 groovyConsole = new groovy.ui.Console();
2471 groovyConsole.setVariable("Jalview", this);
2472 groovyConsole.run();
2475 * We allow only one console at a time, so that AlignFrame menu option
2476 * 'Calculate | Run Groovy script' is unambiguous.
2477 * Disable 'Groovy Console', and enable 'Run script', when the console is
2478 * opened, and the reverse when it is closed
2480 Window window = (Window) groovyConsole.getFrame();
2481 window.addWindowListener(new WindowAdapter()
2484 public void windowClosed(WindowEvent e)
2487 * rebind CMD-Q from Groovy Console to Jalview Quit
2490 enableExecuteGroovy(false);
2496 * show Groovy console window (after close and reopen)
2498 ((Window) groovyConsole.getFrame()).setVisible(true);
2501 * if we got this far, enable 'Run Groovy' in AlignFrame menus
2502 * and disable opening a second console
2504 enableExecuteGroovy(true);
2508 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2509 * binding when opened
2511 protected void addQuitHandler()
2514 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2516 .getKeyStroke(KeyEvent.VK_Q,
2517 jalview.util.ShortcutKeyMaskExWrapper
2518 .getMenuShortcutKeyMaskEx()),
2520 getRootPane().getActionMap().put("Quit", new AbstractAction()
2523 public void actionPerformed(ActionEvent e)
2531 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2534 * true if Groovy console is open
2536 public void enableExecuteGroovy(boolean enabled)
2539 * disable opening a second Groovy console
2540 * (or re-enable when the console is closed)
2542 groovyShell.setEnabled(!enabled);
2544 AlignFrame[] alignFrames = getAlignFrames();
2545 if (alignFrames != null)
2547 for (AlignFrame af : alignFrames)
2549 af.setGroovyEnabled(enabled);
2555 * Progress bars managed by the IProgressIndicator method.
2557 private Hashtable<Long, JPanel> progressBars;
2559 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2564 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2567 public void setProgressBar(String message, long id)
2569 // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
2571 if (progressBars == null)
2573 progressBars = new Hashtable<>();
2574 progressBarHandlers = new Hashtable<>();
2577 if (progressBars.get(Long.valueOf(id)) != null)
2579 JPanel panel = progressBars.remove(Long.valueOf(id));
2580 if (progressBarHandlers.contains(Long.valueOf(id)))
2582 progressBarHandlers.remove(Long.valueOf(id));
2584 removeProgressPanel(panel);
2588 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2595 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2596 * jalview.gui.IProgressIndicatorHandler)
2599 public void registerHandler(final long id,
2600 final IProgressIndicatorHandler handler)
2602 if (progressBarHandlers == null
2603 || !progressBars.containsKey(Long.valueOf(id)))
2605 throw new Error(MessageManager.getString(
2606 "error.call_setprogressbar_before_registering_handler"));
2608 progressBarHandlers.put(Long.valueOf(id), handler);
2609 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2610 if (handler.canCancel())
2612 JButton cancel = new JButton(
2613 MessageManager.getString("action.cancel"));
2614 final IProgressIndicator us = this;
2615 cancel.addActionListener(new ActionListener()
2619 public void actionPerformed(ActionEvent e)
2621 handler.cancelActivity(id);
2622 us.setProgressBar(MessageManager
2623 .formatMessage("label.cancelled_params", new Object[]
2624 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2628 progressPanel.add(cancel, BorderLayout.EAST);
2634 * @return true if any progress bars are still active
2637 public boolean operationInProgress()
2639 if (progressBars != null && progressBars.size() > 0)
2647 * This will return the first AlignFrame holding the given viewport instance.
2648 * It will break if there are more than one AlignFrames viewing a particular
2652 * @return alignFrame for viewport
2654 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2656 if (desktop != null)
2658 AlignmentPanel[] aps = getAlignmentPanels(
2659 viewport.getSequenceSetId());
2660 for (int panel = 0; aps != null && panel < aps.length; panel++)
2662 if (aps[panel] != null && aps[panel].av == viewport)
2664 return aps[panel].alignFrame;
2671 public VamsasApplication getVamsasApplication()
2673 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2679 * flag set if jalview GUI is being operated programmatically
2681 private boolean inBatchMode = false;
2684 * check if jalview GUI is being operated programmatically
2686 * @return inBatchMode
2688 public boolean isInBatchMode()
2694 * set flag if jalview GUI is being operated programmatically
2696 * @param inBatchMode
2698 public void setInBatchMode(boolean inBatchMode)
2700 this.inBatchMode = inBatchMode;
2703 public void startServiceDiscovery()
2705 startServiceDiscovery(false);
2708 public void startServiceDiscovery(boolean blocking)
2710 boolean alive = true;
2711 Thread t0 = null, t1 = null, t2 = null;
2712 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2715 // todo: changesupport handlers need to be transferred
2716 if (discoverer == null)
2718 discoverer = new jalview.ws.jws1.Discoverer();
2719 // register PCS handler for desktop.
2720 discoverer.addPropertyChangeListener(changeSupport);
2722 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2723 // until we phase out completely
2724 (t0 = new Thread(discoverer)).start();
2727 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2729 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2730 .startDiscoverer(changeSupport);
2734 // TODO: do rest service discovery
2743 } catch (Exception e)
2746 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2747 || (t3 != null && t3.isAlive())
2748 || (t0 != null && t0.isAlive());
2754 * called to check if the service discovery process completed successfully.
2758 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2760 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2762 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2763 .getErrorMessages();
2766 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2768 if (serviceChangedDialog == null)
2770 // only run if we aren't already displaying one of these.
2771 addDialogThread(serviceChangedDialog = new Runnable()
2778 * JalviewDialog jd =new JalviewDialog() {
2780 * @Override protected void cancelPressed() { // TODO
2781 * Auto-generated method stub
2783 * }@Override protected void okPressed() { // TODO
2784 * Auto-generated method stub
2786 * }@Override protected void raiseClosed() { // TODO
2787 * Auto-generated method stub
2789 * } }; jd.initDialogFrame(new
2790 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2791 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2792 * + " or mis-configured HTTP proxy settings.<br/>" +
2793 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
2795 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
2796 * ), true, true, "Web Service Configuration Problem", 450,
2799 * jd.waitForInput();
2801 JvOptionPane.showConfirmDialog(Desktop.desktop,
2802 new JLabel("<html><table width=\"450\"><tr><td>"
2803 + ermsg + "</td></tr></table>"
2804 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2805 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2806 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2807 + " Tools->Preferences dialog box to change them.</p></html>"),
2808 "Web Service Configuration Problem",
2809 JvOptionPane.DEFAULT_OPTION,
2810 JvOptionPane.ERROR_MESSAGE);
2811 serviceChangedDialog = null;
2820 "Errors reported by JABA discovery service. Check web services preferences.\n"
2827 private Runnable serviceChangedDialog = null;
2830 * start a thread to open a URL in the configured browser. Pops up a warning
2831 * dialog to the user if there is an exception when calling out to the browser
2836 public static void showUrl(final String url)
2838 showUrl(url, Desktop.instance);
2842 * Like showUrl but allows progress handler to be specified
2846 * (null) or object implementing IProgressIndicator
2848 public static void showUrl(final String url,
2849 final IProgressIndicator progress)
2851 new Thread(new Runnable()
2858 if (progress != null)
2860 progress.setProgressBar(MessageManager
2861 .formatMessage("status.opening_params", new Object[]
2862 { url }), this.hashCode());
2864 jalview.util.BrowserLauncher.openURL(url);
2865 } catch (Exception ex)
2867 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2869 .getString("label.web_browser_not_found_unix"),
2870 MessageManager.getString("label.web_browser_not_found"),
2871 JvOptionPane.WARNING_MESSAGE);
2873 ex.printStackTrace();
2875 if (progress != null)
2877 progress.setProgressBar(null, this.hashCode());
2883 public static WsParamSetManager wsparamManager = null;
2885 public static ParamManager getUserParameterStore()
2887 if (wsparamManager == null)
2889 wsparamManager = new WsParamSetManager();
2891 return wsparamManager;
2895 * static hyperlink handler proxy method for use by Jalview's internal windows
2899 public static void hyperlinkUpdate(HyperlinkEvent e)
2901 if (e.getEventType() == EventType.ACTIVATED)
2906 url = e.getURL().toString();
2907 Desktop.showUrl(url);
2908 } catch (Exception x)
2912 if (Cache.log != null)
2914 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2919 "Couldn't handle string " + url + " as a URL.");
2922 // ignore any exceptions due to dud links.
2929 * single thread that handles display of dialogs to user.
2931 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2934 * flag indicating if dialogExecutor should try to acquire a permit
2936 private volatile boolean dialogPause = true;
2941 private java.util.concurrent.Semaphore block = new Semaphore(0);
2943 private static groovy.ui.Console groovyConsole;
2946 * add another dialog thread to the queue
2950 public void addDialogThread(final Runnable prompter)
2952 dialogExecutor.submit(new Runnable()
2962 } catch (InterruptedException x)
2966 if (instance == null)
2972 SwingUtilities.invokeAndWait(prompter);
2973 } catch (Exception q)
2975 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2981 public void startDialogQueue()
2983 // set the flag so we don't pause waiting for another permit and semaphore
2984 // the current task to begin
2985 dialogPause = false;
2990 * Outputs an image of the desktop to file in EPS format, after prompting the
2991 * user for choice of Text or Lineart character rendering (unless a preference
2992 * has been set). The file name is generated as
2995 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2999 protected void snapShotWindow_actionPerformed(ActionEvent e)
3001 // currently the menu option to do this is not shown
3004 int width = getWidth();
3005 int height = getHeight();
3007 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3008 ImageWriterI writer = new ImageWriterI()
3011 public void exportImage(Graphics g) throws Exception
3014 Cache.log.info("Successfully written snapshot to file "
3015 + of.getAbsolutePath());
3018 String title = "View of desktop";
3019 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3021 exporter.doExport(of, this, width, height, title);
3025 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3026 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3027 * and location last time the view was expanded (if any). However it does not
3028 * remember the split pane divider location - this is set to match the
3029 * 'exploding' frame.
3033 public void explodeViews(SplitFrame sf)
3035 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3036 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3037 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3039 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3041 int viewCount = topPanels.size();
3048 * Processing in reverse order works, forwards order leaves the first panels
3049 * not visible. I don't know why!
3051 for (int i = viewCount - 1; i >= 0; i--)
3054 * Make new top and bottom frames. These take over the respective
3055 * AlignmentPanel objects, including their AlignmentViewports, so the
3056 * cdna/protein relationships between the viewports is carried over to the
3059 * explodedGeometry holds the (x, y) position of the previously exploded
3060 * SplitFrame, and the (width, height) of the AlignFrame component
3062 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3063 AlignFrame newTopFrame = new AlignFrame(topPanel);
3064 newTopFrame.setSize(oldTopFrame.getSize());
3065 newTopFrame.setVisible(true);
3066 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3067 .getExplodedGeometry();
3068 if (geometry != null)
3070 newTopFrame.setSize(geometry.getSize());
3073 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3074 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3075 newBottomFrame.setSize(oldBottomFrame.getSize());
3076 newBottomFrame.setVisible(true);
3077 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3078 .getExplodedGeometry();
3079 if (geometry != null)
3081 newBottomFrame.setSize(geometry.getSize());
3084 topPanel.av.setGatherViewsHere(false);
3085 bottomPanel.av.setGatherViewsHere(false);
3086 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3088 if (geometry != null)
3090 splitFrame.setLocation(geometry.getLocation());
3092 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3096 * Clear references to the panels (now relocated in the new SplitFrames)
3097 * before closing the old SplitFrame.
3100 bottomPanels.clear();
3105 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3106 * back into the given SplitFrame as additional views. Note that the gathered
3107 * frames may themselves have multiple views.
3111 public void gatherViews(GSplitFrame source)
3114 * special handling of explodedGeometry for a view within a SplitFrame: - it
3115 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3116 * height) of the AlignFrame component
3118 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3119 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3120 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3121 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3122 myBottomFrame.viewport
3123 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3124 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3125 myTopFrame.viewport.setGatherViewsHere(true);
3126 myBottomFrame.viewport.setGatherViewsHere(true);
3127 String topViewId = myTopFrame.viewport.getSequenceSetId();
3128 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3130 JInternalFrame[] frames = desktop.getAllFrames();
3131 for (JInternalFrame frame : frames)
3133 if (frame instanceof SplitFrame && frame != source)
3135 SplitFrame sf = (SplitFrame) frame;
3136 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3137 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3138 boolean gatherThis = false;
3139 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3141 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3142 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3143 if (topViewId.equals(topPanel.av.getSequenceSetId())
3144 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3147 topPanel.av.setGatherViewsHere(false);
3148 bottomPanel.av.setGatherViewsHere(false);
3149 topPanel.av.setExplodedGeometry(
3150 new Rectangle(sf.getLocation(), topFrame.getSize()));
3151 bottomPanel.av.setExplodedGeometry(
3152 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3153 myTopFrame.addAlignmentPanel(topPanel, false);
3154 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3160 topFrame.getAlignPanels().clear();
3161 bottomFrame.getAlignPanels().clear();
3168 * The dust settles...give focus to the tab we did this from.
3170 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3173 public static groovy.ui.Console getGroovyConsole()
3175 return groovyConsole;
3179 * handles the payload of a drag and drop event.
3181 * TODO refactor to desktop utilities class
3184 * - Data source strings extracted from the drop event
3186 * - protocol for each data source extracted from the drop event
3190 * - the payload from the drop event
3193 public static void transferFromDropTarget(List<Object> files,
3194 List<DataSourceType> protocols, DropTargetDropEvent evt,
3195 Transferable t) throws Exception
3198 // BH 2018 changed List<String> to List<Object> to allow for File from
3201 // DataFlavor[] flavors = t.getTransferDataFlavors();
3202 // for (int i = 0; i < flavors.length; i++) {
3203 // if (flavors[i].isFlavorJavaFileListType()) {
3204 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3205 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3206 // for (int j = 0; j < list.size(); j++) {
3207 // File file = (File) list.get(j);
3208 // byte[] data = getDroppedFileBytes(file);
3209 // fileName.setText(file.getName() + " - " + data.length + " " +
3210 // evt.getLocation());
3211 // JTextArea target = (JTextArea) ((DropTarget)
3212 // evt.getSource()).getComponent();
3213 // target.setText(new String(data));
3215 // dtde.dropComplete(true);
3220 DataFlavor uriListFlavor = new DataFlavor(
3221 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3224 urlFlavour = new DataFlavor(
3225 "application/x-java-url; class=java.net.URL");
3226 } catch (ClassNotFoundException cfe)
3228 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3231 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3236 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3237 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3238 // means url may be null.
3241 protocols.add(DataSourceType.URL);
3242 files.add(url.toString());
3243 Cache.log.debug("Drop handled as URL dataflavor "
3244 + files.get(files.size() - 1));
3249 if (Platform.isAMacAndNotJS())
3252 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3255 } catch (Throwable ex)
3257 Cache.log.debug("URL drop handler failed.", ex);
3260 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3262 // Works on Windows and MacOSX
3263 Cache.log.debug("Drop handled as javaFileListFlavor");
3264 for (Object file : (List) t
3265 .getTransferData(DataFlavor.javaFileListFlavor))
3268 protocols.add(DataSourceType.FILE);
3273 // Unix like behaviour
3274 boolean added = false;
3276 if (t.isDataFlavorSupported(uriListFlavor))
3278 Cache.log.debug("Drop handled as uriListFlavor");
3279 // This is used by Unix drag system
3280 data = (String) t.getTransferData(uriListFlavor);
3284 // fallback to text: workaround - on OSX where there's a JVM bug
3285 Cache.log.debug("standard URIListFlavor failed. Trying text");
3286 // try text fallback
3287 DataFlavor textDf = new DataFlavor(
3288 "text/plain;class=java.lang.String");
3289 if (t.isDataFlavorSupported(textDf))
3291 data = (String) t.getTransferData(textDf);
3294 Cache.log.debug("Plain text drop content returned "
3295 + (data == null ? "Null - failed" : data));
3300 while (protocols.size() < files.size())
3302 Cache.log.debug("Adding missing FILE protocol for "
3303 + files.get(protocols.size()));
3304 protocols.add(DataSourceType.FILE);
3306 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3307 data, "\r\n"); st.hasMoreTokens();)
3310 String s = st.nextToken();
3311 if (s.startsWith("#"))
3313 // the line is a comment (as per the RFC 2483)
3316 java.net.URI uri = new java.net.URI(s);
3317 if (uri.getScheme().toLowerCase().startsWith("http"))
3319 protocols.add(DataSourceType.URL);
3320 files.add(uri.toString());
3324 // otherwise preserve old behaviour: catch all for file objects
3325 java.io.File file = new java.io.File(uri);
3326 protocols.add(DataSourceType.FILE);
3327 files.add(file.toString());
3332 if (Cache.log.isDebugEnabled())
3334 if (data == null || !added)
3337 if (t.getTransferDataFlavors() != null
3338 && t.getTransferDataFlavors().length > 0)
3341 "Couldn't resolve drop data. Here are the supported flavors:");
3342 for (DataFlavor fl : t.getTransferDataFlavors())
3345 "Supported transfer dataflavor: " + fl.toString());
3346 Object df = t.getTransferData(fl);
3349 Cache.log.debug("Retrieves: " + df);
3353 Cache.log.debug("Retrieved nothing");
3359 Cache.log.debug("Couldn't resolve dataflavor for drop: "
3365 if (Platform.isWindowsAndNotJS())
3367 Cache.log.debug("Scanning dropped content for Windows Link Files");
3369 // resolve any .lnk files in the file drop
3370 for (int f = 0; f < files.size(); f++)
3372 String source = files.get(f).toString().toLowerCase();
3373 if (protocols.get(f).equals(DataSourceType.FILE)
3374 && (source.endsWith(".lnk") || source.endsWith(".url")
3375 || source.endsWith(".site")))
3379 Object obj = files.get(f);
3380 File lf = (obj instanceof File ? (File) obj
3381 : new File((String) obj));
3382 // process link file to get a URL
3383 Cache.log.debug("Found potential link file: " + lf);
3384 WindowsShortcut wscfile = new WindowsShortcut(lf);
3385 String fullname = wscfile.getRealFilename();
3386 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3387 files.set(f, fullname);
3388 Cache.log.debug("Parsed real filename " + fullname
3389 + " to extract protocol: " + protocols.get(f));
3390 } catch (Exception ex)
3393 "Couldn't parse " + files.get(f) + " as a link file.",
3402 * Sets the Preferences property for experimental features to True or False
3403 * depending on the state of the controlling menu item
3406 protected void showExperimental_actionPerformed(boolean selected)
3408 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3412 * Answers a (possibly empty) list of any structure viewer frames (currently
3413 * for either Jmol or Chimera) which are currently open. This may optionally
3414 * be restricted to viewers of a specified class, or viewers linked to a
3415 * specified alignment panel.
3418 * if not null, only return viewers linked to this panel
3419 * @param structureViewerClass
3420 * if not null, only return viewers of this class
3423 public List<StructureViewerBase> getStructureViewers(
3424 AlignmentPanel apanel,
3425 Class<? extends StructureViewerBase> structureViewerClass)
3427 List<StructureViewerBase> result = new ArrayList<>();
3428 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3430 for (JInternalFrame frame : frames)
3432 if (frame instanceof StructureViewerBase)
3434 if (structureViewerClass == null
3435 || structureViewerClass.isInstance(frame))
3438 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3440 result.add((StructureViewerBase) frame);
3448 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3450 private static boolean debugScaleMessageDone = false;
3452 public static void debugScaleMessage(Graphics g)
3454 if (debugScaleMessageDone)
3458 // output used by tests to check HiDPI scaling settings in action
3461 Graphics2D gg = (Graphics2D) g;
3464 AffineTransform t = gg.getTransform();
3465 double scaleX = t.getScaleX();
3466 double scaleY = t.getScaleY();
3467 Cache.debug(debugScaleMessage + scaleX + " (X)");
3468 Cache.debug(debugScaleMessage + scaleY + " (Y)");
3469 debugScaleMessageDone = true;
3473 Cache.debug("Desktop graphics null");
3475 } catch (Exception e)
3477 Cache.debug(Cache.getStackTraceString(e));