*/
package jalview.gui;
+import java.util.Locale;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
+import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
+import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
+import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import jalview.structure.StructureSelectionManager;
import jalview.urls.IdOrgSettings;
import jalview.util.BrowserLauncher;
+import jalview.util.ChannelProperties;
import jalview.util.ImageMaker.TYPE;
import jalview.util.MessageManager;
import jalview.util.Platform;
+import jalview.util.ShortcutKeyMaskExWrapper;
import jalview.util.UrlConstants;
import jalview.viewmodel.AlignmentViewport;
import jalview.ws.WSDiscovererI;
StructureSelectionManagerProvider, ApplicationSingletonI
{
- private static final String CITATION = "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
- + "<br><br>For help, see the FAQ at <a href=\"http://www.jalview.org/faq\">www.jalview.org/faq</a> and/or join the jalview-discuss@jalview.org mailing list"
- + "<br><br>If you use Jalview, please cite:"
- + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
- + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
- + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033";
+ private static final String CITATION;
+ static {
+ URL bg_logo_url = ChannelProperties.getImageURL("bg_logo." + String.valueOf(SplashScreen.logoSize));
+ URL uod_logo_url = ChannelProperties.getImageURL("uod_banner." + String.valueOf(SplashScreen.logoSize));
+ boolean logo = (bg_logo_url != null || uod_logo_url != null);
+ StringBuilder sb = new StringBuilder();
+ sb.append("<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.");
+ if (logo) {
+ sb.append("<br>");
+ }
+ sb.append(bg_logo_url == null ? "" : "<img alt=\"Barton Group logo\" src=\"" + bg_logo_url.toString() + "\">");
+ sb.append(uod_logo_url == null ? ""
+ : " <img alt=\"University of Dundee shield\" src=\"" + uod_logo_url.toString() + "\">");
+ sb.append(
+ "<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");
+ sb.append("<br><br>If you use Jalview, please cite:"
+ + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
+ + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
+ + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033");
+ CITATION = sb.toString();
+ }
private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
@SuppressWarnings("deprecation")
private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
+ public static boolean nosplash = false;
/**
* news reader - null if it was never started.
*/
*/
private Desktop()
{
+ super();
Cache.initLogger();
try
{
*/
doConfigureStructurePrefs();
- setTitle("Jalview " + Cache.getProperty("VERSION"));
- /*
- if (!Platform.isAMac())
- {
- // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- }
- else
- {
- this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
- }
- */
+ setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
- try
- {
- APQHandlers.setAPQHandlers(this);
- } catch (Throwable t)
- {
- System.out.println("Error setting APQHandlers: " + t.toString());
- // t.printStackTrace();
+ /**
+ * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
+ * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
+ * documented or guaranteed to exist, so we access it via reflection. There
+ * appear to be unfathomable criteria about what this string can contain, and it
+ * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
+ * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
+ * but "Jalview non-release" does not. The reflection access may generate a
+ * warning: WARNING: An illegal reflective access operation has occurred
+ * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
+ * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
+ */
+ if (Platform.isLinux()) {
+ try {
+ Toolkit xToolkit = Toolkit.getDefaultToolkit();
+ Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
+ Field awtAppClassNameField = null;
+
+ if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
+ awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
+ }
+
+ String title = ChannelProperties.getProperty("app_name");
+ if (awtAppClassNameField != null) {
+ awtAppClassNameField.setAccessible(true);
+ awtAppClassNameField.set(xToolkit, title);
+ } else {
+ Cache.log.debug("XToolkit: awtAppClassName not found");
+ }
+ } catch (Exception e) {
+ Cache.debug("Error setting awtAppClassName");
+ Cache.trace(Cache.getStackTraceString(e));
}
+ }
- addWindowListener(new WindowAdapter()
- {
+ /**
+ * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
+ * macOS's application menu. APQHandlers will check to see if a handler is
+ * supported before setting it.
+ */
+ try {
+ APQHandlers.setAPQHandlers(this);
+ } catch (Exception e) {
+ System.out.println("Cannot set APQHandlers");
+ // e.printStackTrace();
+ } catch (Throwable t) {
+ Cache.warn("Error setting APQHandlers: " + t.toString());
+ Cache.trace(Cache.getStackTraceString(t));
+ }
+ setIconImages(ChannelProperties.getIconList());
- @Override
- public void windowClosing(WindowEvent ev)
- {
- quit();
- }
- });
+ addWindowListener(new WindowAdapter() {
+
+ @Override
+ public void windowClosing(WindowEvent ev) {
+ quit();
+ }
+ });
boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
getContentPane().add(desktopPane, BorderLayout.CENTER);
desktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
+
// This line prevents Windows Look&Feel resizing all new windows to
// maximum
// if previous window was maximised
setBounds(xPos, yPos, 900, 650);
}
- getIdentifiersOrgData();
-
if (!Platform.isJS())
/**
* Java only
jconsole.setHeader(Cache.getVersionDetailsForConsole());
showConsole(showjconsole);
- showNews.setVisible(false);
+ showNews.setVisible(false); // not sure if we should only do this for interactive session?
experimentalFeatures.setSelected(showExperimental());
+ getIdentifiersOrgData();
+
if (Jalview.isInteractive())
{
// disabled for SeqCanvasTest
checkURLLinks();
// Spawn a thread that shows the splashscreen
-
+ if (!nosplash) {
SwingUtilities.invokeLater(new Runnable()
- {
- @Override
- public void run()
- {
- new SplashScreen(true);
- }
- });
+ {
+ @Override
+ public void run()
+ {
+ new SplashScreen(true);
+ }
+ });
+ }
// Thread off a new instance of the file chooser - this reduces the
// time
});
}
}
+
this.setDropTarget(new java.awt.dnd.DropTarget(desktopPane, this));
this.addWindowListener(new WindowAdapter()
showPasteMenu(evt.getX(), evt.getY());
}
}
-
@Override
public void mouseReleased(MouseEvent evt)
{
public void doConfigureStructurePrefs()
{
// configure services
- StructureSelectionManager ssm = StructureSelectionManager
- .getStructureSelectionManager(this);
- if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
- {
- ssm.setAddTempFacAnnot(
- Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
- ssm.setProcessSecondaryStructure(
- Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
- ssm.setSecStructServices(
- Cache.getDefault(Preferences.USE_RNAVIEW, true));
- }
- else
- {
+ StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
+ if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) {
+ ssm.setAddTempFacAnnot(Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
+ ssm.setProcessSecondaryStructure(Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
+ // JAL-3915 - RNAView is no longer an option so this has no effect
+ ssm.setSecStructServices(Cache.getDefault(Preferences.USE_RNAVIEW, false));
+ } else {
ssm.setAddTempFacAnnot(false);
ssm.setProcessSecondaryStructure(false);
ssm.setSecStructServices(false);
}
}
- public void checkForNews()
- {
+ public void checkForNews() {
final Desktop me = this;
// Thread off the news reader, in case there are connection problems.
- new Thread(new Runnable()
- {
+ new Thread(new Runnable() {
@Override
- public void run()
- {
+ public void run() {
Cache.log.debug("Starting news thread.");
jvnews = new BlogReader(me);
showNews.setVisible(true);
}).start();
}
- public void getIdentifiersOrgData()
- {
- // Thread off the identifiers fetcher
- new Thread(new Runnable()
- {
- @Override
- public void run()
- {
- Cache.log.debug("Downloading data from identifiers.org");
- try
- {
- UrlDownloadClient.download(IdOrgSettings.getUrl(),
- IdOrgSettings.getDownloadLocation());
- } catch (IOException e)
- {
- Cache.log.debug("Exception downloading identifiers.org data"
- + e.getMessage());
+ public void getIdentifiersOrgData() {
+ if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
+ // Thread off the identifiers fetcher
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Cache.log.debug("Downloading data from identifiers.org");
+ try {
+ UrlDownloadClient.download(IdOrgSettings.getUrl(), IdOrgSettings.getDownloadLocation());
+ } catch (IOException e) {
+ Cache.log.debug("Exception downloading identifiers.org data" + e.getMessage());
+ }
}
- }
- }).start();
-
+ }).start();
+ }
}
@Override
- protected void showNews_actionPerformed(ActionEvent e)
- {
+ protected void showNews_actionPerformed(ActionEvent e) {
showNews(showNews.isSelected());
}
String y = Cache.getProperty(windowName + "SCREEN_Y");
String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
- if ((x != null) && (y != null) && (width != null) && (height != null))
- {
- int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
- iw = Integer.parseInt(width), ih = Integer.parseInt(height);
- if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
- {
+ if ((x != null) && (y != null) && (width != null) && (height != null)) {
+ int ix = Integer.parseInt(x), iy = Integer.parseInt(y), iw = Integer.parseInt(width),
+ ih = Integer.parseInt(height);
+ if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) {
// attempt #1 - try to cope with change in screen geometry - this
// version doesn't preserve original jv aspect ratio.
// take ratio of current screen size vs original screen size.
- double sw = ((1f * screenSize.width) / (1f * Integer
- .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
- double sh = ((1f * screenSize.height) / (1f * Integer
- .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
+ double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
+ double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
// rescale the bounds depending upon the current screen geometry.
ix = (int) (ix * sw);
iw = (int) (iw * sw);
iy = (int) (iy * sh);
ih = (int) (ih * sh);
- while (ix >= screenSize.width)
- {
- Cache.log.debug(
- "Window geometry location recall error: shifting horizontal to within screenbounds.");
+ while (ix >= screenSize.width) {
+ Cache.log.debug("Window geometry location recall error: shifting horizontal to within screenbounds.");
ix -= screenSize.width;
}
- while (iy >= screenSize.height)
- {
- Cache.log.debug(
- "Window geometry location recall error: shifting vertical to within screenbounds.");
+ while (iy >= screenSize.height) {
+ Cache.log.debug("Window geometry location recall error: shifting vertical to within screenbounds.");
iy -= screenSize.height;
}
- Cache.log.debug(
- "Got last known dimensions for " + windowName + ": x:" + ix
- + " y:" + iy + " width:" + iw + " height:" + ih);
+ Cache.log.debug("Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw
+ + " height:" + ih);
}
// return dimensions for new instance
return new Rectangle(ix, iy, iw, ih);
}
}
-// /**
-// * Add an internal frame to the Jalview desktop that is allowed to be resized,
-// * has a minimum size of 300px and might or might not be visible
-// *
-// * @param frame
-// * Frame to show
-// * @param title
-// * Visible Title
-// * @param makeVisible
-// * When true, display frame immediately, otherwise, caller must call
-// * setVisible themselves.
-// * @param w
-// * width
-// * @param h
-// * height
-// */
-// @Deprecated
-// public static synchronized void addInternalFrame(
-// final JInternalFrame frame, String title, boolean makeVisible,
-// int w, int h)
-// {
-// // textbox, web services, sequenceFetcher, featureSettings
-// getInstance().addFrame(frame, title, makeVisible, w, h,
-// FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
-// }
-//
-// /**
-// * Add an internal frame to the Jalview desktop that is visible, has a minimum
-// * size of 300px, and may or may not be resizable
-// *
-// * @param frame
-// * Frame to show
-// * @param title
-// * Visible Title
-// * @param w
-// * width
-// * @param h
-// * height
-// * @param resizable
-// * Allow resize
-// */
-// @Deprecated
-// public static synchronized void addInternalFrame(
-// final JInternalFrame frame, String title, int w, int h,
-// boolean resizable)
-// {
-// // annotation, font, calculation, user-defined colors
-// getInstance().addFrame(frame, title, FRAME_MAKE_VISIBLE, w, h,
-// resizable, FRAME_SET_MIN_SIZE_300);
-// }
+
/**
* Adds and opens the given frame to the desktop that is visible, allowed to
{
// 15 classes call this method directly.
+
// TODO: allow callers to determine X and Y position of frame (eg. via
// bounds object).
// TODO: consider fixing method to update entries in the window submenu with
{
openFrameCount++;
+
boolean isEmbedded = (Platform.getEmbeddedAttribute(frame, "id") != null);
boolean hasEmbeddedSize = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
if (!ignoreMinSize)
{
+
// Set default dimension for Alignment Frame window.
// The Alignment Frame window could be added from a number of places,
// hence,
frame.setMaximizable(resizable);
frame.setIconifiable(resizable);
frame.setOpaque(Platform.isJS());
+
if (!isEmbedded && frame.getX() < 1 && frame.getY() < 1)
{
frame.setLocation(xOffset * openFrameCount,
/*
* set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
*/
- KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
- InputEvent.CTRL_DOWN_MASK);
- KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
- Platform.SHORTCUT_KEY_MASK);
+ KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
+ KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
InputMap inputMap = frame
.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
public void run()
{
@SuppressWarnings("unchecked")
- String url = (history instanceof JTextField
- ? ((JTextField) history).getText()
- : ((JComboBox<String>) history).getSelectedItem()
- .toString());
-
- if (url.toLowerCase().endsWith(".jar"))
- {
- if (viewport != null)
- {
- new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
- FileFormat.Jalview);
- }
- else
- {
- new FileLoader().LoadFile(url, DataSourceType.URL,
- FileFormat.Jalview);
+ String url = (history instanceof JTextField ? ((JTextField) history).getText()
+ : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
+
+ if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) {
+ if (viewport != null) {
+ new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
+ } else {
+ new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
}
- }
- else
- {
+ } else {
FileFormatI format = null;
try
{
public String getAboutMessage()
{
StringBuilder message = new StringBuilder(1024);
- message.append("<h1><strong>Version: ")
- .append(Cache.getProperty("VERSION")).append("</strong></h1>")
- .append("<strong>Built: <em>")
- .append(Cache.getDefault("BUILD_DATE", "unknown"))
- .append("</em> from ").append(Cache.getBuildDetailsForSplash())
- .append("</strong>");
+ message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
+ .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
+ .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
+ .append(Cache.getBuildDetailsForSplash()).append("</strong>");
String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
- if (latestVersion.equals("Checking"))
- {
+ if (latestVersion.equals("Checking")) {
// JBP removed this message for 2.11: May be reinstated in future version
// message.append("<br>...Checking latest version...</br>");
- }
- else if (!latestVersion.equals(Cache.getProperty("VERSION")))
- {
+ } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
boolean red = false;
- if (Cache.getProperty("VERSION").toLowerCase()
- .indexOf("automated build") == -1)
- {
+ if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT).indexOf("automated build") == -1) {
red = true;
// Displayed when code version and jnlp version do not match and code
// version is not a development build
message.append("<div style=\"color: #FF0000;font-style: bold;\">");
}
- message.append("<br>!! Version ")
- .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
- .append(" is available for download from ")
- .append(Cache.getDefault("www.jalview.org",
- "http://www.jalview.org"))
- .append(" !!");
- if (red)
- {
+ message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
+ .append(" is available for download from ")
+ .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
+ if (red) {
message.append("</div>");
}
}
message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
message.append(CITATION);
+ message.append("</div>");
return message.toString();
}
* Action on requesting Help documentation
*/
@Override
- public void documentationMenuItem_actionPerformed()
- {
- try
- {
- if (Platform.isJS())
- {
- BrowserLauncher.openURL("http://www.jalview.org/help.html");
- }
- else
+ public void documentationMenuItem_actionPerformed() {
+ try {
+ if (Platform.isJS()) {
+ BrowserLauncher.openURL("https://www.jalview.org/help.html");
+ } else
/**
* Java only
*
{
Help.showHelpWindow();
}
- } catch (Exception ex)
- {
+ } catch (Exception ex) {
System.err.println("Error opening help: " + ex.getMessage());
}
}
* DOCUMENT ME!
*/
@Override
- protected void preferences_actionPerformed(ActionEvent e)
- {
- new Preferences();
+ protected void preferences_actionPerformed(ActionEvent e) {
+ Preferences.openPreferences();
}
/**
approveSave = true;
}
}
-
- if (approveSave || autoSave)
- {
+ if (approveSave || autoSave) {
final Desktop me = this;
final java.io.File chosenFile = projectFile;
new Thread(new Runnable()
JvOptionPane.WARNING_MESSAGE);
}
}
- }).start();
+ }, "Project Loader").start();
}
});
-
chooser.showOpenDialog(this);
}
10, getHeight() - fm.getHeight());
}
}
+ // output debug scale message. Important for jalview.bin.HiDPISettingTest2
+ Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
}
}
return result;
}
+ public static final String debugScaleMessage = "Desktop graphics transform scale=";
+
+ private static boolean debugScaleMessageDone = false;
+
+ public static void debugScaleMessage(Graphics g) {
+ if (debugScaleMessageDone) {
+ return;
+ }
+ // output used by tests to check HiDPI scaling settings in action
+ try {
+ Graphics2D gg = (Graphics2D) g;
+ if (gg != null) {
+ AffineTransform t = gg.getTransform();
+ double scaleX = t.getScaleX();
+ double scaleY = t.getScaleY();
+ Cache.debug(debugScaleMessage + scaleX + " (X)");
+ Cache.debug(debugScaleMessage + scaleY + " (Y)");
+ debugScaleMessageDone = true;
+ } else {
+ Cache.debug("Desktop graphics null");
+ }
+ } catch (Exception e) {
+ Cache.debug(Cache.getStackTraceString(e));
+ }
+ }
}