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(
506 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
508 : Platform.isAMacAndNotJS()
509 ? new AquaInternalFrameManager(
510 desktop.getDesktopManager())
511 : desktop.getDesktopManager())));
513 new DefaultDesktopManager()));
515 Rectangle dims = getLastKnownDimensions("");
522 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
523 int xPos = Math.max(5, (screenSize.width - 900) / 2);
524 int yPos = Math.max(5, (screenSize.height - 650) / 2);
525 setBounds(xPos, yPos, 900, 650);
528 if (!Platform.isJS())
535 jconsole = new Console(this, showjconsole);
536 jconsole.setHeader(Cache.getVersionDetailsForConsole());
537 showConsole(showjconsole);
539 showNews.setVisible(false);
541 experimentalFeatures.setSelected(showExperimental());
543 getIdentifiersOrgData();
547 // Spawn a thread that shows the splashscreen
550 SwingUtilities.invokeLater(new Runnable()
555 new SplashScreen(true);
560 // Thread off a new instance of the file chooser - this reduces the time
562 // takes to open it later on.
563 new Thread(new Runnable()
568 jalview.bin.Console.debug("Filechooser init thread started.");
569 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
570 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
572 jalview.bin.Console.debug("Filechooser init thread finished.");
575 // Add the service change listener
576 changeSupport.addJalviewPropertyChangeListener("services",
577 new PropertyChangeListener()
581 public void propertyChange(PropertyChangeEvent evt)
584 .debug("Firing service changed event for "
585 + evt.getNewValue());
586 JalviewServicesChanged(evt);
591 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
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, () -> {
1194 File selectedFile = chooser.getSelectedFile();
1195 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1197 FileFormatI format = chooser.getSelectedFormat();
1200 * Call IdentifyFile to verify the file contains what its extension implies.
1201 * Skip this step for dynamically added file formats, because IdentifyFile does
1202 * not know how to recognise them.
1204 if (FileFormats.getInstance().isIdentifiable(format))
1208 format = new IdentifyFile().identify(selectedFile,
1209 DataSourceType.FILE);
1210 } catch (FileFormatException e)
1212 // format = null; //??
1216 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1220 chooser.showOpenDialog(this);
1224 * Shows a dialog for input of a URL at which to retrieve alignment data
1229 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1231 // This construct allows us to have a wider textfield
1233 JLabel label = new JLabel(
1234 MessageManager.getString("label.input_file_url"));
1236 JPanel panel = new JPanel(new GridLayout(2, 1));
1240 * the URL to fetch is input in Java: an editable combobox with history JS:
1241 * (pending JAL-3038) a plain text field
1244 String urlBase = "https://www.";
1245 if (Platform.isJS())
1247 history = new JTextField(urlBase, 35);
1256 JComboBox<String> asCombo = new JComboBox<>();
1257 asCombo.setPreferredSize(new Dimension(400, 20));
1258 asCombo.setEditable(true);
1259 asCombo.addItem(urlBase);
1260 String historyItems = Cache.getProperty("RECENT_URL");
1261 if (historyItems != null)
1263 for (String token : historyItems.split("\\t"))
1265 asCombo.addItem(token);
1272 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1273 MessageManager.getString("action.cancel") };
1274 Callable<Void> action = () -> {
1275 @SuppressWarnings("unchecked")
1276 String url = (history instanceof JTextField
1277 ? ((JTextField) history).getText()
1278 : ((JComboBox<String>) history).getEditor().getItem()
1279 .toString().trim());
1281 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1283 if (viewport != null)
1285 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1286 FileFormat.Jalview);
1290 new FileLoader().LoadFile(url, DataSourceType.URL,
1291 FileFormat.Jalview);
1296 FileFormatI format = null;
1299 format = new IdentifyFile().identify(url, DataSourceType.URL);
1300 } catch (FileFormatException e)
1302 // TODO revise error handling, distinguish between
1303 // URL not found and response not valid
1308 String msg = MessageManager.formatMessage("label.couldnt_locate",
1310 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1311 MessageManager.getString("label.url_not_found"),
1312 JvOptionPane.WARNING_MESSAGE);
1314 return null; // Void
1317 if (viewport != null)
1319 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1324 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1327 return null; // Void
1329 String dialogOption = MessageManager
1330 .getString("label.input_alignment_from_url");
1331 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1332 .showInternalDialog(panel, dialogOption,
1333 JvOptionPane.YES_NO_CANCEL_OPTION,
1334 JvOptionPane.PLAIN_MESSAGE, null, options,
1335 MessageManager.getString("action.ok"));
1339 * Opens the CutAndPaste window for the user to paste an alignment in to
1342 * - if not null, the pasted alignment is added to the current
1343 * alignment; if null, to a new alignment window
1346 public void inputTextboxMenuItem_actionPerformed(
1347 AlignmentViewPanel viewPanel)
1349 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1350 cap.setForInput(viewPanel);
1351 Desktop.addInternalFrame(cap,
1352 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1357 * Check with user and saving files before actually quitting
1359 public void desktopQuit()
1361 desktopQuit(true, false);
1364 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1366 final Callable<Void> doDesktopQuit = () -> {
1367 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1368 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1369 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1370 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1371 getBounds().y, getWidth(), getHeight()));
1373 if (jconsole != null)
1375 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1376 jconsole.stopConsole();
1381 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1385 if (dialogExecutor != null)
1387 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();
1399 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1401 // note that shutdown hook will not be run
1402 jalview.bin.Console.debug("Force Quit selected by user");
1403 Runtime.getRuntime().halt(0);
1406 jalview.bin.Console.debug("Quit selected by user");
1409 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1410 // instance.dispose();
1414 return null; // Void
1417 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1418 QuitHandler.defaultCancelQuit);
1422 * Don't call this directly, use desktopQuit() above. Exits the program.
1427 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1428 // not run a second time if gotQuitResponse flag has been set (i.e. user
1429 // confirmed quit of some kind).
1433 private void storeLastKnownDimensions(String string, Rectangle jc)
1435 jalview.bin.Console.debug("Storing last known dimensions for " + string
1436 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1437 + " height:" + jc.height);
1439 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1440 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1441 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1442 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1452 public void aboutMenuItem_actionPerformed(ActionEvent e)
1454 new Thread(new Runnable()
1459 new SplashScreen(false);
1465 * Returns the html text for the About screen, including any available version
1466 * number, build details, author details and citation reference, but without
1467 * the enclosing {@code html} tags
1471 public String getAboutMessage()
1473 StringBuilder message = new StringBuilder(1024);
1474 message.append("<div style=\"font-family: sans-serif;\">")
1475 .append("<h1><strong>Version: ")
1476 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1477 .append("<strong>Built: <em>")
1478 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1479 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1480 .append("</strong>");
1482 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1483 if (latestVersion.equals("Checking"))
1485 // JBP removed this message for 2.11: May be reinstated in future version
1486 // message.append("<br>...Checking latest version...</br>");
1488 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1490 boolean red = false;
1491 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1492 .indexOf("automated build") == -1)
1495 // Displayed when code version and jnlp version do not match and code
1496 // version is not a development build
1497 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1500 message.append("<br>!! Version ")
1501 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1502 .append(" is available for download from ")
1503 .append(Cache.getDefault("www.jalview.org",
1504 "https://www.jalview.org"))
1508 message.append("</div>");
1511 message.append("<br>Authors: ");
1512 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1513 message.append(CITATION);
1515 message.append("</div>");
1517 return message.toString();
1521 * Action on requesting Help documentation
1524 public void documentationMenuItem_actionPerformed()
1528 if (Platform.isJS())
1530 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1539 Help.showHelpWindow();
1541 } catch (Exception ex)
1543 System.err.println("Error opening help: " + ex.getMessage());
1548 public void closeAll_actionPerformed(ActionEvent e)
1550 // TODO show a progress bar while closing?
1551 JInternalFrame[] frames = desktop.getAllFrames();
1552 for (int i = 0; i < frames.length; i++)
1556 frames[i].setClosed(true);
1557 } catch (java.beans.PropertyVetoException ex)
1561 Jalview.setCurrentAlignFrame(null);
1562 System.out.println("ALL CLOSED");
1565 * reset state of singleton objects as appropriate (clear down session state
1566 * when all windows are closed)
1568 StructureSelectionManager ssm = StructureSelectionManager
1569 .getStructureSelectionManager(this);
1577 public void raiseRelated_actionPerformed(ActionEvent e)
1579 reorderAssociatedWindows(false, false);
1583 public void minimizeAssociated_actionPerformed(ActionEvent e)
1585 reorderAssociatedWindows(true, false);
1588 void closeAssociatedWindows()
1590 reorderAssociatedWindows(false, true);
1596 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1600 protected void garbageCollect_actionPerformed(ActionEvent e)
1602 // We simply collect the garbage
1603 jalview.bin.Console.debug("Collecting garbage...");
1605 jalview.bin.Console.debug("Finished garbage collection.");
1611 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1615 protected void showMemusage_actionPerformed(ActionEvent e)
1617 desktop.showMemoryUsage(showMemusage.isSelected());
1624 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1628 protected void showConsole_actionPerformed(ActionEvent e)
1630 showConsole(showConsole.isSelected());
1633 Console jconsole = null;
1636 * control whether the java console is visible or not
1640 void showConsole(boolean selected)
1642 // TODO: decide if we should update properties file
1643 if (jconsole != null) // BH 2018
1645 showConsole.setSelected(selected);
1646 Cache.setProperty("SHOW_JAVA_CONSOLE",
1647 Boolean.valueOf(selected).toString());
1648 jconsole.setVisible(selected);
1652 void reorderAssociatedWindows(boolean minimize, boolean close)
1654 JInternalFrame[] frames = desktop.getAllFrames();
1655 if (frames == null || frames.length < 1)
1660 AlignmentViewport source = null, target = null;
1661 if (frames[0] instanceof AlignFrame)
1663 source = ((AlignFrame) frames[0]).getCurrentView();
1665 else if (frames[0] instanceof TreePanel)
1667 source = ((TreePanel) frames[0]).getViewPort();
1669 else if (frames[0] instanceof PCAPanel)
1671 source = ((PCAPanel) frames[0]).av;
1673 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1675 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1680 for (int i = 0; i < frames.length; i++)
1683 if (frames[i] == null)
1687 if (frames[i] instanceof AlignFrame)
1689 target = ((AlignFrame) frames[i]).getCurrentView();
1691 else if (frames[i] instanceof TreePanel)
1693 target = ((TreePanel) frames[i]).getViewPort();
1695 else if (frames[i] instanceof PCAPanel)
1697 target = ((PCAPanel) frames[i]).av;
1699 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1701 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1704 if (source == target)
1710 frames[i].setClosed(true);
1714 frames[i].setIcon(minimize);
1717 frames[i].toFront();
1721 } catch (java.beans.PropertyVetoException ex)
1736 protected void preferences_actionPerformed(ActionEvent e)
1738 Preferences.openPreferences();
1742 * Prompts the user to choose a file and then saves the Jalview state as a
1743 * Jalview project file
1746 public void saveState_actionPerformed()
1748 saveState_actionPerformed(false);
1751 public void saveState_actionPerformed(boolean saveAs)
1753 java.io.File projectFile = getProjectFile();
1754 // autoSave indicates we already have a file and don't need to ask
1755 boolean autoSave = projectFile != null && !saveAs
1756 && BackupFiles.getEnabled();
1758 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1759 // saveAs="+saveAs+", Backups
1760 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1762 boolean approveSave = false;
1765 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1768 chooser.setFileView(new JalviewFileView());
1769 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1771 int value = chooser.showSaveDialog(this);
1773 if (value == JalviewFileChooser.APPROVE_OPTION)
1775 projectFile = chooser.getSelectedFile();
1776 setProjectFile(projectFile);
1781 if (approveSave || autoSave)
1783 final Desktop me = this;
1784 final java.io.File chosenFile = projectFile;
1785 new Thread(new Runnable()
1790 // TODO: refactor to Jalview desktop session controller action.
1791 setProgressBar(MessageManager.formatMessage(
1792 "label.saving_jalview_project", new Object[]
1793 { chosenFile.getName() }), chosenFile.hashCode());
1794 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1795 // TODO catch and handle errors for savestate
1796 // TODO prevent user from messing with the Desktop whilst we're saving
1799 boolean doBackup = BackupFiles.getEnabled();
1800 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1803 new Jalview2XML().saveState(
1804 doBackup ? backupfiles.getTempFile() : chosenFile);
1808 backupfiles.setWriteSuccess(true);
1809 backupfiles.rollBackupsAndRenameTempFile();
1811 } catch (OutOfMemoryError oom)
1813 new OOMWarning("Whilst saving current state to "
1814 + chosenFile.getName(), oom);
1815 } catch (Exception ex)
1817 jalview.bin.Console.error("Problems whilst trying to save to "
1818 + chosenFile.getName(), ex);
1819 JvOptionPane.showMessageDialog(me,
1820 MessageManager.formatMessage(
1821 "label.error_whilst_saving_current_state_to",
1823 { chosenFile.getName() }),
1824 MessageManager.getString("label.couldnt_save_project"),
1825 JvOptionPane.WARNING_MESSAGE);
1827 setProgressBar(null, chosenFile.hashCode());
1834 public void saveAsState_actionPerformed(ActionEvent e)
1836 saveState_actionPerformed(true);
1839 protected void setProjectFile(File choice)
1841 this.projectFile = choice;
1844 public File getProjectFile()
1846 return this.projectFile;
1850 * Shows a file chooser dialog and tries to read in the selected file as a
1854 public void loadState_actionPerformed()
1856 final String[] suffix = new String[] { "jvp", "jar" };
1857 final String[] desc = new String[] { "Jalview Project",
1858 "Jalview Project (old)" };
1859 JalviewFileChooser chooser = new JalviewFileChooser(
1860 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1861 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1865 chooser.setFileView(new JalviewFileView());
1866 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1867 chooser.setResponseHandler(0, () -> {
1868 File selectedFile = chooser.getSelectedFile();
1869 setProjectFile(selectedFile);
1870 String choice = selectedFile.getAbsolutePath();
1871 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1872 new Thread(new Runnable()
1879 new Jalview2XML().loadJalviewAlign(selectedFile);
1880 } catch (OutOfMemoryError oom)
1882 new OOMWarning("Whilst loading project from " + choice, oom);
1883 } catch (Exception ex)
1885 jalview.bin.Console.error(
1886 "Problems whilst loading project from " + choice, ex);
1887 JvOptionPane.showMessageDialog(Desktop.desktop,
1888 MessageManager.formatMessage(
1889 "label.error_whilst_loading_project_from",
1892 MessageManager.getString("label.couldnt_load_project"),
1893 JvOptionPane.WARNING_MESSAGE);
1896 }, "Project Loader").start();
1900 chooser.showOpenDialog(this);
1904 public void inputSequence_actionPerformed(ActionEvent e)
1906 new SequenceFetcher(this);
1909 JPanel progressPanel;
1911 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1913 public void startLoading(final Object fileName)
1915 if (fileLoadingCount == 0)
1917 fileLoadingPanels.add(addProgressPanel(MessageManager
1918 .formatMessage("label.loading_file", new Object[]
1924 private JPanel addProgressPanel(String string)
1926 if (progressPanel == null)
1928 progressPanel = new JPanel(new GridLayout(1, 1));
1929 totalProgressCount = 0;
1930 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1932 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1933 JProgressBar progressBar = new JProgressBar();
1934 progressBar.setIndeterminate(true);
1936 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1938 thisprogress.add(progressBar, BorderLayout.CENTER);
1939 progressPanel.add(thisprogress);
1940 ((GridLayout) progressPanel.getLayout()).setRows(
1941 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1942 ++totalProgressCount;
1943 instance.validate();
1944 return thisprogress;
1947 int totalProgressCount = 0;
1949 private void removeProgressPanel(JPanel progbar)
1951 if (progressPanel != null)
1953 synchronized (progressPanel)
1955 progressPanel.remove(progbar);
1956 GridLayout gl = (GridLayout) progressPanel.getLayout();
1957 gl.setRows(gl.getRows() - 1);
1958 if (--totalProgressCount < 1)
1960 this.getContentPane().remove(progressPanel);
1961 progressPanel = null;
1968 public void stopLoading()
1971 if (fileLoadingCount < 1)
1973 while (fileLoadingPanels.size() > 0)
1975 removeProgressPanel(fileLoadingPanels.remove(0));
1977 fileLoadingPanels.clear();
1978 fileLoadingCount = 0;
1983 public static int getViewCount(String alignmentId)
1985 AlignmentViewport[] aps = getViewports(alignmentId);
1986 return (aps == null) ? 0 : aps.length;
1991 * @param alignmentId
1992 * - if null, all sets are returned
1993 * @return all AlignmentPanels concerning the alignmentId sequence set
1995 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1997 if (Desktop.desktop == null)
1999 // no frames created and in headless mode
2000 // TODO: verify that frames are recoverable when in headless mode
2003 List<AlignmentPanel> aps = new ArrayList<>();
2004 AlignFrame[] frames = getAlignFrames();
2009 for (AlignFrame af : frames)
2011 for (AlignmentPanel ap : af.alignPanels)
2013 if (alignmentId == null
2014 || alignmentId.equals(ap.av.getSequenceSetId()))
2020 if (aps.size() == 0)
2024 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2029 * get all the viewports on an alignment.
2031 * @param sequenceSetId
2032 * unique alignment id (may be null - all viewports returned in that
2034 * @return all viewports on the alignment bound to sequenceSetId
2036 public static AlignmentViewport[] getViewports(String sequenceSetId)
2038 List<AlignmentViewport> viewp = new ArrayList<>();
2039 if (desktop != null)
2041 AlignFrame[] frames = Desktop.getAlignFrames();
2043 for (AlignFrame afr : frames)
2045 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2046 .equals(sequenceSetId))
2048 if (afr.alignPanels != null)
2050 for (AlignmentPanel ap : afr.alignPanels)
2052 if (sequenceSetId == null
2053 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2061 viewp.add(afr.getViewport());
2065 if (viewp.size() > 0)
2067 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2074 * Explode the views in the given frame into separate AlignFrame
2078 public static void explodeViews(AlignFrame af)
2080 int size = af.alignPanels.size();
2086 // FIXME: ideally should use UI interface API
2087 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2088 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2089 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2090 for (int i = 0; i < size; i++)
2092 AlignmentPanel ap = af.alignPanels.get(i);
2094 AlignFrame newaf = new AlignFrame(ap);
2096 // transfer reference for existing feature settings to new alignFrame
2097 if (ap == af.alignPanel)
2099 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2101 newaf.featureSettings = viewFeatureSettings;
2103 newaf.setFeatureSettingsGeometry(fsBounds);
2107 * Restore the view's last exploded frame geometry if known. Multiple views from
2108 * one exploded frame share and restore the same (frame) position and size.
2110 Rectangle geometry = ap.av.getExplodedGeometry();
2111 if (geometry != null)
2113 newaf.setBounds(geometry);
2116 ap.av.setGatherViewsHere(false);
2118 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2119 AlignFrame.DEFAULT_HEIGHT);
2120 // and materialise a new feature settings dialog instance for the new
2122 // (closes the old as if 'OK' was pressed)
2123 if (ap == af.alignPanel && newaf.featureSettings != null
2124 && newaf.featureSettings.isOpen()
2125 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2127 newaf.showFeatureSettingsUI();
2131 af.featureSettings = null;
2132 af.alignPanels.clear();
2133 af.closeMenuItem_actionPerformed(true);
2138 * Gather expanded views (separate AlignFrame's) with the same sequence set
2139 * identifier back in to this frame as additional views, and close the
2140 * expanded views. Note the expanded frames may themselves have multiple
2141 * views. We take the lot.
2145 public void gatherViews(AlignFrame source)
2147 source.viewport.setGatherViewsHere(true);
2148 source.viewport.setExplodedGeometry(source.getBounds());
2149 JInternalFrame[] frames = desktop.getAllFrames();
2150 String viewId = source.viewport.getSequenceSetId();
2151 for (int t = 0; t < frames.length; t++)
2153 if (frames[t] instanceof AlignFrame && frames[t] != source)
2155 AlignFrame af = (AlignFrame) frames[t];
2156 boolean gatherThis = false;
2157 for (int a = 0; a < af.alignPanels.size(); a++)
2159 AlignmentPanel ap = af.alignPanels.get(a);
2160 if (viewId.equals(ap.av.getSequenceSetId()))
2163 ap.av.setGatherViewsHere(false);
2164 ap.av.setExplodedGeometry(af.getBounds());
2165 source.addAlignmentPanel(ap, false);
2171 if (af.featureSettings != null && af.featureSettings.isOpen())
2173 if (source.featureSettings == null)
2175 // preserve the feature settings geometry for this frame
2176 source.featureSettings = af.featureSettings;
2177 source.setFeatureSettingsGeometry(
2178 af.getFeatureSettingsGeometry());
2182 // close it and forget
2183 af.featureSettings.close();
2186 af.alignPanels.clear();
2187 af.closeMenuItem_actionPerformed(true);
2192 // refresh the feature setting UI for the source frame if it exists
2193 if (source.featureSettings != null && source.featureSettings.isOpen())
2195 source.showFeatureSettingsUI();
2200 public JInternalFrame[] getAllFrames()
2202 return desktop.getAllFrames();
2206 * Checks the given url to see if it gives a response indicating that the user
2207 * should be informed of a new questionnaire.
2211 public void checkForQuestionnaire(String url)
2213 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2214 // javax.swing.SwingUtilities.invokeLater(jvq);
2215 new Thread(jvq).start();
2218 public void checkURLLinks()
2220 // Thread off the URL link checker
2221 addDialogThread(new Runnable()
2226 if (Cache.getDefault("CHECKURLLINKS", true))
2228 // check what the actual links are - if it's just the default don't
2229 // bother with the warning
2230 List<String> links = Preferences.sequenceUrlLinks
2233 // only need to check links if there is one with a
2234 // SEQUENCE_ID which is not the default EMBL_EBI link
2235 ListIterator<String> li = links.listIterator();
2236 boolean check = false;
2237 List<JLabel> urls = new ArrayList<>();
2238 while (li.hasNext())
2240 String link = li.next();
2241 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2242 && !UrlConstants.isDefaultString(link))
2245 int barPos = link.indexOf("|");
2246 String urlMsg = barPos == -1 ? link
2247 : link.substring(0, barPos) + ": "
2248 + link.substring(barPos + 1);
2249 urls.add(new JLabel(urlMsg));
2257 // ask user to check in case URL links use old style tokens
2258 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2259 JPanel msgPanel = new JPanel();
2260 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2261 msgPanel.add(Box.createVerticalGlue());
2262 JLabel msg = new JLabel(MessageManager
2263 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2264 JLabel msg2 = new JLabel(MessageManager
2265 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2267 for (JLabel url : urls)
2273 final JCheckBox jcb = new JCheckBox(
2274 MessageManager.getString("label.do_not_display_again"));
2275 jcb.addActionListener(new ActionListener()
2278 public void actionPerformed(ActionEvent e)
2280 // update Cache settings for "don't show this again"
2281 boolean showWarningAgain = !jcb.isSelected();
2282 Cache.setProperty("CHECKURLLINKS",
2283 Boolean.valueOf(showWarningAgain).toString());
2288 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2290 .getString("label.SEQUENCE_ID_no_longer_used"),
2291 JvOptionPane.WARNING_MESSAGE);
2298 * Proxy class for JDesktopPane which optionally displays the current memory
2299 * usage and highlights the desktop area with a red bar if free memory runs
2304 public class MyDesktopPane extends JDesktopPane implements Runnable
2306 private static final float ONE_MB = 1048576f;
2308 boolean showMemoryUsage = false;
2312 java.text.NumberFormat df;
2314 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2317 public MyDesktopPane(boolean showMemoryUsage)
2319 showMemoryUsage(showMemoryUsage);
2322 public void showMemoryUsage(boolean showMemory)
2324 this.showMemoryUsage = showMemory;
2327 Thread worker = new Thread(this);
2333 public boolean isShowMemoryUsage()
2335 return showMemoryUsage;
2341 df = java.text.NumberFormat.getNumberInstance();
2342 df.setMaximumFractionDigits(2);
2343 runtime = Runtime.getRuntime();
2345 while (showMemoryUsage)
2349 maxMemory = runtime.maxMemory() / ONE_MB;
2350 allocatedMemory = runtime.totalMemory() / ONE_MB;
2351 freeMemory = runtime.freeMemory() / ONE_MB;
2352 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2354 percentUsage = (totalFreeMemory / maxMemory) * 100;
2356 // if (percentUsage < 20)
2358 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2360 // instance.set.setBorder(border1);
2363 // sleep after showing usage
2365 } catch (Exception ex)
2367 ex.printStackTrace();
2373 public void paintComponent(Graphics g)
2375 if (showMemoryUsage && g != null && df != null)
2377 if (percentUsage < 20)
2379 g.setColor(Color.red);
2381 FontMetrics fm = g.getFontMetrics();
2384 g.drawString(MessageManager.formatMessage("label.memory_stats",
2386 { df.format(totalFreeMemory), df.format(maxMemory),
2387 df.format(percentUsage) }),
2388 10, getHeight() - fm.getHeight());
2392 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2393 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2398 * Accessor method to quickly get all the AlignmentFrames loaded.
2400 * @return an array of AlignFrame, or null if none found
2402 public static AlignFrame[] getAlignFrames()
2404 if (Jalview.isHeadlessMode())
2406 // Desktop.desktop is null in headless mode
2407 return new AlignFrame[] { Jalview.currentAlignFrame };
2410 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2416 List<AlignFrame> avp = new ArrayList<>();
2418 for (int i = frames.length - 1; i > -1; i--)
2420 if (frames[i] instanceof AlignFrame)
2422 avp.add((AlignFrame) frames[i]);
2424 else if (frames[i] instanceof SplitFrame)
2427 * Also check for a split frame containing an AlignFrame
2429 GSplitFrame sf = (GSplitFrame) frames[i];
2430 if (sf.getTopFrame() instanceof AlignFrame)
2432 avp.add((AlignFrame) sf.getTopFrame());
2434 if (sf.getBottomFrame() instanceof AlignFrame)
2436 avp.add((AlignFrame) sf.getBottomFrame());
2440 if (avp.size() == 0)
2444 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2449 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2453 public GStructureViewer[] getJmols()
2455 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2461 List<GStructureViewer> avp = new ArrayList<>();
2463 for (int i = frames.length - 1; i > -1; i--)
2465 if (frames[i] instanceof AppJmol)
2467 GStructureViewer af = (GStructureViewer) frames[i];
2471 if (avp.size() == 0)
2475 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2480 * Add Groovy Support to Jalview
2483 public void groovyShell_actionPerformed()
2487 openGroovyConsole();
2488 } catch (Exception ex)
2490 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2491 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2493 MessageManager.getString("label.couldnt_create_groovy_shell"),
2494 MessageManager.getString("label.groovy_support_failed"),
2495 JvOptionPane.ERROR_MESSAGE);
2500 * Open the Groovy console
2502 void openGroovyConsole()
2504 if (groovyConsole == null)
2506 groovyConsole = new groovy.ui.Console();
2507 groovyConsole.setVariable("Jalview", this);
2508 groovyConsole.run();
2511 * We allow only one console at a time, so that AlignFrame menu option
2512 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2513 * enable 'Run script', when the console is opened, and the reverse when it is
2516 Window window = (Window) groovyConsole.getFrame();
2517 window.addWindowListener(new WindowAdapter()
2520 public void windowClosed(WindowEvent e)
2523 * rebind CMD-Q from Groovy Console to Jalview Quit
2526 enableExecuteGroovy(false);
2532 * show Groovy console window (after close and reopen)
2534 ((Window) groovyConsole.getFrame()).setVisible(true);
2537 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2538 * opening a second console
2540 enableExecuteGroovy(true);
2544 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2545 * binding when opened
2547 protected void addQuitHandler()
2550 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2552 .getKeyStroke(KeyEvent.VK_Q,
2553 jalview.util.ShortcutKeyMaskExWrapper
2554 .getMenuShortcutKeyMaskEx()),
2556 getRootPane().getActionMap().put("Quit", new AbstractAction()
2559 public void actionPerformed(ActionEvent e)
2567 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2570 * true if Groovy console is open
2572 public void enableExecuteGroovy(boolean enabled)
2575 * disable opening a second Groovy console (or re-enable when the console is
2578 groovyShell.setEnabled(!enabled);
2580 AlignFrame[] alignFrames = getAlignFrames();
2581 if (alignFrames != null)
2583 for (AlignFrame af : alignFrames)
2585 af.setGroovyEnabled(enabled);
2591 * Progress bars managed by the IProgressIndicator method.
2593 private Hashtable<Long, JPanel> progressBars;
2595 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2600 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2603 public void setProgressBar(String message, long id)
2605 if (progressBars == null)
2607 progressBars = new Hashtable<>();
2608 progressBarHandlers = new Hashtable<>();
2611 if (progressBars.get(Long.valueOf(id)) != null)
2613 JPanel panel = progressBars.remove(Long.valueOf(id));
2614 if (progressBarHandlers.contains(Long.valueOf(id)))
2616 progressBarHandlers.remove(Long.valueOf(id));
2618 removeProgressPanel(panel);
2622 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2629 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2630 * jalview.gui.IProgressIndicatorHandler)
2633 public void registerHandler(final long id,
2634 final IProgressIndicatorHandler handler)
2636 if (progressBarHandlers == null
2637 || !progressBars.containsKey(Long.valueOf(id)))
2639 throw new Error(MessageManager.getString(
2640 "error.call_setprogressbar_before_registering_handler"));
2642 progressBarHandlers.put(Long.valueOf(id), handler);
2643 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2644 if (handler.canCancel())
2646 JButton cancel = new JButton(
2647 MessageManager.getString("action.cancel"));
2648 final IProgressIndicator us = this;
2649 cancel.addActionListener(new ActionListener()
2653 public void actionPerformed(ActionEvent e)
2655 handler.cancelActivity(id);
2656 us.setProgressBar(MessageManager
2657 .formatMessage("label.cancelled_params", new Object[]
2658 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2662 progressPanel.add(cancel, BorderLayout.EAST);
2668 * @return true if any progress bars are still active
2671 public boolean operationInProgress()
2673 if (progressBars != null && progressBars.size() > 0)
2681 * This will return the first AlignFrame holding the given viewport instance.
2682 * It will break if there are more than one AlignFrames viewing a particular
2686 * @return alignFrame for viewport
2688 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2690 if (desktop != null)
2692 AlignmentPanel[] aps = getAlignmentPanels(
2693 viewport.getSequenceSetId());
2694 for (int panel = 0; aps != null && panel < aps.length; panel++)
2696 if (aps[panel] != null && aps[panel].av == viewport)
2698 return aps[panel].alignFrame;
2705 public VamsasApplication getVamsasApplication()
2707 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2713 * flag set if jalview GUI is being operated programmatically
2715 private boolean inBatchMode = false;
2718 * check if jalview GUI is being operated programmatically
2720 * @return inBatchMode
2722 public boolean isInBatchMode()
2728 * set flag if jalview GUI is being operated programmatically
2730 * @param inBatchMode
2732 public void setInBatchMode(boolean inBatchMode)
2734 this.inBatchMode = inBatchMode;
2738 * start service discovery and wait till it is done
2740 public void startServiceDiscovery()
2742 startServiceDiscovery(false);
2746 * start service discovery threads - blocking or non-blocking
2750 public void startServiceDiscovery(boolean blocking)
2752 startServiceDiscovery(blocking, false);
2756 * start service discovery threads
2759 * - false means call returns immediately
2760 * @param ignore_SHOW_JWS2_SERVICES_preference
2761 * - when true JABA services are discovered regardless of user's JWS2
2762 * discovery preference setting
2764 public void startServiceDiscovery(boolean blocking,
2765 boolean ignore_SHOW_JWS2_SERVICES_preference)
2767 boolean alive = true;
2768 Thread t0 = null, t1 = null, t2 = null;
2769 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2772 // todo: changesupport handlers need to be transferred
2773 if (discoverer == null)
2775 discoverer = new jalview.ws.jws1.Discoverer();
2776 // register PCS handler for desktop.
2777 discoverer.addPropertyChangeListener(changeSupport);
2779 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2780 // until we phase out completely
2781 (t0 = new Thread(discoverer)).start();
2784 if (ignore_SHOW_JWS2_SERVICES_preference
2785 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2787 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2788 .startDiscoverer(changeSupport);
2792 // TODO: do rest service discovery
2801 } catch (Exception e)
2804 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2805 || (t3 != null && t3.isAlive())
2806 || (t0 != null && t0.isAlive());
2812 * called to check if the service discovery process completed successfully.
2816 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2818 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2820 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2821 .getErrorMessages();
2824 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2826 if (serviceChangedDialog == null)
2828 // only run if we aren't already displaying one of these.
2829 addDialogThread(serviceChangedDialog = new Runnable()
2836 * JalviewDialog jd =new JalviewDialog() {
2838 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2840 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2842 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2844 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2846 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2847 * + " or mis-configured HTTP proxy settings.<br/>" +
2848 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2849 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2850 * true, true, "Web Service Configuration Problem", 450, 400);
2852 * jd.waitForInput();
2854 JvOptionPane.showConfirmDialog(Desktop.desktop,
2855 new JLabel("<html><table width=\"450\"><tr><td>"
2856 + ermsg + "</td></tr></table>"
2857 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2858 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2859 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2860 + " Tools->Preferences dialog box to change them.</p></html>"),
2861 "Web Service Configuration Problem",
2862 JvOptionPane.DEFAULT_OPTION,
2863 JvOptionPane.ERROR_MESSAGE);
2864 serviceChangedDialog = null;
2872 jalview.bin.Console.error(
2873 "Errors reported by JABA discovery service. Check web services preferences.\n"
2880 private Runnable serviceChangedDialog = null;
2883 * start a thread to open a URL in the configured browser. Pops up a warning
2884 * dialog to the user if there is an exception when calling out to the browser
2889 public static void showUrl(final String url)
2891 showUrl(url, Desktop.instance);
2895 * Like showUrl but allows progress handler to be specified
2899 * (null) or object implementing IProgressIndicator
2901 public static void showUrl(final String url,
2902 final IProgressIndicator progress)
2904 new Thread(new Runnable()
2911 if (progress != null)
2913 progress.setProgressBar(MessageManager
2914 .formatMessage("status.opening_params", new Object[]
2915 { url }), this.hashCode());
2917 jalview.util.BrowserLauncher.openURL(url);
2918 } catch (Exception ex)
2920 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2922 .getString("label.web_browser_not_found_unix"),
2923 MessageManager.getString("label.web_browser_not_found"),
2924 JvOptionPane.WARNING_MESSAGE);
2926 ex.printStackTrace();
2928 if (progress != null)
2930 progress.setProgressBar(null, this.hashCode());
2936 public static WsParamSetManager wsparamManager = null;
2938 public static ParamManager getUserParameterStore()
2940 if (wsparamManager == null)
2942 wsparamManager = new WsParamSetManager();
2944 return wsparamManager;
2948 * static hyperlink handler proxy method for use by Jalview's internal windows
2952 public static void hyperlinkUpdate(HyperlinkEvent e)
2954 if (e.getEventType() == EventType.ACTIVATED)
2959 url = e.getURL().toString();
2960 Desktop.showUrl(url);
2961 } catch (Exception x)
2966 .error("Couldn't handle string " + url + " as a URL.");
2968 // ignore any exceptions due to dud links.
2975 * single thread that handles display of dialogs to user.
2977 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2980 * flag indicating if dialogExecutor should try to acquire a permit
2982 private volatile boolean dialogPause = true;
2987 private java.util.concurrent.Semaphore block = new Semaphore(0);
2989 private static groovy.ui.Console groovyConsole;
2992 * add another dialog thread to the queue
2996 public void addDialogThread(final Runnable prompter)
2998 dialogExecutor.submit(new Runnable()
3008 } catch (InterruptedException x)
3012 if (instance == null)
3018 SwingUtilities.invokeAndWait(prompter);
3019 } catch (Exception q)
3021 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3028 public void startDialogQueue()
3030 // set the flag so we don't pause waiting for another permit and semaphore
3031 // the current task to begin
3032 dialogPause = false;
3037 * Outputs an image of the desktop to file in EPS format, after prompting the
3038 * user for choice of Text or Lineart character rendering (unless a preference
3039 * has been set). The file name is generated as
3042 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3046 protected void snapShotWindow_actionPerformed(ActionEvent e)
3048 // currently the menu option to do this is not shown
3051 int width = getWidth();
3052 int height = getHeight();
3054 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3055 ImageWriterI writer = new ImageWriterI()
3058 public void exportImage(Graphics g) throws Exception
3061 jalview.bin.Console.info("Successfully written snapshot to file "
3062 + of.getAbsolutePath());
3065 String title = "View of desktop";
3066 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3068 exporter.doExport(of, this, width, height, title);
3072 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3073 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3074 * and location last time the view was expanded (if any). However it does not
3075 * remember the split pane divider location - this is set to match the
3076 * 'exploding' frame.
3080 public void explodeViews(SplitFrame sf)
3082 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3083 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3084 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3086 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3088 int viewCount = topPanels.size();
3095 * Processing in reverse order works, forwards order leaves the first panels not
3096 * visible. I don't know why!
3098 for (int i = viewCount - 1; i >= 0; i--)
3101 * Make new top and bottom frames. These take over the respective AlignmentPanel
3102 * objects, including their AlignmentViewports, so the cdna/protein
3103 * relationships between the viewports is carried over to the new split frames.
3105 * explodedGeometry holds the (x, y) position of the previously exploded
3106 * SplitFrame, and the (width, height) of the AlignFrame component
3108 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3109 AlignFrame newTopFrame = new AlignFrame(topPanel);
3110 newTopFrame.setSize(oldTopFrame.getSize());
3111 newTopFrame.setVisible(true);
3112 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3113 .getExplodedGeometry();
3114 if (geometry != null)
3116 newTopFrame.setSize(geometry.getSize());
3119 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3120 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3121 newBottomFrame.setSize(oldBottomFrame.getSize());
3122 newBottomFrame.setVisible(true);
3123 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3124 .getExplodedGeometry();
3125 if (geometry != null)
3127 newBottomFrame.setSize(geometry.getSize());
3130 topPanel.av.setGatherViewsHere(false);
3131 bottomPanel.av.setGatherViewsHere(false);
3132 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3134 if (geometry != null)
3136 splitFrame.setLocation(geometry.getLocation());
3138 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3142 * Clear references to the panels (now relocated in the new SplitFrames) before
3143 * closing the old SplitFrame.
3146 bottomPanels.clear();
3151 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3152 * back into the given SplitFrame as additional views. Note that the gathered
3153 * frames may themselves have multiple views.
3157 public void gatherViews(GSplitFrame source)
3160 * special handling of explodedGeometry for a view within a SplitFrame: - it
3161 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3162 * height) of the AlignFrame component
3164 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3165 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3166 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3167 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3168 myBottomFrame.viewport
3169 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3170 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3171 myTopFrame.viewport.setGatherViewsHere(true);
3172 myBottomFrame.viewport.setGatherViewsHere(true);
3173 String topViewId = myTopFrame.viewport.getSequenceSetId();
3174 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3176 JInternalFrame[] frames = desktop.getAllFrames();
3177 for (JInternalFrame frame : frames)
3179 if (frame instanceof SplitFrame && frame != source)
3181 SplitFrame sf = (SplitFrame) frame;
3182 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3183 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3184 boolean gatherThis = false;
3185 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3187 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3188 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3189 if (topViewId.equals(topPanel.av.getSequenceSetId())
3190 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3193 topPanel.av.setGatherViewsHere(false);
3194 bottomPanel.av.setGatherViewsHere(false);
3195 topPanel.av.setExplodedGeometry(
3196 new Rectangle(sf.getLocation(), topFrame.getSize()));
3197 bottomPanel.av.setExplodedGeometry(
3198 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3199 myTopFrame.addAlignmentPanel(topPanel, false);
3200 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3206 topFrame.getAlignPanels().clear();
3207 bottomFrame.getAlignPanels().clear();
3214 * The dust settles...give focus to the tab we did this from.
3216 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3219 public static groovy.ui.Console getGroovyConsole()
3221 return groovyConsole;
3225 * handles the payload of a drag and drop event.
3227 * TODO refactor to desktop utilities class
3230 * - Data source strings extracted from the drop event
3232 * - protocol for each data source extracted from the drop event
3236 * - the payload from the drop event
3239 public static void transferFromDropTarget(List<Object> files,
3240 List<DataSourceType> protocols, DropTargetDropEvent evt,
3241 Transferable t) throws Exception
3244 DataFlavor uriListFlavor = new DataFlavor(
3245 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3248 urlFlavour = new DataFlavor(
3249 "application/x-java-url; class=java.net.URL");
3250 } catch (ClassNotFoundException cfe)
3252 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3256 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3261 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3262 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3263 // means url may be null.
3266 protocols.add(DataSourceType.URL);
3267 files.add(url.toString());
3268 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3269 + files.get(files.size() - 1));
3274 if (Platform.isAMacAndNotJS())
3277 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3280 } catch (Throwable ex)
3282 jalview.bin.Console.debug("URL drop handler failed.", ex);
3285 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3287 // Works on Windows and MacOSX
3288 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3289 for (Object file : (List) t
3290 .getTransferData(DataFlavor.javaFileListFlavor))
3293 protocols.add(DataSourceType.FILE);
3298 // Unix like behaviour
3299 boolean added = false;
3301 if (t.isDataFlavorSupported(uriListFlavor))
3303 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3304 // This is used by Unix drag system
3305 data = (String) t.getTransferData(uriListFlavor);
3309 // fallback to text: workaround - on OSX where there's a JVM bug
3311 .debug("standard URIListFlavor failed. Trying text");
3312 // try text fallback
3313 DataFlavor textDf = new DataFlavor(
3314 "text/plain;class=java.lang.String");
3315 if (t.isDataFlavorSupported(textDf))
3317 data = (String) t.getTransferData(textDf);
3320 jalview.bin.Console.debug("Plain text drop content returned "
3321 + (data == null ? "Null - failed" : data));
3326 while (protocols.size() < files.size())
3328 jalview.bin.Console.debug("Adding missing FILE protocol for "
3329 + files.get(protocols.size()));
3330 protocols.add(DataSourceType.FILE);
3332 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3333 data, "\r\n"); st.hasMoreTokens();)
3336 String s = st.nextToken();
3337 if (s.startsWith("#"))
3339 // the line is a comment (as per the RFC 2483)
3342 java.net.URI uri = new java.net.URI(s);
3343 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3345 protocols.add(DataSourceType.URL);
3346 files.add(uri.toString());
3350 // otherwise preserve old behaviour: catch all for file objects
3351 java.io.File file = new java.io.File(uri);
3352 protocols.add(DataSourceType.FILE);
3353 files.add(file.toString());
3358 if (jalview.bin.Console.isDebugEnabled())
3360 if (data == null || !added)
3363 if (t.getTransferDataFlavors() != null
3364 && t.getTransferDataFlavors().length > 0)
3366 jalview.bin.Console.debug(
3367 "Couldn't resolve drop data. Here are the supported flavors:");
3368 for (DataFlavor fl : t.getTransferDataFlavors())
3370 jalview.bin.Console.debug(
3371 "Supported transfer dataflavor: " + fl.toString());
3372 Object df = t.getTransferData(fl);
3375 jalview.bin.Console.debug("Retrieves: " + df);
3379 jalview.bin.Console.debug("Retrieved nothing");
3386 .debug("Couldn't resolve dataflavor for drop: "
3392 if (Platform.isWindowsAndNotJS())
3395 .debug("Scanning dropped content for Windows Link Files");
3397 // resolve any .lnk files in the file drop
3398 for (int f = 0; f < files.size(); f++)
3400 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3401 if (protocols.get(f).equals(DataSourceType.FILE)
3402 && (source.endsWith(".lnk") || source.endsWith(".url")
3403 || source.endsWith(".site")))
3407 Object obj = files.get(f);
3408 File lf = (obj instanceof File ? (File) obj
3409 : new File((String) obj));
3410 // process link file to get a URL
3411 jalview.bin.Console.debug("Found potential link file: " + lf);
3412 WindowsShortcut wscfile = new WindowsShortcut(lf);
3413 String fullname = wscfile.getRealFilename();
3414 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3415 files.set(f, fullname);
3416 jalview.bin.Console.debug("Parsed real filename " + fullname
3417 + " to extract protocol: " + protocols.get(f));
3418 } catch (Exception ex)
3420 jalview.bin.Console.error(
3421 "Couldn't parse " + files.get(f) + " as a link file.",
3430 * Sets the Preferences property for experimental features to True or False
3431 * depending on the state of the controlling menu item
3434 protected void showExperimental_actionPerformed(boolean selected)
3436 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3440 * Answers a (possibly empty) list of any structure viewer frames (currently
3441 * for either Jmol or Chimera) which are currently open. This may optionally
3442 * be restricted to viewers of a specified class, or viewers linked to a
3443 * specified alignment panel.
3446 * if not null, only return viewers linked to this panel
3447 * @param structureViewerClass
3448 * if not null, only return viewers of this class
3451 public List<StructureViewerBase> getStructureViewers(
3452 AlignmentPanel apanel,
3453 Class<? extends StructureViewerBase> structureViewerClass)
3455 List<StructureViewerBase> result = new ArrayList<>();
3456 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3458 for (JInternalFrame frame : frames)
3460 if (frame instanceof StructureViewerBase)
3462 if (structureViewerClass == null
3463 || structureViewerClass.isInstance(frame))
3466 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3468 result.add((StructureViewerBase) frame);
3476 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3478 private static boolean debugScaleMessageDone = false;
3480 public static void debugScaleMessage(Graphics g)
3482 if (debugScaleMessageDone)
3486 // output used by tests to check HiDPI scaling settings in action
3489 Graphics2D gg = (Graphics2D) g;
3492 AffineTransform t = gg.getTransform();
3493 double scaleX = t.getScaleX();
3494 double scaleY = t.getScaleY();
3495 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3496 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3497 debugScaleMessageDone = true;
3501 jalview.bin.Console.debug("Desktop graphics null");
3503 } catch (Exception e)
3505 jalview.bin.Console.debug(Cache.getStackTraceString(e));