2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.GridLayout;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.datatransfer.Clipboard;
35 import java.awt.datatransfer.ClipboardOwner;
36 import java.awt.datatransfer.DataFlavor;
37 import java.awt.datatransfer.Transferable;
38 import java.awt.dnd.DnDConstants;
39 import java.awt.dnd.DropTargetDragEvent;
40 import java.awt.dnd.DropTargetDropEvent;
41 import java.awt.dnd.DropTargetEvent;
42 import java.awt.dnd.DropTargetListener;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.InputEvent;
46 import java.awt.event.KeyEvent;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.WindowAdapter;
50 import java.awt.event.WindowEvent;
51 import java.awt.geom.AffineTransform;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.lang.reflect.Field;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Hashtable;
63 import java.util.List;
64 import java.util.ListIterator;
65 import java.util.Vector;
66 import java.util.concurrent.ExecutorService;
67 import java.util.concurrent.Executors;
68 import java.util.concurrent.Semaphore;
70 import javax.swing.AbstractAction;
71 import javax.swing.Action;
72 import javax.swing.ActionMap;
73 import javax.swing.Box;
74 import javax.swing.BoxLayout;
75 import javax.swing.DefaultDesktopManager;
76 import javax.swing.DesktopManager;
77 import javax.swing.InputMap;
78 import javax.swing.JButton;
79 import javax.swing.JCheckBox;
80 import javax.swing.JComboBox;
81 import javax.swing.JComponent;
82 import javax.swing.JDesktopPane;
83 import javax.swing.JInternalFrame;
84 import javax.swing.JLabel;
85 import javax.swing.JMenuItem;
86 import javax.swing.JPanel;
87 import javax.swing.JPopupMenu;
88 import javax.swing.JProgressBar;
89 import javax.swing.JTextField;
90 import javax.swing.KeyStroke;
91 import javax.swing.SwingUtilities;
92 import javax.swing.event.HyperlinkEvent;
93 import javax.swing.event.HyperlinkEvent.EventType;
94 import javax.swing.event.InternalFrameAdapter;
95 import javax.swing.event.InternalFrameEvent;
97 import org.stackoverflowusers.file.WindowsShortcut;
99 import jalview.api.AlignViewportI;
100 import jalview.api.AlignmentViewPanel;
101 import jalview.bin.Cache;
102 import jalview.bin.Jalview;
103 import jalview.gui.ImageExporter.ImageWriterI;
104 import jalview.io.BackupFiles;
105 import jalview.io.DataSourceType;
106 import jalview.io.FileFormat;
107 import jalview.io.FileFormatException;
108 import jalview.io.FileFormatI;
109 import jalview.io.FileFormats;
110 import jalview.io.FileLoader;
111 import jalview.io.FormatAdapter;
112 import jalview.io.IdentifyFile;
113 import jalview.io.JalviewFileChooser;
114 import jalview.io.JalviewFileView;
115 import jalview.jbgui.GSplitFrame;
116 import jalview.jbgui.GStructureViewer;
117 import jalview.project.Jalview2XML;
118 import jalview.structure.StructureSelectionManager;
119 import jalview.urls.IdOrgSettings;
120 import jalview.util.BrowserLauncher;
121 import jalview.util.ChannelProperties;
122 import jalview.util.ImageMaker.TYPE;
123 import jalview.util.MessageManager;
124 import jalview.util.Platform;
125 import jalview.util.ShortcutKeyMaskExWrapper;
126 import jalview.util.UrlConstants;
127 import jalview.viewmodel.AlignmentViewport;
128 import jalview.ws.params.ParamManager;
129 import jalview.ws.utils.UrlDownloadClient;
136 * @version $Revision: 1.155 $
138 public class Desktop extends jalview.jbgui.GDesktop
139 implements DropTargetListener, ClipboardOwner, IProgressIndicator, jalview.api.StructureSelectionManagerProvider {
140 private static final String CITATION;
142 URL bg_logo_url = ChannelProperties.getImageURL("bg_logo." + String.valueOf(SplashScreen.logoSize));
143 URL uod_logo_url = ChannelProperties.getImageURL("uod_banner." + String.valueOf(SplashScreen.logoSize));
144 boolean logo = (bg_logo_url != null || uod_logo_url != null);
145 StringBuilder sb = new StringBuilder();
146 sb.append("<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.");
150 sb.append(bg_logo_url == null ? "" : "<img alt=\"Barton Group logo\" src=\"" + bg_logo_url.toString() + "\">");
151 sb.append(uod_logo_url == null ? ""
152 : " <img alt=\"University of Dundee shield\" src=\"" + uod_logo_url.toString() + "\">");
154 "<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");
155 sb.append("<br><br>If you use Jalview, please cite:"
156 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
157 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
158 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033");
159 CITATION = sb.toString();
162 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
164 private static int DEFAULT_MIN_WIDTH = 300;
166 private static int DEFAULT_MIN_HEIGHT = 250;
168 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
170 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
172 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
174 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
176 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
178 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
180 public static boolean nosplash = false;
183 * news reader - null if it was never started.
185 private BlogReader jvnews = null;
187 private File projectFile;
191 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
193 public void addJalviewPropertyChangeListener(PropertyChangeListener listener) {
194 changeSupport.addJalviewPropertyChangeListener(listener);
198 * @param propertyName
200 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
201 * java.beans.PropertyChangeListener)
203 public void addJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
204 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
208 * @param propertyName
210 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
211 * java.beans.PropertyChangeListener)
213 public void removeJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
214 changeSupport.removeJalviewPropertyChangeListener(propertyName, listener);
217 /** Singleton Desktop instance */
218 public static Desktop instance;
220 public static MyDesktopPane desktop;
222 public static MyDesktopPane getDesktop() {
223 // BH 2018 could use currentThread() here as a reference to a
224 // Hashtable<Thread, MyDesktopPane> in JavaScript
228 static int openFrameCount = 0;
230 static final int xOffset = 30;
232 static final int yOffset = 30;
234 public static jalview.ws.jws1.Discoverer discoverer;
236 public static Object[] jalviewClipboard;
238 public static boolean internalCopy = false;
240 static int fileLoadingCount = 0;
242 class MyDesktopManager implements DesktopManager {
244 private DesktopManager delegate;
246 public MyDesktopManager(DesktopManager delegate) {
247 this.delegate = delegate;
251 public void activateFrame(JInternalFrame f) {
253 delegate.activateFrame(f);
254 } catch (NullPointerException npe) {
255 Point p = getMousePosition();
256 instance.showPasteMenu(p.x, p.y);
261 public void beginDraggingFrame(JComponent f) {
262 delegate.beginDraggingFrame(f);
266 public void beginResizingFrame(JComponent f, int direction) {
267 delegate.beginResizingFrame(f, direction);
271 public void closeFrame(JInternalFrame f) {
272 delegate.closeFrame(f);
276 public void deactivateFrame(JInternalFrame f) {
277 delegate.deactivateFrame(f);
281 public void deiconifyFrame(JInternalFrame f) {
282 delegate.deiconifyFrame(f);
286 public void dragFrame(JComponent f, int newX, int newY) {
290 delegate.dragFrame(f, newX, newY);
294 public void endDraggingFrame(JComponent f) {
295 delegate.endDraggingFrame(f);
300 public void endResizingFrame(JComponent f) {
301 delegate.endResizingFrame(f);
306 public void iconifyFrame(JInternalFrame f) {
307 delegate.iconifyFrame(f);
311 public void maximizeFrame(JInternalFrame f) {
312 delegate.maximizeFrame(f);
316 public void minimizeFrame(JInternalFrame f) {
317 delegate.minimizeFrame(f);
321 public void openFrame(JInternalFrame f) {
322 delegate.openFrame(f);
326 public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
330 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
334 public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
335 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
338 // All other methods, simply delegate
343 * Creates a new Desktop object.
348 * A note to implementors. It is ESSENTIAL that any activities that might block
349 * are spawned off as threads rather than waited for during this constructor.
353 doConfigureStructurePrefs();
354 setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
357 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
358 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
359 * documented or guaranteed to exist, so we access it via reflection. There
360 * appear to be unfathomable criteria about what this string can contain, and it
361 * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
362 * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
363 * but "Jalview non-release" does not. The reflection access may generate a
364 * warning: WARNING: An illegal reflective access operation has occurred
365 * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
366 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
368 if (Platform.isLinux()) {
370 Toolkit xToolkit = Toolkit.getDefaultToolkit();
371 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
372 Field awtAppClassNameField = null;
374 if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
375 awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
378 String title = ChannelProperties.getProperty("app_name");
379 if (awtAppClassNameField != null) {
380 awtAppClassNameField.setAccessible(true);
381 awtAppClassNameField.set(xToolkit, title);
383 Cache.log.debug("XToolkit: awtAppClassName not found");
385 } catch (Exception e) {
386 Cache.debug("Error setting awtAppClassName");
387 Cache.trace(Cache.getStackTraceString(e));
392 * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
393 * macOS's application menu. APQHandlers will check to see if a handler is
394 * supported before setting it.
397 APQHandlers.setAPQHandlers(this);
398 } catch (Exception e) {
399 System.out.println("Cannot set APQHandlers");
400 // e.printStackTrace();
401 } catch (Throwable t) {
402 Cache.warn("Error setting APQHandlers: " + t.toString());
403 Cache.trace(Cache.getStackTraceString(t));
405 setIconImages(ChannelProperties.getIconList());
407 addWindowListener(new WindowAdapter() {
410 public void windowClosing(WindowEvent ev) {
415 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
417 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
418 desktop = new MyDesktopPane(selmemusage);
420 showMemusage.setSelected(selmemusage);
421 desktop.setBackground(Color.white);
423 getContentPane().setLayout(new BorderLayout());
424 // alternate config - have scrollbars - see notes in JAL-153
425 // JScrollPane sp = new JScrollPane();
426 // sp.getViewport().setView(desktop);
427 // getContentPane().add(sp, BorderLayout.CENTER);
429 // BH 2018 - just an experiment to try unclipped JInternalFrames.
430 if (Platform.isJS()) {
431 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
434 getContentPane().add(desktop, BorderLayout.CENTER);
435 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
437 // This line prevents Windows Look&Feel resizing all new windows to maximum
438 // if previous window was maximised
439 desktop.setDesktopManager(new MyDesktopManager((Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
440 : Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(desktop.getDesktopManager())
441 : desktop.getDesktopManager())));
443 Rectangle dims = getLastKnownDimensions("");
447 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
448 int xPos = Math.max(5, (screenSize.width - 900) / 2);
449 int yPos = Math.max(5, (screenSize.height - 650) / 2);
450 setBounds(xPos, yPos, 900, 650);
453 if (!Platform.isJS())
460 jconsole = new Console(this, showjconsole);
461 jconsole.setHeader(Cache.getVersionDetailsForConsole());
462 showConsole(showjconsole);
464 showNews.setVisible(false);
466 experimentalFeatures.setSelected(showExperimental());
468 getIdentifiersOrgData();
472 // Spawn a thread that shows the splashscreen
474 SwingUtilities.invokeLater(new Runnable() {
477 new SplashScreen(true);
482 // Thread off a new instance of the file chooser - this reduces the time
484 // takes to open it later on.
485 new Thread(new Runnable() {
488 Cache.log.debug("Filechooser init thread started.");
489 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
490 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat);
491 Cache.log.debug("Filechooser init thread finished.");
494 // Add the service change listener
495 changeSupport.addJalviewPropertyChangeListener("services", new PropertyChangeListener() {
498 public void propertyChange(PropertyChangeEvent evt) {
499 Cache.log.debug("Firing service changed event for " + evt.getNewValue());
500 JalviewServicesChanged(evt);
505 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
507 this.addWindowListener(new WindowAdapter() {
509 public void windowClosing(WindowEvent evt) {
515 this.addMouseListener(ma = new MouseAdapter() {
517 public void mousePressed(MouseEvent evt) {
518 if (evt.isPopupTrigger()) // Mac
520 showPasteMenu(evt.getX(), evt.getY());
525 public void mouseReleased(MouseEvent evt) {
526 if (evt.isPopupTrigger()) // Windows
528 showPasteMenu(evt.getX(), evt.getY());
532 desktop.addMouseListener(ma);
536 * Answers true if user preferences to enable experimental features is True
541 public boolean showExperimental() {
542 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES, Boolean.FALSE.toString());
543 return Boolean.valueOf(experimental).booleanValue();
546 public void doConfigureStructurePrefs() {
547 // configure services
548 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
549 if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) {
550 ssm.setAddTempFacAnnot(Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
551 ssm.setProcessSecondaryStructure(Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
552 ssm.setSecStructServices(Cache.getDefault(Preferences.USE_RNAVIEW, true));
554 ssm.setAddTempFacAnnot(false);
555 ssm.setProcessSecondaryStructure(false);
556 ssm.setSecStructServices(false);
560 public void checkForNews() {
561 final Desktop me = this;
562 // Thread off the news reader, in case there are connection problems.
563 new Thread(new Runnable() {
566 Cache.log.debug("Starting news thread.");
567 jvnews = new BlogReader(me);
568 showNews.setVisible(true);
569 Cache.log.debug("Completed news thread.");
574 public void getIdentifiersOrgData() {
575 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
576 // Thread off the identifiers fetcher
577 new Thread(new Runnable() {
580 Cache.log.debug("Downloading data from identifiers.org");
582 UrlDownloadClient.download(IdOrgSettings.getUrl(), IdOrgSettings.getDownloadLocation());
583 } catch (IOException e) {
584 Cache.log.debug("Exception downloading identifiers.org data" + e.getMessage());
592 protected void showNews_actionPerformed(ActionEvent e) {
593 showNews(showNews.isSelected());
596 void showNews(boolean visible) {
597 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
598 showNews.setSelected(visible);
599 if (visible && !jvnews.isVisible()) {
600 new Thread(new Runnable() {
603 long now = System.currentTimeMillis();
604 Desktop.instance.setProgressBar(MessageManager.getString("status.refreshing_news"), now);
605 jvnews.refreshNews();
606 Desktop.instance.setProgressBar(null, now);
614 * recover the last known dimensions for a jalview window
616 * @param windowName - empty string is desktop, all other windows have unique
618 * @return null or last known dimensions scaled to current geometry (if last
619 * window geom was known)
621 Rectangle getLastKnownDimensions(String windowName) {
622 // TODO: lock aspect ratio for scaling desktop Bug #0058199
623 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
624 String x = Cache.getProperty(windowName + "SCREEN_X");
625 String y = Cache.getProperty(windowName + "SCREEN_Y");
626 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
627 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
628 if ((x != null) && (y != null) && (width != null) && (height != null)) {
629 int ix = Integer.parseInt(x), iy = Integer.parseInt(y), iw = Integer.parseInt(width),
630 ih = Integer.parseInt(height);
631 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) {
632 // attempt #1 - try to cope with change in screen geometry - this
633 // version doesn't preserve original jv aspect ratio.
634 // take ratio of current screen size vs original screen size.
635 double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
636 double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
637 // rescale the bounds depending upon the current screen geometry.
638 ix = (int) (ix * sw);
639 iw = (int) (iw * sw);
640 iy = (int) (iy * sh);
641 ih = (int) (ih * sh);
642 while (ix >= screenSize.width) {
643 Cache.log.debug("Window geometry location recall error: shifting horizontal to within screenbounds.");
644 ix -= screenSize.width;
646 while (iy >= screenSize.height) {
647 Cache.log.debug("Window geometry location recall error: shifting vertical to within screenbounds.");
648 iy -= screenSize.height;
650 Cache.log.debug("Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw
653 // return dimensions for new instance
654 return new Rectangle(ix, iy, iw, ih);
659 void showPasteMenu(int x, int y) {
660 JPopupMenu popup = new JPopupMenu();
661 JMenuItem item = new JMenuItem(MessageManager.getString("label.paste_new_window"));
662 item.addActionListener(new ActionListener() {
664 public void actionPerformed(ActionEvent evt) {
670 popup.show(this, x, y);
673 public void paste() {
675 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
676 Transferable contents = c.getContents(this);
678 if (contents != null) {
679 String file = (String) contents.getTransferData(DataFlavor.stringFlavor);
681 FileFormatI format = new IdentifyFile().identify(file, DataSourceType.PASTE);
683 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
686 } catch (Exception ex) {
687 System.out.println("Unable to paste alignment from system clipboard:\n" + ex);
692 * Adds and opens the given frame to the desktop
694 * @param frame Frame to show
695 * @param title Visible Title
699 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h) {
700 addInternalFrame(frame, title, true, w, h, true, false);
704 * Add an internal frame to the Jalview desktop
706 * @param frame Frame to show
707 * @param title Visible Title
708 * @param makeVisible When true, display frame immediately, otherwise, caller
709 * must call setVisible themselves.
713 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
715 addInternalFrame(frame, title, makeVisible, w, h, true, false);
719 * Add an internal frame to the Jalview desktop and make it visible
721 * @param frame Frame to show
722 * @param title Visible Title
725 * @param resizable Allow resize
727 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h,
729 addInternalFrame(frame, title, true, w, h, resizable, false);
733 * Add an internal frame to the Jalview desktop
735 * @param frame Frame to show
736 * @param title Visible Title
737 * @param makeVisible When true, display frame immediately, otherwise, caller
738 * must call setVisible themselves.
741 * @param resizable Allow resize
742 * @param ignoreMinSize Do not set the default minimum size for frame
744 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
745 int h, boolean resizable, boolean ignoreMinSize) {
747 // TODO: allow callers to determine X and Y position of frame (eg. via
749 // TODO: consider fixing method to update entries in the window submenu with
750 // the current window title
752 frame.setTitle(title);
753 if (frame.getWidth() < 1 || frame.getHeight() < 1) {
756 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
757 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
758 // IF JALVIEW IS RUNNING HEADLESS
759 // ///////////////////////////////////////////////
760 if (instance == null || (System.getProperty("java.awt.headless") != null
761 && System.getProperty("java.awt.headless").equals("true"))) {
767 if (!ignoreMinSize) {
768 frame.setMinimumSize(new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
770 // Set default dimension for Alignment Frame window.
771 // The Alignment Frame window could be added from a number of places,
773 // I did this here in order not to miss out on any Alignment frame.
774 if (frame instanceof AlignFrame) {
775 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH, ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
779 frame.setVisible(makeVisible);
780 frame.setClosable(true);
781 frame.setResizable(resizable);
782 frame.setMaximizable(resizable);
783 frame.setIconifiable(resizable);
784 frame.setOpaque(Platform.isJS());
786 if (frame.getX() < 1 && frame.getY() < 1) {
787 frame.setLocation(xOffset * openFrameCount, yOffset * ((openFrameCount - 1) % 10) + yOffset);
791 * add an entry for the new frame in the Window menu (and remove it when the
794 final JMenuItem menuItem = new JMenuItem(title);
795 frame.addInternalFrameListener(new InternalFrameAdapter() {
797 public void internalFrameActivated(InternalFrameEvent evt) {
798 JInternalFrame itf = desktop.getSelectedFrame();
800 if (itf instanceof AlignFrame) {
801 Jalview.setCurrentAlignFrame((AlignFrame) itf);
808 public void internalFrameClosed(InternalFrameEvent evt) {
809 PaintRefresher.RemoveComponent(frame);
812 * defensive check to prevent frames being added half off the window
814 if (openFrameCount > 0) {
819 * ensure no reference to alignFrame retained by menu item listener
821 if (menuItem.getActionListeners().length > 0) {
822 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
824 windowMenu.remove(menuItem);
828 menuItem.addActionListener(new ActionListener() {
830 public void actionPerformed(ActionEvent e) {
832 frame.setSelected(true);
833 frame.setIcon(false);
834 } catch (java.beans.PropertyVetoException ex) {
840 setKeyBindings(frame);
844 windowMenu.add(menuItem);
848 frame.setSelected(true);
849 frame.requestFocus();
850 } catch (java.beans.PropertyVetoException ve) {
851 } catch (java.lang.ClassCastException cex) {
853 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
859 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
864 private static void setKeyBindings(JInternalFrame frame) {
865 @SuppressWarnings("serial")
866 final Action closeAction = new AbstractAction() {
868 public void actionPerformed(ActionEvent e) {
874 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
876 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
877 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
879 InputMap inputMap = frame.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
880 String ctrlW = ctrlWKey.toString();
881 inputMap.put(ctrlWKey, ctrlW);
882 inputMap.put(cmdWKey, ctrlW);
884 ActionMap actionMap = frame.getActionMap();
885 actionMap.put(ctrlW, closeAction);
889 public void lostOwnership(Clipboard clipboard, Transferable contents) {
891 Desktop.jalviewClipboard = null;
894 internalCopy = false;
898 public void dragEnter(DropTargetDragEvent evt) {
902 public void dragExit(DropTargetEvent evt) {
906 public void dragOver(DropTargetDragEvent evt) {
910 public void dropActionChanged(DropTargetDragEvent evt) {
916 * @param evt DOCUMENT ME!
919 public void drop(DropTargetDropEvent evt) {
920 boolean success = true;
921 // JAL-1552 - acceptDrop required before getTransferable call for
922 // Java's Transferable for native dnd
923 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
924 Transferable t = evt.getTransferable();
925 List<Object> files = new ArrayList<>();
926 List<DataSourceType> protocols = new ArrayList<>();
929 Desktop.transferFromDropTarget(files, protocols, evt, t);
930 } catch (Exception e) {
937 for (int i = 0; i < files.size(); i++) {
938 // BH 2018 File or String
939 Object file = files.get(i);
940 String fileName = file.toString();
941 DataSourceType protocol = (protocols == null) ? DataSourceType.FILE : protocols.get(i);
942 FileFormatI format = null;
944 if (fileName.endsWith(".jar")) {
945 format = FileFormat.Jalview;
948 format = new IdentifyFile().identify(file, protocol);
950 if (file instanceof File) {
951 Platform.cacheFileData((File) file);
953 new FileLoader().LoadFile(null, file, protocol, format);
956 } catch (Exception ex) {
960 evt.dropComplete(success); // need this to ensure input focus is properly
961 // transfered to any new windows created
967 * @param e DOCUMENT ME!
970 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) {
971 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
972 JalviewFileChooser chooser = JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat,
973 BackupFiles.getEnabled());
975 chooser.setFileView(new JalviewFileView());
976 chooser.setDialogTitle(MessageManager.getString("label.open_local_file"));
977 chooser.setToolTipText(MessageManager.getString("action.open"));
979 chooser.setResponseHandler(0, new Runnable() {
982 File selectedFile = chooser.getSelectedFile();
983 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
985 FileFormatI format = chooser.getSelectedFormat();
988 * Call IdentifyFile to verify the file contains what its extension implies.
989 * Skip this step for dynamically added file formats, because IdentifyFile does
990 * not know how to recognise them.
992 if (FileFormats.getInstance().isIdentifiable(format)) {
994 format = new IdentifyFile().identify(selectedFile, DataSourceType.FILE);
995 } catch (FileFormatException e) {
996 // format = null; //??
1000 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, format);
1003 chooser.showOpenDialog(this);
1007 * Shows a dialog for input of a URL at which to retrieve alignment data
1012 public void inputURLMenuItem_actionPerformed(AlignViewport viewport) {
1013 // This construct allows us to have a wider textfield
1015 JLabel label = new JLabel(MessageManager.getString("label.input_file_url"));
1017 JPanel panel = new JPanel(new GridLayout(2, 1));
1021 * the URL to fetch is input in Java: an editable combobox with history JS:
1022 * (pending JAL-3038) a plain text field
1025 String urlBase = "https://www.";
1026 if (Platform.isJS()) {
1027 history = new JTextField(urlBase, 35);
1035 JComboBox<String> asCombo = new JComboBox<>();
1036 asCombo.setPreferredSize(new Dimension(400, 20));
1037 asCombo.setEditable(true);
1038 asCombo.addItem(urlBase);
1039 String historyItems = Cache.getProperty("RECENT_URL");
1040 if (historyItems != null) {
1041 for (String token : historyItems.split("\\t")) {
1042 asCombo.addItem(token);
1049 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1050 MessageManager.getString("action.cancel") };
1051 Runnable action = new Runnable() {
1054 @SuppressWarnings("unchecked")
1055 String url = (history instanceof JTextField ? ((JTextField) history).getText()
1056 : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
1058 if (url.toLowerCase().endsWith(".jar")) {
1059 if (viewport != null) {
1060 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
1062 new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
1065 FileFormatI format = null;
1067 format = new IdentifyFile().identify(url, DataSourceType.URL);
1068 } catch (FileFormatException e) {
1069 // TODO revise error handling, distinguish between
1070 // URL not found and response not valid
1073 if (format == null) {
1074 String msg = MessageManager.formatMessage("label.couldnt_locate", url);
1075 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1076 MessageManager.getString("label.url_not_found"), JvOptionPane.WARNING_MESSAGE);
1081 if (viewport != null) {
1082 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, format);
1084 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1089 String dialogOption = MessageManager.getString("label.input_alignment_from_url");
1090 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action).showInternalDialog(panel, dialogOption,
1091 JvOptionPane.YES_NO_CANCEL_OPTION, JvOptionPane.PLAIN_MESSAGE, null, options,
1092 MessageManager.getString("action.ok"));
1096 * Opens the CutAndPaste window for the user to paste an alignment in to
1098 * @param viewPanel - if not null, the pasted alignment is added to the current
1099 * alignment; if null, to a new alignment window
1102 public void inputTextboxMenuItem_actionPerformed(AlignmentViewPanel viewPanel) {
1103 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1104 cap.setForInput(viewPanel);
1105 Desktop.addInternalFrame(cap, MessageManager.getString("label.cut_paste_alignmen_file"), true, 600, 500);
1112 public void quit() {
1113 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1114 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1115 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1116 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, getWidth(), getHeight()));
1118 if (jconsole != null) {
1119 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1120 jconsole.stopConsole();
1122 if (jvnews != null) {
1123 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1126 if (dialogExecutor != null) {
1127 dialogExecutor.shutdownNow();
1129 closeAll_actionPerformed(null);
1131 if (groovyConsole != null) {
1132 // suppress a possible repeat prompt to save script
1133 groovyConsole.setDirty(false);
1134 groovyConsole.exit();
1139 private void storeLastKnownDimensions(String string, Rectangle jc) {
1140 Cache.log.debug("Storing last known dimensions for " + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1141 + " height:" + jc.height);
1143 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1144 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1145 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1146 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1152 * @param e DOCUMENT ME!
1155 public void aboutMenuItem_actionPerformed(ActionEvent e) {
1156 new Thread(new Runnable() {
1159 new SplashScreen(false);
1165 * Returns the html text for the About screen, including any available version
1166 * number, build details, author details and citation reference, but without the
1167 * enclosing {@code html} tags
1171 public String getAboutMessage() {
1172 StringBuilder message = new StringBuilder(1024);
1173 message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
1174 .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
1175 .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
1176 .append(Cache.getBuildDetailsForSplash()).append("</strong>");
1178 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1179 if (latestVersion.equals("Checking")) {
1180 // JBP removed this message for 2.11: May be reinstated in future version
1181 // message.append("<br>...Checking latest version...</br>");
1182 } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
1183 boolean red = false;
1184 if (Cache.getProperty("VERSION").toLowerCase().indexOf("automated build") == -1) {
1186 // Displayed when code version and jnlp version do not match and code
1187 // version is not a development build
1188 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1191 message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1192 .append(" is available for download from ")
1193 .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
1195 message.append("</div>");
1198 message.append("<br>Authors: ");
1199 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1200 message.append(CITATION);
1202 message.append("</div>");
1204 return message.toString();
1208 * Action on requesting Help documentation
1211 public void documentationMenuItem_actionPerformed() {
1213 if (Platform.isJS()) {
1214 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1222 Help.showHelpWindow();
1224 } catch (Exception ex) {
1225 System.err.println("Error opening help: " + ex.getMessage());
1230 public void closeAll_actionPerformed(ActionEvent e) {
1231 // TODO show a progress bar while closing?
1232 JInternalFrame[] frames = desktop.getAllFrames();
1233 for (int i = 0; i < frames.length; i++) {
1235 frames[i].setClosed(true);
1236 } catch (java.beans.PropertyVetoException ex) {
1239 Jalview.setCurrentAlignFrame(null);
1240 System.out.println("ALL CLOSED");
1243 * reset state of singleton objects as appropriate (clear down session state
1244 * when all windows are closed)
1246 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
1253 public void raiseRelated_actionPerformed(ActionEvent e) {
1254 reorderAssociatedWindows(false, false);
1258 public void minimizeAssociated_actionPerformed(ActionEvent e) {
1259 reorderAssociatedWindows(true, false);
1262 void closeAssociatedWindows() {
1263 reorderAssociatedWindows(false, true);
1269 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1273 protected void garbageCollect_actionPerformed(ActionEvent e) {
1274 // We simply collect the garbage
1275 Cache.log.debug("Collecting garbage...");
1277 Cache.log.debug("Finished garbage collection.");
1283 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1287 protected void showMemusage_actionPerformed(ActionEvent e) {
1288 desktop.showMemoryUsage(showMemusage.isSelected());
1295 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1299 protected void showConsole_actionPerformed(ActionEvent e) {
1300 showConsole(showConsole.isSelected());
1303 Console jconsole = null;
1306 * control whether the java console is visible or not
1310 void showConsole(boolean selected) {
1311 // TODO: decide if we should update properties file
1312 if (jconsole != null) // BH 2018
1314 showConsole.setSelected(selected);
1315 Cache.setProperty("SHOW_JAVA_CONSOLE", Boolean.valueOf(selected).toString());
1316 jconsole.setVisible(selected);
1320 void reorderAssociatedWindows(boolean minimize, boolean close) {
1321 JInternalFrame[] frames = desktop.getAllFrames();
1322 if (frames == null || frames.length < 1) {
1326 AlignmentViewport source = null, target = null;
1327 if (frames[0] instanceof AlignFrame) {
1328 source = ((AlignFrame) frames[0]).getCurrentView();
1329 } else if (frames[0] instanceof TreePanel) {
1330 source = ((TreePanel) frames[0]).getViewPort();
1331 } else if (frames[0] instanceof PCAPanel) {
1332 source = ((PCAPanel) frames[0]).av;
1333 } else if (frames[0].getContentPane() instanceof PairwiseAlignPanel) {
1334 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1337 if (source != null) {
1338 for (int i = 0; i < frames.length; i++) {
1340 if (frames[i] == null) {
1343 if (frames[i] instanceof AlignFrame) {
1344 target = ((AlignFrame) frames[i]).getCurrentView();
1345 } else if (frames[i] instanceof TreePanel) {
1346 target = ((TreePanel) frames[i]).getViewPort();
1347 } else if (frames[i] instanceof PCAPanel) {
1348 target = ((PCAPanel) frames[i]).av;
1349 } else if (frames[i].getContentPane() instanceof PairwiseAlignPanel) {
1350 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1353 if (source == target) {
1356 frames[i].setClosed(true);
1358 frames[i].setIcon(minimize);
1360 frames[i].toFront();
1364 } catch (java.beans.PropertyVetoException ex) {
1374 * @param e DOCUMENT ME!
1377 protected void preferences_actionPerformed(ActionEvent e) {
1378 Preferences.openPreferences();
1382 * Prompts the user to choose a file and then saves the Jalview state as a
1383 * Jalview project file
1386 public void saveState_actionPerformed() {
1387 saveState_actionPerformed(false);
1390 public void saveState_actionPerformed(boolean saveAs) {
1391 java.io.File projectFile = getProjectFile();
1392 // autoSave indicates we already have a file and don't need to ask
1393 boolean autoSave = projectFile != null && !saveAs && BackupFiles.getEnabled();
1395 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1396 // saveAs="+saveAs+", Backups
1397 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1399 boolean approveSave = false;
1401 JalviewFileChooser chooser = new JalviewFileChooser("jvp", "Jalview Project");
1403 chooser.setFileView(new JalviewFileView());
1404 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1406 int value = chooser.showSaveDialog(this);
1408 if (value == JalviewFileChooser.APPROVE_OPTION) {
1409 projectFile = chooser.getSelectedFile();
1410 setProjectFile(projectFile);
1415 if (approveSave || autoSave) {
1416 final Desktop me = this;
1417 final java.io.File chosenFile = projectFile;
1418 new Thread(new Runnable() {
1421 // TODO: refactor to Jalview desktop session controller action.
1423 MessageManager.formatMessage("label.saving_jalview_project", new Object[] { chosenFile.getName() }),
1424 chosenFile.hashCode());
1425 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1426 // TODO catch and handle errors for savestate
1427 // TODO prevent user from messing with the Desktop whilst we're saving
1429 boolean doBackup = BackupFiles.getEnabled();
1430 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
1432 new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
1435 backupfiles.setWriteSuccess(true);
1436 backupfiles.rollBackupsAndRenameTempFile();
1438 } catch (OutOfMemoryError oom) {
1439 new OOMWarning("Whilst saving current state to " + chosenFile.getName(), oom);
1440 } catch (Exception ex) {
1441 Cache.log.error("Problems whilst trying to save to " + chosenFile.getName(), ex);
1442 JvOptionPane.showMessageDialog(me,
1443 MessageManager.formatMessage("label.error_whilst_saving_current_state_to",
1444 new Object[] { chosenFile.getName() }),
1445 MessageManager.getString("label.couldnt_save_project"), JvOptionPane.WARNING_MESSAGE);
1447 setProgressBar(null, chosenFile.hashCode());
1454 public void saveAsState_actionPerformed(ActionEvent e) {
1455 saveState_actionPerformed(true);
1458 private void setProjectFile(File choice) {
1459 this.projectFile = choice;
1462 public File getProjectFile() {
1463 return this.projectFile;
1467 * Shows a file chooser dialog and tries to read in the selected file as a
1471 public void loadState_actionPerformed() {
1472 final String[] suffix = new String[] { "jvp", "jar" };
1473 final String[] desc = new String[] { "Jalview Project", "Jalview Project (old)" };
1474 JalviewFileChooser chooser = new JalviewFileChooser(Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1475 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1479 chooser.setFileView(new JalviewFileView());
1480 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1481 chooser.setResponseHandler(0, new Runnable() {
1484 File selectedFile = chooser.getSelectedFile();
1485 setProjectFile(selectedFile);
1486 String choice = selectedFile.getAbsolutePath();
1487 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1488 new Thread(new Runnable() {
1492 new Jalview2XML().loadJalviewAlign(selectedFile);
1493 } catch (OutOfMemoryError oom) {
1494 new OOMWarning("Whilst loading project from " + choice, oom);
1495 } catch (Exception ex) {
1496 Cache.log.error("Problems whilst loading project from " + choice, ex);
1497 JvOptionPane.showMessageDialog(Desktop.desktop,
1498 MessageManager.formatMessage("label.error_whilst_loading_project_from", new Object[] { choice }),
1499 MessageManager.getString("label.couldnt_load_project"), JvOptionPane.WARNING_MESSAGE);
1502 }, "Project Loader").start();
1506 chooser.showOpenDialog(this);
1510 public void inputSequence_actionPerformed(ActionEvent e) {
1511 new SequenceFetcher(this);
1514 JPanel progressPanel;
1516 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1518 public void startLoading(final Object fileName) {
1519 if (fileLoadingCount == 0) {
1521 .add(addProgressPanel(MessageManager.formatMessage("label.loading_file", new Object[] { fileName })));
1526 private JPanel addProgressPanel(String string) {
1527 if (progressPanel == null) {
1528 progressPanel = new JPanel(new GridLayout(1, 1));
1529 totalProgressCount = 0;
1530 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1532 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1533 JProgressBar progressBar = new JProgressBar();
1534 progressBar.setIndeterminate(true);
1536 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1538 thisprogress.add(progressBar, BorderLayout.CENTER);
1539 progressPanel.add(thisprogress);
1540 ((GridLayout) progressPanel.getLayout()).setRows(((GridLayout) progressPanel.getLayout()).getRows() + 1);
1541 ++totalProgressCount;
1542 instance.validate();
1543 return thisprogress;
1546 int totalProgressCount = 0;
1548 private void removeProgressPanel(JPanel progbar) {
1549 if (progressPanel != null) {
1550 synchronized (progressPanel) {
1551 progressPanel.remove(progbar);
1552 GridLayout gl = (GridLayout) progressPanel.getLayout();
1553 gl.setRows(gl.getRows() - 1);
1554 if (--totalProgressCount < 1) {
1555 this.getContentPane().remove(progressPanel);
1556 progressPanel = null;
1563 public void stopLoading() {
1565 if (fileLoadingCount < 1) {
1566 while (fileLoadingPanels.size() > 0) {
1567 removeProgressPanel(fileLoadingPanels.remove(0));
1569 fileLoadingPanels.clear();
1570 fileLoadingCount = 0;
1575 public static int getViewCount(String alignmentId) {
1576 AlignmentViewport[] aps = getViewports(alignmentId);
1577 return (aps == null) ? 0 : aps.length;
1582 * @param alignmentId - if null, all sets are returned
1583 * @return all AlignmentPanels concerning the alignmentId sequence set
1585 public static AlignmentPanel[] getAlignmentPanels(String alignmentId) {
1586 if (Desktop.desktop == null) {
1587 // no frames created and in headless mode
1588 // TODO: verify that frames are recoverable when in headless mode
1591 List<AlignmentPanel> aps = new ArrayList<>();
1592 AlignFrame[] frames = getAlignFrames();
1593 if (frames == null) {
1596 for (AlignFrame af : frames) {
1597 for (AlignmentPanel ap : af.alignPanels) {
1598 if (alignmentId == null || alignmentId.equals(ap.av.getSequenceSetId())) {
1603 if (aps.size() == 0) {
1606 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1611 * get all the viewports on an alignment.
1613 * @param sequenceSetId unique alignment id (may be null - all viewports
1614 * returned in that case)
1615 * @return all viewports on the alignment bound to sequenceSetId
1617 public static AlignmentViewport[] getViewports(String sequenceSetId) {
1618 List<AlignmentViewport> viewp = new ArrayList<>();
1619 if (desktop != null) {
1620 AlignFrame[] frames = Desktop.getAlignFrames();
1622 for (AlignFrame afr : frames) {
1623 if (sequenceSetId == null || afr.getViewport().getSequenceSetId().equals(sequenceSetId)) {
1624 if (afr.alignPanels != null) {
1625 for (AlignmentPanel ap : afr.alignPanels) {
1626 if (sequenceSetId == null || sequenceSetId.equals(ap.av.getSequenceSetId())) {
1631 viewp.add(afr.getViewport());
1635 if (viewp.size() > 0) {
1636 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1643 * Explode the views in the given frame into separate AlignFrame
1647 public static void explodeViews(AlignFrame af) {
1648 int size = af.alignPanels.size();
1653 // FIXME: ideally should use UI interface API
1654 FeatureSettings viewFeatureSettings = (af.featureSettings != null && af.featureSettings.isOpen())
1655 ? af.featureSettings
1657 Rectangle fsBounds = af.getFeatureSettingsGeometry();
1658 for (int i = 0; i < size; i++) {
1659 AlignmentPanel ap = af.alignPanels.get(i);
1661 AlignFrame newaf = new AlignFrame(ap);
1663 // transfer reference for existing feature settings to new alignFrame
1664 if (ap == af.alignPanel) {
1665 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap) {
1666 newaf.featureSettings = viewFeatureSettings;
1668 newaf.setFeatureSettingsGeometry(fsBounds);
1672 * Restore the view's last exploded frame geometry if known. Multiple views from
1673 * one exploded frame share and restore the same (frame) position and size.
1675 Rectangle geometry = ap.av.getExplodedGeometry();
1676 if (geometry != null) {
1677 newaf.setBounds(geometry);
1680 ap.av.setGatherViewsHere(false);
1682 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1683 // and materialise a new feature settings dialog instance for the new
1685 // (closes the old as if 'OK' was pressed)
1686 if (ap == af.alignPanel && newaf.featureSettings != null && newaf.featureSettings.isOpen()
1687 && af.alignPanel.getAlignViewport().isShowSequenceFeatures()) {
1688 newaf.showFeatureSettingsUI();
1692 af.featureSettings = null;
1693 af.alignPanels.clear();
1694 af.closeMenuItem_actionPerformed(true);
1699 * Gather expanded views (separate AlignFrame's) with the same sequence set
1700 * identifier back in to this frame as additional views, and close the expanded
1701 * views. Note the expanded frames may themselves have multiple views. We take
1706 public void gatherViews(AlignFrame source) {
1707 source.viewport.setGatherViewsHere(true);
1708 source.viewport.setExplodedGeometry(source.getBounds());
1709 JInternalFrame[] frames = desktop.getAllFrames();
1710 String viewId = source.viewport.getSequenceSetId();
1711 for (int t = 0; t < frames.length; t++) {
1712 if (frames[t] instanceof AlignFrame && frames[t] != source) {
1713 AlignFrame af = (AlignFrame) frames[t];
1714 boolean gatherThis = false;
1715 for (int a = 0; a < af.alignPanels.size(); a++) {
1716 AlignmentPanel ap = af.alignPanels.get(a);
1717 if (viewId.equals(ap.av.getSequenceSetId())) {
1719 ap.av.setGatherViewsHere(false);
1720 ap.av.setExplodedGeometry(af.getBounds());
1721 source.addAlignmentPanel(ap, false);
1726 if (af.featureSettings != null && af.featureSettings.isOpen()) {
1727 if (source.featureSettings == null) {
1728 // preserve the feature settings geometry for this frame
1729 source.featureSettings = af.featureSettings;
1730 source.setFeatureSettingsGeometry(af.getFeatureSettingsGeometry());
1732 // close it and forget
1733 af.featureSettings.close();
1736 af.alignPanels.clear();
1737 af.closeMenuItem_actionPerformed(true);
1742 // refresh the feature setting UI for the source frame if it exists
1743 if (source.featureSettings != null && source.featureSettings.isOpen()) {
1744 source.showFeatureSettingsUI();
1749 public JInternalFrame[] getAllFrames() {
1750 return desktop.getAllFrames();
1754 * Checks the given url to see if it gives a response indicating that the user
1755 * should be informed of a new questionnaire.
1759 public void checkForQuestionnaire(String url) {
1760 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
1761 // javax.swing.SwingUtilities.invokeLater(jvq);
1762 new Thread(jvq).start();
1765 public void checkURLLinks() {
1766 // Thread off the URL link checker
1767 addDialogThread(new Runnable() {
1770 if (Cache.getDefault("CHECKURLLINKS", true)) {
1771 // check what the actual links are - if it's just the default don't
1772 // bother with the warning
1773 List<String> links = Preferences.sequenceUrlLinks.getLinksForMenu();
1775 // only need to check links if there is one with a
1776 // SEQUENCE_ID which is not the default EMBL_EBI link
1777 ListIterator<String> li = links.listIterator();
1778 boolean check = false;
1779 List<JLabel> urls = new ArrayList<>();
1780 while (li.hasNext()) {
1781 String link = li.next();
1782 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID) && !UrlConstants.isDefaultString(link)) {
1784 int barPos = link.indexOf("|");
1785 String urlMsg = barPos == -1 ? link : link.substring(0, barPos) + ": " + link.substring(barPos + 1);
1786 urls.add(new JLabel(urlMsg));
1793 // ask user to check in case URL links use old style tokens
1794 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
1795 JPanel msgPanel = new JPanel();
1796 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
1797 msgPanel.add(Box.createVerticalGlue());
1798 JLabel msg = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
1799 JLabel msg2 = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
1801 for (JLabel url : urls) {
1806 final JCheckBox jcb = new JCheckBox(MessageManager.getString("label.do_not_display_again"));
1807 jcb.addActionListener(new ActionListener() {
1809 public void actionPerformed(ActionEvent e) {
1810 // update Cache settings for "don't show this again"
1811 boolean showWarningAgain = !jcb.isSelected();
1812 Cache.setProperty("CHECKURLLINKS", Boolean.valueOf(showWarningAgain).toString());
1817 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
1818 MessageManager.getString("label.SEQUENCE_ID_no_longer_used"), JvOptionPane.WARNING_MESSAGE);
1825 * Proxy class for JDesktopPane which optionally displays the current memory
1826 * usage and highlights the desktop area with a red bar if free memory runs low.
1830 public class MyDesktopPane extends JDesktopPane implements Runnable {
1831 private static final float ONE_MB = 1048576f;
1833 boolean showMemoryUsage = false;
1837 java.text.NumberFormat df;
1839 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory, percentUsage;
1841 public MyDesktopPane(boolean showMemoryUsage) {
1842 showMemoryUsage(showMemoryUsage);
1845 public void showMemoryUsage(boolean showMemory) {
1846 this.showMemoryUsage = showMemory;
1848 Thread worker = new Thread(this);
1854 public boolean isShowMemoryUsage() {
1855 return showMemoryUsage;
1860 df = java.text.NumberFormat.getNumberInstance();
1861 df.setMaximumFractionDigits(2);
1862 runtime = Runtime.getRuntime();
1864 while (showMemoryUsage) {
1866 maxMemory = runtime.maxMemory() / ONE_MB;
1867 allocatedMemory = runtime.totalMemory() / ONE_MB;
1868 freeMemory = runtime.freeMemory() / ONE_MB;
1869 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
1871 percentUsage = (totalFreeMemory / maxMemory) * 100;
1873 // if (percentUsage < 20)
1875 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
1877 // instance.set.setBorder(border1);
1880 // sleep after showing usage
1882 } catch (Exception ex) {
1883 ex.printStackTrace();
1889 public void paintComponent(Graphics g) {
1890 if (showMemoryUsage && g != null && df != null) {
1891 if (percentUsage < 20) {
1892 g.setColor(Color.red);
1894 FontMetrics fm = g.getFontMetrics();
1897 MessageManager.formatMessage("label.memory_stats",
1898 new Object[] { df.format(totalFreeMemory), df.format(maxMemory), df.format(percentUsage) }),
1899 10, getHeight() - fm.getHeight());
1903 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
1904 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
1909 * Accessor method to quickly get all the AlignmentFrames loaded.
1911 * @return an array of AlignFrame, or null if none found
1913 public static AlignFrame[] getAlignFrames() {
1914 if (Jalview.isHeadlessMode()) {
1915 // Desktop.desktop is null in headless mode
1916 return new AlignFrame[] { Jalview.currentAlignFrame };
1919 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1921 if (frames == null) {
1924 List<AlignFrame> avp = new ArrayList<>();
1926 for (int i = frames.length - 1; i > -1; i--) {
1927 if (frames[i] instanceof AlignFrame) {
1928 avp.add((AlignFrame) frames[i]);
1929 } else if (frames[i] instanceof SplitFrame) {
1931 * Also check for a split frame containing an AlignFrame
1933 GSplitFrame sf = (GSplitFrame) frames[i];
1934 if (sf.getTopFrame() instanceof AlignFrame) {
1935 avp.add((AlignFrame) sf.getTopFrame());
1937 if (sf.getBottomFrame() instanceof AlignFrame) {
1938 avp.add((AlignFrame) sf.getBottomFrame());
1942 if (avp.size() == 0) {
1945 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
1950 * Returns an array of any AppJmol frames in the Desktop (or null if none).
1954 public GStructureViewer[] getJmols() {
1955 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1957 if (frames == null) {
1960 List<GStructureViewer> avp = new ArrayList<>();
1962 for (int i = frames.length - 1; i > -1; i--) {
1963 if (frames[i] instanceof AppJmol) {
1964 GStructureViewer af = (GStructureViewer) frames[i];
1968 if (avp.size() == 0) {
1971 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
1976 * Add Groovy Support to Jalview
1979 public void groovyShell_actionPerformed() {
1981 openGroovyConsole();
1982 } catch (Exception ex) {
1983 Cache.log.error("Groovy Shell Creation failed.", ex);
1984 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
1986 MessageManager.getString("label.couldnt_create_groovy_shell"),
1987 MessageManager.getString("label.groovy_support_failed"), JvOptionPane.ERROR_MESSAGE);
1992 * Open the Groovy console
1994 void openGroovyConsole() {
1995 if (groovyConsole == null) {
1996 groovyConsole = new groovy.ui.Console();
1997 groovyConsole.setVariable("Jalview", this);
1998 groovyConsole.run();
2001 * We allow only one console at a time, so that AlignFrame menu option
2002 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2003 * enable 'Run script', when the console is opened, and the reverse when it is
2006 Window window = (Window) groovyConsole.getFrame();
2007 window.addWindowListener(new WindowAdapter() {
2009 public void windowClosed(WindowEvent e) {
2011 * rebind CMD-Q from Groovy Console to Jalview Quit
2014 enableExecuteGroovy(false);
2020 * show Groovy console window (after close and reopen)
2022 ((Window) groovyConsole.getFrame()).setVisible(true);
2025 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2026 * opening a second console
2028 enableExecuteGroovy(true);
2032 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
2035 protected void addQuitHandler() {
2036 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2037 KeyStroke.getKeyStroke(KeyEvent.VK_Q, jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
2039 getRootPane().getActionMap().put("Quit", new AbstractAction() {
2041 public void actionPerformed(ActionEvent e) {
2048 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2050 * @param enabled true if Groovy console is open
2052 public void enableExecuteGroovy(boolean enabled) {
2054 * disable opening a second Groovy console (or re-enable when the console is
2057 groovyShell.setEnabled(!enabled);
2059 AlignFrame[] alignFrames = getAlignFrames();
2060 if (alignFrames != null) {
2061 for (AlignFrame af : alignFrames) {
2062 af.setGroovyEnabled(enabled);
2068 * Progress bars managed by the IProgressIndicator method.
2070 private Hashtable<Long, JPanel> progressBars;
2072 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2077 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2080 public void setProgressBar(String message, long id) {
2081 if (progressBars == null) {
2082 progressBars = new Hashtable<>();
2083 progressBarHandlers = new Hashtable<>();
2086 if (progressBars.get(Long.valueOf(id)) != null) {
2087 JPanel panel = progressBars.remove(Long.valueOf(id));
2088 if (progressBarHandlers.contains(Long.valueOf(id))) {
2089 progressBarHandlers.remove(Long.valueOf(id));
2091 removeProgressPanel(panel);
2093 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2100 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2101 * jalview.gui.IProgressIndicatorHandler)
2104 public void registerHandler(final long id, final IProgressIndicatorHandler handler) {
2105 if (progressBarHandlers == null || !progressBars.containsKey(Long.valueOf(id))) {
2106 throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
2108 progressBarHandlers.put(Long.valueOf(id), handler);
2109 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2110 if (handler.canCancel()) {
2111 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
2112 final IProgressIndicator us = this;
2113 cancel.addActionListener(new ActionListener() {
2116 public void actionPerformed(ActionEvent e) {
2117 handler.cancelActivity(id);
2118 us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
2119 new Object[] { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
2122 progressPanel.add(cancel, BorderLayout.EAST);
2128 * @return true if any progress bars are still active
2131 public boolean operationInProgress() {
2132 if (progressBars != null && progressBars.size() > 0) {
2139 * This will return the first AlignFrame holding the given viewport instance. It
2140 * will break if there are more than one AlignFrames viewing a particular av.
2143 * @return alignFrame for viewport
2145 public static AlignFrame getAlignFrameFor(AlignViewportI viewport) {
2146 if (desktop != null) {
2147 AlignmentPanel[] aps = getAlignmentPanels(viewport.getSequenceSetId());
2148 for (int panel = 0; aps != null && panel < aps.length; panel++) {
2149 if (aps[panel] != null && aps[panel].av == viewport) {
2150 return aps[panel].alignFrame;
2157 public VamsasApplication getVamsasApplication() {
2158 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2164 * flag set if jalview GUI is being operated programmatically
2166 private boolean inBatchMode = false;
2169 * check if jalview GUI is being operated programmatically
2171 * @return inBatchMode
2173 public boolean isInBatchMode() {
2178 * set flag if jalview GUI is being operated programmatically
2180 * @param inBatchMode
2182 public void setInBatchMode(boolean inBatchMode) {
2183 this.inBatchMode = inBatchMode;
2187 * start service discovery and wait till it is done
2189 public void startServiceDiscovery() {
2190 startServiceDiscovery(false);
2194 * start service discovery threads - blocking or non-blocking
2198 public void startServiceDiscovery(boolean blocking) {
2199 startServiceDiscovery(blocking, false);
2203 * start service discovery threads
2205 * @param blocking - false means call returns
2207 * @param ignore_SHOW_JWS2_SERVICES_preference - when true JABA services are
2208 * discovered regardless of user's
2209 * JWS2 discovery preference setting
2211 public void startServiceDiscovery(boolean blocking, boolean ignore_SHOW_JWS2_SERVICES_preference) {
2212 boolean alive = true;
2213 Thread t0 = null, t1 = null, t2 = null;
2214 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2216 // todo: changesupport handlers need to be transferred
2217 if (discoverer == null) {
2218 discoverer = new jalview.ws.jws1.Discoverer();
2219 // register PCS handler for desktop.
2220 discoverer.addPropertyChangeListener(changeSupport);
2222 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2223 // until we phase out completely
2224 (t0 = new Thread(discoverer)).start();
2227 if (ignore_SHOW_JWS2_SERVICES_preference || Cache.getDefault("SHOW_JWS2_SERVICES", true)) {
2228 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(changeSupport);
2232 // TODO: do rest service discovery
2238 } catch (Exception e) {
2240 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive()) || (t3 != null && t3.isAlive())
2241 || (t0 != null && t0.isAlive());
2247 * called to check if the service discovery process completed successfully.
2251 protected void JalviewServicesChanged(PropertyChangeEvent evt) {
2252 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector) {
2253 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().getErrorMessages();
2254 if (ermsg != null) {
2255 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true)) {
2256 if (serviceChangedDialog == null) {
2257 // only run if we aren't already displaying one of these.
2258 addDialogThread(serviceChangedDialog = new Runnable() {
2263 * JalviewDialog jd =new JalviewDialog() {
2265 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2267 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2269 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2271 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2273 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2274 * + " or mis-configured HTTP proxy settings.<br/>" +
2275 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2276 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2277 * true, true, "Web Service Configuration Problem", 450, 400);
2279 * jd.waitForInput();
2281 JvOptionPane.showConfirmDialog(Desktop.desktop,
2282 new JLabel("<html><table width=\"450\"><tr><td>" + ermsg + "</td></tr></table>"
2283 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2284 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2285 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2286 + " Tools->Preferences dialog box to change them.</p></html>"),
2287 "Web Service Configuration Problem", JvOptionPane.DEFAULT_OPTION, JvOptionPane.ERROR_MESSAGE);
2288 serviceChangedDialog = null;
2294 Cache.log.error("Errors reported by JABA discovery service. Check web services preferences.\n" + ermsg);
2300 private Runnable serviceChangedDialog = null;
2303 * start a thread to open a URL in the configured browser. Pops up a warning
2304 * dialog to the user if there is an exception when calling out to the browser
2309 public static void showUrl(final String url) {
2310 showUrl(url, Desktop.instance);
2314 * Like showUrl but allows progress handler to be specified
2317 * @param progress (null) or object implementing IProgressIndicator
2319 public static void showUrl(final String url, final IProgressIndicator progress) {
2320 new Thread(new Runnable() {
2324 if (progress != null) {
2325 progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[] { url }),
2328 jalview.util.BrowserLauncher.openURL(url);
2329 } catch (Exception ex) {
2330 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2331 MessageManager.getString("label.web_browser_not_found_unix"),
2332 MessageManager.getString("label.web_browser_not_found"), JvOptionPane.WARNING_MESSAGE);
2334 ex.printStackTrace();
2336 if (progress != null) {
2337 progress.setProgressBar(null, this.hashCode());
2343 public static WsParamSetManager wsparamManager = null;
2345 public static ParamManager getUserParameterStore() {
2346 if (wsparamManager == null) {
2347 wsparamManager = new WsParamSetManager();
2349 return wsparamManager;
2353 * static hyperlink handler proxy method for use by Jalview's internal windows
2357 public static void hyperlinkUpdate(HyperlinkEvent e) {
2358 if (e.getEventType() == EventType.ACTIVATED) {
2361 url = e.getURL().toString();
2362 Desktop.showUrl(url);
2363 } catch (Exception x) {
2365 if (Cache.log != null) {
2366 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2368 System.err.println("Couldn't handle string " + url + " as a URL.");
2371 // ignore any exceptions due to dud links.
2378 * single thread that handles display of dialogs to user.
2380 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2383 * flag indicating if dialogExecutor should try to acquire a permit
2385 private volatile boolean dialogPause = true;
2390 private java.util.concurrent.Semaphore block = new Semaphore(0);
2392 private static groovy.ui.Console groovyConsole;
2395 * add another dialog thread to the queue
2399 public void addDialogThread(final Runnable prompter) {
2400 dialogExecutor.submit(new Runnable() {
2406 } catch (InterruptedException x) {
2409 if (instance == null) {
2413 SwingUtilities.invokeAndWait(prompter);
2414 } catch (Exception q) {
2415 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2421 public void startDialogQueue() {
2422 // set the flag so we don't pause waiting for another permit and semaphore
2423 // the current task to begin
2424 dialogPause = false;
2429 * Outputs an image of the desktop to file in EPS format, after prompting the
2430 * user for choice of Text or Lineart character rendering (unless a preference
2431 * has been set). The file name is generated as
2434 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2438 protected void snapShotWindow_actionPerformed(ActionEvent e) {
2439 // currently the menu option to do this is not shown
2442 int width = getWidth();
2443 int height = getHeight();
2444 File of = new File("Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2445 ImageWriterI writer = new ImageWriterI() {
2447 public void exportImage(Graphics g) throws Exception {
2449 Cache.log.info("Successfully written snapshot to file " + of.getAbsolutePath());
2452 String title = "View of desktop";
2453 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS, title);
2454 exporter.doExport(of, this, width, height, title);
2458 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2459 * This respects (remembers) any previous 'exploded geometry' i.e. the size and
2460 * location last time the view was expanded (if any). However it does not
2461 * remember the split pane divider location - this is set to match the
2462 * 'exploding' frame.
2466 public void explodeViews(SplitFrame sf) {
2467 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2468 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2469 List<? extends AlignmentViewPanel> topPanels = oldTopFrame.getAlignPanels();
2470 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame.getAlignPanels();
2471 int viewCount = topPanels.size();
2472 if (viewCount < 2) {
2477 * Processing in reverse order works, forwards order leaves the first panels not
2478 * visible. I don't know why!
2480 for (int i = viewCount - 1; i >= 0; i--) {
2482 * Make new top and bottom frames. These take over the respective AlignmentPanel
2483 * objects, including their AlignmentViewports, so the cdna/protein
2484 * relationships between the viewports is carried over to the new split frames.
2486 * explodedGeometry holds the (x, y) position of the previously exploded
2487 * SplitFrame, and the (width, height) of the AlignFrame component
2489 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
2490 AlignFrame newTopFrame = new AlignFrame(topPanel);
2491 newTopFrame.setSize(oldTopFrame.getSize());
2492 newTopFrame.setVisible(true);
2493 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport()).getExplodedGeometry();
2494 if (geometry != null) {
2495 newTopFrame.setSize(geometry.getSize());
2498 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
2499 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
2500 newBottomFrame.setSize(oldBottomFrame.getSize());
2501 newBottomFrame.setVisible(true);
2502 geometry = ((AlignViewport) bottomPanel.getAlignViewport()).getExplodedGeometry();
2503 if (geometry != null) {
2504 newBottomFrame.setSize(geometry.getSize());
2507 topPanel.av.setGatherViewsHere(false);
2508 bottomPanel.av.setGatherViewsHere(false);
2509 JInternalFrame splitFrame = new SplitFrame(newTopFrame, newBottomFrame);
2510 if (geometry != null) {
2511 splitFrame.setLocation(geometry.getLocation());
2513 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
2517 * Clear references to the panels (now relocated in the new SplitFrames) before
2518 * closing the old SplitFrame.
2521 bottomPanels.clear();
2526 * Gather expanded split frames, sharing the same pairs of sequence set ids,
2527 * back into the given SplitFrame as additional views. Note that the gathered
2528 * frames may themselves have multiple views.
2532 public void gatherViews(GSplitFrame source) {
2534 * special handling of explodedGeometry for a view within a SplitFrame: - it
2535 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
2536 * height) of the AlignFrame component
2538 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
2539 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
2540 myTopFrame.viewport.setExplodedGeometry(
2541 new Rectangle(source.getX(), source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
2542 myBottomFrame.viewport.setExplodedGeometry(
2543 new Rectangle(source.getX(), source.getY(), myBottomFrame.getWidth(), myBottomFrame.getHeight()));
2544 myTopFrame.viewport.setGatherViewsHere(true);
2545 myBottomFrame.viewport.setGatherViewsHere(true);
2546 String topViewId = myTopFrame.viewport.getSequenceSetId();
2547 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
2549 JInternalFrame[] frames = desktop.getAllFrames();
2550 for (JInternalFrame frame : frames) {
2551 if (frame instanceof SplitFrame && frame != source) {
2552 SplitFrame sf = (SplitFrame) frame;
2553 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
2554 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
2555 boolean gatherThis = false;
2556 for (int a = 0; a < topFrame.alignPanels.size(); a++) {
2557 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
2558 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
2559 if (topViewId.equals(topPanel.av.getSequenceSetId())
2560 && bottomViewId.equals(bottomPanel.av.getSequenceSetId())) {
2562 topPanel.av.setGatherViewsHere(false);
2563 bottomPanel.av.setGatherViewsHere(false);
2564 topPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), topFrame.getSize()));
2565 bottomPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), bottomFrame.getSize()));
2566 myTopFrame.addAlignmentPanel(topPanel, false);
2567 myBottomFrame.addAlignmentPanel(bottomPanel, false);
2572 topFrame.getAlignPanels().clear();
2573 bottomFrame.getAlignPanels().clear();
2580 * The dust settles...give focus to the tab we did this from.
2582 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
2585 public static groovy.ui.Console getGroovyConsole() {
2586 return groovyConsole;
2590 * handles the payload of a drag and drop event.
2592 * TODO refactor to desktop utilities class
2594 * @param files - Data source strings extracted from the drop event
2595 * @param protocols - protocol for each data source extracted from the drop
2597 * @param evt - the drop event
2598 * @param t - the payload from the drop event
2601 public static void transferFromDropTarget(List<Object> files, List<DataSourceType> protocols, DropTargetDropEvent evt,
2602 Transferable t) throws Exception {
2604 DataFlavor uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"), urlFlavour = null;
2606 urlFlavour = new DataFlavor("application/x-java-url; class=java.net.URL");
2607 } catch (ClassNotFoundException cfe) {
2608 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
2611 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) {
2614 java.net.URL url = (URL) t.getTransferData(urlFlavour);
2615 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
2616 // means url may be null.
2618 protocols.add(DataSourceType.URL);
2619 files.add(url.toString());
2620 Cache.log.debug("Drop handled as URL dataflavor " + files.get(files.size() - 1));
2623 if (Platform.isAMacAndNotJS()) {
2624 System.err.println("Please ignore plist error - occurs due to problem with java 8 on OSX");
2627 } catch (Throwable ex) {
2628 Cache.log.debug("URL drop handler failed.", ex);
2631 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
2632 // Works on Windows and MacOSX
2633 Cache.log.debug("Drop handled as javaFileListFlavor");
2634 for (Object file : (List) t.getTransferData(DataFlavor.javaFileListFlavor)) {
2636 protocols.add(DataSourceType.FILE);
2639 // Unix like behaviour
2640 boolean added = false;
2642 if (t.isDataFlavorSupported(uriListFlavor)) {
2643 Cache.log.debug("Drop handled as uriListFlavor");
2644 // This is used by Unix drag system
2645 data = (String) t.getTransferData(uriListFlavor);
2648 // fallback to text: workaround - on OSX where there's a JVM bug
2649 Cache.log.debug("standard URIListFlavor failed. Trying text");
2650 // try text fallback
2651 DataFlavor textDf = new DataFlavor("text/plain;class=java.lang.String");
2652 if (t.isDataFlavorSupported(textDf)) {
2653 data = (String) t.getTransferData(textDf);
2656 Cache.log.debug("Plain text drop content returned " + (data == null ? "Null - failed" : data));
2660 while (protocols.size() < files.size()) {
2661 Cache.log.debug("Adding missing FILE protocol for " + files.get(protocols.size()));
2662 protocols.add(DataSourceType.FILE);
2664 for (java.util.StringTokenizer st = new java.util.StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
2666 String s = st.nextToken();
2667 if (s.startsWith("#")) {
2668 // the line is a comment (as per the RFC 2483)
2671 java.net.URI uri = new java.net.URI(s);
2672 if (uri.getScheme().toLowerCase().startsWith("http")) {
2673 protocols.add(DataSourceType.URL);
2674 files.add(uri.toString());
2676 // otherwise preserve old behaviour: catch all for file objects
2677 java.io.File file = new java.io.File(uri);
2678 protocols.add(DataSourceType.FILE);
2679 files.add(file.toString());
2684 if (Cache.log.isDebugEnabled()) {
2685 if (data == null || !added) {
2687 if (t.getTransferDataFlavors() != null && t.getTransferDataFlavors().length > 0) {
2688 Cache.log.debug("Couldn't resolve drop data. Here are the supported flavors:");
2689 for (DataFlavor fl : t.getTransferDataFlavors()) {
2690 Cache.log.debug("Supported transfer dataflavor: " + fl.toString());
2691 Object df = t.getTransferData(fl);
2693 Cache.log.debug("Retrieves: " + df);
2695 Cache.log.debug("Retrieved nothing");
2699 Cache.log.debug("Couldn't resolve dataflavor for drop: " + t.toString());
2704 if (Platform.isWindowsAndNotJS()) {
2705 Cache.log.debug("Scanning dropped content for Windows Link Files");
2707 // resolve any .lnk files in the file drop
2708 for (int f = 0; f < files.size(); f++) {
2709 String source = files.get(f).toString().toLowerCase();
2710 if (protocols.get(f).equals(DataSourceType.FILE)
2711 && (source.endsWith(".lnk") || source.endsWith(".url") || source.endsWith(".site"))) {
2713 Object obj = files.get(f);
2714 File lf = (obj instanceof File ? (File) obj : new File((String) obj));
2715 // process link file to get a URL
2716 Cache.log.debug("Found potential link file: " + lf);
2717 WindowsShortcut wscfile = new WindowsShortcut(lf);
2718 String fullname = wscfile.getRealFilename();
2719 protocols.set(f, FormatAdapter.checkProtocol(fullname));
2720 files.set(f, fullname);
2721 Cache.log.debug("Parsed real filename " + fullname + " to extract protocol: " + protocols.get(f));
2722 } catch (Exception ex) {
2723 Cache.log.error("Couldn't parse " + files.get(f) + " as a link file.", ex);
2731 * Sets the Preferences property for experimental features to True or False
2732 * depending on the state of the controlling menu item
2735 protected void showExperimental_actionPerformed(boolean selected) {
2736 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
2740 * Answers a (possibly empty) list of any structure viewer frames (currently for
2741 * either Jmol or Chimera) which are currently open. This may optionally be
2742 * restricted to viewers of a specified class, or viewers linked to a specified
2745 * @param apanel if not null, only return viewers linked to this
2747 * @param structureViewerClass if not null, only return viewers of this class
2750 public List<StructureViewerBase> getStructureViewers(AlignmentPanel apanel,
2751 Class<? extends StructureViewerBase> structureViewerClass) {
2752 List<StructureViewerBase> result = new ArrayList<>();
2753 JInternalFrame[] frames = Desktop.instance.getAllFrames();
2755 for (JInternalFrame frame : frames) {
2756 if (frame instanceof StructureViewerBase) {
2757 if (structureViewerClass == null || structureViewerClass.isInstance(frame)) {
2758 if (apanel == null || ((StructureViewerBase) frame).isLinkedWith(apanel)) {
2759 result.add((StructureViewerBase) frame);
2767 public static final String debugScaleMessage = "Desktop graphics transform scale=";
2769 private static boolean debugScaleMessageDone = false;
2771 public static void debugScaleMessage(Graphics g) {
2772 if (debugScaleMessageDone) {
2775 // output used by tests to check HiDPI scaling settings in action
2777 Graphics2D gg = (Graphics2D) g;
2779 AffineTransform t = gg.getTransform();
2780 double scaleX = t.getScaleX();
2781 double scaleY = t.getScaleY();
2782 Cache.debug(debugScaleMessage + scaleX + " (X)");
2783 Cache.debug(debugScaleMessage + scaleY + " (Y)");
2784 debugScaleMessageDone = true;
2786 Cache.debug("Desktop graphics null");
2788 } catch (Exception e) {
2789 Cache.debug(Cache.getStackTraceString(e));