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;
24 import java.awt.BorderLayout;
25 import java.awt.Color;
26 import java.awt.Dimension;
27 import java.awt.FontMetrics;
28 import java.awt.Graphics;
29 import java.awt.Graphics2D;
30 import java.awt.GridLayout;
31 import java.awt.Point;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.datatransfer.Clipboard;
36 import java.awt.datatransfer.ClipboardOwner;
37 import java.awt.datatransfer.DataFlavor;
38 import java.awt.datatransfer.Transferable;
39 import java.awt.dnd.DnDConstants;
40 import java.awt.dnd.DropTargetDragEvent;
41 import java.awt.dnd.DropTargetDropEvent;
42 import java.awt.dnd.DropTargetEvent;
43 import java.awt.dnd.DropTargetListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.WindowAdapter;
51 import java.awt.event.WindowEvent;
52 import java.awt.geom.AffineTransform;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
56 import java.io.FileWriter;
57 import java.io.IOException;
58 import java.lang.reflect.Field;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashMap;
63 import java.util.Hashtable;
64 import java.util.List;
65 import java.util.ListIterator;
66 import java.util.Vector;
67 import java.util.concurrent.ExecutionException;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Future;
71 import java.util.concurrent.FutureTask;
72 import java.util.concurrent.Semaphore;
74 import javax.swing.AbstractAction;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.Box;
78 import javax.swing.BoxLayout;
79 import javax.swing.DefaultDesktopManager;
80 import javax.swing.DesktopManager;
81 import javax.swing.InputMap;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JComboBox;
85 import javax.swing.JComponent;
86 import javax.swing.JDesktopPane;
87 import javax.swing.JInternalFrame;
88 import javax.swing.JLabel;
89 import javax.swing.JMenuItem;
90 import javax.swing.JPanel;
91 import javax.swing.JPopupMenu;
92 import javax.swing.JProgressBar;
93 import javax.swing.JTextField;
94 import javax.swing.KeyStroke;
95 import javax.swing.SwingUtilities;
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.api.StructureSelectionManagerProvider;
106 import jalview.bin.ApplicationSingletonProvider;
107 import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
108 import jalview.bin.Cache;
109 import jalview.bin.Jalview;
110 import jalview.gui.ImageExporter.ImageWriterI;
111 import jalview.io.BackupFiles;
112 import jalview.io.DataSourceType;
113 import jalview.io.FileFormat;
114 import jalview.io.FileFormatException;
115 import jalview.io.FileFormatI;
116 import jalview.io.FileFormats;
117 import jalview.io.FileLoader;
118 import jalview.io.FormatAdapter;
119 import jalview.io.IdentifyFile;
120 import jalview.io.JalviewFileChooser;
121 import jalview.io.JalviewFileView;
122 import jalview.jbgui.GDesktop;
123 import jalview.jbgui.GSplitFrame;
124 import jalview.jbgui.GStructureViewer;
125 import jalview.project.Jalview2XML;
126 import jalview.structure.StructureSelectionManager;
127 import jalview.urls.IdOrgSettings;
128 import jalview.util.BrowserLauncher;
129 import jalview.util.ChannelProperties;
130 import jalview.util.ImageMaker.TYPE;
131 import jalview.util.MessageManager;
132 import jalview.util.Platform;
133 import jalview.util.ShortcutKeyMaskExWrapper;
134 import jalview.util.UrlConstants;
135 import jalview.viewmodel.AlignmentViewport;
136 import jalview.ws.WSDiscovererI;
137 import jalview.ws.params.ParamManager;
138 import jalview.ws.utils.UrlDownloadClient;
145 * @version $Revision: 1.155 $
147 @SuppressWarnings("serial")
148 public class Desktop extends GDesktop
149 implements DropTargetListener, ClipboardOwner, IProgressIndicator,
150 StructureSelectionManagerProvider, ApplicationSingletonI
153 private static final String CITATION;
155 URL bg_logo_url = ChannelProperties.getImageURL("bg_logo." + String.valueOf(SplashScreen.logoSize));
156 URL uod_logo_url = ChannelProperties.getImageURL("uod_banner." + String.valueOf(SplashScreen.logoSize));
157 boolean logo = (bg_logo_url != null || uod_logo_url != null);
158 StringBuilder sb = new StringBuilder();
159 sb.append("<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.");
163 sb.append(bg_logo_url == null ? "" : "<img alt=\"Barton Group logo\" src=\"" + bg_logo_url.toString() + "\">");
164 sb.append(uod_logo_url == null ? ""
165 : " <img alt=\"University of Dundee shield\" src=\"" + uod_logo_url.toString() + "\">");
167 "<br><br>For help, see the FAQ at <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and/or join the jalview-discuss@jalview.org mailing list");
168 sb.append("<br><br>If you use Jalview, please cite:"
169 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
170 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
171 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033");
172 CITATION = sb.toString();
175 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
177 private static int DEFAULT_MIN_WIDTH = 300;
179 private static int DEFAULT_MIN_HEIGHT = 250;
181 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
183 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
185 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
187 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
189 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
191 @SuppressWarnings("deprecation")
192 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
194 public static boolean nosplash = false;
196 * news reader - null if it was never started.
198 private BlogReader jvnews = null;
200 private File projectFile;
204 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
207 public void addJalviewPropertyChangeListener(
208 PropertyChangeListener listener)
210 changeSupport.addJalviewPropertyChangeListener(listener);
214 * @param propertyName
216 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
217 * java.beans.PropertyChangeListener)
220 public void addJalviewPropertyChangeListener(String propertyName,
221 PropertyChangeListener listener)
223 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
227 * @param propertyName
229 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
230 * java.beans.PropertyChangeListener)
233 public void removeJalviewPropertyChangeListener(String propertyName,
234 PropertyChangeListener listener)
236 changeSupport.removeJalviewPropertyChangeListener(propertyName,
240 private MyDesktopPane desktopPane;
242 public static MyDesktopPane getDesktopPane()
244 Desktop desktop = getInstance();
245 return desktop == null ? null : desktop.desktopPane;
249 * Answers an 'application scope' singleton instance of this class. Separate
250 * SwingJS 'applets' running in the same browser page will each have a
251 * distinct instance of Desktop.
255 public static Desktop getInstance()
257 return Jalview.isHeadlessMode() ? null
258 : (Desktop) ApplicationSingletonProvider
259 .getInstance(Desktop.class);
262 public static StructureSelectionManager getStructureSelectionManager()
264 return StructureSelectionManager
265 .getStructureSelectionManager(getInstance());
268 int openFrameCount = 0;
270 final int xOffset = 30;
272 final int yOffset = 30;
274 public jalview.ws.jws1.Discoverer discoverer;
276 public Object[] jalviewClipboard;
278 public boolean internalCopy = false;
280 int fileLoadingCount = 0;
282 class MyDesktopManager implements DesktopManager
285 private DesktopManager delegate;
287 public MyDesktopManager(DesktopManager delegate)
289 this.delegate = delegate;
293 public void activateFrame(JInternalFrame f)
297 delegate.activateFrame(f);
298 } catch (NullPointerException npe)
300 Point p = getMousePosition();
301 showPasteMenu(p.x, p.y);
306 public void beginDraggingFrame(JComponent f)
308 delegate.beginDraggingFrame(f);
312 public void beginResizingFrame(JComponent f, int direction)
314 delegate.beginResizingFrame(f, direction);
318 public void closeFrame(JInternalFrame f)
320 delegate.closeFrame(f);
324 public void deactivateFrame(JInternalFrame f)
326 delegate.deactivateFrame(f);
330 public void deiconifyFrame(JInternalFrame f)
332 delegate.deiconifyFrame(f);
336 public void dragFrame(JComponent f, int newX, int newY)
342 delegate.dragFrame(f, newX, newY);
346 public void endDraggingFrame(JComponent f)
348 delegate.endDraggingFrame(f);
349 desktopPane.repaint();
353 public void endResizingFrame(JComponent f)
355 delegate.endResizingFrame(f);
356 desktopPane.repaint();
360 public void iconifyFrame(JInternalFrame f)
362 delegate.iconifyFrame(f);
366 public void maximizeFrame(JInternalFrame f)
368 delegate.maximizeFrame(f);
372 public void minimizeFrame(JInternalFrame f)
374 delegate.minimizeFrame(f);
378 public void openFrame(JInternalFrame f)
380 delegate.openFrame(f);
384 public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
391 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
395 public void setBoundsForFrame(JComponent f, int newX, int newY,
396 int newWidth, int newHeight)
398 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
401 // All other methods, simply delegate
406 * Private constructor enforces singleton pattern. It is called by reflection
407 * from ApplicationSingletonProvider.getInstance().
416 * A note to implementors. It is ESSENTIAL that any activities that might
417 * block are spawned off as threads rather than waited for during this
421 doConfigureStructurePrefs();
422 setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
425 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
426 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
427 * documented or guaranteed to exist, so we access it via reflection. There
428 * appear to be unfathomable criteria about what this string can contain, and it
429 * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
430 * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
431 * but "Jalview non-release" does not. The reflection access may generate a
432 * warning: WARNING: An illegal reflective access operation has occurred
433 * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
434 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
436 if (Platform.isLinux()) {
438 Toolkit xToolkit = Toolkit.getDefaultToolkit();
439 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
440 Field awtAppClassNameField = null;
442 if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
443 awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
446 String title = ChannelProperties.getProperty("app_name");
447 if (awtAppClassNameField != null) {
448 awtAppClassNameField.setAccessible(true);
449 awtAppClassNameField.set(xToolkit, title);
451 Cache.log.debug("XToolkit: awtAppClassName not found");
453 } catch (Exception e) {
454 Cache.debug("Error setting awtAppClassName");
455 Cache.trace(Cache.getStackTraceString(e));
460 * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
461 * macOS's application menu. APQHandlers will check to see if a handler is
462 * supported before setting it.
465 APQHandlers.setAPQHandlers(this);
466 } catch (Exception e) {
467 System.out.println("Cannot set APQHandlers");
468 // e.printStackTrace();
469 } catch (Throwable t) {
470 Cache.warn("Error setting APQHandlers: " + t.toString());
471 Cache.trace(Cache.getStackTraceString(t));
473 setIconImages(ChannelProperties.getIconList());
475 addWindowListener(new WindowAdapter() {
478 public void windowClosing(WindowEvent ev) {
483 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
485 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
486 desktopPane = new MyDesktopPane(selmemusage);
488 showMemusage.setSelected(selmemusage);
489 desktopPane.setBackground(Color.white);
491 getContentPane().setLayout(new BorderLayout());
492 // alternate config - have scrollbars - see notes in JAL-153
493 // JScrollPane sp = new JScrollPane();
494 // sp.getViewport().setView(desktop);
495 // getContentPane().add(sp, BorderLayout.CENTER);
497 // BH 2018 - just an experiment to try unclipped JInternalFrames.
500 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
503 getContentPane().add(desktopPane, BorderLayout.CENTER);
504 desktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
507 // This line prevents Windows Look&Feel resizing all new windows to
509 // if previous window was maximised
510 desktopPane.setDesktopManager(new MyDesktopManager(
511 (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
512 : Platform.isAMacAndNotJS()
513 ? new AquaInternalFrameManager(
514 desktopPane.getDesktopManager())
515 : desktopPane.getDesktopManager())));
517 Rectangle dims = getLastKnownDimensions("");
524 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
525 int xPos = Math.max(5, (screenSize.width - 900) / 2);
526 int yPos = Math.max(5, (screenSize.height - 650) / 2);
527 setBounds(xPos, yPos, 900, 650);
530 if (!Platform.isJS())
537 jconsole = new Console(this, showjconsole);
538 jconsole.setHeader(Cache.getVersionDetailsForConsole());
539 showConsole(showjconsole);
541 showNews.setVisible(false); // not sure if we should only do this for interactive session?
543 experimentalFeatures.setSelected(showExperimental());
545 getIdentifiersOrgData();
547 if (Jalview.isInteractive())
549 // disabled for SeqCanvasTest
552 // Spawn a thread that shows the splashscreen
554 SwingUtilities.invokeLater(new Runnable()
559 new SplashScreen(true);
564 // Thread off a new instance of the file chooser - this reduces the
567 // takes to open it later on.
568 new Thread(new Runnable()
573 Cache.log.debug("Filechooser init thread started.");
574 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
575 JalviewFileChooser.forRead(
576 Cache.getProperty("LAST_DIRECTORY"), fileFormat);
577 Cache.log.debug("Filechooser init thread finished.");
580 // Add the service change listener
581 changeSupport.addJalviewPropertyChangeListener("services",
582 new PropertyChangeListener()
586 public void propertyChange(PropertyChangeEvent evt)
588 Cache.log.debug("Firing service changed event for "
589 + evt.getNewValue());
590 JalviewServicesChanged(evt);
596 this.setDropTarget(new java.awt.dnd.DropTarget(desktopPane, this));
598 this.addWindowListener(new WindowAdapter()
601 public void windowClosing(WindowEvent evt)
608 this.addMouseListener(ma = new MouseAdapter()
611 public void mousePressed(MouseEvent evt)
613 if (evt.isPopupTrigger()) // Mac
615 showPasteMenu(evt.getX(), evt.getY());
619 public void mouseReleased(MouseEvent evt)
621 if (evt.isPopupTrigger()) // Windows
623 showPasteMenu(evt.getX(), evt.getY());
627 desktopPane.addMouseListener(ma);
628 } catch (Throwable t)
636 * Answers true if user preferences to enable experimental features is True
641 public boolean showExperimental()
643 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
644 Boolean.FALSE.toString());
645 return Boolean.valueOf(experimental).booleanValue();
648 public void doConfigureStructurePrefs()
650 // configure services
651 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
652 if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) {
653 ssm.setAddTempFacAnnot(Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
654 ssm.setProcessSecondaryStructure(Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
655 // JAL-3915 - RNAView is no longer an option so this has no effect
656 ssm.setSecStructServices(Cache.getDefault(Preferences.USE_RNAVIEW, false));
658 ssm.setAddTempFacAnnot(false);
659 ssm.setProcessSecondaryStructure(false);
660 ssm.setSecStructServices(false);
664 public void checkForNews() {
665 final Desktop me = this;
666 // Thread off the news reader, in case there are connection problems.
667 new Thread(new Runnable() {
670 Cache.log.debug("Starting news thread.");
671 jvnews = new BlogReader(me);
672 showNews.setVisible(true);
673 Cache.log.debug("Completed news thread.");
678 public void getIdentifiersOrgData() {
679 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
680 // Thread off the identifiers fetcher
681 new Thread(new Runnable() {
684 Cache.log.debug("Downloading data from identifiers.org");
686 UrlDownloadClient.download(IdOrgSettings.getUrl(), IdOrgSettings.getDownloadLocation());
687 } catch (IOException e) {
688 Cache.log.debug("Exception downloading identifiers.org data" + e.getMessage());
696 protected void showNews_actionPerformed(ActionEvent e) {
697 showNews(showNews.isSelected());
700 void showNews(boolean visible)
702 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
703 showNews.setSelected(visible);
704 if (visible && !jvnews.isVisible())
706 new Thread(new Runnable()
711 long now = System.currentTimeMillis();
712 setProgressBar(MessageManager.getString("status.refreshing_news"),
714 jvnews.refreshNews();
715 setProgressBar(null, now);
723 * recover the last known dimensions for a jalview window
726 * - empty string is desktop, all other windows have unique prefix
727 * @return null or last known dimensions scaled to current geometry (if last
728 * window geom was known)
730 Rectangle getLastKnownDimensions(String windowName)
732 // TODO: lock aspect ratio for scaling desktop Bug #0058199
733 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
734 String x = Cache.getProperty(windowName + "SCREEN_X");
735 String y = Cache.getProperty(windowName + "SCREEN_Y");
736 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
737 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
738 if ((x != null) && (y != null) && (width != null) && (height != null)) {
739 int ix = Integer.parseInt(x), iy = Integer.parseInt(y), iw = Integer.parseInt(width),
740 ih = Integer.parseInt(height);
741 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) {
742 // attempt #1 - try to cope with change in screen geometry - this
743 // version doesn't preserve original jv aspect ratio.
744 // take ratio of current screen size vs original screen size.
745 double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
746 double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
747 // rescale the bounds depending upon the current screen geometry.
748 ix = (int) (ix * sw);
749 iw = (int) (iw * sw);
750 iy = (int) (iy * sh);
751 ih = (int) (ih * sh);
752 while (ix >= screenSize.width) {
753 Cache.log.debug("Window geometry location recall error: shifting horizontal to within screenbounds.");
754 ix -= screenSize.width;
756 while (iy >= screenSize.height) {
757 Cache.log.debug("Window geometry location recall error: shifting vertical to within screenbounds.");
758 iy -= screenSize.height;
760 Cache.log.debug("Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw
763 // return dimensions for new instance
764 return new Rectangle(ix, iy, iw, ih);
769 void showPasteMenu(int x, int y)
771 JPopupMenu popup = new JPopupMenu();
772 JMenuItem item = new JMenuItem(
773 MessageManager.getString("label.paste_new_window"));
774 item.addActionListener(new ActionListener()
777 public void actionPerformed(ActionEvent evt)
784 popup.show(this, x, y);
791 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
792 Transferable contents = c.getContents(this);
794 if (contents != null)
796 String file = (String) contents
797 .getTransferData(DataFlavor.stringFlavor);
799 FileFormatI format = new IdentifyFile().identify(file,
800 DataSourceType.PASTE);
802 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
805 } catch (Exception ex)
808 "Unable to paste alignment from system clipboard:\n" + ex);
815 * Adds and opens the given frame to the desktop that is visible, allowed to
816 * resize, and has a 300px minimum width.
827 public static synchronized void addInternalFrame(
828 final JInternalFrame frame, String title, int w, int h)
832 addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, w, h,
833 FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
837 * Add an internal frame to the Jalview desktop that may optionally be
838 * visible, resizable, and allowed to be any size
845 * When true, display frame immediately, otherwise, caller must call
846 * setVisible themselves.
853 * @param ignoreMinSize
854 * Do not set the default minimum size for frame
856 public static synchronized void addInternalFrame(
857 final JInternalFrame frame, String title, boolean makeVisible,
858 int w, int h, boolean resizable, boolean ignoreMinSize)
860 // 15 classes call this method directly.
863 // TODO: allow callers to determine X and Y position of frame (eg. via
865 // TODO: consider fixing method to update entries in the window submenu with
866 // the current window title
868 frame.setTitle(title);
869 if (frame.getWidth() < 1 || frame.getHeight() < 1)
873 if (getInstance() != null)
874 getInstance().addFrame(frame, makeVisible, resizable,
878 // These can now by put into a single int flag, if desired:
880 public final static boolean FRAME_MAKE_VISIBLE = true;
882 public final static boolean FRAME_NOT_VISIBLE = false;
884 public final static boolean FRAME_ALLOW_RESIZE = true;
886 public final static boolean FRAME_NOT_RESIZABLE = false;
888 public final static boolean FRAME_ALLOW_ANY_SIZE = true;
890 public final static boolean FRAME_SET_MIN_SIZE_300 = false;
892 private void addFrame(JInternalFrame frame,
893 boolean makeVisible, boolean resizable,
894 boolean ignoreMinSize)
900 boolean isEmbedded = (Platform.getEmbeddedAttribute(frame, "id") != null);
901 boolean hasEmbeddedSize = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
902 // Web page embedding allows us to ignore minimum size
903 ignoreMinSize |= hasEmbeddedSize;
908 // Set default dimension for Alignment Frame window.
909 // The Alignment Frame window could be added from a number of places,
911 // I did this here in order not to miss out on any Alignment frame.
912 if (frame instanceof AlignFrame)
914 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
915 ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
917 frame.setMinimumSize(
918 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
923 frame.setVisible(makeVisible);
924 frame.setClosable(true);
925 frame.setResizable(resizable);
926 frame.setMaximizable(resizable);
927 frame.setIconifiable(resizable);
928 frame.setOpaque(Platform.isJS());
930 if (!isEmbedded && frame.getX() < 1 && frame.getY() < 1)
932 frame.setLocation(xOffset * openFrameCount,
933 yOffset * ((openFrameCount - 1) % 10) + yOffset);
937 * add an entry for the new frame in the Window menu
938 * (and remove it when the frame is closed)
940 final JMenuItem menuItem = new JMenuItem(frame.getTitle());
941 frame.addInternalFrameListener(new InternalFrameAdapter()
944 public void internalFrameActivated(InternalFrameEvent evt)
946 JInternalFrame itf = getDesktopPane().getSelectedFrame();
949 if (itf instanceof AlignFrame)
951 Jalview.setCurrentAlignFrame((AlignFrame) itf);
958 public void internalFrameClosed(InternalFrameEvent evt)
960 PaintRefresher.RemoveComponent(frame);
963 * defensive check to prevent frames being
964 * added half off the window
966 if (openFrameCount > 0)
972 * ensure no reference to alignFrame retained by menu item listener
974 if (menuItem.getActionListeners().length > 0)
976 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
978 getInstance().windowMenu.remove(menuItem);
982 menuItem.addActionListener(new ActionListener()
985 public void actionPerformed(ActionEvent e)
989 frame.setSelected(true);
990 frame.setIcon(false);
991 } catch (java.beans.PropertyVetoException ex)
993 // System.err.println(ex.toString());
998 setKeyBindings(frame);
1000 getDesktopPane().add(frame);
1002 getInstance().windowMenu.add(menuItem);
1007 frame.setSelected(true);
1008 frame.requestFocus();
1009 } catch (java.beans.PropertyVetoException ve)
1011 } catch (java.lang.ClassCastException cex)
1014 "Squashed a possible GUI implementation error. If you can recreate this, please look at http://issues.jalview.org/browse/JAL-869",
1020 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
1025 private static void setKeyBindings(JInternalFrame frame)
1027 final Action closeAction = new AbstractAction()
1030 public void actionPerformed(ActionEvent e)
1037 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
1039 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
1040 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
1042 InputMap inputMap = frame
1043 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1044 String ctrlW = ctrlWKey.toString();
1045 inputMap.put(ctrlWKey, ctrlW);
1046 inputMap.put(cmdWKey, ctrlW);
1048 ActionMap actionMap = frame.getActionMap();
1049 actionMap.put(ctrlW, closeAction);
1053 public void lostOwnership(Clipboard clipboard, Transferable contents)
1057 jalviewClipboard = null;
1060 internalCopy = false;
1064 public void dragEnter(DropTargetDragEvent evt)
1069 public void dragExit(DropTargetEvent evt)
1074 public void dragOver(DropTargetDragEvent evt)
1079 public void dropActionChanged(DropTargetDragEvent evt)
1090 public void drop(DropTargetDropEvent evt)
1092 boolean success = true;
1093 // JAL-1552 - acceptDrop required before getTransferable call for
1094 // Java's Transferable for native dnd
1095 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
1096 Transferable t = evt.getTransferable();
1097 List<Object> files = new ArrayList<>();
1098 List<DataSourceType> protocols = new ArrayList<>();
1102 transferFromDropTarget(files, protocols, evt, t);
1103 } catch (Exception e)
1105 e.printStackTrace();
1113 for (int i = 0; i < files.size(); i++)
1115 // BH 2018 File or String
1116 Object file = files.get(i);
1117 String fileName = file.toString();
1118 DataSourceType protocol = (protocols == null)
1119 ? DataSourceType.FILE
1121 FileFormatI format = null;
1123 if (fileName.endsWith(".jar"))
1125 format = FileFormat.Jalview;
1130 format = new IdentifyFile().identify(file, protocol);
1132 if (file instanceof File)
1134 Platform.cacheFileData((File) file);
1136 new FileLoader().LoadFile(null, file, protocol, format);
1139 } catch (Exception ex)
1144 evt.dropComplete(success); // need this to ensure input focus is properly
1145 // transfered to any new windows created
1155 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
1157 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1158 JalviewFileChooser chooser = JalviewFileChooser.forRead(
1159 Cache.getProperty("LAST_DIRECTORY"), fileFormat,
1160 BackupFiles.getEnabled());
1162 chooser.setFileView(new JalviewFileView());
1163 chooser.setDialogTitle(
1164 MessageManager.getString("label.open_local_file"));
1165 chooser.setToolTipText(MessageManager.getString("action.open"));
1167 chooser.setResponseHandler(0, new Runnable()
1172 File selectedFile = chooser.getSelectedFile();
1173 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1175 FileFormatI format = chooser.getSelectedFormat();
1178 * Call IdentifyFile to verify the file contains what its extension implies.
1179 * Skip this step for dynamically added file formats, because
1180 * IdentifyFile does not know how to recognise them.
1182 if (FileFormats.getInstance().isIdentifiable(format))
1186 format = new IdentifyFile().identify(selectedFile,
1187 DataSourceType.FILE);
1188 } catch (FileFormatException e)
1190 // format = null; //??
1194 new FileLoader().LoadFile(viewport, selectedFile,
1195 DataSourceType.FILE, format);
1198 chooser.showOpenDialog(this);
1202 * Shows a dialog for input of a URL at which to retrieve alignment data
1207 public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
1209 // This construct allows us to have a wider textfield
1211 JLabel label = new JLabel(
1212 MessageManager.getString("label.input_file_url"));
1214 JPanel panel = new JPanel(new GridLayout(2, 1));
1218 * the URL to fetch is
1219 * Java: an editable combobox with history
1220 * JS: (pending JAL-3038) a plain text field
1223 String urlBase = "http://www.";
1224 if (Platform.isJS())
1226 history = new JTextField(urlBase, 35);
1235 JComboBox<String> asCombo = new JComboBox<>();
1236 asCombo.setPreferredSize(new Dimension(400, 20));
1237 asCombo.setEditable(true);
1238 asCombo.addItem(urlBase);
1239 String historyItems = Cache.getProperty("RECENT_URL");
1240 if (historyItems != null)
1242 for (String token : historyItems.split("\\t"))
1244 asCombo.addItem(token);
1251 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1252 MessageManager.getString("action.cancel") };
1253 Runnable action = new Runnable()
1258 @SuppressWarnings("unchecked")
1259 String url = (history instanceof JTextField ? ((JTextField) history).getText()
1260 : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
1262 if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) {
1263 if (viewport != null) {
1264 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
1266 new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
1269 FileFormatI format = null;
1272 format = new IdentifyFile().identify(url, DataSourceType.URL);
1273 } catch (FileFormatException e)
1275 // TODO revise error handling, distinguish between
1276 // URL not found and response not valid
1281 String msg = MessageManager
1282 .formatMessage("label.couldnt_locate", url);
1283 JvOptionPane.showInternalMessageDialog(getDesktopPane(), msg,
1284 MessageManager.getString("label.url_not_found"),
1285 JvOptionPane.WARNING_MESSAGE);
1290 if (viewport != null)
1292 new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
1297 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1302 String dialogOption = MessageManager
1303 .getString("label.input_alignment_from_url");
1304 JvOptionPane.newOptionDialog(desktopPane).setResponseHandler(0, action)
1305 .showInternalDialog(panel, dialogOption,
1306 JvOptionPane.YES_NO_CANCEL_OPTION,
1307 JvOptionPane.PLAIN_MESSAGE, null, options,
1308 MessageManager.getString("action.ok"));
1312 * Opens the CutAndPaste window for the user to paste an alignment in to
1315 * - if not null, the pasted alignment is added to the current
1316 * alignment; if null, to a new alignment window
1319 public void inputTextboxMenuItem_actionPerformed(
1320 AlignmentViewPanel viewPanel)
1322 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1323 cap.setForInput(viewPanel);
1324 addInternalFrame(cap,
1325 MessageManager.getString("label.cut_paste_alignmen_file"),
1326 FRAME_MAKE_VISIBLE, 600, 500, FRAME_ALLOW_RESIZE,
1327 FRAME_SET_MIN_SIZE_300);
1336 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1337 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1338 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1339 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
1340 getWidth(), getHeight()));
1342 if (jconsole != null)
1344 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1345 jconsole.stopConsole();
1349 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1352 if (dialogExecutor != null)
1354 dialogExecutor.shutdownNow();
1356 closeAll_actionPerformed(null);
1358 if (groovyConsole != null)
1360 // suppress a possible repeat prompt to save script
1361 groovyConsole.setDirty(false);
1362 groovyConsole.exit();
1367 private void storeLastKnownDimensions(String string, Rectangle jc)
1369 Cache.log.debug("Storing last known dimensions for " + string + ": x:"
1370 + jc.x + " y:" + jc.y + " width:" + jc.width + " height:"
1373 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1374 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1375 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1376 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1386 public void aboutMenuItem_actionPerformed(ActionEvent e)
1388 new Thread(new Runnable()
1393 new SplashScreen(false);
1399 * Returns the html text for the About screen, including any available version
1400 * number, build details, author details and citation reference, but without
1401 * the enclosing {@code html} tags
1405 public String getAboutMessage()
1407 StringBuilder message = new StringBuilder(1024);
1408 message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
1409 .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
1410 .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
1411 .append(Cache.getBuildDetailsForSplash()).append("</strong>");
1413 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1414 if (latestVersion.equals("Checking")) {
1415 // JBP removed this message for 2.11: May be reinstated in future version
1416 // message.append("<br>...Checking latest version...</br>");
1417 } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
1418 boolean red = false;
1419 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT).indexOf("automated build") == -1) {
1421 // Displayed when code version and jnlp version do not match and code
1422 // version is not a development build
1423 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1426 message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1427 .append(" is available for download from ")
1428 .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
1430 message.append("</div>");
1433 message.append("<br>Authors: ");
1434 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1435 message.append(CITATION);
1437 message.append("</div>");
1438 return message.toString();
1442 * Action on requesting Help documentation
1445 public void documentationMenuItem_actionPerformed() {
1447 if (Platform.isJS()) {
1448 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1456 Help.showHelpWindow();
1458 } catch (Exception ex) {
1459 System.err.println("Error opening help: " + ex.getMessage());
1464 public void closeAll_actionPerformed(ActionEvent e)
1466 // TODO show a progress bar while closing?
1467 JInternalFrame[] frames = desktopPane.getAllFrames();
1468 for (int i = 0; i < frames.length; i++)
1472 frames[i].setClosed(true);
1473 } catch (java.beans.PropertyVetoException ex)
1477 Jalview.setCurrentAlignFrame(null);
1478 System.out.println("ALL CLOSED");
1481 * reset state of singleton objects as appropriate (clear down session state
1482 * when all windows are closed)
1484 StructureSelectionManager ssm = StructureSelectionManager
1485 .getStructureSelectionManager(this);
1493 public void raiseRelated_actionPerformed(ActionEvent e)
1495 reorderAssociatedWindows(false, false);
1499 public void minimizeAssociated_actionPerformed(ActionEvent e)
1501 reorderAssociatedWindows(true, false);
1504 void closeAssociatedWindows()
1506 reorderAssociatedWindows(false, true);
1512 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1516 protected void garbageCollect_actionPerformed(ActionEvent e)
1518 // We simply collect the garbage
1519 Cache.log.debug("Collecting garbage...");
1521 Cache.log.debug("Finished garbage collection.");
1528 * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
1532 protected void showMemusage_actionPerformed(ActionEvent e)
1534 desktopPane.showMemoryUsage(showMemusage.isSelected());
1541 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1545 protected void showConsole_actionPerformed(ActionEvent e)
1547 showConsole(showConsole.isSelected());
1550 Console jconsole = null;
1553 * control whether the java console is visible or not
1557 void showConsole(boolean selected)
1559 // TODO: decide if we should update properties file
1560 if (jconsole != null) // BH 2018
1562 showConsole.setSelected(selected);
1563 Cache.setProperty("SHOW_JAVA_CONSOLE",
1564 Boolean.valueOf(selected).toString());
1565 jconsole.setVisible(selected);
1569 void reorderAssociatedWindows(boolean minimize, boolean close)
1571 JInternalFrame[] frames = desktopPane.getAllFrames();
1572 if (frames == null || frames.length < 1)
1577 AlignViewportI source = null;
1578 AlignViewportI target = null;
1579 if (frames[0] instanceof AlignFrame)
1581 source = ((AlignFrame) frames[0]).getCurrentView();
1583 else if (frames[0] instanceof TreePanel)
1585 source = ((TreePanel) frames[0]).getViewPort();
1587 else if (frames[0] instanceof PCAPanel)
1589 source = ((PCAPanel) frames[0]).av;
1591 else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
1593 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1598 for (int i = 0; i < frames.length; i++)
1601 if (frames[i] == null)
1605 if (frames[i] instanceof AlignFrame)
1607 target = ((AlignFrame) frames[i]).getCurrentView();
1609 else if (frames[i] instanceof TreePanel)
1611 target = ((TreePanel) frames[i]).getViewPort();
1613 else if (frames[i] instanceof PCAPanel)
1615 target = ((PCAPanel) frames[i]).av;
1617 else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
1619 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1622 if (source == target)
1628 frames[i].setClosed(true);
1632 frames[i].setIcon(minimize);
1635 frames[i].toFront();
1639 } catch (java.beans.PropertyVetoException ex)
1654 protected void preferences_actionPerformed(ActionEvent e) {
1655 Preferences.openPreferences();
1659 * Prompts the user to choose a file and then saves the Jalview state as a
1660 * Jalview project file
1663 public void saveState_actionPerformed()
1665 saveState_actionPerformed(false);
1668 public void saveState_actionPerformed(boolean saveAs)
1670 java.io.File projectFile = getProjectFile();
1671 // autoSave indicates we already have a file and don't need to ask
1672 boolean autoSave = projectFile != null && !saveAs
1673 && BackupFiles.getEnabled();
1675 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1676 // saveAs="+saveAs+", Backups
1677 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1679 boolean approveSave = false;
1682 JalviewFileChooser chooser = new JalviewFileChooser("jvp",
1685 chooser.setFileView(new JalviewFileView());
1686 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1688 int value = chooser.showSaveDialog(this);
1690 if (value == JalviewFileChooser.APPROVE_OPTION)
1692 projectFile = chooser.getSelectedFile();
1693 setProjectFile(projectFile);
1697 if (approveSave || autoSave) {
1698 final Desktop me = this;
1699 final java.io.File chosenFile = projectFile;
1700 new Thread(new Runnable()
1705 // TODO: refactor to Jalview desktop session controller action.
1706 setProgressBar(MessageManager.formatMessage(
1707 "label.saving_jalview_project", new Object[]
1708 { chosenFile.getName() }), chosenFile.hashCode());
1709 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1710 // TODO catch and handle errors for savestate
1711 // TODO prevent user from messing with the Desktop whilst we're saving
1714 boolean doBackup = BackupFiles.getEnabled();
1715 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
1718 new Jalview2XML().saveState(
1719 doBackup ? backupfiles.getTempFile() : chosenFile);
1723 backupfiles.setWriteSuccess(true);
1724 backupfiles.rollBackupsAndRenameTempFile();
1726 } catch (OutOfMemoryError oom)
1728 new OOMWarning("Whilst saving current state to "
1729 + chosenFile.getName(), oom);
1730 } catch (Exception ex)
1732 Cache.log.error("Problems whilst trying to save to "
1733 + chosenFile.getName(), ex);
1734 JvOptionPane.showMessageDialog(me,
1735 MessageManager.formatMessage(
1736 "label.error_whilst_saving_current_state_to",
1738 { chosenFile.getName() }),
1739 MessageManager.getString("label.couldnt_save_project"),
1740 JvOptionPane.WARNING_MESSAGE);
1742 setProgressBar(null, chosenFile.hashCode());
1749 public void saveAsState_actionPerformed(ActionEvent e)
1751 saveState_actionPerformed(true);
1754 private void setProjectFile(File choice)
1756 this.projectFile = choice;
1759 public File getProjectFile()
1761 return this.projectFile;
1765 * Shows a file chooser dialog and tries to read in the selected file as a
1769 public void loadState_actionPerformed()
1771 final String[] suffix = new String[] { "jvp", "jar" };
1772 final String[] desc = new String[] { "Jalview Project",
1773 "Jalview Project (old)" };
1774 JalviewFileChooser chooser = new JalviewFileChooser(
1775 Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1776 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1780 chooser.setFileView(new JalviewFileView());
1781 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1782 chooser.setResponseHandler(0, new Runnable()
1787 File selectedFile = chooser.getSelectedFile();
1788 setProjectFile(selectedFile);
1789 String choice = selectedFile.getAbsolutePath();
1790 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1791 new Thread(new Runnable()
1798 new Jalview2XML().loadJalviewAlign(selectedFile);
1799 } catch (OutOfMemoryError oom)
1801 new OOMWarning("Whilst loading project from " + choice, oom);
1802 } catch (Exception ex)
1805 "Problems whilst loading project from " + choice, ex);
1806 JvOptionPane.showMessageDialog(getDesktopPane(),
1807 MessageManager.formatMessage(
1808 "label.error_whilst_loading_project_from",
1812 .getString("label.couldnt_load_project"),
1813 JvOptionPane.WARNING_MESSAGE);
1816 }, "Project Loader").start();
1819 chooser.showOpenDialog(this);
1823 public void inputSequence_actionPerformed(ActionEvent e)
1825 new SequenceFetcher(this);
1828 JPanel progressPanel;
1830 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1832 public void startLoading(final Object fileName)
1834 if (fileLoadingCount == 0)
1836 fileLoadingPanels.add(addProgressPanel(MessageManager
1837 .formatMessage("label.loading_file", new Object[]
1843 private JPanel addProgressPanel(String string)
1845 if (progressPanel == null)
1847 progressPanel = new JPanel(new GridLayout(1, 1));
1848 totalProgressCount = 0;
1849 getContentPane().add(progressPanel, BorderLayout.SOUTH);
1851 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1852 JProgressBar progressBar = new JProgressBar();
1853 progressBar.setIndeterminate(true);
1855 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1857 thisprogress.add(progressBar, BorderLayout.CENTER);
1858 progressPanel.add(thisprogress);
1859 ((GridLayout) progressPanel.getLayout()).setRows(
1860 ((GridLayout) progressPanel.getLayout()).getRows() + 1);
1861 ++totalProgressCount;
1863 return thisprogress;
1866 int totalProgressCount = 0;
1868 private void removeProgressPanel(JPanel progbar)
1870 if (progressPanel != null)
1872 synchronized (progressPanel)
1874 progressPanel.remove(progbar);
1875 GridLayout gl = (GridLayout) progressPanel.getLayout();
1876 gl.setRows(gl.getRows() - 1);
1877 if (--totalProgressCount < 1)
1879 this.getContentPane().remove(progressPanel);
1880 progressPanel = null;
1887 public void stopLoading()
1890 if (fileLoadingCount < 1)
1892 while (fileLoadingPanels.size() > 0)
1894 removeProgressPanel(fileLoadingPanels.remove(0));
1896 fileLoadingPanels.clear();
1897 fileLoadingCount = 0;
1902 public static int getViewCount(String alignmentId)
1904 AlignmentViewport[] aps = getViewports(alignmentId);
1905 return (aps == null) ? 0 : aps.length;
1910 * @param alignmentId
1911 * - if null, all sets are returned
1912 * @return all AlignmentPanels concerning the alignmentId sequence set
1914 public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
1916 if (getDesktopPane() == null)
1918 // no frames created and in headless mode
1919 // TODO: verify that frames are recoverable when in headless mode
1922 List<AlignmentPanel> aps = new ArrayList<>();
1923 AlignFrame[] frames = getAlignFrames();
1928 for (AlignFrame af : frames)
1930 for (AlignmentPanel ap : af.alignPanels)
1932 if (alignmentId == null
1933 || alignmentId.equals(ap.av.getSequenceSetId()))
1939 if (aps.size() == 0)
1943 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1948 * get all the viewports on an alignment.
1950 * @param sequenceSetId
1951 * unique alignment id (may be null - all viewports returned in that
1953 * @return all viewports on the alignment bound to sequenceSetId
1955 public static AlignmentViewport[] getViewports(String sequenceSetId)
1957 List<AlignmentViewport> viewp = new ArrayList<>();
1958 if (getDesktopPane() != null)
1960 AlignFrame[] frames = getAlignFrames();
1962 for (AlignFrame afr : frames)
1964 if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
1965 .equals(sequenceSetId))
1967 if (afr.alignPanels != null)
1969 for (AlignmentPanel ap : afr.alignPanels)
1971 if (sequenceSetId == null
1972 || sequenceSetId.equals(ap.av.getSequenceSetId()))
1980 viewp.add(afr.getViewport());
1984 if (viewp.size() > 0)
1986 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1993 * Explode the views in the given frame into separate AlignFrame
1997 public static void explodeViews(AlignFrame af)
1999 int size = af.alignPanels.size();
2005 // FIXME: ideally should use UI interface API
2006 FeatureSettings viewFeatureSettings = (af.featureSettings != null
2007 && af.featureSettings.isOpen()) ? af.featureSettings : null;
2008 Rectangle fsBounds = af.getFeatureSettingsGeometry();
2009 for (int i = 0; i < size; i++)
2011 AlignmentPanel ap = af.alignPanels.get(i);
2013 AlignFrame newaf = new AlignFrame(ap);
2015 // transfer reference for existing feature settings to new alignFrame
2016 if (ap == af.alignPanel)
2018 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
2020 newaf.featureSettings = viewFeatureSettings;
2022 newaf.setFeatureSettingsGeometry(fsBounds);
2026 * Restore the view's last exploded frame geometry if known. Multiple
2027 * views from one exploded frame share and restore the same (frame)
2028 * position and size.
2030 Rectangle geometry = ap.av.getExplodedGeometry();
2031 if (geometry != null)
2033 newaf.setBounds(geometry);
2036 ap.av.setGatherViewsHere(false);
2038 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
2039 AlignFrame.DEFAULT_HEIGHT);
2040 // and materialise a new feature settings dialog instance for the new
2042 // (closes the old as if 'OK' was pressed)
2043 if (ap == af.alignPanel && newaf.featureSettings != null
2044 && newaf.featureSettings.isOpen()
2045 && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
2047 newaf.showFeatureSettingsUI();
2051 af.featureSettings = null;
2052 af.alignPanels.clear();
2053 af.closeMenuItem_actionPerformed(true);
2058 * Gather expanded views (separate AlignFrame's) with the same sequence set
2059 * identifier back in to this frame as additional views, and close the
2060 * expanded views. Note the expanded frames may themselves have multiple
2061 * views. We take the lot.
2065 public void gatherViews(AlignFrame source)
2067 source.viewport.setGatherViewsHere(true);
2068 source.viewport.setExplodedGeometry(source.getBounds());
2069 JInternalFrame[] frames = desktopPane.getAllFrames();
2070 String viewId = source.viewport.getSequenceSetId();
2071 for (int t = 0; t < frames.length; t++)
2073 if (frames[t] instanceof AlignFrame && frames[t] != source)
2075 AlignFrame af = (AlignFrame) frames[t];
2076 boolean gatherThis = false;
2077 for (int a = 0; a < af.alignPanels.size(); a++)
2079 AlignmentPanel ap = af.alignPanels.get(a);
2080 if (viewId.equals(ap.av.getSequenceSetId()))
2083 ap.av.setGatherViewsHere(false);
2084 ap.av.setExplodedGeometry(af.getBounds());
2085 source.addAlignmentPanel(ap, false);
2091 if (af.featureSettings != null && af.featureSettings.isOpen())
2093 if (source.featureSettings == null)
2095 // preserve the feature settings geometry for this frame
2096 source.featureSettings = af.featureSettings;
2097 source.setFeatureSettingsGeometry(
2098 af.getFeatureSettingsGeometry());
2102 // close it and forget
2103 af.featureSettings.close();
2106 af.alignPanels.clear();
2107 af.closeMenuItem_actionPerformed(true);
2112 // refresh the feature setting UI for the source frame if it exists
2113 if (source.featureSettings != null && source.featureSettings.isOpen())
2115 source.showFeatureSettingsUI();
2119 public JInternalFrame[] getAllFrames()
2121 return desktopPane.getAllFrames();
2125 * Checks the given url to see if it gives a response indicating that the user
2126 * should be informed of a new questionnaire.
2130 public void checkForQuestionnaire(String url)
2132 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
2133 // javax.swing.SwingUtilities.invokeLater(jvq);
2134 new Thread(jvq).start();
2137 public void checkURLLinks()
2139 // Thread off the URL link checker
2140 addDialogThread(new Runnable()
2145 if (Cache.getDefault("CHECKURLLINKS", true))
2147 // check what the actual links are - if it's just the default don't
2148 // bother with the warning
2149 List<String> links = Preferences.sequenceUrlLinks
2152 // only need to check links if there is one with a
2153 // SEQUENCE_ID which is not the default EMBL_EBI link
2154 ListIterator<String> li = links.listIterator();
2155 boolean check = false;
2156 List<JLabel> urls = new ArrayList<>();
2157 while (li.hasNext())
2159 String link = li.next();
2160 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
2161 && !UrlConstants.isDefaultString(link))
2164 int barPos = link.indexOf("|");
2165 String urlMsg = barPos == -1 ? link
2166 : link.substring(0, barPos) + ": "
2167 + link.substring(barPos + 1);
2168 urls.add(new JLabel(urlMsg));
2176 // ask user to check in case URL links use old style tokens
2177 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
2178 JPanel msgPanel = new JPanel();
2179 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
2180 msgPanel.add(Box.createVerticalGlue());
2181 JLabel msg = new JLabel(MessageManager
2182 .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
2183 JLabel msg2 = new JLabel(MessageManager
2184 .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
2186 for (JLabel url : urls)
2192 final JCheckBox jcb = new JCheckBox(
2193 MessageManager.getString("label.do_not_display_again"));
2194 jcb.addActionListener(new ActionListener()
2197 public void actionPerformed(ActionEvent e)
2199 // update Cache settings for "don't show this again"
2200 boolean showWarningAgain = !jcb.isSelected();
2201 Cache.setProperty("CHECKURLLINKS",
2202 Boolean.valueOf(showWarningAgain).toString());
2207 JvOptionPane.showMessageDialog(desktopPane, msgPanel,
2209 .getString("label.SEQUENCE_ID_no_longer_used"),
2210 JvOptionPane.WARNING_MESSAGE);
2217 * Proxy class for JDesktopPane which optionally displays the current memory
2218 * usage and highlights the desktop area with a red bar if free memory runs
2223 public class MyDesktopPane extends JDesktopPane implements Runnable
2225 private static final float ONE_MB = 1048576f;
2227 boolean showMemoryUsage = false;
2231 java.text.NumberFormat df;
2233 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
2236 public MyDesktopPane(boolean showMemoryUsage)
2238 showMemoryUsage(showMemoryUsage);
2241 public void showMemoryUsage(boolean showMemory)
2243 this.showMemoryUsage = showMemory;
2246 Thread worker = new Thread(this);
2252 public boolean isShowMemoryUsage()
2254 return showMemoryUsage;
2260 df = java.text.NumberFormat.getNumberInstance();
2261 df.setMaximumFractionDigits(2);
2262 runtime = Runtime.getRuntime();
2264 while (showMemoryUsage)
2268 maxMemory = runtime.maxMemory() / ONE_MB;
2269 allocatedMemory = runtime.totalMemory() / ONE_MB;
2270 freeMemory = runtime.freeMemory() / ONE_MB;
2271 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
2273 percentUsage = (totalFreeMemory / maxMemory) * 100;
2275 // if (percentUsage < 20)
2277 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
2279 // instance.set.setBorder(border1);
2282 // sleep after showing usage
2284 } catch (Exception ex)
2286 ex.printStackTrace();
2292 public void paintComponent(Graphics g)
2294 if (showMemoryUsage && g != null && df != null)
2296 if (percentUsage < 20)
2298 g.setColor(Color.red);
2300 FontMetrics fm = g.getFontMetrics();
2303 g.drawString(MessageManager.formatMessage("label.memory_stats",
2305 { df.format(totalFreeMemory), df.format(maxMemory),
2306 df.format(percentUsage) }),
2307 10, getHeight() - fm.getHeight());
2310 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
2311 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
2316 * Accessor method to quickly get all the AlignmentFrames loaded.
2318 * @return an array of AlignFrame, or null if none found
2320 public static AlignFrame[] getAlignFrames()
2322 if (Jalview.isHeadlessMode())
2324 return new AlignFrame[] { Jalview.getInstance().currentAlignFrame };
2327 JInternalFrame[] frames = getDesktopPane().getAllFrames();
2333 List<AlignFrame> avp = new ArrayList<>();
2335 for (int i = frames.length - 1; i > -1; i--)
2337 if (frames[i] instanceof AlignFrame)
2339 avp.add((AlignFrame) frames[i]);
2341 else if (frames[i] instanceof SplitFrame)
2344 * Also check for a split frame containing an AlignFrame
2346 GSplitFrame sf = (GSplitFrame) frames[i];
2347 if (sf.getTopFrame() instanceof AlignFrame)
2349 avp.add((AlignFrame) sf.getTopFrame());
2351 if (sf.getBottomFrame() instanceof AlignFrame)
2353 avp.add((AlignFrame) sf.getBottomFrame());
2357 if (avp.size() == 0)
2361 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
2366 * Returns an array of any AppJmol frames in the Desktop (or null if none).
2370 public GStructureViewer[] getJmols()
2372 JInternalFrame[] frames = desktopPane.getAllFrames();
2378 List<GStructureViewer> avp = new ArrayList<>();
2380 for (int i = frames.length - 1; i > -1; i--)
2382 if (frames[i] instanceof AppJmol)
2384 GStructureViewer af = (GStructureViewer) frames[i];
2388 if (avp.size() == 0)
2392 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2397 * Add Groovy Support to Jalview
2400 public void groovyShell_actionPerformed()
2404 openGroovyConsole();
2405 } catch (Exception ex)
2407 Cache.log.error("Groovy Shell Creation failed.", ex);
2408 JvOptionPane.showInternalMessageDialog(desktopPane,
2410 MessageManager.getString("label.couldnt_create_groovy_shell"),
2411 MessageManager.getString("label.groovy_support_failed"),
2412 JvOptionPane.ERROR_MESSAGE);
2417 * Open the Groovy console
2419 void openGroovyConsole()
2421 if (groovyConsole == null)
2423 groovyConsole = new groovy.ui.Console();
2424 groovyConsole.setVariable("Jalview", this);
2425 groovyConsole.run();
2428 * We allow only one console at a time, so that AlignFrame menu option
2429 * 'Calculate | Run Groovy script' is unambiguous.
2430 * Disable 'Groovy Console', and enable 'Run script', when the console is
2431 * opened, and the reverse when it is closed
2433 Window window = (Window) groovyConsole.getFrame();
2434 window.addWindowListener(new WindowAdapter()
2437 public void windowClosed(WindowEvent e)
2440 * rebind CMD-Q from Groovy Console to Jalview Quit
2443 enableExecuteGroovy(false);
2449 * show Groovy console window (after close and reopen)
2451 ((Window) groovyConsole.getFrame()).setVisible(true);
2454 * if we got this far, enable 'Run Groovy' in AlignFrame menus
2455 * and disable opening a second console
2457 enableExecuteGroovy(true);
2461 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
2462 * binding when opened
2464 protected void addQuitHandler()
2467 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
2468 .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
2469 Platform.SHORTCUT_KEY_MASK),
2471 getRootPane().getActionMap().put("Quit", new AbstractAction()
2474 public void actionPerformed(ActionEvent e)
2482 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2485 * true if Groovy console is open
2487 public void enableExecuteGroovy(boolean enabled)
2490 * disable opening a second Groovy console
2491 * (or re-enable when the console is closed)
2493 groovyShell.setEnabled(!enabled);
2495 AlignFrame[] alignFrames = getAlignFrames();
2496 if (alignFrames != null)
2498 for (AlignFrame af : alignFrames)
2500 af.setGroovyEnabled(enabled);
2506 * Progress bars managed by the IProgressIndicator method.
2508 private Hashtable<Long, JPanel> progressBars;
2510 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2515 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2518 public void setProgressBar(String message, long id)
2520 // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
2522 if (progressBars == null)
2524 progressBars = new Hashtable<>();
2525 progressBarHandlers = new Hashtable<>();
2528 if (progressBars.get(Long.valueOf(id)) != null)
2530 JPanel panel = progressBars.remove(Long.valueOf(id));
2531 if (progressBarHandlers.contains(Long.valueOf(id)))
2533 progressBarHandlers.remove(Long.valueOf(id));
2535 removeProgressPanel(panel);
2539 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2544 public void removeProgressBar(long id)
2547 throw new UnsupportedOperationException("not implemented");
2553 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2554 * jalview.gui.IProgressIndicatorHandler)
2557 public void registerHandler(final long id,
2558 final IProgressIndicatorHandler handler)
2560 if (progressBarHandlers == null
2561 || !progressBars.containsKey(Long.valueOf(id)))
2563 throw new Error(MessageManager.getString(
2564 "error.call_setprogressbar_before_registering_handler"));
2566 progressBarHandlers.put(Long.valueOf(id), handler);
2567 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2568 if (handler.canCancel())
2570 JButton cancel = new JButton(
2571 MessageManager.getString("action.cancel"));
2572 final IProgressIndicator us = this;
2573 cancel.addActionListener(new ActionListener()
2577 public void actionPerformed(ActionEvent e)
2579 handler.cancelActivity(id);
2580 us.setProgressBar(MessageManager
2581 .formatMessage("label.cancelled_params", new Object[]
2582 { ((JLabel) progressPanel.getComponent(0)).getText() }),
2586 progressPanel.add(cancel, BorderLayout.EAST);
2592 * @return true if any progress bars are still active
2595 public boolean operationInProgress()
2597 if (progressBars != null && progressBars.size() > 0)
2605 * This will return the first AlignFrame holding the given viewport instance.
2606 * It will break if there are more than one AlignFrames viewing a particular
2610 * @return alignFrame for viewport
2612 public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
2614 if (getDesktopPane() != null)
2616 AlignmentPanel[] aps = getAlignmentPanels(
2617 viewport.getSequenceSetId());
2618 for (int panel = 0; aps != null && panel < aps.length; panel++)
2620 if (aps[panel] != null && aps[panel].av == viewport)
2622 return aps[panel].alignFrame;
2629 public VamsasApplication getVamsasApplication()
2631 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2637 * flag set if jalview GUI is being operated programmatically
2639 private boolean inBatchMode = false;
2642 * check if jalview GUI is being operated programmatically
2644 * @return inBatchMode
2646 public boolean isInBatchMode()
2652 * set flag if jalview GUI is being operated programmatically
2654 * @param inBatchMode
2656 public void setInBatchMode(boolean inBatchMode)
2658 this.inBatchMode = inBatchMode;
2661 public void startServiceDiscovery()
2663 startServiceDiscovery(false);
2666 public void startServiceDiscovery(boolean blocking)
2668 System.out.println("Starting service discovery");
2669 var tasks = new ArrayList<Future<?>>();
2670 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2672 System.out.println("loading services");
2676 // todo: changesupport handlers need to be transferred
2677 if (discoverer == null)
2679 discoverer = jalview.ws.jws1.Discoverer.getInstance();
2680 // register PCS handler for desktop.
2681 discoverer.addPropertyChangeListener(changeSupport);
2683 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2684 // until we phase out completely
2685 var f = new FutureTask<Void>(discoverer, null);
2686 new Thread(f).start();
2690 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
2692 tasks.add(jalview.ws.jws2.Jws2Discoverer.getInstance().startDiscoverer());
2694 if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
2696 tasks.add(jalview.ws.slivkaws.SlivkaWSDiscoverer.getInstance().startDiscoverer());
2700 for (Future<?> task : tasks) {
2703 // block until all discovery tasks are done
2705 } catch (Exception e)
2707 e.printStackTrace();
2714 * called to check if the service discovery process completed successfully.
2718 protected void JalviewServicesChanged(PropertyChangeEvent evt)
2720 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
2722 final WSDiscovererI discoverer = jalview.ws.jws2.Jws2Discoverer
2724 final String ermsg = discoverer.getErrorMessages();
2725 // CONFLICT:ALT:? final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
2728 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
2730 if (serviceChangedDialog == null)
2732 // only run if we aren't already displaying one of these.
2733 addDialogThread(serviceChangedDialog = new Runnable()
2740 * JalviewDialog jd =new JalviewDialog() {
2742 * @Override protected void cancelPressed() { // TODO
2743 * Auto-generated method stub
2745 * }@Override protected void okPressed() { // TODO
2746 * Auto-generated method stub
2748 * }@Override protected void raiseClosed() { // TODO
2749 * Auto-generated method stub
2751 * } }; jd.initDialogFrame(new
2752 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
2753 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2754 * + " or mis-configured HTTP proxy settings.<br/>" +
2755 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
2757 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
2758 * ), true, true, "Web Service Configuration Problem", 450,
2761 * jd.waitForInput();
2763 JvOptionPane.showConfirmDialog(desktopPane,
2764 new JLabel("<html><table width=\"450\"><tr><td>"
2765 + ermsg + "</td></tr></table>"
2766 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2767 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2768 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2769 + " Tools->Preferences dialog box to change them.</p></html>"),
2770 "Web Service Configuration Problem",
2771 JvOptionPane.DEFAULT_OPTION,
2772 JvOptionPane.ERROR_MESSAGE);
2773 serviceChangedDialog = null;
2782 "Errors reported by JABA discovery service. Check web services preferences.\n"
2789 private Runnable serviceChangedDialog = null;
2792 * start a thread to open a URL in the configured browser. Pops up a warning
2793 * dialog to the user if there is an exception when calling out to the browser
2798 public static void showUrl(final String url)
2800 showUrl(url, getInstance());
2804 * Like showUrl but allows progress handler to be specified
2808 * (null) or object implementing IProgressIndicator
2810 public static void showUrl(final String url,
2811 final IProgressIndicator progress)
2813 new Thread(new Runnable()
2820 if (progress != null)
2822 progress.setProgressBar(MessageManager
2823 .formatMessage("status.opening_params", new Object[]
2824 { url }), this.hashCode());
2826 jalview.util.BrowserLauncher.openURL(url);
2827 } catch (Exception ex)
2829 JvOptionPane.showInternalMessageDialog(getDesktopPane(),
2831 .getString("label.web_browser_not_found_unix"),
2832 MessageManager.getString("label.web_browser_not_found"),
2833 JvOptionPane.WARNING_MESSAGE);
2835 ex.printStackTrace();
2837 if (progress != null)
2839 progress.setProgressBar(null, this.hashCode());
2845 public static WsParamSetManager wsparamManager = null;
2847 public static ParamManager getUserParameterStore()
2849 if (wsparamManager == null)
2851 wsparamManager = new WsParamSetManager();
2853 return wsparamManager;
2857 * static hyperlink handler proxy method for use by Jalview's internal windows
2861 public static void hyperlinkUpdate(HyperlinkEvent e)
2863 if (e.getEventType() == EventType.ACTIVATED)
2868 url = e.getURL().toString();
2870 } catch (Exception x)
2874 if (Cache.log != null)
2876 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2881 "Couldn't handle string " + url + " as a URL.");
2884 // ignore any exceptions due to dud links.
2891 * single thread that handles display of dialogs to user.
2893 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2896 * flag indicating if dialogExecutor should try to acquire a permit
2898 private volatile boolean dialogPause = true;
2903 private java.util.concurrent.Semaphore block = new Semaphore(0);
2905 private static groovy.ui.Console groovyConsole;
2908 * add another dialog thread to the queue
2912 public void addDialogThread(final Runnable prompter)
2914 dialogExecutor.submit(new Runnable()
2924 } catch (InterruptedException x)
2928 if (Jalview.isHeadlessMode())
2934 SwingUtilities.invokeAndWait(prompter);
2935 } catch (Exception q)
2937 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2943 public void startDialogQueue()
2945 // set the flag so we don't pause waiting for another permit and semaphore
2946 // the current task to begin
2947 dialogPause = false;
2952 * Outputs an image of the desktop to file in EPS format, after prompting the
2953 * user for choice of Text or Lineart character rendering (unless a preference
2954 * has been set). The file name is generated as
2957 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2961 protected void snapShotWindow_actionPerformed(ActionEvent e)
2963 // currently the menu option to do this is not shown
2966 int width = getWidth();
2967 int height = getHeight();
2969 "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2970 ImageWriterI writer = new ImageWriterI()
2973 public void exportImage(Graphics g) throws Exception
2976 Cache.log.info("Successfully written snapshot to file "
2977 + of.getAbsolutePath());
2980 String title = "View of desktop";
2981 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
2983 exporter.doExport(of, this, width, height, title);
2987 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2988 * This respects (remembers) any previous 'exploded geometry' i.e. the size
2989 * and location last time the view was expanded (if any). However it does not
2990 * remember the split pane divider location - this is set to match the
2991 * 'exploding' frame.
2995 public void explodeViews(SplitFrame sf)
2997 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2998 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2999 List<? extends AlignmentViewPanel> topPanels = oldTopFrame
3001 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
3003 int viewCount = topPanels.size();
3010 * Processing in reverse order works, forwards order leaves the first panels
3011 * not visible. I don't know why!
3013 for (int i = viewCount - 1; i >= 0; i--)
3016 * Make new top and bottom frames. These take over the respective
3017 * AlignmentPanel objects, including their AlignmentViewports, so the
3018 * cdna/protein relationships between the viewports is carried over to the
3021 * explodedGeometry holds the (x, y) position of the previously exploded
3022 * SplitFrame, and the (width, height) of the AlignFrame component
3024 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
3025 AlignFrame newTopFrame = new AlignFrame(topPanel);
3026 newTopFrame.setSize(oldTopFrame.getSize());
3027 newTopFrame.setVisible(true);
3028 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
3029 .getExplodedGeometry();
3030 if (geometry != null)
3032 newTopFrame.setSize(geometry.getSize());
3035 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
3036 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
3037 newBottomFrame.setSize(oldBottomFrame.getSize());
3038 newBottomFrame.setVisible(true);
3039 geometry = ((AlignViewport) bottomPanel.getAlignViewport())
3040 .getExplodedGeometry();
3041 if (geometry != null)
3043 newBottomFrame.setSize(geometry.getSize());
3046 topPanel.av.setGatherViewsHere(false);
3047 bottomPanel.av.setGatherViewsHere(false);
3048 JInternalFrame splitFrame = new SplitFrame(newTopFrame,
3050 if (geometry != null)
3052 splitFrame.setLocation(geometry.getLocation());
3054 addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
3058 * Clear references to the panels (now relocated in the new SplitFrames)
3059 * before closing the old SplitFrame.
3062 bottomPanels.clear();
3067 * Gather expanded split frames, sharing the same pairs of sequence set ids,
3068 * back into the given SplitFrame as additional views. Note that the gathered
3069 * frames may themselves have multiple views.
3073 public void gatherViews(GSplitFrame source)
3076 * special handling of explodedGeometry for a view within a SplitFrame: - it
3077 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
3078 * height) of the AlignFrame component
3080 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
3081 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
3082 myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
3083 source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
3084 myBottomFrame.viewport
3085 .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
3086 myBottomFrame.getWidth(), myBottomFrame.getHeight()));
3087 myTopFrame.viewport.setGatherViewsHere(true);
3088 myBottomFrame.viewport.setGatherViewsHere(true);
3089 String topViewId = myTopFrame.viewport.getSequenceSetId();
3090 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
3092 JInternalFrame[] frames = desktopPane.getAllFrames();
3093 for (JInternalFrame frame : frames)
3095 if (frame instanceof SplitFrame && frame != source)
3097 SplitFrame sf = (SplitFrame) frame;
3098 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
3099 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
3100 boolean gatherThis = false;
3101 for (int a = 0; a < topFrame.alignPanels.size(); a++)
3103 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
3104 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
3105 if (topViewId.equals(topPanel.av.getSequenceSetId())
3106 && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
3109 topPanel.av.setGatherViewsHere(false);
3110 bottomPanel.av.setGatherViewsHere(false);
3111 topPanel.av.setExplodedGeometry(
3112 new Rectangle(sf.getLocation(), topFrame.getSize()));
3113 bottomPanel.av.setExplodedGeometry(
3114 new Rectangle(sf.getLocation(), bottomFrame.getSize()));
3115 myTopFrame.addAlignmentPanel(topPanel, false);
3116 myBottomFrame.addAlignmentPanel(bottomPanel, false);
3122 topFrame.getAlignPanels().clear();
3123 bottomFrame.getAlignPanels().clear();
3130 * The dust settles...give focus to the tab we did this from.
3132 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
3135 public static groovy.ui.Console getGroovyConsole()
3137 return groovyConsole;
3141 * handles the payload of a drag and drop event.
3143 * TODO refactor to desktop utilities class
3146 * - Data source strings extracted from the drop event
3148 * - protocol for each data source extracted from the drop event
3152 * - the payload from the drop event
3155 @SuppressWarnings("unchecked")
3156 public static void transferFromDropTarget(List<Object> files,
3157 List<DataSourceType> protocols, DropTargetDropEvent evt,
3158 Transferable t) throws Exception
3161 // BH 2018 changed List<String> to List<Object> to allow for File from
3164 // DataFlavor[] flavors = t.getTransferDataFlavors();
3165 // for (int i = 0; i < flavors.length; i++) {
3166 // if (flavors[i].isFlavorJavaFileListType()) {
3167 // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
3168 // List<File> list = (List<File>) t.getTransferData(flavors[i]);
3169 // for (int j = 0; j < list.size(); j++) {
3170 // File file = (File) list.get(j);
3171 // byte[] data = getDroppedFileBytes(file);
3172 // fileName.setText(file.getName() + " - " + data.length + " " +
3173 // evt.getLocation());
3174 // JTextArea target = (JTextArea) ((DropTarget)
3175 // evt.getSource()).getComponent();
3176 // target.setText(new String(data));
3178 // dtde.dropComplete(true);
3183 DataFlavor uriListFlavor = new DataFlavor(
3184 "text/uri-list;class=java.lang.String"), urlFlavour = null;
3187 urlFlavour = new DataFlavor(
3188 "application/x-java-url; class=java.net.URL");
3189 } catch (ClassNotFoundException cfe)
3191 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
3194 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
3199 java.net.URL url = (URL) t.getTransferData(urlFlavour);
3200 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
3201 // means url may be null.
3204 protocols.add(DataSourceType.URL);
3205 files.add(url.toString());
3206 Cache.log.debug("Drop handled as URL dataflavor "
3207 + files.get(files.size() - 1));
3212 if (Platform.isAMacAndNotJS())
3215 "Please ignore plist error - occurs due to problem with java 8 on OSX");
3218 } catch (Throwable ex)
3220 Cache.log.debug("URL drop handler failed.", ex);
3223 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
3225 // Works on Windows and MacOSX
3226 Cache.log.debug("Drop handled as javaFileListFlavor");
3227 for (File file : (List<File>) t
3228 .getTransferData(DataFlavor.javaFileListFlavor))
3231 protocols.add(DataSourceType.FILE);
3236 // Unix like behaviour
3237 boolean added = false;
3239 if (t.isDataFlavorSupported(uriListFlavor))
3241 Cache.log.debug("Drop handled as uriListFlavor");
3242 // This is used by Unix drag system
3243 data = (String) t.getTransferData(uriListFlavor);
3247 // fallback to text: workaround - on OSX where there's a JVM bug
3248 Cache.log.debug("standard URIListFlavor failed. Trying text");
3249 // try text fallback
3250 DataFlavor textDf = new DataFlavor(
3251 "text/plain;class=java.lang.String");
3252 if (t.isDataFlavorSupported(textDf))
3254 data = (String) t.getTransferData(textDf);
3257 Cache.log.debug("Plain text drop content returned "
3258 + (data == null ? "Null - failed" : data));
3263 while (protocols.size() < files.size())
3265 Cache.log.debug("Adding missing FILE protocol for "
3266 + files.get(protocols.size()));
3267 protocols.add(DataSourceType.FILE);
3269 for (java.util.StringTokenizer st = new java.util.StringTokenizer(
3270 data, "\r\n"); st.hasMoreTokens();)
3273 String s = st.nextToken();
3274 if (s.startsWith("#"))
3276 // the line is a comment (as per the RFC 2483)
3279 java.net.URI uri = new java.net.URI(s);
3280 if (uri.getScheme().toLowerCase().startsWith("http"))
3282 protocols.add(DataSourceType.URL);
3283 files.add(uri.toString());
3287 // otherwise preserve old behaviour: catch all for file objects
3288 java.io.File file = new java.io.File(uri);
3289 protocols.add(DataSourceType.FILE);
3290 files.add(file.toString());
3295 if (Cache.log.isDebugEnabled())
3297 if (data == null || !added)
3300 if (t.getTransferDataFlavors() != null
3301 && t.getTransferDataFlavors().length > 0)
3304 "Couldn't resolve drop data. Here are the supported flavors:");
3305 for (DataFlavor fl : t.getTransferDataFlavors())
3308 "Supported transfer dataflavor: " + fl.toString());
3309 Object df = t.getTransferData(fl);
3312 Cache.log.debug("Retrieves: " + df);
3316 Cache.log.debug("Retrieved nothing");
3322 Cache.log.debug("Couldn't resolve dataflavor for drop: "
3328 if (Platform.isWindowsAndNotJS())
3330 Cache.log.debug("Scanning dropped content for Windows Link Files");
3332 // resolve any .lnk files in the file drop
3333 for (int f = 0; f < files.size(); f++)
3335 String source = files.get(f).toString().toLowerCase();
3336 if (protocols.get(f).equals(DataSourceType.FILE)
3337 && (source.endsWith(".lnk") || source.endsWith(".url")
3338 || source.endsWith(".site")))
3342 Object obj = files.get(f);
3343 File lf = (obj instanceof File ? (File) obj
3344 : new File((String) obj));
3345 // process link file to get a URL
3346 Cache.log.debug("Found potential link file: " + lf);
3347 WindowsShortcut wscfile = new WindowsShortcut(lf);
3348 String fullname = wscfile.getRealFilename();
3349 protocols.set(f, FormatAdapter.checkProtocol(fullname));
3350 files.set(f, fullname);
3351 Cache.log.debug("Parsed real filename " + fullname
3352 + " to extract protocol: " + protocols.get(f));
3353 } catch (Exception ex)
3356 "Couldn't parse " + files.get(f) + " as a link file.",
3365 * Sets the Preferences property for experimental features to True or False
3366 * depending on the state of the controlling menu item
3369 protected void showExperimental_actionPerformed(boolean selected)
3371 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
3375 * Answers a (possibly empty) list of any structure viewer frames (currently
3376 * for either Jmol or Chimera) which are currently open. This may optionally
3377 * be restricted to viewers of a specified class, or viewers linked to a
3378 * specified alignment panel.
3381 * if not null, only return viewers linked to this panel
3382 * @param structureViewerClass
3383 * if not null, only return viewers of this class
3386 public List<StructureViewerBase> getStructureViewers(
3387 AlignmentPanel apanel,
3388 Class<? extends StructureViewerBase> structureViewerClass)
3390 List<StructureViewerBase> result = new ArrayList<>();
3391 JInternalFrame[] frames = getAllFrames();
3393 for (JInternalFrame frame : frames)
3395 if (frame instanceof StructureViewerBase)
3397 if (structureViewerClass == null
3398 || structureViewerClass.isInstance(frame))
3401 || ((StructureViewerBase) frame).isLinkedWith(apanel))
3403 result.add((StructureViewerBase) frame);
3411 public static final String debugScaleMessage = "Desktop graphics transform scale=";
3413 private static boolean debugScaleMessageDone = false;
3415 public static void debugScaleMessage(Graphics g) {
3416 if (debugScaleMessageDone) {
3419 // output used by tests to check HiDPI scaling settings in action
3421 Graphics2D gg = (Graphics2D) g;
3423 AffineTransform t = gg.getTransform();
3424 double scaleX = t.getScaleX();
3425 double scaleY = t.getScaleY();
3426 Cache.debug(debugScaleMessage + scaleX + " (X)");
3427 Cache.debug(debugScaleMessage + scaleY + " (Y)");
3428 debugScaleMessageDone = true;
3430 Cache.debug("Desktop graphics null");
3432 } catch (Exception e) {
3433 Cache.debug(Cache.getStackTraceString(e));