2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.util.Locale;
25 import java.awt.BorderLayout;
26 import java.awt.Color;
27 import java.awt.Dimension;
28 import java.awt.FontMetrics;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.GridLayout;
32 import java.awt.Point;
33 import java.awt.Rectangle;
34 import java.awt.Toolkit;
35 import java.awt.Window;
36 import java.awt.datatransfer.Clipboard;
37 import java.awt.datatransfer.ClipboardOwner;
38 import java.awt.datatransfer.DataFlavor;
39 import java.awt.datatransfer.Transferable;
40 import java.awt.dnd.DnDConstants;
41 import java.awt.dnd.DropTargetDragEvent;
42 import java.awt.dnd.DropTargetDropEvent;
43 import java.awt.dnd.DropTargetEvent;
44 import java.awt.dnd.DropTargetListener;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ActionListener;
47 import java.awt.event.InputEvent;
48 import java.awt.event.KeyEvent;
49 import java.awt.event.MouseAdapter;
50 import java.awt.event.MouseEvent;
51 import java.awt.event.WindowAdapter;
52 import java.awt.event.WindowEvent;
53 import java.awt.geom.AffineTransform;
54 import java.beans.PropertyChangeEvent;
55 import java.beans.PropertyChangeListener;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Vector;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Semaphore;
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.Box;
76 import javax.swing.BoxLayout;
77 import javax.swing.DefaultDesktopManager;
78 import javax.swing.DesktopManager;
79 import javax.swing.InputMap;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JComboBox;
83 import javax.swing.JComponent;
84 import javax.swing.JDesktopPane;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.event.HyperlinkEvent;
95 import javax.swing.event.HyperlinkEvent.EventType;
96 import javax.swing.event.InternalFrameAdapter;
97 import javax.swing.event.InternalFrameEvent;
99 import org.stackoverflowusers.file.WindowsShortcut;
101 import jalview.api.AlignViewportI;
102 import jalview.api.AlignmentViewPanel;
103 import jalview.bin.Cache;
104 import jalview.bin.Jalview;
105 import jalview.gui.ImageExporter.ImageWriterI;
106 import jalview.io.BackupFiles;
107 import jalview.io.DataSourceType;
108 import jalview.io.FileFormat;
109 import jalview.io.FileFormatException;
110 import jalview.io.FileFormatI;
111 import jalview.io.FileFormats;
112 import jalview.io.FileLoader;
113 import jalview.io.FormatAdapter;
114 import jalview.io.IdentifyFile;
115 import jalview.io.JalviewFileChooser;
116 import jalview.io.JalviewFileView;
117 import jalview.jbgui.GSplitFrame;
118 import jalview.jbgui.GStructureViewer;
119 import jalview.project.Jalview2XML;
120 import jalview.structure.StructureSelectionManager;
121 import jalview.urls.IdOrgSettings;
122 import jalview.util.BrowserLauncher;
123 import jalview.util.ChannelProperties;
124 import jalview.util.ImageMaker.TYPE;
125 import jalview.util.MessageManager;
126 import jalview.util.Platform;
127 import jalview.util.ShortcutKeyMaskExWrapper;
128 import jalview.util.UrlConstants;
129 import jalview.viewmodel.AlignmentViewport;
130 import jalview.ws.params.ParamManager;
131 import jalview.ws.utils.UrlDownloadClient;
138 * @version $Revision: 1.155 $
140 public class Desktop extends jalview.jbgui.GDesktop
141 implements DropTargetListener, ClipboardOwner, IProgressIndicator, jalview.api.StructureSelectionManagerProvider {
142 private static final String CITATION;
145 URL bg_logo_url = ChannelProperties.getImageURL(
146 "bg_logo." + String.valueOf(SplashScreen.logoSize));
147 URL uod_logo_url = ChannelProperties.getImageURL(
148 "uod_banner." + String.valueOf(SplashScreen.logoSize));
149 boolean logo = (bg_logo_url != null || uod_logo_url != null);
150 StringBuilder sb = new StringBuilder();
152 "<br><br>Jalview is free software released under GPLv3.<br><br>Development is managed by The Barton Group, University of Dundee, Scotland, UK.");
157 sb.append(bg_logo_url == null ? ""
158 : "<img alt=\"Barton Group logo\" src=\""
159 + bg_logo_url.toString() + "\">");
160 sb.append(uod_logo_url == null ? ""
161 : " <img alt=\"University of Dundee shield\" src=\""
162 + uod_logo_url.toString() + "\">");
164 "<br><br>For help, see <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and join <a href=\"https://discourse.jalview.org\">discourse.jalview.org</a>");
165 sb.append("<br><br>If you use Jalview, please cite:"
166 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
167 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
168 + "<br>Bioinformatics <a href=\"https://doi.org/10.1093/bioinformatics/btp033\">doi: 10.1093/bioinformatics/btp033</a>");
169 CITATION = sb.toString();
172 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
174 private static int DEFAULT_MIN_WIDTH = 300;
176 private static int DEFAULT_MIN_HEIGHT = 250;
178 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
180 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
182 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
184 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
186 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
188 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
190 public static boolean nosplash = false;
193 * news reader - null if it was never started.
195 private BlogReader jvnews = null;
197 private File projectFile;
201 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
203 public void addJalviewPropertyChangeListener(PropertyChangeListener listener) {
204 changeSupport.addJalviewPropertyChangeListener(listener);
208 * @param propertyName
210 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
211 * java.beans.PropertyChangeListener)
213 public void addJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
214 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
218 * @param propertyName
220 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
221 * java.beans.PropertyChangeListener)
223 public void removeJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
224 changeSupport.removeJalviewPropertyChangeListener(propertyName, listener);
227 /** Singleton Desktop instance */
228 public static Desktop instance;
230 public static MyDesktopPane desktop;
232 public static MyDesktopPane getDesktop() {
233 // BH 2018 could use currentThread() here as a reference to a
234 // Hashtable<Thread, MyDesktopPane> in JavaScript
238 static int openFrameCount = 0;
240 static final int xOffset = 30;
242 static final int yOffset = 30;
244 public static jalview.ws.jws1.Discoverer discoverer;
246 public static Object[] jalviewClipboard;
248 public static boolean internalCopy = false;
250 static int fileLoadingCount = 0;
252 class MyDesktopManager implements DesktopManager {
254 private DesktopManager delegate;
256 public MyDesktopManager(DesktopManager delegate) {
257 this.delegate = delegate;
261 public void activateFrame(JInternalFrame f) {
263 delegate.activateFrame(f);
264 } catch (NullPointerException npe) {
265 Point p = getMousePosition();
266 instance.showPasteMenu(p.x, p.y);
271 public void beginDraggingFrame(JComponent f) {
272 delegate.beginDraggingFrame(f);
276 public void beginResizingFrame(JComponent f, int direction) {
277 delegate.beginResizingFrame(f, direction);
281 public void closeFrame(JInternalFrame f) {
282 delegate.closeFrame(f);
286 public void deactivateFrame(JInternalFrame f) {
287 delegate.deactivateFrame(f);
291 public void deiconifyFrame(JInternalFrame f) {
292 delegate.deiconifyFrame(f);
296 public void dragFrame(JComponent f, int newX, int newY) {
300 delegate.dragFrame(f, newX, newY);
304 public void endDraggingFrame(JComponent f) {
305 delegate.endDraggingFrame(f);
310 public void endResizingFrame(JComponent f) {
311 delegate.endResizingFrame(f);
316 public void iconifyFrame(JInternalFrame f) {
317 delegate.iconifyFrame(f);
321 public void maximizeFrame(JInternalFrame f) {
322 delegate.maximizeFrame(f);
326 public void minimizeFrame(JInternalFrame f) {
327 delegate.minimizeFrame(f);
331 public void openFrame(JInternalFrame f) {
332 delegate.openFrame(f);
336 public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
340 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
344 public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
345 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
348 // All other methods, simply delegate
353 * Creates a new Desktop object.
358 * A note to implementors. It is ESSENTIAL that any activities that might block
359 * are spawned off as threads rather than waited for during this constructor.
363 doConfigureStructurePrefs();
364 setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
367 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
368 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
369 * documented or guaranteed to exist, so we access it via reflection. There
370 * appear to be unfathomable criteria about what this string can contain, and it
371 * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
372 * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
373 * but "Jalview non-release" does not. The reflection access may generate a
374 * warning: WARNING: An illegal reflective access operation has occurred
375 * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
376 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
378 if (Platform.isLinux()) {
380 Toolkit xToolkit = Toolkit.getDefaultToolkit();
381 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
382 Field awtAppClassNameField = null;
384 if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
385 awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
388 String title = ChannelProperties.getProperty("app_name");
389 if (awtAppClassNameField != null) {
390 awtAppClassNameField.setAccessible(true);
391 awtAppClassNameField.set(xToolkit, title);
393 jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
395 } catch (Exception e) {
396 jalview.bin.Console.debug("Error setting awtAppClassName");
397 jalview.bin.Console.trace(Cache.getStackTraceString(e));
402 * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
403 * macOS's application menu. APQHandlers will check to see if a handler is
404 * supported before setting it.
407 APQHandlers.setAPQHandlers(this);
408 } catch (Exception e) {
409 System.out.println("Cannot set APQHandlers");
410 // e.printStackTrace();
411 } catch (Throwable t) {
412 jalview.bin.Console.warn("Error setting APQHandlers: " + t.toString());
413 jalview.bin.Console.trace(Cache.getStackTraceString(t));
415 setIconImages(ChannelProperties.getIconList());
417 addWindowListener(new WindowAdapter() {
420 public void windowClosing(WindowEvent ev) {
425 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
427 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
428 desktop = new MyDesktopPane(selmemusage);
430 showMemusage.setSelected(selmemusage);
431 desktop.setBackground(Color.white);
433 getContentPane().setLayout(new BorderLayout());
434 // alternate config - have scrollbars - see notes in JAL-153
435 // JScrollPane sp = new JScrollPane();
436 // sp.getViewport().setView(desktop);
437 // getContentPane().add(sp, BorderLayout.CENTER);
439 // BH 2018 - just an experiment to try unclipped JInternalFrames.
440 if (Platform.isJS()) {
441 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
444 getContentPane().add(desktop, BorderLayout.CENTER);
445 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
447 // This line prevents Windows Look&Feel resizing all new windows to maximum
448 // if previous window was maximised
449 desktop.setDesktopManager(new MyDesktopManager((Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
450 : Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(desktop.getDesktopManager())
451 : desktop.getDesktopManager())));
453 Rectangle dims = getLastKnownDimensions("");
457 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
458 int xPos = Math.max(5, (screenSize.width - 900) / 2);
459 int yPos = Math.max(5, (screenSize.height - 650) / 2);
460 setBounds(xPos, yPos, 900, 650);
463 if (!Platform.isJS())
470 jconsole = new Console(this, showjconsole);
471 jconsole.setHeader(Cache.getVersionDetailsForConsole());
472 showConsole(showjconsole);
474 showNews.setVisible(false);
476 experimentalFeatures.setSelected(showExperimental());
478 getIdentifiersOrgData();
482 // Spawn a thread that shows the splashscreen
484 SwingUtilities.invokeLater(new Runnable() {
487 new SplashScreen(true);
492 // Thread off a new instance of the file chooser - this reduces the time
494 // takes to open it later on.
495 new Thread(new Runnable() {
498 jalview.bin.Console.debug("Filechooser init thread started.");
499 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
500 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat);
501 jalview.bin.Console.debug("Filechooser init thread finished.");
504 // Add the service change listener
505 changeSupport.addJalviewPropertyChangeListener("services", new PropertyChangeListener() {
508 public void propertyChange(PropertyChangeEvent evt) {
509 jalview.bin.Console.debug("Firing service changed event for " + evt.getNewValue());
510 JalviewServicesChanged(evt);
515 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
517 this.addWindowListener(new WindowAdapter() {
519 public void windowClosing(WindowEvent evt) {
525 this.addMouseListener(ma = new MouseAdapter() {
527 public void mousePressed(MouseEvent evt) {
528 if (evt.isPopupTrigger()) // Mac
530 showPasteMenu(evt.getX(), evt.getY());
535 public void mouseReleased(MouseEvent evt) {
536 if (evt.isPopupTrigger()) // Windows
538 showPasteMenu(evt.getX(), evt.getY());
542 desktop.addMouseListener(ma);
546 * Answers true if user preferences to enable experimental features is True
551 public boolean showExperimental() {
552 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES, Boolean.FALSE.toString());
553 return Boolean.valueOf(experimental).booleanValue();
556 public void doConfigureStructurePrefs() {
557 // configure services
558 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
559 if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) {
560 ssm.setAddTempFacAnnot(Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
561 ssm.setProcessSecondaryStructure(Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
562 // JAL-3915 - RNAView is no longer an option so this has no effect
563 ssm.setSecStructServices(Cache.getDefault(Preferences.USE_RNAVIEW, false));
565 ssm.setAddTempFacAnnot(false);
566 ssm.setProcessSecondaryStructure(false);
567 ssm.setSecStructServices(false);
571 public void checkForNews() {
572 final Desktop me = this;
573 // Thread off the news reader, in case there are connection problems.
574 new Thread(new Runnable() {
577 jalview.bin.Console.debug("Starting news thread.");
578 jvnews = new BlogReader(me);
579 showNews.setVisible(true);
580 jalview.bin.Console.debug("Completed news thread.");
585 public void getIdentifiersOrgData()
587 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null)
588 {// Thread off the identifiers fetcher
589 new Thread(new Runnable()
594 jalview.bin.Console.debug("Downloading data from identifiers.org");
597 UrlDownloadClient.download(IdOrgSettings.getUrl(),
598 IdOrgSettings.getDownloadLocation());
599 } catch (IOException e)
601 jalview.bin.Console.debug("Exception downloading identifiers.org data"
611 protected void showNews_actionPerformed(ActionEvent e) {
612 showNews(showNews.isSelected());
615 void showNews(boolean visible) {
616 jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
617 showNews.setSelected(visible);
618 if (visible && !jvnews.isVisible()) {
619 new Thread(new Runnable() {
622 long now = System.currentTimeMillis();
623 Desktop.instance.setProgressBar(MessageManager.getString("status.refreshing_news"), now);
624 jvnews.refreshNews();
625 Desktop.instance.setProgressBar(null, now);
633 * recover the last known dimensions for a jalview window
635 * @param windowName - empty string is desktop, all other windows have unique
637 * @return null or last known dimensions scaled to current geometry (if last
638 * window geom was known)
640 Rectangle getLastKnownDimensions(String windowName) {
641 // TODO: lock aspect ratio for scaling desktop Bug #0058199
642 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
643 String x = Cache.getProperty(windowName + "SCREEN_X");
644 String y = Cache.getProperty(windowName + "SCREEN_Y");
645 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
646 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
647 if ((x != null) && (y != null) && (width != null) && (height != null)) {
648 int ix = Integer.parseInt(x), iy = Integer.parseInt(y), iw = Integer.parseInt(width),
649 ih = Integer.parseInt(height);
650 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) {
651 // attempt #1 - try to cope with change in screen geometry - this
652 // version doesn't preserve original jv aspect ratio.
653 // take ratio of current screen size vs original screen size.
654 double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
655 double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
656 // rescale the bounds depending upon the current screen geometry.
657 ix = (int) (ix * sw);
658 iw = (int) (iw * sw);
659 iy = (int) (iy * sh);
660 ih = (int) (ih * sh);
661 while (ix >= screenSize.width) {
662 jalview.bin.Console.debug("Window geometry location recall error: shifting horizontal to within screenbounds.");
663 ix -= screenSize.width;
665 while (iy >= screenSize.height) {
666 jalview.bin.Console.debug("Window geometry location recall error: shifting vertical to within screenbounds.");
667 iy -= screenSize.height;
669 jalview.bin.Console.debug("Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw
672 // return dimensions for new instance
673 return new Rectangle(ix, iy, iw, ih);
678 void showPasteMenu(int x, int y) {
679 JPopupMenu popup = new JPopupMenu();
680 JMenuItem item = new JMenuItem(MessageManager.getString("label.paste_new_window"));
681 item.addActionListener(new ActionListener() {
683 public void actionPerformed(ActionEvent evt) {
689 popup.show(this, x, y);
692 public void paste() {
694 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
695 Transferable contents = c.getContents(this);
697 if (contents != null) {
698 String file = (String) contents.getTransferData(DataFlavor.stringFlavor);
700 FileFormatI format = new IdentifyFile().identify(file, DataSourceType.PASTE);
702 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
705 } catch (Exception ex) {
706 System.out.println("Unable to paste alignment from system clipboard:\n" + ex);
711 * Adds and opens the given frame to the desktop
713 * @param frame Frame to show
714 * @param title Visible Title
718 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h) {
719 addInternalFrame(frame, title, true, w, h, true, false);
723 * Add an internal frame to the Jalview desktop
725 * @param frame Frame to show
726 * @param title Visible Title
727 * @param makeVisible When true, display frame immediately, otherwise, caller
728 * must call setVisible themselves.
732 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
734 addInternalFrame(frame, title, makeVisible, w, h, true, false);
738 * Add an internal frame to the Jalview desktop and make it visible
740 * @param frame Frame to show
741 * @param title Visible Title
744 * @param resizable Allow resize
746 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h,
748 addInternalFrame(frame, title, true, w, h, resizable, false);
752 * Add an internal frame to the Jalview desktop
754 * @param frame Frame to show
755 * @param title Visible Title
756 * @param makeVisible When true, display frame immediately, otherwise, caller
757 * must call setVisible themselves.
760 * @param resizable Allow resize
761 * @param ignoreMinSize Do not set the default minimum size for frame
763 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
764 int h, boolean resizable, boolean ignoreMinSize) {
766 // TODO: allow callers to determine X and Y position of frame (eg. via
768 // TODO: consider fixing method to update entries in the window submenu with
769 // the current window title
771 frame.setTitle(title);
772 if (frame.getWidth() < 1 || frame.getHeight() < 1) {
775 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
776 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
777 // IF JALVIEW IS RUNNING HEADLESS
778 // ///////////////////////////////////////////////
779 if (instance == null || (System.getProperty("java.awt.headless") != null
780 && System.getProperty("java.awt.headless").equals("true"))) {
786 if (!ignoreMinSize) {
787 frame.setMinimumSize(new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
789 // Set default dimension for Alignment Frame window.
790 // The Alignment Frame window could be added from a number of places,
792 // I did this here in order not to miss out on any Alignment frame.
793 if (frame instanceof AlignFrame) {
794 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH, ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
798 frame.setVisible(makeVisible);
799 frame.setClosable(true);
800 frame.setResizable(resizable);
801 frame.setMaximizable(resizable);
802 frame.setIconifiable(resizable);
803 frame.setOpaque(Platform.isJS());
805 if (frame.getX() < 1 && frame.getY() < 1) {
806 frame.setLocation(xOffset * openFrameCount, yOffset * ((openFrameCount - 1) % 10) + yOffset);
810 * add an entry for the new frame in the Window menu (and remove it when the
813 final JMenuItem menuItem = new JMenuItem(title);
814 frame.addInternalFrameListener(new InternalFrameAdapter() {
816 public void internalFrameActivated(InternalFrameEvent evt) {
817 JInternalFrame itf = desktop.getSelectedFrame();
819 if (itf instanceof AlignFrame) {
820 Jalview.setCurrentAlignFrame((AlignFrame) itf);
827 public void internalFrameClosed(InternalFrameEvent evt) {
828 PaintRefresher.RemoveComponent(frame);
831 * defensive check to prevent frames being added half off the window
833 if (openFrameCount > 0) {
838 * ensure no reference to alignFrame retained by menu item listener
840 if (menuItem.getActionListeners().length > 0) {
841 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
843 windowMenu.remove(menuItem);
847 menuItem.addActionListener(new ActionListener() {
849 public void actionPerformed(ActionEvent e) {
851 frame.setSelected(true);
852 frame.setIcon(false);
853 } catch (java.beans.PropertyVetoException ex) {
859 setKeyBindings(frame);
863 windowMenu.add(menuItem);
867 frame.setSelected(true);
868 frame.requestFocus();
869 } catch (java.beans.PropertyVetoException ve) {
870 } catch (java.lang.ClassCastException cex) {
871 jalview.bin.Console.warn(
872 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
878 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
883 private static void setKeyBindings(JInternalFrame frame) {
884 @SuppressWarnings("serial")
885 final Action closeAction = new AbstractAction() {
887 public void actionPerformed(ActionEvent e) {
893 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
895 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
896 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
898 InputMap inputMap = frame.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
899 String ctrlW = ctrlWKey.toString();
900 inputMap.put(ctrlWKey, ctrlW);
901 inputMap.put(cmdWKey, ctrlW);
903 ActionMap actionMap = frame.getActionMap();
904 actionMap.put(ctrlW, closeAction);
908 public void lostOwnership(Clipboard clipboard, Transferable contents) {
910 Desktop.jalviewClipboard = null;
913 internalCopy = false;
917 public void dragEnter(DropTargetDragEvent evt) {
921 public void dragExit(DropTargetEvent evt) {
925 public void dragOver(DropTargetDragEvent evt) {
929 public void dropActionChanged(DropTargetDragEvent evt) {
935 * @param evt DOCUMENT ME!
938 public void drop(DropTargetDropEvent evt) {
939 boolean success = true;
940 // JAL-1552 - acceptDrop required before getTransferable call for
941 // Java's Transferable for native dnd
942 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
943 Transferable t = evt.getTransferable();
944 List<Object> files = new ArrayList<>();
945 List<DataSourceType> protocols = new ArrayList<>();
948 Desktop.transferFromDropTarget(files, protocols, evt, t);
949 } catch (Exception e) {
956 for (int i = 0; i < files.size(); i++) {
957 // BH 2018 File or String
958 Object file = files.get(i);
959 String fileName = file.toString();
960 DataSourceType protocol = (protocols == null) ? DataSourceType.FILE : protocols.get(i);
961 FileFormatI format = null;
963 if (fileName.endsWith(".jar")) {
964 format = FileFormat.Jalview;
967 format = new IdentifyFile().identify(file, protocol);
969 if (file instanceof File) {
970 Platform.cacheFileData((File) file);
972 new FileLoader().LoadFile(null, file, protocol, format);
975 } catch (Exception ex) {
979 evt.dropComplete(success); // need this to ensure input focus is properly
980 // transfered to any new windows created
986 * @param e DOCUMENT ME!
989 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) {
990 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
991 JalviewFileChooser chooser = JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat,
992 BackupFiles.getEnabled());
994 chooser.setFileView(new JalviewFileView());
995 chooser.setDialogTitle(MessageManager.getString("label.open_local_file"));
996 chooser.setToolTipText(MessageManager.getString("action.open"));
998 chooser.setResponseHandler(0, new Runnable() {
1001 File selectedFile = chooser.getSelectedFile();
1002 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1004 FileFormatI format = chooser.getSelectedFormat();
1007 * Call IdentifyFile to verify the file contains what its extension implies.
1008 * Skip this step for dynamically added file formats, because IdentifyFile does
1009 * not know how to recognise them.
1011 if (FileFormats.getInstance().isIdentifiable(format)) {
1013 format = new IdentifyFile().identify(selectedFile, DataSourceType.FILE);
1014 } catch (FileFormatException e) {
1015 // format = null; //??
1019 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, format);
1022 chooser.showOpenDialog(this);
1026 * Shows a dialog for input of a URL at which to retrieve alignment data
1031 public void inputURLMenuItem_actionPerformed(AlignViewport viewport) {
1032 // This construct allows us to have a wider textfield
1034 JLabel label = new JLabel(MessageManager.getString("label.input_file_url"));
1036 JPanel panel = new JPanel(new GridLayout(2, 1));
1040 * the URL to fetch is input in Java: an editable combobox with history JS:
1041 * (pending JAL-3038) a plain text field
1044 String urlBase = "https://www.";
1045 if (Platform.isJS()) {
1046 history = new JTextField(urlBase, 35);
1054 JComboBox<String> asCombo = new JComboBox<>();
1055 asCombo.setPreferredSize(new Dimension(400, 20));
1056 asCombo.setEditable(true);
1057 asCombo.addItem(urlBase);
1058 String historyItems = Cache.getProperty("RECENT_URL");
1059 if (historyItems != null) {
1060 for (String token : historyItems.split("\\t")) {
1061 asCombo.addItem(token);
1068 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1069 MessageManager.getString("action.cancel") };
1070 Runnable action = new Runnable() {
1073 @SuppressWarnings("unchecked")
1074 String url = (history instanceof JTextField ? ((JTextField) history).getText()
1075 : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
1077 if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) {
1078 if (viewport != null) {
1079 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
1081 new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
1084 FileFormatI format = null;
1086 format = new IdentifyFile().identify(url, DataSourceType.URL);
1087 } catch (FileFormatException e) {
1088 // TODO revise error handling, distinguish between
1089 // URL not found and response not valid
1092 if (format == null) {
1093 String msg = MessageManager.formatMessage("label.couldnt_locate", url);
1094 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1095 MessageManager.getString("label.url_not_found"), JvOptionPane.WARNING_MESSAGE);
1100 if (viewport != null) {
1101 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, format);
1103 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1108 String dialogOption = MessageManager.getString("label.input_alignment_from_url");
1109 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action).showInternalDialog(panel, dialogOption,
1110 JvOptionPane.YES_NO_CANCEL_OPTION, JvOptionPane.PLAIN_MESSAGE, null, options,
1111 MessageManager.getString("action.ok"));
1115 * Opens the CutAndPaste window for the user to paste an alignment in to
1117 * @param viewPanel - if not null, the pasted alignment is added to the current
1118 * alignment; if null, to a new alignment window
1121 public void inputTextboxMenuItem_actionPerformed(AlignmentViewPanel viewPanel) {
1122 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1123 cap.setForInput(viewPanel);
1124 Desktop.addInternalFrame(cap, MessageManager.getString("label.cut_paste_alignmen_file"), true, 600, 500);
1131 public void quit() {
1132 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1133 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1134 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1135 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, getWidth(), getHeight()));
1137 if (jconsole != null) {
1138 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1139 jconsole.stopConsole();
1141 if (jvnews != null) {
1142 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1145 if (dialogExecutor != null) {
1146 dialogExecutor.shutdownNow();
1148 closeAll_actionPerformed(null);
1150 if (groovyConsole != null) {
1151 // suppress a possible repeat prompt to save script
1152 groovyConsole.setDirty(false);
1153 groovyConsole.exit();
1158 private void storeLastKnownDimensions(String string, Rectangle jc) {
1159 jalview.bin.Console.debug("Storing last known dimensions for " + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1160 + " height:" + jc.height);
1162 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1163 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1164 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1165 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1171 * @param e DOCUMENT ME!
1174 public void aboutMenuItem_actionPerformed(ActionEvent e) {
1175 new Thread(new Runnable() {
1178 new SplashScreen(false);
1184 * Returns the html text for the About screen, including any available version
1185 * number, build details, author details and citation reference, but without the
1186 * enclosing {@code html} tags
1190 public String getAboutMessage() {
1191 StringBuilder message = new StringBuilder(1024);
1192 message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
1193 .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
1194 .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
1195 .append(Cache.getBuildDetailsForSplash()).append("</strong>");
1197 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1198 if (latestVersion.equals("Checking")) {
1199 // JBP removed this message for 2.11: May be reinstated in future version
1200 // message.append("<br>...Checking latest version...</br>");
1201 } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
1202 boolean red = false;
1203 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT).indexOf("automated build") == -1) {
1205 // Displayed when code version and jnlp version do not match and code
1206 // version is not a development build
1207 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1210 message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1211 .append(" is available for download from ")
1212 .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
1214 message.append("</div>");
1217 message.append("<br>Authors: ");
1218 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1219 message.append(CITATION);
1221 message.append("</div>");
1223 return message.toString();
1227 * Action on requesting Help documentation
1230 public void documentationMenuItem_actionPerformed() {
1232 if (Platform.isJS()) {
1233 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1241 Help.showHelpWindow();
1243 } catch (Exception ex) {
1244 System.err.println("Error opening help: " + ex.getMessage());
1249 public void closeAll_actionPerformed(ActionEvent e) {
1250 // TODO show a progress bar while closing?
1251 JInternalFrame[] frames = desktop.getAllFrames();
1252 for (int i = 0; i < frames.length; i++) {
1254 frames[i].setClosed(true);
1255 } catch (java.beans.PropertyVetoException ex) {
1258 Jalview.setCurrentAlignFrame(null);
1259 System.out.println("ALL CLOSED");
1262 * reset state of singleton objects as appropriate (clear down session state
1263 * when all windows are closed)
1265 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
1272 public void raiseRelated_actionPerformed(ActionEvent e) {
1273 reorderAssociatedWindows(false, false);
1277 public void minimizeAssociated_actionPerformed(ActionEvent e) {
1278 reorderAssociatedWindows(true, false);
1281 void closeAssociatedWindows() {
1282 reorderAssociatedWindows(false, true);
1288 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1292 protected void garbageCollect_actionPerformed(ActionEvent e) {
1293 // We simply collect the garbage
1294 jalview.bin.Console.debug("Collecting garbage...");
1296 jalview.bin.Console.debug("Finished garbage collection.");
1302 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1306 protected void showMemusage_actionPerformed(ActionEvent e) {
1307 desktop.showMemoryUsage(showMemusage.isSelected());
1314 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1318 protected void showConsole_actionPerformed(ActionEvent e) {
1319 showConsole(showConsole.isSelected());
1322 Console jconsole = null;
1325 * control whether the java console is visible or not
1329 void showConsole(boolean selected) {
1330 // TODO: decide if we should update properties file
1331 if (jconsole != null) // BH 2018
1333 showConsole.setSelected(selected);
1334 Cache.setProperty("SHOW_JAVA_CONSOLE", Boolean.valueOf(selected).toString());
1335 jconsole.setVisible(selected);
1339 void reorderAssociatedWindows(boolean minimize, boolean close) {
1340 JInternalFrame[] frames = desktop.getAllFrames();
1341 if (frames == null || frames.length < 1) {
1345 AlignmentViewport source = null, target = null;
1346 if (frames[0] instanceof AlignFrame) {
1347 source = ((AlignFrame) frames[0]).getCurrentView();
1348 } else if (frames[0] instanceof TreePanel) {
1349 source = ((TreePanel) frames[0]).getViewPort();
1350 } else if (frames[0] instanceof PCAPanel) {
1351 source = ((PCAPanel) frames[0]).av;
1352 } else if (frames[0].getContentPane() instanceof PairwiseAlignPanel) {
1353 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1356 if (source != null) {
1357 for (int i = 0; i < frames.length; i++) {
1359 if (frames[i] == null) {
1362 if (frames[i] instanceof AlignFrame) {
1363 target = ((AlignFrame) frames[i]).getCurrentView();
1364 } else if (frames[i] instanceof TreePanel) {
1365 target = ((TreePanel) frames[i]).getViewPort();
1366 } else if (frames[i] instanceof PCAPanel) {
1367 target = ((PCAPanel) frames[i]).av;
1368 } else if (frames[i].getContentPane() instanceof PairwiseAlignPanel) {
1369 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1372 if (source == target) {
1375 frames[i].setClosed(true);
1377 frames[i].setIcon(minimize);
1379 frames[i].toFront();
1383 } catch (java.beans.PropertyVetoException ex) {
1393 * @param e DOCUMENT ME!
1396 protected void preferences_actionPerformed(ActionEvent e) {
1397 Preferences.openPreferences();
1401 * Prompts the user to choose a file and then saves the Jalview state as a
1402 * Jalview project file
1405 public void saveState_actionPerformed() {
1406 saveState_actionPerformed(false);
1409 public void saveState_actionPerformed(boolean saveAs) {
1410 java.io.File projectFile = getProjectFile();
1411 // autoSave indicates we already have a file and don't need to ask
1412 boolean autoSave = projectFile != null && !saveAs && BackupFiles.getEnabled();
1414 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1415 // saveAs="+saveAs+", Backups
1416 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1418 boolean approveSave = false;
1420 JalviewFileChooser chooser = new JalviewFileChooser("jvp", "Jalview Project");
1422 chooser.setFileView(new JalviewFileView());
1423 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1425 int value = chooser.showSaveDialog(this);
1427 if (value == JalviewFileChooser.APPROVE_OPTION) {
1428 projectFile = chooser.getSelectedFile();
1429 setProjectFile(projectFile);
1434 if (approveSave || autoSave) {
1435 final Desktop me = this;
1436 final java.io.File chosenFile = projectFile;
1437 new Thread(new Runnable() {
1440 // TODO: refactor to Jalview desktop session controller action.
1442 MessageManager.formatMessage("label.saving_jalview_project", new Object[] { chosenFile.getName() }),
1443 chosenFile.hashCode());
1444 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1445 // TODO catch and handle errors for savestate
1446 // TODO prevent user from messing with the Desktop whilst we're saving
1448 boolean doBackup = BackupFiles.getEnabled();
1449 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
1451 new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
1454 backupfiles.setWriteSuccess(true);
1455 backupfiles.rollBackupsAndRenameTempFile();
1457 } catch (OutOfMemoryError oom) {
1458 new OOMWarning("Whilst saving current state to " + chosenFile.getName(), oom);
1459 } catch (Exception ex) {
1460 jalview.bin.Console.error("Problems whilst trying to save to " + chosenFile.getName(), ex);
1461 JvOptionPane.showMessageDialog(me,
1462 MessageManager.formatMessage("label.error_whilst_saving_current_state_to",
1463 new Object[] { chosenFile.getName() }),
1464 MessageManager.getString("label.couldnt_save_project"), JvOptionPane.WARNING_MESSAGE);
1466 setProgressBar(null, chosenFile.hashCode());
1473 public void saveAsState_actionPerformed(ActionEvent e) {
1474 saveState_actionPerformed(true);
1477 private void setProjectFile(File choice) {
1478 this.projectFile = choice;
1481 public File getProjectFile() {
1482 return this.projectFile;
1486 * Shows a file chooser dialog and tries to read in the selected file as a
1490 public void loadState_actionPerformed() {
1491 final String[] suffix = new String[] { "jvp", "jar" };
1492 final String[] desc = new String[] { "Jalview Project", "Jalview Project (old)" };
1493 JalviewFileChooser chooser = new JalviewFileChooser(Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1494 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1498 chooser.setFileView(new JalviewFileView());
1499 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1500 chooser.setResponseHandler(0, new Runnable() {
1503 File selectedFile = chooser.getSelectedFile();
1504 setProjectFile(selectedFile);
1505 String choice = selectedFile.getAbsolutePath();
1506 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1507 new Thread(new Runnable() {
1511 new Jalview2XML().loadJalviewAlign(selectedFile);
1512 } catch (OutOfMemoryError oom) {
1513 new OOMWarning("Whilst loading project from " + choice, oom);
1514 } catch (Exception ex) {
1515 jalview.bin.Console.error("Problems whilst loading project from " + choice, ex);
1516 JvOptionPane.showMessageDialog(Desktop.desktop,
1517 MessageManager.formatMessage("label.error_whilst_loading_project_from", new Object[] { choice }),
1518 MessageManager.getString("label.couldnt_load_project"), JvOptionPane.WARNING_MESSAGE);
1521 }, "Project Loader").start();
1525 chooser.showOpenDialog(this);
1529 public void inputSequence_actionPerformed(ActionEvent e) {
1530 new SequenceFetcher(this);
1533 JPanel progressPanel;
1535 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1537 public void startLoading(final Object fileName) {
1538 if (fileLoadingCount == 0) {
1540 .add(addProgressPanel(MessageManager.formatMessage("label.loading_file", new Object[] { fileName })));
1545 private JPanel addProgressPanel(String string) {
1546 if (progressPanel == null) {
1547 progressPanel = new JPanel(new GridLayout(1, 1));
1548 totalProgressCount = 0;
1549 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1551 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1552 JProgressBar progressBar = new JProgressBar();
1553 progressBar.setIndeterminate(true);
1555 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1557 thisprogress.add(progressBar, BorderLayout.CENTER);
1558 progressPanel.add(thisprogress);
1559 ((GridLayout) progressPanel.getLayout()).setRows(((GridLayout) progressPanel.getLayout()).getRows() + 1);
1560 ++totalProgressCount;
1561 instance.validate();
1562 return thisprogress;
1565 int totalProgressCount = 0;
1567 private void removeProgressPanel(JPanel progbar) {
1568 if (progressPanel != null) {
1569 synchronized (progressPanel) {
1570 progressPanel.remove(progbar);
1571 GridLayout gl = (GridLayout) progressPanel.getLayout();
1572 gl.setRows(gl.getRows() - 1);
1573 if (--totalProgressCount < 1) {
1574 this.getContentPane().remove(progressPanel);
1575 progressPanel = null;
1582 public void stopLoading() {
1584 if (fileLoadingCount < 1) {
1585 while (fileLoadingPanels.size() > 0) {
1586 removeProgressPanel(fileLoadingPanels.remove(0));
1588 fileLoadingPanels.clear();
1589 fileLoadingCount = 0;
1594 public static int getViewCount(String alignmentId) {
1595 AlignmentViewport[] aps = getViewports(alignmentId);
1596 return (aps == null) ? 0 : aps.length;
1601 * @param alignmentId - if null, all sets are returned
1602 * @return all AlignmentPanels concerning the alignmentId sequence set
1604 public static AlignmentPanel[] getAlignmentPanels(String alignmentId) {
1605 if (Desktop.desktop == null) {
1606 // no frames created and in headless mode
1607 // TODO: verify that frames are recoverable when in headless mode
1610 List<AlignmentPanel> aps = new ArrayList<>();
1611 AlignFrame[] frames = getAlignFrames();
1612 if (frames == null) {
1615 for (AlignFrame af : frames) {
1616 for (AlignmentPanel ap : af.alignPanels) {
1617 if (alignmentId == null || alignmentId.equals(ap.av.getSequenceSetId())) {
1622 if (aps.size() == 0) {
1625 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1630 * get all the viewports on an alignment.
1632 * @param sequenceSetId unique alignment id (may be null - all viewports
1633 * returned in that case)
1634 * @return all viewports on the alignment bound to sequenceSetId
1636 public static AlignmentViewport[] getViewports(String sequenceSetId) {
1637 List<AlignmentViewport> viewp = new ArrayList<>();
1638 if (desktop != null) {
1639 AlignFrame[] frames = Desktop.getAlignFrames();
1641 for (AlignFrame afr : frames) {
1642 if (sequenceSetId == null || afr.getViewport().getSequenceSetId().equals(sequenceSetId)) {
1643 if (afr.alignPanels != null) {
1644 for (AlignmentPanel ap : afr.alignPanels) {
1645 if (sequenceSetId == null || sequenceSetId.equals(ap.av.getSequenceSetId())) {
1650 viewp.add(afr.getViewport());
1654 if (viewp.size() > 0) {
1655 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1662 * Explode the views in the given frame into separate AlignFrame
1666 public static void explodeViews(AlignFrame af) {
1667 int size = af.alignPanels.size();
1672 // FIXME: ideally should use UI interface API
1673 FeatureSettings viewFeatureSettings = (af.featureSettings != null && af.featureSettings.isOpen())
1674 ? af.featureSettings
1676 Rectangle fsBounds = af.getFeatureSettingsGeometry();
1677 for (int i = 0; i < size; i++) {
1678 AlignmentPanel ap = af.alignPanels.get(i);
1680 AlignFrame newaf = new AlignFrame(ap);
1682 // transfer reference for existing feature settings to new alignFrame
1683 if (ap == af.alignPanel) {
1684 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap) {
1685 newaf.featureSettings = viewFeatureSettings;
1687 newaf.setFeatureSettingsGeometry(fsBounds);
1691 * Restore the view's last exploded frame geometry if known. Multiple views from
1692 * one exploded frame share and restore the same (frame) position and size.
1694 Rectangle geometry = ap.av.getExplodedGeometry();
1695 if (geometry != null) {
1696 newaf.setBounds(geometry);
1699 ap.av.setGatherViewsHere(false);
1701 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1702 // and materialise a new feature settings dialog instance for the new
1704 // (closes the old as if 'OK' was pressed)
1705 if (ap == af.alignPanel && newaf.featureSettings != null && newaf.featureSettings.isOpen()
1706 && af.alignPanel.getAlignViewport().isShowSequenceFeatures()) {
1707 newaf.showFeatureSettingsUI();
1711 af.featureSettings = null;
1712 af.alignPanels.clear();
1713 af.closeMenuItem_actionPerformed(true);
1718 * Gather expanded views (separate AlignFrame's) with the same sequence set
1719 * identifier back in to this frame as additional views, and close the expanded
1720 * views. Note the expanded frames may themselves have multiple views. We take
1725 public void gatherViews(AlignFrame source) {
1726 source.viewport.setGatherViewsHere(true);
1727 source.viewport.setExplodedGeometry(source.getBounds());
1728 JInternalFrame[] frames = desktop.getAllFrames();
1729 String viewId = source.viewport.getSequenceSetId();
1730 for (int t = 0; t < frames.length; t++) {
1731 if (frames[t] instanceof AlignFrame && frames[t] != source) {
1732 AlignFrame af = (AlignFrame) frames[t];
1733 boolean gatherThis = false;
1734 for (int a = 0; a < af.alignPanels.size(); a++) {
1735 AlignmentPanel ap = af.alignPanels.get(a);
1736 if (viewId.equals(ap.av.getSequenceSetId())) {
1738 ap.av.setGatherViewsHere(false);
1739 ap.av.setExplodedGeometry(af.getBounds());
1740 source.addAlignmentPanel(ap, false);
1745 if (af.featureSettings != null && af.featureSettings.isOpen()) {
1746 if (source.featureSettings == null) {
1747 // preserve the feature settings geometry for this frame
1748 source.featureSettings = af.featureSettings;
1749 source.setFeatureSettingsGeometry(af.getFeatureSettingsGeometry());
1751 // close it and forget
1752 af.featureSettings.close();
1755 af.alignPanels.clear();
1756 af.closeMenuItem_actionPerformed(true);
1761 // refresh the feature setting UI for the source frame if it exists
1762 if (source.featureSettings != null && source.featureSettings.isOpen()) {
1763 source.showFeatureSettingsUI();
1768 public JInternalFrame[] getAllFrames() {
1769 return desktop.getAllFrames();
1773 * Checks the given url to see if it gives a response indicating that the user
1774 * should be informed of a new questionnaire.
1778 public void checkForQuestionnaire(String url) {
1779 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
1780 // javax.swing.SwingUtilities.invokeLater(jvq);
1781 new Thread(jvq).start();
1784 public void checkURLLinks() {
1785 // Thread off the URL link checker
1786 addDialogThread(new Runnable() {
1789 if (Cache.getDefault("CHECKURLLINKS", true)) {
1790 // check what the actual links are - if it's just the default don't
1791 // bother with the warning
1792 List<String> links = Preferences.sequenceUrlLinks.getLinksForMenu();
1794 // only need to check links if there is one with a
1795 // SEQUENCE_ID which is not the default EMBL_EBI link
1796 ListIterator<String> li = links.listIterator();
1797 boolean check = false;
1798 List<JLabel> urls = new ArrayList<>();
1799 while (li.hasNext()) {
1800 String link = li.next();
1801 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID) && !UrlConstants.isDefaultString(link)) {
1803 int barPos = link.indexOf("|");
1804 String urlMsg = barPos == -1 ? link : link.substring(0, barPos) + ": " + link.substring(barPos + 1);
1805 urls.add(new JLabel(urlMsg));
1812 // ask user to check in case URL links use old style tokens
1813 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
1814 JPanel msgPanel = new JPanel();
1815 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
1816 msgPanel.add(Box.createVerticalGlue());
1817 JLabel msg = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
1818 JLabel msg2 = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
1820 for (JLabel url : urls) {
1825 final JCheckBox jcb = new JCheckBox(MessageManager.getString("label.do_not_display_again"));
1826 jcb.addActionListener(new ActionListener() {
1828 public void actionPerformed(ActionEvent e) {
1829 // update Cache settings for "don't show this again"
1830 boolean showWarningAgain = !jcb.isSelected();
1831 Cache.setProperty("CHECKURLLINKS", Boolean.valueOf(showWarningAgain).toString());
1836 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
1837 MessageManager.getString("label.SEQUENCE_ID_no_longer_used"), JvOptionPane.WARNING_MESSAGE);
1844 * Proxy class for JDesktopPane which optionally displays the current memory
1845 * usage and highlights the desktop area with a red bar if free memory runs low.
1849 public class MyDesktopPane extends JDesktopPane implements Runnable {
1850 private static final float ONE_MB = 1048576f;
1852 boolean showMemoryUsage = false;
1856 java.text.NumberFormat df;
1858 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory, percentUsage;
1860 public MyDesktopPane(boolean showMemoryUsage) {
1861 showMemoryUsage(showMemoryUsage);
1864 public void showMemoryUsage(boolean showMemory) {
1865 this.showMemoryUsage = showMemory;
1867 Thread worker = new Thread(this);
1873 public boolean isShowMemoryUsage() {
1874 return showMemoryUsage;
1879 df = java.text.NumberFormat.getNumberInstance();
1880 df.setMaximumFractionDigits(2);
1881 runtime = Runtime.getRuntime();
1883 while (showMemoryUsage) {
1885 maxMemory = runtime.maxMemory() / ONE_MB;
1886 allocatedMemory = runtime.totalMemory() / ONE_MB;
1887 freeMemory = runtime.freeMemory() / ONE_MB;
1888 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
1890 percentUsage = (totalFreeMemory / maxMemory) * 100;
1892 // if (percentUsage < 20)
1894 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
1896 // instance.set.setBorder(border1);
1899 // sleep after showing usage
1901 } catch (Exception ex) {
1902 ex.printStackTrace();
1908 public void paintComponent(Graphics g) {
1909 if (showMemoryUsage && g != null && df != null) {
1910 if (percentUsage < 20) {
1911 g.setColor(Color.red);
1913 FontMetrics fm = g.getFontMetrics();
1916 MessageManager.formatMessage("label.memory_stats",
1917 new Object[] { df.format(totalFreeMemory), df.format(maxMemory), df.format(percentUsage) }),
1918 10, getHeight() - fm.getHeight());
1922 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
1923 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
1928 * Accessor method to quickly get all the AlignmentFrames loaded.
1930 * @return an array of AlignFrame, or null if none found
1932 public static AlignFrame[] getAlignFrames() {
1933 if (Jalview.isHeadlessMode()) {
1934 // Desktop.desktop is null in headless mode
1935 return new AlignFrame[] { Jalview.currentAlignFrame };
1938 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1940 if (frames == null) {
1943 List<AlignFrame> avp = new ArrayList<>();
1945 for (int i = frames.length - 1; i > -1; i--) {
1946 if (frames[i] instanceof AlignFrame) {
1947 avp.add((AlignFrame) frames[i]);
1948 } else if (frames[i] instanceof SplitFrame) {
1950 * Also check for a split frame containing an AlignFrame
1952 GSplitFrame sf = (GSplitFrame) frames[i];
1953 if (sf.getTopFrame() instanceof AlignFrame) {
1954 avp.add((AlignFrame) sf.getTopFrame());
1956 if (sf.getBottomFrame() instanceof AlignFrame) {
1957 avp.add((AlignFrame) sf.getBottomFrame());
1961 if (avp.size() == 0) {
1964 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
1969 * Returns an array of any AppJmol frames in the Desktop (or null if none).
1973 public GStructureViewer[] getJmols() {
1974 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1976 if (frames == null) {
1979 List<GStructureViewer> avp = new ArrayList<>();
1981 for (int i = frames.length - 1; i > -1; i--) {
1982 if (frames[i] instanceof AppJmol) {
1983 GStructureViewer af = (GStructureViewer) frames[i];
1987 if (avp.size() == 0) {
1990 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
1995 * Add Groovy Support to Jalview
1998 public void groovyShell_actionPerformed() {
2000 openGroovyConsole();
2001 } catch (Exception ex) {
2002 jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
2003 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2005 MessageManager.getString("label.couldnt_create_groovy_shell"),
2006 MessageManager.getString("label.groovy_support_failed"), JvOptionPane.ERROR_MESSAGE);
2011 * Open the Groovy console
2013 void openGroovyConsole() {
2014 if (groovyConsole == null) {
2015 groovyConsole = new groovy.ui.Console();
2016 groovyConsole.setVariable("Jalview", this);
2017 groovyConsole.run();
2020 * We allow only one console at a time, so that AlignFrame menu option
2021 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2022 * enable 'Run script', when the console is opened, and the reverse when it is
2025 Window window = (Window) groovyConsole.getFrame();
2026 window.addWindowListener(new WindowAdapter() {
2028 public void windowClosed(WindowEvent e) {
2030 * rebind CMD-Q from Groovy Console to Jalview Quit
2033 enableExecuteGroovy(false);
2039 * show Groovy console window (after close and reopen)
2041 ((Window) groovyConsole.getFrame()).setVisible(true);
2044 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2045 * opening a second console
2047 enableExecuteGroovy(true);
2051 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
2054 protected void addQuitHandler() {
2055 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2056 KeyStroke.getKeyStroke(KeyEvent.VK_Q, jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
2058 getRootPane().getActionMap().put("Quit", new AbstractAction() {
2060 public void actionPerformed(ActionEvent e) {
2067 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2069 * @param enabled true if Groovy console is open
2071 public void enableExecuteGroovy(boolean enabled) {
2073 * disable opening a second Groovy console (or re-enable when the console is
2076 groovyShell.setEnabled(!enabled);
2078 AlignFrame[] alignFrames = getAlignFrames();
2079 if (alignFrames != null) {
2080 for (AlignFrame af : alignFrames) {
2081 af.setGroovyEnabled(enabled);
2087 * Progress bars managed by the IProgressIndicator method.
2089 private Hashtable<Long, JPanel> progressBars;
2091 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2096 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2099 public void setProgressBar(String message, long id) {
2100 if (progressBars == null) {
2101 progressBars = new Hashtable<>();
2102 progressBarHandlers = new Hashtable<>();
2105 if (progressBars.get(Long.valueOf(id)) != null) {
2106 JPanel panel = progressBars.remove(Long.valueOf(id));
2107 if (progressBarHandlers.contains(Long.valueOf(id))) {
2108 progressBarHandlers.remove(Long.valueOf(id));
2110 removeProgressPanel(panel);
2112 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2119 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2120 * jalview.gui.IProgressIndicatorHandler)
2123 public void registerHandler(final long id, final IProgressIndicatorHandler handler) {
2124 if (progressBarHandlers == null || !progressBars.containsKey(Long.valueOf(id))) {
2125 throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
2127 progressBarHandlers.put(Long.valueOf(id), handler);
2128 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2129 if (handler.canCancel()) {
2130 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
2131 final IProgressIndicator us = this;
2132 cancel.addActionListener(new ActionListener() {
2135 public void actionPerformed(ActionEvent e) {
2136 handler.cancelActivity(id);
2137 us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
2138 new Object[] { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
2141 progressPanel.add(cancel, BorderLayout.EAST);
2147 * @return true if any progress bars are still active
2150 public boolean operationInProgress() {
2151 if (progressBars != null && progressBars.size() > 0) {
2158 * This will return the first AlignFrame holding the given viewport instance. It
2159 * will break if there are more than one AlignFrames viewing a particular av.
2162 * @return alignFrame for viewport
2164 public static AlignFrame getAlignFrameFor(AlignViewportI viewport) {
2165 if (desktop != null) {
2166 AlignmentPanel[] aps = getAlignmentPanels(viewport.getSequenceSetId());
2167 for (int panel = 0; aps != null && panel < aps.length; panel++) {
2168 if (aps[panel] != null && aps[panel].av == viewport) {
2169 return aps[panel].alignFrame;
2176 public VamsasApplication getVamsasApplication() {
2177 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2183 * flag set if jalview GUI is being operated programmatically
2185 private boolean inBatchMode = false;
2188 * check if jalview GUI is being operated programmatically
2190 * @return inBatchMode
2192 public boolean isInBatchMode() {
2197 * set flag if jalview GUI is being operated programmatically
2199 * @param inBatchMode
2201 public void setInBatchMode(boolean inBatchMode) {
2202 this.inBatchMode = inBatchMode;
2206 * start service discovery and wait till it is done
2208 public void startServiceDiscovery() {
2209 startServiceDiscovery(false);
2213 * start service discovery threads - blocking or non-blocking
2217 public void startServiceDiscovery(boolean blocking) {
2218 startServiceDiscovery(blocking, false);
2222 * start service discovery threads
2224 * @param blocking - false means call returns
2226 * @param ignore_SHOW_JWS2_SERVICES_preference - when true JABA services are
2227 * discovered regardless of user's
2228 * JWS2 discovery preference setting
2230 public void startServiceDiscovery(boolean blocking, boolean ignore_SHOW_JWS2_SERVICES_preference) {
2231 boolean alive = true;
2232 Thread t0 = null, t1 = null, t2 = null;
2233 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2235 // todo: changesupport handlers need to be transferred
2236 if (discoverer == null) {
2237 discoverer = new jalview.ws.jws1.Discoverer();
2238 // register PCS handler for desktop.
2239 discoverer.addPropertyChangeListener(changeSupport);
2241 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2242 // until we phase out completely
2243 (t0 = new Thread(discoverer)).start();
2246 if (ignore_SHOW_JWS2_SERVICES_preference || Cache.getDefault("SHOW_JWS2_SERVICES", true)) {
2247 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(changeSupport);
2251 // TODO: do rest service discovery
2257 } catch (Exception e) {
2259 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive()) || (t3 != null && t3.isAlive())
2260 || (t0 != null && t0.isAlive());
2266 * called to check if the service discovery process completed successfully.
2270 protected void JalviewServicesChanged(PropertyChangeEvent evt) {
2271 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector) {
2272 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().getErrorMessages();
2273 if (ermsg != null) {
2274 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true)) {
2275 if (serviceChangedDialog == null) {
2276 // only run if we aren't already displaying one of these.
2277 addDialogThread(serviceChangedDialog = new Runnable() {
2282 * JalviewDialog jd =new JalviewDialog() {
2284 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2286 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2288 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2290 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2292 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2293 * + " or mis-configured HTTP proxy settings.<br/>" +
2294 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2295 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2296 * true, true, "Web Service Configuration Problem", 450, 400);
2298 * jd.waitForInput();
2300 JvOptionPane.showConfirmDialog(Desktop.desktop,
2301 new JLabel("<html><table width=\"450\"><tr><td>" + ermsg + "</td></tr></table>"
2302 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2303 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2304 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2305 + " Tools->Preferences dialog box to change them.</p></html>"),
2306 "Web Service Configuration Problem", JvOptionPane.DEFAULT_OPTION, JvOptionPane.ERROR_MESSAGE);
2307 serviceChangedDialog = null;
2313 jalview.bin.Console.error("Errors reported by JABA discovery service. Check web services preferences.\n" + ermsg);
2319 private Runnable serviceChangedDialog = null;
2322 * start a thread to open a URL in the configured browser. Pops up a warning
2323 * dialog to the user if there is an exception when calling out to the browser
2328 public static void showUrl(final String url) {
2329 showUrl(url, Desktop.instance);
2333 * Like showUrl but allows progress handler to be specified
2336 * @param progress (null) or object implementing IProgressIndicator
2338 public static void showUrl(final String url, final IProgressIndicator progress) {
2339 new Thread(new Runnable() {
2343 if (progress != null) {
2344 progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[] { url }),
2347 jalview.util.BrowserLauncher.openURL(url);
2348 } catch (Exception ex) {
2349 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2350 MessageManager.getString("label.web_browser_not_found_unix"),
2351 MessageManager.getString("label.web_browser_not_found"), JvOptionPane.WARNING_MESSAGE);
2353 ex.printStackTrace();
2355 if (progress != null) {
2356 progress.setProgressBar(null, this.hashCode());
2362 public static WsParamSetManager wsparamManager = null;
2364 public static ParamManager getUserParameterStore() {
2365 if (wsparamManager == null) {
2366 wsparamManager = new WsParamSetManager();
2368 return wsparamManager;
2372 * static hyperlink handler proxy method for use by Jalview's internal windows
2376 public static void hyperlinkUpdate(HyperlinkEvent e) {
2377 if (e.getEventType() == EventType.ACTIVATED) {
2380 url = e.getURL().toString();
2381 Desktop.showUrl(url);
2382 } catch (Exception x) {
2384 jalview.bin.Console.error("Couldn't handle string " + url + " as a URL.");
2386 // ignore any exceptions due to dud links.
2393 * single thread that handles display of dialogs to user.
2395 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2398 * flag indicating if dialogExecutor should try to acquire a permit
2400 private volatile boolean dialogPause = true;
2405 private java.util.concurrent.Semaphore block = new Semaphore(0);
2407 private static groovy.ui.Console groovyConsole;
2410 * add another dialog thread to the queue
2414 public void addDialogThread(final Runnable prompter) {
2415 dialogExecutor.submit(new Runnable() {
2421 } catch (InterruptedException x) {
2424 if (instance == null) {
2428 SwingUtilities.invokeAndWait(prompter);
2429 } catch (Exception q) {
2430 jalview.bin.Console.warn("Unexpected Exception in dialog thread.", q);
2436 public void startDialogQueue() {
2437 // set the flag so we don't pause waiting for another permit and semaphore
2438 // the current task to begin
2439 dialogPause = false;
2444 * Outputs an image of the desktop to file in EPS format, after prompting the
2445 * user for choice of Text or Lineart character rendering (unless a preference
2446 * has been set). The file name is generated as
2449 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2453 protected void snapShotWindow_actionPerformed(ActionEvent e) {
2454 // currently the menu option to do this is not shown
2457 int width = getWidth();
2458 int height = getHeight();
2459 File of = new File("Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2460 ImageWriterI writer = new ImageWriterI() {
2462 public void exportImage(Graphics g) throws Exception {
2464 jalview.bin.Console.info("Successfully written snapshot to file " + of.getAbsolutePath());
2467 String title = "View of desktop";
2468 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS, title);
2469 exporter.doExport(of, this, width, height, title);
2473 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2474 * This respects (remembers) any previous 'exploded geometry' i.e. the size and
2475 * location last time the view was expanded (if any). However it does not
2476 * remember the split pane divider location - this is set to match the
2477 * 'exploding' frame.
2481 public void explodeViews(SplitFrame sf) {
2482 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2483 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2484 List<? extends AlignmentViewPanel> topPanels = oldTopFrame.getAlignPanels();
2485 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame.getAlignPanels();
2486 int viewCount = topPanels.size();
2487 if (viewCount < 2) {
2492 * Processing in reverse order works, forwards order leaves the first panels not
2493 * visible. I don't know why!
2495 for (int i = viewCount - 1; i >= 0; i--) {
2497 * Make new top and bottom frames. These take over the respective AlignmentPanel
2498 * objects, including their AlignmentViewports, so the cdna/protein
2499 * relationships between the viewports is carried over to the new split frames.
2501 * explodedGeometry holds the (x, y) position of the previously exploded
2502 * SplitFrame, and the (width, height) of the AlignFrame component
2504 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
2505 AlignFrame newTopFrame = new AlignFrame(topPanel);
2506 newTopFrame.setSize(oldTopFrame.getSize());
2507 newTopFrame.setVisible(true);
2508 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport()).getExplodedGeometry();
2509 if (geometry != null) {
2510 newTopFrame.setSize(geometry.getSize());
2513 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
2514 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
2515 newBottomFrame.setSize(oldBottomFrame.getSize());
2516 newBottomFrame.setVisible(true);
2517 geometry = ((AlignViewport) bottomPanel.getAlignViewport()).getExplodedGeometry();
2518 if (geometry != null) {
2519 newBottomFrame.setSize(geometry.getSize());
2522 topPanel.av.setGatherViewsHere(false);
2523 bottomPanel.av.setGatherViewsHere(false);
2524 JInternalFrame splitFrame = new SplitFrame(newTopFrame, newBottomFrame);
2525 if (geometry != null) {
2526 splitFrame.setLocation(geometry.getLocation());
2528 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
2532 * Clear references to the panels (now relocated in the new SplitFrames) before
2533 * closing the old SplitFrame.
2536 bottomPanels.clear();
2541 * Gather expanded split frames, sharing the same pairs of sequence set ids,
2542 * back into the given SplitFrame as additional views. Note that the gathered
2543 * frames may themselves have multiple views.
2547 public void gatherViews(GSplitFrame source) {
2549 * special handling of explodedGeometry for a view within a SplitFrame: - it
2550 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
2551 * height) of the AlignFrame component
2553 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
2554 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
2555 myTopFrame.viewport.setExplodedGeometry(
2556 new Rectangle(source.getX(), source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
2557 myBottomFrame.viewport.setExplodedGeometry(
2558 new Rectangle(source.getX(), source.getY(), myBottomFrame.getWidth(), myBottomFrame.getHeight()));
2559 myTopFrame.viewport.setGatherViewsHere(true);
2560 myBottomFrame.viewport.setGatherViewsHere(true);
2561 String topViewId = myTopFrame.viewport.getSequenceSetId();
2562 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
2564 JInternalFrame[] frames = desktop.getAllFrames();
2565 for (JInternalFrame frame : frames) {
2566 if (frame instanceof SplitFrame && frame != source) {
2567 SplitFrame sf = (SplitFrame) frame;
2568 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
2569 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
2570 boolean gatherThis = false;
2571 for (int a = 0; a < topFrame.alignPanels.size(); a++) {
2572 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
2573 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
2574 if (topViewId.equals(topPanel.av.getSequenceSetId())
2575 && bottomViewId.equals(bottomPanel.av.getSequenceSetId())) {
2577 topPanel.av.setGatherViewsHere(false);
2578 bottomPanel.av.setGatherViewsHere(false);
2579 topPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), topFrame.getSize()));
2580 bottomPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), bottomFrame.getSize()));
2581 myTopFrame.addAlignmentPanel(topPanel, false);
2582 myBottomFrame.addAlignmentPanel(bottomPanel, false);
2587 topFrame.getAlignPanels().clear();
2588 bottomFrame.getAlignPanels().clear();
2595 * The dust settles...give focus to the tab we did this from.
2597 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
2600 public static groovy.ui.Console getGroovyConsole() {
2601 return groovyConsole;
2605 * handles the payload of a drag and drop event.
2607 * TODO refactor to desktop utilities class
2609 * @param files - Data source strings extracted from the drop event
2610 * @param protocols - protocol for each data source extracted from the drop
2612 * @param evt - the drop event
2613 * @param t - the payload from the drop event
2616 public static void transferFromDropTarget(List<Object> files, List<DataSourceType> protocols, DropTargetDropEvent evt,
2617 Transferable t) throws Exception {
2619 DataFlavor uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"), urlFlavour = null;
2621 urlFlavour = new DataFlavor("application/x-java-url; class=java.net.URL");
2622 } catch (ClassNotFoundException cfe) {
2623 jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.", cfe);
2626 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) {
2629 java.net.URL url = (URL) t.getTransferData(urlFlavour);
2630 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
2631 // means url may be null.
2633 protocols.add(DataSourceType.URL);
2634 files.add(url.toString());
2635 jalview.bin.Console.debug("Drop handled as URL dataflavor " + files.get(files.size() - 1));
2638 if (Platform.isAMacAndNotJS()) {
2639 System.err.println("Please ignore plist error - occurs due to problem with java 8 on OSX");
2642 } catch (Throwable ex) {
2643 jalview.bin.Console.debug("URL drop handler failed.", ex);
2646 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
2647 // Works on Windows and MacOSX
2648 jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
2649 for (Object file : (List) t.getTransferData(DataFlavor.javaFileListFlavor)) {
2651 protocols.add(DataSourceType.FILE);
2654 // Unix like behaviour
2655 boolean added = false;
2657 if (t.isDataFlavorSupported(uriListFlavor)) {
2658 jalview.bin.Console.debug("Drop handled as uriListFlavor");
2659 // This is used by Unix drag system
2660 data = (String) t.getTransferData(uriListFlavor);
2663 // fallback to text: workaround - on OSX where there's a JVM bug
2664 jalview.bin.Console.debug("standard URIListFlavor failed. Trying text");
2665 // try text fallback
2666 DataFlavor textDf = new DataFlavor("text/plain;class=java.lang.String");
2667 if (t.isDataFlavorSupported(textDf)) {
2668 data = (String) t.getTransferData(textDf);
2671 jalview.bin.Console.debug("Plain text drop content returned " + (data == null ? "Null - failed" : data));
2675 while (protocols.size() < files.size()) {
2676 jalview.bin.Console.debug("Adding missing FILE protocol for " + files.get(protocols.size()));
2677 protocols.add(DataSourceType.FILE);
2679 for (java.util.StringTokenizer st = new java.util.StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
2681 String s = st.nextToken();
2682 if (s.startsWith("#")) {
2683 // the line is a comment (as per the RFC 2483)
2686 java.net.URI uri = new java.net.URI(s);
2687 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http")) {
2688 protocols.add(DataSourceType.URL);
2689 files.add(uri.toString());
2691 // otherwise preserve old behaviour: catch all for file objects
2692 java.io.File file = new java.io.File(uri);
2693 protocols.add(DataSourceType.FILE);
2694 files.add(file.toString());
2699 if (jalview.bin.Console.isDebugEnabled()) {
2700 if (data == null || !added) {
2702 if (t.getTransferDataFlavors() != null && t.getTransferDataFlavors().length > 0) {
2703 jalview.bin.Console.debug("Couldn't resolve drop data. Here are the supported flavors:");
2704 for (DataFlavor fl : t.getTransferDataFlavors()) {
2705 jalview.bin.Console.debug("Supported transfer dataflavor: " + fl.toString());
2706 Object df = t.getTransferData(fl);
2708 jalview.bin.Console.debug("Retrieves: " + df);
2710 jalview.bin.Console.debug("Retrieved nothing");
2714 jalview.bin.Console.debug("Couldn't resolve dataflavor for drop: " + t.toString());
2719 if (Platform.isWindowsAndNotJS()) {
2720 jalview.bin.Console.debug("Scanning dropped content for Windows Link Files");
2722 // resolve any .lnk files in the file drop
2723 for (int f = 0; f < files.size(); f++) {
2724 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
2725 if (protocols.get(f).equals(DataSourceType.FILE)
2726 && (source.endsWith(".lnk") || source.endsWith(".url") || source.endsWith(".site"))) {
2728 Object obj = files.get(f);
2729 File lf = (obj instanceof File ? (File) obj : new File((String) obj));
2730 // process link file to get a URL
2731 jalview.bin.Console.debug("Found potential link file: " + lf);
2732 WindowsShortcut wscfile = new WindowsShortcut(lf);
2733 String fullname = wscfile.getRealFilename();
2734 protocols.set(f, FormatAdapter.checkProtocol(fullname));
2735 files.set(f, fullname);
2736 jalview.bin.Console.debug("Parsed real filename " + fullname + " to extract protocol: " + protocols.get(f));
2737 } catch (Exception ex) {
2738 jalview.bin.Console.error("Couldn't parse " + files.get(f) + " as a link file.", ex);
2746 * Sets the Preferences property for experimental features to True or False
2747 * depending on the state of the controlling menu item
2750 protected void showExperimental_actionPerformed(boolean selected) {
2751 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
2755 * Answers a (possibly empty) list of any structure viewer frames (currently for
2756 * either Jmol or Chimera) which are currently open. This may optionally be
2757 * restricted to viewers of a specified class, or viewers linked to a specified
2760 * @param apanel if not null, only return viewers linked to this
2762 * @param structureViewerClass if not null, only return viewers of this class
2765 public List<StructureViewerBase> getStructureViewers(AlignmentPanel apanel,
2766 Class<? extends StructureViewerBase> structureViewerClass) {
2767 List<StructureViewerBase> result = new ArrayList<>();
2768 JInternalFrame[] frames = Desktop.instance.getAllFrames();
2770 for (JInternalFrame frame : frames) {
2771 if (frame instanceof StructureViewerBase) {
2772 if (structureViewerClass == null || structureViewerClass.isInstance(frame)) {
2773 if (apanel == null || ((StructureViewerBase) frame).isLinkedWith(apanel)) {
2774 result.add((StructureViewerBase) frame);
2782 public static final String debugScaleMessage = "Desktop graphics transform scale=";
2784 private static boolean debugScaleMessageDone = false;
2786 public static void debugScaleMessage(Graphics g) {
2787 if (debugScaleMessageDone) {
2790 // output used by tests to check HiDPI scaling settings in action
2792 Graphics2D gg = (Graphics2D) g;
2794 AffineTransform t = gg.getTransform();
2795 double scaleX = t.getScaleX();
2796 double scaleY = t.getScaleY();
2797 jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)");
2798 jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)");
2799 debugScaleMessageDone = true;
2801 jalview.bin.Console.debug("Desktop graphics null");
2803 } catch (Exception e) {
2804 jalview.bin.Console.debug(Cache.getStackTraceString(e));