2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Semaphore;
71 import javax.swing.AbstractAction;
72 import javax.swing.Action;
73 import javax.swing.ActionMap;
74 import javax.swing.Box;
75 import javax.swing.BoxLayout;
76 import javax.swing.DefaultDesktopManager;
77 import javax.swing.DesktopManager;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JComboBox;
82 import javax.swing.JComponent;
83 import javax.swing.JDesktopPane;
84 import javax.swing.JInternalFrame;
85 import javax.swing.JLabel;
86 import javax.swing.JMenuItem;
87 import javax.swing.JPanel;
88 import javax.swing.JPopupMenu;
89 import javax.swing.JProgressBar;
90 import javax.swing.JTextField;
91 import javax.swing.KeyStroke;
92 import javax.swing.SwingUtilities;
93 import javax.swing.event.HyperlinkEvent;
94 import javax.swing.event.HyperlinkEvent.EventType;
95 import javax.swing.event.InternalFrameAdapter;
96 import javax.swing.event.InternalFrameEvent;
98 import org.stackoverflowusers.file.WindowsShortcut;
100 import jalview.api.AlignViewportI;
101 import jalview.api.AlignmentViewPanel;
102 import jalview.bin.Cache;
103 import jalview.bin.Jalview;
104 import jalview.gui.ImageExporter.ImageWriterI;
105 import jalview.io.BackupFiles;
106 import jalview.io.DataSourceType;
107 import jalview.io.FileFormat;
108 import jalview.io.FileFormatException;
109 import jalview.io.FileFormatI;
110 import jalview.io.FileFormats;
111 import jalview.io.FileLoader;
112 import jalview.io.FormatAdapter;
113 import jalview.io.IdentifyFile;
114 import jalview.io.JalviewFileChooser;
115 import jalview.io.JalviewFileView;
116 import jalview.jbgui.ClassicJInternalFrame;
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.LaunchUtils;
126 import jalview.util.MessageManager;
127 import jalview.util.Platform;
128 import jalview.util.ShortcutKeyMaskExWrapper;
129 import jalview.util.UrlConstants;
130 import jalview.viewmodel.AlignmentViewport;
131 import jalview.ws.params.ParamManager;
132 import jalview.ws.utils.UrlDownloadClient;
139 * @version $Revision: 1.155 $
141 public class Desktop extends jalview.jbgui.GDesktop
142 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
143 jalview.api.StructureSelectionManagerProvider
145 private static final String CITATION;
148 URL bg_logo_url = ChannelProperties.getImageURL(
149 "bg_logo." + String.valueOf(SplashScreen.logoSize));
150 URL uod_logo_url = ChannelProperties.getImageURL(
151 "uod_banner." + String.valueOf(SplashScreen.logoSize));
152 boolean logo = (bg_logo_url != null || uod_logo_url != null);
153 StringBuilder sb = new StringBuilder();
155 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
160 sb.append(bg_logo_url == null ? ""
161 : "<img alt=\"Barton Group logo\" src=\""
162 + bg_logo_url.toString() + "\">");
163 sb.append(uod_logo_url == null ? ""
164 : " <img alt=\"University of Dundee shield\" src=\""
165 + uod_logo_url.toString() + "\">");
167 "<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>");
168 sb.append("<br><br>If you use Jalview, please cite:"
169 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
170 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
171 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
172 CITATION = sb.toString();
175 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
177 private static int DEFAULT_MIN_WIDTH = 300;
179 private static int DEFAULT_MIN_HEIGHT = 250;
181 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
183 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
185 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
187 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
189 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
191 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
193 public static boolean nosplash = false;
196 * news reader - null if it was never started.
198 private BlogReader jvnews = null;
200 private File projectFile;
204 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
206 public void addJalviewPropertyChangeListener(
207 PropertyChangeListener listener)
209 changeSupport.addJalviewPropertyChangeListener(listener);
213 * @param propertyName
215 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
216 * java.beans.PropertyChangeListener)
218 public void addJalviewPropertyChangeListener(String propertyName,
219 PropertyChangeListener listener)
221 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
225 * @param propertyName
227 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
228 * java.beans.PropertyChangeListener)
230 public void removeJalviewPropertyChangeListener(String propertyName,
231 PropertyChangeListener listener)
233 changeSupport.removeJalviewPropertyChangeListener(propertyName,
237 /** Singleton Desktop instance */
238 public static Desktop instance;
240 public static MyDesktopPane desktop;
242 public static MyDesktopPane getDesktop()
244 // BH 2018 could use currentThread() here as a reference to a
245 // Hashtable<Thread, MyDesktopPane> in JavaScript
249 static int openFrameCount = 0;
251 static final int xOffset = 30;
253 static final int yOffset = 30;
255 public static jalview.ws.jws1.Discoverer discoverer;
257 public static Object[] jalviewClipboard;
259 public static boolean internalCopy = false;
261 static int fileLoadingCount = 0;
263 class MyDesktopManager implements DesktopManager
266 private DesktopManager delegate;
268 public MyDesktopManager(DesktopManager delegate)
270 this.delegate = delegate;
274 public void activateFrame(JInternalFrame f)
278 delegate.activateFrame(f);
279 } catch (NullPointerException npe)
281 Point p = getMousePosition();
282 instance.showPasteMenu(p.x, p.y);
287 public void beginDraggingFrame(JComponent f)
289 delegate.beginDraggingFrame(f);
293 public void beginResizingFrame(JComponent f, int direction)
295 delegate.beginResizingFrame(f, direction);
299 public void closeFrame(JInternalFrame f)
301 delegate.closeFrame(f);
305 public void deactivateFrame(JInternalFrame f)
307 delegate.deactivateFrame(f);
311 public void deiconifyFrame(JInternalFrame f)
313 delegate.deiconifyFrame(f);
317 public void dragFrame(JComponent f, int newX, int newY)
323 delegate.dragFrame(f, newX, newY);
327 public void endDraggingFrame(JComponent f)
329 delegate.endDraggingFrame(f);
334 public void endResizingFrame(JComponent f)
336 delegate.endResizingFrame(f);
341 public void iconifyFrame(JInternalFrame f)
343 delegate.iconifyFrame(f);
347 public void maximizeFrame(JInternalFrame f)
349 delegate.maximizeFrame(f);
353 public void minimizeFrame(JInternalFrame f)
355 delegate.minimizeFrame(f);
359 public void openFrame(JInternalFrame f)
361 delegate.openFrame(f);
365 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
372 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
376 public void setBoundsForFrame(JComponent f, int newX, int newY,
377 int newWidth, int newHeight)
379 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
382 // All other methods, simply delegate
387 * Creates a new Desktop object.
393 * A note to implementors. It is ESSENTIAL that any activities that might
394 * block are spawned off as threads rather than waited for during this
399 doConfigureStructurePrefs();
400 setTitle(ChannelProperties.getProperty("app_name") + " "
401 + Cache.getProperty("VERSION"));
404 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
405 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
406 * officially documented or guaranteed to exist, so we access it via
407 * reflection. There appear to be unfathomable criteria about what this
408 * string can contain, and it if doesn't meet those criteria then "java"
409 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
410 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
411 * not. The reflection access may generate a warning: WARNING: An illegal
412 * reflective access operation has occurred WARNING: Illegal reflective
413 * access by jalview.gui.Desktop () to field
414 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
416 if (Platform.isLinux())
418 if (LaunchUtils.getJavaVersion() >= 11)
420 jalview.bin.Console.info(
421 "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.");
425 Toolkit xToolkit = Toolkit.getDefaultToolkit();
426 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
427 Field awtAppClassNameField = null;
429 if (Arrays.stream(declaredFields)
430 .anyMatch(f -> f.getName().equals("awtAppClassName")))
432 awtAppClassNameField = xToolkit.getClass()
433 .getDeclaredField("awtAppClassName");
436 String title = ChannelProperties.getProperty("app_name");
437 if (awtAppClassNameField != null)
439 awtAppClassNameField.setAccessible(true);
440 awtAppClassNameField.set(xToolkit, title);
444 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
446 } catch (Exception e)
448 jalview.bin.Console.debug("Error setting awtAppClassName");
449 jalview.bin.Console.trace(Cache.getStackTraceString(e));
454 * APQHandlers sets handlers for About, Preferences and Quit actions
455 * peculiar to macOS's application menu. APQHandlers will check to see if a
456 * handler is supported before setting it.
460 APQHandlers.setAPQHandlers(this);
461 } catch (Exception e)
463 System.out.println("Cannot set APQHandlers");
464 // e.printStackTrace();
465 } catch (Throwable t)
468 .warn("Error setting APQHandlers: " + t.toString());
469 jalview.bin.Console.trace(Cache.getStackTraceString(t));
471 setIconImages(ChannelProperties.getIconList());
473 addWindowListener(new WindowAdapter()
477 public void windowClosing(WindowEvent ev)
483 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
485 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
486 desktop = new MyDesktopPane(selmemusage);
488 showMemusage.setSelected(selmemusage);
489 desktop.setBackground(Color.white);
491 getContentPane().setLayout(new BorderLayout());
492 // alternate config - have scrollbars - see notes in JAL-153
493 // JScrollPane sp = new JScrollPane();
494 // sp.getViewport().setView(desktop);
495 // getContentPane().add(sp, BorderLayout.CENTER);
497 // BH 2018 - just an experiment to try unclipped ClassicJInternalFrames.
500 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
503 getContentPane().add(desktop, BorderLayout.CENTER);
504 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
506 // This line prevents Windows Look&Feel resizing all new windows to maximum
507 // if previous window was maximised
508 desktop.setDesktopManager(new MyDesktopManager(
509 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
510 : Platform.isAMacAndNotJS()
511 ? new AquaInternalFrameManager(
512 desktop.getDesktopManager())
513 : desktop.getDesktopManager())));
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));
593 this.addWindowListener(new WindowAdapter()
596 public void windowClosing(WindowEvent evt)
603 this.addMouseListener(ma = new MouseAdapter()
606 public void mousePressed(MouseEvent evt)
608 if (evt.isPopupTrigger()) // Mac
610 showPasteMenu(evt.getX(), evt.getY());
615 public void mouseReleased(MouseEvent evt)
617 if (evt.isPopupTrigger()) // Windows
619 showPasteMenu(evt.getX(), evt.getY());
623 desktop.addMouseListener(ma);
627 * Answers true if user preferences to enable experimental features is True
632 public boolean showExperimental()
634 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
635 Boolean.FALSE.toString());
636 return Boolean.valueOf(experimental).booleanValue();
639 public void doConfigureStructurePrefs()
641 // configure services
642 StructureSelectionManager ssm = StructureSelectionManager
643 .getStructureSelectionManager(this);
644 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
646 ssm.setAddTempFacAnnot(
647 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
648 ssm.setProcessSecondaryStructure(
649 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
650 // JAL-3915 - RNAView is no longer an option so this has no effect
651 ssm.setSecStructServices(
652 Cache.getDefault(Preferences.USE_RNAVIEW, false));
656 ssm.setAddTempFacAnnot(false);
657 ssm.setProcessSecondaryStructure(false);
658 ssm.setSecStructServices(false);
662 public void checkForNews()
664 final Desktop me = this;
665 // Thread off the news reader, in case there are connection problems.
666 new Thread(new Runnable()
671 jalview.bin.Console.debug("Starting news thread.");
672 jvnews = new BlogReader(me);
673 showNews.setVisible(true);
674 jalview.bin.Console.debug("Completed news thread.");
679 public void getIdentifiersOrgData()
681 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
682 {// Thread off the identifiers fetcher
683 new Thread(new Runnable()
689 .debug("Downloading data from identifiers.org");
692 UrlDownloadClient.download(IdOrgSettings.getUrl(),
693 IdOrgSettings.getDownloadLocation());
694 } catch (IOException e)
697 .debug("Exception downloading identifiers.org data"
707 protected void showNews_actionPerformed(ActionEvent e)
709 showNews(showNews.isSelected());
712 void showNews(boolean visible)
714 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
715 showNews.setSelected(visible);
716 if (visible && !jvnews.isVisible())
718 new Thread(new Runnable()
723 long now = System.currentTimeMillis();
724 Desktop.instance.setProgressBar(
725 MessageManager.getString("status.refreshing_news"), now);
726 jvnews.refreshNews();
727 Desktop.instance.setProgressBar(null, now);
735 * recover the last known dimensions for a jalview window
738 * - empty string is desktop, all other windows have unique prefix
739 * @return null or last known dimensions scaled to current geometry (if last
740 * window geom was known)
742 Rectangle getLastKnownDimensions(String windowName)
744 // TODO: lock aspect ratio for scaling desktop Bug #0058199
745 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
746 String x = Cache.getProperty(windowName + "SCREEN_X");
747 String y = Cache.getProperty(windowName + "SCREEN_Y");
748 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
749 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
750 if ((x != null) && (y != null) && (width != null) && (height != null))
752 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
753 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
754 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
756 // attempt #1 - try to cope with change in screen geometry - this
757 // version doesn't preserve original jv aspect ratio.
758 // take ratio of current screen size vs original screen size.
759 double sw = ((1f * screenSize.width) / (1f * Integer
760 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
761 double sh = ((1f * screenSize.height) / (1f * Integer
762 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
763 // rescale the bounds depending upon the current screen geometry.
764 ix = (int) (ix * sw);
765 iw = (int) (iw * sw);
766 iy = (int) (iy * sh);
767 ih = (int) (ih * sh);
768 while (ix >= screenSize.width)
770 jalview.bin.Console.debug(
771 "Window geometry location recall error: shifting horizontal to within screenbounds.");
772 ix -= screenSize.width;
774 while (iy >= screenSize.height)
776 jalview.bin.Console.debug(
777 "Window geometry location recall error: shifting vertical to within screenbounds.");
778 iy -= screenSize.height;
780 jalview.bin.Console.debug(
781 "Got last known dimensions for " + windowName + ": x:" + ix
782 + " y:" + iy + " width:" + iw + " height:" + ih);
784 // return dimensions for new instance
785 return new Rectangle(ix, iy, iw, ih);
790 void showPasteMenu(int x, int y)
792 JPopupMenu popup = new JPopupMenu();
793 JMenuItem item = new JMenuItem(
794 MessageManager.getString("label.paste_new_window"));
795 item.addActionListener(new ActionListener()
798 public void actionPerformed(ActionEvent evt)
805 popup.show(this, x, y);
812 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
813 Transferable contents = c.getContents(this);
815 if (contents != null)
817 String file = (String) contents
818 .getTransferData(DataFlavor.stringFlavor);
820 FileFormatI format = new IdentifyFile().identify(file,
821 DataSourceType.PASTE);
823 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
826 } catch (Exception ex)
829 "Unable to paste alignment from system clipboard:\n" + ex);
834 * Adds and opens the given frame to the desktop
845 public static synchronized void addInternalFrame(
846 final ClassicJInternalFrame frame, String title, int w, int h)
848 addInternalFrame(frame, title, true, w, h, true, false);
852 * Add an internal frame to the Jalview desktop
859 * When true, display frame immediately, otherwise, caller must call
860 * setVisible themselves.
866 public static synchronized void addInternalFrame(
867 final ClassicJInternalFrame frame, String title,
868 boolean makeVisible, int w, int h)
870 addInternalFrame(frame, title, makeVisible, w, h, true, false);
874 * Add an internal frame to the Jalview desktop and make it visible
887 public static synchronized void addInternalFrame(
888 final ClassicJInternalFrame frame, String title, int w, int h,
891 addInternalFrame(frame, title, true, w, h, resizable, false);
895 * Add an internal frame to the Jalview desktop
902 * When true, display frame immediately, otherwise, caller must call
903 * setVisible themselves.
910 * @param ignoreMinSize
911 * Do not set the default minimum size for frame
913 public static synchronized void addInternalFrame(
914 final ClassicJInternalFrame frame, String title,
915 boolean makeVisible, int w, int h, boolean resizable,
916 boolean ignoreMinSize)
919 // TODO: allow callers to determine X and Y position of frame (eg. via
921 // TODO: consider fixing method to update entries in the window submenu with
922 // the current window title
924 frame.setTitle(title);
925 if (frame.getWidth() < 1 || frame.getHeight() < 1)
929 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
930 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
931 // IF JALVIEW IS RUNNING HEADLESS
932 // ///////////////////////////////////////////////
933 if (instance == null || (System.getProperty("java.awt.headless") != null
934 && System.getProperty("java.awt.headless").equals("true")))
943 frame.setMinimumSize(
944 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
946 // Set default dimension for Alignment Frame window.
947 // The Alignment Frame window could be added from a number of places,
949 // I did this here in order not to miss out on any Alignment frame.
950 if (frame instanceof AlignFrame)
952 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
953 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
957 frame.setVisible(makeVisible);
958 frame.setClosable(true);
959 frame.setResizable(resizable);
960 frame.setMaximizable(resizable);
961 frame.setIconifiable(resizable);
962 frame.setOpaque(Platform.isJS());
964 if (frame.getX() < 1 && frame.getY() < 1)
966 frame.setLocation(xOffset * openFrameCount,
967 yOffset * ((openFrameCount - 1) % 10) + yOffset);
971 * add an entry for the new frame in the Window menu (and remove it when the
974 final JMenuItem menuItem = new JMenuItem(title);
975 frame.addInternalFrameListener(new InternalFrameAdapter()
978 public void internalFrameActivated(InternalFrameEvent evt)
980 ClassicJInternalFrame itf = desktop.getSelectedFrame();
983 if (itf instanceof AlignFrame)
985 Jalview.setCurrentAlignFrame((AlignFrame) itf);
992 public void internalFrameClosed(InternalFrameEvent evt)
994 PaintRefresher.RemoveComponent(frame);
997 * defensive check to prevent frames being added half off the window
999 if (openFrameCount > 0)
1005 * ensure no reference to alignFrame retained by menu item listener
1007 if (menuItem.getActionListeners().length > 0)
1009 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
1011 windowMenu.remove(menuItem);
1015 menuItem.addActionListener(new ActionListener()
1018 public void actionPerformed(ActionEvent e)
1022 frame.setSelected(true);
1023 frame.setIcon(false);
1024 } catch (java.beans.PropertyVetoException ex)
1031 setKeyBindings(frame);
1035 windowMenu.add(menuItem);
1040 frame.setSelected(true);
1041 frame.requestFocus();
1042 } catch (java.beans.PropertyVetoException ve)
1044 } catch (java.lang.ClassCastException cex)
1046 jalview.bin.Console.warn(
1047 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1053 * Add key bindings to a ClassicJInternalFrame so that Ctrl-W and Cmd-W will
1058 private static void setKeyBindings(ClassicJInternalFrame frame)
1060 @SuppressWarnings("serial")
1061 final Action closeAction = new AbstractAction()
1064 public void actionPerformed(ActionEvent e)
1071 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1073 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1074 InputEvent.CTRL_DOWN_MASK);
1075 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1076 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1078 InputMap inputMap = frame
1079 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1080 String ctrlW = ctrlWKey.toString();
1081 inputMap.put(ctrlWKey, ctrlW);
1082 inputMap.put(cmdWKey, ctrlW);
1084 ActionMap actionMap = frame.getActionMap();
1085 actionMap.put(ctrlW, closeAction);
1089 public void lostOwnership(Clipboard clipboard, Transferable contents)
1093 Desktop.jalviewClipboard = null;
1096 internalCopy = false;
1100 public void dragEnter(DropTargetDragEvent evt)
1105 public void dragExit(DropTargetEvent evt)
1110 public void dragOver(DropTargetDragEvent evt)
1115 public void dropActionChanged(DropTargetDragEvent evt)
1126 public void drop(DropTargetDropEvent evt)
1128 boolean success = true;
1129 // JAL-1552 - acceptDrop required before getTransferable call for
1130 // Java's Transferable for native dnd
1131 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1132 Transferable t = evt.getTransferable();
1133 List<Object> files = new ArrayList<>();
1134 List<DataSourceType> protocols = new ArrayList<>();
1138 Desktop.transferFromDropTarget(files, protocols, evt, t);
1139 } catch (Exception e)
1141 e.printStackTrace();
1149 for (int i = 0; i < files.size(); i++)
1151 // BH 2018 File or String
1152 Object file = files.get(i);
1153 String fileName = file.toString();
1154 DataSourceType protocol = (protocols == null)
1155 ? DataSourceType.FILE
1157 FileFormatI format = null;
1159 if (fileName.endsWith(".jar"))
1161 format = FileFormat.Jalview;
1166 format = new IdentifyFile().identify(file, protocol);
1168 if (file instanceof File)
1170 Platform.cacheFileData((File) file);
1172 new FileLoader().LoadFile(null, file, protocol, format);
1175 } catch (Exception ex)
1180 evt.dropComplete(success); // need this to ensure input focus is properly
1181 // transfered to any new windows created
1191 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1193 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1194 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1195 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1196 BackupFiles.getEnabled());
1198 chooser.setFileView(new JalviewFileView());
1199 chooser.setDialogTitle(
1200 MessageManager.getString("label.open_local_file"));
1201 chooser.setToolTipText(MessageManager.getString("action.open"));
1203 chooser.setResponseHandler(0, new Runnable()
1208 File selectedFile = chooser.getSelectedFile();
1209 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1211 FileFormatI format = chooser.getSelectedFormat();
1214 * Call IdentifyFile to verify the file contains what its extension implies.
1215 * Skip this step for dynamically added file formats, because IdentifyFile does
1216 * not know how to recognise them.
1218 if (FileFormats.getInstance().isIdentifiable(format))
1222 format = new IdentifyFile().identify(selectedFile,
1223 DataSourceType.FILE);
1224 } catch (FileFormatException e)
1226 // format = null; //??
1230 new FileLoader().LoadFile(viewport, selectedFile,
1231 DataSourceType.FILE, format);
1234 chooser.showOpenDialog(this);
1238 * Shows a dialog for input of a URL at which to retrieve alignment data
1243 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1245 // This construct allows us to have a wider textfield
1247 JLabel label = new JLabel(
1248 MessageManager.getString("label.input_file_url"));
1250 JPanel panel = new JPanel(new GridLayout(2, 1));
1254 * the URL to fetch is input in Java: an editable combobox with history JS:
1255 * (pending JAL-3038) a plain text field
1258 String urlBase = "https://www.";
1259 if (Platform.isJS())
1261 history = new JTextField(urlBase, 35);
1270 JComboBox<String> asCombo = new JComboBox<>();
1271 asCombo.setPreferredSize(new Dimension(400, 20));
1272 asCombo.setEditable(true);
1273 asCombo.addItem(urlBase);
1274 String historyItems = Cache.getProperty("RECENT_URL");
1275 if (historyItems != null)
1277 for (String token : historyItems.split("\\t"))
1279 asCombo.addItem(token);
1286 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1287 MessageManager.getString("action.cancel") };
1288 Runnable action = new Runnable()
1293 @SuppressWarnings("unchecked")
1294 String url = (history instanceof JTextField
1295 ? ((JTextField) history).getText()
1296 : ((JComboBox<String>) history).getEditor().getItem()
1297 .toString().trim());
1299 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1301 if (viewport != null)
1303 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1304 FileFormat.Jalview);
1308 new FileLoader().LoadFile(url, DataSourceType.URL,
1309 FileFormat.Jalview);
1314 FileFormatI format = null;
1317 format = new IdentifyFile().identify(url, DataSourceType.URL);
1318 } catch (FileFormatException e)
1320 // TODO revise error handling, distinguish between
1321 // URL not found and response not valid
1326 String msg = MessageManager
1327 .formatMessage("label.couldnt_locate", url);
1328 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1329 MessageManager.getString("label.url_not_found"),
1330 JvOptionPane.WARNING_MESSAGE);
1335 if (viewport != null)
1337 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1342 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1347 String dialogOption = MessageManager
1348 .getString("label.input_alignment_from_url");
1349 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1350 .showInternalDialog(panel, dialogOption,
1351 JvOptionPane.YES_NO_CANCEL_OPTION,
1352 JvOptionPane.PLAIN_MESSAGE, null, options,
1353 MessageManager.getString("action.ok"));
1357 * Opens the CutAndPaste window for the user to paste an alignment in to
1360 * - if not null, the pasted alignment is added to the current
1361 * alignment; if null, to a new alignment window
1364 public void inputTextboxMenuItem_actionPerformed(
1365 AlignmentViewPanel viewPanel)
1367 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1368 cap.setForInput(viewPanel);
1369 Desktop.addInternalFrame(cap,
1370 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1380 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1381 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1382 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1383 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1384 getWidth(), getHeight()));
1386 if (jconsole != null)
1388 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1389 jconsole.stopConsole();
1393 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1396 if (dialogExecutor != null)
1398 dialogExecutor.shutdownNow();
1400 closeAll_actionPerformed(null);
1402 if (groovyConsole != null)
1404 // suppress a possible repeat prompt to save script
1405 groovyConsole.setDirty(false);
1406 groovyConsole.exit();
1411 private void storeLastKnownDimensions(String string, Rectangle jc)
1413 jalview.bin.Console.debug("Storing last known dimensions for " + string
1414 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1415 + " height:" + jc.height);
1417 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1418 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1419 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1420 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1430 public void aboutMenuItem_actionPerformed(ActionEvent e)
1432 new Thread(new Runnable()
1437 new SplashScreen(false);
1443 * Returns the html text for the About screen, including any available version
1444 * number, build details, author details and citation reference, but without
1445 * the enclosing {@code html} tags
1449 public String getAboutMessage()
1451 StringBuilder message = new StringBuilder(1024);
1452 message.append("<div style=\"font-family: sans-serif;\">")
1453 .append("<h1><strong>Version: ")
1454 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1455 .append("<strong>Built: <em>")
1456 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1457 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1458 .append("</strong>");
1460 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1461 if (latestVersion.equals("Checking"))
1463 // JBP removed this message for 2.11: May be reinstated in future version
1464 // message.append("<br>...Checking latest version...</br>");
1466 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1468 boolean red = false;
1469 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1470 .indexOf("automated build") == -1)
1473 // Displayed when code version and jnlp version do not match and code
1474 // version is not a development build
1475 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1478 message.append("<br>!! Version ")
1479 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1480 .append(" is available for download from ")
1481 .append(Cache.getDefault("www.jalview.org",
1482 "https://www.jalview.org"))
1486 message.append("</div>");
1489 message.append("<br>Authors: ");
1490 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1491 message.append(CITATION);
1493 message.append("</div>");
1495 return message.toString();
1499 * Action on requesting Help documentation
1502 public void documentationMenuItem_actionPerformed()
1506 if (Platform.isJS())
1508 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1517 Help.showHelpWindow();
1519 } catch (Exception ex)
1521 System.err.println("Error opening help: " + ex.getMessage());
1526 public void closeAll_actionPerformed(ActionEvent e)
1528 // TODO show a progress bar while closing?
1529 ClassicJInternalFrame[] frames = desktop.getAllFrames();
1530 for (int i = 0; i < frames.length; i++)
1534 frames[i].setClosed(true);
1535 } catch (java.beans.PropertyVetoException ex)
1539 Jalview.setCurrentAlignFrame(null);
1540 System.out.println("ALL CLOSED");
1543 * reset state of singleton objects as appropriate (clear down session state
1544 * when all windows are closed)
1546 StructureSelectionManager ssm = StructureSelectionManager
1547 .getStructureSelectionManager(this);
1555 public void raiseRelated_actionPerformed(ActionEvent e)
1557 reorderAssociatedWindows(false, false);
1561 public void minimizeAssociated_actionPerformed(ActionEvent e)
1563 reorderAssociatedWindows(true, false);
1566 void closeAssociatedWindows()
1568 reorderAssociatedWindows(false, true);
1574 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1578 protected void garbageCollect_actionPerformed(ActionEvent e)
1580 // We simply collect the garbage
1581 jalview.bin.Console.debug("Collecting garbage...");
1583 jalview.bin.Console.debug("Finished garbage collection.");
1589 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1593 protected void showMemusage_actionPerformed(ActionEvent e)
1595 desktop.showMemoryUsage(showMemusage.isSelected());
1602 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1606 protected void showConsole_actionPerformed(ActionEvent e)
1608 showConsole(showConsole.isSelected());
1611 Console jconsole = null;
1614 * control whether the java console is visible or not
1618 void showConsole(boolean selected)
1620 // TODO: decide if we should update properties file
1621 if (jconsole != null) // BH 2018
1623 showConsole.setSelected(selected);
1624 Cache.setProperty("SHOW_JAVA_CONSOLE",
1625 Boolean.valueOf(selected).toString());
1626 jconsole.setVisible(selected);
1630 void reorderAssociatedWindows(boolean minimize, boolean close)
1632 ClassicJInternalFrame[] frames = desktop.getAllFrames();
1633 if (frames == null || frames.length < 1)
1638 AlignmentViewport source = null, target = null;
1639 if (frames[0] instanceof AlignFrame)
1641 source = ((AlignFrame) frames[0]).getCurrentView();
1643 else if (frames[0] instanceof TreePanel)
1645 source = ((TreePanel) frames[0]).getViewPort();
1647 else if (frames[0] instanceof PCAPanel)
1649 source = ((PCAPanel) frames[0]).av;
1651 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1653 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1658 for (int i = 0; i < frames.length; i++)
1661 if (frames[i] == null)
1665 if (frames[i] instanceof AlignFrame)
1667 target = ((AlignFrame) frames[i]).getCurrentView();
1669 else if (frames[i] instanceof TreePanel)
1671 target = ((TreePanel) frames[i]).getViewPort();
1673 else if (frames[i] instanceof PCAPanel)
1675 target = ((PCAPanel) frames[i]).av;
1677 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1679 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1682 if (source == target)
1688 frames[i].setClosed(true);
1692 frames[i].setIcon(minimize);
1695 frames[i].toFront();
1699 } catch (java.beans.PropertyVetoException ex)
1714 protected void preferences_actionPerformed(ActionEvent e)
1716 Preferences.openPreferences();
1720 * Prompts the user to choose a file and then saves the Jalview state as a
1721 * Jalview project file
1724 public void saveState_actionPerformed()
1726 saveState_actionPerformed(false);
1729 public void saveState_actionPerformed(boolean saveAs)
1731 java.io.File projectFile = getProjectFile();
1732 // autoSave indicates we already have a file and don't need to ask
1733 boolean autoSave = projectFile != null && !saveAs
1734 && BackupFiles.getEnabled();
1736 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1737 // saveAs="+saveAs+", Backups
1738 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1740 boolean approveSave = false;
1743 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1746 chooser.setFileView(new JalviewFileView());
1747 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1749 int value = chooser.showSaveDialog(this);
1751 if (value == JalviewFileChooser.APPROVE_OPTION)
1753 projectFile = chooser.getSelectedFile();
1754 setProjectFile(projectFile);
1759 if (approveSave || autoSave)
1761 final Desktop me = this;
1762 final java.io.File chosenFile = projectFile;
1763 new Thread(new Runnable()
1768 // TODO: refactor to Jalview desktop session controller action.
1769 setProgressBar(MessageManager.formatMessage(
1770 "label.saving_jalview_project", new Object[]
1771 { chosenFile.getName() }), chosenFile.hashCode());
1772 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1773 // TODO catch and handle errors for savestate
1774 // TODO prevent user from messing with the Desktop whilst we're saving
1777 boolean doBackup = BackupFiles.getEnabled();
1778 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1781 new Jalview2XML().saveState(
1782 doBackup ? backupfiles.getTempFile() : chosenFile);
1786 backupfiles.setWriteSuccess(true);
1787 backupfiles.rollBackupsAndRenameTempFile();
1789 } catch (OutOfMemoryError oom)
1791 new OOMWarning("Whilst saving current state to "
1792 + chosenFile.getName(), oom);
1793 } catch (Exception ex)
1795 jalview.bin.Console.error("Problems whilst trying to save to "
1796 + chosenFile.getName(), ex);
1797 JvOptionPane.showMessageDialog(me,
1798 MessageManager.formatMessage(
1799 "label.error_whilst_saving_current_state_to",
1801 { chosenFile.getName() }),
1802 MessageManager.getString("label.couldnt_save_project"),
1803 JvOptionPane.WARNING_MESSAGE);
1805 setProgressBar(null, chosenFile.hashCode());
1812 public void saveAsState_actionPerformed(ActionEvent e)
1814 saveState_actionPerformed(true);
1817 private void setProjectFile(File choice)
1819 this.projectFile = choice;
1822 public File getProjectFile()
1824 return this.projectFile;
1828 * Shows a file chooser dialog and tries to read in the selected file as a
1832 public void loadState_actionPerformed()
1834 final String[] suffix = new String[] { "jvp", "jar" };
1835 final String[] desc = new String[] { "Jalview Project",
1836 "Jalview Project (old)" };
1837 JalviewFileChooser chooser = new JalviewFileChooser(
1838 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1839 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1843 chooser.setFileView(new JalviewFileView());
1844 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1845 chooser.setResponseHandler(0, new Runnable()
1850 File selectedFile = chooser.getSelectedFile();
1851 setProjectFile(selectedFile);
1852 String choice = selectedFile.getAbsolutePath();
1853 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1854 new Thread(new Runnable()
1861 new Jalview2XML().loadJalviewAlign(selectedFile);
1862 } catch (OutOfMemoryError oom)
1864 new OOMWarning("Whilst loading project from " + choice, oom);
1865 } catch (Exception ex)
1867 jalview.bin.Console.error(
1868 "Problems whilst loading project from " + choice, ex);
1869 JvOptionPane.showMessageDialog(Desktop.desktop,
1870 MessageManager.formatMessage(
1871 "label.error_whilst_loading_project_from",
1875 .getString("label.couldnt_load_project"),
1876 JvOptionPane.WARNING_MESSAGE);
1879 }, "Project Loader").start();
1883 chooser.showOpenDialog(this);
1887 public void inputSequence_actionPerformed(ActionEvent e)
1889 new SequenceFetcher(this);
1892 JPanel progressPanel;
1894 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1896 public void startLoading(final Object fileName)
1898 if (fileLoadingCount == 0)
1900 fileLoadingPanels.add(addProgressPanel(MessageManager
1901 .formatMessage("label.loading_file", new Object[]
1907 private JPanel addProgressPanel(String string)
1909 if (progressPanel == null)
1911 progressPanel = new JPanel(new GridLayout(1, 1));
1912 totalProgressCount = 0;
1913 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1915 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1916 JProgressBar progressBar = new JProgressBar();
1917 progressBar.setIndeterminate(true);
1919 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1921 thisprogress.add(progressBar, BorderLayout.CENTER);
1922 progressPanel.add(thisprogress);
1923 ((GridLayout) progressPanel.getLayout()).setRows(
1924 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1925 ++totalProgressCount;
1926 instance.validate();
1927 return thisprogress;
1930 int totalProgressCount = 0;
1932 private void removeProgressPanel(JPanel progbar)
1934 if (progressPanel != null)
1936 synchronized (progressPanel)
1938 progressPanel.remove(progbar);
1939 GridLayout gl = (GridLayout) progressPanel.getLayout();
1940 gl.setRows(gl.getRows() - 1);
1941 if (--totalProgressCount < 1)
1943 this.getContentPane().remove(progressPanel);
1944 progressPanel = null;
1951 public void stopLoading()
1954 if (fileLoadingCount < 1)
1956 while (fileLoadingPanels.size() > 0)
1958 removeProgressPanel(fileLoadingPanels.remove(0));
1960 fileLoadingPanels.clear();
1961 fileLoadingCount = 0;
1966 public static int getViewCount(String alignmentId)
1968 AlignmentViewport[] aps = getViewports(alignmentId);
1969 return (aps == null) ? 0 : aps.length;
1974 * @param alignmentId
1975 * - if null, all sets are returned
1976 * @return all AlignmentPanels concerning the alignmentId sequence set
1978 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1980 if (Desktop.desktop == null)
1982 // no frames created and in headless mode
1983 // TODO: verify that frames are recoverable when in headless mode
1986 List<AlignmentPanel> aps = new ArrayList<>();
1987 AlignFrame[] frames = getAlignFrames();
1992 for (AlignFrame af : frames)
1994 for (AlignmentPanel ap : af.alignPanels)
1996 if (alignmentId == null
1997 || alignmentId.equals(ap.av.getSequenceSetId()))
2003 if (aps.size() == 0)
2007 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2012 * get all the viewports on an alignment.
2014 * @param sequenceSetId
2015 * unique alignment id (may be null - all viewports returned in that
2017 * @return all viewports on the alignment bound to sequenceSetId
2019 public static AlignmentViewport[] getViewports(String sequenceSetId)
2021 List<AlignmentViewport> viewp = new ArrayList<>();
2022 if (desktop != null)
2024 AlignFrame[] frames = Desktop.getAlignFrames();
2026 for (AlignFrame afr : frames)
2028 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2029 .equals(sequenceSetId))
2031 if (afr.alignPanels != null)
2033 for (AlignmentPanel ap : afr.alignPanels)
2035 if (sequenceSetId == null
2036 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2044 viewp.add(afr.getViewport());
2048 if (viewp.size() > 0)
2050 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2057 * Explode the views in the given frame into separate AlignFrame
2061 public static void explodeViews(AlignFrame af)
2063 int size = af.alignPanels.size();
2069 // FIXME: ideally should use UI interface API
2070 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2071 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2072 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2073 for (int i = 0; i < size; i++)
2075 AlignmentPanel ap = af.alignPanels.get(i);
2077 AlignFrame newaf = new AlignFrame(ap);
2079 // transfer reference for existing feature settings to new alignFrame
2080 if (ap == af.alignPanel)
2082 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2084 newaf.featureSettings = viewFeatureSettings;
2086 newaf.setFeatureSettingsGeometry(fsBounds);
2090 * Restore the view's last exploded frame geometry if known. Multiple views from
2091 * one exploded frame share and restore the same (frame) position and size.
2093 Rectangle geometry = ap.av.getExplodedGeometry();
2094 if (geometry != null)
2096 newaf.setBounds(geometry);
2099 ap.av.setGatherViewsHere(false);
2101 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2102 AlignFrame.DEFAULT_HEIGHT);
2103 // and materialise a new feature settings dialog instance for the new
2105 // (closes the old as if 'OK' was pressed)
2106 if (ap == af.alignPanel && newaf.featureSettings != null
2107 && newaf.featureSettings.isOpen()
2108 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2110 newaf.showFeatureSettingsUI();
2114 af.featureSettings = null;
2115 af.alignPanels.clear();
2116 af.closeMenuItem_actionPerformed(true);
2121 * Gather expanded views (separate AlignFrame's) with the same sequence set
2122 * identifier back in to this frame as additional views, and close the
2123 * expanded views. Note the expanded frames may themselves have multiple
2124 * views. We take the lot.
2128 public void gatherViews(AlignFrame source)
2130 source.viewport.setGatherViewsHere(true);
2131 source.viewport.setExplodedGeometry(source.getBounds());
2132 ClassicJInternalFrame[] frames = desktop.getAllFrames();
2133 String viewId = source.viewport.getSequenceSetId();
2134 for (int t = 0; t < frames.length; t++)
2136 if (frames[t] instanceof AlignFrame && frames[t] != source)
2138 AlignFrame af = (AlignFrame) frames[t];
2139 boolean gatherThis = false;
2140 for (int a = 0; a < af.alignPanels.size(); a++)
2142 AlignmentPanel ap = af.alignPanels.get(a);
2143 if (viewId.equals(ap.av.getSequenceSetId()))
2146 ap.av.setGatherViewsHere(false);
2147 ap.av.setExplodedGeometry(af.getBounds());
2148 source.addAlignmentPanel(ap, false);
2154 if (af.featureSettings != null && af.featureSettings.isOpen())
2156 if (source.featureSettings == null)
2158 // preserve the feature settings geometry for this frame
2159 source.featureSettings = af.featureSettings;
2160 source.setFeatureSettingsGeometry(
2161 af.getFeatureSettingsGeometry());
2165 // close it and forget
2166 af.featureSettings.close();
2169 af.alignPanels.clear();
2170 af.closeMenuItem_actionPerformed(true);
2175 // refresh the feature setting UI for the source frame if it exists
2176 if (source.featureSettings != null && source.featureSettings.isOpen())
2178 source.showFeatureSettingsUI();
2183 public ClassicJInternalFrame[] getAllFrames()
2185 return desktop.getAllFrames();
2189 * Checks the given url to see if it gives a response indicating that the user
2190 * should be informed of a new questionnaire.
2194 public void checkForQuestionnaire(String url)
2196 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2197 // javax.swing.SwingUtilities.invokeLater(jvq);
2198 new Thread(jvq).start();
2201 public void checkURLLinks()
2203 // Thread off the URL link checker
2204 addDialogThread(new Runnable()
2209 if (Cache.getDefault("CHECKURLLINKS", true))
2211 // check what the actual links are - if it's just the default don't
2212 // bother with the warning
2213 List<String> links = Preferences.sequenceUrlLinks
2216 // only need to check links if there is one with a
2217 // SEQUENCE_ID which is not the default EMBL_EBI link
2218 ListIterator<String> li = links.listIterator();
2219 boolean check = false;
2220 List<JLabel> urls = new ArrayList<>();
2221 while (li.hasNext())
2223 String link = li.next();
2224 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2225 && !UrlConstants.isDefaultString(link))
2228 int barPos = link.indexOf("|");
2229 String urlMsg = barPos == -1 ? link
2230 : link.substring(0, barPos) + ": "
2231 + link.substring(barPos + 1);
2232 urls.add(new JLabel(urlMsg));
2240 // ask user to check in case URL links use old style tokens
2241 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2242 JPanel msgPanel = new JPanel();
2243 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2244 msgPanel.add(Box.createVerticalGlue());
2245 JLabel msg = new JLabel(MessageManager
2246 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2247 JLabel msg2 = new JLabel(MessageManager
2248 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2250 for (JLabel url : urls)
2256 final JCheckBox jcb = new JCheckBox(
2257 MessageManager.getString("label.do_not_display_again"));
2258 jcb.addActionListener(new ActionListener()
2261 public void actionPerformed(ActionEvent e)
2263 // update Cache settings for "don't show this again"
2264 boolean showWarningAgain = !jcb.isSelected();
2265 Cache.setProperty("CHECKURLLINKS",
2266 Boolean.valueOf(showWarningAgain).toString());
2271 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2273 .getString("label.SEQUENCE_ID_no_longer_used"),
2274 JvOptionPane.WARNING_MESSAGE);
2281 * Proxy class for JDesktopPane which optionally displays the current memory
2282 * usage and highlights the desktop area with a red bar if free memory runs
2287 public class MyDesktopPane extends JDesktopPane implements Runnable
2289 private static final float ONE_MB = 1048576f;
2291 boolean showMemoryUsage = false;
2295 java.text.NumberFormat df;
2297 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2300 public MyDesktopPane(boolean showMemoryUsage)
2302 showMemoryUsage(showMemoryUsage);
2305 public void showMemoryUsage(boolean showMemory)
2307 this.showMemoryUsage = showMemory;
2310 Thread worker = new Thread(this);
2316 public boolean isShowMemoryUsage()
2318 return showMemoryUsage;
2324 df = java.text.NumberFormat.getNumberInstance();
2325 df.setMaximumFractionDigits(2);
2326 runtime = Runtime.getRuntime();
2328 while (showMemoryUsage)
2332 maxMemory = runtime.maxMemory() / ONE_MB;
2333 allocatedMemory = runtime.totalMemory() / ONE_MB;
2334 freeMemory = runtime.freeMemory() / ONE_MB;
2335 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2337 percentUsage = (totalFreeMemory / maxMemory) * 100;
2339 // if (percentUsage < 20)
2341 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2343 // instance.set.setBorder(border1);
2346 // sleep after showing usage
2348 } catch (Exception ex)
2350 ex.printStackTrace();
2356 public void paintComponent(Graphics g)
2358 if (showMemoryUsage && g != null && df != null)
2360 if (percentUsage < 20)
2362 g.setColor(Color.red);
2364 FontMetrics fm = g.getFontMetrics();
2367 g.drawString(MessageManager.formatMessage("label.memory_stats",
2369 { df.format(totalFreeMemory), df.format(maxMemory),
2370 df.format(percentUsage) }),
2371 10, getHeight() - fm.getHeight());
2375 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2376 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2379 public ClassicJInternalFrame getSelectedFrame()
2381 return (ClassicJInternalFrame) super.getSelectedFrame();
2384 public ClassicJInternalFrame[] getAllFrames()
2386 return (ClassicJInternalFrame[]) super.getAllFrames();
2391 * Accessor method to quickly get all the AlignmentFrames loaded.
2393 * @return an array of AlignFrame, or null if none found
2395 public static AlignFrame[] getAlignFrames()
2397 if (Jalview.isHeadlessMode())
2399 // Desktop.desktop is null in headless mode
2400 return new AlignFrame[] { Jalview.currentAlignFrame };
2403 ClassicJInternalFrame[] frames = Desktop.desktop.getAllFrames();
2409 List<AlignFrame> avp = new ArrayList<>();
2411 for (int i = frames.length - 1; i > -1; i--)
2413 if (frames[i] instanceof AlignFrame)
2415 avp.add((AlignFrame) frames[i]);
2417 else if (frames[i] instanceof SplitFrame)
2420 * Also check for a split frame containing an AlignFrame
2422 GSplitFrame sf = (GSplitFrame) frames[i];
2423 if (sf.getTopFrame() instanceof AlignFrame)
2425 avp.add((AlignFrame) sf.getTopFrame());
2427 if (sf.getBottomFrame() instanceof AlignFrame)
2429 avp.add((AlignFrame) sf.getBottomFrame());
2433 if (avp.size() == 0)
2437 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2442 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2446 public GStructureViewer[] getJmols()
2448 ClassicJInternalFrame[] frames = Desktop.desktop.getAllFrames();
2454 List<GStructureViewer> avp = new ArrayList<>();
2456 for (int i = frames.length - 1; i > -1; i--)
2458 if (frames[i] instanceof AppJmol)
2460 GStructureViewer af = (GStructureViewer) frames[i];
2464 if (avp.size() == 0)
2468 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2473 * Add Groovy Support to Jalview
2476 public void groovyShell_actionPerformed()
2480 openGroovyConsole();
2481 } catch (Exception ex)
2483 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2484 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2486 MessageManager.getString("label.couldnt_create_groovy_shell"),
2487 MessageManager.getString("label.groovy_support_failed"),
2488 JvOptionPane.ERROR_MESSAGE);
2493 * Open the Groovy console
2495 void openGroovyConsole()
2497 if (groovyConsole == null)
2499 groovyConsole = new groovy.ui.Console();
2500 groovyConsole.setVariable("Jalview", this);
2501 groovyConsole.run();
2504 * We allow only one console at a time, so that AlignFrame menu option
2505 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2506 * enable 'Run script', when the console is opened, and the reverse when it is
2509 Window window = (Window) groovyConsole.getFrame();
2510 window.addWindowListener(new WindowAdapter()
2513 public void windowClosed(WindowEvent e)
2516 * rebind CMD-Q from Groovy Console to Jalview Quit
2519 enableExecuteGroovy(false);
2525 * show Groovy console window (after close and reopen)
2527 ((Window) groovyConsole.getFrame()).setVisible(true);
2530 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2531 * opening a second console
2533 enableExecuteGroovy(true);
2537 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2538 * binding when opened
2540 protected void addQuitHandler()
2543 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2545 .getKeyStroke(KeyEvent.VK_Q,
2546 jalview.util.ShortcutKeyMaskExWrapper
2547 .getMenuShortcutKeyMaskEx()),
2549 getRootPane().getActionMap().put("Quit", new AbstractAction()
2552 public void actionPerformed(ActionEvent e)
2560 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2563 * true if Groovy console is open
2565 public void enableExecuteGroovy(boolean enabled)
2568 * disable opening a second Groovy console (or re-enable when the console is
2571 groovyShell.setEnabled(!enabled);
2573 AlignFrame[] alignFrames = getAlignFrames();
2574 if (alignFrames != null)
2576 for (AlignFrame af : alignFrames)
2578 af.setGroovyEnabled(enabled);
2584 * Progress bars managed by the IProgressIndicator method.
2586 private Hashtable<Long, JPanel> progressBars;
2588 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2593 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2596 public void setProgressBar(String message, long id)
2598 if (progressBars == null)
2600 progressBars = new Hashtable<>();
2601 progressBarHandlers = new Hashtable<>();
2604 if (progressBars.get(Long.valueOf(id)) != null)
2606 JPanel panel = progressBars.remove(Long.valueOf(id));
2607 if (progressBarHandlers.contains(Long.valueOf(id)))
2609 progressBarHandlers.remove(Long.valueOf(id));
2611 removeProgressPanel(panel);
2615 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2622 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2623 * jalview.gui.IProgressIndicatorHandler)
2626 public void registerHandler(final long id,
2627 final IProgressIndicatorHandler handler)
2629 if (progressBarHandlers == null
2630 || !progressBars.containsKey(Long.valueOf(id)))
2632 throw new Error(MessageManager.getString(
2633 "error.call_setprogressbar_before_registering_handler"));
2635 progressBarHandlers.put(Long.valueOf(id), handler);
2636 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2637 if (handler.canCancel())
2639 JButton cancel = new JButton(
2640 MessageManager.getString("action.cancel"));
2641 final IProgressIndicator us = this;
2642 cancel.addActionListener(new ActionListener()
2646 public void actionPerformed(ActionEvent e)
2648 handler.cancelActivity(id);
2649 us.setProgressBar(MessageManager
2650 .formatMessage("label.cancelled_params", new Object[]
2651 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2655 progressPanel.add(cancel, BorderLayout.EAST);
2661 * @return true if any progress bars are still active
2664 public boolean operationInProgress()
2666 if (progressBars != null && progressBars.size() > 0)
2674 * This will return the first AlignFrame holding the given viewport instance.
2675 * It will break if there are more than one AlignFrames viewing a particular
2679 * @return alignFrame for viewport
2681 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2683 if (desktop != null)
2685 AlignmentPanel[] aps = getAlignmentPanels(
2686 viewport.getSequenceSetId());
2687 for (int panel = 0; aps != null && panel < aps.length; panel++)
2689 if (aps[panel] != null && aps[panel].av == viewport)
2691 return aps[panel].alignFrame;
2698 public VamsasApplication getVamsasApplication()
2700 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2706 * flag set if jalview GUI is being operated programmatically
2708 private boolean inBatchMode = false;
2711 * check if jalview GUI is being operated programmatically
2713 * @return inBatchMode
2715 public boolean isInBatchMode()
2721 * set flag if jalview GUI is being operated programmatically
2723 * @param inBatchMode
2725 public void setInBatchMode(boolean inBatchMode)
2727 this.inBatchMode = inBatchMode;
2731 * start service discovery and wait till it is done
2733 public void startServiceDiscovery()
2735 startServiceDiscovery(false);
2739 * start service discovery threads - blocking or non-blocking
2743 public void startServiceDiscovery(boolean blocking)
2745 startServiceDiscovery(blocking, false);
2749 * start service discovery threads
2752 * - false means call returns immediately
2753 * @param ignore_SHOW_JWS2_SERVICES_preference
2754 * - when true JABA services are discovered regardless of user's JWS2
2755 * discovery preference setting
2757 public void startServiceDiscovery(boolean blocking,
2758 boolean ignore_SHOW_JWS2_SERVICES_preference)
2760 boolean alive = true;
2761 Thread t0 = null, t1 = null, t2 = null;
2762 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2765 // todo: changesupport handlers need to be transferred
2766 if (discoverer == null)
2768 discoverer = new jalview.ws.jws1.Discoverer();
2769 // register PCS handler for desktop.
2770 discoverer.addPropertyChangeListener(changeSupport);
2772 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2773 // until we phase out completely
2774 (t0 = new Thread(discoverer)).start();
2777 if (ignore_SHOW_JWS2_SERVICES_preference
2778 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2780 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2781 .startDiscoverer(changeSupport);
2785 // TODO: do rest service discovery
2794 } catch (Exception e)
2797 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2798 || (t3 != null && t3.isAlive())
2799 || (t0 != null && t0.isAlive());
2805 * called to check if the service discovery process completed successfully.
2809 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2811 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2813 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2814 .getErrorMessages();
2817 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2819 if (serviceChangedDialog == null)
2821 // only run if we aren't already displaying one of these.
2822 addDialogThread(serviceChangedDialog = new Runnable()
2829 * JalviewDialog jd =new JalviewDialog() {
2831 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2833 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2835 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2837 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2839 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2840 * + " or mis-configured HTTP proxy settings.<br/>" +
2841 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2842 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2843 * true, true, "Web Service Configuration Problem", 450, 400);
2845 * jd.waitForInput();
2847 JvOptionPane.showConfirmDialog(Desktop.desktop,
2848 new JLabel("<html><table width=\"450\"><tr><td>"
2849 + ermsg + "</td></tr></table>"
2850 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2851 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2852 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2853 + " Tools->Preferences dialog box to change them.</p></html>"),
2854 "Web Service Configuration Problem",
2855 JvOptionPane.DEFAULT_OPTION,
2856 JvOptionPane.ERROR_MESSAGE);
2857 serviceChangedDialog = null;
2865 jalview.bin.Console.error(
2866 "Errors reported by JABA discovery service. Check web services preferences.\n"
2873 private Runnable serviceChangedDialog = null;
2876 * start a thread to open a URL in the configured browser. Pops up a warning
2877 * dialog to the user if there is an exception when calling out to the browser
2882 public static void showUrl(final String url)
2884 showUrl(url, Desktop.instance);
2888 * Like showUrl but allows progress handler to be specified
2892 * (null) or object implementing IProgressIndicator
2894 public static void showUrl(final String url,
2895 final IProgressIndicator progress)
2897 new Thread(new Runnable()
2904 if (progress != null)
2906 progress.setProgressBar(MessageManager
2907 .formatMessage("status.opening_params", new Object[]
2908 { url }), this.hashCode());
2910 jalview.util.BrowserLauncher.openURL(url);
2911 } catch (Exception ex)
2913 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2915 .getString("label.web_browser_not_found_unix"),
2916 MessageManager.getString("label.web_browser_not_found"),
2917 JvOptionPane.WARNING_MESSAGE);
2919 ex.printStackTrace();
2921 if (progress != null)
2923 progress.setProgressBar(null, this.hashCode());
2929 public static WsParamSetManager wsparamManager = null;
2931 public static ParamManager getUserParameterStore()
2933 if (wsparamManager == null)
2935 wsparamManager = new WsParamSetManager();
2937 return wsparamManager;
2941 * static hyperlink handler proxy method for use by Jalview's internal windows
2945 public static void hyperlinkUpdate(HyperlinkEvent e)
2947 if (e.getEventType() == EventType.ACTIVATED)
2952 url = e.getURL().toString();
2953 Desktop.showUrl(url);
2954 } catch (Exception x)
2959 .error("Couldn't handle string " + url + " as a URL.");
2961 // ignore any exceptions due to dud links.
2968 * single thread that handles display of dialogs to user.
2970 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2973 * flag indicating if dialogExecutor should try to acquire a permit
2975 private volatile boolean dialogPause = true;
2980 private java.util.concurrent.Semaphore block = new Semaphore(0);
2982 private static groovy.ui.Console groovyConsole;
2985 * add another dialog thread to the queue
2989 public void addDialogThread(final Runnable prompter)
2991 dialogExecutor.submit(new Runnable()
3001 } catch (InterruptedException x)
3005 if (instance == null)
3011 SwingUtilities.invokeAndWait(prompter);
3012 } catch (Exception q)
3014 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3021 public void startDialogQueue()
3023 // set the flag so we don't pause waiting for another permit and semaphore
3024 // the current task to begin
3025 dialogPause = false;
3030 * Outputs an image of the desktop to file in EPS format, after prompting the
3031 * user for choice of Text or Lineart character rendering (unless a preference
3032 * has been set). The file name is generated as
3035 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3039 protected void snapShotWindow_actionPerformed(ActionEvent e)
3041 // currently the menu option to do this is not shown
3044 int width = getWidth();
3045 int height = getHeight();
3047 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3048 ImageWriterI writer = new ImageWriterI()
3051 public void exportImage(Graphics g) throws Exception
3054 jalview.bin.Console.info("Successfully written snapshot to file "
3055 + of.getAbsolutePath());
3058 String title = "View of desktop";
3059 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3061 exporter.doExport(of, this, width, height, title);
3065 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3066 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3067 * and location last time the view was expanded (if any). However it does not
3068 * remember the split pane divider location - this is set to match the
3069 * 'exploding' frame.
3073 public void explodeViews(SplitFrame sf)
3075 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3076 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3077 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3079 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3081 int viewCount = topPanels.size();
3088 * Processing in reverse order works, forwards order leaves the first panels not
3089 * visible. I don't know why!
3091 for (int i = viewCount - 1; i >= 0; i--)
3094 * Make new top and bottom frames. These take over the respective AlignmentPanel
3095 * objects, including their AlignmentViewports, so the cdna/protein
3096 * relationships between the viewports is carried over to the new split frames.
3098 * explodedGeometry holds the (x, y) position of the previously exploded
3099 * SplitFrame, and the (width, height) of the AlignFrame component
3101 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3102 AlignFrame newTopFrame = new AlignFrame(topPanel);
3103 newTopFrame.setSize(oldTopFrame.getSize());
3104 newTopFrame.setVisible(true);
3105 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3106 .getExplodedGeometry();
3107 if (geometry != null)
3109 newTopFrame.setSize(geometry.getSize());
3112 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3113 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3114 newBottomFrame.setSize(oldBottomFrame.getSize());
3115 newBottomFrame.setVisible(true);
3116 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3117 .getExplodedGeometry();
3118 if (geometry != null)
3120 newBottomFrame.setSize(geometry.getSize());
3123 topPanel.av.setGatherViewsHere(false);
3124 bottomPanel.av.setGatherViewsHere(false);
3125 ClassicJInternalFrame splitFrame = new SplitFrame(newTopFrame,
3127 if (geometry != null)
3129 splitFrame.setLocation(geometry.getLocation());
3131 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3135 * Clear references to the panels (now relocated in the new SplitFrames) before
3136 * closing the old SplitFrame.
3139 bottomPanels.clear();
3144 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3145 * back into the given SplitFrame as additional views. Note that the gathered
3146 * frames may themselves have multiple views.
3150 public void gatherViews(GSplitFrame source)
3153 * special handling of explodedGeometry for a view within a SplitFrame: - it
3154 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3155 * height) of the AlignFrame component
3157 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3158 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3159 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3160 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3161 myBottomFrame.viewport
3162 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3163 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3164 myTopFrame.viewport.setGatherViewsHere(true);
3165 myBottomFrame.viewport.setGatherViewsHere(true);
3166 String topViewId = myTopFrame.viewport.getSequenceSetId();
3167 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3169 ClassicJInternalFrame[] frames = (ClassicJInternalFrame[]) desktop
3171 for (ClassicJInternalFrame frame : frames)
3173 if (frame instanceof SplitFrame && frame != source)
3175 SplitFrame sf = (SplitFrame) frame;
3176 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3177 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3178 boolean gatherThis = false;
3179 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3181 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3182 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3183 if (topViewId.equals(topPanel.av.getSequenceSetId())
3184 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3187 topPanel.av.setGatherViewsHere(false);
3188 bottomPanel.av.setGatherViewsHere(false);
3189 topPanel.av.setExplodedGeometry(
3190 new Rectangle(sf.getLocation(), topFrame.getSize()));
3191 bottomPanel.av.setExplodedGeometry(
3192 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3193 myTopFrame.addAlignmentPanel(topPanel, false);
3194 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3200 topFrame.getAlignPanels().clear();
3201 bottomFrame.getAlignPanels().clear();
3208 * The dust settles...give focus to the tab we did this from.
3210 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3213 public static groovy.ui.Console getGroovyConsole()
3215 return groovyConsole;
3219 * handles the payload of a drag and drop event.
3221 * TODO refactor to desktop utilities class
3224 * - Data source strings extracted from the drop event
3226 * - protocol for each data source extracted from the drop event
3230 * - the payload from the drop event
3233 public static void transferFromDropTarget(List<Object> files,
3234 List<DataSourceType> protocols, DropTargetDropEvent evt,
3235 Transferable t) throws Exception
3238 DataFlavor uriListFlavor = new DataFlavor(
3239 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3242 urlFlavour = new DataFlavor(
3243 "application/x-java-url; class=java.net.URL");
3244 } catch (ClassNotFoundException cfe)
3246 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3250 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3255 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3256 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3257 // means url may be null.
3260 protocols.add(DataSourceType.URL);
3261 files.add(url.toString());
3262 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3263 + files.get(files.size() - 1));
3268 if (Platform.isAMacAndNotJS())
3271 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3274 } catch (Throwable ex)
3276 jalview.bin.Console.debug("URL drop handler failed.", ex);
3279 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3281 // Works on Windows and MacOSX
3282 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3283 for (Object file : (List) t
3284 .getTransferData(DataFlavor.javaFileListFlavor))
3287 protocols.add(DataSourceType.FILE);
3292 // Unix like behaviour
3293 boolean added = false;
3295 if (t.isDataFlavorSupported(uriListFlavor))
3297 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3298 // This is used by Unix drag system
3299 data = (String) t.getTransferData(uriListFlavor);
3303 // fallback to text: workaround - on OSX where there's a JVM bug
3305 .debug("standard URIListFlavor failed. Trying text");
3306 // try text fallback
3307 DataFlavor textDf = new DataFlavor(
3308 "text/plain;class=java.lang.String");
3309 if (t.isDataFlavorSupported(textDf))
3311 data = (String) t.getTransferData(textDf);
3314 jalview.bin.Console.debug("Plain text drop content returned "
3315 + (data == null ? "Null - failed" : data));
3320 while (protocols.size() < files.size())
3322 jalview.bin.Console.debug("Adding missing FILE protocol for "
3323 + files.get(protocols.size()));
3324 protocols.add(DataSourceType.FILE);
3326 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3327 data, "\r\n"); st.hasMoreTokens();)
3330 String s = st.nextToken();
3331 if (s.startsWith("#"))
3333 // the line is a comment (as per the RFC 2483)
3336 java.net.URI uri = new java.net.URI(s);
3337 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3339 protocols.add(DataSourceType.URL);
3340 files.add(uri.toString());
3344 // otherwise preserve old behaviour: catch all for file objects
3345 java.io.File file = new java.io.File(uri);
3346 protocols.add(DataSourceType.FILE);
3347 files.add(file.toString());
3352 if (jalview.bin.Console.isDebugEnabled())
3354 if (data == null || !added)
3357 if (t.getTransferDataFlavors() != null
3358 && t.getTransferDataFlavors().length > 0)
3360 jalview.bin.Console.debug(
3361 "Couldn't resolve drop data. Here are the supported flavors:");
3362 for (DataFlavor fl : t.getTransferDataFlavors())
3364 jalview.bin.Console.debug(
3365 "Supported transfer dataflavor: " + fl.toString());
3366 Object df = t.getTransferData(fl);
3369 jalview.bin.Console.debug("Retrieves: " + df);
3373 jalview.bin.Console.debug("Retrieved nothing");
3380 .debug("Couldn't resolve dataflavor for drop: "
3386 if (Platform.isWindowsAndNotJS())
3389 .debug("Scanning dropped content for Windows Link Files");
3391 // resolve any .lnk files in the file drop
3392 for (int f = 0; f < files.size(); f++)
3394 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3395 if (protocols.get(f).equals(DataSourceType.FILE)
3396 && (source.endsWith(".lnk") || source.endsWith(".url")
3397 || source.endsWith(".site")))
3401 Object obj = files.get(f);
3402 File lf = (obj instanceof File ? (File) obj
3403 : new File((String) obj));
3404 // process link file to get a URL
3405 jalview.bin.Console.debug("Found potential link file: " + lf);
3406 WindowsShortcut wscfile = new WindowsShortcut(lf);
3407 String fullname = wscfile.getRealFilename();
3408 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3409 files.set(f, fullname);
3410 jalview.bin.Console.debug("Parsed real filename " + fullname
3411 + " to extract protocol: " + protocols.get(f));
3412 } catch (Exception ex)
3414 jalview.bin.Console.error(
3415 "Couldn't parse " + files.get(f) + " as a link file.",
3424 * Sets the Preferences property for experimental features to True or False
3425 * depending on the state of the controlling menu item
3428 protected void showExperimental_actionPerformed(boolean selected)
3430 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3434 * Answers a (possibly empty) list of any structure viewer frames (currently
3435 * for either Jmol or Chimera) which are currently open. This may optionally
3436 * be restricted to viewers of a specified class, or viewers linked to a
3437 * specified alignment panel.
3440 * if not null, only return viewers linked to this panel
3441 * @param structureViewerClass
3442 * if not null, only return viewers of this class
3445 public List<StructureViewerBase> getStructureViewers(
3446 AlignmentPanel apanel,
3447 Class<? extends StructureViewerBase> structureViewerClass)
3449 List<StructureViewerBase> result = new ArrayList<>();
3450 ClassicJInternalFrame[] frames = Desktop.instance.getAllFrames();
3452 for (ClassicJInternalFrame frame : frames)
3454 if (frame instanceof StructureViewerBase)
3456 if (structureViewerClass == null
3457 || structureViewerClass.isInstance(frame))
3460 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3462 result.add((StructureViewerBase) frame);
3470 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3472 private static boolean debugScaleMessageDone = false;
3474 public static void debugScaleMessage(Graphics g)
3476 if (debugScaleMessageDone)
3480 // output used by tests to check HiDPI scaling settings in action
3483 Graphics2D gg = (Graphics2D) g;
3486 AffineTransform t = gg.getTransform();
3487 double scaleX = t.getScaleX();
3488 double scaleY = t.getScaleY();
3489 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3490 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3491 debugScaleMessageDone = true;
3495 jalview.bin.Console.debug("Desktop graphics null");
3497 } catch (Exception e)
3499 jalview.bin.Console.debug(Cache.getStackTraceString(e));