Merge branch 'features/JAL-1667_1668-documentation' into Release_2_8_3_Branch
authortcofoegbu <tcnofoegbu@dundee.ac.uk>
Wed, 22 Apr 2015 15:47:55 +0000 (16:47 +0100)
committertcofoegbu <tcnofoegbu@dundee.ac.uk>
Wed, 22 Apr 2015 15:47:55 +0000 (16:47 +0100)
12 files changed:
help/html/features/splitView.html [new file with mode: 0644]
help/html/menus/alwcalculate.html
resources/lang/Messages.properties
src/jalview/analysis/AlignmentUtils.java
src/jalview/datamodel/AlignedCodonFrame.java
src/jalview/gui/Desktop.java
src/jalview/gui/FontChooser.java
src/jalview/gui/StructureChooser.java
src/jalview/jbgui/GStructureChooser.java
src/jalview/ws/dbsources/PDBRestClient.java
test/jalview/analysis/AlignmentUtilsTests.java
test/jalview/gui/FontChooserTest.java [new file with mode: 0644]

diff --git a/help/html/features/splitView.html b/help/html/features/splitView.html
new file mode 100644 (file)
index 0000000..c9ce093
--- /dev/null
@@ -0,0 +1,74 @@
+<html>
+<!--
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ -->
+<head>
+<title>Split Frame Views</title>
+</head>
+<body>
+<p><strong>Split Frame Views</strong></p>
+<p/></p>Coding DNA (cDNA) and its protein product can be displayed in a split view, with cDNA above and protein below. The two alignments are
+linked, with these features supported:
+<ul>
+<li>mouseover or scrolling of either alignment is followed by the other (unless you turn off <strong><a href="../menus/alwview.html">"View | Automatic Scrolling"</a></strong>)</li>
+<li>on selecting rows, columns or regions in one alignment, the corresponding selection is made in the other</li>
+<li>sequence ordering in one alignment (using the cursor, or <strong><a href="../calculate/sorting.html">"Calculate | Sort")</a></strong> is also applied to the other</li>
+<li>editing (gap insertion / deletion) in the protein alignment is reflected in the cDNA (but not vice versa)</li>
+<li>on <strong><a href="../calculations/tree.html">"Calculate Tree"</a></strong> in either alignment, grouping, colouring and sorting by tree are applied to both</li>
+<li>the <strong><a href="../menus/alwformat.html">"Format | Font"</a></strong> menu option has an option 'Scale protein residues to codons'; select this option to make each protein residue
+the same width as a DNA codon (so the alignments 'line up' vertically)</li>
+</ul>
+<p>An alignment annotation row on the protein alignment shows the <strong><a href="../calculations/consensus.html">cDNA consensus</a></strong> for each peptide column.<br/>
+This consensus may reveal variation in nucleotides coding for conserved protein residues.</p>
+
+<p><strong>Opening a Split Frame View</strong></p>
+<p>A Split Frame View can be opened in one of the following ways:</p>
+<p><strong><em>Add Sequences</em></strong></p>
+<p>If you add (coding) DNA sequences to an open peptide alignment, or vice versa, <em>and</em> at least one DNA sequence translates to one of the
+peptide sequences, then the option to open in a split window is offered. The DNA may include start and/or stop codons, but no non-coding (intron)
+sequence.</p> 
+
+<p>This option is available in Jalview Desktop (when adding sequences by any supported method), and Jalview applet (adding from textbox).
+The additional options below apply to Jalview Desktop only.</p>
+
+<p><strong><em>Translate as cDNA</em></strong></p>
+<p>Menu option <strong><a href="../menus/alwcalculate.html">"Calculate | Translate as cDNA"</a></strong> is available for a nucleotide alignment. Selecting this option shows the DNA and its 
+calculated protein product in a Split Frame view.</p>
+
+<p><strong><em>Get Cross-References</em></strong></p>
+<p>Menu option <strong><a href="../menus/alwcalculate.html">"Calculate | Get Cross-References"</a></strong> is available for fetched sequences which have cross-references to other databases.
+On selecting protein cross-references (for a cDNA alignment), or DNA xrefs (for peptide), a Split Frame view is opened showing cDNA and peptide.</p>
+
+<p><strong><em>Realign Split View</em></strong></p>
+<p>If you invoke a web service to realign either half of a Split Frame, then the resulting realignment is displayed in a new
+Split Frame.</p> 
+<ul>
+<li>the alignment you chose to realign (for example, peptide) is displayed as aligned by the external web service</li>
+<li>Jalview 'aligns' its complement (in this case, cDNA) similarly, by inserting corresponding gaps
+    <ul>
+    <li>NB this is <em>not</em> the same as aligning the complement using the external service, which may give different results</li> 
+    </ul>
+</li>
+</ul> 
+  
+
+<p><em>Split Frame Views were introduced in Jalview 2.?.?</em></p>
+</body>
+</html>
index 7b95a24..50bb37b 100755 (executable)
                                When selected, these numbers are parsed into sequence associated
                                annotation which can then be used to sort the alignment via the Sort
                                by&#8594;Score menu.</em> <br></li>
-
+               <li><strong>Translate as cDNA</strong> (not applet)<br><em>This option is visible for nucleotide alignments. 
+                       Selecting this option shows the DNA's calculated protein product in a new window. Note that the 
+                       translation is not frame- or intron-aware; it simply translates all codons in each sequence, using the
+                       standard <a href="../misc/geneticCode.html">genetic code</a> (any incomplete final codon is discarded). 
+                       You can perform this action on the whole alignment, 
+                       or selected rows, columns, or regions.</em> <br></li>
+               <li><strong>Get Cross-References</strong> (not applet)<br><em>This option is visible where sequences have cross-references to
+                       other standard databases; for example, an EMBL entry may have cross-references to one or more UNIPROT entries.
+                       Select the database to view all cross-referenced sequences in a new window.</em> <br></li>
                <li><strong>Autocalculate Consensus</strong><br> <em>For
                                large alignments it can be useful to deselect &quot;Autocalculate
                                Consensus&quot; when editing. This prevents the sometimes lengthy
index 635ea5f..3d572f5 100644 (file)
@@ -1183,7 +1183,7 @@ label.normalise_group_logo = Normalise Group Logo
 label.show_histogram = Show Histogram
 label.show_logo = Show Logo
 label.normalise_logo = Normalise Logo
-label.no_colour_selection_in_scheme = Please, make a colour selection before to apply colour scheme
+label.no_colour_selection_in_scheme = Please make a colour selection before applying colour scheme
 label.no_colour_selection_warn = Error saving colour scheme
 label.open_split_window? = Would you like to open as a split window, with cDNA and protein linked?
 label.open_split_window = Open split window
@@ -1225,23 +1225,6 @@ label.found_structures_summary = Found Structures Summary
 label.configure_displayed_columns = Configure Displayed Columns
 label.start_jalview = Start Jalview
 label.biojs_html_export = BioJS
-action.back = Back
-label.hide_insertions = Hide Insertions
-label.mark_as_representative = Mark as representative
-label.open_jabaws_web_page = Open JABAWS web page
-label.opens_the_jabaws_server_homepage = Opens the JABAWS server's homepage in web browser
-label.pdb_sequence_getcher = PDB Sequence Fetcher
-label.result = result
-label.results = results
-label.structure_chooser = Structure Chooser
-label.select = Select : 
-label.invert = Invert 
-label.select_pdb_file = Select PDB File
-info.select_filter_option = Select Filter Option/Manual Entry
-info.associate_wit_sequence = Associate with Sequence
-label.search_result = Search Result
-label.found_structures_summary = Found Structures Summary
-label.configure_displayed_columns = Configure Displayed Columns
-label.scale_as_cdna = Scale protein residues to width of codons
+label.scale_as_cdna = Scale protein residues to codons
 label.scale_protein_to_cdna = Scale Protein to cDNA
 label.scale_protein_to_cdna_tip = Make protein residues same width as codons in split frame views
index a811d84..a4aeac7 100644 (file)
@@ -27,6 +27,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -44,7 +45,6 @@ import jalview.datamodel.FeatureProperties;
 import jalview.datamodel.Mapping;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ResidueProperties;
@@ -1279,7 +1279,7 @@ public class AlignmentUtils
   public static AlignmentI makeExonAlignment(SequenceI[] dna,
           Set<AlignedCodonFrame> mappings)
   {
-    Set<AlignedCodonFrame> newMappings = new HashSet<AlignedCodonFrame>();
+    Set<AlignedCodonFrame> newMappings = new LinkedHashSet<AlignedCodonFrame>();
     List<SequenceI> exonSequences = new ArrayList<SequenceI>();
     
     for (SequenceI dnaSeq : dna)
@@ -1287,20 +1287,16 @@ public class AlignmentUtils
       final SequenceI ds = dnaSeq.getDatasetSequence();
       List<AlignedCodonFrame> seqMappings = MappingUtils
               .findMappingsForSequence(ds, mappings);
-      if (!seqMappings.isEmpty())
+      for (AlignedCodonFrame acf : seqMappings)
       {
-        /*
-         * We assume here that only one protein mapping is expected per dna
-         * sequence. Mapping to multiple protein sequences is conceivable but
-         * undefined. Splitting a mapping to one protein sequence across
-         * multiple mappings is possible but pathological. Need closer
-         * constraints on the contents of AlignedCodonFrame.
-         */
         AlignedCodonFrame newMapping = new AlignedCodonFrame();
-        final SequenceI exonSequence = makeExonSequence(ds,
-                seqMappings.get(0), newMapping);
-        exonSequences.add(exonSequence);
-        newMappings.add(newMapping);
+        final List<SequenceI> mappedExons = makeExonSequences(ds, acf,
+                newMapping);
+        if (!mappedExons.isEmpty())
+        {
+          exonSequences.addAll(mappedExons);
+          newMappings.add(newMapping);
+        }
       }
     }
     AlignmentI al = new Alignment(
@@ -1317,71 +1313,88 @@ public class AlignmentUtils
   }
 
   /**
-   * Helper method to make an exon-only sequence and populate its mapping to
-   * protein
+   * Helper method to make exon-only sequences and populate their mappings to
+   * protein products
    * <p>
    * For example, if ggCCaTTcGAg has mappings [3, 4, 6, 7, 9, 10] to protein
    * then generate a sequence CCTTGA with mapping [1, 6] to the same protein
    * residues
+   * <p>
+   * Typically eukaryotic dna will include exons encoding for a single peptide
+   * sequence i.e. return a single result. Bacterial dna may have overlapping
+   * exon mappings coding for multiple peptides so return multiple results
+   * (example EMBL KF591215).
    * 
    * @param dnaSeq
    *          a dna dataset sequence
    * @param mapping
-   *          the current mapping of the sequence to protein
+   *          containing one or more mappings of the sequence to protein
    * @param newMapping
-   *          the new mapping to populate, from the exon-only sequence
+   *          the new mapping to populate, from the exon-only sequences to their
+   *          mapped protein sequences
    * @return
    */
-  protected static SequenceI makeExonSequence(SequenceI dnaSeq,
-          AlignedCodonFrame acf, AlignedCodonFrame newMapping)
+  protected static List<SequenceI> makeExonSequences(SequenceI dnaSeq,
+          AlignedCodonFrame mapping, AlignedCodonFrame newMapping)
   {
-    Mapping mapping = acf.getMappingForSequence(dnaSeq);
+    List<SequenceI> exonSequences = new ArrayList<SequenceI>();
+    List<Mapping> seqMappings = mapping.getMappingsForSequence(dnaSeq);
     final char[] dna = dnaSeq.getSequence();
-    StringBuilder newSequence = new StringBuilder(dnaSeq.getLength());
-
-    /*
-     * Get the codon regions as { [2, 5], [7, 12], [14, 14] etc }
-     */
-    List<int[]> exonRanges = mapping.getMap().getFromRanges();
-    for (int[] range : exonRanges)
+    for (Mapping seqMapping : seqMappings)
     {
-      for (int pos = range[0]; pos <= range[1]; pos++)
+      StringBuilder newSequence = new StringBuilder(dnaSeq.getLength());
+
+      /*
+       * Get the codon regions as { [2, 5], [7, 12], [14, 14] etc }
+       */
+      final List<int[]> dnaExonRanges = seqMapping.getMap().getFromRanges();
+      for (int[] range : dnaExonRanges)
       {
-        newSequence.append(dna[pos - 1]);
+        for (int pos = range[0]; pos <= range[1]; pos++)
+        {
+          newSequence.append(dna[pos - 1]);
+        }
       }
-    }
 
-    SequenceI exon = new Sequence(dnaSeq.getName(), newSequence.toString());
+      SequenceI exon = new Sequence(dnaSeq.getName(),
+              newSequence.toString());
 
-    /*
-     * Locate any xrefs to CDS database on the protein product and attach to the
-     * CDS sequence. Also add as a sub-token of the sequence name.
-     */
-    // default to "CDS" if we can't locate an actual gene id
-    String cdsAccId = FeatureProperties.getCodingFeature(DBRefSource.EMBL);
-    DBRefEntry[] cdsRefs = DBRefUtils.selectRefs(
-            mapping.getTo().getDBRef(), DBRefSource.CODINGDBS);
-    if (cdsRefs != null)
-    {
-      for (DBRefEntry cdsRef : cdsRefs)
+      /*
+       * Locate any xrefs to CDS database on the protein product and attach to
+       * the CDS sequence. Also add as a sub-token of the sequence name.
+       */
+      // default to "CDS" if we can't locate an actual gene id
+      String cdsAccId = FeatureProperties
+              .getCodingFeature(DBRefSource.EMBL);
+      DBRefEntry[] cdsRefs = DBRefUtils.selectRefs(seqMapping.getTo()
+              .getDBRef(), DBRefSource.CODINGDBS);
+      if (cdsRefs != null)
       {
-        exon.addDBRef(new DBRefEntry(cdsRef));
-        cdsAccId = cdsRef.getAccessionId();
+        for (DBRefEntry cdsRef : cdsRefs)
+        {
+          exon.addDBRef(new DBRefEntry(cdsRef));
+          cdsAccId = cdsRef.getAccessionId();
+        }
       }
-    }
-    exon.setName(exon.getName() + "|" + cdsAccId);
-    exon.createDatasetSequence();
+      exon.setName(exon.getName() + "|" + cdsAccId);
+      exon.createDatasetSequence();
 
-    /*
-     * Build new mappings - from the same protein regions, but now to contiguous
-     * exons
-     */
-    List<int[]> exonRange = new ArrayList<int[]>();
-    exonRange.add(new int[]
-    { 1, newSequence.length() });
-    MapList map = new MapList(exonRange, mapping.getMap().getToRanges(), 3, 1);
-    newMapping.addMap(exon.getDatasetSequence(), mapping.getTo(), map);
+      /*
+       * Build new mappings - from the same protein regions, but now to
+       * contiguous exons
+       */
+      List<int[]> exonRange = new ArrayList<int[]>();
+      exonRange.add(new int[]
+      { 1, newSequence.length() });
+      MapList map = new MapList(exonRange, seqMapping.getMap()
+              .getToRanges(),
+              3, 1);
+      newMapping.addMap(exon.getDatasetSequence(), seqMapping.getTo(), map);
+      MapList cdsToDnaMap = new MapList(dnaExonRanges, exonRange, 1, 1);
+      newMapping.addMap(dnaSeq, exon.getDatasetSequence(), cdsToDnaMap);
 
-    return exon;
+      exonSequences.add(exon);
+    }
+    return exonSequences;
   }
 }
index 1f5d827..d0b2731 100644 (file)
@@ -20,6 +20,9 @@
  */
 package jalview.datamodel;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import jalview.util.MapList;
 import jalview.util.MappingUtils;
 
@@ -423,4 +426,37 @@ public class AlignedCodonFrame
     { dnaSeq[codonPos[0] - 1], dnaSeq[codonPos[1] - 1],
         dnaSeq[codonPos[2] - 1] };
   }
+
+  /**
+   * Returns any mappings found which are to (or from) the given sequence, and
+   * to distinct sequences.
+   * 
+   * @param seq
+   * @return
+   */
+  public List<Mapping> getMappingsForSequence(SequenceI seq)
+  {
+    List<Mapping> result = new ArrayList<Mapping>();
+    if (dnaSeqs == null)
+    {
+      return result;
+    }
+    List<SequenceI> related = new ArrayList<SequenceI>();
+    SequenceI seqDs = seq.getDatasetSequence();
+    seqDs = seqDs != null ? seqDs : seq;
+  
+    for (int ds = 0; ds < dnaSeqs.length; ds++)
+    {
+      final Mapping mapping = dnaToProt[ds];
+      if (dnaSeqs[ds] == seqDs || mapping.to == seqDs)
+      {
+        if (!related.contains(mapping.to))
+        {
+          result.add(mapping);
+          related.add(mapping.to);
+        }
+      }
+    }
+    return result;
+  }
 }
index db5bbe5..5f45dc1 100644 (file)
@@ -2981,9 +2981,13 @@ public class Desktop extends jalview.jbgui.GDesktop implements
        */
       AlignmentPanel topPanel = (AlignmentPanel) topPanels.get(i);
       AlignFrame newTopFrame = new AlignFrame(topPanel);
+      newTopFrame.setSize(new Dimension(AlignFrame.DEFAULT_WIDTH,
+              AlignFrame.DEFAULT_HEIGHT));
       newTopFrame.setVisible(true);
       AlignmentPanel bottomPanel = (AlignmentPanel) bottomPanels.get(i);
       AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
+      newBottomFrame.setSize(new Dimension(AlignFrame.DEFAULT_WIDTH,
+              AlignFrame.DEFAULT_HEIGHT));
       newBottomFrame.setVisible(true);
       topPanel.av.setGatherViewsHere(false);
       bottomPanel.av.setGatherViewsHere(false);
index 5e13781..381fbe3 100755 (executable)
@@ -94,8 +94,10 @@ public class FontChooser extends GFontChooser
      * Enable 'scale protein as cDNA' in a SplitFrame view. The selection is
      * stored in the ViewStyle of both dna and protein Viewport
      */
+    scaleAsCdna.setEnabled(false);
     if (ap.av.getCodingComplement() != null)
     {
+      scaleAsCdna.setEnabled(true);
       scaleAsCdna.setVisible(true);
       scaleAsCdna.setSelected(ap.av.isScaleProteinAsCdna());
     }
@@ -243,15 +245,19 @@ public class FontChooser extends GFontChooser
             .getStringBounds("I", getGraphics()).getWidth();
     if (mw < 1 || iw < 1)
     {
-      fontName.setSelectedItem(lastSelected.getName());
-      fontStyle.setSelectedIndex(lastSelStyle);
-      fontSize.setSelectedItem(lastSelSize);
-      monospaced.setSelected(lastSelMono);
       JOptionPane
               .showInternalMessageDialog(
                       this,
                       MessageManager.getString("label.font_doesnt_have_letters_defined"),
                       MessageManager.getString("label.invalid_font"), JOptionPane.WARNING_MESSAGE);
+      /*
+       * Restore previous values - size first to avoid recursive calls to this
+       * point!
+       */
+      fontSize.setSelectedItem(lastSelSize);
+      fontName.setSelectedItem(lastSelected.getName());
+      fontStyle.setSelectedIndex(lastSelStyle);
+      monospaced.setSelected(lastSelMono);
       return;
     }
     if (tp != null)
index 3a54cc1..5c86ce2 100644 (file)
@@ -27,6 +27,7 @@ import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.jbgui.GStructureChooser;
 import jalview.jbgui.PDBDocFieldPreferences;
+import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 import jalview.ws.dbsources.PDBRestClient;
 import jalview.ws.dbsources.PDBRestClient.PDBDocField;
@@ -38,13 +39,15 @@ import java.awt.event.ItemEvent;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.Hashtable;
 import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Vector;
 
 import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
+import javax.swing.table.DefaultTableModel;
+
 
 /**
  * Provides the behaviors for the Structure chooser Panel
@@ -73,6 +76,8 @@ public class StructureChooser extends GStructureChooser
 
   private boolean isValidPBDEntry;
 
+  private static Hashtable<String, PDBEntry> cachedEntryMap;
+
   public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
           AlignmentPanel ap)
   {
@@ -96,6 +101,7 @@ public class StructureChooser extends GStructureChooser
         long startTime = System.currentTimeMillis();
         String msg = MessageManager.getString("status.fetching_db_refs");
         updateProgressIndicator(msg, startTime);
+        loadLocalCachedPDBEntries();
         fetchStructuresMetaData();
         populateFilterComboBox();
         updateProgressIndicator(null, startTime);
@@ -168,6 +174,33 @@ public class StructureChooser extends GStructureChooser
             + " Found (" + totalTime + ")");
   }
 
+  public void loadLocalCachedPDBEntries()
+  {
+    DefaultTableModel tableModel = new DefaultTableModel();
+    tableModel.addColumn("Sequence");
+    tableModel.addColumn("PDB Id");
+    tableModel.addColumn("Type");
+    tableModel.addColumn("File");
+    cachedEntryMap = new Hashtable<String, PDBEntry>();
+    for (SequenceI seq : selectedSequences)
+    {
+      if (seq.getDatasetSequence() != null
+              && seq.getDatasetSequence().getPDBId() != null)
+      {
+        for (PDBEntry pdbEntry : seq.getDatasetSequence().getPDBId())
+        {
+          String[] pdbEntryRowData = new String[]
+          { seq.getDisplayId(false), pdbEntry.getId(), pdbEntry.getType(),
+              pdbEntry.getFile() };
+          tableModel.addRow(pdbEntryRowData);
+          cachedEntryMap.put(seq.getDisplayId(false) + pdbEntry.getId(),
+                  pdbEntry);
+        }
+      }
+    }
+    tbl_local_pdb.setModel(tableModel);
+  }
+
   /**
    * Update the DBRef entry for a given sequence with values retrieved from
    * PDBResponseSummary
@@ -196,7 +229,7 @@ public class StructureChooser extends GStructureChooser
    *          the sequences to build a query for
    * @return the built query string
    */
-  @SuppressWarnings("unchecked")
+
   public static String buildQuery(SequenceI seq)
   {
     String query = seq.getName();
@@ -205,7 +238,7 @@ public class StructureChooser extends GStructureChooser
 
     if (seq.getPDBId() != null)
     {
-      for (PDBEntry entry : (Vector<PDBEntry>) seq.getPDBId())
+      for (PDBEntry entry : seq.getPDBId())
       {
         queryBuilder.append("text:").append(entry.getId()).append(" OR ");
       }
@@ -353,6 +386,8 @@ public class StructureChooser extends GStructureChooser
             VIEWS_ENTER_ID));
     cmb_filterOption.addItem(new FilterOption("From File", "-",
             VIEWS_FROM_FILE));
+    cmb_filterOption.addItem(new FilterOption("Cached PDB Entries", "-",
+            VIEWS_LOCAL_PDB));
   }
 
   /**
@@ -373,7 +408,8 @@ public class StructureChooser extends GStructureChooser
       chk_invertFilter.setVisible(true);
       filterResultSet(selectedFilterOpt.getValue());
     }
-    else
+    else if (selectedFilterOpt.getView() == VIEWS_ENTER_ID
+            || selectedFilterOpt.getView() == VIEWS_FROM_FILE)
     {
       idInputAssSeqPanel.loadCmbAssSeq();
       fileChooserAssSeqPanel.loadCmbAssSeq();
@@ -398,6 +434,13 @@ public class StructureChooser extends GStructureChooser
         btn_view.setEnabled(true);
       }
     }
+    else if (currentView == VIEWS_LOCAL_PDB)
+    {
+      if (tbl_local_pdb.getSelectedRows().length > 0)
+      {
+        btn_view.setEnabled(true);
+      }
+    }
     else if (currentView == VIEWS_ENTER_ID)
     {
       validateAssociationEnterPdb();
@@ -513,35 +556,78 @@ public class StructureChooser extends GStructureChooser
         pdbEntry.setType("PDB");
         pdbEntriesToView[count++] = pdbEntry;
       }
-      new StructureViewer(ap.getStructureSelectionManager())
-              .viewStructures(ap, pdbEntriesToView,
-                      ap.av.collateForPDB(pdbEntriesToView));
+
+      launchStructureViewer(ap.getStructureSelectionManager(),
+              pdbEntriesToView, ap, selectedSequences);
+    }
+    else if(currentView == VIEWS_LOCAL_PDB){
+      int[] selectedRows = tbl_local_pdb.getSelectedRows();
+      PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
+      int count = 0;
+      for (int row : selectedRows)
+      {
+         String entryKey = tbl_local_pdb.getValueAt(row, 0).toString() + tbl_local_pdb.getValueAt(row, 1).toString();
+        pdbEntriesToView[count++] = cachedEntryMap.get(entryKey);
+      }
+      launchStructureViewer(ap.getStructureSelectionManager(),
+              pdbEntriesToView, ap, selectedSequences);
     }
     else if (currentView == VIEWS_ENTER_ID)
     {
-      selectedSequence = ((AssociateSeqOptions) idInputAssSeqPanel
+      SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
               .getCmb_assSeq().getSelectedItem()).getSequence();
+      if (userSelectedSeq != null)
+      {
+        selectedSequence = userSelectedSeq;
+      }
       PDBEntry pdbEntry = new PDBEntry();
       pdbEntry.setId(txt_search.getText());
       pdbEntry.setType("PDB");
       selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
       PDBEntry[] pdbEntriesToView = new PDBEntry[]
       { pdbEntry };
-      new StructureViewer(ap.getStructureSelectionManager())
-              .viewStructures(ap, pdbEntriesToView,
-                      ap.av.collateForPDB(pdbEntriesToView));
+      launchStructureViewer(ap.getStructureSelectionManager(),
+              pdbEntriesToView, ap, new SequenceI[]
+              { selectedSequence });
     }
     else if (currentView == VIEWS_FROM_FILE)
     {
-      selectedSequence = ((AssociateSeqOptions) fileChooserAssSeqPanel
+      SequenceI userSelectedSeq = ((AssociateSeqOptions) fileChooserAssSeqPanel
               .getCmb_assSeq().getSelectedItem()).getSequence();
-      new AssociatePdbFileWithSeq().associatePdbWithSeq(
+      if (userSelectedSeq != null)
+      {
+        selectedSequence = userSelectedSeq;
+      }
+      PDBEntry fileEntry = new AssociatePdbFileWithSeq()
+              .associatePdbWithSeq(
               selectedPdbFileName, jalview.io.AppletFormatAdapter.FILE,
               selectedSequence, true, Desktop.instance);
+
+      launchStructureViewer(ap.getStructureSelectionManager(),
+              new PDBEntry[]
+              { fileEntry }, ap, new SequenceI[]
+              { selectedSequence });
     }
     mainFrame.dispose();
   }
 
+  private void launchStructureViewer(StructureSelectionManager ssm,
+          PDBEntry[] pdbEntriesToView, AlignmentPanel alignPanel,
+          SequenceI[] selectedSequences)
+  {
+    StructureViewer sViewer = new StructureViewer(ssm);
+    if (pdbEntriesToView.length > 1)
+    {
+      sViewer.viewStructures(alignPanel, pdbEntriesToView,
+              alignPanel.av.collateForPDB(pdbEntriesToView));
+    }
+    else
+    {
+      sViewer.viewStructures(pdbEntriesToView[0], selectedSequences, null,
+              alignPanel);
+    }
+  }
+
   /**
    * Populates the combo-box used in associating manually fetched structures to
    * a unique sequence when more than one sequence selection is made.
index b79a7f7..fc035b6 100644 (file)
@@ -34,6 +34,8 @@ import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
 
 import javax.swing.ImageIcon;
 import javax.swing.JButton;
@@ -83,7 +85,7 @@ public abstract class GStructureChooser extends JPanel implements
 
   private JPanel pnl_actions = new JPanel();
 
-  private JPanel pnl_filter = new JPanel();
+  private JPanel pnl_main = new JPanel();
 
   private JPanel pnl_idInput = new JPanel(new FlowLayout());
 
@@ -93,6 +95,8 @@ public abstract class GStructureChooser extends JPanel implements
 
   private JPanel pnl_fileChooserBL = new JPanel(new BorderLayout());
 
+  private JPanel pnl_locPDB = new JPanel(new BorderLayout());
+
   protected JPanel pnl_switchableViews = new JPanel(new CardLayout());
 
   protected CardLayout layout_switchableViews = (CardLayout) (pnl_switchableViews
@@ -132,12 +136,18 @@ public abstract class GStructureChooser extends JPanel implements
 
   protected static final String VIEWS_ENTER_ID = "VIEWS_ENTER_ID";
 
+  protected static final String VIEWS_LOCAL_PDB = "VIEWS_LOCAL_PDB";
+
   protected JTable tbl_summary = new JTable();
 
   protected JScrollPane scrl_foundStructures = new JScrollPane(
           tbl_summary);
 
-  private JTabbedPane tabbedPane = new JTabbedPane();
+  protected JTable tbl_local_pdb = new JTable();
+
+  protected JScrollPane scrl_localPDB = new JScrollPane(tbl_local_pdb);
+
+  private JTabbedPane pnl_filter = new JTabbedPane();
 
   private PDBDocFieldPreferences pdbDocFieldPrefs = new PDBDocFieldPreferences(
           PreferenceSource.STRUCTURE_CHOOSER);
@@ -164,6 +174,15 @@ public abstract class GStructureChooser extends JPanel implements
   private void jbInit() throws Exception
   {
     tbl_summary.setAutoCreateRowSorter(true);
+    tbl_local_pdb.setAutoCreateRowSorter(true);
+    tbl_local_pdb.addMouseListener(new MouseAdapter()
+    {
+      public void mouseClicked(MouseEvent e)
+      {
+        updateCurrentView();
+      }
+    });
+
     btn_view.setFont(new java.awt.Font("Verdana", 0, 12));
     btn_view.setText(MessageManager.getString("action.view"));
     btn_view.addActionListener(new java.awt.event.ActionListener()
@@ -198,6 +217,10 @@ public abstract class GStructureChooser extends JPanel implements
     scrl_foundStructures
             .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
 
+    scrl_localPDB.setPreferredSize(new Dimension(500, 300));
+    scrl_localPDB
+            .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+
     cmb_filterOption.setFont(new java.awt.Font("Verdana", 0, 12));
     chk_invertFilter.setFont(new java.awt.Font("Verdana", 0, 12));
     chk_rememberSettings.setFont(new java.awt.Font("Verdana", 0, 12));
@@ -236,20 +259,19 @@ public abstract class GStructureChooser extends JPanel implements
     pnl_actions.add(btn_cancel);
 
     // pnl_filter.add(lbl_result);
-    pnl_filter.add(cmb_filterOption);
-    pnl_filter.add(lbl_loading);
-    pnl_filter.add(chk_invertFilter);
+    pnl_main.add(cmb_filterOption);
+    pnl_main.add(lbl_loading);
+    pnl_main.add(chk_invertFilter);
     lbl_loading.setVisible(false);
 
-    pnl_idInput.add(txt_search);
-    pnl_idInput.add(lbl_pdbManualFetchStatus);
 
     pnl_fileChooser.add(btn_pdbFromFile);
     pnl_fileChooser.add(lbl_fromFileStatus);
-
     pnl_fileChooserBL.add(fileChooserAssSeqPanel, BorderLayout.NORTH);
     pnl_fileChooserBL.add(pnl_fileChooser, BorderLayout.CENTER);
 
+    pnl_idInput.add(txt_search);
+    pnl_idInput.add(lbl_pdbManualFetchStatus);
     pnl_idInputBL.add(idInputAssSeqPanel, BorderLayout.NORTH);
     pnl_idInputBL.add(pnl_idInput, BorderLayout.CENTER);
     
@@ -270,20 +292,22 @@ public abstract class GStructureChooser extends JPanel implements
         }
       }
     };
-    tabbedPane.addChangeListener(changeListener);
-    tabbedPane.setPreferredSize(new Dimension(500, 300));
-    tabbedPane.add(foundStructureSummary, scrl_foundStructures);
-    tabbedPane.add(
+    pnl_filter.addChangeListener(changeListener);
+    pnl_filter.setPreferredSize(new Dimension(500, 300));
+    pnl_filter.add(foundStructureSummary, scrl_foundStructures);
+    pnl_filter.add(
             MessageManager.getString("label.configure_displayed_columns"),
             pdbDocFieldPrefs);
     
+    pnl_locPDB.add(scrl_localPDB);
 
     pnl_switchableViews.add(pnl_fileChooserBL, VIEWS_FROM_FILE);
     pnl_switchableViews.add(pnl_idInputBL, VIEWS_ENTER_ID);
-    pnl_switchableViews.add(tabbedPane, VIEWS_FILTER);
+    pnl_switchableViews.add(pnl_filter, VIEWS_FILTER);
+    pnl_switchableViews.add(pnl_locPDB, VIEWS_LOCAL_PDB);
     
     this.setLayout(mainLayout);
-    this.add(pnl_filter, java.awt.BorderLayout.NORTH);
+    this.add(pnl_main, java.awt.BorderLayout.NORTH);
     this.add(pnl_switchableViews, java.awt.BorderLayout.CENTER);
     this.add(pnl_actions, java.awt.BorderLayout.SOUTH);
 
index fd0c503..c5642a9 100644 (file)
@@ -31,7 +31,7 @@ import com.sun.jersey.api.json.JSONConfiguration;
  */
 public class PDBRestClient
 {
-  private static String PDB_SEARCH_ENDPOINT = "http://wwwdev.ebi.ac.uk/pdbe/search/pdb/select?";
+  private static String PDB_SEARCH_ENDPOINT = "http://www.ebi.ac.uk/pdbe/search/pdb/select?";
 
   private static int DEFAULT_RESPONSE_SIZE = 200;
 
index 98d77d4..9ef1b9a 100644 (file)
@@ -30,6 +30,8 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -974,7 +976,7 @@ public class AlignmentUtilsTests
    * x-ref to the EMBLCDS record.
    */
   @Test
-  public void testMakeExonSequence()
+  public void testMakeExonSequences()
   {
     SequenceI dna1 = new Sequence("dna1", "aaaGGGcccTTTaaa");
     SequenceI pep1 = new Sequence("pep1", "GF");
@@ -996,7 +998,10 @@ public class AlignmentUtilsTests
     mappings.add(acf);
 
     AlignedCodonFrame newMapping = new AlignedCodonFrame();
-    SequenceI exon = AlignmentUtils.makeExonSequence(dna1, acf, newMapping);
+    List<SequenceI> exons = AlignmentUtils.makeExonSequences(dna1, acf,
+            newMapping);
+    assertEquals(1, exons.size());
+    SequenceI exon = exons.get(0);
 
     assertEquals("GGGTTT", exon.getSequenceAsString());
     assertEquals("dna1|A12345", exon.getName());
@@ -1005,6 +1010,142 @@ public class AlignmentUtilsTests
     assertEquals("EMBLCDS", cdsRef.getSource());
     assertEquals("2", cdsRef.getVersion());
     assertEquals("A12345", cdsRef.getAccessionId());
+  }
+
+  /**
+   * Test the method that makes an exon-only alignment from a DNA sequence and
+   * its product mappings, for the case where there are multiple exon mappings
+   * to different protein products.
+   */
+  @Test
+  public void testMakeExonAlignment_multipleProteins()
+  {
+    SequenceI dna1 = new Sequence("dna1", "aaaGGGcccTTTaaa");
+    SequenceI pep1 = new Sequence("pep1", "GF"); // GGGTTT
+    SequenceI pep2 = new Sequence("pep2", "KP"); // aaaccc
+    SequenceI pep3 = new Sequence("pep3", "KF"); // aaaTTT
+    dna1.createDatasetSequence();
+    pep1.createDatasetSequence();
+    pep2.createDatasetSequence();
+    pep3.createDatasetSequence();
+    pep1.getDatasetSequence().addDBRef(
+            new DBRefEntry("EMBLCDS", "2", "A12345"));
+    pep2.getDatasetSequence().addDBRef(
+            new DBRefEntry("EMBLCDS", "3", "A12346"));
+    pep3.getDatasetSequence().addDBRef(
+            new DBRefEntry("EMBLCDS", "4", "A12347"));
 
+    /*
+     * Make the mappings from dna to protein. Using LinkedHashset is a
+     * convenience so results are in the input order. There is no assertion that
+     * the generated exon sequences are in any particular order.
+     */
+    Set<AlignedCodonFrame> mappings = new LinkedHashSet<AlignedCodonFrame>();
+    // map ...GGG...TTT to GF
+    MapList map = new MapList(new int[]
+    { 4, 6, 10, 12 }, new int[]
+    { 1, 2 }, 3, 1);
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+    acf.addMap(dna1.getDatasetSequence(), pep1.getDatasetSequence(), map);
+    mappings.add(acf);
+
+    // map aaa...ccc to KP
+    map = new MapList(new int[]
+    { 1, 3, 7, 9 }, new int[]
+    { 1, 2 }, 3, 1);
+    acf = new AlignedCodonFrame();
+    acf.addMap(dna1.getDatasetSequence(), pep2.getDatasetSequence(), map);
+    mappings.add(acf);
+
+    // map aaa......TTT to KF
+    map = new MapList(new int[]
+    { 1, 3, 10, 12 }, new int[]
+    { 1, 2 }, 3, 1);
+    acf = new AlignedCodonFrame();
+    acf.addMap(dna1.getDatasetSequence(), pep3.getDatasetSequence(), map);
+    mappings.add(acf);
+
+    /*
+     * Create the Exon alignment; also replaces the dna-to-protein mappings with
+     * exon-to-protein and exon-to-dna mappings
+     */
+    AlignmentI exal = AlignmentUtils.makeExonAlignment(new SequenceI[]
+    { dna1 }, mappings);
+
+    /*
+     * Verify we have 3 exon sequences, mapped to pep1/2/3 respectively
+     */
+    List<SequenceI> exons = exal.getSequences();
+    assertEquals(3, exons.size());
+
+    SequenceI exon = exons.get(0);
+    assertEquals("GGGTTT", exon.getSequenceAsString());
+    assertEquals("dna1|A12345", exon.getName());
+    assertEquals(1, exon.getDBRef().length);
+    DBRefEntry cdsRef = exon.getDBRef()[0];
+    assertEquals("EMBLCDS", cdsRef.getSource());
+    assertEquals("2", cdsRef.getVersion());
+    assertEquals("A12345", cdsRef.getAccessionId());
+
+    exon = exons.get(1);
+    assertEquals("aaaccc", exon.getSequenceAsString());
+    assertEquals("dna1|A12346", exon.getName());
+    assertEquals(1, exon.getDBRef().length);
+    cdsRef = exon.getDBRef()[0];
+    assertEquals("EMBLCDS", cdsRef.getSource());
+    assertEquals("3", cdsRef.getVersion());
+    assertEquals("A12346", cdsRef.getAccessionId());
+
+    exon = exons.get(2);
+    assertEquals("aaaTTT", exon.getSequenceAsString());
+    assertEquals("dna1|A12347", exon.getName());
+    assertEquals(1, exon.getDBRef().length);
+    cdsRef = exon.getDBRef()[0];
+    assertEquals("EMBLCDS", cdsRef.getSource());
+    assertEquals("4", cdsRef.getVersion());
+    assertEquals("A12347", cdsRef.getAccessionId());
+
+    /*
+     * Verify there are mappings from each exon sequence to its protein product
+     * and also to its dna source
+     */
+    Iterator<AlignedCodonFrame> newMappingsIterator = mappings.iterator();
+
+    // mappings for dna1 - exon1 - pep1
+    AlignedCodonFrame exonMapping = newMappingsIterator.next();
+    List<Mapping> dnaMappings = exonMapping.getMappingsForSequence(dna1);
+    assertEquals(1, dnaMappings.size());
+    assertSame(exons.get(0).getDatasetSequence(), dnaMappings.get(0)
+            .getTo());
+    assertEquals("G(1) in CDS should map to G(4) in DNA", 4, dnaMappings
+            .get(0).getMap().getToPosition(1));
+    List<Mapping> peptideMappings = exonMapping
+            .getMappingsForSequence(pep1);
+    assertEquals(1, peptideMappings.size());
+    assertSame(pep1.getDatasetSequence(), peptideMappings.get(0).getTo());
+
+    // mappings for dna1 - exon2 - pep2
+    exonMapping = newMappingsIterator.next();
+    dnaMappings = exonMapping.getMappingsForSequence(dna1);
+    assertEquals(1, dnaMappings.size());
+    assertSame(exons.get(1).getDatasetSequence(), dnaMappings.get(0)
+            .getTo());
+    assertEquals("c(4) in CDS should map to c(7) in DNA", 7, dnaMappings
+            .get(0).getMap().getToPosition(4));
+    peptideMappings = exonMapping.getMappingsForSequence(pep2);
+    assertEquals(1, peptideMappings.size());
+    assertSame(pep2.getDatasetSequence(), peptideMappings.get(0).getTo());
+
+    // mappings for dna1 - exon3 - pep3
+    exonMapping = newMappingsIterator.next();
+    dnaMappings = exonMapping.getMappingsForSequence(dna1);
+    assertEquals(1, dnaMappings.size());
+    assertSame(exons.get(2).getDatasetSequence(), dnaMappings.get(0)
+            .getTo());
+    assertEquals("T(4) in CDS should map to T(10) in DNA", 10, dnaMappings
+            .get(0).getMap().getToPosition(4));
+    peptideMappings = exonMapping.getMappingsForSequence(pep3);
+    assertEquals(1, peptideMappings.size());
+    assertSame(pep3.getDatasetSequence(), peptideMappings.get(0).getTo());
   }
 }
diff --git a/test/jalview/gui/FontChooserTest.java b/test/jalview/gui/FontChooserTest.java
new file mode 100644 (file)
index 0000000..d724fac
--- /dev/null
@@ -0,0 +1,54 @@
+package jalview.gui;
+
+import java.awt.Canvas;
+import java.awt.Font;
+import java.awt.FontMetrics;
+
+import org.junit.Test;
+
+public class FontChooserTest
+{
+
+  /**
+   * Not a real test as it runs no methods on FontChooser and makes no
+   * assertions, but this method writes to sysout the names of any (currently
+   * available, plain) fonts and point sizes that would be rejected by Jalview's
+   * FontChooser as having an I-width of less than 1.0.
+   */
+  @Test
+  public void dumpInvalidFonts()
+  {
+    String[] fonts = java.awt.GraphicsEnvironment
+            .getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
+    for (int pointSize = 1;; pointSize++)
+    {
+      System.out.println(System.lineSeparator()
+              + "Fonts with insufficient width at " + pointSize + "pt:");
+      if (pointSize == 1)
+      {
+        System.out.println("All except:");
+      }
+      int badCount = 0;
+      for (String fontname : fonts)
+      {
+        Font newFont = new Font(fontname, Font.PLAIN, pointSize);
+        FontMetrics fontm = new Canvas().getFontMetrics(newFont);
+        double iw = fontm.getStringBounds("I", null).getWidth();
+        final boolean tooSmall = iw < 1d;
+        if (tooSmall)
+        {
+          badCount++;
+        }
+        if ((pointSize > 1 && tooSmall) || (pointSize == 1 && !tooSmall))
+        {
+          System.out.println(fontname);
+        }
+      }
+      if (badCount == 0)
+      {
+        break;
+      }
+    }
+  }
+
+}