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 KDE).
402 * This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially documented or
403 * guaranteed to exist, so we access it via reflection.
404 * There appear to be unfathomable criteria about what this string can contain, and it if doesn't
405 * meet those criteria then "java" (KDE) or "jalview-bin-Jalview" (GNOME) is used.
406 * "Jalview", "Jalview Develop" and "Jalview Test" seem okay, but "Jalview non-release" does not.
407 * The reflection access may generate a warning:
408 * WARNING: An illegal reflective access operation has occurred
409 * WARNING: Illegal reflective access by jalview.gui.Desktop () to field sun.awt.X11.XToolkit.awtAppClassName
410 * which I don't think can be avoided.
412 if (Platform.isLinux())
416 Toolkit xToolkit = Toolkit.getDefaultToolkit();
417 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
418 Field awtAppClassNameField = null;
420 if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName")))
422 awtAppClassNameField = xToolkit.getClass()
423 .getDeclaredField("awtAppClassName");
426 String title = ChannelProperties.getProperty("app_name");
427 if (awtAppClassNameField != null)
429 awtAppClassNameField.setAccessible(true);
430 awtAppClassNameField.set(xToolkit, title);
434 Cache.log.debug("XToolkit: awtAppClassName not found");
436 } catch (Exception e)
438 Cache.debug("Error setting awtAppClassName");
439 Cache.trace(Cache.getStackTraceString(e));
444 * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to macOS's application menu.
445 * APQHandlers will check to see if a handler is supported before setting it.
449 APQHandlers.setAPQHandlers(this);
450 } catch (Throwable t)
452 Cache.warn("Error setting APQHandlers: " + t.toString());
453 Cache.trace(Cache.getStackTraceString(t));
455 setIconImages(ChannelProperties.getIconList());
457 addWindowListener(new WindowAdapter()
461 public void windowClosing(WindowEvent ev)
467 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
469 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
470 desktop = new MyDesktopPane(selmemusage);
472 showMemusage.setSelected(selmemusage);
473 desktop.setBackground(Color.white);
475 this.setIconImages(ChannelProperties.getIconList());
477 getContentPane().setLayout(new BorderLayout());
478 // alternate config - have scrollbars - see notes in JAL-153
479 // JScrollPane sp = new JScrollPane();
480 // sp.getViewport().setView(desktop);
481 // getContentPane().add(sp, BorderLayout.CENTER);
483 // BH 2018 - just an experiment to try unclipped JInternalFrames.
486 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
489 getContentPane().add(desktop, BorderLayout.CENTER);
490 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
492 // This line prevents Windows Look&Feel resizing all new windows to maximum
493 // if previous window was maximised
494 desktop.setDesktopManager(new MyDesktopManager(
495 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
496 : Platform.isAMacAndNotJS()
497 ? new AquaInternalFrameManager(
498 desktop.getDesktopManager())
499 : desktop.getDesktopManager())));
501 Rectangle dims = getLastKnownDimensions("");
508 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
509 int xPos = Math.max(5, (screenSize.width - 900) / 2);
510 int yPos = Math.max(5, (screenSize.height - 650) / 2);
511 setBounds(xPos, yPos, 900, 650);
514 if (!Platform.isJS())
521 jconsole = new Console(this, showjconsole);
522 jconsole.setHeader(Cache.getVersionDetailsForConsole());
523 showConsole(showjconsole);
525 showNews.setVisible(false);
527 experimentalFeatures.setSelected(showExperimental());
529 getIdentifiersOrgData();
533 // Spawn a thread that shows the splashscreen
536 SwingUtilities.invokeLater(new Runnable()
541 new SplashScreen(true);
546 // Thread off a new instance of the file chooser - this reduces the time
548 // takes to open it later on.
549 new Thread(new Runnable()
554 Cache.log.debug("Filechooser init thread started.");
555 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
556 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
558 Cache.log.debug("Filechooser init thread finished.");
561 // Add the service change listener
562 changeSupport.addJalviewPropertyChangeListener("services",
563 new PropertyChangeListener()
567 public void propertyChange(PropertyChangeEvent evt)
569 Cache.log.debug("Firing service changed event for "
570 + evt.getNewValue());
571 JalviewServicesChanged(evt);
576 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
578 this.addWindowListener(new WindowAdapter()
581 public void windowClosing(WindowEvent evt)
588 this.addMouseListener(ma = new MouseAdapter()
591 public void mousePressed(MouseEvent evt)
593 if (evt.isPopupTrigger()) // Mac
595 showPasteMenu(evt.getX(), evt.getY());
600 public void mouseReleased(MouseEvent evt)
602 if (evt.isPopupTrigger()) // Windows
604 showPasteMenu(evt.getX(), evt.getY());
608 desktop.addMouseListener(ma);
612 * Answers true if user preferences to enable experimental features is True
617 public boolean showExperimental()
619 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
620 Boolean.FALSE.toString());
621 return Boolean.valueOf(experimental).booleanValue();
624 public void doConfigureStructurePrefs()
626 // configure services
627 StructureSelectionManager ssm = StructureSelectionManager
628 .getStructureSelectionManager(this);
629 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
631 ssm.setAddTempFacAnnot(
632 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
633 ssm.setProcessSecondaryStructure(
634 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
635 ssm.setSecStructServices(
636 Cache.getDefault(Preferences.USE_RNAVIEW, true));
640 ssm.setAddTempFacAnnot(false);
641 ssm.setProcessSecondaryStructure(false);
642 ssm.setSecStructServices(false);
646 public void checkForNews()
648 final Desktop me = this;
649 // Thread off the news reader, in case there are connection problems.
650 new Thread(new Runnable()
655 Cache.log.debug("Starting news thread.");
656 jvnews = new BlogReader(me);
657 showNews.setVisible(true);
658 Cache.log.debug("Completed news thread.");
663 public void getIdentifiersOrgData()
665 // Thread off the identifiers fetcher
666 new Thread(new Runnable()
671 Cache.log.debug("Downloading data from identifiers.org");
674 UrlDownloadClient.download(IdOrgSettings.getUrl(),
675 IdOrgSettings.getDownloadLocation());
676 } catch (IOException e)
678 Cache.log.debug("Exception downloading identifiers.org data"
687 protected void showNews_actionPerformed(ActionEvent e)
689 showNews(showNews.isSelected());
692 void showNews(boolean visible)
694 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
695 showNews.setSelected(visible);
696 if (visible && !jvnews.isVisible())
698 new Thread(new Runnable()
703 long now = System.currentTimeMillis();
704 Desktop.instance.setProgressBar(
705 MessageManager.getString("status.refreshing_news"), now);
706 jvnews.refreshNews();
707 Desktop.instance.setProgressBar(null, now);
715 * recover the last known dimensions for a jalview window
718 * - empty string is desktop, all other windows have unique prefix
719 * @return null or last known dimensions scaled to current geometry (if last
720 * window geom was known)
722 Rectangle getLastKnownDimensions(String windowName)
724 // TODO: lock aspect ratio for scaling desktop Bug #0058199
725 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
726 String x = Cache.getProperty(windowName + "SCREEN_X");
727 String y = Cache.getProperty(windowName + "SCREEN_Y");
728 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
729 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
730 if ((x != null) && (y != null) && (width != null) && (height != null))
732 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
733 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
734 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
736 // attempt #1 - try to cope with change in screen geometry - this
737 // version doesn't preserve original jv aspect ratio.
738 // take ratio of current screen size vs original screen size.
739 double sw = ((1f * screenSize.width) / (1f * Integer
740 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
741 double sh = ((1f * screenSize.height) / (1f * Integer
742 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
743 // rescale the bounds depending upon the current screen geometry.
744 ix = (int) (ix * sw);
745 iw = (int) (iw * sw);
746 iy = (int) (iy * sh);
747 ih = (int) (ih * sh);
748 while (ix >= screenSize.width)
751 "Window geometry location recall error: shifting horizontal to within screenbounds.");
752 ix -= screenSize.width;
754 while (iy >= screenSize.height)
757 "Window geometry location recall error: shifting vertical to within screenbounds.");
758 iy -= screenSize.height;
761 "Got last known dimensions for " + windowName + ": x:" + ix
762 + " y:" + iy + " width:" + iw + " height:" + ih);
764 // return dimensions for new instance
765 return new Rectangle(ix, iy, iw, ih);
770 void showPasteMenu(int x, int y)
772 JPopupMenu popup = new JPopupMenu();
773 JMenuItem item = new JMenuItem(
774 MessageManager.getString("label.paste_new_window"));
775 item.addActionListener(new ActionListener()
778 public void actionPerformed(ActionEvent evt)
785 popup.show(this, x, y);
792 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
793 Transferable contents = c.getContents(this);
795 if (contents != null)
797 String file = (String) contents
798 .getTransferData(DataFlavor.stringFlavor);
800 FileFormatI format = new IdentifyFile().identify(file,
801 DataSourceType.PASTE);
803 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
806 } catch (Exception ex)
809 "Unable to paste alignment from system clipboard:\n" + ex);
814 * Adds and opens the given frame to the desktop
825 public static synchronized void addInternalFrame(
826 final JInternalFrame frame, String title, int w, int h)
828 addInternalFrame(frame, title, true, w, h, true, false);
832 * Add an internal frame to the Jalview desktop
839 * When true, display frame immediately, otherwise, caller must call
840 * setVisible themselves.
846 public static synchronized void addInternalFrame(
847 final JInternalFrame frame, String title, boolean makeVisible,
850 addInternalFrame(frame, title, makeVisible, w, h, true, false);
854 * Add an internal frame to the Jalview desktop and make it visible
867 public static synchronized void addInternalFrame(
868 final JInternalFrame frame, String title, int w, int h,
871 addInternalFrame(frame, title, true, w, h, resizable, false);
875 * Add an internal frame to the Jalview desktop
882 * When true, display frame immediately, otherwise, caller must call
883 * setVisible themselves.
890 * @param ignoreMinSize
891 * Do not set the default minimum size for frame
893 public static synchronized void addInternalFrame(
894 final JInternalFrame frame, String title, boolean makeVisible,
895 int w, int h, boolean resizable, boolean ignoreMinSize)
898 // TODO: allow callers to determine X and Y position of frame (eg. via
900 // TODO: consider fixing method to update entries in the window submenu with
901 // the current window title
903 frame.setTitle(title);
904 if (frame.getWidth() < 1 || frame.getHeight() < 1)
908 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
909 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
910 // IF JALVIEW IS RUNNING HEADLESS
911 // ///////////////////////////////////////////////
912 if (instance == null || (System.getProperty("java.awt.headless") != null
913 && System.getProperty("java.awt.headless").equals("true")))
922 frame.setMinimumSize(
923 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
925 // Set default dimension for Alignment Frame window.
926 // The Alignment Frame window could be added from a number of places,
928 // I did this here in order not to miss out on any Alignment frame.
929 if (frame instanceof AlignFrame)
931 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
932 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
936 frame.setVisible(makeVisible);
937 frame.setClosable(true);
938 frame.setResizable(resizable);
939 frame.setMaximizable(resizable);
940 frame.setIconifiable(resizable);
941 frame.setOpaque(Platform.isJS());
943 if (frame.getX() < 1 && frame.getY() < 1)
945 frame.setLocation(xOffset * openFrameCount,
946 yOffset * ((openFrameCount - 1) % 10) + yOffset);
950 * add an entry for the new frame in the Window menu
951 * (and remove it when the frame is closed)
953 final JMenuItem menuItem = new JMenuItem(title);
954 frame.addInternalFrameListener(new InternalFrameAdapter()
957 public void internalFrameActivated(InternalFrameEvent evt)
959 JInternalFrame itf = desktop.getSelectedFrame();
962 if (itf instanceof AlignFrame)
964 Jalview.setCurrentAlignFrame((AlignFrame) itf);
971 public void internalFrameClosed(InternalFrameEvent evt)
973 PaintRefresher.RemoveComponent(frame);
976 * defensive check to prevent frames being
977 * added half off the window
979 if (openFrameCount > 0)
985 * ensure no reference to alignFrame retained by menu item listener
987 if (menuItem.getActionListeners().length > 0)
989 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
991 windowMenu.remove(menuItem);
995 menuItem.addActionListener(new ActionListener()
998 public void actionPerformed(ActionEvent e)
1002 frame.setSelected(true);
1003 frame.setIcon(false);
1004 } catch (java.beans.PropertyVetoException ex)
1006 // System.err.println(ex.toString());
1011 setKeyBindings(frame);
1015 windowMenu.add(menuItem);
1020 frame.setSelected(true);
1021 frame.requestFocus();
1022 } catch (java.beans.PropertyVetoException ve)
1024 } catch (java.lang.ClassCastException cex)
1027 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1033 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1038 private static void setKeyBindings(JInternalFrame frame)
1040 @SuppressWarnings("serial")
1041 final Action closeAction = new AbstractAction()
1044 public void actionPerformed(ActionEvent e)
1051 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1053 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1054 InputEvent.CTRL_DOWN_MASK);
1055 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1056 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1058 InputMap inputMap = frame
1059 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1060 String ctrlW = ctrlWKey.toString();
1061 inputMap.put(ctrlWKey, ctrlW);
1062 inputMap.put(cmdWKey, ctrlW);
1064 ActionMap actionMap = frame.getActionMap();
1065 actionMap.put(ctrlW, closeAction);
1069 public void lostOwnership(Clipboard clipboard, Transferable contents)
1073 Desktop.jalviewClipboard = null;
1076 internalCopy = false;
1080 public void dragEnter(DropTargetDragEvent evt)
1085 public void dragExit(DropTargetEvent evt)
1090 public void dragOver(DropTargetDragEvent evt)
1095 public void dropActionChanged(DropTargetDragEvent evt)
1106 public void drop(DropTargetDropEvent evt)
1108 boolean success = true;
1109 // JAL-1552 - acceptDrop required before getTransferable call for
1110 // Java's Transferable for native dnd
1111 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1112 Transferable t = evt.getTransferable();
1113 List<Object> files = new ArrayList<>();
1114 List<DataSourceType> protocols = new ArrayList<>();
1118 Desktop.transferFromDropTarget(files, protocols, evt, t);
1119 } catch (Exception e)
1121 e.printStackTrace();
1129 for (int i = 0; i < files.size(); i++)
1131 // BH 2018 File or String
1132 Object file = files.get(i);
1133 String fileName = file.toString();
1134 DataSourceType protocol = (protocols == null)
1135 ? DataSourceType.FILE
1137 FileFormatI format = null;
1139 if (fileName.endsWith(".jar"))
1141 format = FileFormat.Jalview;
1146 format = new IdentifyFile().identify(file, protocol);
1148 if (file instanceof File)
1150 Platform.cacheFileData((File) file);
1152 new FileLoader().LoadFile(null, file, protocol, format);
1155 } catch (Exception ex)
1160 evt.dropComplete(success); // need this to ensure input focus is properly
1161 // transfered to any new windows created
1171 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1173 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1174 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1175 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1176 BackupFiles.getEnabled());
1178 chooser.setFileView(new JalviewFileView());
1179 chooser.setDialogTitle(
1180 MessageManager.getString("label.open_local_file"));
1181 chooser.setToolTipText(MessageManager.getString("action.open"));
1183 chooser.setResponseHandler(0, new Runnable()
1188 File selectedFile = chooser.getSelectedFile();
1189 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1191 FileFormatI format = chooser.getSelectedFormat();
1194 * Call IdentifyFile to verify the file contains what its extension implies.
1195 * Skip this step for dynamically added file formats, because
1196 * IdentifyFile does not know how to recognise them.
1198 if (FileFormats.getInstance().isIdentifiable(format))
1202 format = new IdentifyFile().identify(selectedFile,
1203 DataSourceType.FILE);
1204 } catch (FileFormatException e)
1206 // format = null; //??
1210 new FileLoader().LoadFile(viewport, selectedFile,
1211 DataSourceType.FILE, format);
1214 chooser.showOpenDialog(this);
1218 * Shows a dialog for input of a URL at which to retrieve alignment data
1223 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1225 // This construct allows us to have a wider textfield
1227 JLabel label = new JLabel(
1228 MessageManager.getString("label.input_file_url"));
1230 JPanel panel = new JPanel(new GridLayout(2, 1));
1234 * the URL to fetch is input in
1235 * Java: an editable combobox with history
1236 * JS: (pending JAL-3038) a plain text field
1239 String urlBase = "https://www.";
1240 if (Platform.isJS())
1242 history = new JTextField(urlBase, 35);
1251 JComboBox<String> asCombo = new JComboBox<>();
1252 asCombo.setPreferredSize(new Dimension(400, 20));
1253 asCombo.setEditable(true);
1254 asCombo.addItem(urlBase);
1255 String historyItems = Cache.getProperty("RECENT_URL");
1256 if (historyItems != null)
1258 for (String token : historyItems.split("\\t"))
1260 asCombo.addItem(token);
1267 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1268 MessageManager.getString("action.cancel") };
1269 Runnable action = new Runnable()
1274 @SuppressWarnings("unchecked")
1275 String url = (history instanceof JTextField
1276 ? ((JTextField) history).getText()
1277 : ((JComboBox<String>) history).getEditor().getItem()
1278 .toString().trim());
1280 if (url.toLowerCase().endsWith(".jar"))
1282 if (viewport != null)
1284 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1285 FileFormat.Jalview);
1289 new FileLoader().LoadFile(url, DataSourceType.URL,
1290 FileFormat.Jalview);
1295 FileFormatI format = null;
1298 format = new IdentifyFile().identify(url, DataSourceType.URL);
1299 } catch (FileFormatException e)
1301 // TODO revise error handling, distinguish between
1302 // URL not found and response not valid
1307 String msg = MessageManager
1308 .formatMessage("label.couldnt_locate", url);
1309 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1310 MessageManager.getString("label.url_not_found"),
1311 JvOptionPane.WARNING_MESSAGE);
1316 if (viewport != null)
1318 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1323 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1328 String dialogOption = MessageManager
1329 .getString("label.input_alignment_from_url");
1330 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1331 .showInternalDialog(panel, dialogOption,
1332 JvOptionPane.YES_NO_CANCEL_OPTION,
1333 JvOptionPane.PLAIN_MESSAGE, null, options,
1334 MessageManager.getString("action.ok"));
1338 * Opens the CutAndPaste window for the user to paste an alignment in to
1341 * - if not null, the pasted alignment is added to the current
1342 * alignment; if null, to a new alignment window
1345 public void inputTextboxMenuItem_actionPerformed(
1346 AlignmentViewPanel viewPanel)
1348 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1349 cap.setForInput(viewPanel);
1350 Desktop.addInternalFrame(cap,
1351 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1361 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1362 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1363 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1364 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1365 getWidth(), getHeight()));
1367 if (jconsole != null)
1369 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1370 jconsole.stopConsole();
1374 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1377 if (dialogExecutor != null)
1379 dialogExecutor.shutdownNow();
1381 closeAll_actionPerformed(null);
1383 if (groovyConsole != null)
1385 // suppress a possible repeat prompt to save script
1386 groovyConsole.setDirty(false);
1387 groovyConsole.exit();
1392 private void storeLastKnownDimensions(String string, Rectangle jc)
1394 Cache.log.debug("Storing last known dimensions for " + string + ": x:"
1395 + jc.x + " y:" + jc.y + " width:" + jc.width + " height:"
1398 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1399 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1400 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1401 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1411 public void aboutMenuItem_actionPerformed(ActionEvent e)
1413 new Thread(new Runnable()
1418 new SplashScreen(false);
1424 * Returns the html text for the About screen, including any available version
1425 * number, build details, author details and citation reference, but without
1426 * the enclosing {@code html} tags
1430 public String getAboutMessage()
1432 StringBuilder message = new StringBuilder(1024);
1433 message.append("<div style=\"font-family: sans-serif;\">")
1434 .append("<h1><strong>Version: ")
1435 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1436 .append("<strong>Built: <em>")
1437 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1438 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1439 .append("</strong>");
1441 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1442 if (latestVersion.equals("Checking"))
1444 // JBP removed this message for 2.11: May be reinstated in future version
1445 // message.append("<br>...Checking latest version...</br>");
1447 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1449 boolean red = false;
1450 if (Cache.getProperty("VERSION").toLowerCase()
1451 .indexOf("automated build") == -1)
1454 // Displayed when code version and jnlp version do not match and code
1455 // version is not a development build
1456 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1459 message.append("<br>!! Version ")
1460 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1461 .append(" is available for download from ")
1462 .append(Cache.getDefault("www.jalview.org",
1463 "https://www.jalview.org"))
1467 message.append("</div>");
1470 message.append("<br>Authors: ");
1471 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1472 message.append(CITATION);
1474 message.append("</div>");
1476 return message.toString();
1480 * Action on requesting Help documentation
1483 public void documentationMenuItem_actionPerformed()
1487 if (Platform.isJS())
1489 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1498 Help.showHelpWindow();
1500 } catch (Exception ex)
1502 System.err.println("Error opening help: " + ex.getMessage());
1507 public void closeAll_actionPerformed(ActionEvent e)
1509 // TODO show a progress bar while closing?
1510 JInternalFrame[] frames = desktop.getAllFrames();
1511 for (int i = 0; i < frames.length; i++)
1515 frames[i].setClosed(true);
1516 } catch (java.beans.PropertyVetoException ex)
1520 Jalview.setCurrentAlignFrame(null);
1521 System.out.println("ALL CLOSED");
1524 * reset state of singleton objects as appropriate (clear down session state
1525 * when all windows are closed)
1527 StructureSelectionManager ssm = StructureSelectionManager
1528 .getStructureSelectionManager(this);
1536 public void raiseRelated_actionPerformed(ActionEvent e)
1538 reorderAssociatedWindows(false, false);
1542 public void minimizeAssociated_actionPerformed(ActionEvent e)
1544 reorderAssociatedWindows(true, false);
1547 void closeAssociatedWindows()
1549 reorderAssociatedWindows(false, true);
1555 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1559 protected void garbageCollect_actionPerformed(ActionEvent e)
1561 // We simply collect the garbage
1562 Cache.log.debug("Collecting garbage...");
1564 Cache.log.debug("Finished garbage collection.");
1571 * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1575 protected void showMemusage_actionPerformed(ActionEvent e)
1577 desktop.showMemoryUsage(showMemusage.isSelected());
1584 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1588 protected void showConsole_actionPerformed(ActionEvent e)
1590 showConsole(showConsole.isSelected());
1593 Console jconsole = null;
1596 * control whether the java console is visible or not
1600 void showConsole(boolean selected)
1602 // TODO: decide if we should update properties file
1603 if (jconsole != null) // BH 2018
1605 showConsole.setSelected(selected);
1606 Cache.setProperty("SHOW_JAVA_CONSOLE",
1607 Boolean.valueOf(selected).toString());
1608 jconsole.setVisible(selected);
1612 void reorderAssociatedWindows(boolean minimize, boolean close)
1614 JInternalFrame[] frames = desktop.getAllFrames();
1615 if (frames == null || frames.length < 1)
1620 AlignmentViewport source = null, target = null;
1621 if (frames[0] instanceof AlignFrame)
1623 source = ((AlignFrame) frames[0]).getCurrentView();
1625 else if (frames[0] instanceof TreePanel)
1627 source = ((TreePanel) frames[0]).getViewPort();
1629 else if (frames[0] instanceof PCAPanel)
1631 source = ((PCAPanel) frames[0]).av;
1633 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1635 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1640 for (int i = 0; i < frames.length; i++)
1643 if (frames[i] == null)
1647 if (frames[i] instanceof AlignFrame)
1649 target = ((AlignFrame) frames[i]).getCurrentView();
1651 else if (frames[i] instanceof TreePanel)
1653 target = ((TreePanel) frames[i]).getViewPort();
1655 else if (frames[i] instanceof PCAPanel)
1657 target = ((PCAPanel) frames[i]).av;
1659 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1661 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1664 if (source == target)
1670 frames[i].setClosed(true);
1674 frames[i].setIcon(minimize);
1677 frames[i].toFront();
1681 } catch (java.beans.PropertyVetoException ex)
1696 protected void preferences_actionPerformed(ActionEvent e)
1698 Preferences.openPreferences();
1702 * Prompts the user to choose a file and then saves the Jalview state as a
1703 * Jalview project file
1706 public void saveState_actionPerformed()
1708 saveState_actionPerformed(false);
1711 public void saveState_actionPerformed(boolean saveAs)
1713 java.io.File projectFile = getProjectFile();
1714 // autoSave indicates we already have a file and don't need to ask
1715 boolean autoSave = projectFile != null && !saveAs
1716 && BackupFiles.getEnabled();
1718 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1719 // saveAs="+saveAs+", Backups
1720 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1722 boolean approveSave = false;
1725 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1728 chooser.setFileView(new JalviewFileView());
1729 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1731 int value = chooser.showSaveDialog(this);
1733 if (value == JalviewFileChooser.APPROVE_OPTION)
1735 projectFile = chooser.getSelectedFile();
1736 setProjectFile(projectFile);
1741 if (approveSave || autoSave)
1743 final Desktop me = this;
1744 final java.io.File chosenFile = projectFile;
1745 new Thread(new Runnable()
1750 // TODO: refactor to Jalview desktop session controller action.
1751 setProgressBar(MessageManager.formatMessage(
1752 "label.saving_jalview_project", new Object[]
1753 { chosenFile.getName() }), chosenFile.hashCode());
1754 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1755 // TODO catch and handle errors for savestate
1756 // TODO prevent user from messing with the Desktop whilst we're saving
1759 boolean doBackup = BackupFiles.getEnabled();
1760 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1763 new Jalview2XML().saveState(
1764 doBackup ? backupfiles.getTempFile() : chosenFile);
1768 backupfiles.setWriteSuccess(true);
1769 backupfiles.rollBackupsAndRenameTempFile();
1771 } catch (OutOfMemoryError oom)
1773 new OOMWarning("Whilst saving current state to "
1774 + chosenFile.getName(), oom);
1775 } catch (Exception ex)
1777 Cache.log.error("Problems whilst trying to save to "
1778 + chosenFile.getName(), ex);
1779 JvOptionPane.showMessageDialog(me,
1780 MessageManager.formatMessage(
1781 "label.error_whilst_saving_current_state_to",
1783 { chosenFile.getName() }),
1784 MessageManager.getString("label.couldnt_save_project"),
1785 JvOptionPane.WARNING_MESSAGE);
1787 setProgressBar(null, chosenFile.hashCode());
1794 public void saveAsState_actionPerformed(ActionEvent e)
1796 saveState_actionPerformed(true);
1799 private void setProjectFile(File choice)
1801 this.projectFile = choice;
1804 public File getProjectFile()
1806 return this.projectFile;
1810 * Shows a file chooser dialog and tries to read in the selected file as a
1814 public void loadState_actionPerformed()
1816 final String[] suffix = new String[] { "jvp", "jar" };
1817 final String[] desc = new String[] { "Jalview Project",
1818 "Jalview Project (old)" };
1819 JalviewFileChooser chooser = new JalviewFileChooser(
1820 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1821 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1825 chooser.setFileView(new JalviewFileView());
1826 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1827 chooser.setResponseHandler(0, new Runnable()
1832 File selectedFile = chooser.getSelectedFile();
1833 setProjectFile(selectedFile);
1834 String choice = selectedFile.getAbsolutePath();
1835 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1836 new Thread(new Runnable()
1843 new Jalview2XML().loadJalviewAlign(selectedFile);
1844 } catch (OutOfMemoryError oom)
1846 new OOMWarning("Whilst loading project from " + choice, oom);
1847 } catch (Exception ex)
1850 "Problems whilst loading project from " + choice, ex);
1851 JvOptionPane.showMessageDialog(Desktop.desktop,
1852 MessageManager.formatMessage(
1853 "label.error_whilst_loading_project_from",
1857 .getString("label.couldnt_load_project"),
1858 JvOptionPane.WARNING_MESSAGE);
1861 }, "Project Loader").start();
1865 chooser.showOpenDialog(this);
1869 public void inputSequence_actionPerformed(ActionEvent e)
1871 new SequenceFetcher(this);
1874 JPanel progressPanel;
1876 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1878 public void startLoading(final Object fileName)
1880 if (fileLoadingCount == 0)
1882 fileLoadingPanels.add(addProgressPanel(MessageManager
1883 .formatMessage("label.loading_file", new Object[]
1889 private JPanel addProgressPanel(String string)
1891 if (progressPanel == null)
1893 progressPanel = new JPanel(new GridLayout(1, 1));
1894 totalProgressCount = 0;
1895 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1897 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1898 JProgressBar progressBar = new JProgressBar();
1899 progressBar.setIndeterminate(true);
1901 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1903 thisprogress.add(progressBar, BorderLayout.CENTER);
1904 progressPanel.add(thisprogress);
1905 ((GridLayout) progressPanel.getLayout()).setRows(
1906 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1907 ++totalProgressCount;
1908 instance.validate();
1909 return thisprogress;
1912 int totalProgressCount = 0;
1914 private void removeProgressPanel(JPanel progbar)
1916 if (progressPanel != null)
1918 synchronized (progressPanel)
1920 progressPanel.remove(progbar);
1921 GridLayout gl = (GridLayout) progressPanel.getLayout();
1922 gl.setRows(gl.getRows() - 1);
1923 if (--totalProgressCount < 1)
1925 this.getContentPane().remove(progressPanel);
1926 progressPanel = null;
1933 public void stopLoading()
1936 if (fileLoadingCount < 1)
1938 while (fileLoadingPanels.size() > 0)
1940 removeProgressPanel(fileLoadingPanels.remove(0));
1942 fileLoadingPanels.clear();
1943 fileLoadingCount = 0;
1948 public static int getViewCount(String alignmentId)
1950 AlignmentViewport[] aps = getViewports(alignmentId);
1951 return (aps == null) ? 0 : aps.length;
1956 * @param alignmentId
1957 * - if null, all sets are returned
1958 * @return all AlignmentPanels concerning the alignmentId sequence set
1960 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1962 if (Desktop.desktop == null)
1964 // no frames created and in headless mode
1965 // TODO: verify that frames are recoverable when in headless mode
1968 List<AlignmentPanel> aps = new ArrayList<>();
1969 AlignFrame[] frames = getAlignFrames();
1974 for (AlignFrame af : frames)
1976 for (AlignmentPanel ap : af.alignPanels)
1978 if (alignmentId == null
1979 || alignmentId.equals(ap.av.getSequenceSetId()))
1985 if (aps.size() == 0)
1989 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1994 * get all the viewports on an alignment.
1996 * @param sequenceSetId
1997 * unique alignment id (may be null - all viewports returned in that
1999 * @return all viewports on the alignment bound to sequenceSetId
2001 public static AlignmentViewport[] getViewports(String sequenceSetId)
2003 List<AlignmentViewport> viewp = new ArrayList<>();
2004 if (desktop != null)
2006 AlignFrame[] frames = Desktop.getAlignFrames();
2008 for (AlignFrame afr : frames)
2010 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2011 .equals(sequenceSetId))
2013 if (afr.alignPanels != null)
2015 for (AlignmentPanel ap : afr.alignPanels)
2017 if (sequenceSetId == null
2018 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2026 viewp.add(afr.getViewport());
2030 if (viewp.size() > 0)
2032 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2039 * Explode the views in the given frame into separate AlignFrame
2043 public static void explodeViews(AlignFrame af)
2045 int size = af.alignPanels.size();
2051 // FIXME: ideally should use UI interface API
2052 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2053 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2054 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2055 for (int i = 0; i < size; i++)
2057 AlignmentPanel ap = af.alignPanels.get(i);
2059 AlignFrame newaf = new AlignFrame(ap);
2061 // transfer reference for existing feature settings to new alignFrame
2062 if (ap == af.alignPanel)
2064 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2066 newaf.featureSettings = viewFeatureSettings;
2068 newaf.setFeatureSettingsGeometry(fsBounds);
2072 * Restore the view's last exploded frame geometry if known. Multiple
2073 * views from one exploded frame share and restore the same (frame)
2074 * position and size.
2076 Rectangle geometry = ap.av.getExplodedGeometry();
2077 if (geometry != null)
2079 newaf.setBounds(geometry);
2082 ap.av.setGatherViewsHere(false);
2084 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2085 AlignFrame.DEFAULT_HEIGHT);
2086 // and materialise a new feature settings dialog instance for the new
2088 // (closes the old as if 'OK' was pressed)
2089 if (ap == af.alignPanel && newaf.featureSettings != null
2090 && newaf.featureSettings.isOpen()
2091 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2093 newaf.showFeatureSettingsUI();
2097 af.featureSettings = null;
2098 af.alignPanels.clear();
2099 af.closeMenuItem_actionPerformed(true);
2104 * Gather expanded views (separate AlignFrame's) with the same sequence set
2105 * identifier back in to this frame as additional views, and close the
2106 * expanded views. Note the expanded frames may themselves have multiple
2107 * views. We take the lot.
2111 public void gatherViews(AlignFrame source)
2113 source.viewport.setGatherViewsHere(true);
2114 source.viewport.setExplodedGeometry(source.getBounds());
2115 JInternalFrame[] frames = desktop.getAllFrames();
2116 String viewId = source.viewport.getSequenceSetId();
2117 for (int t = 0; t < frames.length; t++)
2119 if (frames[t] instanceof AlignFrame && frames[t] != source)
2121 AlignFrame af = (AlignFrame) frames[t];
2122 boolean gatherThis = false;
2123 for (int a = 0; a < af.alignPanels.size(); a++)
2125 AlignmentPanel ap = af.alignPanels.get(a);
2126 if (viewId.equals(ap.av.getSequenceSetId()))
2129 ap.av.setGatherViewsHere(false);
2130 ap.av.setExplodedGeometry(af.getBounds());
2131 source.addAlignmentPanel(ap, false);
2137 if (af.featureSettings != null && af.featureSettings.isOpen())
2139 if (source.featureSettings == null)
2141 // preserve the feature settings geometry for this frame
2142 source.featureSettings = af.featureSettings;
2143 source.setFeatureSettingsGeometry(
2144 af.getFeatureSettingsGeometry());
2148 // close it and forget
2149 af.featureSettings.close();
2152 af.alignPanels.clear();
2153 af.closeMenuItem_actionPerformed(true);
2158 // refresh the feature setting UI for the source frame if it exists
2159 if (source.featureSettings != null && source.featureSettings.isOpen())
2161 source.showFeatureSettingsUI();
2165 public JInternalFrame[] getAllFrames()
2167 return desktop.getAllFrames();
2171 * Checks the given url to see if it gives a response indicating that the user
2172 * should be informed of a new questionnaire.
2176 public void checkForQuestionnaire(String url)
2178 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2179 // javax.swing.SwingUtilities.invokeLater(jvq);
2180 new Thread(jvq).start();
2183 public void checkURLLinks()
2185 // Thread off the URL link checker
2186 addDialogThread(new Runnable()
2191 if (Cache.getDefault("CHECKURLLINKS", true))
2193 // check what the actual links are - if it's just the default don't
2194 // bother with the warning
2195 List<String> links = Preferences.sequenceUrlLinks
2198 // only need to check links if there is one with a
2199 // SEQUENCE_ID which is not the default EMBL_EBI link
2200 ListIterator<String> li = links.listIterator();
2201 boolean check = false;
2202 List<JLabel> urls = new ArrayList<>();
2203 while (li.hasNext())
2205 String link = li.next();
2206 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2207 && !UrlConstants.isDefaultString(link))
2210 int barPos = link.indexOf("|");
2211 String urlMsg = barPos == -1 ? link
2212 : link.substring(0, barPos) + ": "
2213 + link.substring(barPos + 1);
2214 urls.add(new JLabel(urlMsg));
2222 // ask user to check in case URL links use old style tokens
2223 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2224 JPanel msgPanel = new JPanel();
2225 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2226 msgPanel.add(Box.createVerticalGlue());
2227 JLabel msg = new JLabel(MessageManager
2228 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2229 JLabel msg2 = new JLabel(MessageManager
2230 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2232 for (JLabel url : urls)
2238 final JCheckBox jcb = new JCheckBox(
2239 MessageManager.getString("label.do_not_display_again"));
2240 jcb.addActionListener(new ActionListener()
2243 public void actionPerformed(ActionEvent e)
2245 // update Cache settings for "don't show this again"
2246 boolean showWarningAgain = !jcb.isSelected();
2247 Cache.setProperty("CHECKURLLINKS",
2248 Boolean.valueOf(showWarningAgain).toString());
2253 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2255 .getString("label.SEQUENCE_ID_no_longer_used"),
2256 JvOptionPane.WARNING_MESSAGE);
2263 * Proxy class for JDesktopPane which optionally displays the current memory
2264 * usage and highlights the desktop area with a red bar if free memory runs
2269 public class MyDesktopPane extends JDesktopPane implements Runnable
2271 private static final float ONE_MB = 1048576f;
2273 boolean showMemoryUsage = false;
2277 java.text.NumberFormat df;
2279 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2282 public MyDesktopPane(boolean showMemoryUsage)
2284 showMemoryUsage(showMemoryUsage);
2287 public void showMemoryUsage(boolean showMemory)
2289 this.showMemoryUsage = showMemory;
2292 Thread worker = new Thread(this);
2298 public boolean isShowMemoryUsage()
2300 return showMemoryUsage;
2306 df = java.text.NumberFormat.getNumberInstance();
2307 df.setMaximumFractionDigits(2);
2308 runtime = Runtime.getRuntime();
2310 while (showMemoryUsage)
2314 maxMemory = runtime.maxMemory() / ONE_MB;
2315 allocatedMemory = runtime.totalMemory() / ONE_MB;
2316 freeMemory = runtime.freeMemory() / ONE_MB;
2317 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2319 percentUsage = (totalFreeMemory / maxMemory) * 100;
2321 // if (percentUsage < 20)
2323 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2325 // instance.set.setBorder(border1);
2328 // sleep after showing usage
2330 } catch (Exception ex)
2332 ex.printStackTrace();
2338 public void paintComponent(Graphics g)
2340 if (showMemoryUsage && g != null && df != null)
2342 if (percentUsage < 20)
2344 g.setColor(Color.red);
2346 FontMetrics fm = g.getFontMetrics();
2349 g.drawString(MessageManager.formatMessage("label.memory_stats",
2351 { df.format(totalFreeMemory), df.format(maxMemory),
2352 df.format(percentUsage) }),
2353 10, getHeight() - fm.getHeight());
2357 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2358 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2363 * Accessor method to quickly get all the AlignmentFrames loaded.
2365 * @return an array of AlignFrame, or null if none found
2367 public static AlignFrame[] getAlignFrames()
2369 if (Jalview.isHeadlessMode())
2371 // Desktop.desktop is null in headless mode
2372 return new AlignFrame[] { Jalview.currentAlignFrame };
2375 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2381 List<AlignFrame> avp = new ArrayList<>();
2383 for (int i = frames.length - 1; i > -1; i--)
2385 if (frames[i] instanceof AlignFrame)
2387 avp.add((AlignFrame) frames[i]);
2389 else if (frames[i] instanceof SplitFrame)
2392 * Also check for a split frame containing an AlignFrame
2394 GSplitFrame sf = (GSplitFrame) frames[i];
2395 if (sf.getTopFrame() instanceof AlignFrame)
2397 avp.add((AlignFrame) sf.getTopFrame());
2399 if (sf.getBottomFrame() instanceof AlignFrame)
2401 avp.add((AlignFrame) sf.getBottomFrame());
2405 if (avp.size() == 0)
2409 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2414 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2418 public GStructureViewer[] getJmols()
2420 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2426 List<GStructureViewer> avp = new ArrayList<>();
2428 for (int i = frames.length - 1; i > -1; i--)
2430 if (frames[i] instanceof AppJmol)
2432 GStructureViewer af = (GStructureViewer) frames[i];
2436 if (avp.size() == 0)
2440 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2445 * Add Groovy Support to Jalview
2448 public void groovyShell_actionPerformed()
2452 openGroovyConsole();
2453 } catch (Exception ex)
2455 Cache.log.error("Groovy Shell Creation failed.", ex);
2456 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2458 MessageManager.getString("label.couldnt_create_groovy_shell"),
2459 MessageManager.getString("label.groovy_support_failed"),
2460 JvOptionPane.ERROR_MESSAGE);
2465 * Open the Groovy console
2467 void openGroovyConsole()
2469 if (groovyConsole == null)
2471 groovyConsole = new groovy.ui.Console();
2472 groovyConsole.setVariable("Jalview", this);
2473 groovyConsole.run();
2476 * We allow only one console at a time, so that AlignFrame menu option
2477 * 'Calculate | Run Groovy script' is unambiguous.
2478 * Disable 'Groovy Console', and enable 'Run script', when the console is
2479 * opened, and the reverse when it is closed
2481 Window window = (Window) groovyConsole.getFrame();
2482 window.addWindowListener(new WindowAdapter()
2485 public void windowClosed(WindowEvent e)
2488 * rebind CMD-Q from Groovy Console to Jalview Quit
2491 enableExecuteGroovy(false);
2497 * show Groovy console window (after close and reopen)
2499 ((Window) groovyConsole.getFrame()).setVisible(true);
2502 * if we got this far, enable 'Run Groovy' in AlignFrame menus
2503 * and disable opening a second console
2505 enableExecuteGroovy(true);
2509 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2510 * binding when opened
2512 protected void addQuitHandler()
2515 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2517 .getKeyStroke(KeyEvent.VK_Q,
2518 jalview.util.ShortcutKeyMaskExWrapper
2519 .getMenuShortcutKeyMaskEx()),
2521 getRootPane().getActionMap().put("Quit", new AbstractAction()
2524 public void actionPerformed(ActionEvent e)
2532 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2535 * true if Groovy console is open
2537 public void enableExecuteGroovy(boolean enabled)
2540 * disable opening a second Groovy console
2541 * (or re-enable when the console is closed)
2543 groovyShell.setEnabled(!enabled);
2545 AlignFrame[] alignFrames = getAlignFrames();
2546 if (alignFrames != null)
2548 for (AlignFrame af : alignFrames)
2550 af.setGroovyEnabled(enabled);
2556 * Progress bars managed by the IProgressIndicator method.
2558 private Hashtable<Long, JPanel> progressBars;
2560 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2565 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2568 public void setProgressBar(String message, long id)
2570 // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
2572 if (progressBars == null)
2574 progressBars = new Hashtable<>();
2575 progressBarHandlers = new Hashtable<>();
2578 if (progressBars.get(Long.valueOf(id)) != null)
2580 JPanel panel = progressBars.remove(Long.valueOf(id));
2581 if (progressBarHandlers.contains(Long.valueOf(id)))
2583 progressBarHandlers.remove(Long.valueOf(id));
2585 removeProgressPanel(panel);
2589 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2596 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2597 * jalview.gui.IProgressIndicatorHandler)
2600 public void registerHandler(final long id,
2601 final IProgressIndicatorHandler handler)
2603 if (progressBarHandlers == null
2604 || !progressBars.containsKey(Long.valueOf(id)))
2606 throw new Error(MessageManager.getString(
2607 "error.call_setprogressbar_before_registering_handler"));
2609 progressBarHandlers.put(Long.valueOf(id), handler);
2610 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2611 if (handler.canCancel())
2613 JButton cancel = new JButton(
2614 MessageManager.getString("action.cancel"));
2615 final IProgressIndicator us = this;
2616 cancel.addActionListener(new ActionListener()
2620 public void actionPerformed(ActionEvent e)
2622 handler.cancelActivity(id);
2623 us.setProgressBar(MessageManager
2624 .formatMessage("label.cancelled_params", new Object[]
2625 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2629 progressPanel.add(cancel, BorderLayout.EAST);
2635 * @return true if any progress bars are still active
2638 public boolean operationInProgress()
2640 if (progressBars != null && progressBars.size() > 0)
2648 * This will return the first AlignFrame holding the given viewport instance.
2649 * It will break if there are more than one AlignFrames viewing a particular
2653 * @return alignFrame for viewport
2655 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2657 if (desktop != null)
2659 AlignmentPanel[] aps = getAlignmentPanels(
2660 viewport.getSequenceSetId());
2661 for (int panel = 0; aps != null && panel < aps.length; panel++)
2663 if (aps[panel] != null && aps[panel].av == viewport)
2665 return aps[panel].alignFrame;
2672 public VamsasApplication getVamsasApplication()
2674 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2680 * flag set if jalview GUI is being operated programmatically
2682 private boolean inBatchMode = false;
2685 * check if jalview GUI is being operated programmatically
2687 * @return inBatchMode
2689 public boolean isInBatchMode()
2695 * set flag if jalview GUI is being operated programmatically
2697 * @param inBatchMode
2699 public void setInBatchMode(boolean inBatchMode)
2701 this.inBatchMode = inBatchMode;
2704 public void startServiceDiscovery()
2706 startServiceDiscovery(false);
2709 public void startServiceDiscovery(boolean blocking)
2711 boolean alive = true;
2712 Thread t0 = null, t1 = null, t2 = null;
2713 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2716 // todo: changesupport handlers need to be transferred
2717 if (discoverer == null)
2719 discoverer = new jalview.ws.jws1.Discoverer();
2720 // register PCS handler for desktop.
2721 discoverer.addPropertyChangeListener(changeSupport);
2723 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2724 // until we phase out completely
2725 (t0 = new Thread(discoverer)).start();
2728 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2730 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2731 .startDiscoverer(changeSupport);
2735 // TODO: do rest service discovery
2744 } catch (Exception e)
2747 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2748 || (t3 != null && t3.isAlive())
2749 || (t0 != null && t0.isAlive());
2755 * called to check if the service discovery process completed successfully.
2759 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2761 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2763 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2764 .getErrorMessages();
2767 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2769 if (serviceChangedDialog == null)
2771 // only run if we aren't already displaying one of these.
2772 addDialogThread(serviceChangedDialog = new Runnable()
2779 * JalviewDialog jd =new JalviewDialog() {
2781 * @Override protected void cancelPressed() { // TODO
2782 * Auto-generated method stub
2784 * }@Override protected void okPressed() { // TODO
2785 * Auto-generated method stub
2787 * }@Override protected void raiseClosed() { // TODO
2788 * Auto-generated method stub
2790 * } }; jd.initDialogFrame(new
2791 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2792 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2793 * + " or mis-configured HTTP proxy settings.<br/>" +
2794 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
2796 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
2797 * ), true, true, "Web Service Configuration Problem", 450,
2800 * jd.waitForInput();
2802 JvOptionPane.showConfirmDialog(Desktop.desktop,
2803 new JLabel("<html><table width=\"450\"><tr><td>"
2804 + ermsg + "</td></tr></table>"
2805 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2806 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2807 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2808 + " Tools->Preferences dialog box to change them.</p></html>"),
2809 "Web Service Configuration Problem",
2810 JvOptionPane.DEFAULT_OPTION,
2811 JvOptionPane.ERROR_MESSAGE);
2812 serviceChangedDialog = null;
2821 "Errors reported by JABA discovery service. Check web services preferences.\n"
2828 private Runnable serviceChangedDialog = null;
2831 * start a thread to open a URL in the configured browser. Pops up a warning
2832 * dialog to the user if there is an exception when calling out to the browser
2837 public static void showUrl(final String url)
2839 showUrl(url, Desktop.instance);
2843 * Like showUrl but allows progress handler to be specified
2847 * (null) or object implementing IProgressIndicator
2849 public static void showUrl(final String url,
2850 final IProgressIndicator progress)
2852 new Thread(new Runnable()
2859 if (progress != null)
2861 progress.setProgressBar(MessageManager
2862 .formatMessage("status.opening_params", new Object[]
2863 { url }), this.hashCode());
2865 jalview.util.BrowserLauncher.openURL(url);
2866 } catch (Exception ex)
2868 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2870 .getString("label.web_browser_not_found_unix"),
2871 MessageManager.getString("label.web_browser_not_found"),
2872 JvOptionPane.WARNING_MESSAGE);
2874 ex.printStackTrace();
2876 if (progress != null)
2878 progress.setProgressBar(null, this.hashCode());
2884 public static WsParamSetManager wsparamManager = null;
2886 public static ParamManager getUserParameterStore()
2888 if (wsparamManager == null)
2890 wsparamManager = new WsParamSetManager();
2892 return wsparamManager;
2896 * static hyperlink handler proxy method for use by Jalview's internal windows
2900 public static void hyperlinkUpdate(HyperlinkEvent e)
2902 if (e.getEventType() == EventType.ACTIVATED)
2907 url = e.getURL().toString();
2908 Desktop.showUrl(url);
2909 } catch (Exception x)
2913 if (Cache.log != null)
2915 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2920 "Couldn't handle string " + url + " as a URL.");
2923 // ignore any exceptions due to dud links.
2930 * single thread that handles display of dialogs to user.
2932 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2935 * flag indicating if dialogExecutor should try to acquire a permit
2937 private volatile boolean dialogPause = true;
2942 private java.util.concurrent.Semaphore block = new Semaphore(0);
2944 private static groovy.ui.Console groovyConsole;
2947 * add another dialog thread to the queue
2951 public void addDialogThread(final Runnable prompter)
2953 dialogExecutor.submit(new Runnable()
2963 } catch (InterruptedException x)
2967 if (instance == null)
2973 SwingUtilities.invokeAndWait(prompter);
2974 } catch (Exception q)
2976 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2982 public void startDialogQueue()
2984 // set the flag so we don't pause waiting for another permit and semaphore
2985 // the current task to begin
2986 dialogPause = false;
2991 * Outputs an image of the desktop to file in EPS format, after prompting the
2992 * user for choice of Text or Lineart character rendering (unless a preference
2993 * has been set). The file name is generated as
2996 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3000 protected void snapShotWindow_actionPerformed(ActionEvent e)
3002 // currently the menu option to do this is not shown
3005 int width = getWidth();
3006 int height = getHeight();
3008 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3009 ImageWriterI writer = new ImageWriterI()
3012 public void exportImage(Graphics g) throws Exception
3015 Cache.log.info("Successfully written snapshot to file "
3016 + of.getAbsolutePath());
3019 String title = "View of desktop";
3020 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3022 exporter.doExport(of, this, width, height, title);
3026 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3027 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3028 * and location last time the view was expanded (if any). However it does not
3029 * remember the split pane divider location - this is set to match the
3030 * 'exploding' frame.
3034 public void explodeViews(SplitFrame sf)
3036 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3037 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3038 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3040 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3042 int viewCount = topPanels.size();
3049 * Processing in reverse order works, forwards order leaves the first panels
3050 * not visible. I don't know why!
3052 for (int i = viewCount - 1; i >= 0; i--)
3055 * Make new top and bottom frames. These take over the respective
3056 * AlignmentPanel objects, including their AlignmentViewports, so the
3057 * cdna/protein relationships between the viewports is carried over to the
3060 * explodedGeometry holds the (x, y) position of the previously exploded
3061 * SplitFrame, and the (width, height) of the AlignFrame component
3063 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3064 AlignFrame newTopFrame = new AlignFrame(topPanel);
3065 newTopFrame.setSize(oldTopFrame.getSize());
3066 newTopFrame.setVisible(true);
3067 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3068 .getExplodedGeometry();
3069 if (geometry != null)
3071 newTopFrame.setSize(geometry.getSize());
3074 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3075 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3076 newBottomFrame.setSize(oldBottomFrame.getSize());
3077 newBottomFrame.setVisible(true);
3078 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3079 .getExplodedGeometry();
3080 if (geometry != null)
3082 newBottomFrame.setSize(geometry.getSize());
3085 topPanel.av.setGatherViewsHere(false);
3086 bottomPanel.av.setGatherViewsHere(false);
3087 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3089 if (geometry != null)
3091 splitFrame.setLocation(geometry.getLocation());
3093 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3097 * Clear references to the panels (now relocated in the new SplitFrames)
3098 * before closing the old SplitFrame.
3101 bottomPanels.clear();
3106 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3107 * back into the given SplitFrame as additional views. Note that the gathered
3108 * frames may themselves have multiple views.
3112 public void gatherViews(GSplitFrame source)
3115 * special handling of explodedGeometry for a view within a SplitFrame: - it
3116 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3117 * height) of the AlignFrame component
3119 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3120 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3121 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3122 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3123 myBottomFrame.viewport
3124 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3125 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3126 myTopFrame.viewport.setGatherViewsHere(true);
3127 myBottomFrame.viewport.setGatherViewsHere(true);
3128 String topViewId = myTopFrame.viewport.getSequenceSetId();
3129 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3131 JInternalFrame[] frames = desktop.getAllFrames();
3132 for (JInternalFrame frame : frames)
3134 if (frame instanceof SplitFrame && frame != source)
3136 SplitFrame sf = (SplitFrame) frame;
3137 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3138 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3139 boolean gatherThis = false;
3140 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3142 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3143 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3144 if (topViewId.equals(topPanel.av.getSequenceSetId())
3145 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3148 topPanel.av.setGatherViewsHere(false);
3149 bottomPanel.av.setGatherViewsHere(false);
3150 topPanel.av.setExplodedGeometry(
3151 new Rectangle(sf.getLocation(), topFrame.getSize()));
3152 bottomPanel.av.setExplodedGeometry(
3153 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3154 myTopFrame.addAlignmentPanel(topPanel, false);
3155 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3161 topFrame.getAlignPanels().clear();
3162 bottomFrame.getAlignPanels().clear();
3169 * The dust settles...give focus to the tab we did this from.
3171 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3174 public static groovy.ui.Console getGroovyConsole()
3176 return groovyConsole;
3180 * handles the payload of a drag and drop event.
3182 * TODO refactor to desktop utilities class
3185 * - Data source strings extracted from the drop event
3187 * - protocol for each data source extracted from the drop event
3191 * - the payload from the drop event
3194 public static void transferFromDropTarget(List<Object> files,
3195 List<DataSourceType> protocols, DropTargetDropEvent evt,
3196 Transferable t) throws Exception
3199 // BH 2018 changed List<String> to List<Object> to allow for File from
3202 // DataFlavor[] flavors = t.getTransferDataFlavors();
3203 // for (int i = 0; i < flavors.length; i++) {
3204 // if (flavors[i].isFlavorJavaFileListType()) {
3205 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3206 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3207 // for (int j = 0; j < list.size(); j++) {
3208 // File file = (File) list.get(j);
3209 // byte[] data = getDroppedFileBytes(file);
3210 // fileName.setText(file.getName() + " - " + data.length + " " +
3211 // evt.getLocation());
3212 // JTextArea target = (JTextArea) ((DropTarget)
3213 // evt.getSource()).getComponent();
3214 // target.setText(new String(data));
3216 // dtde.dropComplete(true);
3221 DataFlavor uriListFlavor = new DataFlavor(
3222 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3225 urlFlavour = new DataFlavor(
3226 "application/x-java-url; class=java.net.URL");
3227 } catch (ClassNotFoundException cfe)
3229 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3232 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3237 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3238 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3239 // means url may be null.
3242 protocols.add(DataSourceType.URL);
3243 files.add(url.toString());
3244 Cache.log.debug("Drop handled as URL dataflavor "
3245 + files.get(files.size() - 1));
3250 if (Platform.isAMacAndNotJS())
3253 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3256 } catch (Throwable ex)
3258 Cache.log.debug("URL drop handler failed.", ex);
3261 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3263 // Works on Windows and MacOSX
3264 Cache.log.debug("Drop handled as javaFileListFlavor");
3265 for (Object file : (List) t
3266 .getTransferData(DataFlavor.javaFileListFlavor))
3269 protocols.add(DataSourceType.FILE);
3274 // Unix like behaviour
3275 boolean added = false;
3277 if (t.isDataFlavorSupported(uriListFlavor))
3279 Cache.log.debug("Drop handled as uriListFlavor");
3280 // This is used by Unix drag system
3281 data = (String) t.getTransferData(uriListFlavor);
3285 // fallback to text: workaround - on OSX where there's a JVM bug
3286 Cache.log.debug("standard URIListFlavor failed. Trying text");
3287 // try text fallback
3288 DataFlavor textDf = new DataFlavor(
3289 "text/plain;class=java.lang.String");
3290 if (t.isDataFlavorSupported(textDf))
3292 data = (String) t.getTransferData(textDf);
3295 Cache.log.debug("Plain text drop content returned "
3296 + (data == null ? "Null - failed" : data));
3301 while (protocols.size() < files.size())
3303 Cache.log.debug("Adding missing FILE protocol for "
3304 + files.get(protocols.size()));
3305 protocols.add(DataSourceType.FILE);
3307 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3308 data, "\r\n"); st.hasMoreTokens();)
3311 String s = st.nextToken();
3312 if (s.startsWith("#"))
3314 // the line is a comment (as per the RFC 2483)
3317 java.net.URI uri = new java.net.URI(s);
3318 if (uri.getScheme().toLowerCase().startsWith("http"))
3320 protocols.add(DataSourceType.URL);
3321 files.add(uri.toString());
3325 // otherwise preserve old behaviour: catch all for file objects
3326 java.io.File file = new java.io.File(uri);
3327 protocols.add(DataSourceType.FILE);
3328 files.add(file.toString());
3333 if (Cache.log.isDebugEnabled())
3335 if (data == null || !added)
3338 if (t.getTransferDataFlavors() != null
3339 && t.getTransferDataFlavors().length > 0)
3342 "Couldn't resolve drop data. Here are the supported flavors:");
3343 for (DataFlavor fl : t.getTransferDataFlavors())
3346 "Supported transfer dataflavor: " + fl.toString());
3347 Object df = t.getTransferData(fl);
3350 Cache.log.debug("Retrieves: " + df);
3354 Cache.log.debug("Retrieved nothing");
3360 Cache.log.debug("Couldn't resolve dataflavor for drop: "
3366 if (Platform.isWindowsAndNotJS())
3368 Cache.log.debug("Scanning dropped content for Windows Link Files");
3370 // resolve any .lnk files in the file drop
3371 for (int f = 0; f < files.size(); f++)
3373 String source = files.get(f).toString().toLowerCase();
3374 if (protocols.get(f).equals(DataSourceType.FILE)
3375 && (source.endsWith(".lnk") || source.endsWith(".url")
3376 || source.endsWith(".site")))
3380 Object obj = files.get(f);
3381 File lf = (obj instanceof File ? (File) obj
3382 : new File((String) obj));
3383 // process link file to get a URL
3384 Cache.log.debug("Found potential link file: " + lf);
3385 WindowsShortcut wscfile = new WindowsShortcut(lf);
3386 String fullname = wscfile.getRealFilename();
3387 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3388 files.set(f, fullname);
3389 Cache.log.debug("Parsed real filename " + fullname
3390 + " to extract protocol: " + protocols.get(f));
3391 } catch (Exception ex)
3394 "Couldn't parse " + files.get(f) + " as a link file.",
3403 * Sets the Preferences property for experimental features to True or False
3404 * depending on the state of the controlling menu item
3407 protected void showExperimental_actionPerformed(boolean selected)
3409 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3413 * Answers a (possibly empty) list of any structure viewer frames (currently
3414 * for either Jmol or Chimera) which are currently open. This may optionally
3415 * be restricted to viewers of a specified class, or viewers linked to a
3416 * specified alignment panel.
3419 * if not null, only return viewers linked to this panel
3420 * @param structureViewerClass
3421 * if not null, only return viewers of this class
3424 public List<StructureViewerBase> getStructureViewers(
3425 AlignmentPanel apanel,
3426 Class<? extends StructureViewerBase> structureViewerClass)
3428 List<StructureViewerBase> result = new ArrayList<>();
3429 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3431 for (JInternalFrame frame : frames)
3433 if (frame instanceof StructureViewerBase)
3435 if (structureViewerClass == null
3436 || structureViewerClass.isInstance(frame))
3439 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3441 result.add((StructureViewerBase) frame);
3449 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3451 private static boolean debugScaleMessageDone = false;
3453 public static void debugScaleMessage(Graphics g)
3455 if (debugScaleMessageDone)
3459 // output used by tests to check HiDPI scaling settings in action
3462 Graphics2D gg = (Graphics2D) g;
3465 AffineTransform t = gg.getTransform();
3466 double scaleX = t.getScaleX();
3467 double scaleY = t.getScaleY();
3468 Cache.debug(debugScaleMessage + scaleX + " (X)");
3469 Cache.debug(debugScaleMessage + scaleY + " (Y)");
3470 debugScaleMessageDone = true;
3474 Cache.debug("Desktop graphics null");
3476 } catch (Exception e)
3478 Cache.debug(Cache.getStackTraceString(e));