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) {
854 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
857 "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
859 >>>>>>> 48040645e (JAL-3741 http to https (and jar to jvp for exampleFile defaults!))
864 * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
869 private static void setKeyBindings(JInternalFrame frame) {
870 @SuppressWarnings("serial")
871 final Action closeAction = new AbstractAction() {
873 public void actionPerformed(ActionEvent e) {
879 * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
881 KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
882 KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
884 InputMap inputMap = frame.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
885 String ctrlW = ctrlWKey.toString();
886 inputMap.put(ctrlWKey, ctrlW);
887 inputMap.put(cmdWKey, ctrlW);
889 ActionMap actionMap = frame.getActionMap();
890 actionMap.put(ctrlW, closeAction);
894 public void lostOwnership(Clipboard clipboard, Transferable contents) {
896 Desktop.jalviewClipboard = null;
899 internalCopy = false;
903 public void dragEnter(DropTargetDragEvent evt) {
907 public void dragExit(DropTargetEvent evt) {
911 public void dragOver(DropTargetDragEvent evt) {
915 public void dropActionChanged(DropTargetDragEvent evt) {
921 * @param evt DOCUMENT ME!
924 public void drop(DropTargetDropEvent evt) {
925 boolean success = true;
926 // JAL-1552 - acceptDrop required before getTransferable call for
927 // Java's Transferable for native dnd
928 evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
929 Transferable t = evt.getTransferable();
930 List<Object> files = new ArrayList<>();
931 List<DataSourceType> protocols = new ArrayList<>();
934 Desktop.transferFromDropTarget(files, protocols, evt, t);
935 } catch (Exception e) {
942 for (int i = 0; i < files.size(); i++) {
943 // BH 2018 File or String
944 Object file = files.get(i);
945 String fileName = file.toString();
946 DataSourceType protocol = (protocols == null) ? DataSourceType.FILE : protocols.get(i);
947 FileFormatI format = null;
949 if (fileName.endsWith(".jar")) {
950 format = FileFormat.Jalview;
953 format = new IdentifyFile().identify(file, protocol);
955 if (file instanceof File) {
956 Platform.cacheFileData((File) file);
958 new FileLoader().LoadFile(null, file, protocol, format);
961 } catch (Exception ex) {
965 evt.dropComplete(success); // need this to ensure input focus is properly
966 // transfered to any new windows created
972 * @param e DOCUMENT ME!
975 public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) {
976 String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
977 JalviewFileChooser chooser = JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat,
978 BackupFiles.getEnabled());
980 chooser.setFileView(new JalviewFileView());
981 chooser.setDialogTitle(MessageManager.getString("label.open_local_file"));
982 chooser.setToolTipText(MessageManager.getString("action.open"));
984 chooser.setResponseHandler(0, new Runnable() {
987 File selectedFile = chooser.getSelectedFile();
988 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
990 FileFormatI format = chooser.getSelectedFormat();
993 * Call IdentifyFile to verify the file contains what its extension implies.
994 * Skip this step for dynamically added file formats, because IdentifyFile does
995 * not know how to recognise them.
997 if (FileFormats.getInstance().isIdentifiable(format)) {
999 format = new IdentifyFile().identify(selectedFile, DataSourceType.FILE);
1000 } catch (FileFormatException e) {
1001 // format = null; //??
1005 new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, format);
1008 chooser.showOpenDialog(this);
1012 * Shows a dialog for input of a URL at which to retrieve alignment data
1017 public void inputURLMenuItem_actionPerformed(AlignViewport viewport) {
1018 // This construct allows us to have a wider textfield
1020 JLabel label = new JLabel(MessageManager.getString("label.input_file_url"));
1022 JPanel panel = new JPanel(new GridLayout(2, 1));
1027 history.setPreferredSize(new Dimension(400, 20));
1028 history.setEditable(true);
1029 history.addItem("https://www.");
1031 String historyItems = jalview.bin.Cache.getProperty("RECENT_URL");
1032 >>>>>>> 48040645e (JAL-3741 http to https (and jar to jvp for exampleFile defaults!))
1035 * the URL to fetch is input in Java: an editable combobox with history JS:
1036 * (pending JAL-3038) a plain text field
1039 String urlBase = "https://www.";
1040 if (Platform.isJS()) {
1041 history = new JTextField(urlBase, 35);
1049 JComboBox<String> asCombo = new JComboBox<>();
1050 asCombo.setPreferredSize(new Dimension(400, 20));
1051 asCombo.setEditable(true);
1052 asCombo.addItem(urlBase);
1053 String historyItems = Cache.getProperty("RECENT_URL");
1054 if (historyItems != null) {
1055 for (String token : historyItems.split("\\t")) {
1056 asCombo.addItem(token);
1063 Object[] options = new Object[] { MessageManager.getString("action.ok"),
1064 MessageManager.getString("action.cancel") };
1065 Runnable action = new Runnable() {
1068 @SuppressWarnings("unchecked")
1069 String url = (history instanceof JTextField ? ((JTextField) history).getText()
1070 : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
1072 if (url.toLowerCase().endsWith(".jar")) {
1073 if (viewport != null) {
1074 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
1076 new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
1079 FileFormatI format = null;
1081 format = new IdentifyFile().identify(url, DataSourceType.URL);
1082 } catch (FileFormatException e) {
1083 // TODO revise error handling, distinguish between
1084 // URL not found and response not valid
1087 if (format == null) {
1088 String msg = MessageManager.formatMessage("label.couldnt_locate", url);
1089 JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
1090 MessageManager.getString("label.url_not_found"), JvOptionPane.WARNING_MESSAGE);
1095 if (viewport != null) {
1096 new FileLoader().LoadFile(viewport, url, DataSourceType.URL, format);
1098 new FileLoader().LoadFile(url, DataSourceType.URL, format);
1103 String dialogOption = MessageManager.getString("label.input_alignment_from_url");
1104 JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action).showInternalDialog(panel, dialogOption,
1105 JvOptionPane.YES_NO_CANCEL_OPTION, JvOptionPane.PLAIN_MESSAGE, null, options,
1106 MessageManager.getString("action.ok"));
1110 * Opens the CutAndPaste window for the user to paste an alignment in to
1112 * @param viewPanel - if not null, the pasted alignment is added to the current
1113 * alignment; if null, to a new alignment window
1116 public void inputTextboxMenuItem_actionPerformed(AlignmentViewPanel viewPanel) {
1117 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1118 cap.setForInput(viewPanel);
1119 Desktop.addInternalFrame(cap, MessageManager.getString("label.cut_paste_alignmen_file"), true, 600, 500);
1126 public void quit() {
1127 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1128 Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
1129 Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
1130 storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, getWidth(), getHeight()));
1132 if (jconsole != null) {
1133 storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
1134 jconsole.stopConsole();
1136 if (jvnews != null) {
1137 storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
1140 if (dialogExecutor != null) {
1141 dialogExecutor.shutdownNow();
1143 closeAll_actionPerformed(null);
1145 if (groovyConsole != null) {
1146 // suppress a possible repeat prompt to save script
1147 groovyConsole.setDirty(false);
1148 groovyConsole.exit();
1153 private void storeLastKnownDimensions(String string, Rectangle jc) {
1154 Cache.log.debug("Storing last known dimensions for " + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
1155 + " height:" + jc.height);
1157 Cache.setProperty(string + "SCREEN_X", jc.x + "");
1158 Cache.setProperty(string + "SCREEN_Y", jc.y + "");
1159 Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
1160 Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
1166 * @param e DOCUMENT ME!
1169 public void aboutMenuItem_actionPerformed(ActionEvent e) {
1170 new Thread(new Runnable() {
1173 new SplashScreen(false);
1179 * Returns the html text for the About screen, including any available version
1180 * number, build details, author details and citation reference, but without the
1181 * enclosing {@code html} tags
1185 public String getAboutMessage() {
1186 StringBuilder message = new StringBuilder(1024);
1187 message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
1188 .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
1189 .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
1190 .append(Cache.getBuildDetailsForSplash()).append("</strong>");
1192 String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
1193 if (latestVersion.equals("Checking")) {
1194 // JBP removed this message for 2.11: May be reinstated in future version
1195 // message.append("<br>...Checking latest version...</br>");
1196 } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
1197 boolean red = false;
1198 if (Cache.getProperty("VERSION").toLowerCase().indexOf("automated build") == -1) {
1200 // Displayed when code version and jnlp version do not match and code
1201 // version is not a development build
1202 message.append("<div style=\"color: #FF0000;font-style: bold;\">");
1206 message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
1207 .append(" is available for download from ")
1208 .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
1210 message.append("</div>");
1213 message.append("<br>Authors: ");
1214 message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
1215 message.append(CITATION);
1217 message.append("</div>");
1219 return message.toString();
1221 message.append("<br>!! Version "
1222 + jalview.bin.Cache.getDefault("LATEST_VERSION",
1224 + " is available for download from "
1225 + jalview.bin.Cache.getDefault("www.jalview.org",
1226 "https://www.jalview.org")
1230 message.append("</div>");
1233 message.append("<br>Authors: " + jalview.bin.Cache.getDefault(
1235 "The Jalview Authors (See AUTHORS file for current list)")
1236 + "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
1237 + "<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"
1238 + "<br><br>If you use Jalview, please cite:"
1239 + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
1240 + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
1241 + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033"
1244 >>>>>>> 48040645e (JAL-3741 http to https (and jar to jvp for exampleFile defaults!))
1248 * Action on requesting Help documentation
1251 public void documentationMenuItem_actionPerformed() {
1253 if (Platform.isJS()) {
1254 BrowserLauncher.openURL("https://www.jalview.org/help.html");
1262 Help.showHelpWindow();
1264 } catch (Exception ex) {
1265 System.err.println("Error opening help: " + ex.getMessage());
1270 public void closeAll_actionPerformed(ActionEvent e) {
1271 // TODO show a progress bar while closing?
1272 JInternalFrame[] frames = desktop.getAllFrames();
1273 for (int i = 0; i < frames.length; i++) {
1275 frames[i].setClosed(true);
1276 } catch (java.beans.PropertyVetoException ex) {
1279 Jalview.setCurrentAlignFrame(null);
1280 System.out.println("ALL CLOSED");
1283 * reset state of singleton objects as appropriate (clear down session state
1284 * when all windows are closed)
1286 StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
1293 public void raiseRelated_actionPerformed(ActionEvent e) {
1294 reorderAssociatedWindows(false, false);
1298 public void minimizeAssociated_actionPerformed(ActionEvent e) {
1299 reorderAssociatedWindows(true, false);
1302 void closeAssociatedWindows() {
1303 reorderAssociatedWindows(false, true);
1309 * @seejalview.jbgui.GDesktop#garbageCollect_actionPerformed(java.awt.event.
1313 protected void garbageCollect_actionPerformed(ActionEvent e) {
1314 // We simply collect the garbage
1315 Cache.log.debug("Collecting garbage...");
1317 Cache.log.debug("Finished garbage collection.");
1323 * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
1327 protected void showMemusage_actionPerformed(ActionEvent e) {
1328 desktop.showMemoryUsage(showMemusage.isSelected());
1335 * jalview.jbgui.GDesktop#showConsole_actionPerformed(java.awt.event.ActionEvent
1339 protected void showConsole_actionPerformed(ActionEvent e) {
1340 showConsole(showConsole.isSelected());
1343 Console jconsole = null;
1346 * control whether the java console is visible or not
1350 void showConsole(boolean selected) {
1351 // TODO: decide if we should update properties file
1352 if (jconsole != null) // BH 2018
1354 showConsole.setSelected(selected);
1355 Cache.setProperty("SHOW_JAVA_CONSOLE", Boolean.valueOf(selected).toString());
1356 jconsole.setVisible(selected);
1360 void reorderAssociatedWindows(boolean minimize, boolean close) {
1361 JInternalFrame[] frames = desktop.getAllFrames();
1362 if (frames == null || frames.length < 1) {
1366 AlignmentViewport source = null, target = null;
1367 if (frames[0] instanceof AlignFrame) {
1368 source = ((AlignFrame) frames[0]).getCurrentView();
1369 } else if (frames[0] instanceof TreePanel) {
1370 source = ((TreePanel) frames[0]).getViewPort();
1371 } else if (frames[0] instanceof PCAPanel) {
1372 source = ((PCAPanel) frames[0]).av;
1373 } else if (frames[0].getContentPane() instanceof PairwiseAlignPanel) {
1374 source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
1377 if (source != null) {
1378 for (int i = 0; i < frames.length; i++) {
1380 if (frames[i] == null) {
1383 if (frames[i] instanceof AlignFrame) {
1384 target = ((AlignFrame) frames[i]).getCurrentView();
1385 } else if (frames[i] instanceof TreePanel) {
1386 target = ((TreePanel) frames[i]).getViewPort();
1387 } else if (frames[i] instanceof PCAPanel) {
1388 target = ((PCAPanel) frames[i]).av;
1389 } else if (frames[i].getContentPane() instanceof PairwiseAlignPanel) {
1390 target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
1393 if (source == target) {
1396 frames[i].setClosed(true);
1398 frames[i].setIcon(minimize);
1400 frames[i].toFront();
1404 } catch (java.beans.PropertyVetoException ex) {
1414 * @param e DOCUMENT ME!
1417 protected void preferences_actionPerformed(ActionEvent e) {
1418 Preferences.openPreferences();
1422 * Prompts the user to choose a file and then saves the Jalview state as a
1423 * Jalview project file
1426 public void saveState_actionPerformed() {
1427 saveState_actionPerformed(false);
1430 public void saveState_actionPerformed(boolean saveAs) {
1431 java.io.File projectFile = getProjectFile();
1432 // autoSave indicates we already have a file and don't need to ask
1433 boolean autoSave = projectFile != null && !saveAs && BackupFiles.getEnabled();
1435 // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
1436 // saveAs="+saveAs+", Backups
1437 // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
1439 boolean approveSave = false;
1441 JalviewFileChooser chooser = new JalviewFileChooser("jvp", "Jalview Project");
1443 chooser.setFileView(new JalviewFileView());
1444 chooser.setDialogTitle(MessageManager.getString("label.save_state"));
1446 int value = chooser.showSaveDialog(this);
1448 if (value == JalviewFileChooser.APPROVE_OPTION) {
1449 projectFile = chooser.getSelectedFile();
1450 setProjectFile(projectFile);
1455 if (approveSave || autoSave) {
1456 final Desktop me = this;
1457 final java.io.File chosenFile = projectFile;
1458 new Thread(new Runnable() {
1461 // TODO: refactor to Jalview desktop session controller action.
1463 MessageManager.formatMessage("label.saving_jalview_project", new Object[] { chosenFile.getName() }),
1464 chosenFile.hashCode());
1465 Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
1466 // TODO catch and handle errors for savestate
1467 // TODO prevent user from messing with the Desktop whilst we're saving
1469 boolean doBackup = BackupFiles.getEnabled();
1470 BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
1472 new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
1475 backupfiles.setWriteSuccess(true);
1476 backupfiles.rollBackupsAndRenameTempFile();
1478 } catch (OutOfMemoryError oom) {
1479 new OOMWarning("Whilst saving current state to " + chosenFile.getName(), oom);
1480 } catch (Exception ex) {
1481 Cache.log.error("Problems whilst trying to save to " + chosenFile.getName(), ex);
1482 JvOptionPane.showMessageDialog(me,
1483 MessageManager.formatMessage("label.error_whilst_saving_current_state_to",
1484 new Object[] { chosenFile.getName() }),
1485 MessageManager.getString("label.couldnt_save_project"), JvOptionPane.WARNING_MESSAGE);
1487 setProgressBar(null, chosenFile.hashCode());
1494 public void saveAsState_actionPerformed(ActionEvent e) {
1495 saveState_actionPerformed(true);
1498 private void setProjectFile(File choice) {
1499 this.projectFile = choice;
1502 public File getProjectFile() {
1503 return this.projectFile;
1507 * Shows a file chooser dialog and tries to read in the selected file as a
1511 public void loadState_actionPerformed() {
1512 final String[] suffix = new String[] { "jvp", "jar" };
1513 final String[] desc = new String[] { "Jalview Project", "Jalview Project (old)" };
1514 JalviewFileChooser chooser = new JalviewFileChooser(Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
1515 "Jalview Project", true, BackupFiles.getEnabled()); // last two
1519 chooser.setFileView(new JalviewFileView());
1520 chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
1521 chooser.setResponseHandler(0, new Runnable() {
1524 File selectedFile = chooser.getSelectedFile();
1525 setProjectFile(selectedFile);
1526 String choice = selectedFile.getAbsolutePath();
1527 Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
1528 new Thread(new Runnable() {
1532 new Jalview2XML().loadJalviewAlign(selectedFile);
1533 } catch (OutOfMemoryError oom) {
1534 new OOMWarning("Whilst loading project from " + choice, oom);
1535 } catch (Exception ex) {
1536 Cache.log.error("Problems whilst loading project from " + choice, ex);
1537 JvOptionPane.showMessageDialog(Desktop.desktop,
1538 MessageManager.formatMessage("label.error_whilst_loading_project_from", new Object[] { choice }),
1539 MessageManager.getString("label.couldnt_load_project"), JvOptionPane.WARNING_MESSAGE);
1542 }, "Project Loader").start();
1546 chooser.showOpenDialog(this);
1550 public void inputSequence_actionPerformed(ActionEvent e) {
1551 new SequenceFetcher(this);
1554 JPanel progressPanel;
1556 ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
1558 public void startLoading(final Object fileName) {
1559 if (fileLoadingCount == 0) {
1561 .add(addProgressPanel(MessageManager.formatMessage("label.loading_file", new Object[] { fileName })));
1566 private JPanel addProgressPanel(String string) {
1567 if (progressPanel == null) {
1568 progressPanel = new JPanel(new GridLayout(1, 1));
1569 totalProgressCount = 0;
1570 instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
1572 JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
1573 JProgressBar progressBar = new JProgressBar();
1574 progressBar.setIndeterminate(true);
1576 thisprogress.add(new JLabel(string), BorderLayout.WEST);
1578 thisprogress.add(progressBar, BorderLayout.CENTER);
1579 progressPanel.add(thisprogress);
1580 ((GridLayout) progressPanel.getLayout()).setRows(((GridLayout) progressPanel.getLayout()).getRows() + 1);
1581 ++totalProgressCount;
1582 instance.validate();
1583 return thisprogress;
1586 int totalProgressCount = 0;
1588 private void removeProgressPanel(JPanel progbar) {
1589 if (progressPanel != null) {
1590 synchronized (progressPanel) {
1591 progressPanel.remove(progbar);
1592 GridLayout gl = (GridLayout) progressPanel.getLayout();
1593 gl.setRows(gl.getRows() - 1);
1594 if (--totalProgressCount < 1) {
1595 this.getContentPane().remove(progressPanel);
1596 progressPanel = null;
1603 public void stopLoading() {
1605 if (fileLoadingCount < 1) {
1606 while (fileLoadingPanels.size() > 0) {
1607 removeProgressPanel(fileLoadingPanels.remove(0));
1609 fileLoadingPanels.clear();
1610 fileLoadingCount = 0;
1615 public static int getViewCount(String alignmentId) {
1616 AlignmentViewport[] aps = getViewports(alignmentId);
1617 return (aps == null) ? 0 : aps.length;
1622 * @param alignmentId - if null, all sets are returned
1623 * @return all AlignmentPanels concerning the alignmentId sequence set
1625 public static AlignmentPanel[] getAlignmentPanels(String alignmentId) {
1626 if (Desktop.desktop == null) {
1627 // no frames created and in headless mode
1628 // TODO: verify that frames are recoverable when in headless mode
1631 List<AlignmentPanel> aps = new ArrayList<>();
1632 AlignFrame[] frames = getAlignFrames();
1633 if (frames == null) {
1636 for (AlignFrame af : frames) {
1637 for (AlignmentPanel ap : af.alignPanels) {
1638 if (alignmentId == null || alignmentId.equals(ap.av.getSequenceSetId())) {
1643 if (aps.size() == 0) {
1646 AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
1651 * get all the viewports on an alignment.
1653 * @param sequenceSetId unique alignment id (may be null - all viewports
1654 * returned in that case)
1655 * @return all viewports on the alignment bound to sequenceSetId
1657 public static AlignmentViewport[] getViewports(String sequenceSetId) {
1658 List<AlignmentViewport> viewp = new ArrayList<>();
1659 if (desktop != null) {
1660 AlignFrame[] frames = Desktop.getAlignFrames();
1662 for (AlignFrame afr : frames) {
1663 if (sequenceSetId == null || afr.getViewport().getSequenceSetId().equals(sequenceSetId)) {
1664 if (afr.alignPanels != null) {
1665 for (AlignmentPanel ap : afr.alignPanels) {
1666 if (sequenceSetId == null || sequenceSetId.equals(ap.av.getSequenceSetId())) {
1671 viewp.add(afr.getViewport());
1675 if (viewp.size() > 0) {
1676 return viewp.toArray(new AlignmentViewport[viewp.size()]);
1683 * Explode the views in the given frame into separate AlignFrame
1687 public static void explodeViews(AlignFrame af) {
1688 int size = af.alignPanels.size();
1693 // FIXME: ideally should use UI interface API
1694 FeatureSettings viewFeatureSettings = (af.featureSettings != null && af.featureSettings.isOpen())
1695 ? af.featureSettings
1697 Rectangle fsBounds = af.getFeatureSettingsGeometry();
1698 for (int i = 0; i < size; i++) {
1699 AlignmentPanel ap = af.alignPanels.get(i);
1701 AlignFrame newaf = new AlignFrame(ap);
1703 // transfer reference for existing feature settings to new alignFrame
1704 if (ap == af.alignPanel) {
1705 if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap) {
1706 newaf.featureSettings = viewFeatureSettings;
1708 newaf.setFeatureSettingsGeometry(fsBounds);
1712 * Restore the view's last exploded frame geometry if known. Multiple views from
1713 * one exploded frame share and restore the same (frame) position and size.
1715 Rectangle geometry = ap.av.getExplodedGeometry();
1716 if (geometry != null) {
1717 newaf.setBounds(geometry);
1720 ap.av.setGatherViewsHere(false);
1722 addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1723 // and materialise a new feature settings dialog instance for the new
1725 // (closes the old as if 'OK' was pressed)
1726 if (ap == af.alignPanel && newaf.featureSettings != null && newaf.featureSettings.isOpen()
1727 && af.alignPanel.getAlignViewport().isShowSequenceFeatures()) {
1728 newaf.showFeatureSettingsUI();
1732 af.featureSettings = null;
1733 af.alignPanels.clear();
1734 af.closeMenuItem_actionPerformed(true);
1739 * Gather expanded views (separate AlignFrame's) with the same sequence set
1740 * identifier back in to this frame as additional views, and close the expanded
1741 * views. Note the expanded frames may themselves have multiple views. We take
1746 public void gatherViews(AlignFrame source) {
1747 source.viewport.setGatherViewsHere(true);
1748 source.viewport.setExplodedGeometry(source.getBounds());
1749 JInternalFrame[] frames = desktop.getAllFrames();
1750 String viewId = source.viewport.getSequenceSetId();
1751 for (int t = 0; t < frames.length; t++) {
1752 if (frames[t] instanceof AlignFrame && frames[t] != source) {
1753 AlignFrame af = (AlignFrame) frames[t];
1754 boolean gatherThis = false;
1755 for (int a = 0; a < af.alignPanels.size(); a++) {
1756 AlignmentPanel ap = af.alignPanels.get(a);
1757 if (viewId.equals(ap.av.getSequenceSetId())) {
1759 ap.av.setGatherViewsHere(false);
1760 ap.av.setExplodedGeometry(af.getBounds());
1761 source.addAlignmentPanel(ap, false);
1766 if (af.featureSettings != null && af.featureSettings.isOpen()) {
1767 if (source.featureSettings == null) {
1768 // preserve the feature settings geometry for this frame
1769 source.featureSettings = af.featureSettings;
1770 source.setFeatureSettingsGeometry(af.getFeatureSettingsGeometry());
1772 // close it and forget
1773 af.featureSettings.close();
1776 af.alignPanels.clear();
1777 af.closeMenuItem_actionPerformed(true);
1782 // refresh the feature setting UI for the source frame if it exists
1783 if (source.featureSettings != null && source.featureSettings.isOpen()) {
1784 source.showFeatureSettingsUI();
1789 public JInternalFrame[] getAllFrames() {
1790 return desktop.getAllFrames();
1794 * Checks the given url to see if it gives a response indicating that the user
1795 * should be informed of a new questionnaire.
1799 public void checkForQuestionnaire(String url) {
1800 UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
1801 // javax.swing.SwingUtilities.invokeLater(jvq);
1802 new Thread(jvq).start();
1805 public void checkURLLinks() {
1806 // Thread off the URL link checker
1807 addDialogThread(new Runnable() {
1810 if (Cache.getDefault("CHECKURLLINKS", true)) {
1811 // check what the actual links are - if it's just the default don't
1812 // bother with the warning
1813 List<String> links = Preferences.sequenceUrlLinks.getLinksForMenu();
1815 // only need to check links if there is one with a
1816 // SEQUENCE_ID which is not the default EMBL_EBI link
1817 ListIterator<String> li = links.listIterator();
1818 boolean check = false;
1819 List<JLabel> urls = new ArrayList<>();
1820 while (li.hasNext()) {
1821 String link = li.next();
1822 if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID) && !UrlConstants.isDefaultString(link)) {
1824 int barPos = link.indexOf("|");
1825 String urlMsg = barPos == -1 ? link : link.substring(0, barPos) + ": " + link.substring(barPos + 1);
1826 urls.add(new JLabel(urlMsg));
1833 // ask user to check in case URL links use old style tokens
1834 // ($SEQUENCE_ID$ for sequence id _or_ accession id)
1835 JPanel msgPanel = new JPanel();
1836 msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
1837 msgPanel.add(Box.createVerticalGlue());
1838 JLabel msg = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
1839 JLabel msg2 = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
1841 for (JLabel url : urls) {
1846 final JCheckBox jcb = new JCheckBox(MessageManager.getString("label.do_not_display_again"));
1847 jcb.addActionListener(new ActionListener() {
1849 public void actionPerformed(ActionEvent e) {
1850 // update Cache settings for "don't show this again"
1851 boolean showWarningAgain = !jcb.isSelected();
1852 Cache.setProperty("CHECKURLLINKS", Boolean.valueOf(showWarningAgain).toString());
1857 JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
1858 MessageManager.getString("label.SEQUENCE_ID_no_longer_used"), JvOptionPane.WARNING_MESSAGE);
1865 * Proxy class for JDesktopPane which optionally displays the current memory
1866 * usage and highlights the desktop area with a red bar if free memory runs low.
1870 public class MyDesktopPane extends JDesktopPane implements Runnable {
1871 private static final float ONE_MB = 1048576f;
1873 boolean showMemoryUsage = false;
1877 java.text.NumberFormat df;
1879 float maxMemory, allocatedMemory, freeMemory, totalFreeMemory, percentUsage;
1881 public MyDesktopPane(boolean showMemoryUsage) {
1882 showMemoryUsage(showMemoryUsage);
1885 public void showMemoryUsage(boolean showMemory) {
1886 this.showMemoryUsage = showMemory;
1888 Thread worker = new Thread(this);
1894 public boolean isShowMemoryUsage() {
1895 return showMemoryUsage;
1900 df = java.text.NumberFormat.getNumberInstance();
1901 df.setMaximumFractionDigits(2);
1902 runtime = Runtime.getRuntime();
1904 while (showMemoryUsage) {
1906 maxMemory = runtime.maxMemory() / ONE_MB;
1907 allocatedMemory = runtime.totalMemory() / ONE_MB;
1908 freeMemory = runtime.freeMemory() / ONE_MB;
1909 totalFreeMemory = freeMemory + (maxMemory - allocatedMemory);
1911 percentUsage = (totalFreeMemory / maxMemory) * 100;
1913 // if (percentUsage < 20)
1915 // border1 = BorderFactory.createMatteBorder(12, 12, 12, 12,
1917 // instance.set.setBorder(border1);
1920 // sleep after showing usage
1922 } catch (Exception ex) {
1923 ex.printStackTrace();
1929 public void paintComponent(Graphics g) {
1930 if (showMemoryUsage && g != null && df != null) {
1931 if (percentUsage < 20) {
1932 g.setColor(Color.red);
1934 FontMetrics fm = g.getFontMetrics();
1937 MessageManager.formatMessage("label.memory_stats",
1938 new Object[] { df.format(totalFreeMemory), df.format(maxMemory), df.format(percentUsage) }),
1939 10, getHeight() - fm.getHeight());
1943 // output debug scale message. Important for jalview.bin.HiDPISettingTest2
1944 Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
1949 * Accessor method to quickly get all the AlignmentFrames loaded.
1951 * @return an array of AlignFrame, or null if none found
1953 public static AlignFrame[] getAlignFrames() {
1954 if (Jalview.isHeadlessMode()) {
1955 // Desktop.desktop is null in headless mode
1956 return new AlignFrame[] { Jalview.currentAlignFrame };
1959 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1961 if (frames == null) {
1964 List<AlignFrame> avp = new ArrayList<>();
1966 for (int i = frames.length - 1; i > -1; i--) {
1967 if (frames[i] instanceof AlignFrame) {
1968 avp.add((AlignFrame) frames[i]);
1969 } else if (frames[i] instanceof SplitFrame) {
1971 * Also check for a split frame containing an AlignFrame
1973 GSplitFrame sf = (GSplitFrame) frames[i];
1974 if (sf.getTopFrame() instanceof AlignFrame) {
1975 avp.add((AlignFrame) sf.getTopFrame());
1977 if (sf.getBottomFrame() instanceof AlignFrame) {
1978 avp.add((AlignFrame) sf.getBottomFrame());
1982 if (avp.size() == 0) {
1985 AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
1990 * Returns an array of any AppJmol frames in the Desktop (or null if none).
1994 public GStructureViewer[] getJmols() {
1995 JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1997 if (frames == null) {
2000 List<GStructureViewer> avp = new ArrayList<>();
2002 for (int i = frames.length - 1; i > -1; i--) {
2003 if (frames[i] instanceof AppJmol) {
2004 GStructureViewer af = (GStructureViewer) frames[i];
2008 if (avp.size() == 0) {
2011 GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
2016 * Add Groovy Support to Jalview
2019 public void groovyShell_actionPerformed() {
2021 openGroovyConsole();
2022 } catch (Exception ex) {
2023 Cache.log.error("Groovy Shell Creation failed.", ex);
2024 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2026 MessageManager.getString("label.couldnt_create_groovy_shell"),
2027 MessageManager.getString("label.groovy_support_failed"), JvOptionPane.ERROR_MESSAGE);
2032 * Open the Groovy console
2034 void openGroovyConsole() {
2035 if (groovyConsole == null) {
2036 groovyConsole = new groovy.ui.Console();
2037 groovyConsole.setVariable("Jalview", this);
2038 groovyConsole.run();
2041 * We allow only one console at a time, so that AlignFrame menu option
2042 * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
2043 * enable 'Run script', when the console is opened, and the reverse when it is
2046 Window window = (Window) groovyConsole.getFrame();
2047 window.addWindowListener(new WindowAdapter() {
2049 public void windowClosed(WindowEvent e) {
2051 * rebind CMD-Q from Groovy Console to Jalview Quit
2054 enableExecuteGroovy(false);
2060 * show Groovy console window (after close and reopen)
2062 ((Window) groovyConsole.getFrame()).setVisible(true);
2065 * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
2066 * opening a second console
2068 enableExecuteGroovy(true);
2072 * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
2075 protected void addQuitHandler() {
2076 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
2077 KeyStroke.getKeyStroke(KeyEvent.VK_Q, jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
2079 getRootPane().getActionMap().put("Quit", new AbstractAction() {
2081 public void actionPerformed(ActionEvent e) {
2088 * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
2090 * @param enabled true if Groovy console is open
2092 public void enableExecuteGroovy(boolean enabled) {
2094 * disable opening a second Groovy console (or re-enable when the console is
2097 groovyShell.setEnabled(!enabled);
2099 AlignFrame[] alignFrames = getAlignFrames();
2100 if (alignFrames != null) {
2101 for (AlignFrame af : alignFrames) {
2102 af.setGroovyEnabled(enabled);
2108 * Progress bars managed by the IProgressIndicator method.
2110 private Hashtable<Long, JPanel> progressBars;
2112 private Hashtable<Long, IProgressIndicatorHandler> progressBarHandlers;
2117 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
2120 public void setProgressBar(String message, long id) {
2121 if (progressBars == null) {
2122 progressBars = new Hashtable<>();
2123 progressBarHandlers = new Hashtable<>();
2126 if (progressBars.get(Long.valueOf(id)) != null) {
2127 JPanel panel = progressBars.remove(Long.valueOf(id));
2128 if (progressBarHandlers.contains(Long.valueOf(id))) {
2129 progressBarHandlers.remove(Long.valueOf(id));
2131 removeProgressPanel(panel);
2133 progressBars.put(Long.valueOf(id), addProgressPanel(message));
2140 * @see jalview.gui.IProgressIndicator#registerHandler(long,
2141 * jalview.gui.IProgressIndicatorHandler)
2144 public void registerHandler(final long id, final IProgressIndicatorHandler handler) {
2145 if (progressBarHandlers == null || !progressBars.containsKey(Long.valueOf(id))) {
2146 throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
2148 progressBarHandlers.put(Long.valueOf(id), handler);
2149 final JPanel progressPanel = progressBars.get(Long.valueOf(id));
2150 if (handler.canCancel()) {
2151 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
2152 final IProgressIndicator us = this;
2153 cancel.addActionListener(new ActionListener() {
2156 public void actionPerformed(ActionEvent e) {
2157 handler.cancelActivity(id);
2158 us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
2159 new Object[] { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
2162 progressPanel.add(cancel, BorderLayout.EAST);
2168 * @return true if any progress bars are still active
2171 public boolean operationInProgress() {
2172 if (progressBars != null && progressBars.size() > 0) {
2179 * This will return the first AlignFrame holding the given viewport instance. It
2180 * will break if there are more than one AlignFrames viewing a particular av.
2183 * @return alignFrame for viewport
2185 public static AlignFrame getAlignFrameFor(AlignViewportI viewport) {
2186 if (desktop != null) {
2187 AlignmentPanel[] aps = getAlignmentPanels(viewport.getSequenceSetId());
2188 for (int panel = 0; aps != null && panel < aps.length; panel++) {
2189 if (aps[panel] != null && aps[panel].av == viewport) {
2190 return aps[panel].alignFrame;
2197 public VamsasApplication getVamsasApplication() {
2198 // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
2204 * flag set if jalview GUI is being operated programmatically
2206 private boolean inBatchMode = false;
2209 * check if jalview GUI is being operated programmatically
2211 * @return inBatchMode
2213 public boolean isInBatchMode() {
2218 * set flag if jalview GUI is being operated programmatically
2220 * @param inBatchMode
2222 public void setInBatchMode(boolean inBatchMode) {
2223 this.inBatchMode = inBatchMode;
2227 * start service discovery and wait till it is done
2229 public void startServiceDiscovery() {
2230 startServiceDiscovery(false);
2234 * start service discovery threads - blocking or non-blocking
2238 public void startServiceDiscovery(boolean blocking) {
2239 startServiceDiscovery(blocking, false);
2243 * start service discovery threads
2245 * @param blocking - false means call returns
2247 * @param ignore_SHOW_JWS2_SERVICES_preference - when true JABA services are
2248 * discovered regardless of user's
2249 * JWS2 discovery preference setting
2251 public void startServiceDiscovery(boolean blocking, boolean ignore_SHOW_JWS2_SERVICES_preference) {
2252 boolean alive = true;
2253 Thread t0 = null, t1 = null, t2 = null;
2254 // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
2256 // todo: changesupport handlers need to be transferred
2257 if (discoverer == null) {
2258 discoverer = new jalview.ws.jws1.Discoverer();
2259 // register PCS handler for desktop.
2260 discoverer.addPropertyChangeListener(changeSupport);
2262 // JAL-940 - disabled JWS1 service configuration - always start discoverer
2263 // until we phase out completely
2264 (t0 = new Thread(discoverer)).start();
2267 if (ignore_SHOW_JWS2_SERVICES_preference || Cache.getDefault("SHOW_JWS2_SERVICES", true)) {
2268 t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(changeSupport);
2272 // TODO: do rest service discovery
2278 } catch (Exception e) {
2280 alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive()) || (t3 != null && t3.isAlive())
2281 || (t0 != null && t0.isAlive());
2287 * called to check if the service discovery process completed successfully.
2291 protected void JalviewServicesChanged(PropertyChangeEvent evt) {
2292 if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector) {
2293 final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().getErrorMessages();
2294 if (ermsg != null) {
2295 if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true)) {
2296 if (serviceChangedDialog == null) {
2297 // only run if we aren't already displaying one of these.
2298 addDialogThread(serviceChangedDialog = new Runnable() {
2303 * JalviewDialog jd =new JalviewDialog() {
2305 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
2307 * }@Override protected void okPressed() { // TODO Auto-generated method stub
2309 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
2311 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
2313 * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
2314 * + " or mis-configured HTTP proxy settings.<br/>" +
2315 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
2316 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
2317 * true, true, "Web Service Configuration Problem", 450, 400);
2319 * jd.waitForInput();
2321 JvOptionPane.showConfirmDialog(Desktop.desktop,
2322 new JLabel("<html><table width=\"450\"><tr><td>" + ermsg + "</td></tr></table>"
2323 + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
2324 + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
2325 + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
2326 + " Tools->Preferences dialog box to change them.</p></html>"),
2327 "Web Service Configuration Problem", JvOptionPane.DEFAULT_OPTION, JvOptionPane.ERROR_MESSAGE);
2328 serviceChangedDialog = null;
2334 Cache.log.error("Errors reported by JABA discovery service. Check web services preferences.\n" + ermsg);
2340 private Runnable serviceChangedDialog = null;
2343 * start a thread to open a URL in the configured browser. Pops up a warning
2344 * dialog to the user if there is an exception when calling out to the browser
2349 public static void showUrl(final String url) {
2350 showUrl(url, Desktop.instance);
2354 * Like showUrl but allows progress handler to be specified
2357 * @param progress (null) or object implementing IProgressIndicator
2359 public static void showUrl(final String url, final IProgressIndicator progress) {
2360 new Thread(new Runnable() {
2364 if (progress != null) {
2365 progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[] { url }),
2368 jalview.util.BrowserLauncher.openURL(url);
2369 } catch (Exception ex) {
2370 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2371 MessageManager.getString("label.web_browser_not_found_unix"),
2372 MessageManager.getString("label.web_browser_not_found"), JvOptionPane.WARNING_MESSAGE);
2374 ex.printStackTrace();
2376 if (progress != null) {
2377 progress.setProgressBar(null, this.hashCode());
2383 public static WsParamSetManager wsparamManager = null;
2385 public static ParamManager getUserParameterStore() {
2386 if (wsparamManager == null) {
2387 wsparamManager = new WsParamSetManager();
2389 return wsparamManager;
2393 * static hyperlink handler proxy method for use by Jalview's internal windows
2397 public static void hyperlinkUpdate(HyperlinkEvent e) {
2398 if (e.getEventType() == EventType.ACTIVATED) {
2401 url = e.getURL().toString();
2402 Desktop.showUrl(url);
2403 } catch (Exception x) {
2405 if (Cache.log != null) {
2406 Cache.log.error("Couldn't handle string " + url + " as a URL.");
2408 System.err.println("Couldn't handle string " + url + " as a URL.");
2411 // ignore any exceptions due to dud links.
2418 * single thread that handles display of dialogs to user.
2420 ExecutorService dialogExecutor = Executors.newSingleThreadExecutor();
2423 * flag indicating if dialogExecutor should try to acquire a permit
2425 private volatile boolean dialogPause = true;
2430 private java.util.concurrent.Semaphore block = new Semaphore(0);
2432 private static groovy.ui.Console groovyConsole;
2435 * add another dialog thread to the queue
2439 public void addDialogThread(final Runnable prompter) {
2440 dialogExecutor.submit(new Runnable() {
2446 } catch (InterruptedException x) {
2449 if (instance == null) {
2453 SwingUtilities.invokeAndWait(prompter);
2454 } catch (Exception q) {
2455 Cache.log.warn("Unexpected Exception in dialog thread.", q);
2461 public void startDialogQueue() {
2462 // set the flag so we don't pause waiting for another permit and semaphore
2463 // the current task to begin
2464 dialogPause = false;
2469 * Outputs an image of the desktop to file in EPS format, after prompting the
2470 * user for choice of Text or Lineart character rendering (unless a preference
2471 * has been set). The file name is generated as
2474 * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
2478 protected void snapShotWindow_actionPerformed(ActionEvent e) {
2479 // currently the menu option to do this is not shown
2482 int width = getWidth();
2483 int height = getHeight();
2484 File of = new File("Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
2485 ImageWriterI writer = new ImageWriterI() {
2487 public void exportImage(Graphics g) throws Exception {
2489 Cache.log.info("Successfully written snapshot to file " + of.getAbsolutePath());
2492 String title = "View of desktop";
2493 ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS, title);
2494 exporter.doExport(of, this, width, height, title);
2498 * Explode the views in the given SplitFrame into separate SplitFrame windows.
2499 * This respects (remembers) any previous 'exploded geometry' i.e. the size and
2500 * location last time the view was expanded (if any). However it does not
2501 * remember the split pane divider location - this is set to match the
2502 * 'exploding' frame.
2506 public void explodeViews(SplitFrame sf) {
2507 AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
2508 AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
2509 List<? extends AlignmentViewPanel> topPanels = oldTopFrame.getAlignPanels();
2510 List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame.getAlignPanels();
2511 int viewCount = topPanels.size();
2512 if (viewCount < 2) {
2517 * Processing in reverse order works, forwards order leaves the first panels not
2518 * visible. I don't know why!
2520 for (int i = viewCount - 1; i >= 0; i--) {
2522 * Make new top and bottom frames. These take over the respective AlignmentPanel
2523 * objects, including their AlignmentViewports, so the cdna/protein
2524 * relationships between the viewports is carried over to the new split frames.
2526 * explodedGeometry holds the (x, y) position of the previously exploded
2527 * SplitFrame, and the (width, height) of the AlignFrame component
2529 AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
2530 AlignFrame newTopFrame = new AlignFrame(topPanel);
2531 newTopFrame.setSize(oldTopFrame.getSize());
2532 newTopFrame.setVisible(true);
2533 Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport()).getExplodedGeometry();
2534 if (geometry != null) {
2535 newTopFrame.setSize(geometry.getSize());
2538 AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
2539 AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
2540 newBottomFrame.setSize(oldBottomFrame.getSize());
2541 newBottomFrame.setVisible(true);
2542 geometry = ((AlignViewport) bottomPanel.getAlignViewport()).getExplodedGeometry();
2543 if (geometry != null) {
2544 newBottomFrame.setSize(geometry.getSize());
2547 topPanel.av.setGatherViewsHere(false);
2548 bottomPanel.av.setGatherViewsHere(false);
2549 JInternalFrame splitFrame = new SplitFrame(newTopFrame, newBottomFrame);
2550 if (geometry != null) {
2551 splitFrame.setLocation(geometry.getLocation());
2553 Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
2557 * Clear references to the panels (now relocated in the new SplitFrames) before
2558 * closing the old SplitFrame.
2561 bottomPanels.clear();
2566 * Gather expanded split frames, sharing the same pairs of sequence set ids,
2567 * back into the given SplitFrame as additional views. Note that the gathered
2568 * frames may themselves have multiple views.
2572 public void gatherViews(GSplitFrame source) {
2574 * special handling of explodedGeometry for a view within a SplitFrame: - it
2575 * holds the (x, y) position of the enclosing SplitFrame, and the (width,
2576 * height) of the AlignFrame component
2578 AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
2579 AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
2580 myTopFrame.viewport.setExplodedGeometry(
2581 new Rectangle(source.getX(), source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
2582 myBottomFrame.viewport.setExplodedGeometry(
2583 new Rectangle(source.getX(), source.getY(), myBottomFrame.getWidth(), myBottomFrame.getHeight()));
2584 myTopFrame.viewport.setGatherViewsHere(true);
2585 myBottomFrame.viewport.setGatherViewsHere(true);
2586 String topViewId = myTopFrame.viewport.getSequenceSetId();
2587 String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
2589 JInternalFrame[] frames = desktop.getAllFrames();
2590 for (JInternalFrame frame : frames) {
2591 if (frame instanceof SplitFrame && frame != source) {
2592 SplitFrame sf = (SplitFrame) frame;
2593 AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
2594 AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
2595 boolean gatherThis = false;
2596 for (int a = 0; a < topFrame.alignPanels.size(); a++) {
2597 AlignmentPanel topPanel = topFrame.alignPanels.get(a);
2598 AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
2599 if (topViewId.equals(topPanel.av.getSequenceSetId())
2600 && bottomViewId.equals(bottomPanel.av.getSequenceSetId())) {
2602 topPanel.av.setGatherViewsHere(false);
2603 bottomPanel.av.setGatherViewsHere(false);
2604 topPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), topFrame.getSize()));
2605 bottomPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), bottomFrame.getSize()));
2606 myTopFrame.addAlignmentPanel(topPanel, false);
2607 myBottomFrame.addAlignmentPanel(bottomPanel, false);
2612 topFrame.getAlignPanels().clear();
2613 bottomFrame.getAlignPanels().clear();
2620 * The dust settles...give focus to the tab we did this from.
2622 myTopFrame.setDisplayedView(myTopFrame.alignPanel);
2625 public static groovy.ui.Console getGroovyConsole() {
2626 return groovyConsole;
2630 * handles the payload of a drag and drop event.
2632 * TODO refactor to desktop utilities class
2634 * @param files - Data source strings extracted from the drop event
2635 * @param protocols - protocol for each data source extracted from the drop
2637 * @param evt - the drop event
2638 * @param t - the payload from the drop event
2641 public static void transferFromDropTarget(List<Object> files, List<DataSourceType> protocols, DropTargetDropEvent evt,
2642 Transferable t) throws Exception {
2644 DataFlavor uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"), urlFlavour = null;
2646 urlFlavour = new DataFlavor("application/x-java-url; class=java.net.URL");
2647 } catch (ClassNotFoundException cfe) {
2648 Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
2651 if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) {
2654 java.net.URL url = (URL) t.getTransferData(urlFlavour);
2655 // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
2656 // means url may be null.
2658 protocols.add(DataSourceType.URL);
2659 files.add(url.toString());
2660 Cache.log.debug("Drop handled as URL dataflavor " + files.get(files.size() - 1));
2663 if (Platform.isAMacAndNotJS()) {
2664 System.err.println("Please ignore plist error - occurs due to problem with java 8 on OSX");
2667 } catch (Throwable ex) {
2668 Cache.log.debug("URL drop handler failed.", ex);
2671 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
2672 // Works on Windows and MacOSX
2673 Cache.log.debug("Drop handled as javaFileListFlavor");
2674 for (Object file : (List) t.getTransferData(DataFlavor.javaFileListFlavor)) {
2676 protocols.add(DataSourceType.FILE);
2679 // Unix like behaviour
2680 boolean added = false;
2682 if (t.isDataFlavorSupported(uriListFlavor)) {
2683 Cache.log.debug("Drop handled as uriListFlavor");
2684 // This is used by Unix drag system
2685 data = (String) t.getTransferData(uriListFlavor);
2688 // fallback to text: workaround - on OSX where there's a JVM bug
2689 Cache.log.debug("standard URIListFlavor failed. Trying text");
2690 // try text fallback
2691 DataFlavor textDf = new DataFlavor("text/plain;class=java.lang.String");
2692 if (t.isDataFlavorSupported(textDf)) {
2693 data = (String) t.getTransferData(textDf);
2696 Cache.log.debug("Plain text drop content returned " + (data == null ? "Null - failed" : data));
2700 while (protocols.size() < files.size()) {
2701 Cache.log.debug("Adding missing FILE protocol for " + files.get(protocols.size()));
2702 protocols.add(DataSourceType.FILE);
2704 for (java.util.StringTokenizer st = new java.util.StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
2706 String s = st.nextToken();
2707 if (s.startsWith("#")) {
2708 // the line is a comment (as per the RFC 2483)
2711 java.net.URI uri = new java.net.URI(s);
2712 if (uri.getScheme().toLowerCase().startsWith("http")) {
2713 protocols.add(DataSourceType.URL);
2714 files.add(uri.toString());
2716 // otherwise preserve old behaviour: catch all for file objects
2717 java.io.File file = new java.io.File(uri);
2718 protocols.add(DataSourceType.FILE);
2719 files.add(file.toString());
2724 if (Cache.log.isDebugEnabled()) {
2725 if (data == null || !added) {
2727 if (t.getTransferDataFlavors() != null && t.getTransferDataFlavors().length > 0) {
2728 Cache.log.debug("Couldn't resolve drop data. Here are the supported flavors:");
2729 for (DataFlavor fl : t.getTransferDataFlavors()) {
2730 Cache.log.debug("Supported transfer dataflavor: " + fl.toString());
2731 Object df = t.getTransferData(fl);
2733 Cache.log.debug("Retrieves: " + df);
2735 Cache.log.debug("Retrieved nothing");
2739 Cache.log.debug("Couldn't resolve dataflavor for drop: " + t.toString());
2744 if (Platform.isWindowsAndNotJS()) {
2745 Cache.log.debug("Scanning dropped content for Windows Link Files");
2747 // resolve any .lnk files in the file drop
2748 for (int f = 0; f < files.size(); f++) {
2749 String source = files.get(f).toString().toLowerCase();
2750 if (protocols.get(f).equals(DataSourceType.FILE)
2751 && (source.endsWith(".lnk") || source.endsWith(".url") || source.endsWith(".site"))) {
2753 Object obj = files.get(f);
2754 File lf = (obj instanceof File ? (File) obj : new File((String) obj));
2755 // process link file to get a URL
2756 Cache.log.debug("Found potential link file: " + lf);
2757 WindowsShortcut wscfile = new WindowsShortcut(lf);
2758 String fullname = wscfile.getRealFilename();
2759 protocols.set(f, FormatAdapter.checkProtocol(fullname));
2760 files.set(f, fullname);
2761 Cache.log.debug("Parsed real filename " + fullname + " to extract protocol: " + protocols.get(f));
2762 } catch (Exception ex) {
2763 Cache.log.error("Couldn't parse " + files.get(f) + " as a link file.", ex);
2771 * Sets the Preferences property for experimental features to True or False
2772 * depending on the state of the controlling menu item
2775 protected void showExperimental_actionPerformed(boolean selected) {
2776 Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
2780 * Answers a (possibly empty) list of any structure viewer frames (currently for
2781 * either Jmol or Chimera) which are currently open. This may optionally be
2782 * restricted to viewers of a specified class, or viewers linked to a specified
2785 * @param apanel if not null, only return viewers linked to this
2787 * @param structureViewerClass if not null, only return viewers of this class
2790 public List<StructureViewerBase> getStructureViewers(AlignmentPanel apanel,
2791 Class<? extends StructureViewerBase> structureViewerClass) {
2792 List<StructureViewerBase> result = new ArrayList<>();
2793 JInternalFrame[] frames = Desktop.instance.getAllFrames();
2795 for (JInternalFrame frame : frames) {
2796 if (frame instanceof StructureViewerBase) {
2797 if (structureViewerClass == null || structureViewerClass.isInstance(frame)) {
2798 if (apanel == null || ((StructureViewerBase) frame).isLinkedWith(apanel)) {
2799 result.add((StructureViewerBase) frame);
2807 public static final String debugScaleMessage = "Desktop graphics transform scale=";
2809 private static boolean debugScaleMessageDone = false;
2811 public static void debugScaleMessage(Graphics g) {
2812 if (debugScaleMessageDone) {
2815 // output used by tests to check HiDPI scaling settings in action
2817 Graphics2D gg = (Graphics2D) g;
2819 AffineTransform t = gg.getTransform();
2820 double scaleX = t.getScaleX();
2821 double scaleY = t.getScaleY();
2822 Cache.debug(debugScaleMessage + scaleX + " (X)");
2823 Cache.debug(debugScaleMessage + scaleY + " (Y)");
2824 debugScaleMessageDone = true;
2826 Cache.debug("Desktop graphics null");
2828 } catch (Exception e) {
2829 Cache.debug(Cache.getStackTraceString(e));