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.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JInternalFrame;
85 import javax.swing.JLabel;
86 import javax.swing.JMenuItem;
87 import javax.swing.JPanel;
88 import javax.swing.JPopupMenu;
89 import javax.swing.JProgressBar;
90 import javax.swing.JTextField;
91 import javax.swing.KeyStroke;
92 import javax.swing.SwingUtilities;
93 import javax.swing.event.HyperlinkEvent;
94 import javax.swing.event.HyperlinkEvent.EventType;
95 import javax.swing.event.InternalFrameAdapter;
96 import javax.swing.event.InternalFrameEvent;
98 import org.stackoverflowusers.file.WindowsShortcut;
100 import jalview.api.AlignViewportI;
101 import jalview.api.AlignmentViewPanel;
102 import jalview.bin.Cache;
103 import jalview.bin.Jalview;
104 import jalview.gui.ImageExporter.ImageWriterI;
105 import jalview.io.BackupFiles;
106 import jalview.io.DataSourceType;
107 import jalview.io.FileFormat;
108 import jalview.io.FileFormatException;
109 import jalview.io.FileFormatI;
110 import jalview.io.FileFormats;
111 import jalview.io.FileLoader;
112 import jalview.io.FormatAdapter;
113 import jalview.io.IdentifyFile;
114 import jalview.io.JalviewFileChooser;
115 import jalview.io.JalviewFileView;
116 import jalview.jbgui.GSplitFrame;
117 import jalview.jbgui.GStructureViewer;
118 import jalview.project.Jalview2XML;
119 import jalview.structure.StructureSelectionManager;
120 import jalview.urls.IdOrgSettings;
121 import jalview.util.BrowserLauncher;
122 import jalview.util.ChannelProperties;
123 import jalview.util.ImageMaker.TYPE;
124 import jalview.util.MessageManager;
125 import jalview.util.Platform;
126 import jalview.util.ShortcutKeyMaskExWrapper;
127 import jalview.util.UrlConstants;
128 import jalview.viewmodel.AlignmentViewport;
129 import jalview.ws.params.ParamManager;
130 import jalview.ws.utils.UrlDownloadClient;
137 * @version $Revision: 1.155 $
139 public class Desktop extends jalview.jbgui.GDesktop
140 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
141 jalview.api.StructureSelectionManagerProvider
143 private static final String CITATION;
146 URL bg_logo_url = ChannelProperties.getImageURL(
147 "bg_logo." + String.valueOf(SplashScreen.logoSize));
148 URL uod_logo_url = ChannelProperties.getImageURL(
149 "uod_banner." + String.valueOf(SplashScreen.logoSize));
150 boolean logo = (bg_logo_url != null || uod_logo_url != null);
151 StringBuilder sb = new StringBuilder();
153 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
158 sb.append(bg_logo_url == null ? ""
159 : "<img alt=\"Barton Group logo\" src=\""
160 + bg_logo_url.toString() + "\">");
161 sb.append(uod_logo_url == null ? ""
162 : " <img alt=\"University of Dundee shield\" src=\""
163 + uod_logo_url.toString() + "\">");
165 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
166 sb.append("<br><br>If you use Jalview, please cite:"
167 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
168 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
169 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
170 CITATION = sb.toString();
173 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
175 private static int DEFAULT_MIN_WIDTH = 300;
177 private static int DEFAULT_MIN_HEIGHT = 250;
179 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
181 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
183 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
185 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
187 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
189 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
191 public static boolean nosplash = false;
194 * news reader - null if it was never started.
196 private BlogReader jvnews = null;
198 private File projectFile;
202 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
204 public void addJalviewPropertyChangeListener(
205 PropertyChangeListener listener)
207 changeSupport.addJalviewPropertyChangeListener(listener);
211 * @param propertyName
213 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
214 * java.beans.PropertyChangeListener)
216 public void addJalviewPropertyChangeListener(String propertyName,
217 PropertyChangeListener listener)
219 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
223 * @param propertyName
225 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
226 * java.beans.PropertyChangeListener)
228 public void removeJalviewPropertyChangeListener(String propertyName,
229 PropertyChangeListener listener)
231 changeSupport.removeJalviewPropertyChangeListener(propertyName,
235 /** Singleton Desktop instance */
236 public static Desktop instance;
238 public static MyDesktopPane desktop;
240 public static MyDesktopPane getDesktop()
242 // BH 2018 could use currentThread() here as a reference to a
243 // Hashtable<Thread, MyDesktopPane> in JavaScript
247 static int openFrameCount = 0;
249 static final int xOffset = 30;
251 static final int yOffset = 30;
253 public static jalview.ws.jws1.Discoverer discoverer;
255 public static Object[] jalviewClipboard;
257 public static boolean internalCopy = false;
259 static int fileLoadingCount = 0;
261 class MyDesktopManager implements DesktopManager
264 private DesktopManager delegate;
266 public MyDesktopManager(DesktopManager delegate)
268 this.delegate = delegate;
272 public void activateFrame(JInternalFrame f)
276 delegate.activateFrame(f);
277 } catch (NullPointerException npe)
279 Point p = getMousePosition();
280 instance.showPasteMenu(p.x, p.y);
285 public void beginDraggingFrame(JComponent f)
287 delegate.beginDraggingFrame(f);
291 public void beginResizingFrame(JComponent f, int direction)
293 delegate.beginResizingFrame(f, direction);
297 public void closeFrame(JInternalFrame f)
299 delegate.closeFrame(f);
303 public void deactivateFrame(JInternalFrame f)
305 delegate.deactivateFrame(f);
309 public void deiconifyFrame(JInternalFrame f)
311 delegate.deiconifyFrame(f);
315 public void dragFrame(JComponent f, int newX, int newY)
321 delegate.dragFrame(f, newX, newY);
325 public void endDraggingFrame(JComponent f)
327 delegate.endDraggingFrame(f);
332 public void endResizingFrame(JComponent f)
334 delegate.endResizingFrame(f);
339 public void iconifyFrame(JInternalFrame f)
341 delegate.iconifyFrame(f);
345 public void maximizeFrame(JInternalFrame f)
347 delegate.maximizeFrame(f);
351 public void minimizeFrame(JInternalFrame f)
353 delegate.minimizeFrame(f);
357 public void openFrame(JInternalFrame f)
359 delegate.openFrame(f);
363 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
370 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
374 public void setBoundsForFrame(JComponent f, int newX, int newY,
375 int newWidth, int newHeight)
377 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
380 // All other methods, simply delegate
385 * Creates a new Desktop object.
391 * A note to implementors. It is ESSENTIAL that any activities that might
392 * block are spawned off as threads rather than waited for during this
397 doConfigureStructurePrefs();
398 setTitle(ChannelProperties.getProperty("app_name") + " "
399 + Cache.getProperty("VERSION"));
402 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
403 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
404 * officially documented or guaranteed to exist, so we access it via
405 * reflection. There appear to be unfathomable criteria about what this
406 * string can contain, and it if doesn't meet those criteria then "java"
407 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
408 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
409 * not. The reflection access may generate a warning: WARNING: An illegal
410 * reflective access operation has occurred WARNING: Illegal reflective
411 * access by jalview.gui.Desktop () to field
412 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
414 if (Platform.isLinux())
418 Toolkit xToolkit = Toolkit.getDefaultToolkit();
419 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
420 Field awtAppClassNameField = null;
422 if (Arrays.stream(declaredFields)
423 .anyMatch(f -> f.getName().equals("awtAppClassName")))
425 awtAppClassNameField = xToolkit.getClass()
426 .getDeclaredField("awtAppClassName");
429 String title = ChannelProperties.getProperty("app_name");
430 if (awtAppClassNameField != null)
432 awtAppClassNameField.setAccessible(true);
433 awtAppClassNameField.set(xToolkit, title);
437 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
439 } catch (Exception e)
441 jalview.bin.Console.debug("Error setting awtAppClassName");
442 jalview.bin.Console.trace(Cache.getStackTraceString(e));
446 setIconImages(ChannelProperties.getIconList());
448 addWindowListener(new WindowAdapter()
452 public void windowClosing(WindowEvent ev)
458 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
460 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
461 desktop = new MyDesktopPane(selmemusage);
463 showMemusage.setSelected(selmemusage);
464 desktop.setBackground(Color.white);
466 getContentPane().setLayout(new BorderLayout());
467 // alternate config - have scrollbars - see notes in JAL-153
468 // JScrollPane sp = new JScrollPane();
469 // sp.getViewport().setView(desktop);
470 // getContentPane().add(sp, BorderLayout.CENTER);
472 // BH 2018 - just an experiment to try unclipped JInternalFrames.
475 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
478 getContentPane().add(desktop, BorderLayout.CENTER);
479 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
481 // This line prevents Windows Look&Feel resizing all new windows to maximum
482 // if previous window was maximised
483 desktop.setDesktopManager(new MyDesktopManager(
484 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
485 : Platform.isAMacAndNotJS()
486 ? new AquaInternalFrameManager(
487 desktop.getDesktopManager())
488 : desktop.getDesktopManager())));
490 Rectangle dims = getLastKnownDimensions("");
497 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
498 int xPos = Math.max(5, (screenSize.width - 900) / 2);
499 int yPos = Math.max(5, (screenSize.height - 650) / 2);
500 setBounds(xPos, yPos, 900, 650);
503 if (!Platform.isJS())
510 jconsole = new Console(this, showjconsole);
511 jconsole.setHeader(Cache.getVersionDetailsForConsole());
512 showConsole(showjconsole);
514 showNews.setVisible(false);
516 experimentalFeatures.setSelected(showExperimental());
518 getIdentifiersOrgData();
522 // Spawn a thread that shows the splashscreen
525 SwingUtilities.invokeLater(new Runnable()
530 new SplashScreen(true);
535 // Thread off a new instance of the file chooser - this reduces the time
537 // takes to open it later on.
538 new Thread(new Runnable()
543 jalview.bin.Console.debug("Filechooser init thread started.");
544 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
545 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
547 jalview.bin.Console.debug("Filechooser init thread finished.");
550 // Add the service change listener
551 changeSupport.addJalviewPropertyChangeListener("services",
552 new PropertyChangeListener()
556 public void propertyChange(PropertyChangeEvent evt)
559 .debug("Firing service changed event for "
560 + evt.getNewValue());
561 JalviewServicesChanged(evt);
566 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
568 this.addWindowListener(new WindowAdapter()
571 public void windowClosing(WindowEvent evt)
578 this.addMouseListener(ma = new MouseAdapter()
581 public void mousePressed(MouseEvent evt)
583 if (evt.isPopupTrigger()) // Mac
585 showPasteMenu(evt.getX(), evt.getY());
590 public void mouseReleased(MouseEvent evt)
592 if (evt.isPopupTrigger()) // Windows
594 showPasteMenu(evt.getX(), evt.getY());
598 desktop.addMouseListener(ma);
602 * Answers true if user preferences to enable experimental features is True
607 public boolean showExperimental()
609 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
610 Boolean.FALSE.toString());
611 return Boolean.valueOf(experimental).booleanValue();
614 public void doConfigureStructurePrefs()
616 // configure services
617 StructureSelectionManager ssm = StructureSelectionManager
618 .getStructureSelectionManager(this);
619 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
621 ssm.setAddTempFacAnnot(
622 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
623 ssm.setProcessSecondaryStructure(
624 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
625 // JAL-3915 - RNAView is no longer an option so this has no effect
626 ssm.setSecStructServices(
627 Cache.getDefault(Preferences.USE_RNAVIEW, false));
631 ssm.setAddTempFacAnnot(false);
632 ssm.setProcessSecondaryStructure(false);
633 ssm.setSecStructServices(false);
637 public void checkForNews()
639 final Desktop me = this;
640 // Thread off the news reader, in case there are connection problems.
641 new Thread(new Runnable()
646 jalview.bin.Console.debug("Starting news thread.");
647 jvnews = new BlogReader(me);
648 showNews.setVisible(true);
649 jalview.bin.Console.debug("Completed news thread.");
654 public void getIdentifiersOrgData()
656 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
657 {// Thread off the identifiers fetcher
658 new Thread(new Runnable()
664 .debug("Downloading data from identifiers.org");
667 UrlDownloadClient.download(IdOrgSettings.getUrl(),
668 IdOrgSettings.getDownloadLocation());
669 } catch (IOException e)
672 .debug("Exception downloading identifiers.org data"
682 protected void showNews_actionPerformed(ActionEvent e)
684 showNews(showNews.isSelected());
687 void showNews(boolean visible)
689 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
690 showNews.setSelected(visible);
691 if (visible && !jvnews.isVisible())
693 new Thread(new Runnable()
698 long now = System.currentTimeMillis();
699 Desktop.instance.setProgressBar(
700 MessageManager.getString("status.refreshing_news"), now);
701 jvnews.refreshNews();
702 Desktop.instance.setProgressBar(null, now);
710 * recover the last known dimensions for a jalview window
713 * - empty string is desktop, all other windows have unique prefix
714 * @return null or last known dimensions scaled to current geometry (if last
715 * window geom was known)
717 Rectangle getLastKnownDimensions(String windowName)
719 // TODO: lock aspect ratio for scaling desktop Bug #0058199
720 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
721 String x = Cache.getProperty(windowName + "SCREEN_X");
722 String y = Cache.getProperty(windowName + "SCREEN_Y");
723 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
724 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
725 if ((x != null) && (y != null) && (width != null) && (height != null))
727 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
728 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
729 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
731 // attempt #1 - try to cope with change in screen geometry - this
732 // version doesn't preserve original jv aspect ratio.
733 // take ratio of current screen size vs original screen size.
734 double sw = ((1f * screenSize.width) / (1f * Integer
735 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
736 double sh = ((1f * screenSize.height) / (1f * Integer
737 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
738 // rescale the bounds depending upon the current screen geometry.
739 ix = (int) (ix * sw);
740 iw = (int) (iw * sw);
741 iy = (int) (iy * sh);
742 ih = (int) (ih * sh);
743 while (ix >= screenSize.width)
745 jalview.bin.Console.debug(
746 "Window geometry location recall error: shifting horizontal to within screenbounds.");
747 ix -= screenSize.width;
749 while (iy >= screenSize.height)
751 jalview.bin.Console.debug(
752 "Window geometry location recall error: shifting vertical to within screenbounds.");
753 iy -= screenSize.height;
755 jalview.bin.Console.debug(
756 "Got last known dimensions for " + windowName + ": x:" + ix
757 + " y:" + iy + " width:" + iw + " height:" + ih);
759 // return dimensions for new instance
760 return new Rectangle(ix, iy, iw, ih);
765 void showPasteMenu(int x, int y)
767 JPopupMenu popup = new JPopupMenu();
768 JMenuItem item = new JMenuItem(
769 MessageManager.getString("label.paste_new_window"));
770 item.addActionListener(new ActionListener()
773 public void actionPerformed(ActionEvent evt)
780 popup.show(this, x, y);
787 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
788 Transferable contents = c.getContents(this);
790 if (contents != null)
792 String file = (String) contents
793 .getTransferData(DataFlavor.stringFlavor);
795 FileFormatI format = new IdentifyFile().identify(file,
796 DataSourceType.PASTE);
798 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
801 } catch (Exception ex)
804 "Unable to paste alignment from system clipboard:\n" + ex);
809 * Adds and opens the given frame to the desktop
820 public static synchronized void addInternalFrame(
821 final JInternalFrame frame, String title, int w, int h)
823 addInternalFrame(frame, title, true, w, h, true, false);
827 * Add an internal frame to the Jalview desktop
834 * When true, display frame immediately, otherwise, caller must call
835 * setVisible themselves.
841 public static synchronized void addInternalFrame(
842 final JInternalFrame frame, String title, boolean makeVisible,
845 addInternalFrame(frame, title, makeVisible, w, h, true, false);
849 * Add an internal frame to the Jalview desktop and make it visible
862 public static synchronized void addInternalFrame(
863 final JInternalFrame frame, String title, int w, int h,
866 addInternalFrame(frame, title, true, w, h, resizable, false);
870 * Add an internal frame to the Jalview desktop
877 * When true, display frame immediately, otherwise, caller must call
878 * setVisible themselves.
885 * @param ignoreMinSize
886 * Do not set the default minimum size for frame
888 public static synchronized void addInternalFrame(
889 final JInternalFrame frame, String title, boolean makeVisible,
890 int w, int h, boolean resizable, boolean ignoreMinSize)
893 // TODO: allow callers to determine X and Y position of frame (eg. via
895 // TODO: consider fixing method to update entries in the window submenu with
896 // the current window title
898 frame.setTitle(title);
899 if (frame.getWidth() < 1 || frame.getHeight() < 1)
903 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
904 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
905 // IF JALVIEW IS RUNNING HEADLESS
906 // ///////////////////////////////////////////////
907 if (instance == null || (System.getProperty("java.awt.headless") != null
908 && System.getProperty("java.awt.headless").equals("true")))
917 frame.setMinimumSize(
918 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
920 // Set default dimension for Alignment Frame window.
921 // The Alignment Frame window could be added from a number of places,
923 // I did this here in order not to miss out on any Alignment frame.
924 if (frame instanceof AlignFrame)
926 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
927 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
931 frame.setVisible(makeVisible);
932 frame.setClosable(true);
933 frame.setResizable(resizable);
934 frame.setMaximizable(resizable);
935 frame.setIconifiable(resizable);
936 frame.setOpaque(Platform.isJS());
938 if (frame.getX() < 1 && frame.getY() < 1)
940 frame.setLocation(xOffset * openFrameCount,
941 yOffset * ((openFrameCount - 1) % 10) + yOffset);
945 * add an entry for the new frame in the Window menu (and remove it when the
948 final JMenuItem menuItem = new JMenuItem(title);
949 frame.addInternalFrameListener(new InternalFrameAdapter()
952 public void internalFrameActivated(InternalFrameEvent evt)
954 JInternalFrame itf = desktop.getSelectedFrame();
957 if (itf instanceof AlignFrame)
959 Jalview.setCurrentAlignFrame((AlignFrame) itf);
966 public void internalFrameClosed(InternalFrameEvent evt)
968 PaintRefresher.RemoveComponent(frame);
971 * defensive check to prevent frames being added half off the window
973 if (openFrameCount > 0)
979 * ensure no reference to alignFrame retained by menu item listener
981 if (menuItem.getActionListeners().length > 0)
983 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
985 windowMenu.remove(menuItem);
989 menuItem.addActionListener(new ActionListener()
992 public void actionPerformed(ActionEvent e)
996 frame.setSelected(true);
997 frame.setIcon(false);
998 } catch (java.beans.PropertyVetoException ex)
1005 setKeyBindings(frame);
1009 windowMenu.add(menuItem);
1014 frame.setSelected(true);
1015 frame.requestFocus();
1016 } catch (java.beans.PropertyVetoException ve)
1018 } catch (java.lang.ClassCastException cex)
1020 jalview.bin.Console.warn(
1021 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1027 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1032 private static void setKeyBindings(JInternalFrame frame)
1034 @SuppressWarnings("serial")
1035 final Action closeAction = new AbstractAction()
1038 public void actionPerformed(ActionEvent e)
1045 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1047 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1048 InputEvent.CTRL_DOWN_MASK);
1049 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1050 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1052 InputMap inputMap = frame
1053 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1054 String ctrlW = ctrlWKey.toString();
1055 inputMap.put(ctrlWKey, ctrlW);
1056 inputMap.put(cmdWKey, ctrlW);
1058 ActionMap actionMap = frame.getActionMap();
1059 actionMap.put(ctrlW, closeAction);
1063 public void lostOwnership(Clipboard clipboard, Transferable contents)
1067 Desktop.jalviewClipboard = null;
1070 internalCopy = false;
1074 public void dragEnter(DropTargetDragEvent evt)
1079 public void dragExit(DropTargetEvent evt)
1084 public void dragOver(DropTargetDragEvent evt)
1089 public void dropActionChanged(DropTargetDragEvent evt)
1100 public void drop(DropTargetDropEvent evt)
1102 boolean success = true;
1103 // JAL-1552 - acceptDrop required before getTransferable call for
1104 // Java's Transferable for native dnd
1105 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1106 Transferable t = evt.getTransferable();
1107 List<Object> files = new ArrayList<>();
1108 List<DataSourceType> protocols = new ArrayList<>();
1112 Desktop.transferFromDropTarget(files, protocols, evt, t);
1113 } catch (Exception e)
1115 e.printStackTrace();
1123 for (int i = 0; i < files.size(); i++)
1125 // BH 2018 File or String
1126 Object file = files.get(i);
1127 String fileName = file.toString();
1128 DataSourceType protocol = (protocols == null)
1129 ? DataSourceType.FILE
1131 FileFormatI format = null;
1133 if (fileName.endsWith(".jar"))
1135 format = FileFormat.Jalview;
1140 format = new IdentifyFile().identify(file, protocol);
1142 if (file instanceof File)
1144 Platform.cacheFileData((File) file);
1146 new FileLoader().LoadFile(null, file, protocol, format);
1149 } catch (Exception ex)
1154 evt.dropComplete(success); // need this to ensure input focus is properly
1155 // transfered to any new windows created
1165 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1167 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1168 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1169 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1170 BackupFiles.getEnabled());
1172 chooser.setFileView(new JalviewFileView());
1173 chooser.setDialogTitle(
1174 MessageManager.getString("label.open_local_file"));
1175 chooser.setToolTipText(MessageManager.getString("action.open"));
1177 chooser.setResponseHandler(0, new Runnable()
1182 File selectedFile = chooser.getSelectedFile();
1183 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1185 FileFormatI format = chooser.getSelectedFormat();
1188 * Call IdentifyFile to verify the file contains what its extension implies.
1189 * Skip this step for dynamically added file formats, because IdentifyFile does
1190 * not know how to recognise them.
1192 if (FileFormats.getInstance().isIdentifiable(format))
1196 format = new IdentifyFile().identify(selectedFile,
1197 DataSourceType.FILE);
1198 } catch (FileFormatException e)
1200 // format = null; //??
1204 new FileLoader().LoadFile(viewport, selectedFile,
1205 DataSourceType.FILE, format);
1208 chooser.showOpenDialog(this);
1212 * Shows a dialog for input of a URL at which to retrieve alignment data
1217 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1219 // This construct allows us to have a wider textfield
1221 JLabel label = new JLabel(
1222 MessageManager.getString("label.input_file_url"));
1224 JPanel panel = new JPanel(new GridLayout(2, 1));
1228 * the URL to fetch is input in Java: an editable combobox with history JS:
1229 * (pending JAL-3038) a plain text field
1232 String urlBase = "https://www.";
1233 if (Platform.isJS())
1235 history = new JTextField(urlBase, 35);
1244 JComboBox<String> asCombo = new JComboBox<>();
1245 asCombo.setPreferredSize(new Dimension(400, 20));
1246 asCombo.setEditable(true);
1247 asCombo.addItem(urlBase);
1248 String historyItems = Cache.getProperty("RECENT_URL");
1249 if (historyItems != null)
1251 for (String token : historyItems.split("\\t"))
1253 asCombo.addItem(token);
1260 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1261 MessageManager.getString("action.cancel") };
1262 Runnable action = new Runnable()
1267 @SuppressWarnings("unchecked")
1268 String url = (history instanceof JTextField
1269 ? ((JTextField) history).getText()
1270 : ((JComboBox<String>) history).getEditor().getItem()
1271 .toString().trim());
1273 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1275 if (viewport != null)
1277 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1278 FileFormat.Jalview);
1282 new FileLoader().LoadFile(url, DataSourceType.URL,
1283 FileFormat.Jalview);
1288 FileFormatI format = null;
1291 format = new IdentifyFile().identify(url, DataSourceType.URL);
1292 } catch (FileFormatException e)
1294 // TODO revise error handling, distinguish between
1295 // URL not found and response not valid
1300 String msg = MessageManager
1301 .formatMessage("label.couldnt_locate", url);
1302 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1303 MessageManager.getString("label.url_not_found"),
1304 JvOptionPane.WARNING_MESSAGE);
1309 if (viewport != null)
1311 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1316 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1321 String dialogOption = MessageManager
1322 .getString("label.input_alignment_from_url");
1323 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1324 .showInternalDialog(panel, dialogOption,
1325 JvOptionPane.YES_NO_CANCEL_OPTION,
1326 JvOptionPane.PLAIN_MESSAGE, null, options,
1327 MessageManager.getString("action.ok"));
1331 * Opens the CutAndPaste window for the user to paste an alignment in to
1334 * - if not null, the pasted alignment is added to the current
1335 * alignment; if null, to a new alignment window
1338 public void inputTextboxMenuItem_actionPerformed(
1339 AlignmentViewPanel viewPanel)
1341 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1342 cap.setForInput(viewPanel);
1343 Desktop.addInternalFrame(cap,
1344 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1354 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1355 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1356 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1357 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1358 getWidth(), getHeight()));
1360 if (jconsole != null)
1362 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1363 jconsole.stopConsole();
1367 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1370 if (dialogExecutor != null)
1372 dialogExecutor.shutdownNow();
1374 closeAll_actionPerformed(null);
1376 if (groovyConsole != null)
1378 // suppress a possible repeat prompt to save script
1379 groovyConsole.setDirty(false);
1380 groovyConsole.exit();
1385 private void storeLastKnownDimensions(String string, Rectangle jc)
1387 jalview.bin.Console.debug("Storing last known dimensions for " + string
1388 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1389 + " height:" + jc.height);
1391 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1392 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1393 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1394 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1404 public void aboutMenuItem_actionPerformed(ActionEvent e)
1406 new Thread(new Runnable()
1411 new SplashScreen(false);
1417 * Returns the html text for the About screen, including any available version
1418 * number, build details, author details and citation reference, but without
1419 * the enclosing {@code html} tags
1423 public String getAboutMessage()
1425 StringBuilder message = new StringBuilder(1024);
1426 message.append("<div style=\"font-family: sans-serif;\">")
1427 .append("<h1><strong>Version: ")
1428 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1429 .append("<strong>Built: <em>")
1430 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1431 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1432 .append("</strong>");
1434 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1435 if (latestVersion.equals("Checking"))
1437 // JBP removed this message for 2.11: May be reinstated in future version
1438 // message.append("<br>...Checking latest version...</br>");
1440 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1442 boolean red = false;
1443 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1444 .indexOf("automated build") == -1)
1447 // Displayed when code version and jnlp version do not match and code
1448 // version is not a development build
1449 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1452 message.append("<br>!! Version ")
1453 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1454 .append(" is available for download from ")
1455 .append(Cache.getDefault("www.jalview.org",
1456 "https://www.jalview.org"))
1460 message.append("</div>");
1463 message.append("<br>Authors: ");
1464 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1465 message.append(CITATION);
1467 message.append("</div>");
1469 return message.toString();
1473 * Action on requesting Help documentation
1476 public void documentationMenuItem_actionPerformed()
1480 if (Platform.isJS())
1482 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1491 Help.showHelpWindow();
1493 } catch (Exception ex)
1495 System.err.println("Error opening help: " + ex.getMessage());
1500 public void closeAll_actionPerformed(ActionEvent e)
1502 // TODO show a progress bar while closing?
1503 JInternalFrame[] frames = desktop.getAllFrames();
1504 for (int i = 0; i < frames.length; i++)
1508 frames[i].setClosed(true);
1509 } catch (java.beans.PropertyVetoException ex)
1513 Jalview.setCurrentAlignFrame(null);
1514 System.out.println("ALL CLOSED");
1517 * reset state of singleton objects as appropriate (clear down session state
1518 * when all windows are closed)
1520 StructureSelectionManager ssm = StructureSelectionManager
1521 .getStructureSelectionManager(this);
1529 public void raiseRelated_actionPerformed(ActionEvent e)
1531 reorderAssociatedWindows(false, false);
1535 public void minimizeAssociated_actionPerformed(ActionEvent e)
1537 reorderAssociatedWindows(true, false);
1540 void closeAssociatedWindows()
1542 reorderAssociatedWindows(false, true);
1548 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1552 protected void garbageCollect_actionPerformed(ActionEvent e)
1554 // We simply collect the garbage
1555 jalview.bin.Console.debug("Collecting garbage...");
1557 jalview.bin.Console.debug("Finished garbage collection.");
1563 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1567 protected void showMemusage_actionPerformed(ActionEvent e)
1569 desktop.showMemoryUsage(showMemusage.isSelected());
1576 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1580 protected void showConsole_actionPerformed(ActionEvent e)
1582 showConsole(showConsole.isSelected());
1585 Console jconsole = null;
1588 * control whether the java console is visible or not
1592 void showConsole(boolean selected)
1594 // TODO: decide if we should update properties file
1595 if (jconsole != null) // BH 2018
1597 showConsole.setSelected(selected);
1598 Cache.setProperty("SHOW_JAVA_CONSOLE",
1599 Boolean.valueOf(selected).toString());
1600 jconsole.setVisible(selected);
1604 void reorderAssociatedWindows(boolean minimize, boolean close)
1606 JInternalFrame[] frames = desktop.getAllFrames();
1607 if (frames == null || frames.length < 1)
1612 AlignmentViewport source = null, target = null;
1613 if (frames[0] instanceof AlignFrame)
1615 source = ((AlignFrame) frames[0]).getCurrentView();
1617 else if (frames[0] instanceof TreePanel)
1619 source = ((TreePanel) frames[0]).getViewPort();
1621 else if (frames[0] instanceof PCAPanel)
1623 source = ((PCAPanel) frames[0]).av;
1625 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1627 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1632 for (int i = 0; i < frames.length; i++)
1635 if (frames[i] == null)
1639 if (frames[i] instanceof AlignFrame)
1641 target = ((AlignFrame) frames[i]).getCurrentView();
1643 else if (frames[i] instanceof TreePanel)
1645 target = ((TreePanel) frames[i]).getViewPort();
1647 else if (frames[i] instanceof PCAPanel)
1649 target = ((PCAPanel) frames[i]).av;
1651 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1653 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1656 if (source == target)
1662 frames[i].setClosed(true);
1666 frames[i].setIcon(minimize);
1669 frames[i].toFront();
1673 } catch (java.beans.PropertyVetoException ex)
1688 protected void preferences_actionPerformed(ActionEvent e)
1690 Preferences.openPreferences();
1694 * Prompts the user to choose a file and then saves the Jalview state as a
1695 * Jalview project file
1698 public void saveState_actionPerformed()
1700 saveState_actionPerformed(false);
1703 public void saveState_actionPerformed(boolean saveAs)
1705 java.io.File projectFile = getProjectFile();
1706 // autoSave indicates we already have a file and don't need to ask
1707 boolean autoSave = projectFile != null && !saveAs
1708 && BackupFiles.getEnabled();
1710 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1711 // saveAs="+saveAs+", Backups
1712 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1714 boolean approveSave = false;
1717 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1720 chooser.setFileView(new JalviewFileView());
1721 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1723 int value = chooser.showSaveDialog(this);
1725 if (value == JalviewFileChooser.APPROVE_OPTION)
1727 projectFile = chooser.getSelectedFile();
1728 setProjectFile(projectFile);
1733 if (approveSave || autoSave)
1735 final Desktop me = this;
1736 final java.io.File chosenFile = projectFile;
1737 new Thread(new Runnable()
1742 // TODO: refactor to Jalview desktop session controller action.
1743 setProgressBar(MessageManager.formatMessage(
1744 "label.saving_jalview_project", new Object[]
1745 { chosenFile.getName() }), chosenFile.hashCode());
1746 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1747 // TODO catch and handle errors for savestate
1748 // TODO prevent user from messing with the Desktop whilst we're saving
1751 boolean doBackup = BackupFiles.getEnabled();
1752 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1755 new Jalview2XML().saveState(
1756 doBackup ? backupfiles.getTempFile() : chosenFile);
1760 backupfiles.setWriteSuccess(true);
1761 backupfiles.rollBackupsAndRenameTempFile();
1763 } catch (OutOfMemoryError oom)
1765 new OOMWarning("Whilst saving current state to "
1766 + chosenFile.getName(), oom);
1767 } catch (Exception ex)
1769 jalview.bin.Console.error("Problems whilst trying to save to "
1770 + chosenFile.getName(), ex);
1771 JvOptionPane.showMessageDialog(me,
1772 MessageManager.formatMessage(
1773 "label.error_whilst_saving_current_state_to",
1775 { chosenFile.getName() }),
1776 MessageManager.getString("label.couldnt_save_project"),
1777 JvOptionPane.WARNING_MESSAGE);
1779 setProgressBar(null, chosenFile.hashCode());
1786 public void saveAsState_actionPerformed(ActionEvent e)
1788 saveState_actionPerformed(true);
1791 private void setProjectFile(File choice)
1793 this.projectFile = choice;
1796 public File getProjectFile()
1798 return this.projectFile;
1802 * Shows a file chooser dialog and tries to read in the selected file as a
1806 public void loadState_actionPerformed()
1808 final String[] suffix = new String[] { "jvp", "jar" };
1809 final String[] desc = new String[] { "Jalview Project",
1810 "Jalview Project (old)" };
1811 JalviewFileChooser chooser = new JalviewFileChooser(
1812 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1813 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1817 chooser.setFileView(new JalviewFileView());
1818 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1819 chooser.setResponseHandler(0, new Runnable()
1824 File selectedFile = chooser.getSelectedFile();
1825 setProjectFile(selectedFile);
1826 String choice = selectedFile.getAbsolutePath();
1827 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1828 new Thread(new Runnable()
1835 new Jalview2XML().loadJalviewAlign(selectedFile);
1836 } catch (OutOfMemoryError oom)
1838 new OOMWarning("Whilst loading project from " + choice, oom);
1839 } catch (Exception ex)
1841 jalview.bin.Console.error(
1842 "Problems whilst loading project from " + choice, ex);
1843 JvOptionPane.showMessageDialog(Desktop.desktop,
1844 MessageManager.formatMessage(
1845 "label.error_whilst_loading_project_from",
1849 .getString("label.couldnt_load_project"),
1850 JvOptionPane.WARNING_MESSAGE);
1853 }, "Project Loader").start();
1857 chooser.showOpenDialog(this);
1861 public void inputSequence_actionPerformed(ActionEvent e)
1863 new SequenceFetcher(this);
1866 JPanel progressPanel;
1868 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1870 public void startLoading(final Object fileName)
1872 if (fileLoadingCount == 0)
1874 fileLoadingPanels.add(addProgressPanel(MessageManager
1875 .formatMessage("label.loading_file", new Object[]
1881 private JPanel addProgressPanel(String string)
1883 if (progressPanel == null)
1885 progressPanel = new JPanel(new GridLayout(1, 1));
1886 totalProgressCount = 0;
1887 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1889 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1890 JProgressBar progressBar = new JProgressBar();
1891 progressBar.setIndeterminate(true);
1893 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1895 thisprogress.add(progressBar, BorderLayout.CENTER);
1896 progressPanel.add(thisprogress);
1897 ((GridLayout) progressPanel.getLayout()).setRows(
1898 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1899 ++totalProgressCount;
1900 instance.validate();
1901 return thisprogress;
1904 int totalProgressCount = 0;
1906 private void removeProgressPanel(JPanel progbar)
1908 if (progressPanel != null)
1910 synchronized (progressPanel)
1912 progressPanel.remove(progbar);
1913 GridLayout gl = (GridLayout) progressPanel.getLayout();
1914 gl.setRows(gl.getRows() - 1);
1915 if (--totalProgressCount < 1)
1917 this.getContentPane().remove(progressPanel);
1918 progressPanel = null;
1925 public void stopLoading()
1928 if (fileLoadingCount < 1)
1930 while (fileLoadingPanels.size() > 0)
1932 removeProgressPanel(fileLoadingPanels.remove(0));
1934 fileLoadingPanels.clear();
1935 fileLoadingCount = 0;
1940 public static int getViewCount(String alignmentId)
1942 AlignmentViewport[] aps = getViewports(alignmentId);
1943 return (aps == null) ? 0 : aps.length;
1948 * @param alignmentId
1949 * - if null, all sets are returned
1950 * @return all AlignmentPanels concerning the alignmentId sequence set
1952 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1954 if (Desktop.desktop == null)
1956 // no frames created and in headless mode
1957 // TODO: verify that frames are recoverable when in headless mode
1960 List<AlignmentPanel> aps = new ArrayList<>();
1961 AlignFrame[] frames = getAlignFrames();
1966 for (AlignFrame af : frames)
1968 for (AlignmentPanel ap : af.alignPanels)
1970 if (alignmentId == null
1971 || alignmentId.equals(ap.av.getSequenceSetId()))
1977 if (aps.size() == 0)
1981 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1986 * get all the viewports on an alignment.
1988 * @param sequenceSetId
1989 * unique alignment id (may be null - all viewports returned in that
1991 * @return all viewports on the alignment bound to sequenceSetId
1993 public static AlignmentViewport[] getViewports(String sequenceSetId)
1995 List<AlignmentViewport> viewp = new ArrayList<>();
1996 if (desktop != null)
1998 AlignFrame[] frames = Desktop.getAlignFrames();
2000 for (AlignFrame afr : frames)
2002 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2003 .equals(sequenceSetId))
2005 if (afr.alignPanels != null)
2007 for (AlignmentPanel ap : afr.alignPanels)
2009 if (sequenceSetId == null
2010 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2018 viewp.add(afr.getViewport());
2022 if (viewp.size() > 0)
2024 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2031 * Explode the views in the given frame into separate AlignFrame
2035 public static void explodeViews(AlignFrame af)
2037 int size = af.alignPanels.size();
2043 // FIXME: ideally should use UI interface API
2044 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2045 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2046 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2047 for (int i = 0; i < size; i++)
2049 AlignmentPanel ap = af.alignPanels.get(i);
2051 AlignFrame newaf = new AlignFrame(ap);
2053 // transfer reference for existing feature settings to new alignFrame
2054 if (ap == af.alignPanel)
2056 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2058 newaf.featureSettings = viewFeatureSettings;
2060 newaf.setFeatureSettingsGeometry(fsBounds);
2064 * Restore the view's last exploded frame geometry if known. Multiple views from
2065 * one exploded frame share and restore the same (frame) position and size.
2067 Rectangle geometry = ap.av.getExplodedGeometry();
2068 if (geometry != null)
2070 newaf.setBounds(geometry);
2073 ap.av.setGatherViewsHere(false);
2075 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2076 AlignFrame.DEFAULT_HEIGHT);
2077 // and materialise a new feature settings dialog instance for the new
2079 // (closes the old as if 'OK' was pressed)
2080 if (ap == af.alignPanel && newaf.featureSettings != null
2081 && newaf.featureSettings.isOpen()
2082 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2084 newaf.showFeatureSettingsUI();
2088 af.featureSettings = null;
2089 af.alignPanels.clear();
2090 af.closeMenuItem_actionPerformed(true);
2095 * Gather expanded views (separate AlignFrame's) with the same sequence set
2096 * identifier back in to this frame as additional views, and close the
2097 * expanded views. Note the expanded frames may themselves have multiple
2098 * views. We take the lot.
2102 public void gatherViews(AlignFrame source)
2104 source.viewport.setGatherViewsHere(true);
2105 source.viewport.setExplodedGeometry(source.getBounds());
2106 JInternalFrame[] frames = desktop.getAllFrames();
2107 String viewId = source.viewport.getSequenceSetId();
2108 for (int t = 0; t < frames.length; t++)
2110 if (frames[t] instanceof AlignFrame && frames[t] != source)
2112 AlignFrame af = (AlignFrame) frames[t];
2113 boolean gatherThis = false;
2114 for (int a = 0; a < af.alignPanels.size(); a++)
2116 AlignmentPanel ap = af.alignPanels.get(a);
2117 if (viewId.equals(ap.av.getSequenceSetId()))
2120 ap.av.setGatherViewsHere(false);
2121 ap.av.setExplodedGeometry(af.getBounds());
2122 source.addAlignmentPanel(ap, false);
2128 if (af.featureSettings != null && af.featureSettings.isOpen())
2130 if (source.featureSettings == null)
2132 // preserve the feature settings geometry for this frame
2133 source.featureSettings = af.featureSettings;
2134 source.setFeatureSettingsGeometry(
2135 af.getFeatureSettingsGeometry());
2139 // close it and forget
2140 af.featureSettings.close();
2143 af.alignPanels.clear();
2144 af.closeMenuItem_actionPerformed(true);
2149 // refresh the feature setting UI for the source frame if it exists
2150 if (source.featureSettings != null && source.featureSettings.isOpen())
2152 source.showFeatureSettingsUI();
2157 public JInternalFrame[] getAllFrames()
2159 return desktop.getAllFrames();
2163 * Checks the given url to see if it gives a response indicating that the user
2164 * should be informed of a new questionnaire.
2168 public void checkForQuestionnaire(String url)
2170 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2171 // javax.swing.SwingUtilities.invokeLater(jvq);
2172 new Thread(jvq).start();
2175 public void checkURLLinks()
2177 // Thread off the URL link checker
2178 addDialogThread(new Runnable()
2183 if (Cache.getDefault("CHECKURLLINKS", true))
2185 // check what the actual links are - if it's just the default don't
2186 // bother with the warning
2187 List<String> links = Preferences.sequenceUrlLinks
2190 // only need to check links if there is one with a
2191 // SEQUENCE_ID which is not the default EMBL_EBI link
2192 ListIterator<String> li = links.listIterator();
2193 boolean check = false;
2194 List<JLabel> urls = new ArrayList<>();
2195 while (li.hasNext())
2197 String link = li.next();
2198 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2199 && !UrlConstants.isDefaultString(link))
2202 int barPos = link.indexOf("|");
2203 String urlMsg = barPos == -1 ? link
2204 : link.substring(0, barPos) + ": "
2205 + link.substring(barPos + 1);
2206 urls.add(new JLabel(urlMsg));
2214 // ask user to check in case URL links use old style tokens
2215 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2216 JPanel msgPanel = new JPanel();
2217 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2218 msgPanel.add(Box.createVerticalGlue());
2219 JLabel msg = new JLabel(MessageManager
2220 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2221 JLabel msg2 = new JLabel(MessageManager
2222 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2224 for (JLabel url : urls)
2230 final JCheckBox jcb = new JCheckBox(
2231 MessageManager.getString("label.do_not_display_again"));
2232 jcb.addActionListener(new ActionListener()
2235 public void actionPerformed(ActionEvent e)
2237 // update Cache settings for "don't show this again"
2238 boolean showWarningAgain = !jcb.isSelected();
2239 Cache.setProperty("CHECKURLLINKS",
2240 Boolean.valueOf(showWarningAgain).toString());
2245 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2247 .getString("label.SEQUENCE_ID_no_longer_used"),
2248 JvOptionPane.WARNING_MESSAGE);
2255 * Proxy class for JDesktopPane which optionally displays the current memory
2256 * usage and highlights the desktop area with a red bar if free memory runs
2261 public class MyDesktopPane extends JDesktopPane implements Runnable
2263 private static final float ONE_MB = 1048576f;
2265 boolean showMemoryUsage = false;
2269 java.text.NumberFormat df;
2271 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2274 public MyDesktopPane(boolean showMemoryUsage)
2276 showMemoryUsage(showMemoryUsage);
2279 public void showMemoryUsage(boolean showMemory)
2281 this.showMemoryUsage = showMemory;
2284 Thread worker = new Thread(this);
2290 public boolean isShowMemoryUsage()
2292 return showMemoryUsage;
2298 df = java.text.NumberFormat.getNumberInstance();
2299 df.setMaximumFractionDigits(2);
2300 runtime = Runtime.getRuntime();
2302 while (showMemoryUsage)
2306 maxMemory = runtime.maxMemory() / ONE_MB;
2307 allocatedMemory = runtime.totalMemory() / ONE_MB;
2308 freeMemory = runtime.freeMemory() / ONE_MB;
2309 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2311 percentUsage = (totalFreeMemory / maxMemory) * 100;
2313 // if (percentUsage < 20)
2315 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2317 // instance.set.setBorder(border1);
2320 // sleep after showing usage
2322 } catch (Exception ex)
2324 ex.printStackTrace();
2330 public void paintComponent(Graphics g)
2332 if (showMemoryUsage && g != null && df != null)
2334 if (percentUsage < 20)
2336 g.setColor(Color.red);
2338 FontMetrics fm = g.getFontMetrics();
2341 g.drawString(MessageManager.formatMessage("label.memory_stats",
2343 { df.format(totalFreeMemory), df.format(maxMemory),
2344 df.format(percentUsage) }),
2345 10, getHeight() - fm.getHeight());
2349 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2350 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2355 * Accessor method to quickly get all the AlignmentFrames loaded.
2357 * @return an array of AlignFrame, or null if none found
2359 public static AlignFrame[] getAlignFrames()
2361 if (Jalview.isHeadlessMode())
2363 // Desktop.desktop is null in headless mode
2364 return new AlignFrame[] { Jalview.currentAlignFrame };
2367 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2373 List<AlignFrame> avp = new ArrayList<>();
2375 for (int i = frames.length - 1; i > -1; i--)
2377 if (frames[i] instanceof AlignFrame)
2379 avp.add((AlignFrame) frames[i]);
2381 else if (frames[i] instanceof SplitFrame)
2384 * Also check for a split frame containing an AlignFrame
2386 GSplitFrame sf = (GSplitFrame) frames[i];
2387 if (sf.getTopFrame() instanceof AlignFrame)
2389 avp.add((AlignFrame) sf.getTopFrame());
2391 if (sf.getBottomFrame() instanceof AlignFrame)
2393 avp.add((AlignFrame) sf.getBottomFrame());
2397 if (avp.size() == 0)
2401 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2406 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2410 public GStructureViewer[] getJmols()
2412 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2418 List<GStructureViewer> avp = new ArrayList<>();
2420 for (int i = frames.length - 1; i > -1; i--)
2422 if (frames[i] instanceof AppJmol)
2424 GStructureViewer af = (GStructureViewer) frames[i];
2428 if (avp.size() == 0)
2432 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2437 * Add Groovy Support to Jalview
2440 public void groovyShell_actionPerformed()
2444 openGroovyConsole();
2445 } catch (Exception ex)
2447 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2448 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2450 MessageManager.getString("label.couldnt_create_groovy_shell"),
2451 MessageManager.getString("label.groovy_support_failed"),
2452 JvOptionPane.ERROR_MESSAGE);
2457 * Open the Groovy console
2459 void openGroovyConsole()
2461 if (groovyConsole == null)
2463 groovyConsole = new groovy.ui.Console();
2464 groovyConsole.setVariable("Jalview", this);
2465 groovyConsole.run();
2468 * We allow only one console at a time, so that AlignFrame menu option
2469 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2470 * enable 'Run script', when the console is opened, and the reverse when it is
2473 Window window = (Window) groovyConsole.getFrame();
2474 window.addWindowListener(new WindowAdapter()
2477 public void windowClosed(WindowEvent e)
2480 * rebind CMD-Q from Groovy Console to Jalview Quit
2483 enableExecuteGroovy(false);
2489 * show Groovy console window (after close and reopen)
2491 ((Window) groovyConsole.getFrame()).setVisible(true);
2494 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2495 * opening a second console
2497 enableExecuteGroovy(true);
2501 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2502 * binding when opened
2504 protected void addQuitHandler()
2507 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2509 .getKeyStroke(KeyEvent.VK_Q,
2510 jalview.util.ShortcutKeyMaskExWrapper
2511 .getMenuShortcutKeyMaskEx()),
2513 getRootPane().getActionMap().put("Quit", new AbstractAction()
2516 public void actionPerformed(ActionEvent e)
2524 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2527 * true if Groovy console is open
2529 public void enableExecuteGroovy(boolean enabled)
2532 * disable opening a second Groovy console (or re-enable when the console is
2535 groovyShell.setEnabled(!enabled);
2537 AlignFrame[] alignFrames = getAlignFrames();
2538 if (alignFrames != null)
2540 for (AlignFrame af : alignFrames)
2542 af.setGroovyEnabled(enabled);
2548 * Progress bars managed by the IProgressIndicator method.
2550 private Hashtable<Long, JPanel> progressBars;
2552 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2557 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2560 public void setProgressBar(String message, long id)
2562 if (progressBars == null)
2564 progressBars = new Hashtable<>();
2565 progressBarHandlers = new Hashtable<>();
2568 if (progressBars.get(Long.valueOf(id)) != null)
2570 JPanel panel = progressBars.remove(Long.valueOf(id));
2571 if (progressBarHandlers.contains(Long.valueOf(id)))
2573 progressBarHandlers.remove(Long.valueOf(id));
2575 removeProgressPanel(panel);
2579 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2586 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2587 * jalview.gui.IProgressIndicatorHandler)
2590 public void registerHandler(final long id,
2591 final IProgressIndicatorHandler handler)
2593 if (progressBarHandlers == null
2594 || !progressBars.containsKey(Long.valueOf(id)))
2596 throw new Error(MessageManager.getString(
2597 "error.call_setprogressbar_before_registering_handler"));
2599 progressBarHandlers.put(Long.valueOf(id), handler);
2600 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2601 if (handler.canCancel())
2603 JButton cancel = new JButton(
2604 MessageManager.getString("action.cancel"));
2605 final IProgressIndicator us = this;
2606 cancel.addActionListener(new ActionListener()
2610 public void actionPerformed(ActionEvent e)
2612 handler.cancelActivity(id);
2613 us.setProgressBar(MessageManager
2614 .formatMessage("label.cancelled_params", new Object[]
2615 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2619 progressPanel.add(cancel, BorderLayout.EAST);
2625 * @return true if any progress bars are still active
2628 public boolean operationInProgress()
2630 if (progressBars != null && progressBars.size() > 0)
2638 * This will return the first AlignFrame holding the given viewport instance.
2639 * It will break if there are more than one AlignFrames viewing a particular
2643 * @return alignFrame for viewport
2645 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2647 if (desktop != null)
2649 AlignmentPanel[] aps = getAlignmentPanels(
2650 viewport.getSequenceSetId());
2651 for (int panel = 0; aps != null && panel < aps.length; panel++)
2653 if (aps[panel] != null && aps[panel].av == viewport)
2655 return aps[panel].alignFrame;
2662 public VamsasApplication getVamsasApplication()
2664 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2670 * flag set if jalview GUI is being operated programmatically
2672 private boolean inBatchMode = false;
2675 * check if jalview GUI is being operated programmatically
2677 * @return inBatchMode
2679 public boolean isInBatchMode()
2685 * set flag if jalview GUI is being operated programmatically
2687 * @param inBatchMode
2689 public void setInBatchMode(boolean inBatchMode)
2691 this.inBatchMode = inBatchMode;
2695 * start service discovery and wait till it is done
2697 public void startServiceDiscovery()
2699 startServiceDiscovery(false);
2703 * start service discovery threads - blocking or non-blocking
2707 public void startServiceDiscovery(boolean blocking)
2709 startServiceDiscovery(blocking, false);
2713 * start service discovery threads
2716 * - false means call returns immediately
2717 * @param ignore_SHOW_JWS2_SERVICES_preference
2718 * - when true JABA services are discovered regardless of user's JWS2
2719 * discovery preference setting
2721 public void startServiceDiscovery(boolean blocking,
2722 boolean ignore_SHOW_JWS2_SERVICES_preference)
2724 boolean alive = true;
2725 Thread t0 = null, t1 = null, t2 = null;
2726 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2729 // todo: changesupport handlers need to be transferred
2730 if (discoverer == null)
2732 discoverer = new jalview.ws.jws1.Discoverer();
2733 // register PCS handler for desktop.
2734 discoverer.addPropertyChangeListener(changeSupport);
2736 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2737 // until we phase out completely
2738 (t0 = new Thread(discoverer)).start();
2741 if (ignore_SHOW_JWS2_SERVICES_preference
2742 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2744 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2745 .startDiscoverer(changeSupport);
2749 // TODO: do rest service discovery
2758 } catch (Exception e)
2761 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2762 || (t3 != null && t3.isAlive())
2763 || (t0 != null && t0.isAlive());
2769 * called to check if the service discovery process completed successfully.
2773 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2775 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2777 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2778 .getErrorMessages();
2781 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2783 if (serviceChangedDialog == null)
2785 // only run if we aren't already displaying one of these.
2786 addDialogThread(serviceChangedDialog = new Runnable()
2793 * JalviewDialog jd =new JalviewDialog() {
2795 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2797 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2799 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2801 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2803 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2804 * + " or mis-configured HTTP proxy settings.<br/>" +
2805 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2806 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2807 * true, true, "Web Service Configuration Problem", 450, 400);
2809 * jd.waitForInput();
2811 JvOptionPane.showConfirmDialog(Desktop.desktop,
2812 new JLabel("<html><table width=\"450\"><tr><td>"
2813 + ermsg + "</td></tr></table>"
2814 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2815 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2816 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2817 + " Tools->Preferences dialog box to change them.</p></html>"),
2818 "Web Service Configuration Problem",
2819 JvOptionPane.DEFAULT_OPTION,
2820 JvOptionPane.ERROR_MESSAGE);
2821 serviceChangedDialog = null;
2829 jalview.bin.Console.error(
2830 "Errors reported by JABA discovery service. Check web services preferences.\n"
2837 private Runnable serviceChangedDialog = null;
2840 * start a thread to open a URL in the configured browser. Pops up a warning
2841 * dialog to the user if there is an exception when calling out to the browser
2846 public static void showUrl(final String url)
2848 showUrl(url, Desktop.instance);
2852 * Like showUrl but allows progress handler to be specified
2856 * (null) or object implementing IProgressIndicator
2858 public static void showUrl(final String url,
2859 final IProgressIndicator progress)
2861 new Thread(new Runnable()
2868 if (progress != null)
2870 progress.setProgressBar(MessageManager
2871 .formatMessage("status.opening_params", new Object[]
2872 { url }), this.hashCode());
2874 jalview.util.BrowserLauncher.openURL(url);
2875 } catch (Exception ex)
2877 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2879 .getString("label.web_browser_not_found_unix"),
2880 MessageManager.getString("label.web_browser_not_found"),
2881 JvOptionPane.WARNING_MESSAGE);
2883 ex.printStackTrace();
2885 if (progress != null)
2887 progress.setProgressBar(null, this.hashCode());
2893 public static WsParamSetManager wsparamManager = null;
2895 public static ParamManager getUserParameterStore()
2897 if (wsparamManager == null)
2899 wsparamManager = new WsParamSetManager();
2901 return wsparamManager;
2905 * static hyperlink handler proxy method for use by Jalview's internal windows
2909 public static void hyperlinkUpdate(HyperlinkEvent e)
2911 if (e.getEventType() == EventType.ACTIVATED)
2916 url = e.getURL().toString();
2917 Desktop.showUrl(url);
2918 } catch (Exception x)
2923 .error("Couldn't handle string " + url + " as a URL.");
2925 // ignore any exceptions due to dud links.
2932 * single thread that handles display of dialogs to user.
2934 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2937 * flag indicating if dialogExecutor should try to acquire a permit
2939 private volatile boolean dialogPause = true;
2944 private java.util.concurrent.Semaphore block = new Semaphore(0);
2946 private static groovy.ui.Console groovyConsole;
2949 * add another dialog thread to the queue
2953 public void addDialogThread(final Runnable prompter)
2955 dialogExecutor.submit(new Runnable()
2965 } catch (InterruptedException x)
2969 if (instance == null)
2975 SwingUtilities.invokeAndWait(prompter);
2976 } catch (Exception q)
2978 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
2985 public void startDialogQueue()
2987 // set the flag so we don't pause waiting for another permit and semaphore
2988 // the current task to begin
2989 dialogPause = false;
2994 * Outputs an image of the desktop to file in EPS format, after prompting the
2995 * user for choice of Text or Lineart character rendering (unless a preference
2996 * has been set). The file name is generated as
2999 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3003 protected void snapShotWindow_actionPerformed(ActionEvent e)
3005 // currently the menu option to do this is not shown
3008 int width = getWidth();
3009 int height = getHeight();
3011 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3012 ImageWriterI writer = new ImageWriterI()
3015 public void exportImage(Graphics g) throws Exception
3018 jalview.bin.Console.info("Successfully written snapshot to file "
3019 + of.getAbsolutePath());
3022 String title = "View of desktop";
3023 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3025 exporter.doExport(of, this, width, height, title);
3029 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3030 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3031 * and location last time the view was expanded (if any). However it does not
3032 * remember the split pane divider location - this is set to match the
3033 * 'exploding' frame.
3037 public void explodeViews(SplitFrame sf)
3039 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3040 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3041 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3043 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3045 int viewCount = topPanels.size();
3052 * Processing in reverse order works, forwards order leaves the first panels not
3053 * visible. I don't know why!
3055 for (int i = viewCount - 1; i >= 0; i--)
3058 * Make new top and bottom frames. These take over the respective AlignmentPanel
3059 * objects, including their AlignmentViewports, so the cdna/protein
3060 * relationships between the viewports is carried over to the new split frames.
3062 * explodedGeometry holds the (x, y) position of the previously exploded
3063 * SplitFrame, and the (width, height) of the AlignFrame component
3065 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3066 AlignFrame newTopFrame = new AlignFrame(topPanel);
3067 newTopFrame.setSize(oldTopFrame.getSize());
3068 newTopFrame.setVisible(true);
3069 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3070 .getExplodedGeometry();
3071 if (geometry != null)
3073 newTopFrame.setSize(geometry.getSize());
3076 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3077 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3078 newBottomFrame.setSize(oldBottomFrame.getSize());
3079 newBottomFrame.setVisible(true);
3080 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3081 .getExplodedGeometry();
3082 if (geometry != null)
3084 newBottomFrame.setSize(geometry.getSize());
3087 topPanel.av.setGatherViewsHere(false);
3088 bottomPanel.av.setGatherViewsHere(false);
3089 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3091 if (geometry != null)
3093 splitFrame.setLocation(geometry.getLocation());
3095 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3099 * Clear references to the panels (now relocated in the new SplitFrames) before
3100 * closing the old SplitFrame.
3103 bottomPanels.clear();
3108 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3109 * back into the given SplitFrame as additional views. Note that the gathered
3110 * frames may themselves have multiple views.
3114 public void gatherViews(GSplitFrame source)
3117 * special handling of explodedGeometry for a view within a SplitFrame: - it
3118 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3119 * height) of the AlignFrame component
3121 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3122 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3123 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3124 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3125 myBottomFrame.viewport
3126 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3127 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3128 myTopFrame.viewport.setGatherViewsHere(true);
3129 myBottomFrame.viewport.setGatherViewsHere(true);
3130 String topViewId = myTopFrame.viewport.getSequenceSetId();
3131 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3133 JInternalFrame[] frames = desktop.getAllFrames();
3134 for (JInternalFrame frame : frames)
3136 if (frame instanceof SplitFrame && frame != source)
3138 SplitFrame sf = (SplitFrame) frame;
3139 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3140 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3141 boolean gatherThis = false;
3142 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3144 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3145 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3146 if (topViewId.equals(topPanel.av.getSequenceSetId())
3147 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3150 topPanel.av.setGatherViewsHere(false);
3151 bottomPanel.av.setGatherViewsHere(false);
3152 topPanel.av.setExplodedGeometry(
3153 new Rectangle(sf.getLocation(), topFrame.getSize()));
3154 bottomPanel.av.setExplodedGeometry(
3155 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3156 myTopFrame.addAlignmentPanel(topPanel, false);
3157 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3163 topFrame.getAlignPanels().clear();
3164 bottomFrame.getAlignPanels().clear();
3171 * The dust settles...give focus to the tab we did this from.
3173 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3176 public static groovy.ui.Console getGroovyConsole()
3178 return groovyConsole;
3182 * handles the payload of a drag and drop event.
3184 * TODO refactor to desktop utilities class
3187 * - Data source strings extracted from the drop event
3189 * - protocol for each data source extracted from the drop event
3193 * - the payload from the drop event
3196 public static void transferFromDropTarget(List<Object> files,
3197 List<DataSourceType> protocols, DropTargetDropEvent evt,
3198 Transferable t) throws Exception
3201 DataFlavor uriListFlavor = new DataFlavor(
3202 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3205 urlFlavour = new DataFlavor(
3206 "application/x-java-url; class=java.net.URL");
3207 } catch (ClassNotFoundException cfe)
3209 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3213 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3218 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3219 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3220 // means url may be null.
3223 protocols.add(DataSourceType.URL);
3224 files.add(url.toString());
3225 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3226 + files.get(files.size() - 1));
3231 if (Platform.isAMacAndNotJS())
3234 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3237 } catch (Throwable ex)
3239 jalview.bin.Console.debug("URL drop handler failed.", ex);
3242 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3244 // Works on Windows and MacOSX
3245 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3246 for (Object file : (List) t
3247 .getTransferData(DataFlavor.javaFileListFlavor))
3250 protocols.add(DataSourceType.FILE);
3255 // Unix like behaviour
3256 boolean added = false;
3258 if (t.isDataFlavorSupported(uriListFlavor))
3260 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3261 // This is used by Unix drag system
3262 data = (String) t.getTransferData(uriListFlavor);
3266 // fallback to text: workaround - on OSX where there's a JVM bug
3268 .debug("standard URIListFlavor failed. Trying text");
3269 // try text fallback
3270 DataFlavor textDf = new DataFlavor(
3271 "text/plain;class=java.lang.String");
3272 if (t.isDataFlavorSupported(textDf))
3274 data = (String) t.getTransferData(textDf);
3277 jalview.bin.Console.debug("Plain text drop content returned "
3278 + (data == null ? "Null - failed" : data));
3283 while (protocols.size() < files.size())
3285 jalview.bin.Console.debug("Adding missing FILE protocol for "
3286 + files.get(protocols.size()));
3287 protocols.add(DataSourceType.FILE);
3289 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3290 data, "\r\n"); st.hasMoreTokens();)
3293 String s = st.nextToken();
3294 if (s.startsWith("#"))
3296 // the line is a comment (as per the RFC 2483)
3299 java.net.URI uri = new java.net.URI(s);
3300 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3302 protocols.add(DataSourceType.URL);
3303 files.add(uri.toString());
3307 // otherwise preserve old behaviour: catch all for file objects
3308 java.io.File file = new java.io.File(uri);
3309 protocols.add(DataSourceType.FILE);
3310 files.add(file.toString());
3315 if (jalview.bin.Console.isDebugEnabled())
3317 if (data == null || !added)
3320 if (t.getTransferDataFlavors() != null
3321 && t.getTransferDataFlavors().length > 0)
3323 jalview.bin.Console.debug(
3324 "Couldn't resolve drop data. Here are the supported flavors:");
3325 for (DataFlavor fl : t.getTransferDataFlavors())
3327 jalview.bin.Console.debug(
3328 "Supported transfer dataflavor: " + fl.toString());
3329 Object df = t.getTransferData(fl);
3332 jalview.bin.Console.debug("Retrieves: " + df);
3336 jalview.bin.Console.debug("Retrieved nothing");
3343 .debug("Couldn't resolve dataflavor for drop: "
3349 if (Platform.isWindowsAndNotJS())
3352 .debug("Scanning dropped content for Windows Link Files");
3354 // resolve any .lnk files in the file drop
3355 for (int f = 0; f < files.size(); f++)
3357 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3358 if (protocols.get(f).equals(DataSourceType.FILE)
3359 && (source.endsWith(".lnk") || source.endsWith(".url")
3360 || source.endsWith(".site")))
3364 Object obj = files.get(f);
3365 File lf = (obj instanceof File ? (File) obj
3366 : new File((String) obj));
3367 // process link file to get a URL
3368 jalview.bin.Console.debug("Found potential link file: " + lf);
3369 WindowsShortcut wscfile = new WindowsShortcut(lf);
3370 String fullname = wscfile.getRealFilename();
3371 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3372 files.set(f, fullname);
3373 jalview.bin.Console.debug("Parsed real filename " + fullname
3374 + " to extract protocol: " + protocols.get(f));
3375 } catch (Exception ex)
3377 jalview.bin.Console.error(
3378 "Couldn't parse " + files.get(f) + " as a link file.",
3387 * Sets the Preferences property for experimental features to True or False
3388 * depending on the state of the controlling menu item
3391 protected void showExperimental_actionPerformed(boolean selected)
3393 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3397 * Answers a (possibly empty) list of any structure viewer frames (currently
3398 * for either Jmol or Chimera) which are currently open. This may optionally
3399 * be restricted to viewers of a specified class, or viewers linked to a
3400 * specified alignment panel.
3403 * if not null, only return viewers linked to this panel
3404 * @param structureViewerClass
3405 * if not null, only return viewers of this class
3408 public List<StructureViewerBase> getStructureViewers(
3409 AlignmentPanel apanel,
3410 Class<? extends StructureViewerBase> structureViewerClass)
3412 List<StructureViewerBase> result = new ArrayList<>();
3413 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3415 for (JInternalFrame frame : frames)
3417 if (frame instanceof StructureViewerBase)
3419 if (structureViewerClass == null
3420 || structureViewerClass.isInstance(frame))
3423 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3425 result.add((StructureViewerBase) frame);
3433 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3435 private static boolean debugScaleMessageDone = false;
3437 public static void debugScaleMessage(Graphics g)
3439 if (debugScaleMessageDone)
3443 // output used by tests to check HiDPI scaling settings in action
3446 Graphics2D gg = (Graphics2D) g;
3449 AffineTransform t = gg.getTransform();
3450 double scaleX = t.getScaleX();
3451 double scaleY = t.getScaleY();
3452 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3453 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3454 debugScaleMessageDone = true;
3458 jalview.bin.Console.debug("Desktop graphics null");
3460 } catch (Exception e)
3462 jalview.bin.Console.debug(Cache.getStackTraceString(e));