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.util.Locale;
25 import java.awt.BorderLayout;
26 import java.awt.Color;
27 import java.awt.Dimension;
28 import java.awt.FontMetrics;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.GridLayout;
32 import java.awt.Point;
33 import java.awt.Rectangle;
34 import java.awt.Toolkit;
35 import java.awt.Window;
36 import java.awt.datatransfer.Clipboard;
37 import java.awt.datatransfer.ClipboardOwner;
38 import java.awt.datatransfer.DataFlavor;
39 import java.awt.datatransfer.Transferable;
40 import java.awt.dnd.DnDConstants;
41 import java.awt.dnd.DropTargetDragEvent;
42 import java.awt.dnd.DropTargetDropEvent;
43 import java.awt.dnd.DropTargetEvent;
44 import java.awt.dnd.DropTargetListener;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ActionListener;
47 import java.awt.event.InputEvent;
48 import java.awt.event.KeyEvent;
49 import java.awt.event.MouseAdapter;
50 import java.awt.event.MouseEvent;
51 import java.awt.event.WindowAdapter;
52 import java.awt.event.WindowEvent;
53 import java.awt.geom.AffineTransform;
54 import java.beans.PropertyChangeEvent;
55 import java.beans.PropertyChangeListener;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Vector;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Semaphore;
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.Box;
76 import javax.swing.BoxLayout;
77 import javax.swing.DefaultDesktopManager;
78 import javax.swing.DesktopManager;
79 import javax.swing.InputMap;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JComboBox;
83 import javax.swing.JComponent;
84 import javax.swing.JDesktopPane;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.event.HyperlinkEvent;
95 import javax.swing.event.HyperlinkEvent.EventType;
96 import javax.swing.event.InternalFrameAdapter;
97 import javax.swing.event.InternalFrameEvent;
99 import org.stackoverflowusers.file.WindowsShortcut;
101 import jalview.api.AlignViewportI;
102 import jalview.api.AlignmentViewPanel;
103 import jalview.bin.Cache;
104 import jalview.bin.Jalview;
105 import jalview.gui.ImageExporter.ImageWriterI;
106 import jalview.io.BackupFiles;
107 import jalview.io.DataSourceType;
108 import jalview.io.FileFormat;
109 import jalview.io.FileFormatException;
110 import jalview.io.FileFormatI;
111 import jalview.io.FileFormats;
112 import jalview.io.FileLoader;
113 import jalview.io.FormatAdapter;
114 import jalview.io.IdentifyFile;
115 import jalview.io.JalviewFileChooser;
116 import jalview.io.JalviewFileView;
117 import jalview.jbgui.GSplitFrame;
118 import jalview.jbgui.GStructureViewer;
119 import jalview.project.Jalview2XML;
120 import jalview.structure.StructureSelectionManager;
121 import jalview.urls.IdOrgSettings;
122 import jalview.util.BrowserLauncher;
123 import jalview.util.ChannelProperties;
124 import jalview.util.ImageMaker.TYPE;
125 import jalview.util.MessageManager;
126 import jalview.util.Platform;
127 import jalview.util.ShortcutKeyMaskExWrapper;
128 import jalview.util.UrlConstants;
129 import jalview.viewmodel.AlignmentViewport;
130 import jalview.ws.params.ParamManager;
131 import jalview.ws.utils.UrlDownloadClient;
138 * @version $Revision: 1.155 $
140 public class Desktop extends jalview.jbgui.GDesktop
141 implements DropTargetListener, ClipboardOwner, IProgressIndicator, jalview.api.StructureSelectionManagerProvider {
142 private static final String CITATION;
144 URL bg_logo_url = ChannelProperties.getImageURL("bg_logo." + String.valueOf(SplashScreen.logoSize));
145 URL uod_logo_url = ChannelProperties.getImageURL("uod_banner." + String.valueOf(SplashScreen.logoSize));
146 boolean logo = (bg_logo_url != null || uod_logo_url != null);
147 StringBuilder sb = new StringBuilder();
148 sb.append("<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.");
152 sb.append(bg_logo_url == null ? "" : "<img alt=\"Barton Group logo\" src=\"" + bg_logo_url.toString() + "\">");
153 sb.append(uod_logo_url == null ? ""
154 : " <img alt=\"University of Dundee shield\" src=\"" + uod_logo_url.toString() + "\">");
156 "<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");
157 sb.append("<br><br>If you use Jalview, please cite:"
158 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
159 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
160 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033");
161 CITATION = sb.toString();
164 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
166 private static int DEFAULT_MIN_WIDTH = 300;
168 private static int DEFAULT_MIN_HEIGHT = 250;
170 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
172 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
174 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
176 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
178 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
180 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
182 public static boolean nosplash = false;
185 * news reader - null if it was never started.
187 private BlogReader jvnews = null;
189 private File projectFile;
193 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
195 public void addJalviewPropertyChangeListener(PropertyChangeListener listener) {
196 changeSupport.addJalviewPropertyChangeListener(listener);
200 * @param propertyName
202 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
203 * java.beans.PropertyChangeListener)
205 public void addJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
206 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
210 * @param propertyName
212 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
213 * java.beans.PropertyChangeListener)
215 public void removeJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
216 changeSupport.removeJalviewPropertyChangeListener(propertyName, listener);
219 /** Singleton Desktop instance */
220 public static Desktop instance;
222 public static MyDesktopPane desktop;
224 public static MyDesktopPane getDesktop() {
225 // BH 2018 could use currentThread() here as a reference to a
226 // Hashtable<Thread, MyDesktopPane> in JavaScript
230 static int openFrameCount = 0;
232 static final int xOffset = 30;
234 static final int yOffset = 30;
236 public static jalview.ws.jws1.Discoverer discoverer;
238 public static Object[] jalviewClipboard;
240 public static boolean internalCopy = false;
242 static int fileLoadingCount = 0;
244 class MyDesktopManager implements DesktopManager {
246 private DesktopManager delegate;
248 public MyDesktopManager(DesktopManager delegate) {
249 this.delegate = delegate;
253 public void activateFrame(JInternalFrame f) {
255 delegate.activateFrame(f);
256 } catch (NullPointerException npe) {
257 Point p = getMousePosition();
258 instance.showPasteMenu(p.x, p.y);
263 public void beginDraggingFrame(JComponent f) {
264 delegate.beginDraggingFrame(f);
268 public void beginResizingFrame(JComponent f, int direction) {
269 delegate.beginResizingFrame(f, direction);
273 public void closeFrame(JInternalFrame f) {
274 delegate.closeFrame(f);
278 public void deactivateFrame(JInternalFrame f) {
279 delegate.deactivateFrame(f);
283 public void deiconifyFrame(JInternalFrame f) {
284 delegate.deiconifyFrame(f);
288 public void dragFrame(JComponent f, int newX, int newY) {
292 delegate.dragFrame(f, newX, newY);
296 public void endDraggingFrame(JComponent f) {
297 delegate.endDraggingFrame(f);
302 public void endResizingFrame(JComponent f) {
303 delegate.endResizingFrame(f);
308 public void iconifyFrame(JInternalFrame f) {
309 delegate.iconifyFrame(f);
313 public void maximizeFrame(JInternalFrame f) {
314 delegate.maximizeFrame(f);
318 public void minimizeFrame(JInternalFrame f) {
319 delegate.minimizeFrame(f);
323 public void openFrame(JInternalFrame f) {
324 delegate.openFrame(f);
328 public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
332 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
336 public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
337 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
340 // All other methods, simply delegate
345 * Creates a new Desktop object.
350 * A note to implementors. It is ESSENTIAL that any activities that might block
351 * are spawned off as threads rather than waited for during this constructor.
355 doConfigureStructurePrefs();
356 setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
359 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
360 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
361 * documented or guaranteed to exist, so we access it via reflection. There
362 * appear to be unfathomable criteria about what this string can contain, and it
363 * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
364 * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
365 * but "Jalview non-release" does not. The reflection access may generate a
366 * warning: WARNING: An illegal reflective access operation has occurred
367 * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
368 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
370 if (Platform.isLinux()) {
372 Toolkit xToolkit = Toolkit.getDefaultToolkit();
373 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
374 Field awtAppClassNameField = null;
376 if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
377 awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
380 String title = ChannelProperties.getProperty("app_name");
381 if (awtAppClassNameField != null) {
382 awtAppClassNameField.setAccessible(true);
383 awtAppClassNameField.set(xToolkit, title);
385 Cache.log.debug("XToolkit: awtAppClassName not found");
387 } catch (Exception e) {
388 Cache.debug("Error setting awtAppClassName");
389 Cache.trace(Cache.getStackTraceString(e));
394 * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
395 * macOS's application menu. APQHandlers will check to see if a handler is
396 * supported before setting it.
399 APQHandlers.setAPQHandlers(this);
400 } catch (Exception e) {
401 System.out.println("Cannot set APQHandlers");
402 // e.printStackTrace();
403 } catch (Throwable t) {
404 Cache.warn("Error setting APQHandlers: " + t.toString());
405 Cache.trace(Cache.getStackTraceString(t));
407 setIconImages(ChannelProperties.getIconList());
409 addWindowListener(new WindowAdapter() {
412 public void windowClosing(WindowEvent ev) {
417 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
419 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
420 desktop = new MyDesktopPane(selmemusage);
422 showMemusage.setSelected(selmemusage);
423 desktop.setBackground(Color.white);
425 getContentPane().setLayout(new BorderLayout());
426 // alternate config - have scrollbars - see notes in JAL-153
427 // JScrollPane sp = new JScrollPane();
428 // sp.getViewport().setView(desktop);
429 // getContentPane().add(sp, BorderLayout.CENTER);
431 // BH 2018 - just an experiment to try unclipped JInternalFrames.
432 if (Platform.isJS()) {
433 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
436 getContentPane().add(desktop, BorderLayout.CENTER);
437 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
439 // This line prevents Windows Look&Feel resizing all new windows to maximum
440 // if previous window was maximised
441 desktop.setDesktopManager(new MyDesktopManager((Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
442 : Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(desktop.getDesktopManager())
443 : desktop.getDesktopManager())));
445 Rectangle dims = getLastKnownDimensions("");
449 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
450 int xPos = Math.max(5, (screenSize.width - 900) / 2);
451 int yPos = Math.max(5, (screenSize.height - 650) / 2);
452 setBounds(xPos, yPos, 900, 650);
455 if (!Platform.isJS())
462 jconsole = new Console(this, showjconsole);
463 jconsole.setHeader(Cache.getVersionDetailsForConsole());
464 showConsole(showjconsole);
466 showNews.setVisible(false);
468 experimentalFeatures.setSelected(showExperimental());
470 getIdentifiersOrgData();
474 // Spawn a thread that shows the splashscreen
476 SwingUtilities.invokeLater(new Runnable() {
479 new SplashScreen(true);
484 // Thread off a new instance of the file chooser - this reduces the time
486 // takes to open it later on.
487 new Thread(new Runnable() {
490 Cache.log.debug("Filechooser init thread started.");
491 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
492 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat);
493 Cache.log.debug("Filechooser init thread finished.");
496 // Add the service change listener
497 changeSupport.addJalviewPropertyChangeListener("services", new PropertyChangeListener() {
500 public void propertyChange(PropertyChangeEvent evt) {
501 Cache.log.debug("Firing service changed event for " + evt.getNewValue());
502 JalviewServicesChanged(evt);
507 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
509 this.addWindowListener(new WindowAdapter() {
511 public void windowClosing(WindowEvent evt) {
517 this.addMouseListener(ma = new MouseAdapter() {
519 public void mousePressed(MouseEvent evt) {
520 if (evt.isPopupTrigger()) // Mac
522 showPasteMenu(evt.getX(), evt.getY());
527 public void mouseReleased(MouseEvent evt) {
528 if (evt.isPopupTrigger()) // Windows
530 showPasteMenu(evt.getX(), evt.getY());
534 desktop.addMouseListener(ma);
538 * Answers true if user preferences to enable experimental features is True
543 public boolean showExperimental() {
544 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES, Boolean.FALSE.toString());
545 return Boolean.valueOf(experimental).booleanValue();
548 public void doConfigureStructurePrefs() {
549 // configure services
550 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
551 if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) {
552 ssm.setAddTempFacAnnot(Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
553 ssm.setProcessSecondaryStructure(Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
554 ssm.setSecStructServices(Cache.getDefault(Preferences.USE_RNAVIEW, true));
556 ssm.setAddTempFacAnnot(false);
557 ssm.setProcessSecondaryStructure(false);
558 ssm.setSecStructServices(false);
562 public void checkForNews() {
563 final Desktop me = this;
564 // Thread off the news reader, in case there are connection problems.
565 new Thread(new Runnable() {
568 Cache.log.debug("Starting news thread.");
569 jvnews = new BlogReader(me);
570 showNews.setVisible(true);
571 Cache.log.debug("Completed news thread.");
576 public void getIdentifiersOrgData() {
577 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
578 // Thread off the identifiers fetcher
579 new Thread(new Runnable() {
582 Cache.log.debug("Downloading data from identifiers.org");
584 UrlDownloadClient.download(IdOrgSettings.getUrl(), IdOrgSettings.getDownloadLocation());
585 } catch (IOException e) {
586 Cache.log.debug("Exception downloading identifiers.org data" + e.getMessage());
594 protected void showNews_actionPerformed(ActionEvent e) {
595 showNews(showNews.isSelected());
598 void showNews(boolean visible) {
599 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
600 showNews.setSelected(visible);
601 if (visible && !jvnews.isVisible()) {
602 new Thread(new Runnable() {
605 long now = System.currentTimeMillis();
606 Desktop.instance.setProgressBar(MessageManager.getString("status.refreshing_news"), now);
607 jvnews.refreshNews();
608 Desktop.instance.setProgressBar(null, now);
616 * recover the last known dimensions for a jalview window
618 * @param windowName - empty string is desktop, all other windows have unique
620 * @return null or last known dimensions scaled to current geometry (if last
621 * window geom was known)
623 Rectangle getLastKnownDimensions(String windowName) {
624 // TODO: lock aspect ratio for scaling desktop Bug #0058199
625 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
626 String x = Cache.getProperty(windowName + "SCREEN_X");
627 String y = Cache.getProperty(windowName + "SCREEN_Y");
628 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
629 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
630 if ((x != null) && (y != null) && (width != null) && (height != null)) {
631 int ix = Integer.parseInt(x), iy = Integer.parseInt(y), iw = Integer.parseInt(width),
632 ih = Integer.parseInt(height);
633 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) {
634 // attempt #1 - try to cope with change in screen geometry - this
635 // version doesn't preserve original jv aspect ratio.
636 // take ratio of current screen size vs original screen size.
637 double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
638 double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
639 // rescale the bounds depending upon the current screen geometry.
640 ix = (int) (ix * sw);
641 iw = (int) (iw * sw);
642 iy = (int) (iy * sh);
643 ih = (int) (ih * sh);
644 while (ix >= screenSize.width) {
645 Cache.log.debug("Window geometry location recall error: shifting horizontal to within screenbounds.");
646 ix -= screenSize.width;
648 while (iy >= screenSize.height) {
649 Cache.log.debug("Window geometry location recall error: shifting vertical to within screenbounds.");
650 iy -= screenSize.height;
652 Cache.log.debug("Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw
655 // return dimensions for new instance
656 return new Rectangle(ix, iy, iw, ih);
661 void showPasteMenu(int x, int y) {
662 JPopupMenu popup = new JPopupMenu();
663 JMenuItem item = new JMenuItem(MessageManager.getString("label.paste_new_window"));
664 item.addActionListener(new ActionListener() {
666 public void actionPerformed(ActionEvent evt) {
672 popup.show(this, x, y);
675 public void paste() {
677 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
678 Transferable contents = c.getContents(this);
680 if (contents != null) {
681 String file = (String) contents.getTransferData(DataFlavor.stringFlavor);
683 FileFormatI format = new IdentifyFile().identify(file, DataSourceType.PASTE);
685 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
688 } catch (Exception ex) {
689 System.out.println("Unable to paste alignment from system clipboard:\n" + ex);
694 * Adds and opens the given frame to the desktop
696 * @param frame Frame to show
697 * @param title Visible Title
701 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h) {
702 addInternalFrame(frame, title, true, w, h, true, false);
706 * Add an internal frame to the Jalview desktop
708 * @param frame Frame to show
709 * @param title Visible Title
710 * @param makeVisible When true, display frame immediately, otherwise, caller
711 * must call setVisible themselves.
715 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
717 addInternalFrame(frame, title, makeVisible, w, h, true, false);
721 * Add an internal frame to the Jalview desktop and make it visible
723 * @param frame Frame to show
724 * @param title Visible Title
727 * @param resizable Allow resize
729 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h,
731 addInternalFrame(frame, title, true, w, h, resizable, false);
735 * Add an internal frame to the Jalview desktop
737 * @param frame Frame to show
738 * @param title Visible Title
739 * @param makeVisible When true, display frame immediately, otherwise, caller
740 * must call setVisible themselves.
743 * @param resizable Allow resize
744 * @param ignoreMinSize Do not set the default minimum size for frame
746 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
747 int h, boolean resizable, boolean ignoreMinSize) {
749 // TODO: allow callers to determine X and Y position of frame (eg. via
751 // TODO: consider fixing method to update entries in the window submenu with
752 // the current window title
754 frame.setTitle(title);
755 if (frame.getWidth() < 1 || frame.getHeight() < 1) {
758 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
759 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
760 // IF JALVIEW IS RUNNING HEADLESS
761 // ///////////////////////////////////////////////
762 if (instance == null || (System.getProperty("java.awt.headless") != null
763 && System.getProperty("java.awt.headless").equals("true"))) {
769 if (!ignoreMinSize) {
770 frame.setMinimumSize(new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
772 // Set default dimension for Alignment Frame window.
773 // The Alignment Frame window could be added from a number of places,
775 // I did this here in order not to miss out on any Alignment frame.
776 if (frame instanceof AlignFrame) {
777 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH, ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
781 frame.setVisible(makeVisible);
782 frame.setClosable(true);
783 frame.setResizable(resizable);
784 frame.setMaximizable(resizable);
785 frame.setIconifiable(resizable);
786 frame.setOpaque(Platform.isJS());
788 if (frame.getX() < 1 && frame.getY() < 1) {
789 frame.setLocation(xOffset * openFrameCount, yOffset * ((openFrameCount - 1) % 10) + yOffset);
793 * add an entry for the new frame in the Window menu (and remove it when the
796 final JMenuItem menuItem = new JMenuItem(title);
797 frame.addInternalFrameListener(new InternalFrameAdapter() {
799 public void internalFrameActivated(InternalFrameEvent evt) {
800 JInternalFrame itf = desktop.getSelectedFrame();
802 if (itf instanceof AlignFrame) {
803 Jalview.setCurrentAlignFrame((AlignFrame) itf);
810 public void internalFrameClosed(InternalFrameEvent evt) {
811 PaintRefresher.RemoveComponent(frame);
814 * defensive check to prevent frames being added half off the window
816 if (openFrameCount > 0) {
821 * ensure no reference to alignFrame retained by menu item listener
823 if (menuItem.getActionListeners().length > 0) {
824 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
826 windowMenu.remove(menuItem);
830 menuItem.addActionListener(new ActionListener() {
832 public void actionPerformed(ActionEvent e) {
834 frame.setSelected(true);
835 frame.setIcon(false);
836 } catch (java.beans.PropertyVetoException ex) {
842 setKeyBindings(frame);
846 windowMenu.add(menuItem);
850 frame.setSelected(true);
851 frame.requestFocus();
852 } catch (java.beans.PropertyVetoException ve) {
853 } catch (java.lang.ClassCastException cex) {
855 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
861 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
866 private static void setKeyBindings(JInternalFrame frame) {
867 @SuppressWarnings("serial")
868 final Action closeAction = new AbstractAction() {
870 public void actionPerformed(ActionEvent e) {
876 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
878 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
879 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
881 InputMap inputMap = frame.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
882 String ctrlW = ctrlWKey.toString();
883 inputMap.put(ctrlWKey, ctrlW);
884 inputMap.put(cmdWKey, ctrlW);
886 ActionMap actionMap = frame.getActionMap();
887 actionMap.put(ctrlW, closeAction);
891 public void lostOwnership(Clipboard clipboard, Transferable contents) {
893 Desktop.jalviewClipboard = null;
896 internalCopy = false;
900 public void dragEnter(DropTargetDragEvent evt) {
904 public void dragExit(DropTargetEvent evt) {
908 public void dragOver(DropTargetDragEvent evt) {
912 public void dropActionChanged(DropTargetDragEvent evt) {
918 * @param evt DOCUMENT ME!
921 public void drop(DropTargetDropEvent evt) {
922 boolean success = true;
923 // JAL-1552 - acceptDrop required before getTransferable call for
924 // Java's Transferable for native dnd
925 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
926 Transferable t = evt.getTransferable();
927 List<Object> files = new ArrayList<>();
928 List<DataSourceType> protocols = new ArrayList<>();
931 Desktop.transferFromDropTarget(files, protocols, evt, t);
932 } catch (Exception e) {
939 for (int i = 0; i < files.size(); i++) {
940 // BH 2018 File or String
941 Object file = files.get(i);
942 String fileName = file.toString();
943 DataSourceType protocol = (protocols == null) ? DataSourceType.FILE : protocols.get(i);
944 FileFormatI format = null;
946 if (fileName.endsWith(".jar")) {
947 format = FileFormat.Jalview;
950 format = new IdentifyFile().identify(file, protocol);
952 if (file instanceof File) {
953 Platform.cacheFileData((File) file);
955 new FileLoader().LoadFile(null, file, protocol, format);
958 } catch (Exception ex) {
962 evt.dropComplete(success); // need this to ensure input focus is properly
963 // transfered to any new windows created
969 * @param e DOCUMENT ME!
972 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) {
973 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
974 JalviewFileChooser chooser = JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat,
975 BackupFiles.getEnabled());
977 chooser.setFileView(new JalviewFileView());
978 chooser.setDialogTitle(MessageManager.getString("label.open_local_file"));
979 chooser.setToolTipText(MessageManager.getString("action.open"));
981 chooser.setResponseHandler(0, new Runnable() {
984 File selectedFile = chooser.getSelectedFile();
985 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
987 FileFormatI format = chooser.getSelectedFormat();
990 * Call IdentifyFile to verify the file contains what its extension implies.
991 * Skip this step for dynamically added file formats, because IdentifyFile does
992 * not know how to recognise them.
994 if (FileFormats.getInstance().isIdentifiable(format)) {
996 format = new IdentifyFile().identify(selectedFile, DataSourceType.FILE);
997 } catch (FileFormatException e) {
998 // format = null; //??
1002 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, format);
1005 chooser.showOpenDialog(this);
1009 * Shows a dialog for input of a URL at which to retrieve alignment data
1014 public void inputURLMenuItem_actionPerformed(AlignViewport viewport) {
1015 // This construct allows us to have a wider textfield
1017 JLabel label = new JLabel(MessageManager.getString("label.input_file_url"));
1019 JPanel panel = new JPanel(new GridLayout(2, 1));
1023 * the URL to fetch is input in Java: an editable combobox with history JS:
1024 * (pending JAL-3038) a plain text field
1027 String urlBase = "https://www.";
1028 if (Platform.isJS()) {
1029 history = new JTextField(urlBase, 35);
1037 JComboBox<String> asCombo = new JComboBox<>();
1038 asCombo.setPreferredSize(new Dimension(400, 20));
1039 asCombo.setEditable(true);
1040 asCombo.addItem(urlBase);
1041 String historyItems = Cache.getProperty("RECENT_URL");
1042 if (historyItems != null) {
1043 for (String token : historyItems.split("\\t")) {
1044 asCombo.addItem(token);
1051 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1052 MessageManager.getString("action.cancel") };
1053 Runnable action = new Runnable() {
1056 @SuppressWarnings("unchecked")
1057 String url = (history instanceof JTextField ? ((JTextField) history).getText()
1058 : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
1060 if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) {
1061 if (viewport != null) {
1062 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
1064 new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
1067 FileFormatI format = null;
1069 format = new IdentifyFile().identify(url, DataSourceType.URL);
1070 } catch (FileFormatException e) {
1071 // TODO revise error handling, distinguish between
1072 // URL not found and response not valid
1075 if (format == null) {
1076 String msg = MessageManager.formatMessage("label.couldnt_locate", url);
1077 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1078 MessageManager.getString("label.url_not_found"), JvOptionPane.WARNING_MESSAGE);
1083 if (viewport != null) {
1084 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, format);
1086 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1091 String dialogOption = MessageManager.getString("label.input_alignment_from_url");
1092 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action).showInternalDialog(panel, dialogOption,
1093 JvOptionPane.YES_NO_CANCEL_OPTION, JvOptionPane.PLAIN_MESSAGE, null, options,
1094 MessageManager.getString("action.ok"));
1098 * Opens the CutAndPaste window for the user to paste an alignment in to
1100 * @param viewPanel - if not null, the pasted alignment is added to the current
1101 * alignment; if null, to a new alignment window
1104 public void inputTextboxMenuItem_actionPerformed(AlignmentViewPanel viewPanel) {
1105 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1106 cap.setForInput(viewPanel);
1107 Desktop.addInternalFrame(cap, MessageManager.getString("label.cut_paste_alignmen_file"), true, 600, 500);
1114 public void quit() {
1115 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1116 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1117 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1118 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, getWidth(), getHeight()));
1120 if (jconsole != null) {
1121 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1122 jconsole.stopConsole();
1124 if (jvnews != null) {
1125 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1128 if (dialogExecutor != null) {
1129 dialogExecutor.shutdownNow();
1131 closeAll_actionPerformed(null);
1133 if (groovyConsole != null) {
1134 // suppress a possible repeat prompt to save script
1135 groovyConsole.setDirty(false);
1136 groovyConsole.exit();
1141 private void storeLastKnownDimensions(String string, Rectangle jc) {
1142 Cache.log.debug("Storing last known dimensions for " + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1143 + " height:" + jc.height);
1145 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1146 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1147 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1148 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1154 * @param e DOCUMENT ME!
1157 public void aboutMenuItem_actionPerformed(ActionEvent e) {
1158 new Thread(new Runnable() {
1161 new SplashScreen(false);
1167 * Returns the html text for the About screen, including any available version
1168 * number, build details, author details and citation reference, but without the
1169 * enclosing {@code html} tags
1173 public String getAboutMessage() {
1174 StringBuilder message = new StringBuilder(1024);
1175 message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
1176 .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
1177 .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
1178 .append(Cache.getBuildDetailsForSplash()).append("</strong>");
1180 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1181 if (latestVersion.equals("Checking")) {
1182 // JBP removed this message for 2.11: May be reinstated in future version
1183 // message.append("<br>...Checking latest version...</br>");
1184 } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
1185 boolean red = false;
1186 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT).indexOf("automated build") == -1) {
1188 // Displayed when code version and jnlp version do not match and code
1189 // version is not a development build
1190 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1193 message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1194 .append(" is available for download from ")
1195 .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
1197 message.append("</div>");
1200 message.append("<br>Authors: ");
1201 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1202 message.append(CITATION);
1204 message.append("</div>");
1206 return message.toString();
1210 * Action on requesting Help documentation
1213 public void documentationMenuItem_actionPerformed() {
1215 if (Platform.isJS()) {
1216 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1224 Help.showHelpWindow();
1226 } catch (Exception ex) {
1227 System.err.println("Error opening help: " + ex.getMessage());
1232 public void closeAll_actionPerformed(ActionEvent e) {
1233 // TODO show a progress bar while closing?
1234 JInternalFrame[] frames = desktop.getAllFrames();
1235 for (int i = 0; i < frames.length; i++) {
1237 frames[i].setClosed(true);
1238 } catch (java.beans.PropertyVetoException ex) {
1241 Jalview.setCurrentAlignFrame(null);
1242 System.out.println("ALL CLOSED");
1245 * reset state of singleton objects as appropriate (clear down session state
1246 * when all windows are closed)
1248 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
1255 public void raiseRelated_actionPerformed(ActionEvent e) {
1256 reorderAssociatedWindows(false, false);
1260 public void minimizeAssociated_actionPerformed(ActionEvent e) {
1261 reorderAssociatedWindows(true, false);
1264 void closeAssociatedWindows() {
1265 reorderAssociatedWindows(false, true);
1271 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1275 protected void garbageCollect_actionPerformed(ActionEvent e) {
1276 // We simply collect the garbage
1277 Cache.log.debug("Collecting garbage...");
1279 Cache.log.debug("Finished garbage collection.");
1285 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1289 protected void showMemusage_actionPerformed(ActionEvent e) {
1290 desktop.showMemoryUsage(showMemusage.isSelected());
1297 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1301 protected void showConsole_actionPerformed(ActionEvent e) {
1302 showConsole(showConsole.isSelected());
1305 Console jconsole = null;
1308 * control whether the java console is visible or not
1312 void showConsole(boolean selected) {
1313 // TODO: decide if we should update properties file
1314 if (jconsole != null) // BH 2018
1316 showConsole.setSelected(selected);
1317 Cache.setProperty("SHOW_JAVA_CONSOLE", Boolean.valueOf(selected).toString());
1318 jconsole.setVisible(selected);
1322 void reorderAssociatedWindows(boolean minimize, boolean close) {
1323 JInternalFrame[] frames = desktop.getAllFrames();
1324 if (frames == null || frames.length < 1) {
1328 AlignmentViewport source = null, target = null;
1329 if (frames[0] instanceof AlignFrame) {
1330 source = ((AlignFrame) frames[0]).getCurrentView();
1331 } else if (frames[0] instanceof TreePanel) {
1332 source = ((TreePanel) frames[0]).getViewPort();
1333 } else if (frames[0] instanceof PCAPanel) {
1334 source = ((PCAPanel) frames[0]).av;
1335 } else if (frames[0].getContentPane() instanceof PairwiseAlignPanel) {
1336 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1339 if (source != null) {
1340 for (int i = 0; i < frames.length; i++) {
1342 if (frames[i] == null) {
1345 if (frames[i] instanceof AlignFrame) {
1346 target = ((AlignFrame) frames[i]).getCurrentView();
1347 } else if (frames[i] instanceof TreePanel) {
1348 target = ((TreePanel) frames[i]).getViewPort();
1349 } else if (frames[i] instanceof PCAPanel) {
1350 target = ((PCAPanel) frames[i]).av;
1351 } else if (frames[i].getContentPane() instanceof PairwiseAlignPanel) {
1352 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1355 if (source == target) {
1358 frames[i].setClosed(true);
1360 frames[i].setIcon(minimize);
1362 frames[i].toFront();
1366 } catch (java.beans.PropertyVetoException ex) {
1376 * @param e DOCUMENT ME!
1379 protected void preferences_actionPerformed(ActionEvent e) {
1380 Preferences.openPreferences();
1384 * Prompts the user to choose a file and then saves the Jalview state as a
1385 * Jalview project file
1388 public void saveState_actionPerformed() {
1389 saveState_actionPerformed(false);
1392 public void saveState_actionPerformed(boolean saveAs) {
1393 java.io.File projectFile = getProjectFile();
1394 // autoSave indicates we already have a file and don't need to ask
1395 boolean autoSave = projectFile != null && !saveAs && BackupFiles.getEnabled();
1397 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1398 // saveAs="+saveAs+", Backups
1399 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1401 boolean approveSave = false;
1403 JalviewFileChooser chooser = new JalviewFileChooser("jvp", "Jalview Project");
1405 chooser.setFileView(new JalviewFileView());
1406 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1408 int value = chooser.showSaveDialog(this);
1410 if (value == JalviewFileChooser.APPROVE_OPTION) {
1411 projectFile = chooser.getSelectedFile();
1412 setProjectFile(projectFile);
1417 if (approveSave || autoSave) {
1418 final Desktop me = this;
1419 final java.io.File chosenFile = projectFile;
1420 new Thread(new Runnable() {
1423 // TODO: refactor to Jalview desktop session controller action.
1425 MessageManager.formatMessage("label.saving_jalview_project", new Object[] { chosenFile.getName() }),
1426 chosenFile.hashCode());
1427 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1428 // TODO catch and handle errors for savestate
1429 // TODO prevent user from messing with the Desktop whilst we're saving
1431 boolean doBackup = BackupFiles.getEnabled();
1432 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
1434 new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
1437 backupfiles.setWriteSuccess(true);
1438 backupfiles.rollBackupsAndRenameTempFile();
1440 } catch (OutOfMemoryError oom) {
1441 new OOMWarning("Whilst saving current state to " + chosenFile.getName(), oom);
1442 } catch (Exception ex) {
1443 Cache.log.error("Problems whilst trying to save to " + chosenFile.getName(), ex);
1444 JvOptionPane.showMessageDialog(me,
1445 MessageManager.formatMessage("label.error_whilst_saving_current_state_to",
1446 new Object[] { chosenFile.getName() }),
1447 MessageManager.getString("label.couldnt_save_project"), JvOptionPane.WARNING_MESSAGE);
1449 setProgressBar(null, chosenFile.hashCode());
1456 public void saveAsState_actionPerformed(ActionEvent e) {
1457 saveState_actionPerformed(true);
1460 private void setProjectFile(File choice) {
1461 this.projectFile = choice;
1464 public File getProjectFile() {
1465 return this.projectFile;
1469 * Shows a file chooser dialog and tries to read in the selected file as a
1473 public void loadState_actionPerformed() {
1474 final String[] suffix = new String[] { "jvp", "jar" };
1475 final String[] desc = new String[] { "Jalview Project", "Jalview Project (old)" };
1476 JalviewFileChooser chooser = new JalviewFileChooser(Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1477 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1481 chooser.setFileView(new JalviewFileView());
1482 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1483 chooser.setResponseHandler(0, new Runnable() {
1486 File selectedFile = chooser.getSelectedFile();
1487 setProjectFile(selectedFile);
1488 String choice = selectedFile.getAbsolutePath();
1489 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1490 new Thread(new Runnable() {
1494 new Jalview2XML().loadJalviewAlign(selectedFile);
1495 } catch (OutOfMemoryError oom) {
1496 new OOMWarning("Whilst loading project from " + choice, oom);
1497 } catch (Exception ex) {
1498 Cache.log.error("Problems whilst loading project from " + choice, ex);
1499 JvOptionPane.showMessageDialog(Desktop.desktop,
1500 MessageManager.formatMessage("label.error_whilst_loading_project_from", new Object[] { choice }),
1501 MessageManager.getString("label.couldnt_load_project"), JvOptionPane.WARNING_MESSAGE);
1504 }, "Project Loader").start();
1508 chooser.showOpenDialog(this);
1512 public void inputSequence_actionPerformed(ActionEvent e) {
1513 new SequenceFetcher(this);
1516 JPanel progressPanel;
1518 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1520 public void startLoading(final Object fileName) {
1521 if (fileLoadingCount == 0) {
1523 .add(addProgressPanel(MessageManager.formatMessage("label.loading_file", new Object[] { fileName })));
1528 private JPanel addProgressPanel(String string) {
1529 if (progressPanel == null) {
1530 progressPanel = new JPanel(new GridLayout(1, 1));
1531 totalProgressCount = 0;
1532 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1534 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1535 JProgressBar progressBar = new JProgressBar();
1536 progressBar.setIndeterminate(true);
1538 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1540 thisprogress.add(progressBar, BorderLayout.CENTER);
1541 progressPanel.add(thisprogress);
1542 ((GridLayout) progressPanel.getLayout()).setRows(((GridLayout) progressPanel.getLayout()).getRows() + 1);
1543 ++totalProgressCount;
1544 instance.validate();
1545 return thisprogress;
1548 int totalProgressCount = 0;
1550 private void removeProgressPanel(JPanel progbar) {
1551 if (progressPanel != null) {
1552 synchronized (progressPanel) {
1553 progressPanel.remove(progbar);
1554 GridLayout gl = (GridLayout) progressPanel.getLayout();
1555 gl.setRows(gl.getRows() - 1);
1556 if (--totalProgressCount < 1) {
1557 this.getContentPane().remove(progressPanel);
1558 progressPanel = null;
1565 public void stopLoading() {
1567 if (fileLoadingCount < 1) {
1568 while (fileLoadingPanels.size() > 0) {
1569 removeProgressPanel(fileLoadingPanels.remove(0));
1571 fileLoadingPanels.clear();
1572 fileLoadingCount = 0;
1577 public static int getViewCount(String alignmentId) {
1578 AlignmentViewport[] aps = getViewports(alignmentId);
1579 return (aps == null) ? 0 : aps.length;
1584 * @param alignmentId - if null, all sets are returned
1585 * @return all AlignmentPanels concerning the alignmentId sequence set
1587 public static AlignmentPanel[] getAlignmentPanels(String alignmentId) {
1588 if (Desktop.desktop == null) {
1589 // no frames created and in headless mode
1590 // TODO: verify that frames are recoverable when in headless mode
1593 List<AlignmentPanel> aps = new ArrayList<>();
1594 AlignFrame[] frames = getAlignFrames();
1595 if (frames == null) {
1598 for (AlignFrame af : frames) {
1599 for (AlignmentPanel ap : af.alignPanels) {
1600 if (alignmentId == null || alignmentId.equals(ap.av.getSequenceSetId())) {
1605 if (aps.size() == 0) {
1608 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1613 * get all the viewports on an alignment.
1615 * @param sequenceSetId unique alignment id (may be null - all viewports
1616 * returned in that case)
1617 * @return all viewports on the alignment bound to sequenceSetId
1619 public static AlignmentViewport[] getViewports(String sequenceSetId) {
1620 List<AlignmentViewport> viewp = new ArrayList<>();
1621 if (desktop != null) {
1622 AlignFrame[] frames = Desktop.getAlignFrames();
1624 for (AlignFrame afr : frames) {
1625 if (sequenceSetId == null || afr.getViewport().getSequenceSetId().equals(sequenceSetId)) {
1626 if (afr.alignPanels != null) {
1627 for (AlignmentPanel ap : afr.alignPanels) {
1628 if (sequenceSetId == null || sequenceSetId.equals(ap.av.getSequenceSetId())) {
1633 viewp.add(afr.getViewport());
1637 if (viewp.size() > 0) {
1638 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1645 * Explode the views in the given frame into separate AlignFrame
1649 public static void explodeViews(AlignFrame af) {
1650 int size = af.alignPanels.size();
1655 // FIXME: ideally should use UI interface API
1656 FeatureSettings viewFeatureSettings = (af.featureSettings != null && af.featureSettings.isOpen())
1657 ? af.featureSettings
1659 Rectangle fsBounds = af.getFeatureSettingsGeometry();
1660 for (int i = 0; i < size; i++) {
1661 AlignmentPanel ap = af.alignPanels.get(i);
1663 AlignFrame newaf = new AlignFrame(ap);
1665 // transfer reference for existing feature settings to new alignFrame
1666 if (ap == af.alignPanel) {
1667 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap) {
1668 newaf.featureSettings = viewFeatureSettings;
1670 newaf.setFeatureSettingsGeometry(fsBounds);
1674 * Restore the view's last exploded frame geometry if known. Multiple views from
1675 * one exploded frame share and restore the same (frame) position and size.
1677 Rectangle geometry = ap.av.getExplodedGeometry();
1678 if (geometry != null) {
1679 newaf.setBounds(geometry);
1682 ap.av.setGatherViewsHere(false);
1684 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1685 // and materialise a new feature settings dialog instance for the new
1687 // (closes the old as if 'OK' was pressed)
1688 if (ap == af.alignPanel && newaf.featureSettings != null && newaf.featureSettings.isOpen()
1689 && af.alignPanel.getAlignViewport().isShowSequenceFeatures()) {
1690 newaf.showFeatureSettingsUI();
1694 af.featureSettings = null;
1695 af.alignPanels.clear();
1696 af.closeMenuItem_actionPerformed(true);
1701 * Gather expanded views (separate AlignFrame's) with the same sequence set
1702 * identifier back in to this frame as additional views, and close the expanded
1703 * views. Note the expanded frames may themselves have multiple views. We take
1708 public void gatherViews(AlignFrame source) {
1709 source.viewport.setGatherViewsHere(true);
1710 source.viewport.setExplodedGeometry(source.getBounds());
1711 JInternalFrame[] frames = desktop.getAllFrames();
1712 String viewId = source.viewport.getSequenceSetId();
1713 for (int t = 0; t < frames.length; t++) {
1714 if (frames[t] instanceof AlignFrame && frames[t] != source) {
1715 AlignFrame af = (AlignFrame) frames[t];
1716 boolean gatherThis = false;
1717 for (int a = 0; a < af.alignPanels.size(); a++) {
1718 AlignmentPanel ap = af.alignPanels.get(a);
1719 if (viewId.equals(ap.av.getSequenceSetId())) {
1721 ap.av.setGatherViewsHere(false);
1722 ap.av.setExplodedGeometry(af.getBounds());
1723 source.addAlignmentPanel(ap, false);
1728 if (af.featureSettings != null && af.featureSettings.isOpen()) {
1729 if (source.featureSettings == null) {
1730 // preserve the feature settings geometry for this frame
1731 source.featureSettings = af.featureSettings;
1732 source.setFeatureSettingsGeometry(af.getFeatureSettingsGeometry());
1734 // close it and forget
1735 af.featureSettings.close();
1738 af.alignPanels.clear();
1739 af.closeMenuItem_actionPerformed(true);
1744 // refresh the feature setting UI for the source frame if it exists
1745 if (source.featureSettings != null && source.featureSettings.isOpen()) {
1746 source.showFeatureSettingsUI();
1751 public JInternalFrame[] getAllFrames() {
1752 return desktop.getAllFrames();
1756 * Checks the given url to see if it gives a response indicating that the user
1757 * should be informed of a new questionnaire.
1761 public void checkForQuestionnaire(String url) {
1762 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
1763 // javax.swing.SwingUtilities.invokeLater(jvq);
1764 new Thread(jvq).start();
1767 public void checkURLLinks() {
1768 // Thread off the URL link checker
1769 addDialogThread(new Runnable() {
1772 if (Cache.getDefault("CHECKURLLINKS", true)) {
1773 // check what the actual links are - if it's just the default don't
1774 // bother with the warning
1775 List<String> links = Preferences.sequenceUrlLinks.getLinksForMenu();
1777 // only need to check links if there is one with a
1778 // SEQUENCE_ID which is not the default EMBL_EBI link
1779 ListIterator<String> li = links.listIterator();
1780 boolean check = false;
1781 List<JLabel> urls = new ArrayList<>();
1782 while (li.hasNext()) {
1783 String link = li.next();
1784 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID) && !UrlConstants.isDefaultString(link)) {
1786 int barPos = link.indexOf("|");
1787 String urlMsg = barPos == -1 ? link : link.substring(0, barPos) + ": " + link.substring(barPos + 1);
1788 urls.add(new JLabel(urlMsg));
1795 // ask user to check in case URL links use old style tokens
1796 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
1797 JPanel msgPanel = new JPanel();
1798 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
1799 msgPanel.add(Box.createVerticalGlue());
1800 JLabel msg = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
1801 JLabel msg2 = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
1803 for (JLabel url : urls) {
1808 final JCheckBox jcb = new JCheckBox(MessageManager.getString("label.do_not_display_again"));
1809 jcb.addActionListener(new ActionListener() {
1811 public void actionPerformed(ActionEvent e) {
1812 // update Cache settings for "don't show this again"
1813 boolean showWarningAgain = !jcb.isSelected();
1814 Cache.setProperty("CHECKURLLINKS", Boolean.valueOf(showWarningAgain).toString());
1819 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
1820 MessageManager.getString("label.SEQUENCE_ID_no_longer_used"), JvOptionPane.WARNING_MESSAGE);
1827 * Proxy class for JDesktopPane which optionally displays the current memory
1828 * usage and highlights the desktop area with a red bar if free memory runs low.
1832 public class MyDesktopPane extends JDesktopPane implements Runnable {
1833 private static final float ONE_MB = 1048576f;
1835 boolean showMemoryUsage = false;
1839 java.text.NumberFormat df;
1841 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory, percentUsage;
1843 public MyDesktopPane(boolean showMemoryUsage) {
1844 showMemoryUsage(showMemoryUsage);
1847 public void showMemoryUsage(boolean showMemory) {
1848 this.showMemoryUsage = showMemory;
1850 Thread worker = new Thread(this);
1856 public boolean isShowMemoryUsage() {
1857 return showMemoryUsage;
1862 df = java.text.NumberFormat.getNumberInstance();
1863 df.setMaximumFractionDigits(2);
1864 runtime = Runtime.getRuntime();
1866 while (showMemoryUsage) {
1868 maxMemory = runtime.maxMemory() / ONE_MB;
1869 allocatedMemory = runtime.totalMemory() / ONE_MB;
1870 freeMemory = runtime.freeMemory() / ONE_MB;
1871 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
1873 percentUsage = (totalFreeMemory / maxMemory) * 100;
1875 // if (percentUsage < 20)
1877 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
1879 // instance.set.setBorder(border1);
1882 // sleep after showing usage
1884 } catch (Exception ex) {
1885 ex.printStackTrace();
1891 public void paintComponent(Graphics g) {
1892 if (showMemoryUsage && g != null && df != null) {
1893 if (percentUsage < 20) {
1894 g.setColor(Color.red);
1896 FontMetrics fm = g.getFontMetrics();
1899 MessageManager.formatMessage("label.memory_stats",
1900 new Object[] { df.format(totalFreeMemory), df.format(maxMemory), df.format(percentUsage) }),
1901 10, getHeight() - fm.getHeight());
1905 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
1906 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
1911 * Accessor method to quickly get all the AlignmentFrames loaded.
1913 * @return an array of AlignFrame, or null if none found
1915 public static AlignFrame[] getAlignFrames() {
1916 if (Jalview.isHeadlessMode()) {
1917 // Desktop.desktop is null in headless mode
1918 return new AlignFrame[] { Jalview.currentAlignFrame };
1921 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1923 if (frames == null) {
1926 List<AlignFrame> avp = new ArrayList<>();
1928 for (int i = frames.length - 1; i > -1; i--) {
1929 if (frames[i] instanceof AlignFrame) {
1930 avp.add((AlignFrame) frames[i]);
1931 } else if (frames[i] instanceof SplitFrame) {
1933 * Also check for a split frame containing an AlignFrame
1935 GSplitFrame sf = (GSplitFrame) frames[i];
1936 if (sf.getTopFrame() instanceof AlignFrame) {
1937 avp.add((AlignFrame) sf.getTopFrame());
1939 if (sf.getBottomFrame() instanceof AlignFrame) {
1940 avp.add((AlignFrame) sf.getBottomFrame());
1944 if (avp.size() == 0) {
1947 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
1952 * Returns an array of any AppJmol frames in the Desktop (or null if none).
1956 public GStructureViewer[] getJmols() {
1957 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1959 if (frames == null) {
1962 List<GStructureViewer> avp = new ArrayList<>();
1964 for (int i = frames.length - 1; i > -1; i--) {
1965 if (frames[i] instanceof AppJmol) {
1966 GStructureViewer af = (GStructureViewer) frames[i];
1970 if (avp.size() == 0) {
1973 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
1978 * Add Groovy Support to Jalview
1981 public void groovyShell_actionPerformed() {
1983 openGroovyConsole();
1984 } catch (Exception ex) {
1985 Cache.log.error("Groovy Shell Creation failed.", ex);
1986 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
1988 MessageManager.getString("label.couldnt_create_groovy_shell"),
1989 MessageManager.getString("label.groovy_support_failed"), JvOptionPane.ERROR_MESSAGE);
1994 * Open the Groovy console
1996 void openGroovyConsole() {
1997 if (groovyConsole == null) {
1998 groovyConsole = new groovy.ui.Console();
1999 groovyConsole.setVariable("Jalview", this);
2000 groovyConsole.run();
2003 * We allow only one console at a time, so that AlignFrame menu option
2004 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2005 * enable 'Run script', when the console is opened, and the reverse when it is
2008 Window window = (Window) groovyConsole.getFrame();
2009 window.addWindowListener(new WindowAdapter() {
2011 public void windowClosed(WindowEvent e) {
2013 * rebind CMD-Q from Groovy Console to Jalview Quit
2016 enableExecuteGroovy(false);
2022 * show Groovy console window (after close and reopen)
2024 ((Window) groovyConsole.getFrame()).setVisible(true);
2027 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2028 * opening a second console
2030 enableExecuteGroovy(true);
2034 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
2037 protected void addQuitHandler() {
2038 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2039 KeyStroke.getKeyStroke(KeyEvent.VK_Q, jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
2041 getRootPane().getActionMap().put("Quit", new AbstractAction() {
2043 public void actionPerformed(ActionEvent e) {
2050 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2052 * @param enabled true if Groovy console is open
2054 public void enableExecuteGroovy(boolean enabled) {
2056 * disable opening a second Groovy console (or re-enable when the console is
2059 groovyShell.setEnabled(!enabled);
2061 AlignFrame[] alignFrames = getAlignFrames();
2062 if (alignFrames != null) {
2063 for (AlignFrame af : alignFrames) {
2064 af.setGroovyEnabled(enabled);
2070 * Progress bars managed by the IProgressIndicator method.
2072 private Hashtable<Long, JPanel> progressBars;
2074 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2079 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2082 public void setProgressBar(String message, long id) {
2083 if (progressBars == null) {
2084 progressBars = new Hashtable<>();
2085 progressBarHandlers = new Hashtable<>();
2088 if (progressBars.get(Long.valueOf(id)) != null) {
2089 JPanel panel = progressBars.remove(Long.valueOf(id));
2090 if (progressBarHandlers.contains(Long.valueOf(id))) {
2091 progressBarHandlers.remove(Long.valueOf(id));
2093 removeProgressPanel(panel);
2095 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2102 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2103 * jalview.gui.IProgressIndicatorHandler)
2106 public void registerHandler(final long id, final IProgressIndicatorHandler handler) {
2107 if (progressBarHandlers == null || !progressBars.containsKey(Long.valueOf(id))) {
2108 throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
2110 progressBarHandlers.put(Long.valueOf(id), handler);
2111 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2112 if (handler.canCancel()) {
2113 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
2114 final IProgressIndicator us = this;
2115 cancel.addActionListener(new ActionListener() {
2118 public void actionPerformed(ActionEvent e) {
2119 handler.cancelActivity(id);
2120 us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
2121 new Object[] { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
2124 progressPanel.add(cancel, BorderLayout.EAST);
2130 * @return true if any progress bars are still active
2133 public boolean operationInProgress() {
2134 if (progressBars != null && progressBars.size() > 0) {
2141 * This will return the first AlignFrame holding the given viewport instance. It
2142 * will break if there are more than one AlignFrames viewing a particular av.
2145 * @return alignFrame for viewport
2147 public static AlignFrame getAlignFrameFor(AlignViewportI viewport) {
2148 if (desktop != null) {
2149 AlignmentPanel[] aps = getAlignmentPanels(viewport.getSequenceSetId());
2150 for (int panel = 0; aps != null && panel < aps.length; panel++) {
2151 if (aps[panel] != null && aps[panel].av == viewport) {
2152 return aps[panel].alignFrame;
2159 public VamsasApplication getVamsasApplication() {
2160 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2166 * flag set if jalview GUI is being operated programmatically
2168 private boolean inBatchMode = false;
2171 * check if jalview GUI is being operated programmatically
2173 * @return inBatchMode
2175 public boolean isInBatchMode() {
2180 * set flag if jalview GUI is being operated programmatically
2182 * @param inBatchMode
2184 public void setInBatchMode(boolean inBatchMode) {
2185 this.inBatchMode = inBatchMode;
2189 * start service discovery and wait till it is done
2191 public void startServiceDiscovery() {
2192 startServiceDiscovery(false);
2196 * start service discovery threads - blocking or non-blocking
2200 public void startServiceDiscovery(boolean blocking) {
2201 startServiceDiscovery(blocking, false);
2205 * start service discovery threads
2207 * @param blocking - false means call returns
2209 * @param ignore_SHOW_JWS2_SERVICES_preference - when true JABA services are
2210 * discovered regardless of user's
2211 * JWS2 discovery preference setting
2213 public void startServiceDiscovery(boolean blocking, boolean ignore_SHOW_JWS2_SERVICES_preference) {
2214 boolean alive = true;
2215 Thread t0 = null, t1 = null, t2 = null;
2216 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2218 // todo: changesupport handlers need to be transferred
2219 if (discoverer == null) {
2220 discoverer = new jalview.ws.jws1.Discoverer();
2221 // register PCS handler for desktop.
2222 discoverer.addPropertyChangeListener(changeSupport);
2224 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2225 // until we phase out completely
2226 (t0 = new Thread(discoverer)).start();
2229 if (ignore_SHOW_JWS2_SERVICES_preference || Cache.getDefault("SHOW_JWS2_SERVICES", true)) {
2230 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(changeSupport);
2234 // TODO: do rest service discovery
2240 } catch (Exception e) {
2242 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive()) || (t3 != null && t3.isAlive())
2243 || (t0 != null && t0.isAlive());
2249 * called to check if the service discovery process completed successfully.
2253 protected void JalviewServicesChanged(PropertyChangeEvent evt) {
2254 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector) {
2255 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().getErrorMessages();
2256 if (ermsg != null) {
2257 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true)) {
2258 if (serviceChangedDialog == null) {
2259 // only run if we aren't already displaying one of these.
2260 addDialogThread(serviceChangedDialog = new Runnable() {
2265 * JalviewDialog jd =new JalviewDialog() {
2267 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2269 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2271 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2273 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2275 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2276 * + " or mis-configured HTTP proxy settings.<br/>" +
2277 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2278 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2279 * true, true, "Web Service Configuration Problem", 450, 400);
2281 * jd.waitForInput();
2283 JvOptionPane.showConfirmDialog(Desktop.desktop,
2284 new JLabel("<html><table width=\"450\"><tr><td>" + ermsg + "</td></tr></table>"
2285 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2286 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2287 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2288 + " Tools->Preferences dialog box to change them.</p></html>"),
2289 "Web Service Configuration Problem", JvOptionPane.DEFAULT_OPTION, JvOptionPane.ERROR_MESSAGE);
2290 serviceChangedDialog = null;
2296 Cache.log.error("Errors reported by JABA discovery service. Check web services preferences.\n" + ermsg);
2302 private Runnable serviceChangedDialog = null;
2305 * start a thread to open a URL in the configured browser. Pops up a warning
2306 * dialog to the user if there is an exception when calling out to the browser
2311 public static void showUrl(final String url) {
2312 showUrl(url, Desktop.instance);
2316 * Like showUrl but allows progress handler to be specified
2319 * @param progress (null) or object implementing IProgressIndicator
2321 public static void showUrl(final String url, final IProgressIndicator progress) {
2322 new Thread(new Runnable() {
2326 if (progress != null) {
2327 progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[] { url }),
2330 jalview.util.BrowserLauncher.openURL(url);
2331 } catch (Exception ex) {
2332 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2333 MessageManager.getString("label.web_browser_not_found_unix"),
2334 MessageManager.getString("label.web_browser_not_found"), JvOptionPane.WARNING_MESSAGE);
2336 ex.printStackTrace();
2338 if (progress != null) {
2339 progress.setProgressBar(null, this.hashCode());
2345 public static WsParamSetManager wsparamManager = null;
2347 public static ParamManager getUserParameterStore() {
2348 if (wsparamManager == null) {
2349 wsparamManager = new WsParamSetManager();
2351 return wsparamManager;
2355 * static hyperlink handler proxy method for use by Jalview's internal windows
2359 public static void hyperlinkUpdate(HyperlinkEvent e) {
2360 if (e.getEventType() == EventType.ACTIVATED) {
2363 url = e.getURL().toString();
2364 Desktop.showUrl(url);
2365 } catch (Exception x) {
2367 if (Cache.log != null) {
2368 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2370 System.err.println("Couldn't handle string " + url + " as a URL.");
2373 // ignore any exceptions due to dud links.
2380 * single thread that handles display of dialogs to user.
2382 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2385 * flag indicating if dialogExecutor should try to acquire a permit
2387 private volatile boolean dialogPause = true;
2392 private java.util.concurrent.Semaphore block = new Semaphore(0);
2394 private static groovy.ui.Console groovyConsole;
2397 * add another dialog thread to the queue
2401 public void addDialogThread(final Runnable prompter) {
2402 dialogExecutor.submit(new Runnable() {
2408 } catch (InterruptedException x) {
2411 if (instance == null) {
2415 SwingUtilities.invokeAndWait(prompter);
2416 } catch (Exception q) {
2417 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2423 public void startDialogQueue() {
2424 // set the flag so we don't pause waiting for another permit and semaphore
2425 // the current task to begin
2426 dialogPause = false;
2431 * Outputs an image of the desktop to file in EPS format, after prompting the
2432 * user for choice of Text or Lineart character rendering (unless a preference
2433 * has been set). The file name is generated as
2436 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2440 protected void snapShotWindow_actionPerformed(ActionEvent e) {
2441 // currently the menu option to do this is not shown
2444 int width = getWidth();
2445 int height = getHeight();
2446 File of = new File("Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2447 ImageWriterI writer = new ImageWriterI() {
2449 public void exportImage(Graphics g) throws Exception {
2451 Cache.log.info("Successfully written snapshot to file " + of.getAbsolutePath());
2454 String title = "View of desktop";
2455 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS, title);
2456 exporter.doExport(of, this, width, height, title);
2460 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2461 * This respects (remembers) any previous 'exploded geometry' i.e. the size and
2462 * location last time the view was expanded (if any). However it does not
2463 * remember the split pane divider location - this is set to match the
2464 * 'exploding' frame.
2468 public void explodeViews(SplitFrame sf) {
2469 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2470 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2471 List<? extends AlignmentViewPanel> topPanels = oldTopFrame.getAlignPanels();
2472 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame.getAlignPanels();
2473 int viewCount = topPanels.size();
2474 if (viewCount < 2) {
2479 * Processing in reverse order works, forwards order leaves the first panels not
2480 * visible. I don't know why!
2482 for (int i = viewCount - 1; i >= 0; i--) {
2484 * Make new top and bottom frames. These take over the respective AlignmentPanel
2485 * objects, including their AlignmentViewports, so the cdna/protein
2486 * relationships between the viewports is carried over to the new split frames.
2488 * explodedGeometry holds the (x, y) position of the previously exploded
2489 * SplitFrame, and the (width, height) of the AlignFrame component
2491 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
2492 AlignFrame newTopFrame = new AlignFrame(topPanel);
2493 newTopFrame.setSize(oldTopFrame.getSize());
2494 newTopFrame.setVisible(true);
2495 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport()).getExplodedGeometry();
2496 if (geometry != null) {
2497 newTopFrame.setSize(geometry.getSize());
2500 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
2501 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
2502 newBottomFrame.setSize(oldBottomFrame.getSize());
2503 newBottomFrame.setVisible(true);
2504 geometry = ((AlignViewport) bottomPanel.getAlignViewport()).getExplodedGeometry();
2505 if (geometry != null) {
2506 newBottomFrame.setSize(geometry.getSize());
2509 topPanel.av.setGatherViewsHere(false);
2510 bottomPanel.av.setGatherViewsHere(false);
2511 JInternalFrame splitFrame = new SplitFrame(newTopFrame, newBottomFrame);
2512 if (geometry != null) {
2513 splitFrame.setLocation(geometry.getLocation());
2515 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
2519 * Clear references to the panels (now relocated in the new SplitFrames) before
2520 * closing the old SplitFrame.
2523 bottomPanels.clear();
2528 * Gather expanded split frames, sharing the same pairs of sequence set ids,
2529 * back into the given SplitFrame as additional views. Note that the gathered
2530 * frames may themselves have multiple views.
2534 public void gatherViews(GSplitFrame source) {
2536 * special handling of explodedGeometry for a view within a SplitFrame: - it
2537 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
2538 * height) of the AlignFrame component
2540 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
2541 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
2542 myTopFrame.viewport.setExplodedGeometry(
2543 new Rectangle(source.getX(), source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
2544 myBottomFrame.viewport.setExplodedGeometry(
2545 new Rectangle(source.getX(), source.getY(), myBottomFrame.getWidth(), myBottomFrame.getHeight()));
2546 myTopFrame.viewport.setGatherViewsHere(true);
2547 myBottomFrame.viewport.setGatherViewsHere(true);
2548 String topViewId = myTopFrame.viewport.getSequenceSetId();
2549 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
2551 JInternalFrame[] frames = desktop.getAllFrames();
2552 for (JInternalFrame frame : frames) {
2553 if (frame instanceof SplitFrame && frame != source) {
2554 SplitFrame sf = (SplitFrame) frame;
2555 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
2556 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
2557 boolean gatherThis = false;
2558 for (int a = 0; a < topFrame.alignPanels.size(); a++) {
2559 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
2560 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
2561 if (topViewId.equals(topPanel.av.getSequenceSetId())
2562 && bottomViewId.equals(bottomPanel.av.getSequenceSetId())) {
2564 topPanel.av.setGatherViewsHere(false);
2565 bottomPanel.av.setGatherViewsHere(false);
2566 topPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), topFrame.getSize()));
2567 bottomPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), bottomFrame.getSize()));
2568 myTopFrame.addAlignmentPanel(topPanel, false);
2569 myBottomFrame.addAlignmentPanel(bottomPanel, false);
2574 topFrame.getAlignPanels().clear();
2575 bottomFrame.getAlignPanels().clear();
2582 * The dust settles...give focus to the tab we did this from.
2584 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
2587 public static groovy.ui.Console getGroovyConsole() {
2588 return groovyConsole;
2592 * handles the payload of a drag and drop event.
2594 * TODO refactor to desktop utilities class
2596 * @param files - Data source strings extracted from the drop event
2597 * @param protocols - protocol for each data source extracted from the drop
2599 * @param evt - the drop event
2600 * @param t - the payload from the drop event
2603 public static void transferFromDropTarget(List<Object> files, List<DataSourceType> protocols, DropTargetDropEvent evt,
2604 Transferable t) throws Exception {
2606 DataFlavor uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"), urlFlavour = null;
2608 urlFlavour = new DataFlavor("application/x-java-url; class=java.net.URL");
2609 } catch (ClassNotFoundException cfe) {
2610 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
2613 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) {
2616 java.net.URL url = (URL) t.getTransferData(urlFlavour);
2617 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
2618 // means url may be null.
2620 protocols.add(DataSourceType.URL);
2621 files.add(url.toString());
2622 Cache.log.debug("Drop handled as URL dataflavor " + files.get(files.size() - 1));
2625 if (Platform.isAMacAndNotJS()) {
2626 System.err.println("Please ignore plist error - occurs due to problem with java 8 on OSX");
2629 } catch (Throwable ex) {
2630 Cache.log.debug("URL drop handler failed.", ex);
2633 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
2634 // Works on Windows and MacOSX
2635 Cache.log.debug("Drop handled as javaFileListFlavor");
2636 for (Object file : (List) t.getTransferData(DataFlavor.javaFileListFlavor)) {
2638 protocols.add(DataSourceType.FILE);
2641 // Unix like behaviour
2642 boolean added = false;
2644 if (t.isDataFlavorSupported(uriListFlavor)) {
2645 Cache.log.debug("Drop handled as uriListFlavor");
2646 // This is used by Unix drag system
2647 data = (String) t.getTransferData(uriListFlavor);
2650 // fallback to text: workaround - on OSX where there's a JVM bug
2651 Cache.log.debug("standard URIListFlavor failed. Trying text");
2652 // try text fallback
2653 DataFlavor textDf = new DataFlavor("text/plain;class=java.lang.String");
2654 if (t.isDataFlavorSupported(textDf)) {
2655 data = (String) t.getTransferData(textDf);
2658 Cache.log.debug("Plain text drop content returned " + (data == null ? "Null - failed" : data));
2662 while (protocols.size() < files.size()) {
2663 Cache.log.debug("Adding missing FILE protocol for " + files.get(protocols.size()));
2664 protocols.add(DataSourceType.FILE);
2666 for (java.util.StringTokenizer st = new java.util.StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
2668 String s = st.nextToken();
2669 if (s.startsWith("#")) {
2670 // the line is a comment (as per the RFC 2483)
2673 java.net.URI uri = new java.net.URI(s);
2674 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http")) {
2675 protocols.add(DataSourceType.URL);
2676 files.add(uri.toString());
2678 // otherwise preserve old behaviour: catch all for file objects
2679 java.io.File file = new java.io.File(uri);
2680 protocols.add(DataSourceType.FILE);
2681 files.add(file.toString());
2686 if (Cache.log.isDebugEnabled()) {
2687 if (data == null || !added) {
2689 if (t.getTransferDataFlavors() != null && t.getTransferDataFlavors().length > 0) {
2690 Cache.log.debug("Couldn't resolve drop data. Here are the supported flavors:");
2691 for (DataFlavor fl : t.getTransferDataFlavors()) {
2692 Cache.log.debug("Supported transfer dataflavor: " + fl.toString());
2693 Object df = t.getTransferData(fl);
2695 Cache.log.debug("Retrieves: " + df);
2697 Cache.log.debug("Retrieved nothing");
2701 Cache.log.debug("Couldn't resolve dataflavor for drop: " + t.toString());
2706 if (Platform.isWindowsAndNotJS()) {
2707 Cache.log.debug("Scanning dropped content for Windows Link Files");
2709 // resolve any .lnk files in the file drop
2710 for (int f = 0; f < files.size(); f++) {
2711 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
2712 if (protocols.get(f).equals(DataSourceType.FILE)
2713 && (source.endsWith(".lnk") || source.endsWith(".url") || source.endsWith(".site"))) {
2715 Object obj = files.get(f);
2716 File lf = (obj instanceof File ? (File) obj : new File((String) obj));
2717 // process link file to get a URL
2718 Cache.log.debug("Found potential link file: " + lf);
2719 WindowsShortcut wscfile = new WindowsShortcut(lf);
2720 String fullname = wscfile.getRealFilename();
2721 protocols.set(f, FormatAdapter.checkProtocol(fullname));
2722 files.set(f, fullname);
2723 Cache.log.debug("Parsed real filename " + fullname + " to extract protocol: " + protocols.get(f));
2724 } catch (Exception ex) {
2725 Cache.log.error("Couldn't parse " + files.get(f) + " as a link file.", ex);
2733 * Sets the Preferences property for experimental features to True or False
2734 * depending on the state of the controlling menu item
2737 protected void showExperimental_actionPerformed(boolean selected) {
2738 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
2742 * Answers a (possibly empty) list of any structure viewer frames (currently for
2743 * either Jmol or Chimera) which are currently open. This may optionally be
2744 * restricted to viewers of a specified class, or viewers linked to a specified
2747 * @param apanel if not null, only return viewers linked to this
2749 * @param structureViewerClass if not null, only return viewers of this class
2752 public List<StructureViewerBase> getStructureViewers(AlignmentPanel apanel,
2753 Class<? extends StructureViewerBase> structureViewerClass) {
2754 List<StructureViewerBase> result = new ArrayList<>();
2755 JInternalFrame[] frames = Desktop.instance.getAllFrames();
2757 for (JInternalFrame frame : frames) {
2758 if (frame instanceof StructureViewerBase) {
2759 if (structureViewerClass == null || structureViewerClass.isInstance(frame)) {
2760 if (apanel == null || ((StructureViewerBase) frame).isLinkedWith(apanel)) {
2761 result.add((StructureViewerBase) frame);
2769 public static final String debugScaleMessage = "Desktop graphics transform scale=";
2771 private static boolean debugScaleMessageDone = false;
2773 public static void debugScaleMessage(Graphics g) {
2774 if (debugScaleMessageDone) {
2777 // output used by tests to check HiDPI scaling settings in action
2779 Graphics2D gg = (Graphics2D) g;
2781 AffineTransform t = gg.getTransform();
2782 double scaleX = t.getScaleX();
2783 double scaleY = t.getScaleY();
2784 Cache.debug(debugScaleMessage + scaleX + " (X)");
2785 Cache.debug(debugScaleMessage + scaleY + " (Y)");
2786 debugScaleMessageDone = true;
2788 Cache.debug("Desktop graphics null");
2790 } catch (Exception e) {
2791 Cache.debug(Cache.getStackTraceString(e));