2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Locale;
66 import java.util.Vector;
67 import java.util.concurrent.Callable;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Semaphore;
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.Box;
76 import javax.swing.BoxLayout;
77 import javax.swing.DefaultDesktopManager;
78 import javax.swing.DesktopManager;
79 import javax.swing.InputMap;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JComboBox;
83 import javax.swing.JComponent;
84 import javax.swing.JDesktopPane;
85 import javax.swing.JFrame;
86 import javax.swing.JInternalFrame;
87 import javax.swing.JLabel;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JProgressBar;
92 import javax.swing.JTextField;
93 import javax.swing.KeyStroke;
94 import javax.swing.SwingUtilities;
95 import javax.swing.WindowConstants;
96 import javax.swing.event.HyperlinkEvent;
97 import javax.swing.event.HyperlinkEvent.EventType;
98 import javax.swing.event.InternalFrameAdapter;
99 import javax.swing.event.InternalFrameEvent;
101 import org.stackoverflowusers.file.WindowsShortcut;
103 import jalview.api.AlignViewportI;
104 import jalview.api.AlignmentViewPanel;
105 import jalview.bin.Cache;
106 import jalview.bin.Jalview;
107 import jalview.gui.ImageExporter.ImageWriterI;
108 import jalview.gui.QuitHandler.QResponse;
109 import jalview.io.BackupFiles;
110 import jalview.io.DataSourceType;
111 import jalview.io.FileFormat;
112 import jalview.io.FileFormatException;
113 import jalview.io.FileFormatI;
114 import jalview.io.FileFormats;
115 import jalview.io.FileLoader;
116 import jalview.io.FormatAdapter;
117 import jalview.io.IdentifyFile;
118 import jalview.io.JalviewFileChooser;
119 import jalview.io.JalviewFileView;
120 import jalview.jbgui.GSplitFrame;
121 import jalview.jbgui.GStructureViewer;
122 import jalview.project.Jalview2XML;
123 import jalview.structure.StructureSelectionManager;
124 import jalview.urls.IdOrgSettings;
125 import jalview.util.BrowserLauncher;
126 import jalview.util.ChannelProperties;
127 import jalview.util.ImageMaker.TYPE;
128 import jalview.util.LaunchUtils;
129 import jalview.util.MessageManager;
130 import jalview.util.Platform;
131 import jalview.util.ShortcutKeyMaskExWrapper;
132 import jalview.util.UrlConstants;
133 import jalview.viewmodel.AlignmentViewport;
134 import jalview.ws.params.ParamManager;
135 import jalview.ws.utils.UrlDownloadClient;
142 * @version $Revision: 1.155 $
144 public class Desktop extends jalview.jbgui.GDesktop
145 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
146 jalview.api.StructureSelectionManagerProvider
148 private static final String CITATION;
151 URL bg_logo_url = ChannelProperties.getImageURL(
152 "bg_logo." + String.valueOf(SplashScreen.logoSize));
153 URL uod_logo_url = ChannelProperties.getImageURL(
154 "uod_banner." + String.valueOf(SplashScreen.logoSize));
155 boolean logo = (bg_logo_url != null || uod_logo_url != null);
156 StringBuilder sb = new StringBuilder();
158 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
163 sb.append(bg_logo_url == null ? ""
164 : "<img alt=\"Barton Group logo\" src=\""
165 + bg_logo_url.toString() + "\">");
166 sb.append(uod_logo_url == null ? ""
167 : " <img alt=\"University of Dundee shield\" src=\""
168 + uod_logo_url.toString() + "\">");
170 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
171 sb.append("<br><br>If you use Jalview, please cite:"
172 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
173 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
174 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
175 CITATION = sb.toString();
178 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
180 private static int DEFAULT_MIN_WIDTH = 300;
182 private static int DEFAULT_MIN_HEIGHT = 250;
184 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
186 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
188 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
190 public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
192 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
194 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
196 public static boolean nosplash = false;
199 * news reader - null if it was never started.
201 private BlogReader jvnews = null;
203 private File projectFile;
207 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
209 public void addJalviewPropertyChangeListener(
210 PropertyChangeListener listener)
212 changeSupport.addJalviewPropertyChangeListener(listener);
216 * @param propertyName
218 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
219 * java.beans.PropertyChangeListener)
221 public void addJalviewPropertyChangeListener(String propertyName,
222 PropertyChangeListener listener)
224 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
228 * @param propertyName
230 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
231 * java.beans.PropertyChangeListener)
233 public void removeJalviewPropertyChangeListener(String propertyName,
234 PropertyChangeListener listener)
236 changeSupport.removeJalviewPropertyChangeListener(propertyName,
240 /** Singleton Desktop instance */
241 public static Desktop instance;
243 public static MyDesktopPane desktop;
245 public static MyDesktopPane getDesktop()
247 // BH 2018 could use currentThread() here as a reference to a
248 // Hashtable<Thread, MyDesktopPane> in JavaScript
252 static int openFrameCount = 0;
254 static final int xOffset = 30;
256 static final int yOffset = 30;
258 public static jalview.ws.jws1.Discoverer discoverer;
260 public static Object[] jalviewClipboard;
262 public static boolean internalCopy = false;
264 static int fileLoadingCount = 0;
266 class MyDesktopManager implements DesktopManager
269 private DesktopManager delegate;
271 public MyDesktopManager(DesktopManager delegate)
273 this.delegate = delegate;
277 public void activateFrame(JInternalFrame f)
281 delegate.activateFrame(f);
282 } catch (NullPointerException npe)
284 Point p = getMousePosition();
285 instance.showPasteMenu(p.x, p.y);
290 public void beginDraggingFrame(JComponent f)
292 delegate.beginDraggingFrame(f);
296 public void beginResizingFrame(JComponent f, int direction)
298 delegate.beginResizingFrame(f, direction);
302 public void closeFrame(JInternalFrame f)
304 delegate.closeFrame(f);
308 public void deactivateFrame(JInternalFrame f)
310 delegate.deactivateFrame(f);
314 public void deiconifyFrame(JInternalFrame f)
316 delegate.deiconifyFrame(f);
320 public void dragFrame(JComponent f, int newX, int newY)
326 delegate.dragFrame(f, newX, newY);
330 public void endDraggingFrame(JComponent f)
332 delegate.endDraggingFrame(f);
337 public void endResizingFrame(JComponent f)
339 delegate.endResizingFrame(f);
344 public void iconifyFrame(JInternalFrame f)
346 delegate.iconifyFrame(f);
350 public void maximizeFrame(JInternalFrame f)
352 delegate.maximizeFrame(f);
356 public void minimizeFrame(JInternalFrame f)
358 delegate.minimizeFrame(f);
362 public void openFrame(JInternalFrame f)
364 delegate.openFrame(f);
368 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
375 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
379 public void setBoundsForFrame(JComponent f, int newX, int newY,
380 int newWidth, int newHeight)
382 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
385 // All other methods, simply delegate
390 * Creates a new Desktop object.
396 * A note to implementors. It is ESSENTIAL that any activities that might
397 * block are spawned off as threads rather than waited for during this
402 doConfigureStructurePrefs();
403 setTitle(ChannelProperties.getProperty("app_name") + " "
404 + Cache.getProperty("VERSION"));
407 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
408 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not
409 * officially documented or guaranteed to exist, so we access it via
410 * reflection. There appear to be unfathomable criteria about what this
411 * string can contain, and it if doesn't meet those criteria then "java"
412 * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview
413 * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does
414 * not. The reflection access may generate a warning: WARNING: An illegal
415 * reflective access operation has occurred WARNING: Illegal reflective
416 * access by jalview.gui.Desktop () to field
417 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
419 if (Platform.isLinux())
421 if (LaunchUtils.getJavaVersion() >= 11)
423 jalview.bin.Console.info(
424 "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.");
428 Toolkit xToolkit = Toolkit.getDefaultToolkit();
429 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
430 Field awtAppClassNameField = null;
432 if (Arrays.stream(declaredFields)
433 .anyMatch(f -> f.getName().equals("awtAppClassName")))
435 awtAppClassNameField = xToolkit.getClass()
436 .getDeclaredField("awtAppClassName");
439 String title = ChannelProperties.getProperty("app_name");
440 if (awtAppClassNameField != null)
442 awtAppClassNameField.setAccessible(true);
443 awtAppClassNameField.set(xToolkit, title);
447 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
449 } catch (Exception e)
451 jalview.bin.Console.debug("Error setting awtAppClassName");
452 jalview.bin.Console.trace(Cache.getStackTraceString(e));
456 setIconImages(ChannelProperties.getIconList());
458 // override quit handling when GUI OS close [X] button pressed
459 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
460 addWindowListener(new WindowAdapter()
463 public void windowClosing(WindowEvent ev)
465 QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
469 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
471 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
472 desktop = new MyDesktopPane(selmemusage);
474 showMemusage.setSelected(selmemusage);
475 desktop.setBackground(Color.white);
477 getContentPane().setLayout(new BorderLayout());
478 // alternate config - have scrollbars - see notes in JAL-153
479 // JScrollPane sp = new JScrollPane();
480 // sp.getViewport().setView(desktop);
481 // getContentPane().add(sp, BorderLayout.CENTER);
483 // BH 2018 - just an experiment to try unclipped JInternalFrames.
486 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
489 getContentPane().add(desktop, BorderLayout.CENTER);
490 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
492 // This line prevents Windows Look&Feel resizing all new windows to maximum
493 // if previous window was maximised
494 desktop.setDesktopManager(new MyDesktopManager(
495 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
496 : Platform.isAMacAndNotJS()
497 ? new AquaInternalFrameManager(
498 desktop.getDesktopManager())
499 : desktop.getDesktopManager())));
501 Rectangle dims = getLastKnownDimensions("");
508 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
509 int xPos = Math.max(5, (screenSize.width - 900) / 2);
510 int yPos = Math.max(5, (screenSize.height - 650) / 2);
511 setBounds(xPos, yPos, 900, 650);
514 if (!Platform.isJS())
521 jconsole = new Console(this, showjconsole);
522 jconsole.setHeader(Cache.getVersionDetailsForConsole());
523 showConsole(showjconsole);
525 showNews.setVisible(false);
527 experimentalFeatures.setSelected(showExperimental());
529 getIdentifiersOrgData();
533 // Spawn a thread that shows the splashscreen
536 SwingUtilities.invokeLater(new Runnable()
541 new SplashScreen(true);
546 // Thread off a new instance of the file chooser - this reduces the time
548 // takes to open it later on.
549 new Thread(new Runnable()
554 jalview.bin.Console.debug("Filechooser init thread started.");
555 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
556 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
558 jalview.bin.Console.debug("Filechooser init thread finished.");
561 // Add the service change listener
562 changeSupport.addJalviewPropertyChangeListener("services",
563 new PropertyChangeListener()
567 public void propertyChange(PropertyChangeEvent evt)
570 .debug("Firing service changed event for "
571 + evt.getNewValue());
572 JalviewServicesChanged(evt);
577 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
580 this.addMouseListener(ma = new MouseAdapter()
583 public void mousePressed(MouseEvent evt)
585 if (evt.isPopupTrigger()) // Mac
587 showPasteMenu(evt.getX(), evt.getY());
592 public void mouseReleased(MouseEvent evt)
594 if (evt.isPopupTrigger()) // Windows
596 showPasteMenu(evt.getX(), evt.getY());
600 desktop.addMouseListener(ma);
604 * Answers true if user preferences to enable experimental features is True
609 public boolean showExperimental()
611 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
612 Boolean.FALSE.toString());
613 return Boolean.valueOf(experimental).booleanValue();
616 public void doConfigureStructurePrefs()
618 // configure services
619 StructureSelectionManager ssm = StructureSelectionManager
620 .getStructureSelectionManager(this);
621 if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
623 ssm.setAddTempFacAnnot(
624 Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
625 ssm.setProcessSecondaryStructure(
626 Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
627 // JAL-3915 - RNAView is no longer an option so this has no effect
628 ssm.setSecStructServices(
629 Cache.getDefault(Preferences.USE_RNAVIEW, false));
633 ssm.setAddTempFacAnnot(false);
634 ssm.setProcessSecondaryStructure(false);
635 ssm.setSecStructServices(false);
639 public void checkForNews()
641 final Desktop me = this;
642 // Thread off the news reader, in case there are connection problems.
643 new Thread(new Runnable()
648 jalview.bin.Console.debug("Starting news thread.");
649 jvnews = new BlogReader(me);
650 showNews.setVisible(true);
651 jalview.bin.Console.debug("Completed news thread.");
656 public void getIdentifiersOrgData()
658 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
659 {// Thread off the identifiers fetcher
660 new Thread(new Runnable()
666 .debug("Downloading data from identifiers.org");
669 UrlDownloadClient.download(IdOrgSettings.getUrl(),
670 IdOrgSettings.getDownloadLocation());
671 } catch (IOException e)
674 .debug("Exception downloading identifiers.org data"
684 protected void showNews_actionPerformed(ActionEvent e)
686 showNews(showNews.isSelected());
689 void showNews(boolean visible)
691 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
692 showNews.setSelected(visible);
693 if (visible && !jvnews.isVisible())
695 new Thread(new Runnable()
700 long now = System.currentTimeMillis();
701 Desktop.instance.setProgressBar(
702 MessageManager.getString("status.refreshing_news"), now);
703 jvnews.refreshNews();
704 Desktop.instance.setProgressBar(null, now);
712 * recover the last known dimensions for a jalview window
715 * - empty string is desktop, all other windows have unique prefix
716 * @return null or last known dimensions scaled to current geometry (if last
717 * window geom was known)
719 Rectangle getLastKnownDimensions(String windowName)
721 // TODO: lock aspect ratio for scaling desktop Bug #0058199
722 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
723 String x = Cache.getProperty(windowName + "SCREEN_X");
724 String y = Cache.getProperty(windowName + "SCREEN_Y");
725 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
726 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
727 if ((x != null) && (y != null) && (width != null) && (height != null))
729 int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
730 iw = Integer.parseInt(width), ih = Integer.parseInt(height);
731 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
733 // attempt #1 - try to cope with change in screen geometry - this
734 // version doesn't preserve original jv aspect ratio.
735 // take ratio of current screen size vs original screen size.
736 double sw = ((1f * screenSize.width) / (1f * Integer
737 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
738 double sh = ((1f * screenSize.height) / (1f * Integer
739 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
740 // rescale the bounds depending upon the current screen geometry.
741 ix = (int) (ix * sw);
742 iw = (int) (iw * sw);
743 iy = (int) (iy * sh);
744 ih = (int) (ih * sh);
745 while (ix >= screenSize.width)
747 jalview.bin.Console.debug(
748 "Window geometry location recall error: shifting horizontal to within screenbounds.");
749 ix -= screenSize.width;
751 while (iy >= screenSize.height)
753 jalview.bin.Console.debug(
754 "Window geometry location recall error: shifting vertical to within screenbounds.");
755 iy -= screenSize.height;
757 jalview.bin.Console.debug(
758 "Got last known dimensions for " + windowName + ": x:" + ix
759 + " y:" + iy + " width:" + iw + " height:" + ih);
761 // return dimensions for new instance
762 return new Rectangle(ix, iy, iw, ih);
767 void showPasteMenu(int x, int y)
769 JPopupMenu popup = new JPopupMenu();
770 JMenuItem item = new JMenuItem(
771 MessageManager.getString("label.paste_new_window"));
772 item.addActionListener(new ActionListener()
775 public void actionPerformed(ActionEvent evt)
782 popup.show(this, x, y);
789 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
790 Transferable contents = c.getContents(this);
792 if (contents != null)
794 String file = (String) contents
795 .getTransferData(DataFlavor.stringFlavor);
797 FileFormatI format = new IdentifyFile().identify(file,
798 DataSourceType.PASTE);
800 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
803 } catch (Exception ex)
806 "Unable to paste alignment from system clipboard:\n" + ex);
811 * Adds and opens the given frame to the desktop
822 public static synchronized void addInternalFrame(
823 final JInternalFrame frame, String title, int w, int h)
825 addInternalFrame(frame, title, true, w, h, true, false);
829 * Add an internal frame to the Jalview desktop
836 * When true, display frame immediately, otherwise, caller must call
837 * setVisible themselves.
843 public static synchronized void addInternalFrame(
844 final JInternalFrame frame, String title, boolean makeVisible,
847 addInternalFrame(frame, title, makeVisible, w, h, true, false);
851 * Add an internal frame to the Jalview desktop and make it visible
864 public static synchronized void addInternalFrame(
865 final JInternalFrame frame, String title, int w, int h,
868 addInternalFrame(frame, title, true, w, h, resizable, false);
872 * Add an internal frame to the Jalview desktop
879 * When true, display frame immediately, otherwise, caller must call
880 * setVisible themselves.
887 * @param ignoreMinSize
888 * Do not set the default minimum size for frame
890 public static synchronized void addInternalFrame(
891 final JInternalFrame frame, String title, boolean makeVisible,
892 int w, int h, boolean resizable, boolean ignoreMinSize)
895 // TODO: allow callers to determine X and Y position of frame (eg. via
897 // TODO: consider fixing method to update entries in the window submenu with
898 // the current window title
900 frame.setTitle(title);
901 if (frame.getWidth() < 1 || frame.getHeight() < 1)
905 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
906 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
907 // IF JALVIEW IS RUNNING HEADLESS
908 // ///////////////////////////////////////////////
909 if (instance == null || (System.getProperty("java.awt.headless") != null
910 && System.getProperty("java.awt.headless").equals("true")))
919 frame.setMinimumSize(
920 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
922 // Set default dimension for Alignment Frame window.
923 // The Alignment Frame window could be added from a number of places,
925 // I did this here in order not to miss out on any Alignment frame.
926 if (frame instanceof AlignFrame)
928 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
929 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
933 frame.setVisible(makeVisible);
934 frame.setClosable(true);
935 frame.setResizable(resizable);
936 frame.setMaximizable(resizable);
937 frame.setIconifiable(resizable);
938 frame.setOpaque(Platform.isJS());
940 if (frame.getX() < 1 && frame.getY() < 1)
942 frame.setLocation(xOffset * openFrameCount,
943 yOffset * ((openFrameCount - 1) % 10) + yOffset);
947 * add an entry for the new frame in the Window menu (and remove it when the
950 final JMenuItem menuItem = new JMenuItem(title);
951 frame.addInternalFrameListener(new InternalFrameAdapter()
954 public void internalFrameActivated(InternalFrameEvent evt)
956 JInternalFrame itf = desktop.getSelectedFrame();
959 if (itf instanceof AlignFrame)
961 Jalview.setCurrentAlignFrame((AlignFrame) itf);
968 public void internalFrameClosed(InternalFrameEvent evt)
970 PaintRefresher.RemoveComponent(frame);
973 * defensive check to prevent frames being added half off the window
975 if (openFrameCount > 0)
981 * ensure no reference to alignFrame retained by menu item listener
983 if (menuItem.getActionListeners().length > 0)
985 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
987 windowMenu.remove(menuItem);
991 menuItem.addActionListener(new ActionListener()
994 public void actionPerformed(ActionEvent e)
998 frame.setSelected(true);
999 frame.setIcon(false);
1000 } catch (java.beans.PropertyVetoException ex)
1007 setKeyBindings(frame);
1011 windowMenu.add(menuItem);
1016 frame.setSelected(true);
1017 frame.requestFocus();
1018 } catch (java.beans.PropertyVetoException ve)
1020 } catch (java.lang.ClassCastException cex)
1022 jalview.bin.Console.warn(
1023 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
1029 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1034 private static void setKeyBindings(JInternalFrame frame)
1036 @SuppressWarnings("serial")
1037 final Action closeAction = new AbstractAction()
1040 public void actionPerformed(ActionEvent e)
1047 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1049 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1050 InputEvent.CTRL_DOWN_MASK);
1051 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
1052 ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1054 InputMap inputMap = frame
1055 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1056 String ctrlW = ctrlWKey.toString();
1057 inputMap.put(ctrlWKey, ctrlW);
1058 inputMap.put(cmdWKey, ctrlW);
1060 ActionMap actionMap = frame.getActionMap();
1061 actionMap.put(ctrlW, closeAction);
1065 public void lostOwnership(Clipboard clipboard, Transferable contents)
1069 Desktop.jalviewClipboard = null;
1072 internalCopy = false;
1076 public void dragEnter(DropTargetDragEvent evt)
1081 public void dragExit(DropTargetEvent evt)
1086 public void dragOver(DropTargetDragEvent evt)
1091 public void dropActionChanged(DropTargetDragEvent evt)
1102 public void drop(DropTargetDropEvent evt)
1104 boolean success = true;
1105 // JAL-1552 - acceptDrop required before getTransferable call for
1106 // Java's Transferable for native dnd
1107 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1108 Transferable t = evt.getTransferable();
1109 List<Object> files = new ArrayList<>();
1110 List<DataSourceType> protocols = new ArrayList<>();
1114 Desktop.transferFromDropTarget(files, protocols, evt, t);
1115 } catch (Exception e)
1117 e.printStackTrace();
1125 for (int i = 0; i < files.size(); i++)
1127 // BH 2018 File or String
1128 Object file = files.get(i);
1129 String fileName = file.toString();
1130 DataSourceType protocol = (protocols == null)
1131 ? DataSourceType.FILE
1133 FileFormatI format = null;
1135 if (fileName.endsWith(".jar"))
1137 format = FileFormat.Jalview;
1142 format = new IdentifyFile().identify(file, protocol);
1144 if (file instanceof File)
1146 Platform.cacheFileData((File) file);
1148 new FileLoader().LoadFile(null, file, protocol, format);
1151 } catch (Exception ex)
1156 evt.dropComplete(success); // need this to ensure input focus is properly
1157 // transfered to any new windows created
1167 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1169 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1170 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1171 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1172 BackupFiles.getEnabled());
1174 chooser.setFileView(new JalviewFileView());
1175 chooser.setDialogTitle(
1176 MessageManager.getString("label.open_local_file"));
1177 chooser.setToolTipText(MessageManager.getString("action.open"));
1179 chooser.setResponseHandler(0, () -> {
1180 File selectedFile = chooser.getSelectedFile();
1181 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1183 FileFormatI format = chooser.getSelectedFormat();
1186 * Call IdentifyFile to verify the file contains what its extension implies.
1187 * Skip this step for dynamically added file formats, because IdentifyFile does
1188 * not know how to recognise them.
1190 if (FileFormats.getInstance().isIdentifiable(format))
1194 format = new IdentifyFile().identify(selectedFile,
1195 DataSourceType.FILE);
1196 } catch (FileFormatException e)
1198 // format = null; //??
1202 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
1206 chooser.showOpenDialog(this);
1210 * Shows a dialog for input of a URL at which to retrieve alignment data
1215 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1217 // This construct allows us to have a wider textfield
1219 JLabel label = new JLabel(
1220 MessageManager.getString("label.input_file_url"));
1222 JPanel panel = new JPanel(new GridLayout(2, 1));
1226 * the URL to fetch is input in Java: an editable combobox with history JS:
1227 * (pending JAL-3038) a plain text field
1230 String urlBase = "https://www.";
1231 if (Platform.isJS())
1233 history = new JTextField(urlBase, 35);
1242 JComboBox<String> asCombo = new JComboBox<>();
1243 asCombo.setPreferredSize(new Dimension(400, 20));
1244 asCombo.setEditable(true);
1245 asCombo.addItem(urlBase);
1246 String historyItems = Cache.getProperty("RECENT_URL");
1247 if (historyItems != null)
1249 for (String token : historyItems.split("\\t"))
1251 asCombo.addItem(token);
1258 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1259 MessageManager.getString("action.cancel") };
1260 Callable<Void> action = () -> {
1261 @SuppressWarnings("unchecked")
1262 String url = (history instanceof JTextField
1263 ? ((JTextField) history).getText()
1264 : ((JComboBox<String>) history).getEditor().getItem()
1265 .toString().trim());
1267 if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
1269 if (viewport != null)
1271 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1272 FileFormat.Jalview);
1276 new FileLoader().LoadFile(url, DataSourceType.URL,
1277 FileFormat.Jalview);
1282 FileFormatI format = null;
1285 format = new IdentifyFile().identify(url, DataSourceType.URL);
1286 } catch (FileFormatException e)
1288 // TODO revise error handling, distinguish between
1289 // URL not found and response not valid
1294 String msg = MessageManager.formatMessage("label.couldnt_locate",
1296 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1297 MessageManager.getString("label.url_not_found"),
1298 JvOptionPane.WARNING_MESSAGE);
1300 return null; // Void
1303 if (viewport != null)
1305 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1310 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1313 return null; // Void
1315 String dialogOption = MessageManager
1316 .getString("label.input_alignment_from_url");
1317 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
1318 .showInternalDialog(panel, dialogOption,
1319 JvOptionPane.YES_NO_CANCEL_OPTION,
1320 JvOptionPane.PLAIN_MESSAGE, null, options,
1321 MessageManager.getString("action.ok"));
1325 * Opens the CutAndPaste window for the user to paste an alignment in to
1328 * - if not null, the pasted alignment is added to the current
1329 * alignment; if null, to a new alignment window
1332 public void inputTextboxMenuItem_actionPerformed(
1333 AlignmentViewPanel viewPanel)
1335 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1336 cap.setForInput(viewPanel);
1337 Desktop.addInternalFrame(cap,
1338 MessageManager.getString("label.cut_paste_alignmen_file"), true,
1343 * Check with user and saving files before actually quitting
1345 public void desktopQuit()
1347 desktopQuit(true, false);
1350 public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
1352 final Callable<Void> doDesktopQuit = () -> {
1353 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1354 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1355 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1356 storeLastKnownDimensions("", new Rectangle(getBounds().x,
1357 getBounds().y, getWidth(), getHeight()));
1359 if (jconsole != null)
1361 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1362 jconsole.stopConsole();
1367 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1371 if (dialogExecutor != null)
1373 dialogExecutor.shutdownNow();
1376 closeAll_actionPerformed(null);
1378 if (groovyConsole != null)
1380 // suppress a possible repeat prompt to save script
1381 groovyConsole.setDirty(false);
1382 groovyConsole.exit();
1385 if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
1387 // note that shutdown hook will not be run
1388 jalview.bin.Console.debug("Force Quit selected by user");
1389 Runtime.getRuntime().halt(0);
1392 jalview.bin.Console.debug("Quit selected by user");
1395 instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1396 // instance.dispose();
1400 return null; // Void
1403 return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
1404 QuitHandler.defaultCancelQuit);
1408 * Don't call this directly, use desktopQuit() above. Exits the program.
1413 // this will run the shutdownHook but QuitHandler.getQuitResponse() should
1414 // not run a second time if gotQuitResponse flag has been set (i.e. user
1415 // confirmed quit of some kind).
1419 private void storeLastKnownDimensions(String string, Rectangle jc)
1421 jalview.bin.Console.debug("Storing last known dimensions for " + string
1422 + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1423 + " height:" + jc.height);
1425 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1426 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1427 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1428 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1438 public void aboutMenuItem_actionPerformed(ActionEvent e)
1440 new Thread(new Runnable()
1445 new SplashScreen(false);
1451 * Returns the html text for the About screen, including any available version
1452 * number, build details, author details and citation reference, but without
1453 * the enclosing {@code html} tags
1457 public String getAboutMessage()
1459 StringBuilder message = new StringBuilder(1024);
1460 message.append("<div style=\"font-family: sans-serif;\">")
1461 .append("<h1><strong>Version: ")
1462 .append(Cache.getProperty("VERSION")).append("</strong></h1>")
1463 .append("<strong>Built: <em>")
1464 .append(Cache.getDefault("BUILD_DATE", "unknown"))
1465 .append("</em> from ").append(Cache.getBuildDetailsForSplash())
1466 .append("</strong>");
1468 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1469 if (latestVersion.equals("Checking"))
1471 // JBP removed this message for 2.11: May be reinstated in future version
1472 // message.append("<br>...Checking latest version...</br>");
1474 else if (!latestVersion.equals(Cache.getProperty("VERSION")))
1476 boolean red = false;
1477 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT)
1478 .indexOf("automated build") == -1)
1481 // Displayed when code version and jnlp version do not match and code
1482 // version is not a development build
1483 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1486 message.append("<br>!! Version ")
1487 .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1488 .append(" is available for download from ")
1489 .append(Cache.getDefault("www.jalview.org",
1490 "https://www.jalview.org"))
1494 message.append("</div>");
1497 message.append("<br>Authors: ");
1498 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1499 message.append(CITATION);
1501 message.append("</div>");
1503 return message.toString();
1507 * Action on requesting Help documentation
1510 public void documentationMenuItem_actionPerformed()
1514 if (Platform.isJS())
1516 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1525 Help.showHelpWindow();
1527 } catch (Exception ex)
1529 System.err.println("Error opening help: " + ex.getMessage());
1534 public void closeAll_actionPerformed(ActionEvent e)
1536 // TODO show a progress bar while closing?
1537 JInternalFrame[] frames = desktop.getAllFrames();
1538 for (int i = 0; i < frames.length; i++)
1542 frames[i].setClosed(true);
1543 } catch (java.beans.PropertyVetoException ex)
1547 Jalview.setCurrentAlignFrame(null);
1548 System.out.println("ALL CLOSED");
1551 * reset state of singleton objects as appropriate (clear down session state
1552 * when all windows are closed)
1554 StructureSelectionManager ssm = StructureSelectionManager
1555 .getStructureSelectionManager(this);
1563 public void raiseRelated_actionPerformed(ActionEvent e)
1565 reorderAssociatedWindows(false, false);
1569 public void minimizeAssociated_actionPerformed(ActionEvent e)
1571 reorderAssociatedWindows(true, false);
1574 void closeAssociatedWindows()
1576 reorderAssociatedWindows(false, true);
1582 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1586 protected void garbageCollect_actionPerformed(ActionEvent e)
1588 // We simply collect the garbage
1589 jalview.bin.Console.debug("Collecting garbage...");
1591 jalview.bin.Console.debug("Finished garbage collection.");
1597 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1601 protected void showMemusage_actionPerformed(ActionEvent e)
1603 desktop.showMemoryUsage(showMemusage.isSelected());
1610 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1614 protected void showConsole_actionPerformed(ActionEvent e)
1616 showConsole(showConsole.isSelected());
1619 Console jconsole = null;
1622 * control whether the java console is visible or not
1626 void showConsole(boolean selected)
1628 // TODO: decide if we should update properties file
1629 if (jconsole != null) // BH 2018
1631 showConsole.setSelected(selected);
1632 Cache.setProperty("SHOW_JAVA_CONSOLE",
1633 Boolean.valueOf(selected).toString());
1634 jconsole.setVisible(selected);
1638 void reorderAssociatedWindows(boolean minimize, boolean close)
1640 JInternalFrame[] frames = desktop.getAllFrames();
1641 if (frames == null || frames.length < 1)
1646 AlignmentViewport source = null, target = null;
1647 if (frames[0] instanceof AlignFrame)
1649 source = ((AlignFrame) frames[0]).getCurrentView();
1651 else if (frames[0] instanceof TreePanel)
1653 source = ((TreePanel) frames[0]).getViewPort();
1655 else if (frames[0] instanceof PCAPanel)
1657 source = ((PCAPanel) frames[0]).av;
1659 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1661 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1666 for (int i = 0; i < frames.length; i++)
1669 if (frames[i] == null)
1673 if (frames[i] instanceof AlignFrame)
1675 target = ((AlignFrame) frames[i]).getCurrentView();
1677 else if (frames[i] instanceof TreePanel)
1679 target = ((TreePanel) frames[i]).getViewPort();
1681 else if (frames[i] instanceof PCAPanel)
1683 target = ((PCAPanel) frames[i]).av;
1685 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1687 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1690 if (source == target)
1696 frames[i].setClosed(true);
1700 frames[i].setIcon(minimize);
1703 frames[i].toFront();
1707 } catch (java.beans.PropertyVetoException ex)
1722 protected void preferences_actionPerformed(ActionEvent e)
1724 Preferences.openPreferences();
1728 * Prompts the user to choose a file and then saves the Jalview state as a
1729 * Jalview project file
1732 public void saveState_actionPerformed()
1734 saveState_actionPerformed(false);
1737 public void saveState_actionPerformed(boolean saveAs)
1739 java.io.File projectFile = getProjectFile();
1740 // autoSave indicates we already have a file and don't need to ask
1741 boolean autoSave = projectFile != null && !saveAs
1742 && BackupFiles.getEnabled();
1744 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1745 // saveAs="+saveAs+", Backups
1746 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1748 boolean approveSave = false;
1751 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1754 chooser.setFileView(new JalviewFileView());
1755 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1757 int value = chooser.showSaveDialog(this);
1759 if (value == JalviewFileChooser.APPROVE_OPTION)
1761 projectFile = chooser.getSelectedFile();
1762 setProjectFile(projectFile);
1767 if (approveSave || autoSave)
1769 final Desktop me = this;
1770 final java.io.File chosenFile = projectFile;
1771 new Thread(new Runnable()
1776 // TODO: refactor to Jalview desktop session controller action.
1777 setProgressBar(MessageManager.formatMessage(
1778 "label.saving_jalview_project", new Object[]
1779 { chosenFile.getName() }), chosenFile.hashCode());
1780 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1781 // TODO catch and handle errors for savestate
1782 // TODO prevent user from messing with the Desktop whilst we're saving
1785 boolean doBackup = BackupFiles.getEnabled();
1786 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1789 new Jalview2XML().saveState(
1790 doBackup ? backupfiles.getTempFile() : chosenFile);
1794 backupfiles.setWriteSuccess(true);
1795 backupfiles.rollBackupsAndRenameTempFile();
1797 } catch (OutOfMemoryError oom)
1799 new OOMWarning("Whilst saving current state to "
1800 + chosenFile.getName(), oom);
1801 } catch (Exception ex)
1803 jalview.bin.Console.error("Problems whilst trying to save to "
1804 + chosenFile.getName(), ex);
1805 JvOptionPane.showMessageDialog(me,
1806 MessageManager.formatMessage(
1807 "label.error_whilst_saving_current_state_to",
1809 { chosenFile.getName() }),
1810 MessageManager.getString("label.couldnt_save_project"),
1811 JvOptionPane.WARNING_MESSAGE);
1813 setProgressBar(null, chosenFile.hashCode());
1820 public void saveAsState_actionPerformed(ActionEvent e)
1822 saveState_actionPerformed(true);
1825 private void setProjectFile(File choice)
1827 this.projectFile = choice;
1830 public File getProjectFile()
1832 return this.projectFile;
1836 * Shows a file chooser dialog and tries to read in the selected file as a
1840 public void loadState_actionPerformed()
1842 final String[] suffix = new String[] { "jvp", "jar" };
1843 final String[] desc = new String[] { "Jalview Project",
1844 "Jalview Project (old)" };
1845 JalviewFileChooser chooser = new JalviewFileChooser(
1846 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1847 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1851 chooser.setFileView(new JalviewFileView());
1852 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1853 chooser.setResponseHandler(0, () -> {
1854 File selectedFile = chooser.getSelectedFile();
1855 setProjectFile(selectedFile);
1856 String choice = selectedFile.getAbsolutePath();
1857 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1858 new Thread(new Runnable()
1865 new Jalview2XML().loadJalviewAlign(selectedFile);
1866 } catch (OutOfMemoryError oom)
1868 new OOMWarning("Whilst loading project from " + choice, oom);
1869 } catch (Exception ex)
1871 jalview.bin.Console.error(
1872 "Problems whilst loading project from " + choice, ex);
1873 JvOptionPane.showMessageDialog(Desktop.desktop,
1874 MessageManager.formatMessage(
1875 "label.error_whilst_loading_project_from",
1878 MessageManager.getString("label.couldnt_load_project"),
1879 JvOptionPane.WARNING_MESSAGE);
1882 }, "Project Loader").start();
1886 chooser.showOpenDialog(this);
1890 public void inputSequence_actionPerformed(ActionEvent e)
1892 new SequenceFetcher(this);
1895 JPanel progressPanel;
1897 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1899 public void startLoading(final Object fileName)
1901 if (fileLoadingCount == 0)
1903 fileLoadingPanels.add(addProgressPanel(MessageManager
1904 .formatMessage("label.loading_file", new Object[]
1910 private JPanel addProgressPanel(String string)
1912 if (progressPanel == null)
1914 progressPanel = new JPanel(new GridLayout(1, 1));
1915 totalProgressCount = 0;
1916 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1918 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1919 JProgressBar progressBar = new JProgressBar();
1920 progressBar.setIndeterminate(true);
1922 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1924 thisprogress.add(progressBar, BorderLayout.CENTER);
1925 progressPanel.add(thisprogress);
1926 ((GridLayout) progressPanel.getLayout()).setRows(
1927 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1928 ++totalProgressCount;
1929 instance.validate();
1930 return thisprogress;
1933 int totalProgressCount = 0;
1935 private void removeProgressPanel(JPanel progbar)
1937 if (progressPanel != null)
1939 synchronized (progressPanel)
1941 progressPanel.remove(progbar);
1942 GridLayout gl = (GridLayout) progressPanel.getLayout();
1943 gl.setRows(gl.getRows() - 1);
1944 if (--totalProgressCount < 1)
1946 this.getContentPane().remove(progressPanel);
1947 progressPanel = null;
1954 public void stopLoading()
1957 if (fileLoadingCount < 1)
1959 while (fileLoadingPanels.size() > 0)
1961 removeProgressPanel(fileLoadingPanels.remove(0));
1963 fileLoadingPanels.clear();
1964 fileLoadingCount = 0;
1969 public static int getViewCount(String alignmentId)
1971 AlignmentViewport[] aps = getViewports(alignmentId);
1972 return (aps == null) ? 0 : aps.length;
1977 * @param alignmentId
1978 * - if null, all sets are returned
1979 * @return all AlignmentPanels concerning the alignmentId sequence set
1981 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1983 if (Desktop.desktop == null)
1985 // no frames created and in headless mode
1986 // TODO: verify that frames are recoverable when in headless mode
1989 List<AlignmentPanel> aps = new ArrayList<>();
1990 AlignFrame[] frames = getAlignFrames();
1995 for (AlignFrame af : frames)
1997 for (AlignmentPanel ap : af.alignPanels)
1999 if (alignmentId == null
2000 || alignmentId.equals(ap.av.getSequenceSetId()))
2006 if (aps.size() == 0)
2010 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
2015 * get all the viewports on an alignment.
2017 * @param sequenceSetId
2018 * unique alignment id (may be null - all viewports returned in that
2020 * @return all viewports on the alignment bound to sequenceSetId
2022 public static AlignmentViewport[] getViewports(String sequenceSetId)
2024 List<AlignmentViewport> viewp = new ArrayList<>();
2025 if (desktop != null)
2027 AlignFrame[] frames = Desktop.getAlignFrames();
2029 for (AlignFrame afr : frames)
2031 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
2032 .equals(sequenceSetId))
2034 if (afr.alignPanels != null)
2036 for (AlignmentPanel ap : afr.alignPanels)
2038 if (sequenceSetId == null
2039 || sequenceSetId.equals(ap.av.getSequenceSetId()))
2047 viewp.add(afr.getViewport());
2051 if (viewp.size() > 0)
2053 return viewp.toArray(new AlignmentViewport[viewp.size()]);
2060 * Explode the views in the given frame into separate AlignFrame
2064 public static void explodeViews(AlignFrame af)
2066 int size = af.alignPanels.size();
2072 // FIXME: ideally should use UI interface API
2073 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2074 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2075 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2076 for (int i = 0; i < size; i++)
2078 AlignmentPanel ap = af.alignPanels.get(i);
2080 AlignFrame newaf = new AlignFrame(ap);
2082 // transfer reference for existing feature settings to new alignFrame
2083 if (ap == af.alignPanel)
2085 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2087 newaf.featureSettings = viewFeatureSettings;
2089 newaf.setFeatureSettingsGeometry(fsBounds);
2093 * Restore the view's last exploded frame geometry if known. Multiple views from
2094 * one exploded frame share and restore the same (frame) position and size.
2096 Rectangle geometry = ap.av.getExplodedGeometry();
2097 if (geometry != null)
2099 newaf.setBounds(geometry);
2102 ap.av.setGatherViewsHere(false);
2104 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2105 AlignFrame.DEFAULT_HEIGHT);
2106 // and materialise a new feature settings dialog instance for the new
2108 // (closes the old as if 'OK' was pressed)
2109 if (ap == af.alignPanel && newaf.featureSettings != null
2110 && newaf.featureSettings.isOpen()
2111 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2113 newaf.showFeatureSettingsUI();
2117 af.featureSettings = null;
2118 af.alignPanels.clear();
2119 af.closeMenuItem_actionPerformed(true);
2124 * Gather expanded views (separate AlignFrame's) with the same sequence set
2125 * identifier back in to this frame as additional views, and close the
2126 * expanded views. Note the expanded frames may themselves have multiple
2127 * views. We take the lot.
2131 public void gatherViews(AlignFrame source)
2133 source.viewport.setGatherViewsHere(true);
2134 source.viewport.setExplodedGeometry(source.getBounds());
2135 JInternalFrame[] frames = desktop.getAllFrames();
2136 String viewId = source.viewport.getSequenceSetId();
2137 for (int t = 0; t < frames.length; t++)
2139 if (frames[t] instanceof AlignFrame && frames[t] != source)
2141 AlignFrame af = (AlignFrame) frames[t];
2142 boolean gatherThis = false;
2143 for (int a = 0; a < af.alignPanels.size(); a++)
2145 AlignmentPanel ap = af.alignPanels.get(a);
2146 if (viewId.equals(ap.av.getSequenceSetId()))
2149 ap.av.setGatherViewsHere(false);
2150 ap.av.setExplodedGeometry(af.getBounds());
2151 source.addAlignmentPanel(ap, false);
2157 if (af.featureSettings != null && af.featureSettings.isOpen())
2159 if (source.featureSettings == null)
2161 // preserve the feature settings geometry for this frame
2162 source.featureSettings = af.featureSettings;
2163 source.setFeatureSettingsGeometry(
2164 af.getFeatureSettingsGeometry());
2168 // close it and forget
2169 af.featureSettings.close();
2172 af.alignPanels.clear();
2173 af.closeMenuItem_actionPerformed(true);
2178 // refresh the feature setting UI for the source frame if it exists
2179 if (source.featureSettings != null && source.featureSettings.isOpen())
2181 source.showFeatureSettingsUI();
2186 public JInternalFrame[] getAllFrames()
2188 return desktop.getAllFrames();
2192 * Checks the given url to see if it gives a response indicating that the user
2193 * should be informed of a new questionnaire.
2197 public void checkForQuestionnaire(String url)
2199 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2200 // javax.swing.SwingUtilities.invokeLater(jvq);
2201 new Thread(jvq).start();
2204 public void checkURLLinks()
2206 // Thread off the URL link checker
2207 addDialogThread(new Runnable()
2212 if (Cache.getDefault("CHECKURLLINKS", true))
2214 // check what the actual links are - if it's just the default don't
2215 // bother with the warning
2216 List<String> links = Preferences.sequenceUrlLinks
2219 // only need to check links if there is one with a
2220 // SEQUENCE_ID which is not the default EMBL_EBI link
2221 ListIterator<String> li = links.listIterator();
2222 boolean check = false;
2223 List<JLabel> urls = new ArrayList<>();
2224 while (li.hasNext())
2226 String link = li.next();
2227 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2228 && !UrlConstants.isDefaultString(link))
2231 int barPos = link.indexOf("|");
2232 String urlMsg = barPos == -1 ? link
2233 : link.substring(0, barPos) + ": "
2234 + link.substring(barPos + 1);
2235 urls.add(new JLabel(urlMsg));
2243 // ask user to check in case URL links use old style tokens
2244 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2245 JPanel msgPanel = new JPanel();
2246 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2247 msgPanel.add(Box.createVerticalGlue());
2248 JLabel msg = new JLabel(MessageManager
2249 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2250 JLabel msg2 = new JLabel(MessageManager
2251 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2253 for (JLabel url : urls)
2259 final JCheckBox jcb = new JCheckBox(
2260 MessageManager.getString("label.do_not_display_again"));
2261 jcb.addActionListener(new ActionListener()
2264 public void actionPerformed(ActionEvent e)
2266 // update Cache settings for "don't show this again"
2267 boolean showWarningAgain = !jcb.isSelected();
2268 Cache.setProperty("CHECKURLLINKS",
2269 Boolean.valueOf(showWarningAgain).toString());
2274 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
2276 .getString("label.SEQUENCE_ID_no_longer_used"),
2277 JvOptionPane.WARNING_MESSAGE);
2284 * Proxy class for JDesktopPane which optionally displays the current memory
2285 * usage and highlights the desktop area with a red bar if free memory runs
2290 public class MyDesktopPane extends JDesktopPane implements Runnable
2292 private static final float ONE_MB = 1048576f;
2294 boolean showMemoryUsage = false;
2298 java.text.NumberFormat df;
2300 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2303 public MyDesktopPane(boolean showMemoryUsage)
2305 showMemoryUsage(showMemoryUsage);
2308 public void showMemoryUsage(boolean showMemory)
2310 this.showMemoryUsage = showMemory;
2313 Thread worker = new Thread(this);
2319 public boolean isShowMemoryUsage()
2321 return showMemoryUsage;
2327 df = java.text.NumberFormat.getNumberInstance();
2328 df.setMaximumFractionDigits(2);
2329 runtime = Runtime.getRuntime();
2331 while (showMemoryUsage)
2335 maxMemory = runtime.maxMemory() / ONE_MB;
2336 allocatedMemory = runtime.totalMemory() / ONE_MB;
2337 freeMemory = runtime.freeMemory() / ONE_MB;
2338 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2340 percentUsage = (totalFreeMemory / maxMemory) * 100;
2342 // if (percentUsage < 20)
2344 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2346 // instance.set.setBorder(border1);
2349 // sleep after showing usage
2351 } catch (Exception ex)
2353 ex.printStackTrace();
2359 public void paintComponent(Graphics g)
2361 if (showMemoryUsage && g != null && df != null)
2363 if (percentUsage < 20)
2365 g.setColor(Color.red);
2367 FontMetrics fm = g.getFontMetrics();
2370 g.drawString(MessageManager.formatMessage("label.memory_stats",
2372 { df.format(totalFreeMemory), df.format(maxMemory),
2373 df.format(percentUsage) }),
2374 10, getHeight() - fm.getHeight());
2378 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2379 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2384 * Accessor method to quickly get all the AlignmentFrames loaded.
2386 * @return an array of AlignFrame, or null if none found
2388 public static AlignFrame[] getAlignFrames()
2390 if (Jalview.isHeadlessMode())
2392 // Desktop.desktop is null in headless mode
2393 return new AlignFrame[] { Jalview.currentAlignFrame };
2396 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2402 List<AlignFrame> avp = new ArrayList<>();
2404 for (int i = frames.length - 1; i > -1; i--)
2406 if (frames[i] instanceof AlignFrame)
2408 avp.add((AlignFrame) frames[i]);
2410 else if (frames[i] instanceof SplitFrame)
2413 * Also check for a split frame containing an AlignFrame
2415 GSplitFrame sf = (GSplitFrame) frames[i];
2416 if (sf.getTopFrame() instanceof AlignFrame)
2418 avp.add((AlignFrame) sf.getTopFrame());
2420 if (sf.getBottomFrame() instanceof AlignFrame)
2422 avp.add((AlignFrame) sf.getBottomFrame());
2426 if (avp.size() == 0)
2430 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2435 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2439 public GStructureViewer[] getJmols()
2441 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2447 List<GStructureViewer> avp = new ArrayList<>();
2449 for (int i = frames.length - 1; i > -1; i--)
2451 if (frames[i] instanceof AppJmol)
2453 GStructureViewer af = (GStructureViewer) frames[i];
2457 if (avp.size() == 0)
2461 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2466 * Add Groovy Support to Jalview
2469 public void groovyShell_actionPerformed()
2473 openGroovyConsole();
2474 } catch (Exception ex)
2476 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2477 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2479 MessageManager.getString("label.couldnt_create_groovy_shell"),
2480 MessageManager.getString("label.groovy_support_failed"),
2481 JvOptionPane.ERROR_MESSAGE);
2486 * Open the Groovy console
2488 void openGroovyConsole()
2490 if (groovyConsole == null)
2492 groovyConsole = new groovy.ui.Console();
2493 groovyConsole.setVariable("Jalview", this);
2494 groovyConsole.run();
2497 * We allow only one console at a time, so that AlignFrame menu option
2498 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2499 * enable 'Run script', when the console is opened, and the reverse when it is
2502 Window window = (Window) groovyConsole.getFrame();
2503 window.addWindowListener(new WindowAdapter()
2506 public void windowClosed(WindowEvent e)
2509 * rebind CMD-Q from Groovy Console to Jalview Quit
2512 enableExecuteGroovy(false);
2518 * show Groovy console window (after close and reopen)
2520 ((Window) groovyConsole.getFrame()).setVisible(true);
2523 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2524 * opening a second console
2526 enableExecuteGroovy(true);
2530 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2531 * binding when opened
2533 protected void addQuitHandler()
2536 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2538 .getKeyStroke(KeyEvent.VK_Q,
2539 jalview.util.ShortcutKeyMaskExWrapper
2540 .getMenuShortcutKeyMaskEx()),
2542 getRootPane().getActionMap().put("Quit", new AbstractAction()
2545 public void actionPerformed(ActionEvent e)
2553 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2556 * true if Groovy console is open
2558 public void enableExecuteGroovy(boolean enabled)
2561 * disable opening a second Groovy console (or re-enable when the console is
2564 groovyShell.setEnabled(!enabled);
2566 AlignFrame[] alignFrames = getAlignFrames();
2567 if (alignFrames != null)
2569 for (AlignFrame af : alignFrames)
2571 af.setGroovyEnabled(enabled);
2577 * Progress bars managed by the IProgressIndicator method.
2579 private Hashtable<Long, JPanel> progressBars;
2581 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2586 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2589 public void setProgressBar(String message, long id)
2591 if (progressBars == null)
2593 progressBars = new Hashtable<>();
2594 progressBarHandlers = new Hashtable<>();
2597 if (progressBars.get(Long.valueOf(id)) != null)
2599 JPanel panel = progressBars.remove(Long.valueOf(id));
2600 if (progressBarHandlers.contains(Long.valueOf(id)))
2602 progressBarHandlers.remove(Long.valueOf(id));
2604 removeProgressPanel(panel);
2608 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2615 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2616 * jalview.gui.IProgressIndicatorHandler)
2619 public void registerHandler(final long id,
2620 final IProgressIndicatorHandler handler)
2622 if (progressBarHandlers == null
2623 || !progressBars.containsKey(Long.valueOf(id)))
2625 throw new Error(MessageManager.getString(
2626 "error.call_setprogressbar_before_registering_handler"));
2628 progressBarHandlers.put(Long.valueOf(id), handler);
2629 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2630 if (handler.canCancel())
2632 JButton cancel = new JButton(
2633 MessageManager.getString("action.cancel"));
2634 final IProgressIndicator us = this;
2635 cancel.addActionListener(new ActionListener()
2639 public void actionPerformed(ActionEvent e)
2641 handler.cancelActivity(id);
2642 us.setProgressBar(MessageManager
2643 .formatMessage("label.cancelled_params", new Object[]
2644 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2648 progressPanel.add(cancel, BorderLayout.EAST);
2654 * @return true if any progress bars are still active
2657 public boolean operationInProgress()
2659 if (progressBars != null && progressBars.size() > 0)
2667 * This will return the first AlignFrame holding the given viewport instance.
2668 * It will break if there are more than one AlignFrames viewing a particular
2672 * @return alignFrame for viewport
2674 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2676 if (desktop != null)
2678 AlignmentPanel[] aps = getAlignmentPanels(
2679 viewport.getSequenceSetId());
2680 for (int panel = 0; aps != null && panel < aps.length; panel++)
2682 if (aps[panel] != null && aps[panel].av == viewport)
2684 return aps[panel].alignFrame;
2691 public VamsasApplication getVamsasApplication()
2693 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2699 * flag set if jalview GUI is being operated programmatically
2701 private boolean inBatchMode = false;
2704 * check if jalview GUI is being operated programmatically
2706 * @return inBatchMode
2708 public boolean isInBatchMode()
2714 * set flag if jalview GUI is being operated programmatically
2716 * @param inBatchMode
2718 public void setInBatchMode(boolean inBatchMode)
2720 this.inBatchMode = inBatchMode;
2724 * start service discovery and wait till it is done
2726 public void startServiceDiscovery()
2728 startServiceDiscovery(false);
2732 * start service discovery threads - blocking or non-blocking
2736 public void startServiceDiscovery(boolean blocking)
2738 startServiceDiscovery(blocking, false);
2742 * start service discovery threads
2745 * - false means call returns immediately
2746 * @param ignore_SHOW_JWS2_SERVICES_preference
2747 * - when true JABA services are discovered regardless of user's JWS2
2748 * discovery preference setting
2750 public void startServiceDiscovery(boolean blocking,
2751 boolean ignore_SHOW_JWS2_SERVICES_preference)
2753 boolean alive = true;
2754 Thread t0 = null, t1 = null, t2 = null;
2755 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2758 // todo: changesupport handlers need to be transferred
2759 if (discoverer == null)
2761 discoverer = new jalview.ws.jws1.Discoverer();
2762 // register PCS handler for desktop.
2763 discoverer.addPropertyChangeListener(changeSupport);
2765 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2766 // until we phase out completely
2767 (t0 = new Thread(discoverer)).start();
2770 if (ignore_SHOW_JWS2_SERVICES_preference
2771 || Cache.getDefault("SHOW_JWS2_SERVICES", true))
2773 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2774 .startDiscoverer(changeSupport);
2778 // TODO: do rest service discovery
2787 } catch (Exception e)
2790 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
2791 || (t3 != null && t3.isAlive())
2792 || (t0 != null && t0.isAlive());
2798 * called to check if the service discovery process completed successfully.
2802 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2804 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2806 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
2807 .getErrorMessages();
2810 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2812 if (serviceChangedDialog == null)
2814 // only run if we aren't already displaying one of these.
2815 addDialogThread(serviceChangedDialog = new Runnable()
2822 * JalviewDialog jd =new JalviewDialog() {
2824 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2826 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2828 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2830 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2832 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2833 * + " or mis-configured HTTP proxy settings.<br/>" +
2834 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2835 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2836 * true, true, "Web Service Configuration Problem", 450, 400);
2838 * jd.waitForInput();
2840 JvOptionPane.showConfirmDialog(Desktop.desktop,
2841 new JLabel("<html><table width=\"450\"><tr><td>"
2842 + ermsg + "</td></tr></table>"
2843 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2844 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2845 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2846 + " Tools->Preferences dialog box to change them.</p></html>"),
2847 "Web Service Configuration Problem",
2848 JvOptionPane.DEFAULT_OPTION,
2849 JvOptionPane.ERROR_MESSAGE);
2850 serviceChangedDialog = null;
2858 jalview.bin.Console.error(
2859 "Errors reported by JABA discovery service. Check web services preferences.\n"
2866 private Runnable serviceChangedDialog = null;
2869 * start a thread to open a URL in the configured browser. Pops up a warning
2870 * dialog to the user if there is an exception when calling out to the browser
2875 public static void showUrl(final String url)
2877 showUrl(url, Desktop.instance);
2881 * Like showUrl but allows progress handler to be specified
2885 * (null) or object implementing IProgressIndicator
2887 public static void showUrl(final String url,
2888 final IProgressIndicator progress)
2890 new Thread(new Runnable()
2897 if (progress != null)
2899 progress.setProgressBar(MessageManager
2900 .formatMessage("status.opening_params", new Object[]
2901 { url }), this.hashCode());
2903 jalview.util.BrowserLauncher.openURL(url);
2904 } catch (Exception ex)
2906 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2908 .getString("label.web_browser_not_found_unix"),
2909 MessageManager.getString("label.web_browser_not_found"),
2910 JvOptionPane.WARNING_MESSAGE);
2912 ex.printStackTrace();
2914 if (progress != null)
2916 progress.setProgressBar(null, this.hashCode());
2922 public static WsParamSetManager wsparamManager = null;
2924 public static ParamManager getUserParameterStore()
2926 if (wsparamManager == null)
2928 wsparamManager = new WsParamSetManager();
2930 return wsparamManager;
2934 * static hyperlink handler proxy method for use by Jalview's internal windows
2938 public static void hyperlinkUpdate(HyperlinkEvent e)
2940 if (e.getEventType() == EventType.ACTIVATED)
2945 url = e.getURL().toString();
2946 Desktop.showUrl(url);
2947 } catch (Exception x)
2952 .error("Couldn't handle string " + url + " as a URL.");
2954 // ignore any exceptions due to dud links.
2961 * single thread that handles display of dialogs to user.
2963 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2966 * flag indicating if dialogExecutor should try to acquire a permit
2968 private volatile boolean dialogPause = true;
2973 private java.util.concurrent.Semaphore block = new Semaphore(0);
2975 private static groovy.ui.Console groovyConsole;
2978 * add another dialog thread to the queue
2982 public void addDialogThread(final Runnable prompter)
2984 dialogExecutor.submit(new Runnable()
2994 } catch (InterruptedException x)
2998 if (instance == null)
3004 SwingUtilities.invokeAndWait(prompter);
3005 } catch (Exception q)
3007 jalview.bin.Console.warn("Unexpected Exception in dialog thread.",
3014 public void startDialogQueue()
3016 // set the flag so we don't pause waiting for another permit and semaphore
3017 // the current task to begin
3018 dialogPause = false;
3023 * Outputs an image of the desktop to file in EPS format, after prompting the
3024 * user for choice of Text or Lineart character rendering (unless a preference
3025 * has been set). The file name is generated as
3028 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
3032 protected void snapShotWindow_actionPerformed(ActionEvent e)
3034 // currently the menu option to do this is not shown
3037 int width = getWidth();
3038 int height = getHeight();
3040 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
3041 ImageWriterI writer = new ImageWriterI()
3044 public void exportImage(Graphics g) throws Exception
3047 jalview.bin.Console.info("Successfully written snapshot to file "
3048 + of.getAbsolutePath());
3051 String title = "View of desktop";
3052 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
3054 exporter.doExport(of, this, width, height, title);
3058 * Explode the views in the given SplitFrame into separate SplitFrame windows.
3059 * This respects (remembers) any previous 'exploded geometry' i.e. the size
3060 * and location last time the view was expanded (if any). However it does not
3061 * remember the split pane divider location - this is set to match the
3062 * 'exploding' frame.
3066 public void explodeViews(SplitFrame sf)
3068 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
3069 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
3070 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3072 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3074 int viewCount = topPanels.size();
3081 * Processing in reverse order works, forwards order leaves the first panels not
3082 * visible. I don't know why!
3084 for (int i = viewCount - 1; i >= 0; i--)
3087 * Make new top and bottom frames. These take over the respective AlignmentPanel
3088 * objects, including their AlignmentViewports, so the cdna/protein
3089 * relationships between the viewports is carried over to the new split frames.
3091 * explodedGeometry holds the (x, y) position of the previously exploded
3092 * SplitFrame, and the (width, height) of the AlignFrame component
3094 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3095 AlignFrame newTopFrame = new AlignFrame(topPanel);
3096 newTopFrame.setSize(oldTopFrame.getSize());
3097 newTopFrame.setVisible(true);
3098 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3099 .getExplodedGeometry();
3100 if (geometry != null)
3102 newTopFrame.setSize(geometry.getSize());
3105 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3106 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3107 newBottomFrame.setSize(oldBottomFrame.getSize());
3108 newBottomFrame.setVisible(true);
3109 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3110 .getExplodedGeometry();
3111 if (geometry != null)
3113 newBottomFrame.setSize(geometry.getSize());
3116 topPanel.av.setGatherViewsHere(false);
3117 bottomPanel.av.setGatherViewsHere(false);
3118 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3120 if (geometry != null)
3122 splitFrame.setLocation(geometry.getLocation());
3124 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3128 * Clear references to the panels (now relocated in the new SplitFrames) before
3129 * closing the old SplitFrame.
3132 bottomPanels.clear();
3137 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3138 * back into the given SplitFrame as additional views. Note that the gathered
3139 * frames may themselves have multiple views.
3143 public void gatherViews(GSplitFrame source)
3146 * special handling of explodedGeometry for a view within a SplitFrame: - it
3147 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3148 * height) of the AlignFrame component
3150 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3151 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3152 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3153 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3154 myBottomFrame.viewport
3155 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3156 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3157 myTopFrame.viewport.setGatherViewsHere(true);
3158 myBottomFrame.viewport.setGatherViewsHere(true);
3159 String topViewId = myTopFrame.viewport.getSequenceSetId();
3160 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3162 JInternalFrame[] frames = desktop.getAllFrames();
3163 for (JInternalFrame frame : frames)
3165 if (frame instanceof SplitFrame && frame != source)
3167 SplitFrame sf = (SplitFrame) frame;
3168 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3169 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3170 boolean gatherThis = false;
3171 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3173 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3174 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3175 if (topViewId.equals(topPanel.av.getSequenceSetId())
3176 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3179 topPanel.av.setGatherViewsHere(false);
3180 bottomPanel.av.setGatherViewsHere(false);
3181 topPanel.av.setExplodedGeometry(
3182 new Rectangle(sf.getLocation(), topFrame.getSize()));
3183 bottomPanel.av.setExplodedGeometry(
3184 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3185 myTopFrame.addAlignmentPanel(topPanel, false);
3186 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3192 topFrame.getAlignPanels().clear();
3193 bottomFrame.getAlignPanels().clear();
3200 * The dust settles...give focus to the tab we did this from.
3202 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3205 public static groovy.ui.Console getGroovyConsole()
3207 return groovyConsole;
3211 * handles the payload of a drag and drop event.
3213 * TODO refactor to desktop utilities class
3216 * - Data source strings extracted from the drop event
3218 * - protocol for each data source extracted from the drop event
3222 * - the payload from the drop event
3225 public static void transferFromDropTarget(List<Object> files,
3226 List<DataSourceType> protocols, DropTargetDropEvent evt,
3227 Transferable t) throws Exception
3230 DataFlavor uriListFlavor = new DataFlavor(
3231 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3234 urlFlavour = new DataFlavor(
3235 "application/x-java-url; class=java.net.URL");
3236 } catch (ClassNotFoundException cfe)
3238 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.",
3242 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3247 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3248 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3249 // means url may be null.
3252 protocols.add(DataSourceType.URL);
3253 files.add(url.toString());
3254 jalview.bin.Console.debug("Drop handled as URL dataflavor "
3255 + files.get(files.size() - 1));
3260 if (Platform.isAMacAndNotJS())
3263 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3266 } catch (Throwable ex)
3268 jalview.bin.Console.debug("URL drop handler failed.", ex);
3271 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3273 // Works on Windows and MacOSX
3274 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
3275 for (Object file : (List) t
3276 .getTransferData(DataFlavor.javaFileListFlavor))
3279 protocols.add(DataSourceType.FILE);
3284 // Unix like behaviour
3285 boolean added = false;
3287 if (t.isDataFlavorSupported(uriListFlavor))
3289 jalview.bin.Console.debug("Drop handled as uriListFlavor");
3290 // This is used by Unix drag system
3291 data = (String) t.getTransferData(uriListFlavor);
3295 // fallback to text: workaround - on OSX where there's a JVM bug
3297 .debug("standard URIListFlavor failed. Trying text");
3298 // try text fallback
3299 DataFlavor textDf = new DataFlavor(
3300 "text/plain;class=java.lang.String");
3301 if (t.isDataFlavorSupported(textDf))
3303 data = (String) t.getTransferData(textDf);
3306 jalview.bin.Console.debug("Plain text drop content returned "
3307 + (data == null ? "Null - failed" : data));
3312 while (protocols.size() < files.size())
3314 jalview.bin.Console.debug("Adding missing FILE protocol for "
3315 + files.get(protocols.size()));
3316 protocols.add(DataSourceType.FILE);
3318 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3319 data, "\r\n"); st.hasMoreTokens();)
3322 String s = st.nextToken();
3323 if (s.startsWith("#"))
3325 // the line is a comment (as per the RFC 2483)
3328 java.net.URI uri = new java.net.URI(s);
3329 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http"))
3331 protocols.add(DataSourceType.URL);
3332 files.add(uri.toString());
3336 // otherwise preserve old behaviour: catch all for file objects
3337 java.io.File file = new java.io.File(uri);
3338 protocols.add(DataSourceType.FILE);
3339 files.add(file.toString());
3344 if (jalview.bin.Console.isDebugEnabled())
3346 if (data == null || !added)
3349 if (t.getTransferDataFlavors() != null
3350 && t.getTransferDataFlavors().length > 0)
3352 jalview.bin.Console.debug(
3353 "Couldn't resolve drop data. Here are the supported flavors:");
3354 for (DataFlavor fl : t.getTransferDataFlavors())
3356 jalview.bin.Console.debug(
3357 "Supported transfer dataflavor: " + fl.toString());
3358 Object df = t.getTransferData(fl);
3361 jalview.bin.Console.debug("Retrieves: " + df);
3365 jalview.bin.Console.debug("Retrieved nothing");
3372 .debug("Couldn't resolve dataflavor for drop: "
3378 if (Platform.isWindowsAndNotJS())
3381 .debug("Scanning dropped content for Windows Link Files");
3383 // resolve any .lnk files in the file drop
3384 for (int f = 0; f < files.size(); f++)
3386 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
3387 if (protocols.get(f).equals(DataSourceType.FILE)
3388 && (source.endsWith(".lnk") || source.endsWith(".url")
3389 || source.endsWith(".site")))
3393 Object obj = files.get(f);
3394 File lf = (obj instanceof File ? (File) obj
3395 : new File((String) obj));
3396 // process link file to get a URL
3397 jalview.bin.Console.debug("Found potential link file: " + lf);
3398 WindowsShortcut wscfile = new WindowsShortcut(lf);
3399 String fullname = wscfile.getRealFilename();
3400 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3401 files.set(f, fullname);
3402 jalview.bin.Console.debug("Parsed real filename " + fullname
3403 + " to extract protocol: " + protocols.get(f));
3404 } catch (Exception ex)
3406 jalview.bin.Console.error(
3407 "Couldn't parse " + files.get(f) + " as a link file.",
3416 * Sets the Preferences property for experimental features to True or False
3417 * depending on the state of the controlling menu item
3420 protected void showExperimental_actionPerformed(boolean selected)
3422 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3426 * Answers a (possibly empty) list of any structure viewer frames (currently
3427 * for either Jmol or Chimera) which are currently open. This may optionally
3428 * be restricted to viewers of a specified class, or viewers linked to a
3429 * specified alignment panel.
3432 * if not null, only return viewers linked to this panel
3433 * @param structureViewerClass
3434 * if not null, only return viewers of this class
3437 public List<StructureViewerBase> getStructureViewers(
3438 AlignmentPanel apanel,
3439 Class<? extends StructureViewerBase> structureViewerClass)
3441 List<StructureViewerBase> result = new ArrayList<>();
3442 JInternalFrame[] frames = Desktop.instance.getAllFrames();
3444 for (JInternalFrame frame : frames)
3446 if (frame instanceof StructureViewerBase)
3448 if (structureViewerClass == null
3449 || structureViewerClass.isInstance(frame))
3452 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3454 result.add((StructureViewerBase) frame);
3462 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3464 private static boolean debugScaleMessageDone = false;
3466 public static void debugScaleMessage(Graphics g)
3468 if (debugScaleMessageDone)
3472 // output used by tests to check HiDPI scaling settings in action
3475 Graphics2D gg = (Graphics2D) g;
3478 AffineTransform t = gg.getTransform();
3479 double scaleX = t.getScaleX();
3480 double scaleY = t.getScaleY();
3481 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
3482 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
3483 debugScaleMessageDone = true;
3487 jalview.bin.Console.debug("Desktop graphics null");
3489 } catch (Exception e)
3491 jalview.bin.Console.debug(Cache.getStackTraceString(e));