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.Callable;
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.JFrame;
86 import javax.swing.JInternalFrame;
87 import javax.swing.JLabel;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JProgressBar;
92 import javax.swing.JTextField;
93 import javax.swing.KeyStroke;
94 import javax.swing.SwingUtilities;
95 import javax.swing.WindowConstants;
96 import javax.swing.event.HyperlinkEvent;
97 import javax.swing.event.HyperlinkEvent.EventType;
98 import javax.swing.event.InternalFrameAdapter;
99 import javax.swing.event.InternalFrameEvent;
101 import org.stackoverflowusers.file.WindowsShortcut;
103 import jalview.api.AlignViewportI;
104 import jalview.api.AlignmentViewPanel;
105 import jalview.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.gui.ImageExporter.ImageWriterI;
108 import jalview.gui.QuitHandler.QResponse;
109 import jalview.io.BackupFiles;
110 import jalview.io.DataSourceType;
111 import jalview.io.FileFormat;
112 import jalview.io.FileFormatException;
113 import jalview.io.FileFormatI;
114 import jalview.io.FileFormats;
115 import jalview.io.FileLoader;
116 import jalview.io.FormatAdapter;
117 import jalview.io.IdentifyFile;
118 import jalview.io.JalviewFileChooser;
119 import jalview.io.JalviewFileView;
120 import jalview.jbgui.GSplitFrame;
121 import jalview.jbgui.GStructureViewer;
122 import jalview.project.Jalview2XML;
123 import jalview.structure.StructureSelectionManager;
124 import jalview.urls.IdOrgSettings;
125 import jalview.util.BrowserLauncher;
126 import jalview.util.ChannelProperties;
127 import jalview.util.ImageMaker.TYPE;
128 import jalview.util.LaunchUtils;
129 import jalview.util.MessageManager;
130 import jalview.util.Platform;
131 import jalview.util.ShortcutKeyMaskExWrapper;
132 import jalview.util.UrlConstants;
133 import jalview.viewmodel.AlignmentViewport;
134 import jalview.ws.params.ParamManager;
135 import jalview.ws.utils.UrlDownloadClient;
142 * @version $Revision: 1.155 $
144 public class Desktop extends jalview.jbgui.GDesktop
145 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
146 jalview.api.StructureSelectionManagerProvider
148 private static final String CITATION;
151 URL bg_logo_url = ChannelProperties.getImageURL(
152 "bg_logo." + String.valueOf(SplashScreen.logoSize));
153 URL uod_logo_url = ChannelProperties.getImageURL(
154 "uod_banner." + String.valueOf(SplashScreen.logoSize));
155 boolean logo = (bg_logo_url != null || uod_logo_url != null);
156 StringBuilder sb = new StringBuilder();
158 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
163 sb.append(bg_logo_url == null ? ""
164 : "<img alt=\"Barton Group logo\" src=\""
165 + bg_logo_url.toString() + "\">");
166 sb.append(uod_logo_url == null ? ""
167 : " <img alt=\"University of Dundee shield\" src=\""
168 + uod_logo_url.toString() + "\">");
170 "<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>");
171 sb.append("<br><br>If you use Jalview, please cite:"
172 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
173 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
174 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
175 CITATION = sb.toString();
178 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
180 private static int DEFAULT_MIN_WIDTH = 300;
182 private static int DEFAULT_MIN_HEIGHT = 250;
184 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
186 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
188 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
190 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
192 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
194 private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
196 public static void setLiveDragMode(boolean b)
198 DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
199 : JDesktopPane.OUTLINE_DRAG_MODE;
201 desktop.setDragMode(DRAG_MODE);
204 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
206 public static boolean nosplash = false;
209 * news reader - null if it was never started.
211 private BlogReader jvnews = null;
213 private File projectFile;
217 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
219 public void addJalviewPropertyChangeListener(
220 PropertyChangeListener listener)
222 changeSupport.addJalviewPropertyChangeListener(listener);
226 * @param propertyName
228 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
229 * java.beans.PropertyChangeListener)
231 public void addJalviewPropertyChangeListener(String propertyName,
232 PropertyChangeListener listener)
234 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
238 * @param propertyName
240 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
241 * java.beans.PropertyChangeListener)
243 public void removeJalviewPropertyChangeListener(String propertyName,
244 PropertyChangeListener listener)
246 changeSupport.removeJalviewPropertyChangeListener(propertyName,
250 /** Singleton Desktop instance */
251 public static Desktop instance;
253 public static MyDesktopPane desktop;
255 public static MyDesktopPane getDesktop()
257 // BH 2018 could use currentThread() here as a reference to a
258 // Hashtable<Thread, MyDesktopPane> in JavaScript
262 static int openFrameCount = 0;
264 static final int xOffset = 30;
266 static final int yOffset = 30;
268 public static jalview.ws.jws1.Discoverer discoverer;
270 public static Object[] jalviewClipboard;
272 public static boolean internalCopy = false;
274 static int fileLoadingCount = 0;
276 class MyDesktopManager implements DesktopManager
279 private DesktopManager delegate;
281 public MyDesktopManager(DesktopManager delegate)
283 this.delegate = delegate;
287 public void activateFrame(JInternalFrame f)
291 delegate.activateFrame(f);
292 } catch (NullPointerException npe)
294 Point p = getMousePosition();
295 instance.showPasteMenu(p.x, p.y);
300 public void beginDraggingFrame(JComponent f)
302 delegate.beginDraggingFrame(f);
306 public void beginResizingFrame(JComponent f, int direction)
308 delegate.beginResizingFrame(f, direction);
312 public void closeFrame(JInternalFrame f)
314 delegate.closeFrame(f);
318 public void deactivateFrame(JInternalFrame f)
320 delegate.deactivateFrame(f);
324 public void deiconifyFrame(JInternalFrame f)
326 delegate.deiconifyFrame(f);
330 public void dragFrame(JComponent f, int newX, int newY)
336 delegate.dragFrame(f, newX, newY);
340 public void endDraggingFrame(JComponent f)
342 delegate.endDraggingFrame(f);
347 public void endResizingFrame(JComponent f)
349 delegate.endResizingFrame(f);
354 public void iconifyFrame(JInternalFrame f)
356 delegate.iconifyFrame(f);
360 public void maximizeFrame(JInternalFrame f)
362 delegate.maximizeFrame(f);
366 public void minimizeFrame(JInternalFrame f)
368 delegate.minimizeFrame(f);
372 public void openFrame(JInternalFrame f)
374 delegate.openFrame(f);
378 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
385 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
389 public void setBoundsForFrame(JComponent f, int newX, int newY,
390 int newWidth, int newHeight)
392 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
395 // All other methods, simply delegate
400 * Creates a new Desktop object.
406 * A note to implementors. It is ESSENTIAL that any activities that might
407 * block are spawned off as threads rather than waited for during this
412 doConfigureStructurePrefs();
413 setTitle(ChannelProperties.getProperty("app_name") + " "
414 + Cache.getProperty("VERSION"));
417 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
418 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
419 * officially documented or guaranteed to exist, so we access it via
420 * reflection. There appear to be unfathomable criteria about what this
421 * string can contain, and it if doesn't meet those criteria then "java"
422 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
423 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
424 * not. The reflection access may generate a warning: WARNING: An illegal
425 * reflective access operation has occurred WARNING: Illegal reflective
426 * access by jalview.gui.Desktop () to field
427 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
429 if (Platform.isLinux())
431 if (LaunchUtils.getJavaVersion() >= 11)
433 jalview.bin.Console.info(
434 "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.");
438 Toolkit xToolkit = Toolkit.getDefaultToolkit();
439 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
440 Field awtAppClassNameField = null;
442 if (Arrays.stream(declaredFields)
443 .anyMatch(f -> f.getName().equals("awtAppClassName")))
445 awtAppClassNameField = xToolkit.getClass()
446 .getDeclaredField("awtAppClassName");
449 String title = ChannelProperties.getProperty("app_name");
450 if (awtAppClassNameField != null)
452 awtAppClassNameField.setAccessible(true);
453 awtAppClassNameField.set(xToolkit, title);
457 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
459 } catch (Exception e)
461 jalview.bin.Console.debug("Error setting awtAppClassName");
462 jalview.bin.Console.trace(Cache.getStackTraceString(e));
466 setIconImages(ChannelProperties.getIconList());
468 // override quit handling when GUI OS close [X] button pressed
469 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
470 addWindowListener(new WindowAdapter()
473 public void windowClosing(WindowEvent ev)
475 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
479 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
481 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
482 desktop = new MyDesktopPane(selmemusage);
484 showMemusage.setSelected(selmemusage);
485 desktop.setBackground(Color.white);
487 getContentPane().setLayout(new BorderLayout());
488 // alternate config - have scrollbars - see notes in JAL-153
489 // JScrollPane sp = new JScrollPane();
490 // sp.getViewport().setView(desktop);
491 // getContentPane().add(sp, BorderLayout.CENTER);
493 // BH 2018 - just an experiment to try unclipped JInternalFrames.
496 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
499 getContentPane().add(desktop, BorderLayout.CENTER);
500 desktop.setDragMode(DRAG_MODE);
502 // This line prevents Windows Look&Feel resizing all new windows to maximum
503 // if previous window was maximised
504 desktop.setDesktopManager(new MyDesktopManager(
505 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
506 : Platform.isAMacAndNotJS()
507 ? new AquaInternalFrameManager(
508 desktop.getDesktopManager())
509 : desktop.getDesktopManager())));
511 Rectangle dims = getLastKnownDimensions("");
518 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
519 int xPos = Math.max(5, (screenSize.width - 900) / 2);
520 int yPos = Math.max(5, (screenSize.height - 650) / 2);
521 setBounds(xPos, yPos, 900, 650);
524 if (!Platform.isJS())
531 jconsole = new Console(this, showjconsole);
532 jconsole.setHeader(Cache.getVersionDetailsForConsole());
533 showConsole(showjconsole);
535 showNews.setVisible(false);
537 experimentalFeatures.setSelected(showExperimental());
539 getIdentifiersOrgData();
543 // Spawn a thread that shows the splashscreen
546 SwingUtilities.invokeLater(new Runnable()
551 new SplashScreen(true);
556 // Thread off a new instance of the file chooser - this reduces the time
558 // takes to open it later on.
559 new Thread(new Runnable()
564 jalview.bin.Console.debug("Filechooser init thread started.");
565 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
566 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
568 jalview.bin.Console.debug("Filechooser init thread finished.");
571 // Add the service change listener
572 changeSupport.addJalviewPropertyChangeListener("services",
573 new PropertyChangeListener()
577 public void propertyChange(PropertyChangeEvent evt)
580 .debug("Firing service changed event for "
581 + evt.getNewValue());
582 JalviewServicesChanged(evt);
587 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
590 this.addMouseListener(ma = new MouseAdapter()
593 public void mousePressed(MouseEvent evt)
595 if (evt.isPopupTrigger()) // Mac
597 showPasteMenu(evt.getX(), evt.getY());
602 public void mouseReleased(MouseEvent evt)
604 if (evt.isPopupTrigger()) // Windows
606 showPasteMenu(evt.getX(), evt.getY());
610 desktop.addMouseListener(ma);
614 * Answers true if user preferences to enable experimental features is True
619 public boolean showExperimental()
621 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
622 Boolean.FALSE.toString());
623 return Boolean.valueOf(experimental).booleanValue();
626 public void doConfigureStructurePrefs()
628 // configure services
629 StructureSelectionManager ssm = StructureSelectionManager
630 .getStructureSelectionManager(this);
631 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
633 ssm.setAddTempFacAnnot(
634 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
635 ssm.setProcessSecondaryStructure(
636 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
637 // JAL-3915 - RNAView is no longer an option so this has no effect
638 ssm.setSecStructServices(
639 Cache.getDefault(Preferences.USE_RNAVIEW, false));
643 ssm.setAddTempFacAnnot(false);
644 ssm.setProcessSecondaryStructure(false);
645 ssm.setSecStructServices(false);
649 public void checkForNews()
651 final Desktop me = this;
652 // Thread off the news reader, in case there are connection problems.
653 new Thread(new Runnable()
658 jalview.bin.Console.debug("Starting news thread.");
659 jvnews = new BlogReader(me);
660 showNews.setVisible(true);
661 jalview.bin.Console.debug("Completed news thread.");
666 public void getIdentifiersOrgData()
668 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
669 {// Thread off the identifiers fetcher
670 new Thread(new Runnable()
676 .debug("Downloading data from identifiers.org");
679 UrlDownloadClient.download(IdOrgSettings.getUrl(),
680 IdOrgSettings.getDownloadLocation());
681 } catch (IOException e)
684 .debug("Exception downloading identifiers.org data"
694 protected void showNews_actionPerformed(ActionEvent e)
696 showNews(showNews.isSelected());
699 void showNews(boolean visible)
701 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
702 showNews.setSelected(visible);
703 if (visible && !jvnews.isVisible())
705 new Thread(new Runnable()
710 long now = System.currentTimeMillis();
711 Desktop.instance.setProgressBar(
712 MessageManager.getString("status.refreshing_news"), now);
713 jvnews.refreshNews();
714 Desktop.instance.setProgressBar(null, now);
722 * recover the last known dimensions for a jalview window
725 * - empty string is desktop, all other windows have unique prefix
726 * @return null or last known dimensions scaled to current geometry (if last
727 * window geom was known)
729 Rectangle getLastKnownDimensions(String windowName)
731 // TODO: lock aspect ratio for scaling desktop Bug #0058199
732 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
733 String x = Cache.getProperty(windowName + "SCREEN_X");
734 String y = Cache.getProperty(windowName + "SCREEN_Y");
735 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
736 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
737 if ((x != null) && (y != null) && (width != null) && (height != null))
739 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
740 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
741 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
743 // attempt #1 - try to cope with change in screen geometry - this
744 // version doesn't preserve original jv aspect ratio.
745 // take ratio of current screen size vs original screen size.
746 double sw = ((1f * screenSize.width) / (1f * Integer
747 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
748 double sh = ((1f * screenSize.height) / (1f * Integer
749 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
750 // rescale the bounds depending upon the current screen geometry.
751 ix = (int) (ix * sw);
752 iw = (int) (iw * sw);
753 iy = (int) (iy * sh);
754 ih = (int) (ih * sh);
755 while (ix >= screenSize.width)
757 jalview.bin.Console.debug(
758 "Window geometry location recall error: shifting horizontal to within screenbounds.");
759 ix -= screenSize.width;
761 while (iy >= screenSize.height)
763 jalview.bin.Console.debug(
764 "Window geometry location recall error: shifting vertical to within screenbounds.");
765 iy -= screenSize.height;
767 jalview.bin.Console.debug(
768 "Got last known dimensions for " + windowName + ": x:" + ix
769 + " y:" + iy + " width:" + iw + " height:" + ih);
771 // return dimensions for new instance
772 return new Rectangle(ix, iy, iw, ih);
777 void showPasteMenu(int x, int y)
779 JPopupMenu popup = new JPopupMenu();
780 JMenuItem item = new JMenuItem(
781 MessageManager.getString("label.paste_new_window"));
782 item.addActionListener(new ActionListener()
785 public void actionPerformed(ActionEvent evt)
792 popup.show(this, x, y);
799 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
800 Transferable contents = c.getContents(this);
802 if (contents != null)
804 String file = (String) contents
805 .getTransferData(DataFlavor.stringFlavor);
807 FileFormatI format = new IdentifyFile().identify(file,
808 DataSourceType.PASTE);
810 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
813 } catch (Exception ex)
816 "Unable to paste alignment from system clipboard:\n" + ex);
821 * Adds and opens the given frame to the desktop
832 public static synchronized void addInternalFrame(
833 final JInternalFrame frame, String title, int w, int h)
835 addInternalFrame(frame, title, true, w, h, true, false);
839 * Add an internal frame to the Jalview desktop
846 * When true, display frame immediately, otherwise, caller must call
847 * setVisible themselves.
853 public static synchronized void addInternalFrame(
854 final JInternalFrame frame, String title, boolean makeVisible,
857 addInternalFrame(frame, title, makeVisible, w, h, true, false);
861 * Add an internal frame to the Jalview desktop and make it visible
874 public static synchronized void addInternalFrame(
875 final JInternalFrame frame, String title, int w, int h,
878 addInternalFrame(frame, title, true, w, h, resizable, false);
882 * Add an internal frame to the Jalview desktop
889 * When true, display frame immediately, otherwise, caller must call
890 * setVisible themselves.
897 * @param ignoreMinSize
898 * Do not set the default minimum size for frame
900 public static synchronized void addInternalFrame(
901 final JInternalFrame frame, String title, boolean makeVisible,
902 int w, int h, boolean resizable, boolean ignoreMinSize)
905 // TODO: allow callers to determine X and Y position of frame (eg. via
907 // TODO: consider fixing method to update entries in the window submenu with
908 // the current window title
910 frame.setTitle(title);
911 if (frame.getWidth() < 1 || frame.getHeight() < 1)
915 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
916 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
917 // IF JALVIEW IS RUNNING HEADLESS
918 // ///////////////////////////////////////////////
919 if (instance == null || (System.getProperty("java.awt.headless") != null
920 && System.getProperty("java.awt.headless").equals("true")))
929 frame.setMinimumSize(
930 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
932 // Set default dimension for Alignment Frame window.
933 // The Alignment Frame window could be added from a number of places,
935 // I did this here in order not to miss out on any Alignment frame.
936 if (frame instanceof AlignFrame)
938 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
939 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
943 frame.setVisible(makeVisible);
944 frame.setClosable(true);
945 frame.setResizable(resizable);
946 frame.setMaximizable(resizable);
947 frame.setIconifiable(resizable);
948 frame.setOpaque(Platform.isJS());
950 if (frame.getX() < 1 && frame.getY() < 1)
952 frame.setLocation(xOffset * openFrameCount,
953 yOffset * ((openFrameCount - 1) % 10) + yOffset);
957 * add an entry for the new frame in the Window menu (and remove it when the
960 final JMenuItem menuItem = new JMenuItem(title);
961 frame.addInternalFrameListener(new InternalFrameAdapter()
964 public void internalFrameActivated(InternalFrameEvent evt)
966 JInternalFrame itf = desktop.getSelectedFrame();
969 if (itf instanceof AlignFrame)
971 Jalview.setCurrentAlignFrame((AlignFrame) itf);
978 public void internalFrameClosed(InternalFrameEvent evt)
980 PaintRefresher.RemoveComponent(frame);
983 * defensive check to prevent frames being added half off the window
985 if (openFrameCount > 0)
991 * ensure no reference to alignFrame retained by menu item listener
993 if (menuItem.getActionListeners().length > 0)
995 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
997 windowMenu.remove(menuItem);
1001 menuItem.addActionListener(new ActionListener()
1004 public void actionPerformed(ActionEvent e)
1008 frame.setSelected(true);
1009 frame.setIcon(false);
1010 } catch (java.beans.PropertyVetoException ex)
1017 setKeyBindings(frame);
1021 windowMenu.add(menuItem);
1026 frame.setSelected(true);
1027 frame.requestFocus();
1028 } catch (java.beans.PropertyVetoException ve)
1030 } catch (java.lang.ClassCastException cex)
1032 jalview.bin.Console.warn(
1033 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1039 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1044 private static void setKeyBindings(JInternalFrame frame)
1046 @SuppressWarnings("serial")
1047 final Action closeAction = new AbstractAction()
1050 public void actionPerformed(ActionEvent e)
1057 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1059 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1060 InputEvent.CTRL_DOWN_MASK);
1061 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1062 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1064 InputMap inputMap = frame
1065 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1066 String ctrlW = ctrlWKey.toString();
1067 inputMap.put(ctrlWKey, ctrlW);
1068 inputMap.put(cmdWKey, ctrlW);
1070 ActionMap actionMap = frame.getActionMap();
1071 actionMap.put(ctrlW, closeAction);
1075 public void lostOwnership(Clipboard clipboard, Transferable contents)
1079 Desktop.jalviewClipboard = null;
1082 internalCopy = false;
1086 public void dragEnter(DropTargetDragEvent evt)
1091 public void dragExit(DropTargetEvent evt)
1096 public void dragOver(DropTargetDragEvent evt)
1101 public void dropActionChanged(DropTargetDragEvent evt)
1112 public void drop(DropTargetDropEvent evt)
1114 boolean success = true;
1115 // JAL-1552 - acceptDrop required before getTransferable call for
1116 // Java's Transferable for native dnd
1117 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1118 Transferable t = evt.getTransferable();
1119 List<Object> files = new ArrayList<>();
1120 List<DataSourceType> protocols = new ArrayList<>();
1124 Desktop.transferFromDropTarget(files, protocols, evt, t);
1125 } catch (Exception e)
1127 e.printStackTrace();
1135 for (int i = 0; i < files.size(); i++)
1137 // BH 2018 File or String
1138 Object file = files.get(i);
1139 String fileName = file.toString();
1140 DataSourceType protocol = (protocols == null)
1141 ? DataSourceType.FILE
1143 FileFormatI format = null;
1145 if (fileName.endsWith(".jar"))
1147 format = FileFormat.Jalview;
1152 format = new IdentifyFile().identify(file, protocol);
1154 if (file instanceof File)
1156 Platform.cacheFileData((File) file);
1158 new FileLoader().LoadFile(null, file, protocol, format);
1161 } catch (Exception ex)
1166 evt.dropComplete(success); // need this to ensure input focus is properly
1167 // transfered to any new windows created
1177 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1179 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1180 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1181 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1182 BackupFiles.getEnabled());
1184 chooser.setFileView(new JalviewFileView());
1185 chooser.setDialogTitle(
1186 MessageManager.getString("label.open_local_file"));
1187 chooser.setToolTipText(MessageManager.getString("action.open"));
1189 chooser.setResponseHandler(0, () -> {
1190 File selectedFile = chooser.getSelectedFile();
1191 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1193 FileFormatI format = chooser.getSelectedFormat();
1196 * Call IdentifyFile to verify the file contains what its extension implies.
1197 * Skip this step for dynamically added file formats, because IdentifyFile does
1198 * not know how to recognise them.
1200 if (FileFormats.getInstance().isIdentifiable(format))
1204 format = new IdentifyFile().identify(selectedFile,
1205 DataSourceType.FILE);
1206 } catch (FileFormatException e)
1208 // format = null; //??
1212 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1216 chooser.showOpenDialog(this);
1220 * Shows a dialog for input of a URL at which to retrieve alignment data
1225 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1227 // This construct allows us to have a wider textfield
1229 JLabel label = new JLabel(
1230 MessageManager.getString("label.input_file_url"));
1232 JPanel panel = new JPanel(new GridLayout(2, 1));
1236 * the URL to fetch is input in Java: an editable combobox with history JS:
1237 * (pending JAL-3038) a plain text field
1240 String urlBase = "https://www.";
1241 if (Platform.isJS())
1243 history = new JTextField(urlBase, 35);
1252 JComboBox<String> asCombo = new JComboBox<>();
1253 asCombo.setPreferredSize(new Dimension(400, 20));
1254 asCombo.setEditable(true);
1255 asCombo.addItem(urlBase);
1256 String historyItems = Cache.getProperty("RECENT_URL");
1257 if (historyItems != null)
1259 for (String token : historyItems.split("\\t"))
1261 asCombo.addItem(token);
1268 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1269 MessageManager.getString("action.cancel") };
1270 Callable<Void> action = () -> {
1271 @SuppressWarnings("unchecked")
1272 String url = (history instanceof JTextField
1273 ? ((JTextField) history).getText()
1274 : ((JComboBox<String>) history).getEditor().getItem()
1275 .toString().trim());
1277 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1279 if (viewport != null)
1281 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1282 FileFormat.Jalview);
1286 new FileLoader().LoadFile(url, DataSourceType.URL,
1287 FileFormat.Jalview);
1292 FileFormatI format = null;
1295 format = new IdentifyFile().identify(url, DataSourceType.URL);
1296 } catch (FileFormatException e)
1298 // TODO revise error handling, distinguish between
1299 // URL not found and response not valid
1304 String msg = MessageManager.formatMessage("label.couldnt_locate",
1306 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1307 MessageManager.getString("label.url_not_found"),
1308 JvOptionPane.WARNING_MESSAGE);
1310 return null; // Void
1313 if (viewport != null)
1315 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1320 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1323 return null; // Void
1325 String dialogOption = MessageManager
1326 .getString("label.input_alignment_from_url");
1327 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1328 .showInternalDialog(panel, dialogOption,
1329 JvOptionPane.YES_NO_CANCEL_OPTION,
1330 JvOptionPane.PLAIN_MESSAGE, null, options,
1331 MessageManager.getString("action.ok"));
1335 * Opens the CutAndPaste window for the user to paste an alignment in to
1338 * - if not null, the pasted alignment is added to the current
1339 * alignment; if null, to a new alignment window
1342 public void inputTextboxMenuItem_actionPerformed(
1343 AlignmentViewPanel viewPanel)
1345 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1346 cap.setForInput(viewPanel);
1347 Desktop.addInternalFrame(cap,
1348 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1353 * Check with user and saving files before actually quitting
1355 public void desktopQuit()
1357 desktopQuit(true, false);
1360 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1362 final Callable<Void> doDesktopQuit = () -> {
1363 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1364 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1365 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1366 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1367 getBounds().y, getWidth(), getHeight()));
1369 if (jconsole != null)
1371 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1372 jconsole.stopConsole();
1377 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1380 closeAll_actionPerformed(null);
1382 // check for aborted quit
1383 if (QuitHandler.quitCancelled())
1385 jalview.bin.Console.debug("Desktop aborting quit");
1389 if (dialogExecutor != null)
1391 dialogExecutor.shutdownNow();
1394 if (groovyConsole != null)
1396 // suppress a possible repeat prompt to save script
1397 groovyConsole.setDirty(false);
1398 groovyConsole.exit();
1401 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1403 // note that shutdown hook will not be run
1404 jalview.bin.Console.debug("Force Quit selected by user");
1405 Runtime.getRuntime().halt(0);
1408 jalview.bin.Console.debug("Quit selected by user");
1411 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1412 // instance.dispose();
1416 return null; // Void
1419 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1420 QuitHandler.defaultCancelQuit);
1424 * Don't call this directly, use desktopQuit() above. Exits the program.
1429 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1430 // not run a second time if gotQuitResponse flag has been set (i.e. user
1431 // confirmed quit of some kind).
1435 private void storeLastKnownDimensions(String string, Rectangle jc)
1437 jalview.bin.Console.debug("Storing last known dimensions for " + string
1438 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1439 + " height:" + jc.height);
1441 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1442 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1443 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1444 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1454 public void aboutMenuItem_actionPerformed(ActionEvent e)
1456 new Thread(new Runnable()
1461 new SplashScreen(false);
1467 * Returns the html text for the About screen, including any available version
1468 * number, build details, author details and citation reference, but without
1469 * the enclosing {@code html} tags
1473 public String getAboutMessage()
1475 StringBuilder message = new StringBuilder(1024);
1476 message.append("<div style=\"font-family: sans-serif;\">")
1477 .append("<h1><strong>Version: ")
1478 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1479 .append("<strong>Built: <em>")
1480 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1481 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1482 .append("</strong>");
1484 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1485 if (latestVersion.equals("Checking"))
1487 // JBP removed this message for 2.11: May be reinstated in future version
1488 // message.append("<br>...Checking latest version...</br>");
1490 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1492 boolean red = false;
1493 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1494 .indexOf("automated build") == -1)
1497 // Displayed when code version and jnlp version do not match and code
1498 // version is not a development build
1499 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1502 message.append("<br>!! Version ")
1503 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1504 .append(" is available for download from ")
1505 .append(Cache.getDefault("www.jalview.org",
1506 "https://www.jalview.org"))
1510 message.append("</div>");
1513 message.append("<br>Authors: ");
1514 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1515 message.append(CITATION);
1517 message.append("</div>");
1519 return message.toString();
1523 * Action on requesting Help documentation
1526 public void documentationMenuItem_actionPerformed()
1530 if (Platform.isJS())
1532 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1541 Help.showHelpWindow();
1543 } catch (Exception ex)
1545 System.err.println("Error opening help: " + ex.getMessage());
1550 public void closeAll_actionPerformed(ActionEvent e)
1552 // TODO show a progress bar while closing?
1553 JInternalFrame[] frames = desktop.getAllFrames();
1554 boolean quitting = QuitHandler.quitting();
1555 for (int i = 0; i < frames.length; i++)
1559 frames[i].setClosed(true);
1560 // check for cancelled quit
1561 if (quitting && QuitHandler.quitCancelled())
1565 } catch (java.beans.PropertyVetoException ex)
1569 Jalview.setCurrentAlignFrame(null);
1570 System.out.println("ALL CLOSED");
1573 * reset state of singleton objects as appropriate (clear down session state
1574 * when all windows are closed)
1576 StructureSelectionManager ssm = StructureSelectionManager
1577 .getStructureSelectionManager(this);
1585 public void raiseRelated_actionPerformed(ActionEvent e)
1587 reorderAssociatedWindows(false, false);
1591 public void minimizeAssociated_actionPerformed(ActionEvent e)
1593 reorderAssociatedWindows(true, false);
1596 void closeAssociatedWindows()
1598 reorderAssociatedWindows(false, true);
1604 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1608 protected void garbageCollect_actionPerformed(ActionEvent e)
1610 // We simply collect the garbage
1611 jalview.bin.Console.debug("Collecting garbage...");
1613 jalview.bin.Console.debug("Finished garbage collection.");
1619 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1623 protected void showMemusage_actionPerformed(ActionEvent e)
1625 desktop.showMemoryUsage(showMemusage.isSelected());
1632 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1636 protected void showConsole_actionPerformed(ActionEvent e)
1638 showConsole(showConsole.isSelected());
1641 Console jconsole = null;
1644 * control whether the java console is visible or not
1648 void showConsole(boolean selected)
1650 // TODO: decide if we should update properties file
1651 if (jconsole != null) // BH 2018
1653 showConsole.setSelected(selected);
1654 Cache.setProperty("SHOW_JAVA_CONSOLE",
1655 Boolean.valueOf(selected).toString());
1656 jconsole.setVisible(selected);
1660 void reorderAssociatedWindows(boolean minimize, boolean close)
1662 JInternalFrame[] frames = desktop.getAllFrames();
1663 if (frames == null || frames.length < 1)
1668 AlignmentViewport source = null, target = null;
1669 if (frames[0] instanceof AlignFrame)
1671 source = ((AlignFrame) frames[0]).getCurrentView();
1673 else if (frames[0] instanceof TreePanel)
1675 source = ((TreePanel) frames[0]).getViewPort();
1677 else if (frames[0] instanceof PCAPanel)
1679 source = ((PCAPanel) frames[0]).av;
1681 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1683 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1688 for (int i = 0; i < frames.length; i++)
1691 if (frames[i] == null)
1695 if (frames[i] instanceof AlignFrame)
1697 target = ((AlignFrame) frames[i]).getCurrentView();
1699 else if (frames[i] instanceof TreePanel)
1701 target = ((TreePanel) frames[i]).getViewPort();
1703 else if (frames[i] instanceof PCAPanel)
1705 target = ((PCAPanel) frames[i]).av;
1707 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1709 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1712 if (source == target)
1718 frames[i].setClosed(true);
1722 frames[i].setIcon(minimize);
1725 frames[i].toFront();
1729 } catch (java.beans.PropertyVetoException ex)
1744 protected void preferences_actionPerformed(ActionEvent e)
1746 Preferences.openPreferences();
1750 * Prompts the user to choose a file and then saves the Jalview state as a
1751 * Jalview project file
1754 public void saveState_actionPerformed()
1756 saveState_actionPerformed(false);
1759 public void saveState_actionPerformed(boolean saveAs)
1761 java.io.File projectFile = getProjectFile();
1762 // autoSave indicates we already have a file and don't need to ask
1763 boolean autoSave = projectFile != null && !saveAs
1764 && BackupFiles.getEnabled();
1766 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1767 // saveAs="+saveAs+", Backups
1768 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1770 boolean approveSave = false;
1773 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1776 chooser.setFileView(new JalviewFileView());
1777 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1779 int value = chooser.showSaveDialog(this);
1781 if (value == JalviewFileChooser.APPROVE_OPTION)
1783 projectFile = chooser.getSelectedFile();
1784 setProjectFile(projectFile);
1789 if (approveSave || autoSave)
1791 final Desktop me = this;
1792 final java.io.File chosenFile = projectFile;
1793 new Thread(new Runnable()
1798 // TODO: refactor to Jalview desktop session controller action.
1799 setProgressBar(MessageManager.formatMessage(
1800 "label.saving_jalview_project", new Object[]
1801 { chosenFile.getName() }), chosenFile.hashCode());
1802 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1803 // TODO catch and handle errors for savestate
1804 // TODO prevent user from messing with the Desktop whilst we're saving
1807 boolean doBackup = BackupFiles.getEnabled();
1808 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1811 new Jalview2XML().saveState(
1812 doBackup ? backupfiles.getTempFile() : chosenFile);
1816 backupfiles.setWriteSuccess(true);
1817 backupfiles.rollBackupsAndRenameTempFile();
1819 } catch (OutOfMemoryError oom)
1821 new OOMWarning("Whilst saving current state to "
1822 + chosenFile.getName(), oom);
1823 } catch (Exception ex)
1825 jalview.bin.Console.error("Problems whilst trying to save to "
1826 + chosenFile.getName(), ex);
1827 JvOptionPane.showMessageDialog(me,
1828 MessageManager.formatMessage(
1829 "label.error_whilst_saving_current_state_to",
1831 { chosenFile.getName() }),
1832 MessageManager.getString("label.couldnt_save_project"),
1833 JvOptionPane.WARNING_MESSAGE);
1835 setProgressBar(null, chosenFile.hashCode());
1842 public void saveAsState_actionPerformed(ActionEvent e)
1844 saveState_actionPerformed(true);
1847 protected void setProjectFile(File choice)
1849 this.projectFile = choice;
1852 public File getProjectFile()
1854 return this.projectFile;
1858 * Shows a file chooser dialog and tries to read in the selected file as a
1862 public void loadState_actionPerformed()
1864 final String[] suffix = new String[] { "jvp", "jar" };
1865 final String[] desc = new String[] { "Jalview Project",
1866 "Jalview Project (old)" };
1867 JalviewFileChooser chooser = new JalviewFileChooser(
1868 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1869 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1873 chooser.setFileView(new JalviewFileView());
1874 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1875 chooser.setResponseHandler(0, () -> {
1876 File selectedFile = chooser.getSelectedFile();
1877 setProjectFile(selectedFile);
1878 String choice = selectedFile.getAbsolutePath();
1879 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1880 new Thread(new Runnable()
1887 new Jalview2XML().loadJalviewAlign(selectedFile);
1888 } catch (OutOfMemoryError oom)
1890 new OOMWarning("Whilst loading project from " + choice, oom);
1891 } catch (Exception ex)
1893 jalview.bin.Console.error(
1894 "Problems whilst loading project from " + choice, ex);
1895 JvOptionPane.showMessageDialog(Desktop.desktop,
1896 MessageManager.formatMessage(
1897 "label.error_whilst_loading_project_from",
1900 MessageManager.getString("label.couldnt_load_project"),
1901 JvOptionPane.WARNING_MESSAGE);
1904 }, "Project Loader").start();
1908 chooser.showOpenDialog(this);
1912 public void inputSequence_actionPerformed(ActionEvent e)
1914 new SequenceFetcher(this);
1917 JPanel progressPanel;
1919 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1921 public void startLoading(final Object fileName)
1923 if (fileLoadingCount == 0)
1925 fileLoadingPanels.add(addProgressPanel(MessageManager
1926 .formatMessage("label.loading_file", new Object[]
1932 private JPanel addProgressPanel(String string)
1934 if (progressPanel == null)
1936 progressPanel = new JPanel(new GridLayout(1, 1));
1937 totalProgressCount = 0;
1938 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1940 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1941 JProgressBar progressBar = new JProgressBar();
1942 progressBar.setIndeterminate(true);
1944 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1946 thisprogress.add(progressBar, BorderLayout.CENTER);
1947 progressPanel.add(thisprogress);
1948 ((GridLayout) progressPanel.getLayout()).setRows(
1949 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1950 ++totalProgressCount;
1951 instance.validate();
1952 return thisprogress;
1955 int totalProgressCount = 0;
1957 private void removeProgressPanel(JPanel progbar)
1959 if (progressPanel != null)
1961 synchronized (progressPanel)
1963 progressPanel.remove(progbar);
1964 GridLayout gl = (GridLayout) progressPanel.getLayout();
1965 gl.setRows(gl.getRows() - 1);
1966 if (--totalProgressCount < 1)
1968 this.getContentPane().remove(progressPanel);
1969 progressPanel = null;
1976 public void stopLoading()
1979 if (fileLoadingCount < 1)
1981 while (fileLoadingPanels.size() > 0)
1983 removeProgressPanel(fileLoadingPanels.remove(0));
1985 fileLoadingPanels.clear();
1986 fileLoadingCount = 0;
1991 public static int getViewCount(String alignmentId)
1993 AlignmentViewport[] aps = getViewports(alignmentId);
1994 return (aps == null) ? 0 : aps.length;
1999 * @param alignmentId
2000 * - if null, all sets are returned
2001 * @return all AlignmentPanels concerning the alignmentId sequence set
2003 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
2005 if (Desktop.desktop == null)
2007 // no frames created and in headless mode
2008 // TODO: verify that frames are recoverable when in headless mode
2011 List<AlignmentPanel> aps = new ArrayList<>();
2012 AlignFrame[] frames = getAlignFrames();
2017 for (AlignFrame af : frames)
2019 for (AlignmentPanel ap : af.alignPanels)
2021 if (alignmentId == null
2022 || alignmentId.equals(ap.av.getSequenceSetId()))
2028 if (aps.size() == 0)
2032 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2037 * get all the viewports on an alignment.
2039 * @param sequenceSetId
2040 * unique alignment id (may be null - all viewports returned in that
2042 * @return all viewports on the alignment bound to sequenceSetId
2044 public static AlignmentViewport[] getViewports(String sequenceSetId)
2046 List<AlignmentViewport> viewp = new ArrayList<>();
2047 if (desktop != null)
2049 AlignFrame[] frames = Desktop.getAlignFrames();
2051 for (AlignFrame afr : frames)
2053 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2054 .equals(sequenceSetId))
2056 if (afr.alignPanels != null)
2058 for (AlignmentPanel ap : afr.alignPanels)
2060 if (sequenceSetId == null
2061 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2069 viewp.add(afr.getViewport());
2073 if (viewp.size() > 0)
2075 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2082 * Explode the views in the given frame into separate AlignFrame
2086 public static void explodeViews(AlignFrame af)
2088 int size = af.alignPanels.size();
2094 // FIXME: ideally should use UI interface API
2095 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2096 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2097 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2098 for (int i = 0; i < size; i++)
2100 AlignmentPanel ap = af.alignPanels.get(i);
2102 AlignFrame newaf = new AlignFrame(ap);
2104 // transfer reference for existing feature settings to new alignFrame
2105 if (ap == af.alignPanel)
2107 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2109 newaf.featureSettings = viewFeatureSettings;
2111 newaf.setFeatureSettingsGeometry(fsBounds);
2115 * Restore the view's last exploded frame geometry if known. Multiple views from
2116 * one exploded frame share and restore the same (frame) position and size.
2118 Rectangle geometry = ap.av.getExplodedGeometry();
2119 if (geometry != null)
2121 newaf.setBounds(geometry);
2124 ap.av.setGatherViewsHere(false);
2126 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2127 AlignFrame.DEFAULT_HEIGHT);
2128 // and materialise a new feature settings dialog instance for the new
2130 // (closes the old as if 'OK' was pressed)
2131 if (ap == af.alignPanel && newaf.featureSettings != null
2132 && newaf.featureSettings.isOpen()
2133 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2135 newaf.showFeatureSettingsUI();
2139 af.featureSettings = null;
2140 af.alignPanels.clear();
2141 af.closeMenuItem_actionPerformed(true);
2146 * Gather expanded views (separate AlignFrame's) with the same sequence set
2147 * identifier back in to this frame as additional views, and close the
2148 * expanded views. Note the expanded frames may themselves have multiple
2149 * views. We take the lot.
2153 public void gatherViews(AlignFrame source)
2155 source.viewport.setGatherViewsHere(true);
2156 source.viewport.setExplodedGeometry(source.getBounds());
2157 JInternalFrame[] frames = desktop.getAllFrames();
2158 String viewId = source.viewport.getSequenceSetId();
2159 for (int t = 0; t < frames.length; t++)
2161 if (frames[t] instanceof AlignFrame && frames[t] != source)
2163 AlignFrame af = (AlignFrame) frames[t];
2164 boolean gatherThis = false;
2165 for (int a = 0; a < af.alignPanels.size(); a++)
2167 AlignmentPanel ap = af.alignPanels.get(a);
2168 if (viewId.equals(ap.av.getSequenceSetId()))
2171 ap.av.setGatherViewsHere(false);
2172 ap.av.setExplodedGeometry(af.getBounds());
2173 source.addAlignmentPanel(ap, false);
2179 if (af.featureSettings != null && af.featureSettings.isOpen())
2181 if (source.featureSettings == null)
2183 // preserve the feature settings geometry for this frame
2184 source.featureSettings = af.featureSettings;
2185 source.setFeatureSettingsGeometry(
2186 af.getFeatureSettingsGeometry());
2190 // close it and forget
2191 af.featureSettings.close();
2194 af.alignPanels.clear();
2195 af.closeMenuItem_actionPerformed(true);
2200 // refresh the feature setting UI for the source frame if it exists
2201 if (source.featureSettings != null && source.featureSettings.isOpen())
2203 source.showFeatureSettingsUI();
2208 public JInternalFrame[] getAllFrames()
2210 return desktop.getAllFrames();
2214 * Checks the given url to see if it gives a response indicating that the user
2215 * should be informed of a new questionnaire.
2219 public void checkForQuestionnaire(String url)
2221 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2222 // javax.swing.SwingUtilities.invokeLater(jvq);
2223 new Thread(jvq).start();
2226 public void checkURLLinks()
2228 // Thread off the URL link checker
2229 addDialogThread(new Runnable()
2234 if (Cache.getDefault("CHECKURLLINKS", true))
2236 // check what the actual links are - if it's just the default don't
2237 // bother with the warning
2238 List<String> links = Preferences.sequenceUrlLinks
2241 // only need to check links if there is one with a
2242 // SEQUENCE_ID which is not the default EMBL_EBI link
2243 ListIterator<String> li = links.listIterator();
2244 boolean check = false;
2245 List<JLabel> urls = new ArrayList<>();
2246 while (li.hasNext())
2248 String link = li.next();
2249 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2250 && !UrlConstants.isDefaultString(link))
2253 int barPos = link.indexOf("|");
2254 String urlMsg = barPos == -1 ? link
2255 : link.substring(0, barPos) + ": "
2256 + link.substring(barPos + 1);
2257 urls.add(new JLabel(urlMsg));
2265 // ask user to check in case URL links use old style tokens
2266 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2267 JPanel msgPanel = new JPanel();
2268 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2269 msgPanel.add(Box.createVerticalGlue());
2270 JLabel msg = new JLabel(MessageManager
2271 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2272 JLabel msg2 = new JLabel(MessageManager
2273 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2275 for (JLabel url : urls)
2281 final JCheckBox jcb = new JCheckBox(
2282 MessageManager.getString("label.do_not_display_again"));
2283 jcb.addActionListener(new ActionListener()
2286 public void actionPerformed(ActionEvent e)
2288 // update Cache settings for "don't show this again"
2289 boolean showWarningAgain = !jcb.isSelected();
2290 Cache.setProperty("CHECKURLLINKS",
2291 Boolean.valueOf(showWarningAgain).toString());
2296 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2298 .getString("label.SEQUENCE_ID_no_longer_used"),
2299 JvOptionPane.WARNING_MESSAGE);
2306 * Proxy class for JDesktopPane which optionally displays the current memory
2307 * usage and highlights the desktop area with a red bar if free memory runs
2312 public class MyDesktopPane extends JDesktopPane implements Runnable
2314 private static final float ONE_MB = 1048576f;
2316 boolean showMemoryUsage = false;
2320 java.text.NumberFormat df;
2322 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2325 public MyDesktopPane(boolean showMemoryUsage)
2327 showMemoryUsage(showMemoryUsage);
2330 public void showMemoryUsage(boolean showMemory)
2332 this.showMemoryUsage = showMemory;
2335 Thread worker = new Thread(this);
2341 public boolean isShowMemoryUsage()
2343 return showMemoryUsage;
2349 df = java.text.NumberFormat.getNumberInstance();
2350 df.setMaximumFractionDigits(2);
2351 runtime = Runtime.getRuntime();
2353 while (showMemoryUsage)
2357 maxMemory = runtime.maxMemory() / ONE_MB;
2358 allocatedMemory = runtime.totalMemory() / ONE_MB;
2359 freeMemory = runtime.freeMemory() / ONE_MB;
2360 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2362 percentUsage = (totalFreeMemory / maxMemory) * 100;
2364 // if (percentUsage < 20)
2366 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2368 // instance.set.setBorder(border1);
2371 // sleep after showing usage
2373 } catch (Exception ex)
2375 ex.printStackTrace();
2381 public void paintComponent(Graphics g)
2383 if (showMemoryUsage && g != null && df != null)
2385 if (percentUsage < 20)
2387 g.setColor(Color.red);
2389 FontMetrics fm = g.getFontMetrics();
2392 g.drawString(MessageManager.formatMessage("label.memory_stats",
2394 { df.format(totalFreeMemory), df.format(maxMemory),
2395 df.format(percentUsage) }),
2396 10, getHeight() - fm.getHeight());
2400 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2401 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2406 * Accessor method to quickly get all the AlignmentFrames loaded.
2408 * @return an array of AlignFrame, or null if none found
2410 public static AlignFrame[] getAlignFrames()
2412 if (Jalview.isHeadlessMode())
2414 // Desktop.desktop is null in headless mode
2415 return new AlignFrame[] { Jalview.currentAlignFrame };
2418 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2424 List<AlignFrame> avp = new ArrayList<>();
2426 for (int i = frames.length - 1; i > -1; i--)
2428 if (frames[i] instanceof AlignFrame)
2430 avp.add((AlignFrame) frames[i]);
2432 else if (frames[i] instanceof SplitFrame)
2435 * Also check for a split frame containing an AlignFrame
2437 GSplitFrame sf = (GSplitFrame) frames[i];
2438 if (sf.getTopFrame() instanceof AlignFrame)
2440 avp.add((AlignFrame) sf.getTopFrame());
2442 if (sf.getBottomFrame() instanceof AlignFrame)
2444 avp.add((AlignFrame) sf.getBottomFrame());
2448 if (avp.size() == 0)
2452 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2457 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2461 public GStructureViewer[] getJmols()
2463 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2469 List<GStructureViewer> avp = new ArrayList<>();
2471 for (int i = frames.length - 1; i > -1; i--)
2473 if (frames[i] instanceof AppJmol)
2475 GStructureViewer af = (GStructureViewer) frames[i];
2479 if (avp.size() == 0)
2483 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2488 * Add Groovy Support to Jalview
2491 public void groovyShell_actionPerformed()
2495 openGroovyConsole();
2496 } catch (Exception ex)
2498 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2499 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2501 MessageManager.getString("label.couldnt_create_groovy_shell"),
2502 MessageManager.getString("label.groovy_support_failed"),
2503 JvOptionPane.ERROR_MESSAGE);
2508 * Open the Groovy console
2510 void openGroovyConsole()
2512 if (groovyConsole == null)
2514 groovyConsole = new groovy.ui.Console();
2515 groovyConsole.setVariable("Jalview", this);
2516 groovyConsole.run();
2519 * We allow only one console at a time, so that AlignFrame menu option
2520 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2521 * enable 'Run script', when the console is opened, and the reverse when it is
2524 Window window = (Window) groovyConsole.getFrame();
2525 window.addWindowListener(new WindowAdapter()
2528 public void windowClosed(WindowEvent e)
2531 * rebind CMD-Q from Groovy Console to Jalview Quit
2534 enableExecuteGroovy(false);
2540 * show Groovy console window (after close and reopen)
2542 ((Window) groovyConsole.getFrame()).setVisible(true);
2545 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2546 * opening a second console
2548 enableExecuteGroovy(true);
2552 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2553 * binding when opened
2555 protected void addQuitHandler()
2558 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2560 .getKeyStroke(KeyEvent.VK_Q,
2561 jalview.util.ShortcutKeyMaskExWrapper
2562 .getMenuShortcutKeyMaskEx()),
2564 getRootPane().getActionMap().put("Quit", new AbstractAction()
2567 public void actionPerformed(ActionEvent e)
2575 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2578 * true if Groovy console is open
2580 public void enableExecuteGroovy(boolean enabled)
2583 * disable opening a second Groovy console (or re-enable when the console is
2586 groovyShell.setEnabled(!enabled);
2588 AlignFrame[] alignFrames = getAlignFrames();
2589 if (alignFrames != null)
2591 for (AlignFrame af : alignFrames)
2593 af.setGroovyEnabled(enabled);
2599 * Progress bars managed by the IProgressIndicator method.
2601 private Hashtable<Long, JPanel> progressBars;
2603 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2608 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2611 public void setProgressBar(String message, long id)
2613 if (progressBars == null)
2615 progressBars = new Hashtable<>();
2616 progressBarHandlers = new Hashtable<>();
2619 if (progressBars.get(Long.valueOf(id)) != null)
2621 JPanel panel = progressBars.remove(Long.valueOf(id));
2622 if (progressBarHandlers.contains(Long.valueOf(id)))
2624 progressBarHandlers.remove(Long.valueOf(id));
2626 removeProgressPanel(panel);
2630 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2637 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2638 * jalview.gui.IProgressIndicatorHandler)
2641 public void registerHandler(final long id,
2642 final IProgressIndicatorHandler handler)
2644 if (progressBarHandlers == null
2645 || !progressBars.containsKey(Long.valueOf(id)))
2647 throw new Error(MessageManager.getString(
2648 "error.call_setprogressbar_before_registering_handler"));
2650 progressBarHandlers.put(Long.valueOf(id), handler);
2651 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2652 if (handler.canCancel())
2654 JButton cancel = new JButton(
2655 MessageManager.getString("action.cancel"));
2656 final IProgressIndicator us = this;
2657 cancel.addActionListener(new ActionListener()
2661 public void actionPerformed(ActionEvent e)
2663 handler.cancelActivity(id);
2664 us.setProgressBar(MessageManager
2665 .formatMessage("label.cancelled_params", new Object[]
2666 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2670 progressPanel.add(cancel, BorderLayout.EAST);
2676 * @return true if any progress bars are still active
2679 public boolean operationInProgress()
2681 if (progressBars != null && progressBars.size() > 0)
2689 * This will return the first AlignFrame holding the given viewport instance.
2690 * It will break if there are more than one AlignFrames viewing a particular
2694 * @return alignFrame for viewport
2696 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2698 if (desktop != null)
2700 AlignmentPanel[] aps = getAlignmentPanels(
2701 viewport.getSequenceSetId());
2702 for (int panel = 0; aps != null && panel < aps.length; panel++)
2704 if (aps[panel] != null && aps[panel].av == viewport)
2706 return aps[panel].alignFrame;
2713 public VamsasApplication getVamsasApplication()
2715 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2721 * flag set if jalview GUI is being operated programmatically
2723 private boolean inBatchMode = false;
2726 * check if jalview GUI is being operated programmatically
2728 * @return inBatchMode
2730 public boolean isInBatchMode()
2736 * set flag if jalview GUI is being operated programmatically
2738 * @param inBatchMode
2740 public void setInBatchMode(boolean inBatchMode)
2742 this.inBatchMode = inBatchMode;
2746 * start service discovery and wait till it is done
2748 public void startServiceDiscovery()
2750 startServiceDiscovery(false);
2754 * start service discovery threads - blocking or non-blocking
2758 public void startServiceDiscovery(boolean blocking)
2760 startServiceDiscovery(blocking, false);
2764 * start service discovery threads
2767 * - false means call returns immediately
2768 * @param ignore_SHOW_JWS2_SERVICES_preference
2769 * - when true JABA services are discovered regardless of user's JWS2
2770 * discovery preference setting
2772 public void startServiceDiscovery(boolean blocking,
2773 boolean ignore_SHOW_JWS2_SERVICES_preference)
2775 boolean alive = true;
2776 Thread t0 = null, t1 = null, t2 = null;
2777 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2780 // todo: changesupport handlers need to be transferred
2781 if (discoverer == null)
2783 discoverer = new jalview.ws.jws1.Discoverer();
2784 // register PCS handler for desktop.
2785 discoverer.addPropertyChangeListener(changeSupport);
2787 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2788 // until we phase out completely
2789 (t0 = new Thread(discoverer)).start();
2792 if (ignore_SHOW_JWS2_SERVICES_preference
2793 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2795 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2796 .startDiscoverer(changeSupport);
2800 // TODO: do rest service discovery
2809 } catch (Exception e)
2812 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2813 || (t3 != null && t3.isAlive())
2814 || (t0 != null && t0.isAlive());
2820 * called to check if the service discovery process completed successfully.
2824 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2826 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2828 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2829 .getErrorMessages();
2832 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2834 if (serviceChangedDialog == null)
2836 // only run if we aren't already displaying one of these.
2837 addDialogThread(serviceChangedDialog = new Runnable()
2844 * JalviewDialog jd =new JalviewDialog() {
2846 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2848 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2850 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2852 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2854 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2855 * + " or mis-configured HTTP proxy settings.<br/>" +
2856 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2857 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2858 * true, true, "Web Service Configuration Problem", 450, 400);
2860 * jd.waitForInput();
2862 JvOptionPane.showConfirmDialog(Desktop.desktop,
2863 new JLabel("<html><table width=\"450\"><tr><td>"
2864 + ermsg + "</td></tr></table>"
2865 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2866 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2867 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2868 + " Tools->Preferences dialog box to change them.</p></html>"),
2869 "Web Service Configuration Problem",
2870 JvOptionPane.DEFAULT_OPTION,
2871 JvOptionPane.ERROR_MESSAGE);
2872 serviceChangedDialog = null;
2880 jalview.bin.Console.error(
2881 "Errors reported by JABA discovery service. Check web services preferences.\n"
2888 private Runnable serviceChangedDialog = null;
2891 * start a thread to open a URL in the configured browser. Pops up a warning
2892 * dialog to the user if there is an exception when calling out to the browser
2897 public static void showUrl(final String url)
2899 showUrl(url, Desktop.instance);
2903 * Like showUrl but allows progress handler to be specified
2907 * (null) or object implementing IProgressIndicator
2909 public static void showUrl(final String url,
2910 final IProgressIndicator progress)
2912 new Thread(new Runnable()
2919 if (progress != null)
2921 progress.setProgressBar(MessageManager
2922 .formatMessage("status.opening_params", new Object[]
2923 { url }), this.hashCode());
2925 jalview.util.BrowserLauncher.openURL(url);
2926 } catch (Exception ex)
2928 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2930 .getString("label.web_browser_not_found_unix"),
2931 MessageManager.getString("label.web_browser_not_found"),
2932 JvOptionPane.WARNING_MESSAGE);
2934 ex.printStackTrace();
2936 if (progress != null)
2938 progress.setProgressBar(null, this.hashCode());
2944 public static WsParamSetManager wsparamManager = null;
2946 public static ParamManager getUserParameterStore()
2948 if (wsparamManager == null)
2950 wsparamManager = new WsParamSetManager();
2952 return wsparamManager;
2956 * static hyperlink handler proxy method for use by Jalview's internal windows
2960 public static void hyperlinkUpdate(HyperlinkEvent e)
2962 if (e.getEventType() == EventType.ACTIVATED)
2967 url = e.getURL().toString();
2968 Desktop.showUrl(url);
2969 } catch (Exception x)
2974 .error("Couldn't handle string " + url + " as a URL.");
2976 // ignore any exceptions due to dud links.
2983 * single thread that handles display of dialogs to user.
2985 ExecutorService dialogExecutor = Executors.newFixedThreadPool(3);
2988 * flag indicating if dialogExecutor should try to acquire a permit
2990 private volatile boolean dialogPause = true;
2995 private java.util.concurrent.Semaphore block = new Semaphore(0);
2997 private static groovy.ui.Console groovyConsole;
3000 * add another dialog thread to the queue
3004 public void addDialogThread(final Runnable prompter)
3006 dialogExecutor.submit(new Runnable()
3016 } catch (InterruptedException x)
3020 if (instance == null)
3026 SwingUtilities.invokeAndWait(prompter);
3027 } catch (Exception q)
3029 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3036 public void startDialogQueue()
3038 // set the flag so we don't pause waiting for another permit and semaphore
3039 // the current task to begin
3040 dialogPause = false;
3045 * Outputs an image of the desktop to file in EPS format, after prompting the
3046 * user for choice of Text or Lineart character rendering (unless a preference
3047 * has been set). The file name is generated as
3050 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3054 protected void snapShotWindow_actionPerformed(ActionEvent e)
3056 // currently the menu option to do this is not shown
3059 int width = getWidth();
3060 int height = getHeight();
3062 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3063 ImageWriterI writer = new ImageWriterI()
3066 public void exportImage(Graphics g) throws Exception
3069 jalview.bin.Console.info("Successfully written snapshot to file "
3070 + of.getAbsolutePath());
3073 String title = "View of desktop";
3074 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3076 exporter.doExport(of, this, width, height, title);
3080 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3081 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3082 * and location last time the view was expanded (if any). However it does not
3083 * remember the split pane divider location - this is set to match the
3084 * 'exploding' frame.
3088 public void explodeViews(SplitFrame sf)
3090 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3091 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3092 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3094 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3096 int viewCount = topPanels.size();
3103 * Processing in reverse order works, forwards order leaves the first panels not
3104 * visible. I don't know why!
3106 for (int i = viewCount - 1; i >= 0; i--)
3109 * Make new top and bottom frames. These take over the respective AlignmentPanel
3110 * objects, including their AlignmentViewports, so the cdna/protein
3111 * relationships between the viewports is carried over to the new split frames.
3113 * explodedGeometry holds the (x, y) position of the previously exploded
3114 * SplitFrame, and the (width, height) of the AlignFrame component
3116 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3117 AlignFrame newTopFrame = new AlignFrame(topPanel);
3118 newTopFrame.setSize(oldTopFrame.getSize());
3119 newTopFrame.setVisible(true);
3120 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3121 .getExplodedGeometry();
3122 if (geometry != null)
3124 newTopFrame.setSize(geometry.getSize());
3127 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3128 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3129 newBottomFrame.setSize(oldBottomFrame.getSize());
3130 newBottomFrame.setVisible(true);
3131 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3132 .getExplodedGeometry();
3133 if (geometry != null)
3135 newBottomFrame.setSize(geometry.getSize());
3138 topPanel.av.setGatherViewsHere(false);
3139 bottomPanel.av.setGatherViewsHere(false);
3140 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3142 if (geometry != null)
3144 splitFrame.setLocation(geometry.getLocation());
3146 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3150 * Clear references to the panels (now relocated in the new SplitFrames) before
3151 * closing the old SplitFrame.
3154 bottomPanels.clear();
3159 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3160 * back into the given SplitFrame as additional views. Note that the gathered
3161 * frames may themselves have multiple views.
3165 public void gatherViews(GSplitFrame source)
3168 * special handling of explodedGeometry for a view within a SplitFrame: - it
3169 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3170 * height) of the AlignFrame component
3172 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3173 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3174 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3175 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3176 myBottomFrame.viewport
3177 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3178 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3179 myTopFrame.viewport.setGatherViewsHere(true);
3180 myBottomFrame.viewport.setGatherViewsHere(true);
3181 String topViewId = myTopFrame.viewport.getSequenceSetId();
3182 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3184 JInternalFrame[] frames = desktop.getAllFrames();
3185 for (JInternalFrame frame : frames)
3187 if (frame instanceof SplitFrame && frame != source)
3189 SplitFrame sf = (SplitFrame) frame;
3190 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3191 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3192 boolean gatherThis = false;
3193 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3195 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3196 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3197 if (topViewId.equals(topPanel.av.getSequenceSetId())
3198 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3201 topPanel.av.setGatherViewsHere(false);
3202 bottomPanel.av.setGatherViewsHere(false);
3203 topPanel.av.setExplodedGeometry(
3204 new Rectangle(sf.getLocation(), topFrame.getSize()));
3205 bottomPanel.av.setExplodedGeometry(
3206 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3207 myTopFrame.addAlignmentPanel(topPanel, false);
3208 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3214 topFrame.getAlignPanels().clear();
3215 bottomFrame.getAlignPanels().clear();
3222 * The dust settles...give focus to the tab we did this from.
3224 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3227 public static groovy.ui.Console getGroovyConsole()
3229 return groovyConsole;
3233 * handles the payload of a drag and drop event.
3235 * TODO refactor to desktop utilities class
3238 * - Data source strings extracted from the drop event
3240 * - protocol for each data source extracted from the drop event
3244 * - the payload from the drop event
3247 public static void transferFromDropTarget(List<Object> files,
3248 List<DataSourceType> protocols, DropTargetDropEvent evt,
3249 Transferable t) throws Exception
3252 DataFlavor uriListFlavor = new DataFlavor(
3253 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3256 urlFlavour = new DataFlavor(
3257 "application/x-java-url; class=java.net.URL");
3258 } catch (ClassNotFoundException cfe)
3260 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3264 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3269 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3270 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3271 // means url may be null.
3274 protocols.add(DataSourceType.URL);
3275 files.add(url.toString());
3276 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3277 + files.get(files.size() - 1));
3282 if (Platform.isAMacAndNotJS())
3285 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3288 } catch (Throwable ex)
3290 jalview.bin.Console.debug("URL drop handler failed.", ex);
3293 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3295 // Works on Windows and MacOSX
3296 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3297 for (Object file : (List) t
3298 .getTransferData(DataFlavor.javaFileListFlavor))
3301 protocols.add(DataSourceType.FILE);
3306 // Unix like behaviour
3307 boolean added = false;
3309 if (t.isDataFlavorSupported(uriListFlavor))
3311 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3312 // This is used by Unix drag system
3313 data = (String) t.getTransferData(uriListFlavor);
3317 // fallback to text: workaround - on OSX where there's a JVM bug
3319 .debug("standard URIListFlavor failed. Trying text");
3320 // try text fallback
3321 DataFlavor textDf = new DataFlavor(
3322 "text/plain;class=java.lang.String");
3323 if (t.isDataFlavorSupported(textDf))
3325 data = (String) t.getTransferData(textDf);
3328 jalview.bin.Console.debug("Plain text drop content returned "
3329 + (data == null ? "Null - failed" : data));
3334 while (protocols.size() < files.size())
3336 jalview.bin.Console.debug("Adding missing FILE protocol for "
3337 + files.get(protocols.size()));
3338 protocols.add(DataSourceType.FILE);
3340 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3341 data, "\r\n"); st.hasMoreTokens();)
3344 String s = st.nextToken();
3345 if (s.startsWith("#"))
3347 // the line is a comment (as per the RFC 2483)
3350 java.net.URI uri = new java.net.URI(s);
3351 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3353 protocols.add(DataSourceType.URL);
3354 files.add(uri.toString());
3358 // otherwise preserve old behaviour: catch all for file objects
3359 java.io.File file = new java.io.File(uri);
3360 protocols.add(DataSourceType.FILE);
3361 files.add(file.toString());
3366 if (jalview.bin.Console.isDebugEnabled())
3368 if (data == null || !added)
3371 if (t.getTransferDataFlavors() != null
3372 && t.getTransferDataFlavors().length > 0)
3374 jalview.bin.Console.debug(
3375 "Couldn't resolve drop data. Here are the supported flavors:");
3376 for (DataFlavor fl : t.getTransferDataFlavors())
3378 jalview.bin.Console.debug(
3379 "Supported transfer dataflavor: " + fl.toString());
3380 Object df = t.getTransferData(fl);
3383 jalview.bin.Console.debug("Retrieves: " + df);
3387 jalview.bin.Console.debug("Retrieved nothing");
3394 .debug("Couldn't resolve dataflavor for drop: "
3400 if (Platform.isWindowsAndNotJS())
3403 .debug("Scanning dropped content for Windows Link Files");
3405 // resolve any .lnk files in the file drop
3406 for (int f = 0; f < files.size(); f++)
3408 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3409 if (protocols.get(f).equals(DataSourceType.FILE)
3410 && (source.endsWith(".lnk") || source.endsWith(".url")
3411 || source.endsWith(".site")))
3415 Object obj = files.get(f);
3416 File lf = (obj instanceof File ? (File) obj
3417 : new File((String) obj));
3418 // process link file to get a URL
3419 jalview.bin.Console.debug("Found potential link file: " + lf);
3420 WindowsShortcut wscfile = new WindowsShortcut(lf);
3421 String fullname = wscfile.getRealFilename();
3422 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3423 files.set(f, fullname);
3424 jalview.bin.Console.debug("Parsed real filename " + fullname
3425 + " to extract protocol: " + protocols.get(f));
3426 } catch (Exception ex)
3428 jalview.bin.Console.error(
3429 "Couldn't parse " + files.get(f) + " as a link file.",
3438 * Sets the Preferences property for experimental features to True or False
3439 * depending on the state of the controlling menu item
3442 protected void showExperimental_actionPerformed(boolean selected)
3444 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3448 * Answers a (possibly empty) list of any structure viewer frames (currently
3449 * for either Jmol or Chimera) which are currently open. This may optionally
3450 * be restricted to viewers of a specified class, or viewers linked to a
3451 * specified alignment panel.
3454 * if not null, only return viewers linked to this panel
3455 * @param structureViewerClass
3456 * if not null, only return viewers of this class
3459 public List<StructureViewerBase> getStructureViewers(
3460 AlignmentPanel apanel,
3461 Class<? extends StructureViewerBase> structureViewerClass)
3463 List<StructureViewerBase> result = new ArrayList<>();
3464 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3466 for (JInternalFrame frame : frames)
3468 if (frame instanceof StructureViewerBase)
3470 if (structureViewerClass == null
3471 || structureViewerClass.isInstance(frame))
3474 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3476 result.add((StructureViewerBase) frame);
3484 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3486 private static boolean debugScaleMessageDone = false;
3488 public static void debugScaleMessage(Graphics g)
3490 if (debugScaleMessageDone)
3494 // output used by tests to check HiDPI scaling settings in action
3497 Graphics2D gg = (Graphics2D) g;
3500 AffineTransform t = gg.getTransform();
3501 double scaleX = t.getScaleX();
3502 double scaleY = t.getScaleY();
3503 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3504 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3505 debugScaleMessageDone = true;
3509 jalview.bin.Console.debug("Desktop graphics null");
3511 } catch (Exception e)
3513 jalview.bin.Console.debug(Cache.getStackTraceString(e));