From: Jim Procter Date: Fri, 25 May 2018 12:58:01 +0000 (+0100) Subject: Merge branch 'bug/JAL-2992panelHeight' into releases/Release_2_10_4_Branch X-Git-Tag: Release_2_10_5~66 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=cb33fc905f3b51b39f603cf0a6142d26a9aace78;hp=31ebae5b435a9bfaab6fdbe9eb2c86ba8ef00589;p=jalview.git Merge branch 'bug/JAL-2992panelHeight' into releases/Release_2_10_4_Branch --- diff --git a/THIRDPARTYLIBS b/THIRDPARTYLIBS index e2baa85..d0d9125 100644 --- a/THIRDPARTYLIBS +++ b/THIRDPARTYLIBS @@ -9,6 +9,7 @@ ext.edu.ucsf.rbvi.strucviz2 includes sources originally developed by Scooter Mor jalview.ext.android includes code taken from the Android Open Source Project (https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util). The Apache 2.0 Licence (http://www.apache.org/licenses/LICENSE-2.0) is acknowledged in the source code. + org.stackoverflowusers.file.WindowsShortcuts was downloaded from http://github.com/codebling/WindowsShortcuts via https://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java Licensing information for each library is given below: diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index a80ac17..4d87b80 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -267,6 +267,7 @@ label.use_rnaview = Use RNAView for secondary structure label.autoadd_secstr = Add secondary structure annotation to alignment label.autoadd_temp = Add Temperature Factor annotation to alignment label.structure_viewer = Default structure viewer +label.double_click_to_browse = Double-click to browse for file label.chimera_path = Path to Chimera program label.chimera_path_tip = Jalview will first try any path entered here, else standard installation locations.
Double-click to browse for file. label.invalid_chimera_path = Chimera path not found or not executable diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index 61bf42a..d7c040c 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -1173,6 +1173,7 @@ action.select_by_annotation=Seleccionar/Ocultar Columnas por Anotaci action.export_features=Exportar Características error.invalid_regex=Expresión regular inválida label.autoadd_temp=Añadir anotación factor de temperatura al alineamiento +label.double_click_to_browse = Haga doble clic para buscar fichero label.chimera_path_tip=Jalview intentará primero las rutas introducidas aquí, Y si no las rutas usuales de instalación label.structure_chooser=Selector de Estructuras label.structure_chooser_manual_association=Selector de Estructuras - asociación manual @@ -1219,13 +1220,13 @@ exception.resource_not_be_found=El recurso solicitado no se ha encontrado label.aacon_calculations=cálculos AACon label.pdb_web-service_error=Error de servicio web PDB exception.unable_to_detect_internet_connection=Jalview no puede detectar una conexión a Internet -label.chimera_path=Ruta de acceso a programa Chimera +label.chimera_path=Ruta de acceso a Chimera warn.delete_all=Borrar todas las secuencias cerrará la ventana del alineamiento.
Confirmar o Cancelar. label.select_all=Seleccionar Todos label.alpha_helix=Hélice Alfa label.chimera_help=Ayuda para Chimera label.find_tip=Buscar alineamiento, selección o IDs de secuencia para una subsecuencia (sin huecos) -label.structure_viewer=Visualizador de estructura por defecto +label.structure_viewer=Visualizador por defecto label.embbed_biojson=Incrustar BioJSON al exportar HTML label.transparency_tip=Ajustar la transparencia a "ver a través" los colores de las características. label.choose_annotations=Escoja anotaciones diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java b/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java index effe556..09a9713 100644 --- a/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java +++ b/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java @@ -97,7 +97,7 @@ public class StructureManager this.haveGUI = haveGUI; // Create the Chimera interface chimeraManager = new ChimeraManager(this); - chimSelectionList = new ArrayList(); + chimSelectionList = new ArrayList<>(); pathProps = new Properties(); } @@ -110,7 +110,7 @@ public class StructureManager ModelType type) { // new models - Map> newModels = new HashMap>(); + Map> newModels = new HashMap<>(); if (chimObjNames.size() > 0) { List names = chimObjNames.iterator().next(); @@ -846,7 +846,7 @@ public class StructureManager // alDialog.dispose(); // } // System.out.println("launch align dialog"); - List chimObjectList = new ArrayList(); + List chimObjectList = new ArrayList<>(); for (ChimeraModel model : chimeraManager.getChimeraModels()) { if (useChains) @@ -887,7 +887,7 @@ public class StructureManager public List getAllChimeraResidueAttributes() { - List attributes = new ArrayList(); + List attributes = new ArrayList<>(); // attributes.addAll(rinManager.getResAttrs()); attributes.addAll(chimeraManager.getAttrList()); return attributes; @@ -898,7 +898,7 @@ public class StructureManager // TODO: [Optional] Change priority of Chimera paths public static List getChimeraPaths() { - List pathList = new ArrayList(); + List pathList = new ArrayList<>(); // if no network is available and the settings have been modified by the // user, check for a @@ -934,8 +934,18 @@ public class StructureManager } else if (os.startsWith("Windows")) { - pathList.add("\\Program Files\\Chimera\\bin\\chimera"); - pathList.add("C:\\Program Files\\Chimera\\bin\\chimera.exe"); + for (String root : new String[] { "\\Program Files", + "C:\\Program Files", "\\Program Files (x86)", + "C:\\Program Files (x86)" }) + { + for (String version : new String[] { "1.11", "1.11.1", "1.11.2", + "1.12", "1.12.1", "1.12.2", "1.13" }) + { + pathList.add(root + "\\Chimera " + version + "\\bin\\chimera"); + pathList.add( + root + "\\Chimera " + version + "\\bin\\chimera.exe"); + } + } } else if (os.startsWith("Mac")) { diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 9a696e9..8be4eac 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -32,6 +32,7 @@ import jalview.io.FileFormatException; import jalview.io.FileFormatI; import jalview.io.FileFormats; import jalview.io.FileLoader; +import jalview.io.FormatAdapter; import jalview.io.IdentifyFile; import jalview.io.JalviewFileChooser; import jalview.io.JalviewFileView; @@ -116,6 +117,8 @@ import javax.swing.event.InternalFrameEvent; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; +import org.stackoverflowusers.file.WindowsShortcut; + /** * Jalview Desktop * @@ -3294,13 +3297,67 @@ public class Desktop extends jalview.jbgui.GDesktop return groovyConsole; } + /** + * handles the payload of a drag and drop event. + * + * TODO refactor to desktop utilities class + * + * @param files + * - Data source strings extracted from the drop event + * @param protocols + * - protocol for each data source extracted from the drop event + * @param evt + * - the drop event + * @param t + * - the payload from the drop event + * @throws Exception + */ public static void transferFromDropTarget(List files, List protocols, DropTargetDropEvent evt, Transferable t) throws Exception { DataFlavor uriListFlavor = new DataFlavor( - "text/uri-list;class=java.lang.String"); + "text/uri-list;class=java.lang.String"), urlFlavour = null; + try + { + urlFlavour = new DataFlavor( + "application/x-java-url; class=java.net.URL"); + } catch (ClassNotFoundException cfe) + { + Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe); + } + + if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) + { + + try + { + java.net.URL url = (URL) t.getTransferData(urlFlavour); + // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099 + // means url may be null. + if (url != null) + { + protocols.add(DataSourceType.URL); + files.add(url.toString()); + Cache.log.debug("Drop handled as URL dataflavor " + + files.get(files.size() - 1)); + return; + } + else + { + if (Platform.isAMac()) + { + System.err.println( + "Please ignore plist error - occurs due to problem with java 8 on OSX"); + } + ; + } + } catch (Throwable ex) + { + Cache.log.debug("URL drop handler failed.", ex); + } + } if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { // Works on Windows and MacOSX @@ -3328,63 +3385,112 @@ public class Desktop extends jalview.jbgui.GDesktop // fallback to text: workaround - on OSX where there's a JVM bug Cache.log.debug("standard URIListFlavor failed. Trying text"); // try text fallback - data = (String) t.getTransferData( - new DataFlavor("text/plain;class=java.lang.String")); - if (Cache.log.isDebugEnabled()) + DataFlavor textDf = new DataFlavor( + "text/plain;class=java.lang.String"); + if (t.isDataFlavorSupported(textDf)) { - Cache.log.debug("fallback returned " + data); + data = (String) t.getTransferData(textDf); } + + Cache.log.debug("Plain text drop content returned " + + (data == null ? "Null - failed" : data)); + } - while (protocols.size() < files.size()) - { - Cache.log.debug("Adding missing FILE protocol for " - + files.get(protocols.size())); - protocols.add(DataSourceType.FILE); - } - for (java.util.StringTokenizer st = new java.util.StringTokenizer( - data, "\r\n"); st.hasMoreTokens();) + if (data != null) { - added = true; - String s = st.nextToken(); - if (s.startsWith("#")) + while (protocols.size() < files.size()) { - // the line is a comment (as per the RFC 2483) - continue; - } - java.net.URI uri = new java.net.URI(s); - if (uri.getScheme().toLowerCase().startsWith("http")) - { - protocols.add(DataSourceType.URL); - files.add(uri.toString()); + Cache.log.debug("Adding missing FILE protocol for " + + files.get(protocols.size())); + protocols.add(DataSourceType.FILE); } - else + for (java.util.StringTokenizer st = new java.util.StringTokenizer( + data, "\r\n"); st.hasMoreTokens();) { - // otherwise preserve old behaviour: catch all for file objects - java.io.File file = new java.io.File(uri); - protocols.add(DataSourceType.FILE); - files.add(file.toString()); + added = true; + String s = st.nextToken(); + if (s.startsWith("#")) + { + // the line is a comment (as per the RFC 2483) + continue; + } + java.net.URI uri = new java.net.URI(s); + if (uri.getScheme().toLowerCase().startsWith("http")) + { + protocols.add(DataSourceType.URL); + files.add(uri.toString()); + } + else + { + // otherwise preserve old behaviour: catch all for file objects + java.io.File file = new java.io.File(uri); + protocols.add(DataSourceType.FILE); + files.add(file.toString()); + } } } + if (Cache.log.isDebugEnabled()) { if (data == null || !added) { - Cache.log.debug( - "Couldn't resolve drop data. Here are the supported flavors:"); - for (DataFlavor fl : t.getTransferDataFlavors()) + + if (t.getTransferDataFlavors() != null + && t.getTransferDataFlavors().length > 0) { Cache.log.debug( - "Supported transfer dataflavor: " + fl.toString()); - Object df = t.getTransferData(fl); - if (df != null) + "Couldn't resolve drop data. Here are the supported flavors:"); + for (DataFlavor fl : t.getTransferDataFlavors()) { - Cache.log.debug("Retrieves: " + df); - } - else - { - Cache.log.debug("Retrieved nothing"); + Cache.log.debug( + "Supported transfer dataflavor: " + fl.toString()); + Object df = t.getTransferData(fl); + if (df != null) + { + Cache.log.debug("Retrieves: " + df); + } + else + { + Cache.log.debug("Retrieved nothing"); + } } } + else + { + Cache.log.debug("Couldn't resolve dataflavor for drop: " + + t.toString()); + } + } + } + } + if (Platform.isWindows()) + + { + Cache.log.debug("Scanning dropped content for Windows Link Files"); + + // resolve any .lnk files in the file drop + for (int f = 0; f < files.size(); f++) + { + String source = files.get(f).toLowerCase(); + if (protocols.get(f).equals(DataSourceType.FILE) + && (source.endsWith(".lnk") || source.endsWith(".url") + || source.endsWith(".site"))) + { + try { + File lf = new File(files.get(f)); + // process link file to get a URL + Cache.log.debug("Found potential link file: " + lf); + WindowsShortcut wscfile = new WindowsShortcut(lf); + String fullname = wscfile.getRealFilename(); + protocols.set(f, FormatAdapter.checkProtocol(fullname)); + files.set(f, fullname); + Cache.log.debug("Parsed real filename " + fullname + + " to extract protocol: " + protocols.get(f)); + } + catch (Exception ex) + { + Cache.log.error("Couldn't parse "+files.get(f)+" as a link file.",ex); + } } } } diff --git a/src/jalview/gui/SeqCanvas.java b/src/jalview/gui/SeqCanvas.java index 2d8eb7d..8f315bd 100755 --- a/src/jalview/gui/SeqCanvas.java +++ b/src/jalview/gui/SeqCanvas.java @@ -652,6 +652,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI ViewportRanges ranges = av.getRanges(); ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues); + // we need to call this again to make sure the startColumn + + // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths + // correctly. + calculateWrappedGeometry(canvasWidth, canvasHeight); + /* * draw one width at a time (including any scales or annotation shown), * until we have run out of either alignment or vertical space available diff --git a/src/jalview/jbgui/GPreferences.java b/src/jalview/jbgui/GPreferences.java index 1ca0802..26e0919 100755 --- a/src/jalview/jbgui/GPreferences.java +++ b/src/jalview/jbgui/GPreferences.java @@ -524,7 +524,9 @@ public class GPreferences extends JPanel MessageManager.getString("label.default_browser_unix")); defaultBrowser.setFont(LABEL_FONT); defaultBrowser.setText(""); - + final String tooltip = JvSwingUtils.wrapTooltip(true, + MessageManager.getString("label.double_click_to_browse")); + defaultBrowser.setToolTipText(tooltip); defaultBrowser.addMouseListener(new MouseAdapter() { @Override @@ -1206,14 +1208,14 @@ public class GPreferences extends JPanel pathLabel.setFont(new java.awt.Font("SansSerif", 0, 11)); pathLabel.setHorizontalAlignment(SwingConstants.LEFT); pathLabel.setText(MessageManager.getString("label.chimera_path")); - final String tooltip = JvSwingUtils.wrapTooltip(true, - MessageManager.getString("label.chimera_path_tip")); - pathLabel.setToolTipText(tooltip); pathLabel.setBounds(new Rectangle(10, ypos, 140, height)); structureTab.add(pathLabel); chimeraPath.setFont(LABEL_FONT); chimeraPath.setText(""); + final String tooltip = JvSwingUtils.wrapTooltip(true, + MessageManager.getString("label.chimera_path_tip")); + chimeraPath.setToolTipText(tooltip); chimeraPath.setBounds(new Rectangle(160, ypos, 300, height)); chimeraPath.addMouseListener(new MouseAdapter() { @@ -1512,6 +1514,9 @@ public class GPreferences extends JPanel startupCheckbox.setSelected(true); startupFileTextfield.setFont(LABEL_FONT); startupFileTextfield.setBounds(new Rectangle(172, 310, 330, 20)); + final String tooltip = JvSwingUtils.wrapTooltip(true, + MessageManager.getString("label.double_click_to_browse")); + startupFileTextfield.setToolTipText(tooltip); startupFileTextfield.addMouseListener(new MouseAdapter() { @Override diff --git a/src/org/stackoverflowusers/file/WindowsShortcut.java b/src/org/stackoverflowusers/file/WindowsShortcut.java new file mode 100644 index 0000000..671e002 --- /dev/null +++ b/src/org/stackoverflowusers/file/WindowsShortcut.java @@ -0,0 +1,215 @@ +package org.stackoverflowusers.file; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; + +/** + * Represents a Windows shortcut (typically visible to Java only as a '.lnk' file). + * + * Retrieved 2011-09-23 from http://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775 + * Originally called LnkParser + * + * Written by: (the stack overflow users, obviously!) + * Apache Commons VFS dependency removed by crysxd (why were we using that!?) https://github.com/crysxd + * Headerified, refactored and commented by Code Bling http://stackoverflow.com/users/675721/code-bling + * Network file support added by Stefan Cordes http://stackoverflow.com/users/81330/stefan-cordes + * Adapted by Sam Brightman http://stackoverflow.com/users/2492/sam-brightman + * Based on information in 'The Windows Shortcut File Format' by Jesse Hager <jessehager@iname.com> + * And somewhat based on code from the book 'Swing Hacks: Tips and Tools for Killer GUIs' + * by Joshua Marinacci and Chris Adamson + * ISBN: 0-596-00907-0 + * http://www.oreilly.com/catalog/swinghks/ + */ +public class WindowsShortcut +{ + private boolean isDirectory; + private boolean isLocal; + private String real_file; + + /** + * Provides a quick test to see if this could be a valid link ! + * If you try to instantiate a new WindowShortcut and the link is not valid, + * Exceptions may be thrown and Exceptions are extremely slow to generate, + * therefore any code needing to loop through several files should first check this. + * + * @param file the potential link + * @return true if may be a link, false otherwise + * @throws IOException if an IOException is thrown while reading from the file + */ + public static boolean isPotentialValidLink(File file) throws IOException { + final int minimum_length = 0x64; + InputStream fis = new FileInputStream(file); + boolean isPotentiallyValid = false; + try { + isPotentiallyValid = file.isFile() + && file.getName().toLowerCase().endsWith(".lnk") + && fis.available() >= minimum_length + && isMagicPresent(getBytes(fis, 32)); + } finally { + fis.close(); + } + return isPotentiallyValid; + } + + public WindowsShortcut(File file) throws IOException, ParseException { + InputStream in = new FileInputStream(file); + try { + parseLink(getBytes(in)); + } finally { + in.close(); + } + } + + /** + * @return the name of the filesystem object pointed to by this shortcut + */ + public String getRealFilename() { + return real_file; + } + + /** + * Tests if the shortcut points to a local resource. + * @return true if the 'local' bit is set in this shortcut, false otherwise + */ + public boolean isLocal() { + return isLocal; + } + + /** + * Tests if the shortcut points to a directory. + * @return true if the 'directory' bit is set in this shortcut, false otherwise + */ + public boolean isDirectory() { + return isDirectory; + } + + /** + * Gets all the bytes from an InputStream + * @param in the InputStream from which to read bytes + * @return array of all the bytes contained in 'in' + * @throws IOException if an IOException is encountered while reading the data from the InputStream + */ + private static byte[] getBytes(InputStream in) throws IOException { + return getBytes(in, null); + } + + /** + * Gets up to max bytes from an InputStream + * @param in the InputStream from which to read bytes + * @param max maximum number of bytes to read + * @return array of all the bytes contained in 'in' + * @throws IOException if an IOException is encountered while reading the data from the InputStream + */ + private static byte[] getBytes(InputStream in, Integer max) throws IOException { + // read the entire file into a byte buffer + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + byte[] buff = new byte[256]; + while (max == null || max > 0) { + int n = in.read(buff); + if (n == -1) { + break; + } + bout.write(buff, 0, n); + if (max != null) + max -= n; + } + in.close(); + return bout.toByteArray(); + } + + private static boolean isMagicPresent(byte[] link) { + final int magic = 0x0000004C; + final int magic_offset = 0x00; + return link.length >= 32 && bytesToDword(link, magic_offset) == magic; + } + + /** + * Gobbles up link data by parsing it and storing info in member fields + * @param link all the bytes from the .lnk file + */ + private void parseLink(byte[] link) throws ParseException { + try { + if (!isMagicPresent(link)) + throw new ParseException("Invalid shortcut; magic is missing", 0); + + // get the flags byte + byte flags = link[0x14]; + + // get the file attributes byte + final int file_atts_offset = 0x18; + byte file_atts = link[file_atts_offset]; + byte is_dir_mask = (byte)0x10; + if ((file_atts & is_dir_mask) > 0) { + isDirectory = true; + } else { + isDirectory = false; + } + + // if the shell settings are present, skip them + final int shell_offset = 0x4c; + final byte has_shell_mask = (byte)0x01; + int shell_len = 0; + if ((flags & has_shell_mask) > 0) { + // the plus 2 accounts for the length marker itself + shell_len = bytesToWord(link, shell_offset) + 2; + } + + // get to the file settings + int file_start = 0x4c + shell_len; + + final int file_location_info_flag_offset_offset = 0x08; + int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset]; + isLocal = (file_location_info_flag & 2) == 0; + // get the local volume and local system values + //final int localVolumeTable_offset_offset = 0x0C; + final int basename_offset_offset = 0x10; + final int networkVolumeTable_offset_offset = 0x14; + final int finalname_offset_offset = 0x18; + int finalname_offset = link[file_start + finalname_offset_offset] + file_start; + String finalname = getNullDelimitedString(link, finalname_offset); + if (isLocal) { + int basename_offset = link[file_start + basename_offset_offset] + file_start; + String basename = getNullDelimitedString(link, basename_offset); + real_file = basename + finalname; + } else { + int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start; + int shareName_offset_offset = 0x08; + int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset] + + networkVolumeTable_offset; + String shareName = getNullDelimitedString(link, shareName_offset); + real_file = shareName + "\\" + finalname; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParseException("Could not be parsed, probably not a valid WindowsShortcut", 0); + } + } + + private static String getNullDelimitedString(byte[] bytes, int off) { + int len = 0; + // count bytes until the null character (0) + while (true) { + if (bytes[off + len] == 0) { + break; + } + len++; + } + return new String(bytes, off, len); + } + + /* + * convert two bytes into a short note, this is little endian because it's + * for an Intel only OS. + */ + private static int bytesToWord(byte[] bytes, int off) { + return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); + } + + private static int bytesToDword(byte[] bytes, int off) { + return (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off); + } + +}