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.LaunchUtils;
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,
142 jalview.api.StructureSelectionManagerProvider
144 private static final String CITATION;
147 URL bg_logo_url = ChannelProperties.getImageURL(
148 "bg_logo." + String.valueOf(SplashScreen.logoSize));
149 URL uod_logo_url = ChannelProperties.getImageURL(
150 "uod_banner." + String.valueOf(SplashScreen.logoSize));
151 boolean logo = (bg_logo_url != null || uod_logo_url != null);
152 StringBuilder sb = new StringBuilder();
154 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
159 sb.append(bg_logo_url == null ? ""
160 : "<img alt=\"Barton Group logo\" src=\""
161 + bg_logo_url.toString() + "\">");
162 sb.append(uod_logo_url == null ? ""
163 : " <img alt=\"University of Dundee shield\" src=\""
164 + uod_logo_url.toString() + "\">");
166 "<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>");
167 sb.append("<br><br>If you use Jalview, please cite:"
168 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
169 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
170 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
171 CITATION = sb.toString();
174 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
176 private static int DEFAULT_MIN_WIDTH = 300;
178 private static int DEFAULT_MIN_HEIGHT = 250;
180 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
182 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
184 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
186 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
188 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
190 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
192 public static void setLiveDragMode(boolean b)
194 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
195 : JDesktopPane.OUTLINE_DRAG_MODE;
197 desktop.setDragMode(DRAG_MODE);
200 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
202 public static boolean nosplash = false;
205 * news reader - null if it was never started.
207 private BlogReader jvnews = null;
209 private File projectFile;
213 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
215 public void addJalviewPropertyChangeListener(
216 PropertyChangeListener listener)
218 changeSupport.addJalviewPropertyChangeListener(listener);
222 * @param propertyName
224 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
225 * java.beans.PropertyChangeListener)
227 public void addJalviewPropertyChangeListener(String propertyName,
228 PropertyChangeListener listener)
230 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
234 * @param propertyName
236 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
237 * java.beans.PropertyChangeListener)
239 public void removeJalviewPropertyChangeListener(String propertyName,
240 PropertyChangeListener listener)
242 changeSupport.removeJalviewPropertyChangeListener(propertyName,
246 /** Singleton Desktop instance */
247 public static Desktop instance;
249 public static MyDesktopPane desktop;
251 public static MyDesktopPane getDesktop()
253 // BH 2018 could use currentThread() here as a reference to a
254 // Hashtable<Thread, MyDesktopPane> in JavaScript
258 static int openFrameCount = 0;
260 static final int xOffset = 30;
262 static final int yOffset = 30;
264 public static jalview.ws.jws1.Discoverer discoverer;
266 public static Object[] jalviewClipboard;
268 public static boolean internalCopy = false;
270 static int fileLoadingCount = 0;
272 class MyDesktopManager implements DesktopManager
275 private DesktopManager delegate;
277 public MyDesktopManager(DesktopManager delegate)
279 this.delegate = delegate;
283 public void activateFrame(JInternalFrame f)
287 delegate.activateFrame(f);
288 } catch (NullPointerException npe)
290 Point p = getMousePosition();
291 instance.showPasteMenu(p.x, p.y);
296 public void beginDraggingFrame(JComponent f)
298 delegate.beginDraggingFrame(f);
302 public void beginResizingFrame(JComponent f, int direction)
304 delegate.beginResizingFrame(f, direction);
308 public void closeFrame(JInternalFrame f)
310 delegate.closeFrame(f);
314 public void deactivateFrame(JInternalFrame f)
316 delegate.deactivateFrame(f);
320 public void deiconifyFrame(JInternalFrame f)
322 delegate.deiconifyFrame(f);
326 public void dragFrame(JComponent f, int newX, int newY)
332 delegate.dragFrame(f, newX, newY);
336 public void endDraggingFrame(JComponent f)
338 delegate.endDraggingFrame(f);
343 public void endResizingFrame(JComponent f)
345 delegate.endResizingFrame(f);
350 public void iconifyFrame(JInternalFrame f)
352 delegate.iconifyFrame(f);
356 public void maximizeFrame(JInternalFrame f)
358 delegate.maximizeFrame(f);
362 public void minimizeFrame(JInternalFrame f)
364 delegate.minimizeFrame(f);
368 public void openFrame(JInternalFrame f)
370 delegate.openFrame(f);
374 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
381 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
385 public void setBoundsForFrame(JComponent f, int newX, int newY,
386 int newWidth, int newHeight)
388 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
391 // All other methods, simply delegate
396 * Creates a new Desktop object.
402 * A note to implementors. It is ESSENTIAL that any activities that might
403 * block are spawned off as threads rather than waited for during this
408 doConfigureStructurePrefs();
409 setTitle(ChannelProperties.getProperty("app_name") + " "
410 + Cache.getProperty("VERSION"));
413 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
414 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
415 * officially documented or guaranteed to exist, so we access it via
416 * reflection. There appear to be unfathomable criteria about what this
417 * string can contain, and it if doesn't meet those criteria then "java"
418 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
419 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
420 * not. The reflection access may generate a warning: WARNING: An illegal
421 * reflective access operation has occurred WARNING: Illegal reflective
422 * access by jalview.gui.Desktop () to field
423 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
425 if (Platform.isLinux())
427 if (LaunchUtils.getJavaVersion() >= 11)
429 jalview.bin.Console.info(
430 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
434 Toolkit xToolkit = Toolkit.getDefaultToolkit();
435 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
436 Field awtAppClassNameField = null;
438 if (Arrays.stream(declaredFields)
439 .anyMatch(f -> f.getName().equals("awtAppClassName")))
441 awtAppClassNameField = xToolkit.getClass()
442 .getDeclaredField("awtAppClassName");
445 String title = ChannelProperties.getProperty("app_name");
446 if (awtAppClassNameField != null)
448 awtAppClassNameField.setAccessible(true);
449 awtAppClassNameField.set(xToolkit, title);
453 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
455 } catch (Exception e)
457 jalview.bin.Console.debug("Error setting awtAppClassName");
458 jalview.bin.Console.trace(Cache.getStackTraceString(e));
462 setIconImages(ChannelProperties.getIconList());
464 addWindowListener(new WindowAdapter()
468 public void windowClosing(WindowEvent ev)
474 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
476 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
477 desktop = new MyDesktopPane(selmemusage);
479 showMemusage.setSelected(selmemusage);
480 desktop.setBackground(Color.white);
482 getContentPane().setLayout(new BorderLayout());
483 // alternate config - have scrollbars - see notes in JAL-153
484 // JScrollPane sp = new JScrollPane();
485 // sp.getViewport().setView(desktop);
486 // getContentPane().add(sp, BorderLayout.CENTER);
488 // BH 2018 - just an experiment to try unclipped JInternalFrames.
491 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
494 getContentPane().add(desktop, BorderLayout.CENTER);
495 desktop.setDragMode(DRAG_MODE);
497 // This line prevents Windows Look&Feel resizing all new windows to maximum
498 // if previous window was maximised
499 desktop.setDesktopManager(new MyDesktopManager(
500 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
501 : Platform.isAMacAndNotJS()
502 ? new AquaInternalFrameManager(
503 desktop.getDesktopManager())
504 : desktop.getDesktopManager())));
506 Rectangle dims = getLastKnownDimensions("");
513 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
514 int xPos = Math.max(5, (screenSize.width - 900) / 2);
515 int yPos = Math.max(5, (screenSize.height - 650) / 2);
516 setBounds(xPos, yPos, 900, 650);
519 if (!Platform.isJS())
526 jconsole = new Console(this, showjconsole);
527 jconsole.setHeader(Cache.getVersionDetailsForConsole());
528 showConsole(showjconsole);
530 showNews.setVisible(false);
532 experimentalFeatures.setSelected(showExperimental());
534 getIdentifiersOrgData();
538 // Spawn a thread that shows the splashscreen
541 SwingUtilities.invokeLater(new Runnable()
546 new SplashScreen(true);
551 // Thread off a new instance of the file chooser - this reduces the time
553 // takes to open it later on.
554 new Thread(new Runnable()
559 jalview.bin.Console.debug("Filechooser init thread started.");
560 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
561 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
563 jalview.bin.Console.debug("Filechooser init thread finished.");
566 // Add the service change listener
567 changeSupport.addJalviewPropertyChangeListener("services",
568 new PropertyChangeListener()
572 public void propertyChange(PropertyChangeEvent evt)
575 .debug("Firing service changed event for "
576 + evt.getNewValue());
577 JalviewServicesChanged(evt);
582 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
584 this.addWindowListener(new WindowAdapter()
587 public void windowClosing(WindowEvent evt)
594 this.addMouseListener(ma = new MouseAdapter()
597 public void mousePressed(MouseEvent evt)
599 if (evt.isPopupTrigger()) // Mac
601 showPasteMenu(evt.getX(), evt.getY());
606 public void mouseReleased(MouseEvent evt)
608 if (evt.isPopupTrigger()) // Windows
610 showPasteMenu(evt.getX(), evt.getY());
614 desktop.addMouseListener(ma);
618 * Answers true if user preferences to enable experimental features is True
623 public boolean showExperimental()
625 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
626 Boolean.FALSE.toString());
627 return Boolean.valueOf(experimental).booleanValue();
630 public void doConfigureStructurePrefs()
632 // configure services
633 StructureSelectionManager ssm = StructureSelectionManager
634 .getStructureSelectionManager(this);
635 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
637 ssm.setAddTempFacAnnot(
638 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
639 ssm.setProcessSecondaryStructure(
640 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
641 // JAL-3915 - RNAView is no longer an option so this has no effect
642 ssm.setSecStructServices(
643 Cache.getDefault(Preferences.USE_RNAVIEW, false));
647 ssm.setAddTempFacAnnot(false);
648 ssm.setProcessSecondaryStructure(false);
649 ssm.setSecStructServices(false);
653 public void checkForNews()
655 final Desktop me = this;
656 // Thread off the news reader, in case there are connection problems.
657 new Thread(new Runnable()
662 jalview.bin.Console.debug("Starting news thread.");
663 jvnews = new BlogReader(me);
664 showNews.setVisible(true);
665 jalview.bin.Console.debug("Completed news thread.");
670 public void getIdentifiersOrgData()
672 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
673 {// Thread off the identifiers fetcher
674 new Thread(new Runnable()
680 .debug("Downloading data from identifiers.org");
683 UrlDownloadClient.download(IdOrgSettings.getUrl(),
684 IdOrgSettings.getDownloadLocation());
685 } catch (IOException e)
688 .debug("Exception downloading identifiers.org data"
698 protected void showNews_actionPerformed(ActionEvent e)
700 showNews(showNews.isSelected());
703 void showNews(boolean visible)
705 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
706 showNews.setSelected(visible);
707 if (visible && !jvnews.isVisible())
709 new Thread(new Runnable()
714 long now = System.currentTimeMillis();
715 Desktop.instance.setProgressBar(
716 MessageManager.getString("status.refreshing_news"), now);
717 jvnews.refreshNews();
718 Desktop.instance.setProgressBar(null, now);
726 * recover the last known dimensions for a jalview window
729 * - empty string is desktop, all other windows have unique prefix
730 * @return null or last known dimensions scaled to current geometry (if last
731 * window geom was known)
733 Rectangle getLastKnownDimensions(String windowName)
735 // TODO: lock aspect ratio for scaling desktop Bug #0058199
736 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
737 String x = Cache.getProperty(windowName + "SCREEN_X");
738 String y = Cache.getProperty(windowName + "SCREEN_Y");
739 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
740 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
741 if ((x != null) && (y != null) && (width != null) && (height != null))
743 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
744 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
745 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
747 // attempt #1 - try to cope with change in screen geometry - this
748 // version doesn't preserve original jv aspect ratio.
749 // take ratio of current screen size vs original screen size.
750 double sw = ((1f * screenSize.width) / (1f * Integer
751 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
752 double sh = ((1f * screenSize.height) / (1f * Integer
753 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
754 // rescale the bounds depending upon the current screen geometry.
755 ix = (int) (ix * sw);
756 iw = (int) (iw * sw);
757 iy = (int) (iy * sh);
758 ih = (int) (ih * sh);
759 while (ix >= screenSize.width)
761 jalview.bin.Console.debug(
762 "Window geometry location recall error: shifting horizontal to within screenbounds.");
763 ix -= screenSize.width;
765 while (iy >= screenSize.height)
767 jalview.bin.Console.debug(
768 "Window geometry location recall error: shifting vertical to within screenbounds.");
769 iy -= screenSize.height;
771 jalview.bin.Console.debug(
772 "Got last known dimensions for " + windowName + ": x:" + ix
773 + " y:" + iy + " width:" + iw + " height:" + ih);
775 // return dimensions for new instance
776 return new Rectangle(ix, iy, iw, ih);
781 void showPasteMenu(int x, int y)
783 JPopupMenu popup = new JPopupMenu();
784 JMenuItem item = new JMenuItem(
785 MessageManager.getString("label.paste_new_window"));
786 item.addActionListener(new ActionListener()
789 public void actionPerformed(ActionEvent evt)
796 popup.show(this, x, y);
803 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
804 Transferable contents = c.getContents(this);
806 if (contents != null)
808 String file = (String) contents
809 .getTransferData(DataFlavor.stringFlavor);
811 FileFormatI format = new IdentifyFile().identify(file,
812 DataSourceType.PASTE);
814 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
817 } catch (Exception ex)
820 "Unable to paste alignment from system clipboard:\n" + ex);
825 * Adds and opens the given frame to the desktop
836 public static synchronized void addInternalFrame(
837 final JInternalFrame frame, String title, int w, int h)
839 addInternalFrame(frame, title, true, w, h, true, false);
843 * Add an internal frame to the Jalview desktop
850 * When true, display frame immediately, otherwise, caller must call
851 * setVisible themselves.
857 public static synchronized void addInternalFrame(
858 final JInternalFrame frame, String title, boolean makeVisible,
861 addInternalFrame(frame, title, makeVisible, w, h, true, false);
865 * Add an internal frame to the Jalview desktop and make it visible
878 public static synchronized void addInternalFrame(
879 final JInternalFrame frame, String title, int w, int h,
882 addInternalFrame(frame, title, true, w, h, resizable, false);
886 * Add an internal frame to the Jalview desktop
893 * When true, display frame immediately, otherwise, caller must call
894 * setVisible themselves.
901 * @param ignoreMinSize
902 * Do not set the default minimum size for frame
904 public static synchronized void addInternalFrame(
905 final JInternalFrame frame, String title, boolean makeVisible,
906 int w, int h, boolean resizable, boolean ignoreMinSize)
909 // TODO: allow callers to determine X and Y position of frame (eg. via
911 // TODO: consider fixing method to update entries in the window submenu with
912 // the current window title
914 frame.setTitle(title);
915 if (frame.getWidth() < 1 || frame.getHeight() < 1)
919 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
920 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
921 // IF JALVIEW IS RUNNING HEADLESS
922 // ///////////////////////////////////////////////
923 if (instance == null || (System.getProperty("java.awt.headless") != null
924 && System.getProperty("java.awt.headless").equals("true")))
933 frame.setMinimumSize(
934 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
936 // Set default dimension for Alignment Frame window.
937 // The Alignment Frame window could be added from a number of places,
939 // I did this here in order not to miss out on any Alignment frame.
940 if (frame instanceof AlignFrame)
942 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
943 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
947 frame.setVisible(makeVisible);
948 frame.setClosable(true);
949 frame.setResizable(resizable);
950 frame.setMaximizable(resizable);
951 frame.setIconifiable(resizable);
952 frame.setOpaque(Platform.isJS());
954 if (frame.getX() < 1 && frame.getY() < 1)
956 frame.setLocation(xOffset * openFrameCount,
957 yOffset * ((openFrameCount - 1) % 10) + yOffset);
961 * add an entry for the new frame in the Window menu (and remove it when the
964 final JMenuItem menuItem = new JMenuItem(title);
965 frame.addInternalFrameListener(new InternalFrameAdapter()
968 public void internalFrameActivated(InternalFrameEvent evt)
970 JInternalFrame itf = desktop.getSelectedFrame();
973 if (itf instanceof AlignFrame)
975 Jalview.setCurrentAlignFrame((AlignFrame) itf);
982 public void internalFrameClosed(InternalFrameEvent evt)
984 PaintRefresher.RemoveComponent(frame);
987 * defensive check to prevent frames being added half off the window
989 if (openFrameCount > 0)
995 * ensure no reference to alignFrame retained by menu item listener
997 if (menuItem.getActionListeners().length > 0)
999 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1001 windowMenu.remove(menuItem);
1005 menuItem.addActionListener(new ActionListener()
1008 public void actionPerformed(ActionEvent e)
1012 frame.setSelected(true);
1013 frame.setIcon(false);
1014 } catch (java.beans.PropertyVetoException ex)
1021 setKeyBindings(frame);
1025 windowMenu.add(menuItem);
1030 frame.setSelected(true);
1031 frame.requestFocus();
1032 } catch (java.beans.PropertyVetoException ve)
1034 } catch (java.lang.ClassCastException cex)
1036 jalview.bin.Console.warn(
1037 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1043 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1048 private static void setKeyBindings(JInternalFrame frame)
1050 @SuppressWarnings("serial")
1051 final Action closeAction = new AbstractAction()
1054 public void actionPerformed(ActionEvent e)
1061 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1063 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1064 InputEvent.CTRL_DOWN_MASK);
1065 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1066 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1068 InputMap inputMap = frame
1069 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1070 String ctrlW = ctrlWKey.toString();
1071 inputMap.put(ctrlWKey, ctrlW);
1072 inputMap.put(cmdWKey, ctrlW);
1074 ActionMap actionMap = frame.getActionMap();
1075 actionMap.put(ctrlW, closeAction);
1079 public void lostOwnership(Clipboard clipboard, Transferable contents)
1083 Desktop.jalviewClipboard = null;
1086 internalCopy = false;
1090 public void dragEnter(DropTargetDragEvent evt)
1095 public void dragExit(DropTargetEvent evt)
1100 public void dragOver(DropTargetDragEvent evt)
1105 public void dropActionChanged(DropTargetDragEvent evt)
1116 public void drop(DropTargetDropEvent evt)
1118 boolean success = true;
1119 // JAL-1552 - acceptDrop required before getTransferable call for
1120 // Java's Transferable for native dnd
1121 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1122 Transferable t = evt.getTransferable();
1123 List<Object> files = new ArrayList<>();
1124 List<DataSourceType> protocols = new ArrayList<>();
1128 Desktop.transferFromDropTarget(files, protocols, evt, t);
1129 } catch (Exception e)
1131 e.printStackTrace();
1139 for (int i = 0; i < files.size(); i++)
1141 // BH 2018 File or String
1142 Object file = files.get(i);
1143 String fileName = file.toString();
1144 DataSourceType protocol = (protocols == null)
1145 ? DataSourceType.FILE
1147 FileFormatI format = null;
1149 if (fileName.endsWith(".jar"))
1151 format = FileFormat.Jalview;
1156 format = new IdentifyFile().identify(file, protocol);
1158 if (file instanceof File)
1160 Platform.cacheFileData((File) file);
1162 new FileLoader().LoadFile(null, file, protocol, format);
1165 } catch (Exception ex)
1170 evt.dropComplete(success); // need this to ensure input focus is properly
1171 // transfered to any new windows created
1181 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1183 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1184 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1185 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1186 BackupFiles.getEnabled());
1188 chooser.setFileView(new JalviewFileView());
1189 chooser.setDialogTitle(
1190 MessageManager.getString("label.open_local_file"));
1191 chooser.setToolTipText(MessageManager.getString("action.open"));
1193 chooser.setResponseHandler(0, new Runnable()
1198 File selectedFile = chooser.getSelectedFile();
1199 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1201 FileFormatI format = chooser.getSelectedFormat();
1204 * Call IdentifyFile to verify the file contains what its extension implies.
1205 * Skip this step for dynamically added file formats, because IdentifyFile does
1206 * not know how to recognise them.
1208 if (FileFormats.getInstance().isIdentifiable(format))
1212 format = new IdentifyFile().identify(selectedFile,
1213 DataSourceType.FILE);
1214 } catch (FileFormatException e)
1216 // format = null; //??
1220 new FileLoader().LoadFile(viewport, selectedFile,
1221 DataSourceType.FILE, format);
1224 chooser.showOpenDialog(this);
1228 * Shows a dialog for input of a URL at which to retrieve alignment data
1233 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1235 // This construct allows us to have a wider textfield
1237 JLabel label = new JLabel(
1238 MessageManager.getString("label.input_file_url"));
1240 JPanel panel = new JPanel(new GridLayout(2, 1));
1244 * the URL to fetch is input in Java: an editable combobox with history JS:
1245 * (pending JAL-3038) a plain text field
1248 String urlBase = "https://www.";
1249 if (Platform.isJS())
1251 history = new JTextField(urlBase, 35);
1260 JComboBox<String> asCombo = new JComboBox<>();
1261 asCombo.setPreferredSize(new Dimension(400, 20));
1262 asCombo.setEditable(true);
1263 asCombo.addItem(urlBase);
1264 String historyItems = Cache.getProperty("RECENT_URL");
1265 if (historyItems != null)
1267 for (String token : historyItems.split("\\t"))
1269 asCombo.addItem(token);
1276 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1277 MessageManager.getString("action.cancel") };
1278 Runnable action = new Runnable()
1283 @SuppressWarnings("unchecked")
1284 String url = (history instanceof JTextField
1285 ? ((JTextField) history).getText()
1286 : ((JComboBox<String>) history).getEditor().getItem()
1287 .toString().trim());
1289 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1291 if (viewport != null)
1293 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1294 FileFormat.Jalview);
1298 new FileLoader().LoadFile(url, DataSourceType.URL,
1299 FileFormat.Jalview);
1304 FileFormatI format = null;
1307 format = new IdentifyFile().identify(url, DataSourceType.URL);
1308 } catch (FileFormatException e)
1310 // TODO revise error handling, distinguish between
1311 // URL not found and response not valid
1316 String msg = MessageManager
1317 .formatMessage("label.couldnt_locate", url);
1318 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1319 MessageManager.getString("label.url_not_found"),
1320 JvOptionPane.WARNING_MESSAGE);
1325 if (viewport != null)
1327 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1332 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1337 String dialogOption = MessageManager
1338 .getString("label.input_alignment_from_url");
1339 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1340 .showInternalDialog(panel, dialogOption,
1341 JvOptionPane.YES_NO_CANCEL_OPTION,
1342 JvOptionPane.PLAIN_MESSAGE, null, options,
1343 MessageManager.getString("action.ok"));
1347 * Opens the CutAndPaste window for the user to paste an alignment in to
1350 * - if not null, the pasted alignment is added to the current
1351 * alignment; if null, to a new alignment window
1354 public void inputTextboxMenuItem_actionPerformed(
1355 AlignmentViewPanel viewPanel)
1357 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1358 cap.setForInput(viewPanel);
1359 Desktop.addInternalFrame(cap,
1360 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1370 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1371 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1372 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1373 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1374 getWidth(), getHeight()));
1376 if (jconsole != null)
1378 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1379 jconsole.stopConsole();
1383 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1386 if (dialogExecutor != null)
1388 dialogExecutor.shutdownNow();
1390 closeAll_actionPerformed(null);
1392 if (groovyConsole != null)
1394 // suppress a possible repeat prompt to save script
1395 groovyConsole.setDirty(false);
1396 groovyConsole.exit();
1401 private void storeLastKnownDimensions(String string, Rectangle jc)
1403 jalview.bin.Console.debug("Storing last known dimensions for " + string
1404 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1405 + " height:" + jc.height);
1407 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1408 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1409 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1410 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1420 public void aboutMenuItem_actionPerformed(ActionEvent e)
1422 new Thread(new Runnable()
1427 new SplashScreen(false);
1433 * Returns the html text for the About screen, including any available version
1434 * number, build details, author details and citation reference, but without
1435 * the enclosing {@code html} tags
1439 public String getAboutMessage()
1441 StringBuilder message = new StringBuilder(1024);
1442 message.append("<div style=\"font-family: sans-serif;\">")
1443 .append("<h1><strong>Version: ")
1444 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1445 .append("<strong>Built: <em>")
1446 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1447 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1448 .append("</strong>");
1450 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1451 if (latestVersion.equals("Checking"))
1453 // JBP removed this message for 2.11: May be reinstated in future version
1454 // message.append("<br>...Checking latest version...</br>");
1456 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1458 boolean red = false;
1459 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1460 .indexOf("automated build") == -1)
1463 // Displayed when code version and jnlp version do not match and code
1464 // version is not a development build
1465 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1468 message.append("<br>!! Version ")
1469 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1470 .append(" is available for download from ")
1471 .append(Cache.getDefault("www.jalview.org",
1472 "https://www.jalview.org"))
1476 message.append("</div>");
1479 message.append("<br>Authors: ");
1480 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1481 message.append(CITATION);
1483 message.append("</div>");
1485 return message.toString();
1489 * Action on requesting Help documentation
1492 public void documentationMenuItem_actionPerformed()
1496 if (Platform.isJS())
1498 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1507 Help.showHelpWindow();
1509 } catch (Exception ex)
1511 System.err.println("Error opening help: " + ex.getMessage());
1516 public void closeAll_actionPerformed(ActionEvent e)
1518 // TODO show a progress bar while closing?
1519 JInternalFrame[] frames = desktop.getAllFrames();
1520 for (int i = 0; i < frames.length; i++)
1524 frames[i].setClosed(true);
1525 } catch (java.beans.PropertyVetoException ex)
1529 Jalview.setCurrentAlignFrame(null);
1530 System.out.println("ALL CLOSED");
1533 * reset state of singleton objects as appropriate (clear down session state
1534 * when all windows are closed)
1536 StructureSelectionManager ssm = StructureSelectionManager
1537 .getStructureSelectionManager(this);
1545 public void raiseRelated_actionPerformed(ActionEvent e)
1547 reorderAssociatedWindows(false, false);
1551 public void minimizeAssociated_actionPerformed(ActionEvent e)
1553 reorderAssociatedWindows(true, false);
1556 void closeAssociatedWindows()
1558 reorderAssociatedWindows(false, true);
1564 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1568 protected void garbageCollect_actionPerformed(ActionEvent e)
1570 // We simply collect the garbage
1571 jalview.bin.Console.debug("Collecting garbage...");
1573 jalview.bin.Console.debug("Finished garbage collection.");
1579 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1583 protected void showMemusage_actionPerformed(ActionEvent e)
1585 desktop.showMemoryUsage(showMemusage.isSelected());
1592 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1596 protected void showConsole_actionPerformed(ActionEvent e)
1598 showConsole(showConsole.isSelected());
1601 Console jconsole = null;
1604 * control whether the java console is visible or not
1608 void showConsole(boolean selected)
1610 // TODO: decide if we should update properties file
1611 if (jconsole != null) // BH 2018
1613 showConsole.setSelected(selected);
1614 Cache.setProperty("SHOW_JAVA_CONSOLE",
1615 Boolean.valueOf(selected).toString());
1616 jconsole.setVisible(selected);
1620 void reorderAssociatedWindows(boolean minimize, boolean close)
1622 JInternalFrame[] frames = desktop.getAllFrames();
1623 if (frames == null || frames.length < 1)
1628 AlignmentViewport source = null, target = null;
1629 if (frames[0] instanceof AlignFrame)
1631 source = ((AlignFrame) frames[0]).getCurrentView();
1633 else if (frames[0] instanceof TreePanel)
1635 source = ((TreePanel) frames[0]).getViewPort();
1637 else if (frames[0] instanceof PCAPanel)
1639 source = ((PCAPanel) frames[0]).av;
1641 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1643 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1648 for (int i = 0; i < frames.length; i++)
1651 if (frames[i] == null)
1655 if (frames[i] instanceof AlignFrame)
1657 target = ((AlignFrame) frames[i]).getCurrentView();
1659 else if (frames[i] instanceof TreePanel)
1661 target = ((TreePanel) frames[i]).getViewPort();
1663 else if (frames[i] instanceof PCAPanel)
1665 target = ((PCAPanel) frames[i]).av;
1667 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1669 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1672 if (source == target)
1678 frames[i].setClosed(true);
1682 frames[i].setIcon(minimize);
1685 frames[i].toFront();
1689 } catch (java.beans.PropertyVetoException ex)
1704 protected void preferences_actionPerformed(ActionEvent e)
1706 Preferences.openPreferences();
1710 * Prompts the user to choose a file and then saves the Jalview state as a
1711 * Jalview project file
1714 public void saveState_actionPerformed()
1716 saveState_actionPerformed(false);
1719 public void saveState_actionPerformed(boolean saveAs)
1721 java.io.File projectFile = getProjectFile();
1722 // autoSave indicates we already have a file and don't need to ask
1723 boolean autoSave = projectFile != null && !saveAs
1724 && BackupFiles.getEnabled();
1726 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1727 // saveAs="+saveAs+", Backups
1728 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1730 boolean approveSave = false;
1733 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1736 chooser.setFileView(new JalviewFileView());
1737 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1739 int value = chooser.showSaveDialog(this);
1741 if (value == JalviewFileChooser.APPROVE_OPTION)
1743 projectFile = chooser.getSelectedFile();
1744 setProjectFile(projectFile);
1749 if (approveSave || autoSave)
1751 final Desktop me = this;
1752 final java.io.File chosenFile = projectFile;
1753 new Thread(new Runnable()
1758 // TODO: refactor to Jalview desktop session controller action.
1759 setProgressBar(MessageManager.formatMessage(
1760 "label.saving_jalview_project", new Object[]
1761 { chosenFile.getName() }), chosenFile.hashCode());
1762 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1763 // TODO catch and handle errors for savestate
1764 // TODO prevent user from messing with the Desktop whilst we're saving
1767 boolean doBackup = BackupFiles.getEnabled();
1768 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1771 new Jalview2XML().saveState(
1772 doBackup ? backupfiles.getTempFile() : chosenFile);
1776 backupfiles.setWriteSuccess(true);
1777 backupfiles.rollBackupsAndRenameTempFile();
1779 } catch (OutOfMemoryError oom)
1781 new OOMWarning("Whilst saving current state to "
1782 + chosenFile.getName(), oom);
1783 } catch (Exception ex)
1785 jalview.bin.Console.error("Problems whilst trying to save to "
1786 + chosenFile.getName(), ex);
1787 JvOptionPane.showMessageDialog(me,
1788 MessageManager.formatMessage(
1789 "label.error_whilst_saving_current_state_to",
1791 { chosenFile.getName() }),
1792 MessageManager.getString("label.couldnt_save_project"),
1793 JvOptionPane.WARNING_MESSAGE);
1795 setProgressBar(null, chosenFile.hashCode());
1802 public void saveAsState_actionPerformed(ActionEvent e)
1804 saveState_actionPerformed(true);
1807 private void setProjectFile(File choice)
1809 this.projectFile = choice;
1812 public File getProjectFile()
1814 return this.projectFile;
1818 * Shows a file chooser dialog and tries to read in the selected file as a
1822 public void loadState_actionPerformed()
1824 final String[] suffix = new String[] { "jvp", "jar" };
1825 final String[] desc = new String[] { "Jalview Project",
1826 "Jalview Project (old)" };
1827 JalviewFileChooser chooser = new JalviewFileChooser(
1828 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1829 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1833 chooser.setFileView(new JalviewFileView());
1834 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1835 chooser.setResponseHandler(0, new Runnable()
1840 File selectedFile = chooser.getSelectedFile();
1841 setProjectFile(selectedFile);
1842 String choice = selectedFile.getAbsolutePath();
1843 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1844 new Thread(new Runnable()
1851 new Jalview2XML().loadJalviewAlign(selectedFile);
1852 } catch (OutOfMemoryError oom)
1854 new OOMWarning("Whilst loading project from " + choice, oom);
1855 } catch (Exception ex)
1857 jalview.bin.Console.error(
1858 "Problems whilst loading project from " + choice, ex);
1859 JvOptionPane.showMessageDialog(Desktop.desktop,
1860 MessageManager.formatMessage(
1861 "label.error_whilst_loading_project_from",
1865 .getString("label.couldnt_load_project"),
1866 JvOptionPane.WARNING_MESSAGE);
1869 }, "Project Loader").start();
1873 chooser.showOpenDialog(this);
1877 public void inputSequence_actionPerformed(ActionEvent e)
1879 new SequenceFetcher(this);
1882 JPanel progressPanel;
1884 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1886 public void startLoading(final Object fileName)
1888 if (fileLoadingCount == 0)
1890 fileLoadingPanels.add(addProgressPanel(MessageManager
1891 .formatMessage("label.loading_file", new Object[]
1897 private JPanel addProgressPanel(String string)
1899 if (progressPanel == null)
1901 progressPanel = new JPanel(new GridLayout(1, 1));
1902 totalProgressCount = 0;
1903 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1905 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1906 JProgressBar progressBar = new JProgressBar();
1907 progressBar.setIndeterminate(true);
1909 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1911 thisprogress.add(progressBar, BorderLayout.CENTER);
1912 progressPanel.add(thisprogress);
1913 ((GridLayout) progressPanel.getLayout()).setRows(
1914 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1915 ++totalProgressCount;
1916 instance.validate();
1917 return thisprogress;
1920 int totalProgressCount = 0;
1922 private void removeProgressPanel(JPanel progbar)
1924 if (progressPanel != null)
1926 synchronized (progressPanel)
1928 progressPanel.remove(progbar);
1929 GridLayout gl = (GridLayout) progressPanel.getLayout();
1930 gl.setRows(gl.getRows() - 1);
1931 if (--totalProgressCount < 1)
1933 this.getContentPane().remove(progressPanel);
1934 progressPanel = null;
1941 public void stopLoading()
1944 if (fileLoadingCount < 1)
1946 while (fileLoadingPanels.size() > 0)
1948 removeProgressPanel(fileLoadingPanels.remove(0));
1950 fileLoadingPanels.clear();
1951 fileLoadingCount = 0;
1956 public static int getViewCount(String alignmentId)
1958 AlignmentViewport[] aps = getViewports(alignmentId);
1959 return (aps == null) ? 0 : aps.length;
1964 * @param alignmentId
1965 * - if null, all sets are returned
1966 * @return all AlignmentPanels concerning the alignmentId sequence set
1968 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1970 if (Desktop.desktop == null)
1972 // no frames created and in headless mode
1973 // TODO: verify that frames are recoverable when in headless mode
1976 List<AlignmentPanel> aps = new ArrayList<>();
1977 AlignFrame[] frames = getAlignFrames();
1982 for (AlignFrame af : frames)
1984 for (AlignmentPanel ap : af.alignPanels)
1986 if (alignmentId == null
1987 || alignmentId.equals(ap.av.getSequenceSetId()))
1993 if (aps.size() == 0)
1997 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2002 * get all the viewports on an alignment.
2004 * @param sequenceSetId
2005 * unique alignment id (may be null - all viewports returned in that
2007 * @return all viewports on the alignment bound to sequenceSetId
2009 public static AlignmentViewport[] getViewports(String sequenceSetId)
2011 List<AlignmentViewport> viewp = new ArrayList<>();
2012 if (desktop != null)
2014 AlignFrame[] frames = Desktop.getAlignFrames();
2016 for (AlignFrame afr : frames)
2018 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2019 .equals(sequenceSetId))
2021 if (afr.alignPanels != null)
2023 for (AlignmentPanel ap : afr.alignPanels)
2025 if (sequenceSetId == null
2026 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2034 viewp.add(afr.getViewport());
2038 if (viewp.size() > 0)
2040 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2047 * Explode the views in the given frame into separate AlignFrame
2051 public static void explodeViews(AlignFrame af)
2053 int size = af.alignPanels.size();
2059 // FIXME: ideally should use UI interface API
2060 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2061 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2062 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2063 for (int i = 0; i < size; i++)
2065 AlignmentPanel ap = af.alignPanels.get(i);
2067 AlignFrame newaf = new AlignFrame(ap);
2069 // transfer reference for existing feature settings to new alignFrame
2070 if (ap == af.alignPanel)
2072 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2074 newaf.featureSettings = viewFeatureSettings;
2076 newaf.setFeatureSettingsGeometry(fsBounds);
2080 * Restore the view's last exploded frame geometry if known. Multiple views from
2081 * one exploded frame share and restore the same (frame) position and size.
2083 Rectangle geometry = ap.av.getExplodedGeometry();
2084 if (geometry != null)
2086 newaf.setBounds(geometry);
2089 ap.av.setGatherViewsHere(false);
2091 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2092 AlignFrame.DEFAULT_HEIGHT);
2093 // and materialise a new feature settings dialog instance for the new
2095 // (closes the old as if 'OK' was pressed)
2096 if (ap == af.alignPanel && newaf.featureSettings != null
2097 && newaf.featureSettings.isOpen()
2098 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2100 newaf.showFeatureSettingsUI();
2104 af.featureSettings = null;
2105 af.alignPanels.clear();
2106 af.closeMenuItem_actionPerformed(true);
2111 * Gather expanded views (separate AlignFrame's) with the same sequence set
2112 * identifier back in to this frame as additional views, and close the
2113 * expanded views. Note the expanded frames may themselves have multiple
2114 * views. We take the lot.
2118 public void gatherViews(AlignFrame source)
2120 source.viewport.setGatherViewsHere(true);
2121 source.viewport.setExplodedGeometry(source.getBounds());
2122 JInternalFrame[] frames = desktop.getAllFrames();
2123 String viewId = source.viewport.getSequenceSetId();
2124 for (int t = 0; t < frames.length; t++)
2126 if (frames[t] instanceof AlignFrame && frames[t] != source)
2128 AlignFrame af = (AlignFrame) frames[t];
2129 boolean gatherThis = false;
2130 for (int a = 0; a < af.alignPanels.size(); a++)
2132 AlignmentPanel ap = af.alignPanels.get(a);
2133 if (viewId.equals(ap.av.getSequenceSetId()))
2136 ap.av.setGatherViewsHere(false);
2137 ap.av.setExplodedGeometry(af.getBounds());
2138 source.addAlignmentPanel(ap, false);
2144 if (af.featureSettings != null && af.featureSettings.isOpen())
2146 if (source.featureSettings == null)
2148 // preserve the feature settings geometry for this frame
2149 source.featureSettings = af.featureSettings;
2150 source.setFeatureSettingsGeometry(
2151 af.getFeatureSettingsGeometry());
2155 // close it and forget
2156 af.featureSettings.close();
2159 af.alignPanels.clear();
2160 af.closeMenuItem_actionPerformed(true);
2165 // refresh the feature setting UI for the source frame if it exists
2166 if (source.featureSettings != null && source.featureSettings.isOpen())
2168 source.showFeatureSettingsUI();
2173 public JInternalFrame[] getAllFrames()
2175 return desktop.getAllFrames();
2179 * Checks the given url to see if it gives a response indicating that the user
2180 * should be informed of a new questionnaire.
2184 public void checkForQuestionnaire(String url)
2186 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2187 // javax.swing.SwingUtilities.invokeLater(jvq);
2188 new Thread(jvq).start();
2191 public void checkURLLinks()
2193 // Thread off the URL link checker
2194 addDialogThread(new Runnable()
2199 if (Cache.getDefault("CHECKURLLINKS", true))
2201 // check what the actual links are - if it's just the default don't
2202 // bother with the warning
2203 List<String> links = Preferences.sequenceUrlLinks
2206 // only need to check links if there is one with a
2207 // SEQUENCE_ID which is not the default EMBL_EBI link
2208 ListIterator<String> li = links.listIterator();
2209 boolean check = false;
2210 List<JLabel> urls = new ArrayList<>();
2211 while (li.hasNext())
2213 String link = li.next();
2214 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2215 && !UrlConstants.isDefaultString(link))
2218 int barPos = link.indexOf("|");
2219 String urlMsg = barPos == -1 ? link
2220 : link.substring(0, barPos) + ": "
2221 + link.substring(barPos + 1);
2222 urls.add(new JLabel(urlMsg));
2230 // ask user to check in case URL links use old style tokens
2231 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2232 JPanel msgPanel = new JPanel();
2233 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2234 msgPanel.add(Box.createVerticalGlue());
2235 JLabel msg = new JLabel(MessageManager
2236 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2237 JLabel msg2 = new JLabel(MessageManager
2238 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2240 for (JLabel url : urls)
2246 final JCheckBox jcb = new JCheckBox(
2247 MessageManager.getString("label.do_not_display_again"));
2248 jcb.addActionListener(new ActionListener()
2251 public void actionPerformed(ActionEvent e)
2253 // update Cache settings for "don't show this again"
2254 boolean showWarningAgain = !jcb.isSelected();
2255 Cache.setProperty("CHECKURLLINKS",
2256 Boolean.valueOf(showWarningAgain).toString());
2261 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2263 .getString("label.SEQUENCE_ID_no_longer_used"),
2264 JvOptionPane.WARNING_MESSAGE);
2271 * Proxy class for JDesktopPane which optionally displays the current memory
2272 * usage and highlights the desktop area with a red bar if free memory runs
2277 public class MyDesktopPane extends JDesktopPane implements Runnable
2279 private static final float ONE_MB = 1048576f;
2281 boolean showMemoryUsage = false;
2285 java.text.NumberFormat df;
2287 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2290 public MyDesktopPane(boolean showMemoryUsage)
2292 showMemoryUsage(showMemoryUsage);
2295 public void showMemoryUsage(boolean showMemory)
2297 this.showMemoryUsage = showMemory;
2300 Thread worker = new Thread(this);
2306 public boolean isShowMemoryUsage()
2308 return showMemoryUsage;
2314 df = java.text.NumberFormat.getNumberInstance();
2315 df.setMaximumFractionDigits(2);
2316 runtime = Runtime.getRuntime();
2318 while (showMemoryUsage)
2322 maxMemory = runtime.maxMemory() / ONE_MB;
2323 allocatedMemory = runtime.totalMemory() / ONE_MB;
2324 freeMemory = runtime.freeMemory() / ONE_MB;
2325 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2327 percentUsage = (totalFreeMemory / maxMemory) * 100;
2329 // if (percentUsage < 20)
2331 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2333 // instance.set.setBorder(border1);
2336 // sleep after showing usage
2338 } catch (Exception ex)
2340 ex.printStackTrace();
2346 public void paintComponent(Graphics g)
2348 if (showMemoryUsage && g != null && df != null)
2350 if (percentUsage < 20)
2352 g.setColor(Color.red);
2354 FontMetrics fm = g.getFontMetrics();
2357 g.drawString(MessageManager.formatMessage("label.memory_stats",
2359 { df.format(totalFreeMemory), df.format(maxMemory),
2360 df.format(percentUsage) }),
2361 10, getHeight() - fm.getHeight());
2365 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2366 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2371 * Accessor method to quickly get all the AlignmentFrames loaded.
2373 * @return an array of AlignFrame, or null if none found
2375 public static AlignFrame[] getAlignFrames()
2377 if (Jalview.isHeadlessMode())
2379 // Desktop.desktop is null in headless mode
2380 return new AlignFrame[] { Jalview.currentAlignFrame };
2383 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2389 List<AlignFrame> avp = new ArrayList<>();
2391 for (int i = frames.length - 1; i > -1; i--)
2393 if (frames[i] instanceof AlignFrame)
2395 avp.add((AlignFrame) frames[i]);
2397 else if (frames[i] instanceof SplitFrame)
2400 * Also check for a split frame containing an AlignFrame
2402 GSplitFrame sf = (GSplitFrame) frames[i];
2403 if (sf.getTopFrame() instanceof AlignFrame)
2405 avp.add((AlignFrame) sf.getTopFrame());
2407 if (sf.getBottomFrame() instanceof AlignFrame)
2409 avp.add((AlignFrame) sf.getBottomFrame());
2413 if (avp.size() == 0)
2417 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2422 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2426 public GStructureViewer[] getJmols()
2428 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2434 List<GStructureViewer> avp = new ArrayList<>();
2436 for (int i = frames.length - 1; i > -1; i--)
2438 if (frames[i] instanceof AppJmol)
2440 GStructureViewer af = (GStructureViewer) frames[i];
2444 if (avp.size() == 0)
2448 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2453 * Add Groovy Support to Jalview
2456 public void groovyShell_actionPerformed()
2460 openGroovyConsole();
2461 } catch (Exception ex)
2463 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2464 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2466 MessageManager.getString("label.couldnt_create_groovy_shell"),
2467 MessageManager.getString("label.groovy_support_failed"),
2468 JvOptionPane.ERROR_MESSAGE);
2473 * Open the Groovy console
2475 void openGroovyConsole()
2477 if (groovyConsole == null)
2479 groovyConsole = new groovy.ui.Console();
2480 groovyConsole.setVariable("Jalview", this);
2481 groovyConsole.run();
2484 * We allow only one console at a time, so that AlignFrame menu option
2485 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2486 * enable 'Run script', when the console is opened, and the reverse when it is
2489 Window window = (Window) groovyConsole.getFrame();
2490 window.addWindowListener(new WindowAdapter()
2493 public void windowClosed(WindowEvent e)
2496 * rebind CMD-Q from Groovy Console to Jalview Quit
2499 enableExecuteGroovy(false);
2505 * show Groovy console window (after close and reopen)
2507 ((Window) groovyConsole.getFrame()).setVisible(true);
2510 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2511 * opening a second console
2513 enableExecuteGroovy(true);
2517 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2518 * binding when opened
2520 protected void addQuitHandler()
2523 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2525 .getKeyStroke(KeyEvent.VK_Q,
2526 jalview.util.ShortcutKeyMaskExWrapper
2527 .getMenuShortcutKeyMaskEx()),
2529 getRootPane().getActionMap().put("Quit", new AbstractAction()
2532 public void actionPerformed(ActionEvent e)
2540 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2543 * true if Groovy console is open
2545 public void enableExecuteGroovy(boolean enabled)
2548 * disable opening a second Groovy console (or re-enable when the console is
2551 groovyShell.setEnabled(!enabled);
2553 AlignFrame[] alignFrames = getAlignFrames();
2554 if (alignFrames != null)
2556 for (AlignFrame af : alignFrames)
2558 af.setGroovyEnabled(enabled);
2564 * Progress bars managed by the IProgressIndicator method.
2566 private Hashtable<Long, JPanel> progressBars;
2568 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2573 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2576 public void setProgressBar(String message, long id)
2578 if (progressBars == null)
2580 progressBars = new Hashtable<>();
2581 progressBarHandlers = new Hashtable<>();
2584 if (progressBars.get(Long.valueOf(id)) != null)
2586 JPanel panel = progressBars.remove(Long.valueOf(id));
2587 if (progressBarHandlers.contains(Long.valueOf(id)))
2589 progressBarHandlers.remove(Long.valueOf(id));
2591 removeProgressPanel(panel);
2595 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2602 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2603 * jalview.gui.IProgressIndicatorHandler)
2606 public void registerHandler(final long id,
2607 final IProgressIndicatorHandler handler)
2609 if (progressBarHandlers == null
2610 || !progressBars.containsKey(Long.valueOf(id)))
2612 throw new Error(MessageManager.getString(
2613 "error.call_setprogressbar_before_registering_handler"));
2615 progressBarHandlers.put(Long.valueOf(id), handler);
2616 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2617 if (handler.canCancel())
2619 JButton cancel = new JButton(
2620 MessageManager.getString("action.cancel"));
2621 final IProgressIndicator us = this;
2622 cancel.addActionListener(new ActionListener()
2626 public void actionPerformed(ActionEvent e)
2628 handler.cancelActivity(id);
2629 us.setProgressBar(MessageManager
2630 .formatMessage("label.cancelled_params", new Object[]
2631 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2635 progressPanel.add(cancel, BorderLayout.EAST);
2641 * @return true if any progress bars are still active
2644 public boolean operationInProgress()
2646 if (progressBars != null && progressBars.size() > 0)
2654 * This will return the first AlignFrame holding the given viewport instance.
2655 * It will break if there are more than one AlignFrames viewing a particular
2659 * @return alignFrame for viewport
2661 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2663 if (desktop != null)
2665 AlignmentPanel[] aps = getAlignmentPanels(
2666 viewport.getSequenceSetId());
2667 for (int panel = 0; aps != null && panel < aps.length; panel++)
2669 if (aps[panel] != null && aps[panel].av == viewport)
2671 return aps[panel].alignFrame;
2678 public VamsasApplication getVamsasApplication()
2680 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2686 * flag set if jalview GUI is being operated programmatically
2688 private boolean inBatchMode = false;
2691 * check if jalview GUI is being operated programmatically
2693 * @return inBatchMode
2695 public boolean isInBatchMode()
2701 * set flag if jalview GUI is being operated programmatically
2703 * @param inBatchMode
2705 public void setInBatchMode(boolean inBatchMode)
2707 this.inBatchMode = inBatchMode;
2711 * start service discovery and wait till it is done
2713 public void startServiceDiscovery()
2715 startServiceDiscovery(false);
2719 * start service discovery threads - blocking or non-blocking
2723 public void startServiceDiscovery(boolean blocking)
2725 startServiceDiscovery(blocking, false);
2729 * start service discovery threads
2732 * - false means call returns immediately
2733 * @param ignore_SHOW_JWS2_SERVICES_preference
2734 * - when true JABA services are discovered regardless of user's JWS2
2735 * discovery preference setting
2737 public void startServiceDiscovery(boolean blocking,
2738 boolean ignore_SHOW_JWS2_SERVICES_preference)
2740 boolean alive = true;
2741 Thread t0 = null, t1 = null, t2 = null;
2742 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2745 // todo: changesupport handlers need to be transferred
2746 if (discoverer == null)
2748 discoverer = new jalview.ws.jws1.Discoverer();
2749 // register PCS handler for desktop.
2750 discoverer.addPropertyChangeListener(changeSupport);
2752 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2753 // until we phase out completely
2754 (t0 = new Thread(discoverer)).start();
2757 if (ignore_SHOW_JWS2_SERVICES_preference
2758 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2760 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2761 .startDiscoverer(changeSupport);
2765 // TODO: do rest service discovery
2774 } catch (Exception e)
2777 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2778 || (t3 != null && t3.isAlive())
2779 || (t0 != null && t0.isAlive());
2785 * called to check if the service discovery process completed successfully.
2789 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2791 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2793 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2794 .getErrorMessages();
2797 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2799 if (serviceChangedDialog == null)
2801 // only run if we aren't already displaying one of these.
2802 addDialogThread(serviceChangedDialog = new Runnable()
2809 * JalviewDialog jd =new JalviewDialog() {
2811 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2813 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2815 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2817 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2819 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2820 * + " or mis-configured HTTP proxy settings.<br/>" +
2821 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2822 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2823 * true, true, "Web Service Configuration Problem", 450, 400);
2825 * jd.waitForInput();
2827 JvOptionPane.showConfirmDialog(Desktop.desktop,
2828 new JLabel("<html><table width=\"450\"><tr><td>"
2829 + ermsg + "</td></tr></table>"
2830 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2831 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2832 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2833 + " Tools->Preferences dialog box to change them.</p></html>"),
2834 "Web Service Configuration Problem",
2835 JvOptionPane.DEFAULT_OPTION,
2836 JvOptionPane.ERROR_MESSAGE);
2837 serviceChangedDialog = null;
2845 jalview.bin.Console.error(
2846 "Errors reported by JABA discovery service. Check web services preferences.\n"
2853 private Runnable serviceChangedDialog = null;
2856 * start a thread to open a URL in the configured browser. Pops up a warning
2857 * dialog to the user if there is an exception when calling out to the browser
2862 public static void showUrl(final String url)
2864 showUrl(url, Desktop.instance);
2868 * Like showUrl but allows progress handler to be specified
2872 * (null) or object implementing IProgressIndicator
2874 public static void showUrl(final String url,
2875 final IProgressIndicator progress)
2877 new Thread(new Runnable()
2884 if (progress != null)
2886 progress.setProgressBar(MessageManager
2887 .formatMessage("status.opening_params", new Object[]
2888 { url }), this.hashCode());
2890 jalview.util.BrowserLauncher.openURL(url);
2891 } catch (Exception ex)
2893 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2895 .getString("label.web_browser_not_found_unix"),
2896 MessageManager.getString("label.web_browser_not_found"),
2897 JvOptionPane.WARNING_MESSAGE);
2899 ex.printStackTrace();
2901 if (progress != null)
2903 progress.setProgressBar(null, this.hashCode());
2909 public static WsParamSetManager wsparamManager = null;
2911 public static ParamManager getUserParameterStore()
2913 if (wsparamManager == null)
2915 wsparamManager = new WsParamSetManager();
2917 return wsparamManager;
2921 * static hyperlink handler proxy method for use by Jalview's internal windows
2925 public static void hyperlinkUpdate(HyperlinkEvent e)
2927 if (e.getEventType() == EventType.ACTIVATED)
2932 url = e.getURL().toString();
2933 Desktop.showUrl(url);
2934 } catch (Exception x)
2939 .error("Couldn't handle string " + url + " as a URL.");
2941 // ignore any exceptions due to dud links.
2948 * single thread that handles display of dialogs to user.
2950 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2953 * flag indicating if dialogExecutor should try to acquire a permit
2955 private volatile boolean dialogPause = true;
2960 private java.util.concurrent.Semaphore block = new Semaphore(0);
2962 private static groovy.ui.Console groovyConsole;
2965 * add another dialog thread to the queue
2969 public void addDialogThread(final Runnable prompter)
2971 dialogExecutor.submit(new Runnable()
2981 } catch (InterruptedException x)
2985 if (instance == null)
2991 SwingUtilities.invokeAndWait(prompter);
2992 } catch (Exception q)
2994 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3001 public void startDialogQueue()
3003 // set the flag so we don't pause waiting for another permit and semaphore
3004 // the current task to begin
3005 dialogPause = false;
3010 * Outputs an image of the desktop to file in EPS format, after prompting the
3011 * user for choice of Text or Lineart character rendering (unless a preference
3012 * has been set). The file name is generated as
3015 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3019 protected void snapShotWindow_actionPerformed(ActionEvent e)
3021 // currently the menu option to do this is not shown
3024 int width = getWidth();
3025 int height = getHeight();
3027 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3028 ImageWriterI writer = new ImageWriterI()
3031 public void exportImage(Graphics g) throws Exception
3034 jalview.bin.Console.info("Successfully written snapshot to file "
3035 + of.getAbsolutePath());
3038 String title = "View of desktop";
3039 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3041 exporter.doExport(of, this, width, height, title);
3045 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3046 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3047 * and location last time the view was expanded (if any). However it does not
3048 * remember the split pane divider location - this is set to match the
3049 * 'exploding' frame.
3053 public void explodeViews(SplitFrame sf)
3055 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3056 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3057 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3059 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3061 int viewCount = topPanels.size();
3068 * Processing in reverse order works, forwards order leaves the first panels not
3069 * visible. I don't know why!
3071 for (int i = viewCount - 1; i >= 0; i--)
3074 * Make new top and bottom frames. These take over the respective AlignmentPanel
3075 * objects, including their AlignmentViewports, so the cdna/protein
3076 * relationships between the viewports is carried over to the new split frames.
3078 * explodedGeometry holds the (x, y) position of the previously exploded
3079 * SplitFrame, and the (width, height) of the AlignFrame component
3081 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3082 AlignFrame newTopFrame = new AlignFrame(topPanel);
3083 newTopFrame.setSize(oldTopFrame.getSize());
3084 newTopFrame.setVisible(true);
3085 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3086 .getExplodedGeometry();
3087 if (geometry != null)
3089 newTopFrame.setSize(geometry.getSize());
3092 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3093 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3094 newBottomFrame.setSize(oldBottomFrame.getSize());
3095 newBottomFrame.setVisible(true);
3096 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3097 .getExplodedGeometry();
3098 if (geometry != null)
3100 newBottomFrame.setSize(geometry.getSize());
3103 topPanel.av.setGatherViewsHere(false);
3104 bottomPanel.av.setGatherViewsHere(false);
3105 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3107 if (geometry != null)
3109 splitFrame.setLocation(geometry.getLocation());
3111 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3115 * Clear references to the panels (now relocated in the new SplitFrames) before
3116 * closing the old SplitFrame.
3119 bottomPanels.clear();
3124 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3125 * back into the given SplitFrame as additional views. Note that the gathered
3126 * frames may themselves have multiple views.
3130 public void gatherViews(GSplitFrame source)
3133 * special handling of explodedGeometry for a view within a SplitFrame: - it
3134 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3135 * height) of the AlignFrame component
3137 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3138 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3139 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3140 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3141 myBottomFrame.viewport
3142 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3143 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3144 myTopFrame.viewport.setGatherViewsHere(true);
3145 myBottomFrame.viewport.setGatherViewsHere(true);
3146 String topViewId = myTopFrame.viewport.getSequenceSetId();
3147 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3149 JInternalFrame[] frames = desktop.getAllFrames();
3150 for (JInternalFrame frame : frames)
3152 if (frame instanceof SplitFrame && frame != source)
3154 SplitFrame sf = (SplitFrame) frame;
3155 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3156 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3157 boolean gatherThis = false;
3158 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3160 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3161 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3162 if (topViewId.equals(topPanel.av.getSequenceSetId())
3163 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3166 topPanel.av.setGatherViewsHere(false);
3167 bottomPanel.av.setGatherViewsHere(false);
3168 topPanel.av.setExplodedGeometry(
3169 new Rectangle(sf.getLocation(), topFrame.getSize()));
3170 bottomPanel.av.setExplodedGeometry(
3171 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3172 myTopFrame.addAlignmentPanel(topPanel, false);
3173 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3179 topFrame.getAlignPanels().clear();
3180 bottomFrame.getAlignPanels().clear();
3187 * The dust settles...give focus to the tab we did this from.
3189 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3192 public static groovy.ui.Console getGroovyConsole()
3194 return groovyConsole;
3198 * handles the payload of a drag and drop event.
3200 * TODO refactor to desktop utilities class
3203 * - Data source strings extracted from the drop event
3205 * - protocol for each data source extracted from the drop event
3209 * - the payload from the drop event
3212 public static void transferFromDropTarget(List<Object> files,
3213 List<DataSourceType> protocols, DropTargetDropEvent evt,
3214 Transferable t) throws Exception
3217 DataFlavor uriListFlavor = new DataFlavor(
3218 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3221 urlFlavour = new DataFlavor(
3222 "application/x-java-url; class=java.net.URL");
3223 } catch (ClassNotFoundException cfe)
3225 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3229 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3234 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3235 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3236 // means url may be null.
3239 protocols.add(DataSourceType.URL);
3240 files.add(url.toString());
3241 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3242 + files.get(files.size() - 1));
3247 if (Platform.isAMacAndNotJS())
3250 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3253 } catch (Throwable ex)
3255 jalview.bin.Console.debug("URL drop handler failed.", ex);
3258 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3260 // Works on Windows and MacOSX
3261 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3262 for (Object file : (List) t
3263 .getTransferData(DataFlavor.javaFileListFlavor))
3266 protocols.add(DataSourceType.FILE);
3271 // Unix like behaviour
3272 boolean added = false;
3274 if (t.isDataFlavorSupported(uriListFlavor))
3276 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3277 // This is used by Unix drag system
3278 data = (String) t.getTransferData(uriListFlavor);
3282 // fallback to text: workaround - on OSX where there's a JVM bug
3284 .debug("standard URIListFlavor failed. Trying text");
3285 // try text fallback
3286 DataFlavor textDf = new DataFlavor(
3287 "text/plain;class=java.lang.String");
3288 if (t.isDataFlavorSupported(textDf))
3290 data = (String) t.getTransferData(textDf);
3293 jalview.bin.Console.debug("Plain text drop content returned "
3294 + (data == null ? "Null - failed" : data));
3299 while (protocols.size() < files.size())
3301 jalview.bin.Console.debug("Adding missing FILE protocol for "
3302 + files.get(protocols.size()));
3303 protocols.add(DataSourceType.FILE);
3305 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3306 data, "\r\n"); st.hasMoreTokens();)
3309 String s = st.nextToken();
3310 if (s.startsWith("#"))
3312 // the line is a comment (as per the RFC 2483)
3315 java.net.URI uri = new java.net.URI(s);
3316 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3318 protocols.add(DataSourceType.URL);
3319 files.add(uri.toString());
3323 // otherwise preserve old behaviour: catch all for file objects
3324 java.io.File file = new java.io.File(uri);
3325 protocols.add(DataSourceType.FILE);
3326 files.add(file.toString());
3331 if (jalview.bin.Console.isDebugEnabled())
3333 if (data == null || !added)
3336 if (t.getTransferDataFlavors() != null
3337 && t.getTransferDataFlavors().length > 0)
3339 jalview.bin.Console.debug(
3340 "Couldn't resolve drop data. Here are the supported flavors:");
3341 for (DataFlavor fl : t.getTransferDataFlavors())
3343 jalview.bin.Console.debug(
3344 "Supported transfer dataflavor: " + fl.toString());
3345 Object df = t.getTransferData(fl);
3348 jalview.bin.Console.debug("Retrieves: " + df);
3352 jalview.bin.Console.debug("Retrieved nothing");
3359 .debug("Couldn't resolve dataflavor for drop: "
3365 if (Platform.isWindowsAndNotJS())
3368 .debug("Scanning dropped content for Windows Link Files");
3370 // resolve any .lnk files in the file drop
3371 for (int f = 0; f < files.size(); f++)
3373 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3374 if (protocols.get(f).equals(DataSourceType.FILE)
3375 && (source.endsWith(".lnk") || source.endsWith(".url")
3376 || source.endsWith(".site")))
3380 Object obj = files.get(f);
3381 File lf = (obj instanceof File ? (File) obj
3382 : new File((String) obj));
3383 // process link file to get a URL
3384 jalview.bin.Console.debug("Found potential link file: " + lf);
3385 WindowsShortcut wscfile = new WindowsShortcut(lf);
3386 String fullname = wscfile.getRealFilename();
3387 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3388 files.set(f, fullname);
3389 jalview.bin.Console.debug("Parsed real filename " + fullname
3390 + " to extract protocol: " + protocols.get(f));
3391 } catch (Exception ex)
3393 jalview.bin.Console.error(
3394 "Couldn't parse " + files.get(f) + " as a link file.",
3403 * Sets the Preferences property for experimental features to True or False
3404 * depending on the state of the controlling menu item
3407 protected void showExperimental_actionPerformed(boolean selected)
3409 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3413 * Answers a (possibly empty) list of any structure viewer frames (currently
3414 * for either Jmol or Chimera) which are currently open. This may optionally
3415 * be restricted to viewers of a specified class, or viewers linked to a
3416 * specified alignment panel.
3419 * if not null, only return viewers linked to this panel
3420 * @param structureViewerClass
3421 * if not null, only return viewers of this class
3424 public List<StructureViewerBase> getStructureViewers(
3425 AlignmentPanel apanel,
3426 Class<? extends StructureViewerBase> structureViewerClass)
3428 List<StructureViewerBase> result = new ArrayList<>();
3429 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3431 for (JInternalFrame frame : frames)
3433 if (frame instanceof StructureViewerBase)
3435 if (structureViewerClass == null
3436 || structureViewerClass.isInstance(frame))
3439 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3441 result.add((StructureViewerBase) frame);
3449 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3451 private static boolean debugScaleMessageDone = false;
3453 public static void debugScaleMessage(Graphics g)
3455 if (debugScaleMessageDone)
3459 // output used by tests to check HiDPI scaling settings in action
3462 Graphics2D gg = (Graphics2D) g;
3465 AffineTransform t = gg.getTransform();
3466 double scaleX = t.getScaleX();
3467 double scaleY = t.getScaleY();
3468 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3469 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3470 debugScaleMessageDone = true;
3474 jalview.bin.Console.debug("Desktop graphics null");
3476 } catch (Exception e)
3478 jalview.bin.Console.debug(Cache.getStackTraceString(e));