2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.util.Locale;
25 import java.awt.BorderLayout;
26 import java.awt.Color;
27 import java.awt.Dimension;
28 import java.awt.FontMetrics;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.GridLayout;
32 import java.awt.Point;
33 import java.awt.Rectangle;
34 import java.awt.Toolkit;
35 import java.awt.Window;
36 import java.awt.datatransfer.Clipboard;
37 import java.awt.datatransfer.ClipboardOwner;
38 import java.awt.datatransfer.DataFlavor;
39 import java.awt.datatransfer.Transferable;
40 import java.awt.dnd.DnDConstants;
41 import java.awt.dnd.DropTargetDragEvent;
42 import java.awt.dnd.DropTargetDropEvent;
43 import java.awt.dnd.DropTargetEvent;
44 import java.awt.dnd.DropTargetListener;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ActionListener;
47 import java.awt.event.InputEvent;
48 import java.awt.event.KeyEvent;
49 import java.awt.event.MouseAdapter;
50 import java.awt.event.MouseEvent;
51 import java.awt.event.WindowAdapter;
52 import java.awt.event.WindowEvent;
53 import java.awt.geom.AffineTransform;
54 import java.beans.PropertyChangeEvent;
55 import java.beans.PropertyChangeListener;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Vector;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Semaphore;
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.Box;
76 import javax.swing.BoxLayout;
77 import javax.swing.DefaultDesktopManager;
78 import javax.swing.DesktopManager;
79 import javax.swing.InputMap;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JComboBox;
83 import javax.swing.JComponent;
84 import javax.swing.JDesktopPane;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.event.HyperlinkEvent;
95 import javax.swing.event.HyperlinkEvent.EventType;
96 import javax.swing.event.InternalFrameAdapter;
97 import javax.swing.event.InternalFrameEvent;
99 import org.stackoverflowusers.file.WindowsShortcut;
101 import jalview.api.AlignViewportI;
102 import jalview.api.AlignmentViewPanel;
103 import jalview.bin.Cache;
104 import jalview.bin.Jalview;
105 import jalview.gui.ImageExporter.ImageWriterI;
106 import jalview.io.BackupFiles;
107 import jalview.io.DataSourceType;
108 import jalview.io.FileFormat;
109 import jalview.io.FileFormatException;
110 import jalview.io.FileFormatI;
111 import jalview.io.FileFormats;
112 import jalview.io.FileLoader;
113 import jalview.io.FormatAdapter;
114 import jalview.io.IdentifyFile;
115 import jalview.io.JalviewFileChooser;
116 import jalview.io.JalviewFileView;
117 import jalview.jbgui.GSplitFrame;
118 import jalview.jbgui.GStructureViewer;
119 import jalview.project.Jalview2XML;
120 import jalview.structure.StructureSelectionManager;
121 import jalview.urls.IdOrgSettings;
122 import jalview.util.BrowserLauncher;
123 import jalview.util.ChannelProperties;
124 import jalview.util.ImageMaker.TYPE;
125 import jalview.util.MessageManager;
126 import jalview.util.Platform;
127 import jalview.util.ShortcutKeyMaskExWrapper;
128 import jalview.util.UrlConstants;
129 import jalview.viewmodel.AlignmentViewport;
130 import jalview.ws.params.ParamManager;
131 import jalview.ws.utils.UrlDownloadClient;
138 * @version $Revision: 1.155 $
140 public class Desktop extends jalview.jbgui.GDesktop
141 implements DropTargetListener, ClipboardOwner, IProgressIndicator, jalview.api.StructureSelectionManagerProvider {
142 private static final String CITATION;
145 URL bg_logo_url = ChannelProperties.getImageURL(
146 "bg_logo." + String.valueOf(SplashScreen.logoSize));
147 URL uod_logo_url = ChannelProperties.getImageURL(
148 "uod_banner." + String.valueOf(SplashScreen.logoSize));
149 boolean logo = (bg_logo_url != null || uod_logo_url != null);
150 StringBuilder sb = new StringBuilder();
152 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
157 sb.append(bg_logo_url == null ? ""
158 : "<img alt=\"Barton Group logo\" src=\""
159 + bg_logo_url.toString() + "\">");
160 sb.append(uod_logo_url == null ? ""
161 : " <img alt=\"University of Dundee shield\" src=\""
162 + uod_logo_url.toString() + "\">");
164 "<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>");
165 sb.append("<br><br>If you use Jalview, please cite:"
166 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
167 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
168 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
169 CITATION = sb.toString();
172 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
174 private static int DEFAULT_MIN_WIDTH = 300;
176 private static int DEFAULT_MIN_HEIGHT = 250;
178 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
180 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
182 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
184 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
186 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
188 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
190 public static boolean nosplash = false;
193 * news reader - null if it was never started.
195 private BlogReader jvnews = null;
197 private File projectFile;
201 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
203 public void addJalviewPropertyChangeListener(PropertyChangeListener listener) {
204 changeSupport.addJalviewPropertyChangeListener(listener);
208 * @param propertyName
210 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
211 * java.beans.PropertyChangeListener)
213 public void addJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
214 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
218 * @param propertyName
220 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
221 * java.beans.PropertyChangeListener)
223 public void removeJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
224 changeSupport.removeJalviewPropertyChangeListener(propertyName, listener);
227 /** Singleton Desktop instance */
228 public static Desktop instance;
230 public static MyDesktopPane desktop;
232 public static MyDesktopPane getDesktop() {
233 // BH 2018 could use currentThread() here as a reference to a
234 // Hashtable<Thread, MyDesktopPane> in JavaScript
238 static int openFrameCount = 0;
240 static final int xOffset = 30;
242 static final int yOffset = 30;
244 public static jalview.ws.jws1.Discoverer discoverer;
246 public static Object[] jalviewClipboard;
248 public static boolean internalCopy = false;
250 static int fileLoadingCount = 0;
252 class MyDesktopManager implements DesktopManager {
254 private DesktopManager delegate;
256 public MyDesktopManager(DesktopManager delegate) {
257 this.delegate = delegate;
261 public void activateFrame(JInternalFrame f) {
263 delegate.activateFrame(f);
264 } catch (NullPointerException npe) {
265 Point p = getMousePosition();
266 instance.showPasteMenu(p.x, p.y);
271 public void beginDraggingFrame(JComponent f) {
272 delegate.beginDraggingFrame(f);
276 public void beginResizingFrame(JComponent f, int direction) {
277 delegate.beginResizingFrame(f, direction);
281 public void closeFrame(JInternalFrame f) {
282 delegate.closeFrame(f);
286 public void deactivateFrame(JInternalFrame f) {
287 delegate.deactivateFrame(f);
291 public void deiconifyFrame(JInternalFrame f) {
292 delegate.deiconifyFrame(f);
296 public void dragFrame(JComponent f, int newX, int newY) {
300 delegate.dragFrame(f, newX, newY);
304 public void endDraggingFrame(JComponent f) {
305 delegate.endDraggingFrame(f);
310 public void endResizingFrame(JComponent f) {
311 delegate.endResizingFrame(f);
316 public void iconifyFrame(JInternalFrame f) {
317 delegate.iconifyFrame(f);
321 public void maximizeFrame(JInternalFrame f) {
322 delegate.maximizeFrame(f);
326 public void minimizeFrame(JInternalFrame f) {
327 delegate.minimizeFrame(f);
331 public void openFrame(JInternalFrame f) {
332 delegate.openFrame(f);
336 public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
340 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
344 public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
345 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
348 // All other methods, simply delegate
353 * Creates a new Desktop object.
358 * A note to implementors. It is ESSENTIAL that any activities that might block
359 * are spawned off as threads rather than waited for during this constructor.
363 doConfigureStructurePrefs();
364 setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
367 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
368 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
369 * documented or guaranteed to exist, so we access it via reflection. There
370 * appear to be unfathomable criteria about what this string can contain, and it
371 * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
372 * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
373 * but "Jalview non-release" does not. The reflection access may generate a
374 * warning: WARNING: An illegal reflective access operation has occurred
375 * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
376 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
378 if (Platform.isLinux()) {
380 Toolkit xToolkit = Toolkit.getDefaultToolkit();
381 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
382 Field awtAppClassNameField = null;
384 if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
385 awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
388 String title = ChannelProperties.getProperty("app_name");
389 if (awtAppClassNameField != null) {
390 awtAppClassNameField.setAccessible(true);
391 awtAppClassNameField.set(xToolkit, title);
393 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
395 } catch (Exception e) {
396 jalview.bin.Console.debug("Error setting awtAppClassName");
397 jalview.bin.Console.trace(Cache.getStackTraceString(e));
402 * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
403 * macOS's application menu. APQHandlers will check to see if a handler is
404 * supported before setting it.
407 APQHandlers.setAPQHandlers(this);
408 } catch (Exception e) {
409 System.out.println("Cannot set APQHandlers");
410 // e.printStackTrace();
411 } catch (Throwable t) {
412 jalview.bin.Console.warn("Error setting APQHandlers: " + t.toString());
413 jalview.bin.Console.trace(Cache.getStackTraceString(t));
415 setIconImages(ChannelProperties.getIconList());
417 addWindowListener(new WindowAdapter() {
420 public void windowClosing(WindowEvent ev) {
425 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
427 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
428 desktop = new MyDesktopPane(selmemusage);
430 showMemusage.setSelected(selmemusage);
431 desktop.setBackground(Color.white);
433 getContentPane().setLayout(new BorderLayout());
434 // alternate config - have scrollbars - see notes in JAL-153
435 // JScrollPane sp = new JScrollPane();
436 // sp.getViewport().setView(desktop);
437 // getContentPane().add(sp, BorderLayout.CENTER);
439 // BH 2018 - just an experiment to try unclipped JInternalFrames.
440 if (Platform.isJS()) {
441 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
444 getContentPane().add(desktop, BorderLayout.CENTER);
445 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
447 // This line prevents Windows Look&Feel resizing all new windows to maximum
448 // if previous window was maximised
449 desktop.setDesktopManager(new MyDesktopManager((Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
450 : Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(desktop.getDesktopManager())
451 : desktop.getDesktopManager())));
453 Rectangle dims = getLastKnownDimensions("");
457 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
458 int xPos = Math.max(5, (screenSize.width - 900) / 2);
459 int yPos = Math.max(5, (screenSize.height - 650) / 2);
460 setBounds(xPos, yPos, 900, 650);
463 if (!Platform.isJS())
470 jconsole = new Console(this, showjconsole);
471 jconsole.setHeader(Cache.getVersionDetailsForConsole());
472 showConsole(showjconsole);
474 showNews.setVisible(false);
476 experimentalFeatures.setSelected(showExperimental());
478 getIdentifiersOrgData();
482 // Spawn a thread that shows the splashscreen
484 SwingUtilities.invokeLater(new Runnable() {
487 new SplashScreen(true);
492 // Thread off a new instance of the file chooser - this reduces the time
494 // takes to open it later on.
495 new Thread(new Runnable() {
498 jalview.bin.Console.debug("Filechooser init thread started.");
499 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
500 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat);
501 jalview.bin.Console.debug("Filechooser init thread finished.");
504 // Add the service change listener
505 changeSupport.addJalviewPropertyChangeListener("services", new PropertyChangeListener() {
508 public void propertyChange(PropertyChangeEvent evt) {
509 jalview.bin.Console.debug("Firing service changed event for " + evt.getNewValue());
510 JalviewServicesChanged(evt);
515 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
517 this.addWindowListener(new WindowAdapter() {
519 public void windowClosing(WindowEvent evt) {
525 this.addMouseListener(ma = new MouseAdapter() {
527 public void mousePressed(MouseEvent evt) {
528 if (evt.isPopupTrigger()) // Mac
530 showPasteMenu(evt.getX(), evt.getY());
535 public void mouseReleased(MouseEvent evt) {
536 if (evt.isPopupTrigger()) // Windows
538 showPasteMenu(evt.getX(), evt.getY());
542 desktop.addMouseListener(ma);
546 * Answers true if user preferences to enable experimental features is True
551 public boolean showExperimental() {
552 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES, Boolean.FALSE.toString());
553 return Boolean.valueOf(experimental).booleanValue();
556 public void doConfigureStructurePrefs() {
557 // configure services
558 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
559 if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) {
560 ssm.setAddTempFacAnnot(Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
561 ssm.setProcessSecondaryStructure(Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
562 // JAL-3915 - RNAView is no longer an option so this has no effect
563 ssm.setSecStructServices(Cache.getDefault(Preferences.USE_RNAVIEW, false));
565 ssm.setAddTempFacAnnot(false);
566 ssm.setProcessSecondaryStructure(false);
567 ssm.setSecStructServices(false);
571 public void checkForNews() {
572 final Desktop me = this;
573 // Thread off the news reader, in case there are connection problems.
574 new Thread(new Runnable() {
577 jalview.bin.Console.debug("Starting news thread.");
578 jvnews = new BlogReader(me);
579 showNews.setVisible(true);
580 jalview.bin.Console.debug("Completed news thread.");
585 public void getIdentifiersOrgData() {
586 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
587 // Thread off the identifiers fetcher
588 new Thread(new Runnable() {
591 jalview.bin.Console.debug("Downloading data from identifiers.org");
593 UrlDownloadClient.download(IdOrgSettings.getUrl(), IdOrgSettings.getDownloadLocation());
594 } catch (IOException e) {
595 jalview.bin.Console.debug("Exception downloading identifiers.org data" + e.getMessage());
603 protected void showNews_actionPerformed(ActionEvent e) {
604 showNews(showNews.isSelected());
607 void showNews(boolean visible) {
608 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
609 showNews.setSelected(visible);
610 if (visible && !jvnews.isVisible()) {
611 new Thread(new Runnable() {
614 long now = System.currentTimeMillis();
615 Desktop.instance.setProgressBar(MessageManager.getString("status.refreshing_news"), now);
616 jvnews.refreshNews();
617 Desktop.instance.setProgressBar(null, now);
625 * recover the last known dimensions for a jalview window
627 * @param windowName - empty string is desktop, all other windows have unique
629 * @return null or last known dimensions scaled to current geometry (if last
630 * window geom was known)
632 Rectangle getLastKnownDimensions(String windowName) {
633 // TODO: lock aspect ratio for scaling desktop Bug #0058199
634 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
635 String x = Cache.getProperty(windowName + "SCREEN_X");
636 String y = Cache.getProperty(windowName + "SCREEN_Y");
637 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
638 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
639 if ((x != null) && (y != null) && (width != null) && (height != null)) {
640 int ix = Integer.parseInt(x), iy = Integer.parseInt(y), iw = Integer.parseInt(width),
641 ih = Integer.parseInt(height);
642 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) {
643 // attempt #1 - try to cope with change in screen geometry - this
644 // version doesn't preserve original jv aspect ratio.
645 // take ratio of current screen size vs original screen size.
646 double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
647 double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
648 // rescale the bounds depending upon the current screen geometry.
649 ix = (int) (ix * sw);
650 iw = (int) (iw * sw);
651 iy = (int) (iy * sh);
652 ih = (int) (ih * sh);
653 while (ix >= screenSize.width) {
654 jalview.bin.Console.debug("Window geometry location recall error: shifting horizontal to within screenbounds.");
655 ix -= screenSize.width;
657 while (iy >= screenSize.height) {
658 jalview.bin.Console.debug("Window geometry location recall error: shifting vertical to within screenbounds.");
659 iy -= screenSize.height;
661 jalview.bin.Console.debug("Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw
664 // return dimensions for new instance
665 return new Rectangle(ix, iy, iw, ih);
670 void showPasteMenu(int x, int y) {
671 JPopupMenu popup = new JPopupMenu();
672 JMenuItem item = new JMenuItem(MessageManager.getString("label.paste_new_window"));
673 item.addActionListener(new ActionListener() {
675 public void actionPerformed(ActionEvent evt) {
681 popup.show(this, x, y);
684 public void paste() {
686 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
687 Transferable contents = c.getContents(this);
689 if (contents != null) {
690 String file = (String) contents.getTransferData(DataFlavor.stringFlavor);
692 FileFormatI format = new IdentifyFile().identify(file, DataSourceType.PASTE);
694 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
697 } catch (Exception ex) {
698 System.out.println("Unable to paste alignment from system clipboard:\n" + ex);
703 * Adds and opens the given frame to the desktop
705 * @param frame Frame to show
706 * @param title Visible Title
710 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h) {
711 addInternalFrame(frame, title, true, w, h, true, false);
715 * Add an internal frame to the Jalview desktop
717 * @param frame Frame to show
718 * @param title Visible Title
719 * @param makeVisible When true, display frame immediately, otherwise, caller
720 * must call setVisible themselves.
724 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
726 addInternalFrame(frame, title, makeVisible, w, h, true, false);
730 * Add an internal frame to the Jalview desktop and make it visible
732 * @param frame Frame to show
733 * @param title Visible Title
736 * @param resizable Allow resize
738 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h,
740 addInternalFrame(frame, title, true, w, h, resizable, false);
744 * Add an internal frame to the Jalview desktop
746 * @param frame Frame to show
747 * @param title Visible Title
748 * @param makeVisible When true, display frame immediately, otherwise, caller
749 * must call setVisible themselves.
752 * @param resizable Allow resize
753 * @param ignoreMinSize Do not set the default minimum size for frame
755 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
756 int h, boolean resizable, boolean ignoreMinSize) {
758 // TODO: allow callers to determine X and Y position of frame (eg. via
760 // TODO: consider fixing method to update entries in the window submenu with
761 // the current window title
763 frame.setTitle(title);
764 if (frame.getWidth() < 1 || frame.getHeight() < 1) {
767 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
768 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
769 // IF JALVIEW IS RUNNING HEADLESS
770 // ///////////////////////////////////////////////
771 if (instance == null || (System.getProperty("java.awt.headless") != null
772 && System.getProperty("java.awt.headless").equals("true"))) {
778 if (!ignoreMinSize) {
779 frame.setMinimumSize(new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
781 // Set default dimension for Alignment Frame window.
782 // The Alignment Frame window could be added from a number of places,
784 // I did this here in order not to miss out on any Alignment frame.
785 if (frame instanceof AlignFrame) {
786 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH, ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
790 frame.setVisible(makeVisible);
791 frame.setClosable(true);
792 frame.setResizable(resizable);
793 frame.setMaximizable(resizable);
794 frame.setIconifiable(resizable);
795 frame.setOpaque(Platform.isJS());
797 if (frame.getX() < 1 && frame.getY() < 1) {
798 frame.setLocation(xOffset * openFrameCount, yOffset * ((openFrameCount - 1) % 10) + yOffset);
802 * add an entry for the new frame in the Window menu (and remove it when the
805 final JMenuItem menuItem = new JMenuItem(title);
806 frame.addInternalFrameListener(new InternalFrameAdapter() {
808 public void internalFrameActivated(InternalFrameEvent evt) {
809 JInternalFrame itf = desktop.getSelectedFrame();
811 if (itf instanceof AlignFrame) {
812 Jalview.setCurrentAlignFrame((AlignFrame) itf);
819 public void internalFrameClosed(InternalFrameEvent evt) {
820 PaintRefresher.RemoveComponent(frame);
823 * defensive check to prevent frames being added half off the window
825 if (openFrameCount > 0) {
830 * ensure no reference to alignFrame retained by menu item listener
832 if (menuItem.getActionListeners().length > 0) {
833 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
835 windowMenu.remove(menuItem);
839 menuItem.addActionListener(new ActionListener() {
841 public void actionPerformed(ActionEvent e) {
843 frame.setSelected(true);
844 frame.setIcon(false);
845 } catch (java.beans.PropertyVetoException ex) {
851 setKeyBindings(frame);
855 windowMenu.add(menuItem);
859 frame.setSelected(true);
860 frame.requestFocus();
861 } catch (java.beans.PropertyVetoException ve) {
862 } catch (java.lang.ClassCastException cex) {
863 jalview.bin.Console.warn(
864 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
870 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
875 private static void setKeyBindings(JInternalFrame frame) {
876 @SuppressWarnings("serial")
877 final Action closeAction = new AbstractAction() {
879 public void actionPerformed(ActionEvent e) {
885 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
887 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
888 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
890 InputMap inputMap = frame.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
891 String ctrlW = ctrlWKey.toString();
892 inputMap.put(ctrlWKey, ctrlW);
893 inputMap.put(cmdWKey, ctrlW);
895 ActionMap actionMap = frame.getActionMap();
896 actionMap.put(ctrlW, closeAction);
900 public void lostOwnership(Clipboard clipboard, Transferable contents) {
902 Desktop.jalviewClipboard = null;
905 internalCopy = false;
909 public void dragEnter(DropTargetDragEvent evt) {
913 public void dragExit(DropTargetEvent evt) {
917 public void dragOver(DropTargetDragEvent evt) {
921 public void dropActionChanged(DropTargetDragEvent evt) {
927 * @param evt DOCUMENT ME!
930 public void drop(DropTargetDropEvent evt) {
931 boolean success = true;
932 // JAL-1552 - acceptDrop required before getTransferable call for
933 // Java's Transferable for native dnd
934 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
935 Transferable t = evt.getTransferable();
936 List<Object> files = new ArrayList<>();
937 List<DataSourceType> protocols = new ArrayList<>();
940 Desktop.transferFromDropTarget(files, protocols, evt, t);
941 } catch (Exception e) {
948 for (int i = 0; i < files.size(); i++) {
949 // BH 2018 File or String
950 Object file = files.get(i);
951 String fileName = file.toString();
952 DataSourceType protocol = (protocols == null) ? DataSourceType.FILE : protocols.get(i);
953 FileFormatI format = null;
955 if (fileName.endsWith(".jar")) {
956 format = FileFormat.Jalview;
959 format = new IdentifyFile().identify(file, protocol);
961 if (file instanceof File) {
962 Platform.cacheFileData((File) file);
964 new FileLoader().LoadFile(null, file, protocol, format);
967 } catch (Exception ex) {
971 evt.dropComplete(success); // need this to ensure input focus is properly
972 // transfered to any new windows created
978 * @param e DOCUMENT ME!
981 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) {
982 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
983 JalviewFileChooser chooser = JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat,
984 BackupFiles.getEnabled());
986 chooser.setFileView(new JalviewFileView());
987 chooser.setDialogTitle(MessageManager.getString("label.open_local_file"));
988 chooser.setToolTipText(MessageManager.getString("action.open"));
990 chooser.setResponseHandler(0, new Runnable() {
993 File selectedFile = chooser.getSelectedFile();
994 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
996 FileFormatI format = chooser.getSelectedFormat();
999 * Call IdentifyFile to verify the file contains what its extension implies.
1000 * Skip this step for dynamically added file formats, because IdentifyFile does
1001 * not know how to recognise them.
1003 if (FileFormats.getInstance().isIdentifiable(format)) {
1005 format = new IdentifyFile().identify(selectedFile, DataSourceType.FILE);
1006 } catch (FileFormatException e) {
1007 // format = null; //??
1011 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, format);
1014 chooser.showOpenDialog(this);
1018 * Shows a dialog for input of a URL at which to retrieve alignment data
1023 public void inputURLMenuItem_actionPerformed(AlignViewport viewport) {
1024 // This construct allows us to have a wider textfield
1026 JLabel label = new JLabel(MessageManager.getString("label.input_file_url"));
1028 JPanel panel = new JPanel(new GridLayout(2, 1));
1032 * the URL to fetch is input in Java: an editable combobox with history JS:
1033 * (pending JAL-3038) a plain text field
1036 String urlBase = "https://www.";
1037 if (Platform.isJS()) {
1038 history = new JTextField(urlBase, 35);
1046 JComboBox<String> asCombo = new JComboBox<>();
1047 asCombo.setPreferredSize(new Dimension(400, 20));
1048 asCombo.setEditable(true);
1049 asCombo.addItem(urlBase);
1050 String historyItems = Cache.getProperty("RECENT_URL");
1051 if (historyItems != null) {
1052 for (String token : historyItems.split("\\t")) {
1053 asCombo.addItem(token);
1060 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1061 MessageManager.getString("action.cancel") };
1062 Runnable action = new Runnable() {
1065 @SuppressWarnings("unchecked")
1066 String url = (history instanceof JTextField ? ((JTextField) history).getText()
1067 : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
1069 if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) {
1070 if (viewport != null) {
1071 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
1073 new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
1076 FileFormatI format = null;
1078 format = new IdentifyFile().identify(url, DataSourceType.URL);
1079 } catch (FileFormatException e) {
1080 // TODO revise error handling, distinguish between
1081 // URL not found and response not valid
1084 if (format == null) {
1085 String msg = MessageManager.formatMessage("label.couldnt_locate", url);
1086 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1087 MessageManager.getString("label.url_not_found"), JvOptionPane.WARNING_MESSAGE);
1092 if (viewport != null) {
1093 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, format);
1095 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1100 String dialogOption = MessageManager.getString("label.input_alignment_from_url");
1101 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action).showInternalDialog(panel, dialogOption,
1102 JvOptionPane.YES_NO_CANCEL_OPTION, JvOptionPane.PLAIN_MESSAGE, null, options,
1103 MessageManager.getString("action.ok"));
1107 * Opens the CutAndPaste window for the user to paste an alignment in to
1109 * @param viewPanel - if not null, the pasted alignment is added to the current
1110 * alignment; if null, to a new alignment window
1113 public void inputTextboxMenuItem_actionPerformed(AlignmentViewPanel viewPanel) {
1114 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1115 cap.setForInput(viewPanel);
1116 Desktop.addInternalFrame(cap, MessageManager.getString("label.cut_paste_alignmen_file"), true, 600, 500);
1123 public void quit() {
1124 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1125 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1126 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1127 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, getWidth(), getHeight()));
1129 if (jconsole != null) {
1130 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1131 jconsole.stopConsole();
1133 if (jvnews != null) {
1134 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1137 if (dialogExecutor != null) {
1138 dialogExecutor.shutdownNow();
1140 closeAll_actionPerformed(null);
1142 if (groovyConsole != null) {
1143 // suppress a possible repeat prompt to save script
1144 groovyConsole.setDirty(false);
1145 groovyConsole.exit();
1150 private void storeLastKnownDimensions(String string, Rectangle jc) {
1151 jalview.bin.Console.debug("Storing last known dimensions for " + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1152 + " height:" + jc.height);
1154 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1155 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1156 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1157 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1163 * @param e DOCUMENT ME!
1166 public void aboutMenuItem_actionPerformed(ActionEvent e) {
1167 new Thread(new Runnable() {
1170 new SplashScreen(false);
1176 * Returns the html text for the About screen, including any available version
1177 * number, build details, author details and citation reference, but without the
1178 * enclosing {@code html} tags
1182 public String getAboutMessage() {
1183 StringBuilder message = new StringBuilder(1024);
1184 message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
1185 .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
1186 .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
1187 .append(Cache.getBuildDetailsForSplash()).append("</strong>");
1189 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1190 if (latestVersion.equals("Checking")) {
1191 // JBP removed this message for 2.11: May be reinstated in future version
1192 // message.append("<br>...Checking latest version...</br>");
1193 } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
1194 boolean red = false;
1195 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT).indexOf("automated build") == -1) {
1197 // Displayed when code version and jnlp version do not match and code
1198 // version is not a development build
1199 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1202 message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1203 .append(" is available for download from ")
1204 .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
1206 message.append("</div>");
1209 message.append("<br>Authors: ");
1210 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1211 message.append(CITATION);
1213 message.append("</div>");
1215 return message.toString();
1219 * Action on requesting Help documentation
1222 public void documentationMenuItem_actionPerformed() {
1224 if (Platform.isJS()) {
1225 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1233 Help.showHelpWindow();
1235 } catch (Exception ex) {
1236 System.err.println("Error opening help: " + ex.getMessage());
1241 public void closeAll_actionPerformed(ActionEvent e) {
1242 // TODO show a progress bar while closing?
1243 JInternalFrame[] frames = desktop.getAllFrames();
1244 for (int i = 0; i < frames.length; i++) {
1246 frames[i].setClosed(true);
1247 } catch (java.beans.PropertyVetoException ex) {
1250 Jalview.setCurrentAlignFrame(null);
1251 System.out.println("ALL CLOSED");
1254 * reset state of singleton objects as appropriate (clear down session state
1255 * when all windows are closed)
1257 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
1264 public void raiseRelated_actionPerformed(ActionEvent e) {
1265 reorderAssociatedWindows(false, false);
1269 public void minimizeAssociated_actionPerformed(ActionEvent e) {
1270 reorderAssociatedWindows(true, false);
1273 void closeAssociatedWindows() {
1274 reorderAssociatedWindows(false, true);
1280 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1284 protected void garbageCollect_actionPerformed(ActionEvent e) {
1285 // We simply collect the garbage
1286 jalview.bin.Console.debug("Collecting garbage...");
1288 jalview.bin.Console.debug("Finished garbage collection.");
1294 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1298 protected void showMemusage_actionPerformed(ActionEvent e) {
1299 desktop.showMemoryUsage(showMemusage.isSelected());
1306 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1310 protected void showConsole_actionPerformed(ActionEvent e) {
1311 showConsole(showConsole.isSelected());
1314 Console jconsole = null;
1317 * control whether the java console is visible or not
1321 void showConsole(boolean selected) {
1322 // TODO: decide if we should update properties file
1323 if (jconsole != null) // BH 2018
1325 showConsole.setSelected(selected);
1326 Cache.setProperty("SHOW_JAVA_CONSOLE", Boolean.valueOf(selected).toString());
1327 jconsole.setVisible(selected);
1331 void reorderAssociatedWindows(boolean minimize, boolean close) {
1332 JInternalFrame[] frames = desktop.getAllFrames();
1333 if (frames == null || frames.length < 1) {
1337 AlignmentViewport source = null, target = null;
1338 if (frames[0] instanceof AlignFrame) {
1339 source = ((AlignFrame) frames[0]).getCurrentView();
1340 } else if (frames[0] instanceof TreePanel) {
1341 source = ((TreePanel) frames[0]).getViewPort();
1342 } else if (frames[0] instanceof PCAPanel) {
1343 source = ((PCAPanel) frames[0]).av;
1344 } else if (frames[0].getContentPane() instanceof PairwiseAlignPanel) {
1345 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1348 if (source != null) {
1349 for (int i = 0; i < frames.length; i++) {
1351 if (frames[i] == null) {
1354 if (frames[i] instanceof AlignFrame) {
1355 target = ((AlignFrame) frames[i]).getCurrentView();
1356 } else if (frames[i] instanceof TreePanel) {
1357 target = ((TreePanel) frames[i]).getViewPort();
1358 } else if (frames[i] instanceof PCAPanel) {
1359 target = ((PCAPanel) frames[i]).av;
1360 } else if (frames[i].getContentPane() instanceof PairwiseAlignPanel) {
1361 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1364 if (source == target) {
1367 frames[i].setClosed(true);
1369 frames[i].setIcon(minimize);
1371 frames[i].toFront();
1375 } catch (java.beans.PropertyVetoException ex) {
1385 * @param e DOCUMENT ME!
1388 protected void preferences_actionPerformed(ActionEvent e) {
1389 Preferences.openPreferences();
1393 * Prompts the user to choose a file and then saves the Jalview state as a
1394 * Jalview project file
1397 public void saveState_actionPerformed() {
1398 saveState_actionPerformed(false);
1401 public void saveState_actionPerformed(boolean saveAs) {
1402 java.io.File projectFile = getProjectFile();
1403 // autoSave indicates we already have a file and don't need to ask
1404 boolean autoSave = projectFile != null && !saveAs && BackupFiles.getEnabled();
1406 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1407 // saveAs="+saveAs+", Backups
1408 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1410 boolean approveSave = false;
1412 JalviewFileChooser chooser = new JalviewFileChooser("jvp", "Jalview Project");
1414 chooser.setFileView(new JalviewFileView());
1415 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1417 int value = chooser.showSaveDialog(this);
1419 if (value == JalviewFileChooser.APPROVE_OPTION) {
1420 projectFile = chooser.getSelectedFile();
1421 setProjectFile(projectFile);
1426 if (approveSave || autoSave) {
1427 final Desktop me = this;
1428 final java.io.File chosenFile = projectFile;
1429 new Thread(new Runnable() {
1432 // TODO: refactor to Jalview desktop session controller action.
1434 MessageManager.formatMessage("label.saving_jalview_project", new Object[] { chosenFile.getName() }),
1435 chosenFile.hashCode());
1436 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1437 // TODO catch and handle errors for savestate
1438 // TODO prevent user from messing with the Desktop whilst we're saving
1440 boolean doBackup = BackupFiles.getEnabled();
1441 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
1443 new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
1446 backupfiles.setWriteSuccess(true);
1447 backupfiles.rollBackupsAndRenameTempFile();
1449 } catch (OutOfMemoryError oom) {
1450 new OOMWarning("Whilst saving current state to " + chosenFile.getName(), oom);
1451 } catch (Exception ex) {
1452 jalview.bin.Console.error("Problems whilst trying to save to " + chosenFile.getName(), ex);
1453 JvOptionPane.showMessageDialog(me,
1454 MessageManager.formatMessage("label.error_whilst_saving_current_state_to",
1455 new Object[] { chosenFile.getName() }),
1456 MessageManager.getString("label.couldnt_save_project"), JvOptionPane.WARNING_MESSAGE);
1458 setProgressBar(null, chosenFile.hashCode());
1465 public void saveAsState_actionPerformed(ActionEvent e) {
1466 saveState_actionPerformed(true);
1469 private void setProjectFile(File choice) {
1470 this.projectFile = choice;
1473 public File getProjectFile() {
1474 return this.projectFile;
1478 * Shows a file chooser dialog and tries to read in the selected file as a
1482 public void loadState_actionPerformed() {
1483 final String[] suffix = new String[] { "jvp", "jar" };
1484 final String[] desc = new String[] { "Jalview Project", "Jalview Project (old)" };
1485 JalviewFileChooser chooser = new JalviewFileChooser(Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1486 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1490 chooser.setFileView(new JalviewFileView());
1491 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1492 chooser.setResponseHandler(0, new Runnable() {
1495 File selectedFile = chooser.getSelectedFile();
1496 setProjectFile(selectedFile);
1497 String choice = selectedFile.getAbsolutePath();
1498 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1499 new Thread(new Runnable() {
1503 new Jalview2XML().loadJalviewAlign(selectedFile);
1504 } catch (OutOfMemoryError oom) {
1505 new OOMWarning("Whilst loading project from " + choice, oom);
1506 } catch (Exception ex) {
1507 jalview.bin.Console.error("Problems whilst loading project from " + choice, ex);
1508 JvOptionPane.showMessageDialog(Desktop.desktop,
1509 MessageManager.formatMessage("label.error_whilst_loading_project_from", new Object[] { choice }),
1510 MessageManager.getString("label.couldnt_load_project"), JvOptionPane.WARNING_MESSAGE);
1513 }, "Project Loader").start();
1517 chooser.showOpenDialog(this);
1521 public void inputSequence_actionPerformed(ActionEvent e) {
1522 new SequenceFetcher(this);
1525 JPanel progressPanel;
1527 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1529 public void startLoading(final Object fileName) {
1530 if (fileLoadingCount == 0) {
1532 .add(addProgressPanel(MessageManager.formatMessage("label.loading_file", new Object[] { fileName })));
1537 private JPanel addProgressPanel(String string) {
1538 if (progressPanel == null) {
1539 progressPanel = new JPanel(new GridLayout(1, 1));
1540 totalProgressCount = 0;
1541 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1543 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1544 JProgressBar progressBar = new JProgressBar();
1545 progressBar.setIndeterminate(true);
1547 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1549 thisprogress.add(progressBar, BorderLayout.CENTER);
1550 progressPanel.add(thisprogress);
1551 ((GridLayout) progressPanel.getLayout()).setRows(((GridLayout) progressPanel.getLayout()).getRows() + 1);
1552 ++totalProgressCount;
1553 instance.validate();
1554 return thisprogress;
1557 int totalProgressCount = 0;
1559 private void removeProgressPanel(JPanel progbar) {
1560 if (progressPanel != null) {
1561 synchronized (progressPanel) {
1562 progressPanel.remove(progbar);
1563 GridLayout gl = (GridLayout) progressPanel.getLayout();
1564 gl.setRows(gl.getRows() - 1);
1565 if (--totalProgressCount < 1) {
1566 this.getContentPane().remove(progressPanel);
1567 progressPanel = null;
1574 public void stopLoading() {
1576 if (fileLoadingCount < 1) {
1577 while (fileLoadingPanels.size() > 0) {
1578 removeProgressPanel(fileLoadingPanels.remove(0));
1580 fileLoadingPanels.clear();
1581 fileLoadingCount = 0;
1586 public static int getViewCount(String alignmentId) {
1587 AlignmentViewport[] aps = getViewports(alignmentId);
1588 return (aps == null) ? 0 : aps.length;
1593 * @param alignmentId - if null, all sets are returned
1594 * @return all AlignmentPanels concerning the alignmentId sequence set
1596 public static AlignmentPanel[] getAlignmentPanels(String alignmentId) {
1597 if (Desktop.desktop == null) {
1598 // no frames created and in headless mode
1599 // TODO: verify that frames are recoverable when in headless mode
1602 List<AlignmentPanel> aps = new ArrayList<>();
1603 AlignFrame[] frames = getAlignFrames();
1604 if (frames == null) {
1607 for (AlignFrame af : frames) {
1608 for (AlignmentPanel ap : af.alignPanels) {
1609 if (alignmentId == null || alignmentId.equals(ap.av.getSequenceSetId())) {
1614 if (aps.size() == 0) {
1617 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1622 * get all the viewports on an alignment.
1624 * @param sequenceSetId unique alignment id (may be null - all viewports
1625 * returned in that case)
1626 * @return all viewports on the alignment bound to sequenceSetId
1628 public static AlignmentViewport[] getViewports(String sequenceSetId) {
1629 List<AlignmentViewport> viewp = new ArrayList<>();
1630 if (desktop != null) {
1631 AlignFrame[] frames = Desktop.getAlignFrames();
1633 for (AlignFrame afr : frames) {
1634 if (sequenceSetId == null || afr.getViewport().getSequenceSetId().equals(sequenceSetId)) {
1635 if (afr.alignPanels != null) {
1636 for (AlignmentPanel ap : afr.alignPanels) {
1637 if (sequenceSetId == null || sequenceSetId.equals(ap.av.getSequenceSetId())) {
1642 viewp.add(afr.getViewport());
1646 if (viewp.size() > 0) {
1647 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1654 * Explode the views in the given frame into separate AlignFrame
1658 public static void explodeViews(AlignFrame af) {
1659 int size = af.alignPanels.size();
1664 // FIXME: ideally should use UI interface API
1665 FeatureSettings viewFeatureSettings = (af.featureSettings != null && af.featureSettings.isOpen())
1666 ? af.featureSettings
1668 Rectangle fsBounds = af.getFeatureSettingsGeometry();
1669 for (int i = 0; i < size; i++) {
1670 AlignmentPanel ap = af.alignPanels.get(i);
1672 AlignFrame newaf = new AlignFrame(ap);
1674 // transfer reference for existing feature settings to new alignFrame
1675 if (ap == af.alignPanel) {
1676 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap) {
1677 newaf.featureSettings = viewFeatureSettings;
1679 newaf.setFeatureSettingsGeometry(fsBounds);
1683 * Restore the view's last exploded frame geometry if known. Multiple views from
1684 * one exploded frame share and restore the same (frame) position and size.
1686 Rectangle geometry = ap.av.getExplodedGeometry();
1687 if (geometry != null) {
1688 newaf.setBounds(geometry);
1691 ap.av.setGatherViewsHere(false);
1693 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1694 // and materialise a new feature settings dialog instance for the new
1696 // (closes the old as if 'OK' was pressed)
1697 if (ap == af.alignPanel && newaf.featureSettings != null && newaf.featureSettings.isOpen()
1698 && af.alignPanel.getAlignViewport().isShowSequenceFeatures()) {
1699 newaf.showFeatureSettingsUI();
1703 af.featureSettings = null;
1704 af.alignPanels.clear();
1705 af.closeMenuItem_actionPerformed(true);
1710 * Gather expanded views (separate AlignFrame's) with the same sequence set
1711 * identifier back in to this frame as additional views, and close the expanded
1712 * views. Note the expanded frames may themselves have multiple views. We take
1717 public void gatherViews(AlignFrame source) {
1718 source.viewport.setGatherViewsHere(true);
1719 source.viewport.setExplodedGeometry(source.getBounds());
1720 JInternalFrame[] frames = desktop.getAllFrames();
1721 String viewId = source.viewport.getSequenceSetId();
1722 for (int t = 0; t < frames.length; t++) {
1723 if (frames[t] instanceof AlignFrame && frames[t] != source) {
1724 AlignFrame af = (AlignFrame) frames[t];
1725 boolean gatherThis = false;
1726 for (int a = 0; a < af.alignPanels.size(); a++) {
1727 AlignmentPanel ap = af.alignPanels.get(a);
1728 if (viewId.equals(ap.av.getSequenceSetId())) {
1730 ap.av.setGatherViewsHere(false);
1731 ap.av.setExplodedGeometry(af.getBounds());
1732 source.addAlignmentPanel(ap, false);
1737 if (af.featureSettings != null && af.featureSettings.isOpen()) {
1738 if (source.featureSettings == null) {
1739 // preserve the feature settings geometry for this frame
1740 source.featureSettings = af.featureSettings;
1741 source.setFeatureSettingsGeometry(af.getFeatureSettingsGeometry());
1743 // close it and forget
1744 af.featureSettings.close();
1747 af.alignPanels.clear();
1748 af.closeMenuItem_actionPerformed(true);
1753 // refresh the feature setting UI for the source frame if it exists
1754 if (source.featureSettings != null && source.featureSettings.isOpen()) {
1755 source.showFeatureSettingsUI();
1760 public JInternalFrame[] getAllFrames() {
1761 return desktop.getAllFrames();
1765 * Checks the given url to see if it gives a response indicating that the user
1766 * should be informed of a new questionnaire.
1770 public void checkForQuestionnaire(String url) {
1771 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
1772 // javax.swing.SwingUtilities.invokeLater(jvq);
1773 new Thread(jvq).start();
1776 public void checkURLLinks() {
1777 // Thread off the URL link checker
1778 addDialogThread(new Runnable() {
1781 if (Cache.getDefault("CHECKURLLINKS", true)) {
1782 // check what the actual links are - if it's just the default don't
1783 // bother with the warning
1784 List<String> links = Preferences.sequenceUrlLinks.getLinksForMenu();
1786 // only need to check links if there is one with a
1787 // SEQUENCE_ID which is not the default EMBL_EBI link
1788 ListIterator<String> li = links.listIterator();
1789 boolean check = false;
1790 List<JLabel> urls = new ArrayList<>();
1791 while (li.hasNext()) {
1792 String link = li.next();
1793 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID) && !UrlConstants.isDefaultString(link)) {
1795 int barPos = link.indexOf("|");
1796 String urlMsg = barPos == -1 ? link : link.substring(0, barPos) + ": " + link.substring(barPos + 1);
1797 urls.add(new JLabel(urlMsg));
1804 // ask user to check in case URL links use old style tokens
1805 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
1806 JPanel msgPanel = new JPanel();
1807 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
1808 msgPanel.add(Box.createVerticalGlue());
1809 JLabel msg = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
1810 JLabel msg2 = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
1812 for (JLabel url : urls) {
1817 final JCheckBox jcb = new JCheckBox(MessageManager.getString("label.do_not_display_again"));
1818 jcb.addActionListener(new ActionListener() {
1820 public void actionPerformed(ActionEvent e) {
1821 // update Cache settings for "don't show this again"
1822 boolean showWarningAgain = !jcb.isSelected();
1823 Cache.setProperty("CHECKURLLINKS", Boolean.valueOf(showWarningAgain).toString());
1828 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
1829 MessageManager.getString("label.SEQUENCE_ID_no_longer_used"), JvOptionPane.WARNING_MESSAGE);
1836 * Proxy class for JDesktopPane which optionally displays the current memory
1837 * usage and highlights the desktop area with a red bar if free memory runs low.
1841 public class MyDesktopPane extends JDesktopPane implements Runnable {
1842 private static final float ONE_MB = 1048576f;
1844 boolean showMemoryUsage = false;
1848 java.text.NumberFormat df;
1850 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory, percentUsage;
1852 public MyDesktopPane(boolean showMemoryUsage) {
1853 showMemoryUsage(showMemoryUsage);
1856 public void showMemoryUsage(boolean showMemory) {
1857 this.showMemoryUsage = showMemory;
1859 Thread worker = new Thread(this);
1865 public boolean isShowMemoryUsage() {
1866 return showMemoryUsage;
1871 df = java.text.NumberFormat.getNumberInstance();
1872 df.setMaximumFractionDigits(2);
1873 runtime = Runtime.getRuntime();
1875 while (showMemoryUsage) {
1877 maxMemory = runtime.maxMemory() / ONE_MB;
1878 allocatedMemory = runtime.totalMemory() / ONE_MB;
1879 freeMemory = runtime.freeMemory() / ONE_MB;
1880 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
1882 percentUsage = (totalFreeMemory / maxMemory) * 100;
1884 // if (percentUsage < 20)
1886 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
1888 // instance.set.setBorder(border1);
1891 // sleep after showing usage
1893 } catch (Exception ex) {
1894 ex.printStackTrace();
1900 public void paintComponent(Graphics g) {
1901 if (showMemoryUsage && g != null && df != null) {
1902 if (percentUsage < 20) {
1903 g.setColor(Color.red);
1905 FontMetrics fm = g.getFontMetrics();
1908 MessageManager.formatMessage("label.memory_stats",
1909 new Object[] { df.format(totalFreeMemory), df.format(maxMemory), df.format(percentUsage) }),
1910 10, getHeight() - fm.getHeight());
1914 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
1915 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
1920 * Accessor method to quickly get all the AlignmentFrames loaded.
1922 * @return an array of AlignFrame, or null if none found
1924 public static AlignFrame[] getAlignFrames() {
1925 if (Jalview.isHeadlessMode()) {
1926 // Desktop.desktop is null in headless mode
1927 return new AlignFrame[] { Jalview.currentAlignFrame };
1930 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1932 if (frames == null) {
1935 List<AlignFrame> avp = new ArrayList<>();
1937 for (int i = frames.length - 1; i > -1; i--) {
1938 if (frames[i] instanceof AlignFrame) {
1939 avp.add((AlignFrame) frames[i]);
1940 } else if (frames[i] instanceof SplitFrame) {
1942 * Also check for a split frame containing an AlignFrame
1944 GSplitFrame sf = (GSplitFrame) frames[i];
1945 if (sf.getTopFrame() instanceof AlignFrame) {
1946 avp.add((AlignFrame) sf.getTopFrame());
1948 if (sf.getBottomFrame() instanceof AlignFrame) {
1949 avp.add((AlignFrame) sf.getBottomFrame());
1953 if (avp.size() == 0) {
1956 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
1961 * Returns an array of any AppJmol frames in the Desktop (or null if none).
1965 public GStructureViewer[] getJmols() {
1966 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1968 if (frames == null) {
1971 List<GStructureViewer> avp = new ArrayList<>();
1973 for (int i = frames.length - 1; i > -1; i--) {
1974 if (frames[i] instanceof AppJmol) {
1975 GStructureViewer af = (GStructureViewer) frames[i];
1979 if (avp.size() == 0) {
1982 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
1987 * Add Groovy Support to Jalview
1990 public void groovyShell_actionPerformed() {
1992 openGroovyConsole();
1993 } catch (Exception ex) {
1994 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
1995 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
1997 MessageManager.getString("label.couldnt_create_groovy_shell"),
1998 MessageManager.getString("label.groovy_support_failed"), JvOptionPane.ERROR_MESSAGE);
2003 * Open the Groovy console
2005 void openGroovyConsole() {
2006 if (groovyConsole == null) {
2007 groovyConsole = new groovy.ui.Console();
2008 groovyConsole.setVariable("Jalview", this);
2009 groovyConsole.run();
2012 * We allow only one console at a time, so that AlignFrame menu option
2013 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2014 * enable 'Run script', when the console is opened, and the reverse when it is
2017 Window window = (Window) groovyConsole.getFrame();
2018 window.addWindowListener(new WindowAdapter() {
2020 public void windowClosed(WindowEvent e) {
2022 * rebind CMD-Q from Groovy Console to Jalview Quit
2025 enableExecuteGroovy(false);
2031 * show Groovy console window (after close and reopen)
2033 ((Window) groovyConsole.getFrame()).setVisible(true);
2036 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2037 * opening a second console
2039 enableExecuteGroovy(true);
2043 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
2046 protected void addQuitHandler() {
2047 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2048 KeyStroke.getKeyStroke(KeyEvent.VK_Q, jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
2050 getRootPane().getActionMap().put("Quit", new AbstractAction() {
2052 public void actionPerformed(ActionEvent e) {
2059 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2061 * @param enabled true if Groovy console is open
2063 public void enableExecuteGroovy(boolean enabled) {
2065 * disable opening a second Groovy console (or re-enable when the console is
2068 groovyShell.setEnabled(!enabled);
2070 AlignFrame[] alignFrames = getAlignFrames();
2071 if (alignFrames != null) {
2072 for (AlignFrame af : alignFrames) {
2073 af.setGroovyEnabled(enabled);
2079 * Progress bars managed by the IProgressIndicator method.
2081 private Hashtable<Long, JPanel> progressBars;
2083 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2088 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2091 public void setProgressBar(String message, long id) {
2092 if (progressBars == null) {
2093 progressBars = new Hashtable<>();
2094 progressBarHandlers = new Hashtable<>();
2097 if (progressBars.get(Long.valueOf(id)) != null) {
2098 JPanel panel = progressBars.remove(Long.valueOf(id));
2099 if (progressBarHandlers.contains(Long.valueOf(id))) {
2100 progressBarHandlers.remove(Long.valueOf(id));
2102 removeProgressPanel(panel);
2104 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2111 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2112 * jalview.gui.IProgressIndicatorHandler)
2115 public void registerHandler(final long id, final IProgressIndicatorHandler handler) {
2116 if (progressBarHandlers == null || !progressBars.containsKey(Long.valueOf(id))) {
2117 throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
2119 progressBarHandlers.put(Long.valueOf(id), handler);
2120 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2121 if (handler.canCancel()) {
2122 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
2123 final IProgressIndicator us = this;
2124 cancel.addActionListener(new ActionListener() {
2127 public void actionPerformed(ActionEvent e) {
2128 handler.cancelActivity(id);
2129 us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
2130 new Object[] { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
2133 progressPanel.add(cancel, BorderLayout.EAST);
2139 * @return true if any progress bars are still active
2142 public boolean operationInProgress() {
2143 if (progressBars != null && progressBars.size() > 0) {
2150 * This will return the first AlignFrame holding the given viewport instance. It
2151 * will break if there are more than one AlignFrames viewing a particular av.
2154 * @return alignFrame for viewport
2156 public static AlignFrame getAlignFrameFor(AlignViewportI viewport) {
2157 if (desktop != null) {
2158 AlignmentPanel[] aps = getAlignmentPanels(viewport.getSequenceSetId());
2159 for (int panel = 0; aps != null && panel < aps.length; panel++) {
2160 if (aps[panel] != null && aps[panel].av == viewport) {
2161 return aps[panel].alignFrame;
2168 public VamsasApplication getVamsasApplication() {
2169 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2175 * flag set if jalview GUI is being operated programmatically
2177 private boolean inBatchMode = false;
2180 * check if jalview GUI is being operated programmatically
2182 * @return inBatchMode
2184 public boolean isInBatchMode() {
2189 * set flag if jalview GUI is being operated programmatically
2191 * @param inBatchMode
2193 public void setInBatchMode(boolean inBatchMode) {
2194 this.inBatchMode = inBatchMode;
2198 * start service discovery and wait till it is done
2200 public void startServiceDiscovery() {
2201 startServiceDiscovery(false);
2205 * start service discovery threads - blocking or non-blocking
2209 public void startServiceDiscovery(boolean blocking) {
2210 startServiceDiscovery(blocking, false);
2214 * start service discovery threads
2216 * @param blocking - false means call returns
2218 * @param ignore_SHOW_JWS2_SERVICES_preference - when true JABA services are
2219 * discovered regardless of user's
2220 * JWS2 discovery preference setting
2222 public void startServiceDiscovery(boolean blocking, boolean ignore_SHOW_JWS2_SERVICES_preference) {
2223 boolean alive = true;
2224 Thread t0 = null, t1 = null, t2 = null;
2225 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2227 // todo: changesupport handlers need to be transferred
2228 if (discoverer == null) {
2229 discoverer = new jalview.ws.jws1.Discoverer();
2230 // register PCS handler for desktop.
2231 discoverer.addPropertyChangeListener(changeSupport);
2233 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2234 // until we phase out completely
2235 (t0 = new Thread(discoverer)).start();
2238 if (ignore_SHOW_JWS2_SERVICES_preference || Cache.getDefault("SHOW_JWS2_SERVICES", true)) {
2239 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(changeSupport);
2243 // TODO: do rest service discovery
2249 } catch (Exception e) {
2251 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive()) || (t3 != null && t3.isAlive())
2252 || (t0 != null && t0.isAlive());
2258 * called to check if the service discovery process completed successfully.
2262 protected void JalviewServicesChanged(PropertyChangeEvent evt) {
2263 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector) {
2264 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().getErrorMessages();
2265 if (ermsg != null) {
2266 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true)) {
2267 if (serviceChangedDialog == null) {
2268 // only run if we aren't already displaying one of these.
2269 addDialogThread(serviceChangedDialog = new Runnable() {
2274 * JalviewDialog jd =new JalviewDialog() {
2276 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2278 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2280 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2282 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2284 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2285 * + " or mis-configured HTTP proxy settings.<br/>" +
2286 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2287 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2288 * true, true, "Web Service Configuration Problem", 450, 400);
2290 * jd.waitForInput();
2292 JvOptionPane.showConfirmDialog(Desktop.desktop,
2293 new JLabel("<html><table width=\"450\"><tr><td>" + ermsg + "</td></tr></table>"
2294 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2295 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2296 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2297 + " Tools->Preferences dialog box to change them.</p></html>"),
2298 "Web Service Configuration Problem", JvOptionPane.DEFAULT_OPTION, JvOptionPane.ERROR_MESSAGE);
2299 serviceChangedDialog = null;
2305 jalview.bin.Console.error("Errors reported by JABA discovery service. Check web services preferences.\n" + ermsg);
2311 private Runnable serviceChangedDialog = null;
2314 * start a thread to open a URL in the configured browser. Pops up a warning
2315 * dialog to the user if there is an exception when calling out to the browser
2320 public static void showUrl(final String url) {
2321 showUrl(url, Desktop.instance);
2325 * Like showUrl but allows progress handler to be specified
2328 * @param progress (null) or object implementing IProgressIndicator
2330 public static void showUrl(final String url, final IProgressIndicator progress) {
2331 new Thread(new Runnable() {
2335 if (progress != null) {
2336 progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[] { url }),
2339 jalview.util.BrowserLauncher.openURL(url);
2340 } catch (Exception ex) {
2341 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2342 MessageManager.getString("label.web_browser_not_found_unix"),
2343 MessageManager.getString("label.web_browser_not_found"), JvOptionPane.WARNING_MESSAGE);
2345 ex.printStackTrace();
2347 if (progress != null) {
2348 progress.setProgressBar(null, this.hashCode());
2354 public static WsParamSetManager wsparamManager = null;
2356 public static ParamManager getUserParameterStore() {
2357 if (wsparamManager == null) {
2358 wsparamManager = new WsParamSetManager();
2360 return wsparamManager;
2364 * static hyperlink handler proxy method for use by Jalview's internal windows
2368 public static void hyperlinkUpdate(HyperlinkEvent e) {
2369 if (e.getEventType() == EventType.ACTIVATED) {
2372 url = e.getURL().toString();
2373 Desktop.showUrl(url);
2374 } catch (Exception x) {
2376 jalview.bin.Console.error("Couldn't handle string " + url + " as a URL.");
2378 // ignore any exceptions due to dud links.
2385 * single thread that handles display of dialogs to user.
2387 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2390 * flag indicating if dialogExecutor should try to acquire a permit
2392 private volatile boolean dialogPause = true;
2397 private java.util.concurrent.Semaphore block = new Semaphore(0);
2399 private static groovy.ui.Console groovyConsole;
2402 * add another dialog thread to the queue
2406 public void addDialogThread(final Runnable prompter) {
2407 dialogExecutor.submit(new Runnable() {
2413 } catch (InterruptedException x) {
2416 if (instance == null) {
2420 SwingUtilities.invokeAndWait(prompter);
2421 } catch (Exception q) {
2422 jalview.bin.Console.warn("Unexpected Exception in dialog thread.", q);
2428 public void startDialogQueue() {
2429 // set the flag so we don't pause waiting for another permit and semaphore
2430 // the current task to begin
2431 dialogPause = false;
2436 * Outputs an image of the desktop to file in EPS format, after prompting the
2437 * user for choice of Text or Lineart character rendering (unless a preference
2438 * has been set). The file name is generated as
2441 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2445 protected void snapShotWindow_actionPerformed(ActionEvent e) {
2446 // currently the menu option to do this is not shown
2449 int width = getWidth();
2450 int height = getHeight();
2451 File of = new File("Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2452 ImageWriterI writer = new ImageWriterI() {
2454 public void exportImage(Graphics g) throws Exception {
2456 jalview.bin.Console.info("Successfully written snapshot to file " + of.getAbsolutePath());
2459 String title = "View of desktop";
2460 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS, title);
2461 exporter.doExport(of, this, width, height, title);
2465 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2466 * This respects (remembers) any previous 'exploded geometry' i.e. the size and
2467 * location last time the view was expanded (if any). However it does not
2468 * remember the split pane divider location - this is set to match the
2469 * 'exploding' frame.
2473 public void explodeViews(SplitFrame sf) {
2474 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2475 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2476 List<? extends AlignmentViewPanel> topPanels = oldTopFrame.getAlignPanels();
2477 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame.getAlignPanels();
2478 int viewCount = topPanels.size();
2479 if (viewCount < 2) {
2484 * Processing in reverse order works, forwards order leaves the first panels not
2485 * visible. I don't know why!
2487 for (int i = viewCount - 1; i >= 0; i--) {
2489 * Make new top and bottom frames. These take over the respective AlignmentPanel
2490 * objects, including their AlignmentViewports, so the cdna/protein
2491 * relationships between the viewports is carried over to the new split frames.
2493 * explodedGeometry holds the (x, y) position of the previously exploded
2494 * SplitFrame, and the (width, height) of the AlignFrame component
2496 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
2497 AlignFrame newTopFrame = new AlignFrame(topPanel);
2498 newTopFrame.setSize(oldTopFrame.getSize());
2499 newTopFrame.setVisible(true);
2500 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport()).getExplodedGeometry();
2501 if (geometry != null) {
2502 newTopFrame.setSize(geometry.getSize());
2505 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
2506 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
2507 newBottomFrame.setSize(oldBottomFrame.getSize());
2508 newBottomFrame.setVisible(true);
2509 geometry = ((AlignViewport) bottomPanel.getAlignViewport()).getExplodedGeometry();
2510 if (geometry != null) {
2511 newBottomFrame.setSize(geometry.getSize());
2514 topPanel.av.setGatherViewsHere(false);
2515 bottomPanel.av.setGatherViewsHere(false);
2516 JInternalFrame splitFrame = new SplitFrame(newTopFrame, newBottomFrame);
2517 if (geometry != null) {
2518 splitFrame.setLocation(geometry.getLocation());
2520 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
2524 * Clear references to the panels (now relocated in the new SplitFrames) before
2525 * closing the old SplitFrame.
2528 bottomPanels.clear();
2533 * Gather expanded split frames, sharing the same pairs of sequence set ids,
2534 * back into the given SplitFrame as additional views. Note that the gathered
2535 * frames may themselves have multiple views.
2539 public void gatherViews(GSplitFrame source) {
2541 * special handling of explodedGeometry for a view within a SplitFrame: - it
2542 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
2543 * height) of the AlignFrame component
2545 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
2546 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
2547 myTopFrame.viewport.setExplodedGeometry(
2548 new Rectangle(source.getX(), source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
2549 myBottomFrame.viewport.setExplodedGeometry(
2550 new Rectangle(source.getX(), source.getY(), myBottomFrame.getWidth(), myBottomFrame.getHeight()));
2551 myTopFrame.viewport.setGatherViewsHere(true);
2552 myBottomFrame.viewport.setGatherViewsHere(true);
2553 String topViewId = myTopFrame.viewport.getSequenceSetId();
2554 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
2556 JInternalFrame[] frames = desktop.getAllFrames();
2557 for (JInternalFrame frame : frames) {
2558 if (frame instanceof SplitFrame && frame != source) {
2559 SplitFrame sf = (SplitFrame) frame;
2560 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
2561 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
2562 boolean gatherThis = false;
2563 for (int a = 0; a < topFrame.alignPanels.size(); a++) {
2564 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
2565 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
2566 if (topViewId.equals(topPanel.av.getSequenceSetId())
2567 && bottomViewId.equals(bottomPanel.av.getSequenceSetId())) {
2569 topPanel.av.setGatherViewsHere(false);
2570 bottomPanel.av.setGatherViewsHere(false);
2571 topPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), topFrame.getSize()));
2572 bottomPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), bottomFrame.getSize()));
2573 myTopFrame.addAlignmentPanel(topPanel, false);
2574 myBottomFrame.addAlignmentPanel(bottomPanel, false);
2579 topFrame.getAlignPanels().clear();
2580 bottomFrame.getAlignPanels().clear();
2587 * The dust settles...give focus to the tab we did this from.
2589 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
2592 public static groovy.ui.Console getGroovyConsole() {
2593 return groovyConsole;
2597 * handles the payload of a drag and drop event.
2599 * TODO refactor to desktop utilities class
2601 * @param files - Data source strings extracted from the drop event
2602 * @param protocols - protocol for each data source extracted from the drop
2604 * @param evt - the drop event
2605 * @param t - the payload from the drop event
2608 public static void transferFromDropTarget(List<Object> files, List<DataSourceType> protocols, DropTargetDropEvent evt,
2609 Transferable t) throws Exception {
2611 DataFlavor uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"), urlFlavour = null;
2613 urlFlavour = new DataFlavor("application/x-java-url; class=java.net.URL");
2614 } catch (ClassNotFoundException cfe) {
2615 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.", cfe);
2618 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) {
2621 java.net.URL url = (URL) t.getTransferData(urlFlavour);
2622 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
2623 // means url may be null.
2625 protocols.add(DataSourceType.URL);
2626 files.add(url.toString());
2627 jalview.bin.Console.debug("Drop handled as URL dataflavor " + files.get(files.size() - 1));
2630 if (Platform.isAMacAndNotJS()) {
2631 System.err.println("Please ignore plist error - occurs due to problem with java 8 on OSX");
2634 } catch (Throwable ex) {
2635 jalview.bin.Console.debug("URL drop handler failed.", ex);
2638 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
2639 // Works on Windows and MacOSX
2640 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
2641 for (Object file : (List) t.getTransferData(DataFlavor.javaFileListFlavor)) {
2643 protocols.add(DataSourceType.FILE);
2646 // Unix like behaviour
2647 boolean added = false;
2649 if (t.isDataFlavorSupported(uriListFlavor)) {
2650 jalview.bin.Console.debug("Drop handled as uriListFlavor");
2651 // This is used by Unix drag system
2652 data = (String) t.getTransferData(uriListFlavor);
2655 // fallback to text: workaround - on OSX where there's a JVM bug
2656 jalview.bin.Console.debug("standard URIListFlavor failed. Trying text");
2657 // try text fallback
2658 DataFlavor textDf = new DataFlavor("text/plain;class=java.lang.String");
2659 if (t.isDataFlavorSupported(textDf)) {
2660 data = (String) t.getTransferData(textDf);
2663 jalview.bin.Console.debug("Plain text drop content returned " + (data == null ? "Null - failed" : data));
2667 while (protocols.size() < files.size()) {
2668 jalview.bin.Console.debug("Adding missing FILE protocol for " + files.get(protocols.size()));
2669 protocols.add(DataSourceType.FILE);
2671 for (java.util.StringTokenizer st = new java.util.StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
2673 String s = st.nextToken();
2674 if (s.startsWith("#")) {
2675 // the line is a comment (as per the RFC 2483)
2678 java.net.URI uri = new java.net.URI(s);
2679 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http")) {
2680 protocols.add(DataSourceType.URL);
2681 files.add(uri.toString());
2683 // otherwise preserve old behaviour: catch all for file objects
2684 java.io.File file = new java.io.File(uri);
2685 protocols.add(DataSourceType.FILE);
2686 files.add(file.toString());
2691 if (jalview.bin.Console.isDebugEnabled()) {
2692 if (data == null || !added) {
2694 if (t.getTransferDataFlavors() != null && t.getTransferDataFlavors().length > 0) {
2695 jalview.bin.Console.debug("Couldn't resolve drop data. Here are the supported flavors:");
2696 for (DataFlavor fl : t.getTransferDataFlavors()) {
2697 jalview.bin.Console.debug("Supported transfer dataflavor: " + fl.toString());
2698 Object df = t.getTransferData(fl);
2700 jalview.bin.Console.debug("Retrieves: " + df);
2702 jalview.bin.Console.debug("Retrieved nothing");
2706 jalview.bin.Console.debug("Couldn't resolve dataflavor for drop: " + t.toString());
2711 if (Platform.isWindowsAndNotJS()) {
2712 jalview.bin.Console.debug("Scanning dropped content for Windows Link Files");
2714 // resolve any .lnk files in the file drop
2715 for (int f = 0; f < files.size(); f++) {
2716 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
2717 if (protocols.get(f).equals(DataSourceType.FILE)
2718 && (source.endsWith(".lnk") || source.endsWith(".url") || source.endsWith(".site"))) {
2720 Object obj = files.get(f);
2721 File lf = (obj instanceof File ? (File) obj : new File((String) obj));
2722 // process link file to get a URL
2723 jalview.bin.Console.debug("Found potential link file: " + lf);
2724 WindowsShortcut wscfile = new WindowsShortcut(lf);
2725 String fullname = wscfile.getRealFilename();
2726 protocols.set(f, FormatAdapter.checkProtocol(fullname));
2727 files.set(f, fullname);
2728 jalview.bin.Console.debug("Parsed real filename " + fullname + " to extract protocol: " + protocols.get(f));
2729 } catch (Exception ex) {
2730 jalview.bin.Console.error("Couldn't parse " + files.get(f) + " as a link file.", ex);
2738 * Sets the Preferences property for experimental features to True or False
2739 * depending on the state of the controlling menu item
2742 protected void showExperimental_actionPerformed(boolean selected) {
2743 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
2747 * Answers a (possibly empty) list of any structure viewer frames (currently for
2748 * either Jmol or Chimera) which are currently open. This may optionally be
2749 * restricted to viewers of a specified class, or viewers linked to a specified
2752 * @param apanel if not null, only return viewers linked to this
2754 * @param structureViewerClass if not null, only return viewers of this class
2757 public List<StructureViewerBase> getStructureViewers(AlignmentPanel apanel,
2758 Class<? extends StructureViewerBase> structureViewerClass) {
2759 List<StructureViewerBase> result = new ArrayList<>();
2760 JInternalFrame[] frames = Desktop.instance.getAllFrames();
2762 for (JInternalFrame frame : frames) {
2763 if (frame instanceof StructureViewerBase) {
2764 if (structureViewerClass == null || structureViewerClass.isInstance(frame)) {
2765 if (apanel == null || ((StructureViewerBase) frame).isLinkedWith(apanel)) {
2766 result.add((StructureViewerBase) frame);
2774 public static final String debugScaleMessage = "Desktop graphics transform scale=";
2776 private static boolean debugScaleMessageDone = false;
2778 public static void debugScaleMessage(Graphics g) {
2779 if (debugScaleMessageDone) {
2782 // output used by tests to check HiDPI scaling settings in action
2784 Graphics2D gg = (Graphics2D) g;
2786 AffineTransform t = gg.getTransform();
2787 double scaleX = t.getScaleX();
2788 double scaleY = t.getScaleY();
2789 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
2790 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
2791 debugScaleMessageDone = true;
2793 jalview.bin.Console.debug("Desktop graphics null");
2795 } catch (Exception e) {
2796 jalview.bin.Console.debug(Cache.getStackTraceString(e));