2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.util.Locale;
25 import java.awt.BorderLayout;
26 import java.awt.Color;
27 import java.awt.Dimension;
28 import java.awt.FontMetrics;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.GridLayout;
32 import java.awt.Point;
33 import java.awt.Rectangle;
34 import java.awt.Toolkit;
35 import java.awt.Window;
36 import java.awt.datatransfer.Clipboard;
37 import java.awt.datatransfer.ClipboardOwner;
38 import java.awt.datatransfer.DataFlavor;
39 import java.awt.datatransfer.Transferable;
40 import java.awt.dnd.DnDConstants;
41 import java.awt.dnd.DropTargetDragEvent;
42 import java.awt.dnd.DropTargetDropEvent;
43 import java.awt.dnd.DropTargetEvent;
44 import java.awt.dnd.DropTargetListener;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ActionListener;
47 import java.awt.event.InputEvent;
48 import java.awt.event.KeyEvent;
49 import java.awt.event.MouseAdapter;
50 import java.awt.event.MouseEvent;
51 import java.awt.event.WindowAdapter;
52 import java.awt.event.WindowEvent;
53 import java.awt.geom.AffineTransform;
54 import java.beans.PropertyChangeEvent;
55 import java.beans.PropertyChangeListener;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.lang.reflect.Field;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.ListIterator;
67 import java.util.Vector;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.Semaphore;
72 import javax.swing.AbstractAction;
73 import javax.swing.Action;
74 import javax.swing.ActionMap;
75 import javax.swing.Box;
76 import javax.swing.BoxLayout;
77 import javax.swing.DefaultDesktopManager;
78 import javax.swing.DesktopManager;
79 import javax.swing.InputMap;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JComboBox;
83 import javax.swing.JComponent;
84 import javax.swing.JDesktopPane;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JProgressBar;
91 import javax.swing.JTextField;
92 import javax.swing.KeyStroke;
93 import javax.swing.SwingUtilities;
94 import javax.swing.event.HyperlinkEvent;
95 import javax.swing.event.HyperlinkEvent.EventType;
96 import javax.swing.event.InternalFrameAdapter;
97 import javax.swing.event.InternalFrameEvent;
99 import org.stackoverflowusers.file.WindowsShortcut;
101 import jalview.api.AlignViewportI;
102 import jalview.api.AlignmentViewPanel;
103 import jalview.bin.Cache;
104 import jalview.bin.Jalview;
105 import jalview.gui.ImageExporter.ImageWriterI;
106 import jalview.io.BackupFiles;
107 import jalview.io.DataSourceType;
108 import jalview.io.FileFormat;
109 import jalview.io.FileFormatException;
110 import jalview.io.FileFormatI;
111 import jalview.io.FileFormats;
112 import jalview.io.FileLoader;
113 import jalview.io.FormatAdapter;
114 import jalview.io.IdentifyFile;
115 import jalview.io.JalviewFileChooser;
116 import jalview.io.JalviewFileView;
117 import jalview.jbgui.GSplitFrame;
118 import jalview.jbgui.GStructureViewer;
119 import jalview.project.Jalview2XML;
120 import jalview.structure.StructureSelectionManager;
121 import jalview.urls.IdOrgSettings;
122 import jalview.util.BrowserLauncher;
123 import jalview.util.ChannelProperties;
124 import jalview.util.ImageMaker.TYPE;
125 import jalview.util.MessageManager;
126 import jalview.util.Platform;
127 import jalview.util.ShortcutKeyMaskExWrapper;
128 import jalview.util.UrlConstants;
129 import jalview.viewmodel.AlignmentViewport;
130 import jalview.ws.params.ParamManager;
131 import jalview.ws.utils.UrlDownloadClient;
138 * @version $Revision: 1.155 $
140 public class Desktop extends jalview.jbgui.GDesktop
141 implements DropTargetListener, ClipboardOwner, IProgressIndicator, jalview.api.StructureSelectionManagerProvider {
142 private static final String CITATION;
144 URL bg_logo_url = ChannelProperties.getImageURL("bg_logo." + String.valueOf(SplashScreen.logoSize));
145 URL uod_logo_url = ChannelProperties.getImageURL("uod_banner." + String.valueOf(SplashScreen.logoSize));
146 boolean logo = (bg_logo_url != null || uod_logo_url != null);
147 StringBuilder sb = new StringBuilder();
148 sb.append("<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.");
152 sb.append(bg_logo_url == null ? "" : "<img alt=\"Barton Group logo\" src=\"" + bg_logo_url.toString() + "\">");
153 sb.append(uod_logo_url == null ? ""
154 : " <img alt=\"University of Dundee shield\" src=\"" + uod_logo_url.toString() + "\">");
156 "<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");
157 sb.append("<br><br>If you use Jalview, please cite:"
158 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
159 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
160 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033");
161 CITATION = sb.toString();
164 private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
166 private static int DEFAULT_MIN_WIDTH = 300;
168 private static int DEFAULT_MIN_HEIGHT = 250;
170 private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
172 private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
174 private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
176 protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
178 public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
180 private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
182 public static boolean nosplash = false;
185 * news reader - null if it was never started.
187 private BlogReader jvnews = null;
189 private File projectFile;
193 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
195 public void addJalviewPropertyChangeListener(PropertyChangeListener listener) {
196 changeSupport.addJalviewPropertyChangeListener(listener);
200 * @param propertyName
202 * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
203 * java.beans.PropertyChangeListener)
205 public void addJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
206 changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
210 * @param propertyName
212 * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
213 * java.beans.PropertyChangeListener)
215 public void removeJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
216 changeSupport.removeJalviewPropertyChangeListener(propertyName, listener);
219 /** Singleton Desktop instance */
220 public static Desktop instance;
222 public static MyDesktopPane desktop;
224 public static MyDesktopPane getDesktop() {
225 // BH 2018 could use currentThread() here as a reference to a
226 // Hashtable<Thread, MyDesktopPane> in JavaScript
230 static int openFrameCount = 0;
232 static final int xOffset = 30;
234 static final int yOffset = 30;
236 public static jalview.ws.jws1.Discoverer discoverer;
238 public static Object[] jalviewClipboard;
240 public static boolean internalCopy = false;
242 static int fileLoadingCount = 0;
244 class MyDesktopManager implements DesktopManager {
246 private DesktopManager delegate;
248 public MyDesktopManager(DesktopManager delegate) {
249 this.delegate = delegate;
253 public void activateFrame(JInternalFrame f) {
255 delegate.activateFrame(f);
256 } catch (NullPointerException npe) {
257 Point p = getMousePosition();
258 instance.showPasteMenu(p.x, p.y);
263 public void beginDraggingFrame(JComponent f) {
264 delegate.beginDraggingFrame(f);
268 public void beginResizingFrame(JComponent f, int direction) {
269 delegate.beginResizingFrame(f, direction);
273 public void closeFrame(JInternalFrame f) {
274 delegate.closeFrame(f);
278 public void deactivateFrame(JInternalFrame f) {
279 delegate.deactivateFrame(f);
283 public void deiconifyFrame(JInternalFrame f) {
284 delegate.deiconifyFrame(f);
288 public void dragFrame(JComponent f, int newX, int newY) {
292 delegate.dragFrame(f, newX, newY);
296 public void endDraggingFrame(JComponent f) {
297 delegate.endDraggingFrame(f);
302 public void endResizingFrame(JComponent f) {
303 delegate.endResizingFrame(f);
308 public void iconifyFrame(JInternalFrame f) {
309 delegate.iconifyFrame(f);
313 public void maximizeFrame(JInternalFrame f) {
314 delegate.maximizeFrame(f);
318 public void minimizeFrame(JInternalFrame f) {
319 delegate.minimizeFrame(f);
323 public void openFrame(JInternalFrame f) {
324 delegate.openFrame(f);
328 public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
332 delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
336 public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
337 delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
340 // All other methods, simply delegate
345 * Creates a new Desktop object.
350 * A note to implementors. It is ESSENTIAL that any activities that might block
351 * are spawned off as threads rather than waited for during this constructor.
355 doConfigureStructurePrefs();
356 setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
359 * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
360 * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
361 * documented or guaranteed to exist, so we access it via reflection. There
362 * appear to be unfathomable criteria about what this string can contain, and it
363 * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
364 * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
365 * but "Jalview non-release" does not. The reflection access may generate a
366 * warning: WARNING: An illegal reflective access operation has occurred
367 * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
368 * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
370 if (Platform.isLinux()) {
372 Toolkit xToolkit = Toolkit.getDefaultToolkit();
373 Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
374 Field awtAppClassNameField = null;
376 if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
377 awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
380 String title = ChannelProperties.getProperty("app_name");
381 if (awtAppClassNameField != null) {
382 awtAppClassNameField.setAccessible(true);
383 awtAppClassNameField.set(xToolkit, title);
385 Cache.log.debug("XToolkit: awtAppClassName not found");
387 } catch (Exception e) {
388 Cache.debug("Error setting awtAppClassName");
389 Cache.trace(Cache.getStackTraceString(e));
394 * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
395 * macOS's application menu. APQHandlers will check to see if a handler is
396 * supported before setting it.
399 APQHandlers.setAPQHandlers(this);
400 } catch (Exception e) {
401 System.out.println("Cannot set APQHandlers");
402 // e.printStackTrace();
403 } catch (Throwable t) {
404 Cache.warn("Error setting APQHandlers: " + t.toString());
405 Cache.trace(Cache.getStackTraceString(t));
407 setIconImages(ChannelProperties.getIconList());
409 addWindowListener(new WindowAdapter() {
412 public void windowClosing(WindowEvent ev) {
417 boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
419 boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
420 desktop = new MyDesktopPane(selmemusage);
422 showMemusage.setSelected(selmemusage);
423 desktop.setBackground(Color.white);
425 getContentPane().setLayout(new BorderLayout());
426 // alternate config - have scrollbars - see notes in JAL-153
427 // JScrollPane sp = new JScrollPane();
428 // sp.getViewport().setView(desktop);
429 // getContentPane().add(sp, BorderLayout.CENTER);
431 // BH 2018 - just an experiment to try unclipped JInternalFrames.
432 if (Platform.isJS()) {
433 getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
436 getContentPane().add(desktop, BorderLayout.CENTER);
437 desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
439 // This line prevents Windows Look&Feel resizing all new windows to maximum
440 // if previous window was maximised
441 desktop.setDesktopManager(new MyDesktopManager((Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
442 : Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(desktop.getDesktopManager())
443 : desktop.getDesktopManager())));
445 Rectangle dims = getLastKnownDimensions("");
449 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
450 int xPos = Math.max(5, (screenSize.width - 900) / 2);
451 int yPos = Math.max(5, (screenSize.height - 650) / 2);
452 setBounds(xPos, yPos, 900, 650);
455 if (!Platform.isJS())
462 jconsole = new Console(this, showjconsole);
463 jconsole.setHeader(Cache.getVersionDetailsForConsole());
464 showConsole(showjconsole);
466 showNews.setVisible(false);
468 experimentalFeatures.setSelected(showExperimental());
470 getIdentifiersOrgData();
474 // Spawn a thread that shows the splashscreen
476 SwingUtilities.invokeLater(new Runnable() {
479 new SplashScreen(true);
484 // Thread off a new instance of the file chooser - this reduces the time
486 // takes to open it later on.
487 new Thread(new Runnable() {
490 Cache.log.debug("Filechooser init thread started.");
491 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
492 JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat);
493 Cache.log.debug("Filechooser init thread finished.");
496 // Add the service change listener
497 changeSupport.addJalviewPropertyChangeListener("services", new PropertyChangeListener() {
500 public void propertyChange(PropertyChangeEvent evt) {
501 Cache.log.debug("Firing service changed event for " + evt.getNewValue());
502 JalviewServicesChanged(evt);
507 this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
509 this.addWindowListener(new WindowAdapter() {
511 public void windowClosing(WindowEvent evt) {
517 this.addMouseListener(ma = new MouseAdapter() {
519 public void mousePressed(MouseEvent evt) {
520 if (evt.isPopupTrigger()) // Mac
522 showPasteMenu(evt.getX(), evt.getY());
527 public void mouseReleased(MouseEvent evt) {
528 if (evt.isPopupTrigger()) // Windows
530 showPasteMenu(evt.getX(), evt.getY());
534 desktop.addMouseListener(ma);
538 * Answers true if user preferences to enable experimental features is True
543 public boolean showExperimental() {
544 String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES, Boolean.FALSE.toString());
545 return Boolean.valueOf(experimental).booleanValue();
548 public void doConfigureStructurePrefs() {
549 // configure services
550 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
551 if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) {
552 ssm.setAddTempFacAnnot(Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
553 ssm.setProcessSecondaryStructure(Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
554 // JAL-3915 - RNAView is no longer an option so this has no effect
555 ssm.setSecStructServices(Cache.getDefault(Preferences.USE_RNAVIEW, false));
557 ssm.setAddTempFacAnnot(false);
558 ssm.setProcessSecondaryStructure(false);
559 ssm.setSecStructServices(false);
563 public void checkForNews() {
564 final Desktop me = this;
565 // Thread off the news reader, in case there are connection problems.
566 new Thread(new Runnable() {
569 Cache.log.debug("Starting news thread.");
570 jvnews = new BlogReader(me);
571 showNews.setVisible(true);
572 Cache.log.debug("Completed news thread.");
577 public void getIdentifiersOrgData() {
578 if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
579 // Thread off the identifiers fetcher
580 new Thread(new Runnable() {
583 Cache.log.debug("Downloading data from identifiers.org");
585 UrlDownloadClient.download(IdOrgSettings.getUrl(), IdOrgSettings.getDownloadLocation());
586 } catch (IOException e) {
587 Cache.log.debug("Exception downloading identifiers.org data" + e.getMessage());
595 protected void showNews_actionPerformed(ActionEvent e) {
596 showNews(showNews.isSelected());
599 void showNews(boolean visible) {
600 Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
601 showNews.setSelected(visible);
602 if (visible && !jvnews.isVisible()) {
603 new Thread(new Runnable() {
606 long now = System.currentTimeMillis();
607 Desktop.instance.setProgressBar(MessageManager.getString("status.refreshing_news"), now);
608 jvnews.refreshNews();
609 Desktop.instance.setProgressBar(null, now);
617 * recover the last known dimensions for a jalview window
619 * @param windowName - empty string is desktop, all other windows have unique
621 * @return null or last known dimensions scaled to current geometry (if last
622 * window geom was known)
624 Rectangle getLastKnownDimensions(String windowName) {
625 // TODO: lock aspect ratio for scaling desktop Bug #0058199
626 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
627 String x = Cache.getProperty(windowName + "SCREEN_X");
628 String y = Cache.getProperty(windowName + "SCREEN_Y");
629 String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
630 String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
631 if ((x != null) && (y != null) && (width != null) && (height != null)) {
632 int ix = Integer.parseInt(x), iy = Integer.parseInt(y), iw = Integer.parseInt(width),
633 ih = Integer.parseInt(height);
634 if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) {
635 // attempt #1 - try to cope with change in screen geometry - this
636 // version doesn't preserve original jv aspect ratio.
637 // take ratio of current screen size vs original screen size.
638 double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
639 double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
640 // rescale the bounds depending upon the current screen geometry.
641 ix = (int) (ix * sw);
642 iw = (int) (iw * sw);
643 iy = (int) (iy * sh);
644 ih = (int) (ih * sh);
645 while (ix >= screenSize.width) {
646 Cache.log.debug("Window geometry location recall error: shifting horizontal to within screenbounds.");
647 ix -= screenSize.width;
649 while (iy >= screenSize.height) {
650 Cache.log.debug("Window geometry location recall error: shifting vertical to within screenbounds.");
651 iy -= screenSize.height;
653 Cache.log.debug("Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw
656 // return dimensions for new instance
657 return new Rectangle(ix, iy, iw, ih);
662 void showPasteMenu(int x, int y) {
663 JPopupMenu popup = new JPopupMenu();
664 JMenuItem item = new JMenuItem(MessageManager.getString("label.paste_new_window"));
665 item.addActionListener(new ActionListener() {
667 public void actionPerformed(ActionEvent evt) {
673 popup.show(this, x, y);
676 public void paste() {
678 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
679 Transferable contents = c.getContents(this);
681 if (contents != null) {
682 String file = (String) contents.getTransferData(DataFlavor.stringFlavor);
684 FileFormatI format = new IdentifyFile().identify(file, DataSourceType.PASTE);
686 new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
689 } catch (Exception ex) {
690 System.out.println("Unable to paste alignment from system clipboard:\n" + ex);
695 * Adds and opens the given frame to the desktop
697 * @param frame Frame to show
698 * @param title Visible Title
702 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h) {
703 addInternalFrame(frame, title, true, w, h, true, false);
707 * Add an internal frame to the Jalview desktop
709 * @param frame Frame to show
710 * @param title Visible Title
711 * @param makeVisible When true, display frame immediately, otherwise, caller
712 * must call setVisible themselves.
716 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
718 addInternalFrame(frame, title, makeVisible, w, h, true, false);
722 * Add an internal frame to the Jalview desktop and make it visible
724 * @param frame Frame to show
725 * @param title Visible Title
728 * @param resizable Allow resize
730 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h,
732 addInternalFrame(frame, title, true, w, h, resizable, false);
736 * Add an internal frame to the Jalview desktop
738 * @param frame Frame to show
739 * @param title Visible Title
740 * @param makeVisible When true, display frame immediately, otherwise, caller
741 * must call setVisible themselves.
744 * @param resizable Allow resize
745 * @param ignoreMinSize Do not set the default minimum size for frame
747 public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
748 int h, boolean resizable, boolean ignoreMinSize) {
750 // TODO: allow callers to determine X and Y position of frame (eg. via
752 // TODO: consider fixing method to update entries in the window submenu with
753 // the current window title
755 frame.setTitle(title);
756 if (frame.getWidth() < 1 || frame.getHeight() < 1) {
759 // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
760 // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
761 // IF JALVIEW IS RUNNING HEADLESS
762 // ///////////////////////////////////////////////
763 if (instance == null || (System.getProperty("java.awt.headless") != null
764 && System.getProperty("java.awt.headless").equals("true"))) {
770 if (!ignoreMinSize) {
771 frame.setMinimumSize(new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
773 // Set default dimension for Alignment Frame window.
774 // The Alignment Frame window could be added from a number of places,
776 // I did this here in order not to miss out on any Alignment frame.
777 if (frame instanceof AlignFrame) {
778 frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH, ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
782 frame.setVisible(makeVisible);
783 frame.setClosable(true);
784 frame.setResizable(resizable);
785 frame.setMaximizable(resizable);
786 frame.setIconifiable(resizable);
787 frame.setOpaque(Platform.isJS());
789 if (frame.getX() < 1 && frame.getY() < 1) {
790 frame.setLocation(xOffset * openFrameCount, yOffset * ((openFrameCount - 1) % 10) + yOffset);
794 * add an entry for the new frame in the Window menu (and remove it when the
797 final JMenuItem menuItem = new JMenuItem(title);
798 frame.addInternalFrameListener(new InternalFrameAdapter() {
800 public void internalFrameActivated(InternalFrameEvent evt) {
801 JInternalFrame itf = desktop.getSelectedFrame();
803 if (itf instanceof AlignFrame) {
804 Jalview.setCurrentAlignFrame((AlignFrame) itf);
811 public void internalFrameClosed(InternalFrameEvent evt) {
812 PaintRefresher.RemoveComponent(frame);
815 * defensive check to prevent frames being added half off the window
817 if (openFrameCount > 0) {
822 * ensure no reference to alignFrame retained by menu item listener
824 if (menuItem.getActionListeners().length > 0) {
825 menuItem.removeActionListener(menuItem.getActionListeners()[0]);
827 windowMenu.remove(menuItem);
831 menuItem.addActionListener(new ActionListener() {
833 public void actionPerformed(ActionEvent e) {
835 frame.setSelected(true);
836 frame.setIcon(false);
837 } catch (java.beans.PropertyVetoException ex) {
843 setKeyBindings(frame);
847 windowMenu.add(menuItem);
851 frame.setSelected(true);
852 frame.requestFocus();
853 } catch (java.beans.PropertyVetoException ve) {
854 } catch (java.lang.ClassCastException cex) {
856 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
862 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
867 private static void setKeyBindings(JInternalFrame frame) {
868 @SuppressWarnings("serial")
869 final Action closeAction = new AbstractAction() {
871 public void actionPerformed(ActionEvent e) {
877 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
879 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
880 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
882 InputMap inputMap = frame.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
883 String ctrlW = ctrlWKey.toString();
884 inputMap.put(ctrlWKey, ctrlW);
885 inputMap.put(cmdWKey, ctrlW);
887 ActionMap actionMap = frame.getActionMap();
888 actionMap.put(ctrlW, closeAction);
892 public void lostOwnership(Clipboard clipboard, Transferable contents) {
894 Desktop.jalviewClipboard = null;
897 internalCopy = false;
901 public void dragEnter(DropTargetDragEvent evt) {
905 public void dragExit(DropTargetEvent evt) {
909 public void dragOver(DropTargetDragEvent evt) {
913 public void dropActionChanged(DropTargetDragEvent evt) {
919 * @param evt DOCUMENT ME!
922 public void drop(DropTargetDropEvent evt) {
923 boolean success = true;
924 // JAL-1552 - acceptDrop required before getTransferable call for
925 // Java's Transferable for native dnd
926 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
927 Transferable t = evt.getTransferable();
928 List<Object> files = new ArrayList<>();
929 List<DataSourceType> protocols = new ArrayList<>();
932 Desktop.transferFromDropTarget(files, protocols, evt, t);
933 } catch (Exception e) {
940 for (int i = 0; i < files.size(); i++) {
941 // BH 2018 File or String
942 Object file = files.get(i);
943 String fileName = file.toString();
944 DataSourceType protocol = (protocols == null) ? DataSourceType.FILE : protocols.get(i);
945 FileFormatI format = null;
947 if (fileName.endsWith(".jar")) {
948 format = FileFormat.Jalview;
951 format = new IdentifyFile().identify(file, protocol);
953 if (file instanceof File) {
954 Platform.cacheFileData((File) file);
956 new FileLoader().LoadFile(null, file, protocol, format);
959 } catch (Exception ex) {
963 evt.dropComplete(success); // need this to ensure input focus is properly
964 // transfered to any new windows created
970 * @param e DOCUMENT ME!
973 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) {
974 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
975 JalviewFileChooser chooser = JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat,
976 BackupFiles.getEnabled());
978 chooser.setFileView(new JalviewFileView());
979 chooser.setDialogTitle(MessageManager.getString("label.open_local_file"));
980 chooser.setToolTipText(MessageManager.getString("action.open"));
982 chooser.setResponseHandler(0, new Runnable() {
985 File selectedFile = chooser.getSelectedFile();
986 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
988 FileFormatI format = chooser.getSelectedFormat();
991 * Call IdentifyFile to verify the file contains what its extension implies.
992 * Skip this step for dynamically added file formats, because IdentifyFile does
993 * not know how to recognise them.
995 if (FileFormats.getInstance().isIdentifiable(format)) {
997 format = new IdentifyFile().identify(selectedFile, DataSourceType.FILE);
998 } catch (FileFormatException e) {
999 // format = null; //??
1003 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, format);
1006 chooser.showOpenDialog(this);
1010 * Shows a dialog for input of a URL at which to retrieve alignment data
1015 public void inputURLMenuItem_actionPerformed(AlignViewport viewport) {
1016 // This construct allows us to have a wider textfield
1018 JLabel label = new JLabel(MessageManager.getString("label.input_file_url"));
1020 JPanel panel = new JPanel(new GridLayout(2, 1));
1024 * the URL to fetch is input in Java: an editable combobox with history JS:
1025 * (pending JAL-3038) a plain text field
1028 String urlBase = "https://www.";
1029 if (Platform.isJS()) {
1030 history = new JTextField(urlBase, 35);
1038 JComboBox<String> asCombo = new JComboBox<>();
1039 asCombo.setPreferredSize(new Dimension(400, 20));
1040 asCombo.setEditable(true);
1041 asCombo.addItem(urlBase);
1042 String historyItems = Cache.getProperty("RECENT_URL");
1043 if (historyItems != null) {
1044 for (String token : historyItems.split("\\t")) {
1045 asCombo.addItem(token);
1052 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1053 MessageManager.getString("action.cancel") };
1054 Runnable action = new Runnable() {
1057 @SuppressWarnings("unchecked")
1058 String url = (history instanceof JTextField ? ((JTextField) history).getText()
1059 : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
1061 if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) {
1062 if (viewport != null) {
1063 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
1065 new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
1068 FileFormatI format = null;
1070 format = new IdentifyFile().identify(url, DataSourceType.URL);
1071 } catch (FileFormatException e) {
1072 // TODO revise error handling, distinguish between
1073 // URL not found and response not valid
1076 if (format == null) {
1077 String msg = MessageManager.formatMessage("label.couldnt_locate", url);
1078 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1079 MessageManager.getString("label.url_not_found"), JvOptionPane.WARNING_MESSAGE);
1084 if (viewport != null) {
1085 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, format);
1087 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1092 String dialogOption = MessageManager.getString("label.input_alignment_from_url");
1093 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action).showInternalDialog(panel, dialogOption,
1094 JvOptionPane.YES_NO_CANCEL_OPTION, JvOptionPane.PLAIN_MESSAGE, null, options,
1095 MessageManager.getString("action.ok"));
1099 * Opens the CutAndPaste window for the user to paste an alignment in to
1101 * @param viewPanel - if not null, the pasted alignment is added to the current
1102 * alignment; if null, to a new alignment window
1105 public void inputTextboxMenuItem_actionPerformed(AlignmentViewPanel viewPanel) {
1106 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1107 cap.setForInput(viewPanel);
1108 Desktop.addInternalFrame(cap, MessageManager.getString("label.cut_paste_alignmen_file"), true, 600, 500);
1115 public void quit() {
1116 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1117 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1118 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1119 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, getWidth(), getHeight()));
1121 if (jconsole != null) {
1122 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1123 jconsole.stopConsole();
1125 if (jvnews != null) {
1126 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1129 if (dialogExecutor != null) {
1130 dialogExecutor.shutdownNow();
1132 closeAll_actionPerformed(null);
1134 if (groovyConsole != null) {
1135 // suppress a possible repeat prompt to save script
1136 groovyConsole.setDirty(false);
1137 groovyConsole.exit();
1142 private void storeLastKnownDimensions(String string, Rectangle jc) {
1143 Cache.log.debug("Storing last known dimensions for " + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1144 + " height:" + jc.height);
1146 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1147 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1148 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1149 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1155 * @param e DOCUMENT ME!
1158 public void aboutMenuItem_actionPerformed(ActionEvent e) {
1159 new Thread(new Runnable() {
1162 new SplashScreen(false);
1168 * Returns the html text for the About screen, including any available version
1169 * number, build details, author details and citation reference, but without the
1170 * enclosing {@code html} tags
1174 public String getAboutMessage() {
1175 StringBuilder message = new StringBuilder(1024);
1176 message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
1177 .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
1178 .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
1179 .append(Cache.getBuildDetailsForSplash()).append("</strong>");
1181 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1182 if (latestVersion.equals("Checking")) {
1183 // JBP removed this message for 2.11: May be reinstated in future version
1184 // message.append("<br>...Checking latest version...</br>");
1185 } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
1186 boolean red = false;
1187 if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT).indexOf("automated build") == -1) {
1189 // Displayed when code version and jnlp version do not match and code
1190 // version is not a development build
1191 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1194 message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1195 .append(" is available for download from ")
1196 .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
1198 message.append("</div>");
1201 message.append("<br>Authors: ");
1202 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1203 message.append(CITATION);
1205 message.append("</div>");
1207 return message.toString();
1211 * Action on requesting Help documentation
1214 public void documentationMenuItem_actionPerformed() {
1216 if (Platform.isJS()) {
1217 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1225 Help.showHelpWindow();
1227 } catch (Exception ex) {
1228 System.err.println("Error opening help: " + ex.getMessage());
1233 public void closeAll_actionPerformed(ActionEvent e) {
1234 // TODO show a progress bar while closing?
1235 JInternalFrame[] frames = desktop.getAllFrames();
1236 for (int i = 0; i < frames.length; i++) {
1238 frames[i].setClosed(true);
1239 } catch (java.beans.PropertyVetoException ex) {
1242 Jalview.setCurrentAlignFrame(null);
1243 System.out.println("ALL CLOSED");
1246 * reset state of singleton objects as appropriate (clear down session state
1247 * when all windows are closed)
1249 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
1256 public void raiseRelated_actionPerformed(ActionEvent e) {
1257 reorderAssociatedWindows(false, false);
1261 public void minimizeAssociated_actionPerformed(ActionEvent e) {
1262 reorderAssociatedWindows(true, false);
1265 void closeAssociatedWindows() {
1266 reorderAssociatedWindows(false, true);
1272 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1276 protected void garbageCollect_actionPerformed(ActionEvent e) {
1277 // We simply collect the garbage
1278 Cache.log.debug("Collecting garbage...");
1280 Cache.log.debug("Finished garbage collection.");
1286 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1290 protected void showMemusage_actionPerformed(ActionEvent e) {
1291 desktop.showMemoryUsage(showMemusage.isSelected());
1298 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1302 protected void showConsole_actionPerformed(ActionEvent e) {
1303 showConsole(showConsole.isSelected());
1306 Console jconsole = null;
1309 * control whether the java console is visible or not
1313 void showConsole(boolean selected) {
1314 // TODO: decide if we should update properties file
1315 if (jconsole != null) // BH 2018
1317 showConsole.setSelected(selected);
1318 Cache.setProperty("SHOW_JAVA_CONSOLE", Boolean.valueOf(selected).toString());
1319 jconsole.setVisible(selected);
1323 void reorderAssociatedWindows(boolean minimize, boolean close) {
1324 JInternalFrame[] frames = desktop.getAllFrames();
1325 if (frames == null || frames.length < 1) {
1329 AlignmentViewport source = null, target = null;
1330 if (frames[0] instanceof AlignFrame) {
1331 source = ((AlignFrame) frames[0]).getCurrentView();
1332 } else if (frames[0] instanceof TreePanel) {
1333 source = ((TreePanel) frames[0]).getViewPort();
1334 } else if (frames[0] instanceof PCAPanel) {
1335 source = ((PCAPanel) frames[0]).av;
1336 } else if (frames[0].getContentPane() instanceof PairwiseAlignPanel) {
1337 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1340 if (source != null) {
1341 for (int i = 0; i < frames.length; i++) {
1343 if (frames[i] == null) {
1346 if (frames[i] instanceof AlignFrame) {
1347 target = ((AlignFrame) frames[i]).getCurrentView();
1348 } else if (frames[i] instanceof TreePanel) {
1349 target = ((TreePanel) frames[i]).getViewPort();
1350 } else if (frames[i] instanceof PCAPanel) {
1351 target = ((PCAPanel) frames[i]).av;
1352 } else if (frames[i].getContentPane() instanceof PairwiseAlignPanel) {
1353 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1356 if (source == target) {
1359 frames[i].setClosed(true);
1361 frames[i].setIcon(minimize);
1363 frames[i].toFront();
1367 } catch (java.beans.PropertyVetoException ex) {
1377 * @param e DOCUMENT ME!
1380 protected void preferences_actionPerformed(ActionEvent e) {
1381 Preferences.openPreferences();
1385 * Prompts the user to choose a file and then saves the Jalview state as a
1386 * Jalview project file
1389 public void saveState_actionPerformed() {
1390 saveState_actionPerformed(false);
1393 public void saveState_actionPerformed(boolean saveAs) {
1394 java.io.File projectFile = getProjectFile();
1395 // autoSave indicates we already have a file and don't need to ask
1396 boolean autoSave = projectFile != null && !saveAs && BackupFiles.getEnabled();
1398 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1399 // saveAs="+saveAs+", Backups
1400 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1402 boolean approveSave = false;
1404 JalviewFileChooser chooser = new JalviewFileChooser("jvp", "Jalview Project");
1406 chooser.setFileView(new JalviewFileView());
1407 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1409 int value = chooser.showSaveDialog(this);
1411 if (value == JalviewFileChooser.APPROVE_OPTION) {
1412 projectFile = chooser.getSelectedFile();
1413 setProjectFile(projectFile);
1418 if (approveSave || autoSave) {
1419 final Desktop me = this;
1420 final java.io.File chosenFile = projectFile;
1421 new Thread(new Runnable() {
1424 // TODO: refactor to Jalview desktop session controller action.
1426 MessageManager.formatMessage("label.saving_jalview_project", new Object[] { chosenFile.getName() }),
1427 chosenFile.hashCode());
1428 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1429 // TODO catch and handle errors for savestate
1430 // TODO prevent user from messing with the Desktop whilst we're saving
1432 boolean doBackup = BackupFiles.getEnabled();
1433 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
1435 new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
1438 backupfiles.setWriteSuccess(true);
1439 backupfiles.rollBackupsAndRenameTempFile();
1441 } catch (OutOfMemoryError oom) {
1442 new OOMWarning("Whilst saving current state to " + chosenFile.getName(), oom);
1443 } catch (Exception ex) {
1444 Cache.log.error("Problems whilst trying to save to " + chosenFile.getName(), ex);
1445 JvOptionPane.showMessageDialog(me,
1446 MessageManager.formatMessage("label.error_whilst_saving_current_state_to",
1447 new Object[] { chosenFile.getName() }),
1448 MessageManager.getString("label.couldnt_save_project"), JvOptionPane.WARNING_MESSAGE);
1450 setProgressBar(null, chosenFile.hashCode());
1457 public void saveAsState_actionPerformed(ActionEvent e) {
1458 saveState_actionPerformed(true);
1461 private void setProjectFile(File choice) {
1462 this.projectFile = choice;
1465 public File getProjectFile() {
1466 return this.projectFile;
1470 * Shows a file chooser dialog and tries to read in the selected file as a
1474 public void loadState_actionPerformed() {
1475 final String[] suffix = new String[] { "jvp", "jar" };
1476 final String[] desc = new String[] { "Jalview Project", "Jalview Project (old)" };
1477 JalviewFileChooser chooser = new JalviewFileChooser(Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1478 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1482 chooser.setFileView(new JalviewFileView());
1483 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1484 chooser.setResponseHandler(0, new Runnable() {
1487 File selectedFile = chooser.getSelectedFile();
1488 setProjectFile(selectedFile);
1489 String choice = selectedFile.getAbsolutePath();
1490 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1491 new Thread(new Runnable() {
1495 new Jalview2XML().loadJalviewAlign(selectedFile);
1496 } catch (OutOfMemoryError oom) {
1497 new OOMWarning("Whilst loading project from " + choice, oom);
1498 } catch (Exception ex) {
1499 Cache.log.error("Problems whilst loading project from " + choice, ex);
1500 JvOptionPane.showMessageDialog(Desktop.desktop,
1501 MessageManager.formatMessage("label.error_whilst_loading_project_from", new Object[] { choice }),
1502 MessageManager.getString("label.couldnt_load_project"), JvOptionPane.WARNING_MESSAGE);
1505 }, "Project Loader").start();
1509 chooser.showOpenDialog(this);
1513 public void inputSequence_actionPerformed(ActionEvent e) {
1514 new SequenceFetcher(this);
1517 JPanel progressPanel;
1519 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1521 public void startLoading(final Object fileName) {
1522 if (fileLoadingCount == 0) {
1524 .add(addProgressPanel(MessageManager.formatMessage("label.loading_file", new Object[] { fileName })));
1529 private JPanel addProgressPanel(String string) {
1530 if (progressPanel == null) {
1531 progressPanel = new JPanel(new GridLayout(1, 1));
1532 totalProgressCount = 0;
1533 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1535 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1536 JProgressBar progressBar = new JProgressBar();
1537 progressBar.setIndeterminate(true);
1539 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1541 thisprogress.add(progressBar, BorderLayout.CENTER);
1542 progressPanel.add(thisprogress);
1543 ((GridLayout) progressPanel.getLayout()).setRows(((GridLayout) progressPanel.getLayout()).getRows() + 1);
1544 ++totalProgressCount;
1545 instance.validate();
1546 return thisprogress;
1549 int totalProgressCount = 0;
1551 private void removeProgressPanel(JPanel progbar) {
1552 if (progressPanel != null) {
1553 synchronized (progressPanel) {
1554 progressPanel.remove(progbar);
1555 GridLayout gl = (GridLayout) progressPanel.getLayout();
1556 gl.setRows(gl.getRows() - 1);
1557 if (--totalProgressCount < 1) {
1558 this.getContentPane().remove(progressPanel);
1559 progressPanel = null;
1566 public void stopLoading() {
1568 if (fileLoadingCount < 1) {
1569 while (fileLoadingPanels.size() > 0) {
1570 removeProgressPanel(fileLoadingPanels.remove(0));
1572 fileLoadingPanels.clear();
1573 fileLoadingCount = 0;
1578 public static int getViewCount(String alignmentId) {
1579 AlignmentViewport[] aps = getViewports(alignmentId);
1580 return (aps == null) ? 0 : aps.length;
1585 * @param alignmentId - if null, all sets are returned
1586 * @return all AlignmentPanels concerning the alignmentId sequence set
1588 public static AlignmentPanel[] getAlignmentPanels(String alignmentId) {
1589 if (Desktop.desktop == null) {
1590 // no frames created and in headless mode
1591 // TODO: verify that frames are recoverable when in headless mode
1594 List<AlignmentPanel> aps = new ArrayList<>();
1595 AlignFrame[] frames = getAlignFrames();
1596 if (frames == null) {
1599 for (AlignFrame af : frames) {
1600 for (AlignmentPanel ap : af.alignPanels) {
1601 if (alignmentId == null || alignmentId.equals(ap.av.getSequenceSetId())) {
1606 if (aps.size() == 0) {
1609 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1614 * get all the viewports on an alignment.
1616 * @param sequenceSetId unique alignment id (may be null - all viewports
1617 * returned in that case)
1618 * @return all viewports on the alignment bound to sequenceSetId
1620 public static AlignmentViewport[] getViewports(String sequenceSetId) {
1621 List<AlignmentViewport> viewp = new ArrayList<>();
1622 if (desktop != null) {
1623 AlignFrame[] frames = Desktop.getAlignFrames();
1625 for (AlignFrame afr : frames) {
1626 if (sequenceSetId == null || afr.getViewport().getSequenceSetId().equals(sequenceSetId)) {
1627 if (afr.alignPanels != null) {
1628 for (AlignmentPanel ap : afr.alignPanels) {
1629 if (sequenceSetId == null || sequenceSetId.equals(ap.av.getSequenceSetId())) {
1634 viewp.add(afr.getViewport());
1638 if (viewp.size() > 0) {
1639 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1646 * Explode the views in the given frame into separate AlignFrame
1650 public static void explodeViews(AlignFrame af) {
1651 int size = af.alignPanels.size();
1656 // FIXME: ideally should use UI interface API
1657 FeatureSettings viewFeatureSettings = (af.featureSettings != null && af.featureSettings.isOpen())
1658 ? af.featureSettings
1660 Rectangle fsBounds = af.getFeatureSettingsGeometry();
1661 for (int i = 0; i < size; i++) {
1662 AlignmentPanel ap = af.alignPanels.get(i);
1664 AlignFrame newaf = new AlignFrame(ap);
1666 // transfer reference for existing feature settings to new alignFrame
1667 if (ap == af.alignPanel) {
1668 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap) {
1669 newaf.featureSettings = viewFeatureSettings;
1671 newaf.setFeatureSettingsGeometry(fsBounds);
1675 * Restore the view's last exploded frame geometry if known. Multiple views from
1676 * one exploded frame share and restore the same (frame) position and size.
1678 Rectangle geometry = ap.av.getExplodedGeometry();
1679 if (geometry != null) {
1680 newaf.setBounds(geometry);
1683 ap.av.setGatherViewsHere(false);
1685 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1686 // and materialise a new feature settings dialog instance for the new
1688 // (closes the old as if 'OK' was pressed)
1689 if (ap == af.alignPanel && newaf.featureSettings != null && newaf.featureSettings.isOpen()
1690 && af.alignPanel.getAlignViewport().isShowSequenceFeatures()) {
1691 newaf.showFeatureSettingsUI();
1695 af.featureSettings = null;
1696 af.alignPanels.clear();
1697 af.closeMenuItem_actionPerformed(true);
1702 * Gather expanded views (separate AlignFrame's) with the same sequence set
1703 * identifier back in to this frame as additional views, and close the expanded
1704 * views. Note the expanded frames may themselves have multiple views. We take
1709 public void gatherViews(AlignFrame source) {
1710 source.viewport.setGatherViewsHere(true);
1711 source.viewport.setExplodedGeometry(source.getBounds());
1712 JInternalFrame[] frames = desktop.getAllFrames();
1713 String viewId = source.viewport.getSequenceSetId();
1714 for (int t = 0; t < frames.length; t++) {
1715 if (frames[t] instanceof AlignFrame && frames[t] != source) {
1716 AlignFrame af = (AlignFrame) frames[t];
1717 boolean gatherThis = false;
1718 for (int a = 0; a < af.alignPanels.size(); a++) {
1719 AlignmentPanel ap = af.alignPanels.get(a);
1720 if (viewId.equals(ap.av.getSequenceSetId())) {
1722 ap.av.setGatherViewsHere(false);
1723 ap.av.setExplodedGeometry(af.getBounds());
1724 source.addAlignmentPanel(ap, false);
1729 if (af.featureSettings != null && af.featureSettings.isOpen()) {
1730 if (source.featureSettings == null) {
1731 // preserve the feature settings geometry for this frame
1732 source.featureSettings = af.featureSettings;
1733 source.setFeatureSettingsGeometry(af.getFeatureSettingsGeometry());
1735 // close it and forget
1736 af.featureSettings.close();
1739 af.alignPanels.clear();
1740 af.closeMenuItem_actionPerformed(true);
1745 // refresh the feature setting UI for the source frame if it exists
1746 if (source.featureSettings != null && source.featureSettings.isOpen()) {
1747 source.showFeatureSettingsUI();
1752 public JInternalFrame[] getAllFrames() {
1753 return desktop.getAllFrames();
1757 * Checks the given url to see if it gives a response indicating that the user
1758 * should be informed of a new questionnaire.
1762 public void checkForQuestionnaire(String url) {
1763 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
1764 // javax.swing.SwingUtilities.invokeLater(jvq);
1765 new Thread(jvq).start();
1768 public void checkURLLinks() {
1769 // Thread off the URL link checker
1770 addDialogThread(new Runnable() {
1773 if (Cache.getDefault("CHECKURLLINKS", true)) {
1774 // check what the actual links are - if it's just the default don't
1775 // bother with the warning
1776 List<String> links = Preferences.sequenceUrlLinks.getLinksForMenu();
1778 // only need to check links if there is one with a
1779 // SEQUENCE_ID which is not the default EMBL_EBI link
1780 ListIterator<String> li = links.listIterator();
1781 boolean check = false;
1782 List<JLabel> urls = new ArrayList<>();
1783 while (li.hasNext()) {
1784 String link = li.next();
1785 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID) && !UrlConstants.isDefaultString(link)) {
1787 int barPos = link.indexOf("|");
1788 String urlMsg = barPos == -1 ? link : link.substring(0, barPos) + ": " + link.substring(barPos + 1);
1789 urls.add(new JLabel(urlMsg));
1796 // ask user to check in case URL links use old style tokens
1797 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
1798 JPanel msgPanel = new JPanel();
1799 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
1800 msgPanel.add(Box.createVerticalGlue());
1801 JLabel msg = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
1802 JLabel msg2 = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
1804 for (JLabel url : urls) {
1809 final JCheckBox jcb = new JCheckBox(MessageManager.getString("label.do_not_display_again"));
1810 jcb.addActionListener(new ActionListener() {
1812 public void actionPerformed(ActionEvent e) {
1813 // update Cache settings for "don't show this again"
1814 boolean showWarningAgain = !jcb.isSelected();
1815 Cache.setProperty("CHECKURLLINKS", Boolean.valueOf(showWarningAgain).toString());
1820 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
1821 MessageManager.getString("label.SEQUENCE_ID_no_longer_used"), JvOptionPane.WARNING_MESSAGE);
1828 * Proxy class for JDesktopPane which optionally displays the current memory
1829 * usage and highlights the desktop area with a red bar if free memory runs low.
1833 public class MyDesktopPane extends JDesktopPane implements Runnable {
1834 private static final float ONE_MB = 1048576f;
1836 boolean showMemoryUsage = false;
1840 java.text.NumberFormat df;
1842 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory, percentUsage;
1844 public MyDesktopPane(boolean showMemoryUsage) {
1845 showMemoryUsage(showMemoryUsage);
1848 public void showMemoryUsage(boolean showMemory) {
1849 this.showMemoryUsage = showMemory;
1851 Thread worker = new Thread(this);
1857 public boolean isShowMemoryUsage() {
1858 return showMemoryUsage;
1863 df = java.text.NumberFormat.getNumberInstance();
1864 df.setMaximumFractionDigits(2);
1865 runtime = Runtime.getRuntime();
1867 while (showMemoryUsage) {
1869 maxMemory = runtime.maxMemory() / ONE_MB;
1870 allocatedMemory = runtime.totalMemory() / ONE_MB;
1871 freeMemory = runtime.freeMemory() / ONE_MB;
1872 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
1874 percentUsage = (totalFreeMemory / maxMemory) * 100;
1876 // if (percentUsage < 20)
1878 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
1880 // instance.set.setBorder(border1);
1883 // sleep after showing usage
1885 } catch (Exception ex) {
1886 ex.printStackTrace();
1892 public void paintComponent(Graphics g) {
1893 if (showMemoryUsage && g != null && df != null) {
1894 if (percentUsage < 20) {
1895 g.setColor(Color.red);
1897 FontMetrics fm = g.getFontMetrics();
1900 MessageManager.formatMessage("label.memory_stats",
1901 new Object[] { df.format(totalFreeMemory), df.format(maxMemory), df.format(percentUsage) }),
1902 10, getHeight() - fm.getHeight());
1906 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
1907 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
1912 * Accessor method to quickly get all the AlignmentFrames loaded.
1914 * @return an array of AlignFrame, or null if none found
1916 public static AlignFrame[] getAlignFrames() {
1917 if (Jalview.isHeadlessMode()) {
1918 // Desktop.desktop is null in headless mode
1919 return new AlignFrame[] { Jalview.currentAlignFrame };
1922 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1924 if (frames == null) {
1927 List<AlignFrame> avp = new ArrayList<>();
1929 for (int i = frames.length - 1; i > -1; i--) {
1930 if (frames[i] instanceof AlignFrame) {
1931 avp.add((AlignFrame) frames[i]);
1932 } else if (frames[i] instanceof SplitFrame) {
1934 * Also check for a split frame containing an AlignFrame
1936 GSplitFrame sf = (GSplitFrame) frames[i];
1937 if (sf.getTopFrame() instanceof AlignFrame) {
1938 avp.add((AlignFrame) sf.getTopFrame());
1940 if (sf.getBottomFrame() instanceof AlignFrame) {
1941 avp.add((AlignFrame) sf.getBottomFrame());
1945 if (avp.size() == 0) {
1948 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
1953 * Returns an array of any AppJmol frames in the Desktop (or null if none).
1957 public GStructureViewer[] getJmols() {
1958 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1960 if (frames == null) {
1963 List<GStructureViewer> avp = new ArrayList<>();
1965 for (int i = frames.length - 1; i > -1; i--) {
1966 if (frames[i] instanceof AppJmol) {
1967 GStructureViewer af = (GStructureViewer) frames[i];
1971 if (avp.size() == 0) {
1974 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
1979 * Add Groovy Support to Jalview
1982 public void groovyShell_actionPerformed() {
1984 openGroovyConsole();
1985 } catch (Exception ex) {
1986 Cache.log.error("Groovy Shell Creation failed.", ex);
1987 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
1989 MessageManager.getString("label.couldnt_create_groovy_shell"),
1990 MessageManager.getString("label.groovy_support_failed"), JvOptionPane.ERROR_MESSAGE);
1995 * Open the Groovy console
1997 void openGroovyConsole() {
1998 if (groovyConsole == null) {
1999 groovyConsole = new groovy.ui.Console();
2000 groovyConsole.setVariable("Jalview", this);
2001 groovyConsole.run();
2004 * We allow only one console at a time, so that AlignFrame menu option
2005 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2006 * enable 'Run script', when the console is opened, and the reverse when it is
2009 Window window = (Window) groovyConsole.getFrame();
2010 window.addWindowListener(new WindowAdapter() {
2012 public void windowClosed(WindowEvent e) {
2014 * rebind CMD-Q from Groovy Console to Jalview Quit
2017 enableExecuteGroovy(false);
2023 * show Groovy console window (after close and reopen)
2025 ((Window) groovyConsole.getFrame()).setVisible(true);
2028 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2029 * opening a second console
2031 enableExecuteGroovy(true);
2035 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
2038 protected void addQuitHandler() {
2039 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2040 KeyStroke.getKeyStroke(KeyEvent.VK_Q, jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
2042 getRootPane().getActionMap().put("Quit", new AbstractAction() {
2044 public void actionPerformed(ActionEvent e) {
2051 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2053 * @param enabled true if Groovy console is open
2055 public void enableExecuteGroovy(boolean enabled) {
2057 * disable opening a second Groovy console (or re-enable when the console is
2060 groovyShell.setEnabled(!enabled);
2062 AlignFrame[] alignFrames = getAlignFrames();
2063 if (alignFrames != null) {
2064 for (AlignFrame af : alignFrames) {
2065 af.setGroovyEnabled(enabled);
2071 * Progress bars managed by the IProgressIndicator method.
2073 private Hashtable<Long, JPanel> progressBars;
2075 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2080 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2083 public void setProgressBar(String message, long id) {
2084 if (progressBars == null) {
2085 progressBars = new Hashtable<>();
2086 progressBarHandlers = new Hashtable<>();
2089 if (progressBars.get(Long.valueOf(id)) != null) {
2090 JPanel panel = progressBars.remove(Long.valueOf(id));
2091 if (progressBarHandlers.contains(Long.valueOf(id))) {
2092 progressBarHandlers.remove(Long.valueOf(id));
2094 removeProgressPanel(panel);
2096 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2103 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2104 * jalview.gui.IProgressIndicatorHandler)
2107 public void registerHandler(final long id, final IProgressIndicatorHandler handler) {
2108 if (progressBarHandlers == null || !progressBars.containsKey(Long.valueOf(id))) {
2109 throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
2111 progressBarHandlers.put(Long.valueOf(id), handler);
2112 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2113 if (handler.canCancel()) {
2114 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
2115 final IProgressIndicator us = this;
2116 cancel.addActionListener(new ActionListener() {
2119 public void actionPerformed(ActionEvent e) {
2120 handler.cancelActivity(id);
2121 us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
2122 new Object[] { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
2125 progressPanel.add(cancel, BorderLayout.EAST);
2131 * @return true if any progress bars are still active
2134 public boolean operationInProgress() {
2135 if (progressBars != null && progressBars.size() > 0) {
2142 * This will return the first AlignFrame holding the given viewport instance. It
2143 * will break if there are more than one AlignFrames viewing a particular av.
2146 * @return alignFrame for viewport
2148 public static AlignFrame getAlignFrameFor(AlignViewportI viewport) {
2149 if (desktop != null) {
2150 AlignmentPanel[] aps = getAlignmentPanels(viewport.getSequenceSetId());
2151 for (int panel = 0; aps != null && panel < aps.length; panel++) {
2152 if (aps[panel] != null && aps[panel].av == viewport) {
2153 return aps[panel].alignFrame;
2160 public VamsasApplication getVamsasApplication() {
2161 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2167 * flag set if jalview GUI is being operated programmatically
2169 private boolean inBatchMode = false;
2172 * check if jalview GUI is being operated programmatically
2174 * @return inBatchMode
2176 public boolean isInBatchMode() {
2181 * set flag if jalview GUI is being operated programmatically
2183 * @param inBatchMode
2185 public void setInBatchMode(boolean inBatchMode) {
2186 this.inBatchMode = inBatchMode;
2190 * start service discovery and wait till it is done
2192 public void startServiceDiscovery() {
2193 startServiceDiscovery(false);
2197 * start service discovery threads - blocking or non-blocking
2201 public void startServiceDiscovery(boolean blocking) {
2202 startServiceDiscovery(blocking, false);
2206 * start service discovery threads
2208 * @param blocking - false means call returns
2210 * @param ignore_SHOW_JWS2_SERVICES_preference - when true JABA services are
2211 * discovered regardless of user's
2212 * JWS2 discovery preference setting
2214 public void startServiceDiscovery(boolean blocking, boolean ignore_SHOW_JWS2_SERVICES_preference) {
2215 boolean alive = true;
2216 Thread t0 = null, t1 = null, t2 = null;
2217 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2219 // todo: changesupport handlers need to be transferred
2220 if (discoverer == null) {
2221 discoverer = new jalview.ws.jws1.Discoverer();
2222 // register PCS handler for desktop.
2223 discoverer.addPropertyChangeListener(changeSupport);
2225 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2226 // until we phase out completely
2227 (t0 = new Thread(discoverer)).start();
2230 if (ignore_SHOW_JWS2_SERVICES_preference || Cache.getDefault("SHOW_JWS2_SERVICES", true)) {
2231 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(changeSupport);
2235 // TODO: do rest service discovery
2241 } catch (Exception e) {
2243 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive()) || (t3 != null && t3.isAlive())
2244 || (t0 != null && t0.isAlive());
2250 * called to check if the service discovery process completed successfully.
2254 protected void JalviewServicesChanged(PropertyChangeEvent evt) {
2255 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector) {
2256 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().getErrorMessages();
2257 if (ermsg != null) {
2258 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true)) {
2259 if (serviceChangedDialog == null) {
2260 // only run if we aren't already displaying one of these.
2261 addDialogThread(serviceChangedDialog = new Runnable() {
2266 * JalviewDialog jd =new JalviewDialog() {
2268 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2270 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2272 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2274 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2276 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2277 * + " or mis-configured HTTP proxy settings.<br/>" +
2278 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2279 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2280 * true, true, "Web Service Configuration Problem", 450, 400);
2282 * jd.waitForInput();
2284 JvOptionPane.showConfirmDialog(Desktop.desktop,
2285 new JLabel("<html><table width=\"450\"><tr><td>" + ermsg + "</td></tr></table>"
2286 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2287 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2288 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2289 + " Tools->Preferences dialog box to change them.</p></html>"),
2290 "Web Service Configuration Problem", JvOptionPane.DEFAULT_OPTION, JvOptionPane.ERROR_MESSAGE);
2291 serviceChangedDialog = null;
2297 Cache.log.error("Errors reported by JABA discovery service. Check web services preferences.\n" + ermsg);
2303 private Runnable serviceChangedDialog = null;
2306 * start a thread to open a URL in the configured browser. Pops up a warning
2307 * dialog to the user if there is an exception when calling out to the browser
2312 public static void showUrl(final String url) {
2313 showUrl(url, Desktop.instance);
2317 * Like showUrl but allows progress handler to be specified
2320 * @param progress (null) or object implementing IProgressIndicator
2322 public static void showUrl(final String url, final IProgressIndicator progress) {
2323 new Thread(new Runnable() {
2327 if (progress != null) {
2328 progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[] { url }),
2331 jalview.util.BrowserLauncher.openURL(url);
2332 } catch (Exception ex) {
2333 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2334 MessageManager.getString("label.web_browser_not_found_unix"),
2335 MessageManager.getString("label.web_browser_not_found"), JvOptionPane.WARNING_MESSAGE);
2337 ex.printStackTrace();
2339 if (progress != null) {
2340 progress.setProgressBar(null, this.hashCode());
2346 public static WsParamSetManager wsparamManager = null;
2348 public static ParamManager getUserParameterStore() {
2349 if (wsparamManager == null) {
2350 wsparamManager = new WsParamSetManager();
2352 return wsparamManager;
2356 * static hyperlink handler proxy method for use by Jalview's internal windows
2360 public static void hyperlinkUpdate(HyperlinkEvent e) {
2361 if (e.getEventType() == EventType.ACTIVATED) {
2364 url = e.getURL().toString();
2365 Desktop.showUrl(url);
2366 } catch (Exception x) {
2368 if (Cache.log != null) {
2369 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2371 System.err.println("Couldn't handle string " + url + " as a URL.");
2374 // ignore any exceptions due to dud links.
2381 * single thread that handles display of dialogs to user.
2383 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2386 * flag indicating if dialogExecutor should try to acquire a permit
2388 private volatile boolean dialogPause = true;
2393 private java.util.concurrent.Semaphore block = new Semaphore(0);
2395 private static groovy.ui.Console groovyConsole;
2398 * add another dialog thread to the queue
2402 public void addDialogThread(final Runnable prompter) {
2403 dialogExecutor.submit(new Runnable() {
2409 } catch (InterruptedException x) {
2412 if (instance == null) {
2416 SwingUtilities.invokeAndWait(prompter);
2417 } catch (Exception q) {
2418 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2424 public void startDialogQueue() {
2425 // set the flag so we don't pause waiting for another permit and semaphore
2426 // the current task to begin
2427 dialogPause = false;
2432 * Outputs an image of the desktop to file in EPS format, after prompting the
2433 * user for choice of Text or Lineart character rendering (unless a preference
2434 * has been set). The file name is generated as
2437 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2441 protected void snapShotWindow_actionPerformed(ActionEvent e) {
2442 // currently the menu option to do this is not shown
2445 int width = getWidth();
2446 int height = getHeight();
2447 File of = new File("Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2448 ImageWriterI writer = new ImageWriterI() {
2450 public void exportImage(Graphics g) throws Exception {
2452 Cache.log.info("Successfully written snapshot to file " + of.getAbsolutePath());
2455 String title = "View of desktop";
2456 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS, title);
2457 exporter.doExport(of, this, width, height, title);
2461 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2462 * This respects (remembers) any previous 'exploded geometry' i.e. the size and
2463 * location last time the view was expanded (if any). However it does not
2464 * remember the split pane divider location - this is set to match the
2465 * 'exploding' frame.
2469 public void explodeViews(SplitFrame sf) {
2470 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2471 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2472 List<? extends AlignmentViewPanel> topPanels = oldTopFrame.getAlignPanels();
2473 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame.getAlignPanels();
2474 int viewCount = topPanels.size();
2475 if (viewCount < 2) {
2480 * Processing in reverse order works, forwards order leaves the first panels not
2481 * visible. I don't know why!
2483 for (int i = viewCount - 1; i >= 0; i--) {
2485 * Make new top and bottom frames. These take over the respective AlignmentPanel
2486 * objects, including their AlignmentViewports, so the cdna/protein
2487 * relationships between the viewports is carried over to the new split frames.
2489 * explodedGeometry holds the (x, y) position of the previously exploded
2490 * SplitFrame, and the (width, height) of the AlignFrame component
2492 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
2493 AlignFrame newTopFrame = new AlignFrame(topPanel);
2494 newTopFrame.setSize(oldTopFrame.getSize());
2495 newTopFrame.setVisible(true);
2496 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport()).getExplodedGeometry();
2497 if (geometry != null) {
2498 newTopFrame.setSize(geometry.getSize());
2501 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
2502 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
2503 newBottomFrame.setSize(oldBottomFrame.getSize());
2504 newBottomFrame.setVisible(true);
2505 geometry = ((AlignViewport) bottomPanel.getAlignViewport()).getExplodedGeometry();
2506 if (geometry != null) {
2507 newBottomFrame.setSize(geometry.getSize());
2510 topPanel.av.setGatherViewsHere(false);
2511 bottomPanel.av.setGatherViewsHere(false);
2512 JInternalFrame splitFrame = new SplitFrame(newTopFrame, newBottomFrame);
2513 if (geometry != null) {
2514 splitFrame.setLocation(geometry.getLocation());
2516 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
2520 * Clear references to the panels (now relocated in the new SplitFrames) before
2521 * closing the old SplitFrame.
2524 bottomPanels.clear();
2529 * Gather expanded split frames, sharing the same pairs of sequence set ids,
2530 * back into the given SplitFrame as additional views. Note that the gathered
2531 * frames may themselves have multiple views.
2535 public void gatherViews(GSplitFrame source) {
2537 * special handling of explodedGeometry for a view within a SplitFrame: - it
2538 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
2539 * height) of the AlignFrame component
2541 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
2542 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
2543 myTopFrame.viewport.setExplodedGeometry(
2544 new Rectangle(source.getX(), source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
2545 myBottomFrame.viewport.setExplodedGeometry(
2546 new Rectangle(source.getX(), source.getY(), myBottomFrame.getWidth(), myBottomFrame.getHeight()));
2547 myTopFrame.viewport.setGatherViewsHere(true);
2548 myBottomFrame.viewport.setGatherViewsHere(true);
2549 String topViewId = myTopFrame.viewport.getSequenceSetId();
2550 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
2552 JInternalFrame[] frames = desktop.getAllFrames();
2553 for (JInternalFrame frame : frames) {
2554 if (frame instanceof SplitFrame && frame != source) {
2555 SplitFrame sf = (SplitFrame) frame;
2556 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
2557 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
2558 boolean gatherThis = false;
2559 for (int a = 0; a < topFrame.alignPanels.size(); a++) {
2560 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
2561 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
2562 if (topViewId.equals(topPanel.av.getSequenceSetId())
2563 && bottomViewId.equals(bottomPanel.av.getSequenceSetId())) {
2565 topPanel.av.setGatherViewsHere(false);
2566 bottomPanel.av.setGatherViewsHere(false);
2567 topPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), topFrame.getSize()));
2568 bottomPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), bottomFrame.getSize()));
2569 myTopFrame.addAlignmentPanel(topPanel, false);
2570 myBottomFrame.addAlignmentPanel(bottomPanel, false);
2575 topFrame.getAlignPanels().clear();
2576 bottomFrame.getAlignPanels().clear();
2583 * The dust settles...give focus to the tab we did this from.
2585 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
2588 public static groovy.ui.Console getGroovyConsole() {
2589 return groovyConsole;
2593 * handles the payload of a drag and drop event.
2595 * TODO refactor to desktop utilities class
2597 * @param files - Data source strings extracted from the drop event
2598 * @param protocols - protocol for each data source extracted from the drop
2600 * @param evt - the drop event
2601 * @param t - the payload from the drop event
2604 public static void transferFromDropTarget(List<Object> files, List<DataSourceType> protocols, DropTargetDropEvent evt,
2605 Transferable t) throws Exception {
2607 DataFlavor uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"), urlFlavour = null;
2609 urlFlavour = new DataFlavor("application/x-java-url; class=java.net.URL");
2610 } catch (ClassNotFoundException cfe) {
2611 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
2614 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) {
2617 java.net.URL url = (URL) t.getTransferData(urlFlavour);
2618 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
2619 // means url may be null.
2621 protocols.add(DataSourceType.URL);
2622 files.add(url.toString());
2623 Cache.log.debug("Drop handled as URL dataflavor " + files.get(files.size() - 1));
2626 if (Platform.isAMacAndNotJS()) {
2627 System.err.println("Please ignore plist error - occurs due to problem with java 8 on OSX");
2630 } catch (Throwable ex) {
2631 Cache.log.debug("URL drop handler failed.", ex);
2634 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
2635 // Works on Windows and MacOSX
2636 Cache.log.debug("Drop handled as javaFileListFlavor");
2637 for (Object file : (List) t.getTransferData(DataFlavor.javaFileListFlavor)) {
2639 protocols.add(DataSourceType.FILE);
2642 // Unix like behaviour
2643 boolean added = false;
2645 if (t.isDataFlavorSupported(uriListFlavor)) {
2646 Cache.log.debug("Drop handled as uriListFlavor");
2647 // This is used by Unix drag system
2648 data = (String) t.getTransferData(uriListFlavor);
2651 // fallback to text: workaround - on OSX where there's a JVM bug
2652 Cache.log.debug("standard URIListFlavor failed. Trying text");
2653 // try text fallback
2654 DataFlavor textDf = new DataFlavor("text/plain;class=java.lang.String");
2655 if (t.isDataFlavorSupported(textDf)) {
2656 data = (String) t.getTransferData(textDf);
2659 Cache.log.debug("Plain text drop content returned " + (data == null ? "Null - failed" : data));
2663 while (protocols.size() < files.size()) {
2664 Cache.log.debug("Adding missing FILE protocol for " + files.get(protocols.size()));
2665 protocols.add(DataSourceType.FILE);
2667 for (java.util.StringTokenizer st = new java.util.StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
2669 String s = st.nextToken();
2670 if (s.startsWith("#")) {
2671 // the line is a comment (as per the RFC 2483)
2674 java.net.URI uri = new java.net.URI(s);
2675 if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http")) {
2676 protocols.add(DataSourceType.URL);
2677 files.add(uri.toString());
2679 // otherwise preserve old behaviour: catch all for file objects
2680 java.io.File file = new java.io.File(uri);
2681 protocols.add(DataSourceType.FILE);
2682 files.add(file.toString());
2687 if (Cache.log.isDebugEnabled()) {
2688 if (data == null || !added) {
2690 if (t.getTransferDataFlavors() != null && t.getTransferDataFlavors().length > 0) {
2691 Cache.log.debug("Couldn't resolve drop data. Here are the supported flavors:");
2692 for (DataFlavor fl : t.getTransferDataFlavors()) {
2693 Cache.log.debug("Supported transfer dataflavor: " + fl.toString());
2694 Object df = t.getTransferData(fl);
2696 Cache.log.debug("Retrieves: " + df);
2698 Cache.log.debug("Retrieved nothing");
2702 Cache.log.debug("Couldn't resolve dataflavor for drop: " + t.toString());
2707 if (Platform.isWindowsAndNotJS()) {
2708 Cache.log.debug("Scanning dropped content for Windows Link Files");
2710 // resolve any .lnk files in the file drop
2711 for (int f = 0; f < files.size(); f++) {
2712 String source = files.get(f).toString().toLowerCase(Locale.ROOT);
2713 if (protocols.get(f).equals(DataSourceType.FILE)
2714 && (source.endsWith(".lnk") || source.endsWith(".url") || source.endsWith(".site"))) {
2716 Object obj = files.get(f);
2717 File lf = (obj instanceof File ? (File) obj : new File((String) obj));
2718 // process link file to get a URL
2719 Cache.log.debug("Found potential link file: " + lf);
2720 WindowsShortcut wscfile = new WindowsShortcut(lf);
2721 String fullname = wscfile.getRealFilename();
2722 protocols.set(f, FormatAdapter.checkProtocol(fullname));
2723 files.set(f, fullname);
2724 Cache.log.debug("Parsed real filename " + fullname + " to extract protocol: " + protocols.get(f));
2725 } catch (Exception ex) {
2726 Cache.log.error("Couldn't parse " + files.get(f) + " as a link file.", ex);
2734 * Sets the Preferences property for experimental features to True or False
2735 * depending on the state of the controlling menu item
2738 protected void showExperimental_actionPerformed(boolean selected) {
2739 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
2743 * Answers a (possibly empty) list of any structure viewer frames (currently for
2744 * either Jmol or Chimera) which are currently open. This may optionally be
2745 * restricted to viewers of a specified class, or viewers linked to a specified
2748 * @param apanel if not null, only return viewers linked to this
2750 * @param structureViewerClass if not null, only return viewers of this class
2753 public List<StructureViewerBase> getStructureViewers(AlignmentPanel apanel,
2754 Class<? extends StructureViewerBase> structureViewerClass) {
2755 List<StructureViewerBase> result = new ArrayList<>();
2756 JInternalFrame[] frames = Desktop.instance.getAllFrames();
2758 for (JInternalFrame frame : frames) {
2759 if (frame instanceof StructureViewerBase) {
2760 if (structureViewerClass == null || structureViewerClass.isInstance(frame)) {
2761 if (apanel == null || ((StructureViewerBase) frame).isLinkedWith(apanel)) {
2762 result.add((StructureViewerBase) frame);
2770 public static final String debugScaleMessage = "Desktop graphics transform scale=";
2772 private static boolean debugScaleMessageDone = false;
2774 public static void debugScaleMessage(Graphics g) {
2775 if (debugScaleMessageDone) {
2778 // output used by tests to check HiDPI scaling settings in action
2780 Graphics2D gg = (Graphics2D) g;
2782 AffineTransform t = gg.getTransform();
2783 double scaleX = t.getScaleX();
2784 double scaleY = t.getScaleY();
2785 Cache.debug(debugScaleMessage + scaleX + " (X)");
2786 Cache.debug(debugScaleMessage + scaleY + " (Y)");
2787 debugScaleMessageDone = true;
2789 Cache.debug("Desktop graphics null");
2791 } catch (Exception e) {
2792 Cache.debug(Cache.getStackTraceString(e));