JAL-2738 update spike branch with latest
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 24 Oct 2017 12:50:46 +0000 (13:50 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 24 Oct 2017 12:50:46 +0000 (13:50 +0100)
90 files changed:
.checkstyle
examples/groovy/PIDmatrix.groovy [new file with mode: 0644]
help/html/releases.html
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraResidue.java
src/jalview/api/AlignViewportI.java
src/jalview/api/AlignmentViewPanel.java
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AnnotationColourChooser.java
src/jalview/appletgui/AnnotationColumnChooser.java
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/AnnotationRowFilter.java
src/jalview/appletgui/FeatureRenderer.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/appletgui/FontChooser.java
src/jalview/appletgui/IdPanel.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/RedundancyPanel.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqPanel.java
src/jalview/appletgui/SliderPanel.java
src/jalview/appletgui/SplitFrame.java
src/jalview/appletgui/UserDefinedColours.java
src/jalview/controller/AlignViewController.java
src/jalview/datamodel/CigarArray.java
src/jalview/datamodel/Sequence.java
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/ext/ensembl/EnsemblGenomes.java
src/jalview/ext/ensembl/EnsemblLookup.java
src/jalview/ext/ensembl/EnsemblProtein.java
src/jalview/ext/ensembl/EnsemblRestClient.java
src/jalview/ext/ensembl/EnsemblSymbol.java
src/jalview/ext/ensembl/EnsemblXref.java
src/jalview/ext/ensembl/Species.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/rbvi/chimera/AtomSpecModel.java
src/jalview/fts/service/pdb/PDBFTSPanel.java
src/jalview/fts/service/uniprot/UniprotFTSPanel.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationChooser.java
src/jalview/gui/AnnotationColourChooser.java
src/jalview/gui/AnnotationColumnChooser.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/AnnotationRowFilter.java
src/jalview/gui/FeatureColourChooser.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/FontChooser.java
src/jalview/gui/IdPanel.java
src/jalview/gui/IdwidthAdjuster.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/RedundancyPanel.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SliderPanel.java
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/TextColourChooser.java
src/jalview/gui/TreePanel.java
src/jalview/gui/UserDefinedColours.java
src/jalview/io/WSWUBlastClient.java
src/jalview/renderer/ScaleRenderer.java
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/schemes/RNAHelicesColourChooser.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/workers/AnnotationWorker.java
src/jalview/workers/ColumnCounterSetWorker.java
src/jalview/workers/ConsensusThread.java
src/jalview/workers/ConservationThread.java
src/jalview/workers/StrucConsensusThread.java
src/jalview/ws/DasSequenceFeatureFetcher.java
src/jalview/ws/jws2/AADisorderClient.java
src/jalview/ws/jws2/AbstractJabaCalcWorker.java
test/jalview/datamodel/CigarArrayTest.java [new file with mode: 0644]
test/jalview/datamodel/SequenceTest.java
test/jalview/ext/ensembl/EnsemblGeneTest.java
test/jalview/ext/ensembl/EnsemblRestClientTest.java
test/jalview/ext/ensembl/EnsemblSeqProxyTest.java
test/jalview/ext/ensembl/SpeciesTest.java [new file with mode: 0644]
test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java
test/jalview/gui/AlignmentPanelTest.java
test/jalview/gui/SeqCanvasTest.java [new file with mode: 0644]
test/jalview/renderer/ScaleRendererTest.java
test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java
utils/checkstyle/checkstyle-suppress.xml
utils/checkstyle/import-control.xml

index 0329bb7..a87fa04 100644 (file)
@@ -8,5 +8,4 @@
     <file-match-pattern match-pattern="src/.*.java" include-pattern="true"/>
     <file-match-pattern match-pattern="resources/.*.properties" include-pattern="true"/>
   </fileset>
-  <filter name="NonSrcDirs" enabled="false"/>
 </fileset-config>
diff --git a/examples/groovy/PIDmatrix.groovy b/examples/groovy/PIDmatrix.groovy
new file mode 100644 (file)
index 0000000..b97abcc
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+import jalview.analysis.scoremodels.ScoreModels
+import jalview.analysis.scoremodels.SimilarityParams
+
+// generate matrix for current selection using standard Jalview PID
+
+printSimilarityMatrix(true,true,SimilarityParams.Jalview)
+
+/** 
+ * this function prints a sequence similarity matrix in PHYLIP format. 
+ * printSimilarityMatrix(selected-only, include-ids, pidMethod)
+ * 
+ * Allowed values for pidMethod:
+ * 
+ * Jalview's Comparison.PID method includes matching gaps 
+ * and counts over the length of the shorter gapped sequence
+ * SimilarityParams.Jalview;
+ *
+ * 'SeqSpace' mode PCA calculation does not count matching 
+ * gaps but uses longest gapped sequence length
+ *  SimilarityParams.SeqSpace;
+ *
+ * PID calcs from the Raghava-Barton paper
+ * SimilarityParams.PID1: ignores gap-gap, does not score gap-residue,
+ * includes gap-residue in lengths, matches on longer of two sequences.
+ * 
+ * SimilarityParams.PID2: ignores gap-gap,ignores gap-residue, 
+ * matches on longer of two sequences
+ * 
+ * SimilarityParams.PID3: ignores gap-gap,ignores gap-residue, 
+ * matches on shorter of sequences only
+ * 
+ * SimilarityParams.PID4: ignores gap-gap,does not score gap-residue,
+ * includes gap-residue in lengths,matches on shorter of sequences only.
+ */
+
+void printSimilarityMatrix(boolean selview=false, boolean includeids=true, SimilarityParams pidMethod) {
+
+  def currentAlignFrame = jalview.bin.Jalview.getCurrentAlignFrame()
+
+  jalview.gui.AlignViewport av = currentAlignFrame.getCurrentView()
+
+  jalview.datamodel.AlignmentView seqStrings = av.getAlignmentView(selview)
+
+  if (!selview || av.getSelectionGroup()==null) {
+    start = 0
+    end = av.getAlignment().getWidth()
+    seqs = av.getAlignment().getSequencesArray()
+  } else {
+    start = av.getSelectionGroup().getStartRes()
+    end = av.getSelectionGroup().getEndRes() + 1
+    seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment())
+  }
+
+  distanceCalc = ScoreModels.getInstance().getScoreModel("PID",
+      (jalview.api.AlignmentViewPanel) currentAlignFrame.alignPanel)
+
+  def distance=distanceCalc.findSimilarities(
+      seqStrings.getSequenceStrings(jalview.util.Comparison.GAP_DASH),pidMethod)
+
+  // output the PHYLIP Matrix
+
+  print distance.width()+" "+distance.height()+"\n"
+
+  p = 0
+
+  for (v in 1..distance.height()) {
+
+    if (includeids) {
+      print seqs[p++].getDisplayId(false)+" "
+    }
+
+    for (r in 1..distance.width()) {
+      print distance.getValue(v-1,r-1)+" "
+    }
+
+    print "\n"
+  }
+}
\ No newline at end of file
index 8e55ca1..cb2b278 100755 (executable)
@@ -81,13 +81,29 @@ li:before {
               <!-- JAL-2446 -->Faster and more efficient management and
               rendering of sequence features
             </li>
+            <li>
+              <!-- JAL 2523-->More reliable Ensembl fetching with HTTP
+              429 rate limit request hander
+            </li>
+            <li>
+              <!-- JAL-2773 -->Structure views don't get updated unless
+              their colours have changed
+            </li>
           </ul>
+          <ul><li>Example groovy script for generating a matrix of percent identity scores for current alignment.</li></ul>
       </td>
       <td><div align="left">
           <em></em>
           <ul>
+            <li><!-- JAL-2777 -->Structures with whitespace chainCode cannot be viewed in Chimera</li>
             <li><!-- JAL-2728 -->Protein annotation panel too high in CDS/Protein view
             </li> 
+            <li><!-- JAL-2757 -->Can't edit the query after the server error warning icon is shown in Uniprot and PDB Free Text Search Dialogs
+            </li> 
+            <li><!-- JAL-2253 -->Slow EnsemblGenome ID lookup</li>
+            <li><!-- JAL-2682 -->Race condition when parsing sequence ID strings in parallel</li>
+            <li><!-- JAL-2739 -->Hidden column marker in last column not rendered when switching back from Wrapped to normal view</li> 
+            <li><!-- JAL-2768 -->Annotation display corrupted when scrolling right in unwapped alignment view</li> 
           </ul>
       </td>
     </tr>
index 0045e97..3abbe75 100644 (file)
@@ -383,6 +383,8 @@ public class ChimeraResidue implements ChimeraStructuralObject,
   public void splitInsertionCode(String residue)
   {
     // OK, split the index into number and insertion code
+    // JBPNote - m.matches() can be true even if there is no resnum - this can
+    // cause NumberFormatExceptions below
     Pattern p = Pattern.compile("(\\d*)([A-Z]?)");
     Matcher m = p.matcher(residue);
     if (m.matches())
index 9e6d1c0..3cb06c1 100644 (file)
@@ -24,7 +24,6 @@ import jalview.analysis.Conservation;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
-import jalview.datamodel.CigarArray;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.ProfilesI;
 import jalview.datamodel.SearchResultsI;
@@ -243,16 +242,6 @@ public interface AlignViewportI extends ViewStyleI
   void clearSequenceColours();
 
   /**
-   * This method returns the visible alignment as text, as seen on the GUI, ie
-   * if columns are hidden they will not be returned in the result. Use this for
-   * calculating trees, PCA, redundancy etc on views which contain hidden
-   * columns.
-   * 
-   * @return String[]
-   */
-  CigarArray getViewAsCigars(boolean selectedRegionOnly);
-
-  /**
    * return a compact representation of the current alignment selection to pass
    * to an analysis function
    * 
@@ -486,6 +475,7 @@ public interface AlignViewportI extends ViewStyleI
    * 
    * @return
    */
+  @Override
   boolean isProteinFontAsCdna();
 
   /**
@@ -493,5 +483,6 @@ public interface AlignViewportI extends ViewStyleI
    * 
    * @return
    */
+  @Override
   void setProteinFontAsCdna(boolean b);
 }
index ef59996..0b1ca21 100644 (file)
@@ -43,9 +43,10 @@ public interface AlignmentViewPanel extends OOMHandlerI
    * 
    * @param updateOverview
    *          - if true, the overview panel will also be updated and repainted
+   * @param updateStructures
+   *          - if true then any linked structure views will also be updated
    */
-
-  void paintAlignment(boolean updateOverview);
+  void paintAlignment(boolean updateOverview, boolean updateStructures);
 
   /**
    * automatically adjust annotation panel height for new annotation whilst
index 9d44479..46bd4fd 100644 (file)
@@ -383,7 +383,7 @@ public class APopupMenu extends java.awt.PopupMenu
   void addFeatureLinks(final SequenceI seq, List<String> links)
   {
     Menu linkMenu = new Menu(MessageManager.getString("action.link"));
-    Map<String, List<String>> linkset = new LinkedHashMap<String, List<String>>();
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
 
     for (String link : links)
     {
@@ -485,8 +485,8 @@ public class APopupMenu extends java.awt.PopupMenu
      * Temporary store to hold distinct calcId / type pairs for the tooltip.
      * Using TreeMap means calcIds are shown in alphabetical order.
      */
-    SortedMap<String, String> tipEntries = new TreeMap<String, String>();
-    final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<SequenceI, List<AlignmentAnnotation>>();
+    SortedMap<String, String> tipEntries = new TreeMap<>();
+    final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<>();
     AlignmentI al = this.ap.av.getAlignment();
     AlignmentUtils.findAddableReferenceAnnotations(forSequences, tipEntries,
             candidates, al);
@@ -825,8 +825,8 @@ public class APopupMenu extends java.awt.PopupMenu
       }
 
       int gSize = sg.getSize();
-      List<SequenceI> seqs = new ArrayList<SequenceI>();
-      List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+      List<SequenceI> seqs = new ArrayList<>();
+      List<SequenceFeature> features = new ArrayList<>();
 
       for (int i = 0; i < gSize; i++)
       {
@@ -930,7 +930,7 @@ public class APopupMenu extends java.awt.PopupMenu
     {
       seq.setName(dialog.getName());
       seq.setDescription(dialog.getDescription());
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
     }
   }
 
@@ -1163,7 +1163,7 @@ public class APopupMenu extends java.awt.PopupMenu
 
   void refresh()
   {
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 
   protected void clustalColour_actionPerformed()
@@ -1339,7 +1339,7 @@ public class APopupMenu extends java.awt.PopupMenu
     SequenceGroup sg = ap.av.getSelectionGroup();
     ap.av.getAlignment().deleteGroup(sg);
     ap.av.setSelectionGroup(null);
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 
   void createGroupMenuItem_actionPerformed()
@@ -1440,8 +1440,8 @@ public class APopupMenu extends java.awt.PopupMenu
      * the insertion order, which is the order of the annotations on the
      * alignment.
      */
-    Map<String, List<List<String>>> shownTypes = new LinkedHashMap<String, List<List<String>>>();
-    Map<String, List<List<String>>> hiddenTypes = new LinkedHashMap<String, List<List<String>>>();
+    Map<String, List<List<String>>> shownTypes = new LinkedHashMap<>();
+    Map<String, List<List<String>>> hiddenTypes = new LinkedHashMap<>();
     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
             AlignmentAnnotationUtils.asList(annotations), forSequences);
 
index ed04a0a..676d3cf 100644 (file)
@@ -343,7 +343,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     createAlignFrameWindow(embedded);
     validate();
     alignPanel.adjustAnnotationHeight();
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, true);
   }
 
   public AlignViewport getAlignViewport()
@@ -415,7 +415,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       {
         viewport.featureSettings.refreshTable();
       }
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
       statusBar.setText(MessageManager
               .getString("label.successfully_added_features_alignment"));
     }
@@ -691,7 +691,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       break;
 
     }
-    alignPanel.paintAlignment(true);
+    // TODO: repaint flags set only if the keystroke warrants it
+    alignPanel.paintAlignment(true, true);
   }
 
   /**
@@ -917,7 +918,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       applyAutoAnnotationSettings_actionPerformed();
     }
-    alignPanel.paintAlignment(true);
+    // TODO: repaint flags set only if warranted
+    alignPanel.paintAlignment(true, true);
   }
 
   /**
@@ -1094,7 +1096,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     else if (source == invertColSel)
     {
       viewport.invertColumnSelection();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(false, false);
       viewport.sendSelection();
     }
     else if (source == remove2LeftMenuItem)
@@ -1128,34 +1130,34 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     else if (source == showColumns)
     {
       viewport.showAllHiddenColumns();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
       viewport.sendSelection();
     }
     else if (source == showSeqs)
     {
       viewport.showAllHiddenSeqs();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
       // uncomment if we want to slave sequence selections in split frame
       // viewport.sendSelection();
     }
     else if (source == hideColumns)
     {
       viewport.hideSelectedColumns();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
       viewport.sendSelection();
     }
     else if (source == hideSequences
             && viewport.getSelectionGroup() != null)
     {
       viewport.hideAllSelectedSeqs();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
       // uncomment if we want to slave sequence selections in split frame
       // viewport.sendSelection();
     }
     else if (source == hideAllButSelection)
     {
       toggleHiddenRegions(false, false);
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
       viewport.sendSelection();
     }
     else if (source == hideAllSelection)
@@ -1164,14 +1166,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       viewport.expandColSelection(sg, false);
       viewport.hideAllSelectedSeqs();
       viewport.hideSelectedColumns();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
       viewport.sendSelection();
     }
     else if (source == showAllHidden)
     {
       viewport.showAllHiddenColumns();
       viewport.showAllHiddenSeqs();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
       viewport.sendSelection();
     }
     else if (source == showGroupConsensus)
@@ -1781,7 +1783,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     }
     viewport.getAlignment().moveSelectedSequencesByOne(sg,
             up ? null : viewport.getHiddenRepSequences(), up);
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
 
     /*
      * Also move cDNA/protein complement sequences
@@ -1793,7 +1795,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
               viewport, complement);
       complement.getAlignment().moveSelectedSequencesByOne(mappedSelection,
               up ? null : complement.getHiddenRepSequences(), up);
-      getSplitFrame().getComplement(this).alignPanel.paintAlignment(true);
+      getSplitFrame().getComplement(this).alignPanel.paintAlignment(true,
+              false);
     }
   }
 
@@ -2226,7 +2229,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
       alignPanel.updateAnnotation();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
     }
   }
 
@@ -2263,7 +2266,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     // JAL-2034 - should delegate to
     // alignPanel to decide if overview needs
     // updating.
-    alignPanel.paintAlignment(false);
+    alignPanel.paintAlignment(false, false);
     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
     viewport.sendSelection();
   }
@@ -2283,7 +2286,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     // JAL-2034 - should delegate to
     // alignPanel to decide if overview needs
     // updating.
-    alignPanel.paintAlignment(false);
+    alignPanel.paintAlignment(false, false);
     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
     viewport.sendSelection();
   }
@@ -2303,7 +2306,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
   public void invertColSel_actionPerformed()
   {
     viewport.invertColumnSelection();
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
     viewport.sendSelection();
   }
@@ -2595,19 +2598,19 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
   {
     viewport.setShowJVSuffix(seqLimits.getState());
     alignPanel.fontChanged();
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   protected void colourTextMenuItem_actionPerformed()
   {
     viewport.setColourText(colourTextMenuItem.getState());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   protected void displayNonconservedMenuItem_actionPerformed()
   {
     viewport.setShowUnconserved(displayNonconservedMenuItem.getState());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   protected void wrapMenuItem_actionPerformed()
@@ -2617,7 +2620,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     scaleAbove.setEnabled(wrapMenuItem.getState());
     scaleLeft.setEnabled(wrapMenuItem.getState());
     scaleRight.setEnabled(wrapMenuItem.getState());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   public void overviewMenuItem_actionPerformed()
@@ -2660,7 +2663,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
   {
     viewport.setGlobalColourScheme(cs);
 
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, true);
   }
 
   protected void modifyPID_actionPerformed()
@@ -2735,7 +2738,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     addHistoryItem(new OrderCommand("Pairwise Sort", oldOrder,
             viewport.getAlignment()));
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   public void sortIDMenuItem_actionPerformed()
@@ -2744,7 +2747,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     AlignmentSorter.sortByID(viewport.getAlignment());
     addHistoryItem(
             new OrderCommand("ID Sort", oldOrder, viewport.getAlignment()));
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   public void sortLengthMenuItem_actionPerformed()
@@ -2753,7 +2756,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     AlignmentSorter.sortByLength(viewport.getAlignment());
     addHistoryItem(new OrderCommand("Length Sort", oldOrder,
             viewport.getAlignment()));
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   public void sortGroupMenuItem_actionPerformed()
@@ -2762,7 +2765,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     AlignmentSorter.sortByGroup(viewport.getAlignment());
     addHistoryItem(new OrderCommand("Group Sort", oldOrder,
             viewport.getAlignment()));
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
 
   }
 
@@ -2802,7 +2805,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
           current.insertCharAt(Width - 1, viewport.getGapCharacter());
         }
       }
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(false, false);
     }
 
     if ((viewport.getSelectionGroup() != null
@@ -2866,7 +2869,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
           current.insertCharAt(Width - 1, viewport.getGapCharacter());
         }
       }
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(false, false);
 
     }
 
@@ -2919,7 +2922,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     addHistoryItem(new OrderCommand(MessageManager
             .formatMessage("label.order_by_params", new String[]
             { title }), oldOrder, viewport.getAlignment()));
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   /**
@@ -2977,7 +2980,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       addHistoryItem(new OrderCommand(undoname, oldOrder,
               viewport.getAlignment()));
     }
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
     return true;
   }
 
index 8e333ba..906acc1 100644 (file)
@@ -530,7 +530,7 @@ public class AlignmentPanel extends Panel
       vpRanges.scrollToWrappedVisible(start);
     }
 
-    paintAlignment(redrawOverview);
+    paintAlignment(redrawOverview, false);
     return true;
   }
 
@@ -579,7 +579,7 @@ public class AlignmentPanel extends Panel
     apvscroll.addNotify();
     hscroll.addNotify();
     validate();
-    paintAlignment(true);
+    paintAlignment(true, false);
   }
 
   /**
@@ -930,7 +930,8 @@ public class AlignmentPanel extends Panel
    * Repaint the alignment and annotations, and, optionally, any overview window
    */
   @Override
-  public void paintAlignment(boolean updateOverview)
+  public void paintAlignment(boolean updateOverview,
+          boolean updateStructures)
   {
     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
             av.isShowAutocalculatedAbove());
@@ -938,13 +939,14 @@ public class AlignmentPanel extends Panel
             av.getSortAnnotationsBy());
     repaint();
 
-    if (updateOverview)
+    if (updateStructures)
     {
-      // TODO: determine if this paintAlignment changed structure colours
       jalview.structure.StructureSelectionManager
               .getStructureSelectionManager(av.applet)
               .sequenceColoursChanged(this);
-
+    }
+    if (updateOverview)
+    {
       if (overviewPanel != null)
       {
         overviewPanel.updateOverviewImage();
index 8de751a..533226e 100644 (file)
@@ -85,7 +85,7 @@ public class AnnotationColourChooser extends Panel implements
     oldcs = av.getGlobalColourScheme();
     if (av.getAlignment().getGroups() != null)
     {
-      oldgroupColours = new HashMap<SequenceGroup, ColourSchemeI>();
+      oldgroupColours = new HashMap<>();
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
         oldgroupColours.put(sg, sg.getColourScheme());
@@ -180,8 +180,8 @@ public class AnnotationColourChooser extends Panel implements
     // TODO remove duplication with gui.AnnotationRowFilter
     // TODO add 'per sequence only' option / parameter
 
-    annotationLabels = new HashMap<AlignmentAnnotation, String>();
-    Vector<String> list = new Vector<String>();
+    annotationLabels = new HashMap<>();
+    Vector<String> list = new Vector<>();
     AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
     if (anns == null)
     {
@@ -376,7 +376,7 @@ public class AnnotationColourChooser extends Panel implements
     else if (evt.getSource() == cancel)
     {
       reset();
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, true);
       frame.setVisible(false);
     }
 
@@ -417,7 +417,7 @@ public class AnnotationColourChooser extends Panel implements
       }
 
       currentAnnotation.threshold.value = slider.getValue() / 1000f;
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
     }
   }
 
@@ -559,7 +559,7 @@ public class AnnotationColourChooser extends Panel implements
 
     // update colours in linked windows
     ap.alignmentChanged();
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 
   void reset()
@@ -572,7 +572,7 @@ public class AnnotationColourChooser extends Panel implements
         sg.setColourScheme(oldgroupColours.get(sg));
       }
     }
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 
   @Override
@@ -588,7 +588,7 @@ public class AnnotationColourChooser extends Panel implements
   @Override
   public void mouseReleased(MouseEvent evt)
   {
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 
   @Override
index b4c1d54..7674de7 100644 (file)
@@ -306,7 +306,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
         av.getAlignment().setHiddenColumns(oldHidden);
       }
       av.sendSelection();
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, true);
     }
 
   }
@@ -348,7 +348,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
           sliderDragging = false;
           valueChanged(true);
         }
-        ap.paintAlignment(true);
+        ap.paintAlignment(true, true);
       }
     });
   }
@@ -359,8 +359,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     if (slider.isEnabled())
     {
       getCurrentAnnotation().threshold.value = slider.getValue() / 1000f;
-      updateView();
-      ap.paintAlignment(false);
+      updateView(); // this also calls paintAlignment(true,true)
     }
   }
 
@@ -515,7 +514,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     filterParams = null;
     av.setAnnotationColumnSelectionState(this);
     av.sendSelection();
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 
   public HiddenColumns getOldHiddenColumns()
index 2fb737a..d8f65a5 100755 (executable)
@@ -226,7 +226,8 @@ public class AnnotationLabels extends Panel
     ap.annotationPanel.adjustPanelHeight();
     setSize(getSize().width, ap.annotationPanel.getSize().height);
     ap.validate();
-    ap.paintAlignment(true);
+    // TODO: only paint if we needed to
+    ap.paintAlignment(true, true);
   }
 
   boolean editLabelDescription(AlignmentAnnotation annotation)
@@ -548,7 +549,7 @@ public class AnnotationLabels extends Panel
                 {
                   ap.av.setIgnoreGapsConsensus(cbmi.getState(), ap);
                 }
-                ap.paintAlignment(true);
+                ap.paintAlignment(true, true);
               }
             });
             popup.add(cbmi);
@@ -756,7 +757,7 @@ public class AnnotationLabels extends Panel
                 }
               }
             }
-            ap.paintAlignment(false);
+            ap.paintAlignment(false, false);
             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
             ap.av.sendSelection();
           }
@@ -813,7 +814,7 @@ public class AnnotationLabels extends Panel
               sg.addSequence(aa[selectedRow].sequenceRef, false);
             }
             ap.av.setSelectionGroup(sg);
-            ap.paintAlignment(false);
+            ap.paintAlignment(false, false);
             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
             ap.av.sendSelection();
           }
index 6fe71de..c06f7b1 100755 (executable)
@@ -439,7 +439,8 @@ public class AnnotationPanel extends Panel
       graphStretchY = evt.getY();
       av.calcPanelHeight();
       needValidating = true;
-      ap.paintAlignment(true);
+      // TODO: only update overview visible geometry
+      ap.paintAlignment(true, false);
     }
     else
     {
index 5efd177..c96dbab 100644 (file)
@@ -114,7 +114,7 @@ public abstract class AnnotationRowFilter extends Panel
   public void cancel_actionPerformed(ActionEvent e)
   {
     reset();
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
     frame.setVisible(false);
   }
 
index b35c079..df407d6 100644 (file)
@@ -493,7 +493,7 @@ public class FeatureRenderer
     }
     // findAllFeatures();
 
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
 
     return true;
   }
index 20d4d74..9a67499 100755 (executable)
@@ -377,8 +377,8 @@ public class FeatureSettings extends Panel
   // Group selection states
   void resetTable(boolean groupsChanged)
   {
-    List<String> displayableTypes = new ArrayList<String>();
-    Set<String> foundGroups = new HashSet<String>();
+    List<String> displayableTypes = new ArrayList<>();
+    Set<String> foundGroups = new HashSet<>();
 
     AlignmentI alignment = av.getAlignment();
 
@@ -391,7 +391,7 @@ public class FeatureSettings extends Panel
        * and keep track of which groups are visible
        */
       Set<String> groups = seq.getFeatures().getFeatureGroups(true);
-      Set<String> visibleGroups = new HashSet<String>();
+      Set<String> visibleGroups = new HashSet<>();
       for (String group : groups)
       {
         // if (group == null || fr.checkGroupVisibility(group, true))
@@ -600,7 +600,7 @@ public class FeatureSettings extends Panel
 
     fr.setFeaturePriority(data);
 
-    ap.paintAlignment(updateOverview);
+    ap.paintAlignment(updateOverview, updateOverview);
   }
 
   MyCheckbox selectedCheck;
@@ -680,7 +680,7 @@ public class FeatureSettings extends Panel
   {
     featurePanel.removeAll();
     resetTable(false);
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 
   @Override
@@ -732,7 +732,7 @@ public class FeatureSettings extends Panel
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
     fr.setTransparency((100 - transparency.getValue()) / 100f);
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 
   class MyCheckbox extends Checkbox
index c9a92b2..443ebce 100644 (file)
@@ -261,7 +261,7 @@ public class FontChooser extends Panel implements ItemListener
       {
         ap.av.setCharWidth(oldCharWidth);
       }
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, false);
     }
     else if (tp != null)
     {
index 7d9d278..15e269c 100755 (executable)
@@ -70,7 +70,7 @@ public class IdPanel extends Panel
     // TODO: add in group link parameter
 
     // make a list of label,url pairs
-    HashMap<String, String> urlList = new HashMap<String, String>();
+    HashMap<String, String> urlList = new HashMap<>();
     if (viewport.applet != null)
     {
       for (int i = 1; i < 10; i++)
@@ -198,7 +198,7 @@ public class IdPanel extends Panel
     }
 
     lastid = seq;
-    alignPanel.paintAlignment(false);
+    alignPanel.paintAlignment(false, false);
   }
 
   @Override
@@ -295,7 +295,7 @@ public class IdPanel extends Panel
       }
       else
       {
-        nlinks = new ArrayList<String>();
+        nlinks = new ArrayList<>();
       }
 
       for (SequenceFeature sf : sq.getFeatures().getNonPositionalFeatures())
@@ -333,7 +333,7 @@ public class IdPanel extends Panel
       selectSeq(seq);
     }
 
-    alignPanel.paintAlignment(false);
+    alignPanel.paintAlignment(false, false);
   }
 
   void selectSeq(int seq)
@@ -459,7 +459,7 @@ public class IdPanel extends Panel
           running = false;
         }
 
-        alignPanel.paintAlignment(true);
+        alignPanel.paintAlignment(true, false);
         try
         {
           Thread.sleep(100);
index e74e1cd..0256055 100755 (executable)
@@ -200,7 +200,7 @@ public class OverviewPanel extends Panel implements Runnable,
                 av.getAlignment().getHiddenSequences(),
                 av.getAlignment().getHiddenColumns());
       }
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
     }
   }
 
index 2aba20c..bd36b0d 100644 (file)
@@ -160,7 +160,7 @@ public class RedundancyPanel extends SliderPanel
 
     float value = slider.getValue();
 
-    List<SequenceI> redundantSequences = new ArrayList<SequenceI>();
+    List<SequenceI> redundantSequences = new ArrayList<>();
     for (int i = 0; i < redundancy.length; i++)
     {
       if (value <= redundancy[i])
@@ -247,7 +247,7 @@ public class RedundancyPanel extends SliderPanel
               ap.av.getAlignment().getSequences());
     }
 
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
 
     if (historyList.size() == 0)
     {
index 514c3f9..7d4150d 100755 (executable)
@@ -141,7 +141,7 @@ public class ScalePanel extends Panel
       sg.setStartRes(min);
       sg.setEndRes(max);
     }
-    ap.paintAlignment(false);
+    ap.paintAlignment(false, false);
     av.sendSelection();
   }
 
@@ -167,7 +167,7 @@ public class ScalePanel extends Panel
         {
           av.showColumn(reveal[0]);
           reveal = null;
-          ap.paintAlignment(true);
+          ap.paintAlignment(true, true);
           av.sendSelection();
         }
       });
@@ -183,7 +183,7 @@ public class ScalePanel extends Panel
           {
             av.showAllHiddenColumns();
             reveal = null;
-            ap.paintAlignment(true);
+            ap.paintAlignment(true, true);
             av.sendSelection();
           }
         });
@@ -208,7 +208,7 @@ public class ScalePanel extends Panel
             av.setSelectionGroup(null);
           }
 
-          ap.paintAlignment(true);
+          ap.paintAlignment(true, true);
           av.sendSelection();
         }
       });
@@ -239,7 +239,7 @@ public class ScalePanel extends Panel
 
     if (!stretchingGroup)
     {
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
 
       return;
     }
@@ -256,7 +256,7 @@ public class ScalePanel extends Panel
     }
 
     stretchingGroup = false;
-    ap.paintAlignment(false);
+    ap.paintAlignment(false, false);
     av.sendSelection();
   }
 
@@ -285,7 +285,7 @@ public class ScalePanel extends Panel
     {
       stretchingGroup = true;
       cs.stretchGroup(res, sg, min, max);
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
     }
   }
 
index f36a8e2..55320ed 100644 (file)
@@ -335,7 +335,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       sg.addSequence(sequence, false);
       av.setSelectionGroup(sg);
     }
-    ap.paintAlignment(false);
+    ap.paintAlignment(false, false);
     av.sendSelection();
   }
 
@@ -983,7 +983,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
       lastMousePress = evt.getPoint();
 
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
       ap.annotationPanel.image = null;
       return;
     }
@@ -1450,7 +1450,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         {
           if (links == null)
           {
-            links = new Vector<String>();
+            links = new Vector<>();
           }
           links.addAll(sf.links);
         }
@@ -1524,7 +1524,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       }
     }
     PaintRefresher.Refresh(ap, av.getSequenceSetId());
-    ap.paintAlignment(needOverviewUpdate);
+    ap.paintAlignment(needOverviewUpdate, needOverviewUpdate);
     needOverviewUpdate = false;
     changeEndRes = false;
     changeStartRes = false;
index 565ebe8..5841e80 100644 (file)
@@ -441,7 +441,7 @@ public class SliderPanel extends Panel
   @Override
   public void mouseReleased(MouseEvent evt)
   {
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 
   @Override
index ed531d3..777e307 100644 (file)
@@ -187,9 +187,9 @@ public class SplitFrame extends EmbmenuFrame
     createSplitFrameWindow(embedded, applet);
     validate();
     topFrame.alignPanel.adjustAnnotationHeight();
-    topFrame.alignPanel.paintAlignment(true);
+    topFrame.alignPanel.paintAlignment(true, true);
     bottomFrame.alignPanel.adjustAnnotationHeight();
-    bottomFrame.alignPanel.paintAlignment(true);
+    bottomFrame.alignPanel.paintAlignment(true, true);
   }
 
   /**
index d1c0e1b..6831a73 100644 (file)
@@ -64,7 +64,7 @@ public class UserDefinedColours extends Panel
 
   Button selectedButton;
 
-  Vector<Color> oldColours = new Vector<Color>();
+  Vector<Color> oldColours = new Vector<>();
 
   ColourSchemeI oldColourScheme;
 
@@ -520,7 +520,7 @@ public class UserDefinedColours extends Panel
                 ap.av.isIgnoreGapsConsensus());
       }
       ap.seqPanel.seqCanvas.img = null;
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, true);
     }
     else if (jmol != null)
     {
@@ -599,7 +599,7 @@ public class UserDefinedColours extends Panel
       {
         ap.av.setGlobalColourScheme(oldColourScheme);
       }
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, true);
     }
 
     frame.setVisible(false);
index dd05843..69e31cf 100644 (file)
@@ -190,7 +190,7 @@ public class AlignViewController implements AlignViewControllerI
       if (changed)
       {
         viewport.setColumnSelection(cs);
-        alignPanel.paintAlignment(true);
+        alignPanel.paintAlignment(false, false);
         int columnCount = invert
                 ? (sqcol.getEndRes() - sqcol.getStartRes() + 1)
                         - bs.cardinality()
@@ -215,7 +215,7 @@ public class AlignViewController implements AlignViewControllerI
       if (!extendCurrent)
       {
         cs.clear();
-        alignPanel.paintAlignment(true);
+        alignPanel.paintAlignment(false, false);
       }
     }
     return false;
@@ -337,7 +337,7 @@ public class AlignViewController implements AlignViewControllerI
     AlignmentSorter.sortByFeature(typ, gps, start, stop, al, method);
     avcg.addHistoryItem(new OrderCommand(methodText, oldOrder,
             viewport.getAlignment()));
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
 
   }
 
@@ -375,7 +375,7 @@ public class AlignViewController implements AlignViewControllerI
       {
         avcg.getFeatureSettingsUI().discoverAllFeatureData();
       }
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
     }
 
     return featuresFile;
@@ -414,7 +414,7 @@ public class AlignViewController implements AlignViewControllerI
       if (changed)
       {
         viewport.setColumnSelection(cs);
-        alignPanel.paintAlignment(true);
+        alignPanel.paintAlignment(false, false);
         int columnCount = invert
                 ? (sqcol.getEndRes() - sqcol.getStartRes() + 1)
                         - bs.cardinality()
@@ -438,7 +438,7 @@ public class AlignViewController implements AlignViewControllerI
       if (!extendCurrent)
       {
         cs.clear();
-        alignPanel.paintAlignment(true);
+        alignPanel.paintAlignment(false, false);
       }
     }
     return false;
index b6224c2..1723f1d 100644 (file)
@@ -170,32 +170,30 @@ public class CigarArray extends CigarBase
         hideStart = region[0];
         hideEnd = region[1];
         // edit hidden regions to selection range
-        if (hideStart < last)
+
+        // just move on if hideEnd is before last
+        if (hideEnd < last)
         {
-          if (hideEnd > last)
-          {
-            hideStart = last;
-          }
-          else
-          {
-            continue;
-          }
+          continue;
         }
-
+        // exit if next region is after end
         if (hideStart > end)
         {
           break;
         }
 
-        if (hideEnd > end)
+        // truncate region at start if last falls in region
+        if ((hideStart < last) && (hideEnd >= last))
         {
-          hideEnd = end;
+          hideStart = last;
         }
 
-        if (hideStart > hideEnd)
+        // truncate region at end if end falls in region
+        if (hideEnd > end) // already checked that hideStart<=end
         {
-          break;
+          hideEnd = end;
         }
+
         /**
          * form operations...
          */
@@ -207,7 +205,7 @@ public class CigarArray extends CigarBase
         last = hideEnd + 1;
       }
       // Final match if necessary.
-      if (last < end)
+      if (last <= end)
       {
         addOperation(CigarArray.M, end - last + 1);
       }
index 98b0fb3..9680766 100755 (executable)
@@ -38,8 +38,6 @@ import java.util.List;
 import java.util.ListIterator;
 import java.util.Vector;
 
-import com.stevesoft.pat.Regex;
-
 import fr.orsay.lri.varna.models.rna.RNA;
 
 /**
@@ -51,11 +49,6 @@ import fr.orsay.lri.varna.models.rna.RNA;
  */
 public class Sequence extends ASequence implements SequenceI
 {
-  private static final Regex limitrx = new Regex(
-          "[/][0-9]{1,}[-][0-9]{1,}$");
-
-  private static final Regex endrx = new Regex("[0-9]{1,}$");
-
   SequenceI datasetSequence;
 
   String name;
@@ -151,6 +144,10 @@ public class Sequence extends ASequence implements SequenceI
     checkValidRange();
   }
 
+  /**
+   * If 'name' ends in /i-j, where i >= j > 0 are integers, extracts i and j as
+   * start and end respectively and removes the suffix from the name
+   */
   void parseId()
   {
     if (name == null)
@@ -159,17 +156,37 @@ public class Sequence extends ASequence implements SequenceI
               "POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
       name = "";
     }
-    // Does sequence have the /start-end signature?
-    if (limitrx.search(name))
+    int slashPos = name.lastIndexOf('/');
+    if (slashPos > -1 && slashPos < name.length() - 1)
     {
-      name = limitrx.left();
-      endrx.search(limitrx.stringMatched());
-      setStart(Integer.parseInt(limitrx.stringMatched().substring(1,
-              endrx.matchedFrom() - 1)));
-      setEnd(Integer.parseInt(endrx.stringMatched()));
+      String suffix = name.substring(slashPos + 1);
+      String[] range = suffix.split("-");
+      if (range.length == 2)
+      {
+        try
+        {
+          int from = Integer.valueOf(range[0]);
+          int to = Integer.valueOf(range[1]);
+          if (from > 0 && to >= from)
+          {
+            name = name.substring(0, slashPos);
+            setStart(from);
+            setEnd(to);
+            checkValidRange();
+          }
+        } catch (NumberFormatException e)
+        {
+          // leave name unchanged if suffix is invalid
+        }
+      }
     }
   }
 
+  /**
+   * Ensures that 'end' is not before the end of the sequence, that is,
+   * (end-start+1) is at least as long as the count of ungapped positions. Note
+   * that end is permitted to be beyond the end of the sequence data.
+   */
   void checkValidRange()
   {
     // Note: JAL-774 :
@@ -178,7 +195,7 @@ public class Sequence extends ASequence implements SequenceI
       int endRes = 0;
       for (int j = 0; j < sequence.length; j++)
       {
-        if (!jalview.util.Comparison.isGap(sequence[j]))
+        if (!Comparison.isGap(sequence[j]))
         {
           endRes++;
         }
@@ -453,15 +470,15 @@ public class Sequence extends ASequence implements SequenceI
   }
 
   /**
-   * DOCUMENT ME!
+   * Sets the sequence name. If the name ends in /start-end, then the start-end
+   * values are parsed out and set, and the suffix is removed from the name.
    * 
-   * @param name
-   *          DOCUMENT ME!
+   * @param theName
    */
   @Override
-  public void setName(String name)
+  public void setName(String theName)
   {
-    this.name = name;
+    this.name = theName;
     this.parseId();
   }
 
@@ -1884,7 +1901,9 @@ public class Sequence extends ASequence implements SequenceI
      * and we may have included adjacent or enclosing features;
      * remove any that are not enclosing, non-contact features
      */
-    if (endPos > this.end || Comparison.isGap(sequence[toColumn - 1]))
+    boolean endColumnIsGapped = toColumn > 0 && toColumn <= sequence.length
+            && Comparison.isGap(sequence[toColumn - 1]);
+    if (endPos > this.end || endColumnIsGapped)
     {
       ListIterator<SequenceFeature> it = result.listIterator();
       while (it.hasNext())
index 115ecd4..afff4c2 100644 (file)
@@ -205,8 +205,9 @@ public class EnsemblGene extends EnsemblSeqProxy
   }
 
   /**
-   * Converts a query, which may contain one or more gene or transcript
-   * identifiers, into a non-redundant list of gene identifiers.
+   * Converts a query, which may contain one or more gene, transcript, or
+   * external (to Ensembl) identifiers, into a non-redundant list of gene
+   * identifiers.
    * 
    * @param accessions
    * @return
@@ -217,54 +218,30 @@ public class EnsemblGene extends EnsemblSeqProxy
 
     for (String acc : accessions.split(getAccessionSeparator()))
     {
-      if (isGeneIdentifier(acc))
-      {
-        if (!geneIds.contains(acc))
-        {
-          geneIds.add(acc);
-        }
-      }
-
       /*
-       * if given a transcript id, look up its gene parent
+       * First try lookup as an Ensembl (gene or transcript) identifier
        */
-      else if (isTranscriptIdentifier(acc))
+      String geneId = new EnsemblLookup(getDomain()).getGeneId(acc);
+      if (geneId != null)
       {
-        String geneId = new EnsemblLookup(getDomain()).getParent(acc);
-        if (geneId != null && !geneIds.contains(geneId))
+        if (!geneIds.contains(geneId))
         {
           geneIds.add(geneId);
         }
       }
-      else if (isProteinIdentifier(acc))
-      {
-        String tscriptId = new EnsemblLookup(getDomain()).getParent(acc);
-        if (tscriptId != null)
-        {
-          String geneId = new EnsemblLookup(getDomain())
-                  .getParent(tscriptId);
-
-          if (geneId != null && !geneIds.contains(geneId))
-          {
-            geneIds.add(geneId);
-          }
-        }
-        // NOTE - acc is lost if it resembles an ENS.+ ID but isn't actually
-        // resolving to one... e.g. ENSMICP00000009241
-      }
-      /*
-       * if given a gene or other external name, lookup and fetch 
-       * the corresponding gene for all model organisms 
-       */
       else
       {
+        /*
+         * if given a gene or other external name, lookup and fetch 
+         * the corresponding gene for all model organisms 
+         */
         List<String> ids = new EnsemblSymbol(getDomain(), getDbSource(),
-                getDbVersion()).getIds(acc);
-        for (String geneId : ids)
+                getDbVersion()).getGeneIds(acc);
+        for (String id : ids)
         {
-          if (!geneIds.contains(geneId))
+          if (!geneIds.contains(id))
           {
-            geneIds.add(geneId);
+            geneIds.add(id);
           }
         }
       }
@@ -273,30 +250,6 @@ public class EnsemblGene extends EnsemblSeqProxy
   }
 
   /**
-   * Attempts to get Ensembl stable identifiers for model organisms for a gene
-   * name by calling the xrefs symbol REST service to resolve the gene name.
-   * 
-   * @param query
-   * @return
-   */
-  protected String getGeneIdentifiersForName(String query)
-  {
-    List<String> ids = new EnsemblSymbol(getDomain(), getDbSource(),
-            getDbVersion()).getIds(query);
-    if (ids != null)
-    {
-      for (String id : ids)
-      {
-        if (isGeneIdentifier(id))
-        {
-          return id;
-        }
-      }
-    }
-    return null;
-  }
-
-  /**
    * Constructs all transcripts for the gene, as identified by "transcript"
    * features whose Parent is the requested gene. The coding transcript
    * sequences (i.e. with introns omitted) are added to the alignment.
index ef46a5b..b40df50 100644 (file)
@@ -39,17 +39,12 @@ public class EnsemblGenomes extends EnsemblGene
   }
 
   @Override
-  public boolean isGeneIdentifier(String query)
-  {
-    return true;
-  }
-
-  @Override
   public String getDbName()
   {
     return "EnsemblGenomes";
   }
 
+  private String Wrong[];
   @Override
   public String getTestQuery()
   {
index 0968663..f314b0a 100644 (file)
@@ -28,17 +28,16 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Function;
 
 import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 
 /**
- * A client for the Ensembl lookup REST endpoint; used to find the Parent gene
- * identifier given a transcript identifier.
+ * A client for the Ensembl lookup REST endpoint
  * 
  * @author gmcarstairs
- *
  */
 public class EnsemblLookup extends EnsemblRestClient
 {
@@ -46,6 +45,12 @@ public class EnsemblLookup extends EnsemblRestClient
 
   private static final String PARENT = "Parent";
 
+  private static final String OBJECT_TYPE_TRANSLATION = "Translation";
+  private static final String OBJECT_TYPE_TRANSCRIPT = "Transcript";
+  private static final String ID = "id";
+  private static final String OBJECT_TYPE_GENE = "Gene";
+  private static final String OBJECT_TYPE = "object_type";
+
   /**
    * Default constructor (to use rest.ensembl.org)
    */
@@ -90,7 +95,7 @@ public class EnsemblLookup extends EnsemblRestClient
   protected URL getUrl(String identifier)
   {
     String url = getDomain() + "/lookup/id/" + identifier
-            + "?content-type=application/json";
+            + CONTENT_TYPE_JSON;
     try
     {
       return new URL(url);
@@ -125,9 +130,9 @@ public class EnsemblLookup extends EnsemblRestClient
    * @param identifier
    * @return
    */
-  public String getParent(String identifier)
+  public String getGeneId(String identifier)
   {
-    return getAttribute(identifier, PARENT);
+    return getResult(identifier, br -> parseGeneId(br));
   }
 
   /**
@@ -139,7 +144,7 @@ public class EnsemblLookup extends EnsemblRestClient
    */
   public String getSpecies(String identifier)
   {
-    return getAttribute(identifier, SPECIES);
+    return getResult(identifier, br -> getAttribute(br, SPECIES));
   }
 
   /**
@@ -147,7 +152,8 @@ public class EnsemblLookup extends EnsemblRestClient
    * @param attribute
    * @return
    */
-  protected String getAttribute(String identifier, String attribute)
+  protected String getResult(String identifier,
+          Function<BufferedReader, String> parser)
   {
     List<String> ids = Arrays.asList(new String[] { identifier });
 
@@ -159,7 +165,7 @@ public class EnsemblLookup extends EnsemblRestClient
       {
         br = getHttpResponse(url, ids);
       }
-      return (parseResponse(br, attribute));
+      return br == null ? null : parser.apply(br);
     } catch (IOException e)
     {
       // ignore
@@ -180,27 +186,75 @@ public class EnsemblLookup extends EnsemblRestClient
   }
 
   /**
-   * Parses the value of 'attribute' from the JSON response and returns the
-   * value, or null if not found
+   * Answers the value of 'attribute' from the JSON response, or null if not
+   * found
    * 
    * @param br
    * @param attribute
    * @return
-   * @throws IOException
    */
-  protected String parseResponse(BufferedReader br, String attribute) throws IOException
+  protected String getAttribute(BufferedReader br, String attribute)
+  {
+    String value = null;
+    JSONParser jp = new JSONParser();
+    try
+    {
+      JSONObject val = (JSONObject) jp.parse(br);
+      value = val.get(attribute).toString();
+    } catch (ParseException | NullPointerException | IOException e)
+    {
+      // ignore
+    }
+    return value;
+  }
+
+  /**
+   * Parses the JSON response and returns the gene identifier, or null if not
+   * found. If the returned object_type is Gene, returns the id, if Transcript
+   * returns the Parent. If it is Translation (peptide identifier), then the
+   * Parent is the transcript identifier, so we redo the search with this value.
+   * 
+   * @param br
+   * @return
+   */
+  protected String parseGeneId(BufferedReader br)
   {
-    String parent = null;
+    String geneId = null;
     JSONParser jp = new JSONParser();
     try
     {
       JSONObject val = (JSONObject) jp.parse(br);
-      parent = val.get(attribute).toString();
-    } catch (ParseException | NullPointerException e)
+      String type = val.get(OBJECT_TYPE).toString();
+      if (OBJECT_TYPE_GENE.equalsIgnoreCase(type))
+      {
+        geneId = val.get(ID).toString();
+      }
+      else if (OBJECT_TYPE_TRANSCRIPT.equalsIgnoreCase(type))
+      {
+        geneId = val.get(PARENT).toString();
+      }
+      else if (OBJECT_TYPE_TRANSLATION.equalsIgnoreCase(type))
+      {
+        String transcriptId = val.get(PARENT).toString();
+        try
+        {
+          geneId = getGeneId(transcriptId);
+        } catch (StackOverflowError e)
+        {
+          /*
+           * unlikely data condition error!
+           */
+          System.err
+                  .println("** Ensembl lookup "
+                          + getUrl(transcriptId).toString()
+                          + " looping on Parent!");
+        }
+      }
+    } catch (ParseException | IOException e)
     {
       // ignore
     }
-    return parent;
+    return geneId;
   }
 
 }
index 1554a0b..99006aa 100644 (file)
@@ -23,8 +23,6 @@ package jalview.ext.ensembl;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceFeature;
 
-import java.util.List;
-
 import com.stevesoft.pat.Regex;
 
 /**
index 7cabfbb..e3d1215 100644 (file)
@@ -31,6 +31,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
+import java.net.ProtocolException;
 import java.net.URL;
 import java.util.HashMap;
 import java.util.List;
@@ -42,8 +43,6 @@ import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
 
-import com.stevesoft.pat.Regex;
-
 /**
  * Base class for Ensembl REST service clients
  * 
@@ -55,15 +54,21 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
 
   private static final int CONNECT_TIMEOUT_MS = 10 * 1000; // 10 seconds
 
+  private static final int MAX_RETRIES = 3;
+
+  private static final int HTTP_OK = 200;
+
+  private static final int HTTP_OVERLOAD = 429;
+
   /*
    * update these constants when Jalview has been checked / updated for
    * changes to Ensembl REST API (ref JAL-2105)
    * @see https://github.com/Ensembl/ensembl-rest/wiki/Change-log
    * @see http://rest.ensembl.org/info/rest?content-type=application/json
    */
-  private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "5.0";
+  private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "6.0";
 
-  private static final String LATEST_ENSEMBL_REST_VERSION = "5.0";
+  private static final String LATEST_ENSEMBL_REST_VERSION = "6.1";
 
   private static final String REST_CHANGE_LOG = "https://github.com/Ensembl/ensembl-rest/wiki/Change-log";
 
@@ -76,18 +81,11 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
 
   private final static long VERSION_RETEST_INTERVAL = 1000L * 3600; // 1 hr
 
-  private static final Regex PROTEIN_REGEX = new Regex(
-          "(ENS)([A-Z]{3}|)P[0-9]{11}$");
-
-  private static final Regex TRANSCRIPT_REGEX = new Regex(
-          "(ENS)([A-Z]{3}|)T[0-9]{11}$");
-
-  private static final Regex GENE_REGEX = new Regex(
-          "(ENS)([A-Z]{3}|)G[0-9]{11}$");
+  protected static final String CONTENT_TYPE_JSON = "?content-type=application/json";
 
   static
   {
-    domainData = new HashMap<String, EnsemblData>();
+    domainData = new HashMap<>();
     domainData.put(ENSEMBL_REST,
             new EnsemblData(ENSEMBL_REST, LATEST_ENSEMBL_REST_VERSION));
     domainData.put(ENSEMBL_GENOMES_REST, new EnsemblData(
@@ -114,42 +112,6 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
     setDomain(d);
   }
 
-  /**
-   * Answers true if the query matches the regular expression pattern for an
-   * Ensembl transcript stable identifier
-   * 
-   * @param query
-   * @return
-   */
-  public boolean isTranscriptIdentifier(String query)
-  {
-    return query == null ? false : TRANSCRIPT_REGEX.search(query);
-  }
-
-  /**
-   * Answers true if the query matches the regular expression pattern for an
-   * Ensembl protein stable identifier
-   * 
-   * @param query
-   * @return
-   */
-  public boolean isProteinIdentifier(String query)
-  {
-    return query == null ? false : PROTEIN_REGEX.search(query);
-  }
-
-  /**
-   * Answers true if the query matches the regular expression pattern for an
-   * Ensembl gene stable identifier
-   * 
-   * @param query
-   * @return
-   */
-  public boolean isGeneIdentifier(String query)
-  {
-    return query == null ? false : GENE_REGEX.search(query);
-  }
-
   @Override
   public boolean queryInProgress()
   {
@@ -204,21 +166,25 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
    * @see http://rest.ensembl.org/documentation/info/ping
    * @return
    */
-  private boolean checkEnsembl()
+  boolean checkEnsembl()
   {
     BufferedReader br = null;
     try
     {
       // note this format works for both ensembl and ensemblgenomes
       // info/ping.json works for ensembl only (March 2016)
-      URL ping = new URL(
-              getDomain() + "/info/ping?content-type=application/json");
+      URL ping = new URL(getDomain() + "/info/ping" + CONTENT_TYPE_JSON);
 
       /*
        * expect {"ping":1} if ok
        * if ping takes more than 2 seconds to respond, treat as if unavailable
        */
       br = getHttpResponse(ping, null, 2 * 1000);
+      if (br == null)
+      {
+        // error reponse status
+        return false;
+      }
       JSONParser jp = new JSONParser();
       JSONObject val = (JSONObject) jp.parse(br);
       String pingString = val.get("ping").toString();
@@ -281,7 +247,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
   }
 
   /**
-   * Writes the HTTP request and gets the response as a reader.
+   * Sends the HTTP request and gets the response as a reader
    * 
    * @param url
    * @param ids
@@ -295,7 +261,56 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
   protected BufferedReader getHttpResponse(URL url, List<String> ids,
           int readTimeout) throws IOException
   {
-    // long now = System.currentTimeMillis();
+    int retriesLeft = MAX_RETRIES;
+    HttpURLConnection connection = null;
+    int responseCode = 0;
+
+    while (retriesLeft > 0)
+    {
+      connection = tryConnection(url, ids, readTimeout);
+      responseCode = connection.getResponseCode();
+      if (responseCode == HTTP_OVERLOAD) // 429
+      {
+        retriesLeft--;
+        checkRetryAfter(connection);
+      }
+      else
+      {
+        retriesLeft = 0;
+      }
+    }
+    if (responseCode != HTTP_OK) // 200
+    {
+      /*
+       * note: a GET request for an invalid id returns an error code e.g. 415
+       * but POST request returns 200 and an empty Fasta response 
+       */
+      System.err.println("Response code " + responseCode + " for " + url);
+      return null;
+    }
+
+    InputStream response = connection.getInputStream();
+
+    // System.out.println(getClass().getName() + " took "
+    // + (System.currentTimeMillis() - now) + "ms to fetch");
+
+    BufferedReader reader = null;
+    reader = new BufferedReader(new InputStreamReader(response, "UTF-8"));
+    return reader;
+  }
+
+  /**
+   * @param url
+   * @param ids
+   * @param readTimeout
+   * @return
+   * @throws IOException
+   * @throws ProtocolException
+   */
+  protected HttpURLConnection tryConnection(URL url, List<String> ids,
+          int readTimeout) throws IOException, ProtocolException
+  {
+    // System.out.println(System.currentTimeMillis() + " " + url);
     HttpURLConnection connection = (HttpURLConnection) url.openConnection();
 
     /*
@@ -320,77 +335,40 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
     {
       writePostBody(connection, ids);
     }
-
-    int responseCode = connection.getResponseCode();
-
-    if (responseCode != 200)
-    {
-      /*
-       * note: a GET request for an invalid id returns an error code e.g. 415
-       * but POST request returns 200 and an empty Fasta response 
-       */
-      System.err.println("Response code " + responseCode + " for " + url);
-      return null;
-    }
-    // get content
-    InputStream response = connection.getInputStream();
-
-    // System.out.println(getClass().getName() + " took "
-    // + (System.currentTimeMillis() - now) + "ms to fetch");
-
-    checkRateLimits(connection);
-
-    BufferedReader reader = null;
-    reader = new BufferedReader(new InputStreamReader(response, "UTF-8"));
-    return reader;
+    return connection;
   }
 
   /**
-   * Inspect response headers for any sign of server overload and respect any
-   * 'retry-after' directive
+   * Inspects response headers for a 'retry-after' directive, and waits for the
+   * directed period (if less than 10 seconds)
    * 
    * @see https://github.com/Ensembl/ensembl-rest/wiki/Rate-Limits
    * @param connection
    */
-  void checkRateLimits(HttpURLConnection connection)
+  void checkRetryAfter(HttpURLConnection connection)
   {
-    // number of requests allowed per time interval:
-    String limit = connection.getHeaderField("X-RateLimit-Limit");
-    // length of quota time interval in seconds:
-    // String period = connection.getHeaderField("X-RateLimit-Period");
-    // seconds remaining until usage quota is reset:
-    String reset = connection.getHeaderField("X-RateLimit-Reset");
-    // number of requests remaining from quota for current period:
-    String remaining = connection.getHeaderField("X-RateLimit-Remaining");
-    // number of seconds to wait before retrying (if remaining == 0)
     String retryDelay = connection.getHeaderField("Retry-After");
 
     // to test:
     // retryDelay = "5";
 
-    EnsemblData info = domainData.get(getDomain());
     if (retryDelay != null)
     {
-      System.err.println("Ensembl REST service rate limit exceeded, wait "
-              + retryDelay + " seconds before retrying");
       try
       {
-        info.retryAfter = System.currentTimeMillis()
-                + (1000 * Integer.valueOf(retryDelay));
-      } catch (NumberFormatException e)
+        int retrySecs = Integer.valueOf(retryDelay);
+        if (retrySecs > 0 && retrySecs < 10)
+        {
+          System.err
+                  .println("Ensembl REST service rate limit exceeded, waiting "
+                          + retryDelay + " seconds before retrying");
+          Thread.sleep(1000 * retrySecs);
+        }
+      } catch (NumberFormatException | InterruptedException e)
       {
-        System.err
-                .println("Unexpected value for Retry-After: " + retryDelay);
+        System.err.println("Error handling Retry-After: " + e.getMessage());
       }
     }
-    else
-    {
-      info.retryAfter = 0;
-      // debug:
-      // System.out.println(String.format(
-      // "%s Ensembl requests remaining of %s (reset in %ss)",
-      // remaining, limit, reset));
-    }
   }
 
   /**
@@ -408,20 +386,6 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
     long now = System.currentTimeMillis();
 
     /*
-     * check if we are waiting for 'Retry-After' to expire
-     */
-    if (info.retryAfter > now)
-    {
-      System.err.println("Still " + (1 + (info.retryAfter - now) / 1000)
-              + " secs to wait before retrying Ensembl");
-      return false;
-    }
-    else
-    {
-      info.retryAfter = 0;
-    }
-
-    /*
      * recheck if Ensembl is up if it was down, or the recheck period has elapsed
      */
     boolean retestAvailability = (now
@@ -497,9 +461,12 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
     URL url = null;
     try
     {
-      url = new URL(
-              getDomain() + "/info/rest?content-type=application/json");
+      url = new URL(getDomain() + "/info/rest" + CONTENT_TYPE_JSON);
       BufferedReader br = getHttpResponse(url, null);
+      if (br == null)
+      {
+        return;
+      }
       JSONObject val = (JSONObject) jp.parse(br);
       String version = val.get("release").toString();
       String majorVersion = version.substring(0, version.indexOf("."));
@@ -558,18 +525,35 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
   {
     JSONParser jp = new JSONParser();
     URL url = null;
+    BufferedReader br = null;
+
     try
     {
-      url = new URL(
-              getDomain() + "/info/data?content-type=application/json");
-      BufferedReader br = getHttpResponse(url, null);
-      JSONObject val = (JSONObject) jp.parse(br);
-      JSONArray versions = (JSONArray) val.get("releases");
-      domainData.get(getDomain()).dataVersion = versions.get(0).toString();
+      url = new URL(getDomain() + "/info/data" + CONTENT_TYPE_JSON);
+      br = getHttpResponse(url, null);
+      if (br != null)
+      {
+        JSONObject val = (JSONObject) jp.parse(br);
+        JSONArray versions = (JSONArray) val.get("releases");
+        domainData.get(getDomain()).dataVersion = versions.get(0)
+                .toString();
+      }
     } catch (Throwable t)
     {
       System.err.println(
               "Error checking Ensembl data version: " + t.getMessage());
+    } finally
+    {
+      if (br != null)
+      {
+        try
+        {
+          br.close();
+        } catch (IOException e)
+        {
+          // ignore
+        }
+      }
     }
   }
 
index 9f86731..75598a0 100644 (file)
@@ -42,6 +42,10 @@ import org.json.simple.parser.ParseException;
  */
 public class EnsemblSymbol extends EnsemblXref
 {
+  private static final String GENE = "gene";
+  private static final String TYPE = "type";
+  private static final String ID = "id";
+
   /**
    * Constructor given the target domain to fetch data from
    * 
@@ -73,8 +77,9 @@ public class EnsemblSymbol extends EnsemblXref
       while (rvals.hasNext())
       {
         JSONObject val = (JSONObject) rvals.next();
-        String id = val.get("id").toString();
-        if (id != null && isGeneIdentifier(id))
+        String id = val.get(ID).toString();
+        String type = val.get(TYPE).toString();
+        if (id != null && GENE.equals(type))
         {
           result = id;
           break;
@@ -87,12 +92,31 @@ public class EnsemblSymbol extends EnsemblXref
     return result;
   }
 
-  protected URL getUrl(String id, Species species)
+  /**
+   * Constructs the URL for the REST symbol endpoint
+   * 
+   * @param id
+   *          the accession id (Ensembl or external)
+   * @param species
+   *          a species name recognisable by Ensembl
+   * @param type
+   *          an optional type to filter the response (gene, transcript,
+   *          translation)
+   * @return
+   */
+  protected URL getUrl(String id, Species species, String... type)
   {
-    String url = getDomain() + "/xrefs/symbol/" + species.toString() + "/"
-            + id + "?content-type=application/json";
+    StringBuilder sb = new StringBuilder();
+    sb.append(getDomain()).append("/xrefs/symbol/")
+            .append(species.toString()).append("/").append(id)
+            .append(CONTENT_TYPE_JSON);
+    for (String t : type)
+    {
+      sb.append("&object_type=").append(t);
+    }
     try
     {
+      String url = sb.toString();
       return new URL(url);
     } catch (MalformedURLException e)
     {
@@ -107,7 +131,7 @@ public class EnsemblSymbol extends EnsemblXref
    * @param identifier
    * @return
    */
-  public List<String> getIds(String identifier)
+  public List<String> getGeneIds(String identifier)
   {
     List<String> result = new ArrayList<String>();
     List<String> ids = new ArrayList<String>();
@@ -119,19 +143,20 @@ public class EnsemblSymbol extends EnsemblXref
     {
       for (String query : queries)
       {
-        for (Species taxon : Species.values())
+        for (Species taxon : Species.getModelOrganisms())
         {
-          if (taxon.isModelOrganism())
+          URL url = getUrl(query, taxon, GENE);
+          if (url != null)
           {
-            URL url = getUrl(query, taxon);
-            if (url != null)
-            {
-              br = getHttpResponse(url, ids);
-            }
-            String geneId = parseSymbolResponse(br);
-            if (geneId != null)
+            br = getHttpResponse(url, ids);
+            if (br != null)
             {
-              result.add(geneId);
+              String geneId = parseSymbolResponse(br);
+              System.out.println(url + " returned " + geneId);
+              if (geneId != null && !result.contains(geneId))
+              {
+                result.add(geneId);
+              }
             }
           }
         }
index c0b00b1..27c448e 100644 (file)
@@ -124,8 +124,11 @@ class EnsemblXref extends EnsemblRestClient
       if (url != null)
       {
         br = getHttpResponse(url, ids);
+        if (br != null)
+        {
+          result = parseResponse(br);
+        }
       }
-      return (parseResponse(br));
     } catch (IOException e)
     {
       // ignore
@@ -168,16 +171,13 @@ class EnsemblXref extends EnsemblRestClient
       while (rvals.hasNext())
       {
         JSONObject val = (JSONObject) rvals.next();
-        String dbName = val.get("dbname").toString();
-        if (dbName.equals(GO_GENE_ONTOLOGY))
-        {
-          continue;
-        }
+        String db = val.get("dbname").toString();
         String id = val.get("primary_id").toString();
-        if (dbName != null && id != null)
+        if (db != null && id != null
+                && !GO_GENE_ONTOLOGY.equals(db))
         {
-          dbName = DBRefUtils.getCanonicalName(dbName);
-          DBRefEntry dbref = new DBRefEntry(dbName, getXRefVersion(), id);
+          db = DBRefUtils.getCanonicalName(db);
+          DBRefEntry dbref = new DBRefEntry(db, getXRefVersion(), id);
           result.add(dbref);
         }
       }
@@ -211,7 +211,7 @@ class EnsemblXref extends EnsemblRestClient
   protected URL getUrl(String identifier)
   {
     String url = getDomain() + "/xrefs/id/" + identifier
-            + "?content-type=application/json&all_levels=1";
+            + CONTENT_TYPE_JSON + "&all_levels=1";
     try
     {
       return new URL(url);
index af01225..cc5465e 100644 (file)
@@ -20,6 +20,9 @@
  */
 package jalview.ext.ensembl;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Selected species identifiers used by Ensembl
  * 
@@ -38,6 +41,18 @@ enum Species
   chimpanzee(false), cat(false), zebrafish(true), chicken(true),
   dmelanogaster(true);
 
+  static Set<Species> modelOrganisms = new HashSet<>();
+
+  static
+  {
+    for (Species s : values())
+    {
+      if (s.isModelOrganism())
+      {
+        modelOrganisms.add(s);
+      }
+    }
+  }
   boolean modelOrganism;
 
   private Species(boolean model)
@@ -49,4 +64,9 @@ enum Species
   {
     return modelOrganism;
   }
+
+  public static Set<Species> getModelOrganisms()
+  {
+    return modelOrganisms;
+  }
 }
index 96dfcfe..50aba62 100644 (file)
@@ -975,6 +975,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
                 (String) data[0]);
         // also highlight in alignment
+        // deliberate fall through
       case HOVER:
         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
                 (String) data[0]);
index f923f7f..39d6704 100644 (file)
@@ -120,7 +120,7 @@ public class AtomSpecModel
 
       for (String chain : modelData.keySet())
       {
-        chain = chain.trim();
+        chain = " ".equals(chain) ? chain : chain.trim();
 
         List<int[]> rangeList = modelData.get(chain);
 
@@ -192,9 +192,10 @@ public class AtomSpecModel
     {
       sb.append(start).append("-").append(end);
     }
-    if (chain.length() > 0)
-    {
-      sb.append(".").append(chain);
+
+    sb.append(".");
+    if (!" ".equals(chain)) {
+      sb.append(chain);
     }
   }
 }
index 2a53ab9..19f7db4 100644 (file)
@@ -43,13 +43,13 @@ public class PDBFTSPanel extends GFTSPanel
 
   private static final String PDB_FTS_CACHE_KEY = "CACHE.PDB_FTS";
 
-  public PDBFTSPanel(SequenceFetcher seqFetcher)
+  public PDBFTSPanel(SequenceFetcher fetcher)
   {
     super();
     pageLimit = PDBFTSRestClient.getInstance().getDefaultResponsePageSize();
-    this.seqFetcher = seqFetcher;
-    this.progressIndicator = (seqFetcher == null) ? null
-            : seqFetcher.getProgressIndicator();
+    this.seqFetcher = fetcher;
+    this.progressIndicator = (fetcher == null) ? null
+            : fetcher.getProgressIndicator();
   }
 
   @Override
@@ -86,15 +86,16 @@ public class PDBFTSPanel extends GFTSPanel
           request.setSearchTerm(searchTerm + ")");
           request.setOffSet(offSet);
           request.setWantedFields(wantedFields);
-          FTSRestClientI pdbRestCleint = PDBFTSRestClient.getInstance();
+          FTSRestClientI pdbRestClient = PDBFTSRestClient.getInstance();
           FTSRestResponse resultList;
           try
           {
-            resultList = pdbRestCleint.executeRequest(request);
+            resultList = pdbRestClient.executeRequest(request);
           } catch (Exception e)
           {
             setErrorMessage(e.getMessage());
             checkForErrors();
+            setSearchInProgress(false);
             return;
           }
 
index 2dad2f7..020331a 100644 (file)
@@ -40,18 +40,18 @@ public class UniprotFTSPanel extends GFTSPanel
   private static String defaultFTSFrameTitle = MessageManager
           .getString("label.uniprot_sequence_fetcher");
 
-  private static Map<String, Integer> tempUserPrefs = new HashMap<String, Integer>();
+  private static Map<String, Integer> tempUserPrefs = new HashMap<>();
 
   private static final String UNIPROT_FTS_CACHE_KEY = "CACHE.UNIPROT_FTS";
 
-  public UniprotFTSPanel(SequenceFetcher seqFetcher)
+  public UniprotFTSPanel(SequenceFetcher fetcher)
   {
     super();
     pageLimit = UniProtFTSRestClient.getInstance()
             .getDefaultResponsePageSize();
-    this.seqFetcher = seqFetcher;
-    this.progressIndicator = (seqFetcher == null) ? null
-            : seqFetcher.getProgressIndicator();
+    this.seqFetcher = fetcher;
+    this.progressIndicator = (fetcher == null) ? null
+            : fetcher.getProgressIndicator();
   }
 
   @Override
@@ -85,17 +85,17 @@ public class UniprotFTSPanel extends GFTSPanel
           request.setSearchTerm(searchTerm);
           request.setOffSet(offSet);
           request.setWantedFields(wantedFields);
-          FTSRestClientI uniProtRestCleint = UniProtFTSRestClient
+          FTSRestClientI uniProtRestClient = UniProtFTSRestClient
                   .getInstance();
           FTSRestResponse resultList;
           try
           {
-            resultList = uniProtRestCleint.executeRequest(request);
+            resultList = uniProtRestClient.executeRequest(request);
           } catch (Exception e)
           {
-            e.printStackTrace();
             setErrorMessage(e.getMessage());
             checkForErrors();
+            setSearchInProgress(false);
             return;
           }
 
@@ -183,7 +183,7 @@ public class UniprotFTSPanel extends GFTSPanel
   {
     disableActionButtons();
     StringBuilder selectedIds = new StringBuilder();
-    HashSet<String> selectedIdsSet = new HashSet<String>();
+    HashSet<String> selectedIdsSet = new HashSet<>();
     int primaryKeyColIndex = 0;
     try
     {
index f6b8392..bf94ec0 100644 (file)
@@ -1713,7 +1713,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     viewport.getAlignment().moveSelectedSequencesByOne(sg,
             viewport.getHiddenRepSequences(), up);
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   synchronized void slideSequences(boolean right, int size)
@@ -2399,7 +2399,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
       alignPanel.updateAnnotation();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
     }
   }
 
@@ -2425,7 +2425,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // JAL-2034 - should delegate to
     // alignPanel to decide if overview needs
     // updating.
-    alignPanel.paintAlignment(false);
+    alignPanel.paintAlignment(false, false);
     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
   }
 
@@ -2450,7 +2450,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // JAL-2034 - should delegate to
     // alignPanel to decide if overview needs
     // updating.
-    alignPanel.paintAlignment(false);
+    alignPanel.paintAlignment(false, false);
     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
     viewport.sendSelection();
   }
@@ -2481,7 +2481,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // alignPanel to decide if overview needs
     // updating.
 
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
     viewport.sendSelection();
   }
@@ -2490,7 +2490,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void invertColSel_actionPerformed(ActionEvent e)
   {
     viewport.invertColumnSelection();
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
     viewport.sendSelection();
   }
 
@@ -2870,21 +2870,21 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     alignPanel.getIdPanel().getIdCanvas()
             .setPreferredSize(alignPanel.calculateIdWidth());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   @Override
   public void idRightAlign_actionPerformed(ActionEvent e)
   {
     viewport.setRightAlignIds(idRightAlign.isSelected());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   @Override
   public void centreColumnLabels_actionPerformed(ActionEvent e)
   {
     viewport.setCentreColumnLabels(centreColumnLabelsMenuItem.getState());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   /*
@@ -2917,7 +2917,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void colourTextMenuItem_actionPerformed(ActionEvent e)
   {
     viewport.setColourText(colourTextMenuItem.isSelected());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   /**
@@ -2946,7 +2946,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void showAllColumns_actionPerformed(ActionEvent e)
   {
     viewport.showAllHiddenColumns();
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, true);
     viewport.sendSelection();
   }
 
@@ -3050,7 +3050,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     viewport.expandColSelection(sg, false);
     viewport.hideAllSelectedSeqs();
     viewport.hideSelectedColumns();
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, true);
     viewport.sendSelection();
   }
 
@@ -3066,7 +3066,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     viewport.showAllHiddenColumns();
     viewport.showAllHiddenSeqs();
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, true);
     viewport.sendSelection();
   }
 
@@ -3074,7 +3074,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void hideSelColumns_actionPerformed(ActionEvent e)
   {
     viewport.hideSelectedColumns();
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, true);
     viewport.sendSelection();
   }
 
@@ -3095,7 +3095,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void scaleAbove_actionPerformed(ActionEvent e)
   {
     viewport.setScaleAboveWrapped(scaleAbove.isSelected());
-    alignPanel.paintAlignment(true);
+    // TODO: do we actually need to update overview for scale above change ?
+    alignPanel.paintAlignment(true, false);
   }
 
   /**
@@ -3108,7 +3109,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void scaleLeft_actionPerformed(ActionEvent e)
   {
     viewport.setScaleLeftWrapped(scaleLeft.isSelected());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   /**
@@ -3121,7 +3122,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void scaleRight_actionPerformed(ActionEvent e)
   {
     viewport.setScaleRightWrapped(scaleRight.isSelected());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   /**
@@ -3134,7 +3135,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void viewBoxesMenuItem_actionPerformed(ActionEvent e)
   {
     viewport.setShowBoxes(viewBoxesMenuItem.isSelected());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   /**
@@ -3147,7 +3148,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void viewTextMenuItem_actionPerformed(ActionEvent e)
   {
     viewport.setShowText(viewTextMenuItem.isSelected());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   /**
@@ -3160,7 +3161,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void renderGapsMenuItem_actionPerformed(ActionEvent e)
   {
     viewport.setRenderGaps(renderGapsMenuItem.isSelected());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   public FeatureSettings featureSettings;
@@ -3198,7 +3199,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void showSeqFeatures_actionPerformed(ActionEvent evt)
   {
     viewport.setShowSequenceFeatures(showSeqFeatures.isSelected());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, true);
   }
 
   /**
@@ -3355,7 +3356,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     viewport.setGlobalColourScheme(cs);
 
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, true);
   }
 
   /**
@@ -3440,7 +3441,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             viewport.getAlignment().getSequenceAt(0));
     addHistoryItem(new OrderCommand("Pairwise Sort", oldOrder,
             viewport.getAlignment()));
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   /**
@@ -3456,7 +3457,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     AlignmentSorter.sortByID(viewport.getAlignment());
     addHistoryItem(
             new OrderCommand("ID Sort", oldOrder, viewport.getAlignment()));
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   /**
@@ -3472,7 +3473,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     AlignmentSorter.sortByLength(viewport.getAlignment());
     addHistoryItem(new OrderCommand("Length Sort", oldOrder,
             viewport.getAlignment()));
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   /**
@@ -3489,7 +3490,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     addHistoryItem(new OrderCommand("Group Sort", oldOrder,
             viewport.getAlignment()));
 
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
   }
 
   /**
@@ -3646,7 +3647,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         addHistoryItem(new OrderCommand(order.getName(), oldOrder,
                 viewport.getAlignment()));
 
-        alignPanel.paintAlignment(true);
+        alignPanel.paintAlignment(true, false);
       }
     });
   }
@@ -3675,7 +3676,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 viewport.getAlignment());// ,viewport.getSelectionGroup());
         addHistoryItem(new OrderCommand("Sort by " + scoreLabel, oldOrder,
                 viewport.getAlignment()));
-        alignPanel.paintAlignment(true);
+        alignPanel.paintAlignment(true, false);
       }
     });
   }
@@ -3790,7 +3791,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       addHistoryItem(new OrderCommand(undoname, oldOrder,
               viewport.getAlignment()));
     }
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, false);
     return true;
   }
 
@@ -4505,7 +4506,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                       assocfiles++;
                     }
                   }
-                  alignPanel.paintAlignment(true);
+                  // TODO: do we need to update overview ? only if features are
+                  // shown I guess
+                  alignPanel.paintAlignment(true, false);
                 }
               }
             }
@@ -4652,7 +4655,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           {
             if (parseFeaturesFile(file, sourceType))
             {
-              alignPanel.paintAlignment(true);
+              alignPanel.paintAlignment(true, true);
             }
           }
           else
@@ -4667,7 +4670,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         alignPanel.adjustAnnotationHeight();
         viewport.updateSequenceIdColours();
         buildSortByAnnotationScoresMenu();
-        alignPanel.paintAlignment(true);
+        alignPanel.paintAlignment(true, true);
       }
     } catch (Exception ex)
     {
@@ -5192,7 +5195,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void showUnconservedMenuItem_actionPerformed(ActionEvent e)
   {
     viewport.setShowUnconserved(showNonconservedMenuItem.getState());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   /*
@@ -5281,7 +5284,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
       alignPanel.updateAnnotation();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
     }
   }
 
@@ -5293,7 +5296,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       viewport.getAlignment().setSeqrep(null);
       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
       alignPanel.updateAnnotation();
-      alignPanel.paintAlignment(true);
+      alignPanel.paintAlignment(true, true);
     }
   }
 
@@ -5383,7 +5386,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     this.alignPanel.av.setSortAnnotationsBy(getAnnotationSortOrder());
     this.alignPanel.av
             .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(false, false);
   }
 
   /**
index c700635..b2c4809 100644 (file)
@@ -97,9 +97,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   private AnnotationLabels alabels;
 
-  // this value is set false when selection area being dragged
-  boolean fastPaint = true;
-
   private int hextent = 0;
 
   private int vextent = 0;
@@ -214,7 +211,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     alignFrame.updateEditMenuBar();
 
-    paintAlignment(true);
+    // no idea if we need to update structure
+    paintAlignment(true, true);
 
   }
 
@@ -481,7 +479,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       scrollNeeded = vpRanges.scrollToWrappedVisible(start);
     }
 
-    paintAlignment(redrawOverview);
+    paintAlignment(redrawOverview, false);
 
     return scrollNeeded;
   }
@@ -538,7 +536,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     validateAnnotationDimensions(true);
     addNotify();
-    paintAlignment(true);
+    // TODO: many places call this method and also paintAlignment with various
+    // different settings. this means multiple redraws are triggered...
+    paintAlignment(true, false);
   }
 
   /**
@@ -632,9 +632,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
       }
       else
       {
-        int widthInRes = (canvasWidth / av.getCharWidth()) - 1;
+        int widthInRes = (canvasWidth / av.getCharWidth());
         int heightInSeq = (getSeqPanel().seqCanvas.getHeight()
-                / av.getCharHeight()) - 1;
+                / av.getCharHeight());
 
         vpRanges.setViewportWidth(widthInRes);
         vpRanges.setViewportHeight(heightInSeq);
@@ -768,10 +768,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       }
       vpRanges.setViewportStartAndHeight(y, height);
     }
-    if (!fastPaint)
-    {
-      repaint();
-    }
+    repaint();
   }
 
   /**
@@ -837,12 +834,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
     repaint();
   }
 
-  /**
-   * Repaint the alignment including the annotations and overview panels (if
-   * shown).
+  /* (non-Javadoc)
+   * @see jalview.api.AlignmentViewPanel#paintAlignment(boolean)
    */
   @Override
-  public void paintAlignment(boolean updateOverview)
+  public void paintAlignment(boolean updateOverview,
+          boolean updateStructures)
   {
     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
             av.isShowAutocalculatedAbove());
@@ -850,10 +847,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
             av.getSortAnnotationsBy());
     repaint();
 
-    if (updateOverview)
+    if (updateStructures)
     {
-      // TODO: determine if this paintAlignment changed structure colours
       av.getStructureSelectionManager().sequenceColoursChanged(this);
+    }
+    if (updateOverview)
+    {
 
       if (overviewPanel != null)
       {
@@ -1866,7 +1865,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (adjustHeight)
     {
       // sort, repaint, update overview
-      paintAlignment(true);
+      paintAlignment(true, false);
     }
     else
     {
index 26796de..84883d7 100644 (file)
@@ -82,7 +82,7 @@ public class AnnotationChooser extends JPanel
   private boolean applyToUnselectedSequences;
 
   // currently selected 'annotation type' checkboxes
-  private Map<String, String> selectedTypes = new HashMap<String, String>();
+  private Map<String, String> selectedTypes = new HashMap<>();
 
   /**
    * Constructor.
@@ -202,7 +202,7 @@ public class AnnotationChooser extends JPanel
     // this.ap.alabels.setSize(this.ap.alabels.getSize().width,
     // this.ap.annotationPanel.getSize().height);
     // this.ap.validate();
-    this.ap.paintAlignment(true);
+    this.ap.paintAlignment(true, false);
   }
 
   /**
@@ -233,7 +233,7 @@ public class AnnotationChooser extends JPanel
     // this.ap.alabels.setSize(this.ap.alabels.getSize().width,
     // this.ap.annotationPanel.getSize().height);
     // this.ap.validate();
-    this.ap.paintAlignment(true);
+    this.ap.paintAlignment(true, false);
   }
 
   /**
@@ -251,7 +251,7 @@ public class AnnotationChooser extends JPanel
 
     this.ap.updateAnnotation();
     // this.ap.annotationPanel.adjustPanelHeight();
-    this.ap.paintAlignment(true);
+    this.ap.paintAlignment(true, false);
   }
 
   /**
@@ -356,7 +356,7 @@ public class AnnotationChooser extends JPanel
   public static List<String> getAnnotationTypes(AlignmentI alignment,
           boolean sequenceSpecificOnly)
   {
-    List<String> result = new ArrayList<String>();
+    List<String> result = new ArrayList<>();
     for (AlignmentAnnotation aa : alignment.getAlignmentAnnotation())
     {
       if (!sequenceSpecificOnly || aa.sequenceRef != null)
index 8d123bb..153f70c 100644 (file)
@@ -79,7 +79,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     oldcs = av.getGlobalColourScheme();
     if (av.getAlignment().getGroups() != null)
     {
-      oldgroupColours = new Hashtable<SequenceGroup, ColourSchemeI>();
+      oldgroupColours = new Hashtable<>();
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
         if (sg.getColourScheme() != null)
@@ -122,7 +122,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     }
     Vector<String> annotItems = getAnnotationItems(
             seqAssociated.isSelected());
-    annotations = new JComboBox<String>(annotItems);
+    annotations = new JComboBox<>(annotItems);
 
     populateThresholdComboBox(threshold);
 
@@ -341,7 +341,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       getCurrentAnnotation().threshold.value = slider.getValue() / 1000f;
       propagateSeqAssociatedThreshold(updateAllAnnotation,
               getCurrentAnnotation());
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
     }
   }
 
@@ -415,12 +415,9 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     colorAlignmentContaining(getCurrentAnnotation(), selectedThresholdItem);
 
     ap.alignmentChanged();
-    // ensure all associated views (overviews, structures, etc) are notified of
-    // updated colours.
-    ap.paintAlignment(true);
   }
 
-  protected boolean colorAlignmentContaining(AlignmentAnnotation currentAnn,
+  protected void colorAlignmentContaining(AlignmentAnnotation currentAnn,
           int selectedThresholdOption)
   {
 
@@ -460,7 +457,6 @@ public class AnnotationColourChooser extends AnnotationRowFilter
                 acg.getInstance(sg, ap.av.getHiddenRepSequences()));
       }
     }
-    return false;
   }
 
 }
index 84b2c6f..020e027 100644 (file)
@@ -254,7 +254,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
         av.getAlignment().setHiddenColumns(oldHidden);
       }
       av.sendSelection();
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, true);
     }
   }
 
@@ -267,7 +267,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
       updateView();
       propagateSeqAssociatedThreshold(updateAllAnnotation,
               getCurrentAnnotation());
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
     }
   }
 
@@ -391,7 +391,8 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
     av.getColumnSelection().filterAnnotations(
             getCurrentAnnotation().annotations, filterParams);
 
-    if (getActionOption() == ACTION_OPTION_HIDE)
+    boolean hideCols = getActionOption() == ACTION_OPTION_HIDE;
+    if (hideCols)
     {
       av.hideSelectedColumns();
     }
@@ -399,7 +400,8 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
 
     filterParams = null;
     av.setAnnotationColumnSelectionState(this);
-    ap.paintAlignment(true);
+    // only update overview and structures if columns were hidden
+    ap.paintAlignment(hideCols, hideCols);
   }
 
   public HiddenColumns getOldHiddenColumns()
index d07cae2..b94a615 100755 (executable)
@@ -705,7 +705,7 @@ public class AnnotationLabels extends JPanel
         d = ap.annotationSpaceFillerHolder.getPreferredSize();
         ap.annotationSpaceFillerHolder
                 .setPreferredSize(new Dimension(d.width, d.height - dif));
-        ap.paintAlignment(true);
+        ap.paintAlignment(true, false);
       }
 
       ap.addNotify();
@@ -855,7 +855,7 @@ public class AnnotationLabels extends JPanel
               }
             }
 
-            ap.paintAlignment(false);
+            ap.paintAlignment(false, false);
             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
             ap.av.sendSelection();
           }
@@ -912,7 +912,7 @@ public class AnnotationLabels extends JPanel
               sg.addSequence(aa[selectedRow].sequenceRef, false);
             }
             ap.av.setSelectionGroup(sg);
-            ap.paintAlignment(false);
+            ap.paintAlignment(false, false);
             PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
             ap.av.sendSelection();
           }
index be8f5f6..12d1369 100755 (executable)
@@ -670,7 +670,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       }
       graphStretchY = evt.getY();
       adjustPanelHeight();
-      ap.paintAlignment(true);
+      ap.paintAlignment(false, false);
     }
     else
     {
index 8b2486f..71ad6a5 100644 (file)
@@ -80,7 +80,7 @@ public abstract class AnnotationRowFilter extends JPanel
    */
   protected boolean sliderDragging = false;
 
-  protected JComboBox<String> threshold = new JComboBox<String>();
+  protected JComboBox<String> threshold = new JComboBox<>();
 
   protected JComboBox<String> annotations;
 
@@ -177,7 +177,6 @@ public abstract class AnnotationRowFilter extends JPanel
           sliderDragging = false;
           valueChanged(true);
         }
-        ap.paintAlignment(true);
       }
     });
   }
@@ -192,9 +191,9 @@ public abstract class AnnotationRowFilter extends JPanel
    */
   public Vector<String> getAnnotationItems(boolean isSeqAssociated)
   {
-    annotationLabels = new HashMap<AlignmentAnnotation, String>();
+    annotationLabels = new HashMap<>();
 
-    Vector<String> list = new Vector<String>();
+    Vector<String> list = new Vector<>();
     int index = 1;
     int[] anmap = new int[av.getAlignment()
             .getAlignmentAnnotation().length];
@@ -271,7 +270,7 @@ public abstract class AnnotationRowFilter extends JPanel
   public void cancel_actionPerformed()
   {
     reset();
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
     try
     {
       frame.setClosed(true);
@@ -413,6 +412,11 @@ public abstract class AnnotationRowFilter extends JPanel
     this.currentAnnotation = annotation;
   }
 
+  /**
+   * update associated view model and trigger any necessary repaints.
+   * 
+   * @param updateAllAnnotation
+   */
   protected abstract void valueChanged(boolean updateAllAnnotation);
 
   protected abstract void updateView();
index b27328d..d8db546 100644 (file)
@@ -147,7 +147,7 @@ public class FeatureColourChooser extends JalviewDialog
          */
         if (ap != null)
         {
-          ap.paintAlignment(true);
+          ap.paintAlignment(true, true);
         }
       }
     });
@@ -396,9 +396,9 @@ public class FeatureColourChooser extends JalviewDialog
    * feature type, and repaints the alignment, and optionally the Overview
    * and/or structure viewer if open
    * 
-   * @param updateOverview
+   * @param updateStructsAndOverview
    */
-  void changeColour(boolean updateOverview)
+  void changeColour(boolean updateStructsAndOverview)
   {
     // Check if combobox is still adjusting
     if (adjusting)
@@ -507,7 +507,7 @@ public class FeatureColourChooser extends JalviewDialog
     }
     fr.setColour(type, acg);
     cs = acg;
-    ap.paintAlignment(updateOverview);
+    ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
   }
 
   @Override
@@ -539,7 +539,7 @@ public class FeatureColourChooser extends JalviewDialog
   void reset()
   {
     fr.setColour(type, oldcs);
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
     cs = null;
   }
 
@@ -565,7 +565,7 @@ public class FeatureColourChooser extends JalviewDialog
       /*
        * force repaint of any Overview window or structure
        */
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, true);
     } catch (NumberFormatException ex)
     {
     }
index 17f5a71..9c4b009 100644 (file)
@@ -243,7 +243,7 @@ public class FeatureRenderer
       JPanel choosePanel = new JPanel();
       choosePanel.add(new JLabel(
               MessageManager.getString("label.select_feature") + ":"));
-      final JComboBox<String> overlaps = new JComboBox<String>();
+      final JComboBox<String> overlaps = new JComboBox<>();
       List<String> added = new ArrayList<>();
       for (SequenceFeature sf : features)
       {
@@ -487,7 +487,7 @@ public class FeatureRenderer
 
         featuresAdded();
 
-        alignPanel.paintAlignment(true);
+        alignPanel.paintAlignment(true, true);
 
         return true;
       }
@@ -497,7 +497,7 @@ public class FeatureRenderer
       }
     }
 
-    alignPanel.paintAlignment(true);
+    alignPanel.paintAlignment(true, true);
 
     return true;
   }
index 0963b31..b768339 100644 (file)
@@ -131,6 +131,11 @@ public class FeatureSettings extends JPanel
   private static final int MIN_WIDTH = 400;
 
   private static final int MIN_HEIGHT = 400;
+  
+  /**
+   * when true, constructor is still executing - so ignore UI events
+   */
+  protected volatile boolean inConstruction = true;
 
   /**
    * Constructor
@@ -303,6 +308,7 @@ public class FeatureSettings extends JPanel
               };
             });
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
+    inConstruction = false;
   }
 
   protected void popupSort(final int selectedRow, final String type,
@@ -485,7 +491,7 @@ public class FeatureSettings extends JPanel
   @Override
   synchronized public void discoverAllFeatureData()
   {
-    Set<String> allGroups = new HashSet<String>();
+    Set<String> allGroups = new HashSet<>();
     AlignmentI alignment = af.getViewport().getAlignment();
 
     for (int i = 0; i < alignment.getHeight(); i++)
@@ -557,12 +563,12 @@ public class FeatureSettings extends JPanel
       return;
     }
     resettingTable = true;
-    typeWidth = new Hashtable<String, float[]>();
+    typeWidth = new Hashtable<>();
     // TODO: change avWidth calculation to 'per-sequence' average and use long
     // rather than float
 
-    Set<String> displayableTypes = new HashSet<String>();
-    Set<String> foundGroups = new HashSet<String>();
+    Set<String> displayableTypes = new HashSet<>();
+    Set<String> foundGroups = new HashSet<>();
 
     /*
      * determine which feature types may be visible depending on 
@@ -578,7 +584,7 @@ public class FeatureSettings extends JPanel
        * and keep track of which groups are visible
        */
       Set<String> groups = seq.getFeatures().getFeatureGroups(true);
-      Set<String> visibleGroups = new HashSet<String>();
+      Set<String> visibleGroups = new HashSet<>();
       for (String group : groups)
       {
         if (group == null || checkGroupState(group))
@@ -1058,7 +1064,7 @@ public class FeatureSettings extends JPanel
   {
     if (fr.setFeaturePriority(data, visibleNew))
     {
-      af.alignPanel.paintAlignment(true);
+      af.alignPanel.paintAlignment(true, true);
     }
   }
 
@@ -1237,8 +1243,11 @@ public class FeatureSettings extends JPanel
       @Override
       public void stateChanged(ChangeEvent evt)
       {
-        fr.setTransparency((100 - transparency.getValue()) / 100f);
-        af.alignPanel.paintAlignment(true);
+        if (!inConstruction)
+        {
+          fr.setTransparency((100 - transparency.getValue()) / 100f);
+          af.alignPanel.paintAlignment(true,true);
+        }
       }
     });
 
index 6cddcca..80ac189 100755 (executable)
@@ -183,7 +183,7 @@ public class FontChooser extends GFontChooser
   {
     ap.av.antiAlias = smoothFont.isSelected();
     ap.getAnnotationPanel().image = null;
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, false);
     if (ap.av.getCodingComplement() != null && ap.av.isProteinFontAsCdna())
     {
       ((AlignViewport) ap.av
@@ -235,7 +235,7 @@ public class FontChooser extends GFontChooser
       ap.av.setScaleProteinAsCdna(oldProteinScale);
       ap.av.setProteinFontAsCdna(oldMirrorFont);
       ap.av.antiAlias = oldSmoothFont;
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, false);
 
       if (scaleAsCdna.isVisible() && scaleAsCdna.isEnabled())
       {
index f0aefb1..35fd1b4 100755 (executable)
@@ -138,7 +138,7 @@ public class IdPanel extends JPanel
     }
 
     lastid = seq;
-    alignPanel.paintAlignment(false);
+    alignPanel.paintAlignment(false, false);
   }
 
   /**
@@ -313,7 +313,7 @@ public class IdPanel extends JPanel
 
     av.isSelectionGroupChanged(true);
 
-    alignPanel.paintAlignment(false);
+    alignPanel.paintAlignment(false, false);
   }
 
   /**
@@ -508,7 +508,7 @@ public class IdPanel extends JPanel
           running = false;
         }
 
-        alignPanel.paintAlignment(false);
+        alignPanel.paintAlignment(false, false);
 
         try
         {
index 3c4107f..8400543 100755 (executable)
@@ -75,6 +75,7 @@ public class IdwidthAdjuster extends JPanel
    * @param evt
    *          DOCUMENT ME!
    */
+  @Override
   public void mousePressed(MouseEvent evt)
   {
     oldX = evt.getX();
@@ -86,6 +87,7 @@ public class IdwidthAdjuster extends JPanel
    * @param evt
    *          DOCUMENT ME!
    */
+  @Override
   public void mouseReleased(MouseEvent evt)
   {
     active = false;
@@ -112,6 +114,7 @@ public class IdwidthAdjuster extends JPanel
    * @param evt
    *          DOCUMENT ME!
    */
+  @Override
   public void mouseEntered(MouseEvent evt)
   {
     active = true;
@@ -124,6 +127,7 @@ public class IdwidthAdjuster extends JPanel
    * @param evt
    *          DOCUMENT ME!
    */
+  @Override
   public void mouseExited(MouseEvent evt)
   {
     active = false;
@@ -136,6 +140,7 @@ public class IdwidthAdjuster extends JPanel
    * @param evt
    *          DOCUMENT ME!
    */
+  @Override
   public void mouseDragged(MouseEvent evt)
   {
     active = true;
@@ -149,7 +154,7 @@ public class IdwidthAdjuster extends JPanel
     {
       viewport.setIdWidth(newWidth);
 
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, false);
     }
 
     oldX = evt.getX();
@@ -161,6 +166,7 @@ public class IdwidthAdjuster extends JPanel
    * @param evt
    *          DOCUMENT ME!
    */
+  @Override
   public void mouseMoved(MouseEvent evt)
   {
   }
@@ -171,6 +177,7 @@ public class IdwidthAdjuster extends JPanel
    * @param evt
    *          DOCUMENT ME!
    */
+  @Override
   public void mouseClicked(MouseEvent evt)
   {
   }
@@ -181,6 +188,7 @@ public class IdwidthAdjuster extends JPanel
    * @param g
    *          DOCUMENT ME!
    */
+  @Override
   public void paintComponent(Graphics g)
   {
     g.setColor(Color.white);
index 33c86bc..40f5764 100644 (file)
@@ -1842,7 +1842,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       }
 
       sequence.setName(dialog.getName().replace(' ', '_'));
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
     }
 
     sequence.setDescription(dialog.getDescription());
index 8bf2fba..c4390c0 100755 (executable)
@@ -54,7 +54,7 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
 
   AlignmentPanel ap;
 
-  Stack<CommandI> historyList = new Stack<CommandI>();
+  Stack<CommandI> historyList = new Stack<>();
 
   // simpler than synching with alignFrame.
 
@@ -194,7 +194,7 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
     }
 
     float value = slider.getValue();
-    List<SequenceI> redundantSequences = new ArrayList<SequenceI>();
+    List<SequenceI> redundantSequences = new ArrayList<>();
     for (int i = 0; i < redundancy.length; i++)
     {
       if (value <= redundancy[i])
@@ -295,7 +295,7 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
       af.updateEditMenuBar();
     }
 
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
 
     if (historyList.size() == 0)
     {
index 1db4051..e677769 100755 (executable)
@@ -168,7 +168,7 @@ public class ScalePanel extends JPanel
         {
           av.showColumn(reveal[0]);
           reveal = null;
-          ap.paintAlignment(true);
+          ap.paintAlignment(true, true);
           av.sendSelection();
         }
       });
@@ -184,7 +184,7 @@ public class ScalePanel extends JPanel
           {
             av.showAllHiddenColumns();
             reveal = null;
-            ap.paintAlignment(true);
+            ap.paintAlignment(true, true);
             av.sendSelection();
           }
         });
@@ -208,7 +208,7 @@ public class ScalePanel extends JPanel
             av.setSelectionGroup(null);
           }
 
-          ap.paintAlignment(true);
+          ap.paintAlignment(true, true);
           av.sendSelection();
         }
       });
@@ -260,7 +260,7 @@ public class ScalePanel extends JPanel
       sg.setEndRes(max);
     }
     av.setSelectionGroup(sg);
-    ap.paintAlignment(false);
+    ap.paintAlignment(false, false);
     av.sendSelection();
   }
 
@@ -297,7 +297,7 @@ public class ScalePanel extends JPanel
       }
       else
       {
-        ap.paintAlignment(false);
+        ap.paintAlignment(false, false);
       }
       return;
     }
@@ -316,7 +316,7 @@ public class ScalePanel extends JPanel
       }
     }
     stretchingGroup = false;
-    ap.paintAlignment(false);
+    ap.paintAlignment(false, false);
     av.sendSelection();
   }
 
@@ -346,7 +346,7 @@ public class ScalePanel extends JPanel
     {
       stretchingGroup = true;
       cs.stretchGroup(res, sg, min, max);
-      ap.paintAlignment(false);
+      ap.paintAlignment(false, false);
     }
   }
 
index 4e896a0..2a9c704 100755 (executable)
@@ -27,6 +27,7 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
+import jalview.util.Comparison;
 import jalview.viewmodel.ViewportListenerI;
 import jalview.viewmodel.ViewportRanges;
 
@@ -46,53 +47,56 @@ import java.util.List;
 import javax.swing.JComponent;
 
 /**
- * DOCUMENT ME!
+ * The Swing component on which the alignment sequences, and annotations (if
+ * shown), are drawn. This includes scales above, left and right (if shown) in
+ * Wrapped mode, but not the scale above in Unwrapped mode.
  * 
- * @author $author$
- * @version $Revision$
  */
 public class SeqCanvas extends JComponent implements ViewportListenerI
 {
-  private static String ZEROS = "0000000000";
+  private static final String ZEROS = "0000000000";
 
   final FeatureRenderer fr;
 
-  final SequenceRenderer seqRdr;
-
   BufferedImage img;
 
-  Graphics2D gg;
-
   AlignViewport av;
 
-  boolean fastPaint = false;
+  int cursorX = 0;
 
-  int labelWidthWest;
+  int cursorY = 0;
 
-  int labelWidthEast;
+  private final SequenceRenderer seqRdr;
 
-  int cursorX = 0;
+  private boolean fastPaint = false;
 
-  int cursorY = 0;
+  private boolean fastpainting = false;
 
-  int charHeight = 0;
+  private AnnotationPanel annotations;
 
-  int charWidth = 0;
+  /*
+   * measurements for drawing a wrapped alignment
+   */
+  private int labelWidthEast; // label right width in pixels if shown
+
+  private int labelWidthWest; // label left width in pixels if shown
 
-  boolean fastpainting = false;
+  private int wrappedSpaceAboveAlignment; // gap between widths
 
-  AnnotationPanel annotations;
+  private int wrappedRepeatHeightPx; // height in pixels of wrapped width
+
+  private int wrappedVisibleWidths; // number of wrapped widths displayed
+
+  private Graphics2D gg;
 
   /**
    * Creates a new SeqCanvas object.
    * 
-   * @param av
-   *          DOCUMENT ME!
+   * @param ap
    */
   public SeqCanvas(AlignmentPanel ap)
   {
     this.av = ap.av;
-    updateViewport();
     fr = new FeatureRenderer(ap);
     seqRdr = new SequenceRenderer(av);
     setLayout(new BorderLayout());
@@ -112,29 +116,36 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     return fr;
   }
 
-  private void updateViewport()
-  {
-    charHeight = av.getCharHeight();
-    charWidth = av.getCharWidth();
-  }
-
   /**
-   * DOCUMENT ME!
+   * Draws the scale above a region of a wrapped alignment, consisting of a
+   * column number every major interval (10 columns).
    * 
    * @param g
-   *          DOCUMENT ME!
+   *          the graphics context to draw on, positioned at the start (bottom
+   *          left) of the line on which to draw any scale marks
    * @param startx
-   *          DOCUMENT ME!
+   *          start alignment column (0..)
    * @param endx
-   *          DOCUMENT ME!
+   *          end alignment column (0..)
    * @param ypos
-   *          DOCUMENT ME!
+   *          y offset to draw at
    */
   private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
   {
-    updateViewport();
-    for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
-            endx))
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
+    /*
+     * white fill the scale space (for the fastPaint case)
+     */
+    g.setColor(Color.white);
+    g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
+            charHeight * 3 / 2 + 2);
+    g.setColor(Color.black);
+
+    List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
+            endx);
+    for (ScaleMark mark : marks)
     {
       int mpos = mark.column; // (i - startx - 1)
       if (mpos < 0)
@@ -149,137 +160,119 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         {
           g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
         }
-        g.drawLine((mpos * charWidth) + (charWidth / 2),
-                (ypos + 2) - (charHeight / 2),
-                (mpos * charWidth) + (charWidth / 2), ypos - 2);
+
+        /*
+         * draw a tick mark below the column number, centred on the column;
+         * height of tick mark is 4 pixels less than half a character
+         */
+        int xpos = (mpos * charWidth) + (charWidth / 2);
+        g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
       }
     }
   }
 
   /**
-   * DOCUMENT ME!
+   * Draw the scale to the left or right of a wrapped alignment
    * 
    * @param g
-   *          DOCUMENT ME!
+   *          graphics context, positioned at the start of the scale to be drawn
    * @param startx
-   *          DOCUMENT ME!
+   *          first column of wrapped width (0.. excluding any hidden columns)
    * @param endx
-   *          DOCUMENT ME!
+   *          last column of wrapped width (0.. excluding any hidden columns)
    * @param ypos
-   *          DOCUMENT ME!
+   *          vertical offset at which to begin the scale
+   * @param left
+   *          if true, scale is left of residues, if false, scale is right
    */
-  void drawWestScale(Graphics g, int startx, int endx, int ypos)
+  void drawVerticalScale(Graphics g, final int startx, final int endx,
+          final int ypos, final boolean left)
   {
-    FontMetrics fm = getFontMetrics(av.getFont());
-    ypos += charHeight;
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
 
-    if (av.hasHiddenColumns())
-    {
-      startx = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(startx);
-      endx = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(endx);
-    }
+    int yPos = ypos + charHeight;
+    int startX = startx;
+    int endX = endx;
 
-    int maxwidth = av.getAlignment().getWidth();
     if (av.hasHiddenColumns())
     {
-      maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
+      HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
+      startX = hiddenColumns.adjustForHiddenColumns(startx);
+      endX = hiddenColumns.adjustForHiddenColumns(endx);
     }
+    FontMetrics fm = getFontMetrics(av.getFont());
 
-    // WEST SCALE
     for (int i = 0; i < av.getAlignment().getHeight(); i++)
     {
       SequenceI seq = av.getAlignment().getSequenceAt(i);
-      int index = startx;
-      int value = -1;
 
-      while (index < endx)
+      /*
+       * find sequence position of first non-gapped position -
+       * to the right if scale left, to the left if scale right
+       */
+      int index = left ? startX : endX;
+      int value = -1;
+      while (index >= startX && index <= endX)
       {
-        if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
+        if (!Comparison.isGap(seq.getCharAt(index)))
+        {
+          value = seq.findPosition(index);
+          break;
+        }
+        if (left)
         {
           index++;
-
-          continue;
         }
-
-        value = av.getAlignment().getSequenceAt(i).findPosition(index);
-
-        break;
-      }
-
-      if (value != -1)
-      {
-        int x = labelWidthWest - fm.stringWidth(String.valueOf(value))
-                - charWidth / 2;
-        g.drawString(value + "", x,
-                (ypos + (i * charHeight)) - (charHeight / 5));
-      }
-    }
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param g
-   *          DOCUMENT ME!
-   * @param startx
-   *          DOCUMENT ME!
-   * @param endx
-   *          DOCUMENT ME!
-   * @param ypos
-   *          DOCUMENT ME!
-   */
-  void drawEastScale(Graphics g, int startx, int endx, int ypos)
-  {
-    ypos += charHeight;
-
-    if (av.hasHiddenColumns())
-    {
-      endx = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(endx);
-    }
-
-    SequenceI seq;
-    // EAST SCALE
-    for (int i = 0; i < av.getAlignment().getHeight(); i++)
-    {
-      seq = av.getAlignment().getSequenceAt(i);
-      int index = endx;
-      int value = -1;
-
-      while (index > startx)
-      {
-        if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
+        else
         {
           index--;
-
-          continue;
         }
-
-        value = seq.findPosition(index);
-
-        break;
       }
 
+      /*
+       * white fill the space for the scale
+       */
+      g.setColor(Color.white);
+      int y = (yPos + (i * charHeight)) - (charHeight / 5);
+      // fillRect origin is top left of rectangle
+      g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
+              charHeight + 1);
+
       if (value != -1)
       {
-        g.drawString(String.valueOf(value), 0,
-                (ypos + (i * charHeight)) - (charHeight / 5));
+        /*
+         * draw scale value, right justified within its width less half a
+         * character width padding on the right
+         */
+        int labelSpace = left ? labelWidthWest : labelWidthEast;
+        labelSpace -= charWidth / 2; // leave space to the right
+        String valueAsString = String.valueOf(value);
+        int labelLength = fm.stringWidth(valueAsString);
+        int xOffset = labelSpace - labelLength;
+        g.setColor(Color.black);
+        g.drawString(valueAsString, xOffset, y);
       }
     }
   }
 
-
   /**
-   * need to make this thread safe move alignment rendering in response to
-   * slider adjustment
+   * Does a fast paint of an alignment in response to a scroll. Most of the
+   * visible region is simply copied and shifted, and then any newly visible
+   * columns or rows are drawn. The scroll may be horizontal or vertical, but
+   * not both at once. Scrolling may be the result of
+   * <ul>
+   * <li>dragging a scroll bar</li>
+   * <li>clicking in the scroll bar</li>
+   * <li>scrolling by trackpad, middle mouse button, or other device</li>
+   * <li>by moving the box in the Overview window</li>
+   * <li>programmatically to make a highlighted position visible</li>
+   * </ul>
    * 
    * @param horizontal
-   *          shift along
+   *          columns to shift right (positive) or left (negative)
    * @param vertical
-   *          shift up or down in repaint
+   *          rows to shift down (positive) or up (negative)
    */
   public void fastPaint(int horizontal, int vertical)
   {
@@ -289,15 +282,19 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     }
     fastpainting = true;
     fastPaint = true;
-    updateViewport();
 
-    ViewportRanges ranges = av.getRanges();
-    int startRes = ranges.getStartRes();
-    int endRes = ranges.getEndRes();
-    int startSeq = ranges.getStartSeq();
-    int endSeq = ranges.getEndSeq();
-    int transX = 0;
-    int transY = 0;
+    try
+    {
+      int charHeight = av.getCharHeight();
+      int charWidth = av.getCharWidth();
+    
+      ViewportRanges ranges = av.getRanges();
+      int startRes = ranges.getStartRes();
+      int endRes = ranges.getEndRes();
+      int startSeq = ranges.getStartSeq();
+      int endSeq = ranges.getEndSeq();
+      int transX = 0;
+      int transY = 0;
 
     gg.copyArea(horizontal * charWidth, vertical * charHeight,
             img.getWidth(), img.getHeight(), -horizontal * charWidth,
@@ -340,15 +337,19 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     gg.translate(-transX, -transY);
 
     repaint();
-    fastpainting = false;
+    } finally
+    {
+      fastpainting = false;
+    }
   }
 
   @Override
   public void paintComponent(Graphics g)
   {
-    super.paintComponent(g);
-
-    updateViewport();
+    super.paintComponent(g);    
+    
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
 
     ViewportRanges ranges = av.getRanges();
 
@@ -411,7 +412,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       g.drawImage(lcimg, 0, 0, this);
     }
   }
-
+  
   /**
    * Draw an alignment panel for printing
    * 
@@ -519,6 +520,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   {
     BufferedImage lcimg = null;
 
+    int charWidth = av.getCharWidth();
+    int charHeight = av.getCharHeight();
+    
     int width = getWidth();
     int height = getHeight();
 
@@ -558,29 +562,30 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    */
   public int getWrappedCanvasWidth(int canvasWidth)
   {
-    FontMetrics fm = getFontMetrics(av.getFont());
+    int charWidth = av.getCharWidth();
 
-    labelWidthEast = 0;
-    labelWidthWest = 0;
+    FontMetrics fm = getFontMetrics(av.getFont());
 
-    if (av.getScaleRightWrapped())
+    int labelWidth = 0;
+    
+    if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
     {
-      labelWidthEast = getLabelWidth(fm);
+      labelWidth = getLabelWidth(fm);
     }
 
-    if (av.getScaleLeftWrapped())
-    {
-      labelWidthWest = labelWidthEast > 0 ? labelWidthEast
-              : getLabelWidth(fm);
-    }
+    labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
+
+    labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
 
     return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
   }
 
   /**
-   * Returns a pixel width suitable for showing the largest sequence coordinate
-   * (end position) in the alignment. Returns 2 plus the number of decimal
-   * digits to be shown (3 for 1-10, 4 for 11-99 etc).
+   * Returns a pixel width sufficient to show the largest sequence coordinate
+   * (end position) in the alignment, calculated as the FontMetrics width of
+   * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
+   * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
+   * half a character width space on either side.
    * 
    * @param fm
    * @return
@@ -598,160 +603,305 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
     }
 
-    int length = 2;
+    int length = 0;
     for (int i = maxWidth; i > 0; i /= 10)
     {
       length++;
     }
 
-    return fm.stringWidth(ZEROS.substring(0, length));
+    return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
   }
 
   /**
-   * DOCUMENT ME!
+   * Draws as many widths of a wrapped alignment as can fit in the visible
+   * window
    * 
    * @param g
-   *          DOCUMENT ME!
    * @param canvasWidth
-   *          DOCUMENT ME!
+   *          available width in pixels
    * @param canvasHeight
-   *          DOCUMENT ME!
-   * @param startRes
-   *          DOCUMENT ME!
+   *          available height in pixels
+   * @param startColumn
+   *          the first column (0...) of the alignment to draw
    */
-  private void drawWrappedPanel(Graphics g, int canvasWidth,
-          int canvasHeight, int startRes)
+  public void drawWrappedPanel(Graphics g, int canvasWidth,
+          int canvasHeight, final int startColumn)
   {
-    updateViewport();
-    AlignmentI al = av.getAlignment();
+    int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
 
-    int labelWidth = 0;
-    if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
+    av.setWrappedWidth(wrappedWidthInResidues);
+
+    ViewportRanges ranges = av.getRanges();
+    ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
+
+    /*
+     * draw one width at a time (including any scales or annotation shown),
+     * until we have run out of either alignment or vertical space available
+     */
+    int ypos = wrappedSpaceAboveAlignment;
+    int maxWidth = ranges.getVisibleAlignmentWidth();
+
+    int start = startColumn;
+    int currentWidth = 0;
+    while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
     {
-      FontMetrics fm = getFontMetrics(av.getFont());
-      labelWidth = getLabelWidth(fm);
+      int endColumn = Math
+              .min(maxWidth, start + wrappedWidthInResidues - 1);
+      drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
+      ypos += wrappedRepeatHeightPx;
+      start += wrappedWidthInResidues;
+      currentWidth++;
     }
 
-    labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
-    labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
+    drawWrappedDecorators(g, startColumn);
+  }
 
-    int hgap = charHeight;
-    if (av.getScaleAboveWrapped())
+  /**
+   * Calculates and saves values needed when rendering a wrapped alignment.
+   * These depend on many factors, including
+   * <ul>
+   * <li>canvas width and height</li>
+   * <li>number of visible sequences, and height of annotations if shown</li>
+   * <li>font and character width</li>
+   * <li>whether scales are shown left, right or above the alignment</li>
+   * </ul>
+   * 
+   * @param canvasWidth
+   * @param canvasHeight
+   * @return the number of residue columns in each width
+   */
+  protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
+  {
+    int charHeight = av.getCharHeight();
+
+    /*
+     * vertical space in pixels between wrapped widths of alignment
+     * - one character height, or two if scale above is drawn
+     */
+    wrappedSpaceAboveAlignment = charHeight
+            * (av.getScaleAboveWrapped() ? 2 : 1);
+
+    /*
+     * height in pixels of the wrapped widths
+     */
+    wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
+    // add sequences
+    wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
+            * charHeight;
+    // add annotations panel height if shown
+    wrappedRepeatHeightPx += getAnnotationHeight();
+
+    /*
+     * number of visible widths (the last one may be part height),
+     * ensuring a part height includes at least one sequence
+     */
+    ViewportRanges ranges = av.getRanges();
+    wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
+    int remainder = canvasHeight % wrappedRepeatHeightPx;
+    if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
     {
-      hgap += charHeight;
+      wrappedVisibleWidths++;
     }
 
-    int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
-    int cHeight = av.getAlignment().getHeight() * charHeight;
+    /*
+     * compute width in residues; this also sets East and West label widths
+     */
+    int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
 
-    av.setWrappedWidth(cWidth);
+    /*
+     *  limit visibleWidths to not exceed width of alignment
+     */
+    int xMax = ranges.getVisibleAlignmentWidth();
+    int startToEnd = xMax - ranges.getStartRes();
+    int maxWidths = startToEnd / wrappedWidthInResidues;
+    if (startToEnd % wrappedWidthInResidues > 0)
+    {
+      maxWidths++;
+    }
+    wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
 
-    av.getRanges().setViewportStartAndWidth(startRes, cWidth);
+    return wrappedWidthInResidues;
+  }
 
-    int endx;
-    int ypos = hgap;
-    int maxwidth = av.getAlignment().getWidth();
+  /**
+   * Draws one width of a wrapped alignment, including sequences and
+   * annnotations, if shown, but not scales or hidden column markers
+   * 
+   * @param g
+   * @param ypos
+   * @param startColumn
+   * @param endColumn
+   * @param canvasHeight
+   */
+  protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
+          int endColumn, int canvasHeight)
+  {
+    ViewportRanges ranges = av.getRanges();
+    int viewportWidth = ranges.getViewportWidth();
 
-    if (av.hasHiddenColumns())
+    int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
+
+    /*
+     * move right before drawing by the width of the scale left (if any)
+     * plus column offset from left margin (usually zero, but may be non-zero
+     * when fast painting is drawing just a few columns)
+     */
+    int charWidth = av.getCharWidth();
+    int xOffset = labelWidthWest
+            + ((startColumn - ranges.getStartRes()) % viewportWidth)
+            * charWidth;
+    g.translate(xOffset, 0);
+
+    // When printing we have an extra clipped region,
+    // the Printable page which we need to account for here
+    Shape clip = g.getClip();
+
+    if (clip == null)
     {
-      maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth);
+      g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
+    }
+    else
+    {
+      g.setClip(0, (int) clip.getBounds().getY(),
+              viewportWidth * charWidth, (int) clip.getBounds().getHeight());
     }
 
-    int annotationHeight = getAnnotationHeight();
+    /*
+     * white fill the region to be drawn (so incremental fast paint doesn't
+     * scribble over an existing image)
+     */
+    gg.setColor(Color.white);
+    gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
+            wrappedRepeatHeightPx);
 
-    while ((ypos <= canvasHeight) && (startRes < maxwidth))
-    {
-      endx = startRes + cWidth - 1;
+    drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
+            ypos);
 
-      if (endx > maxwidth)
+    int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
+
+    if (av.isShowAnnotation())
+    {
+      g.translate(0, cHeight + ypos + 3);
+      if (annotations == null)
       {
-        endx = maxwidth;
+        annotations = new AnnotationPanel(av);
       }
 
-      g.setFont(av.getFont());
-      g.setColor(Color.black);
+      annotations.renderer.drawComponent(annotations, av, g, -1,
+              startColumn, endx + 1);
+      g.translate(0, -cHeight - ypos - 3);
+    }
+    g.setClip(clip);
+    g.translate(-xOffset, 0);
+  }
+
+  /**
+   * Draws scales left, right and above (if shown), and any hidden column
+   * markers, on all widths of the wrapped alignment
+   * 
+   * @param g
+   * @param startColumn
+   */
+  protected void drawWrappedDecorators(Graphics g, final int startColumn)
+  {
+    int charWidth = av.getCharWidth();
+
+    g.setFont(av.getFont());
+    g.setColor(Color.black);
+
+    int ypos = wrappedSpaceAboveAlignment;
+    ViewportRanges ranges = av.getRanges();
+    int viewportWidth = ranges.getViewportWidth();
+    int maxWidth = ranges.getVisibleAlignmentWidth();
+    int widthsDrawn = 0;
+    int startCol = startColumn;
+
+    while (widthsDrawn < wrappedVisibleWidths)
+    {
+      int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
 
       if (av.getScaleLeftWrapped())
       {
-        drawWestScale(g, startRes, endx, ypos);
+        drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
       }
 
       if (av.getScaleRightWrapped())
       {
-        g.translate(canvasWidth - labelWidthEast, 0);
-        drawEastScale(g, startRes, endx, ypos);
-        g.translate(-(canvasWidth - labelWidthEast), 0);
+        int x = labelWidthWest + viewportWidth * charWidth;
+        g.translate(x, 0);
+        drawVerticalScale(g, startCol, endColumn, ypos, false);
+        g.translate(-x, 0);
       }
 
+      /*
+       * white fill region of scale above and hidden column markers
+       * (to support incremental fast paint of image)
+       */
+      g.translate(labelWidthWest, 0);
+      g.setColor(Color.white);
+      g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
+              * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
+      g.setColor(Color.black);
+      g.translate(-labelWidthWest, 0);
+
       g.translate(labelWidthWest, 0);
 
       if (av.getScaleAboveWrapped())
       {
-        drawNorthScale(g, startRes, endx, ypos);
+        drawNorthScale(g, startCol, endColumn, ypos);
       }
 
       if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
       {
-        g.setColor(Color.blue);
-        int res;
-        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-        List<Integer> positions = hidden.findHiddenRegionPositions();
-        for (int pos : positions)
-        {
-          res = pos - startRes;
-
-          if (res < 0 || res > endx - startRes)
-          {
-            continue;
-          }
-
-          gg.fillPolygon(
-                  new int[]
-                  { res * charWidth - charHeight / 4,
-                      res * charWidth + charHeight / 4, res * charWidth },
-                  new int[]
-                  { ypos - (charHeight / 2), ypos - (charHeight / 2),
-                      ypos - (charHeight / 2) + 8 },
-                  3);
-
-        }
+        drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
       }
 
-      // When printing we have an extra clipped region,
-      // the Printable page which we need to account for here
-      Shape clip = g.getClip();
+      g.translate(-labelWidthWest, 0);
 
-      if (clip == null)
-      {
-        g.setClip(0, 0, cWidth * charWidth, canvasHeight);
-      }
-      else
-      {
-        g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
-                (int) clip.getBounds().getHeight());
-      }
+      ypos += wrappedRepeatHeightPx;
+      startCol += viewportWidth;
+      widthsDrawn++;
+    }
+  }
+
+  /**
+   * Draws markers (triangles) above hidden column positions between startColumn
+   * and endColumn.
+   * 
+   * @param g
+   * @param ypos
+   * @param startColumn
+   * @param endColumn
+   */
+  protected void drawHiddenColumnMarkers(Graphics g, int ypos,
+          int startColumn, int endColumn)
+  {
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
 
-      drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
+    g.setColor(Color.blue);
+    HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+    List<Integer> positions = hidden.findHiddenRegionPositions();
+    for (int pos : positions)
+    {
+      int res = pos - startColumn;
 
-      if (av.isShowAnnotation())
+      if (res < 0 || res > endColumn - startColumn + 1)
       {
-        g.translate(0, cHeight + ypos + 3);
-        if (annotations == null)
-        {
-          annotations = new AnnotationPanel(av);
-        }
-
-        annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
-                endx + 1);
-        g.translate(0, -cHeight - ypos - 3);
+        continue;
       }
-      g.setClip(clip);
-      g.translate(-labelWidthWest, 0);
-
-      ypos += cHeight + annotationHeight + hgap;
 
-      startRes += cWidth;
+      /*
+       * draw a downward-pointing triangle at the hidden columns location
+       * (before the following visible column)
+       */
+      int xMiddle = res * charWidth;
+      int[] xPoints = new int[] { xMiddle - charHeight / 4,
+          xMiddle + charHeight / 4, xMiddle };
+      int yTop = ypos - (charHeight / 2);
+      int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
+      g.fillPolygon(xPoints, yPoints, 3);
     }
   }
 
@@ -762,6 +912,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
           int canvasWidth,
           int canvasHeight, int startRes)
   {
+       int charHeight = av.getCharHeight();
+       int charWidth = av.getCharWidth();
+         
     // height gap above each panel
     int hgap = charHeight;
     if (av.getScaleAboveWrapped())
@@ -834,22 +987,24 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    * marker.
    * 
    * @param g1
-   *          Graphics object to draw with
+   *          the graphics context, positioned at the first residue to be drawn
    * @param startRes
-   *          offset of the first column in the visible region (0..)
+   *          offset of the first column to draw (0..)
    * @param endRes
-   *          offset of the last column in the visible region (0..)
+   *          offset of the last column to draw (0..)
    * @param startSeq
-   *          offset of the first sequence in the visible region (0..)
+   *          offset of the first sequence to draw (0..)
    * @param endSeq
-   *          offset of the last sequence in the visible region (0..)
+   *          offset of the last sequence to draw (0..)
    * @param yOffset
    *          vertical offset at which to draw (for wrapped alignments)
    */
   public void drawPanel(Graphics g1, final int startRes, final int endRes,
           final int startSeq, final int endSeq, final int yOffset)
   {
-    updateViewport();
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
     if (!av.hasHiddenColumns())
     {
       draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
@@ -939,6 +1094,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   private void draw(Graphics g, int startRes, int endRes, int startSeq,
           int endSeq, int offset)
   {
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
     g.setFont(av.getFont());
     seqRdr.prepare(g, av.isRenderGaps());
 
@@ -1118,6 +1276,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
           int startRes, int endRes, int startSeq, int endSeq, int offset)
   {
+       int charWidth = av.getCharWidth();
+         
     if (!av.hasHiddenColumns())
     {
       drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
@@ -1179,6 +1339,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
           int startRes, int endRes, int startSeq, int endSeq,
           int verticalOffset)
   {
+       int charHeight = av.getCharHeight();
+       int charWidth = av.getCharWidth();
+         
     int visWidth = (endRes - startRes + 1) * charWidth;
 
     int oldY = -1;
@@ -1347,14 +1510,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       return false;
     }
     boolean wrapped = av.getWrapAlignment();
-
     try
     {
       fastPaint = !noFastPaint;
       fastpainting = fastPaint;
 
-      updateViewport();
-
       /*
        * to avoid redrawing the whole visible region, we instead
        * redraw just the minimal regions to remove previous highlights
@@ -1498,52 +1658,303 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     {
       fastPaint = true;
       repaint();
+      return;
     }
-    else if (av.getWrapAlignment())
+
+    int scrollX = 0;
+    if (eventName.equals(ViewportRanges.STARTRES))
     {
-      if (eventName.equals(ViewportRanges.STARTRES))
+      // Make sure we're not trying to draw a panel
+      // larger than the visible window
+      ViewportRanges vpRanges = av.getRanges();
+      scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
+      int range = vpRanges.getViewportWidth();
+      if (scrollX > range)
       {
-        repaint();
+        scrollX = range;
+      }
+      else if (scrollX < -range)
+      {
+        scrollX = -range;
       }
     }
-    else
+
+    // Both scrolling and resizing change viewport ranges: scrolling changes
+    // both start and end points, but resize only changes end values.
+    // Here we only want to fastpaint on a scroll, with resize using a normal
+    // paint, so scroll events are identified as changes to the horizontal or
+    // vertical start value.
+
+    // scroll - startres and endres both change
+    if (eventName.equals(ViewportRanges.STARTRES))
     {
-      int scrollX = 0;
-      if (eventName.equals(ViewportRanges.STARTRES))
+      if (av.getWrapAlignment())
       {
-        // Make sure we're not trying to draw a panel
-        // larger than the visible window
-        ViewportRanges vpRanges = av.getRanges();
-        scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
-        int range = vpRanges.getEndRes() - vpRanges.getStartRes();
-        if (scrollX > range)
-        {
-          scrollX = range;
-        }
-        else if (scrollX < -range)
-        {
-          scrollX = -range;
-        }
+        fastPaintWrapped(scrollX);
       }
-
-      // Both scrolling and resizing change viewport ranges: scrolling changes
-      // both start and end points, but resize only changes end values.
-      // Here we only want to fastpaint on a scroll, with resize using a normal
-      // paint, so scroll events are identified as changes to the horizontal or
-      // vertical start value.
-      if (eventName.equals(ViewportRanges.STARTRES))
+      else
       {
-        // scroll - startres and endres both change
         fastPaint(scrollX, 0);
       }
-      else if (eventName.equals(ViewportRanges.STARTSEQ))
+    }
+    else if (eventName.equals(ViewportRanges.STARTSEQ))
+    {
+      // scroll
+      fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+    }
+  }
+
+  /**
+   * Does a minimal update of the image for a scroll movement. This method
+   * handles scroll movements of up to one width of the wrapped alignment (one
+   * click in the vertical scrollbar). Larger movements (for example after a
+   * scroll to highlight a mapped position) trigger a full redraw instead.
+   * 
+   * @param scrollX
+   *          number of positions scrolled (right if positive, left if negative)
+   */
+  protected void fastPaintWrapped(int scrollX)
+  {
+    ViewportRanges ranges = av.getRanges();
+
+    if (Math.abs(scrollX) > ranges.getViewportWidth())
+    {
+      /*
+       * shift of more than one view width is 
+       * overcomplicated to handle in this method
+       */
+      fastPaint = false;
+      repaint();
+      return;
+    }
+
+    if (fastpainting || gg == null)
+    {
+      return;
+    }
+
+    fastPaint = true;
+    fastpainting = true;
+
+    try
+    {
+      calculateWrappedGeometry(getWidth(), getHeight());
+
+      /*
+       * relocate the regions of the alignment that are still visible
+       */
+      shiftWrappedAlignment(-scrollX);
+
+      /*
+       * add new columns (sequence, annotation)
+       * - at top left if scrollX < 0 
+       * - at right of last two widths if scrollX > 0
+       */
+      if (scrollX < 0)
+      {
+        int startRes = ranges.getStartRes();
+        drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
+                - scrollX - 1, getHeight());
+      }
+      else
+      {
+        fastPaintWrappedAddRight(scrollX);
+      }
+
+      /*
+       * draw all scales (if  shown) and hidden column markers
+       */
+      drawWrappedDecorators(gg, ranges.getStartRes());
+
+      repaint();
+    } finally
+    {
+      fastpainting = false;
+    }
+  }
+
+  /**
+   * Draws the specified number of columns at the 'end' (bottom right) of a
+   * wrapped alignment view, including sequences and annotations if shown, but
+   * not scales. Also draws the same number of columns at the right hand end of
+   * the second last width shown, if the last width is not full height (so
+   * cannot simply be copied from the graphics image).
+   * 
+   * @param columns
+   */
+  protected void fastPaintWrappedAddRight(int columns)
+  {
+    if (columns == 0)
+    {
+      return;
+    }
+
+    ViewportRanges ranges = av.getRanges();
+    int viewportWidth = ranges.getViewportWidth();
+    int charWidth = av.getCharWidth();
+
+    /**
+     * draw full height alignment in the second last row, last columns, if the
+     * last row was not full height
+     */
+    int visibleWidths = wrappedVisibleWidths;
+    int canvasHeight = getHeight();
+    boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
+
+    if (lastWidthPartHeight)
+    {
+      int widthsAbove = Math.max(0, visibleWidths - 2);
+      int ypos = wrappedRepeatHeightPx * widthsAbove
+              + wrappedSpaceAboveAlignment;
+      int endRes = ranges.getEndRes();
+      endRes += widthsAbove * viewportWidth;
+      int startRes = endRes - columns;
+      int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
+              * charWidth;
+
+      /*
+       * white fill first to erase annotations
+       */
+      gg.translate(xOffset, 0);
+      gg.setColor(Color.white);
+      gg.fillRect(labelWidthWest, ypos,
+              (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
+      gg.translate(-xOffset, 0);
+
+      drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
+    }
+
+    /*
+     * draw newly visible columns in last wrapped width (none if we
+     * have reached the end of the alignment)
+     * y-offset for drawing last width is height of widths above,
+     * plus one gap row
+     */
+    int widthsAbove = visibleWidths - 1;
+    int ypos = wrappedRepeatHeightPx * widthsAbove
+            + wrappedSpaceAboveAlignment;
+    int endRes = ranges.getEndRes();
+    endRes += widthsAbove * viewportWidth;
+    int startRes = endRes - columns + 1;
+
+    /*
+     * white fill first to erase annotations
+     */
+    int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
+            * charWidth;
+    gg.translate(xOffset, 0);
+    gg.setColor(Color.white);
+    int width = viewportWidth * charWidth - xOffset;
+    gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
+    gg.translate(-xOffset, 0);
+
+    gg.setFont(av.getFont());
+    gg.setColor(Color.black);
+
+    if (startRes < ranges.getVisibleAlignmentWidth())
+    {
+      drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
+    }
+
+    /*
+     * and finally, white fill any space below the visible alignment
+     */
+    int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
+    if (heightBelow > 0)
+    {
+      gg.setColor(Color.white);
+      gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
+    }
+  }
+
+  /**
+   * Shifts the visible alignment by the specified number of columns - left if
+   * negative, right if positive. Copies and moves sequences and annotations (if
+   * shown). Scales, hidden column markers and any newly visible columns must be
+   * drawn separately.
+   * 
+   * @param positions
+   */
+  protected void shiftWrappedAlignment(int positions)
+  {
+    if (positions == 0)
+    {
+      return;
+    }
+    int charWidth = av.getCharWidth();
+
+    int canvasHeight = getHeight();
+    ViewportRanges ranges = av.getRanges();
+    int viewportWidth = ranges.getViewportWidth();
+    int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
+            * charWidth;
+    int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
+    int xMax = ranges.getVisibleAlignmentWidth();
+
+    if (positions > 0)
+    {
+      /*
+       * shift right (after scroll left)
+       * for each wrapped width (starting with the last), copy (width-positions) 
+       * columns from the left margin to the right margin, and copy positions 
+       * columns from the right margin of the row above (if any) to the 
+       * left margin of the current row
+       */
+
+      /*
+       * get y-offset of last wrapped width, first row of sequences
+       */
+      int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
+      y += wrappedSpaceAboveAlignment;
+      int copyFromLeftStart = labelWidthWest;
+      int copyFromRightStart = copyFromLeftStart + widthToCopy;
+
+      while (y >= 0)
+      {
+        gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
+                positions * charWidth, 0);
+        if (y > 0)
+        {
+          gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
+                  positions * charWidth, heightToCopy, -widthToCopy,
+                  wrappedRepeatHeightPx);
+        }
+
+        y -= wrappedRepeatHeightPx;
+      }
+    }
+    else
+    {
+      /*
+       * shift left (after scroll right)
+       * for each wrapped width (starting with the first), copy (width-positions) 
+       * columns from the right margin to the left margin, and copy positions 
+       * columns from the left margin of the row below (if any) to the 
+       * right margin of the current row
+       */
+      int xpos = av.getRanges().getStartRes();
+      int y = wrappedSpaceAboveAlignment;
+      int copyFromRightStart = labelWidthWest - positions * charWidth;
+
+      while (y < canvasHeight)
       {
-        // scroll
-        fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+        gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
+                positions * charWidth, 0);
+        if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
+                && (xpos + viewportWidth <= xMax))
+        {
+          gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
+                  * charWidth, heightToCopy, widthToCopy,
+                  -wrappedRepeatHeightPx);
+        }
+
+        y += wrappedRepeatHeightPx;
+        xpos += viewportWidth;
       }
     }
   }
 
+  
   /**
    * Redraws any positions in the search results in the visible region of a
    * wrapped alignment. Any highlights are drawn depending on the search results
@@ -1560,11 +1971,13 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     {
       return false;
     }
-  
+    int charHeight = av.getCharHeight();
+
     boolean matchFound = false;
 
+    calculateWrappedGeometry(getWidth(), getHeight());
     int wrappedWidth = av.getWrappedWidth();
-    int wrappedHeight = getRepeatHeightWrapped();
+    int wrappedHeight = wrappedRepeatHeightPx;
 
     ViewportRanges ranges = av.getRanges();
     int canvasHeight = getHeight();
@@ -1663,23 +2076,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   }
 
   /**
-   * Answers the height in pixels of a repeating section of the wrapped
-   * alignment, including space above, scale above if shown, sequences, and
-   * annotation panel if shown
+   * Answers the width in pixels of the left scale labels (0 if not shown)
    * 
    * @return
    */
-  protected int getRepeatHeightWrapped()
+  int getLabelWidthWest()
   {
-    // gap (and maybe scale) above
-    int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
-
-    // add sequences
-    repeatHeight += av.getRanges().getViewportHeight() * charHeight;
-
-    // add annotations panel height if shown
-    repeatHeight += getAnnotationHeight();
-
-    return repeatHeight;
+    return labelWidthWest;
   }
 }
index ad80a3e..282c810 100644 (file)
@@ -214,8 +214,8 @@ public class SeqPanel extends JPanel
               + hgap + seqCanvas.getAnnotationHeight();
 
       int y = evt.getY();
-      y -= hgap;
-      x = Math.max(0, x - seqCanvas.labelWidthWest);
+      y = Math.max(0, y - hgap);
+      x = Math.max(0, x - seqCanvas.getLabelWidthWest());
 
       int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
       if (cwidth < 1)
@@ -483,7 +483,7 @@ public class SeqPanel extends JPanel
       av.setSelectionGroup(sg);
     }
 
-    ap.paintAlignment(false);
+    ap.paintAlignment(false, false);
     av.sendSelection();
   }
 
@@ -851,11 +851,12 @@ public class SeqPanel extends JPanel
 
   /**
    * set when the current UI interaction has resulted in a change that requires
-   * overview shading to be recalculated. this could be changed to something
-   * more expressive that indicates what actually has changed, so selective
-   * redraws can be applied
+   * shading in overviews and structures to be recalculated. this could be
+   * changed to a something more expressive that indicates what actually has
+   * changed, so selective redraws can be applied (ie. only structures, only
+   * overview, etc)
    */
-  private boolean needOverviewUpdate = false; // TODO: refactor to avcontroller
+  private boolean updateOverviewAndStructs = false; // TODO: refactor to avcontroller
 
   /**
    * set if av.getSelectionGroup() refers to a group that is defined on the
@@ -1056,7 +1057,7 @@ public class SeqPanel extends JPanel
         }
         if (newWidth > 0)
         {
-          ap.paintAlignment(false);
+          ap.paintAlignment(false, false);
           if (copyChanges)
           {
             /*
@@ -1655,7 +1656,7 @@ public class SeqPanel extends JPanel
     final int res = findColumn(evt);
     final int seq = findSeq(evt);
     oldSeq = seq;
-    needOverviewUpdate = false;
+    updateOverviewAndStructs = false;
 
     startWrapBlock = wrappedBlock;
 
@@ -1809,7 +1810,7 @@ public class SeqPanel extends JPanel
     // always do this - annotation has own state
     // but defer colourscheme update until hidden sequences are passed in
     boolean vischange = stretchGroup.recalcConservation(true);
-    needOverviewUpdate |= vischange && av.isSelectionDefinedGroup()
+    updateOverviewAndStructs |= vischange && av.isSelectionDefinedGroup()
             && afterDrag;
     if (stretchGroup.cs != null)
     {
@@ -1829,8 +1830,10 @@ public class SeqPanel extends JPanel
       }
     }
     PaintRefresher.Refresh(this, av.getSequenceSetId());
-    ap.paintAlignment(needOverviewUpdate);
-    needOverviewUpdate = false;
+    // TODO: structure colours only need updating if stretchGroup used to or now
+    // does contain sequences with structure views
+    ap.paintAlignment(updateOverviewAndStructs, updateOverviewAndStructs);
+    updateOverviewAndStructs = false;
     changeEndRes = false;
     changeStartRes = false;
     stretchGroup = null;
@@ -1884,7 +1887,7 @@ public class SeqPanel extends JPanel
       if (res > (stretchGroup.getStartRes() - 1))
       {
         stretchGroup.setEndRes(res);
-        needOverviewUpdate |= av.isSelectionDefinedGroup();
+        updateOverviewAndStructs |= av.isSelectionDefinedGroup();
       }
     }
     else if (changeStartRes)
@@ -1892,7 +1895,7 @@ public class SeqPanel extends JPanel
       if (res < (stretchGroup.getEndRes() + 1))
       {
         stretchGroup.setStartRes(res);
-        needOverviewUpdate |= av.isSelectionDefinedGroup();
+        updateOverviewAndStructs |= av.isSelectionDefinedGroup();
       }
     }
 
@@ -1926,7 +1929,7 @@ public class SeqPanel extends JPanel
       if (stretchGroup.getSequences(null).contains(nextSeq))
       {
         stretchGroup.deleteSequence(seq, false);
-        needOverviewUpdate |= av.isSelectionDefinedGroup();
+        updateOverviewAndStructs |= av.isSelectionDefinedGroup();
       }
       else
       {
@@ -1936,7 +1939,7 @@ public class SeqPanel extends JPanel
         }
 
         stretchGroup.addSequence(nextSeq, false);
-        needOverviewUpdate |= av.isSelectionDefinedGroup();
+        updateOverviewAndStructs |= av.isSelectionDefinedGroup();
       }
     }
 
index e6ec822..93a2457 100755 (executable)
@@ -127,7 +127,7 @@ public class SliderPanel extends GSliderPanel
       @Override
       public void mouseReleased(MouseEvent evt)
       {
-        ap.paintAlignment(true);
+        ap.paintAlignment(true, true);
       }
     });
 
index 3ba9947..c8854a7 100644 (file)
@@ -83,18 +83,18 @@ public abstract class StructureViewerBase extends GStructureViewer
   /**
    * list of sequenceSet ids associated with the view
    */
-  protected List<String> _aps = new ArrayList<String>();
+  protected List<String> _aps = new ArrayList<>();
 
   /**
    * list of alignment panels to use for superposition
    */
-  protected Vector<AlignmentPanel> _alignwith = new Vector<AlignmentPanel>();
+  protected Vector<AlignmentPanel> _alignwith = new Vector<>();
 
   /**
    * list of alignment panels that are used for colouring structures by aligned
    * sequences
    */
-  protected Vector<AlignmentPanel> _colourwith = new Vector<AlignmentPanel>();
+  protected Vector<AlignmentPanel> _colourwith = new Vector<>();
 
   private String viewId = null;
 
@@ -170,7 +170,7 @@ public abstract class StructureViewerBase extends GStructureViewer
   {
     if (_alignwith == null)
     {
-      _alignwith = new Vector<AlignmentPanel>();
+      _alignwith = new Vector<>();
     }
     if (_alignwith.size() == 0 && ap != null)
     {
@@ -468,7 +468,9 @@ public abstract class StructureViewerBase extends GStructureViewer
     if (apanel.getSeqPanel().seqCanvas.fr != null)
     {
       apanel.getSeqPanel().seqCanvas.fr.featuresAdded();
-      apanel.paintAlignment(true);
+      // note - we don't do a refresh for structure here because we do it
+      // explicitly for all panels later on
+      apanel.paintAlignment(true, false);
     }
 
     /*
@@ -717,11 +719,11 @@ public abstract class StructureViewerBase extends GStructureViewer
 
     if (_colourwith == null)
     {
-      _colourwith = new Vector<AlignmentPanel>();
+      _colourwith = new Vector<>();
     }
     if (_alignwith == null)
     {
-      _alignwith = new Vector<AlignmentPanel>();
+      _alignwith = new Vector<>();
     }
 
     ViewSelectionMenu seqColourBy = new ViewSelectionMenu(
@@ -888,7 +890,7 @@ public abstract class StructureViewerBase extends GStructureViewer
     binding.setColourBySequence(seqColour.isSelected());
     if (_colourwith == null)
     {
-      _colourwith = new Vector<AlignmentPanel>();
+      _colourwith = new Vector<>();
     }
     if (binding.isColourBySequence())
     {
index 3986561..53e2dee 100644 (file)
@@ -184,9 +184,9 @@ public class TextColourChooser
    */
   protected void saveInitialSettings()
   {
-    groupColour1 = new HashMap<SequenceGroup, Color>();
-    groupColour2 = new HashMap<SequenceGroup, Color>();
-    groupThreshold = new HashMap<SequenceGroup, Integer>();
+    groupColour1 = new HashMap<>();
+    groupColour2 = new HashMap<>();
+    groupThreshold = new HashMap<>();
 
     if (sg == null)
     {
@@ -237,7 +237,7 @@ public class TextColourChooser
       sg.textColour = col;
     }
 
-    ap.paintAlignment(true);
+    ap.paintAlignment(false, false);
   }
 
   void colour2Changed(Color col)
@@ -255,7 +255,7 @@ public class TextColourChooser
       sg.textColour2 = col;
     }
 
-    ap.paintAlignment(true);
+    ap.paintAlignment(false, false);
   }
 
   void thresholdChanged(int value)
@@ -273,7 +273,7 @@ public class TextColourChooser
       sg.thresholdTextColour = value;
     }
 
-    ap.paintAlignment(true);
+    ap.paintAlignment(false, false);
   }
 
   void setGroupTextColour()
index 5e14fce..9403057 100755 (executable)
@@ -493,7 +493,7 @@ public class TreePanel extends GTreePanel
 
     if (treeCanvas.applyToAllViews)
     {
-      final ArrayList<CommandI> commands = new ArrayList<CommandI>();
+      final ArrayList<CommandI> commands = new ArrayList<>();
       for (AlignmentPanel ap : PaintRefresher
               .getAssociatedPanels(av.getSequenceSetId()))
       {
@@ -550,13 +550,14 @@ public class TreePanel extends GTreePanel
 
   public CommandI sortAlignmentIn(AlignmentPanel ap)
   {
+    // TODO: move to alignment view controller
     AlignmentViewport viewport = ap.av;
     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
     AlignmentSorter.sortByTree(viewport.getAlignment(), tree);
     CommandI undo;
     undo = new OrderCommand("Tree Sort", oldOrder, viewport.getAlignment());
 
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, false);
     return undo;
   }
 
index 8b45c40..3290500 100755 (executable)
@@ -136,7 +136,7 @@ public class UserDefinedColours extends GUserDefinedColours
   UserDefinedColours()
   {
     super();
-    selectedButtons = new ArrayList<JButton>();
+    selectedButtons = new ArrayList<>();
   }
 
   void showFrame()
@@ -163,7 +163,7 @@ public class UserDefinedColours extends GUserDefinedColours
 
     if (upperCaseButtons == null)
     {
-      upperCaseButtons = new ArrayList<JButton>();
+      upperCaseButtons = new ArrayList<>();
     }
 
     for (int i = 0; i < 20; i++)
@@ -194,7 +194,7 @@ public class UserDefinedColours extends GUserDefinedColours
 
       if (lowerCaseButtons == null)
       {
-        lowerCaseButtons = new ArrayList<JButton>();
+        lowerCaseButtons = new ArrayList<>();
       }
 
       for (int i = 0; i < 20; i++)
@@ -631,8 +631,8 @@ public class UserDefinedColours extends GUserDefinedColours
   @Override
   protected void loadbutton_actionPerformed()
   {
-    upperCaseButtons = new ArrayList<JButton>();
-    lowerCaseButtons = new ArrayList<JButton>();
+    upperCaseButtons = new ArrayList<>();
+    lowerCaseButtons = new ArrayList<>();
 
     JalviewFileChooser chooser = new JalviewFileChooser("jc",
             "Jalview User Colours");
@@ -876,7 +876,7 @@ public class UserDefinedColours extends GUserDefinedColours
   protected void cancelButton_actionPerformed()
   {
     ap.alignFrame.changeColour(oldColourScheme);
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
 
     try
     {
index 6e7df71..d526a31 100755 (executable)
@@ -184,7 +184,7 @@ public class WSWUBlastClient
         }
       }
     }
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, false);
 
   }
 
index 9fec256..d92608c 100644 (file)
@@ -34,12 +34,24 @@ import java.util.List;
  */
 public class ScaleRenderer
 {
+  /**
+   * Represents one major or minor scale mark
+   */
   public final class ScaleMark
   {
+    /**
+     * true for a major scale mark, false for minor
+     */
     public final boolean major;
 
+    /**
+     * visible column position (0..) e.g. 19
+     */
     public final int column;
 
+    /**
+     * text (if any) to show e.g. "20"
+     */
     public final String text;
 
     ScaleMark(boolean isMajor, int col, String txt)
@@ -48,19 +60,27 @@ public class ScaleRenderer
       column = col;
       text = txt;
     }
+
+    /**
+     * String representation for inspection when debugging only
+     */
+    @Override
+    public String toString()
+    {
+      return String.format("%s:%d:%s", major ? "major" : "minor", column,
+              text);
+    }
   }
 
   /**
-   * calculate positions markers on the alignment ruler
+   * Calculates position markers on the alignment ruler
    * 
    * @param av
    * @param startx
-   *          left-most column in visible view
+   *          left-most column in visible view (0..)
    * @param endx
-   *          - right-most column in visible view
-   * @return List of ScaleMark holding boolean: true/false for major/minor mark,
-   *         marker position in alignment column coords, a String to be rendered
-   *         at the position (or null)
+   *          - right-most column in visible view (0..)
+   * @return
    */
   public List<ScaleMark> calculateMarks(AlignViewportI av, int startx,
           int endx)
@@ -87,41 +107,40 @@ public class ScaleRenderer
       scalestartx += 5;
     }
     List<ScaleMark> marks = new ArrayList<ScaleMark>();
-    String string;
-    int refN, iadj;
     // todo: add a 'reference origin column' to set column number relative to
-    for (int i = scalestartx; i < endx; i += 5)
+    for (int i = scalestartx; i <= endx; i += 5)
     {
       if (((i - refSp) % 10) == 0)
       {
+        String text;
         if (refSeq == null)
         {
-          iadj = av.getAlignment().getHiddenColumns()
+          int iadj = av.getAlignment().getHiddenColumns()
                   .adjustForHiddenColumns(i - 1) + 1;
-          string = String.valueOf(iadj);
+          text = String.valueOf(iadj);
         }
         else
         {
-          iadj = av.getAlignment().getHiddenColumns()
+          int iadj = av.getAlignment().getHiddenColumns()
                   .adjustForHiddenColumns(i - 1);
-          refN = refSeq.findPosition(iadj);
+          int refN = refSeq.findPosition(iadj);
           // TODO show bounds if position is a gap
           // - ie L--R -> "1L|2R" for
           // marker
           if (iadj < refStartI)
           {
-            string = String.valueOf(iadj - refStartI);
+            text = String.valueOf(iadj - refStartI);
           }
           else if (iadj > refEndI)
           {
-            string = "+" + String.valueOf(iadj - refEndI);
+            text = "+" + String.valueOf(iadj - refEndI);
           }
           else
           {
-            string = String.valueOf(refN) + refSeq.getCharAt(iadj);
+            text = String.valueOf(refN) + refSeq.getCharAt(iadj);
           }
         }
-        marks.add(new ScaleMark(true, i - startx - 1, string));
+        marks.add(new ScaleMark(true, i - startx - 1, text));
       }
       else
       {
index f16522f..1f47da3 100644 (file)
@@ -216,7 +216,8 @@ public class FeatureRenderer extends FeatureRendererModel
       return null;
     }
 
-    if (Comparison.isGap(seq.getCharAt(column)))
+    // column is 'base 1' but getCharAt is an array index (ie from 0)
+    if (Comparison.isGap(seq.getCharAt(column - 1)))
     {
       /*
        * returning null allows the colour scheme to provide gap colour
index 15cb157..9809fa9 100644 (file)
@@ -58,7 +58,7 @@ public class RNAHelicesColourChooser
     oldcs = av.getGlobalColourScheme();
     if (av.getAlignment().getGroups() != null)
     {
-      oldgroupColours = new Hashtable<SequenceGroup, ColourSchemeI>();
+      oldgroupColours = new Hashtable<>();
       for (SequenceGroup sg : ap.getAlignment().getGroups())
       {
         if (sg.getColourScheme() != null)
@@ -71,7 +71,7 @@ public class RNAHelicesColourChooser
     this.ap = ap;
 
     adjusting = true;
-    Vector<String> list = new Vector<String>();
+    Vector<String> list = new Vector<>();
     int index = 1;
     AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
     if (anns != null)
@@ -105,6 +105,6 @@ public class RNAHelicesColourChooser
 
     av.setGlobalColourScheme(rhc);
 
-    ap.paintAlignment(true);
+    ap.paintAlignment(true, true);
   }
 }
index 3702cd0..b260cab 100644 (file)
@@ -33,7 +33,6 @@ import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.Annotation;
-import jalview.datamodel.CigarArray;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.HiddenSequences;
@@ -1673,13 +1672,6 @@ public abstract class AlignmentViewport
   }
 
   @Override
-  public CigarArray getViewAsCigars(boolean selectedRegionOnly)
-  {
-    return new CigarArray(alignment, alignment.getHiddenColumns(),
-            (selectedRegionOnly ? selectionGroup : null));
-  }
-
-  @Override
   public jalview.datamodel.AlignmentView getAlignmentView(
           boolean selectedOnly)
   {
index 8569039..8f37f15 100644 (file)
@@ -51,7 +51,7 @@ class AnnotationWorker extends AlignCalcWorker
           AnnotationProviderI counter)
   {
     super(viewport, panel);
-    ourAnnots = new ArrayList<AlignmentAnnotation>();
+    ourAnnots = new ArrayList<>();
     this.counter = counter;
     calcMan.registerWorker(this);
   }
@@ -121,7 +121,10 @@ class AnnotationWorker extends AlignCalcWorker
     if (ap != null)
     {
       ap.adjustAnnotationHeight();
-      ap.paintAlignment(true);
+      // TODO: only need to update colour and geometry if panel height changes
+      // and view is coloured by annotation, and the annotation is actually
+      // changed!
+      ap.paintAlignment(true, true);
     }
   }
 
index 24cb717..74695fe 100644 (file)
@@ -57,7 +57,7 @@ class ColumnCounterSetWorker extends AlignCalcWorker
           AlignmentViewPanel panel, FeatureSetCounterI counter)
   {
     super(viewport, panel);
-    ourAnnots = new ArrayList<AlignmentAnnotation>();
+    ourAnnots = new ArrayList<>();
     this.counter = counter;
     calcMan.registerWorker(this);
   }
@@ -116,7 +116,7 @@ class ColumnCounterSetWorker extends AlignCalcWorker
       {
         ap.adjustAnnotationHeight();
       }
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, true);
     }
 
   }
index 4242b2a..335529c 100644 (file)
@@ -64,7 +64,7 @@ public class ConsensusThread extends AlignCalcWorker
         {
           if (ap != null)
           {
-            ap.paintAlignment(false);
+            ap.paintAlignment(false, false);
           }
           Thread.sleep(200);
         } catch (Exception ex)
@@ -93,7 +93,7 @@ public class ConsensusThread extends AlignCalcWorker
 
       if (ap != null)
       {
-        ap.paintAlignment(true);
+        ap.paintAlignment(true, true);
       }
     } catch (OutOfMemoryError error)
     {
index 571234c..54b0191 100644 (file)
@@ -75,7 +75,7 @@ public class ConservationThread extends AlignCalcWorker
         abortAndDestroy();
         return;
       }
-      List<AlignmentAnnotation> ourAnnot = new ArrayList<AlignmentAnnotation>();
+      List<AlignmentAnnotation> ourAnnot = new ArrayList<>();
       AlignmentI alignment = alignViewport.getAlignment();
       conservation = alignViewport.getAlignmentConservationAnnotation();
       quality = alignViewport.getAlignmentQualityAnnot();
@@ -123,7 +123,7 @@ public class ConservationThread extends AlignCalcWorker
     }
     if (ap != null)
     {
-      ap.paintAlignment(true);
+      ap.paintAlignment(true, true);
     }
 
   }
index 5ed2885..61ec3d0 100644 (file)
@@ -139,7 +139,7 @@ public class StrucConsensusThread extends AlignCalcWorker
       calcMan.workerComplete(this);
       if (ap != null)
       {
-        ap.paintAlignment(true);
+        ap.paintAlignment(true, true);
       }
     }
 
index 0a61dff..c661e2c 100644 (file)
@@ -135,7 +135,7 @@ public class DasSequenceFeatureFetcher
           boolean useJDasMultiThread)
   {
     this.useJDASMultiThread = useJDasMultiThread;
-    this.selectedSources = new ArrayList<jalviewSourceI>();
+    this.selectedSources = new ArrayList<>();
     // filter both sequences and sources to eliminate duplicates
     for (jalviewSourceI src : selectedSources2)
     {
@@ -316,17 +316,17 @@ public class DasSequenceFeatureFetcher
     FeaturesClientMultipleSources fc = new FeaturesClientMultipleSources();
     fc.setConnProps(sourceRegistry.getSessionHandler());
     // Now sending requests one at a time to each server
-    ArrayList<jalviewSourceI> srcobj = new ArrayList<jalviewSourceI>();
-    ArrayList<String> src = new ArrayList<String>();
-    List<List<String>> ids = new ArrayList<List<String>>();
-    List<List<DBRefEntry>> idobj = new ArrayList<List<DBRefEntry>>();
-    List<Map<String, SequenceI>> sqset = new ArrayList<Map<String, SequenceI>>();
+    ArrayList<jalviewSourceI> srcobj = new ArrayList<>();
+    ArrayList<String> src = new ArrayList<>();
+    List<List<String>> ids = new ArrayList<>();
+    List<List<DBRefEntry>> idobj = new ArrayList<>();
+    List<Map<String, SequenceI>> sqset = new ArrayList<>();
     for (jalviewSourceI _sr : selectedSources)
     {
 
-      Map<String, SequenceI> slist = new HashMap<String, SequenceI>();
-      List<DBRefEntry> idob = new ArrayList<DBRefEntry>();
-      List<String> qset = new ArrayList<String>();
+      Map<String, SequenceI> slist = new HashMap<>();
+      List<DBRefEntry> idob = new ArrayList<>();
+      List<String> qset = new ArrayList<>();
 
       for (SequenceI seq : sequences)
       {
@@ -368,8 +368,8 @@ public class DasSequenceFeatureFetcher
         sqset.add(slist);
       }
     }
-    Map<String, Map<List<String>, Exception>> errors = new HashMap<String, Map<List<String>, Exception>>();
-    Map<String, Map<List<String>, DasGFFAdapter>> results = new HashMap<String, Map<List<String>, DasGFFAdapter>>();
+    Map<String, Map<List<String>, Exception>> errors = new HashMap<>();
+    Map<String, Map<List<String>, DasGFFAdapter>> results = new HashMap<>();
     if (!useJDASMultiThread)
     {
       Iterator<String> sources = src.iterator();
@@ -390,7 +390,7 @@ public class DasSequenceFeatureFetcher
             if (ers == null)
             {
               results.put(source,
-                      ers = new HashMap<List<String>, DasGFFAdapter>());
+                      ers = new HashMap<>());
             }
             ers.put(qid, dga);
           } catch (Exception ex)
@@ -399,7 +399,7 @@ public class DasSequenceFeatureFetcher
             if (ers == null)
             {
               errors.put(source,
-                      ers = new HashMap<List<String>, Exception>());
+                      ers = new HashMap<>());
             }
             ers.put(qid, ex);
           }
@@ -438,7 +438,7 @@ public class DasSequenceFeatureFetcher
           Map<List<String>, DasGFFAdapter> results,
           Map<List<String>, Exception> errors)
   {
-    Set<SequenceI> sequences = new HashSet<SequenceI>();
+    Set<SequenceI> sequences = new HashSet<>();
     String source = jvsource.getSourceURL();
     // process features
     DasGFFAdapter result = (results == null) ? null : results.get(ids);
@@ -622,7 +622,7 @@ public class DasSequenceFeatureFetcher
         if (seq == af.getViewport().getAlignment().getSequenceAt(index)
                 .getDatasetSequence())
         {
-          af.alignPanel.paintAlignment(true);
+          af.alignPanel.paintAlignment(true, true);
           index = end;
           break;
         }
@@ -647,8 +647,8 @@ public class DasSequenceFeatureFetcher
     // TODO: minimal list of DAS queries to make by querying with untyped ID if
     // distinct from any typed IDs
 
-    List<DBRefEntry> ids = new ArrayList<DBRefEntry>();
-    List<String> qstring = new ArrayList<String>();
+    List<DBRefEntry> ids = new ArrayList<>();
+    List<String> qstring = new ArrayList<>();
     boolean dasCoordSysFound = false;
 
     if (uprefs != null)
index c24ea05..9a2316c 100644 (file)
@@ -99,22 +99,22 @@ public class AADisorderClient extends JabawsCalcWorker
   {
     // TODO: turn this into some kind of configuration file that's a bit easier
     // to edit
-    featureMap = new HashMap<String, Map<String, String[]>>();
+    featureMap = new HashMap<>();
     Map<String, String[]> fmap;
     featureMap.put(compbio.ws.client.Services.IUPredWS.toString(),
-            fmap = new HashMap<String, String[]>());
+            fmap = new HashMap<>());
     fmap.put("Glob",
             new String[]
             { "Globular Domain", "Predicted globular domain" });
     featureMap.put(compbio.ws.client.Services.JronnWS.toString(),
-            fmap = new HashMap<String, String[]>());
+            fmap = new HashMap<>());
     featureMap.put(compbio.ws.client.Services.DisemblWS.toString(),
-            fmap = new HashMap<String, String[]>());
+            fmap = new HashMap<>());
     fmap.put("REM465", new String[] { "REM465", "Missing density" });
     fmap.put("HOTLOOPS", new String[] { "HOTLOOPS", "Flexible loops" });
     fmap.put("COILS", new String[] { "COILS", "Random coil" });
     featureMap.put(compbio.ws.client.Services.GlobPlotWS.toString(),
-            fmap = new HashMap<String, String[]>());
+            fmap = new HashMap<>());
     fmap.put("GlobDoms",
             new String[]
             { "Globular Domain", "Predicted globular domain" });
@@ -122,9 +122,9 @@ public class AADisorderClient extends JabawsCalcWorker
             new String[]
             { "Protein Disorder", "Probable unstructured peptide region" });
     Map<String, Map<String, Object>> amap;
-    annotMap = new HashMap<String, Map<String, Map<String, Object>>>();
+    annotMap = new HashMap<>();
     annotMap.put(compbio.ws.client.Services.GlobPlotWS.toString(),
-            amap = new HashMap<String, Map<String, Object>>());
+            amap = new HashMap<>());
     amap.put("Dydx", new HashMap<String, Object>());
     amap.get("Dydx").put(DONTCOMBINE, DONTCOMBINE);
     amap.get("Dydx").put(THRESHOLD, new double[] { 1, 0 });
@@ -135,7 +135,7 @@ public class AADisorderClient extends JabawsCalcWorker
     amap.put("RawScore", new HashMap<String, Object>());
     amap.get("RawScore").put(INVISIBLE, INVISIBLE);
     annotMap.put(compbio.ws.client.Services.DisemblWS.toString(),
-            amap = new HashMap<String, Map<String, Object>>());
+            amap = new HashMap<>());
     amap.put("COILS", new HashMap<String, Object>());
     amap.put("HOTLOOPS", new HashMap<String, Object>());
     amap.put("REM465", new HashMap<String, Object>());
@@ -148,7 +148,7 @@ public class AADisorderClient extends JabawsCalcWorker
     amap.get("REM465").put(RANGE, new float[] { 0, 1 });
 
     annotMap.put(compbio.ws.client.Services.IUPredWS.toString(),
-            amap = new HashMap<String, Map<String, Object>>());
+            amap = new HashMap<>());
     amap.put("Long", new HashMap<String, Object>());
     amap.put("Short", new HashMap<String, Object>());
     amap.get("Long").put(THRESHOLD, new double[] { 1, 0.5 });
@@ -156,7 +156,7 @@ public class AADisorderClient extends JabawsCalcWorker
     amap.get("Short").put(THRESHOLD, new double[] { 1, 0.5 });
     amap.get("Short").put(RANGE, new float[] { 0, 1 });
     annotMap.put(compbio.ws.client.Services.JronnWS.toString(),
-            amap = new HashMap<String, Map<String, Object>>());
+            amap = new HashMap<>());
     amap.put("JRonn", new HashMap<String, Object>());
     amap.get("JRonn").put(THRESHOLD, new double[] { 1, 0.5 });
     amap.get("JRonn").put(RANGE, new float[] { 0, 1 });
@@ -173,8 +173,8 @@ public class AADisorderClient extends JabawsCalcWorker
       Map<String, Map<String, Object>> annotTypeMap = annotMap
               .get(service.serviceType);
       boolean dispFeatures = false;
-      Map<String, Object> fc = new Hashtable<String, Object>();
-      List<AlignmentAnnotation> ourAnnot = new ArrayList<AlignmentAnnotation>();
+      Map<String, Object> fc = new Hashtable<>();
+      List<AlignmentAnnotation> ourAnnot = new ArrayList<>();
       /**
        * grouping for any annotation rows created
        */
@@ -358,7 +358,6 @@ public class AADisorderClient extends JabawsCalcWorker
             // only do this if the alignFrame is currently showing this view.
             af.setShowSeqFeatures(true);
           }
-          ap.paintAlignment(true);
         }
         if (ourAnnot.size() > 0)
         {
@@ -366,6 +365,7 @@ public class AADisorderClient extends JabawsCalcWorker
           // new alignment annotation rows created.
           updateOurAnnots(ourAnnot);
           ap.adjustAnnotationHeight();
+          ap.paintAlignment(true, true);
         }
       }
     }
index 26fe0a2..b972ab8 100644 (file)
@@ -372,7 +372,8 @@ public abstract class AbstractJabaCalcWorker extends AlignCalcWorker
         {
           guiProgress.setProgressBar("", progressId);
         }
-        ap.paintAlignment(true);
+        // TODO: may not need to paintAlignment again !
+        ap.paintAlignment(false, false);
       }
       if (msg.length() > 0)
       {
@@ -582,7 +583,7 @@ public abstract class AbstractJabaCalcWorker extends AlignCalcWorker
   protected boolean checkDone()
   {
     calcMan.notifyStart(this);
-    ap.paintAlignment(false);
+    ap.paintAlignment(false, false);
     while (!calcMan.notifyWorking(this))
     {
       if (calcMan.isWorking(this))
@@ -593,7 +594,7 @@ public abstract class AbstractJabaCalcWorker extends AlignCalcWorker
       {
         if (ap != null)
         {
-          ap.paintAlignment(false);
+          ap.paintAlignment(false, false);
         }
 
         Thread.sleep(200);
diff --git a/test/jalview/datamodel/CigarArrayTest.java b/test/jalview/datamodel/CigarArrayTest.java
new file mode 100644 (file)
index 0000000..7bee423
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+package jalview.datamodel;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.gui.JvOptionPane;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class CigarArrayTest
+{
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+
+  @Test(groups = "Functional")
+  public void testConstructor()
+  {
+    SequenceI seq1 = new Sequence("sq1",
+            "ASFDDABACBACBACBACBACBACBABCABCBACBABCAB");
+    Sequence seq2 = new Sequence("sq2",
+            "TTTTTTACBCBABCABCABCABCBACBACBABCABCABCBA");
+
+    // construct alignment
+    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
+
+    // hide columns
+    HiddenColumns hc = new HiddenColumns();
+    hc.hideColumns(3, 6);
+    hc.hideColumns(16, 20);
+
+    // select group
+    SequenceGroup sg1 = new SequenceGroup();
+    sg1.addSequence(seq1, false);
+    sg1.setStartRes(2);
+    sg1.setEndRes(23);
+
+    // Cigar array meanings:
+    // M = match
+    // D = deletion
+    // I = insertion
+    // number preceding M/D/I is the number of residues which
+    // match/are deleted/are inserted
+    // In the CigarArray constructor only matches or deletions are created, as
+    // we are comparing a sequence to its own subsequence (the group) + hidden
+    // columns.
+
+    // no hidden columns case
+    CigarArray cig = new CigarArray(al, null, sg1);
+    String result = cig.getCigarstring();
+    assertEquals(result, "22M");
+
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "1M4D9M5D3M");
+
+    // group starts at hidden cols
+    sg1.setStartRes(3);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "4D9M5D3M");
+
+    // group starts at last but 1 hidden col
+    sg1.setStartRes(5);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "2D9M5D3M");
+
+    // group starts at last hidden col
+    sg1.setStartRes(6);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "1D9M5D3M");
+
+    // group starts just after hidden region
+    sg1.setStartRes(7);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "9M5D3M");
+
+    // group ends just before start of hidden region
+    sg1.setStartRes(5);
+    sg1.setEndRes(15);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "2D9M");
+
+    // group ends at start of hidden region
+    sg1.setEndRes(16);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "2D9M1D");
+
+    // group ends 1 after start of hidden region
+    sg1.setEndRes(17);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "2D9M2D");
+
+    // group ends at end of hidden region
+    sg1.setEndRes(20);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "2D9M5D");
+
+    // group ends just after end of hidden region
+    sg1.setEndRes(21);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "2D9M5D1M");
+
+    // group ends 2 after end of hidden region
+    sg1.setEndRes(22);
+    cig = new CigarArray(al, hc, sg1);
+    result = cig.getCigarstring();
+    assertEquals(result, "2D9M5D2M");
+  }
+}
index 6844072..c0cb09c 100644 (file)
@@ -1672,4 +1672,135 @@ public class SequenceTest
     assertEquals(new Range(8, 13), sq.findPositions(1, 13)); // the lot
     assertEquals(new Range(8, 13), sq.findPositions(1, 99));
   }
+
+  @Test(groups = { "Functional" })
+  public void testFindFeatures_largeEndPos()
+  {
+    /*
+     * imitate a PDB sequence where end is larger than end position
+     */
+    SequenceI sq = new Sequence("test", "-ABC--DEF--", 1, 20);
+    sq.createDatasetSequence();
+  
+    assertTrue(sq.findFeatures(1, 9).isEmpty());
+    // should be no array bounds exception - JAL-2772
+    assertTrue(sq.findFeatures(1, 15).isEmpty());
+  
+    // add feature on BCD
+    SequenceFeature sfBCD = new SequenceFeature("Cath", "desc", 2, 4, 2f,
+            null);
+    sq.addSequenceFeature(sfBCD);
+  
+    // no features in columns 1-2 (-A)
+    List<SequenceFeature> found = sq.findFeatures(1, 2);
+    assertTrue(found.isEmpty());
+  
+    // columns 1-6 (-ABC--) includes BCD
+    found = sq.findFeatures(1, 6);
+    assertEquals(1, found.size());
+    assertTrue(found.contains(sfBCD));
+
+    // columns 10-11 (--) should find nothing
+    found = sq.findFeatures(10, 11);
+    assertEquals(0, found.size());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetName()
+  {
+    SequenceI sq = new Sequence("test", "-ABC---DE-F--");
+    assertEquals("test", sq.getName());
+    assertEquals(1, sq.getStart());
+    assertEquals(6, sq.getEnd());
+
+    sq.setName("testing");
+    assertEquals("testing", sq.getName());
+
+    sq.setName("test/8-10");
+    assertEquals("test", sq.getName());
+    assertEquals(8, sq.getStart());
+    assertEquals(13, sq.getEnd()); // note end is recomputed
+
+    sq.setName("testing/7-99");
+    assertEquals("testing", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd()); // end may be beyond physical end
+
+    sq.setName("/2-3");
+    assertEquals("", sq.getName());
+    assertEquals(2, sq.getStart());
+    assertEquals(7, sq.getEnd());
+
+    sq.setName("test/"); // invalid
+    assertEquals("test/", sq.getName());
+    assertEquals(2, sq.getStart());
+    assertEquals(7, sq.getEnd());
+
+    sq.setName("test/6-13/7-99");
+    assertEquals("test/6-13", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd());
+
+    sq.setName("test/0-5"); // 0 is invalid - ignored
+    assertEquals("test/0-5", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd());
+
+    sq.setName("test/a-5"); // a is invalid - ignored
+    assertEquals("test/a-5", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd());
+
+    sq.setName("test/6-5"); // start > end is invalid - ignored
+    assertEquals("test/6-5", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd());
+
+    sq.setName("test/5"); // invalid - ignored
+    assertEquals("test/5", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd());
+
+    sq.setName("test/-5"); // invalid - ignored
+    assertEquals("test/-5", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd());
+
+    sq.setName("test/5-"); // invalid - ignored
+    assertEquals("test/5-", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd());
+
+    sq.setName("test/5-6-7"); // invalid - ignored
+    assertEquals("test/5-6-7", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd());
+
+    sq.setName(null); // invalid, gets converted to space
+    assertEquals("", sq.getName());
+    assertEquals(7, sq.getStart());
+    assertEquals(99, sq.getEnd());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testCheckValidRange()
+  {
+    Sequence sq = new Sequence("test/7-12", "-ABC---DE-F--");
+    assertEquals(7, sq.getStart());
+    assertEquals(12, sq.getEnd());
+
+    /*
+     * checkValidRange ensures end is at least the last residue position
+     */
+    PA.setValue(sq, "end", 2);
+    sq.checkValidRange();
+    assertEquals(12, sq.getEnd());
+
+    /*
+     * end may be beyond the last residue position
+     */
+    PA.setValue(sq, "end", 22);
+    sq.checkValidRange();
+    assertEquals(22, sq.getEnd());
+  }
 }
index a8c491c..5920b89 100644 (file)
@@ -296,4 +296,28 @@ public class EnsemblGeneTest
     assertEquals(-1, fc.compare("coding_exon", "feature_variant"));
     assertEquals(1f, fc.getTransparency());
   }
+
+  @Test(groups = "Network")
+  public void testGetGeneIds()
+  {
+    /*
+     * ENSG00000158828 gene id PINK1 human
+     * ENST00000321556 transcript for the same gene - should not be duplicated
+     * P30419 Uniprot identifier for ENSG00000136448
+     * ENST00000592782 transcript for Uniprot gene - should not be duplicated
+     * BRAF - gene name resolvabe (at time of writing) for 6 model species
+     */
+    String ids = "ENSG00000158828 ENST00000321556 P30419 ENST00000592782 BRAF";
+    EnsemblGene testee = new EnsemblGene();
+    List<String> geneIds = testee.getGeneIds(ids);
+    assertEquals(8, geneIds.size());
+    assertTrue(geneIds.contains("ENSG00000158828"));
+    assertTrue(geneIds.contains("ENSG00000136448"));
+    assertTrue(geneIds.contains("ENSG00000157764")); // BRAF human
+    assertTrue(geneIds.contains("ENSMUSG00000002413")); // mouse
+    assertTrue(geneIds.contains("ENSRNOG00000010957")); // rat
+    assertTrue(geneIds.contains("ENSXETG00000004845")); // xenopus
+    assertTrue(geneIds.contains("ENSDARG00000017661")); // zebrafish
+    assertTrue(geneIds.contains("ENSGALG00000012865")); // chicken
+  }
 }
index 31001da..cc3a3db 100644 (file)
  */
 package jalview.ext.ensembl;
 
+import static org.testng.Assert.assertTrue;
+
 import jalview.datamodel.AlignmentI;
 
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.List;
 
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 public class EnsemblRestClientTest
 {
+  private EnsemblRestClient sf;
 
   @Test(suiteName = "live")
   public void testIsEnsemblAvailable()
   {
-    EnsemblRestClient sf = new EnsemblRestClient()
+    boolean isAvailable = sf.isEnsemblAvailable();
+    if (isAvailable)
+    {
+      System.out.println("Ensembl is UP!");
+    }
+    else
+    {
+      System.err
+              .println("Ensembl is DOWN or unreachable ******************* BAD!");
+    }
+  }
+
+  @BeforeMethod(alwaysRun = true)
+  protected void setUp()
+  {
+    sf = new EnsemblRestClient()
     {
 
       @Override
@@ -74,16 +93,14 @@ public class EnsemblRestClientTest
       }
 
     };
-    boolean isAvailable = sf.isEnsemblAvailable();
-    if (isAvailable)
-    {
-      System.out.println("Ensembl is UP!");
-    }
-    else
+  }
+
+  @Test(groups = "Network")
+  public void testCheckEnsembl_overload()
+  {
+    for (int i = 0; i < 20; i++)
     {
-      System.err
-              .println("Ensembl is DOWN or unreachable ******************* BAD!");
+      assertTrue(sf.checkEnsembl(), "Error on " + (i + 1) + "th ping");
     }
   }
-
 }
index aa2c315..e2af26b 100644 (file)
@@ -191,34 +191,6 @@ public class EnsemblSeqProxyTest
 
   }
 
-  @Test(groups = "Functional")
-  public void testIsTranscriptIdentifier()
-  {
-    EnsemblSeqProxy testee = new EnsemblGene();
-    assertFalse(testee.isTranscriptIdentifier(null));
-    assertFalse(testee.isTranscriptIdentifier(""));
-    assertFalse(testee.isTranscriptIdentifier("ENSG00000012345"));
-    assertTrue(testee.isTranscriptIdentifier("ENST00000012345"));
-    assertTrue(testee.isTranscriptIdentifier("ENSMUST00000012345"));
-    assertFalse(testee.isTranscriptIdentifier("enst00000012345"));
-    assertFalse(testee.isTranscriptIdentifier("ENST000000123456"));
-    assertFalse(testee.isTranscriptIdentifier("ENST0000001234"));
-  }
-
-  @Test(groups = "Functional")
-  public void testIsGeneIdentifier()
-  {
-    EnsemblSeqProxy testee = new EnsemblGene();
-    assertFalse(testee.isGeneIdentifier(null));
-    assertFalse(testee.isGeneIdentifier(""));
-    assertFalse(testee.isGeneIdentifier("ENST00000012345"));
-    assertTrue(testee.isGeneIdentifier("ENSG00000012345"));
-    assertTrue(testee.isGeneIdentifier("ENSMUSG00000012345"));
-    assertFalse(testee.isGeneIdentifier("ensg00000012345"));
-    assertFalse(testee.isGeneIdentifier("ENSG000000123456"));
-    assertFalse(testee.isGeneIdentifier("ENSG0000001234"));
-  }
-
   /**
    * Test the method that appends a single allele's reverse complement to a
    * string buffer
diff --git a/test/jalview/ext/ensembl/SpeciesTest.java b/test/jalview/ext/ensembl/SpeciesTest.java
new file mode 100644 (file)
index 0000000..44658e7
--- /dev/null
@@ -0,0 +1,30 @@
+package jalview.ext.ensembl;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.testng.annotations.Test;
+
+public class SpeciesTest
+{
+  @Test
+  public void testGetModelOrganisms()
+  {
+    Set<Species> models = Species.getModelOrganisms();
+    assertTrue(models.contains(Species.human));
+    assertFalse(models.contains(Species.horse));
+    for (Species s : Species.values())
+    {
+      if (s.isModelOrganism())
+      {
+        assertTrue(models.contains(s));
+      }
+      else
+      {
+        assertFalse(models.contains(s));
+      }
+    }
+  }
+}
index c9e1cad..63d5e4e 100644 (file)
@@ -29,6 +29,11 @@ public class AtomSpecModelTest
     assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
     model.addRange(0, 3, 10, "C"); // subsumes 5-9
     assertEquals(model.getAtomSpec(), "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B");
+    model.addRange(5, 25, 35, " "); // empty chain code - e.g. from homology
+                                    // modelling
+    assertEquals(model.getAtomSpec(),
+            "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B|#5:25-35.");
+
   }
 
 }
index b228ba1..2819dbf 100644 (file)
@@ -21,6 +21,7 @@
 package jalview.gui;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
 
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
@@ -218,4 +219,31 @@ public class AlignmentPanelTest
             .getAlignment().getWidth() - 1 - 21); // 21 is the number of hidden
                                                   // columns
   }
+
+  /**
+   * Test that update layout reverts to original (unwrapped) values for endRes
+   * and endSeq when switching from wrapped to unwrapped mode (JAL-2739)
+   */
+  @Test(groups = "Functional")
+  public void TestUpdateLayout_endRes()
+  {
+    // get details of original alignment dimensions
+    ViewportRanges ranges = af.getViewport().getRanges();
+    int endres = ranges.getEndRes();
+
+    // wrap
+    af.alignPanel.getAlignViewport().setWrapAlignment(true);
+    af.alignPanel.updateLayout();
+
+    // endRes changes
+    assertNotEquals(ranges.getEndRes(), endres);
+
+    // unwrap
+    af.alignPanel.getAlignViewport().setWrapAlignment(false);
+    af.alignPanel.updateLayout();
+
+    // endRes and endSeq back to original values
+    assertEquals(ranges.getEndRes(), endres);
+
+  }
 }
diff --git a/test/jalview/gui/SeqCanvasTest.java b/test/jalview/gui/SeqCanvasTest.java
new file mode 100644 (file)
index 0000000..05b9aea
--- /dev/null
@@ -0,0 +1,281 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.datamodel.AlignmentI;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+
+import java.awt.Font;
+import java.awt.FontMetrics;
+
+import junit.extensions.PA;
+
+import org.testng.annotations.Test;
+
+public class SeqCanvasTest
+{
+  /**
+   * Test the method that computes wrapped width in residues, height of wrapped
+   * widths in pixels, and the number of widths visible
+   */
+  @Test(groups = "Functional")
+  public void testCalculateWrappedGeometry_noAnnotations()
+  {
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+            "examples/uniref50.fa", DataSourceType.FILE);
+    AlignViewport av = af.getViewport();
+    AlignmentI al = av.getAlignment();
+    assertEquals(al.getWidth(), 157);
+    assertEquals(al.getHeight(), 15);
+
+    av.setWrapAlignment(true);
+    av.getRanges().setStartEndSeq(0, 14);
+    av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+    assertEquals(charHeight, 17);
+    assertEquals(charWidth, 12);
+
+    SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
+
+    /*
+     * first with scales above, left, right
+     */
+    av.setShowAnnotation(false);
+    av.setScaleAboveWrapped(true);
+    av.setScaleLeftWrapped(true);
+    av.setScaleRightWrapped(true);
+    FontMetrics fm = testee.getFontMetrics(av.getFont());
+    int labelWidth = fm.stringWidth("000") + charWidth;
+    assertEquals(labelWidth, 39); // 3 x 9 + charWidth
+
+    /*
+     * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
+     * take the whole multiple of character widths
+     */
+    int canvasWidth = 400;
+    int canvasHeight = 300;
+    int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth;
+    int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(wrappedWidth, residueColumns);
+    assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth);
+    assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth);
+    assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"),
+            2 * charHeight);
+    int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
+    assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
+
+    /*
+     * repeat height is 17 * (2 + 15) = 289
+     * make canvas height 2 * 289 + 3 * charHeight so just enough to
+     * draw 2 widths and the first sequence of a third
+     */
+    canvasHeight = charHeight * (17 * 2 + 3);
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+
+    /*
+     * reduce canvas height by 1 pixel - should not be enough height
+     * to draw 3 widths
+     */
+    canvasHeight -= 1;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+
+    /*
+     * turn off scale above - can now fit in 2 and a bit widths
+     */
+    av.setScaleAboveWrapped(false);
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+
+    /*
+     * reduce height to enough for 2 widths and not quite a third
+     * i.e. two repeating heights + spacer + sequence - 1 pixel
+     */
+    canvasHeight = charHeight * (16 * 2 + 2) - 1;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+
+    /*
+     * make canvas width enough for scales and 20 residues
+     */
+    canvasWidth = 2 * labelWidth + 20 * charWidth;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 20);
+
+    /*
+     * reduce width by 1 pixel - rounds down to 19 residues
+     */
+    canvasWidth -= 1;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 19);
+
+    /*
+     * turn off West scale - adds labelWidth (39) to available for residues
+     * which with the 11 remainder makes 50 which is 4 more charWidths rem 2
+     */
+    av.setScaleLeftWrapped(false);
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 23);
+
+    /*
+     * add 10 pixels to width to fit in another whole residue column
+     */
+    canvasWidth += 9;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 23);
+    canvasWidth += 1;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 24);
+
+    /*
+     * turn off East scale to gain 39 more pixels (3 columns remainder 3)
+     */
+    av.setScaleRightWrapped(false);
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 27);
+
+    /*
+     * add 9 pixels to width to gain a residue column
+     */
+    canvasWidth += 8;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 27);
+    canvasWidth += 1;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 28);
+
+    /*
+     * now West but not East scale - lose 39 pixels or 4 columns
+     */
+    av.setScaleLeftWrapped(true);
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 24);
+
+    /*
+     * adding 3 pixels to width regains one column
+     */
+    canvasWidth += 2;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 24);
+    canvasWidth += 1;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 25);
+
+    /*
+     * turn off scales left and right, make width exactly 157 columns
+     */
+    av.setScaleLeftWrapped(false);
+    canvasWidth = al.getWidth() * charWidth;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
+  }
+
+  /**
+   * Test the method that computes wrapped width in residues, height of wrapped
+   * widths in pixels, and the number of widths visible
+   */
+  @Test(groups = "Functional")
+  public void testCalculateWrappedGeometry_withAnnotations()
+  {
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+            "examples/uniref50.fa", DataSourceType.FILE);
+    AlignViewport av = af.getViewport();
+    AlignmentI al = av.getAlignment();
+    assertEquals(al.getWidth(), 157);
+    assertEquals(al.getHeight(), 15);
+  
+    av.setWrapAlignment(true);
+    av.getRanges().setStartEndSeq(0, 14);
+    av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+    assertEquals(charHeight, 17);
+    assertEquals(charWidth, 12);
+  
+    SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
+  
+    /*
+     * first with scales above, left, right
+     */
+    av.setShowAnnotation(true);
+    av.setScaleAboveWrapped(true);
+    av.setScaleLeftWrapped(true);
+    av.setScaleRightWrapped(true);
+    FontMetrics fm = testee.getFontMetrics(av.getFont());
+    int labelWidth = fm.stringWidth("000") + charWidth;
+    assertEquals(labelWidth, 39); // 3 x 9 + charWidth
+    int annotationHeight = testee.getAnnotationHeight();
+
+    /*
+     * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
+     * take the whole multiple of character widths
+     */
+    int canvasWidth = 400;
+    int canvasHeight = 300;
+    int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth;
+    int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(wrappedWidth, residueColumns);
+    assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth);
+    assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth);
+    assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"),
+            2 * charHeight);
+    int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
+    assertEquals(repeatingHeight, charHeight * (2 + al.getHeight())
+            + annotationHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
+  
+    /*
+     * repeat height is 17 * (2 + 15) = 289 + annotationHeight = 507
+     * make canvas height 2 * 289 + 3 * charHeight so just enough to
+     * draw 2 widths and the first sequence of a third
+     */
+    canvasHeight = charHeight * (17 * 2 + 3) + 2 * annotationHeight;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+  
+    /*
+     * reduce canvas height by 1 pixel - should not be enough height
+     * to draw 3 widths
+     */
+    canvasHeight -= 1;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+  
+    /*
+     * turn off scale above - can now fit in 2 and a bit widths
+     */
+    av.setScaleAboveWrapped(false);
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+  
+    /*
+     * reduce height to enough for 2 widths and not quite a third
+     * i.e. two repeating heights + spacer + sequence - 1 pixel
+     */
+    canvasHeight = charHeight * (16 * 2 + 2) + 2 * annotationHeight - 1;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+
+    /*
+     * add 1 pixel to height - should now get 3 widths drawn
+     */
+    canvasHeight += 1;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+  }
+}
index cf1039f..0af67cd 100644 (file)
@@ -26,11 +26,11 @@ public class ScaleRendererTest
     AlignViewport av = af.getViewport();
 
     /*
-     * scale has minor ticks at 5 and 15, major at 10 and 20
+     * scale has minor ticks at 5, 15, 25, major at 10 and 20
      * (these are base 1, ScaleMark holds base 0 values)
      */
     List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, 0, 25);
-    assertEquals(marks.size(), 4);
+    assertEquals(marks.size(), 5);
 
     assertFalse(marks.get(0).major);
     assertEquals(marks.get(0).column, 4);
@@ -48,6 +48,10 @@ public class ScaleRendererTest
     assertEquals(marks.get(3).column, 19);
     assertEquals(marks.get(3).text, "20");
 
+    assertFalse(marks.get(4).major);
+    assertEquals(marks.get(4).column, 24);
+    assertNull(marks.get(4).text);
+
     /*
      * now hide columns 9-11 and 18-20 (base 1)
      * scale marks are now in the same columns as before, but
@@ -56,7 +60,7 @@ public class ScaleRendererTest
     av.hideColumns(8, 10);
     av.hideColumns(17, 19);
     marks = new ScaleRenderer().calculateMarks(av, 0, 25);
-    assertEquals(marks.size(), 4);
+    assertEquals(marks.size(), 5);
     assertFalse(marks.get(0).major);
     assertEquals(marks.get(0).column, 4);
     assertNull(marks.get(0).text);
@@ -69,5 +73,8 @@ public class ScaleRendererTest
     assertTrue(marks.get(3).major);
     assertEquals(marks.get(3).column, 19);
     assertEquals(marks.get(3).text, "26"); // +6 hidden columns
+    assertFalse(marks.get(4).major);
+    assertEquals(marks.get(4).column, 24);
+    assertNull(marks.get(4).text);
   }
 }
index 7fd7abc..f6dfed6 100644 (file)
@@ -2,6 +2,7 @@ package jalview.renderer.seqfeatures;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotEquals;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
@@ -285,6 +286,28 @@ public class FeatureColourFinderTest
   }
 
   @Test(groups = "Functional")
+  public void testFindFeatureAtEnd()
+  {
+    /*
+     * terminal residue feature
+     */
+    seq.addSequenceFeature(new SequenceFeature("PDBRESNUM", "pdb res 1",
+            seq.getEnd(), seq.getEnd(), Float.NaN, "1seq.pdb"));
+    fr.setColour("PDBRESNUM", new FeatureColour(Color.red));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+
+    /*
+     * final column should have PDBRESNUM feature, the others not
+     */
+    Color c = finder.findFeatureColour(Color.blue, seq,
+            seq.getLength() - 2);
+    assertNotEquals(c, Color.red);
+    c = finder.findFeatureColour(Color.blue, seq, seq.getLength() - 1);
+    assertEquals(c, Color.red);
+  }
+
+  @Test(groups = "Functional")
   public void testFindFeatureColour_graduatedFeatureColour()
   {
     seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 2,
index ac9e260..122b8d0 100644 (file)
     <!-- 
        Suppress check of externally sourced code 
     --> 
-    <suppress checks="[a-zA-Z0-9]*" files="com[\\/]*"/>
-    <suppress checks="[a-zA-Z0-9]*" files="ext[\\/]*"/>
-    <suppress checks="[a-zA-Z0-9]*" files="org[\\/]*"/>
-    <suppress checks="[a-zA-Z0-9]*" files="uk[\\/]*"/>
+    <suppress checks="[a-zA-Z0-9]*" files="[\\/]com[\\/]github*"/>
+    <suppress checks="[a-zA-Z0-9]*" files="[\\/]com[\\/]stevesoft*"/>
+    <suppress checks="[a-zA-Z0-9]*" files="[\\/]ext[\\/]edu*"/>
+    <suppress checks="[a-zA-Z0-9]*" files="[\\/]ext[\\/]vamsas*"/>
+    <suppress checks="[a-zA-Z0-9]*" files="[\\/]org[\\/]jibble*"/>
+    <suppress checks="[a-zA-Z0-9]*" files="[\\/]uk[\\/]ac*"/>
     
     <!-- 
        ImportControl can only handle one top level package
index 946f71c..c47aaec 100644 (file)
                <subpackage name="datamodel">
                <disallow pkg="jalview.gui"/>
                <allow pkg="fr.orsay.lri.varna"/>
-                       <subpackage name="xdb">
-                               <subpackage name="embl">
-                               <allow pkg="org.exolab.castor"/>
-                           </subpackage>
+                       <subpackage name="xdb.embl">
+                       <allow pkg="org.exolab.castor"/>
                    </subpackage>
            </subpackage>
                
+               <subpackage name="ext">
+                       <subpackage name="ensembl">
+                       <allow pkg="javax.ws"/>
+                       <allow pkg="org.json"/>
+                       </subpackage>
+                       <subpackage name="htsjdk">
+                       <allow pkg="htsjdk"/>
+                       </subpackage>
+                       <subpackage name="jmol">
+                       <allow pkg="MCview"/>
+                       <allow pkg="org.jmol"/>
+                       </subpackage>
+                       <subpackage name="paradise">
+                       <allow pkg="org.apache"/>
+                       <allow pkg="org.json"/>
+                       </subpackage>
+                       <subpackage name="rbvi">
+                       <allow pkg="ext.edu.ucsf"/>
+                       <allow pkg="javax.servlet"/>
+                       </subpackage>
+                       <subpackage name="so">
+                       <allow pkg="org.biojava"/>
+                       </subpackage>
+                       <subpackage name="varna">
+                       <allow pkg="fr.orsay"/>
+                       </subpackage>
+           </subpackage>
+               
                <subpackage name="fts">
                <allow pkg="javax.swing"/>
                <allow pkg="javax.ws"/>
                </subpackage>
 
                <subpackage name="schemes">
-                       <allow pkg="org.exolab.castor"/>
+                       <allow pkg="org.exolab.castor" class="jalview.schemes.ColourSchemeLoader"/>
                </subpackage>
 
                <subpackage name="structure">