Merge branch 'releases/Release_2_10_4_Branch' Release_2_10_4b1
authorJim Procter <jprocter@issues.jalview.org>
Thu, 7 Jun 2018 15:25:04 +0000 (16:25 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Thu, 7 Jun 2018 15:25:04 +0000 (16:25 +0100)
24 files changed:
RELEASE
THIRDPARTYLIBS
build.xml
examples/testdata/test.aln
help/help.jhm
help/html/releases.html
help/html/whatsNew.html
resources/lang/Messages.properties
resources/lang/Messages_es.properties
resources/uniprot_mapping.xml
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
src/jalview/datamodel/xdb/uniprot/UniprotFeature.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/Desktop.java
src/jalview/gui/SeqCanvas.java
src/jalview/io/ClustalFile.java
src/jalview/io/FeaturesFile.java
src/jalview/io/FileParse.java
src/jalview/jbgui/GPreferences.java
src/jalview/ws/dbsources/Uniprot.java
src/org/stackoverflowusers/file/WindowsShortcut.java [new file with mode: 0644]
test/jalview/io/ClustalFileTest.java [new file with mode: 0644]
test/jalview/ws/dbsources/UniprotTest.java

diff --git a/RELEASE b/RELEASE
index 5ad87c8..c61d86c 100644 (file)
--- a/RELEASE
+++ b/RELEASE
@@ -1,2 +1,2 @@
 jalview.release=releases/Release_2_10_4_Branch
-jalview.version=2.10.4
+jalview.version=2.10.4b1
index e2baa85..d0d9125 100644 (file)
@@ -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:
 
index 3d26efc..133941a 100755 (executable)
--- a/build.xml
+++ b/build.xml
   </target>
 
   <target name="buildindices" depends="init, prepare" unless="help.uptodate">
+    <replace value="${JALVIEW_VERSION}">
+      <replacetoken><![CDATA[$$Version-Rel$$]]></replacetoken>
+      <fileset dir="${outputDir}/${helpDir}">
+        <include name="help.jhm" />
+      </fileset>
+    </replace>
+
     <java classname="com.sun.java.help.search.Indexer" classpathref="build.classpath" fork="true" dir="${outputDir}/${helpDir}">
       <arg line="html" />
     </java>
index 08a7ac3..6582b12 100644 (file)
@@ -1,53 +1,13 @@
 CLUSTAL
 
-FER_CAPAA/1-97     -----------------------------------------------------------A
-FER_CAPAN/1-144    MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA
-FER1_SOLLC/1-144   MA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA
-Q93XJ9_SOLTU/1-144 MA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA
-FER1_PEA/1-149     MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA
-Q7XA98_TRIPR/1-152 MATT---PALYGTAVSTSFMRRQPVPMSV-ATTTTTKAFPSGFGLKSVSTKRGDLAVAMA
-FER1_MESCR/1-148   MAAT--TAALSGATMSTAFAPK--TPPMTAALPTNVGR--ALFGLKS-SASR-GRVTAMA
-FER1_SPIOL/1-147   MAAT--TTTMMG--MATTFVPKPQAPPMMAALPSNTGR--SLFGLKT-GSR--GGRMTMA
-FER3_RAPSA/1-96    -----------------------------------------------------------A
-FER1_ARATH/1-148   MAST----ALSSAIVGTSFIRRSPAPISLRSLPSANTQ--SLFGLKS-GTARGGRVTAMA
-FER_BRANA/1-96     -----------------------------------------------------------A
-FER2_ARATH/1-148   MAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA
-Q93Z60_ARATH/1-118 MAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA
-FER1_MAIZE/1-150   MATVLGSPRAPAFFFSSSSLRAAPAPTAV--ALPAAKV--GIMGRSA-SSRR--RLRAQA
-O80429_MAIZE/1-140 MAAT---------ALSMSILR---APPPCFSSPLRLRV--AVAKPLA-APMRRQLLRAQA
-1A70|/1-97         -----------------------------------------------------------A
-
-FER_CAPAA/1-97     SYKVKLITPDGPIEFDCPDDVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG
-FER_CAPAN/1-144    SYKVKLITPDGPIEFDCPDNVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG
-FER1_SOLLC/1-144   SYKVKLITPEGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGSVDQSDG
-Q93XJ9_SOLTU/1-144 SYKVKLITPDGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGTVDQSDG
-FER1_PEA/1-149     SYKVKLVTPDGTQEFECPSDVYILDHAEEVGIDLPYSCRAGSCSSCAGKVVGGEVDQSDG
-Q7XA98_TRIPR/1-152 TYKVKLITPEGPQEFDCPDDVYILDHAEEVGIELPYSCRAGSCSSCAGKVVNGNVNQEDG
-FER1_MESCR/1-148   AYKVTLVTPEGKQELECPDDVYILDAAEEAGIDLPYSCRAGSCSSCAGKVTSGSVNQDDG
-FER1_SPIOL/1-147   AYKVTLVTPTGNVEFQCPDDVYILDAAEEEGIDLPYSCRAGSCSSCAGKLKTGSLNQDDQ
-FER3_RAPSA/1-96    TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
-FER1_ARATH/1-148   TYKVKFITPEGELEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
-FER_BRANA/1-96     TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGFVDQSDE
-FER2_ARATH/1-148   TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ
-Q93Z60_ARATH/1-118 TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ
-FER1_MAIZE/1-150   TYNVKLITPEGEVELQVPDDVYILDQAEEDGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
-O80429_MAIZE/1-140 TYNVKLITPEGEVELQVPDDVYILDFAEEEGIDLPFSCRAGSCSSCAGKVVSGSVDQSDQ
-1A70|/1-97         AYKVTLVTPTGNVEFQCPDDVYILDAAEEEGIDLPYSCRAGSCSSCAGKLKTGSLNQDDQ
-
-FER_CAPAA/1-97     NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-
-FER_CAPAN/1-144    NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-
-FER1_SOLLC/1-144   NFLDEDQEAAGFVLTCVAYPKGDVTIETHKEEELTA-
-Q93XJ9_SOLTU/1-144 KFLDDDQEAAGFVLTCVAYPKCDVTIETHKEEELTA-
-FER1_PEA/1-149     SFLDDEQIEAGFVLTCVAYPTSDVVIETHKEEDLTA-
-Q7XA98_TRIPR/1-152 SFLDDEQIEGGWVLTCVAFPTSDVTIETHKEEELTA-
-FER1_MESCR/1-148   SFLDDDQIKEGWVLTCVAYPTGDVTIETHKEEELTA-
-FER1_SPIOL/1-147   SFLDDDQIDEGWVLTCAAYPVSDVTIETHKEEELTA-
-FER3_RAPSA/1-96    SFLDDDQIAEGFVLTCAAYPTSDVTIETHREEDMV--
-FER1_ARATH/1-148   SFLDDEQIGEGFVLTCAAYPTSDVTIETHKEEDIV--
-FER_BRANA/1-96     SFLDDDQIAEGFVLTCAAYPTSDVTIETHKEEELV--
-FER2_ARATH/1-148   SFLDDEQMSEGYVLTCVAYPTSDVVIETHKEEAIM--
-Q93Z60_ARATH/1-118 SFLDD--------------------------------
-FER1_MAIZE/1-150   SYLDDGQIADGWVLTCHAYPTSDVVIETHKEEELTGA
-O80429_MAIZE/1-140 SFLNDNQVADGWVLTCAAYPTSDVVIETHKEDDLL--
-1A70|/1-97         SFLDDDQIDEGWVLTCAAYPVSDVTIETHKKEELTA
-
+FER_CAPAA/1-97     -----------------------------------------------------------A 1
+FER_CAPAN/1-144    MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA 48
+FER1_SOLLC/1-144   MA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA 48
+Q93XJ9_SOLTU/1-144 MA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA 48
+FER1_PEA/1-149     MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA 53
+
+FER_CAPAA/1-97     SYKVKLI 8
+FER_CAPAN/1-144    SYKVKLI 55
+FER1_SOLLC/1-144   SYKVKLI 55
+Q93XJ9_SOLTU/1-144 SYKVKLI 55
+FER1_PEA/1-149     SYKVKLV 60
index d9a201a..e4bf12c 100755 (executable)
@@ -22,7 +22,7 @@
    <mapID target="home" url="html/index.html" />
    
    <mapID target="new" url="html/whatsNew.html"/>
-   <mapID target="release" url="html/releases.html#Jalview.2.10.4"/>
+   <mapID target="release" url="html/releases.html#Jalview.$$Version-Rel$$"/>
    <mapID target="alannotation" url="html/features/annotation.html"/>
    <mapID target="keys" url="html/keys.html"/>
    <mapID target="newkeys" url="html/features/newkeystrokes.html"/>
index 1ad46e3..75c8e06 100755 (executable)
@@ -70,6 +70,64 @@ li:before {
     <tr>
       <td width="60" nowrap>
         <div align="center">
+          <strong><a name="Jalview.2.10.4b1">2.10.4b1</a><br />
+            <em>7/06/2018</em></strong>
+        </div>
+      </td>
+      <td><div align="left">
+          <em></em>
+          <ul>
+            <li>
+              <!-- JAL-2920 -->Use HGVS nomenclature for variant
+              annotation retrieved from Uniprot
+            </li>
+            <li>
+              <!-- JAL-1460 -->Windows File Shortcuts can be dragged
+              onto the Jalview Desktop
+            </li>
+          </ul>
+        </div></td>
+      <td><div align="left">
+          <em></em>
+          <ul>
+            <li>
+              <!-- JAL-3017 -->Cannot import features with multiple
+              variant elements (blocks import of some Uniprot records)
+            </li>
+            <li>
+              <!-- JAL-2997 -->Clustal files with sequence positions in
+              right-hand column parsed correctly
+            </li>
+            <li>
+              <!-- JAL-2991 -->Wrap view - export to SVG - IDs shown but
+              not alignment area in exported graphic
+            </li>
+            <li>
+              <!-- JAL-2993 -->F2/Keyboard mode edits work when Overview
+              window has input focus
+            </li>
+            <li>
+              <!-- JAL-2992 -->Annotation panel set too high when
+              annotation added to view (Windows)
+            </li>
+            <li>
+              <!-- JAL-3009 -->Jalview Desktop is slow to start up when network connectivity is poor
+            </li>
+            <li>
+              <!-- JAL-1460 -->Drag URL from chrome, firefox, IE to
+              Jalview desktop on Windows doesn't open file<br /> <em>Dragging
+                the currently open URL and links from a page viewed in
+                Firefox or Chrome on Windows is now fully supported. If
+                you are using Edge, only links in the page can be
+                dragged, and with Internet Explorer, only the currently
+                open URL in the browser can be dropped onto Jalview.</em>
+            </li>
+          </ul>
+        </div></td>
+    </tr>
+    <tr>
+      <td width="60" nowrap>
+        <div align="center">
           <strong><a name="Jalview.2.10.4">2.10.4</a><br /> <em>10/05/2018</em></strong>
         </div>
       </td>
index 0abd2a7..ed6f5c2 100755 (executable)
 </head>
 <body>
   <p>
-    <strong>What's new in Jalview 2.10.4 ?</strong>
-  </p>
-  <p>
-    This is the May 2018 release of Jalview, and the last in the 2.10.x series. Jalview 2.10.4 includes:
+    <strong>What's new in Jalview 2.10.4b1 ?</strong>
   </p>
+  <p>This is the first patch release for Jalview 2.10.4. It includes
+    the following new patches:</p>
+  <ul>
+    <li>HGVS nomenclature used for variant annotation retrieved
+      from Uniprot</li>
+    <li>Uniprot import fails for some sequences (Cannot import
+      features with multiple variant elements)</li>
+    <li>Clustal files with sequence positions in right-hand column
+      are now parsed correctly</li>
+    <li>Wrap view - export to SVG - IDs shown but not alignment
+      area in exported graphic</li>
+    <li>F2/Keyboard mode edits work when Overview window has input
+      focus</li>
+    <li>Windows specific fixes:
+      <ul>
+        <li>Annotation panel set too high when annotation added to
+          view</li>
+        <li>Updated search paths for Chimera default installation</li>
+        <li>Windows File Shortcuts can be dragged onto the Jalview
+          Desktop</li>
+        <li>Drag URL from Chrome, Firefox, IE to Jalview desktop on
+          Windows doesn't open file:<br /> Dragging the currently open
+          URL and links from a page viewed in Firefox or Chrome on
+          Windows is now fully supported.<br />
+        <strong>If you are using Edge</strong>, only links in the page
+          can be dragged.<br />
+        <strong>With Internet Explorer</strong>, only the currently open
+          URL in the browser can be dropped onto Jalview.
+        </li>
+      </ul>
+    </li>
+  </ul>
+  <p>Highlights in the 2.10.4 series include:</p>
   <ul>
     <li>Numerous efficiency improvements in the renderer and overview when working with large alignments with lots of hidden columns</li>
     <li>Use of HTTPS when connecting to Uniprot, Ensembl and other EBI web services</li>
index a80ac17..4d87b80 100644 (file)
@@ -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.<br>Double-click to browse for file.
 label.invalid_chimera_path = Chimera path not found or not executable
index 61bf42a..d7c040c 100644 (file)
@@ -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=<html>Borrar todas las secuencias cerrará la ventana del alineamiento.<br>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
index 832d3e5..68868c4 100755 (executable)
@@ -18,6 +18,7 @@
  * The Jalview Authors are detailed in the 'AUTHORS' file.
 -->
 <mapping>
+  <!-- see https://www.uniprot.org/docs/uniprot.xsd for latest Uniprot XML schema -->
        <class name="jalview.datamodel.xdb.uniprot.UniprotFile">
                  <map-to xml="uniprot"/>               
                  <field name="UniprotEntries" type="jalview.datamodel.xdb.uniprot.UniprotEntry" collection="vector">
@@ -69,7 +70,7 @@
     <field name="end">
       <bind-xml name="position" node="attribute" location="location/end"/>
     </field>
-    <field name="variation">
+    <field name="variation" collection="vector" type="string">
      <bind-xml name="variation"/>
     </field>
     <field name="original">
index effe556..09a9713 100644 (file)
@@ -97,7 +97,7 @@ public class StructureManager
     this.haveGUI = haveGUI;
     // Create the Chimera interface
     chimeraManager = new ChimeraManager(this);
-    chimSelectionList = new ArrayList<ChimeraStructuralObject>();
+    chimSelectionList = new ArrayList<>();
     pathProps = new Properties();
   }
 
@@ -110,7 +110,7 @@ public class StructureManager
           ModelType type)
   {
     // new models
-    Map<String, List<ChimeraModel>> newModels = new HashMap<String, List<ChimeraModel>>();
+    Map<String, List<ChimeraModel>> newModels = new HashMap<>();
     if (chimObjNames.size() > 0)
     {
       List<String> names = chimObjNames.iterator().next();
@@ -846,7 +846,7 @@ public class StructureManager
     // alDialog.dispose();
     // }
     // System.out.println("launch align dialog");
-    List<ChimeraStructuralObject> chimObjectList = new ArrayList<ChimeraStructuralObject>();
+    List<ChimeraStructuralObject> chimObjectList = new ArrayList<>();
     for (ChimeraModel model : chimeraManager.getChimeraModels())
     {
       if (useChains)
@@ -887,7 +887,7 @@ public class StructureManager
 
   public List<String> getAllChimeraResidueAttributes()
   {
-    List<String> attributes = new ArrayList<String>();
+    List<String> 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<String> getChimeraPaths()
   {
-    List<String> pathList = new ArrayList<String>();
+    List<String> 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"))
     {
index 4c2ae24..8bd5652 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.datamodel.xdb.uniprot;
 
+import java.util.Vector;
+
 /**
  * A data model class for binding from Uniprot XML via uniprot_mapping.xml
  */
@@ -31,7 +33,7 @@ public class UniprotFeature
 
   private String original = null;
 
-  private String variation = null;
+  private Vector<String> variation = null;
 
   private String status;
 
@@ -51,19 +53,7 @@ public class UniprotFeature
 
   public String getDescription()
   {
-    if (description == null && variation == null && original == null)
-    {
-      return null;
-    }
-    return (description == null ? "" : description)
-            + (variation != null
-                    ? (description != null ? " " : "") + "Variation: '"
-                            + variation + "'"
-                    : "")
-            + (original != null
-                    ? ((description != null || variation != null) ? " "
-                            : "") + "Original: '" + original + "'"
-                    : "");
+    return description;
   }
 
   public void setDescription(String d)
@@ -122,12 +112,12 @@ public class UniprotFeature
     this.original = original;
   }
 
-  public String getVariation()
+  public Vector<String> getVariation()
   {
     return variation;
   }
 
-  public void setVariation(String variant)
+  public void setVariation(Vector<String> variant)
   {
     this.variation = variant;
   }
index 9821e9e..a27b495 100644 (file)
@@ -3259,6 +3259,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 alignPanel.setOverviewPanel(null);
               };
             });
+    if (getKeyListeners().length > 0)
+    {
+      frame.addKeyListener(getKeyListeners()[0]);
+    }
 
     alignPanel.setOverviewPanel(overview);
   }
index f5634d2..2c5684a 100644 (file)
@@ -48,7 +48,6 @@ import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
-import java.awt.Insets;
 import java.awt.event.AdjustmentEvent;
 import java.awt.event.AdjustmentListener;
 import java.awt.event.ComponentAdapter;
@@ -556,13 +555,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
       /*
        * Estimate available height in the AlignFrame for alignment +
        * annotations. Deduct an estimate for title bar, menu bar, scale panel,
-       * hscroll, status bar (as these are not laid out we can't inspect their
-       * actual heights). Insets gives frame borders.
+       * hscroll, status bar, insets. 
        */
-      int stuff = Platform.isAMac() ? 80 : 100;
-      Insets insets = alignFrame.getInsets();
-      int availableHeight = alignFrame.getHeight() - stuff - insets.top
-              - insets.bottom;
+      int stuff = Platform.isAMac() ? 120 : 140;
+      int availableHeight = alignFrame.getHeight() - stuff;
 
       /*
        * If not enough vertical space, maximize annotation height while keeping
index 9a696e9..259e693 100644 (file)
@@ -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
  * 
@@ -512,7 +515,7 @@ public class Desktop extends jalview.jbgui.GDesktop
   {
     final Desktop me = this;
     // Thread off the news reader, in case there are connection problems.
-    addDialogThread(new Runnable()
+    new Thread(new Runnable()
     {
       @Override
       public void run()
@@ -523,13 +526,13 @@ public class Desktop extends jalview.jbgui.GDesktop
         showNews.setVisible(true);
         Cache.log.debug("Completed news thread.");
       }
-    });
+    }).start();
   }
 
   public void getIdentifiersOrgData()
   {
     // Thread off the identifiers fetcher
-    addDialogThread(new Runnable()
+    new Thread(new Runnable()
     {
       @Override
       public void run()
@@ -546,7 +549,8 @@ public class Desktop extends jalview.jbgui.GDesktop
                   + e.getMessage());
         }
       }
-    });
+    }).start();
+    ;
   }
 
   @Override
@@ -3294,13 +3298,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<String> files,
           List<DataSourceType> 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 +3386,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("#"))
-        {
-          // 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"))
+        while (protocols.size() < files.size())
         {
-          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)
-            {
-              Cache.log.debug("Retrieves: " + df);
-            }
-            else
+                    "Couldn't resolve drop data. Here are the supported flavors:");
+            for (DataFlavor fl : t.getTransferDataFlavors())
             {
-              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);
+          }
         }
       }
     }
index 2d8eb7d..8f315bd 100755 (executable)
@@ -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
index c21b02c..afb2009 100755 (executable)
@@ -26,7 +26,8 @@ import jalview.datamodel.SequenceI;
 import jalview.util.Format;
 
 import java.io.IOException;
-import java.util.Hashtable;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.Vector;
 
@@ -59,12 +60,11 @@ public class ClustalFile extends AlignFile
   {
     int i = 0;
     boolean flag = false;
-    boolean rna = false;
     boolean top = false;
-    StringBuffer pssecstr = new StringBuffer(),
-            consstr = new StringBuffer();
-    Vector headers = new Vector();
-    Hashtable seqhash = new Hashtable();
+    StringBuffer pssecstr = new StringBuffer();
+    StringBuffer consstr = new StringBuffer();
+    Vector<String> headers = new Vector<>();
+    Map<String, StringBuffer> seqhash = new HashMap<>();
     StringBuffer tempseq;
     String line, id;
     StringTokenizer str;
@@ -77,9 +77,11 @@ public class ClustalFile extends AlignFile
         {
           top = true;
         }
-        if (line.indexOf(" ") != 0)
+        boolean isConservation = line.startsWith(SPACE)
+                || line.startsWith(TAB);
+        if (!isConservation)
         {
-          str = new StringTokenizer(line, " ");
+          str = new StringTokenizer(line);
 
           if (str.hasMoreTokens())
           {
@@ -95,7 +97,7 @@ public class ClustalFile extends AlignFile
               {
                 if (seqhash.containsKey(id))
                 {
-                  tempseq = (StringBuffer) seqhash.get(id);
+                  tempseq = seqhash.get(id);
                 }
                 else
                 {
@@ -173,7 +175,7 @@ public class ClustalFile extends AlignFile
       AlignmentAnnotation lastssa = null;
       if (pssecstr.length() == maxLength)
       {
-        Vector ss = new Vector();
+        Vector<AlignmentAnnotation> ss = new Vector<>();
         AlignmentAnnotation ssa = lastssa = StockholmFile
                 .parseAnnotationRow(ss, "secondary structure",
                         pssecstr.toString());
@@ -182,7 +184,7 @@ public class ClustalFile extends AlignFile
       }
       if (consstr.length() == maxLength)
       {
-        Vector ss = new Vector();
+        Vector<AlignmentAnnotation> ss = new Vector<>();
         AlignmentAnnotation ssa = StockholmFile.parseAnnotationRow(ss,
                 "secondary structure", consstr.toString());
         ssa.label = "Consensus Secondary Structure";
@@ -238,19 +240,19 @@ public class ClustalFile extends AlignFile
         out.append(new Format("%-" + maxid + "s")
                 .form(printId(s[j], jvsuffix) + " "));
 
-        int start = i * len;
-        int end = start + len;
+        int chunkStart = i * len;
+        int chunkEnd = chunkStart + len;
 
         int length = s[j].getLength();
-        if ((end < length) && (start < length))
+        if ((chunkEnd < length) && (chunkStart < length))
         {
-          out.append(s[j].getSequenceAsString(start, end));
+          out.append(s[j].getSequenceAsString(chunkStart, chunkEnd));
         }
         else
         {
-          if (start < length)
+          if (chunkStart < length)
           {
-            out.append(s[j].getSequenceAsString().substring(start));
+            out.append(s[j].getSequenceAsString().substring(chunkStart));
           }
         }
 
index d2282b1..e0722c0 100755 (executable)
@@ -72,8 +72,6 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
 
   private static final String NOTE = "Note";
 
-  protected static final String TAB = "\t";
-
   protected static final String GFF_VERSION = "##gff-version";
 
   private AlignmentI lastmatchedAl = null;
@@ -180,11 +178,11 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
           Map<String, FeatureColourI> colours, boolean removeHTML,
           boolean relaxedIdmatching)
   {
-    Map<String, String> gffProps = new HashMap<String, String>();
+    Map<String, String> gffProps = new HashMap<>();
     /*
      * keep track of any sequences we try to create from the data
      */
-    List<SequenceI> newseqs = new ArrayList<SequenceI>();
+    List<SequenceI> newseqs = new ArrayList<>();
 
     String line = null;
     try
@@ -534,7 +532,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
      * sort groups alphabetically, and ensure that features with a
      * null or empty group are output after those in named groups
      */
-    List<String> sortedGroups = new ArrayList<String>(visibleFeatureGroups);
+    List<String> sortedGroups = new ArrayList<>(visibleFeatureGroups);
     sortedGroups.remove(null);
     sortedGroups.remove("");
     Collections.sort(sortedGroups);
@@ -577,7 +575,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
       for (int i = 0; i < sequences.length; i++)
       {
         String sequenceName = sequences[i].getName();
-        List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+        List<SequenceFeature> features = new ArrayList<>();
         if (types.length > 0)
         {
           features.addAll(sequences[i].getFeatures().getFeaturesForGroup(
@@ -688,7 +686,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
       dataset = new Alignment(new SequenceI[] {});
     }
 
-    Map<String, FeatureColourI> featureColours = new HashMap<String, FeatureColourI>();
+    Map<String, FeatureColourI> featureColours = new HashMap<>();
     boolean parseResult = parse(dataset, featureColours, false, true);
     if (!parseResult)
     {
@@ -748,7 +746,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
 
     for (SequenceI seq : sequences)
     {
-      List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+      List<SequenceFeature> features = new ArrayList<>();
       if (includeNonPositionalFeatures)
       {
         features.addAll(seq.getFeatures().getNonPositionalFeatures());
index c0328d5..7117d0f 100755 (executable)
@@ -45,6 +45,10 @@ import java.util.zip.GZIPInputStream;
  */
 public class FileParse
 {
+  protected static final String SPACE = " ";
+
+  protected static final String TAB = "\t";
+
   /**
    * text specifying source of data. usually filename or url.
    */
index 1ca0802..26e0919 100755 (executable)
@@ -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
index 6b09eb6..167cd97 100644 (file)
@@ -32,6 +32,8 @@ import jalview.datamodel.SequenceI;
 import jalview.datamodel.xdb.uniprot.UniprotEntry;
 import jalview.datamodel.xdb.uniprot.UniprotFeature;
 import jalview.datamodel.xdb.uniprot.UniprotFile;
+import jalview.schemes.ResidueProperties;
+import jalview.util.StringUtils;
 import jalview.ws.seqfetcher.DbSourceProxyImpl;
 
 import java.io.InputStream;
@@ -40,6 +42,7 @@ import java.io.Reader;
 import java.net.URL;
 import java.net.URLConnection;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Vector;
 
 import org.exolab.castor.mapping.Mapping;
@@ -278,7 +281,7 @@ public class Uniprot extends DbSourceProxyImpl
       for (UniprotFeature uf : entry.getFeature())
       {
         SequenceFeature copy = new SequenceFeature(uf.getType(),
-                uf.getDescription(), uf.getBegin(), uf.getEnd(), "Uniprot");
+                getDescription(uf), uf.getBegin(), uf.getEnd(), "Uniprot");
         copy.setStatus(uf.getStatus());
         sequence.addSequenceFeature(copy);
       }
@@ -291,6 +294,94 @@ public class Uniprot extends DbSourceProxyImpl
   }
 
   /**
+   * Constructs a feature description from the description and (optionally)
+   * original and variant fields of the Uniprot XML feature
+   * 
+   * @param uf
+   * @return
+   */
+  protected static String getDescription(UniprotFeature uf)
+  {
+    String orig = uf.getOriginal();
+    List<String> variants = uf.getVariation();
+    StringBuilder sb = new StringBuilder();
+
+    /*
+     * append variant in standard format if present
+     * e.g. p.Arg59Lys
+     * multiple variants are split over lines using <br>
+     */
+    boolean asHtml = false;
+    if (orig != null && !orig.isEmpty() && variants != null
+            && !variants.isEmpty())
+    {
+      int p = 0;
+      for (String var : variants)
+      {
+        // TODO proper HGVS nomenclature for delins structural variations
+        // http://varnomen.hgvs.org/recommendations/protein/variant/delins/
+        // for now we are pragmatic - any orig/variant sequence longer than
+        // three characters is shown with single-character notation rather than
+        // three-letter notation
+        sb.append("p.");
+        if (orig.length() < 4)
+        {
+          for (int c = 0, clen = orig.length(); c < clen; c++)
+          {
+            char origchar = orig.charAt(c);
+            String orig3 = ResidueProperties.aa2Triplet.get("" + origchar);
+            sb.append(orig3 == null ? origchar
+                    : StringUtils.toSentenceCase(orig3));
+          }
+        }
+        else
+        {
+          sb.append(orig);
+        }
+
+        sb.append(Integer.toString(uf.getPosition()));
+
+        if (var.length() < 4)
+        {
+          for (int c = 0, clen = var.length(); c < clen; c++)
+          {
+            char varchar = var.charAt(c);
+            String var3 = ResidueProperties.aa2Triplet.get("" + varchar);
+
+            sb.append(var3 != null ? StringUtils.toSentenceCase(var3)
+                    : "" + varchar);
+          }
+        }
+        else
+        {
+          sb.append(var);
+        }
+        if (++p != variants.size())
+        {
+          sb.append("<br/>&nbsp;&nbsp;");
+          asHtml = true;
+        }
+        else
+        {
+          sb.append(" ");
+        }
+      }
+    }
+    String description = uf.getDescription();
+    if (description != null)
+    {
+      sb.append(description);
+    }
+    if (asHtml)
+    {
+      sb.insert(0, "<html>");
+      sb.append("</html>");
+    }
+
+    return sb.toString();
+  }
+
+  /**
    * 
    * @param entry
    *          UniportEntry
diff --git a/src/org/stackoverflowusers/file/WindowsShortcut.java b/src/org/stackoverflowusers/file/WindowsShortcut.java
new file mode 100644 (file)
index 0000000..671e002
--- /dev/null
@@ -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 &lt;jessehager@iname.com&gt;
+ *   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);
+    }
+
+}
diff --git a/test/jalview/io/ClustalFileTest.java b/test/jalview/io/ClustalFileTest.java
new file mode 100644 (file)
index 0000000..1da2c75
--- /dev/null
@@ -0,0 +1,67 @@
+package jalview.io;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.datamodel.SequenceI;
+
+import java.io.IOException;
+
+import org.testng.annotations.Test;
+
+public class ClustalFileTest
+{
+  @Test(groups="Functional")
+  public void testParse_withNumbering() throws IOException
+  {
+    //@formatter:off
+    String data = "CLUSTAL\n\n"
+            + "FER_CAPAA/1-8      -----------------------------------------------------------A\t1\n"
+            + "FER_CAPAN/1-55     MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA 48\n"
+            + "FER1_SOLLC/1-55    MA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA 48\n"
+            + "Q93XJ9_SOLTU/1-55  MA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA 48\n"
+            + "FER1_PEA/1-60      MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA 53\n\n"
+            + "FER_CAPAA/1-8      SYKVKLI 8\n"
+            + "FER_CAPAN/1-55     SYKVKLI 55\n"
+            + "FER1_SOLLC/1-55    SYKVKLI 55\n"
+            + "Q93XJ9_SOLTU/1-55  SYKVKLI 55\n"
+            + "FER1_PEA/1-60      SYKVKLV 60\n"
+            + "                   .*     .:....*******..** ..........**  ********...*:::*  ...\n"
+            + "\t\t.:.::.  *\n";
+    //@formatter:on
+    ClustalFile cf = new ClustalFile(data, DataSourceType.PASTE);
+    cf.parse();
+    SequenceI[] seqs = cf.getSeqsAsArray();
+    assertEquals(seqs.length, 5);
+    assertEquals(seqs[0].getName(), "FER_CAPAA");
+    assertEquals(seqs[0].getStart(), 1);
+    assertEquals(seqs[0].getEnd(), 8);
+    assertTrue(seqs[0].getSequenceAsString().endsWith("ASYKVKLI"));
+  }
+
+  @Test(groups="Functional")
+  public void testParse_noNumbering() throws IOException
+  {
+    //@formatter:off
+    String data = "CLUSTAL\n\n"
+            + "FER_CAPAA/1-8      -----------------------------------------------------------A\n"
+            + "FER_CAPAN/1-55     MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA\n"
+            + "FER1_SOLLC/1-55    MA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA\n"
+            + "Q93XJ9_SOLTU/1-55  MA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA\n"
+            + "FER1_PEA/1-60      MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA\n\n"
+            + "FER_CAPAA/1-8      SYKVKLI\n"
+            + "FER_CAPAN/1-55     SYKVKLI\n"
+            + "FER1_SOLLC/1-55    SYKVKLI\n"
+            + "Q93XJ9_SOLTU/1-55  SYKVKLI\n"
+            + "FER1_PEA/1-60      SYKVKLV\n";
+    //@formatter:on
+    ClustalFile cf = new ClustalFile(data, DataSourceType.PASTE);
+    cf.parse();
+    SequenceI[] seqs = cf.getSeqsAsArray();
+    assertEquals(seqs.length, 5);
+    assertEquals(seqs[0].getName(), "FER_CAPAA");
+    assertEquals(seqs[0].getStart(), 1);
+    assertEquals(seqs[0].getEnd(), 8);
+    assertTrue(seqs[0].getSequenceAsString().endsWith("ASYKVKLI"));
+  }
+}
index c603a11..060c303 100644 (file)
@@ -35,6 +35,7 @@ import java.io.Reader;
 import java.io.StringReader;
 import java.util.Vector;
 
+import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -66,6 +67,9 @@ public class UniprotTest
           + "<feature type=\"sequence variant\"><original>M</original><variation>L</variation><location><position position=\"41\"/></location></feature>"
           + "<feature type=\"sequence variant\" description=\"Pathogenic\"><original>M</original><variation>L</variation><location><position position=\"41\"/></location></feature>"
           + "<feature type=\"sequence variant\" description=\"Pathogenic\"><original>M</original><location><position position=\"41\"/></location></feature>"
+          + "<feature type=\"sequence variant\" description=\"Foo\"><variation>L</variation><variation>LMV</variation><original>M</original><location><position position=\"42\"/></location></feature>"
+          + "<feature type=\"sequence variant\" description=\"Foo\"><variation>LL</variation><variation>LMV</variation><original>ML</original><location><begin position=\"42\"/><end position=\"43\"/></location></feature>"
+          + "<feature type=\"sequence variant\" description=\"Foo Too\"><variation>LL</variation><variation>LMVK</variation><original>MLML</original><location><begin position=\"42\"/><end position=\"45\"/></location></feature>"
           + "<sequence length=\"10\" mass=\"27410\" checksum=\"8CB760AACF88FE6C\" modified=\"2008-01-15\" version=\"1\">MHAPL VSKDL</sequence></entry>"
           + "</uniprot>";
 
@@ -101,7 +105,7 @@ public class UniprotTest
      * Check sequence features
      */
     Vector<UniprotFeature> features = entry.getFeature();
-    assertEquals(6, features.size());
+    assertEquals(9, features.size());
     UniprotFeature sf = features.get(0);
     assertEquals("signal peptide", sf.getType());
     assertNull(sf.getDescription());
@@ -123,25 +127,50 @@ public class UniprotTest
 
     sf = features.get(3);
     assertEquals("sequence variant", sf.getType());
-    assertEquals("Variation: 'L' Original: 'M'", sf.getDescription());
+    assertNull(sf.getDescription());
     assertEquals(41, sf.getPosition());
     assertEquals(41, sf.getBegin());
     assertEquals(41, sf.getEnd());
 
     sf = features.get(4);
     assertEquals("sequence variant", sf.getType());
-    assertEquals("Pathogenic Variation: 'L' Original: 'M'",
-            sf.getDescription());
+    assertEquals("Pathogenic", sf.getDescription());
     assertEquals(41, sf.getPosition());
     assertEquals(41, sf.getBegin());
     assertEquals(41, sf.getEnd());
 
     sf = features.get(5);
     assertEquals("sequence variant", sf.getType());
-    assertEquals("Pathogenic Original: 'M'", sf.getDescription());
+    assertEquals("Pathogenic", sf.getDescription());
     assertEquals(41, sf.getPosition());
     assertEquals(41, sf.getBegin());
     assertEquals(41, sf.getEnd());
+
+    sf = features.get(6);
+    assertEquals("sequence variant", sf.getType());
+    assertEquals("Foo",
+            sf.getDescription());
+    assertEquals(42, sf.getPosition());
+    assertEquals(42, sf.getBegin());
+    assertEquals(42, sf.getEnd());
+    Assert.assertEquals(Uniprot.getDescription(sf),
+            "<html>p.Met42Leu" + "<br/>&nbsp;&nbsp;"
+                    + "p.Met42LeuMetVal Foo</html>");
+
+    sf = features.get(7);
+    assertEquals(42, sf.getBegin());
+    assertEquals(43, sf.getEnd());
+    Assert.assertEquals(Uniprot.getDescription(sf),
+            "<html>p.MetLeu42LeuLeu" + "<br/>&nbsp;&nbsp;"
+                    + "p.MetLeu42LeuMetVal Foo</html>");
+
+    sf = features.get(8);
+    assertEquals(42, sf.getBegin());
+    assertEquals(45, sf.getEnd());
+    Assert.assertEquals(Uniprot.getDescription(sf),
+            "<html>p.MLML42LeuLeu" + "<br/>&nbsp;&nbsp;"
+                    + "p.MLML42LMVK Foo Too</html>");
+
     /*
      * Check cross-references
      */
@@ -210,4 +239,53 @@ public class UniprotTest
     assertEquals(expectedDescription,
             Uniprot.getUniprotEntryDescription(entry));
   }
+
+  @Test(groups = { "Functional" })
+  public void testGetDescription()
+  {
+    UniprotFeature uf = new UniprotFeature();
+    assertEquals("", Uniprot.getDescription(uf));
+
+    uf.setDescription("Hello");
+    assertEquals("Hello", Uniprot.getDescription(uf));
+
+    uf.setPosition(23);
+    uf.setOriginal("K");
+    Vector<String> vars = new Vector<>();
+    vars.add("y");
+    uf.setVariation(vars);
+    assertEquals("p.Lys23Tyr Hello", Uniprot.getDescription(uf));
+
+    // multiple variants generate an html description over more than one line
+    vars.add("W");
+    assertEquals("<html>p.Lys23Tyr<br/>&nbsp;&nbsp;p.Lys23Trp Hello</html>",
+            Uniprot.getDescription(uf));
+
+    /*
+     * indel cases
+     * up to 3 bases (original or variant) are shown using 3 letter code
+     */
+    vars.clear();
+    vars.add("KWE");
+    uf.setOriginal("KLS");
+    assertEquals("p.LysLeuSer23LysTrpGlu Hello",
+            Uniprot.getDescription(uf));
+
+    // adding a fourth original base switches to single letter code
+    uf.setOriginal("KLST");
+    assertEquals("p.KLST23LysTrpGlu Hello", Uniprot.getDescription(uf));
+
+    // adding a fourth variant switches to single letter code
+    vars.clear();
+    vars.add("KWES");
+    assertEquals("p.KLST23KWES Hello", Uniprot.getDescription(uf));
+
+    vars.clear();
+    vars.add("z"); // unknown variant - fails gracefully
+    uf.setOriginal("K");
+    assertEquals("p.Lys23z Hello", Uniprot.getDescription(uf));
+
+    uf.setVariation(null); // variant missing - is ignored
+    assertEquals("Hello", Uniprot.getDescription(uf));
+  }
 }